All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.modeshape.jcr.query.optimize.RaiseSelectCriteria Maven / Gradle / Ivy

There is a newer version: 5.4.1.Final
Show newest version
/*
 * 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.optimize;

import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import org.modeshape.common.annotation.Immutable;
import org.modeshape.jcr.query.QueryContext;
import org.modeshape.jcr.query.model.Column;
import org.modeshape.jcr.query.model.Constraint;
import org.modeshape.jcr.query.model.EquiJoinCondition;
import org.modeshape.jcr.query.model.JoinCondition;
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.SelectorName;
import org.modeshape.jcr.query.model.Visitable;
import org.modeshape.jcr.query.model.Visitors;
import org.modeshape.jcr.query.model.Visitors.AbstractVisitor;
import org.modeshape.jcr.query.plan.PlanNode;
import org.modeshape.jcr.query.plan.PlanNode.Property;
import org.modeshape.jcr.query.plan.PlanNode.Type;
import org.modeshape.jcr.query.plan.PlanUtil;

/**
 * An {@link OptimizerRule optimizer rule} that moves up higher in the plan any SELECT node that appears below a JOIN node and
 * that applies to selectors that are on the other side of the join.
 * 

* This step is often counterintuitive, since one of the best optimizations a query optimizer can do is to * {@link PushSelectCriteria push down SELECT nodes} as far as they'll go. But consider the case of a SOURCE node that appears * below a JOIN, where the SOURCE node is a view. The optimizer {@link ReplaceViews replaces the SOURCE node with the view * definition}, and if the view definition includes a SELECT node, that SELECT node appears below the JOIN. Plus, that SELECT node * is already pushed down as far as it can go (assuming the view isn't defined to use another view). However, the JOIN may use the * same selector on the opposite side, and it may be possible that the same SELECT node may apply to the other side of the JOIN. * In this case, we can push up the SELECT node higher than the JOIN, and then the push-down would cause the SELECT to be * copied to both sides of the JOIN. *

*

* Here is an example plan that involves a JOIN of two SOURCE nodes: * *

 *          ...
 *           |
 *         JOIN
 *        /     \
 *       /       SOURCE({@link Property#SOURCE_NAME SOURCE_NAME}="t1")   
 *      /
 *   SOURCE({@link Property#SOURCE_NAME SOURCE_NAME}="v1")
 * 
* * If the right-side SOURCE references the "t1" table, and the left-side SOURCE references a view "v1" defined as " * SELECT * FROM t1 WHERE t1.id < 3", then the {@link ReplaceViews} rule would change this plan to be: * *
 *           ...
 *           |
 *         JOIN
 *        /     \
 *       /       SOURCE({@link Property#SOURCE_NAME SOURCE_NAME}="t1")   
 *      /
 *    PROJECT
 *      |
 *    SELECT     applies the "t1.id < 3" criteria
 *      |
 *   SOURCE({@link Property#SOURCE_NAME SOURCE_NAME}="t1")
 * 
* * Again, the SELECT cannot be pushed down any further. But the whole query can be made more efficient - because the SELECT on the * left-side of the JOIN will include only those tuples from 't1' that satisfy the SELECT, the JOIN will only include those tuples * that also satisfy this criteria, even though more tuples are returned from the right-side SOURCE. *

*

* In this case, the left-hand SELECT can actually be copied to the right-hand side of the JOIN, resulting in: * *

 *           ...
 *           |
 *         JOIN
 *        /     \
 *       /       SELECT   applies the "t1.id < 3" criteria
 *      /          |
 *    PROJECT    SOURCE({@link Property#SOURCE_NAME SOURCE_NAME}="t1")   
 *      |
 *    SELECT   applies the "t1.id < 3" criteria
 *      |
 *   SOURCE({@link Property#SOURCE_NAME SOURCE_NAME}="t1")
 * 
* *

*/ @Immutable public class RaiseSelectCriteria implements OptimizerRule { public static final RaiseSelectCriteria INSTANCE = new RaiseSelectCriteria(); @Override public PlanNode execute( QueryContext context, PlanNode plan, LinkedList ruleStack ) { Set copiedSelectNodes = new HashSet(); for (PlanNode join : plan.findAllAtOrBelow(Type.JOIN)) { // Get the join condition ... JoinCondition joinCondition = join.getProperty(Property.JOIN_CONDITION, JoinCondition.class); if (joinCondition instanceof EquiJoinCondition) { EquiJoinCondition equiJoinCondition = (EquiJoinCondition)joinCondition; SelectorName selector1 = equiJoinCondition.selector1Name(); SelectorName selector2 = equiJoinCondition.selector2Name(); String property1 = equiJoinCondition.getProperty1Name(); String property2 = equiJoinCondition.getProperty2Name(); // Walk up the tree looking for SELECT nodes that apply to one of the sides ... PlanNode node = join.getParent(); while (node != null) { if (!copiedSelectNodes.contains(node)) { PlanNode copy = copySelectNode(context, node, selector1, property1, selector2, property2); if (copy != null) { node.insertAsParent(copy); copiedSelectNodes.add(node); copiedSelectNodes.add(copy); } else { copy = copySelectNode(context, node, selector2, property2, selector1, property1); if (copy != null) { node.insertAsParent(copy); copiedSelectNodes.add(node); copiedSelectNodes.add(copy); } } } node = node.getParent(); } } } return plan; } protected PlanNode copySelectNode( QueryContext context, PlanNode selectNode, SelectorName selectorName, String propertyName, SelectorName copySelectorName, String copyPropertyName ) { if (selectNode.isNot(Type.SELECT)) return null; if (selectNode.getSelectors().size() != 1 || !selectNode.getSelectors().contains(selectorName)) return null; Constraint constraint = selectNode.getProperty(Property.SELECT_CRITERIA, Constraint.class); Set columns = getColumnsReferencedBy(constraint); if (columns.size() != 1) return null; Column column = columns.iterator().next(); if (!column.selectorName().equals(selectorName)) return null; if (!column.getPropertyName().equals(propertyName)) return null; // We know that this constraint ONLY applies to the referenced selector and property, // so we will duplicate this constraint ... // Create the new node ... PlanNode copy = new PlanNode(Type.SELECT, copySelectorName); // Copy the constraint, but change the references to the copy selector and property ... PlanUtil.ColumnMapping mappings = new PlanUtil.ColumnMapping(selectorName); mappings.map(propertyName, new Column(copySelectorName, copyPropertyName, copyPropertyName)); Constraint newCriteria = PlanUtil.replaceReferences(context, constraint, mappings, copy); copy.setProperty(Property.SELECT_CRITERIA, newCriteria); return copy; } @Override public String toString() { return getClass().getSimpleName(); } /** * Get the set of Column objects that represent those columns referenced by the visitable object. * * @param visitable the object to be visited * @return the set of Column objects, with column names that always are the string-form of the {@link Column#getPropertyName() * property name}; never null */ public static Set getColumnsReferencedBy( Visitable visitable ) { if (visitable == null) return Collections.emptySet(); final Set symbols = new HashSet(); // Walk the entire structure, so only supply a StrategyVisitor (that does no navigation) ... Visitors.visitAll(visitable, new AbstractVisitor() { protected void addColumnFor( SelectorName selectorName, String property ) { symbols.add(new Column(selectorName, property, property)); } @Override public void visit( Column column ) { symbols.add(column); } @Override public void visit( EquiJoinCondition joinCondition ) { addColumnFor(joinCondition.selector1Name(), joinCondition.getProperty1Name()); addColumnFor(joinCondition.selector2Name(), joinCondition.getProperty2Name()); } @Override public void visit( PropertyExistence prop ) { addColumnFor(prop.selectorName(), prop.getPropertyName()); } @Override public void visit( PropertyValue prop ) { addColumnFor(prop.selectorName(), prop.getPropertyName()); } @Override public void visit( ReferenceValue ref ) { String propertyName = ref.getPropertyName(); if (propertyName != null) { addColumnFor(ref.selectorName(), propertyName); } } }); return symbols; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy