Merge lp:~mmcm/akiban-server/sql-subquery-aggregate-bound-tables into lp:~akiban-technologies/akiban-server/trunk

Proposed by Mike McMahon
Status: Merged
Approved by: Thomas Jones-Low
Approved revision: 2713
Merged at revision: 2710
Proposed branch: lp:~mmcm/akiban-server/sql-subquery-aggregate-bound-tables
Merge into: lp:~akiban-technologies/akiban-server/trunk
Diff against target: 433 lines (+179/-119)
7 files modified
src/main/java/com/akiban/sql/optimizer/rule/AggregateMapper.java (+33/-34)
src/main/java/com/akiban/sql/optimizer/rule/AggregateSplitter.java (+4/-3)
src/main/java/com/akiban/sql/optimizer/rule/JoinAndIndexPicker.java (+10/-82)
src/main/java/com/akiban/sql/optimizer/rule/SubqueryBoundTablesTracker.java (+112/-0)
src/test/resources/com/akiban/sql/optimizer/rule/aggregate/README.txt (+2/-0)
src/test/resources/com/akiban/sql/optimizer/rule/aggregate/subquery-1.expected (+14/-0)
src/test/resources/com/akiban/sql/optimizer/rule/aggregate/subquery-1.sql (+4/-0)
To merge this branch: bzr merge lp:~mmcm/akiban-server/sql-subquery-aggregate-bound-tables
Reviewer Review Type Date Requested Status
Thomas Jones-Low Approve
Review via email: mp+176257@code.launchpad.net

Description of the change

Don't try to remap a column after an aggregate if it comes from a table bound by an outer query.

See new test or query in associated bug. But note that these kinds of queries where this issues arises may not be executed very efficiently in cases where join ordering decides to execute the subquery repeatedly.

To post a comment you must log in.
Revision history for this message
Thomas Jones-Low (tjoneslo) wrote :

As described

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/main/java/com/akiban/sql/optimizer/rule/AggregateMapper.java'
2--- src/main/java/com/akiban/sql/optimizer/rule/AggregateMapper.java 2013-03-22 20:05:57 +0000
3+++ src/main/java/com/akiban/sql/optimizer/rule/AggregateMapper.java 2013-07-22 18:00:43 +0000
4@@ -48,53 +48,49 @@
5
6 @Override
7 public void apply(PlanContext plan) {
8- List<AggregateSource> sources = new AggregateSourceFinder().find(plan.getPlan());
9- for (AggregateSource source : sources) {
10- Mapper m = new Mapper(plan.getRulesContext(), source);
11- m.remap(source);
12+ List<AggregateSourceState> sources = new AggregateSourceFinder(plan).find();
13+ for (AggregateSourceState source : sources) {
14+ Mapper m = new Mapper(plan.getRulesContext(), source.aggregateSource, source.containingQuery);
15+ m.remap(source.aggregateSource);
16 }
17 }
18
19- static class AggregateSourceFinder implements PlanVisitor, ExpressionVisitor {
20- List<AggregateSource> result = new ArrayList<>();
21-
22- public List<AggregateSource> find(PlanNode root) {
23- root.accept(this);
24+ static class AggregateSourceFinder extends SubqueryBoundTablesTracker {
25+ List<AggregateSourceState> result = new ArrayList<>();
26+
27+ public AggregateSourceFinder(PlanContext planContext) {
28+ super(planContext);
29+ }
30+
31+ public List<AggregateSourceState> find() {
32+ run();
33 return result;
34 }
35
36 @Override
37- public boolean visitEnter(PlanNode n) {
38- return visit(n);
39- }
40- @Override
41- public boolean visitLeave(PlanNode n) {
42- return true;
43- }
44- @Override
45 public boolean visit(PlanNode n) {
46+ super.visit(n);
47 if (n instanceof AggregateSource)
48- result.add((AggregateSource)n);
49- return true;
50- }
51-
52- @Override
53- public boolean visitEnter(ExpressionNode n) {
54- return visit(n);
55- }
56- @Override
57- public boolean visitLeave(ExpressionNode n) {
58- return true;
59- }
60- @Override
61- public boolean visit(ExpressionNode n) {
62- return true;
63+ result.add(new AggregateSourceState((AggregateSource)n, currentQuery()));
64+ return true;
65+ }
66+ }
67+
68+ static class AggregateSourceState {
69+ AggregateSource aggregateSource;
70+ BaseQuery containingQuery;
71+
72+ public AggregateSourceState(AggregateSource aggregateSource,
73+ BaseQuery containingQuery) {
74+ this.aggregateSource = aggregateSource;
75+ this.containingQuery = containingQuery;
76 }
77 }
78
79 static class Mapper implements ExpressionRewriteVisitor {
80 private RulesContext rulesContext;
81 private AggregateSource source;
82+ private BaseQuery query;
83 private Set<ColumnSource> aggregated = new HashSet<>();
84 private Map<ExpressionNode,ExpressionNode> map =
85 new HashMap<>();
86@@ -119,9 +115,10 @@
87 return implicitAggregateSetting;
88 }
89
90- public Mapper(RulesContext rulesContext, AggregateSource source) {
91+ public Mapper(RulesContext rulesContext, AggregateSource source, BaseQuery query) {
92 this.rulesContext = rulesContext;
93 this.source = source;
94+ this.query = query;
95 aggregated.add(source);
96 // Map all the group by expressions at the start.
97 // This means that if you GROUP BY x+1, you can ORDER BY
98@@ -184,7 +181,9 @@
99 }
100 if (expr instanceof ColumnExpression) {
101 ColumnExpression column = (ColumnExpression)expr;
102- if (!aggregated.contains(column.getTable())) {
103+ ColumnSource table = column.getTable();
104+ if (!aggregated.contains(table) &&
105+ !query.getOuterTables().contains(table)) {
106 return nonAggregate(column);
107 }
108 }
109
110=== modified file 'src/main/java/com/akiban/sql/optimizer/rule/AggregateSplitter.java'
111--- src/main/java/com/akiban/sql/optimizer/rule/AggregateSplitter.java 2013-03-22 20:05:57 +0000
112+++ src/main/java/com/akiban/sql/optimizer/rule/AggregateSplitter.java 2013-07-22 18:00:43 +0000
113@@ -18,6 +18,7 @@
114 package com.akiban.sql.optimizer.rule;
115
116 import com.akiban.sql.optimizer.rule.AggregateMapper.AggregateSourceFinder;
117+import com.akiban.sql.optimizer.rule.AggregateMapper.AggregateSourceState;
118
119 import com.akiban.server.error.UnsupportedSQLException;
120
121@@ -43,9 +44,9 @@
122
123 @Override
124 public void apply(PlanContext plan) {
125- List<AggregateSource> sources = new AggregateSourceFinder().find(plan.getPlan());
126- for (AggregateSource source : sources) {
127- split(source);
128+ List<AggregateSourceState> sources = new AggregateSourceFinder(plan).find();
129+ for (AggregateSourceState source : sources) {
130+ split(source.aggregateSource);
131 }
132 }
133
134
135=== modified file 'src/main/java/com/akiban/sql/optimizer/rule/JoinAndIndexPicker.java'
136--- src/main/java/com/akiban/sql/optimizer/rule/JoinAndIndexPicker.java 2013-03-22 20:05:57 +0000
137+++ src/main/java/com/akiban/sql/optimizer/rule/JoinAndIndexPicker.java 2013-07-22 18:00:43 +0000
138@@ -47,10 +47,7 @@
139
140 @Override
141 public void apply(PlanContext planContext) {
142- BaseQuery query = (BaseQuery)planContext.getPlan();
143- List<Picker> pickers =
144- new JoinsFinder(planContext)
145- .find(query);
146+ List<Picker> pickers = new JoinsFinder(planContext).find();
147 for (Picker picker : pickers) {
148 picker.apply();
149 }
150@@ -943,73 +940,39 @@
151 }
152 }
153
154- // Purpose is twofold:
155 // Find top-level joins and note what query they come from;
156- // Annotate subqueries with their outer table references.
157 // Top-level queries and those used in expressions are returned directly.
158 // Derived tables are deferred, since they need to be planned in
159 // the context of various join orders to allow for join predicated
160 // to be pushed "inside." So they are stored in a Map accessible
161 // to other Pickers.
162- static class JoinsFinder implements PlanVisitor, ExpressionVisitor {
163+ static class JoinsFinder extends SubqueryBoundTablesTracker {
164 List<Picker> result;
165 Map<SubquerySource,Picker> subpickers;
166- BaseQuery rootQuery;
167- Deque<SubqueryState> subqueries = new ArrayDeque<>();
168- PlanContext planContext;
169
170 public JoinsFinder(PlanContext planContext) {
171- this.planContext = planContext;
172+ super(planContext);
173 }
174
175- public List<Picker> find(BaseQuery query) {
176+ public List<Picker> find() {
177 result = new ArrayList<>();
178 subpickers = new HashMap<>();
179- rootQuery = query;
180- query.accept(this);
181+ run();
182 result.removeAll(subpickers.values()); // Do these in context.
183 return result;
184 }
185
186 @Override
187- public boolean visitEnter(PlanNode n) {
188- if (n instanceof Subquery) {
189- subqueries.push(new SubqueryState((Subquery)n));
190- return true;
191- }
192- return visit(n);
193- }
194-
195- @Override
196- public boolean visitLeave(PlanNode n) {
197- if (n instanceof Subquery) {
198- SubqueryState s = subqueries.pop();
199- Set<ColumnSource> outerTables = s.getTablesReferencedButNotDefined();
200- s.subquery.setOuterTables(outerTables);
201- if (!subqueries.isEmpty())
202- subqueries.peek().tablesReferenced.addAll(outerTables);
203- }
204- return true;
205- }
206-
207- @Override
208 public boolean visit(PlanNode n) {
209- if (!subqueries.isEmpty() &&
210- (n instanceof ColumnSource)) {
211- boolean added = subqueries.peek().tablesDefined.add((ColumnSource)n);
212- assert added : "Table defined more than once";
213- }
214+ super.visit(n);
215 if ((n instanceof Joinable) && !(n instanceof TableSource)) {
216 Joinable j = (Joinable)n;
217 while (j.getOutput() instanceof Joinable)
218 j = (Joinable)j.getOutput();
219- BaseQuery query = rootQuery;
220+ BaseQuery query = currentQuery();
221 SubquerySource subquerySource = null;
222- if (!subqueries.isEmpty()) {
223- query = subqueries.peek().subquery;
224- if (query.getOutput() instanceof SubquerySource)
225- subquerySource = (SubquerySource)query.getOutput();
226- }
227+ if (query.getOutput() instanceof SubquerySource)
228+ subquerySource = (SubquerySource)query.getOutput();
229 for (Picker picker : result) {
230 if (picker.joinable == j)
231 // Already have another set of joins to same root join.
232@@ -1022,40 +985,5 @@
233 }
234 return true;
235 }
236-
237- @Override
238- public boolean visitEnter(ExpressionNode n) {
239- return visit(n);
240- }
241-
242- @Override
243- public boolean visitLeave(ExpressionNode n) {
244- return true;
245- }
246-
247- @Override
248- public boolean visit(ExpressionNode n) {
249- if (!subqueries.isEmpty() &&
250- (n instanceof ColumnExpression)) {
251- subqueries.peek().tablesReferenced.add(((ColumnExpression)n).getTable());
252- }
253- return true;
254- }
255- }
256-
257- static class SubqueryState {
258- Subquery subquery;
259- Set<ColumnSource> tablesReferenced = new HashSet<>();
260- Set<ColumnSource> tablesDefined = new HashSet<>();
261-
262- public SubqueryState(Subquery subquery) {
263- this.subquery = subquery;
264- }
265-
266- public Set<ColumnSource> getTablesReferencedButNotDefined() {
267- tablesReferenced.removeAll(tablesDefined);
268- return tablesReferenced;
269- }
270- }
271-
272+ }
273 }
274
275=== added file 'src/main/java/com/akiban/sql/optimizer/rule/SubqueryBoundTablesTracker.java'
276--- src/main/java/com/akiban/sql/optimizer/rule/SubqueryBoundTablesTracker.java 1970-01-01 00:00:00 +0000
277+++ src/main/java/com/akiban/sql/optimizer/rule/SubqueryBoundTablesTracker.java 2013-07-22 18:00:43 +0000
278@@ -0,0 +1,112 @@
279+/**
280+ * Copyright (C) 2009-2013 Akiban Technologies, Inc.
281+ *
282+ * This program is free software: you can redistribute it and/or modify
283+ * it under the terms of the GNU Affero General Public License as published by
284+ * the Free Software Foundation, either version 3 of the License, or
285+ * (at your option) any later version.
286+ *
287+ * This program is distributed in the hope that it will be useful,
288+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
289+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
290+ * GNU Affero General Public License for more details.
291+ *
292+ * You should have received a copy of the GNU Affero General Public License
293+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
294+ */
295+
296+package com.akiban.sql.optimizer.rule;
297+
298+import com.akiban.sql.optimizer.plan.*;
299+
300+import java.util.*;
301+
302+/** Annotate subqueries with their outer table references. */
303+public abstract class SubqueryBoundTablesTracker implements PlanVisitor, ExpressionVisitor {
304+ protected PlanContext planContext;
305+ protected BaseQuery rootQuery;
306+ protected Deque<SubqueryState> subqueries = new ArrayDeque<>();
307+
308+ protected SubqueryBoundTablesTracker(PlanContext planContext) {
309+ this.planContext = planContext;
310+ }
311+
312+ protected void run() {
313+ rootQuery = (BaseQuery)planContext.getPlan();
314+ rootQuery.accept(this);
315+ }
316+
317+ @Override
318+ public boolean visitEnter(PlanNode n) {
319+ if (n instanceof Subquery) {
320+ subqueries.push(new SubqueryState((Subquery)n));
321+ return true;
322+ }
323+ return visit(n);
324+ }
325+
326+ @Override
327+ public boolean visitLeave(PlanNode n) {
328+ if (n instanceof Subquery) {
329+ SubqueryState s = subqueries.pop();
330+ Set<ColumnSource> outerTables = s.getTablesReferencedButNotDefined();
331+ s.subquery.setOuterTables(outerTables);
332+ if (!subqueries.isEmpty())
333+ subqueries.peek().tablesReferenced.addAll(outerTables);
334+ }
335+ return true;
336+ }
337+
338+ @Override
339+ public boolean visit(PlanNode n) {
340+ if (!subqueries.isEmpty() &&
341+ (n instanceof ColumnSource)) {
342+ boolean added = subqueries.peek().tablesDefined.add((ColumnSource)n);
343+ assert added : "Table defined more than once";
344+ }
345+ return true;
346+ }
347+
348+ @Override
349+ public boolean visitEnter(ExpressionNode n) {
350+ return visit(n);
351+ }
352+
353+ @Override
354+ public boolean visitLeave(ExpressionNode n) {
355+ return true;
356+ }
357+
358+ @Override
359+ public boolean visit(ExpressionNode n) {
360+ if (!subqueries.isEmpty() &&
361+ (n instanceof ColumnExpression)) {
362+ subqueries.peek().tablesReferenced.add(((ColumnExpression)n).getTable());
363+ }
364+ return true;
365+ }
366+
367+ protected BaseQuery currentQuery() {
368+ if (subqueries.isEmpty()) {
369+ return rootQuery;
370+ }
371+ else {
372+ return subqueries.peek().subquery;
373+ }
374+ }
375+
376+ static class SubqueryState {
377+ Subquery subquery;
378+ Set<ColumnSource> tablesReferenced = new HashSet<>();
379+ Set<ColumnSource> tablesDefined = new HashSet<>();
380+
381+ public SubqueryState(Subquery subquery) {
382+ this.subquery = subquery;
383+ }
384+
385+ public Set<ColumnSource> getTablesReferencedButNotDefined() {
386+ tablesReferenced.removeAll(tablesDefined);
387+ return tablesReferenced;
388+ }
389+ }
390+}
391
392=== modified file 'src/test/resources/com/akiban/sql/optimizer/rule/aggregate/README.txt'
393--- src/test/resources/com/akiban/sql/optimizer/rule/aggregate/README.txt 2013-01-10 22:09:09 +0000
394+++ src/test/resources/com/akiban/sql/optimizer/rule/aggregate/README.txt 2013-07-22 18:00:43 +0000
395@@ -26,6 +26,8 @@
396
397 order-by-distinct: ORDER BY expression also DISTINCT
398
399+subquery-1: a subquery with outer table comparison
400+
401 sum-all: aggregate without GROUP BY
402
403 update: UPDATE to aggregate subexpression
404
405=== added file 'src/test/resources/com/akiban/sql/optimizer/rule/aggregate/subquery-1.expected'
406--- src/test/resources/com/akiban/sql/optimizer/rule/aggregate/subquery-1.expected 1970-01-01 00:00:00 +0000
407+++ src/test/resources/com/akiban/sql/optimizer/rule/aggregate/subquery-1.expected 2013-07-22 18:00:43 +0000
408@@ -0,0 +1,14 @@
409+SelectQuery@56686e67
410+ ResultSet@62285882[name]
411+ Project@46caf126[customers.name]
412+ Select@359e1a19[ANY(Subquery@1ec37acf)]
413+ TableSource@b1de02c(customers)
414+
415+Subquery@1ec37acf
416+ Project@3b31a09c[customers.cid == GROUP[0]]
417+ Select@33b3bbd0[]
418+ AggregateSource@37278020([orders.cid, orders.order_date],[])
419+ Select@5cef6f34[items.sku == 1234]
420+ JoinNode@6b6dee96(INNER[orders.oid == items.oid])
421+ TableSource@24cd12a(orders)
422+ TableSource@1381d94(items)
423\ No newline at end of file
424
425=== added file 'src/test/resources/com/akiban/sql/optimizer/rule/aggregate/subquery-1.sql'
426--- src/test/resources/com/akiban/sql/optimizer/rule/aggregate/subquery-1.sql 1970-01-01 00:00:00 +0000
427+++ src/test/resources/com/akiban/sql/optimizer/rule/aggregate/subquery-1.sql 2013-07-22 18:00:43 +0000
428@@ -0,0 +1,4 @@
429+SELECT name FROM customers
430+ WHERE cid IN (SELECT orders.cid FROM orders INNER JOIN items USING (oid)
431+ WHERE sku = '1234'
432+ GROUP BY orders.cid, orders.order_date)
433\ No newline at end of file

Subscribers

People subscribed via source and target branches