001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/io/datastore/sde/SDEWhereBuilder.java $ 002 /*---------------- FILE HEADER ------------------------------------------ 003 004 This file is part of deegree. 005 Copyright (C) 2006 by: M.O.S.S. Computer Grafik Systeme GmbH 006 Hohenbrunner Weg 13 007 D-82024 Taufkirchen 008 http://www.moss.de/ 009 010 This library is free software; you can redistribute it and/or 011 modify it under the terms of the GNU Lesser General Public 012 License as published by the Free Software Foundation; either 013 version 2.1 of the License, or (at your option) any later version. 014 015 This library is distributed in the hope that it will be useful, 016 but WITHOUT ANY WARRANTY; without even the implied warranty of 017 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 018 Lesser General Public License for more details. 019 020 You should have received a copy of the GNU Lesser General Public 021 License along with this library; if not, write to the Free Software 022 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 023 024 ---------------------------------------------------------------------------*/ 025 package org.deegree.io.datastore.sde; 026 027 import java.sql.Types; 028 import java.util.ArrayList; 029 import java.util.Iterator; 030 import java.util.List; 031 032 import org.deegree.framework.log.ILogger; 033 import org.deegree.framework.log.LoggerFactory; 034 import org.deegree.io.datastore.DatastoreException; 035 import org.deegree.io.datastore.PropertyPathResolvingException; 036 import org.deegree.io.datastore.schema.MappedFeatureType; 037 import org.deegree.io.datastore.schema.MappedGeometryPropertyType; 038 import org.deegree.io.datastore.schema.MappedPropertyType; 039 import org.deegree.io.datastore.schema.MappedSimplePropertyType; 040 import org.deegree.io.datastore.schema.content.MappingField; 041 import org.deegree.io.datastore.schema.content.MappingGeometryField; 042 import org.deegree.io.datastore.schema.content.SimpleContent; 043 import org.deegree.io.datastore.sql.TableAliasGenerator; 044 import org.deegree.io.datastore.sql.wherebuilder.GeometryPropertyNode; 045 import org.deegree.io.datastore.sql.wherebuilder.PropertyNode; 046 import org.deegree.io.datastore.sql.wherebuilder.QueryTableTree; 047 import org.deegree.io.datastore.sql.wherebuilder.SimplePropertyNode; 048 import org.deegree.io.sdeapi.SDEAdapter; 049 import org.deegree.model.filterencoding.ArithmeticExpression; 050 import org.deegree.model.filterencoding.ComparisonOperation; 051 import org.deegree.model.filterencoding.ComplexFilter; 052 import org.deegree.model.filterencoding.Expression; 053 import org.deegree.model.filterencoding.ExpressionDefines; 054 import org.deegree.model.filterencoding.FeatureFilter; 055 import org.deegree.model.filterencoding.Filter; 056 import org.deegree.model.filterencoding.FilterTools; 057 import org.deegree.model.filterencoding.Function; 058 import org.deegree.model.filterencoding.Literal; 059 import org.deegree.model.filterencoding.LogicalOperation; 060 import org.deegree.model.filterencoding.Operation; 061 import org.deegree.model.filterencoding.OperationDefines; 062 import org.deegree.model.filterencoding.PropertyIsBetweenOperation; 063 import org.deegree.model.filterencoding.PropertyIsCOMPOperation; 064 import org.deegree.model.filterencoding.PropertyIsLikeOperation; 065 import org.deegree.model.filterencoding.PropertyIsNullOperation; 066 import org.deegree.model.filterencoding.PropertyName; 067 import org.deegree.model.filterencoding.SpatialOperation; 068 import org.deegree.ogcbase.PropertyPath; 069 070 import com.esri.sde.sdk.client.SeCoordinateReference; 071 import com.esri.sde.sdk.client.SeFilter; 072 import com.esri.sde.sdk.client.SeLayer; 073 import com.esri.sde.sdk.client.SeShape; 074 import com.esri.sde.sdk.client.SeShapeFilter; 075 076 /** 077 * <code>WhereBuilder</code> implementation for ArcSDE. 078 * 079 * @author <a href="mailto:cpollmann@moss.de">Christoph Pollmann</a> 080 * @author last edited by: $Author: apoth $ 081 * 082 * @version $Revision: 7844 $, $Date: 2007-07-25 09:45:07 +0200 (Mi, 25 Jul 2007) $ 083 */ 084 public class SDEWhereBuilder { 085 086 protected static final ILogger LOG = LoggerFactory.getLogger( SDEWhereBuilder.class ); 087 088 protected MappedFeatureType rootFeatureType; 089 090 protected Filter filter; 091 092 protected QueryTableTree queryTableTree; 093 094 protected List<PropertyPath> filterPropertyPaths = new ArrayList<PropertyPath>(); 095 096 /** 097 * Creates a new instance of <code>SDEWhereBuilder</code> for the given parameters. 098 * 099 * @param rootFts 100 * selected feature types, more than one type means that the types are joined 101 * @param aliases 102 * aliases for the feature types, may be null (must have same length as rootFts 103 * otherwise) 104 * @param filter 105 * @param aliasGenerator 106 * @throws DatastoreException 107 */ 108 public SDEWhereBuilder( MappedFeatureType[] rootFts, String[] aliases, Filter filter, 109 TableAliasGenerator aliasGenerator ) throws DatastoreException { 110 111 this.rootFeatureType = rootFts [0]; 112 this.queryTableTree = new QueryTableTree( rootFts, aliases, aliasGenerator ); 113 this.filter = filter; 114 if ( filter != null ) { 115 if ( !( filter instanceof ComplexFilter || filter instanceof FeatureFilter ) ) { 116 throw new DatastoreException( "Invalid filter type: '" + filter.getClass() 117 + "'. Filter must be a ComplexFilter or a FeatureFilter." ); 118 } 119 buildFilterPropertyNameMap(); 120 for ( PropertyPath property : this.filterPropertyPaths ) { 121 this.queryTableTree.addFilterProperty( property ); 122 } 123 } 124 } 125 126 /** 127 * @return 128 */ 129 public String getRootTableAlias() { 130 return this.queryTableTree.getRootAlias(); 131 } 132 133 /** 134 * @return filter 135 */ 136 public Filter getFilter() { 137 return this.filter; 138 } 139 140 /** 141 * Returns the internal (database specific) SRS code used in the geometry field of the given 142 * <code>SpatialOperation</code>. 143 * 144 * @param operation 145 * <code>SpatialOperation</code> for which the internal SRS is needed 146 * @return the internal (database specific) SRS code. 147 */ 148 protected int getInternalSRS( SpatialOperation operation ) { 149 PropertyPath propertyPath = operation.getPropertyName().getValue(); 150 PropertyNode propertyNode = this.queryTableTree.getPropertyNode( propertyPath ); 151 if ( propertyNode == null ) { 152 String msg = "Internal error in WhereBuilder: no PropertyNode for path '" + propertyPath 153 + "' in QueryTableTree."; 154 LOG.logError( msg ); 155 throw new RuntimeException( msg ); 156 } else if ( !( propertyNode instanceof GeometryPropertyNode ) ) { 157 String msg = "Internal error in WhereBuilder: unexpected PropertyNode type: '" 158 + propertyNode.getClass().getName() + "'. Must be a GeometryPropertyNode."; 159 LOG.logError( msg ); 160 throw new RuntimeException( msg ); 161 } 162 MappedGeometryPropertyType gpc = (MappedGeometryPropertyType) propertyNode.getProperty(); 163 MappingGeometryField field = gpc.getMappingField(); 164 return field.getSRS(); 165 } 166 167 /** 168 * @param propertyName 169 * @return 170 */ 171 protected int getPropertyNameSQLType( PropertyName propertyName ) { 172 PropertyPath propertyPath = propertyName.getValue(); 173 PropertyNode propertyNode = this.queryTableTree.getPropertyNode( propertyPath ); 174 if ( propertyNode == null ) { 175 String msg = "Internal error in WhereBuilder: no PropertyNode for path '" + propertyPath 176 + "' in QueryTableTree."; 177 LOG.logError( msg ); 178 throw new RuntimeException( msg ); 179 } 180 MappedPropertyType propertyType = propertyNode.getProperty(); 181 if ( !( propertyType instanceof MappedSimplePropertyType ) ) { 182 String msg = "Error in WhereBuilder: cannot compare against properties of type '" + propertyType.getClass() 183 + "'."; 184 LOG.logError( msg ); 185 throw new RuntimeException( msg ); 186 } 187 188 SimpleContent content = ( (MappedSimplePropertyType) propertyType ).getContent(); 189 if ( !( content instanceof MappingField ) ) { 190 String msg = "Virtual properties are currently ignored in SDEWhereBuilder#getPropertyNameSQLType(PropertyName)."; 191 LOG.logError( msg ); 192 return Types.VARCHAR; 193 } 194 195 int targetSqlType = ( (MappingField) content ).getType(); 196 return targetSqlType; 197 } 198 199 /** 200 * @throws PropertyPathResolvingException 201 */ 202 protected void buildFilterPropertyNameMap() 203 throws PropertyPathResolvingException { 204 if ( this.filter instanceof ComplexFilter ) { 205 buildPropertyNameMapFromOperation( ( (ComplexFilter) this.filter ).getOperation() ); 206 } else if ( this.filter instanceof FeatureFilter ) { 207 // FeatureFilter doesn't have real properties, so we don't have to add them here 208 // maybe for join tables and table aliases we need some auxiliary constructions??? 209 // throw new PropertyPathResolvingException( "FeatureFilter not implemented yet." ); 210 } 211 } 212 213 private void buildPropertyNameMapFromOperation( Operation operation ) 214 throws PropertyPathResolvingException { 215 switch ( OperationDefines.getTypeById( operation.getOperatorId() ) ) { 216 case OperationDefines.TYPE_SPATIAL: { 217 registerPropertyName( ( (SpatialOperation) operation ).getPropertyName() ); 218 break; 219 } 220 case OperationDefines.TYPE_COMPARISON: { 221 buildPropertyNameMap( (ComparisonOperation) operation ); 222 break; 223 } 224 case OperationDefines.TYPE_LOGICAL: { 225 buildPropertyNameMap( (LogicalOperation) operation ); 226 break; 227 } 228 default: { 229 break; 230 } 231 } 232 } 233 234 private void buildPropertyNameMap( ComparisonOperation operation ) 235 throws PropertyPathResolvingException { 236 switch ( operation.getOperatorId() ) { 237 case OperationDefines.PROPERTYISEQUALTO: 238 case OperationDefines.PROPERTYISLESSTHAN: 239 case OperationDefines.PROPERTYISGREATERTHAN: 240 case OperationDefines.PROPERTYISLESSTHANOREQUALTO: 241 case OperationDefines.PROPERTYISGREATERTHANOREQUALTO: { 242 buildPropertyNameMap( ( (PropertyIsCOMPOperation) operation ).getFirstExpression() ); 243 buildPropertyNameMap( ( (PropertyIsCOMPOperation) operation ).getSecondExpression() ); 244 break; 245 } 246 case OperationDefines.PROPERTYISLIKE: { 247 registerPropertyName( ( (PropertyIsLikeOperation) operation ).getPropertyName() ); 248 break; 249 } 250 case OperationDefines.PROPERTYISNULL: { 251 buildPropertyNameMap( ( (PropertyIsNullOperation) operation ).getPropertyName() ); 252 break; 253 } 254 case OperationDefines.PROPERTYISBETWEEN: { 255 buildPropertyNameMap( ( (PropertyIsBetweenOperation) operation ).getLowerBoundary() ); 256 buildPropertyNameMap( ( (PropertyIsBetweenOperation) operation ).getUpperBoundary() ); 257 registerPropertyName( ( (PropertyIsBetweenOperation) operation ).getPropertyName() ); 258 break; 259 } 260 default: { 261 break; 262 } 263 } 264 } 265 266 private void buildPropertyNameMap( LogicalOperation operation ) 267 throws PropertyPathResolvingException { 268 List operationList = operation.getArguments(); 269 Iterator it = operationList.iterator(); 270 while ( it.hasNext() ) { 271 buildPropertyNameMapFromOperation( (Operation) it.next() ); 272 } 273 } 274 275 private void buildPropertyNameMap( Expression expression ) 276 throws PropertyPathResolvingException { 277 switch ( expression.getExpressionId() ) { 278 case ExpressionDefines.PROPERTYNAME: { 279 registerPropertyName( (PropertyName) expression ); 280 break; 281 } 282 case ExpressionDefines.ADD: 283 case ExpressionDefines.SUB: 284 case ExpressionDefines.MUL: 285 case ExpressionDefines.DIV: { 286 buildPropertyNameMap( ( (ArithmeticExpression) expression ).getFirstExpression() ); 287 buildPropertyNameMap( ( (ArithmeticExpression) expression ).getSecondExpression() ); 288 break; 289 } 290 case ExpressionDefines.FUNCTION: { 291 // TODO: What about PropertyNames used here? 292 break; 293 } 294 case ExpressionDefines.EXPRESSION: 295 case ExpressionDefines.LITERAL: { 296 break; 297 } 298 } 299 } 300 301 private void registerPropertyName( PropertyName propertyName ) { 302 this.filterPropertyPaths.add( propertyName.getValue() ); 303 } 304 305 /* 306 * appendJoinTableList => String[] der Tabellennamen (mehrfach vorkommende Namen nicht erlaubt) 307 * appendOuterJoins => mit SDE bei versionierten Tabellen realisierbar??? 308 */ 309 310 /** 311 * Appends the SQL condition from the <code>Filter</code> to the given sql statement. 312 * 313 * @param whereCondition 314 */ 315 public final void appendWhereCondition( StringBuffer whereCondition ) { 316 if ( filter instanceof ComplexFilter ) { 317 appendComplexFilterAsSQL( whereCondition, (ComplexFilter) filter ); 318 } else if ( filter instanceof FeatureFilter ) { 319 FeatureFilter featureFilter = (FeatureFilter) filter; 320 if ( featureFilter.getFeatureIds().size() > 0 ) { 321 appendFeatureFilterAsSQL( whereCondition, featureFilter ); 322 } 323 } else { 324 // assert false : "Unexpected filter type."; 325 } 326 } 327 328 /** 329 * Appends an SQL fragment for the given object. 330 * 331 * @param query 332 * @param filter 333 */ 334 protected void appendComplexFilterAsSQL( StringBuffer query, ComplexFilter filter ) { 335 appendOperationAsSQL( query, filter.getOperation() ); 336 } 337 338 /** 339 * Appends an SQL fragment for the given object to the given sql statement. 340 * 341 * @param query 342 * @param operation 343 */ 344 protected void appendOperationAsSQL( StringBuffer query, Operation operation ) { 345 346 switch ( OperationDefines.getTypeById( operation.getOperatorId() ) ) { 347 case OperationDefines.TYPE_SPATIAL: { 348 // handled seperately with buildSpatialFilter() 349 break; 350 } 351 case OperationDefines.TYPE_COMPARISON: { 352 appendComparisonOperationAsSQL( query, (ComparisonOperation) operation ); 353 break; 354 } 355 case OperationDefines.TYPE_LOGICAL: { 356 appendLogicalOperationAsSQL( query, (LogicalOperation) operation ); 357 break; 358 } 359 default: { 360 break; 361 } 362 } 363 } 364 365 /** 366 * Appends an SQL fragment for the given object to the given sql statement. 367 * 368 * @param query 369 * @param operation 370 */ 371 protected void appendComparisonOperationAsSQL( StringBuffer query, ComparisonOperation operation ) { 372 switch ( operation.getOperatorId() ) { 373 case OperationDefines.PROPERTYISEQUALTO: 374 case OperationDefines.PROPERTYISLESSTHAN: 375 case OperationDefines.PROPERTYISGREATERTHAN: 376 case OperationDefines.PROPERTYISLESSTHANOREQUALTO: 377 case OperationDefines.PROPERTYISGREATERTHANOREQUALTO: { 378 appendPropertyIsCOMPOperationAsSQL( query, (PropertyIsCOMPOperation) operation ); 379 break; 380 } 381 case OperationDefines.PROPERTYISLIKE: { 382 appendPropertyIsLikeOperationAsSQL( query, (PropertyIsLikeOperation) operation ); 383 break; 384 } 385 case OperationDefines.PROPERTYISNULL: { 386 appendPropertyIsNullOperationAsSQL( query, (PropertyIsNullOperation) operation ); 387 break; 388 } 389 case OperationDefines.PROPERTYISBETWEEN: { 390 appendPropertyIsBetweenOperationAsSQL( query, (PropertyIsBetweenOperation) operation ); 391 break; 392 } 393 } 394 } 395 396 /** 397 * Appends an SQL fragment for the given object to the given sql statement. 398 * 399 * @param query 400 * @param operation 401 */ 402 protected void appendPropertyIsCOMPOperationAsSQL( StringBuffer query, PropertyIsCOMPOperation operation ) { 403 Expression firstExpr = operation.getFirstExpression(); 404 if ( !( firstExpr instanceof PropertyName ) ) { 405 throw new IllegalArgumentException( "First expression in a comparison must " 406 + "always be a 'PropertyName' element." ); 407 } 408 int targetSqlType = getPropertyNameSQLType( (PropertyName) firstExpr ); 409 if ( operation.isMatchCase() ) { 410 appendExpressionAsSQL( query, firstExpr, targetSqlType ); 411 } else { 412 List<Expression> list = new ArrayList<Expression>(); 413 list.add( firstExpr ); 414 Function func = new Function( "LOWER", list ); 415 appendFunctionAsSQL( query, func, targetSqlType ); 416 } 417 switch ( operation.getOperatorId() ) { 418 case OperationDefines.PROPERTYISEQUALTO: { 419 query.append( " = " ); 420 break; 421 } 422 case OperationDefines.PROPERTYISLESSTHAN: { 423 query.append( " < " ); 424 break; 425 } 426 case OperationDefines.PROPERTYISGREATERTHAN: { 427 query.append( " > " ); 428 break; 429 } 430 case OperationDefines.PROPERTYISLESSTHANOREQUALTO: { 431 query.append( " <= " ); 432 break; 433 } 434 case OperationDefines.PROPERTYISGREATERTHANOREQUALTO: { 435 query.append( " >= " ); 436 break; 437 } 438 } 439 if ( operation.isMatchCase() ) { 440 appendExpressionAsSQL( query, operation.getSecondExpression(), targetSqlType ); 441 } else { 442 List<Expression> list = new ArrayList<Expression>(); 443 list.add( operation.getSecondExpression() ); 444 Function func = new Function( "LOWER", list ); 445 appendFunctionAsSQL( query, func, targetSqlType ); 446 } 447 } 448 449 /** 450 * Appends an SQL fragment for the given object to the given sql statement. Replacing and escape 451 * handling is based on a finite automaton with 2 states: 452 * <p> 453 * (escapeMode) 454 * <ul> 455 * <li>' is appended as \', \ is appended as \\</li> 456 * <li>every character (including the escapeChar) is simply appended</li> 457 * <li>- unset escapeMode</li> 458 * (escapeMode is false) 459 * </ul> 460 * <ul> 461 * <li>' is appended as \', \ is appended as \\</li> 462 * <li>escapeChar means: skip char, set escapeMode</li> 463 * <li>wildCard means: append %</li> 464 * <li>singleChar means: append ?</li> 465 * </ul> 466 * </p> 467 * 468 * NOTE: Currently, the method uses a quirk and appends the generated argument inline, i.e. not 469 * using query.addArgument(). This is because of a problem that occurred for example in 470 * Postgresql; the execution of the inline version is *much* faster (at least with version 8.0). 471 * 472 * @param query 473 * @param operation 474 */ 475 protected void appendPropertyIsLikeOperationAsSQL( StringBuffer query, PropertyIsLikeOperation operation ) { 476 477 String literal = operation.getLiteral().getValue(); 478 char escapeChar = operation.getEscapeChar(); 479 char wildCard = operation.getWildCard(); 480 char singleChar = operation.getSingleChar(); 481 boolean escapeMode = false; 482 int length = literal.length(); 483 int targetSqlType = getPropertyNameSQLType( operation.getPropertyName() ); 484 if ( operation.isMatchCase() ) { 485 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 486 } else { 487 List<PropertyName> list = new ArrayList<PropertyName>(); 488 list.add( operation.getPropertyName() ); 489 Function func = new Function( "LOWER", list ); 490 appendFunctionAsSQL( query, func, targetSqlType ); 491 } 492 query.append( " LIKE '" ); 493 StringBuffer parameter = new StringBuffer(); 494 for ( int i = 0; i < length; i++ ) { 495 char c = literal.charAt( i ); 496 if ( escapeMode ) { 497 // ' must (even in escapeMode) be converted to \' 498 if ( c == '\'' ) 499 parameter.append( "\'" ); 500 // \ must (even in escapeMode) be converted to \\ 501 else if ( c == '\\' ) 502 parameter.append( "\\\\" ); 503 else 504 parameter.append( c ); 505 escapeMode = false; 506 } else { 507 // escapeChar means: switch to escapeMode 508 if ( c == escapeChar ) 509 escapeMode = true; 510 // wildCard must be converted to % 511 else if ( c == wildCard ) 512 parameter.append( '%' ); 513 // singleChar must be converted to ? 514 else if ( c == singleChar ) 515 parameter.append( '?' ); 516 // ' must be converted to \' 517 else if ( c == '\'' ) 518 parameter.append( "$'$" ); 519 // % must be converted to \' 520 else if ( c == '%' ) 521 parameter.append( "$%$" ); 522 // ? must be converted to \' 523 // else if (c == '?') sb.append("$?$"); 524 // \ must (even in escapeMode) be converted to \\ 525 else if ( c == '\\' ) 526 parameter.append( "\\\\" ); 527 else 528 parameter.append( c ); 529 } 530 } 531 if ( operation.isMatchCase() ) { 532 query.append( parameter ); 533 } else { 534 query.append( parameter.toString().toLowerCase() ); 535 } 536 query.append( '\'' ); 537 // query.addArgument( parameter.toString() ); 538 } 539 540 /** 541 * Appends an SQL fragment for the given object to the given sql statement. 542 * 543 * @param query 544 * @param operation 545 */ 546 protected void appendPropertyIsNullOperationAsSQL( StringBuffer query, PropertyIsNullOperation operation ) { 547 appendPropertyNameAsSQL( query, operation.getPropertyName() ); 548 query.append( " IS NULL" ); 549 } 550 551 /** 552 * Appends an SQL fragment for the given object to the given sql statement. 553 * 554 * @param query 555 * @param operation 556 */ 557 protected void appendPropertyIsBetweenOperationAsSQL( StringBuffer query, PropertyIsBetweenOperation operation ) { 558 559 PropertyName propertyName = operation.getPropertyName(); 560 int targetSqlType = getPropertyNameSQLType( propertyName ); 561 appendExpressionAsSQL( query, operation.getLowerBoundary(), targetSqlType ); 562 query.append( " <= " ); 563 appendPropertyNameAsSQL( query, propertyName ); 564 query.append( " AND " ); 565 appendPropertyNameAsSQL( query, propertyName ); 566 query.append( " <= " ); 567 appendExpressionAsSQL( query, operation.getUpperBoundary(), targetSqlType ); 568 } 569 570 /** 571 * Appends an SQL fragment for the given object to the given sql statement. 572 * 573 * @param query 574 * @param expression 575 * @param targetSqlType 576 * sql type code to be used for literals at the bottom of the expression tree 577 */ 578 protected void appendExpressionAsSQL( StringBuffer query, Expression expression, int targetSqlType ) { 579 switch ( expression.getExpressionId() ) { 580 case ExpressionDefines.PROPERTYNAME: { 581 appendPropertyNameAsSQL( query, (PropertyName) expression ); 582 break; 583 } 584 case ExpressionDefines.LITERAL: { 585 appendLiteralAsSQL( query, (Literal) expression, targetSqlType ); 586 break; 587 } 588 case ExpressionDefines.FUNCTION: { 589 Function function = (Function) expression; 590 appendFunctionAsSQL( query, function, targetSqlType ); 591 break; 592 } 593 case ExpressionDefines.ADD: 594 case ExpressionDefines.SUB: 595 case ExpressionDefines.MUL: 596 case ExpressionDefines.DIV: { 597 appendArithmeticExpressionAsSQL( query, (ArithmeticExpression) expression, targetSqlType ); 598 break; 599 } 600 case ExpressionDefines.EXPRESSION: 601 default: { 602 throw new IllegalArgumentException( "Unexpected expression type: " + expression.getExpressionName() ); 603 } 604 } 605 } 606 607 /** 608 * Appends an SQL fragment for the given object to the given sql statement. 609 * 610 * @param query 611 * @param literal 612 * @param targetSqlType 613 */ 614 protected void appendLiteralAsSQL( StringBuffer query, Literal literal, int targetSqlType ) { 615 switch ( targetSqlType ) { 616 case java.sql.Types.DECIMAL: 617 case java.sql.Types.DOUBLE: 618 case java.sql.Types.FLOAT: 619 case java.sql.Types.INTEGER: 620 case java.sql.Types.NUMERIC: 621 case java.sql.Types.REAL: 622 case java.sql.Types.SMALLINT: 623 case java.sql.Types.TINYINT: 624 query.append( literal.getValue() ); 625 break; 626 default: 627 query.append( "'" + literal.getValue() + "'" ); 628 break; 629 } 630 } 631 632 /** 633 * Appends an SQL fragment for the given object to the given sql statement. 634 * 635 * @param propertyName 636 * @return 637 */ 638 protected MappingField getPropertyNameMapping( PropertyName propertyName ) { 639 640 PropertyPath propertyPath = propertyName.getValue(); 641 LOG.logDebug( "Looking up '" + propertyPath + "' in the query table tree." ); 642 MappingField mappingField = null; 643 PropertyNode propertyNode = this.queryTableTree.getPropertyNode( propertyPath ); 644 if ( propertyNode == null ) { 645 String msg = "Internal error in WhereBuilder: no PropertyNode for path '" + propertyPath 646 + "' in QueryTableTree."; 647 LOG.logError( msg ); 648 throw new RuntimeException( msg ); 649 } else if ( propertyNode instanceof SimplePropertyNode ) { 650 SimpleContent content = ( (MappedSimplePropertyType) ( propertyNode.getProperty() ) ).getContent(); 651 if ( !( content instanceof MappingField ) ) { 652 String msg = "Virtual properties are currently ignored in WhereBuilder#appendPropertyPathAsSQL(StatementBuffer,PropertyPath)."; 653 LOG.logError( msg ); 654 throw new RuntimeException( msg ); 655 } 656 mappingField = (MappingField) content; 657 } else if ( propertyNode instanceof GeometryPropertyNode ) { 658 mappingField = ( (MappedGeometryPropertyType) propertyNode.getProperty() ).getMappingField(); 659 } else { 660 String msg = "Internal error in WhereBuilder: unhandled PropertyNode type: '" 661 + propertyNode.getClass().getName() + "'."; 662 LOG.logError( msg ); 663 throw new RuntimeException( msg ); 664 } 665 return mappingField; 666 } 667 668 /** 669 * Appends an SQL fragment for the given object to the given sql statement. 670 * 671 * @param query 672 * @param propertyName 673 */ 674 protected void appendPropertyNameAsSQL( StringBuffer query, PropertyName propertyName ) { 675 676 MappingField mappingField = getPropertyNameMapping( propertyName ); 677 // with ArcSDE because of versioning not applicable 678 // query.append( mappingField.getTable() ); 679 // query.append( '.' ); 680 query.append( mappingField.getField() ); 681 } 682 683 /** 684 * Appends an SQL fragment for the given object to the given sql statement. 685 * 686 * @param query 687 * @param expression 688 * @param targetSqlType 689 */ 690 protected void appendArithmeticExpressionAsSQL( StringBuffer query, ArithmeticExpression expression, 691 int targetSqlType ) { 692 query.append( '(' ); 693 appendExpressionAsSQL( query, expression.getFirstExpression(), targetSqlType ); 694 switch ( expression.getExpressionId() ) { 695 case ExpressionDefines.ADD: { 696 query.append( '+' ); 697 break; 698 } 699 case ExpressionDefines.SUB: { 700 query.append( '-' ); 701 break; 702 } 703 case ExpressionDefines.MUL: { 704 query.append( '*' ); 705 break; 706 } 707 case ExpressionDefines.DIV: { 708 query.append( '/' ); 709 break; 710 } 711 } 712 appendExpressionAsSQL( query, expression.getSecondExpression(), targetSqlType ); 713 query.append( ')' ); 714 } 715 716 /** 717 * Appends an SQL fragment for the given object to the given sql statement. 718 * 719 * @param query 720 * @param function 721 * @param targetSqlType 722 */ 723 protected void appendFunctionAsSQL( StringBuffer query, Function function, int targetSqlType ) { 724 query.append( function.getName() ); 725 query.append( " (" ); 726 List list = function.getArguments(); 727 for ( int i = 0; i < list.size(); i++ ) { 728 Expression expression = (Expression) list.get( i ); 729 appendExpressionAsSQL( query, expression, targetSqlType ); 730 if ( i != list.size() - 1 ) 731 query.append( ", " ); 732 } 733 query.append( ")" ); 734 } 735 736 /** 737 * Appends an SQL fragment for the given object to the given sql statement. 738 * 739 * @param query 740 * @param operation 741 */ 742 protected void appendLogicalOperationAsSQL( StringBuffer query, LogicalOperation operation ) { 743 List argumentList = operation.getArguments(); 744 switch ( operation.getOperatorId() ) { 745 case OperationDefines.AND: { 746 for ( int i = 0; i < argumentList.size(); i++ ) { 747 Operation argument = (Operation) argumentList.get( i ); 748 query.append( '(' ); 749 appendOperationAsSQL( query, argument ); 750 query.append( ')' ); 751 if ( i != argumentList.size() - 1 ) 752 query.append( " AND " ); 753 } 754 break; 755 } 756 case OperationDefines.OR: { 757 for ( int i = 0; i < argumentList.size(); i++ ) { 758 Operation argument = (Operation) argumentList.get( i ); 759 query.append( '(' ); 760 appendOperationAsSQL( query, argument ); 761 query.append( ')' ); 762 if ( i != argumentList.size() - 1 ) 763 query.append( " OR " ); 764 } 765 break; 766 } 767 case OperationDefines.NOT: { 768 Operation argument = (Operation) argumentList.get( 0 ); 769 query.append( "NOT (" ); 770 appendOperationAsSQL( query, argument ); 771 query.append( ')' ); 772 break; 773 } 774 } 775 } 776 777 /** 778 * Appends an SQL fragment for the given object to the given sql statement. 779 * 780 * TODO Handle compound primary keys correctly. 781 * 782 * @param query 783 * @param filter 784 */ 785 protected void appendFeatureFilterAsSQL( StringBuffer query, FeatureFilter filter ) { 786 ArrayList list = filter.getFeatureIds(); 787 MappingField mapping = rootFeatureType.getGMLId().getIdFields()[0]; 788 String quote = ""; 789 switch ( mapping.getType() ) { 790 case java.sql.Types.DECIMAL: 791 case java.sql.Types.DOUBLE: 792 case java.sql.Types.FLOAT: 793 case java.sql.Types.INTEGER: 794 case java.sql.Types.NUMERIC: 795 case java.sql.Types.REAL: 796 case java.sql.Types.SMALLINT: 797 case java.sql.Types.TINYINT: 798 break; 799 default: 800 quote = "'"; 801 break; 802 } 803 query.append( ' ' ); 804 query.append( mapping.getField() ); 805 try { 806 for ( int i = 0; i < list.size(); i++ ) { 807 if ( 0 == i ) 808 query.append( " IN (" + quote ); 809 else 810 query.append( quote + "," + quote ); 811 String fid = ( (org.deegree.model.filterencoding.FeatureId) list.get( i ) ).getValue(); 812 Object fidValue = org.deegree.io.datastore.FeatureId.removeFIDPrefix( fid, rootFeatureType.getGMLId() ); 813 query.append( fidValue.toString() ); 814 } 815 } catch ( Exception e ) { 816 LOG.logError( "Error converting feature id", e ); 817 } 818 query.append( quote + ")" ); 819 } 820 821 /** 822 * Generates an SQL-fragment for the given object. 823 * 824 * @param filter 825 * @param layers 826 * @return 827 * 828 * @throws DatastoreException 829 */ 830 protected SeFilter[] buildSpatialFilter( ComplexFilter filter, List layers ) 831 throws DatastoreException { 832 833 SpatialOperation[] spatialOps = FilterTools.extractSpatialFilter( filter ); 834 if ( null == spatialOps || 0 == spatialOps.length ) 835 return null; 836 837 SeFilter[] spatialFilter = new SeFilter[spatialOps.length]; 838 839 for ( int i = 0; i < spatialOps.length; i++ ) { 840 try { 841 MappingField mappingField = getPropertyNameMapping( spatialOps[i].getPropertyName() ); 842 String filterTable = mappingField.getTable(); 843 String filterColumn = mappingField.getField(); 844 845 SeCoordinateReference coordRef = null; 846 String[] splitted = filterTable.toUpperCase().split( "\\." ); 847 String tmp = splitted[splitted.length - 1]; 848 for ( int k = 0; k < layers.size(); k++ ) { 849 SeLayer layer = (SeLayer) layers.get( k ); 850 splitted = layer.getName().toUpperCase().split( "\\." ); 851 if ( splitted[splitted.length - 1].equals( tmp ) ) { 852 coordRef = layer.getCoordRef(); 853 break; 854 } 855 } 856 if ( null == coordRef ) { 857 coordRef = new SeCoordinateReference(); 858 } 859 860 int filterMethod = -1; 861 boolean filterTruth = true; 862 switch ( spatialOps[i].getOperatorId() ) { 863 case OperationDefines.CROSSES: { 864 filterMethod = SeFilter.METHOD_LCROSS; 865 break; 866 } 867 case OperationDefines.EQUALS: { 868 filterMethod = SeFilter.METHOD_IDENTICAL; 869 break; 870 } 871 case OperationDefines.WITHIN: { 872 filterMethod = SeFilter.METHOD_SC_NO_ET; 873 break; 874 } 875 case OperationDefines.OVERLAPS: { 876 filterMethod = SeFilter.METHOD_ENVP; 877 break; 878 } 879 case OperationDefines.TOUCHES: { 880 filterMethod = SeFilter.METHOD_ET_OR_AI; 881 break; 882 } 883 case OperationDefines.DISJOINT: { 884 filterMethod = SeFilter.METHOD_SC_NO_ET; 885 filterTruth = false; 886 break; 887 } 888 case OperationDefines.INTERSECTS: { 889 filterMethod = SeFilter.METHOD_AI; 890 break; 891 } 892 case OperationDefines.CONTAINS: { 893 filterMethod = SeFilter.METHOD_AI_OR_ET; 894 break; 895 } 896 case OperationDefines.BBOX: { 897 filterMethod = SeFilter.METHOD_ENVP; 898 break; 899 } 900 case OperationDefines.DWITHIN: 901 case OperationDefines.BEYOND: 902 default: { 903 continue; 904 } 905 } 906 SeShape filterGeom = SDEAdapter.export( spatialOps[i].getGeometry(), coordRef ); 907 spatialFilter[i] = new SeShapeFilter( filterTable, filterColumn, filterGeom, filterMethod, filterTruth ); 908 } catch ( Exception e ) { 909 e.printStackTrace(); 910 throw new DatastoreException( "Error creating spatial filter", e ); 911 } 912 } 913 return spatialFilter; 914 } 915 }