1 /*
2 * ModeShape (http://www.modeshape.org)
3 * See the COPYRIGHT.txt file distributed with this work for information
4 * regarding copyright ownership. Some portions may be licensed
5 * to Red Hat, Inc. under one or more contributor license agreements.
6 * See the AUTHORS.txt file in the distribution for a full listing of
7 * individual contributors.
8 *
9 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10 * is licensed to you under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation; either version 2.1 of
12 * the License, or (at your option) any later version.
13 *
14 * ModeShape is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this software; if not, write to the Free
21 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23 */
24 package org.modeshape.graph.query.optimize;
25
26 import java.util.Collections;
27 import java.util.HashSet;
28 import java.util.LinkedList;
29 import java.util.Map;
30 import java.util.Set;
31 import net.jcip.annotations.Immutable;
32 import org.modeshape.graph.query.QueryContext;
33 import org.modeshape.graph.query.model.SelectorName;
34 import org.modeshape.graph.query.plan.CanonicalPlanner;
35 import org.modeshape.graph.query.plan.PlanNode;
36 import org.modeshape.graph.query.plan.PlanUtil;
37 import org.modeshape.graph.query.plan.PlanNode.Property;
38 import org.modeshape.graph.query.plan.PlanNode.Type;
39 import org.modeshape.graph.query.validate.Schemata;
40 import org.modeshape.graph.query.validate.Schemata.Table;
41 import org.modeshape.graph.query.validate.Schemata.View;
42
43 /**
44 * An {@link OptimizerRule optimizer rule} that replaces any SOURCE nodes that happen to be {@link View views}. This rewriting
45 * changes all of the elements of the plan that reference the SOURCE and it's columns, including criteria, project nodes, etc.
46 * <p>
47 * For example, here is the portion of a plan that uses a single SOURCE that is defined to use a view.
48 *
49 * <pre>
50 * ...
51 * |
52 * SOURCE1
53 * </pre>
54 *
55 * This same SOURCE node is then replaced with the view's definition:
56 *
57 * <pre>
58 * ...
59 * |
60 * PROJECT with the list of columns being SELECTed
61 * |
62 * SELECT1
63 * | One or more SELECT plan nodes that each have
64 * SELECT2 a single non-join constraint that are then all AND-ed
65 * | together
66 * SELECTn
67 * |
68 * SOURCE
69 * </pre>
70 *
71 * </p>
72 */
73 @Immutable
74 public class ReplaceViews implements OptimizerRule {
75
76 public static final ReplaceViews INSTANCE = new ReplaceViews();
77
78 /**
79 * {@inheritDoc}
80 *
81 * @see org.modeshape.graph.query.optimize.OptimizerRule#execute(org.modeshape.graph.query.QueryContext,
82 * org.modeshape.graph.query.plan.PlanNode, java.util.LinkedList)
83 */
84 public PlanNode execute( QueryContext context,
85 PlanNode plan,
86 LinkedList<OptimizerRule> ruleStack ) {
87 CanonicalPlanner planner = new CanonicalPlanner();
88
89 // Prepare the maps that will record the old-to-new mappings from the old view SOURCE nodes to the table SOURCEs ...
90
91 // For each of the SOURCE nodes ...
92 Schemata schemata = context.getSchemata();
93 Set<PlanNode> processedSources = new HashSet<PlanNode>();
94 boolean foundViews = false;
95 do {
96 foundViews = false;
97 for (PlanNode sourceNode : plan.findAllAtOrBelow(Type.SOURCE)) {
98 if (processedSources.contains(sourceNode)) continue;
99 processedSources.add(sourceNode);
100
101 // Resolve the node to find the definition in the schemata ...
102 SelectorName tableName = sourceNode.getProperty(Property.SOURCE_NAME, SelectorName.class);
103 SelectorName tableAlias = sourceNode.getProperty(Property.SOURCE_ALIAS, SelectorName.class);
104 Table table = schemata.getTable(tableName);
105 if (table instanceof View) {
106 View view = (View)table;
107 PlanNode viewPlan = planner.createPlan(context, view.getDefinition());
108 if (viewPlan == null) continue; // there were likely errors when creating the plan
109
110 // If the view doesn't have an alias, or if the view's alias doesn't match the table's name/alias ...
111 PlanNode viewProjectNode = viewPlan.findAtOrBelow(Type.PROJECT);
112 if (viewProjectNode.getSelectors().size() == 1) {
113 SelectorName tableAliasOrName = tableAlias != null ? tableAlias : tableName;
114 SelectorName viewAlias = viewProjectNode.getSelectors().iterator().next();
115 // Replace the view's alias ...
116 Map<SelectorName, SelectorName> replacements = Collections.singletonMap(viewAlias, tableAliasOrName);
117 PlanUtil.replaceReferencesToRemovedSource(context, viewPlan, replacements);
118 }
119
120 // Insert the view plan under the parent SOURCE node ...
121 sourceNode.addLastChild(viewPlan);
122
123 // Remove the source node ...
124 sourceNode.extractFromParent();
125
126 // // Replace the original view's name with the name/alias ...
127 PlanNode parent = viewPlan.getParent();
128 if (parent != null) {
129 PlanUtil.ColumnMapping aliasMappings = null;
130 if (tableAlias != null) {
131 aliasMappings = PlanUtil.createMappingForAliased(tableAlias, view, viewPlan);
132 PlanUtil.replaceViewReferences(context, parent, aliasMappings);
133 }
134 PlanUtil.ColumnMapping viewMappings = PlanUtil.createMappingFor(view, viewPlan);
135 PlanUtil.replaceViewReferences(context, parent, viewMappings);
136 }
137
138 if (viewPlan.is(Type.PROJECT)) {
139 // The PROJECT from the plan may actually not be needed if there is another PROJECT above it ...
140 PlanNode node = viewPlan.getParent();
141 while (node != null) {
142 if (node.isOneOf(Type.JOIN)) break;
143 if (node.is(Type.PROJECT) && viewPlan.getSelectors().containsAll(node.getSelectors())) {
144 viewPlan.extractFromParent();
145 break;
146 }
147 node = node.getParent();
148 }
149 }
150 foundViews = true;
151 }
152 }
153 } while (foundViews);
154
155 if (foundViews) {
156 // We'll need to try to push up criteria from the join, but we only should do this after this rule
157 // is completely done ...
158 if (!(ruleStack.getFirst() instanceof RaiseSelectCriteria)) {
159 ruleStack.addFirst(RaiseSelectCriteria.INSTANCE);
160 ruleStack.addFirst(PushSelectCriteria.INSTANCE);
161 }
162
163 // We re-wrote at least one SOURCE, but the resulting plan tree for the view could actually reference
164 // other views. Therefore, re-run this rule ...
165 ruleStack.addFirst(this);
166 }
167 return plan;
168 }
169
170 /**
171 * {@inheritDoc}
172 *
173 * @see java.lang.Object#toString()
174 */
175 @Override
176 public String toString() {
177 return getClass().getSimpleName();
178 }
179
180 }