| /* |
| |
| Derby - Class org.apache.derby.impl.sql.compile.PredicateList |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to you under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| |
| package org.apache.derby.impl.sql.compile; |
| |
| import java.lang.reflect.Modifier; |
| import java.util.ArrayList; |
| import java.util.List; |
| import org.apache.derby.iapi.error.StandardException; |
| import org.apache.derby.iapi.reference.ClassName; |
| import org.apache.derby.iapi.services.classfile.VMOpcode; |
| import org.apache.derby.iapi.services.compiler.LocalField; |
| import org.apache.derby.iapi.services.compiler.MethodBuilder; |
| import org.apache.derby.iapi.services.context.ContextManager; |
| import org.apache.derby.shared.common.sanity.SanityManager; |
| import org.apache.derby.iapi.sql.compile.AccessPath; |
| import org.apache.derby.iapi.sql.compile.CompilerContext; |
| import org.apache.derby.iapi.sql.compile.ExpressionClassBuilderInterface; |
| import org.apache.derby.iapi.sql.compile.Optimizable; |
| import org.apache.derby.iapi.sql.compile.OptimizablePredicate; |
| import org.apache.derby.iapi.sql.compile.OptimizablePredicateList; |
| import org.apache.derby.iapi.sql.compile.RequiredRowOrdering; |
| import org.apache.derby.iapi.sql.compile.RowOrdering; |
| import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor; |
| import org.apache.derby.iapi.sql.dictionary.TableDescriptor; |
| import org.apache.derby.iapi.sql.execute.ExecutionFactory; |
| import org.apache.derby.iapi.store.access.ScanController; |
| import org.apache.derby.iapi.types.DataValueDescriptor; |
| import org.apache.derby.iapi.util.JBitSet; |
| |
| /** |
| * A PredicateList represents the list of top level predicates. |
| * Each top level predicate consists of an AndNode whose leftOperand is the |
| * top level predicate and whose rightOperand is true. It extends |
| * QueryTreeNodeVector. |
| * |
| */ |
| |
| class PredicateList extends QueryTreeNodeVector<Predicate> |
| implements OptimizablePredicateList |
| { |
| private int numberOfStartPredicates; |
| private int numberOfStopPredicates; |
| private int numberOfQualifiers; |
| |
| PredicateList(ContextManager cm) |
| { |
| super(Predicate.class, cm); |
| } |
| |
| |
| /* |
| * OptimizableList interface |
| */ |
| |
| /** |
| * @see org.apache.derby.iapi.sql.compile.OptimizablePredicateList#getOptPredicate |
| */ |
| public OptimizablePredicate getOptPredicate(int index) |
| { |
| return (OptimizablePredicate) elementAt(index); |
| } |
| |
| /** |
| * @see org.apache.derby.iapi.sql.compile.OptimizablePredicateList#removeOptPredicate |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public final void removeOptPredicate(int predCtr) throws StandardException |
| { |
| Predicate predicate = removeElementAt(predCtr); |
| |
| if (predicate.isStartKey()) |
| numberOfStartPredicates--; |
| if (predicate.isStopKey()) |
| numberOfStopPredicates--; |
| if (predicate.isQualifier()) |
| numberOfQualifiers--; |
| } |
| |
| /** |
| * Another version of removeOptPredicate that takes the Predicate to be |
| * removed, rather than the position of the Predicate. This is not part |
| * any interface (yet). |
| */ |
| public final void removeOptPredicate(OptimizablePredicate pred) |
| { |
| removeElement((Predicate) pred); |
| |
| if (pred.isStartKey()) |
| numberOfStartPredicates--; |
| if (pred.isStopKey()) |
| numberOfStopPredicates--; |
| if (pred.isQualifier()) |
| numberOfQualifiers--; |
| } |
| |
| |
| /** @see OptimizablePredicateList#addOptPredicate */ |
| public void addOptPredicate(OptimizablePredicate optPredicate) |
| { |
| addElement((Predicate)optPredicate); |
| |
| if (optPredicate.isStartKey()) |
| numberOfStartPredicates++; |
| if (optPredicate.isStopKey()) |
| numberOfStopPredicates++; |
| if (optPredicate.isQualifier()) |
| numberOfQualifiers++; |
| } |
| |
| /** |
| * Another flavor of addOptPredicate that inserts the given predicate |
| * at a given position. This is not yet part of any interface. |
| */ |
| public void addOptPredicate(OptimizablePredicate optPredicate, int position) |
| { |
| insertElementAt((Predicate) optPredicate, position); |
| |
| if (optPredicate.isStartKey()) |
| numberOfStartPredicates++; |
| if (optPredicate.isStopKey()) |
| numberOfStopPredicates++; |
| if (optPredicate.isQualifier()) |
| numberOfQualifiers++; |
| } |
| |
| |
| /** |
| * @see OptimizablePredicateList#useful |
| * @exception StandardException Thrown on error |
| */ |
| public boolean useful(Optimizable optTable, ConglomerateDescriptor cd) |
| throws StandardException |
| { |
| boolean retval = false; |
| |
| /* |
| ** Most of this assumes BTREE, |
| ** so should move into a configurable module |
| */ |
| |
| /* If the conglomerate isn't an index, the predicate isn't useful */ |
| if ( ! cd.isIndex()) |
| return false; |
| |
| /* |
| ** A PredicateList is useful for a BTREE if it contains a relational |
| ** operator directly below a top-level AND comparing the first column |
| ** in the index to an expression that does not contain a reference |
| ** to the table in question. Let's look for that. |
| */ |
| for (Predicate pred : this) |
| { |
| RelationalOperator relop = pred.getRelop(); |
| |
| /* InListOperatorNodes, while not relational operators, may still |
| * be useful. There are two cases: a) we transformed the IN-list |
| * into a probe predicate of the form "col = ?", which can then be |
| * optimized/generated as a start/stop key and used for "multi- |
| * probing" at execution; or b) we did *not* transform the IN-list, |
| * in which case we'll generate _dynamic_ start and stop keys in |
| * order to improve scan performance (beetle 3858). In either case |
| * the IN-list may still prove "useful". |
| */ |
| InListOperatorNode inNode = pred.getSourceInList(); |
| boolean isIn = (inNode != null); |
| |
| /* If it's not a relational operator and it's not "in", then it's |
| * not useful. |
| */ |
| if (!isIn && (relop == null)) |
| continue; |
| |
| /* |
| ** If the relational operator is neither a useful start key |
| ** nor a useful stop key for this table, it is not useful |
| ** for limiting an index scan. |
| */ |
| if ( (! isIn) && ( ! relop.usefulStartKey(optTable) ) && |
| ( ! relop.usefulStopKey(optTable) ) ) |
| { |
| continue; |
| } |
| |
| /* |
| ** Look for a the first column of the index on one side of the |
| ** relop. If it's not found, this Predicate is not optimizable. |
| */ |
| ColumnReference indexCol = null; |
| |
| if (isIn) |
| { |
| if (inNode.getLeftOperand() instanceof ColumnReference) |
| { |
| indexCol = (ColumnReference) inNode.getLeftOperand(); |
| if (indexCol.getColumnNumber() != |
| cd.getIndexDescriptor().baseColumnPositions()[0]) |
| { |
| indexCol = null; |
| } |
| } |
| } |
| else |
| { |
| indexCol = |
| relop.getColumnOperand( |
| optTable, |
| cd.getIndexDescriptor().baseColumnPositions()[0]); |
| } |
| |
| if (indexCol == null) |
| { |
| continue; |
| } |
| |
| /* |
| ** Look at the expression that the index column is compared to. |
| ** If it contains columns from the table in question, the |
| ** Predicate is not optimizable. |
| */ |
| if ((isIn && inNode.selfReference(indexCol)) || |
| (! isIn && relop.selfComparison(indexCol))) |
| { |
| continue; |
| } |
| |
| /* The Predicate is optimizable */ |
| retval = true; |
| break; |
| } |
| |
| return retval; |
| } |
| |
| /** |
| * @see OptimizablePredicateList#pushUsefulPredicates |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public void pushUsefulPredicates(Optimizable optTable) |
| throws StandardException |
| { |
| AccessPath ap = optTable.getTrulyTheBestAccessPath(); |
| |
| orderUsefulPredicates(optTable, |
| ap.getConglomerateDescriptor(), |
| true, |
| ap.getNonMatchingIndexScan(), |
| ap.getCoveringIndexScan()); |
| } |
| |
| /** |
| * @see OptimizablePredicateList#classify |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public void classify(Optimizable optTable, ConglomerateDescriptor cd) |
| throws StandardException |
| { |
| /* |
| ** Don't push the predicates - at this point, we are only determining |
| ** which predicates are useful. Also, we don't know yet whether |
| ** we have a non-matching index scan or a covering index scan - |
| ** this method call will help determine that. So, let's say they're |
| ** false for now. |
| */ |
| orderUsefulPredicates(optTable, cd, false, false, false); |
| } |
| |
| /** @see OptimizablePredicateList#markAllPredicatesQualifiers */ |
| public void markAllPredicatesQualifiers() |
| { |
| for (Predicate p : this) |
| { |
| p.markQualifier(); |
| } |
| |
| numberOfQualifiers = size(); |
| } |
| |
| /** |
| * @see OptimizablePredicateList#hasEqualityPredicateOnOrderedColumn |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public int hasEqualityPredicateOnOrderedColumn(Optimizable optTable, |
| int columnNumber, |
| boolean isNullOkay) |
| throws StandardException |
| { |
| ValueNode opNode; |
| int size = size(); |
| for (int index = 0; index < size; index++) |
| { |
| AndNode andNode; |
| Predicate predicate; |
| predicate = elementAt(index); |
| //We are not looking at constant comparison predicate. |
| if (predicate.getReferencedMap().hasSingleBitSet()) |
| { |
| continue; |
| } |
| |
| andNode = predicate.getAndNode(); |
| |
| // skip non-equality predicates |
| opNode = andNode.getLeftOperand(); |
| |
| if (opNode.optimizableEqualityNode(optTable, |
| columnNumber, |
| isNullOkay)) |
| { |
| return index; |
| } |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * @see OptimizablePredicateList#hasOptimizableEqualityPredicate |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public boolean hasOptimizableEqualityPredicate(Optimizable optTable, |
| int columnNumber, |
| boolean isNullOkay) |
| throws StandardException |
| { |
| for (Predicate predicate : this) |
| { |
| AndNode andNode = predicate.getAndNode(); |
| |
| // skip non-equality predicates |
| ValueNode opNode = andNode.getLeftOperand(); |
| |
| if (opNode.optimizableEqualityNode(optTable, |
| columnNumber, |
| isNullOkay)) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * @see OptimizablePredicateList#hasOptimizableEquijoin |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public boolean hasOptimizableEquijoin(Optimizable optTable, |
| int columnNumber) |
| throws StandardException |
| { |
| for (Predicate predicate : this) |
| { |
| // This method is used by HashJoinStrategy to determine if |
| // there are any equality predicates that can be used to |
| // perform a hash join (see the findHashKeyColumns() |
| // method in HashJoinStrategy.java). That said, if the |
| // predicate was scoped and pushed down from an outer query, |
| // then it's no longer possible to perform the hash join |
| // because one of the operands is in an outer query and |
| // the other (scoped) operand is down in a subquery. Thus |
| // we skip this predicate if it has been scoped. |
| if (predicate.isScopedForPush()) |
| { |
| continue; |
| } |
| |
| AndNode andNode = predicate.getAndNode(); |
| |
| ValueNode opNode = andNode.getLeftOperand(); |
| |
| if ( ! opNode.optimizableEqualityNode(optTable, |
| columnNumber, |
| false)) |
| { |
| continue; |
| } |
| |
| /* |
| ** Skip comparisons that are not qualifiers for the table |
| ** in question. |
| */ |
| if ( ! ((RelationalOperator) opNode).isQualifier(optTable, false)) |
| { |
| continue; |
| } |
| |
| /* |
| ** Skip non-join comparisons. |
| */ |
| if (predicate.getReferencedMap().hasSingleBitSet()) |
| { |
| continue; |
| } |
| |
| // We found a match |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * @see OptimizablePredicateList#putOptimizableEqualityPredicateFirst |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public void putOptimizableEqualityPredicateFirst(Optimizable optTable, |
| int columnNumber) |
| throws StandardException |
| { |
| int size = size(); |
| for (int index = 0; index < size; index++) |
| { |
| Predicate predicate = elementAt(index); |
| AndNode andNode = predicate.getAndNode(); |
| |
| // skip non-equality predicates |
| ValueNode opNode = andNode.getLeftOperand(); |
| |
| if (!opNode.optimizableEqualityNode(optTable, columnNumber, false)) |
| continue; |
| |
| // We found a match - make this entry first in the list |
| if (index != 0) |
| { |
| removeElementAt(index); |
| insertElementAt(predicate, 0); |
| } |
| |
| return; |
| } |
| |
| /* We should never get here since this method only called when we |
| * know that the desired equality predicate exists. |
| */ |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.THROWASSERT( |
| "Could not find the expected equality predicate on column #" + |
| columnNumber); |
| } |
| } |
| |
| private void orderUsefulPredicates(Optimizable optTable, |
| ConglomerateDescriptor cd, |
| boolean pushPreds, |
| boolean nonMatchingIndexScan, |
| boolean coveringIndexScan) |
| throws StandardException |
| { |
| int[] baseColumnPositions; |
| boolean[] isAscending; |
| int size = size(); |
| Predicate[] usefulPredicates = new Predicate[size]; |
| int usefulCount = 0; |
| |
| /* |
| ** Clear all the scan flags for this predicate list, so that the |
| ** flags that get set are only for the given conglomerate. |
| */ |
| for (Predicate p : this) |
| { |
| p.clearScanFlags(); |
| } |
| |
| /* |
| ** RESOLVE: For now, not pushing any predicates for heaps. When this |
| ** changes, we also need to make the scan in |
| ** TableScanResultSet.getCurrentRow() check the qualifiers to see |
| ** if the row still qualifies (there is a new method in ScanController |
| ** for this. |
| */ |
| |
| /* Is a heap scan or a non-matching index scan on a covering index? */ |
| if ((cd == null) || (! cd.isIndex()) || |
| (nonMatchingIndexScan && coveringIndexScan)) |
| { |
| /* |
| ** For the heap, the useful predicates are the relational |
| ** operators that have a column from the table on one side, |
| ** and an expression that doesn't have a column from that |
| ** table on the other side. |
| ** |
| ** For the heap, all useful predicates become Qualifiers, so |
| ** they don't have to be in any order. |
| ** |
| ** NOTE: We can logically delete the current element when |
| ** traversing the list in the next loop, |
| ** so we must build an array of elements to |
| ** delete while looping and then delete them |
| ** in reverse order after completing the loop. |
| */ |
| Predicate[] preds = new Predicate[size]; |
| |
| for (int index = 0; index < size; index++) |
| { |
| Predicate pred = elementAt(index); |
| |
| /* |
| ** Skip over it if it's not a relational operator (this includes |
| ** BinaryComparisonOperators and IsNullNodes. |
| */ |
| if (!pred.isRelationalOpPredicate()) |
| { |
| // possible OR clause, check for it. |
| |
| if (!pred.isPushableOrClause(optTable)) |
| { |
| /* NOT an OR or AND, so go on to next predicate. |
| * |
| * Note: if "pred" (or any predicates in the tree |
| * beneath "pred") is an IN-list probe predicate |
| * then we'll "revert" it to its original form |
| * (i.e. to the source InListOperatorNode from |
| * which it originated) as part of code generation. |
| * See generateExpression() in BinaryOperatorNode. |
| */ |
| continue; |
| } |
| } |
| else |
| { |
| if ( ! pred.getRelop().isQualifier(optTable, pushPreds)) |
| { |
| // NOT a qualifier, go on to next predicate. |
| continue; |
| } |
| } |
| |
| pred.markQualifier(); |
| |
| if (SanityManager.DEBUG) |
| { |
| if (pred.isInListProbePredicate()) |
| { |
| SanityManager.THROWASSERT("Found an IN-list probe " + |
| "predicate (" + pred.binaryRelOpColRefsToString() + |
| ") that was marked as a qualifier, which should " + |
| "not happen."); |
| } |
| } |
| |
| if (pushPreds) |
| { |
| /* Push the predicate down. |
| * (Just record for now.) |
| */ |
| if (optTable.pushOptPredicate(pred)) |
| { |
| preds[index] = pred; |
| } |
| } |
| } |
| |
| /* Now we actually push the predicates down */ |
| for (int inner = size - 1; inner >= 0; inner--) |
| { |
| if (preds[inner] != null) |
| { |
| removeOptPredicate(preds[inner]); |
| } |
| } |
| |
| return; |
| } |
| |
| baseColumnPositions = cd.getIndexDescriptor().baseColumnPositions(); |
| isAscending = cd.getIndexDescriptor().isAscending(); |
| |
| /* If we have a "useful" IN list probe predicate we will generate a |
| * start/stop key for optTable of the form "col = <val>", where <val> |
| * is the first value in the IN-list. Then during normal index multi- |
| * probing (esp. as implemented by exec/MultiProbeTableScanResultSet) |
| * we will use that start/stop key as a "placeholder" into which we'll |
| * plug the values from the IN-list one at a time. |
| * |
| * That said, if we're planning to do a hash join with optTable then |
| * we won't generate a MultiProbeTableScanResult; instead we'll |
| * generate a HashScanResultSet, which does not (yet) account for |
| * IN-list multi-probing. That means the start/stop key "col = <val>" |
| * would be treated as a regular restriction, which could lead to |
| * incorrect results. So if we're dealing with a hash join, we do |
| * not consider IN-list probe predicates to be "useful". DERBY-2500. |
| * |
| * Note that it should be possible to enhance HashScanResultSet to |
| * correctly perform index multi-probing at some point, and there |
| * would indeed be benefits to doing so (namely, we would scan fewer |
| * rows from disk and build a smaller hash table). But until that |
| * happens we have to make sure we do not consider probe predicates |
| * to be "useful" for hash joins. |
| * |
| * Only need to do this check if "pushPreds" is true, i.e. if we're |
| * modifying access paths and thus we know for sure that we are going |
| * to generate a hash join. |
| */ |
| boolean skipProbePreds = pushPreds && |
| optTable.getTrulyTheBestAccessPath().getJoinStrategy().isHashJoin(); |
| |
| /* |
| ** Create an array of useful predicates. Also, count how many |
| ** useful predicates there are. |
| */ |
| for (Predicate pred : this) |
| { |
| ColumnReference indexCol = null; |
| int indexPosition; |
| RelationalOperator relop = pred.getRelop(); |
| |
| /* InListOperatorNodes, while not relational operators, may still |
| * be useful. There are two cases: a) we transformed the IN-list |
| * into a probe predicate of the form "col = ?", which can then be |
| * optimized/generated as a start/stop key and used for "multi- |
| * probing" at execution; or b) we did *not* transform the IN-list, |
| * in which case we'll generate _dynamic_ start and stop keys in |
| * order to improve scan performance (beetle 3858). In either case |
| * the IN-list may still prove "useful". |
| */ |
| InListOperatorNode inNode = pred.getSourceInList(); |
| boolean isIn = (inNode != null); |
| |
| /* If it's not an "in" operator and either a) it's not a relational |
| * operator or b) it's not a qualifier, then it's not useful for |
| * limiting the scan, so skip it. |
| */ |
| if (!isIn && |
| ((relop == null) || !relop.isQualifier(optTable, pushPreds))) |
| { |
| continue; |
| } |
| |
| /* Skip it if we're doing a hash join and it's a probe predicate. |
| * Then, since the probe predicate is deemed not useful, it will |
| * be implicitly "reverted" to its underlying IN-list as part of |
| * code generation. |
| */ |
| if (skipProbePreds && pred.isInListProbePredicate()) |
| continue; |
| |
| /* Look for an index column on one side of the relop */ |
| for (indexPosition = 0; |
| indexPosition < baseColumnPositions.length; |
| indexPosition++) |
| { |
| if (isIn) |
| { |
| if (inNode.getLeftOperand() instanceof ColumnReference) |
| { |
| indexCol = (ColumnReference) inNode.getLeftOperand(); |
| if ((optTable.getTableNumber() != indexCol.getTableNumber()) || |
| (indexCol.getColumnNumber() != baseColumnPositions[indexPosition]) || |
| inNode.selfReference(indexCol)) |
| indexCol = null; |
| else if (pred.isInListProbePredicate() |
| && (indexPosition > 0)) |
| { |
| /* If the predicate is an IN-list probe predicate |
| * then we only consider it to be useful if the |
| * referenced column is the *first* one in the |
| * index (i.e. if (indexPosition == 0)). Otherwise |
| * the predicate would be treated as a qualifier |
| * for store, which could lead to incorrect |
| * results. |
| */ |
| indexCol = null; |
| } |
| } |
| } |
| else |
| { |
| indexCol = |
| relop.getColumnOperand( |
| optTable, |
| baseColumnPositions[indexPosition]); |
| } |
| if (indexCol != null) |
| break; |
| } |
| |
| /* |
| ** Skip over it if there is no index column on one side of the |
| ** operand. |
| */ |
| if (indexCol == null) |
| { |
| /* If we're pushing predicates then this is the last time |
| * we'll get here before code generation. So if we have |
| * any IN-list probe predicates that are not useful, we'll |
| * need to "revert" them back to their original IN-list |
| * form so that they can be generated as regular IN-list |
| * restrictions. That "revert" operation happens in |
| * the generateExpression() method of BinaryOperatorNode. |
| */ |
| continue; |
| } |
| |
| pred.setIndexPosition(indexPosition); |
| |
| /* Remember the useful predicate */ |
| usefulPredicates[usefulCount++] = pred; |
| } |
| |
| /* We can end up with no useful |
| * predicates with a force index override - |
| * Each predicate is on a non-key column or both |
| * sides of the operator are columns from the same table. |
| * There's no predicates to push down, so return and we'll |
| * evaluate them in a PRN. |
| */ |
| if (usefulCount == 0) |
| return; |
| |
| /* The array of useful predicates may have unused slots. Shrink it */ |
| if (usefulPredicates.length > usefulCount) |
| { |
| Predicate[] shrink = new Predicate[usefulCount]; |
| |
| System.arraycopy(usefulPredicates, 0, shrink, 0, usefulCount); |
| |
| usefulPredicates = shrink; |
| } |
| |
| /* Sort the array of useful predicates in index position order */ |
| java.util.Arrays.sort(usefulPredicates); |
| |
| /* Push the sorted predicates down to the Optimizable table */ |
| int currentStartPosition = -1; |
| boolean gapInStartPositions = false; |
| int currentStopPosition = -1; |
| boolean gapInStopPositions = false; |
| boolean seenNonEquals = false; |
| int firstNonEqualsPosition = -1; |
| int lastStartEqualsPosition = -1; |
| |
| /* beetle 4572. We need to truncate if necessary potential multi-column |
| * start key up to the first one whose start operator is GT, and make |
| * start operator GT; |
| * or start operator is GE if there's no such column. We need to |
| * truncate if necessary potential multi-column stop key up to the |
| * first one whose stop operator is GE, and make stop operator GE; or |
| * stop operator is GT if no such column. |
| * eg., start key (a,b,c,d,e,f), potential start operators |
| * (GE,GE,GE,GT,GE,GT) |
| * then start key should really be (a,b,c,d) with start operator GT. |
| */ |
| boolean seenGE = false, seenGT = false; |
| |
| for (int i = 0; i < usefulCount; i++) |
| { |
| Predicate thisPred = usefulPredicates[i]; |
| int thisIndexPosition = thisPred.getIndexPosition(); |
| boolean thisPredMarked = false; |
| RelationalOperator relop = thisPred.getRelop(); |
| int thisOperator = -1; |
| |
| boolean isIn = (thisPred.getSourceInList() != null); |
| |
| if (relop != null) |
| thisOperator = relop.getOperator(); |
| |
| /* Allow only one start and stop position per index column */ |
| if (currentStartPosition != thisIndexPosition) |
| { |
| /* |
| ** We're working on a new index column for the start position. |
| ** Is it just one more than the previous position? |
| */ |
| if ((thisIndexPosition - currentStartPosition) > 1) |
| { |
| /* |
| ** There's a gap in the start positions. Don't mark any |
| ** more predicates as start predicates. |
| */ |
| gapInStartPositions = true; |
| } |
| else if ((thisOperator == RelationalOperator.EQUALS_RELOP) || |
| (thisOperator == RelationalOperator.IS_NULL_RELOP)) |
| { |
| /* Remember the last "=" or IS NULL predicate in the start |
| * position. (The sort on the predicates above has ensured |
| * that these predicates appear 1st within the predicates on |
| * a specific column.) |
| */ |
| lastStartEqualsPosition = thisIndexPosition; |
| } |
| |
| if ( ! gapInStartPositions) |
| { |
| /* |
| ** There is no gap in start positions. Is this predicate |
| ** useful as a start position? This depends on the |
| ** operator - for example, indexCol = <expr> is useful, |
| ** while indexCol < <expr> is not useful with asc index |
| ** we simply need to reverse the logic for desc indexes |
| ** |
| ** The relop has to figure out whether the index column |
| ** is on the left or the right, so pass the Optimizable |
| ** table to help it. |
| */ |
| if (! seenGT && |
| (isIn || ((relop.usefulStartKey(optTable) && isAscending[thisIndexPosition]) || |
| (relop.usefulStopKey(optTable) && ! isAscending[thisIndexPosition])))) |
| { |
| thisPred.markStartKey(); |
| currentStartPosition = thisIndexPosition; |
| thisPredMarked = true; |
| seenGT = (thisPred.getStartOperator(optTable) == ScanController.GT); |
| } |
| } |
| } |
| |
| /* Same as above, except for stop keys */ |
| if (currentStopPosition != thisIndexPosition) |
| { |
| if ((thisIndexPosition - currentStopPosition) > 1) |
| { |
| gapInStopPositions = true; |
| } |
| |
| if ( ! gapInStopPositions) |
| { |
| if (! seenGE && |
| (isIn || ((relop.usefulStopKey(optTable) && isAscending[thisIndexPosition]) || |
| (relop.usefulStartKey(optTable) && ! isAscending[thisIndexPosition])))) |
| { |
| thisPred.markStopKey(); |
| currentStopPosition = thisIndexPosition; |
| thisPredMarked = true; |
| seenGE = (thisPred.getStopOperator(optTable) == ScanController.GE); |
| } |
| } |
| } |
| |
| /* Mark this predicate as a qualifier if it is not a start/stop |
| * position or if we have already seen a previous column whose |
| * RELOPS do not include "=" or IS NULL. For example, if |
| * the index is on (a, b, c) and the predicates are a > 1 and b = 1 |
| * and c = 1, then b = 1 and c = 1 also need to be a qualifications, |
| * otherwise we may match on (2, 0, 3). |
| */ |
| if ( (! isIn) && // store can never treat "in" as qualifier |
| ((! thisPredMarked ) || |
| (seenNonEquals && thisIndexPosition != firstNonEqualsPosition) |
| ) ) |
| { |
| thisPred.markQualifier(); |
| } |
| |
| /* Remember if we have seen a column without an "=" */ |
| if (lastStartEqualsPosition != thisIndexPosition && |
| firstNonEqualsPosition == -1 && |
| (thisOperator != RelationalOperator.EQUALS_RELOP) && |
| (thisOperator != RelationalOperator.IS_NULL_RELOP)) |
| { |
| seenNonEquals = true; |
| /* Remember the column */ |
| firstNonEqualsPosition = thisIndexPosition; |
| } |
| |
| if (pushPreds) |
| { |
| /* we only roughly detected that the predicate may be useful |
| * earlier, it may turn out that it's not actually start/stop |
| * key because another better predicate on the column is chosen. |
| * We don't want to push "in" in this case, since it's not a |
| * qualifier. Beetle 4316. |
| */ |
| if (isIn && ! thisPredMarked) |
| { |
| /* If we get here for an IN-list probe pred then we know |
| * that we are *not* using the probe predicate as a |
| * start/stop key. We also know that we're in the middle |
| * of modifying access paths (because pushPreds is true), |
| * which means we are preparing to generate code. Those |
| * two facts together mean we have to "revert" the |
| * probe predicate back to its original state so that |
| * it can be generated as normal IN-list. That "revert" |
| * operation happens from within the generateExpression() |
| * method of BinaryOperatorNode.java. |
| */ |
| continue; |
| } |
| |
| /* |
| ** Push the predicate down. They get pushed down in the |
| ** order of the index. |
| */ |
| |
| /* If this is an InListOperator predicate, make a copy of the |
| * the predicate (including the AND node contained within it) |
| * and then push the _copy_ (instead of the original) into |
| * optTable. We need to do this to avoid having the exact |
| * same Predicate object (and in particular, the exact same |
| * AndNode object) be referenced in both optTable and this.v, |
| * which can lead to an infinite recursion loop when we get to |
| * restorePredicates(), and thus to stack overflow |
| * (beetle 4974). |
| * |
| * Note: we don't do this if the predicate is an IN-list |
| * probe predicate. In that case we want to push the |
| * predicate down to the base table for special handling. |
| */ |
| Predicate predToPush; |
| if (isIn && !thisPred.isInListProbePredicate()) |
| { |
| AndNode andCopy = new AndNode( |
| thisPred.getAndNode().getLeftOperand(), |
| thisPred.getAndNode().getRightOperand(), |
| getContextManager()); |
| andCopy.copyFields(thisPred.getAndNode()); |
| Predicate predCopy = new Predicate( |
| andCopy, |
| thisPred.getReferencedSet(), |
| getContextManager()); |
| predCopy.copyFields(thisPred); |
| predToPush = predCopy; |
| } |
| else |
| { |
| predToPush = thisPred; |
| } |
| |
| if (optTable.pushOptPredicate(predToPush)) |
| { |
| /* Although we generated dynamic start and stop keys |
| * for "in", we still need this predicate for further |
| * restriction--*unless* we're dealing with a probe |
| * predicate, in which case the restriction is handled |
| * via execution-time index probes (for more see |
| * execute/MultiProbeTableScanResultSet.java). |
| */ |
| if (!isIn || thisPred.isInListProbePredicate()) |
| removeOptPredicate(thisPred); |
| } |
| else if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(false, |
| "pushOptPredicate expected to be true"); |
| } |
| } |
| else |
| { |
| /* |
| ** We're not pushing the predicates down, so put them at the |
| ** beginning of this predicate list in index order. |
| */ |
| removeOptPredicate(thisPred); |
| addOptPredicate(thisPred, i); |
| } |
| } |
| } |
| |
| /** |
| * Add a Predicate to the list. |
| * |
| * @param predicate A Predicate to add to the list |
| * |
| * @exception StandardException Thrown on error |
| */ |
| |
| void addPredicate(Predicate predicate) throws StandardException |
| { |
| if (predicate.isStartKey()) |
| numberOfStartPredicates++; |
| if (predicate.isStopKey()) |
| numberOfStopPredicates++; |
| if (predicate.isQualifier()) |
| numberOfQualifiers++; |
| |
| addElement(predicate); |
| } |
| |
| /** |
| * Transfer the non-qualifiers from this predicate list to the specified |
| * predicate list. |
| * This is useful for arbitrary hash join, where we need to separate the 2 |
| * as the qualifiers get applied when probing the hash table and the |
| * non-qualifiers get * applied afterwards. |
| * |
| * @param optTable The optimizable that we want qualifiers for |
| * @param otherPL ParameterList for non-qualifiers |
| * |
| * @exception StandardException Thrown on error |
| */ |
| protected void transferNonQualifiers(Optimizable optTable, PredicateList otherPL) |
| throws StandardException |
| { |
| /* Walk list backwards since we can delete while |
| * traversing the list. |
| */ |
| for (int index = size() - 1; index >= 0; index--) |
| { |
| Predicate pred = elementAt(index); |
| |
| // Transfer each non-qualifier |
| if (!pred.isRelationalOpPredicate() || |
| !pred.getRelop().isQualifier(optTable, false)) |
| { |
| pred.clearScanFlags(); |
| removeElementAt(index); |
| otherPL.addElement(pred); |
| } |
| } |
| |
| // Mark all remaining predicates as qualifiers |
| markAllPredicatesQualifiers(); |
| } |
| |
| /** |
| * Categorize the predicates in the list. Initially, this means |
| * building a bit map of the referenced tables for each predicate. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void categorize() |
| throws StandardException |
| { |
| for (Predicate p : this) |
| { |
| p.categorize(); |
| } |
| } |
| |
| |
| /** |
| * Eliminate predicates of the form: |
| * AndNode |
| * / \ |
| * true BooleanConstantNode true BooleanConstantNode |
| * This is useful when checking for a NOP PRN as the |
| * Like transformation on c1 like 'ASDF%' can leave |
| * one of these predicates in the list. |
| */ |
| void eliminateBooleanTrueAndBooleanTrue() |
| { |
| /* Walk list backwards since we can delete while |
| * traversing the list. |
| */ |
| for (int index = size() - 1; index >= 0; index--) |
| { |
| AndNode nextAnd; |
| /* Look at the current predicate from the predicate list */ |
| nextAnd = elementAt(index).getAndNode(); |
| |
| if ((nextAnd.getLeftOperand().isBooleanTrue()) && |
| (nextAnd.getRightOperand().isBooleanTrue())) |
| { |
| removeElementAt(index); |
| } |
| } |
| |
| } |
| |
| /** |
| * Rebuild a constant expression tree from the remaining constant |
| * predicates and delete those entries from the PredicateList. |
| * The rightOperand of every top level AndNode is always a true |
| * BooleanConstantNode, so we can blindly overwrite that pointer. |
| * Optimizations: |
| * |
| * We take this opportunity to eliminate: |
| * AndNode |
| * / \ |
| * true BooleanConstantNode true BooleanConstantNode |
| * |
| * We remove the AndNode if the predicate list is a single AndNode: |
| * AndNode |
| * / \ |
| * LeftOperand RightOperand |
| * |
| * becomes: |
| * LeftOperand |
| * |
| * If the leftOperand of any AndNode is False, then the entire expression |
| * will be False. The expression simple becomes: |
| * false BooleanConstantNode |
| * |
| * @return ValueNode The rebuilt expression tree. |
| */ |
| ValueNode restoreConstantPredicates() |
| throws StandardException |
| { |
| AndNode nextAnd; |
| AndNode falseAnd = null; |
| ValueNode restriction = null; |
| |
| /* Walk list backwards since we can delete while |
| * traversing the list. |
| */ |
| for (int index = size() - 1; index >= 0; index--) |
| { |
| /* Look at the current predicate from the predicate list */ |
| nextAnd = elementAt(index).getAndNode(); |
| |
| // Skip over the predicate if it is not a constant expression |
| if (! nextAnd.isConstantExpression()) |
| { |
| continue; |
| } |
| |
| // This node is a constant expression, so we can remove it from the list |
| removeElementAt(index); |
| |
| /* We can skip over TRUE AND TRUE */ |
| if ((nextAnd.getLeftOperand().isBooleanTrue()) && |
| (nextAnd.getRightOperand().isBooleanTrue())) |
| { |
| continue; |
| } |
| |
| /* Remember if we see a false BooleanConstantNode */ |
| if (nextAnd.getLeftOperand().isBooleanFalse()) |
| { |
| falseAnd = nextAnd; |
| } |
| |
| if (restriction != null) |
| { |
| nextAnd.setRightOperand(restriction); |
| /* If any of the predicates is nullable, then the resulting |
| * tree must be nullable. |
| */ |
| if (restriction.getTypeServices().isNullable()) |
| { |
| nextAnd.setNullability(true); |
| } |
| } |
| restriction = nextAnd; |
| } |
| |
| /* If restriction is a single AndNode, then it's rightOperand must be |
| * a true BooleanConstantNode. We simply chop out the AndNode and set |
| * restriction to AndNode.leftOperand. |
| */ |
| if ((restriction != null) && |
| (((AndNode) restriction).getRightOperand().isBooleanTrue())) |
| { |
| restriction = ((AndNode) restriction).getLeftOperand(); |
| } |
| else if (falseAnd != null) |
| { |
| /* Expression is ... AND FALSE AND ... |
| * Replace the entire expression with a false BooleanConstantNode. |
| */ |
| restriction = falseAnd.getLeftOperand(); |
| } |
| |
| return restriction; |
| } |
| |
| /** |
| * Rebuild an expression tree from the remaining predicates and delete those |
| * entries from the PredicateList. |
| * The rightOperand of every top level AndNode is always a true |
| * BooleanConstantNode, so we can blindly overwrite that pointer. |
| * Optimizations: |
| * |
| * We take this opportunity to eliminate: |
| * AndNode |
| * / \ |
| * true BooleanConstantNode true BooleanConstantNode |
| * |
| * We remove the AndNode if the predicate list is a single AndNode: |
| * AndNode |
| * / \ |
| * LeftOperand RightOperand |
| * |
| * becomes: |
| * LeftOperand |
| * |
| * If the leftOperand of any AndNode is False, then the entire expression |
| * will be False. The expression simple becomes: |
| * false BooleanConstantNode |
| * |
| * @return ValueNode The rebuilt expression tree. |
| */ |
| ValueNode restorePredicates() |
| throws StandardException |
| { |
| AndNode nextAnd; |
| AndNode falseAnd = null; |
| ValueNode restriction = null; |
| |
| int size = size(); |
| for (int index = 0; index < size; index++) |
| { |
| nextAnd = elementAt(index).getAndNode(); |
| |
| /* We can skip over TRUE AND TRUE */ |
| if ((nextAnd.getLeftOperand().isBooleanTrue()) && |
| (nextAnd.getRightOperand().isBooleanTrue())) |
| { |
| continue; |
| } |
| |
| /* Remember if we see a false BooleanConstantNode */ |
| if (nextAnd.getLeftOperand().isBooleanFalse()) |
| { |
| falseAnd = nextAnd; |
| } |
| |
| if (restriction != null) |
| { |
| nextAnd.setRightOperand(restriction); |
| /* If any of the predicates is nullable, then the resulting |
| * tree must be nullable. |
| */ |
| if (restriction.getTypeServices().isNullable()) |
| { |
| nextAnd.setNullability(true); |
| } |
| } |
| restriction = nextAnd; |
| } |
| |
| /* If restriction is a single AndNode, then it's rightOperand must be |
| * a true BooleanConstantNode. We simply chop out the AndNode and set |
| * restriction to AndNode.leftOperand. |
| */ |
| if ((restriction != null) && |
| (((AndNode) restriction).getRightOperand().isBooleanTrue())) |
| { |
| restriction = ((AndNode) restriction).getLeftOperand(); |
| } |
| else if (falseAnd != null) |
| { |
| /* Expression is ... AND FALSE AND ... |
| * Replace the entire expression with a simple false |
| * BooleanConstantNode. |
| */ |
| restriction = falseAnd.getLeftOperand(); |
| } |
| |
| /* Remove all predicates from the list */ |
| removeAllElements(); |
| return restriction; |
| } |
| |
| /** |
| * Remap all ColumnReferences in this tree to be clones of the |
| * underlying expression. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void remapColumnReferencesToExpressions() throws StandardException |
| { |
| for (Predicate p : this) |
| { |
| p.setAndNode( |
| (AndNode)p.getAndNode().remapColumnReferencesToExpressions()); |
| } |
| } |
| |
| /** |
| * Break apart the search clause into matching a PredicateList |
| * where each top level predicate is a separate element in the list. |
| * Build a bit map to represent the FromTables referenced within each |
| * top level predicate. |
| * NOTE: We want the rightOperand of every AndNode to be true, in order |
| * to simplify the algorithm for putting the predicates back into the tree. |
| * (As we put an AndNode back into the tree, we can ignore it's rightOperand.) |
| * |
| * @param numTables Number of tables in the DML Statement |
| * @param searchClause The search clause to operate on. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void pullExpressions(int numTables, |
| ValueNode searchClause) |
| throws StandardException |
| { |
| AndNode thisAnd; |
| AndNode topAnd; |
| JBitSet newJBitSet; |
| Predicate newPred; |
| |
| if (searchClause != null) |
| { |
| topAnd = (AndNode) searchClause; |
| BooleanConstantNode trueNode = |
| new BooleanConstantNode(true, getContextManager()); |
| |
| while (topAnd.getRightOperand() instanceof AndNode) |
| { |
| /* Break out the next top AndNode */ |
| thisAnd = topAnd; |
| topAnd = (AndNode) topAnd.getRightOperand(); |
| thisAnd.setRightOperand(null); |
| |
| /* Set the rightOperand to true */ |
| thisAnd.setRightOperand(trueNode); |
| |
| /* Add the top AndNode to the PredicateList */ |
| newJBitSet = new JBitSet(numTables); |
| newPred = |
| new Predicate(thisAnd, newJBitSet, getContextManager()); |
| addPredicate(newPred); |
| } |
| |
| /* Add the last top AndNode to the PredicateList */ |
| newJBitSet = new JBitSet(numTables); |
| newPred = new Predicate(topAnd, newJBitSet, getContextManager()); |
| addPredicate(newPred); |
| } |
| } |
| |
| /** |
| * XOR fromMap with the referenced table map in every remaining |
| * Predicate in the list. This is useful when pushing down |
| * multi-table predicates. |
| * |
| * @param fromMap The JBitSet to XOR with. |
| */ |
| void xorReferencedSet(JBitSet fromMap) |
| { |
| for (Predicate p : this) |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT( |
| fromMap.size() == p.getReferencedSet().size(), |
| "fromMap.size() (" + fromMap.size() + |
| ") does not equal predicate.getReferencedSet().size() (" + |
| p.getReferencedSet().size()); |
| } |
| |
| p.getReferencedSet().xor(fromMap); |
| } |
| } |
| |
| private void countScanFlags() |
| { |
| for (Predicate p : this) |
| { |
| if (p.isStartKey()) |
| numberOfStartPredicates++; |
| if (p.isStopKey()) |
| numberOfStopPredicates++; |
| if (p.isQualifier()) |
| numberOfQualifiers++; |
| } |
| } |
| |
| /** |
| * Check if a node is representing a constant or a parameter. |
| * |
| * @param node the node to check |
| * @return {@code true} if the node is a constant or a parameter, {@code |
| * false} otherwise |
| */ |
| private static boolean isConstantOrParameterNode(ValueNode node) { |
| return node instanceof ConstantNode || node instanceof ParameterNode; |
| } |
| |
| /** |
| * Push all predicates, which can be pushed, into the underlying select. |
| * A predicate can be pushed into an underlying select if the source of |
| * every ColumnReference in the predicate is itself a ColumnReference. |
| * |
| * This is useful when attempting to push predicates into non-flattenable |
| * views or derived tables or into unions. |
| * |
| * @param select The underlying SelectNode. |
| * @param copyPredicate Whether to make a copy of the predicate |
| * before pushing |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void pushExpressionsIntoSelect(SelectNode select, boolean copyPredicate) |
| throws StandardException |
| { |
| /* Walk list backwards since we can delete while |
| * traversing the list. |
| */ |
| for (int index = size() - 1; index >= 0; index--) |
| { |
| Predicate predicate = elementAt(index); |
| |
| CollectNodesVisitor<ColumnReference> getCRs = |
| new CollectNodesVisitor<ColumnReference>(ColumnReference.class); |
| |
| predicate.getAndNode().accept(getCRs); |
| List<ColumnReference> colRefs = getCRs.getList(); |
| |
| /* state doesn't become true until we find the 1st |
| * ColumnReference. (We probably will always find |
| * at least 1 CR, but just to be safe, ...) |
| */ |
| boolean state = colRefs.size() > 0; |
| if (state) |
| { |
| for (ColumnReference ref : colRefs) |
| { |
| if (!ref.pointsToColumnReference()) |
| { |
| state = false; |
| break; |
| } |
| } |
| } |
| |
| if (!state) |
| continue; |
| |
| if (copyPredicate) |
| { |
| // Copy this predicate and push this instead |
| AndNode andNode = predicate.getAndNode(); |
| ValueNode leftOperand; |
| ColumnReference crNode; |
| BinaryRelationalOperatorNode opNode=null; |
| InListOperatorNode inNode=null; |
| |
| // Make sure we are only pushing binary relations and InList for |
| // copyPredicate case. It should be benificial to push expressions that |
| // can be pushed, so they can be applied closer to the data. |
| |
| if (andNode.getLeftOperand() instanceof BinaryRelationalOperatorNode) |
| { |
| opNode = (BinaryRelationalOperatorNode) andNode.getLeftOperand(); |
| // Investigate using invariant interface to check rightOperand |
| if (! (opNode.getLeftOperand() instanceof ColumnReference) || |
| ! isConstantOrParameterNode(opNode.getRightOperand())) |
| continue; |
| |
| crNode = (ColumnReference) opNode.getLeftOperand(); |
| } |
| else if (andNode.getLeftOperand() instanceof InListOperatorNode) |
| { |
| inNode = (InListOperatorNode) andNode.getLeftOperand(); |
| |
| if (!(inNode.getLeftOperand() instanceof ColumnReference)) |
| { |
| // A predicate can be pushed into an underlying select |
| // if the source of every ColumnReference in the |
| // predicate is itself a ColumnReference. |
| // In this case the left operand is not a |
| // ColumnReference so do not push. |
| |
| continue; |
| } |
| else if (!(inNode.getRightOperandList().isConstantExpression())) |
| { |
| // only push down constant expressions, |
| // skipping this one that is not |
| |
| continue; |
| } |
| |
| crNode = (ColumnReference) inNode.getLeftOperand(); |
| } |
| else |
| { |
| continue; |
| } |
| |
| // Remap this crNode to underlying column reference in the select, if possible. |
| ColumnReference newCRNode = select.findColumnReferenceInResult(crNode.getColumnName()); |
| if (newCRNode == null) |
| continue; |
| |
| // Create a copy of the predicate to push down |
| // <column> <relop> <value> AND TRUE |
| if (andNode.getLeftOperand() instanceof BinaryRelationalOperatorNode) |
| { |
| /* If the operator is a binary relational operator that was |
| * created for a probe predicate then we have to make a |
| * copy of the underlying IN-list as well, so that we can |
| * give it the correct left operand (i.e. the new Column |
| * Reference node). Then we pass that copy into the new |
| * relational operator node. |
| */ |
| inNode = opNode.getInListOp(); |
| if (inNode != null) |
| { |
| inNode = inNode.shallowCopy(); |
| inNode.setLeftOperand(newCRNode); |
| } |
| |
| BinaryRelationalOperatorNode newRelop = |
| new BinaryRelationalOperatorNode( |
| opNode.kind, |
| newCRNode, |
| opNode.getRightOperand(), |
| inNode, |
| opNode.getForQueryRewrite(), |
| getContextManager()); |
| newRelop.bindComparisonOperator(); |
| leftOperand = newRelop; |
| } |
| else |
| { |
| InListOperatorNode newInNode = new InListOperatorNode( |
| newCRNode, |
| inNode.getRightOperandList(), |
| getContextManager()); |
| newInNode.setType(inNode.getTypeServices()); |
| leftOperand = newInNode; |
| } |
| |
| // Convert the predicate into CNF form: |
| // |
| // AND |
| // / \ |
| // leftOperand true |
| // |
| ValueNode trueNode = |
| new BooleanConstantNode(true, getContextManager()); |
| AndNode newAnd = |
| new AndNode(leftOperand, trueNode, getContextManager()); |
| newAnd.postBindFixup(); |
| JBitSet tableMap = new JBitSet(select.getReferencedTableMap().size()); |
| |
| // Use newly constructed predicate |
| predicate = |
| new Predicate(newAnd, tableMap, getContextManager()); |
| } |
| else |
| { |
| // keep the counters up to date when removing a predicate |
| if (predicate.isStartKey()) |
| numberOfStartPredicates--; |
| if (predicate.isStopKey()) |
| numberOfStopPredicates--; |
| if (predicate.isQualifier()) |
| numberOfQualifiers--; |
| |
| /* Clear all of the scan flags since they may be different |
| * due to the splitting of the list. |
| */ |
| predicate.clearScanFlags(); |
| // Remove this predicate from the list |
| removeElementAt(index); |
| } |
| |
| // Push it into the select |
| select.pushExpressionsIntoSelect(predicate); |
| } |
| } |
| |
| /** |
| * Mark all of the RCs and the RCs in their RC/VCN chain |
| * referenced in the predicate list as referenced. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void markReferencedColumns() |
| throws StandardException |
| { |
| CollectNodesVisitor<ColumnReference> collectCRs = |
| new CollectNodesVisitor<ColumnReference>(ColumnReference.class); |
| |
| for (Predicate p : this) |
| { |
| p.getAndNode().accept(collectCRs); |
| } |
| |
| for (ColumnReference ref : collectCRs.getList()) |
| { |
| ResultColumn source = ref.getSource(); |
| |
| // DERBY-4391: Don't try to call markAllRCsInChainReferenced() if |
| // source is null. This can happen if the ColumnReference is |
| // pointing to a column that is not from a base table. For instance |
| // if we have a VALUES clause like (VALUES (1, 2), (3, 4)) V1(I, J) |
| // then a column reference to V1.I won't have a source. |
| if (source != null) { |
| source.markAllRCsInChainReferenced(); |
| } |
| } |
| } |
| |
| /** |
| * Update the array of columns in = conditions with constants |
| * or correlation or join columns. This is useful when doing |
| * subquery flattening on the basis of an equality condition. |
| * |
| * @param tableNumber The tableNumber of the table from which |
| * the columns of interest come from. |
| * @param eqOuterCols Array of booleans for noting which columns |
| * are in = predicates with constants or |
| * correlation columns. |
| * @param tableNumbers Array of table numbers in this query block. |
| * @param resultColTable tableNumber is the table the result columns are |
| * coming from |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void checkTopPredicatesForEqualsConditions( |
| int tableNumber, |
| boolean[] eqOuterCols, |
| int[] tableNumbers, |
| JBitSet[] tableColMap, |
| boolean resultColTable) |
| throws StandardException |
| { |
| for (Predicate p : this) |
| { |
| p.getAndNode().checkTopPredicatesForEqualsConditions( |
| tableNumber, eqOuterCols, tableNumbers, tableColMap, |
| resultColTable); |
| } |
| } |
| |
| /** |
| * Check if all of the predicates in the list are pushable. |
| * |
| * @return Whether or not all of the predicates in the list are pushable. |
| */ |
| boolean allPushable() |
| { |
| for (Predicate p : this) |
| { |
| if (! p.getPushable()) |
| { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Check if all the predicates reference a given {@code FromBaseTable}. |
| * |
| * @param fbt the {@code FromBaseTable} to check for |
| * @return {@code true} if the table is referenced by all predicates, |
| * {@code false} otherwise |
| */ |
| boolean allReference(FromBaseTable fbt) { |
| int tableNumber = fbt.getTableNumber(); |
| |
| for (Predicate p : this) { |
| if (!p.getReferencedSet().get(tableNumber)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Build a list of pushable predicates, if any, |
| * that satisfy the referencedTableMap. |
| * |
| * @param referencedTableMap The referenced table map |
| * |
| * @return A list of pushable predicates, if any, |
| * that satisfy the referencedTableMap. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| PredicateList getPushablePredicates(JBitSet referencedTableMap) |
| throws StandardException |
| { |
| PredicateList pushPList = null; |
| |
| // Walk the list backwards because of possible deletes |
| for (int index = size() - 1; index >= 0; index--) |
| { |
| Predicate predicate = elementAt(index); |
| if (! predicate.getPushable()) |
| { |
| continue; |
| } |
| |
| JBitSet curBitSet = predicate.getReferencedSet(); |
| |
| /* Do we have a match? */ |
| if (referencedTableMap.contains(curBitSet)) |
| { |
| /* Add the matching predicate to the push list */ |
| if (pushPList == null) |
| { |
| pushPList = new PredicateList(getContextManager()); |
| } |
| pushPList.addPredicate(predicate); |
| |
| /* Remap all of the ColumnReferences to point to the |
| * source of the values. |
| */ |
| RemapCRsVisitor rcrv = new RemapCRsVisitor(true); |
| predicate.getAndNode().accept(rcrv); |
| |
| /* Remove the matching predicate from the outer list */ |
| removeElementAt(index); |
| } |
| } |
| return pushPList; |
| } |
| |
| /** |
| * Decrement the level of any CRs from the subquery's |
| * FROM list that are interesting to transitive closure. |
| * |
| * @param fromList The subquery's FROM list. |
| * @param decrement Decrement size. |
| */ |
| void decrementLevel(FromList fromList, int decrement) |
| { |
| int[] tableNumbers = fromList.getTableNumbers(); |
| |
| /* For each top level relop, find all top level |
| * CRs from the subquery and decrement their |
| * nesting level. |
| */ |
| for (Predicate predicate : this) |
| { |
| ColumnReference cr1 = null; |
| ColumnReference cr2 = null; |
| ValueNode vn = predicate.getAndNode().getLeftOperand(); |
| |
| if (vn instanceof BinaryOperatorNode) |
| { |
| BinaryOperatorNode bon = (BinaryOperatorNode) vn; |
| if (bon.getLeftOperand() instanceof ColumnReference) |
| { |
| cr1 = (ColumnReference) bon.getLeftOperand(); |
| } |
| if (bon.getRightOperand() instanceof ColumnReference) |
| { |
| cr2 = (ColumnReference) bon.getRightOperand(); |
| } |
| } |
| else if (vn instanceof UnaryOperatorNode) |
| { |
| UnaryOperatorNode uon = (UnaryOperatorNode) vn; |
| if (uon.getOperand() instanceof ColumnReference) |
| { |
| cr1 = (ColumnReference) uon.getOperand(); |
| } |
| } |
| |
| /* See if any of the CRs need to have their |
| * source level decremented. |
| */ |
| if (cr1 != null) |
| { |
| int sourceTable = cr1.getTableNumber(); |
| for (int inner = 0; inner < tableNumbers.length; inner++) |
| if (tableNumbers[inner] == sourceTable) |
| { |
| cr1.setSourceLevel( |
| cr1.getSourceLevel() - decrement); |
| break; |
| } |
| } |
| |
| if (cr2 != null) |
| { |
| int sourceTable = cr2.getTableNumber(); |
| for (int inner = 0; inner < tableNumbers.length; inner++) |
| if (tableNumbers[inner] == sourceTable) |
| { |
| cr2.setSourceLevel( |
| cr2.getSourceLevel() - decrement); |
| break; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Perform transitive closure on join clauses. For each table in the query, |
| * we build a list of equijoin clauses of the form: |
| * ColumnReference relop ColumnReference |
| * Each join clause is put on 2 lists since it joins 2 tables. |
| * |
| * We then walk the array of lists. We first walk it as the outer list. |
| * For each equijoin predicate, we assign an equivalence class if it does |
| * not yet have one. We then walk the predicate list (as middle) for the |
| * other table, searching for other equijoins with the middle table number |
| * and column number. All such predicates are assigned the same |
| * equivalence class. We then walk the predicate list (as inner) for the |
| * other side of the middle predicate to see if we can find an equijoin |
| * between outer and inner. If so, then we simply assign it to the same |
| * equivalence class. If not, then we add the new equijoin clause. |
| * |
| * Note that an equijoin predicate between two tables CANNOT be |
| * used for transitive closure, if either of the tables is in the |
| * fromlist for NOT EXISTS. In that case, the join predicate |
| * actually specifies that the rows from the indicated table must |
| * NOT exist, and therefore those non-existent rows cannot be |
| * transitively joined to the other matching tables. See DERBY-3033 |
| * for a description of a situation in which this actually arises. |
| * |
| * @param numTables The number of tables in the query |
| * @param fromList The FromList in question. |
| * @param cc The CompilerContext to use |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void joinClauseTransitiveClosure(int numTables, |
| FromList fromList, CompilerContext cc) |
| throws StandardException |
| { |
| // Nothing to do if < 3 tables |
| if (fromList.size() < 3) |
| { |
| return; |
| } |
| |
| /* Create an array of numTables PredicateLists to hold the join clauses. */ |
| PredicateList[] joinClauses = new PredicateList[numTables]; |
| for (int index = 0; index < numTables; index++) |
| { |
| joinClauses[index] = new PredicateList(getContextManager()); |
| } |
| |
| /* Pull the equijoin clauses, putting each one in the list for |
| * each of the tables being joined. |
| */ |
| for (Predicate predicate : this) |
| { |
| ValueNode vn = predicate.getAndNode().getLeftOperand(); |
| |
| if (! (vn.isBinaryEqualsOperatorNode())) |
| { |
| continue; |
| } |
| |
| /* Is this an equijoin clause between 2 ColumnReferences? */ |
| BinaryRelationalOperatorNode equals = |
| (BinaryRelationalOperatorNode) vn; |
| ValueNode left = equals.getLeftOperand(); |
| ValueNode right = equals.getRightOperand(); |
| |
| if ((left instanceof ColumnReference && |
| right instanceof ColumnReference)) |
| { |
| ColumnReference leftCR = (ColumnReference) left; |
| ColumnReference rightCR = (ColumnReference) right; |
| if (leftCR.getSourceLevel() == rightCR.getSourceLevel() && |
| leftCR.getTableNumber() != rightCR.getTableNumber() && |
| !fromList.tableNumberIsNotExists(leftCR.getTableNumber()) && |
| !fromList.tableNumberIsNotExists(rightCR.getTableNumber())) |
| { |
| // Add the equijoin clause to each of the lists |
| joinClauses[leftCR.getTableNumber()].addElement(predicate); |
| joinClauses[rightCR.getTableNumber()].addElement(predicate); |
| } |
| continue; |
| } |
| } |
| |
| /* Walk each of the PredicateLists, using each 1 as the starting point |
| * of an equivalence class. |
| */ |
| for (int index = 0; index < numTables; index++) |
| { |
| PredicateList outerJCL = joinClauses[index]; |
| |
| // Skip the empty lists |
| if (outerJCL.size() == 0) |
| { |
| continue; |
| } |
| |
| /* Put all of the join clauses that already have an equivalence |
| * class at the head of the outer list to optimize search. |
| */ |
| ArrayList<Predicate> movePreds = new ArrayList<Predicate>(); |
| for (int jcIndex = outerJCL.size() - 1; jcIndex >= 0; jcIndex--) |
| { |
| Predicate predicate = outerJCL.elementAt(jcIndex); |
| if (predicate.getEquivalenceClass() != -1) |
| { |
| outerJCL.removeElementAt(jcIndex); |
| movePreds.add(predicate); |
| } |
| } |
| for (int mpIndex = 0; mpIndex < movePreds.size(); mpIndex++) |
| { |
| outerJCL.insertElementAt(movePreds.get(mpIndex), 0); |
| } |
| |
| // Walk this list as the outer |
| for (int outerIndex = 0; outerIndex < outerJCL.size(); outerIndex++) |
| { |
| ColumnReference innerCR = null; |
| ColumnReference outerCR; |
| int outerTableNumber = index; |
| int middleTableNumber; |
| int outerColumnNumber; |
| int middleColumnNumber; |
| int outerEC; |
| |
| /* Assign an equivalence class to those Predicates |
| * that have not already been assigned an equivalence class. |
| */ |
| Predicate outerP = outerJCL.elementAt(outerIndex); |
| |
| if (outerP.getEquivalenceClass() == -1) |
| { |
| outerP.setEquivalenceClass(cc.getNextEquivalenceClass()); |
| } |
| outerEC = outerP.getEquivalenceClass(); |
| |
| // Get the table and column numbers |
| BinaryRelationalOperatorNode equals = |
| (BinaryRelationalOperatorNode) outerP.getAndNode().getLeftOperand(); |
| ColumnReference leftCR = (ColumnReference) equals.getLeftOperand(); |
| ColumnReference rightCR = (ColumnReference) equals.getRightOperand(); |
| |
| if (leftCR.getTableNumber() == outerTableNumber) |
| { |
| outerColumnNumber = leftCR.getColumnNumber(); |
| middleTableNumber = rightCR.getTableNumber(); |
| middleColumnNumber = rightCR.getColumnNumber(); |
| outerCR = leftCR; |
| } |
| else |
| { |
| outerColumnNumber = rightCR.getColumnNumber(); |
| middleTableNumber = leftCR.getTableNumber(); |
| middleColumnNumber = leftCR.getColumnNumber(); |
| outerCR = rightCR; |
| } |
| |
| /* Walk the other list as the middle to find other join clauses |
| * in the chain/equivalence class |
| */ |
| PredicateList middleJCL = joinClauses[middleTableNumber]; |
| for (Predicate middleP : middleJCL) |
| { |
| /* Skip those Predicates that have already been |
| * assigned a different equivalence class. |
| */ |
| if (middleP.getEquivalenceClass() != -1 && |
| middleP.getEquivalenceClass() != outerEC) |
| { |
| continue; |
| } |
| |
| int innerTableNumber; |
| int innerColumnNumber; |
| |
| // Get the table and column numbers |
| BinaryRelationalOperatorNode middleEquals = |
| (BinaryRelationalOperatorNode) middleP.getAndNode().getLeftOperand(); |
| ColumnReference mLeftCR = (ColumnReference) middleEquals.getLeftOperand(); |
| ColumnReference mRightCR = (ColumnReference) middleEquals.getRightOperand(); |
| |
| /* Find the other side of the equijoin, skipping this predicate if |
| * not on middleColumnNumber. |
| */ |
| if (mLeftCR.getTableNumber() == middleTableNumber) |
| { |
| if (mLeftCR.getColumnNumber() != middleColumnNumber) |
| { |
| continue; |
| } |
| innerTableNumber = mRightCR.getTableNumber(); |
| innerColumnNumber = mRightCR.getColumnNumber(); |
| } |
| else |
| { |
| if (mRightCR.getColumnNumber() != middleColumnNumber) |
| { |
| continue; |
| } |
| innerTableNumber = mLeftCR.getTableNumber(); |
| innerColumnNumber = mLeftCR.getColumnNumber(); |
| } |
| |
| // Skip over outerTableNumber.outerColumnNumber = middleTableNumber.middleColumnNumber |
| if (outerTableNumber == innerTableNumber && |
| outerColumnNumber == innerColumnNumber) |
| { |
| continue; |
| } |
| |
| // Put this predicate into the outer equivalence class |
| middleP.setEquivalenceClass(outerEC); |
| |
| /* Now go to the inner list and see if there is an equijoin |
| * between inner and outer on innerColumnNumber and outerColumnNumber. |
| * If so, then we continue our walk across middle, otherwise we |
| * add a new equijoin to both the inner and outer lists before |
| * continuing to walk across middle. |
| */ |
| |
| int newTableNumber; |
| int newColumnNumber; |
| Predicate innerP = null; |
| PredicateList innerJCL = joinClauses[innerTableNumber]; |
| int innerIndex = 0; |
| for ( ; innerIndex < innerJCL.size(); innerIndex++) |
| { |
| innerP = innerJCL.elementAt(innerIndex); |
| |
| // Skip over predicates with other equivalence classes |
| if (innerP.getEquivalenceClass() != -1 && |
| innerP.getEquivalenceClass() != outerEC) |
| { |
| continue; |
| } |
| |
| /* Now we see if the inner predicate completes the loop. |
| * If so, then add it to the outer equivalence class |
| * and stop. |
| */ |
| |
| // Get the table and column numbers |
| BinaryRelationalOperatorNode innerEquals = |
| (BinaryRelationalOperatorNode) innerP.getAndNode().getLeftOperand(); |
| ColumnReference iLeftCR = (ColumnReference) innerEquals.getLeftOperand(); |
| ColumnReference iRightCR = (ColumnReference) innerEquals.getRightOperand(); |
| |
| if (iLeftCR.getTableNumber() == innerTableNumber) |
| { |
| if (iLeftCR.getColumnNumber() != innerColumnNumber) |
| { |
| continue; |
| } |
| newTableNumber = iRightCR.getTableNumber(); |
| newColumnNumber = iRightCR.getColumnNumber(); |
| innerCR = iLeftCR; |
| } |
| else |
| { |
| if (iRightCR.getColumnNumber() != innerColumnNumber) |
| { |
| continue; |
| } |
| newTableNumber = iLeftCR.getTableNumber(); |
| newColumnNumber = iLeftCR.getColumnNumber(); |
| innerCR = iRightCR; |
| } |
| |
| // Did we find the equijoin between inner and outer |
| if (newTableNumber == outerTableNumber && |
| newColumnNumber == outerColumnNumber) |
| { |
| break; |
| } |
| } |
| |
| // Did we find an equijoin on inner and outer |
| if (innerIndex != innerJCL.size()) |
| { |
| // match found |
| // Put this predicate into the outer equivalence class |
| innerP.setEquivalenceClass(outerEC); |
| continue; |
| } |
| |
| // No match, add new equijoin |
| // Build a new predicate |
| BinaryRelationalOperatorNode newEquals = |
| new BinaryRelationalOperatorNode( |
| BinaryRelationalOperatorNode.K_EQUALS, |
| outerCR.getClone(), |
| innerCR.getClone(), |
| false, |
| getContextManager()); |
| newEquals.bindComparisonOperator(); |
| |
| // Create new predicate into CNF: |
| // |
| // AND |
| // / \ |
| // newEquals true |
| // |
| ValueNode trueNode = |
| new BooleanConstantNode(true, getContextManager()); |
| AndNode newAnd = |
| new AndNode(newEquals, trueNode, getContextManager()); |
| newAnd.postBindFixup(); |
| |
| // Add a new predicate to both the equijoin clauses and this list |
| JBitSet tableMap = new JBitSet(numTables); |
| newAnd.categorize(tableMap, false); |
| Predicate newPred = |
| new Predicate(newAnd, tableMap, getContextManager()); |
| newPred.setEquivalenceClass(outerEC); |
| addPredicate(newPred); |
| /* Add the new predicate right after the outer position |
| * so that we can follow all of the predicates in equivalence |
| * classes before those that do not yet have equivalence classes. |
| */ |
| if (outerIndex != outerJCL.size() - 1) |
| { |
| outerJCL.insertElementAt(newPred, outerIndex + 1); |
| } |
| else |
| { |
| outerJCL.addElement(newPred); |
| } |
| |
| if (outerJCL != innerJCL) { |
| innerJCL.addElement(newPred); |
| } else { |
| // DERBY-4387: Avoid adding <t1>.a = <t1>.b twice to |
| // the same predicate list, so do nothing since we |
| // already added predicate to outerJCL above. |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Perform transitive closure on search clauses. We build a |
| * list of search clauses of the form: |
| * <ColumnReference> <RelationalOperator> [<ConstantNode>] |
| * We also build a list of equijoin conditions of form: |
| * <ColumnReference1> = <ColumnReference2> |
| * where both columns are from different tables in the same query block. |
| * For each search clause in the list, we search the equijoin list to see |
| * if there is an equijoin clause on the same column. If so, then we |
| * search the search clause list for a search condition on the column |
| * being joined against with the same relation operator and constant. If |
| * a match is found, then there is no need to add a new predicate. |
| * Otherwise, we add a new search condition on the column being joined |
| * with. In either case, if the relational operator in the search |
| * clause is an "=" then we mark the equijoin clause as being redundant. |
| * Redundant equijoin clauses will be removed at the end of the search as |
| * they are * unnecessary. |
| * |
| * @param numTables The number of tables in the query |
| * @param hashJoinSpecified Whether or not user specified a hash join |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void searchClauseTransitiveClosure(int numTables, boolean hashJoinSpecified) |
| throws StandardException |
| { |
| PredicateList equijoinClauses = new PredicateList(getContextManager()); |
| PredicateList searchClauses = new PredicateList(getContextManager()); |
| RelationalOperator equalsNode = null; |
| |
| int size = size(); |
| for (int index = 0; index < size; index++) |
| { |
| AndNode andNode; |
| Predicate predicate; |
| predicate = elementAt(index); |
| |
| andNode = predicate.getAndNode(); |
| |
| |
| // Skip anything that's not a RelationalOperator |
| if (!predicate.isRelationalOpPredicate()) |
| { |
| continue; |
| } |
| |
| RelationalOperator operator = (RelationalOperator) andNode.getLeftOperand(); |
| // Is this an equijoin? |
| if (((ValueNode)operator).isBinaryEqualsOperatorNode()) |
| { |
| BinaryRelationalOperatorNode equals = (BinaryRelationalOperatorNode) operator; |
| // Remember any equals node for redundancy check at end |
| equalsNode = equals; |
| ValueNode left = equals.getLeftOperand(); |
| ValueNode right = equals.getRightOperand(); |
| if ((left instanceof ColumnReference && right instanceof ColumnReference)) |
| { |
| ColumnReference leftCR = (ColumnReference) left; |
| ColumnReference rightCR = (ColumnReference) right; |
| if (leftCR.getSourceLevel() == rightCR.getSourceLevel() && |
| leftCR.getTableNumber() != rightCR.getTableNumber()) |
| { |
| equijoinClauses.addElement(predicate); |
| } |
| continue; |
| } |
| } |
| |
| // Is this a usable search clause? |
| if (operator instanceof UnaryComparisonOperatorNode) |
| { |
| if (((UnaryComparisonOperatorNode) operator).getOperand() instanceof ColumnReference) |
| { |
| searchClauses.addElement(predicate); |
| } |
| continue; |
| } |
| else if (operator instanceof BinaryComparisonOperatorNode) |
| { |
| BinaryComparisonOperatorNode bcon = (BinaryComparisonOperatorNode) operator; |
| ValueNode left = bcon.getLeftOperand(); |
| ValueNode right = bcon.getRightOperand(); |
| |
| // RESOLVE: Consider using variant type of the expression, instead of |
| // ConstantNode or ParameterNode in the future. |
| if (left instanceof ColumnReference && |
| isConstantOrParameterNode(right)) |
| { |
| searchClauses.addElement(predicate); |
| } |
| else if (isConstantOrParameterNode(left) && |
| right instanceof ColumnReference) |
| { |
| // put the ColumnReference on the left to simplify things |
| andNode.setLeftOperand(bcon.getSwappedEquivalent()); |
| searchClauses.addElement(predicate); |
| } |
| continue; |
| } |
| } |
| |
| // Nothing to do if no search clauses or equijoin clauses |
| if (equijoinClauses.size() == 0 || searchClauses.size() == 0) |
| { |
| return; |
| } |
| |
| /* Now we do the real work. |
| * NOTE: We can append to the searchClauses while walking |
| * them, thus we cannot cache the value of size(). |
| */ |
| for (int scIndex = 0; scIndex < searchClauses.size(); scIndex++) |
| { |
| ColumnReference searchCR; |
| DataValueDescriptor searchODV = null; |
| RelationalOperator ro = (RelationalOperator) |
| (searchClauses.elementAt(scIndex). |
| getAndNode()).getLeftOperand(); |
| |
| // Find the ColumnReference and constant value, if any, in the search clause |
| if (ro instanceof UnaryComparisonOperatorNode) |
| { |
| searchCR = (ColumnReference) ((UnaryComparisonOperatorNode) ro).getOperand(); |
| } |
| else |
| { |
| searchCR = (ColumnReference) ((BinaryComparisonOperatorNode) ro).getLeftOperand(); |
| |
| // Don't get value for parameterNode since not known yet. |
| if (((BinaryComparisonOperatorNode) ro).getRightOperand() instanceof ConstantNode) |
| { |
| ConstantNode currCN = (ConstantNode) ((BinaryComparisonOperatorNode) ro).getRightOperand(); |
| searchODV = currCN.getValue(); |
| } |
| else searchODV = null; |
| } |
| // Cache the table and column numbers of searchCR |
| int tableNumber = searchCR.getTableNumber(); |
| int colNumber = searchCR.getColumnNumber(); |
| |
| // Look for any equijoin clauses of interest |
| for (Predicate predicate : equijoinClauses) |
| { |
| /* Skip the current equijoin clause if it has already been used |
| * when adding a new search clause of the same type |
| * via transitive closure. |
| * NOTE: We check the type of the search clause instead of just the |
| * fact that a search clause was added because multiple search clauses |
| * can get added when preprocessing LIKE and BETWEEN. |
| */ |
| if (predicate.transitiveSearchClauseAdded(ro)) |
| { |
| continue; |
| } |
| |
| BinaryRelationalOperatorNode equals = (BinaryRelationalOperatorNode) |
| predicate.getAndNode().getLeftOperand(); |
| ColumnReference leftCR = (ColumnReference) equals.getLeftOperand(); |
| ColumnReference rightCR = (ColumnReference) equals.getRightOperand(); |
| ColumnReference otherCR; |
| |
| if (leftCR.getTableNumber() == tableNumber && |
| leftCR.getColumnNumber() == colNumber) |
| { |
| otherCR = rightCR; |
| } |
| else if (rightCR.getTableNumber() == tableNumber && |
| rightCR.getColumnNumber() == colNumber) |
| { |
| otherCR = leftCR; |
| } |
| else |
| { |
| // this is not a matching equijoin clause |
| continue; |
| } |
| |
| /* At this point we've found a search clause and an equijoin that |
| * are candidates for adding a new search clause via transitive |
| * closure. Look to see if a matching search clause already |
| * exists on the other table. If not, then add one. |
| * NOTE: In either case we mark the join clause has having added |
| * a search clause of this type to short circuit any future searches |
| */ |
| predicate.setTransitiveSearchClauseAdded(ro); |
| |
| boolean match = false; |
| ColumnReference searchCR2; |
| RelationalOperator ro2; |
| |
| for (Predicate sc : searchClauses) |
| { |
| DataValueDescriptor currODV = null; |
| ro2 = (RelationalOperator)sc.getAndNode().getLeftOperand(); |
| |
| // Find the ColumnReference in the search clause |
| if (ro2 instanceof UnaryComparisonOperatorNode) |
| { |
| searchCR2 = (ColumnReference) ((UnaryComparisonOperatorNode) ro2).getOperand(); |
| } |
| else |
| { |
| searchCR2 = (ColumnReference) ((BinaryComparisonOperatorNode) ro2).getLeftOperand(); |
| if (((BinaryComparisonOperatorNode) ro2).getRightOperand() instanceof ConstantNode) |
| { |
| ConstantNode currCN = (ConstantNode) ((BinaryComparisonOperatorNode) ro2).getRightOperand(); |
| currODV = currCN.getValue(); |
| } |
| else currODV = null; |
| } |
| |
| /* Is this a match? A match is a search clause with |
| * the same operator on the same column with a comparison against |
| * the same value. |
| */ |
| if (searchCR2.getTableNumber() == otherCR.getTableNumber() && |
| searchCR2.getColumnNumber() == otherCR.getColumnNumber() && |
| ((currODV != null && searchODV != null && currODV.compare(searchODV) == 0) || |
| (currODV == null && searchODV == null)) && |
| ro2.getOperator() == ro.getOperator() && |
| ro2.getClass().getName().equals(ro.getClass().getName())) |
| { |
| match = true; |
| break; |
| } |
| } |
| |
| // Add the new search clause if no match found |
| if (! match) |
| { |
| // Build a new predicate |
| OperatorNode roClone = |
| (OperatorNode)ro.getTransitiveSearchClause( |
| (ColumnReference)otherCR.getClone()); |
| /* Set type info for the operator node */ |
| if (roClone instanceof BinaryComparisonOperatorNode) |
| { |
| ((BinaryComparisonOperatorNode) roClone).bindComparisonOperator(); |
| } |
| else |
| { |
| ((UnaryComparisonOperatorNode) roClone).bindComparisonOperator(); |
| } |
| |
| // Convert the roClone into CNF: |
| // |
| // AND |
| // / \ |
| // roClone true |
| // |
| ValueNode trueNode = |
| new BooleanConstantNode(true, getContextManager()); |
| AndNode newAnd = |
| new AndNode(roClone, trueNode, getContextManager()); |
| newAnd.postBindFixup(); |
| |
| // Add a new predicate to both the search clauses and this list |
| JBitSet tableMap = new JBitSet(numTables); |
| newAnd.categorize(tableMap, false); |
| Predicate newPred = |
| new Predicate(newAnd, tableMap, getContextManager()); |
| addPredicate(newPred); |
| searchClauses.addElement(newPred); |
| } |
| } |
| } |
| |
| /* Finally, we eliminate any equijoin clauses made redundant by |
| * transitive closure, but only if the user did not specify a hash join |
| * in the current query block. |
| */ |
| if (hashJoinSpecified) |
| { |
| return; |
| } |
| |
| /* Walk list backwards since we can delete while |
| * traversing the list. |
| */ |
| for (int index = size() - 1; index >= 0; index--) |
| { |
| Predicate predicate = elementAt(index); |
| |
| if (predicate.transitiveSearchClauseAdded(equalsNode)) |
| { |
| removeElementAt(index); |
| } |
| } |
| } |
| |
| /** |
| * Remove redundant predicates. A redundant predicate has an equivalence |
| * class (!= -1) and there are other predicates in the same equivalence |
| * class after it in the list. (Actually, we remove all of the predicates |
| * in the same equivalence class that appear after this one.) |
| */ |
| void removeRedundantPredicates() |
| { |
| /* Walk backwards since we may remove 1 or more |
| * elements for each predicate in the outer pass. |
| */ |
| int outer = size() - 1; |
| while (outer >= 0) |
| { |
| Predicate predicate = elementAt(outer); |
| int equivalenceClass = predicate.getEquivalenceClass(); |
| |
| if (equivalenceClass == -1) |
| { |
| outer--; |
| continue; |
| } |
| |
| // Walk the rest of the list backwards. |
| for (int inner = outer - 1; inner >= 0; inner--) |
| { |
| Predicate innerPredicate = elementAt(inner); |
| if (innerPredicate.getEquivalenceClass() == equivalenceClass) |
| { |
| /* Only 1 predicate per column can be marked as a start |
| * and/or a stop position. |
| * When removing a redundant predicate, we need to make sure |
| * that the outer predicate gets marked as a start and/or |
| * stop key if the inner predicate is a start and/or stop |
| * key. In this case, we are not changing the number of |
| * start and/or stop positions, so we leave that count alone. |
| */ |
| if (innerPredicate.isStartKey()) |
| { |
| predicate.markStartKey(); |
| } |
| if (innerPredicate.isStopKey()) |
| { |
| predicate.markStopKey(); |
| } |
| if (innerPredicate.isStartKey() || innerPredicate.isStopKey()) |
| { |
| if (innerPredicate.isQualifier()) |
| { |
| // Bug 5868 - Query returns duplicate rows. In order to fix this, |
| // If the inner predicate is a qualifer along with being a start and/or stop, |
| // then mark the outer predicate as a qualifer too(along with marking it as a start |
| // and/or stop) if it is not already marked as qualifer and increment the qualifiers counter |
| // The reason we do this is that a start and/or stop key is not equivalent to |
| // a qualifier. In the orderUsefulPredicates method in this class(comment on line 786), |
| // we mark a start/stop as qualifier if we have already seen a previous column in composite |
| // index whose RELOPS do not include '=' or IS NULL. And hence we should not disregard |
| // the qualifier flag of inner predicate |
| if (!predicate.isQualifier()) |
| { |
| predicate.markQualifier(); |
| numberOfQualifiers++; |
| } |
| } |
| } |
| /* |
| * If the redundant predicate is a qualifier, then we must |
| * decrement the qualifier count. (Remaining predicate is |
| * already marked correctly.) |
| */ |
| if (innerPredicate.isQualifier()) |
| { |
| numberOfQualifiers--; |
| } |
| removeElementAt(inner); |
| outer--; |
| } |
| } |
| |
| outer--; |
| } |
| } |
| |
| /** |
| * @see OptimizablePredicateList#transferPredicates |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public void transferPredicates(OptimizablePredicateList otherList, |
| JBitSet referencedTableMap, |
| Optimizable table) |
| throws StandardException |
| { |
| Predicate predicate; |
| PredicateList theOtherList = (PredicateList) otherList; |
| |
| /* Walk list backwards since we can delete while |
| * traversing the list. |
| */ |
| for (int index = size() - 1; index >= 0; index--) |
| { |
| predicate = elementAt(index); |
| |
| if (SanityManager.DEBUG) |
| { |
| if (referencedTableMap.size() != predicate.getReferencedSet().size()) |
| { |
| SanityManager.THROWASSERT( |
| "referencedTableMap.size() (" + referencedTableMap.size() + |
| ") does not equal predicate.getReferencedSet().size() (" + |
| predicate.getReferencedSet().size()); |
| } |
| } |
| |
| if (referencedTableMap.contains(predicate.getReferencedSet())) |
| { |
| // We need to keep the counters up to date when removing a predicate |
| if (predicate.isStartKey()) |
| numberOfStartPredicates--; |
| if (predicate.isStopKey()) |
| numberOfStopPredicates--; |
| if (predicate.isQualifier()) |
| numberOfQualifiers--; |
| |
| /* Clear all of the scan flags since they may be different |
| * due to the splitting of the list. |
| */ |
| predicate.clearScanFlags(); |
| // Do the actual xfer |
| theOtherList.addPredicate(predicate); |
| removeElementAt(index); |
| } |
| } |
| |
| // order the useful predicates on the other list |
| AccessPath ap = table.getTrulyTheBestAccessPath(); |
| theOtherList.orderUsefulPredicates( |
| table, |
| ap.getConglomerateDescriptor(), |
| false, |
| ap.getNonMatchingIndexScan(), |
| ap.getCoveringIndexScan()); |
| |
| // count the start/stop positions and qualifiers |
| theOtherList.countScanFlags(); |
| } |
| |
| /** |
| * @see OptimizablePredicateList#transferAllPredicates |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public void transferAllPredicates(OptimizablePredicateList otherList) |
| throws StandardException |
| { |
| PredicateList theOtherList = (PredicateList) otherList; |
| |
| for (Predicate predicate : this) |
| { |
| /* |
| ** Clear all of the scan flags since they may be different |
| ** when the new list is re-classified |
| */ |
| predicate.clearScanFlags(); |
| |
| // Add the predicate to the other list |
| theOtherList.addPredicate(predicate); |
| } |
| |
| // Remove all of the predicates from this list |
| removeAllElements(); |
| |
| /* |
| ** This list is now empty, so there are no start predicates, |
| ** stop predicates, or qualifiers. |
| */ |
| numberOfStartPredicates = 0; |
| numberOfStopPredicates = 0; |
| numberOfQualifiers= 0; |
| } |
| |
| /** |
| * @see OptimizablePredicateList#copyPredicatesToOtherList |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public void copyPredicatesToOtherList(OptimizablePredicateList otherList) |
| throws StandardException |
| { |
| for (int i = 0; i < size(); i++) |
| { |
| otherList.addOptPredicate(getOptPredicate(i)); |
| } |
| } |
| |
| /** |
| * @see OptimizablePredicateList#isRedundantPredicate |
| */ |
| public boolean isRedundantPredicate(int predNum) |
| { |
| Predicate pred = elementAt(predNum); |
| if (pred.getEquivalenceClass() == -1) |
| { |
| return false; |
| } |
| for (int index = 0; index < predNum; index++) |
| { |
| if (elementAt(index).getEquivalenceClass() == |
| pred.getEquivalenceClass()) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @see OptimizablePredicateList#setPredicatesAndProperties |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public void setPredicatesAndProperties(OptimizablePredicateList otherList) |
| throws StandardException |
| { |
| PredicateList theOtherList = (PredicateList) otherList; |
| |
| theOtherList.removeAllElements(); |
| |
| for (int i = 0; i < size(); i++) |
| { |
| theOtherList.addOptPredicate(getOptPredicate(i)); |
| } |
| |
| theOtherList.numberOfStartPredicates = numberOfStartPredicates; |
| theOtherList.numberOfStopPredicates = numberOfStopPredicates; |
| theOtherList.numberOfQualifiers = numberOfQualifiers; |
| } |
| |
| /** @see OptimizablePredicateList#startOperator */ |
| public int startOperator(Optimizable optTable) |
| { |
| int startOperator; |
| |
| /* |
| ** This is the value we will use if there are no keys. It doesn't |
| ** matter what it is, as long as the operator is one of GT or GE |
| ** (to match the openScan() interface). |
| */ |
| startOperator = ScanController.GT; |
| |
| int size = size(); |
| /* beetle 4572. start operator should be the last start key column's |
| * start operator. Note that all previous ones should be GE. |
| */ |
| for (int index = size - 1; index >= 0; index--) |
| { |
| Predicate pred = elementAt(index); |
| |
| if ( ! pred.isStartKey() ) |
| continue; |
| |
| startOperator = pred.getStartOperator(optTable); |
| break; |
| } |
| return startOperator; |
| } |
| |
| /** |
| * @see OptimizablePredicateList#generateStopKey |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public void generateStopKey(ExpressionClassBuilderInterface acbi, |
| MethodBuilder mb, |
| Optimizable optTable) |
| throws StandardException |
| { |
| ExpressionClassBuilder acb = (ExpressionClassBuilder) acbi; |
| |
| /* |
| ** To make the stop-key allocating function we cycle through |
| ** the Predicates and generate the function and initializer: |
| ** |
| ** private ExecIndexRow exprN() |
| ** { ExecIndexRow r = getExecutionFactory().getIndexableRow(# stop keys); |
| ** for (pred = each predicate in list) |
| ** { |
| ** if (pred.isStartKey()) |
| ** { |
| ** pred.generateKey(acb); |
| ** } |
| ** } |
| ** } |
| ** |
| ** If there are no start predicates, we do not generate anything. |
| */ |
| |
| if (numberOfStopPredicates != 0) |
| { |
| /* This sets up the method and the static field */ |
| MethodBuilder exprFun = acb.newExprFun(); |
| |
| /* Now we fill in the body of the method */ |
| LocalField rowField = |
| generateIndexableRow(acb, numberOfStopPredicates); |
| |
| int colNum = 0; |
| |
| for (Predicate pred : this) |
| { |
| if ( ! pred.isStopKey() ) |
| continue; |
| |
| generateSetColumn(acb, exprFun, colNum, |
| pred, optTable, rowField, false); |
| |
| colNum++; |
| } |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(colNum == numberOfStopPredicates, |
| "Number of stop predicates does not match"); |
| } |
| |
| finishKey(acb, mb, exprFun, rowField); |
| return; |
| } |
| |
| mb.pushNull(ClassName.GeneratedMethod); |
| } |
| |
| /** @see OptimizablePredicateList#stopOperator */ |
| public int stopOperator(Optimizable optTable) |
| { |
| int stopOperator; |
| |
| /* |
| ** This is the value we will use if there are no keys. It doesn't |
| ** matter what it is, as long as the operator is one of GT or GE |
| ** (to match the openScan() interface). |
| */ |
| stopOperator = ScanController.GT; |
| |
| int size = size(); |
| /* beetle 4572. stop operator should be the last start key column's |
| * stop operator. Note that all previous ones should be GT. |
| */ |
| for (int index = size - 1; index >= 0; index--) |
| { |
| Predicate pred = elementAt(index); |
| |
| if ( ! pred.isStopKey() ) |
| continue; |
| |
| stopOperator = pred.getStopOperator(optTable); |
| break; |
| } |
| return stopOperator; |
| } |
| |
| private void generateSingleQualifierCode( |
| MethodBuilder consMB, |
| Optimizable optTable, |
| boolean absolute, |
| ExpressionClassBuilder acb, |
| RelationalOperator or_node, |
| LocalField qualField, |
| int array_idx_1, |
| int array_idx_2) |
| throws StandardException |
| { |
| consMB.getField(qualField); // first arg for setQualifier |
| |
| // get instance for getQualifier call |
| consMB.pushThis(); |
| consMB.callMethod( |
| VMOpcode.INVOKEVIRTUAL, |
| acb.getBaseClassName(), |
| "getExecutionFactory", ExecutionFactory.MODULE, 0); |
| |
| // Column Id - first arg |
| if (absolute) |
| or_node.generateAbsoluteColumnId(consMB, optTable); |
| else |
| or_node.generateRelativeColumnId(consMB, optTable); |
| |
| // Operator - second arg |
| or_node.generateOperator(consMB, optTable); |
| |
| // Method to evaluate qualifier -- third arg |
| or_node.generateQualMethod(acb, consMB, optTable); |
| |
| // Receiver for above method - fourth arg |
| acb.pushThisAsActivation(consMB); |
| |
| // Ordered Nulls? - fifth arg |
| or_node.generateOrderedNulls(consMB); |
| |
| |
| /* |
| ** "Unknown" return value. For qualifiers, |
| ** we never want to return rows where the |
| ** result of a comparison is unknown. |
| ** But we can't just generate false, because |
| ** the comparison result could be negated. |
| ** So, generate the same as the negation |
| ** operand - that way, false will not be |
| ** negated, and true will be negated to false. |
| */ |
| or_node.generateNegate(consMB, optTable); |
| |
| /* Negate comparison result? */ |
| or_node.generateNegate(consMB, optTable); |
| |
| /* variantType for qualifier's orderable */ |
| consMB.push(or_node.getOrderableVariantType(optTable)); |
| |
| consMB.callMethod( |
| VMOpcode.INVOKEINTERFACE, |
| ExecutionFactory.MODULE, |
| "getQualifier", ClassName.Qualifier, 8); |
| |
| // result of getQualifier() is second arg for setQualifier |
| |
| consMB.push(array_idx_1); // third arg for setQualifier |
| consMB.push(array_idx_2); // fourth arg for setQualifier |
| |
| consMB.callMethod( |
| VMOpcode.INVOKESTATIC, |
| acb.getBaseClassName(), |
| "setQualifier", "void", 4); |
| } |
| |
| /** |
| * If there is an IN-list probe predicate in this list then generate |
| * the corresponding IN-list values as a DataValueDescriptor array, |
| * to be used for probing at execution time. Also generate a boolean |
| * value indicating whether or not the values are already in sorted |
| * order. |
| * |
| * Assumption is that by the time we get here there is at most one |
| * IN-list probe predicate in this list. |
| * |
| * @param acb The ActivationClassBuilder for the class we're building |
| * @param mb The MethodBuilder for the method we're building |
| */ |
| void generateInListValues(ExpressionClassBuilder acb, |
| MethodBuilder mb) throws StandardException |
| { |
| for (int index = size() - 1; index >= 0; index--) |
| { |
| Predicate pred = elementAt(index); |
| |
| // Don't do anything if it's not an IN-list probe predicate. |
| if (!pred.isInListProbePredicate()) |
| continue; |
| |
| /* We're going to generate the relevant code for the probe |
| * predicate below, so we no longer need it to be in the |
| * list. Remove it now. |
| */ |
| removeOptPredicate(pred); |
| |
| /* This list is a store restriction list for a specific base |
| * table, and we can only have one probe predicate per base |
| * table (any others, if any, will be "reverted" back to |
| * their original InListOperatorNodes and generated as |
| * qualifiers). So make sure there are no other probe preds |
| * in this list. |
| */ |
| if (SanityManager.DEBUG) |
| { |
| for (int i = 0; i < index; i++) |
| { |
| if (elementAt(i).isInListProbePredicate()) |
| { |
| SanityManager.THROWASSERT("Found multiple probe " + |
| "predicates for IN-list when only one was " + |
| "expected."); |
| } |
| } |
| } |
| |
| InListOperatorNode ilon = pred.getSourceInList(); |
| mb.getField(ilon.generateListAsArray(acb, mb)); |
| |
| if (ilon.sortDescending()) |
| mb.push(RowOrdering.DESCENDING); |
| else if (!ilon.isOrdered()) |
| { |
| /* If there is no requirement to sort descending and the |
| * IN list values have not already been sorted, then we |
| * sort them in ascending order at execution time. |
| */ |
| mb.push(RowOrdering.ASCENDING); |
| } |
| else |
| { |
| /* DONTCARE here means we don't have to sort the IN |
| * values at execution time because we already did |
| * it as part of compilation (esp. preprocessing). |
| * This can only be the case if all values in the IN |
| * list are literals (as opposed to parameters). |
| */ |
| mb.push(RowOrdering.DONTCARE); |
| } |
| |
| return; |
| } |
| |
| /* If we get here then we didn't find any probe predicates. But |
| * if that's true then we shouldn't have made it to this method |
| * to begin with. |
| */ |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.THROWASSERT("Attempted to generate IN-list values" + |
| "for multi-probing but no probe predicates were found."); |
| } |
| } |
| |
| /** |
| * @see OptimizablePredicateList#generateQualifiers |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public void generateQualifiers( |
| ExpressionClassBuilderInterface acbi, |
| MethodBuilder mb, |
| Optimizable optTable, |
| boolean absolute) |
| throws StandardException |
| { |
| String retvalType = ClassName.Qualifier + "[][]"; |
| |
| // If there are no qualifiers, return null. |
| if (numberOfQualifiers == 0) { |
| mb.pushNull(retvalType); |
| return; |
| } |
| |
| ExpressionClassBuilder acb = (ExpressionClassBuilder) acbi; |
| |
| MethodBuilder consMB = acb.getConstructor(); |
| MethodBuilder executeMB = acb.getExecuteMethod(); |
| |
| /* Create and initialize the array of Qualifiers */ |
| LocalField qualField = |
| acb.newFieldDeclaration(Modifier.PRIVATE, retvalType); |
| |
| |
| /* |
| ** Stick a reinitialize of the Qualifier array in execute(). |
| ** Done because although we call Exec/Qualifier.clearOrderableCache() |
| ** before each query, we only clear the cache for VARIANT and |
| ** SCAN_INVARIANT qualifiers. However, each time the same |
| ** statement is executed, even the QUERY_INVARIANT qualifiers |
| ** need to be flushed. For example: |
| ** prepare select c1 from t where c1 = (select max(c1) from t) as p; |
| ** execute p; -- we now have the materialized subquery result (1) |
| ** -- in our predicate |
| ** insert into t values 666; |
| ** execute p; -- we need to clear out 1 and recache the subq result |
| */ |
| |
| // PUSHCOMPILER |
| // if (mb == executeMB) { |
| // System.out.println("adding code to method in two places"); |
| // new Throwable().printStackTrace(); |
| // } |
| // |
| |
| // generate code to reinitializeQualifiers(Qualifier[][] qualifiers) |
| executeMB.getField(qualField); // first arg to reinitializeQualifiers() |
| executeMB.callMethod( |
| VMOpcode.INVOKESTATIC, |
| acb.getBaseClassName(), "reinitializeQualifiers", "void", 1); |
| |
| if (SanityManager.DEBUG) |
| { |
| if (numberOfQualifiers > size()) |
| { |
| SanityManager.THROWASSERT( |
| "numberOfQualifiers(" + numberOfQualifiers + |
| ") > size(" + size() + ")." + ":" + this.hashCode()); |
| } |
| } |
| |
| // Determine number of leading AND qualifiers, and subsequent |
| // trailing OR qualifiers. |
| int num_of_or_conjunctions = 0; |
| for (int i = 0; i < numberOfQualifiers; i++) |
| { |
| if (elementAt(i).isOrList()) |
| { |
| num_of_or_conjunctions++; |
| } |
| } |
| |
| |
| /* Assign the initializer to the Qualifier[] field */ |
| consMB.pushNewArray( |
| ClassName.Qualifier + "[]", num_of_or_conjunctions + 1); |
| consMB.setField(qualField); |
| |
| // Allocate qualifiers[0] which is an entry for each of the leading |
| // AND clauses. |
| |
| consMB.getField(qualField); // 1st arg allocateQualArray |
| consMB.push(0); // 2nd arg allocateQualArray |
| consMB.push(numberOfQualifiers - num_of_or_conjunctions); // 3rd arg allocateQualArray |
| |
| consMB.callMethod( |
| VMOpcode.INVOKESTATIC, |
| acb.getBaseClassName(), |
| "allocateQualArray", "void", 3); |
| |
| /* Sort the qualifiers by "selectivity" before generating. |
| * We want the qualifiers ordered by selectivity with the |
| * most selective ones first. There are 3 groups of qualifiers: |
| * = and IS NULL are the most selective, |
| * <> and IS NOT NULL are the least selective and |
| * all of the other RELOPs are in between. |
| * We break the list into 4 parts (3 types of qualifiers and |
| * then everything else) and then rebuild the ordered list. |
| * RESOLVE - we will eventually want to order the qualifiers |
| * by (column #, selectivity) once the store does just in time |
| * instantiation. |
| */ |
| orderQualifiers(); |
| |
| /* Generate each of the qualifiers, if any */ |
| |
| // First generate the "leading" AND qualifiers. |
| int qualNum = 0; |
| int size = size(); |
| boolean gotOrQualifier = false; |
| |
| for (int index = 0; index < size; index++) |
| { |
| |
| Predicate pred = elementAt(index); |
| |
| if (!pred.isQualifier()) |
| { |
| continue; |
| } |
| else if (pred.isOrList()) |
| { |
| gotOrQualifier = true; |
| |
| // will generate the OR qualifiers below. |
| break; |
| } |
| else |
| { |
| generateSingleQualifierCode( |
| consMB, |
| optTable, |
| absolute, |
| acb, |
| pred.getRelop(), |
| qualField, |
| 0, |
| qualNum); |
| |
| qualNum++; |
| } |
| } |
| |
| if (gotOrQualifier) |
| { |
| |
| // process each set of or's into a list which are AND'd. Each |
| // predicate will become an array list in the qualifier array of |
| // array's. |
| // |
| // The first list of And's went into qual[0][0...N] |
| // Now each subquent predicate is actually a list of OR's so |
| // will be passed as: |
| // 1st OR predicate -> qual[1][0.. number of OR terms] |
| // 2nd OR predicate -> qual[2][0.. number of OR terms] |
| // ... |
| // |
| int and_idx = 1; |
| |
| // The remaining qualifiers must all be OR predicates, which |
| // are pushed slightly differently than the leading AND qualifiers. |
| |
| for (int index = qualNum; index < size; index++, and_idx++) |
| { |
| |
| Predicate pred = elementAt(index); |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(pred.isOrList()); |
| } |
| |
| // create an ArrayList of the OR nodes. We need the count |
| // of Or's in order to first generate the allocateQualArray() |
| // call, then we walk the list assigning each of the OR's to |
| // entries in the array in generateSingleQualifierCode(). |
| ArrayList<RelationalOperator> a_list = new ArrayList<RelationalOperator>(); |
| |
| QueryTreeNode node = pred.getAndNode().getLeftOperand(); |
| |
| while (node instanceof OrNode) |
| { |
| OrNode or_node = (OrNode) node; |
| |
| // The left operand of OR node is one of the terms, |
| // (ie. A = 1) |
| if (or_node.getLeftOperand() instanceof RelationalOperator) |
| { |
| a_list.add( (RelationalOperator) or_node.getLeftOperand()); |
| } |
| |
| // The next OR node in the list if linked to the right. |
| node = or_node.getRightOperand(); |
| } |
| |
| // Allocate an array to hold each of the terms of this OR, |
| // clause. ie. (a = 1 or b = 2), will allocate a 2 entry array. |
| |
| consMB.getField(qualField); // 1st arg allocateQualArray |
| consMB.push(and_idx); // 2nd arg allocateQualArray |
| consMB.push(a_list.size()); // 3rd arg allocateQualArray |
| |
| consMB.callMethod( |
| VMOpcode.INVOKESTATIC, |
| acb.getBaseClassName(), |
| "allocateQualArray", "void", 3); |
| |
| |
| // finally transfer the nodes to the 2-d qualifier |
| for (int i = 0; i < a_list.size(); i++) |
| { |
| generateSingleQualifierCode( |
| consMB, |
| optTable, |
| absolute, |
| acb, |
| a_list.get(i), |
| qualField, |
| and_idx, |
| i); |
| |
| } |
| |
| qualNum++; |
| } |
| |
| } |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(qualNum == numberOfQualifiers, |
| qualNum + " Qualifiers found, " + |
| numberOfQualifiers + " expected."); |
| } |
| |
| /* |
| ** Return a reference to the field that holds the initialized |
| ** array of Qualifiers. |
| */ |
| mb.getField(qualField); |
| } |
| |
| |
| /* Sort the qualifiers by "selectivity" before generating. |
| * We want the qualifiers ordered by selectivity with the |
| * most selective ones first. There are 3 groups of qualifiers: |
| * = and IS NULL are the most selective, |
| * <> and IS NOT NULL are the least selective and |
| * all of the other RELOPs are in between. |
| * We break the list into 4 parts (3 types of qualifiers and |
| * then everything else) and then rebuild the ordered list. |
| * RESOLVE - we will eventually want to order the qualifiers |
| * by (column #, selectivity) once the store does just in time |
| * instantiation. |
| */ |
| private static final int QUALIFIER_ORDER_EQUALS = 0; |
| private static final int QUALIFIER_ORDER_OTHER_RELOP = 1; |
| private static final int QUALIFIER_ORDER_NOT_EQUALS = 2; |
| private static final int QUALIFIER_ORDER_NON_QUAL = 3; |
| private static final int QUALIFIER_ORDER_OR_CLAUSE = 4; |
| private static final int QUALIFIER_NUM_CATEGORIES = 5; |
| private void orderQualifiers() |
| { |
| // Sort the predicates into buckets, sortList[0] is the most |
| // selective, while sortList[4] is the least restrictive. |
| // |
| // sortList[0]: "= and IS NULL" |
| // sortList[1]: "a set of OR'd conjunctions" |
| // sortList[2]: "all other relop's" |
| // sortList[3]: "<> and IS NOT NULL" |
| // sortList[4]: "everything else" |
| PredicateList[] sortList = new PredicateList[QUALIFIER_NUM_CATEGORIES]; |
| |
| for (int i = sortList.length - 1; i >= 0; i--) |
| sortList[i] = new PredicateList(getContextManager()); |
| |
| int predIndex; |
| |
| for (Predicate pred : this) |
| { |
| if (! pred.isQualifier()) |
| { |
| sortList[QUALIFIER_ORDER_NON_QUAL].addElement(pred); |
| continue; |
| } |
| |
| AndNode node = pred.getAndNode(); |
| |
| if (!(node.getLeftOperand() instanceof OrNode)) |
| { |
| RelationalOperator relop = |
| (RelationalOperator) node.getLeftOperand(); |
| |
| int op = relop.getOperator(); |
| |
| switch (op) |
| { |
| case RelationalOperator.EQUALS_RELOP: |
| case RelationalOperator.IS_NULL_RELOP: |
| sortList[QUALIFIER_ORDER_EQUALS].addElement(pred); |
| break; |
| |
| case RelationalOperator.NOT_EQUALS_RELOP: |
| case RelationalOperator.IS_NOT_NULL_RELOP: |
| sortList[QUALIFIER_ORDER_NOT_EQUALS].addElement(pred); |
| break; |
| |
| default: |
| sortList[QUALIFIER_ORDER_OTHER_RELOP].addElement(pred); |
| } |
| } |
| else |
| { |
| sortList[QUALIFIER_ORDER_OR_CLAUSE].addElement(pred); |
| } |
| } |
| |
| /* Rebuild the list */ |
| predIndex = 0; |
| |
| for (int index = 0; index < QUALIFIER_NUM_CATEGORIES; index++) |
| { |
| for (int items = 0; items < sortList[index].size(); items++) |
| { |
| setElementAt(sortList[index].elementAt(items), predIndex++); |
| } |
| } |
| } |
| |
| /** |
| * @see OptimizablePredicateList#generateStartKey |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public void generateStartKey(ExpressionClassBuilderInterface acbi, |
| MethodBuilder mb, |
| Optimizable optTable) |
| throws StandardException |
| { |
| ExpressionClassBuilder acb = (ExpressionClassBuilder) acbi; |
| |
| /* |
| ** To make the start-key allocating function we cycle through |
| ** the Predicates and generate the function and initializer: |
| ** |
| ** private Object exprN() |
| ** { ExecIndexRow r = getExecutionFactory().getIndexableRow(# start keys); |
| ** for (pred = each predicate in list) |
| ** { |
| ** if (pred.isStartKey()) |
| ** { |
| ** pred.generateKey(acb); |
| ** } |
| ** } |
| ** } |
| ** |
| ** If there are no start predicates, we do not generate anything. |
| */ |
| |
| if (numberOfStartPredicates != 0) |
| { |
| /* This sets up the method and the static field */ |
| MethodBuilder exprFun = acb.newExprFun(); |
| |
| /* Now we fill in the body of the method */ |
| LocalField rowField = generateIndexableRow(acb, numberOfStartPredicates); |
| |
| int colNum = 0; |
| |
| for (Predicate pred : this) |
| { |
| if ( ! pred.isStartKey() ) |
| continue; |
| |
| generateSetColumn(acb, exprFun, colNum, |
| pred, optTable, rowField, true); |
| |
| colNum++; |
| } |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(colNum == numberOfStartPredicates, |
| "Number of start predicates does not match"); |
| } |
| |
| finishKey(acb, mb, exprFun, rowField); |
| return; |
| } |
| |
| mb.pushNull(ClassName.GeneratedMethod); |
| } |
| |
| /** |
| * @see OptimizablePredicateList#sameStartStopPosition |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public boolean sameStartStopPosition() |
| throws StandardException |
| { |
| /* We can only use the same row for both the |
| * start and stop positions if the number of |
| * start and stop predicates are the same. |
| */ |
| if (numberOfStartPredicates != numberOfStopPredicates) |
| { |
| return false; |
| } |
| |
| /* We can only use the same row for both the |
| * start and stop positions when a predicate is |
| * a start key iff it is a stop key. |
| */ |
| |
| for (Predicate pred : this) |
| { |
| if ( (pred.isStartKey() && (! pred.isStopKey())) || |
| (pred.isStopKey() && (! pred.isStartKey()))) |
| { |
| return false; |
| } |
| /* "in"'s dynamic start and stop key are not the same, beetle 3858 |
| */ |
| if (pred.getAndNode().getLeftOperand() instanceof InListOperatorNode) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Generate the indexable row for a start key or stop key. |
| * |
| * @param acb The ActivationClassBuilder for the class we're building |
| * @param numberOfColumns The number of columns in the key |
| * |
| * @return The field that holds the indexable row |
| */ |
| private LocalField generateIndexableRow(ExpressionClassBuilder acb, int numberOfColumns) |
| { |
| MethodBuilder mb = acb.getConstructor(); |
| /* |
| ** Generate a call to get an indexable row |
| ** with the given number of columns |
| */ |
| acb.pushGetExecutionFactoryExpression(mb); // instance |
| mb.push(numberOfColumns); |
| mb.callMethod(VMOpcode.INVOKEINTERFACE, ClassName.ExecutionFactory, "getIndexableRow", ClassName.ExecIndexRow, 1); |
| |
| /* |
| ** Assign the indexable row to a field, and put this assignment into |
| ** the constructor for the activation class. This way, we only have |
| ** to get the row once. |
| */ |
| LocalField field = |
| acb.newFieldDeclaration(Modifier.PRIVATE, ClassName.ExecIndexRow); |
| |
| mb.setField(field); |
| |
| return field; |
| } |
| |
| /** |
| * Generate the code to set the value from a predicate in an index column. |
| * |
| * @param acb The ActivationClassBuilder for the class we're building |
| * @param exprFun The MethodBuilder for the method we're building |
| * @param columnNumber The position number of the column we're setting |
| * the value in (zero-based) |
| * @param pred The Predicate with the value to put in the index column |
| * @param optTable The Optimizable table the column is in |
| * @param rowField The field that holds the indexable row |
| * @param isStartKey Are we generating start or stop key? This information |
| * is useful for "in"'s dynamic start/stop key, bug 3858 |
| * |
| * @exception StandardException Thrown on error |
| */ |
| private void generateSetColumn(ExpressionClassBuilder acb, |
| MethodBuilder exprFun, |
| int columnNumber, |
| Predicate pred, |
| Optimizable optTable, |
| LocalField rowField, |
| boolean isStartKey) |
| throws StandardException |
| { |
| MethodBuilder mb; |
| |
| /* Code gets generated in constructor if comparison against |
| * a constant, otherwise gets generated in the current |
| * statement block. |
| */ |
| boolean withKnownConstant = false; |
| if (pred.compareWithKnownConstant(optTable, false)) |
| { |
| withKnownConstant = true; |
| mb = acb.getConstructor(); |
| } |
| else |
| { |
| mb = exprFun; |
| } |
| |
| int[] baseColumns = optTable.getTrulyTheBestAccessPath(). |
| getConglomerateDescriptor(). |
| getIndexDescriptor().baseColumnPositions(); |
| boolean[] isAscending = optTable.getTrulyTheBestAccessPath(). |
| getConglomerateDescriptor(). |
| getIndexDescriptor().isAscending(); |
| |
| /* If the predicate is an IN-list probe predicate then we are |
| * using it as a start/stop key "placeholder", to be over-ridden |
| * at execution time. Put differently, we want to generate |
| * "column = ?" as a start/stop key and then use the "?" value |
| * as a placeholder into which we'll plug the various IN values |
| * at execution time. |
| * |
| * In that case "isIn" will be false here, which is fine: there's |
| * no need to generate dynamic start/stop keys like we do for |
| * "normal" IN lists because we're just using the key as a place- |
| * holder. So by generating the probe predicate ("column = ?") |
| * as a normal one-sided start/stop key, we get our requisite |
| * execution-time placeholder and that's that. For more on how |
| * we use this "placeholder", see MultiProbeTableScanResultSet. |
| * |
| * Note that we generate the corresponding IN-list values |
| * separately (see generateInListValues() in this class). |
| */ |
| boolean isIn = pred.getAndNode().getLeftOperand() instanceof InListOperatorNode; |
| |
| /* |
| ** Generate statements of the form |
| ** |
| ** r.setColumn(columnNumber, columnExpression); |
| ** |
| ** and put the generated statement in the allocator function. |
| */ |
| mb.getField(rowField); |
| mb.push(columnNumber + 1); |
| |
| // second arg |
| if (isIn) |
| { |
| pred.getSourceInList().generateStartStopKey( |
| isAscending[columnNumber], isStartKey, acb, mb); |
| } |
| else |
| pred.generateExpressionOperand(optTable, baseColumns[columnNumber], acb, mb); |
| |
| mb.upCast(ClassName.DataValueDescriptor); |
| |
| mb.callMethod(VMOpcode.INVOKEINTERFACE, ClassName.Row, "setColumn", "void", 2); |
| |
| /* Also tell the row if this column uses ordered null semantics */ |
| if (!isIn) |
| { |
| RelationalOperator relop = pred.getRelop(); |
| boolean setOrderedNulls = relop.orderedNulls(); |
| |
| /* beetle 4464, performance work. If index column is not nullable |
| * (which is frequent), we should treat it as though nulls are |
| * ordered (indeed because they don't exist) so that we do not have |
| * to check null at scan time for each row, each column. This is |
| * an overload to "is null" operator, so that we have less overhead, |
| * provided that they don't interfere. It doesn't interfere if it |
| * doesn't overload if key is null. If key is null, but operator |
| * is not orderedNull type (is null), skipScan will use this flag |
| * (false) to skip scan. |
| */ |
| if ((! setOrderedNulls) && |
| ! relop.getColumnOperand(optTable).getTypeServices().isNullable()) |
| { |
| if (withKnownConstant) |
| setOrderedNulls = true; |
| else |
| { |
| ValueNode keyExp = |
| relop.getExpressionOperand( |
| optTable.getTableNumber(), |
| baseColumns[columnNumber], |
| (FromTable)optTable); |
| |
| if (keyExp instanceof ColumnReference) |
| setOrderedNulls = |
| ! ((ColumnReference) keyExp).getTypeServices().isNullable(); |
| } |
| } |
| if (setOrderedNulls) |
| { |
| mb.getField(rowField); |
| mb.push(columnNumber); |
| mb.callMethod(VMOpcode.INVOKEINTERFACE, ClassName.ExecIndexRow, "orderedNulls", "void", 1); |
| } |
| } |
| } |
| |
| /** |
| * Finish generating a start or stop key |
| * |
| * @param acb The ActivationClassBuilder for the class we're building |
| * @param exprFun The MethodBuilder for the method we're building |
| * @param rowField The name of the field that holds the indexable row |
| */ |
| private void finishKey(ExpressionClassBuilder acb, |
| MethodBuilder mb, |
| MethodBuilder exprFun, |
| LocalField rowField) |
| { |
| /* Generate return statement and add to exprFun */ |
| exprFun.getField(rowField); |
| exprFun.methodReturn(); |
| /* We are done putting stuff in exprFun */ |
| exprFun.complete(); |
| |
| /* |
| ** What we use is the access of the static field, |
| ** i.e. the pointer to the method. |
| */ |
| acb.pushMethodReference(mb, exprFun); |
| } |
| |
| /* Class implementation */ |
| boolean constantColumn(ColumnReference colRef) |
| { |
| boolean retval = false; |
| |
| /* |
| ** Walk this list |
| */ |
| for (Predicate pred : this) |
| { |
| RelationalOperator relop = pred.getRelop(); |
| |
| if (pred.isRelationalOpPredicate()) |
| { |
| if (relop.getOperator() == RelationalOperator.EQUALS_RELOP) |
| { |
| ValueNode exprOp = relop.getOperand( |
| colRef, |
| pred.getReferencedSet().size(), |
| true |
| ); |
| |
| if (exprOp != null) |
| { |
| if (exprOp.isConstantExpression()) |
| { |
| retval = true; |
| break; |
| } |
| } |
| } |
| else if (relop.getOperator() == |
| RelationalOperator.IS_NULL_RELOP) |
| { |
| ColumnReference columnOp = |
| (ColumnReference)relop.getOperand( |
| colRef, |
| pred.getReferencedSet().size(), |
| false |
| ); |
| |
| if (columnOp != null) |
| { |
| retval = true; |
| } |
| } |
| } |
| } |
| |
| return retval; |
| } |
| |
| /** |
| * @see OptimizablePredicateList#adjustForSortElimination |
| * |
| * Currently this method only accounts for IN list multi-probing |
| * predicates (DERBY-3279). |
| */ |
| public void adjustForSortElimination( |
| RequiredRowOrdering ordering) throws StandardException |
| { |
| // Nothing to do if there's no required ordering. |
| if (ordering == null) |
| return; |
| |
| /* Walk through the predicate list and search for any |
| * multi-probing predicates. If we find any which |
| * operate on a column that is part of the received |
| * ORDER BY, then check to see if the ORDER BY requires |
| * a DESCENDING sort. If so, then we must take note |
| * of this requirement so that the IN list values for |
| * the probe predicate are sorted in DESCENDING order |
| * at execution time. |
| */ |
| OrderByList orderBy = (OrderByList)ordering; |
| for (Predicate pred : this) |
| { |
| if (!pred.isInListProbePredicate()) |
| continue; |
| |
| BinaryRelationalOperatorNode bron = |
| (BinaryRelationalOperatorNode)pred.getRelop(); |
| |
| if (orderBy.requiresDescending( |
| (ColumnReference)bron.getLeftOperand(), |
| pred.getReferencedSet().size())) |
| { |
| pred.getSourceInList(true).markSortDescending(); |
| } |
| } |
| } |
| |
| /** |
| * @see OptimizablePredicateList#selectivity |
| */ |
| public double selectivity(Optimizable optTable) |
| throws StandardException |
| { |
| TableDescriptor td = optTable.getTableDescriptor(); |
| ConglomerateDescriptor[] conglomerates = td.getConglomerateDescriptors(); |
| |
| int numPredicates = size(); |
| int numConglomerates = conglomerates.length; |
| |
| if (numConglomerates == 1) |
| return -1.0d; // one conglomerate; must be heap. |
| |
| if (numPredicates == 0) |
| return -1.0d; // no predicates why bother? |
| |
| boolean nothingYet = true; |
| |
| /* before we start, lets select non-redundant prediates into a working |
| * list; we'll work with the workingPredicates list from now on in this |
| * routine. |
| */ |
| PredicateList workingPredicates = |
| new PredicateList(getContextManager()); |
| |
| for (int i = 0; i < numPredicates; i++) |
| { |
| if (isRedundantPredicate(i)) |
| continue; |
| |
| /* to workingPredicates only add useful predicates... */ |
| workingPredicates.addOptPredicate(elementAt(i)); |
| } |
| |
| int numWorkingPredicates = workingPredicates.size(); |
| |
| /*-------------------------------------------------------------------- |
| * In the first phase, the routine initializes an array of |
| * predicateWrapperLists-- one list for each conglomerate that has |
| * statistics. |
| * |
| * predsForConglomerates is an array of pwList. For each conglomerate we |
| * keep a pwList of predicates that have an equals predicate on a column |
| * in the conglomerate. |
| * |
| * As an example consider a table T, with indices on |
| * T(c1)-->conglom_one, T(c2,c1)-->conglom_two. |
| * |
| * if we have the following predicates: |
| * T.c1=T1.x (p1) and T.c1=T2.y (p2) and T.c2=T1.z (p3), then we'll have the |
| * after the first loop is done, we'll have the following setup. |
| * |
| * conglom_one: pwList [p1,p2] |
| * conglom_two: pwList [p1,p2,p3]. |
| * |
| * Note that although p1,p2 appear on both conglomerates, the |
| * indexPosition of p1 and p2 on the first list is 0 (first index |
| * position) while the index position of p1,p2 on the second list is 1 |
| * (second index position). |
| * |
| * PredicateWrapper and PredicateWrapperLists are inner classes used |
| * only in this file. |
| * -------------------------------------------------------------------- */ |
| PredicateWrapperList[] |
| predsForConglomerates = new PredicateWrapperList[numConglomerates]; |
| |
| for (int i = 0; i < numConglomerates; i++) |
| { |
| ConglomerateDescriptor cd = conglomerates[i]; |
| |
| if (!cd.isIndex()) |
| continue; |
| |
| if (!td.statisticsExist(cd)) |
| continue; |
| |
| int[] baseColumnList = |
| cd.getIndexDescriptor().baseColumnPositions(); |
| |
| for (int j = 0; j < numWorkingPredicates; j++) |
| { |
| Predicate pred = workingPredicates.elementAt(j); |
| |
| int ip = pred.hasEqualOnColumnList(baseColumnList, |
| optTable); |
| |
| if (ip < 0) |
| continue; // look at the next predicate. |
| |
| nothingYet = false; |
| if (predsForConglomerates[i] == null) |
| { |
| predsForConglomerates[i] = new PredicateWrapperList(numWorkingPredicates); |
| } |
| PredicateWrapper newpw = new PredicateWrapper(ip, pred, j); |
| predsForConglomerates[i].insert(newpw); |
| } // for (j = 0; |
| } // for (i = 0; i < ...) |
| |
| if (nothingYet) |
| { |
| return -1.0; |
| } |
| |
| /*------------------------------------------------------------------ |
| * In the second phase we, |
| * walk the predsForConglomerateList again-- if we find |
| * a break in the indexPositions we remove the predicates |
| * after the gap. To clarify, if we have equal predicates on the first |
| * and the third index positions, we can throw away the predicate on |
| * the 3rd index position-- it doesn't do us any good. |
| *-------------------------------------------------------------------*/ |
| |
| int maxOverlap = -1; |
| for (int i = 0; i < numConglomerates; i++) |
| { |
| if (predsForConglomerates[i] == null) |
| continue; |
| |
| predsForConglomerates[i].retainLeadingContiguous(); |
| } // for (i = 0; i < ...) |
| |
| |
| calculateWeight(predsForConglomerates, numWorkingPredicates); |
| |
| /*------------------------------------------------------------------- |
| * In the third phase we loop through predsForConglomerates choosing the |
| * best fit (chooseLongestMatch) of predicates. we use the statistics |
| * for the set of predicates returned by chooseLongestMatch and then |
| * loop until we can't find any more statistics or we have exhausted all |
| * the predicates for which we are trying to find statistics. |
| *--------------------------------------------------------------------*/ |
| double selectivity = 1.0; |
| |
| ArrayList<Predicate> maxPreds = new ArrayList<Predicate>(); |
| |
| while (true) |
| { |
| maxPreds.clear(); |
| int conglomIndex = chooseLongestMatch(predsForConglomerates, |
| maxPreds, numWorkingPredicates); |
| |
| if (conglomIndex == -1) |
| break; // no more stats available. |
| |
| selectivity *= |
| td.selectivityForConglomerate(conglomerates[conglomIndex], maxPreds.size()); |
| |
| for (int i = 0; i < maxPreds.size(); i++) |
| { |
| /* remove the predicates that we've calculated the selectivity |
| * of, from workingPredicates. |
| */ |
| Predicate p = maxPreds.get(i); |
| workingPredicates.removeOptPredicate(p); |
| } |
| |
| if (workingPredicates.size() == 0) |
| break; |
| } |
| |
| if (workingPredicates.size() != 0) |
| { |
| selectivity *= workingPredicates.selectivityNoStatistics(optTable); |
| } |
| |
| return selectivity; |
| } |
| |
| /* assign a weight to each predicate-- the maximum weight that a predicate |
| * can have is numUsefulPredicates. If a predicate corresponds to the first |
| * index position then its weight is numUsefulPredicates. The weight of a |
| * pwlist is the sum of the weights of individual predicates. |
| */ |
| private void calculateWeight(PredicateWrapperList[] pwList, int numUsefulPredicates) |
| { |
| int[] s = new int[numUsefulPredicates]; |
| |
| for (int i = 0; i < pwList.length; i++) |
| { |
| if (pwList[i] == null) |
| continue; |
| |
| for (int j = 0; j < pwList[i].size(); j++) |
| { |
| s[pwList[i].elementAt(j).getPredicateID()] += |
| (numUsefulPredicates - j); |
| } |
| } |
| |
| for (int i = 0; i < pwList.length; i++) |
| { |
| int w = 0; |
| |
| if (pwList[i] == null) |
| continue; |
| |
| for (int j = 0; j < pwList[i].size(); j++) |
| { |
| w += s[pwList[i].elementAt(j).getPredicateID()]; |
| } |
| pwList[i].setWeight(w); |
| } |
| } |
| /** |
| * choose the statistic which has the maximum match with the predicates. |
| * value is returned in ret. |
| */ |
| private int chooseLongestMatch(PredicateWrapperList[] predArray, List<Predicate> ret, |
| int numWorkingPredicates) |
| { |
| int max = 0, maxWeight = 0; |
| int position = -1; |
| |
| for (int i = 0; i < predArray.length; i++) |
| { |
| if (predArray[i] == null) |
| continue; |
| |
| if (predArray[i].uniqueSize() == 0) |
| continue; |
| |
| if (predArray[i].uniqueSize() > max) |
| { |
| max = predArray[i].uniqueSize(); |
| position = i; |
| maxWeight = predArray[i].getWeight(); |
| } |
| |
| if (predArray[i].uniqueSize() == max) |
| { |
| /* if the matching length is the same, choose the one with the |
| * lower weight. |
| */ |
| if (predArray[i].getWeight() > maxWeight) |
| continue; |
| |
| position = i; |
| max = predArray[i].uniqueSize(); |
| maxWeight = predArray[i].getWeight(); |
| } |
| } |
| |
| if (position == -1) |
| return -1; |
| |
| /* the problem here is that I might have more than one predicate |
| * matching on the same index position; i.e |
| * col_1 = .. [p1] AND col_1 = .. [p2] AND col_2 = ..[p3]; |
| * In that case that maximum matching predicate is [p1] and [p3] but |
| * [p2] shuld be considered again later. |
| */ |
| PredicateWrapperList pwl = predArray[position]; |
| List<PredicateWrapper> uniquepreds = pwl.createLeadingUnique(); |
| |
| /* uniqueprds is a list of predicates (along with wrapper) that I'm |
| going to use to get statistics from-- we now have to delete these |
| predicates from all the predicateWrapperLists! |
| */ |
| for (int i = 0; i < uniquepreds.size(); i++) |
| { |
| Predicate p = uniquepreds.get(i).getPredicate(); |
| ret.add(p); |
| for (int j = 0; j < predArray.length; j++) |
| { |
| if (predArray[j] == null) |
| continue; |
| |
| pwl = predArray[j]; |
| /* note that we use object identity with the predicate and not |
| * the predicatewrapper to remove it from the prediate wrapper |
| * lists. |
| */ |
| pwl.removeElement(p); |
| } |
| } |
| |
| /* if removing this predicate caused a gap, get rid of everything after |
| * the gaps. |
| */ |
| for (int i = 0; i < predArray.length; i++) |
| { |
| if (predArray[i] == null) |
| continue; |
| predArray[i].retainLeadingContiguous(); |
| } |
| |
| /* recalculate the weights of the pwlists... */ |
| calculateWeight(predArray, numWorkingPredicates); |
| return position; |
| } |
| |
| /** |
| * Compute selectivity the old fashioned way. |
| */ |
| private double selectivityNoStatistics(Optimizable optTable) |
| throws StandardException |
| { |
| double selectivity = 1.0; |
| |
| for (int i = 0; i < size(); i++) |
| { |
| OptimizablePredicate pred = (OptimizablePredicate)elementAt(i); |
| selectivity *= pred.selectivity(optTable); |
| } |
| |
| return selectivity; |
| } |
| |
| /** |
| * Inner class which helps statistics routines do their work. |
| * We need to keep track of the index position for each predicate for each |
| * index while we're manipulating predicates and statistics. Each predicate |
| * does have internal state for indexPosition, but this is a more permanent |
| * sort of indexPosition, which keeps track of the position for the index |
| * being considered in estimateCost. For us, each predicate can have |
| * different index positions for different indices. |
| */ |
| private static class PredicateWrapper |
| { |
| int indexPosition; |
| Predicate pred; |
| int predicateID; |
| |
| PredicateWrapper(int ip, Predicate p, int predicateID) |
| { |
| this.indexPosition = ip; |
| this.pred = p; |
| this.predicateID = predicateID; |
| } |
| |
| int getIndexPosition() { return indexPosition; } |
| Predicate getPredicate() { return pred; } |
| int getPredicateID() { return predicateID; } |
| |
| /* a predicatewrapper is BEFORE another predicate wrapper iff its index |
| * position is less than the index position of the other. |
| */ |
| boolean before(PredicateWrapper other) |
| { |
| return (indexPosition < other.getIndexPosition()); |
| } |
| |
| /* for our purposes two predicates at the same index |
| position are contiguous. (have i spelled this right?) |
| */ |
| boolean contiguous(PredicateWrapper other) |
| { |
| int otherIP = other.getIndexPosition(); |
| return ((indexPosition == otherIP) || (indexPosition - otherIP == 1) |
| || (indexPosition - otherIP == -1)); |
| } |
| |
| } |
| |
| /** Another inner class which is basically a List of Predicate Wrappers. |
| */ |
| private static class PredicateWrapperList |
| { |
| private final ArrayList<PredicateWrapper> pwList; |
| int numPreds; |
| int numDuplicates; |
| int weight; |
| |
| PredicateWrapperList(int maxValue) |
| { |
| pwList = new ArrayList<PredicateWrapper>(maxValue); |
| } |
| |
| void removeElement(Predicate p) |
| { |
| for (int i = numPreds - 1; i >= 0; i--) |
| { |
| Predicate predOnList = elementAt(i).getPredicate(); |
| if (predOnList == p) // object equality is what we need |
| removeElementAt(i); |
| } |
| } |
| |
| void removeElementAt(int index) |
| { |
| if (index < numPreds - 1) |
| { |
| PredicateWrapper nextPW = elementAt(index+1); |
| if (nextPW.getIndexPosition() == index) |
| numDuplicates--; |
| } |
| pwList.remove(index); |
| numPreds--; |
| } |
| |
| PredicateWrapper elementAt(int i) |
| { |
| return pwList.get(i); |
| } |
| |
| void insert(PredicateWrapper pw) |
| { |
| int i; |
| for (i = 0; i < pwList.size(); i++) |
| { |
| if (pw.getIndexPosition() == elementAt(i).getIndexPosition()) |
| numDuplicates++; |
| |
| if (pw.before(elementAt(i))) |
| break; |
| } |
| numPreds++; |
| pwList.add(i, pw); |
| } |
| |
| int size() |
| { |
| return numPreds; |
| } |
| |
| /* number of unique references to a column; i.e two predicates which |
| * refer to the same column in the table will give rise to duplicates. |
| */ |
| int uniqueSize() |
| { |
| if (numPreds > 0) |
| return numPreds - numDuplicates; |
| return 0; |
| } |
| |
| /* From a list of PredicateWrappers, retain only contiguous ones. Get |
| * rid of everything after the first gap. |
| */ |
| void retainLeadingContiguous() |
| { |
| if (pwList.isEmpty()) { |
| return; |
| } |
| |
| if (elementAt(0).getIndexPosition() != 0) |
| { |
| pwList.clear(); |
| numPreds = numDuplicates = 0; |
| return; |
| } |
| |
| int j; |
| for (j = 0; j < numPreds - 1; j++) |
| { |
| if (!(elementAt(j).contiguous(elementAt(j+1)))) |
| break; |
| } |
| |
| /* j points to the last good one; i.e |
| * 0 1 1 2 4 4 |
| * j points to 2. remove 4 and everything after it. |
| * Beetle 4321 - need to remove from back to front |
| * to prevent array out of bounds problems |
| * |
| */ |
| |
| for (int k = numPreds - 1; k > j; k--) |
| { |
| if (elementAt(k).getIndexPosition() == |
| elementAt(k-1).getIndexPosition()) |
| numDuplicates--; |
| pwList.remove(k); |
| } |
| numPreds = j + 1; |
| } |
| |
| /* From a list of PW, extract the leading unique predicates; i.e if the |
| * PW's with index positions are strung together like this: |
| * 0 0 1 2 3 3 |
| * I need to extract out 0 1 2 3. |
| * leaving 0 2 3 in there. |
| */ |
| private List<PredicateWrapper> createLeadingUnique() |
| { |
| if (numPreds == 0) |
| return null; |
| |
| int lastIndexPosition = elementAt(0).getIndexPosition(); |
| |
| if (lastIndexPosition != 0) |
| return null; |
| |
| ArrayList<PredicateWrapper> scratch = new ArrayList<PredicateWrapper>(); |
| scratch.add(elementAt(0)); // always add 0. |
| |
| for (int i = 1; i < numPreds; i++) |
| { |
| if (elementAt(i).getIndexPosition() == lastIndexPosition) |
| continue; |
| lastIndexPosition = elementAt(i).getIndexPosition(); |
| scratch.add(elementAt(i)); |
| } |
| return scratch; |
| } |
| |
| void setWeight(int weight) |
| { |
| this.weight = weight; |
| } |
| |
| int getWeight() |
| { |
| return weight; |
| } |
| } |
| } |