View Javadoc

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 }