org.modeshape.jcr.query.optimize.ReplaceViews 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.List;
import java.util.Map;
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.SelectorName;
import org.modeshape.jcr.query.plan.CanonicalPlanner;
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;
import org.modeshape.jcr.query.validate.Schemata;
import org.modeshape.jcr.query.validate.Schemata.Table;
import org.modeshape.jcr.query.validate.Schemata.View;
/**
* An {@link OptimizerRule optimizer rule} that replaces any SOURCE nodes that happen to be {@link View views}. This rewriting
* changes all of the elements of the plan that reference the SOURCE and it's columns, including criteria, project nodes, etc.
*
* For example, here is the portion of a plan that uses a single SOURCE that is defined to use a view.
*
*
* ...
* |
* SOURCE1
*
*
* This same SOURCE node is then replaced with the view's definition:
*
*
* ...
* |
* PROJECT with the list of columns being SELECTed
* |
* SELECT1
* | One or more SELECT plan nodes that each have
* SELECT2 a single non-join constraint that are then all AND-ed
* | together
* SELECTn
* |
* SOURCE
*
*
*
*/
@Immutable
public class ReplaceViews implements OptimizerRule {
public static final ReplaceViews INSTANCE = new ReplaceViews();
@Override
public PlanNode execute( QueryContext context,
PlanNode plan,
LinkedList ruleStack ) {
CanonicalPlanner planner = new CanonicalPlanner();
// Prepare the maps that will record the old-to-new mappings from the old view SOURCE nodes to the table SOURCEs ...
// For each of the SOURCE nodes ...
Schemata schemata = context.getSchemata();
Set processedSources = new HashSet();
boolean foundViews = false;
do {
foundViews = false;
for (PlanNode sourceNode : plan.findAllAtOrBelow(Type.SOURCE)) {
if (processedSources.contains(sourceNode)) continue;
processedSources.add(sourceNode);
// Resolve the node to find the definition in the schemata ...
SelectorName tableName = sourceNode.getProperty(Property.SOURCE_NAME, SelectorName.class);
SelectorName tableAlias = sourceNode.getProperty(Property.SOURCE_ALIAS, SelectorName.class);
Table table = schemata.getTable(tableName);
if (table instanceof View) {
View view = (View)table;
PlanNode viewPlan = planner.createPlan(context, view.getDefinition());
if (viewPlan == null) continue; // there were likely errors when creating the plan
// If the view doesn't have an alias, or if the view's alias doesn't match the table's name/alias ...
PlanNode viewProjectNode = viewPlan.findAtOrBelow(Type.PROJECT);
if (viewProjectNode.getSelectors().size() == 1) {
SelectorName tableAliasOrName = tableAlias != null ? tableAlias : tableName;
SelectorName viewAlias = viewProjectNode.getSelectors().iterator().next();
// Replace the view's alias ...
Map replacements = Collections.singletonMap(viewAlias, tableAliasOrName);
PlanUtil.replaceReferencesToRemovedSource(context, viewPlan, replacements);
if (!context.getHints().validateColumnExistance) {
// Find the next highest PROJECT node above the source ...
PlanNode project = sourceNode.findAncestor(Type.PROJECT);
if (project != null) {
List projectedColumns = project.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class);
// There may be columns that don't appear in the source, so make sure they are there ...
viewPlan = PlanUtil.addMissingProjectColumns(context, viewProjectNode, projectedColumns);
assert viewPlan != null;
}
}
}
// Insert the view plan under the parent SOURCE node ...
sourceNode.addLastChild(viewPlan);
// Remove the source node ...
sourceNode.extractFromParent();
// // Replace the original view's name with the name/alias ...
PlanNode parent = viewPlan.getParent();
if (parent != null) {
PlanUtil.ColumnMapping aliasMappings = null;
if (tableAlias != null) {
aliasMappings = PlanUtil.createMappingForAliased(tableAlias, view, viewPlan);
PlanUtil.replaceViewReferences(context, parent, aliasMappings);
}
PlanUtil.ColumnMapping viewMappings = PlanUtil.createMappingFor(view, viewPlan);
PlanUtil.replaceViewReferences(context, parent, viewMappings);
}
if (viewPlan.is(Type.PROJECT)) {
// The PROJECT from the plan may actually not be needed if there is another PROJECT above it ...
PlanNode node = viewPlan.getParent();
while (node != null) {
if (node.isOneOf(Type.JOIN)) break;
if (node.is(Type.PROJECT) && viewPlan.getSelectors().containsAll(node.getSelectors())) {
viewPlan.extractFromParent();
break;
}
node = node.getParent();
}
}
foundViews = true;
}
}
} while (foundViews);
if (foundViews) {
// We'll need to try to push up criteria from the join, but we only should do this after this rule
// is completely done ...
if (!(ruleStack.getFirst() instanceof RaiseSelectCriteria)) {
ruleStack.addFirst(RaiseSelectCriteria.INSTANCE);
ruleStack.addFirst(PushSelectCriteria.INSTANCE);
}
// We re-wrote at least one SOURCE, but the resulting plan tree for the view could actually reference
// other views. Therefore, re-run this rule ...
ruleStack.addFirst(this);
}
return plan;
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy