2 Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; version 2 of the License.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 package com.mysql.clusterj.core.query;
20 import com.mysql.clusterj.ClusterJUserException;
21 import com.mysql.clusterj.core.metadata.AbstractDomainFieldHandlerImpl;
23 import com.mysql.clusterj.core.query.PredicateImpl.ScanType;
24 import com.mysql.clusterj.core.spi.QueryExecutionContext;
25 import com.mysql.clusterj.core.store.Index;
26 import com.mysql.clusterj.core.store.IndexScanOperation;
27 import com.mysql.clusterj.core.store.Operation;
29 import com.mysql.clusterj.core.util.I18NHelper;
30 import com.mysql.clusterj.core.util.Logger;
31 import com.mysql.clusterj.core.util.LoggerFactoryService;
33 import java.util.ArrayList;
34 import java.util.List;
37 * This class is responsible for deciding whether an index can be used
38 * for a specific query. An instance of this class is created to evaluate
39 * a query and to decide whether the index can be used in executing the
40 * query. An inner class represents each candidateColumn in the index.
42 * An instance of this class is created for each index for each query, and an instance of
43 * the candidate column for each column of the index. During execution of the query,
44 * the query terms are used to mark the candidate columns and associate candidate columns
45 * with each query term. Each query term might be associated with multiple candidate columns,
46 * one for each index containing the column referenced by the query term.
49 public class CandidateIndexImpl {
51 /** My message translator */
52 static final I18NHelper local = I18NHelper.getInstance(CandidateIndexImpl.class);
55 static final Logger logger = LoggerFactoryService.getFactory()
56 .getInstance(CandidateIndexImpl.class);
58 private String className = "none";
59 private Index storeIndex;
60 private String indexName = "none";
61 private boolean unique;
62 private boolean multiRange = false;
63 private CandidateColumnImpl[] candidateColumns = null;
64 private ScanType scanType = PredicateImpl.ScanType.TABLE_SCAN;
65 private int fieldScore = 1;
67 public CandidateIndexImpl(
68 String className, Index storeIndex, boolean unique, AbstractDomainFieldHandlerImpl[] fields) {
69 if (logger.isDebugEnabled()) logger.debug("className: " + className
70 + " storeIndex: " + storeIndex.getName()
71 + " unique: " + Boolean.toString(unique)
72 + " fields: " + toString(fields));
73 this.className = className;
74 this.storeIndex = storeIndex;
75 this.indexName = storeIndex.getName();
77 this.candidateColumns = new CandidateColumnImpl[fields.length];
78 if (fields.length == 1) {
79 // for a single field with multiple columns, score the number of columns
80 this.fieldScore = fields[0].getColumnNames().length;
83 for (AbstractDomainFieldHandlerImpl domainFieldHandler: fields) {
84 CandidateColumnImpl candidateColumn = new CandidateColumnImpl(domainFieldHandler);
85 candidateColumns[i++] = candidateColumn;
87 if (logger.isDebugEnabled()) logger.debug(toString());
90 private String toString(AbstractDomainFieldHandlerImpl[] fields) {
91 StringBuilder builder = new StringBuilder();
93 for (AbstractDomainFieldHandlerImpl field: fields) {
94 builder.append(separator);
95 builder.append(field.getName());
99 return builder.toString();
102 /** The CandidateIndexImpl used in cases of no where clause. */
103 static CandidateIndexImpl indexForNullWhereClause = new CandidateIndexImpl();
105 /** The accessor for the no where clause candidate index. */
106 public static CandidateIndexImpl getIndexForNullWhereClause() {
107 return indexForNullWhereClause;
110 /** The CandidateIndexImpl used in cases of no where clause. */
111 protected CandidateIndexImpl() {
112 // candidateColumns will be null if no usable columns in the index
116 public String toString() {
117 StringBuffer buffer = new StringBuffer();
118 buffer.append("CandidateIndexImpl for class: ");
119 buffer.append(className);
120 buffer.append(" index: ");
121 buffer.append(indexName);
122 buffer.append(" unique: ");
123 buffer.append(unique);
124 if (candidateColumns != null) {
125 for (CandidateColumnImpl column:candidateColumns) {
126 buffer.append(" field: ");
127 buffer.append(column.domainFieldHandler.getName());
130 buffer.append(" no fields.");
132 return buffer.toString();
135 public void markLowerBound(int fieldNumber, PredicateImpl predicate, boolean strict) {
136 if (candidateColumns != null) {
137 candidateColumns[fieldNumber].markLowerBound(predicate, strict);
141 public void markUpperBound(int fieldNumber, PredicateImpl predicate, boolean strict) {
142 if (candidateColumns != null) {
143 candidateColumns[fieldNumber].markUpperBound(predicate, strict);
147 public void markEqualBound(int fieldNumber, PredicateImpl predicate) {
148 if (candidateColumns != null) {
149 candidateColumns[fieldNumber].markEqualBound(predicate);
153 public void markInBound(int fieldNumber, InPredicateImpl predicate) {
154 if (candidateColumns != null) {
155 candidateColumns[fieldNumber].markInBound(predicate);
159 String getIndexName() {
163 CandidateColumnImpl lastLowerBoundColumn = null;
164 CandidateColumnImpl lastUpperBoundColumn = null;
166 /** Evaluate the suitability of this index for the query. An instance represents each
167 * index, with primary key and non-hash-key indexes having two instances, one for the
168 * unique index and one for the btree index for the same column(s).
169 * Unique indexes where all of the columns are compared equal return a score of 100.
170 * Btree indexes get one point for each query term that can be used (maximum of two
171 * points for each comparison). Greater than and less than get one point each.
172 * Equals and In get two points each. Once a gap is found in query terms for either
173 * upper or lower bound, processing for that bound stops.
174 * The last query term (candidate column) for each of the lower and upper bound is noted.
175 * The method is synchronized because the method modifies the state of the instance,
176 * which might be shared by multiple threads.
177 * @return the score of this index.
179 synchronized int getScore() {
180 if (candidateColumns == null) {
184 boolean lowerBoundDone = false;
185 boolean upperBoundDone = false;
187 // all columns need to have equal bound
188 for (CandidateColumnImpl column: candidateColumns) {
189 if (!(column.equalBound)) {
190 // not equal bound; can't use unique index
194 if ("PRIMARY".equals(indexName)) {
195 scanType = PredicateImpl.ScanType.PRIMARY_KEY;
197 scanType = PredicateImpl.ScanType.UNIQUE_KEY;
202 // leading columns need any kind of bound
203 // extra credit for equals
204 for (CandidateColumnImpl candidateColumn: candidateColumns) {
205 if ((candidateColumn.equalBound)) {
206 scanType = PredicateImpl.ScanType.INDEX_SCAN;
207 if (!lowerBoundDone) {
208 result += fieldScore;
209 lastLowerBoundColumn = candidateColumn;
211 if (!upperBoundDone) {
212 result += fieldScore;
213 lastUpperBoundColumn = candidateColumn;
215 } else if ((candidateColumn.inBound)) {
216 scanType = PredicateImpl.ScanType.INDEX_SCAN;
218 if (!lowerBoundDone) {
219 result += fieldScore;
220 lastLowerBoundColumn = candidateColumn;
222 if (!upperBoundDone) {
223 result += fieldScore;
224 lastUpperBoundColumn = candidateColumn;
226 } else if (!(lowerBoundDone && upperBoundDone)) {
227 // lower bound and upper bound are independent
228 boolean hasLowerBound = candidateColumn.hasLowerBound();
229 boolean hasUpperBound = candidateColumn.hasUpperBound();
230 // keep going until both upper and lower are done
231 if (hasLowerBound || hasUpperBound) {
232 scanType = PredicateImpl.ScanType.INDEX_SCAN;
234 if (!lowerBoundDone) {
236 result += fieldScore;
237 lastLowerBoundColumn = candidateColumn;
239 lowerBoundDone = true;
242 if (!upperBoundDone) {
244 result += fieldScore;
245 lastUpperBoundColumn = candidateColumn;
247 upperBoundDone = true;
250 if (lowerBoundDone && upperBoundDone) {
255 if (lastLowerBoundColumn != null) {
256 lastLowerBoundColumn.markLastLowerBoundColumn();
258 if (lastUpperBoundColumn != null) {
259 lastUpperBoundColumn.markLastUpperBoundColumn();
265 public ScanType getScanType() {
269 /* No bound is complete yet */
270 private final int BOUND_STATUS_NO_BOUND_DONE = 0;
271 /* The lower bound is complete */
272 private final int BOUND_STATUS_LOWER_BOUND_DONE = 1;
273 /* The upper bound is complete */
274 private final int BOUND_STATUS_UPPER_BOUND_DONE = 2;
275 /* Both bounds are complete */
276 private final int BOUND_STATUS_BOTH_BOUNDS_DONE = 3;
278 /** Set bounds for the operation defined for this index. This index was chosen as
279 * the best index to use for the query.
280 * Each query term (candidate column) is used to set a bound. The bound depends on
281 * the type of query term, whether the term is the last term, and whether the
282 * bound type (upper or lower) has already been completely specified.
283 * Equal and In query terms can be used for an equal bound, a lower bound, or an upper
284 * bound. Strict bounds that are not the last bound are converted to non-strict bounds.
285 * In query terms are decomposed into multiple range bounds, one range for each
286 * value in the query term.
287 * @param context the query execution context, containing the parameter values
288 * @param op the index scan operation
290 void operationSetBounds(QueryExecutionContext context, IndexScanOperation op) {
292 // find how many query terms are inPredicates
293 List<Integer> parameterSizes = new ArrayList<Integer>();
294 for (CandidateColumnImpl candidateColumn:candidateColumns) {
295 if (candidateColumn.hasInBound()) {
296 parameterSizes.add(candidateColumn.getParameterSize(context));
299 if (parameterSizes.size() > 1) {
300 throw new ClusterJUserException(local.message("ERR_Too_Many_In_For_Index", indexName));
302 // if only one column in the index, optimize
303 if (candidateColumns.length == 1) {
304 candidateColumns[0].operationSetAllBounds(context, op);
306 // set multiple bounds; one for each item in the parameter (context)
307 for (int parameterIndex = 0; parameterIndex < parameterSizes.get(0); ++parameterIndex) {
308 int boundStatus = BOUND_STATUS_NO_BOUND_DONE;
309 for (CandidateColumnImpl candidateColumn:candidateColumns) {
310 if (logger.isDetailEnabled()) logger.detail(
311 "parameterIndex: " + parameterIndex
312 + " boundStatus: " + boundStatus
313 + " candidateColumn: " + candidateColumn.domainFieldHandler.getName());
314 // execute the bounds operation if anything left to do
315 if (boundStatus != BOUND_STATUS_BOTH_BOUNDS_DONE) {
316 boundStatus = candidateColumn.operationSetBounds(context, op, parameterIndex, boundStatus);
319 // after all columns are done, mark the end of bounds
320 op.endBound(parameterIndex);
325 int boundStatus = BOUND_STATUS_NO_BOUND_DONE;
326 for (CandidateColumnImpl candidateColumn:candidateColumns) {
327 if (logger.isDetailEnabled()) logger.detail("boundStatus: " + boundStatus
328 + " candidateColumn: " + candidateColumn.domainFieldHandler.getName());
329 // execute the bounds operation for each query term
330 if (boundStatus != BOUND_STATUS_BOTH_BOUNDS_DONE) {
331 boundStatus = candidateColumn.operationSetBounds(context, op, -1, boundStatus);
337 void operationSetKeys(QueryExecutionContext context,
339 for (CandidateColumnImpl candidateColumn:candidateColumns) {
340 // execute the equal operation
341 candidateColumn.operationSetKeys(context, op);
346 * This class represents one column in an index, and its corresponding query term(s).
347 * The column might be associated with a lower bound term, an upper bound term,
348 * an equal term, or an in term.
350 class CandidateColumnImpl {
352 protected AbstractDomainFieldHandlerImpl domainFieldHandler;
353 protected PredicateImpl lowerBoundPredicate;
354 protected PredicateImpl upperBoundPredicate;
355 protected PredicateImpl equalPredicate;
356 protected InPredicateImpl inPredicate;
357 protected Boolean lowerBoundStrict = null;
358 protected Boolean upperBoundStrict = null;
359 protected boolean equalBound = false;
360 protected boolean inBound = false;
361 protected boolean lastLowerBoundColumn = false;
362 protected boolean lastUpperBoundColumn = false;
364 protected boolean hasLowerBound() {
365 return lowerBoundPredicate != null || equalPredicate != null || inPredicate != null;
368 /** Set all bounds in the operation, ending each bound with an end_of_bound.
370 * @param context the query context
371 * @param op the operation
373 public void operationSetAllBounds(QueryExecutionContext context, IndexScanOperation op) {
374 inPredicate.operationSetAllBounds(context, op);
377 public int getParameterSize(QueryExecutionContext context) {
378 // TODO Auto-generated method stub
379 return inPredicate.getParameterSize(context);
382 protected boolean hasUpperBound() {
383 return upperBoundPredicate != null || equalPredicate != null || inPredicate != null;
386 protected boolean hasInBound() {
390 private CandidateColumnImpl(AbstractDomainFieldHandlerImpl domainFieldHandler) {
391 this.domainFieldHandler = domainFieldHandler;
394 private void markLastLowerBoundColumn() {
395 lastLowerBoundColumn = true;
398 private void markLastUpperBoundColumn() {
399 lastUpperBoundColumn = true;
402 private void markLowerBound(PredicateImpl predicate, boolean strict) {
403 lowerBoundStrict = strict;
404 this.lowerBoundPredicate = predicate;
407 private void markUpperBound(PredicateImpl predicate, boolean strict) {
408 upperBoundStrict = strict;
409 this.upperBoundPredicate = predicate;
412 private void markEqualBound(PredicateImpl predicate) {
414 this.equalPredicate = predicate;
417 public void markInBound(InPredicateImpl predicate) {
419 this.inPredicate = predicate;
422 /** Set bounds into each predicate that has been defined.
424 * @param op the operation
425 * @param index for inPredicates, the index into the parameter
428 private int operationSetBounds(
429 QueryExecutionContext context, IndexScanOperation op, int index, int boundStatus) {
431 if (logger.isDetailEnabled()) logger.detail("column: " + domainFieldHandler.getName()
432 + " boundStatus: " + boundStatus
433 + " lastLowerBoundColumn: " + lastLowerBoundColumn
434 + " lastUpperBoundColumn: " + lastUpperBoundColumn);
435 switch(boundStatus) {
436 case BOUND_STATUS_BOTH_BOUNDS_DONE:
437 // cannot set either lower or upper bound
438 return BOUND_STATUS_BOTH_BOUNDS_DONE;
439 case BOUND_STATUS_NO_BOUND_DONE:
440 // can set either/both lower or upper bound
441 if (equalPredicate != null) {
442 equalPredicate.operationSetBounds(context, op, true);
444 if (inPredicate != null) {
445 inPredicate.operationSetBound(context, op, index, true);
447 if (lowerBoundPredicate != null) {
448 lowerBoundPredicate.operationSetLowerBound(context, op, lastLowerBoundColumn);
450 if (upperBoundPredicate != null) {
451 upperBoundPredicate.operationSetUpperBound(context, op, lastUpperBoundColumn);
454 case BOUND_STATUS_LOWER_BOUND_DONE:
455 // cannot set lower, only upper bound
456 if (equalPredicate != null) {
457 equalPredicate.operationSetUpperBound(context, op, lastUpperBoundColumn);
459 if (inPredicate != null) {
460 inPredicate.operationSetUpperBound(context, op, index);
462 if (upperBoundPredicate != null) {
463 upperBoundPredicate.operationSetUpperBound(context, op, lastUpperBoundColumn);
466 case BOUND_STATUS_UPPER_BOUND_DONE:
467 // cannot set upper, only lower bound
468 if (equalPredicate != null) {
469 equalPredicate.operationSetLowerBound(context, op, lastLowerBoundColumn);
471 if (inPredicate != null) {
472 inPredicate.operationSetLowerBound(context, op, index);
474 if (lowerBoundPredicate != null) {
475 lowerBoundPredicate.operationSetLowerBound(context, op, lastLowerBoundColumn);
479 if (!hasLowerBound()) {
480 // if this has no lower bound, set lower bound done
481 boundStatus |= BOUND_STATUS_LOWER_BOUND_DONE;
483 if (!hasUpperBound()) {
484 // if this has no upper bound, set upper bound done
485 boundStatus |= BOUND_STATUS_UPPER_BOUND_DONE;
490 private void operationSetKeys(QueryExecutionContext context, Operation op) {
491 equalPredicate.operationEqual(context, op);
496 /** Determine whether this index supports exactly the number of conditions.
497 * For ordered indexes, any number of conditions are supported via filters.
498 * For hash indexes, only the number of columns in the index are supported.
499 * @param numberOfConditions the number of conditions in the query predicate
500 * @return if this index supports exactly the number of conditions
502 public boolean supportsConditionsOfLength(int numberOfConditions) {
504 return numberOfConditions == candidateColumns.length;
510 public Index getStoreIndex() {
514 public boolean isMultiRange() {