blob: 15e4da4f6f1d3ececa46b530d2527824c2367801 [file] [log] [blame]
/*
Derby - Class org.apache.derby.impl.sql.compile.SetOperatorNode
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.util.HashMap;
import java.util.Properties;
import org.apache.derby.shared.common.error.StandardException;
import org.apache.derby.shared.common.reference.SQLState;
import org.apache.derby.iapi.services.context.ContextManager;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.sql.compile.CostEstimate;
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.dictionary.TableDescriptor;
import org.apache.derby.iapi.types.DataTypeDescriptor;
import org.apache.derby.iapi.util.JBitSet;
/**
* A SetOperatorNode represents a UNION, INTERSECT, or EXCEPT in a DML statement. Binding and optimization
* preprocessing is the same for all of these operations, so they share bind methods in this abstract class.
*
* The class contains a boolean telling whether the operation should eliminate
* duplicate rows.
*
*/
abstract class SetOperatorNode extends TableOperatorNode
{
/**
** Tells whether to eliminate duplicate rows. all == TRUE means do
** not eliminate duplicates, all == FALSE means eliminate duplicates.
*/
boolean all;
QueryExpressionClauses qec = new QueryExpressionClauses();
// List of scoped predicates for pushing during optimization.
private PredicateList leftOptPredicates;
private PredicateList rightOptPredicates;
// List of original (unscoped) predicates that we tried to push
// during the most recent phase of optimization.
private PredicateList pushedPredicates;
// Mapping of original predicates to scoped predicates, used to
// avoid re-scoping predicates unnecessarily.
private HashMap<Predicate,Predicate> leftScopedPreds;
private HashMap<Predicate,Predicate> rightScopedPreds;
/**
* Constructor for a SetOperatorNode.
*
* @param leftResult The ResultSetNode on the left side of this union
* @param rightResult The ResultSetNode on the right side of this union
* @param all Whether or not this is an ALL.
* @param tableProperties Properties list associated with the table
* @param cm The context manager
*
* @exception StandardException Thrown on error
*/
SetOperatorNode(ResultSetNode leftResult,
ResultSetNode rightResult,
boolean all,
Properties tableProperties,
ContextManager cm) throws StandardException {
super(leftResult, rightResult, tableProperties, cm);
this.all = all;
/* resultColumns cannot be null, so we make a copy of the left RCL
* for now. At bind() time, we need to recopy the list because there
* may have been a "*" in the list. (We will set the names and
* column types at that time, as expected.)
*/
setResultColumns( leftResultSet.getResultColumns().copyListAndObjects() );
}
/**
* @see Optimizable#modifyAccessPath
*
* @exception StandardException Thrown on error
*/
public Optimizable modifyAccessPath(JBitSet outerTables,
PredicateList predList) throws StandardException
{
// When we optimized this node we attempted to push predicates down to
// the children, which means the best access path for the children
// might depend on those predicates. So now that we're preparing
// to generate the best paths, we have to push those same predicates
// down again (this is the last time) so that the children can use
// them as appropriate. NOTE: If our final choice for join strategy
// is a hash join, then we do not push the predicates because we'll
// need them to be at this level in order to find out which of them
// is the equijoin predicate that is required by hash join.
if ((predList != null) &&
!getTrulyTheBestAccessPath().getJoinStrategy().isHashJoin())
{
for (int i = predList.size() - 1; i >= 0; i--)
if (pushOptPredicate(predList.getOptPredicate(i)))
predList.removeOptPredicate(i);
}
/*
* It's possible that we tried to push a predicate down to this node's
* children but failed to do so. This can happen if this node's
* children both match the criteria for pushing a predicate (namely,
* they reference base tables) but the children's children do not.
* Ex.
* select * from
* (select i,j from t2 UNION
* values (1,1),(2,2),(3,3),(4,4) UNION
* select i,j from t1
* ) x0 (i,j),
* t5 where x0.i = t5.i;
*
* This will yield a tree resembling the following:
*
* UNION
* / \
* UNION SELECT (T1)
* / \
* SELECT (T2) VALUES
*
* In this case the top UNION ("this") will push the predicate down,
* but the second UNION will _not_ push the predicate because
* it can't be pushed to the VALUES clause. This means that
* after we're done modifying the paths for "this" node (the top
* UNION), the predicate will still be sitting in our leftOptPredicates
* list. If that's the case, then we have to make sure the predicate,
* which was _not_ enforced in the left child, is enforced at this
* level. We do that by generating a ProjectRestrictNode above this
* node. Yes, this means the predicate will actually be applied
* twice to the right child (in this case), but that's okay as it
* won't affect the results.
*/
// Get the cost estimate for this node so that we can put it in
// the new ProjectRestrictNode, if one is needed.
CostEstimate ce = getFinalCostEstimate();
// Modify this node's access paths.
ResultSetNode topNode = (ResultSetNode)modifyAccessPath(outerTables);
/* Now see if there are any left over predicates; if so, then we
* have to generate a ProjectRestrictNode. Note: we want to check
* all SetOpNodes that exist in the subtree rooted at this SetOpNode.
* Since we just modified access paths on this node, it's possible
* that the SetOperatorNode chain (if there was one) is now "broken"
* as a result of the insertion of new nodes. For example, prior
* to modification of access paths we may have a chain such as:
*
* UnionNode (0)
* / \
* UnionNode (1) SelectNode (2)
* / \
* SelectNode (3) SelectNode (4)
*
* Now if UnionNode(1) did not specify "ALL" then as part of the
* above call to modifyAccessPaths() we will have inserted a
* DistinctNode above it, thus giving:
*
* UnionNode (0)
* / \
* DistinctNode (5) SelectNode (2)
* |
* UnionNode (1)
* / \
* SelectNode (3) SelectNode (4)
*
* So our chain of UnionNode's has now been "broken" by an intervening
* DistinctNode. For this reason we can't just walk the chain of
* SetOperatorNodes looking for unpushed predicates (because the
* chain might be broken and then we could miss some nodes). Instead,
* we have to get a collection of all relevant nodes that exist beneath
* this SetOpNode and call hasUnPushedPredicates() on each one. For
* now we only consider UnionNodes to be "relevant" because those are
* the only ones that might actually have unpushed predicates.
*
* If we find any UnionNodes that *do* have unpushed predicates then
* we have to use a PRN to enforce the predicate at the level of
* this, the top-most, SetOperatorNode.
*/
// Find all UnionNodes in the subtree.
CollectNodesVisitor<UnionNode> cnv =
new CollectNodesVisitor<UnionNode>(UnionNode.class);
this.accept(cnv);
// Now see if any of them have unpushed predicates.
boolean genPRN = false;
for (UnionNode node : cnv.getList())
{
if (node.hasUnPushedPredicates())
{
genPRN = true;
break;
}
}
if (genPRN)
{
// When we generate the project restrict node, we pass in the
// "pushedPredicates" list because that has the predicates in
// _unscoped_ form, which means they are intended for _this_
// node instead of this node's children. That's exactly what
// we want.
ResultSetNode prnRSN = new ProjectRestrictNode(
topNode, // Child ResultSet
topNode.getResultColumns(), // Projection
null, // Restriction
pushedPredicates, // Restriction as PredicateList
null, // Subquerys in Projection
null, // Subquerys in Restriction
null, // Table properties
getContextManager());
prnRSN.setCostEstimate( ce.cloneMe() );
prnRSN.setReferencedTableMap(topNode.getReferencedTableMap());
topNode = prnRSN;
}
return (Optimizable)topNode;
}
/**
* @see org.apache.derby.iapi.sql.compile.Optimizable#pushOptPredicate
*
* Take a predicate and push it down to both the left AND right result
* sets. Return "true" if we successfully pushed it to both sides,
* and "false" otherwise. The assumption is that if we return "true",
* the caller will take the predicate and remove it from its own list
* of predicates to evaluate; if we return false, then the predicate
* will be evaluated at the level of the caller. So returning "false"
* means that the left and right result sets for this node will be fully
* returned, and then the predicate will be evaluated against the
* <set-operator> of those result sets (as of DERBY-805, the only set
* operator calling this method is UnionNode). If we can push the
* predicate down to both children, though, we can evaluate it closer
* to store, which means that each child result set returns only the
* correctly qualified rows, and thus the calling set operator will
* have a smaller result set on which to operate, which can boost
* performance.
*
* That said, if we can't push the predicate to _both_ sides, we don't
* push it at all. The reason is that if we push to one side but not
* to the other, we would have to ask the question of whether we should
* return "true" (meaning that the predicate would be removed from the
* caller's list and thus would _not_ be evaluated at the <set-operator>
* level) or "false" (meaning that the caller would keep the predicate
* and evaluate it at the <set-operator> level). Depending on the query
* in question, both answers could end up returning incorrect results.
*
* For example, if we push it to the right but not to the left, then
* leave it in the caller's list, the optimizer for the caller might
* decide to use the predicate to do a hash join with some outer result
* set (if the predicate is an equijoin predicate). That would result
* in materialization of the calling node and of its children--but since
* we pushed a predicate that depends on the outer table down into the
* right child, materialization of the right child will only return the
* rows that join with the _first_ row of the outer result set, which
* is wrong.
*
* If, on the other hand, we push the predicate to one side and then tell
* the caller to remove it from its list, the side to which we did _not_
* push the predicate could return rows that aren't qualified. Then,
* since the caller removed the predicate from its list, it (the caller)
* will not evaluate the predicate on its own result set--and thus we
* can end up returning rows that we weren't supposed to return.
*
* So all of that said, only push (and return "true") if we think we
* can push the predicate to both sides.
*
* @exception StandardException Thrown on error
*/
@Override
public boolean pushOptPredicate(OptimizablePredicate optimizablePredicate)
throws StandardException
{
// This method was added to SetOperatorNode as part of DERBY-805,
// which was only targeted for UnionNodes. So for now, we don't
// do anything if "this" isn't a Union. This check can be removed
// when support for other SetOperators is added.
if (!(this instanceof UnionNode))
return false;
// We only handle certain types of predicates here; if the received
// predicate doesn't qualify, then don't push it.
Predicate pred = (Predicate)optimizablePredicate;
if (!pred.pushableToSubqueries())
return false;
// Check to see if the child nodes reference any base tables; if either
// child does not reference at least one base table, then we don't try
// to push the predicate.
JBitSet tableNums = new JBitSet(getReferencedTableMap().size());
BaseTableNumbersVisitor btnVis =
new BaseTableNumbersVisitor(tableNums);
// Check the left child.
leftResultSet.accept(btnVis);
boolean canPush = (tableNums.getFirstSetBit() != -1);
/* If we can't push it to _both_ children, then we don't push at all.
* RESOLVE: We can add the ability to push a predicate to one side
* only by putting a ProjectRestrictNode between the union node and
* the child as a place to park the predicate. To make things simple,
* we might want to always put ProjectRestrictNodes under both sides
* of the union during preprocessing (i.e. after binding but before
* optimization). In some cases the extra nodes won't be needed, but
* PRNs (and the corresponding ProjectRestrictResultSets) are cheap.
* Also, we could eliminate unnecessary ProjectRestrictNodes at the
* end of optimization (possibly in modifyAccessPaths()). Until all
* of that is implemented, though, we only push if we can push to
* both sides...
*/
if (!canPush)
return false;
// Check the right child.
tableNums.clearAll();
rightResultSet.accept(btnVis);
canPush = (tableNums.getFirstSetBit() != -1);
if (!canPush)
return false;
// Get a list of all of the underlying base tables that this node
// references. We pass this down when scoping so that we can tell
// if the operands are actually supposed to be scoped to _this_
// node's children. Note that in order for the predicate to
// have been pushed this far, at least one of its operands must
// apply to this node--we don't know which one it is, though,
// so we use this tableNums info to figure that out.
tableNums.clearAll();
this.accept(btnVis);
/* What we want to do here is push the predicate to the left/right
* child. That means that we need to find the equivalent column(s)
* in each child.
* Ex:
*
* select * from
* (select i,j from t1 union select i,j from t2) X1,
* (select a,b from t3 union select a,b from t4) X2
* where X1.j = X2.b;
*
* In this example, X1.j maps to "t1" for the left side of the
* union (X1) and "t2" for the right side of the union. So we have
* to get versions of the predicate that are appropriate to each
* side. That's what the call to getPredScopedForResultSet()
* in the following code does.
*/
// For details on how this whichRC variable is used, see the
// comments in BinaryRelationalOperatorNode.getScopedOperand().
int [] whichRC = new int[] { -1 };
// See if we already have a scoped version of the predicate cached,
// and if so just use that.
Predicate scopedPred = null;
if (leftScopedPreds == null)
leftScopedPreds = new HashMap<Predicate,Predicate>();
else
scopedPred = leftScopedPreds.get(pred);
if (scopedPred == null)
{
scopedPred = pred.getPredScopedForResultSet(
tableNums, leftResultSet, whichRC);
leftScopedPreds.put(pred, scopedPred);
}
// Add the scoped predicate to our list for the left child.
getLeftOptPredicateList().addOptPredicate(scopedPred);
scopedPred = null;
if (rightScopedPreds == null)
rightScopedPreds = new HashMap<Predicate,Predicate>();
else
scopedPred = rightScopedPreds.get(pred);
if (scopedPred == null)
{
scopedPred = pred.getPredScopedForResultSet(
tableNums, rightResultSet, whichRC);
rightScopedPreds.put(pred, scopedPred);
}
// Add the scoped predicate to our list for the right child.
getRightOptPredicateList().addOptPredicate(scopedPred);
// Add the predicate (in its original form) to our list of predicates
// that we've pushed during this phase of optimization. We need to
// keep this list of pushed predicates around so that we can do
// a "pull" of them later, if needed. We also need this list for
// cases where predicates are not pushed all the way down; see
// modifyAccessPaths() in this class for more.
if (pushedPredicates == null)
pushedPredicates = new PredicateList(getContextManager());
pushedPredicates.addOptPredicate(pred);
return true;
}
/**
* @see Optimizable#pullOptPredicates
*
* @exception StandardException Thrown on error
*/
@Override
public void pullOptPredicates(
OptimizablePredicateList optimizablePredicates)
throws StandardException
{
if (pushedPredicates == null)
// we didn't push anything, so nothing to pull.
return;
// It's possible that we tried to push a predicate down to this
// SetOperatorNode's children but weren't actually able to do so
// (see modifyAccessPaths() in this class for details on when that
// can happen). In that case the predicates will still be sitting
// in the left/right predicate list; we can ignore them here by
// just discarding them. When it comes time to modifyAccessPaths,
// though, we'll handle them correctly--i.e. we'll generate a
// ProjectRestrictNode over this node to ensure the predicates are
// enforced.
if (leftOptPredicates != null)
leftOptPredicates.removeAllElements();
if (rightOptPredicates != null)
rightOptPredicates.removeAllElements();
/* Note that predicates which have been explicitly scoped should
* not be pulled. The reason is that a scoped predicate can only
* be pushed to a specific, target result set. When it comes time
* to pull the predicate up, there's no need to pull the scoped
* predicate because it, by definition, was only intended for this
* specific result set and therefore cannot be pushed anywhere else.
* So at "pull" time, we can just discard the scoped predicates. We
* do, however, need to pull the original, unscoped predicate from
* which the scoped predicate was created because we can potentially
* push that predicate elsewhere
*/
RemapCRsVisitor rcrv = new RemapCRsVisitor(false);
for (int i = 0; i < pushedPredicates.size(); i++)
{
Predicate pred = (Predicate)pushedPredicates.getOptPredicate(i);
if (pred.isScopedForPush())
{
/* We don't need to pull the predicate if it's scoped, but
* since scoped predicates are cached between rounds of
* optimization, it's possible that we'll reuse the scoped
* predicate again in a later round. So to make sure we
* get a "fresh start" in later rounds, we un-remap the
* predicate here.
*/
pred.getAndNode().accept(rcrv);
continue;
}
optimizablePredicates.addOptPredicate(pred);
}
// We're done with the pushedPredicates list, so clear it out
// in preparation for another phase of optimization.
pushedPredicates.removeAllElements();
}
/**
* It's possible that we tried to push predicates to this node's
* children but failed to do so. This can happen if this node's
* children both satisfy the criteria for pushing a predicate
* (namely, they reference base tables) but the children's
* children do not (see modifyAccessPaths() above for an example
* of how that can happen). So this method determines whether
* or not this particular SetOperatorNode has predicates which
* were *not* successfully pushed to both of its children (note:
* this currently only applies to UnionNodes).
*
* @return True if this SetOperatorNode has unpushed predicates;
* false otherwise.
*/
protected boolean hasUnPushedPredicates()
{
// Check this node.
return
((leftOptPredicates != null) && (leftOptPredicates.size() > 0)) ||
((rightOptPredicates != null) && (rightOptPredicates.size() > 0));
}
/**
* Convert this object to a String. See comments in QueryTreeNode.java
* for how this should be done for tree printing.
*
* @return This object as a String
*/
@Override
public String toString()
{
if (SanityManager.DEBUG)
{
return "all: " + all + "\n" +
super.toString();
}
else
{
return "";
}
}
/**
* Prints the sub-nodes of this object. See QueryTreeNode.java for
* how tree printing is supposed to work.
*
* @param depth The depth of this node in the tree
*/
@Override
void printSubNodes(int depth)
{
if (SanityManager.DEBUG)
{
super.printSubNodes(depth);
printQueryExpressionSuffixClauses(depth, qec);
}
}
/**
* Bind the result columns of this ResultSetNode when there is no
* base table to bind them to. This is useful for SELECT statements,
* where the result columns get their types from the expressions that
* live under them.
*
* @param fromListParam FromList to use/append to.
*
* @exception StandardException Thrown on error
*/
@Override
void bindResultColumns(FromList fromListParam)
throws StandardException
{
super.bindResultColumns(fromListParam);
/* Now we build our RCL */
buildRCL();
}
/**
* Bind the result columns for this ResultSetNode to a base table.
* This is useful for INSERT and UPDATE statements, where the
* result columns get their types from the table being updated or
* inserted into.
* If a result column list is specified, then the verification that the
* result column list does not contain any duplicates will be done when
* binding them by name.
*
* @param targetTableDescriptor The TableDescriptor for the table being
* updated or inserted into
* @param targetColumnList For INSERT statements, the user
* does not have to supply column
* names (for example, "insert into t
* values (1,2,3)". When this
* parameter is null, it means that
* the user did not supply column
* names, and so the binding should
* be done based on order. When it
* is not null, it means do the binding
* by name, not position.
* @param statement Calling DMLStatementNode (Insert or Update)
* @param fromListParam FromList to use/append to.
*
* @exception StandardException Thrown on error
*/
@Override
void bindResultColumns(TableDescriptor targetTableDescriptor,
FromVTI targetVTI, ResultColumnList targetColumnList,
DMLStatementNode statement, FromList fromListParam)
throws StandardException
{
super.bindResultColumns(targetTableDescriptor,
targetVTI,
targetColumnList, statement,
fromListParam);
/* Now we build our RCL */
buildRCL();
}
/**
* Build the RCL for this node. We propagate the RCL up from the
* left child to form this node's RCL.
*
* @exception StandardException Thrown on error
*/
private void buildRCL() throws StandardException
{
/* Verify that both sides of the union have the same # of columns in their
* RCL.
*/
if (leftResultSet.getResultColumns().visibleSize() !=
rightResultSet.getResultColumns().visibleSize())
{
throw StandardException.newException(SQLState.LANG_UNION_UNMATCHED_COLUMNS,
getOperatorName());
}
/* We need to recreate resultColumns for this node, since there
* may have been 1 or more *'s in the left's SELECT list.
*/
setResultColumns( leftResultSet.getResultColumns().copyListAndObjects() );
// The generated grouping columns of the left result set should not be
// part of the result from the set operation (DERBY-3764).
getResultColumns().removeGeneratedGroupingColumns();
getResultColumns().removeOrderByColumns();
/* Create new expressions with the dominant types after verifying
* union compatibility between left and right sides.
*/
getResultColumns().setUnionResultExpression(rightResultSet.getResultColumns(), tableNumber, level, getOperatorName());
}
/**
* Bind the result columns of a table constructor to the types in the
* given ResultColumnList. Use when inserting from a table constructor,
* and there are nulls in the values clauses.
*
* @param rcl The ResultColumnList with the types to bind to
*
* @exception StandardException Thrown on error.
*/
@Override
void bindUntypedNullsToResultColumns(ResultColumnList rcl)
throws StandardException
{
/*
** If the RCL from the parent is null, then
** the types are coming from the union itself.
** So we have to cross check the two child
** rcls.
*/
if (rcl == null)
{
ResultColumnList lrcl = rightResultSet.getResultColumns();
ResultColumnList rrcl = leftResultSet.getResultColumns();
leftResultSet.bindUntypedNullsToResultColumns(rrcl);
rightResultSet.bindUntypedNullsToResultColumns(lrcl);
}
else
{
leftResultSet.bindUntypedNullsToResultColumns(rcl);
rightResultSet.bindUntypedNullsToResultColumns(rcl);
}
}
/**
* {@inheritDoc}
*/
@Override
void replaceOrForbidDefaults(TableDescriptor ttd,
ResultColumnList tcl,
boolean allowDefaults)
throws StandardException
{
leftResultSet.replaceOrForbidDefaults(ttd, tcl, allowDefaults);
rightResultSet.replaceOrForbidDefaults(ttd, tcl, allowDefaults);
}
/**
* Get the parameter types from the given RowResultSetNode into the
* given array of types. If an array position is already filled in,
* don't clobber it.
*
* @param types The array of types to fill in
* @param rrsn The RowResultSetNode from which to take the param types
*
* @return The number of new types found in the RowResultSetNode
*/
int getParamColumnTypes(DataTypeDescriptor[] types, RowResultSetNode rrsn)
throws StandardException
{
int numTypes = 0;
/* Look for columns where we have not found a non-? yet. */
for (int i = 0; i < types.length; i++)
{
if (types[i] == null)
{
ResultColumn rc = rrsn.getResultColumns().elementAt(i);
if ( ! (rc.getExpression().requiresTypeFromContext()))
{
types[i] = rc.getExpression().getTypeServices();
numTypes++;
}
}
}
return numTypes;
}
/**
* Set the type of each ? parameter in the given RowResultSetNode
* according to its ordinal position in the given array of types.
*
* @param types An array of types containing the proper type for each
* ? parameter, by ordinal position.
* @param rrsn A RowResultSetNode that could contain ? parameters whose
* types need to be set.
*
* @exception StandardException Thrown on error
*/
void setParamColumnTypes(DataTypeDescriptor[] types, RowResultSetNode rrsn)
throws StandardException
{
/*
** Look for ? parameters in the result column list
** of each RowResultSetNode
*/
ResultColumnList rrcl = rrsn.getResultColumns();
int rrclSize = rrcl.size();
for (int index = 0; index < rrclSize; index++)
{
ResultColumn rc = rrcl.elementAt(index);
if (rc.getExpression().requiresTypeFromContext())
{
/*
** We found a ? - set its type to the type from the
** type array.
*/
rc.getExpression().setType(types[index]);
}
}
}
@Override
public void bindExpressions(FromList fromList) throws StandardException {
// Actions for UnionNode qua top node of a multi-valued table value
// constructor
for (int i = 0; i < qec.size(); i++) {
final OrderByList obl = qec.getOrderByList(i);
if (obl != null) {
obl.bindOrderByColumns(this);
obl.pullUpOrderByColumns(this);
}
bindOffsetFetch(qec.getOffset(i), qec.getFetchFirst(i));
}
super.bindExpressions(fromList);
}
/**
* Bind the expressions in the target list. This means binding the
* sub-expressions, as well as figuring out what the return type is
* for each expression. This is useful for EXISTS subqueries, where we
* need to validate the target list before blowing it away and replacing
* it with a SELECT true.
*
* @exception StandardException Thrown on error
*/
@Override
void bindTargetExpressions(FromList fromListParam)
throws StandardException
{
leftResultSet.bindTargetExpressions(fromListParam);
rightResultSet.bindTargetExpressions(fromListParam);
}
@Override
public void pushQueryExpressionSuffix() {
qec.push();
}
/**
* Push the order by list down from the cursor node
* into its child result set so that the optimizer
* has all of the information that it needs to
* consider sort avoidance.
*
* @param orderByList The order by list
*/
@Override
void pushOrderByList(OrderByList orderByList)
{
qec.setOrderByList(orderByList);
}
/**
* Push down the offset and fetch first parameters, if any, to this node.
*
* @param offset the OFFSET, if any
* @param fetchFirst the OFFSET FIRST, if any
* @param hasJDBClimitClause true if the clauses were added by (and have the semantics of) a JDBC limit clause
*/
@Override
void pushOffsetFetchFirst( ValueNode offset, ValueNode fetchFirst, boolean hasJDBClimitClause )
{
qec.setOffset(offset);
qec.setFetchFirst(fetchFirst);
qec.setHasJDBCLimitClause(hasJDBClimitClause);
}
/**
* Put a ProjectRestrictNode on top of each FromTable in the FromList.
* ColumnReferences must continue to point to the same ResultColumn, so
* that ResultColumn must percolate up to the new PRN. However,
* that ResultColumn will point to a new expression, a VirtualColumnNode,
* which points to the FromTable and the ResultColumn that is the source for
* the ColumnReference.
* (The new PRN will have the original of the ResultColumnList and
* the ResultColumns from that list. The FromTable will get shallow copies
* of the ResultColumnList and its ResultColumns. ResultColumn.expression
* will remain at the FromTable, with the PRN getting a new
* VirtualColumnNode for each ResultColumn.expression.)
* We then project out the non-referenced columns. If there are no referenced
* columns, then the PRN's ResultColumnList will consist of a single ResultColumn
* whose expression is 1.
*
* @param numTables Number of tables in the DML Statement
* @param gbl The group by list, if any
* @param fromList The from list, if any
*
* @return The preprocessed ResultSetNode that can be optimized
*
* @exception StandardException Thrown on error
*/
@Override
ResultSetNode preprocess(int numTables,
GroupByList gbl,
FromList fromList)
throws StandardException
{
ResultSetNode newTop = this;
/* RESOLVE - what does numTables and referencedTableMap mean here? */
leftResultSet = leftResultSet.preprocess(numTables, gbl, fromList);
rightResultSet = rightResultSet.preprocess(numTables, gbl, fromList);
/* Build the referenced table map (left || right) */
setReferencedTableMap( (JBitSet) leftResultSet.getReferencedTableMap().clone() );
getReferencedTableMap().or(rightResultSet.getReferencedTableMap());
/* If this is a UNION without an all and we have
* an order by then we can consider eliminating the sort for the
* order by. All of the columns in the order by list must
* be ascending in order to do this. There are 2 cases:
* o The order by list is an in order prefix of the columns
* in the select list. In this case the output of the
* sort from the distinct will be in the right order
* so we simply eliminate the order by list.
* o The order by list is a subset of the columns in the
* the select list. In this case we need to reorder the
* columns in the select list so that the ordering columns
* are an in order prefix of the select list and put a PRN
* above the select so that the shape of the result set
* is as expected.
*/
for (int i = 0; i < qec.size(); i++) {
OrderByList obl = qec.getOrderByList(i);
if ((! all) && obl != null &&
obl.allAscending())
{
/* Order by list currently restricted to columns in select
* list, so we will always eliminate the order by here.
*/
if (obl.isInOrderPrefix(getResultColumns()))
{
obl = null;
qec.setOrderByList(i, null);
}
/* RESOLVE - We currently only eliminate the order by if it is
* a prefix of the select list. We do not currently do the
* elimination if the order by is not a prefix because the code
* doesn't work. The problem has something to do with the
* fact that we generate additional nodes between the union
* and the PRN (for reordering that we would generate here)
* when modifying the access paths. VCNs under the PRN can be
* seen as correlated since their source resultset is the Union
* which is no longer the result set directly under them. This
* causes the wrong code to get generated. (jerry - 11/3/98)
* (bug 59)
*/
}
// UnionNode qua top of table value constructor with ordering
// If we have more than 1 ORDERBY columns, we may be able to
// remove duplicate columns, e.g., "ORDER BY 1, 1, 2".
if (obl != null && obl.size() > 1) {
obl.removeDupColumns();
}
}
return newTop;
}
/**
* Ensure that the top of the RSN tree has a PredicateList.
*
* @param numTables The number of tables in the query.
* @return ResultSetNode A RSN tree with a node which has a PredicateList on top.
*
* @exception StandardException Thrown on error
*/
@Override
ResultSetNode ensurePredicateList(int numTables)
throws StandardException
{
return genProjectRestrict(numTables);
}
/**
* Verify that a SELECT * is valid for this type of subquery.
*
* @param outerFromList The FromList from the outer query block(s)
* @param subqueryType The subquery type
*
* @exception StandardException Thrown on error
*/
@Override
void verifySelectStarSubquery(FromList outerFromList, int subqueryType)
throws StandardException
{
/* Check both sides - SELECT * is not valid on either side */
leftResultSet.verifySelectStarSubquery(outerFromList, subqueryType);
rightResultSet.verifySelectStarSubquery(outerFromList, subqueryType);
}
/**
* Determine whether or not the specified name is an exposed name in
* the current query block.
*
* @param name The specified name to search for as an exposed name.
* @param schemaName Schema name, if non-null.
* @param exactMatch Whether or not we need an exact match on specified schema and table
* names or match on table id.
*
* @return The FromTable, if any, with the exposed name.
*
* @exception StandardException Thrown on error
*/
@Override
FromTable getFromTableByName(String name, String schemaName, boolean exactMatch)
throws StandardException
{
/* We search both sides for a TableOperatorNode (join nodes)
* but only the left side for a UnionNode.
*/
return leftResultSet.getFromTableByName(name, schemaName, exactMatch);
}
/**
* Set the result column for the subquery to a boolean true,
* Useful for transformations such as
* changing:
* where exists (select ... from ...)
* to:
* where (select true from ...)
*
* NOTE: No transformation is performed if the ResultColumn.expression is
* already the correct boolean constant.
*
* This method is used during binding of EXISTS predicates to map
* a subquery's result column list into a single TRUE node. For
* SELECT and VALUES subqueries this transformation is pretty
* straightforward. But for set operators (ex. INTERSECT) we have
* to do some extra work. To see why, assume we have the following
* query:
*
* select * from ( values 'BAD' ) as T
* where exists ((values 1) intersect (values 2))
*
* If we treated the INTERSECT in this query the same way that we
* treat SELECT/VALUES subqueries then the above query would get
* transformed into:
*
* select * from ( values 'BAD' ) as T
* where ((values TRUE) intersect (values TRUE))
*
* Since both children of the INTERSECT would then have the same value,
* the result of set operation would be a single value (TRUE), which
* means the WHERE clause would evaluate to TRUE and thus the query
* would return one row with value 'BAD'. That would be wrong.
*
* To avoid this problem, we internally wrap this SetOperatorNode
* inside a "SELECT *" subquery and then we change the new SelectNode's
* result column list (as opposed to *this* nodes' result column list)
* to a singe boolean true node:
*
* select * from ( values 'BAD' ) as T where
* SELECT TRUE FROM ((values 1) intersect (values 2))
*
* In this case the left and right children of the INTERSECT retain
* their values, which ensures that the result of the intersect
* operation will be correct. Since (1 intersect 2) is an empty
* result set, the internally generated SELECT node will return
* zero rows, which in turn means the WHERE predicate will return
* NULL (an empty result set from a SubqueryNode is treated as NULL
* at execution time; see impl/sql/execute/AnyResultSet). Since
* NULL is not the same as TRUE the query will correctly return
* zero rows. DERBY-2370.
*
* @param onlyConvertAlls Boolean, whether or not to just convert *'s
*
* @exception StandardException Thrown on error
*/
@Override
ResultSetNode setResultToBooleanTrueNode(boolean onlyConvertAlls)
throws StandardException
{
// First create a FromList to hold this node (and only this node).
FromList fromList = new FromList(
getOptimizerFactory().doJoinOrderOptimization(),
getContextManager());
fromList.addFromTable(this);
/* It's possible that this SetOperatorNode (or more specifically,
* one of its children) references tables from an outer query, ex:
*
* select j from onerow where exists
* (select 1 from diffrow where 1 = 0 INTERSECT
* select * from diffrow where onerow.j < k)
*
* In this case the right child of the INTERSECT node references
* the outer table "onerow". In order to ensure that the new
* subquery binds correctly we mark the new FromList as "transparent",
* which means that the FromTables it contains (namely, this node
* and its children) will still be able to see (and reference) the
* outer table.
*/
fromList.markAsTransparent();
// Now create a ResultColumnList that simply holds the "*".
ResultColumnList rcl = new ResultColumnList(getContextManager());
ResultColumn allResultColumn =
new AllResultColumn(null, getContextManager());
rcl.addResultColumn(allResultColumn);
/* Create a new SELECT node of the form:
* SELECT * FROM <thisSetOperatorNode>
*/
ResultSetNode result = new SelectNode(
rcl, // ResultColumns
fromList, // FROM list
null, // WHERE clause
null, // GROUP BY list
null, // having clause
null, /* window list */
null, /* optimizer override plan */
getContextManager());
/* And finally, transform the "*" in the new SELECT node
* into a TRUE constant node. This ultimately gives us:
*
* SELECT TRUE FROM <thisSetOperatorNode>
*
* which has a single result column that is a boolean TRUE
* constant. So we're done.
*/
return result.setResultToBooleanTrueNode(onlyConvertAlls);
}
/**
* Evaluate whether or not the subquery in a FromSubquery is flattenable.
* Currently, a FSqry is flattenable if all of the following are true:
* o Subquery is a SelectNode. (ie, not a RowResultSetNode or a UnionNode)
* o It contains no top level subqueries. (RESOLVE - we can relax this)
* o It does not contain a group by or having clause
* o It does not contain aggregates.
*
* @param fromList The outer from list
*
* @return boolean Whether or not the FromSubquery is flattenable.
*/
@Override
boolean flattenableInFromSubquery(FromList fromList)
{
/* Unions in FromSubquerys are not flattenable. */
return false;
}
/**
* Return whether or not to materialize this ResultSet tree.
*
* @return Whether or not to materialize this ResultSet tree.
* would return valid results.
*
* @exception StandardException Thrown on error
*/
@Override
boolean performMaterialization(JBitSet outerTables)
throws StandardException
{
// RESOLVE - just say no to materialization right now - should be a cost based decision
return false;
/* Actual materialization, if appropriate, will be placed by our parent PRN.
* This is because PRN might have a join condition to apply. (Materialization
* can only occur before that.
*/
//return true;
}
/**
* @return the operator name: "UNION", "INTERSECT", or "EXCEPT"
*/
abstract String getOperatorName();
/**
* Retrieve the list of optimizable predicates that are
* targeted for the left child. Create a new (empty)
* list if the list is null.
*/
PredicateList getLeftOptPredicateList()
throws StandardException
{
if (leftOptPredicates == null) {
leftOptPredicates = new PredicateList(getContextManager());
}
return leftOptPredicates;
}
/**
* Retrieve the list of optimizable predicates that are
* targeted for the right child. Create a new (empty)
* list if the list is null.
*/
PredicateList getRightOptPredicateList()
throws StandardException
{
if (rightOptPredicates == null) {
rightOptPredicates = new PredicateList(getContextManager());
}
return rightOptPredicates;
}
}