org.modeshape.jcr.query.optimize.CopyCriteria Maven / Gradle / Ivy
/*
* 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.SameNodeJoinCondition;
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 copies SELECT nodes that apply to one side of a equi-join condition so that they
* also apply to the other side fo the equi-join condition.
*/
@Immutable
public class CopyCriteria implements OptimizerRule {
public static final CopyCriteria INSTANCE = new CopyCriteria();
@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();
}
}
if (joinCondition instanceof EquiJoinCondition || joinCondition instanceof SameNodeJoinCondition) {
// Then for each side of the join ...
PlanNode left = join.getFirstChild();
PlanNode right = join.getLastChild();
copySelectNodes(context, left, right);
copySelectNodes(context, right, left);
}
}
return plan;
}
protected void copySelectNodes( QueryContext context,
PlanNode fromJoined,
PlanNode toJoined ) {
// Find all of the selectors used on the 'to' side ...
Set toSelectors = new HashSet();
for (PlanNode toNode : toJoined.findAllAtOrBelow()) {
toSelectors.addAll(toNode.getSelectors());
}
PlanNode nodeBelowSelects = null;
// Walk down the 'fromJoined' side looking for all SELECT nodes ...
for (PlanNode select : fromJoined.findAllAtOrBelow(Type.SELECT)) {
// If all of the SELECT's selectors are also found on the right ...
if (toSelectors.containsAll(select.getSelectors())) {
// Copy the criteria ...
PlanNode copy = new PlanNode(Type.SELECT, select.getSelectors());
copy.setProperty(Property.SELECT_CRITERIA, select.getProperty(Property.SELECT_CRITERIA));
if (nodeBelowSelects == null) {
nodeBelowSelects = toJoined.findAtOrBelow(Type.SOURCE, Type.JOIN, Type.SET_OPERATION, Type.NULL);
if (nodeBelowSelects == null) {
nodeBelowSelects = toJoined;
}
}
nodeBelowSelects.insertAsParent(copy);
nodeBelowSelects = copy;
}
}
}
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