/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.data.h2; import geodb.GeoDB; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; import java.util.Map; import java.util.logging.Level; import org.geotools.geometry.jts.Geometries; import org.geotools.jdbc.JDBCDataStore; import org.geotools.jdbc.SQLDialect; import org.geotools.referencing.CRS; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.feature.type.PropertyDescriptor; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.io.ParseException; import com.vividsolutions.jts.io.WKBReader; import com.vividsolutions.jts.io.WKTWriter; /** * Delegate for {@link H2DialectBasic} and {@link H2DialectPrepared} which implements * the common parts of the dialect api. * * @author Justin Deoliveira, OpenGEO * * * @source $URL$ */ public class H2Dialect extends SQLDialect { public static String H2_SPATIAL_INDEX = "org.geotools.data.h2.spatialIndex"; public H2Dialect( JDBCDataStore dataStore ) { super( dataStore ); } public String getNameEscape() { return "\""; } public void registerSqlTypeToClassMappings(Map<Integer, Class<?>> mappings) { super.registerSqlTypeToClassMappings(mappings); //geometries //mappings.put(new Integer(Types.OTHER), Geometry.class); mappings.put(new Integer(Types.BLOB), Geometry.class); } public void registerClassToSqlMappings(Map<Class<?>, Integer> mappings) { super.registerClassToSqlMappings(mappings); //geometries //TODO: only map geometry? mappings.put(Geometry.class, new Integer(Types.BLOB)); mappings.put(Point.class, new Integer(Types.BLOB)); mappings.put(LineString.class, new Integer(Types.BLOB)); mappings.put(Polygon.class, new Integer(Types.BLOB)); mappings.put(GeometryCollection.class, new Integer(Types.BLOB)); mappings.put(MultiPoint.class, new Integer(Types.BLOB)); mappings.put(MultiLineString.class, new Integer(Types.BLOB)); mappings.put(MultiPolygon.class, new Integer(Types.BLOB)); } @Override public void initializeConnection(Connection cx) throws SQLException { //spatialize the database GeoDB.InitGeoDB(cx); } @Override public boolean includeTable(String schemaName, String tableName, Connection cx) throws SQLException { if ("_GEODB".equals(tableName) || tableName.endsWith("_HATBOX")) { return false; } return true; } @Override public Class<?> getMapping(ResultSet columnMetaData, Connection cx) throws SQLException { //do a check for a column remark which marks this as a geometry String remark = columnMetaData.getString( "REMARKS" ); if ( remark != null ) { Geometries g = Geometries.getForName(remark); if (g != null) { return g.getBinding(); } } return null; } @Override public void encodePostColumnCreateTable(AttributeDescriptor att, StringBuffer sql) { if ( att instanceof GeometryDescriptor ) { //try to narrow down the type with a comment Class binding = att.getType().getBinding(); if (isConcreteGeometry(binding)) { sql.append( " COMMENT '").append( binding.getSimpleName().toUpperCase() ) .append( "'"); } } } @Override public void postCreateTable(String schemaName, SimpleFeatureType featureType, Connection cx) throws SQLException { Statement st = cx.createStatement(); String tableName = featureType.getTypeName(); try { //post process the feature type and set up constraints based on geometry type for ( PropertyDescriptor ad : featureType.getDescriptors() ) { if ( ad instanceof GeometryDescriptor ) { GeometryDescriptor gd = (GeometryDescriptor)ad; Class binding = ad.getType().getBinding(); String propertyName = ad.getName().getLocalPart(); if ( isConcreteGeometry( binding ) ) { StringBuffer sql = new StringBuffer(); sql.append( "ALTER TABLE "); encodeTableName(tableName, sql); sql.append( " ADD CONSTRAINT " ); encodeTableName( tableName + "_"+propertyName + "GeometryType", sql ); sql.append( " CHECK "); encodeColumnName( propertyName, sql ); sql.append( " IS NULL OR"); sql.append( " GeometryType("); encodeColumnName( propertyName, sql ); sql.append( ") = '").append( Geometries.getForBinding(binding).getName() .toUpperCase() ).append( "'"); LOGGER.fine( sql.toString() ); st.execute( sql.toString() ); } //create a spatial index CoordinateReferenceSystem crs = gd.getCoordinateReferenceSystem(); if (crs == null) { continue; } Integer epsg = null; try { epsg = CRS.lookupEpsgCode(crs, true); } catch (FactoryException e) { LOGGER.log(Level.FINER, "Unable to look epsg code", e); } if (epsg != null) { StringBuffer sql = new StringBuffer(); sql.append("CALL CreateSpatialIndex("); if (schemaName == null) { sql.append("NULL"); } else { sql.append("'").append(schemaName).append("'"); } sql.append(",'").append(tableName).append("'"); sql.append(",'").append(propertyName).append("'"); sql.append(",'").append(epsg).append("')"); LOGGER.fine(sql.toString()); st.execute(sql.toString()); } } } } finally { dataStore.closeSafe( st ); } } @Override public void postCreateFeatureType(SimpleFeatureType featureType, DatabaseMetaData metadata, String schemaName, Connection cx) throws SQLException { //figure out if the table has a spatial index and mark the feature type as so if (featureType.getGeometryDescriptor() == null) { return; } String idxTableName = featureType.getTypeName() + "_HATBOX"; ResultSet rs = metadata.getTables(null, schemaName, idxTableName, new String[]{"TABLE"}); try { if (rs.next()) { featureType.getGeometryDescriptor().getUserData().put(H2_SPATIAL_INDEX, idxTableName); } } finally { dataStore.closeSafe(rs); } } boolean isConcreteGeometry( Class binding ) { return Point.class.isAssignableFrom(binding) || LineString.class.isAssignableFrom(binding) || Polygon.class.isAssignableFrom(binding) || MultiPoint.class.isAssignableFrom( binding ) || MultiLineString.class.isAssignableFrom(binding) || MultiPolygon.class.isAssignableFrom( binding ); } public Integer getGeometrySRID(String schemaName, String tableName, String columnName, Connection cx) throws SQLException { //first try getting from table metadata int srid = GeoDB.GetSRID(cx, schemaName, tableName); if (srid > -1) { return srid; } //try grabbing directly from a geometry StringBuffer sql = new StringBuffer(); sql.append("SELECT ST_SRID("); encodeColumnName(columnName, sql); sql.append(") "); sql.append("FROM "); if (schemaName != null) { encodeTableName(schemaName, sql); sql.append("."); } encodeSchemaName(tableName, sql); sql.append(" WHERE "); encodeColumnName(columnName, sql); sql.append(" is not null LIMIT 1"); dataStore.getLogger().fine(sql.toString()); Statement st = cx.createStatement(); try { ResultSet rs = st.executeQuery(sql.toString()); try { if (rs.next()) { return new Integer(rs.getInt(1)); } else { //could not find o return null; } } finally { dataStore.closeSafe(rs); } } finally { dataStore.closeSafe(st); } } public void encodeGeometryEnvelope(String tableName, String geometryColumn, StringBuffer sql) { //TODO: change spatialdbbox to use envelope sql.append("ST_Envelope("); encodeColumnName(geometryColumn, sql); sql.append(")"); } @Override public Envelope decodeGeometryEnvelope(ResultSet rs, int column, Connection cx) throws SQLException, IOException { //TODO: change spatialdb in a box to return ReferencedEnvelope return (Envelope) rs.getObject(column); } public void encodeGeometryValue(Geometry value, int srid, StringBuffer sql) throws IOException { sql.append("ST_GeomFromText ('"); sql.append(new WKTWriter().write(value)); sql.append("',"); sql.append(srid); sql.append(")"); } public Geometry decodeGeometryValue(GeometryDescriptor descriptor, ResultSet rs, String column, GeometryFactory factory, Connection cx ) throws IOException, SQLException { byte[] bytes = rs.getBytes(column); if (bytes == null) { return null; } try { return new WKBReader(factory).read(bytes); } catch (ParseException e) { throw (IOException) new IOException().initCause(e); } //return JTS.geometryFromBytes( bytes ); } public void encodePrimaryKey(String column, StringBuffer sql) { encodeColumnName(column, sql); sql.append(" int AUTO_INCREMENT(1) PRIMARY KEY"); } @Override public String getSequenceForColumn(String schemaName, String tableName, String columnName, Connection cx) throws SQLException { String sequenceName = tableName + "_" + columnName + "_SEQUENCE"; //sequence names have to be upper case to select values from them sequenceName = sequenceName.toUpperCase(); Statement st = cx.createStatement(); try { StringBuffer sql = new StringBuffer(); sql.append( "SELECT * FROM INFORMATION_SCHEMA.SEQUENCES "); sql.append( "WHERE SEQUENCE_NAME = '").append( sequenceName ).append( "'" ); dataStore.getLogger().fine( sql.toString() ); ResultSet rs = st.executeQuery( sql.toString() ); try { if ( rs.next() ) { return sequenceName; } } finally { dataStore.closeSafe(rs); } } finally { dataStore.closeSafe(st); } return null; } @Override public Object getNextSequenceValue(String schemaName, String sequenceName, Connection cx) throws SQLException { Statement st = cx.createStatement(); try { String sql = "SELECT NEXTVAL('" + sequenceName + "')"; dataStore.getLogger().fine( sql ); ResultSet rs = st.executeQuery( sql ); try { rs.next(); return rs.getInt( 1 ); } finally { dataStore.closeSafe( rs ); } } finally { dataStore.closeSafe( st ); } } @Override public Object getNextAutoGeneratedValue(String schemaName, String tableName, String columnName, Connection cx) throws SQLException { Statement st = cx.createStatement(); try { ResultSet rs = st.executeQuery("SELECT b.COLUMN_DEFAULT " + " FROM INFORMATION_SCHEMA.INDEXES A, INFORMATION_SCHEMA.COLUMNS B " + "WHERE a.TABLE_NAME = b.TABLE_NAME " + " AND a.COLUMN_NAME = b.COLUMN_NAME " + " AND a.TABLE_NAME = '" + tableName + "' " + " AND a.COLUMN_NAME = '" + columnName + "' " + " AND a.PRIMARY_KEY = TRUE"); //figure out which sequence to query String sequence = null; try { //TODO: there has to be a better way to do this rs.next(); String string = rs.getString(1); sequence = string.substring(string.indexOf("SYSTEM_SEQUENCE"), string.length() - 1); } finally { dataStore.closeSafe(rs); } try { if (schemaName != null) { rs = st.executeQuery("SELECT CURRVAL('" + schemaName + "','" + sequence + "')"); } else { rs = st.executeQuery("SELECT CURRVAL('" + sequence + "')"); } rs.next(); int value = rs.getInt(1); return new Integer(value + 1); } finally { dataStore.closeSafe(rs); } } finally { dataStore.closeSafe(st); } } @Override public boolean isLimitOffsetSupported() { return true; } @Override public void applyLimitOffset(StringBuffer sql, int limit, int offset) { if(limit > 0 && limit < Integer.MAX_VALUE) { sql.append(" LIMIT " + limit); if(offset > 0) { sql.append(" OFFSET " + offset); } } else if(offset > 0) { // H2 pretends to have limit specified along with offset sql.append(" LIMIT " + Integer.MAX_VALUE); sql.append(" OFFSET " + offset); } } }