/* * ModeShape (http://www.modeshape.org) * * Licensed 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.modeshape.jcr.query.plan; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.modeshape.jcr.query.QueryContext; import org.modeshape.jcr.query.model.And; import org.modeshape.jcr.query.model.ArithmeticOperand; import org.modeshape.jcr.query.model.ArithmeticOperator; import org.modeshape.jcr.query.model.Between; import org.modeshape.jcr.query.model.BindVariableName; import org.modeshape.jcr.query.model.Cast; import org.modeshape.jcr.query.model.ChildCount; import org.modeshape.jcr.query.model.ChildNode; import org.modeshape.jcr.query.model.ChildNodeJoinCondition; import org.modeshape.jcr.query.model.Column; import org.modeshape.jcr.query.model.Comparison; import org.modeshape.jcr.query.model.Constraint; import org.modeshape.jcr.query.model.DescendantNode; import org.modeshape.jcr.query.model.DescendantNodeJoinCondition; import org.modeshape.jcr.query.model.DynamicOperand; import org.modeshape.jcr.query.model.EquiJoinCondition; import org.modeshape.jcr.query.model.FullTextSearch; import org.modeshape.jcr.query.model.FullTextSearchScore; import org.modeshape.jcr.query.model.JoinCondition; import org.modeshape.jcr.query.model.Length; import org.modeshape.jcr.query.model.LowerCase; import org.modeshape.jcr.query.model.NodeDepth; import org.modeshape.jcr.query.model.NodeLocalName; import org.modeshape.jcr.query.model.NodeName; import org.modeshape.jcr.query.model.NodePath; import org.modeshape.jcr.query.model.Not; import org.modeshape.jcr.query.model.Or; import org.modeshape.jcr.query.model.Ordering; import org.modeshape.jcr.query.model.PropertyExistence; import org.modeshape.jcr.query.model.PropertyValue; import org.modeshape.jcr.query.model.ReferenceValue; import org.modeshape.jcr.query.model.Relike; import org.modeshape.jcr.query.model.SameNode; import org.modeshape.jcr.query.model.SameNodeJoinCondition; import org.modeshape.jcr.query.model.SelectorName; import org.modeshape.jcr.query.model.SetCriteria; import org.modeshape.jcr.query.model.StaticOperand; import org.modeshape.jcr.query.model.Subquery; import org.modeshape.jcr.query.model.UpperCase; import org.modeshape.jcr.query.model.Visitors; import org.modeshape.jcr.query.model.Visitors.AbstractVisitor; import org.modeshape.jcr.query.plan.PlanNode.Property; import org.modeshape.jcr.query.plan.PlanNode.Type; import org.modeshape.jcr.query.validate.ImmutableColumn; import org.modeshape.jcr.query.validate.Schemata; import org.modeshape.jcr.query.validate.Schemata.View; /** * Utilities for working with {@link PlanNode}s. */ public class PlanUtil { /** * Collected the minimum set of columns from the supplied table that are required by or used within the plan at the supplied * node or above. This method first looks to see if the supplied node is a {@link Type#PROJECT} node, and if so immediately * returns that node's list of {@link Property#PROJECT_COLUMNS projected columns}. Otherwise, this method finds all of the * {@link Type#SOURCE} nodes below the supplied plan node, and accumulates the set of sources for which the columns are to be * found. The method then walks up the plan, looking for references to columns of the supplied table, starting with the * supplied node and walking up until a {@link PlanNode.Type#PROJECT PROJECT} node is found or the top of the plan is reached. * <p> * The resulting column list will always contain at the front the {@link Property#PROJECT_COLUMNS columns} from the nearest * PROJECT ancestor, followed by any columns required by criteria. This is done so that the processing of the nearest PROJECT * ancestor node can simply use the sublist of each tuple. * </p> * * @param context the query context; may not be null * @param planNode the plan node at which the required columns need to be found; may not be null * @return the list of {@link Column} objects that are used in the plan; never null but possibly empty if no columns are * actually needed */ public static List<Column> findRequiredColumns( QueryContext context, PlanNode planNode ) { List<Column> columns = null; PlanNode node = planNode; // First find the columns from the nearest PROJECT ancestor ... do { switch (node.getType()) { case PROJECT: columns = node.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class); node = null; break; default: node = node.getParent(); break; } } while (node != null); // Find the names of the selectors ... Set<SelectorName> names = new HashSet<SelectorName>(); for (PlanNode source : planNode.findAllAtOrBelow(Type.SOURCE)) { names.add(source.getProperty(Property.SOURCE_NAME, SelectorName.class)); SelectorName alias = source.getProperty(Property.SOURCE_ALIAS, SelectorName.class); if (alias != null) names.add(alias); } // Add the PROJECT columns first ... RequiredColumnVisitor collectionVisitor = new RequiredColumnVisitor(names); if (columns != null) { for (Column projectedColumn : columns) { collectionVisitor.visit(projectedColumn); } } // Now add the columns from the JOIN, SELECT, PROJECT and SORT ancestors ... node = planNode; do { switch (node.getType()) { case JOIN: List<Constraint> criteria = node.getPropertyAsList(Property.JOIN_CONSTRAINTS, Constraint.class); JoinCondition joinCondition = node.getProperty(Property.JOIN_CONDITION, JoinCondition.class); Visitors.visitAll(criteria, collectionVisitor); Visitors.visitAll(joinCondition, collectionVisitor); break; case SELECT: Constraint constraint = node.getProperty(Property.SELECT_CRITERIA, Constraint.class); Visitors.visitAll(constraint, collectionVisitor); break; case SORT: List<Object> orderBys = node.getPropertyAsList(Property.SORT_ORDER_BY, Object.class); if (orderBys != null && !orderBys.isEmpty()) { if (orderBys.get(0) instanceof Ordering) { for (int i = 0; i != orderBys.size(); ++i) { Ordering ordering = (Ordering)orderBys.get(i); Visitors.visitAll(ordering, collectionVisitor); } } } break; case PROJECT: if (node != planNode) { // Already handled above, but we can stop looking for columns ... return collectionVisitor.getRequiredColumns(); } break; default: break; } node = node.getParent(); } while (node != null); return collectionVisitor.getRequiredColumns(); } public static List<String> findRequiredColumnTypes( QueryContext context, List<Column> columns, PlanNode node ) { if (node.getType() == Type.PROJECT) { assert node.getChildCount() == 1; node = node.getFirstChild(); } // See if there are any PROJECT nodes below this node ... List<PlanNode> projects = node.findAllFirstNodesAtOrBelow(Type.PROJECT); if (!projects.isEmpty()) { List<String> types = new ArrayList<String>(columns.size()); for (Column column : columns) { boolean added = false; for (PlanNode project : projects) { List<Column> projectedColumns = project.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class); List<String> projectedTypes = project.getPropertyAsList(Property.PROJECT_COLUMN_TYPES, String.class); if (projectedTypes == null) continue; for (int i = 0; i != projectedColumns.size(); ++i) { Column projectedColumn = projectedColumns.get(i); if (column.equals(projectedColumn)) { types.add(projectedTypes.get(i)); added = true; break; } } if (added) break; } } if (types.size() == columns.size()) return types; } // Otherwise, look for the sources ... List<String> types = new ArrayList<String>(columns.size()); List<PlanNode> sources = node.findAllAtOrBelow(Type.SOURCE); for (Column column : columns) { boolean added = false; for (PlanNode source : sources) { SelectorName alias = source.getProperty(Property.SOURCE_ALIAS, SelectorName.class); SelectorName name = source.getProperty(Property.SOURCE_NAME, SelectorName.class); if ((alias != null && alias.equals(column.selectorName())) || name.equals(column.selectorName())) { List<Schemata.Column> sourceColumns = source.getPropertyAsList(Property.SOURCE_COLUMNS, Schemata.Column.class); for (Schemata.Column sourceColumn : sourceColumns) { if (sourceColumn.getName().equals(column.getColumnName()) || sourceColumn.getName().equals(column.getPropertyName())) { types.add(sourceColumn.getPropertyTypeName()); added = true; break; } } if (added) break; } } if (!added) { // The column could not be found in the source, but it may be referring to a residual property. // Therefore, we'll use the default type ... types.add(context.getTypeSystem().getDefaultType()); } } return types; } public static class RequiredColumnVisitor extends AbstractVisitor { private final Set<SelectorName> names; private final List<Column> columns = new LinkedList<Column>(); private final Set<String> requiredColumnNames = new HashSet<String>(); public RequiredColumnVisitor( Set<SelectorName> names ) { this.names = names; } @Override public void visit( PropertyExistence existence ) { requireColumn(existence.selectorName(), existence.getPropertyName()); } @Override public void visit( PropertyValue value ) { requireColumn(value.selectorName(), value.getPropertyName()); } @Override public void visit( ReferenceValue value ) { String propertyName = value.getPropertyName(); if (propertyName != null) { requireColumn(value.selectorName(), propertyName); } } @Override public void visit( EquiJoinCondition condition ) { requireColumn(condition.selector1Name(), condition.getProperty1Name()); requireColumn(condition.selector2Name(), condition.getProperty2Name()); } @Override public void visit( Column column ) { requireColumn(column.selectorName(), column.getPropertyName(), column.getColumnName()); } protected void requireColumn( SelectorName selector, String propertyName ) { requireColumn(selector, propertyName, null); } protected void requireColumn( SelectorName selector, String propertyName, String alias ) { if (names.contains(selector)) { // The column is part of the table we're interested in ... if (alias != null && !alias.equals(propertyName)) { if (requiredColumnNames.add(propertyName) && requiredColumnNames.add(alias)) { columns.add(new Column(selector, propertyName, alias)); } } else { if (requiredColumnNames.add(propertyName)) { alias = propertyName; columns.add(new Column(selector, propertyName, alias)); } } } } /** * Get the columns that are required. * * @return the columns; never null */ public List<Column> getRequiredColumns() { return columns; } } public static void replaceReferencesToRemovedSource( QueryContext context, PlanNode planNode, Map<SelectorName, SelectorName> rewrittenSelectors ) { switch (planNode.getType()) { case PROJECT: List<Column> columns = planNode.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class); for (int i = 0; i != columns.size(); ++i) { Column column = columns.get(i); SelectorName replacement = rewrittenSelectors.get(column.selectorName()); if (replacement != null) { columns.set(i, new Column(replacement, column.getPropertyName(), column.getColumnName())); } } break; case SELECT: Constraint constraint = planNode.getProperty(Property.SELECT_CRITERIA, Constraint.class); Constraint newConstraint = replaceReferencesToRemovedSource(context, constraint, rewrittenSelectors); if (constraint != newConstraint) { planNode.setProperty(Property.SELECT_CRITERIA, newConstraint); } break; case SORT: List<Object> orderBys = planNode.getPropertyAsList(Property.SORT_ORDER_BY, Object.class); if (orderBys != null && !orderBys.isEmpty()) { if (orderBys.get(0) instanceof SelectorName) { for (int i = 0; i != orderBys.size(); ++i) { SelectorName selectorName = (SelectorName)orderBys.get(i); SelectorName replacement = rewrittenSelectors.get(selectorName); if (replacement != null) { orderBys.set(i, replacement); } } } else { for (int i = 0; i != orderBys.size(); ++i) { Ordering ordering = (Ordering)orderBys.get(i); DynamicOperand operand = ordering.getOperand(); orderBys.set(i, replaceReferencesToRemovedSource(context, operand, rewrittenSelectors)); } } } break; case JOIN: // Update the join condition ... JoinCondition joinCondition = planNode.getProperty(Property.JOIN_CONDITION, JoinCondition.class); JoinCondition newCondition = replaceReferencesToRemovedSource(context, joinCondition, rewrittenSelectors); if (joinCondition != newCondition) { planNode.setProperty(Property.JOIN_CONDITION, newCondition); } // Update the join criteria (if there are some) ... List<Constraint> constraints = planNode.getPropertyAsList(Property.JOIN_CONSTRAINTS, Constraint.class); if (constraints != null && !constraints.isEmpty()) { for (int i = 0; i != constraints.size(); ++i) { Constraint old = constraints.get(i); Constraint replacement = replaceReferencesToRemovedSource(context, old, rewrittenSelectors); if (replacement != old) { constraints.set(i, replacement); } } } break; case SOURCE: // Check the source alias ... SelectorName sourceAlias = planNode.getProperty(Property.SOURCE_ALIAS, SelectorName.class); SelectorName replacement = rewrittenSelectors.get(sourceAlias); if (replacement == null) { // Try the source name ... SelectorName sourceName = planNode.getProperty(Property.SOURCE_NAME, SelectorName.class); replacement = rewrittenSelectors.get(sourceName); } if (replacement != null) { planNode.setProperty(Property.SOURCE_ALIAS, replacement); planNode.getSelectors().remove(sourceAlias); planNode.getSelectors().add(replacement); } break; case GROUP: case SET_OPERATION: case DUP_REMOVE: case LIMIT: case NULL: case DEPENDENT_QUERY: case ACCESS: case INDEX: // None of these have to be changed ... break; } // Update the selectors referenced by the node ... Set<SelectorName> selectorsToAdd = null; for (Iterator<SelectorName> iter = planNode.getSelectors().iterator(); iter.hasNext();) { SelectorName replacement = rewrittenSelectors.get(iter.next()); if (replacement != null) { iter.remove(); if (selectorsToAdd == null) selectorsToAdd = new HashSet<SelectorName>(); selectorsToAdd.add(replacement); } } if (selectorsToAdd != null) planNode.getSelectors().addAll(selectorsToAdd); // Now call recursively on the children ... for (PlanNode child : planNode) { replaceReferencesToRemovedSource(context, child, rewrittenSelectors); } } public static DynamicOperand replaceReferencesToRemovedSource( QueryContext context, DynamicOperand operand, Map<SelectorName, SelectorName> rewrittenSelectors ) { if (operand instanceof FullTextSearchScore) { FullTextSearchScore score = (FullTextSearchScore)operand; SelectorName replacement = rewrittenSelectors.get(score.selectorName()); if (replacement == null) return score; return new FullTextSearchScore(replacement); } if (operand instanceof Length) { Length operation = (Length)operand; PropertyValue wrapped = operation.getPropertyValue(); SelectorName replacement = rewrittenSelectors.get(wrapped.selectorName()); if (replacement == null) return operand; return new Length(new PropertyValue(replacement, wrapped.getPropertyName())); } if (operand instanceof LowerCase) { LowerCase operation = (LowerCase)operand; SelectorName replacement = rewrittenSelectors.get(operation.selectorName()); if (replacement == null) return operand; return new LowerCase(replaceReferencesToRemovedSource(context, operation.getOperand(), rewrittenSelectors)); } if (operand instanceof UpperCase) { UpperCase operation = (UpperCase)operand; SelectorName replacement = rewrittenSelectors.get(operation.selectorName()); if (replacement == null) return operand; return new UpperCase(replaceReferencesToRemovedSource(context, operation.getOperand(), rewrittenSelectors)); } if (operand instanceof Cast) { Cast operation = (Cast) operand; SelectorName replacement = rewrittenSelectors.get(operation.selectorName()); if (replacement == null) return operand; return new Cast(replaceReferencesToRemovedSource(context, operation.getOperand(), rewrittenSelectors), operation.getDesiredTypeName()); } if (operand instanceof NodeName) { NodeName name = (NodeName)operand; SelectorName replacement = rewrittenSelectors.get(name.selectorName()); if (replacement == null) return name; return new NodeName(replacement); } if (operand instanceof NodeLocalName) { NodeLocalName name = (NodeLocalName)operand; SelectorName replacement = rewrittenSelectors.get(name.selectorName()); if (replacement == null) return name; return new NodeLocalName(replacement); } if (operand instanceof PropertyValue) { PropertyValue value = (PropertyValue)operand; SelectorName replacement = rewrittenSelectors.get(value.selectorName()); if (replacement == null) return operand; return new PropertyValue(replacement, value.getPropertyName()); } if (operand instanceof ReferenceValue) { ReferenceValue value = (ReferenceValue)operand; SelectorName replacement = rewrittenSelectors.get(value.selectorName()); if (replacement == null) return operand; return new ReferenceValue(replacement, value.getPropertyName()); } if (operand instanceof NodeDepth) { NodeDepth depth = (NodeDepth)operand; SelectorName replacement = rewrittenSelectors.get(depth.selectorName()); if (replacement == null) return operand; return new NodeDepth(replacement); } if (operand instanceof NodePath) { NodePath path = (NodePath)operand; SelectorName replacement = rewrittenSelectors.get(path.selectorName()); if (replacement == null) return operand; return new NodePath(replacement); } if (operand instanceof ChildCount) { ChildCount count = (ChildCount)operand; SelectorName replacement = rewrittenSelectors.get(count.selectorName()); if (replacement == null) return operand; return new ChildCount(replacement); } return operand; } public static Constraint replaceReferencesToRemovedSource( QueryContext context, Constraint constraint, Map<SelectorName, SelectorName> rewrittenSelectors ) { if (constraint instanceof And) { And and = (And)constraint; Constraint left = replaceReferencesToRemovedSource(context, and.left(), rewrittenSelectors); Constraint right = replaceReferencesToRemovedSource(context, and.right(), rewrittenSelectors); if (left == and.left() && right == and.right()) return and; return new And(left, right); } if (constraint instanceof Or) { Or or = (Or)constraint; Constraint left = replaceReferencesToRemovedSource(context, or.left(), rewrittenSelectors); Constraint right = replaceReferencesToRemovedSource(context, or.right(), rewrittenSelectors); if (left == or.left() && right == or.right()) return or; return new Or(left, right); } if (constraint instanceof Not) { Not not = (Not)constraint; Constraint wrapped = replaceReferencesToRemovedSource(context, not.getConstraint(), rewrittenSelectors); if (wrapped == not.getConstraint()) return not; return new Not(wrapped); } if (constraint instanceof SameNode) { SameNode sameNode = (SameNode)constraint; SelectorName replacement = rewrittenSelectors.get(sameNode.selectorName()); if (replacement == null) return sameNode; return new SameNode(replacement, sameNode.getPath()); } if (constraint instanceof ChildNode) { ChildNode childNode = (ChildNode)constraint; SelectorName replacement = rewrittenSelectors.get(childNode.selectorName()); if (replacement == null) return childNode; return new ChildNode(replacement, childNode.getParentPath()); } if (constraint instanceof DescendantNode) { DescendantNode descendantNode = (DescendantNode)constraint; SelectorName replacement = rewrittenSelectors.get(descendantNode.selectorName()); if (replacement == null) return descendantNode; return new DescendantNode(replacement, descendantNode.getAncestorPath()); } if (constraint instanceof PropertyExistence) { PropertyExistence existence = (PropertyExistence)constraint; SelectorName replacement = rewrittenSelectors.get(existence.selectorName()); if (replacement == null) return existence; return new PropertyExistence(replacement, existence.getPropertyName()); } if (constraint instanceof FullTextSearch) { FullTextSearch search = (FullTextSearch)constraint; SelectorName replacement = rewrittenSelectors.get(search.selectorName()); if (replacement == null) return search; return new FullTextSearch(replacement, search.getPropertyName(), search.fullTextSearchExpression()); } if (constraint instanceof Between) { Between between = (Between)constraint; DynamicOperand lhs = between.getOperand(); StaticOperand lower = between.getLowerBound(); // Current only a literal; therefore, no reference to selector StaticOperand upper = between.getUpperBound(); // Current only a literal; therefore, no reference to selector DynamicOperand newLhs = replaceReferencesToRemovedSource(context, lhs, rewrittenSelectors); if (lhs == newLhs) return between; return new Between(newLhs, lower, upper, between.isLowerBoundIncluded(), between.isUpperBoundIncluded()); } if (constraint instanceof Comparison) { Comparison comparison = (Comparison)constraint; DynamicOperand lhs = comparison.getOperand1(); StaticOperand rhs = comparison.getOperand2(); // Current only a literal; therefore, no reference to selector DynamicOperand newLhs = replaceReferencesToRemovedSource(context, lhs, rewrittenSelectors); if (lhs == newLhs) return comparison; return new Comparison(newLhs, comparison.operator(), rhs); } if (constraint instanceof SetCriteria) { SetCriteria criteria = (SetCriteria)constraint; DynamicOperand lhs = criteria.leftOperand(); DynamicOperand newLhs = replaceReferencesToRemovedSource(context, lhs, rewrittenSelectors); if (lhs == newLhs) return constraint; return new SetCriteria(newLhs, criteria.rightOperands()); } return constraint; } public static JoinCondition replaceReferencesToRemovedSource( QueryContext context, JoinCondition joinCondition, Map<SelectorName, SelectorName> rewrittenSelectors ) { if (joinCondition instanceof EquiJoinCondition) { EquiJoinCondition condition = (EquiJoinCondition)joinCondition; SelectorName replacement1 = rewrittenSelectors.get(condition.selector1Name()); SelectorName replacement2 = rewrittenSelectors.get(condition.selector2Name()); if (replacement1 == null) replacement1 = condition.selector1Name(); if (replacement2 == null) replacement2 = condition.selector2Name(); if (replacement1 == condition.selector1Name() && replacement2 == condition.selector2Name()) return condition; return new EquiJoinCondition(replacement1, condition.getProperty1Name(), replacement2, condition.getProperty2Name()); } if (joinCondition instanceof SameNodeJoinCondition) { SameNodeJoinCondition condition = (SameNodeJoinCondition)joinCondition; SelectorName replacement1 = rewrittenSelectors.get(condition.selector1Name()); SelectorName replacement2 = rewrittenSelectors.get(condition.selector2Name()); if (replacement1 == null) replacement1 = condition.selector1Name(); if (replacement2 == null) replacement2 = condition.selector2Name(); if (replacement1 == condition.selector1Name() && replacement2 == condition.selector2Name()) return condition; return new SameNodeJoinCondition(replacement1, replacement2, condition.getSelector2Path()); } if (joinCondition instanceof ChildNodeJoinCondition) { ChildNodeJoinCondition condition = (ChildNodeJoinCondition)joinCondition; SelectorName childSelector = rewrittenSelectors.get(condition.childSelectorName()); SelectorName parentSelector = rewrittenSelectors.get(condition.parentSelectorName()); if (childSelector == null) childSelector = condition.childSelectorName(); if (parentSelector == null) parentSelector = condition.parentSelectorName(); if (childSelector == condition.childSelectorName() && parentSelector == condition.parentSelectorName()) return condition; return new ChildNodeJoinCondition(parentSelector, childSelector); } if (joinCondition instanceof DescendantNodeJoinCondition) { DescendantNodeJoinCondition condition = (DescendantNodeJoinCondition)joinCondition; SelectorName ancestor = rewrittenSelectors.get(condition.ancestorSelectorName()); SelectorName descendant = rewrittenSelectors.get(condition.descendantSelectorName()); if (ancestor == null) ancestor = condition.ancestorSelectorName(); if (descendant == null) descendant = condition.descendantSelectorName(); if (ancestor == condition.ancestorSelectorName() && descendant == condition.descendantSelectorName()) return condition; return new ChildNodeJoinCondition(ancestor, descendant); } return joinCondition; } public static void replaceViewReferences( QueryContext context, PlanNode topOfViewInPlan, ColumnMapping mappings ) { assert topOfViewInPlan != null; SelectorName viewName = mappings.getOriginalName(); // Walk up the plan to change any references to the view columns into references to the source columns... PlanNode node = topOfViewInPlan; List<PlanNode> potentiallyRemovableSources = new LinkedList<PlanNode>(); do { // Move to the parent ... replaceViewReferences(context, node, mappings, viewName, potentiallyRemovableSources); node = node.getParent(); } while (node != null); // Remove any source node that was the view but that is no longer used in any higher node ... for (PlanNode sourceNode : potentiallyRemovableSources) { boolean stillRequired = false; node = sourceNode.getParent(); while (node != null) { if (node.getSelectors().contains(viewName)) { stillRequired = true; break; } node = node.getParent(); } if (!stillRequired) { assert sourceNode.getParent() != null; sourceNode.extractFromParent(); } } } protected static void replaceViewReferences( QueryContext context, PlanNode node, ColumnMapping mappings, SelectorName viewName, List<PlanNode> potentiallyRemovableSources ) { assert node != null; assert viewName != null; // Remove the view from the selectors ... if (node.getSelectors().remove(viewName)) { switch (node.getType()) { case PROJECT: // Adjust the columns ... List<Column> columns = node.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class); if (columns != null) { for (int i = 0; i != columns.size(); ++i) { Column column = columns.get(i); if (column.selectorName().equals(viewName)) { // This column references the view ... String columnName = column.getPropertyName(); String columnAlias = column.getColumnName(); // Find the source column that this view column corresponds to ... Column sourceColumn = mappings.getMappedColumn(columnName); if (sourceColumn != null) { SelectorName sourceName = sourceColumn.selectorName(); // Replace the view column with one that uses the same alias but that references the // source // column ... columns.set(i, new Column(sourceName, sourceColumn.getPropertyName(), columnAlias)); node.addSelector(sourceName); } else { if (mappings.getMappedSelectorNames().size() == 1) { SelectorName sourceName = mappings.getSingleMappedSelectorName(); if (sourceName != null) { Column newColumn = null; if (columnName != null) { newColumn = new Column(sourceName, columnName, columnAlias); } else { newColumn = new Column(sourceName); } columns.set(i, newColumn); node.addSelector(sourceName); } else { node.addSelector(column.selectorName()); } } else { node.addSelector(column.selectorName()); } } } else { node.addSelector(column.selectorName()); } } } break; case SELECT: Constraint constraint = node.getProperty(Property.SELECT_CRITERIA, Constraint.class); Constraint newConstraint = replaceReferences(context, constraint, mappings, node); if (constraint != newConstraint) { node.getSelectors().clear(); node.addSelectors(Visitors.getSelectorsReferencedBy(newConstraint)); node.setProperty(Property.SELECT_CRITERIA, newConstraint); } else { node.addSelector(viewName); } break; case SOURCE: SelectorName sourceName = node.getProperty(Property.SOURCE_NAME, SelectorName.class); assert sourceName.equals(sourceName); // selector name already matches potentiallyRemovableSources.add(node); break; case JOIN: JoinCondition joinCondition = node.getProperty(Property.JOIN_CONDITION, JoinCondition.class); JoinCondition newJoinCondition = replaceViewReferences(context, joinCondition, mappings, node); node.getSelectors().clear(); node.setProperty(Property.JOIN_CONDITION, newJoinCondition); node.addSelectors(Visitors.getSelectorsReferencedBy(newJoinCondition)); List<Constraint> joinConstraints = node.getPropertyAsList(Property.JOIN_CONSTRAINTS, Constraint.class); if (joinConstraints != null && !joinConstraints.isEmpty()) { List<Constraint> newConstraints = new ArrayList<Constraint>(joinConstraints.size()); for (Constraint joinConstraint : joinConstraints) { newConstraint = replaceReferences(context, joinConstraint, mappings, node); newConstraints.add(newConstraint); node.addSelectors(Visitors.getSelectorsReferencedBy(newConstraint)); } node.setProperty(Property.JOIN_CONSTRAINTS, newConstraints); } break; case ACCESS: // Add all the selectors used by the subnodes ... for (PlanNode child : node) { node.addSelectors(child.getSelectors()); } break; case SORT: // The selector names and Ordering objects needs to be changed ... List<Ordering> orderings = node.getPropertyAsList(Property.SORT_ORDER_BY, Ordering.class); List<Ordering> newOrderings = new ArrayList<Ordering>(orderings.size()); node.getSelectors().clear(); for (Ordering ordering : orderings) { DynamicOperand operand = ordering.getOperand(); DynamicOperand newOperand = replaceViewReferences(context, operand, mappings, node); if (newOperand != operand) { ordering = new Ordering(newOperand, ordering.order(), ordering.nullOrder()); } node.addSelectors(Visitors.getSelectorsReferencedBy(ordering)); newOrderings.add(ordering); } node.setProperty(Property.SORT_ORDER_BY, newOrderings); break; case GROUP: // Don't yet use GROUP BY case SET_OPERATION: case DEPENDENT_QUERY: case DUP_REMOVE: case LIMIT: case INDEX: case NULL: break; } } } public static Constraint replaceReferences( QueryContext context, Constraint constraint, ColumnMapping mapping, PlanNode node ) { if (constraint instanceof And) { And and = (And)constraint; Constraint left = replaceReferences(context, and.left(), mapping, node); Constraint right = replaceReferences(context, and.right(), mapping, node); if (left == and.left() && right == and.right()) return and; return new And(left, right); } if (constraint instanceof Or) { Or or = (Or)constraint; Constraint left = replaceReferences(context, or.left(), mapping, node); Constraint right = replaceReferences(context, or.right(), mapping, node); if (left == or.left() && right == or.right()) return or; return new Or(left, right); } if (constraint instanceof Not) { Not not = (Not)constraint; Constraint wrapped = replaceReferences(context, not.getConstraint(), mapping, node); return wrapped == not.getConstraint() ? not : new Not(wrapped); } if (constraint instanceof SameNode) { SameNode sameNode = (SameNode)constraint; if (!mapping.getOriginalName().equals(sameNode.selectorName())) return sameNode; if (!mapping.isMappedToSingleSelector()) return sameNode; SelectorName selector = mapping.getSingleMappedSelectorName(); node.addSelector(selector); return new SameNode(selector, sameNode.getPath()); } if (constraint instanceof ChildNode) { ChildNode childNode = (ChildNode)constraint; if (!mapping.getOriginalName().equals(childNode.selectorName())) return childNode; if (!mapping.isMappedToSingleSelector()) return childNode; SelectorName selector = mapping.getSingleMappedSelectorName(); node.addSelector(selector); return new ChildNode(selector, childNode.getParentPath()); } if (constraint instanceof DescendantNode) { DescendantNode descendantNode = (DescendantNode)constraint; if (!mapping.getOriginalName().equals(descendantNode.selectorName())) return descendantNode; if (!mapping.isMappedToSingleSelector()) return descendantNode; SelectorName selector = mapping.getSingleMappedSelectorName(); node.addSelector(selector); return new DescendantNode(selector, descendantNode.getAncestorPath()); } if (constraint instanceof PropertyExistence) { PropertyExistence existence = (PropertyExistence)constraint; if (!mapping.getOriginalName().equals(existence.selectorName())) return existence; Column sourceColumn = mapping.getMappedColumn(existence.getPropertyName()); if (sourceColumn == null) return existence; node.addSelector(sourceColumn.selectorName()); return new PropertyExistence(sourceColumn.selectorName(), sourceColumn.getPropertyName()); } if (constraint instanceof FullTextSearch) { FullTextSearch search = (FullTextSearch)constraint; if (!mapping.getOriginalName().equals(search.selectorName())) return search; Column sourceColumn = mapping.getMappedColumn(search.getPropertyName()); if (sourceColumn == null) { if (search.getPropertyName() == null && mapping.getMappedSelectorNames().size() == 1) { SelectorName newSelectorName = mapping.getSingleMappedSelectorName(); if (newSelectorName != null) { node.addSelector(newSelectorName); return new FullTextSearch(newSelectorName, search.getPropertyName(), search.fullTextSearchExpression(), search.getFullTextSearchExpression()); } } return search; } node.addSelector(sourceColumn.selectorName()); return new FullTextSearch(sourceColumn.selectorName(), sourceColumn.getPropertyName(), search.fullTextSearchExpression(), search.getFullTextSearchExpression()); } if (constraint instanceof SetCriteria) { SetCriteria set = (SetCriteria)constraint; DynamicOperand oldLeft = set.leftOperand(); Set<SelectorName> selectorNames = oldLeft.selectorNames(); if (selectorNames.size() == 1 && !selectorNames.contains(mapping.getOriginalName())) return set; DynamicOperand newLeft = replaceViewReferences(context, oldLeft, mapping, node); if (newLeft == oldLeft) return set; return new SetCriteria(newLeft, set.rightOperands()); } if (constraint instanceof Between) { Between between = (Between)constraint; DynamicOperand lhs = between.getOperand(); StaticOperand lower = between.getLowerBound(); // Current only a literal; therefore, no reference to selector StaticOperand upper = between.getUpperBound(); // Current only a literal; therefore, no reference to selector DynamicOperand newLhs = replaceViewReferences(context, lhs, mapping, node); if (lhs == newLhs) return between; return new Between(newLhs, lower, upper, between.isLowerBoundIncluded(), between.isUpperBoundIncluded()); } if (constraint instanceof Comparison) { Comparison comparison = (Comparison)constraint; DynamicOperand lhs = comparison.getOperand1(); StaticOperand rhs = comparison.getOperand2(); // Current only a literal; therefore, no reference to selector DynamicOperand newLhs = replaceViewReferences(context, lhs, mapping, node); if (lhs == newLhs) return comparison; return new Comparison(newLhs, comparison.operator(), rhs); } return constraint; } public static DynamicOperand replaceViewReferences( QueryContext context, DynamicOperand operand, ColumnMapping mapping, PlanNode node ) { if (operand instanceof ArithmeticOperand) { ArithmeticOperand arith = (ArithmeticOperand)operand; DynamicOperand newLeft = replaceViewReferences(context, arith.getLeft(), mapping, node); DynamicOperand newRight = replaceViewReferences(context, arith.getRight(), mapping, node); return new ArithmeticOperand(newLeft, arith.operator(), newRight); } if (operand instanceof FullTextSearchScore) { FullTextSearchScore score = (FullTextSearchScore)operand; if (!mapping.getOriginalName().equals(score.selectorName())) return score; if (mapping.isMappedToSingleSelector()) { return new FullTextSearchScore(mapping.getSingleMappedSelectorName()); } // There are multiple mappings, so we have to create a composite score ... DynamicOperand composite = null; for (SelectorName name : mapping.getMappedSelectorNames()) { FullTextSearchScore mappedScore = new FullTextSearchScore(name); if (composite == null) { composite = mappedScore; } else { composite = new ArithmeticOperand(composite, ArithmeticOperator.ADD, mappedScore); } } return composite; } if (operand instanceof Length) { Length operation = (Length)operand; return new Length((PropertyValue)replaceViewReferences(context, operation.getPropertyValue(), mapping, node)); } if (operand instanceof LowerCase) { LowerCase operation = (LowerCase)operand; return new LowerCase(replaceViewReferences(context, operation.getOperand(), mapping, node)); } if (operand instanceof UpperCase) { UpperCase operation = (UpperCase)operand; return new UpperCase(replaceViewReferences(context, operation.getOperand(), mapping, node)); } if (operand instanceof Cast) { Cast operation = (Cast)operand; return new Cast(replaceViewReferences(context, operation.getOperand(), mapping, node), operation.getDesiredTypeName()); } if (operand instanceof NodeName) { NodeName name = (NodeName)operand; if (!mapping.getOriginalName().equals(name.selectorName())) return name; if (!mapping.isMappedToSingleSelector()) return name; node.addSelector(mapping.getSingleMappedSelectorName()); return new NodeName(mapping.getSingleMappedSelectorName()); } if (operand instanceof NodeLocalName) { NodeLocalName name = (NodeLocalName)operand; if (!mapping.getOriginalName().equals(name.selectorName())) return name; if (!mapping.isMappedToSingleSelector()) return name; node.addSelector(mapping.getSingleMappedSelectorName()); return new NodeLocalName(mapping.getSingleMappedSelectorName()); } if (operand instanceof PropertyValue) { PropertyValue value = (PropertyValue)operand; if (!mapping.getOriginalName().equals(value.selectorName())) return value; Column sourceColumn = mapping.getMappedColumn(value.getPropertyName()); if (sourceColumn == null) { if (mapping.getMappedSelectorNames().size() == 1) { SelectorName newSelectorName = mapping.getSingleMappedSelectorName(); if (newSelectorName != null) { node.addSelector(newSelectorName); return new PropertyValue(newSelectorName, value.getPropertyName()); } } return value; } node.addSelector(sourceColumn.selectorName()); return new PropertyValue(sourceColumn.selectorName(), sourceColumn.getPropertyName()); } if (operand instanceof ReferenceValue) { ReferenceValue value = (ReferenceValue)operand; if (!mapping.getOriginalName().equals(value.selectorName())) return value; Column sourceColumn = mapping.getMappedColumn(value.getPropertyName()); if (sourceColumn == null) return value; node.addSelector(sourceColumn.selectorName()); return new ReferenceValue(sourceColumn.selectorName(), sourceColumn.getPropertyName()); } if (operand instanceof NodeDepth) { NodeDepth depth = (NodeDepth)operand; if (!mapping.getOriginalName().equals(depth.selectorName())) return depth; if (!mapping.isMappedToSingleSelector()) return depth; node.addSelector(mapping.getSingleMappedSelectorName()); return new NodeDepth(mapping.getSingleMappedSelectorName()); } if (operand instanceof NodePath) { NodePath path = (NodePath)operand; if (!mapping.getOriginalName().equals(path.selectorName())) return path; if (!mapping.isMappedToSingleSelector()) return path; node.addSelector(mapping.getSingleMappedSelectorName()); return new NodePath(mapping.getSingleMappedSelectorName()); } if (operand instanceof ChildCount) { ChildCount count = (ChildCount)operand; if (!mapping.getOriginalName().equals(count.selectorName())) return count; if (!mapping.isMappedToSingleSelector()) return count; node.addSelector(mapping.getSingleMappedSelectorName()); return new ChildCount(mapping.getSingleMappedSelectorName()); } return operand; } public static JoinCondition replaceViewReferences( QueryContext context, JoinCondition joinCondition, ColumnMapping mapping, PlanNode node ) { if (joinCondition instanceof EquiJoinCondition) { EquiJoinCondition condition = (EquiJoinCondition)joinCondition; SelectorName replacement1 = condition.selector1Name(); SelectorName replacement2 = condition.selector2Name(); String property1 = condition.getProperty1Name(); String property2 = condition.getProperty2Name(); if (replacement1.equals(mapping.getOriginalName())) { Column sourceColumn = mapping.getMappedColumn(property1); if (sourceColumn != null) { replacement1 = sourceColumn.selectorName(); property1 = sourceColumn.getPropertyName(); } } if (replacement2.equals(mapping.getOriginalName())) { Column sourceColumn = mapping.getMappedColumn(property2); if (sourceColumn != null) { replacement2 = sourceColumn.selectorName(); property2 = sourceColumn.getPropertyName(); } } if (replacement1 == condition.selector1Name() && replacement2 == condition.selector2Name()) return condition; node.addSelector(replacement1, replacement2); return new EquiJoinCondition(replacement1, property1, replacement2, property2); } // All the remaining conditions can only be rewritten if there is a single source ... if (!mapping.isMappedToSingleSelector()) return joinCondition; // There is only a single source ... SelectorName viewName = mapping.getOriginalName(); SelectorName sourceName = mapping.getSingleMappedSelectorName(); if (joinCondition instanceof SameNodeJoinCondition) { SameNodeJoinCondition condition = (SameNodeJoinCondition)joinCondition; SelectorName replacement1 = condition.selector1Name(); SelectorName replacement2 = condition.selector2Name(); if (replacement1.equals(viewName)) replacement1 = sourceName; if (replacement2.equals(viewName)) replacement2 = sourceName; if (replacement1 == condition.selector1Name() && replacement2 == condition.selector2Name()) return condition; node.addSelector(replacement1, replacement2); if (condition.getSelector2Path() == null) return new SameNodeJoinCondition(replacement1, replacement2); return new SameNodeJoinCondition(replacement1, replacement2, condition.getSelector2Path()); } if (joinCondition instanceof ChildNodeJoinCondition) { ChildNodeJoinCondition condition = (ChildNodeJoinCondition)joinCondition; SelectorName childSelector = condition.childSelectorName(); SelectorName parentSelector = condition.parentSelectorName(); if (childSelector.equals(viewName)) childSelector = sourceName; if (parentSelector.equals(viewName)) parentSelector = sourceName; if (childSelector == condition.childSelectorName() && parentSelector == condition.parentSelectorName()) return condition; node.addSelector(childSelector, parentSelector); return new ChildNodeJoinCondition(parentSelector, childSelector); } if (joinCondition instanceof DescendantNodeJoinCondition) { DescendantNodeJoinCondition condition = (DescendantNodeJoinCondition)joinCondition; SelectorName ancestor = condition.ancestorSelectorName(); SelectorName descendant = condition.descendantSelectorName(); if (ancestor.equals(viewName)) ancestor = sourceName; if (descendant.equals(viewName)) descendant = sourceName; if (ancestor == condition.ancestorSelectorName() && descendant == condition.descendantSelectorName()) return condition; node.addSelector(ancestor, descendant); return new DescendantNodeJoinCondition(ancestor, descendant); } return joinCondition; } public static ColumnMapping createMappingFor( View view, PlanNode viewPlan ) { ColumnMapping mapping = new ColumnMapping(view.getName()); // Find the PROJECT node in the view plan ... PlanNode project = viewPlan.findAtOrBelow(Type.PROJECT); assert project != null; // Get the Columns from the PROJECT in the plan node ... List<Column> projectedColumns = project.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class); // Get the Schemata columns defined by the view ... List<org.modeshape.jcr.query.validate.Schemata.Column> viewColumns = view.getColumns(); for (int i = 0; i != viewColumns.size(); ++i) { Column projectedColunn = projectedColumns.get(i); String viewColumnName = viewColumns.get(i).getName(); mapping.map(viewColumnName, projectedColunn); } if (viewColumns.size() < projectedColumns.size()) { // There are some extra projected columns, likely because the view did not know about them ... for (int i = viewColumns.size(); i != projectedColumns.size(); ++i) { Column projectedColunn = projectedColumns.get(i); String viewColumnName = projectedColunn.getPropertyName(); mapping.map(viewColumnName, projectedColunn); } } return mapping; } public static ColumnMapping createMappingForAliased( SelectorName viewAlias, View view, PlanNode viewPlan ) { ColumnMapping mapping = new ColumnMapping(viewAlias); // Find the PROJECT node in the view plan ... PlanNode project = viewPlan.findAtOrBelow(Type.PROJECT); assert project != null; // Get the Columns from the PROJECT in the plan node ... List<Column> projectedColumns = project.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class); // Get the Schemata columns defined by the view ... List<org.modeshape.jcr.query.validate.Schemata.Column> viewColumns = view.getColumns(); for (int i = 0; i != viewColumns.size(); ++i) { Column projectedColunn = projectedColumns.get(i); String viewColumnName = viewColumns.get(i).getName(); mapping.map(viewColumnName, projectedColunn); } if (viewColumns.size() < projectedColumns.size()) { // There are some extra projected columns, likely because the view did not know about them ... for (int i = viewColumns.size(); i != projectedColumns.size(); ++i) { Column projectedColunn = projectedColumns.get(i); String viewColumnName = projectedColunn.getPropertyName(); mapping.map(viewColumnName, projectedColunn); } } return mapping; } /** * Defines how the view columns are mapped (or resolved) into the columns from the source tables. */ public static class ColumnMapping { private final SelectorName originalName; private final Map<String, Column> mappedColumnsByOriginalColumnName = new HashMap<String, Column>(); private final Set<SelectorName> mappedSelectorNames = new LinkedHashSet<SelectorName>(); // maintains insertion order public ColumnMapping( SelectorName originalName ) { this.originalName = originalName; } public void map( String originalColumnName, Column projectedColumn ) { mappedColumnsByOriginalColumnName.put(originalColumnName, projectedColumn); mappedSelectorNames.add(projectedColumn.selectorName()); } public SelectorName getOriginalName() { return originalName; } public Column getMappedColumn( String viewColumnName ) { return mappedColumnsByOriginalColumnName.get(viewColumnName); } public boolean isMappedToSingleSelector() { return mappedSelectorNames.size() == 1; } /** * @return tableNames */ public Set<SelectorName> getMappedSelectorNames() { return mappedSelectorNames; } public SelectorName getSingleMappedSelectorName() { return isMappedToSingleSelector() ? mappedSelectorNames.iterator().next() : null; } } public static Constraint replaceAliasesWithProperties( QueryContext context, Constraint constraint, Map<String, String> propertyByAlias ) { if (propertyByAlias.isEmpty()) return constraint; if (constraint instanceof And) { And and = (And)constraint; Constraint left = replaceAliasesWithProperties(context, and.left(), propertyByAlias); Constraint right = replaceAliasesWithProperties(context, and.right(), propertyByAlias); if (left == and.left() && right == and.right()) return and; return new And(left, right); } if (constraint instanceof Or) { Or or = (Or)constraint; Constraint left = replaceAliasesWithProperties(context, or.left(), propertyByAlias); Constraint right = replaceAliasesWithProperties(context, or.right(), propertyByAlias); if (left == or.left() && right == or.right()) return or; return new Or(left, right); } if (constraint instanceof Not) { Not not = (Not)constraint; Constraint wrapped = replaceAliasesWithProperties(context, not.getConstraint(), propertyByAlias); if (wrapped == not.getConstraint()) return not; return new Not(wrapped); } if (constraint instanceof SameNode) { return constraint; } if (constraint instanceof ChildNode) { return constraint; } if (constraint instanceof DescendantNode) { return constraint; } if (constraint instanceof PropertyExistence) { return constraint; } if (constraint instanceof FullTextSearch) { return constraint; } if (constraint instanceof Between) { Between between = (Between)constraint; DynamicOperand lhs = replaceAliasesWithProperties(context, between.getOperand(), propertyByAlias); StaticOperand lower = between.getLowerBound(); // Current only a literal; therefore, no alias StaticOperand upper = between.getUpperBound(); // Current only a literal; therefore, no alias if (lower == between.getOperand()) return between; return new Between(lhs, lower, upper, between.isLowerBoundIncluded(), between.isUpperBoundIncluded()); } if (constraint instanceof Comparison) { Comparison comparison = (Comparison)constraint; DynamicOperand lhs = replaceAliasesWithProperties(context, comparison.getOperand1(), propertyByAlias); if (lhs == comparison.getOperand1()) return comparison; return new Comparison(lhs, comparison.operator(), comparison.getOperand2()); } if (constraint instanceof Relike) { Relike relike = (Relike)constraint; StaticOperand op1 = relike.getOperand1(); PropertyValue op2 = relike.getOperand2(); PropertyValue newOp2 = replaceAliasesWithProperties(context, op2, propertyByAlias); if (op2 == newOp2) return relike; return new Relike(op1, op2); } if (constraint instanceof SetCriteria) { SetCriteria criteria = (SetCriteria)constraint; DynamicOperand lhs = replaceAliasesWithProperties(context, criteria.leftOperand(), propertyByAlias); if (lhs == criteria.leftOperand()) return criteria; return new SetCriteria(lhs, criteria.rightOperands()); } return constraint; } protected static PropertyValue replaceAliasesWithProperties( QueryContext context, PropertyValue value, Map<String, String> propertyByAlias ) { String original = value.getPropertyName(); String propName = propertyByAlias.get(original); return propName != null ? new PropertyValue(value.selectorName(), propName) : value; } public static DynamicOperand replaceAliasesWithProperties( QueryContext context, DynamicOperand operand, Map<String, String> propertyByAlias ) { if (operand instanceof ArithmeticOperand) { ArithmeticOperand arith = (ArithmeticOperand)operand; DynamicOperand newLeft = replaceAliasesWithProperties(context, arith.getLeft(), propertyByAlias); DynamicOperand newRight = replaceAliasesWithProperties(context, arith.getRight(), propertyByAlias); if (newLeft == arith.getLeft() && newRight == arith.getRight()) return arith; return new ArithmeticOperand(newLeft, arith.operator(), newRight); } if (operand instanceof FullTextSearchScore) { return operand; } if (operand instanceof Length) { Length operation = (Length)operand; PropertyValue value = operation.getPropertyValue(); PropertyValue newValue = replaceAliasesWithProperties(context, value, propertyByAlias); if (newValue == value) return operation; return new Length(newValue); } if (operand instanceof LowerCase) { LowerCase operation = (LowerCase)operand; DynamicOperand oldOperand = operation.getOperand(); DynamicOperand newOperand = replaceAliasesWithProperties(context, oldOperand, propertyByAlias); if (oldOperand == newOperand) return operation; return new LowerCase(newOperand); } if (operand instanceof UpperCase) { UpperCase operation = (UpperCase)operand; DynamicOperand oldOperand = operation.getOperand(); DynamicOperand newOperand = replaceAliasesWithProperties(context, oldOperand, propertyByAlias); if (oldOperand == newOperand) return operation; return new UpperCase(newOperand); } if (operand instanceof Cast) { Cast operation = (Cast)operand; DynamicOperand oldOperand = operation.getOperand(); DynamicOperand newOperand = replaceAliasesWithProperties(context, oldOperand, propertyByAlias); if (oldOperand == newOperand) return operation; return new Cast(newOperand, operation.getDesiredTypeName()); } if (operand instanceof NodeName) { return operand; } if (operand instanceof NodeLocalName) { return operand; } if (operand instanceof PropertyValue) { PropertyValue value = (PropertyValue)operand; return replaceAliasesWithProperties(context, value, propertyByAlias); } if (operand instanceof ReferenceValue) { ReferenceValue value = (ReferenceValue)operand; String original = value.getPropertyName(); String propName = propertyByAlias.get(original); if (propName == null) return value; return new ReferenceValue(value.selectorName(), propName); } if (operand instanceof NodeDepth) { return operand; } if (operand instanceof NodePath) { return operand; } if (operand instanceof ChildCount) { return operand; } return operand; } public static Constraint replaceSubqueriesWithBindVariables( QueryContext context, Constraint constraint, Map<String, Subquery> subqueriesByVariableName ) { if (constraint instanceof And) { And and = (And)constraint; Constraint left = replaceSubqueriesWithBindVariables(context, and.left(), subqueriesByVariableName); Constraint right = replaceSubqueriesWithBindVariables(context, and.right(), subqueriesByVariableName); if (left == and.left() && right == and.right()) return and; return new And(left, right); } if (constraint instanceof Or) { Or or = (Or)constraint; Constraint left = replaceSubqueriesWithBindVariables(context, or.left(), subqueriesByVariableName); Constraint right = replaceSubqueriesWithBindVariables(context, or.right(), subqueriesByVariableName); if (left == or.left() && right == or.right()) return or; return new Or(left, right); } if (constraint instanceof Not) { Not not = (Not)constraint; Constraint wrapped = replaceSubqueriesWithBindVariables(context, not.getConstraint(), subqueriesByVariableName); if (wrapped == not.getConstraint()) return not; return new Not(wrapped); } if (constraint instanceof SameNode) { return constraint; } if (constraint instanceof ChildNode) { return constraint; } if (constraint instanceof DescendantNode) { return constraint; } if (constraint instanceof PropertyExistence) { return constraint; } if (constraint instanceof FullTextSearch) { return constraint; } if (constraint instanceof Between) { Between between = (Between)constraint; DynamicOperand lhs = between.getOperand(); StaticOperand lower = between.getLowerBound(); // Current only a literal; therefore, no reference to selector StaticOperand upper = between.getUpperBound(); // Current only a literal; therefore, no reference to selector StaticOperand newLower = replaceSubqueriesWithBindVariables(context, lower, subqueriesByVariableName); StaticOperand newUpper = replaceSubqueriesWithBindVariables(context, upper, subqueriesByVariableName); if (lower == newLower && upper == newUpper) return between; return new Between(lhs, newLower, newUpper, between.isLowerBoundIncluded(), between.isUpperBoundIncluded()); } if (constraint instanceof Comparison) { Comparison comparison = (Comparison)constraint; DynamicOperand lhs = comparison.getOperand1(); StaticOperand rhs = comparison.getOperand2(); // Current only a literal; therefore, no reference to selector StaticOperand newRhs = replaceSubqueriesWithBindVariables(context, rhs, subqueriesByVariableName); if (rhs == newRhs) return comparison; return new Comparison(lhs, comparison.operator(), newRhs); } if (constraint instanceof Relike) { Relike relike = (Relike)constraint; StaticOperand op1 = relike.getOperand1(); PropertyValue op2 = relike.getOperand2(); StaticOperand newOp1 = replaceSubqueriesWithBindVariables(context, op1, subqueriesByVariableName); if (op1 == newOp1) return relike; return new Relike(op1, op2); } if (constraint instanceof SetCriteria) { SetCriteria criteria = (SetCriteria)constraint; DynamicOperand lhs = criteria.leftOperand(); boolean foundSubquery = false; List<StaticOperand> newStaticOperands = new LinkedList<StaticOperand>(); for (StaticOperand rhs : criteria.rightOperands()) { StaticOperand newRhs = replaceSubqueriesWithBindVariables(context, rhs, subqueriesByVariableName); newStaticOperands.add(newRhs); if (rhs != newRhs) { foundSubquery = true; } } if (!foundSubquery) return criteria; return new SetCriteria(lhs, newStaticOperands); } return constraint; } public static StaticOperand replaceSubqueriesWithBindVariables( QueryContext context, StaticOperand staticOperand, Map<String, Subquery> subqueriesByVariableName ) { if (staticOperand instanceof Subquery) { Subquery subquery = (Subquery)staticOperand; // Create a variable name ... int i = 1; String variableName = Subquery.VARIABLE_PREFIX; while (context.getVariables().containsKey(variableName + i)) { ++i; } variableName = variableName + i; subqueriesByVariableName.put(variableName, subquery); context.getVariables().put(variableName, null); // Replace with a variable substitution ... return new BindVariableName(variableName); } return staticOperand; } public static PlanNode addMissingProjectColumns( QueryContext context, PlanNode node, List<Column> allProjectedColumns ) { PlanNode project = node.findAtOrBelow(Type.PROJECT); if (project == null) return node; PlanNode source = node.findAtOrBelow(Type.SOURCE); List<Schemata.Column> sourceColumns = source.getPropertyAsList(Property.SOURCE_COLUMNS, Schemata.Column.class); List<Column> projected = project.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class); List<String> projectedTypes = project.getPropertyAsList(Property.PROJECT_COLUMN_TYPES, String.class); Set<String> projectedPropertyNames = new HashSet<String>(); for (Column p : projected) { projectedPropertyNames.add(p.getPropertyName()); } String defaultTypeName = context.getTypeSystem().getDefaultType(); List<Schemata.Column> newSourceColumns = new ArrayList<Schemata.Column>(sourceColumns); boolean added = false; for (Column expected : allProjectedColumns) { if (!project.getSelectors().contains(expected.selectorName())) { // The column does not belong to this project node ... continue; } if (projectedPropertyNames.contains(expected.getPropertyName())) continue; // Add the column ... projected.add(expected); projectedTypes.add(defaultTypeName); // Now add a Schemata column for the missing column ... Schemata.Column sourceColumn = new AbsentColumn(expected.getPropertyName(), defaultTypeName); newSourceColumns.add(sourceColumn); added = true; } if (added) { source.setProperty(Property.SOURCE_COLUMNS, newSourceColumns); } return node; } public static void removeDuplicateSelectNodesUnderEachAccessNode( QueryContext context, PlanNode node ) { // For each of the ACCESS queries ... for (PlanNode access : node.findAllAtOrBelow(Type.ACCESS)) { Set<Constraint> existingCriteria = new HashSet<>(); for (PlanNode select : access.findAllAtOrBelow(Type.SELECT)) { Constraint constraint = select.getProperty(Property.SELECT_CRITERIA, Constraint.class); if (!existingCriteria.add(constraint)) { // It was already found, so remove this node ... select.extractFromParent(); } } } } protected static class AbsentColumn extends ImmutableColumn { protected AbsentColumn( String name, String type ) { super(name, type); } } }