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