001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/io/datastore/sql/wherebuilder/WhereBuilder.java $ 002 /*---------------------------------------------------------------------------- 003 This file is part of deegree, http://deegree.org/ 004 Copyright (C) 2001-2009 by: 005 Department of Geography, University of Bonn 006 and 007 lat/lon GmbH 008 009 This library is free software; you can redistribute it and/or modify it under 010 the terms of the GNU Lesser General Public License as published by the Free 011 Software Foundation; either version 2.1 of the License, or (at your option) 012 any later version. 013 This library is distributed in the hope that it will be useful, but WITHOUT 014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 016 details. 017 You should have received a copy of the GNU Lesser General Public License 018 along with this library; if not, write to the Free Software Foundation, Inc., 019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 020 021 Contact information: 022 023 lat/lon GmbH 024 Aennchenstr. 19, 53177 Bonn 025 Germany 026 http://lat-lon.de/ 027 028 Department of Geography, University of Bonn 029 Prof. Dr. Klaus Greve 030 Postfach 1147, 53001 Bonn 031 Germany 032 http://www.geographie.uni-bonn.de/deegree/ 033 034 e-mail: info@deegree.org 035 ----------------------------------------------------------------------------*/ 036 package org.deegree.io.datastore.sql.wherebuilder; 037 038 import static java.sql.Types.DOUBLE; 039 import static java.sql.Types.INTEGER; 040 import static java.sql.Types.SMALLINT; 041 import static org.deegree.model.filterencoding.ExpressionDefines.ADD; 042 import static org.deegree.model.filterencoding.ExpressionDefines.DIV; 043 import static org.deegree.model.filterencoding.ExpressionDefines.MUL; 044 import static org.deegree.model.filterencoding.ExpressionDefines.SUB; 045 import static org.deegree.model.filterencoding.OperationDefines.BBOX; 046 047 import java.sql.Types; 048 import java.util.ArrayList; 049 import java.util.Hashtable; 050 import java.util.Iterator; 051 import java.util.List; 052 import java.util.Stack; 053 054 import org.deegree.framework.log.ILogger; 055 import org.deegree.framework.log.LoggerFactory; 056 import org.deegree.i18n.Messages; 057 import org.deegree.io.datastore.DatastoreException; 058 import org.deegree.io.datastore.PropertyPathResolvingException; 059 import org.deegree.io.datastore.schema.MappedFeaturePropertyType; 060 import org.deegree.io.datastore.schema.MappedFeatureType; 061 import org.deegree.io.datastore.schema.MappedGeometryPropertyType; 062 import org.deegree.io.datastore.schema.MappedPropertyType; 063 import org.deegree.io.datastore.schema.MappedSimplePropertyType; 064 import org.deegree.io.datastore.schema.TableRelation; 065 import org.deegree.io.datastore.schema.content.ConstantContent; 066 import org.deegree.io.datastore.schema.content.MappingField; 067 import org.deegree.io.datastore.schema.content.SQLFunctionCall; 068 import org.deegree.io.datastore.schema.content.SimpleContent; 069 import org.deegree.io.datastore.sql.StatementBuffer; 070 import org.deegree.io.datastore.sql.TableAliasGenerator; 071 import org.deegree.io.datastore.sql.VirtualContentProvider; 072 import org.deegree.model.feature.schema.FeatureType; 073 import org.deegree.model.filterencoding.ArithmeticExpression; 074 import org.deegree.model.filterencoding.ComparisonOperation; 075 import org.deegree.model.filterencoding.ComplexFilter; 076 import org.deegree.model.filterencoding.DBFunction; 077 import org.deegree.model.filterencoding.Expression; 078 import org.deegree.model.filterencoding.ExpressionDefines; 079 import org.deegree.model.filterencoding.FeatureFilter; 080 import org.deegree.model.filterencoding.Filter; 081 import org.deegree.model.filterencoding.FilterEvaluationException; 082 import org.deegree.model.filterencoding.Function; 083 import org.deegree.model.filterencoding.Literal; 084 import org.deegree.model.filterencoding.LogicalOperation; 085 import org.deegree.model.filterencoding.Operation; 086 import org.deegree.model.filterencoding.OperationDefines; 087 import org.deegree.model.filterencoding.PropertyIsBetweenOperation; 088 import org.deegree.model.filterencoding.PropertyIsCOMPOperation; 089 import org.deegree.model.filterencoding.PropertyIsLikeOperation; 090 import org.deegree.model.filterencoding.PropertyIsNullOperation; 091 import org.deegree.model.filterencoding.PropertyName; 092 import org.deegree.model.filterencoding.SpatialOperation; 093 import org.deegree.ogcbase.PropertyPath; 094 import org.deegree.ogcbase.SortProperty; 095 096 /** 097 * Creates SQL-WHERE clauses from OGC filter expressions (to restrict SQL statements to all stored features that match a 098 * given filter). 099 * <p> 100 * Also handles the creation of ORDER-BY clauses. 101 * 102 * @author <a href="mailto:tfr@users.sourceforge.net">Torsten Friebe </a> 103 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a> 104 * @author last edited by: $Author: aschmitz $ 105 * 106 * @version $Revision: 23585 $, $Date: 2010-04-13 14:07:18 +0200 (Di, 13 Apr 2010) $ 107 */ 108 public class WhereBuilder { 109 110 private static final ILogger LOG = LoggerFactory.getLogger( WhereBuilder.class ); 111 112 // database specific SRS code for unspecified SRS 113 protected static final int SRS_UNDEFINED = -1; 114 115 /** Targeted feature types. */ 116 protected MappedFeatureType[] rootFts; 117 118 /** {@link Filter} for which the corresponding WHERE-clause will be generated. */ 119 protected Filter filter; 120 121 protected SortProperty[] sortProperties; 122 123 protected VirtualContentProvider vcProvider; 124 125 protected QueryTableTree queryTableTree; 126 127 protected List<PropertyPath> filterPropertyPaths = new ArrayList<PropertyPath>(); 128 129 protected List<PropertyPath> sortPropertyPaths = new ArrayList<PropertyPath>(); 130 131 private Hashtable<String, String> functionMap = new Hashtable<String, String>(); 132 133 /** 134 * Creates a new <code>WhereBuilder</code> instance. 135 * 136 * @param rootFts 137 * selected feature types, more than one type means that the types are joined 138 * @param aliases 139 * aliases for the feature types, may be null (must have same length as rootFts otherwise) 140 * @param filter 141 * @param sortProperties 142 * @param aliasGenerator 143 * aliasGenerator to be used to generate table aliases, may be null 144 * @param vcProvider 145 * @throws DatastoreException 146 */ 147 public WhereBuilder( MappedFeatureType[] rootFts, String[] aliases, Filter filter, SortProperty[] sortProperties, 148 TableAliasGenerator aliasGenerator, VirtualContentProvider vcProvider ) 149 throws DatastoreException { 150 151 this.rootFts = rootFts; 152 this.queryTableTree = new QueryTableTree( rootFts, aliases, aliasGenerator ); 153 154 // add filter properties to the QueryTableTree 155 this.filter = filter; 156 if ( filter != null ) { 157 assert filter instanceof ComplexFilter || filter instanceof FeatureFilter; 158 buildFilterPropertyNameMap(); 159 for ( PropertyPath property : this.filterPropertyPaths ) { 160 this.queryTableTree.addFilterProperty( property ); 161 } 162 fillFunctionNameMap(); 163 } 164 165 // add sort properties to the QueryTableTree 166 this.sortProperties = sortProperties; 167 if ( sortProperties != null ) { 168 for ( SortProperty property : sortProperties ) { 169 this.sortPropertyPaths.add( property.getSortProperty() ); 170 this.queryTableTree.addSortProperty( property.getSortProperty() ); 171 } 172 } 173 174 this.vcProvider = vcProvider; 175 176 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 177 LOG.logDebug( "QueryTableTree:\n" + this.queryTableTree ); 178 } 179 } 180 181 /** 182 * Returns the table alias used for the specified root feature type. 183 * 184 * @param i 185 * index of the requested root feature type 186 * @return the alias used for the root table 187 */ 188 public String getRootTableAlias( int i ) { 189 return this.queryTableTree.getRootNodes()[i].getTableAlias(); 190 } 191 192 /** 193 * Returns the associated <code>Filter</code> instance. 194 * 195 * @return the associated <code>Filter</code> instance 196 */ 197 public Filter getFilter() { 198 return this.filter; 199 } 200 201 protected MappedGeometryPropertyType getGeometryProperty( PropertyName propName ) { 202 PropertyPath propertyPath = propName.getValue(); 203 PropertyNode propertyNode = this.queryTableTree.getPropertyNode( propertyPath ); 204 assert propertyNode != null; 205 assert propertyNode instanceof GeometryPropertyNode; 206 LOG.logDebug( "Found geometry property with path: " + propertyPath + " and propName: " 207 + propertyNode.getProperty().getName() ); 208 return (MappedGeometryPropertyType) propertyNode.getProperty(); 209 } 210 211 // /** 212 // * Returns the SRS of the {@link MappedGeometryPropertyType} that is identified by the given 213 // * {@link PropertyPath}. 214 // * 215 // * @param propertyPath 216 // * @return the default SRS of the geometry property type 217 // */ 218 // protected String getSrs( PropertyPath propertyPath ) { 219 // PropertyNode propertyNode = this.queryTableTree.getPropertyNode( propertyPath ); 220 // assert propertyNode != null; 221 // assert propertyNode instanceof GeometryPropertyNode; 222 // MappedGeometryPropertyType geoProp = (MappedGeometryPropertyType) propertyNode.getProperty(); 223 // return geoProp.getSRS().toString(); 224 // } 225 // 226 // /** 227 // * Returns the internal Srs of the {@link MappedGeometryPropertyType} that is identified by 228 // the 229 // * given {@link PropertyPath}. 230 // * 231 // * @param propertyPath 232 // * @return the default SRS of the geometry property type 233 // */ 234 // protected int getInternalSrsCode( PropertyPath propertyPath ) { 235 // PropertyNode propertyNode = this.queryTableTree.getPropertyNode( propertyPath ); 236 // assert propertyNode != null; 237 // assert propertyNode instanceof GeometryPropertyNode; 238 // MappedGeometryPropertyType geoProp = (MappedGeometryPropertyType) propertyNode.getProperty(); 239 // return geoProp.getMappingField().getSRS(); 240 // } 241 242 protected int getPropertyNameSQLType( PropertyName propertyName ) { 243 244 PropertyPath propertyPath = propertyName.getValue(); 245 PropertyNode propertyNode = this.queryTableTree.getPropertyNode( propertyPath ); 246 assert propertyNode != null; 247 248 // can be useful if the assertions are not enabled 249 if ( propertyNode == null ) { 250 LOG.logDebug( "Null propertyNode for propertyName: " + propertyName + " with queryTable: " 251 + this.queryTableTree ); 252 } 253 254 MappedPropertyType propertyType = propertyNode.getProperty(); 255 if ( !( propertyType instanceof MappedSimplePropertyType ) ) { 256 String msg = "Error in WhereBuilder: cannot compare against properties of type '" + propertyType.getClass() 257 + "'."; 258 LOG.logError( msg ); 259 throw new RuntimeException( msg ); 260 } 261 262 SimpleContent content = ( (MappedSimplePropertyType) propertyType ).getContent(); 263 if ( !( content instanceof MappingField ) ) { 264 String msg = "Virtual properties are currently ignored in WhereBuilder#getPropertyNameSQLType(PropertyName)."; 265 LOG.logWarning( msg ); 266 return Types.VARCHAR; 267 } 268 269 int targetSqlType = ( (MappingField) content ).getType(); 270 return targetSqlType; 271 } 272 273 protected void buildFilterPropertyNameMap() 274 throws PropertyPathResolvingException { 275 if ( this.filter instanceof ComplexFilter ) { 276 buildPropertyNameMapFromOperation( ( (ComplexFilter) this.filter ).getOperation() ); 277 } else if ( this.filter instanceof FeatureFilter ) { 278 // TODO 279 // throw new PropertyPathResolvingException( "FeatureFilter not implemented yet." ); 280 } 281 } 282 283 private void buildPropertyNameMapFromOperation( Operation operation ) 284 throws PropertyPathResolvingException { 285 switch ( OperationDefines.getTypeById( operation.getOperatorId() ) ) { 286 case OperationDefines.TYPE_SPATIAL: { 287 PropertyName name = ( (SpatialOperation) operation ).getPropertyName(); 288 289 // possible in case of BBOX 290 if ( name == null && operation.getOperatorId() == BBOX ) { 291 // use first ft and first geom property 292 FeatureType ft = queryTableTree.getRootNodes()[0].getFeatureType(); 293 name = new PropertyName( ft.getGeometryProperties()[0].getName() ); 294 // modify the operation, since the property name is used later on from the datastores' where builders 295 ( (SpatialOperation) operation ).setPropertyName( name ); 296 } 297 registerPropertyName( name ); 298 break; 299 } 300 case OperationDefines.TYPE_COMPARISON: { 301 buildPropertyNameMap( (ComparisonOperation) operation ); 302 break; 303 } 304 case OperationDefines.TYPE_LOGICAL: { 305 buildPropertyNameMap( (LogicalOperation) operation ); 306 break; 307 } 308 default: { 309 break; 310 } 311 } 312 } 313 314 private void buildPropertyNameMap( ComparisonOperation operation ) 315 throws PropertyPathResolvingException { 316 switch ( operation.getOperatorId() ) { 317 case OperationDefines.PROPERTYISEQUALTO: 318 case OperationDefines.PROPERTYISNOTEQUALTO: 319 case OperationDefines.PROPERTYISLESSTHAN: 320 case OperationDefines.PROPERTYISGREATERTHAN: 321 case OperationDefines.PROPERTYISLESSTHANOREQUALTO: 322 case OperationDefines.PROPERTYISGREATERTHANOREQUALTO: { 323 buildPropertyNameMap( ( (PropertyIsCOMPOperation) operation ).getFirstExpression() ); 324 buildPropertyNameMap( ( (PropertyIsCOMPOperation) operation ).getSecondExpression() ); 325 break; 326 } 327 case OperationDefines.PROPERTYISLIKE: { 328 registerPropertyName( ( (PropertyIsLikeOperation) operation ).getPropertyName() ); 329 break; 330 } 331 case OperationDefines.PROPERTYISNULL: { 332 buildPropertyNameMap( ( (PropertyIsNullOperation) operation ).getPropertyName() ); 333 break; 334 } 335 case OperationDefines.PROPERTYISBETWEEN: { 336 buildPropertyNameMap( ( (PropertyIsBetweenOperation) operation ).getLowerBoundary() ); 337 buildPropertyNameMap( ( (PropertyIsBetweenOperation) operation ).getUpperBoundary() ); 338 registerPropertyName( ( (PropertyIsBetweenOperation) operation ).getPropertyName() ); 339 break; 340 } 341 default: { 342 break; 343 } 344 } 345 } 346 347 private void buildPropertyNameMap( LogicalOperation operation ) 348 throws PropertyPathResolvingException { 349 List<?> operationList = operation.getArguments(); 350 Iterator<?> it = operationList.iterator(); 351 while ( it.hasNext() ) { 352 buildPropertyNameMapFromOperation( (Operation) it.next() ); 353 } 354 } 355 356 private void buildPropertyNameMap( Expression expression ) 357 throws PropertyPathResolvingException { 358 switch ( expression.getExpressionId() ) { 359 case ExpressionDefines.PROPERTYNAME: { 360 registerPropertyName( (PropertyName) expression ); 361 break; 362 } 363 case ExpressionDefines.ADD: 364 case ExpressionDefines.SUB: 365 case ExpressionDefines.MUL: 366 case ExpressionDefines.DIV: { 367 buildPropertyNameMap( ( (ArithmeticExpression) expression ).getFirstExpression() ); 368 buildPropertyNameMap( ( (ArithmeticExpression) expression ).getSecondExpression() ); 369 break; 370 } 371 case ExpressionDefines.FUNCTION: { 372 // TODO: What about PropertyNames used here? 373 break; 374 } 375 case ExpressionDefines.EXPRESSION: 376 case ExpressionDefines.LITERAL: { 377 break; 378 } 379 } 380 } 381 382 private void registerPropertyName( PropertyName propertyName ) { 383 this.filterPropertyPaths.add( propertyName.getValue() ); 384 } 385 386 /** 387 * Appends the alias-qualified, comma separated list of all tables to be joined in order to represent the associated 388 * filter expression (and possibly feature type joins). 389 * <p> 390 * The list consist of left outer joins ("x LEFT OUTER JOIN y") and cross-product joins ("x,y"): 391 * <ul> 392 * <li>left outer joins are generated for each join that is necessary, because of filter expressions that target 393 * properties stored in related tables (condition joins) 394 * <li>cross-product joins are generated for all feature type root tables (feature type joins) that have not joined 395 * by filter expression joins before</li> 396 * </ul> 397 * 398 * @param query 399 * the list is appended to this <code>SQLStatement</code> 400 */ 401 public void appendJoinTableList( StatementBuffer query ) { 402 403 FeatureTypeNode[] rootNodes = this.queryTableTree.getRootNodes(); 404 appendOuterJoinTableList( query, rootNodes[0] ); 405 for ( int i = 1; i < rootNodes.length; i++ ) { 406 query.append( ',' ); 407 appendOuterJoinTableList( query, rootNodes[i] ); 408 } 409 } 410 411 /** 412 * Appends the alias-qualified, comma separated list of tables to be joined (for one root feature type node). This 413 * includes the join conditions (necessary for the filter conditions), which are generated in ANSI-SQL left outer 414 * join style. 415 * 416 * @param query 417 * the list is appended to this <code>SQLStatement</code> 418 * @param rootNode 419 * one root feature type node in the <code>QueryTableTree</code> 420 */ 421 private void appendOuterJoinTableList( StatementBuffer query, FeatureTypeNode rootNode ) { 422 423 query.append( rootNode.getTable() ); 424 query.append( ' ' ); 425 query.append( rootNode.getTableAlias() ); 426 Stack<PropertyNode> propertyNodeStack = new Stack<PropertyNode>(); 427 PropertyNode[] propertyNodes = rootNode.getPropertyNodes(); 428 for ( int i = 0; i < propertyNodes.length; i++ ) { 429 propertyNodeStack.push( propertyNodes[i] ); 430 } 431 432 while ( !propertyNodeStack.isEmpty() ) { 433 PropertyNode currentNode = propertyNodeStack.pop(); 434 String fromAlias = currentNode.getParent().getTableAlias(); 435 TableRelation[] tableRelations = currentNode.getPathFromParent(); 436 if ( tableRelations != null && tableRelations.length != 0 ) { 437 String[] toAliases = currentNode.getTableAliases(); 438 appendOuterJoins( tableRelations, fromAlias, toAliases, query ); 439 440 } 441 if ( currentNode instanceof FeaturePropertyNode ) { 442 FeaturePropertyNode featurePropertyNode = (FeaturePropertyNode) currentNode; 443 FeatureTypeNode[] childNodes = ( (FeaturePropertyNode) currentNode ).getFeatureTypeNodes(); 444 for ( int i = 0; i < childNodes.length; i++ ) { 445 // TODO is this way of skipping root tables o.k.? 446 if ( childNodes[i].getFtAlias() != null ) { 447 continue; 448 } 449 String toTable = childNodes[i].getTable(); 450 String toAlias = childNodes[i].getTableAlias(); 451 String[] pathAliases = featurePropertyNode.getTableAliases(); 452 if ( pathAliases.length == 0 ) { 453 fromAlias = featurePropertyNode.getParent().getTableAlias(); 454 } else { 455 fromAlias = pathAliases[pathAliases.length - 1]; 456 } 457 MappedFeaturePropertyType content = (MappedFeaturePropertyType) featurePropertyNode.getProperty(); 458 TableRelation[] relations = content.getTableRelations(); 459 TableRelation relation = relations[relations.length - 1]; 460 appendOuterJoin( relation, fromAlias, toAlias, toTable, query ); 461 propertyNodes = childNodes[i].getPropertyNodes(); 462 for ( int j = 0; j < propertyNodes.length; j++ ) { 463 propertyNodeStack.push( propertyNodes[j] ); 464 } 465 } 466 } 467 } 468 } 469 470 private void appendOuterJoins( TableRelation[] tableRelation, String fromAlias, String[] toAliases, 471 StatementBuffer query ) { 472 for ( int i = 0; i < toAliases.length; i++ ) { 473 String toAlias = toAliases[i]; 474 appendOuterJoin( tableRelation[i], fromAlias, toAlias, query ); 475 fromAlias = toAlias; 476 } 477 } 478 479 private void appendOuterJoin( TableRelation tableRelation, String fromAlias, String toAlias, StatementBuffer query ) { 480 481 query.append( " LEFT OUTER JOIN " ); 482 query.append( tableRelation.getToTable() ); 483 query.append( " " ); 484 query.append( toAlias ); 485 query.append( " ON " ); 486 487 MappingField[] fromFields = tableRelation.getFromFields(); 488 MappingField[] toFields = tableRelation.getToFields(); 489 for ( int i = 0; i < fromFields.length; i++ ) { 490 if ( toAlias.equals( "" ) ) { 491 toAlias = tableRelation.getToTable(); 492 } 493 query.append( toAlias ); 494 query.append( "." ); 495 query.append( toFields[i].getField() ); 496 query.append( "=" ); 497 if ( fromAlias.equals( "" ) ) { 498 fromAlias = tableRelation.getFromTable(); 499 } 500 query.append( fromAlias ); 501 query.append( "." ); 502 query.append( fromFields[i].getField() ); 503 if ( i != fromFields.length - 1 ) { 504 query.append( " AND " ); 505 } 506 } 507 } 508 509 private void appendOuterJoin( TableRelation tableRelation, String fromAlias, String toAlias, String toTable, 510 StatementBuffer query ) { 511 512 query.append( " LEFT OUTER JOIN " ); 513 query.append( toTable ); 514 query.append( " " ); 515 query.append( toAlias ); 516 query.append( " ON " ); 517 518 MappingField[] fromFields = tableRelation.getFromFields(); 519 MappingField[] toFields = tableRelation.getToFields(); 520 for ( int i = 0; i < fromFields.length; i++ ) { 521 if ( toAlias.equals( "" ) ) { 522 toAlias = toTable; 523 } 524 query.append( toAlias ); 525 query.append( "." ); 526 query.append( toFields[i].getField() ); 527 query.append( "=" ); 528 if ( fromAlias.equals( "" ) ) { 529 fromAlias = tableRelation.getFromTable(); 530 } 531 query.append( fromAlias ); 532 query.append( "." ); 533 query.append( fromFields[i].getField() ); 534 if ( i != fromFields.length - 1 ) { 535 query.append( " AND " ); 536 } 537 } 538 } 539 540 /** 541 * Appends an SQL WHERE-condition corresponding to the <code>Filter</code> to the given SQL statement. 542 * 543 * @param query 544 * @throws DatastoreException 545 */ 546 public final void appendWhereCondition( StatementBuffer query ) 547 throws DatastoreException { 548 if ( filter instanceof ComplexFilter ) { 549 query.append( " WHERE " ); 550 appendComplexFilterAsSQL( query, (ComplexFilter) filter ); 551 } else if ( filter instanceof FeatureFilter ) { 552 FeatureFilter featureFilter = (FeatureFilter) filter; 553 if ( featureFilter.getFeatureIds().size() > 0 ) { 554 query.append( " WHERE " ); 555 appendFeatureFilterAsSQL( query, featureFilter ); 556 } 557 } else if ( filter != null ) { 558 assert false : "Unexpected filter type: " + filter.getClass(); 559 } 560 } 561 562 /** 563 * Appends an SQL "ORDER BY"-condition that corresponds to the sort properties of the query to the given SQL 564 * statement. 565 * 566 * @param query 567 * @throws DatastoreException 568 */ 569 public void appendOrderByCondition( StatementBuffer query ) 570 throws DatastoreException { 571 572 // ignore properties that are unsuitable as sort criteria (like constant properties) 573 List<SortProperty> sortProps = new ArrayList<SortProperty>(); 574 575 if ( this.sortProperties != null && this.sortProperties.length != 0 ) { 576 for ( int i = 0; i < this.sortProperties.length; i++ ) { 577 SortProperty sortProperty = this.sortProperties[i]; 578 PropertyPath path = sortProperty.getSortProperty(); 579 PropertyNode propertyNode = this.queryTableTree.getPropertyNode( path ); 580 MappedPropertyType pt = propertyNode.getProperty(); 581 if ( !( pt instanceof MappedSimplePropertyType ) ) { 582 String msg = Messages.getMessage( "DATASTORE_INVALID_SORT_PROPERTY", pt.getName() ); 583 throw new DatastoreException( msg ); 584 } 585 SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent(); 586 if ( content.isSortable() ) { 587 sortProps.add( sortProperty ); 588 } else { 589 String msg = "Ignoring sort criterion - property '" + path.getAsString() 590 + "' is not suitable for sorting."; 591 LOG.logDebug( msg ); 592 } 593 } 594 } 595 596 if ( sortProps.size() > 0 ) { 597 query.append( " ORDER BY " ); 598 } 599 600 for ( int i = 0; i < sortProps.size(); i++ ) { 601 SortProperty sortProperty = sortProps.get( i ); 602 PropertyPath path = sortProperty.getSortProperty(); 603 appendPropertyPathAsSQL( query, path ); 604 if ( !sortProperty.getSortOrder() ) { 605 query.append( " DESC" ); 606 } 607 if ( i != sortProps.size() - 1 ) { 608 query.append( ',' ); 609 } 610 } 611 } 612 613 /** 614 * Appends an SQL fragment for the given object. 615 * 616 * @param query 617 * @param filter 618 * @throws DatastoreException 619 */ 620 protected void appendComplexFilterAsSQL( StatementBuffer query, ComplexFilter filter ) 621 throws DatastoreException { 622 appendOperationAsSQL( query, filter.getOperation() ); 623 } 624 625 /** 626 * Appends an SQL fragment for the given object to the given sql statement. 627 * 628 * @param query 629 * @param operation 630 * @throws DatastoreException 631 */ 632 protected void appendOperationAsSQL( StatementBuffer query, Operation operation ) 633 throws DatastoreException { 634 635 switch ( OperationDefines.getTypeById( operation.getOperatorId() ) ) { 636 case OperationDefines.TYPE_SPATIAL: { 637 appendSpatialOperationAsSQL( query, (SpatialOperation) operation ); 638 break; 639 } 640 case OperationDefines.TYPE_COMPARISON: { 641 try { 642 appendComparisonOperationAsSQL( query, (ComparisonOperation) operation ); 643 } catch ( FilterEvaluationException e ) { 644 LOG.logDebug( "Stack trace of eaten exception: ", e ); 645 // TODO unknown what will break if this exception is actually thrown 646 // new DatastoreException( e.getMessage(), e ); 647 } 648 break; 649 } 650 case OperationDefines.TYPE_LOGICAL: { 651 appendLogicalOperationAsSQL( query, (LogicalOperation) operation ); 652 break; 653 } 654 default: { 655 break; 656 } 657 } 658 } 659 660 /** 661 * Appends an SQL fragment for the given object to the given sql statement. 662 * 663 * @param query 664 * @param operation 665 * @throws FilterEvaluationException 666 */ 667 protected void appendComparisonOperationAsSQL( StatementBuffer query, ComparisonOperation operation ) 668 throws FilterEvaluationException { 669 switch ( operation.getOperatorId() ) { 670 case OperationDefines.PROPERTYISEQUALTO: 671 case OperationDefines.PROPERTYISNOTEQUALTO: 672 case OperationDefines.PROPERTYISLESSTHAN: 673 case OperationDefines.PROPERTYISGREATERTHAN: 674 case OperationDefines.PROPERTYISLESSTHANOREQUALTO: 675 case OperationDefines.PROPERTYISGREATERTHANOREQUALTO: { 676 appendPropertyIsCOMPOperationAsSQL( query, (PropertyIsCOMPOperation) operation ); 677 break; 678 } 679 case OperationDefines.PROPERTYISLIKE: { 680 appendPropertyIsLikeOperationAsSQL( query, (PropertyIsLikeOperation) operation ); 681 break; 682 } 683 case OperationDefines.PROPERTYISNULL: { 684 appendPropertyIsNullOperationAsSQL( query, (PropertyIsNullOperation) operation ); 685 break; 686 } 687 case OperationDefines.PROPERTYISBETWEEN: { 688 appendPropertyIsBetweenOperationAsSQL( query, (PropertyIsBetweenOperation) operation ); 689 break; 690 } 691 } 692 } 693 694 /** 695 * Appends an SQL fragment for the given object to the given sql statement. 696 * 697 * @param query 698 * @param operation 699 * @throws FilterEvaluationException 700 */ 701 protected void appendPropertyIsCOMPOperationAsSQL( StatementBuffer query, PropertyIsCOMPOperation operation ) 702 throws FilterEvaluationException { 703 Expression firstExpr = operation.getFirstExpression(); 704 if ( !( firstExpr instanceof PropertyName ) ) { 705 throw new IllegalArgumentException( "First expression in a comparison must " 706 + "always be a 'PropertyName' element." ); 707 } 708 int targetSqlType = getPropertyNameSQLType( (PropertyName) firstExpr ); 709 if ( operation.isMatchCase() ) { 710 appendExpressionAsSQL( query, firstExpr, targetSqlType ); 711 } else { 712 List<Expression> list = new ArrayList<Expression>(); 713 list.add( firstExpr ); 714 Function func = new DBFunction( getFunctionName( "LOWER" ), list ); 715 appendFunctionAsSQL( query, func, targetSqlType ); 716 } 717 switch ( operation.getOperatorId() ) { 718 case OperationDefines.PROPERTYISEQUALTO: { 719 query.append( " = " ); 720 break; 721 } 722 case OperationDefines.PROPERTYISNOTEQUALTO: { 723 query.append( " <> " ); 724 break; 725 } 726 case OperationDefines.PROPERTYISLESSTHAN: { 727 query.append( " < " ); 728 break; 729 } 730 case OperationDefines.PROPERTYISGREATERTHAN: { 731 query.append( " > " ); 732 break; 733 } 734 case OperationDefines.PROPERTYISLESSTHANOREQUALTO: { 735 query.append( " <= " ); 736 break; 737 } 738 case OperationDefines.PROPERTYISGREATERTHANOREQUALTO: { 739 query.append( " >= " ); 740 break; 741 } 742 } 743 if ( operation.isMatchCase() ) { 744 appendExpressionAsSQL( query, operation.getSecondExpression(), targetSqlType ); 745 } else { 746 List<Expression> list = new ArrayList<Expression>(); 747 list.add( operation.getSecondExpression() ); 748 Function func = new DBFunction( getFunctionName( "LOWER" ), list ); 749 appendFunctionAsSQL( query, func, targetSqlType ); 750 } 751 } 752 753 /** 754 * Appends an SQL fragment for the given object to the given sql statement. 755 * 756 * @param query 757 * @param operation 758 * @throws FilterEvaluationException 759 */ 760 protected void appendPropertyIsLikeOperationAsSQL( StatementBuffer query, PropertyIsLikeOperation operation ) 761 throws FilterEvaluationException { 762 763 String literal = operation.getLiteral().getValue(); 764 String escape = "" + operation.getEscapeChar(); 765 String wildCard = "" + operation.getWildCard(); 766 String singleChar = "" + operation.getSingleChar(); 767 768 SpecialCharString specialString = new SpecialCharString( literal, wildCard, singleChar, escape ); 769 String sqlEncoded = specialString.toSQLStyle( !operation.isMatchCase() ); 770 771 int targetSqlType = getPropertyNameSQLType( operation.getPropertyName() ); 772 773 // if isMatchCase == false surround first argument with LOWER (...) and convert characters 774 // in second argument to lower case 775 if ( operation.isMatchCase() ) { 776 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 777 } else { 778 List<Expression> list = new ArrayList<Expression>(); 779 list.add( operation.getPropertyName() ); 780 Function func = new DBFunction( getFunctionName( "LOWER" ), list ); 781 appendFunctionAsSQL( query, func, targetSqlType ); 782 } 783 784 query.append( " LIKE ? ESCAPE ?" ); 785 query.addArgument( sqlEncoded, Types.VARCHAR ); 786 query.addArgument( "\\", Types.VARCHAR ); 787 } 788 789 /** 790 * Appends an SQL fragment for the given object to the given sql statement. 791 * 792 * @param query 793 * @param operation 794 */ 795 protected void appendPropertyIsNullOperationAsSQL( StatementBuffer query, PropertyIsNullOperation operation ) { 796 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 797 query.append( " IS NULL" ); 798 } 799 800 /** 801 * Appends an SQL fragment for the given object to the given sql statement. 802 * 803 * @param query 804 * @param operation 805 * @throws FilterEvaluationException 806 */ 807 protected void appendPropertyIsBetweenOperationAsSQL( StatementBuffer query, PropertyIsBetweenOperation operation ) 808 throws FilterEvaluationException { 809 810 PropertyName propertyName = operation.getPropertyName(); 811 int targetSqlType = getPropertyNameSQLType( propertyName ); 812 appendExpressionAsSQL( query, operation.getLowerBoundary(), targetSqlType ); 813 query.append( " <= " ); 814 appendPropertyNameAsSQL( query, propertyName ); 815 query.append( " AND " ); 816 appendPropertyNameAsSQL( query, propertyName ); 817 query.append( " <= " ); 818 appendExpressionAsSQL( query, operation.getUpperBoundary(), targetSqlType ); 819 } 820 821 /** 822 * Appends an SQL fragment for the given object to the given sql statement. 823 * 824 * @param query 825 * @param expression 826 * @param targetSqlType 827 * sql type code to be used for literals at the bottom of the expression tree 828 * @throws FilterEvaluationException 829 */ 830 protected void appendExpressionAsSQL( StatementBuffer query, Expression expression, int targetSqlType ) 831 throws FilterEvaluationException { 832 switch ( expression.getExpressionId() ) { 833 case ExpressionDefines.PROPERTYNAME: { 834 appendPropertyNameAsSQL( query, (PropertyName) expression ); 835 break; 836 } 837 case ExpressionDefines.LITERAL: { 838 appendLiteralAsSQL( query, (Literal) expression, targetSqlType ); 839 break; 840 } 841 case ExpressionDefines.FUNCTION: { 842 Function function = (Function) expression; 843 appendFunctionAsSQL( query, function, targetSqlType ); 844 break; 845 } 846 case ExpressionDefines.ADD: 847 case ExpressionDefines.SUB: 848 case ExpressionDefines.MUL: 849 case ExpressionDefines.DIV: { 850 appendArithmeticExpressionAsSQL( query, (ArithmeticExpression) expression, targetSqlType ); 851 break; 852 } 853 case ExpressionDefines.EXPRESSION: 854 default: { 855 throw new IllegalArgumentException( "Unexpected expression type: " + expression.getExpressionName() ); 856 } 857 } 858 } 859 860 /** 861 * Appends an SQL fragment for the given object to the given sql statement. 862 * 863 * @param query 864 * @param literal 865 * @param targetSqlType 866 */ 867 protected void appendLiteralAsSQL( StatementBuffer query, Literal literal, int targetSqlType ) { 868 query.append( '?' ); 869 query.addArgument( literal.getValue(), targetSqlType ); 870 } 871 872 /** 873 * Appends an SQL fragment for the given object to the given sql statement. 874 * 875 * @param query 876 * @param propertyName 877 */ 878 protected void appendPropertyNameAsSQL( StatementBuffer query, PropertyName propertyName ) { 879 880 PropertyPath propertyPath = propertyName.getValue(); 881 appendPropertyPathAsSQL( query, propertyPath ); 882 } 883 884 /** 885 * Appends an SQL fragment for the given object to the given sql statement. 886 * 887 * @param query 888 * @param propertyPath 889 */ 890 protected void appendPropertyPathAsSQL( StatementBuffer query, PropertyPath propertyPath ) { 891 892 LOG.logDebug( "Looking up '" + propertyPath + "' in the query table tree." ); 893 MappingField mappingField = null; 894 PropertyNode propertyNode = this.queryTableTree.getPropertyNode( propertyPath ); 895 assert ( propertyNode != null ); 896 if ( propertyNode instanceof SimplePropertyNode ) { 897 SimpleContent content = ( (MappedSimplePropertyType) ( propertyNode.getProperty() ) ).getContent(); 898 if ( !( content instanceof MappingField ) ) { 899 if ( content instanceof ConstantContent ) { 900 query.append( "'" + ( (ConstantContent) content ).getValue() + "'" ); 901 return; 902 } else if ( content instanceof SQLFunctionCall ) { 903 SQLFunctionCall call = (SQLFunctionCall) content; 904 String tableAlias = null; 905 String[] tableAliases = propertyNode.getTableAliases(); 906 if ( tableAliases == null || tableAliases.length == 0 ) { 907 tableAlias = propertyNode.getParent().getTableAlias(); 908 } else { 909 tableAlias = tableAliases[tableAliases.length - 1]; 910 } 911 this.vcProvider.appendSQLFunctionCall( query, tableAlias, call ); 912 return; 913 } 914 String msg = "Virtual properties are currently ignored in WhereBuilder#appendPropertyPathAsSQL(StatementBuffer,PropertyPath)."; 915 LOG.logWarning( msg ); 916 assert false; 917 } 918 mappingField = (MappingField) content; 919 } else if ( propertyNode instanceof GeometryPropertyNode ) { 920 mappingField = ( (MappedGeometryPropertyType) propertyNode.getProperty() ).getMappingField(); 921 } else { 922 String msg = "Internal error in WhereBuilder: unhandled PropertyNode type: '" 923 + propertyNode.getClass().getName() + "'."; 924 LOG.logError( msg ); 925 throw new RuntimeException( msg ); 926 } 927 String tableAlias = null; 928 String[] tableAliases = propertyNode.getTableAliases(); 929 if ( tableAliases == null || tableAliases.length == 0 ) { 930 tableAlias = propertyNode.getParent().getTableAlias(); 931 } else { 932 tableAlias = tableAliases[tableAliases.length - 1]; 933 } 934 if ( tableAlias != "" ) { 935 query.append( tableAlias ); 936 query.append( '.' ); 937 } else { 938 query.append( mappingField.getTable() ); 939 query.append( '.' ); 940 } 941 query.append( mappingField.getField() ); 942 } 943 944 /** 945 * Appends an SQL fragment for the given object to the given sql statement. 946 * 947 * @param query 948 * @param expression 949 * @param targetSqlType 950 * @throws FilterEvaluationException 951 */ 952 protected void appendArithmeticExpressionAsSQL( StatementBuffer query, ArithmeticExpression expression, 953 int targetSqlType ) 954 throws FilterEvaluationException { 955 956 // quirk to enable SQL-level type conversions of numbers. We really should use numbers as literals in the SQL 957 // string. 958 switch ( targetSqlType ) { 959 case INTEGER: 960 case SMALLINT: { 961 targetSqlType = DOUBLE; 962 } 963 } 964 965 query.append( '(' ); 966 appendExpressionAsSQL( query, expression.getFirstExpression(), targetSqlType ); 967 switch ( expression.getExpressionId() ) { 968 case ADD: { 969 query.append( '+' ); 970 break; 971 } 972 case SUB: { 973 query.append( '-' ); 974 break; 975 } 976 case MUL: { 977 query.append( '*' ); 978 break; 979 } 980 case DIV: { 981 query.append( '/' ); 982 break; 983 } 984 } 985 appendExpressionAsSQL( query, expression.getSecondExpression(), targetSqlType ); 986 query.append( ')' ); 987 } 988 989 /** 990 * Appends an SQL fragment for the given object to the given sql statement. 991 * 992 * @param query 993 * @param function 994 * @param targetSqlType 995 * @throws FilterEvaluationException 996 */ 997 protected void appendFunctionAsSQL( StatementBuffer query, Function function, int targetSqlType ) 998 throws FilterEvaluationException { 999 if ( function instanceof DBFunction ) { 1000 query.append( function.getName() ); 1001 query.append( " (" ); 1002 List<?> list = function.getArguments(); 1003 for ( int i = 0; i < list.size(); i++ ) { 1004 Expression expression = (Expression) list.get( i ); 1005 appendExpressionAsSQL( query, expression, targetSqlType ); 1006 if ( i != list.size() - 1 ) 1007 query.append( ", " ); 1008 } 1009 query.append( ")" ); 1010 } else { 1011 Object o = function.evaluate( null ); 1012 if ( o != null ) { 1013 Literal literal = new Literal( o.toString() ); 1014 appendExpressionAsSQL( query, literal, targetSqlType ); 1015 } 1016 } 1017 } 1018 1019 /** 1020 * Appends an SQL fragment for the given object to the given sql statement. 1021 * 1022 * @param query 1023 * @param operation 1024 * @throws DatastoreException 1025 */ 1026 protected void appendLogicalOperationAsSQL( StatementBuffer query, LogicalOperation operation ) 1027 throws DatastoreException { 1028 List<?> argumentList = operation.getArguments(); 1029 switch ( operation.getOperatorId() ) { 1030 case OperationDefines.AND: { 1031 for ( int i = 0; i < argumentList.size(); i++ ) { 1032 Operation argument = (Operation) argumentList.get( i ); 1033 query.append( '(' ); 1034 appendOperationAsSQL( query, argument ); 1035 query.append( ')' ); 1036 if ( i != argumentList.size() - 1 ) 1037 query.append( " AND " ); 1038 } 1039 break; 1040 } 1041 case OperationDefines.OR: { 1042 for ( int i = 0; i < argumentList.size(); i++ ) { 1043 Operation argument = (Operation) argumentList.get( i ); 1044 query.append( '(' ); 1045 appendOperationAsSQL( query, argument ); 1046 query.append( ')' ); 1047 if ( i != argumentList.size() - 1 ) 1048 query.append( " OR " ); 1049 } 1050 break; 1051 } 1052 case OperationDefines.NOT: { 1053 Operation argument = (Operation) argumentList.get( 0 ); 1054 query.append( "NOT (" ); 1055 appendOperationAsSQL( query, argument ); 1056 query.append( ')' ); 1057 break; 1058 } 1059 } 1060 } 1061 1062 /** 1063 * Appends an SQL fragment for the given object to the given sql statement. 1064 * 1065 * TODO Handle compound primary keys correctly. 1066 * 1067 * @param query 1068 * @param filter 1069 * @throws DatastoreException 1070 */ 1071 protected void appendFeatureFilterAsSQL( StatementBuffer query, FeatureFilter filter ) 1072 throws DatastoreException { 1073 1074 // List list = filter.getFeatureIds(); 1075 // Iterator it = list.iterator(); 1076 // while (it.hasNext()) { 1077 // FeatureId fid = (FeatureId) it.next(); 1078 // MappingField mapping = null; 1079 // DatastoreMapping mapping = featureType.getFidDefinition().getFidFields()[0]; 1080 // query.append( ' ' ); 1081 // query.append( this.joinTableTree.getAlias() ); 1082 // query.append( "." ); 1083 // query.append( mapping.getField() ); 1084 // query.append( "=?" ); 1085 // query.addArgument( fid.getValue() ); 1086 // if ( it.hasNext() ) { 1087 // query.append( " OR" ); 1088 // } 1089 // } 1090 1091 if ( this.rootFts.length > 1 ) { 1092 String msg = Messages.getMessage( "DATASTORE_FEATURE_QUERY_MORE_THAN_FEATURE_TYPE" ); 1093 throw new DatastoreException( msg ); 1094 } 1095 1096 MappedFeatureType rootFt = this.rootFts[0]; 1097 MappingField[] idFields = rootFt.getGMLId().getIdFields(); 1098 MappingField idField = null; 1099 String tbl = getRootTableAlias( 0 ); 1100 ArrayList<?> list = filter.getFeatureIds(); 1101 try { 1102 for ( int i = 0; i < idFields.length; i++ ) { 1103 idField = idFields[i]; 1104 if ( i == 0 ) { 1105 query.append( ' ' ); 1106 } else { 1107 query.append( " AND " ); 1108 } 1109 if ( null != tbl && 0 < tbl.length() ) { 1110 query.append( tbl ); 1111 query.append( "." ); 1112 } 1113 query.append( idField.getField() ); 1114 for ( int j = 0; j < list.size(); j++ ) { 1115 if ( j == 0 ) { 1116 query.append( " IN (?" ); 1117 } else { 1118 query.append( ",?" ); 1119 } 1120 if ( j == list.size() - 1 ) { 1121 query.append( ")" ); 1122 } 1123 String fid = ( (org.deegree.model.filterencoding.FeatureId) list.get( j ) ).getValue(); 1124 Object fidValue = org.deegree.io.datastore.FeatureId.removeFIDPrefix( fid, rootFt.getGMLId() ); 1125 if ( idFields.length > 1 ) { // Equal to: if ( fidValue instanceof Object[] ) { 1126 fidValue = ( (Object[]) fidValue )[i]; 1127 } 1128 query.addArgument( fidValue, idField.getType() ); 1129 } 1130 } 1131 } catch ( Exception e ) { 1132 LOG.logError( "Error converting feature id", e ); 1133 } 1134 } 1135 1136 /** 1137 * Appends an SQL fragment for the given object to the given sql statement. As this depends on the handling of 1138 * geometry data by the concrete database in use, this method must be overwritten by any datastore implementation 1139 * that has spatial capabilities. 1140 * 1141 * @param query 1142 * @param operation 1143 * @throws DatastoreException 1144 */ 1145 protected void appendSpatialOperationAsSQL( @SuppressWarnings("unused") StatementBuffer query, 1146 @SuppressWarnings("unused") SpatialOperation operation ) 1147 throws DatastoreException { 1148 String msg = "Spatial operations are not supported by the WhereBuilder implementation in use: '" + getClass() 1149 + "'"; 1150 throw new DatastoreException( msg ); 1151 } 1152 1153 /** 1154 * Prepares the function map for functions with implementation specific names, e.g. upper case conversion in ORACLE 1155 * = UPPER(string); POSTGRES = UPPER(string), and MS Access = UCase(string). Default SQL-function name map function 1156 * 'UPPER' is 'UPPER'. If this function shall be used with user databases e.g. SQLServer a specialized WhereBuilder 1157 * must override this method. 1158 */ 1159 protected void fillFunctionNameMap() { 1160 functionMap.clear(); 1161 functionMap.put( "LOWER", "LOWER" ); 1162 } 1163 1164 /** 1165 * Get the function with the specified name. 1166 * 1167 * @param name 1168 * the function name 1169 * @return the mapped function name 1170 */ 1171 protected String getFunctionName( String name ) { 1172 String f = functionMap.get( name ); 1173 if ( null == f ) 1174 f = name; 1175 return f; 1176 } 1177 }