1 /* 2 Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. 3 4 This program is free software; you can redistribute it and/or modify 5 it under the terms of the GNU General Public License, version 2.0, 6 as published by the Free Software Foundation. 7 8 This program is also distributed with certain software (including 9 but not limited to OpenSSL) that is licensed under separate terms, 10 as designated in a particular file or component or in included license 11 documentation. The authors of MySQL hereby grant you an additional 12 permission to link the program and your derivative works with the 13 separately licensed software that they have included with MySQL. 14 15 This program is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU General Public License, version 2.0, for more details. 19 20 You should have received a copy of the GNU General Public License 21 along with this program; if not, write to the Free Software 22 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 */ 24 25 package com.mysql.clusterj.core.query; 26 27 import com.mysql.clusterj.ClusterJUserException; 28 import com.mysql.clusterj.core.metadata.AbstractDomainFieldHandlerImpl; 29 30 import com.mysql.clusterj.core.query.PredicateImpl.ScanType; 31 import com.mysql.clusterj.core.spi.QueryExecutionContext; 32 import com.mysql.clusterj.core.store.Index; 33 import com.mysql.clusterj.core.store.IndexScanOperation; 34 import com.mysql.clusterj.core.store.Operation; 35 36 import com.mysql.clusterj.core.util.I18NHelper; 37 import com.mysql.clusterj.core.util.Logger; 38 import com.mysql.clusterj.core.util.LoggerFactoryService; 39 40 import java.util.ArrayList; 41 import java.util.List; 42 43 /** 44 * This class is responsible for deciding whether an index can be used 45 * for a specific query. An instance of this class is created to evaluate 46 * a query and to decide whether the index can be used in executing the 47 * query. An inner class represents each candidateColumn in the index. 48 * 49 * An instance of this class is created for each index for each query, and an instance of 50 * the candidate column for each column of the index. During execution of the query, 51 * the query terms are used to mark the candidate columns and associate candidate columns 52 * with each query term. Each query term might be associated with multiple candidate columns, 53 * one for each index containing the column referenced by the query term. 54 * 55 */ 56 public final class CandidateIndexImpl { 57 58 /** My message translator */ 59 static final I18NHelper local = I18NHelper.getInstance(CandidateIndexImpl.class); 60 61 /** My logger */ 62 static final Logger logger = LoggerFactoryService.getFactory() 63 .getInstance(CandidateIndexImpl.class); 64 65 private String className = "none"; 66 private Index storeIndex; 67 private String indexName = "none"; 68 private boolean unique; 69 private boolean multiRange = false; 70 private CandidateColumnImpl[] candidateColumns = null; 71 private ScanType scanType = PredicateImpl.ScanType.TABLE_SCAN; 72 private int fieldScore = 1; 73 protected int score = 0; 74 private boolean canBound = true; 75 CandidateIndexImpl( String className, Index storeIndex, boolean unique, AbstractDomainFieldHandlerImpl[] fields)76 public CandidateIndexImpl( 77 String className, Index storeIndex, boolean unique, AbstractDomainFieldHandlerImpl[] fields) { 78 if (logger.isDebugEnabled()) logger.debug("className: " + className 79 + " storeIndex: " + storeIndex.getName() 80 + " unique: " + Boolean.toString(unique) 81 + " fields: " + toString(fields)); 82 this.className = className; 83 this.storeIndex = storeIndex; 84 this.indexName = storeIndex.getName(); 85 this.unique = unique; 86 this.candidateColumns = new CandidateColumnImpl[fields.length]; 87 if (fields.length == 1) { 88 // for a single field with multiple columns, score the number of columns 89 this.fieldScore = fields[0].getColumnNames().length; 90 } 91 int i = 0; 92 for (AbstractDomainFieldHandlerImpl domainFieldHandler: fields) { 93 CandidateColumnImpl candidateColumn = new CandidateColumnImpl(domainFieldHandler); 94 candidateColumns[i++] = candidateColumn; 95 } 96 if (logger.isDebugEnabled()) logger.debug(toString()); 97 } 98 toString(AbstractDomainFieldHandlerImpl[] fields)99 private String toString(AbstractDomainFieldHandlerImpl[] fields) { 100 StringBuilder builder = new StringBuilder(); 101 char separator = '['; 102 for (AbstractDomainFieldHandlerImpl field: fields) { 103 builder.append(separator); 104 builder.append(field.getName()); 105 separator = ' '; 106 } 107 builder.append(']'); 108 return builder.toString(); 109 } 110 111 /** The CandidateIndexImpl used in cases of no where clause. */ 112 static CandidateIndexImpl indexForNullWhereClause = new CandidateIndexImpl(); 113 114 /** The accessor for the no where clause candidate index. */ getIndexForNullWhereClause()115 public static CandidateIndexImpl getIndexForNullWhereClause() { 116 return indexForNullWhereClause; 117 } 118 119 /** The CandidateIndexImpl used in cases of no where clause. */ CandidateIndexImpl()120 protected CandidateIndexImpl() { 121 // candidateColumns will be null if no usable columns in the index 122 } 123 124 @Override toString()125 public String toString() { 126 StringBuilder buffer = new StringBuilder(); 127 buffer.append("CandidateIndexImpl for class: "); 128 buffer.append(className); 129 buffer.append(" index: "); 130 buffer.append(indexName); 131 buffer.append(" unique: "); 132 buffer.append(unique); 133 if (candidateColumns != null) { 134 for (CandidateColumnImpl column:candidateColumns) { 135 buffer.append(" field: "); 136 buffer.append(column.domainFieldHandler.getName()); 137 } 138 } else { 139 buffer.append(" no fields."); 140 } 141 return buffer.toString(); 142 } 143 markLowerBound(int fieldNumber, PredicateImpl predicate, boolean strict)144 public void markLowerBound(int fieldNumber, PredicateImpl predicate, boolean strict) { 145 if (candidateColumns != null) { 146 candidateColumns[fieldNumber].markLowerBound(predicate, strict); 147 } 148 } 149 markUpperBound(int fieldNumber, PredicateImpl predicate, boolean strict)150 public void markUpperBound(int fieldNumber, PredicateImpl predicate, boolean strict) { 151 if (candidateColumns != null) { 152 candidateColumns[fieldNumber].markUpperBound(predicate, strict); 153 } 154 } 155 markEqualBound(int fieldNumber, PredicateImpl predicate)156 public void markEqualBound(int fieldNumber, PredicateImpl predicate) { 157 if (candidateColumns != null) { 158 candidateColumns[fieldNumber].markEqualBound(predicate); 159 } 160 } 161 markInBound(int fieldNumber, InPredicateImpl predicate)162 public void markInBound(int fieldNumber, InPredicateImpl predicate) { 163 if (candidateColumns != null) { 164 candidateColumns[fieldNumber].markInBound(predicate); 165 } 166 } 167 getIndexName()168 String getIndexName() { 169 return indexName; 170 } 171 172 CandidateColumnImpl lastLowerBoundColumn = null; 173 CandidateColumnImpl lastUpperBoundColumn = null; 174 175 /** Evaluate the suitability of this index for the query. An instance represents each 176 * index, with primary key and non-hash-key indexes having two instances, one for the 177 * unique index and one for the btree index for the same column(s). 178 * Unique indexes where all of the columns are compared equal return a score of 100. 179 * Btree indexes get one point for each query term that can be used (maximum of two 180 * points for each comparison). Greater than and less than get one point each. 181 * Equals and In get two points each. Once a gap is found in query terms for either 182 * upper or lower bound, processing for that bound stops. 183 * The last query term (candidate column) for each of the lower and upper bound is noted. 184 * The method is synchronized because the method modifies the state of the instance, 185 * which might be shared by multiple threads. 186 */ score()187 synchronized void score() { 188 score = 0; 189 if (candidateColumns == null) { 190 return; 191 } 192 boolean lowerBoundDone = false; 193 boolean upperBoundDone = false; 194 if (unique) { 195 // all columns need to have equal bound 196 for (CandidateColumnImpl column: candidateColumns) { 197 if (!(column.equalBound)) { 198 // not equal bound; can't use unique index 199 return; 200 } 201 } 202 if ("PRIMARY".equals(indexName)) { 203 scanType = PredicateImpl.ScanType.PRIMARY_KEY; 204 } else { 205 scanType = PredicateImpl.ScanType.UNIQUE_KEY; 206 } 207 score = 100; 208 return; 209 } else { 210 // range index 211 // leading columns need any kind of bound 212 // extra credit for equals 213 boolean firstColumn = true; 214 for (CandidateColumnImpl candidateColumn: candidateColumns) { 215 if ((candidateColumn.equalBound)) { 216 scanType = PredicateImpl.ScanType.INDEX_SCAN; 217 if (!lowerBoundDone) { 218 score += fieldScore; 219 lastLowerBoundColumn = candidateColumn; 220 } 221 if (!upperBoundDone) { 222 score += fieldScore; 223 lastUpperBoundColumn = candidateColumn; 224 } 225 } else if ((candidateColumn.inBound)) { 226 scanType = PredicateImpl.ScanType.INDEX_SCAN; 227 if (firstColumn) { 228 multiRange = true; 229 } 230 if (!lowerBoundDone) { 231 score += fieldScore; 232 lastLowerBoundColumn = candidateColumn; 233 } 234 if (!upperBoundDone) { 235 score += fieldScore; 236 lastUpperBoundColumn = candidateColumn; 237 } 238 } else if (!(lowerBoundDone && upperBoundDone)) { 239 // lower bound and upper bound are independent 240 boolean hasLowerBound = candidateColumn.hasLowerBound(); 241 boolean hasUpperBound = candidateColumn.hasUpperBound(); 242 // keep going until both upper and lower are done 243 if (hasLowerBound || hasUpperBound) { 244 scanType = PredicateImpl.ScanType.INDEX_SCAN; 245 } 246 if (!lowerBoundDone) { 247 if (hasLowerBound) { 248 score += fieldScore; 249 lastLowerBoundColumn = candidateColumn; 250 } else { 251 lowerBoundDone = true; 252 } 253 } 254 if (!upperBoundDone) { 255 if (hasUpperBound) { 256 score += fieldScore; 257 lastUpperBoundColumn = candidateColumn; 258 } else { 259 upperBoundDone = true; 260 } 261 } 262 if (lowerBoundDone && upperBoundDone) { 263 continue; 264 } 265 } 266 firstColumn = false; 267 } 268 if (lastLowerBoundColumn != null) { 269 lastLowerBoundColumn.markLastLowerBoundColumn(); 270 } 271 if (lastUpperBoundColumn != null) { 272 lastUpperBoundColumn.markLastUpperBoundColumn(); 273 } 274 } 275 return; 276 } 277 getScanType()278 public ScanType getScanType() { 279 return scanType; 280 } 281 282 /* No bound is complete yet */ 283 private final int BOUND_STATUS_NO_BOUND_DONE = 0; 284 /* The lower bound is complete */ 285 private final int BOUND_STATUS_LOWER_BOUND_DONE = 1; 286 /* The upper bound is complete */ 287 private final int BOUND_STATUS_UPPER_BOUND_DONE = 2; 288 /* Both bounds are complete */ 289 private final int BOUND_STATUS_BOTH_BOUNDS_DONE = 3; 290 291 /** Set bounds for the operation defined for this index. This index was chosen as 292 * the best index to use for the query. 293 * Each query term (candidate column) is used to set a bound. The bound depends on 294 * the type of query term, whether the term is the last term, and whether the 295 * bound type (upper or lower) has already been completely specified. 296 * Equal and In query terms can be used for an equal bound, a lower bound, or an upper 297 * bound. Strict bounds that are not the last bound are converted to non-strict bounds. 298 * In query terms are decomposed into multiple range bounds, one range for each 299 * value in the query term. 300 * @param context the query execution context, containing the parameter values 301 * @param op the index scan operation 302 */ operationSetBounds(QueryExecutionContext context, IndexScanOperation op)303 void operationSetBounds(QueryExecutionContext context, IndexScanOperation op) { 304 if (multiRange) { 305 // find how many query terms are inPredicates 306 List<Integer> parameterSizes = new ArrayList<Integer>(); 307 for (CandidateColumnImpl candidateColumn:candidateColumns) { 308 if (candidateColumn.hasInBound()) { 309 parameterSizes.add(candidateColumn.getParameterSize(context)); 310 } 311 } 312 if (parameterSizes.size() > 1) { 313 throw new ClusterJUserException(local.message("ERR_Too_Many_In_For_Index", indexName)); 314 } 315 // if only one column in the index, optimize 316 if (candidateColumns.length == 1) { 317 candidateColumns[0].operationSetAllBounds(context, op); 318 } else { 319 // set multiple bounds; one for each item in the parameter (context) 320 for (int parameterIndex = 0; parameterIndex < parameterSizes.get(0); ++parameterIndex) { 321 int boundStatus = BOUND_STATUS_NO_BOUND_DONE; 322 for (CandidateColumnImpl candidateColumn:candidateColumns) { 323 if (logger.isDetailEnabled()) logger.detail( 324 "parameterIndex: " + parameterIndex 325 + " boundStatus: " + boundStatus 326 + " candidateColumn: " + candidateColumn.domainFieldHandler.getName()); 327 // execute the bounds operation if anything left to do 328 if (boundStatus != BOUND_STATUS_BOTH_BOUNDS_DONE) { 329 boundStatus = candidateColumn.operationSetBounds(context, op, parameterIndex, boundStatus); 330 } 331 } 332 // after all columns are done, mark the end of bounds 333 op.endBound(parameterIndex); 334 } 335 } 336 } else { 337 // not multi-range 338 int boundStatus = BOUND_STATUS_NO_BOUND_DONE; 339 for (CandidateColumnImpl candidateColumn:candidateColumns) { 340 if (logger.isDetailEnabled()) logger.detail("boundStatus: " + boundStatus 341 + " candidateColumn: " + candidateColumn.domainFieldHandler.getName()); 342 // execute the bounds operation for each query term 343 if (boundStatus != BOUND_STATUS_BOTH_BOUNDS_DONE) { 344 boundStatus = candidateColumn.operationSetBounds(context, op, -1, boundStatus); 345 } 346 } 347 } 348 } 349 operationSetKeys(QueryExecutionContext context, Operation op)350 void operationSetKeys(QueryExecutionContext context, 351 Operation op) { 352 for (CandidateColumnImpl candidateColumn:candidateColumns) { 353 // execute the equal operation 354 candidateColumn.operationSetKeys(context, op); 355 } 356 } 357 358 /** 359 * This class represents one column in an index, and its corresponding query term(s). 360 * The column might be associated with a lower bound term, an upper bound term, 361 * an equal term, or an in term. 362 */ 363 class CandidateColumnImpl { 364 365 protected AbstractDomainFieldHandlerImpl domainFieldHandler; 366 protected PredicateImpl predicate; 367 protected PredicateImpl lowerBoundPredicate; 368 protected PredicateImpl upperBoundPredicate; 369 protected PredicateImpl equalPredicate; 370 protected InPredicateImpl inPredicate; 371 protected Boolean lowerBoundStrict = null; 372 protected Boolean upperBoundStrict = null; 373 protected boolean equalBound = false; 374 protected boolean inBound = false; 375 protected boolean lastLowerBoundColumn = false; 376 protected boolean lastUpperBoundColumn = false; 377 hasLowerBound()378 protected boolean hasLowerBound() { 379 return lowerBoundPredicate != null || equalPredicate != null || inPredicate != null; 380 } 381 382 /** Set all bounds in the operation, ending each bound with an end_of_bound. 383 * 384 * @param context the query context 385 * @param op the operation 386 */ operationSetAllBounds(QueryExecutionContext context, IndexScanOperation op)387 public void operationSetAllBounds(QueryExecutionContext context, IndexScanOperation op) { 388 inPredicate.operationSetAllBounds(context, op); 389 } 390 getParameterSize(QueryExecutionContext context)391 public int getParameterSize(QueryExecutionContext context) { 392 return inPredicate.getParameterSize(context); 393 } 394 hasUpperBound()395 protected boolean hasUpperBound() { 396 return upperBoundPredicate != null || equalPredicate != null || inPredicate != null; 397 } 398 hasInBound()399 protected boolean hasInBound() { 400 return inBound; 401 } 402 CandidateColumnImpl(AbstractDomainFieldHandlerImpl domainFieldHandler)403 private CandidateColumnImpl(AbstractDomainFieldHandlerImpl domainFieldHandler) { 404 this.domainFieldHandler = domainFieldHandler; 405 } 406 markLastLowerBoundColumn()407 private void markLastLowerBoundColumn() { 408 lastLowerBoundColumn = true; 409 } 410 markLastUpperBoundColumn()411 private void markLastUpperBoundColumn() { 412 lastUpperBoundColumn = true; 413 } 414 markLowerBound(PredicateImpl predicate, boolean strict)415 private void markLowerBound(PredicateImpl predicate, boolean strict) { 416 lowerBoundStrict = strict; 417 this.lowerBoundPredicate = predicate; 418 this.predicate = predicate; 419 } 420 markUpperBound(PredicateImpl predicate, boolean strict)421 private void markUpperBound(PredicateImpl predicate, boolean strict) { 422 upperBoundStrict = strict; 423 this.upperBoundPredicate = predicate; 424 this.predicate = predicate; 425 } 426 markEqualBound(PredicateImpl predicate)427 private void markEqualBound(PredicateImpl predicate) { 428 equalBound = true; 429 this.equalPredicate = predicate; 430 this.predicate = predicate; 431 } 432 markInBound(InPredicateImpl predicate)433 public void markInBound(InPredicateImpl predicate) { 434 inBound = true; 435 this.inPredicate = predicate; 436 this.predicate = predicate; 437 } 438 439 /** Set bounds into each predicate that has been defined. 440 * 441 * @param op the operation 442 * @param index for inPredicates, the index into the parameter 443 * @param boundStatus 444 */ operationSetBounds( QueryExecutionContext context, IndexScanOperation op, int index, int boundStatus)445 private int operationSetBounds( 446 QueryExecutionContext context, IndexScanOperation op, int index, int boundStatus) { 447 if (inPredicate != null && index == -1 448 || !canBound) { 449 // "in" predicate cannot be used to set bounds unless it is the first column in the index 450 // if index scan but no valid bounds to set skip bounds 451 return BOUND_STATUS_BOTH_BOUNDS_DONE; 452 } 453 454 int boundSet = PredicateImpl.NO_BOUND_SET; 455 456 if (logger.isDetailEnabled()) logger.detail("column: " + domainFieldHandler.getName() 457 + " boundStatus: " + boundStatus 458 + " lastLowerBoundColumn: " + lastLowerBoundColumn 459 + " lastUpperBoundColumn: " + lastUpperBoundColumn); 460 switch(boundStatus) { 461 case BOUND_STATUS_BOTH_BOUNDS_DONE: 462 // cannot set either lower or upper bound 463 return BOUND_STATUS_BOTH_BOUNDS_DONE; 464 case BOUND_STATUS_NO_BOUND_DONE: 465 // can set either/both lower or upper bound 466 if (equalPredicate != null) { 467 boundSet |= equalPredicate.operationSetBounds(context, op, true); 468 } 469 if (inPredicate != null) { 470 boundSet |= inPredicate.operationSetBound(context, op, index, true); 471 } 472 if (lowerBoundPredicate != null) { 473 boundSet |= lowerBoundPredicate.operationSetLowerBound(context, op, lastLowerBoundColumn); 474 } 475 if (upperBoundPredicate != null) { 476 boundSet |= upperBoundPredicate.operationSetUpperBound(context, op, lastUpperBoundColumn); 477 } 478 break; 479 case BOUND_STATUS_LOWER_BOUND_DONE: 480 // cannot set lower, only upper bound 481 if (equalPredicate != null) { 482 boundSet |= equalPredicate.operationSetUpperBound(context, op, lastUpperBoundColumn); 483 } 484 if (inPredicate != null) { 485 boundSet |= inPredicate.operationSetUpperBound(context, op, index); 486 } 487 if (upperBoundPredicate != null) { 488 boundSet |= upperBoundPredicate.operationSetUpperBound(context, op, lastUpperBoundColumn); 489 } 490 break; 491 case BOUND_STATUS_UPPER_BOUND_DONE: 492 // cannot set upper, only lower bound 493 if (equalPredicate != null) { 494 boundSet |= equalPredicate.operationSetLowerBound(context, op, lastLowerBoundColumn); 495 } 496 if (inPredicate != null) { 497 boundSet |= inPredicate.operationSetLowerBound(context, op, index); 498 } 499 if (lowerBoundPredicate != null) { 500 boundSet |= lowerBoundPredicate.operationSetLowerBound(context, op, lastLowerBoundColumn); 501 } 502 break; 503 } 504 if (0 == (boundSet & PredicateImpl.LOWER_BOUND_SET)) { 505 // didn't set lower bound 506 boundStatus |= BOUND_STATUS_LOWER_BOUND_DONE; 507 } 508 509 if (0 == (boundSet & PredicateImpl.UPPER_BOUND_SET)) { 510 // didn't set upper bound 511 boundStatus |= BOUND_STATUS_UPPER_BOUND_DONE; 512 } 513 514 return boundStatus; 515 } 516 operationSetKeys(QueryExecutionContext context, Operation op)517 private void operationSetKeys(QueryExecutionContext context, Operation op) { 518 equalPredicate.operationEqual(context, op); 519 } 520 521 } 522 523 /** Determine whether this index supports exactly the number of conditions. 524 * For ordered indexes, any number of conditions are supported via filters. 525 * For hash indexes, only the number of columns in the index are supported. 526 * @param numberOfConditions the number of conditions in the query predicate 527 * @return if this index supports exactly the number of conditions 528 */ supportsConditionsOfLength(int numberOfConditions)529 public boolean supportsConditionsOfLength(int numberOfConditions) { 530 if (unique) { 531 return numberOfConditions == candidateColumns.length; 532 } else { 533 return true; 534 } 535 } 536 getStoreIndex()537 public Index getStoreIndex() { 538 return storeIndex; 539 } 540 getScore()541 public int getScore() { 542 return score; 543 } 544 isMultiRange()545 public boolean isMultiRange() { 546 return multiRange; 547 } 548 isUnique()549 public boolean isUnique() { 550 return unique; 551 } 552 553 /** Is this index usable in the current context? 554 * If a primary or unique index, all parameters must be non-null. 555 * If a btree index, the parameter for the first comparison must be non-null. 556 * If ordering is specified, the ordering fields must appear in the proper position in the index. 557 * <ul><li>Returns -1 if this index is unusable. 558 * </li><li>Returns 0 if this index is usable but has no filtering terms 559 * </li><li>Returns 1 if this index is usable and has at least one usable filtering term 560 * </li></ul> 561 * @param context the query execution context 562 * @param orderingFields the fields in the ordering 563 * @return the usability of this index 564 */ isUsable(QueryExecutionContext context, String[] orderingFields)565 public int isUsable(QueryExecutionContext context, String[] orderingFields) { 566 boolean ordering = orderingFields != null; 567 if (ordering && !containsAllOrderingFields(orderingFields)) { 568 return -1; 569 } 570 571 // ordering is ok; unique indexes have to have no null parameters 572 if (unique && score > 0) { 573 return context.hasNoNullParameters()?1:-1; 574 } else { 575 // index scan; the first parameter must not be null 576 if (candidateColumns == null) { 577 // this is a dummy index for "no where clause" 578 canBound = false; 579 } else { 580 CandidateColumnImpl candidateColumn = candidateColumns[0]; 581 PredicateImpl predicate = candidateColumn.predicate; 582 canBound = predicate != null && predicate.isUsable(context); 583 } 584 // if first parameter is null, can scan but not bound 585 if (canBound) { 586 if (logger.isDebugEnabled()) logger.debug("for " + indexName + " canBound true -> returns 1"); 587 scanType = PredicateImpl.ScanType.INDEX_SCAN; 588 return 1; 589 } else { 590 if (ordering) { 591 if (logger.isDebugEnabled()) logger.debug("for " + indexName + " canBound false -> returns 0"); 592 scanType = PredicateImpl.ScanType.INDEX_SCAN; 593 return 0; 594 } else { 595 if (logger.isDebugEnabled()) logger.debug("for " + indexName + " canBound false -> returns -1"); 596 return -1; 597 } 598 } 599 } 600 } 601 602 /** Does this index contain all ordering fields? 603 * 604 * @param orderingFields the ordering fields 605 * @return true if this ordered index contains all ordering fields in the proper position with no gaps 606 */ containsAllOrderingFields(String[] orderingFields)607 public boolean containsAllOrderingFields(String[] orderingFields) { 608 if (isUnique()) { 609 return false; 610 } 611 int candidateColumnIndex = 0; 612 if (orderingFields != null) { 613 for (String orderingField: orderingFields) { 614 if (candidateColumnIndex >= candidateColumns.length) { 615 // too many columns in orderingFields for this index 616 if (logger.isDebugEnabled()) logger.debug("Index " + indexName + " cannot be used because " 617 + orderingField + " is not part of this index."); 618 return false; 619 } 620 // each ordering field must correspond in order to the index fields 621 CandidateColumnImpl candidateColumn = candidateColumns[candidateColumnIndex++]; 622 if (!orderingField.equals(candidateColumn.domainFieldHandler.getName())) { 623 // the ordering field is not in the proper position in this candidate index 624 if (logger.isDebugEnabled()) { 625 logger.debug("Index " + indexName + " cannot be used because CandidateColumn " 626 + candidateColumn.domainFieldHandler.getName() + " does not match " + orderingField); 627 } 628 return false; 629 } 630 } 631 if (logger.isDebugEnabled()) { 632 logger.debug("CandidateIndexImpl.containsAllOrderingFields found possible index (unique: " 633 + unique + ") " + indexName); 634 } 635 scanType = PredicateImpl.ScanType.INDEX_SCAN; 636 return true; 637 } 638 return false; 639 } 640 641 } 642