Package org.jboss.dna.graph.query.plan

Source Code of org.jboss.dna.graph.query.plan.PlanUtil$RequiredColumnVisitor

/*
* JBoss DNA (http://www.jboss.org/dna)
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.  Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
* See the AUTHORS.txt file in the distribution for a full listing of
* individual contributors.
*
* JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
* is licensed to you under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* JBoss DNA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.dna.graph.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.jboss.dna.graph.query.QueryContext;
import org.jboss.dna.graph.query.model.And;
import org.jboss.dna.graph.query.model.ArithmeticOperand;
import org.jboss.dna.graph.query.model.ArithmeticOperator;
import org.jboss.dna.graph.query.model.Between;
import org.jboss.dna.graph.query.model.ChildNode;
import org.jboss.dna.graph.query.model.ChildNodeJoinCondition;
import org.jboss.dna.graph.query.model.Column;
import org.jboss.dna.graph.query.model.Comparison;
import org.jboss.dna.graph.query.model.Constraint;
import org.jboss.dna.graph.query.model.DescendantNode;
import org.jboss.dna.graph.query.model.DescendantNodeJoinCondition;
import org.jboss.dna.graph.query.model.DynamicOperand;
import org.jboss.dna.graph.query.model.EquiJoinCondition;
import org.jboss.dna.graph.query.model.FullTextSearch;
import org.jboss.dna.graph.query.model.FullTextSearchScore;
import org.jboss.dna.graph.query.model.JoinCondition;
import org.jboss.dna.graph.query.model.Length;
import org.jboss.dna.graph.query.model.LowerCase;
import org.jboss.dna.graph.query.model.NodeDepth;
import org.jboss.dna.graph.query.model.NodeLocalName;
import org.jboss.dna.graph.query.model.NodeName;
import org.jboss.dna.graph.query.model.NodePath;
import org.jboss.dna.graph.query.model.Not;
import org.jboss.dna.graph.query.model.Or;
import org.jboss.dna.graph.query.model.Ordering;
import org.jboss.dna.graph.query.model.PropertyExistence;
import org.jboss.dna.graph.query.model.PropertyValue;
import org.jboss.dna.graph.query.model.SameNode;
import org.jboss.dna.graph.query.model.SameNodeJoinCondition;
import org.jboss.dna.graph.query.model.SelectorName;
import org.jboss.dna.graph.query.model.StaticOperand;
import org.jboss.dna.graph.query.model.UpperCase;
import org.jboss.dna.graph.query.model.Visitors;
import org.jboss.dna.graph.query.model.Visitors.AbstractVisitor;
import org.jboss.dna.graph.query.plan.PlanNode.Property;
import org.jboss.dna.graph.query.plan.PlanNode.Type;
import org.jboss.dna.graph.query.validate.Schemata.Table;
import org.jboss.dna.graph.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:
                    Constraint criteria = node.getProperty(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:
                    criteria = node.getProperty(Property.SELECT_CRITERIA, Constraint.class);
                    Visitors.visitAll(criteria, 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();
    }

    protected 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>();

        protected RequiredColumnVisitor( Set<SelectorName> names ) {
            this.names = names;
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.graph.query.model.Visitors.AbstractVisitor#visit(org.jboss.dna.graph.query.model.PropertyExistence)
         */
        @Override
        public void visit( PropertyExistence existence ) {
            requireColumn(existence.getSelectorName(), existence.getPropertyName());
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.graph.query.model.Visitors.AbstractVisitor#visit(org.jboss.dna.graph.query.model.PropertyValue)
         */
        @Override
        public void visit( PropertyValue value ) {
            requireColumn(value.getSelectorName(), value.getPropertyName());
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.graph.query.model.Visitors.AbstractVisitor#visit(org.jboss.dna.graph.query.model.EquiJoinCondition)
         */
        @Override
        public void visit( EquiJoinCondition condition ) {
            requireColumn(condition.getSelector1Name(), condition.getProperty1Name());
            requireColumn(condition.getSelector2Name(), condition.getProperty2Name());
        }

        /**
         * {@inheritDoc}
         *
         * @see org.jboss.dna.graph.query.model.Visitors.AbstractVisitor#visit(org.jboss.dna.graph.query.model.Column)
         */
        @Override
        public void visit( Column column ) {
            requireColumn(column.getSelectorName(), 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.getSelectorName());
                    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 GROUP:
            case SET_OPERATION:
            case DUP_REMOVE:
            case LIMIT:
            case NULL:
            case SOURCE:
            case ACCESS:
                // 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.getSelectorName());
            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.getSelectorName());
            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.getSelectorName());
            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.getSelectorName());
            if (replacement == null) return operand;
            return new UpperCase(replaceReferencesToRemovedSource(context, operation.getOperand(), rewrittenSelectors));
        }
        if (operand instanceof NodeName) {
            NodeName name = (NodeName)operand;
            SelectorName replacement = rewrittenSelectors.get(name.getSelectorName());
            if (replacement == null) return name;
            return new NodeName(replacement);
        }
        if (operand instanceof NodeLocalName) {
            NodeLocalName name = (NodeLocalName)operand;
            SelectorName replacement = rewrittenSelectors.get(name.getSelectorName());
            if (replacement == null) return name;
            return new NodeLocalName(replacement);
        }
        if (operand instanceof PropertyValue) {
            PropertyValue value = (PropertyValue)operand;
            SelectorName replacement = rewrittenSelectors.get(value.getSelectorName());
            if (replacement == null) return operand;
            return new PropertyValue(replacement, value.getPropertyName());
        }
        if (operand instanceof NodeDepth) {
            NodeDepth depth = (NodeDepth)operand;
            SelectorName replacement = rewrittenSelectors.get(depth.getSelectorName());
            if (replacement == null) return operand;
            return new NodeDepth(replacement);
        }
        if (operand instanceof NodePath) {
            NodePath path = (NodePath)operand;
            SelectorName replacement = rewrittenSelectors.get(path.getSelectorName());
            if (replacement == null) return operand;
            return new NodePath(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.getLeft(), rewrittenSelectors);
            Constraint right = replaceReferencesToRemovedSource(context, and.getRight(), rewrittenSelectors);
            if (left == and.getLeft() && right == and.getRight()) return and;
            return new And(left, right);
        }
        if (constraint instanceof Or) {
            Or or = (Or)constraint;
            Constraint left = replaceReferencesToRemovedSource(context, or.getLeft(), rewrittenSelectors);
            Constraint right = replaceReferencesToRemovedSource(context, or.getRight(), rewrittenSelectors);
            if (left == or.getLeft() && right == or.getRight()) 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.getSelectorName());
            if (replacement == null) return sameNode;
            return new SameNode(replacement, sameNode.getPath());
        }
        if (constraint instanceof ChildNode) {
            ChildNode childNode = (ChildNode)constraint;
            SelectorName replacement = rewrittenSelectors.get(childNode.getSelectorName());
            if (replacement == null) return childNode;
            return new ChildNode(replacement, childNode.getParentPath());
        }
        if (constraint instanceof DescendantNode) {
            DescendantNode descendantNode = (DescendantNode)constraint;
            SelectorName replacement = rewrittenSelectors.get(descendantNode.getSelectorName());
            if (replacement == null) return descendantNode;
            return new DescendantNode(replacement, descendantNode.getAncestorPath());
        }
        if (constraint instanceof PropertyExistence) {
            PropertyExistence existence = (PropertyExistence)constraint;
            SelectorName replacement = rewrittenSelectors.get(existence.getSelectorName());
            if (replacement == null) return existence;
            return new PropertyExistence(replacement, existence.getPropertyName());
        }
        if (constraint instanceof FullTextSearch) {
            FullTextSearch search = (FullTextSearch)constraint;
            SelectorName replacement = rewrittenSelectors.get(search.getSelectorName());
            if (replacement == null) return search;
            return new FullTextSearch(replacement, search.getPropertyName(), search.getFullTextSearchExpression());
        }
        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.getOperator(), rhs);
        }
        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.getSelector1Name());
            SelectorName replacement2 = rewrittenSelectors.get(condition.getSelector2Name());
            if (replacement1 == condition.getSelector1Name() && replacement2 == condition.getSelector2Name()) return condition;
            return new EquiJoinCondition(replacement1, condition.getProperty1Name(), replacement2, condition.getProperty2Name());
        }
        if (joinCondition instanceof SameNodeJoinCondition) {
            SameNodeJoinCondition condition = (SameNodeJoinCondition)joinCondition;
            SelectorName replacement1 = rewrittenSelectors.get(condition.getSelector1Name());
            SelectorName replacement2 = rewrittenSelectors.get(condition.getSelector2Name());
            if (replacement1 == condition.getSelector1Name() && replacement2 == condition.getSelector2Name()) return condition;
            return new SameNodeJoinCondition(replacement1, replacement2, condition.getSelector2Path());
        }
        if (joinCondition instanceof ChildNodeJoinCondition) {
            ChildNodeJoinCondition condition = (ChildNodeJoinCondition)joinCondition;
            SelectorName childSelector = rewrittenSelectors.get(condition.getChildSelectorName());
            SelectorName parentSelector = rewrittenSelectors.get(condition.getParentSelectorName());
            if (childSelector == condition.getChildSelectorName() && parentSelector == condition.getParentSelectorName()) return condition;
            return new ChildNodeJoinCondition(parentSelector, childSelector);
        }
        if (joinCondition instanceof DescendantNodeJoinCondition) {
            DescendantNodeJoinCondition condition = (DescendantNodeJoinCondition)joinCondition;
            SelectorName ancestor = rewrittenSelectors.get(condition.getAncestorSelectorName());
            SelectorName descendant = rewrittenSelectors.get(condition.getDescendantSelectorName());
            if (ancestor == condition.getAncestorSelectorName() && descendant == condition.getDescendantSelectorName()) 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 {
            // 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.getSelectorName().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.getSelectorName();
                                        // 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 {
                                        node.addSelector(column.getSelectorName());
                                    }
                                } else {
                                    node.addSelector(column.getSelectorName());
                                }
                            }
                        }
                        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);
                        }
                        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.getOrder());
                            }
                            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 DUP_REMOVE:
                    case LIMIT:
                    case NULL:
                        break;
                }
            }
            // Move to the parent ...
            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();
            }
        }
    }

    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.getLeft(), mapping, node);
            Constraint right = replaceReferences(context, and.getRight(), mapping, node);
            if (left == and.getLeft() && right == and.getRight()) return and;
            return new And(left, right);
        }
        if (constraint instanceof Or) {
            Or or = (Or)constraint;
            Constraint left = replaceReferences(context, or.getLeft(), mapping, node);
            Constraint right = replaceReferences(context, or.getRight(), mapping, node);
            if (left == or.getLeft() && right == or.getRight()) 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.getSelectorName())) 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.getSelectorName())) 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.getSelectorName())) 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.getSelectorName())) return existence;
            Column sourceColumn = mapping.getMappedColumn(existence.getPropertyName());
            if (sourceColumn == null) return existence;
            node.addSelector(sourceColumn.getSelectorName());
            return new PropertyExistence(sourceColumn.getSelectorName(), sourceColumn.getPropertyName());
        }
        if (constraint instanceof FullTextSearch) {
            FullTextSearch search = (FullTextSearch)constraint;
            if (!mapping.getOriginalName().equals(search.getSelectorName())) return search;
            Column sourceColumn = mapping.getMappedColumn(search.getPropertyName());
            if (sourceColumn == null) return search;
            node.addSelector(sourceColumn.getSelectorName());
            return new FullTextSearch(sourceColumn.getSelectorName(), sourceColumn.getPropertyName(),
                                      search.getFullTextSearchExpression());
        }
        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.getOperator(), 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.getOperator(), newRight);
        }
        if (operand instanceof FullTextSearchScore) {
            FullTextSearchScore score = (FullTextSearchScore)operand;
            if (!mapping.getOriginalName().equals(score.getSelectorName())) 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 NodeName) {
            NodeName name = (NodeName)operand;
            if (!mapping.getOriginalName().equals(name.getSelectorName())) 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.getSelectorName())) 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.getSelectorName())) return value;
            Column sourceColumn = mapping.getMappedColumn(value.getPropertyName());
            if (sourceColumn == null) return value;
            node.addSelector(sourceColumn.getSelectorName());
            return new PropertyValue(sourceColumn.getSelectorName(), sourceColumn.getPropertyName());
        }
        if (operand instanceof NodeDepth) {
            NodeDepth depth = (NodeDepth)operand;
            if (!mapping.getOriginalName().equals(depth.getSelectorName())) 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.getSelectorName())) return path;
            if (!mapping.isMappedToSingleSelector()) return path;
            node.addSelector(mapping.getSingleMappedSelectorName());
            return new NodePath(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.getSelector1Name();
            SelectorName replacement2 = condition.getSelector2Name();
            String property1 = condition.getProperty1Name();
            String property2 = condition.getProperty2Name();
            if (replacement1.equals(mapping.getOriginalName())) {
                Column sourceColumn = mapping.getMappedColumn(property1);
                if (sourceColumn != null) {
                    replacement1 = sourceColumn.getSelectorName();
                    property1 = sourceColumn.getPropertyName();
                }
            }
            if (replacement2.equals(mapping.getOriginalName())) {
                Column sourceColumn = mapping.getMappedColumn(property2);
                if (sourceColumn != null) {
                    replacement2 = sourceColumn.getSelectorName();
                    property2 = sourceColumn.getPropertyName();
                }
            }
            if (replacement1 == condition.getSelector1Name() && replacement2 == condition.getSelector2Name()) 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.getSelector1Name();
            SelectorName replacement2 = condition.getSelector2Name();
            if (replacement1.equals(viewName)) replacement1 = sourceName;
            if (replacement2.equals(viewName)) replacement2 = sourceName;
            if (replacement1 == condition.getSelector1Name() && replacement2 == condition.getSelector2Name()) 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.getChildSelectorName();
            SelectorName parentSelector = condition.getParentSelectorName();
            if (childSelector.equals(viewName)) childSelector = sourceName;
            if (parentSelector.equals(viewName)) parentSelector = sourceName;
            if (childSelector == condition.getChildSelectorName() && parentSelector == condition.getParentSelectorName()) return condition;
            node.addSelector(childSelector, parentSelector);
            return new ChildNodeJoinCondition(parentSelector, childSelector);
        }
        if (joinCondition instanceof DescendantNodeJoinCondition) {
            DescendantNodeJoinCondition condition = (DescendantNodeJoinCondition)joinCondition;
            SelectorName ancestor = condition.getAncestorSelectorName();
            SelectorName descendant = condition.getDescendantSelectorName();
            if (ancestor.equals(viewName)) ancestor = sourceName;
            if (descendant.equals(viewName)) descendant = sourceName;
            if (ancestor == condition.getAncestorSelectorName() && descendant == condition.getDescendantSelectorName()) return condition;
            node.addSelector(ancestor, descendant);
            return new ChildNodeJoinCondition(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.jboss.dna.graph.query.validate.Schemata.Column> viewColumns = view.getColumns();
        assert viewColumns.size() == projectedColumns.size();

        for (int i = 0; i != viewColumns.size(); ++i) {
            Column projectedColunn = projectedColumns.get(i);
            String viewColumnName = viewColumns.get(i).getName();
            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.jboss.dna.graph.query.validate.Schemata.Column> viewColumns = view.getColumns();
        assert viewColumns.size() == projectedColumns.size();

        for (int i = 0; i != viewColumns.size(); ++i) {
            Column projectedColunn = projectedColumns.get(i);
            String viewColumnName = viewColumns.get(i).getName();
            mapping.map(viewColumnName, projectedColunn);
        }
        return mapping;
    }

    public static ColumnMapping createMappingForAliased( SelectorName tableAlias,
                                                         Table table,
                                                         PlanNode tableSourceNode ) {
        ColumnMapping mapping = new ColumnMapping(tableAlias);

        // Find the projected columns on the nearest PROJECT ...
        PlanNode project = tableSourceNode.findAncestor(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);

        for (int i = 0; i != projectedColumns.size(); ++i) {
            Column projectedColumn = projectedColumns.get(i);
            Column projectedColumnInTable = projectedColumns.get(i).with(table.getName());
            org.jboss.dna.graph.query.validate.Schemata.Column column = table.getColumn(projectedColumnInTable.getPropertyName());
            mapping.map(column.getName(), projectedColumnInTable);
            if (projectedColumn.getColumnName() != null) {
                // The projected column has an alias, so add a mapping for it, too
                mapping.map(projectedColumn.getColumnName(), projectedColumnInTable);
            }
        }
        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.getSelectorName());
        }

        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;
        }
    }

}
TOP

Related Classes of org.jboss.dna.graph.query.plan.PlanUtil$RequiredColumnVisitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.