001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/io/datastore/sql/postgis/PostGISWhereBuilder.java $ 002 /*---------------- FILE HEADER ------------------------------------------ 003 004 This file is part of deegree. 005 Copyright (C) 2001-2008 by: 006 EXSE, Department of Geography, University of Bonn 007 http://www.giub.uni-bonn.de/deegree/ 008 lat/lon GmbH 009 http://www.lat-lon.de 010 011 This library is free software; you can redistribute it and/or 012 modify it under the terms of the GNU Lesser General Public 013 License as published by the Free Software Foundation; either 014 version 2.1 of the License, or (at your option) any later version. 015 016 This library is distributed in the hope that it will be useful, 017 but WITHOUT ANY WARRANTY; without even the implied warranty of 018 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 019 Lesser General Public License for more details. 020 021 You should have received a copy of the GNU Lesser General Public 022 License along with this library; if not, write to the Free Software 023 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 024 025 Contact: 026 027 Andreas Poth 028 lat/lon GmbH 029 Aennchenstr. 19 030 53115 Bonn 031 Germany 032 E-Mail: poth@lat-lon.de 033 034 Prof. Dr. Klaus Greve 035 Department of Geography 036 University of Bonn 037 Meckenheimer Allee 166 038 53115 Bonn 039 Germany 040 E-Mail: greve@giub.uni-bonn.de 041 042 043 ---------------------------------------------------------------------------*/ 044 045 package org.deegree.io.datastore.sql.postgis; 046 047 import java.sql.Types; 048 import java.util.ArrayList; 049 import java.util.List; 050 051 import org.deegree.i18n.Messages; 052 import org.deegree.io.datastore.DatastoreException; 053 import org.deegree.io.datastore.schema.MappedFeatureType; 054 import org.deegree.io.datastore.schema.MappedGeometryPropertyType; 055 import org.deegree.io.datastore.sql.StatementBuffer; 056 import org.deegree.io.datastore.sql.TableAliasGenerator; 057 import org.deegree.io.datastore.sql.VirtualContentProvider; 058 import org.deegree.io.datastore.sql.wherebuilder.SpecialCharString; 059 import org.deegree.io.datastore.sql.wherebuilder.WhereBuilder; 060 import org.deegree.model.filterencoding.DBFunction; 061 import org.deegree.model.filterencoding.Expression; 062 import org.deegree.model.filterencoding.Filter; 063 import org.deegree.model.filterencoding.FilterEvaluationException; 064 import org.deegree.model.filterencoding.Function; 065 import org.deegree.model.filterencoding.OperationDefines; 066 import org.deegree.model.filterencoding.PropertyIsLikeOperation; 067 import org.deegree.model.filterencoding.SpatialOperation; 068 import org.deegree.model.spatialschema.Envelope; 069 import org.deegree.model.spatialschema.Geometry; 070 import org.deegree.model.spatialschema.GeometryException; 071 import org.deegree.ogcbase.SortProperty; 072 import org.postgis.PGboxbase; 073 import org.postgis.PGgeometry; 074 075 /** 076 * {@link WhereBuilder} implementation for PostGIS databases. 077 * 078 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </A> 079 * @author last edited by: $Author: apoth $ 080 * 081 * @version $Revision: 9342 $, $Date: 2007-12-27 13:32:57 +0100 (Do, 27 Dez 2007) $ 082 */ 083 class PostGISWhereBuilder extends WhereBuilder { 084 085 private PostGISDatastore ds; 086 087 /** 088 * Creates a new instance of <code>PostGISWhereBuilder</code> from the given parameters. 089 * 090 * @param rootFts 091 * selected feature types, more than one type means that the types are joined 092 * @param aliases 093 * aliases for the feature types, may be null (must have same length as rootFts otherwise) 094 * @param filter 095 * filter that restricts the matched features 096 * @param sortProperties 097 * sort criteria for the result, may be null or empty 098 * @param aliasGenerator 099 * used to generate unique table aliases 100 * @param vcProvider 101 * @throws DatastoreException 102 */ 103 public PostGISWhereBuilder( MappedFeatureType[] rootFts, String[] aliases, Filter filter, 104 SortProperty[] sortProperties, TableAliasGenerator aliasGenerator, 105 VirtualContentProvider vcProvider ) throws DatastoreException { 106 super( rootFts, aliases, filter, sortProperties, aliasGenerator, vcProvider ); 107 this.ds = (PostGISDatastore) rootFts[0].getGMLSchema().getDatastore(); 108 } 109 110 /** 111 * Appends an SQL fragment for the given object to the given sql statement. 112 * 113 * NOTE: Currently, the method uses a quirk and appends the generated argument inline, i.e. not using 114 * query.addArgument(). This is because of a problem that occurred in PostgreSQL; the execution of the inline 115 * version is *much* faster (at least with version 8.0). 116 * 117 * @param query 118 * @param operation 119 * @throws FilterEvaluationException 120 */ 121 @Override 122 protected void appendPropertyIsLikeOperationAsSQL( StatementBuffer query, PropertyIsLikeOperation operation ) throws FilterEvaluationException { 123 124 String literal = operation.getLiteral().getValue(); 125 String escape = "" + operation.getEscapeChar(); 126 String wildCard = "" + operation.getWildCard(); 127 String singleChar = "" + operation.getSingleChar(); 128 129 SpecialCharString specialString = new SpecialCharString( literal, wildCard, singleChar, escape ); 130 String sqlEncoded = specialString.toSQLStyle( !operation.isMatchCase() ); 131 132 int targetSqlType = getPropertyNameSQLType( operation.getPropertyName() ); 133 134 // if isMatchCase == false surround first argument with LOWER (...) and convert characters 135 // in second argument to lower case 136 if ( operation.isMatchCase() ) { 137 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 138 } else { 139 List<Expression> list = new ArrayList<Expression>(); 140 list.add( operation.getPropertyName() ); 141 Function func = new DBFunction( getFunctionName( "LOWER" ), list ); 142 appendFunctionAsSQL( query, func, targetSqlType ); 143 } 144 145 query.append( " LIKE '" ); 146 query.append( sqlEncoded ); 147 query.append( "'" ); 148 } 149 150 /** 151 * Generates an SQL-fragment for the given object. 152 * 153 * TODO: Implement BBOX faster using explicit B0X-constructor 154 * 155 * @throws DatastoreException 156 */ 157 @Override 158 protected void appendSpatialOperationAsSQL( StatementBuffer query, SpatialOperation operation ) 159 throws DatastoreException { 160 161 try { 162 switch ( operation.getOperatorId() ) { 163 case OperationDefines.BBOX: { 164 appendBBOXOperationAsSQL( query, operation ); 165 break; 166 } 167 case OperationDefines.INTERSECTS: { 168 appendIntersectsOperationAsSQL( query, operation ); 169 break; 170 } 171 case OperationDefines.CROSSES: { 172 appendSimpleOperationAsSQL( query, operation, "crosses" ); 173 break; 174 } 175 case OperationDefines.EQUALS: { 176 appendSimpleOperationAsSQL( query, operation, "equals" ); 177 break; 178 } 179 case OperationDefines.WITHIN: { 180 appendSimpleOperationAsSQL( query, operation, "within" ); 181 break; 182 } 183 case OperationDefines.OVERLAPS: { 184 appendSimpleOperationAsSQL( query, operation, "overlaps" ); 185 break; 186 } 187 case OperationDefines.TOUCHES: { 188 appendSimpleOperationAsSQL( query, operation, "touches" ); 189 break; 190 } 191 case OperationDefines.DISJOINT: { 192 appendSimpleOperationAsSQL( query, operation, "disjoint" ); 193 break; 194 } 195 case OperationDefines.CONTAINS: { 196 appendSimpleOperationAsSQL( query, operation, "contains" ); 197 break; 198 } 199 case OperationDefines.DWITHIN: { 200 appendDWithinOperationAsSQL( query, operation ); 201 break; 202 } 203 case OperationDefines.BEYOND: { 204 appendBeyondOperationAsSQL( query, operation ); 205 break; 206 } 207 default: { 208 String msg = "Spatial operator " + OperationDefines.getNameById( operation.getOperatorId() ) 209 + " is not supported by '" + this.getClass().toString() + "'."; 210 throw new DatastoreException( msg ); 211 } 212 } 213 } catch ( GeometryException e ) { 214 throw new DatastoreException( e ); 215 } 216 217 } 218 219 private void appendSimpleOperationAsSQL( StatementBuffer query, SpatialOperation operation, String operationName ) 220 throws GeometryException, DatastoreException { 221 query.append( operationName ); 222 query.append( "(" ); 223 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 224 query.append( ',' ); 225 appendGeometryArgument( query, this.getGeometryProperty( operation.getPropertyName() ), operation.getGeometry() ); 226 query.append( ')' ); 227 } 228 229 private void appendIntersectsOperationAsSQL( StatementBuffer query, SpatialOperation operation ) 230 throws GeometryException, DatastoreException { 231 232 Envelope env = operation.getGeometry().getEnvelope(); 233 MappedGeometryPropertyType geoProperty = this.getGeometryProperty( operation.getPropertyName() ); 234 235 String argumentSRS = null; 236 if ( env.getCoordinateSystem() != null ) { 237 argumentSRS = env.getCoordinateSystem().getName(); 238 } 239 String propertySRS = geoProperty.getCS().getName(); 240 int internalSRS = geoProperty.getMappingField().getSRS(); 241 242 int createSRSCode = getArgumentSRSCode( argumentSRS, propertySRS, internalSRS ); 243 PGboxbase box = PGgeometryAdapter.export( env ); 244 StringBuffer bbox = new StringBuffer( 323 ); 245 bbox.append( "SetSRID(?," + createSRSCode + ")" ); 246 247 int targetSRSCode = getTargetSRSCode( argumentSRS, propertySRS, internalSRS ); 248 if ( targetSRSCode != SRS_UNDEFINED ) { 249 bbox = new StringBuffer( this.ds.buildSRSTransformCall( bbox.toString(), targetSRSCode ) ); 250 } 251 252 // use the bbox operator (&&) to filter using the spatial index 253 query.append( "(" ); 254 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 255 query.append( " && " ); 256 query.append( bbox.toString() ); 257 query.addArgument( box, Types.OTHER ); 258 259 query.append( " AND intersects (" ); 260 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 261 query.append( ',' ); 262 appendGeometryArgument( query, getGeometryProperty( operation.getPropertyName() ), operation.getGeometry() ); 263 query.append( "))" ); 264 } 265 266 private void appendBBOXOperationAsSQL( StatementBuffer query, SpatialOperation operation ) 267 throws DatastoreException, GeometryException { 268 269 Envelope env = operation.getGeometry().getEnvelope(); 270 MappedGeometryPropertyType geoProperty = this.getGeometryProperty( operation.getPropertyName() ); 271 272 String argumentSRS = null; 273 if ( env.getCoordinateSystem() != null ) { 274 argumentSRS = env.getCoordinateSystem().getName(); 275 } 276 String propertySRS = geoProperty.getCS().getName(); 277 int internalSRS = geoProperty.getMappingField().getSRS(); 278 279 int createSRSCode = getArgumentSRSCode( argumentSRS, propertySRS, internalSRS ); 280 PGboxbase box = PGgeometryAdapter.export( env ); 281 StringBuffer bbox = new StringBuffer( 323 ); 282 bbox.append( "SetSRID(?," + createSRSCode + ")" ); 283 284 int targetSRSCode = getTargetSRSCode( argumentSRS, propertySRS, internalSRS ); 285 if ( targetSRSCode != SRS_UNDEFINED ) { 286 bbox = new StringBuffer( this.ds.buildSRSTransformCall( bbox.toString(), targetSRSCode ) ); 287 } 288 289 // only the && operator uses the spatial index 290 // intersects, contains etc. do not use spatial indexing!!!! 291 query.append( "(" ); 292 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 293 query.append( " && " ); 294 query.append( bbox.toString() ); 295 query.addArgument( box, Types.OTHER ); 296 297 // it is necessary to add an explicit intersects as well, because the && operator only 298 // checks for intersection of the bbox with the bboxes of the geometries (and not the 299 // geometries themselves) 300 query.append( " AND intersects (" ); 301 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 302 query.append( ',' ); 303 query.append( bbox.toString() ); 304 query.addArgument( box, Types.OTHER ); 305 query.append( "))" ); 306 } 307 308 private void appendDWithinOperationAsSQL( StatementBuffer query, SpatialOperation operation ) 309 throws GeometryException, DatastoreException { 310 query.append( "distance(" ); 311 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 312 query.append( ',' ); 313 appendGeometryArgument( query, this.getGeometryProperty( operation.getPropertyName() ), operation.getGeometry() ); 314 query.append( ")<=" ); 315 query.append( "" + operation.getDistance() ); 316 } 317 318 private void appendBeyondOperationAsSQL( StatementBuffer query, SpatialOperation operation ) 319 throws GeometryException, DatastoreException { 320 query.append( "distance(" ); 321 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 322 query.append( ',' ); 323 appendGeometryArgument( query, this.getGeometryProperty( operation.getPropertyName() ), operation.getGeometry() ); 324 query.append( ")>" ); 325 query.append( "" + operation.getDistance() ); 326 } 327 328 /** 329 * Construct and append the geometry argument using the correct internal SRS and perform a transform call to the 330 * internal SRS of the {@link MappedGeometryPropertyType} if necessary. 331 * 332 * @param query 333 * @param geoProperty 334 * @param geometry 335 * @throws DatastoreException 336 * @throws GeometryException 337 */ 338 private void appendGeometryArgument( StatementBuffer query, MappedGeometryPropertyType geoProperty, 339 Geometry geometry ) 340 throws DatastoreException, GeometryException { 341 342 String argumentSRS = null; 343 if ( geometry.getCoordinateSystem() != null ) { 344 argumentSRS = geometry.getCoordinateSystem().getName(); 345 } 346 String propertySRS = geoProperty.getCS().getName(); 347 int internalSRS = geoProperty.getMappingField().getSRS(); 348 349 int createSRSCode = getArgumentSRSCode( argumentSRS, propertySRS, internalSRS ); 350 PGgeometry argument = PGgeometryAdapter.export( geometry, createSRSCode ); 351 352 int targetSRSCode = getTargetSRSCode( argumentSRS, propertySRS, internalSRS ); 353 if ( targetSRSCode != SRS_UNDEFINED ) { 354 query.append( ds.buildSRSTransformCall( "?", targetSRSCode ) ); 355 } else { 356 query.append( '?' ); 357 } 358 query.addArgument( argument, Types.OTHER ); 359 } 360 361 /** 362 * Returns the internal SRS code that must be used for the creation of a geometry argument used in a spatial 363 * operator. 364 * 365 * @param literalSRS 366 * @param propertySRS 367 * @param internalSrs 368 * @return the internal SRS code that must be used for the creation of a geometry argument 369 * @throws DatastoreException 370 */ 371 private int getArgumentSRSCode( String argumentSRS, String propertySRS, int internalSrs ) 372 throws DatastoreException { 373 int argumentSRSCode = internalSrs; 374 if ( argumentSRS == null ) { 375 argumentSRSCode = internalSrs; 376 } else if ( !propertySRS.equals( argumentSRS ) ) { 377 argumentSRSCode = this.ds.getNativeSRSCode( argumentSRS ); 378 if ( argumentSRSCode == SRS_UNDEFINED ) { 379 String msg = Messages.getMessage( "DATASTORE_SQL_NATIVE_CT_UNKNOWN_SRS", 380 PostGISDatastore.class.getName(), argumentSRS ); 381 throw new DatastoreException( msg ); 382 } 383 } 384 return argumentSRSCode; 385 } 386 387 /** 388 * Returns the internal SRS code that must be used for the transform call for a geometry argument used in a spatial 389 * operator. 390 * 391 * @param literalSRS 392 * @param propertySRS 393 * @param internalSrs 394 * @return the internal SRS code that must be used for the transform call of a geometry argument, or -1 if no 395 * transformation is necessary 396 */ 397 private int getTargetSRSCode( String argumentSRS, String propertySRS, int internalSrs ) 398 throws DatastoreException { 399 int targetSRS = SRS_UNDEFINED; 400 if ( argumentSRS != null && !argumentSRS.equals( propertySRS ) ) { 401 if ( internalSrs == SRS_UNDEFINED ) { 402 String msg = Messages.getMessage( "DATASTORE_SRS_NOT_SPECIFIED2", argumentSRS, propertySRS ); 403 throw new DatastoreException( msg ); 404 } 405 targetSRS = internalSrs; 406 } 407 return targetSRS; 408 } 409 }