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.HashSet;
27 import java.util.LinkedList;
28 import java.util.List;
29 import java.util.Set;
30 import org.modeshape.graph.query.QueryContext;
31 import org.modeshape.graph.query.model.Column;
32 import org.modeshape.graph.query.model.SelectorName;
33 import org.modeshape.graph.query.plan.PlanNode;
34 import org.modeshape.graph.query.plan.PlanUtil;
35 import org.modeshape.graph.query.plan.PlanNode.Property;
36 import org.modeshape.graph.query.plan.PlanNode.Type;
37
38 /**
39 * This rule attempts to ensure the proper location of {@link Type#PROJECT} nodes. For example, every {@link Type#ACCESS} node
40 * needs to have a PROJECT node below it and above any other nodes (e.g., {@link Type#SELECT} or {@link Type#SOURCE} nodes). This
41 * rule ensures that the PROJECT exists, but it also attempts to reduce any unnecessary columns in existing PROJECT nodes.
42 */
43 public class PushProjects implements OptimizerRule {
44
45 public static final PushProjects INSTANCE = new PushProjects();
46
47 /**
48 * {@inheritDoc}
49 *
50 * @see org.modeshape.graph.query.optimize.OptimizerRule#execute(org.modeshape.graph.query.QueryContext,
51 * org.modeshape.graph.query.plan.PlanNode, java.util.LinkedList)
52 */
53 public PlanNode execute( QueryContext context,
54 PlanNode plan,
55 LinkedList<OptimizerRule> ruleStack ) {
56
57 // Find all of the ACCESS nodes to make sure there is a PROJECT ...
58 for (PlanNode access : plan.findAllAtOrBelow(Type.ACCESS)) {
59 // Find the first node that is below any LIMIT, SORT, or DUP_REMOVE ...
60 PlanNode parentOfProject = access;
61 while (parentOfProject.isOneOf(Type.LIMIT, Type.SORT, Type.DUP_REMOVE)) {
62 parentOfProject = parentOfProject.getParent();
63 }
64
65 // Is there already a PROJECT here ?
66 assert parentOfProject.getChildCount() == 1; // should only have one child ...
67 PlanNode child = parentOfProject.getFirstChild(); // should only have one child ...
68 if (child.is(Type.PROJECT)) {
69 // Check to see if there is a PROJECT above the access node ...
70 PlanNode accessParent = access.getParent();
71 if (accessParent == null || accessParent.isNot(Type.PROJECT)) continue;
72 // Otherwise, the parent is a PROJECT, but there is another PROJECT above the ACCESS node.
73 // Remove the lower PROJECT so the next code block moves the top PROJECT down below the ACCESS node ...
74 assert accessParent.is(Type.PROJECT);
75 child.extractFromParent();
76 child = parentOfProject.getFirstChild();
77 }
78
79 // If the parent of the ACCESS node is a PROJECT, then we can simply move it to here ...
80 PlanNode accessParent = access.getParent();
81 if (accessParent != null && accessParent.is(Type.PROJECT)) {
82 PlanNode project = accessParent;
83 if (project == plan) {
84 // The PROJECT node is the root, so the ACCESS node will be the root ...
85 plan = access;
86 access.removeFromParent();
87 } else {
88 project.extractFromParent();
89 }
90 child.insertAsParent(project);
91 if (plan == access) return plan;
92
93 // We need to make sure we have all of the columns needed for any ancestor ...
94 List<Column> requiredColumns = PlanUtil.findRequiredColumns(context, project);
95 project.setProperty(Property.PROJECT_COLUMNS, requiredColumns);
96 project.addSelectors(getSelectorsFor(requiredColumns));
97 continue;
98 }
99
100 // There is no PROJECT, so find the columns that are required by the plan above this point ...
101 List<Column> requiredColumns = PlanUtil.findRequiredColumns(context, child);
102
103 // And insert the PROJECT ...
104 PlanNode projectNode = new PlanNode(Type.PROJECT);
105 projectNode.setProperty(Property.PROJECT_COLUMNS, requiredColumns);
106 projectNode.addSelectors(getSelectorsFor(requiredColumns));
107 child.insertAsParent(projectNode);
108 }
109 return plan;
110 }
111
112 protected Set<SelectorName> getSelectorsFor( List<Column> columns ) {
113 Set<SelectorName> selectors = new HashSet<SelectorName>();
114 for (Column column : columns) {
115 selectors.add(column.getSelectorName());
116 }
117 return selectors;
118 }
119
120 /**
121 * {@inheritDoc}
122 *
123 * @see java.lang.Object#toString()
124 */
125 @Override
126 public String toString() {
127 return getClass().getSimpleName();
128 }
129 }