| 1 | /** |
| 2 | * Copyright (C) 2006 NetMind Consulting Bt. |
| 3 | * |
| 4 | * This library is free software; you can redistribute it and/or |
| 5 | * modify it under the terms of the GNU Lesser General Public |
| 6 | * License as published by the Free Software Foundation; either |
| 7 | * version 3 of the License, or (at your option) any later version. |
| 8 | * |
| 9 | * This library is distributed in the hope that it will be useful, |
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 12 | * Lesser General Public License for more details. |
| 13 | * |
| 14 | * You should have received a copy of the GNU Lesser General Public |
| 15 | * License along with this library; if not, write to the Free Software |
| 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 17 | */ |
| 18 | |
| 19 | package hu.netmind.beankeeper.parser; |
| 20 | |
| 21 | import hu.netmind.beankeeper.model.*; |
| 22 | import hu.netmind.beankeeper.schema.SchemaManager; |
| 23 | import hu.netmind.beankeeper.type.TypeHandlerTracker; |
| 24 | import java.util.*; |
| 25 | import org.apache.log4j.Logger; |
| 26 | |
| 27 | /** |
| 28 | * The parser resolver. Handles resolving objects and attributes |
| 29 | * to table names and such. This class also handles the symbol table. |
| 30 | * @author Brautigam Robert |
| 31 | * @version Revision: $Revision$ |
| 32 | */ |
| 33 | public class WhereResolver |
| 34 | { |
| 35 | private static Logger logger = Logger.getLogger(WhereResolver.class); |
| 36 | |
| 37 | private HashMap symbolTable; |
| 38 | private SymbolTableEntry lastSymbolTableEntry; |
| 39 | // Main class stuff only used by 'find', when selecting single class |
| 40 | private SpecifiedTableTerm mainTerm; |
| 41 | private ClassInfo mainClassInfo; |
| 42 | private ClassSpecifier mainClassSpecifier; |
| 43 | |
| 44 | private SchemaManager schemaManager = null; |
| 45 | private ClassTracker classTracker = null; |
| 46 | private TypeHandlerTracker typeHandlerTracker = null; |
| 47 | |
| 48 | public WhereResolver(ClassTracker classTracker, TypeHandlerTracker typeHandlerTracker, |
| 49 | SchemaManager schemaManager) |
| 50 | { |
| 51 | this.classTracker=classTracker; |
| 52 | this.typeHandlerTracker=typeHandlerTracker; |
| 53 | this.schemaManager=schemaManager; |
| 54 | symbolTable = new HashMap(); |
| 55 | mainTerm = null; |
| 56 | mainClassInfo = null; |
| 57 | } |
| 58 | |
| 59 | /** |
| 60 | * Resolve main table name. |
| 61 | */ |
| 62 | public TableTerm resolve(ClassSpecifier specifier, boolean selected) |
| 63 | { |
| 64 | String alias = specifier.getAlias()!=null?specifier.getAlias():specifier.getClassName(); |
| 65 | // Insert symbol table entry |
| 66 | SymbolTableEntry entry = (SymbolTableEntry) symbolTable.get(alias); |
| 67 | if ( entry == null ) |
| 68 | { |
| 69 | // Determine class for specifier |
| 70 | ClassInfo classInfo = classTracker.getClassInfo( |
| 71 | classTracker.getMatchingClassEntry(specifier.getClassName())); |
| 72 | if ( classInfo == null ) |
| 73 | throw new ParserException(ParserException.SYMBOL_ERROR,"could not find class for table name: "+specifier.getClassName()); |
| 74 | // Entry was null, create |
| 75 | entry = new SymbolTableEntry(); |
| 76 | entry.specifiedTerm = new SpecifiedTableTerm( |
| 77 | schemaManager.getTableName(classInfo.getSourceEntry()), specifier.getAlias()); |
| 78 | entry.automatic = false; |
| 79 | entry.expression = null; |
| 80 | entry.selected=true; |
| 81 | entry.classInfo = classInfo; |
| 82 | symbolTable.put(alias,entry); |
| 83 | lastSymbolTableEntry=entry; |
| 84 | if ( specifier.getAlias() == null ) |
| 85 | { |
| 86 | // Insert default table name symbol too, for later left table |
| 87 | // term fixing, which will use the full table name |
| 88 | symbolTable.put(entry.specifiedTerm.getTableName(),entry); |
| 89 | } |
| 90 | logger.debug("adding table to symbol table: "+entry.specifiedTerm); |
| 91 | // Remember main table |
| 92 | if ( mainTerm == null ) |
| 93 | { |
| 94 | logger.debug("table entry "+entry.specifiedTerm+" is selected, adding left tables."); |
| 95 | // Remember |
| 96 | mainClassSpecifier = specifier; |
| 97 | mainClassInfo = classInfo; |
| 98 | mainTerm = entry.specifiedTerm; // Main term is the selected class/table |
| 99 | // Fix entry to contain all relevant classes |
| 100 | if ( selected ) |
| 101 | fixLeftTableTerms(entry.specifiedTerm,entry); |
| 102 | } |
| 103 | } |
| 104 | // Construct table term |
| 105 | TableTerm tableTerm = new TableTerm(entry.specifiedTerm); |
| 106 | // Return with table term |
| 107 | logger.debug("translated: "+specifier+" -> "+tableTerm); |
| 108 | return tableTerm; |
| 109 | } |
| 110 | |
| 111 | private ReferenceTerm getReferenceTerm(SymbolTableEntry entry) |
| 112 | { |
| 113 | switch ( entry.type ) |
| 114 | { |
| 115 | case SymbolTableEntry.TYPE_HANDLED: |
| 116 | return new ReferenceTerm(entry.specifiedTerm,entry.referenceColumn); |
| 117 | case SymbolTableEntry.TYPE_OBJECT: |
| 118 | return new ReferenceTerm(entry.specifiedTerm,"persistence_id"); |
| 119 | default: |
| 120 | return null; |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | /** |
| 125 | * Resolver attribute chain. |
| 126 | */ |
| 127 | public ReferenceTerm resolve(ClassSpecifier specifier, List attributeSpecifiers) |
| 128 | { |
| 129 | // Resolve class specifier |
| 130 | // This is not as easy as it sounds: if there are no attribute |
| 131 | // specifiers and there is no alias given, it may mean that |
| 132 | // this identifier represents an attribute to selected main class |
| 133 | // (if there is one). |
| 134 | if ( ((attributeSpecifiers==null) || (attributeSpecifiers.size()==0)) |
| 135 | && (specifier.getAlias()==null) && (mainClassInfo!=null) ) |
| 136 | { |
| 137 | String attributeName = specifier.getClassName(); |
| 138 | if ( mainClassInfo.getAttributeType(attributeName) != null ) |
| 139 | { |
| 140 | // Simulate a normal entry |
| 141 | specifier = mainClassSpecifier; |
| 142 | attributeSpecifiers = new ArrayList(); |
| 143 | attributeSpecifiers.add(new AttributeSpecifier(attributeName,null,null)); |
| 144 | } |
| 145 | } |
| 146 | // Now go through names, the root name does already exist, |
| 147 | // because that is the class. All other names are |
| 148 | // combined with attributes (book.title). This also should |
| 149 | // be inserted into symbol table, if it is a class. |
| 150 | resolve(specifier,false); // Resolve class |
| 151 | StringBuffer alias = new StringBuffer(specifier.getAlias()==null?specifier.getClassName():specifier.getAlias()); |
| 152 | SymbolTableEntry previousEntry = (SymbolTableEntry) symbolTable.get(alias.toString()); |
| 153 | ClassInfo previousInfo = previousEntry.classInfo; |
| 154 | AttributeSpecifier primitiveAttributeSpecifier = null; |
| 155 | for ( int i=0; (attributeSpecifiers!=null) && (i<attributeSpecifiers.size()); i++ ) |
| 156 | { |
| 157 | AttributeSpecifier spec = (AttributeSpecifier) attributeSpecifiers.get(i); |
| 158 | String attributeName = spec.getIdentifier(); |
| 159 | Class attributeClass = previousInfo.getAttributeType(attributeName); |
| 160 | if ( attributeClass == null ) |
| 161 | throw new ParserException(ParserException.ABORT,"can not resolve the identifier '"+ |
| 162 | attributeName+"' on '"+alias.toString()+"', classinfo: "+previousInfo); |
| 163 | alias.append("."+attributeName); |
| 164 | logger.debug("processing attribute: "+alias); |
| 165 | ClassTracker.ClassType attributeType = classTracker.getType(attributeClass); |
| 166 | // Handle additional array specifier. |
| 167 | if ( (spec.getKeyname() != null) && (attributeType != ClassTracker.ClassType.TYPE_HANDLED) ) |
| 168 | throw new ParserException(ParserException.ABORT,"additional array specifier found, but type is not 'handled': "+alias); |
| 169 | if ( attributeType == ClassTracker.ClassType.TYPE_HANDLED ) |
| 170 | { |
| 171 | logger.debug("attribute '"+alias+"' found to be a handled type, letting the handler in."); |
| 172 | // If the array specifier is given, add to alias |
| 173 | if ( spec.getKeyname() != null ) |
| 174 | alias.append("['"+spec.getKeyname()+"']"); |
| 175 | // Calculate the symbol table entry for this part |
| 176 | SymbolTableEntry entry = (SymbolTableEntry) symbolTable.get(alias.toString()); |
| 177 | if ( entry == null ) |
| 178 | { |
| 179 | entry = typeHandlerTracker. |
| 180 | getHandler(attributeClass).getSymbolEntry(spec,previousEntry, |
| 181 | previousInfo,getReferenceTerm(previousEntry)); |
| 182 | if ( entry == null ) |
| 183 | { |
| 184 | // Entry was not created, this means this is a primitive type |
| 185 | logger.debug("handled attribute '"+alias+"' found to be a primitive attribute."); |
| 186 | // A primitive type should be the last in the list |
| 187 | if ( i+1 < attributeSpecifiers.size() ) |
| 188 | throw new ParserException(ParserException.ABORT,"A handled primitive type encountered, but not the last in list: "+alias.toString()); |
| 189 | // Mark primitive type |
| 190 | primitiveAttributeSpecifier = spec; |
| 191 | entry = previousEntry; // It's entry is it's parent |
| 192 | } else if ( entry.specifiedTerm.getAlias() != null ) { |
| 193 | // Entry wants to be in the symbol table |
| 194 | symbolTable.put(entry.specifiedTerm.getAlias(),entry); |
| 195 | lastSymbolTableEntry=entry; |
| 196 | } else { |
| 197 | // Entry does no want into the symbol table, |
| 198 | // so create an extremal symbol. |
| 199 | int num = 0; |
| 200 | while ( symbolTable.containsKey(alias.toString()+"-notused"+num) ) |
| 201 | num++; |
| 202 | symbolTable.put(alias.toString()+"-notused"+num,entry); |
| 203 | lastSymbolTableEntry=entry; |
| 204 | } |
| 205 | } |
| 206 | previousEntry = entry; |
| 207 | // If this is not the last specifier, then calculate the |
| 208 | // class info for the next specifier. |
| 209 | if ( i+1 < attributeSpecifiers.size() ) |
| 210 | previousInfo = typeHandlerTracker. |
| 211 | getHandler(attributeClass).getSymbolInfo(entry,spec); |
| 212 | } |
| 213 | // Reserved type |
| 214 | if ( attributeType == ClassTracker.ClassType.TYPE_RESERVED ) |
| 215 | throw new ParserException(ParserException.ABORT,"attribute type is reserved, can not handle it: "+alias.toString()); |
| 216 | // Attribute is an object |
| 217 | if ( attributeType == ClassTracker.ClassType.TYPE_OBJECT ) |
| 218 | { |
| 219 | ClassInfo containerInfo = previousInfo; |
| 220 | if ( spec.getClassName() != null ) |
| 221 | previousInfo = classTracker.getClassInfo( |
| 222 | classTracker.getMatchingClassEntry(spec.getClassName())); |
| 223 | else |
| 224 | previousInfo = classTracker.getClassInfo(previousInfo.getAttributeType(attributeName),null); |
| 225 | if ( logger.isDebugEnabled() ) |
| 226 | logger.debug("attribute '"+alias+"' found to be an object, with entry: "+previousInfo.getSourceEntry()); |
| 227 | // If the attribute is non-storable, then it will get flagged in |
| 228 | // fixNonstorableTerms() method. But here, we let it through, because |
| 229 | // maybe, it is compared to a primitive type, in which case, we still |
| 230 | // can make a meaningful expression out of it (in fixPrimitiveExpression) |
| 231 | if ( ! previousInfo.isStorable() ) |
| 232 | { |
| 233 | ReferenceTerm idTerm = new ReferenceTerm( |
| 234 | previousEntry.specifiedTerm,attributeName); |
| 235 | previousEntry.termList.add(idTerm); |
| 236 | if ( logger.isDebugEnabled() ) |
| 237 | logger.debug("attribute '"+alias+"' refers to a non-storable object, so reference will be: "+idTerm); |
| 238 | return idTerm; |
| 239 | } |
| 240 | // Get entry |
| 241 | SymbolTableEntry entry = (SymbolTableEntry) symbolTable.get(alias.toString()); |
| 242 | if ( entry == null ) |
| 243 | { |
| 244 | // Create entry |
| 245 | entry = new SymbolTableEntry(); |
| 246 | entry.specifiedTerm = new SpecifiedTableTerm( |
| 247 | schemaManager.getTableName(previousInfo.getSourceEntry()),null); |
| 248 | entry.automatic = true; |
| 249 | entry.type = SymbolTableEntry.TYPE_OBJECT; |
| 250 | entry.classInfo = previousInfo; |
| 251 | symbolTable.put(alias.toString(),entry); |
| 252 | lastSymbolTableEntry=entry; |
| 253 | // Create expression |
| 254 | Expression expr = new Expression(); |
| 255 | // Determine whether the entry's tableName is correct, or if |
| 256 | // a superclass's table is required for attribute |
| 257 | TableTerm previousTerm = new TableTerm(previousEntry.specifiedTerm); |
| 258 | ClassEntry superClassEntry = containerInfo.getAttributeClassEntry(attributeName); |
| 259 | if ( logger.isDebugEnabled() ) |
| 260 | logger.debug("selecting object attribute "+attributeName+", class: "+ |
| 261 | previousEntry.classInfo.getSourceEntry()+", superclass: "+superClassEntry); |
| 262 | if ( ! previousEntry.classInfo.getSourceEntry().equals(superClassEntry) ) |
| 263 | { |
| 264 | // Class holding the attribute differs from entry's class. |
| 265 | // Note: if a superclass is in charge, there should be no |
| 266 | // dynamic name. |
| 267 | ClassInfo superInfo = classTracker.getClassInfo(superClassEntry); |
| 268 | String superTableName = schemaManager.getTableName(superClassEntry); |
| 269 | previousTerm = new TableTerm(superTableName,null); |
| 270 | if ( logger.isDebugEnabled() ) |
| 271 | logger.debug("determining whether to allocate supertable: "+previousTerm+", tables allocated: "+previousEntry.allocatedSuperTerms); |
| 272 | if ( ! previousEntry.allocatedSuperTerms.contains(previousTerm) ) |
| 273 | { |
| 274 | logger.debug("allocating superclass: "+superClassEntry); |
| 275 | // This superclass was not yet allocated, so add |
| 276 | // as a related left term |
| 277 | Expression joinExpr = new Expression(); |
| 278 | ReferenceTerm connectLeftTerm = new ReferenceTerm(previousEntry.specifiedTerm,"persistence_id"); |
| 279 | previousEntry.termList.add(connectLeftTerm); |
| 280 | joinExpr.add(connectLeftTerm); |
| 281 | joinExpr.add("="); |
| 282 | ReferenceTerm connectRightTerm = new ReferenceTerm(previousTerm,"persistence_id"); |
| 283 | previousEntry.termList.add(connectRightTerm); |
| 284 | joinExpr.add(connectRightTerm); |
| 285 | SpecifiedTableTerm.LeftjoinEntry leftEntry = new SpecifiedTableTerm.LeftjoinEntry(); |
| 286 | leftEntry.term=previousTerm; |
| 287 | leftEntry.expression=joinExpr; |
| 288 | previousEntry.specifiedTerm.getRelatedLeftTerms().add(leftEntry); |
| 289 | // Add to list |
| 290 | previousEntry.allocatedSuperTerms.add(previousTerm); |
| 291 | } |
| 292 | } |
| 293 | // Now join the term to the parent term |
| 294 | if ( i+1 >= attributeSpecifiers.size() ) |
| 295 | { |
| 296 | // Object is the last in the reference list, so it is not |
| 297 | // dereferenced. In this case, we must provide a column |
| 298 | // of persistence_id, which is null if the parent object's |
| 299 | // attribute is null, _or_ if it's not null, but the object |
| 300 | // is deleted. To do this, we only left-join the table of |
| 301 | // the type, so we will get a persistence_id anyway. |
| 302 | ReferenceTerm refTerm = getReferenceTerm(entry); |
| 303 | entry.termList.add(refTerm); |
| 304 | entry.expression=expr; |
| 305 | entry.leftTerm=true; // Mark as not standalone |
| 306 | // Add leftjoin entry |
| 307 | SpecifiedTableTerm.LeftjoinEntry leftEntry = new SpecifiedTableTerm.LeftjoinEntry(); |
| 308 | leftEntry.term=refTerm; |
| 309 | Expression joinExpr = new Expression(); |
| 310 | leftEntry.expression=joinExpr; |
| 311 | ReferenceTerm toTerm = new ReferenceTerm(previousTerm,attributeName); |
| 312 | previousEntry.termList.add(toTerm); |
| 313 | joinExpr.add(toTerm); |
| 314 | joinExpr.add("="); |
| 315 | ReferenceTerm valueTerm = new ReferenceTerm(refTerm,"persistence_id"); |
| 316 | joinExpr.add(valueTerm); |
| 317 | entry.termList.add(valueTerm); |
| 318 | previousEntry.specifiedTerm.getReferencedLeftTerms().add(leftEntry); |
| 319 | } else { |
| 320 | // Object is not the last in the reference list, meaning it is |
| 321 | // dereferenced, which in turn means, that it must be hard-joined |
| 322 | // so all the attributes exist, or the select fails if the object |
| 323 | // can not be joined |
| 324 | ReferenceTerm leftTerm = new ReferenceTerm(previousTerm,attributeName); |
| 325 | previousEntry.termList.add(leftTerm); |
| 326 | if ( ! expr.isEmpty() ) |
| 327 | expr.add("and"); |
| 328 | expr.add(leftTerm); |
| 329 | expr.add("="); |
| 330 | ReferenceTerm refTerm = getReferenceTerm(entry); |
| 331 | expr.add(refTerm); |
| 332 | entry.termList.add(refTerm); |
| 333 | entry.expression=expr; |
| 334 | } |
| 335 | } |
| 336 | previousEntry = entry; |
| 337 | } |
| 338 | // Attribute is primitive type |
| 339 | if ( attributeType == ClassTracker.ClassType.TYPE_PRIMITIVE ) |
| 340 | { |
| 341 | logger.debug("attribute '"+alias+"' found to be a primitive attribute."); |
| 342 | // A primitive type should be the last in the list |
| 343 | if ( i+1 < attributeSpecifiers.size() ) |
| 344 | throw new ParserException(ParserException.ABORT,"A primitive type encountered, but not the last in list: "+alias.toString()); |
| 345 | // Mark primitive type |
| 346 | primitiveAttributeSpecifier = spec; |
| 347 | } |
| 348 | } |
| 349 | // If we're here, that means, attribute list has ended. |
| 350 | // Either in a primitive type attribute or in object. |
| 351 | SymbolTableEntry entry = (SymbolTableEntry) symbolTable.get(alias.toString()); |
| 352 | if ( entry == null ) |
| 353 | { |
| 354 | // This means we are in an attribute, so get previous entry |
| 355 | entry = previousEntry; |
| 356 | } |
| 357 | // Construct and return term |
| 358 | ReferenceTerm result = null; |
| 359 | if ( primitiveAttributeSpecifier != null ) |
| 360 | { |
| 361 | // Attribute specified, this means, we must check whether |
| 362 | // this attribute is part of the class in entry or in superclass. |
| 363 | // "persistenceid" is considered part of every class, so no superclasses |
| 364 | // are selected for that. |
| 365 | TableTerm tableTerm = new TableTerm(entry.specifiedTerm); |
| 366 | ClassEntry superClassEntry = entry.classInfo.getAttributeClassEntry(primitiveAttributeSpecifier.getIdentifier()); |
| 367 | if ( logger.isDebugEnabled() ) |
| 368 | logger.debug("selecting primitive attribute "+primitiveAttributeSpecifier.getIdentifier()+ |
| 369 | ", class: "+entry.classInfo.getSourceEntry()+", superclass: "+superClassEntry); |
| 370 | if ( (! entry.classInfo.getSourceEntry().equals(superClassEntry)) && |
| 371 | (!"persistenceid".equalsIgnoreCase(primitiveAttributeSpecifier.getIdentifier())) ) |
| 372 | { |
| 373 | // Class holding the attribute differs from entry's class |
| 374 | ClassInfo superInfo = classTracker.getClassInfo(superClassEntry); |
| 375 | String superTableName = schemaManager.getTableName(superClassEntry); |
| 376 | tableTerm = new TableTerm(superTableName,null); |
| 377 | if ( ! entry.allocatedSuperTerms.contains(tableTerm) ) |
| 378 | { |
| 379 | logger.debug("primitive attribute is a related class' attribute, and not yet allocated."); |
| 380 | // This superclass was not yet allocated |
| 381 | Expression joinExpr = new Expression(); |
| 382 | ReferenceTerm connectLeftTerm = new ReferenceTerm(entry.specifiedTerm,"persistence_id"); |
| 383 | entry.termList.add(connectLeftTerm); |
| 384 | joinExpr.add(connectLeftTerm); |
| 385 | joinExpr.add("="); |
| 386 | ReferenceTerm connectRightTerm = new ReferenceTerm(tableTerm,"persistence_id"); |
| 387 | entry.termList.add(connectRightTerm); |
| 388 | joinExpr.add(connectRightTerm); |
| 389 | SpecifiedTableTerm.LeftjoinEntry leftEntry = new SpecifiedTableTerm.LeftjoinEntry(); |
| 390 | leftEntry.term=tableTerm; |
| 391 | leftEntry.expression=joinExpr; |
| 392 | entry.specifiedTerm.getRelatedLeftTerms().add(leftEntry); |
| 393 | // Add to list |
| 394 | entry.allocatedSuperTerms.add(tableTerm); |
| 395 | } |
| 396 | } |
| 397 | // Construct result |
| 398 | String attributeName = primitiveAttributeSpecifier.getIdentifier(); |
| 399 | boolean id = false; |
| 400 | if ( "persistenceid".equalsIgnoreCase(attributeName) ) |
| 401 | { |
| 402 | attributeName = "persistence_id"; |
| 403 | id = true; |
| 404 | } |
| 405 | result = new ReferenceTerm(tableTerm,attributeName); |
| 406 | if ( id ) // Mark specifically for id reference |
| 407 | result.setId(); |
| 408 | } else { |
| 409 | // Term did not end in attribute specification, so leave |
| 410 | // persistence id, which is available in all tables |
| 411 | result = getReferenceTerm(entry); |
| 412 | } |
| 413 | entry.termList.add(result); |
| 414 | return result; // Return term |
| 415 | } |
| 416 | |
| 417 | /** |
| 418 | * Fix time control to apply transaction time too, if the statement |
| 419 | * has common tables with transaction modified tables. |
| 420 | */ |
| 421 | public void fixTimeControl(QueryStatement stmt, Set modifiedTables) |
| 422 | { |
| 423 | if ( stmt.getTimeControl().isApplyTransaction() ) |
| 424 | return; |
| 425 | // Go through all entries and whatch for tables |
| 426 | Iterator entryIterator = symbolTable.values().iterator(); |
| 427 | while ( entryIterator.hasNext() ) |
| 428 | { |
| 429 | SymbolTableEntry entry = (SymbolTableEntry) entryIterator.next(); |
| 430 | boolean found = false; |
| 431 | if ( modifiedTables.contains(entry.specifiedTerm.getTableName()) ) |
| 432 | found=true; |
| 433 | for ( int i=0; i<entry.specifiedTerm.getRelatedLeftTerms().size(); i++ ) |
| 434 | if ( modifiedTables.contains(((SpecifiedTableTerm.LeftjoinEntry) |
| 435 | entry.specifiedTerm.getRelatedLeftTerms().get(i)).term.getTableName()) ) |
| 436 | found=true; |
| 437 | for ( int i=0; i<entry.specifiedTerm.getReferencedLeftTerms().size(); i++ ) |
| 438 | if ( modifiedTables.contains(((SpecifiedTableTerm.LeftjoinEntry) |
| 439 | entry.specifiedTerm.getReferencedLeftTerms().get(i)).term.getTableName()) ) |
| 440 | found=true; |
| 441 | // If found a common table, then set transaction to apply |
| 442 | // and return |
| 443 | if ( found ) |
| 444 | { |
| 445 | stmt.getTimeControl().setApplyTransaction(true); |
| 446 | return; // Transaction applies, common table found |
| 447 | } |
| 448 | } |
| 449 | } |
| 450 | |
| 451 | /** |
| 452 | * Fix atomic expressions for 2VL (2 valued logic), instead of sql 3VL. |
| 453 | * This means, that while "a.attr = b.attr" will not work in sql when |
| 454 | * both are null, BeanKeeper supposes, that a developer wants that expression |
| 455 | * to evaluate to 'true', when both attributes are null. |
| 456 | */ |
| 457 | private Expression fixThreeValuedLogicExpressions(ReferenceTerm term1, |
| 458 | ReferenceTerm term2, String op) |
| 459 | { |
| 460 | if ( logger.isDebugEnabled() ) |
| 461 | logger.debug("checking for 3VL in following: "+term1+" "+op+" "+term2); |
| 462 | Expression result = new Expression(); |
| 463 | // Check whether both terms refer to non-primitve types, because if at |
| 464 | // least one is primitive, then they can't be null anyway |
| 465 | ClassInfo info1 = classTracker.getClassInfo(schemaManager.getClassEntry(term1.getTableName())); |
| 466 | ClassInfo info2 = classTracker.getClassInfo(schemaManager.getClassEntry(term2.getTableName())); |
| 467 | if ( (!term1.getColumnName().equals("persistence_id")) && |
| 468 | (!term2.getColumnName().equals("persistence_id")) && |
| 469 | ( (info1.getAttributeType(term1.getColumnName()).isPrimitive()) || |
| 470 | (info2.getAttributeType(term2.getColumnName()).isPrimitive())) ) |
| 471 | { |
| 472 | // At least one term refers to a primitive type, so that can't be null, |
| 473 | // no 3VL logic will interfere with operator |
| 474 | result.add(term1); |
| 475 | result.add(op); |
| 476 | result.add(term2); |
| 477 | return result; |
| 478 | } |
| 479 | // Both terms are referring to non-primitive types, so modify them |
| 480 | // to support 'null' comparisons |
| 481 | result.setMarkedBlock(true); // Should be in paranthesis |
| 482 | if ( op.equals("=") ) |
| 483 | { |
| 484 | // We need to create the following expression: |
| 485 | // ( term1=term2 or term1 is null and term2 is null ) |
| 486 | result.add(term1); |
| 487 | result.add("="); |
| 488 | result.add(term2); |
| 489 | result.add("or"); |
| 490 | result.add(term1); |
| 491 | result.add("is null"); |
| 492 | result.add("and"); |
| 493 | result.add(term2); |
| 494 | result.add("is null"); |
| 495 | } else { |
| 496 | // We need the following (why is xor missing from sql?): |
| 497 | // ( term1<>term2 or term1 is null and term2 is not null or |
| 498 | // term1 is not null and term2 is null ) |
| 499 | result.add(term1); |
| 500 | result.add("<>"); |
| 501 | result.add(term2); |
| 502 | result.add("or"); |
| 503 | result.add(term1); |
| 504 | result.add("is null"); |
| 505 | result.add("and"); |
| 506 | result.add(term2); |
| 507 | result.add("is not null"); |
| 508 | result.add("or"); |
| 509 | result.add(term1); |
| 510 | result.add("is not null"); |
| 511 | result.add("and"); |
| 512 | result.add(term2); |
| 513 | result.add("is null"); |
| 514 | } |
| 515 | return result; |
| 516 | } |
| 517 | |
| 518 | /** |
| 519 | * Fix atomic (2 terms, 1 operator) expression artifacts. |
| 520 | */ |
| 521 | public Expression fixAtomicExpressions(Object term1, Object term2, String op) |
| 522 | { |
| 523 | if ( logger.isDebugEnabled() ) |
| 524 | logger.debug("fixing atomic expression: "+term1+" <"+op+"> "+term2); |
| 525 | // If there is a constant term present, then select it. If |
| 526 | // they are both constant terms, then same logic still applies. |
| 527 | Expression result = null; |
| 528 | Object constantTerm = term2; |
| 529 | Object otherTerm = term1; |
| 530 | if ( term1 instanceof ConstantTerm ) |
| 531 | { |
| 532 | constantTerm = term1; |
| 533 | otherTerm = term2; |
| 534 | } |
| 535 | // Check whether there is a constant term, which is primitive. In this case |
| 536 | // we must check, that it's a primitive or not, because |
| 537 | // in this case we may be forced to alter the other term. |
| 538 | if ( (otherTerm instanceof ReferenceTerm) && |
| 539 | (constantTerm instanceof ConstantTerm) && |
| 540 | (((ConstantTerm) constantTerm).getValue()!=null) ) |
| 541 | { |
| 542 | result = fixPrimitiveExpression((ReferenceTerm)otherTerm, |
| 543 | (ConstantTerm)constantTerm,op); |
| 544 | return result; |
| 545 | } |
| 546 | // If operator is not equals or not-equals, then 3VL conversion is not applicable |
| 547 | if ( (!op.equals("=")) && (!op.equals("!=")) && (!op.equals("<>")) ) |
| 548 | { |
| 549 | result = new Expression(); |
| 550 | result.add(term1); |
| 551 | result.add(op); |
| 552 | result.add(term2); |
| 553 | return result; |
| 554 | } |
| 555 | // Do 3VL convert logic |
| 556 | if ( constantTerm instanceof ConstantTerm ) |
| 557 | { |
| 558 | // There is at least one constant term in the expression |
| 559 | if ( ((ConstantTerm)constantTerm).getValue()==null ) |
| 560 | { |
| 561 | // At least one constant term is null in the expression, |
| 562 | // so change the operator to fit sql 3VL |
| 563 | result = new Expression(); |
| 564 | result.add(otherTerm); |
| 565 | if ( op.equals("=") ) |
| 566 | result.add("is null"); |
| 567 | else |
| 568 | result.add("is not null"); |
| 569 | } |
| 570 | } else { |
| 571 | // Both terms are referenceterms, check 3VL between them |
| 572 | if ( (term1 instanceof ReferenceTerm) && (term2 instanceof ReferenceTerm) ) |
| 573 | result = fixThreeValuedLogicExpressions((ReferenceTerm) term1, |
| 574 | (ReferenceTerm) term2, op); |
| 575 | } |
| 576 | return result; |
| 577 | } |
| 578 | |
| 579 | /** |
| 580 | * Fix a primitive expression. |
| 581 | * The problem is expressions like this: find holder where holder.attr = 'Ni'. |
| 582 | * If the holder.attr is an object type, and not declared 'primitive' |
| 583 | * (in which case it still can hold primitive boxed types), then |
| 584 | * the parser can not know it is meant to be primitive only when it |
| 585 | * comes to the right term which is primitive constant. So in this case, we |
| 586 | * must alter the expression to include the boxed primitive type's table. |
| 587 | */ |
| 588 | private Expression fixPrimitiveExpression(ReferenceTerm leftTerm, |
| 589 | ConstantTerm rightTerm, String op) |
| 590 | { |
| 591 | // Make the prototype expression |
| 592 | Expression result = new Expression(); |
| 593 | result.add(leftTerm); |
| 594 | result.add(op); |
| 595 | result.add(rightTerm); |
| 596 | // Make a few checks |
| 597 | if ( classTracker.getType(rightTerm.getValue().getClass()) != |
| 598 | ClassTracker.ClassType.TYPE_PRIMITIVE ) |
| 599 | return result; // Not a primitive constant, so there no point |
| 600 | if ( rightTerm.isId() || leftTerm.isId() ) |
| 601 | return result; // Is an Id expression specifically, and not primitive boxes types |
| 602 | ClassInfo leftInfo = classTracker.getClassInfo(schemaManager.getClassEntry(leftTerm.getTableName())); |
| 603 | if ( (leftInfo!=null) && |
| 604 | (classTracker.getType(leftInfo.getAttributeType(leftTerm.getColumnName())) |
| 605 | == ClassTracker.ClassType.TYPE_PRIMITIVE) ) |
| 606 | return result; // Attribute is primitive, so no modification needed for checking |
| 607 | if ( logger.isDebugEnabled() ) |
| 608 | logger.debug("fixing primitive expression: "+result); |
| 609 | // Now insert expression referencing the boxed primitive type's table |
| 610 | // Note: We must insert a symbol table entry for the primitive type's |
| 611 | // table, but it's not a real symbol, it should have an extremal |
| 612 | // name. |
| 613 | ClassInfo primitiveInfo = classTracker.getClassInfo(rightTerm.getValue().getClass(),rightTerm.getValue()); |
| 614 | String primitiveTableName = schemaManager.getTableName(primitiveInfo.getSourceEntry()); |
| 615 | TableTerm primitiveTableTerm = new TableTerm(primitiveTableName,null); |
| 616 | SymbolTableEntry entry = new SymbolTableEntry(); |
| 617 | entry.specifiedTerm=new SpecifiedTableTerm(primitiveTableName,null); |
| 618 | entry.automatic = true; |
| 619 | entry.type = SymbolTableEntry.TYPE_OBJECT; |
| 620 | symbolTable.put("primitive"+symbolTable.size(),entry); |
| 621 | // Create contact expression to primitive table |
| 622 | Expression contactExpr = new Expression(); |
| 623 | contactExpr.add(leftTerm); |
| 624 | contactExpr.add("="); |
| 625 | ReferenceTerm contactTerm = new ReferenceTerm(primitiveTableTerm,"persistence_id"); |
| 626 | contactExpr.add(contactTerm); |
| 627 | entry.termList.add(contactTerm); |
| 628 | entry.expression=contactExpr; |
| 629 | // Replace the reference term with new primitive term |
| 630 | ReferenceTerm valueTerm = new ReferenceTerm(primitiveTableTerm,"value"); |
| 631 | entry.termList.add(valueTerm); |
| 632 | result.set(0,valueTerm); |
| 633 | return result; |
| 634 | } |
| 635 | |
| 636 | |
| 637 | private boolean fixContainsNegated(Expression expr, boolean isNegated) |
| 638 | { |
| 639 | if ( expr == null ) |
| 640 | return false; |
| 641 | boolean originalNegated = isNegated; |
| 642 | isNegated=false; |
| 643 | for ( int i=0; i<expr.size(); i++ ) |
| 644 | { |
| 645 | Object obj = expr.get(i); |
| 646 | if (obj instanceof String) |
| 647 | { |
| 648 | if ( "contains".equalsIgnoreCase((String) obj) ) |
| 649 | { |
| 650 | if ( isNegated ^ originalNegated ) |
| 651 | return true; |
| 652 | expr.set(i,"="); // Replace with '=' sign |
| 653 | } else |
| 654 | isNegated = "not".equalsIgnoreCase((String) obj); |
| 655 | } |
| 656 | if ( (obj instanceof Expression ) && |
| 657 | (fixContainsNegated((Expression) obj,isNegated ^ originalNegated)) ) |
| 658 | return true; |
| 659 | } |
| 660 | return false; |
| 661 | } |
| 662 | |
| 663 | /** |
| 664 | * Fix unaliased generated table term. |
| 665 | */ |
| 666 | private Expression fixAutomaticTerms(Expression expr) |
| 667 | { |
| 668 | // First, generate all temporary names |
| 669 | Expression plusExpression = new Expression(); |
| 670 | String namePrefix = "t_"; |
| 671 | int tempNameIndex = 1; |
| 672 | // Entries will be filtered through a set, because a single |
| 673 | // entry could be present in the map multiple times (for example |
| 674 | // a main table is available as an alias, and as a table name entry) |
| 675 | Iterator iterator = new HashSet(symbolTable.values()).iterator(); |
| 676 | while ( iterator.hasNext() ) |
| 677 | { |
| 678 | SymbolTableEntry entry = (SymbolTableEntry) iterator.next(); |
| 679 | // Generate index for class and all superclasses! |
| 680 | HashMap tableNameAliases = new HashMap(); |
| 681 | ArrayList tableTerms = new ArrayList(entry.allocatedSuperTerms); |
| 682 | if ( entry.automatic ) |
| 683 | tableTerms.add(entry.specifiedTerm); |
| 684 | for ( int i=0; i<tableTerms.size(); i++ ) |
| 685 | { |
| 686 | TableTerm term = (TableTerm) tableTerms.get(i); |
| 687 | while ( symbolTable.get(namePrefix+tempNameIndex) != null) |
| 688 | tempNameIndex++; |
| 689 | tableNameAliases.put(term.getTableName(),namePrefix+tempNameIndex); |
| 690 | term.setAlias(namePrefix+tempNameIndex); |
| 691 | tempNameIndex++; |
| 692 | } |
| 693 | // Walk through referred terms and fill in the gaps |
| 694 | for ( int i=0; i<entry.termList.size(); i++ ) |
| 695 | { |
| 696 | TableTerm term = (TableTerm) entry.termList.get(i); |
| 697 | String newAlias = (String) tableNameAliases.get(term.getTableName()); |
| 698 | if ( newAlias != null ) |
| 699 | term.setAlias(newAlias); |
| 700 | } |
| 701 | // Remember it's expression |
| 702 | if ( (entry.expression != null) && (entry.expression.size()>0) ) |
| 703 | { |
| 704 | if ( logger.isDebugEnabled() ) |
| 705 | logger.debug("entry '"+entry.specifiedTerm+"' has connector expression: "+entry.expression); |
| 706 | if ( plusExpression.size() > 0 ) |
| 707 | plusExpression.add("and"); |
| 708 | plusExpression.addAll(entry.expression); |
| 709 | } |
| 710 | } |
| 711 | // Now create final expression |
| 712 | Expression result = expr; |
| 713 | if ( plusExpression.size() != 0 ) |
| 714 | { |
| 715 | if ( result.size() != 0 ) |
| 716 | { |
| 717 | result = new Expression(); |
| 718 | result.add(plusExpression); |
| 719 | result.add("and"); |
| 720 | result.add(expr); |
| 721 | } else { |
| 722 | result = plusExpression; |
| 723 | } |
| 724 | } |
| 725 | // Return |
| 726 | return result; |
| 727 | } |
| 728 | |
| 729 | /** |
| 730 | * Add all left table terms to term, all terms are at this point fixed. |
| 731 | * Note that method does not directly use the related classes returned |
| 732 | * by the classtracker, because the supplied leftterms already may |
| 733 | * contain aliases that couldn't be recovered elsewhere. |
| 734 | */ |
| 735 | private void fixLeftTableTerms(ClassInfo info, SpecifiedTableTerm term, List leftTerms) |
| 736 | { |
| 737 | // Calculate left table terms |
| 738 | Set relatedClassEntries = new HashSet(classTracker.getRelatedClassEntries(info.getSourceEntry())); |
| 739 | for ( int i=0; i<leftTerms.size(); i++ ) |
| 740 | { |
| 741 | // Go through left term, if this is a left term to the info, then |
| 742 | // insert it to this term's left terms. |
| 743 | // Note: class info can be null, if creating the table for that class |
| 744 | // previously failed. |
| 745 | TableTerm leftTerm = ((SpecifiedTableTerm.LeftjoinEntry) leftTerms.get(i)).term; |
| 746 | ClassInfo classInfo = classTracker.getClassInfo(schemaManager.getClassEntry(leftTerm.getTableName())); |
| 747 | if ( classInfo != null ) |
| 748 | { |
| 749 | ClassEntry leftEntry = classInfo.getSourceEntry(); |
| 750 | if ( relatedClassEntries.contains(leftEntry) ) |
| 751 | { |
| 752 | SpecifiedTableTerm.LeftjoinEntry entry = new SpecifiedTableTerm.LeftjoinEntry(); |
| 753 | entry.term=leftTerm; |
| 754 | term.getRelatedLeftTerms().add(entry); |
| 755 | Expression joinExpr = new Expression(); |
| 756 | joinExpr.add(new ReferenceTerm(term,"persistence_id")); |
| 757 | joinExpr.add("="); |
| 758 | joinExpr.add(new ReferenceTerm(leftTerm,"persistence_id")); |
| 759 | entry.expression=joinExpr; |
| 760 | } |
| 761 | } |
| 762 | } |
| 763 | } |
| 764 | |
| 765 | /** |
| 766 | * Get the left table terms, which will contained in the select. These are all |
| 767 | * super-, and sub-classes of the specified term. This method can be used on the |
| 768 | * selected term, which has to include all of these to return all relevant attributes. |
| 769 | * Note: all related classes will be included in the allocatedSuperTerms attribute, |
| 770 | * which is not correct, since it contains subtables too. |
| 771 | */ |
| 772 | private void fixLeftTableTerms(SpecifiedTableTerm term, SymbolTableEntry mainEntry) |
| 773 | { |
| 774 | // Calculate left table terms |
| 775 | List relatedClassEntries = classTracker.getRelatedClassEntries(mainEntry.classInfo.getSourceEntry()); |
| 776 | if ( logger.isDebugEnabled() ) |
| 777 | logger.debug("found related classes: "+relatedClassEntries+", to class info: "+mainEntry.classInfo); |
| 778 | for ( int i=0; i<relatedClassEntries.size(); i++ ) |
| 779 | { |
| 780 | ClassEntry relatedClassEntry = (ClassEntry) relatedClassEntries.get(i); |
| 781 | ClassInfo relatedClassInfo = classTracker.getClassInfo(relatedClassEntry); |
| 782 | if ( logger.isDebugEnabled() ) |
| 783 | logger.debug("found left joined class: "+relatedClassEntry+", class info: "+relatedClassInfo); |
| 784 | if ( relatedClassInfo == null ) |
| 785 | throw new ParserException(ParserException.ABORT,"object class not found for loading: '"+relatedClassEntry+"'"); |
| 786 | // Create the term and add to mainterm |
| 787 | TableTerm leftTerm = new TableTerm(schemaManager.getTableName(relatedClassEntry),null); |
| 788 | if ( logger.isDebugEnabled() ) |
| 789 | logger.debug("adding left term to: "+mainEntry.specifiedTerm+", left term: "+leftTerm); |
| 790 | mainEntry.termList.add(leftTerm); |
| 791 | mainEntry.allocatedSuperTerms.add(leftTerm); |
| 792 | // Add left join entry to the specified term |
| 793 | SpecifiedTableTerm.LeftjoinEntry entry = new SpecifiedTableTerm.LeftjoinEntry(); |
| 794 | mainEntry.specifiedTerm.getRelatedLeftTerms().add(entry); |
| 795 | entry.term=leftTerm; |
| 796 | Expression joinExpr = new Expression(); |
| 797 | entry.expression=joinExpr; |
| 798 | ReferenceTerm toTerm = new ReferenceTerm(term,"persistence_id"); |
| 799 | mainEntry.termList.add(toTerm); |
| 800 | joinExpr.add(toTerm); |
| 801 | joinExpr.add("="); |
| 802 | ReferenceTerm valueTerm = new ReferenceTerm(leftTerm,"persistence_id"); |
| 803 | mainEntry.termList.add(valueTerm); |
| 804 | } |
| 805 | } |
| 806 | |
| 807 | /** |
| 808 | * Add date constraint expressions. |
| 809 | */ |
| 810 | private Expression fixDateConstraints(Collection localAllTableTerms, Expression expr, TimeControl timeControl) |
| 811 | { |
| 812 | if ( logger.isDebugEnabled() ) |
| 813 | logger.debug("fixing date constraints for tables: "+localAllTableTerms); |
| 814 | Expression dateExpression = new Expression(); |
| 815 | // Generate date constraints for all tables which are not left-join tables |
| 816 | Iterator tablesIterator = localAllTableTerms.iterator(); |
| 817 | while ( tablesIterator.hasNext() ) |
| 818 | { |
| 819 | SpecifiedTableTerm term = (SpecifiedTableTerm) tablesIterator.next(); |
| 820 | // Add to date constraints |
| 821 | if ( logger.isDebugEnabled() ) |
| 822 | logger.debug("adding date constraint to term: "+term); |
| 823 | if ( dateExpression.size() != 0 ) |
| 824 | dateExpression.add("and"); |
| 825 | timeControl.apply(dateExpression,term); |
| 826 | // Fix term's left join expressions too |
| 827 | for ( int o=0; o<term.getRelatedLeftTerms().size(); o++ ) |
| 828 | { |
| 829 | SpecifiedTableTerm.LeftjoinEntry entry = (SpecifiedTableTerm.LeftjoinEntry) term.getRelatedLeftTerms().get(o); |
| 830 | entry.expression.add("and"); |
| 831 | timeControl.apply(entry.expression,entry.term); |
| 832 | if ( logger.isDebugEnabled() ) |
| 833 | logger.debug("adding date constraint to left term: "+entry.term); |
| 834 | } |
| 835 | for ( int o=0; o<term.getReferencedLeftTerms().size(); o++ ) |
| 836 | { |
| 837 | SpecifiedTableTerm.LeftjoinEntry entry = (SpecifiedTableTerm.LeftjoinEntry) term.getReferencedLeftTerms().get(o); |
| 838 | entry.expression.add("and"); |
| 839 | timeControl.apply(entry.expression,entry.term); |
| 840 | if ( logger.isDebugEnabled() ) |
| 841 | logger.debug("adding date constraint to left join term term: "+entry.term); |
| 842 | } |
| 843 | } |
| 844 | // Return |
| 845 | Expression result = expr; |
| 846 | if ( dateExpression.size() != 0 ) |
| 847 | { |
| 848 | if ( (result!=null) && (result.size() != 0) ) |
| 849 | { |
| 850 | result = new Expression(); |
| 851 | result.add(dateExpression); |
| 852 | result.add("and"); |
| 853 | result.add(expr); |
| 854 | } else { |
| 855 | result = dateExpression; |
| 856 | } |
| 857 | } |
| 858 | // Return |
| 859 | return result; |
| 860 | } |
| 861 | |
| 862 | private boolean validViewSelectTables(QueryStatement stmt) |
| 863 | { |
| 864 | if ( stmt.getMode() == QueryStatement.MODE_FIND ) |
| 865 | return true; // Valid, because it is in find mode |
| 866 | // Check whether all selected terms are storable |
| 867 | for ( int i=0; i<stmt.getSelectTerms().size(); i++ ) |
| 868 | { |
| 869 | TableTerm term = (TableTerm) stmt.getSelectTerms().get(i); |
| 870 | ClassInfo info = classTracker.getClassInfo(schemaManager.getClassEntry(term.getTableName())); |
| 871 | if ( ! info.isStorable() ) |
| 872 | return false; |
| 873 | } |
| 874 | return true; |
| 875 | } |
| 876 | |
| 877 | private void fixNonstorableTerms(List selectTerms, Expression expr) |
| 878 | { |
| 879 | if ( logger.isDebugEnabled() ) |
| 880 | logger.debug("fixing nonstorable terms in expression: "+expr); |
| 881 | // Go through all terms, and those which are not storable and |
| 882 | // not selected should cause an error. This is because since there is |
| 883 | // no ids table, we can not determine what to write in the place of |
| 884 | // these terms. On some occasions, it would be possible to substitute |
| 885 | // them, but not generally. |
| 886 | // Note: this didn't work correctly with ids table either, because |
| 887 | // the time control was not used in these cases! (On the abstract |
| 888 | // object) |
| 889 | if ( expr == null ) |
| 890 | return; |
| 891 | for ( int i=0; i<expr.size(); i++ ) |
| 892 | { |
| 893 | Object value = expr.get(i); |
| 894 | if ( value instanceof Expression ) |
| 895 | { |
| 896 | fixNonstorableTerms(selectTerms,(Expression) value); |
| 897 | } else if ( value instanceof TableTerm ) { |
| 898 | // Found a table term |
| 899 | TableTerm term = (TableTerm) value; |
| 900 | ClassInfo info = classTracker.getClassInfo(schemaManager.getClassEntry(term.getTableName())); |
| 901 | if ( info == null ) |
| 902 | continue; // No worries, most likely an internal table |
| 903 | ClassEntry entry = info.getSourceEntry(); |
| 904 | if ( (!selectTerms.contains(term)) && (!info.isStorable()) ) |
| 905 | { |
| 906 | // A non-storable entry's term found which is not selected, |
| 907 | // so throw error. |
| 908 | throw new ParserException(ParserException.ABORT,"found non-storable, non-selected term: '"+term+"'. "+ |
| 909 | "Non-storable classes can not be used if they are not the subject of the select."); |
| 910 | } |
| 911 | } |
| 912 | } |
| 913 | } |
| 914 | |
| 915 | private void fixOrderBys(QueryStatement stmt) |
| 916 | { |
| 917 | if ( stmt.getOrderByList() == null ) |
| 918 | stmt.setOrderByList(new ArrayList()); |
| 919 | if ( stmt.getMode() == QueryStatement.MODE_FIND ) |
| 920 | { |
| 921 | // Find mode: add default order by on persistence_id |
| 922 | TableTerm mainTerm = (TableTerm) stmt.getSelectTerms().get(0); |
| 923 | OrderBy orderBy = new OrderBy(new ReferenceTerm(mainTerm,"persistence_id"),OrderBy.ASCENDING); |
| 924 | if ( ! stmt.getOrderByList().contains(orderBy) ) |
| 925 | stmt.getOrderByList().add(orderBy); |
| 926 | } else { |
| 927 | // View mode: add all view attributes as order bys, so |
| 928 | // listing becomes unambigous. |
| 929 | for ( int i=0; i<stmt.getSelectTerms().size(); i++ ) |
| 930 | { |
| 931 | ReferenceTerm term = (ReferenceTerm) stmt.getSelectTerms().get(i); |
| 932 | OrderBy orderBy = new OrderBy(term,OrderBy.ASCENDING); |
| 933 | if ( ! stmt.getOrderByList().contains(orderBy) ) |
| 934 | stmt.getOrderByList().add(orderBy); |
| 935 | } |
| 936 | } |
| 937 | if ( logger.isDebugEnabled() ) |
| 938 | logger.debug("fixed order by list to: "+stmt.getOrderByList()); |
| 939 | } |
| 940 | |
| 941 | private void validateAggregateClauses(QueryStatement stmt) |
| 942 | { |
| 943 | // If this statement has "having" and "group by" clauses |
| 944 | // then it must be a "view" query |
| 945 | if ( (((stmt.getHavingExpression()!=null) && (!stmt.getHavingExpression().isEmpty())) || |
| 946 | ((stmt.getGroupByList()!=null) && (!stmt.getGroupByList().isEmpty()))) && |
| 947 | stmt.getMode() == QueryStatement.MODE_FIND ) |
| 948 | throw new ParserException(ParserException.ABORT,"query has 'group by' or 'having' clauses as a 'find' query, only 'view' queries can be aggregate"); |
| 949 | // If the group by list is not empty, then all references in the group by list |
| 950 | // must be a non-aggregated term in the select list, and all the others in the |
| 951 | // select list must be aggregated |
| 952 | if ( (stmt.getGroupByList()!=null) && (! stmt.getGroupByList().isEmpty()) ) |
| 953 | { |
| 954 | // Make a copy |
| 955 | List<ReferenceTerm> selectTerms = new ArrayList<ReferenceTerm>(stmt.getSelectTerms()); |
| 956 | if ( logger.isDebugEnabled() ) |
| 957 | logger.debug("checking select terms for aggregation with group by clause: "+selectTerms); |
| 958 | // Check those in the group by list |
| 959 | for ( ReferenceTerm term : (List<ReferenceTerm>)stmt.getGroupByList() ) |
| 960 | { |
| 961 | // Search for this term in the select list. If found |
| 962 | // it must be non-aggregate |
| 963 | Iterator<ReferenceTerm> selectTermsIterator = selectTerms.iterator(); |
| 964 | while ( selectTermsIterator.hasNext() ) |
| 965 | { |
| 966 | ReferenceTerm selectTerm = selectTermsIterator.next(); |
| 967 | if ( (term.equals(selectTerm)) && (term.getColumnName().equals(selectTerm.getColumnName())) ) |
| 968 | { |
| 969 | if ( selectTerm.getFunction() != null ) |
| 970 | throw new ParserException(ParserException.ABORT,"term '"+selectTerm+ |
| 971 | "' must not have a function on it, since it's also in the 'group by' clause"); |
| 972 | else |
| 973 | selectTermsIterator.remove(); |
| 974 | } |
| 975 | } |
| 976 | } |
| 977 | // Check the rest, that they are aggregate |
| 978 | for ( ReferenceTerm selectTerm : selectTerms ) |
| 979 | { |
| 980 | if ( (selectTerm.getFunction()==null) || |
| 981 | (! (selectTerm.getFunction() instanceof AggregateFunction)) ) |
| 982 | throw new ParserException(ParserException.ABORT,"select term '"+selectTerm+"' must be aggregate, since it's not in the 'group by' clause"); |
| 983 | } |
| 984 | } else { |
| 985 | // If there is no group by list, then make sure all selected terms are non-aggregate |
| 986 | if ( logger.isDebugEnabled() ) |
| 987 | logger.debug("checking selected terms for aggregated ones, there is no group by statement"); |
| 988 | boolean foundNonAggregate = false; |
| 989 | boolean foundAggregate = false; |
| 990 | for ( Object selectTerm : stmt.getSelectTerms() ) |
| 991 | { |
| 992 | if ( ! (selectTerm instanceof ReferenceTerm) ) |
| 993 | continue; |
| 994 | ReferenceTerm selectRefTerm = (ReferenceTerm) selectTerm; |
| 995 | if ( (selectRefTerm.getFunction()!=null) && |
| 996 | (selectRefTerm.getFunction() instanceof AggregateFunction) ) |
| 997 | foundAggregate = true; |
| 998 | else |
| 999 | foundNonAggregate = true; |
| 1000 | } |
| 1001 | if ( (foundAggregate) && (foundNonAggregate) ) |
| 1002 | throw new ParserException(ParserException.ABORT,"there are aggregate and non-aggregate columns in the selection, but there is no group-by clause"); |
| 1003 | } |
| 1004 | // Validate having clause. It must contain only reference terms that are either in the |
| 1005 | // group by clause and not aggregated, or aggregated. |
| 1006 | if ( (stmt.getHavingExpression()!=null) && (! stmt.getHavingExpression().isEmpty()) ) |
| 1007 | { |
| 1008 | List openTerms = new ArrayList(); |
| 1009 | openTerms.addAll(stmt.getHavingExpression()); |
| 1010 | while ( ! openTerms.isEmpty() ) |
| 1011 | { |
| 1012 | // Get one |
| 1013 | Object term = openTerms.remove(0); |
| 1014 | // Handle |
| 1015 | if ( term instanceof Expression ) |
| 1016 | openTerms.addAll((Expression) term); |
| 1017 | if ( term instanceof ReferenceTerm ) |
| 1018 | { |
| 1019 | ReferenceTerm refTerm = (ReferenceTerm) term; |
| 1020 | if ( (refTerm.getFunction() == null) || (!(refTerm.getFunction() instanceof AggregateFunction)) ) |
| 1021 | { |
| 1022 | // This is not an aggregated reference term, therefore it must be in the |
| 1023 | // group by clause |
| 1024 | boolean foundTerm = false; |
| 1025 | for ( ReferenceTerm groupTerm : (List<ReferenceTerm>) stmt.getGroupByList() ) |
| 1026 | if ( (groupTerm.equals(refTerm)) && (groupTerm.getColumnName().equals(refTerm.getColumnName())) ) |
| 1027 | foundTerm = true; |
| 1028 | if ( ! foundTerm ) |
| 1029 | throw new ParserException(ParserException.ABORT,"term in having clause '"+term+ |
| 1030 | "' was not aggregated and also not part of the 'group by' clause"); |
| 1031 | } |
| 1032 | } |
| 1033 | } |
| 1034 | } |
| 1035 | } |
| 1036 | |
| 1037 | /** |
| 1038 | * Generate the final statements. |
| 1039 | */ |
| 1040 | public QueryStatementList generate(QueryStatement stmt) |
| 1041 | { |
| 1042 | Expression expr = stmt.getQueryExpression(); |
| 1043 | if ( expr == null ) |
| 1044 | expr = new Expression(); |
| 1045 | if ( logger.isDebugEnabled() ) |
| 1046 | { |
| 1047 | logger.debug("symbol table before finalizing: "+symbolTable); |
| 1048 | logger.debug("expression before finalizing: "+expr); |
| 1049 | } |
| 1050 | // Add default order bys |
| 1051 | fixOrderBys(stmt); |
| 1052 | // Check whether 'contains' operator is negated (this is not allowed) |
| 1053 | // if it does not, then substitute with '=' sign. |
| 1054 | if ( fixContainsNegated(expr,false) ) |
| 1055 | throw new ParserException(ParserException.ABORT,"'contains' operator is negated, this may not mean what you think it means, so it's disallowed."); |
| 1056 | // If this is a view select, check whether all selected terms |
| 1057 | // are storable. |
| 1058 | if ( ! validViewSelectTables(stmt) ) |
| 1059 | throw new ParserException(ParserException.ABORT,"view selects can not contain non-storable terms "+ |
| 1060 | "(ie. interface types and abstract objects as selected terms)."); |
| 1061 | // Generate all the specified terms the query will use. Note that |
| 1062 | // leftterms (neither super-,sub-classes nor referenced terms) |
| 1063 | // Note: at this point, the tables have no aliases yet, so keep'em |
| 1064 | // in list not set. |
| 1065 | List allTableTerms = new ArrayList(); |
| 1066 | Iterator entryIterator = symbolTable.values().iterator(); |
| 1067 | while ( entryIterator.hasNext() ) |
| 1068 | { |
| 1069 | SymbolTableEntry entry = (SymbolTableEntry) entryIterator.next(); |
| 1070 | SpecifiedTableTerm term = entry.specifiedTerm; |
| 1071 | if ( ! entry.leftTerm ) |
| 1072 | allTableTerms.add(term); |
| 1073 | } |
| 1074 | // Generate aliases and connector expressions |
| 1075 | stmt.setQueryExpression(fixAutomaticTerms(expr)); |
| 1076 | // Check sanity of aggregate parts |
| 1077 | validateAggregateClauses(stmt); |
| 1078 | // Fix all non-storable terms that are used in the expression but |
| 1079 | // not selected. |
| 1080 | fixNonstorableTerms(stmt.getSelectTerms(),stmt.getQueryExpression()); |
| 1081 | // Now generate all the root queries for this query. |
| 1082 | // View selects can not contain non-storable terms, and find |
| 1083 | // selects can only contain 1 main term, so get the roots for |
| 1084 | // this main term, and iterate over it's roots. |
| 1085 | QueryStatementList stmts = new QueryStatementList(); |
| 1086 | SpecifiedTableTerm selectTerm = mainTerm; |
| 1087 | ClassEntry selectEntry = schemaManager.getClassEntry(selectTerm.getTableName()); |
| 1088 | List roots = classTracker.getStorableRootClassEntries(selectEntry); |
| 1089 | // Prepare local lists, these will be used later |
| 1090 | Set localAllTableTermsCore = new HashSet(allTableTerms); |
| 1091 | localAllTableTermsCore.remove(selectTerm); |
| 1092 | if ( logger.isDebugEnabled() ) |
| 1093 | logger.debug("determined roots for "+selectEntry+": "+roots); |
| 1094 | // Now generate the statements themselves |
| 1095 | for ( int r=0; r<roots.size(); r++ ) |
| 1096 | { |
| 1097 | // Clone statement |
| 1098 | QueryStatement subStmt = stmt.deepCopy(); |
| 1099 | Set localAllTableTerms = new HashSet(localAllTableTermsCore); |
| 1100 | // The counter determines with which root to replace select term with |
| 1101 | ClassEntry rootEntry = (ClassEntry) roots.get(r); |
| 1102 | if ( logger.isDebugEnabled() ) |
| 1103 | logger.debug("entry "+selectEntry+" is to be replaced with root entry: "+rootEntry); |
| 1104 | ClassInfo rootInfo = classTracker.getClassInfo(rootEntry); |
| 1105 | SpecifiedTableTerm rootTerm = new SpecifiedTableTerm(schemaManager.getTableName(rootEntry), |
| 1106 | selectTerm.getAlias()); |
| 1107 | rootTerm.setReferencedLeftTerms(selectTerm.getReferencedLeftTerms()); // Keep referenced terms |
| 1108 | fixLeftTableTerms(rootInfo,rootTerm,selectTerm.getRelatedLeftTerms()); // Add left table terms |
| 1109 | if ( logger.isDebugEnabled() ) |
| 1110 | logger.debug("entry "+selectEntry+" ("+selectTerm+") is replaced with root entry: "+rootEntry+" ("+rootTerm+")"); |
| 1111 | // Do replace operation in statement. This will replace |
| 1112 | // the term everywhere. |
| 1113 | subStmt.replace(selectTerm,rootTerm); |
| 1114 | // The specified tables are stored in the query itself |
| 1115 | localAllTableTerms.add(rootTerm); |
| 1116 | subStmt.setSpecifiedTerms(localAllTableTerms); |
| 1117 | if ( logger.isDebugEnabled() ) |
| 1118 | logger.debug("statement will have specified terms: "+localAllTableTerms); |
| 1119 | // Generate static representation (it is important, that this is |
| 1120 | // before date constraints are added |
| 1121 | subStmt.setStaticRepresentation(subStmt.getMode()+" "+ |
| 1122 | subStmt.getSelectTerms().toString()+" "+ |
| 1123 | subStmt.getSpecifiedTerms().toString()+" "+ |
| 1124 | (subStmt.getQueryExpression()!=null?subStmt.getQueryExpression().toString():"")+ |
| 1125 | (subStmt.getGroupByList()!=null?subStmt.getGroupByList().toString():"")+ |
| 1126 | (subStmt.getHavingExpression()!=null?subStmt.getHavingExpression().toString():"")+ |
| 1127 | (subStmt.getOrderByList()!=null?subStmt.getOrderByList().toString():"")); |
| 1128 | // Generate date constraints |
| 1129 | subStmt.setQueryExpression( |
| 1130 | fixDateConstraints(localAllTableTerms, |
| 1131 | subStmt.getQueryExpression(), subStmt.getTimeControl())); |
| 1132 | // Debug |
| 1133 | if ( logger.isDebugEnabled() ) |
| 1134 | logger.debug("generated statement, selected: "+subStmt.getSelectTerms()+ |
| 1135 | ", specified: "+subStmt.getSpecifiedTerms()+ |
| 1136 | ", expression: "+subStmt.getQueryExpression()+ |
| 1137 | ", group by: "+subStmt.getGroupByList()+ |
| 1138 | ", having: "+subStmt.getHavingExpression()+ |
| 1139 | ", order by: "+subStmt.getOrderByList()); |
| 1140 | // Insert complete query into result query list |
| 1141 | stmts.add(subStmt); |
| 1142 | } |
| 1143 | // Now generate the query which will return all table names |
| 1144 | if ( stmts.size() > 1 ) |
| 1145 | { |
| 1146 | // Clone statement |
| 1147 | QueryStatement subStmt = stmt.deepCopy(); |
| 1148 | // Set specified terms |
| 1149 | subStmt.getSpecifiedTerms().addAll(localAllTableTermsCore); |
| 1150 | // Generate static representation (it is important, that this is |
| 1151 | // before date constraints are added |
| 1152 | subStmt.setStaticRepresentation(subStmt.getMode()+" "+ |
| 1153 | subStmt.getSelectTerms().toString()+" "+ |
| 1154 | subStmt.getSpecifiedTerms().toString()+" "+ |
| 1155 | (subStmt.getQueryExpression()!=null?subStmt.getQueryExpression().toString():"")+ |
| 1156 | (subStmt.getOrderByList()!=null?subStmt.getOrderByList().toString():"")); |
| 1157 | // Generate date constraints on all but the main term |
| 1158 | subStmt.setQueryExpression( |
| 1159 | fixDateConstraints(localAllTableTermsCore, |
| 1160 | subStmt.getQueryExpression(), subStmt.getTimeControl())); |
| 1161 | if ( logger.isDebugEnabled() ) |
| 1162 | logger.debug("generated root statement, selected: "+subStmt.getSelectTerms()+ |
| 1163 | ", specified: "+subStmt.getSpecifiedTerms()+ |
| 1164 | ", expression: "+subStmt.getQueryExpression()+", order by: "+subStmt.getOrderByList()); |
| 1165 | // Add |
| 1166 | stmts.setRoot(subStmt); |
| 1167 | } |
| 1168 | return stmts; |
| 1169 | } |
| 1170 | |
| 1171 | public static class SymbolTableEntry |
| 1172 | { |
| 1173 | public static final int TYPE_HANDLED = 1; |
| 1174 | public static final int TYPE_OBJECT = 3; |
| 1175 | public static final int TYPE_PRIMITIVE = 4; |
| 1176 | |
| 1177 | public SpecifiedTableTerm specifiedTerm; |
| 1178 | // The term this entry represents, if any |
| 1179 | public String referenceColumn; |
| 1180 | // Used by handled types, definies the |
| 1181 | // reference column, if sub-specified. |
| 1182 | public boolean selected; // Whether this entry will be selected |
| 1183 | public List termList; // List of terms in which could not |
| 1184 | // be resolved yet |
| 1185 | public List allocatedSuperTerms; |
| 1186 | // The list of terms of superclasses |
| 1187 | // already allocated (used) |
| 1188 | public ClassInfo classInfo;// Class info if symbol is a table |
| 1189 | public boolean automatic; // Whether symbol should be automatically |
| 1190 | // generated |
| 1191 | public boolean leftTerm; // Whether this term is standalone, or |
| 1192 | // depending on another term |
| 1193 | public Expression expression; |
| 1194 | // The expression this entry generated |
| 1195 | public int type = TYPE_OBJECT; |
| 1196 | |
| 1197 | public SymbolTableEntry() |
| 1198 | { |
| 1199 | termList = new LinkedList(); |
| 1200 | allocatedSuperTerms = new LinkedList(); |
| 1201 | leftTerm=false; |
| 1202 | } |
| 1203 | |
| 1204 | public String toString() |
| 1205 | { |
| 1206 | return "[Symbol entry: "+specifiedTerm+", "+type+":"+automatic+":"+leftTerm+"]"; |
| 1207 | } |
| 1208 | } |
| 1209 | } |
| 1210 | |