forked from dolthub/vitess
-
Notifications
You must be signed in to change notification settings - Fork 0
/
select.go
341 lines (320 loc) · 10.6 KB
/
select.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
/*
Copyright 2017 Google Inc.
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 planbuilder
import (
"errors"
"fmt"
"vitess.io/vitess/go/vt/sqlparser"
"vitess.io/vitess/go/vt/vtgate/engine"
)
// buildSelectPlan is the new function to build a Select plan.
func buildSelectPlan(sel *sqlparser.Select, vschema ContextVSchema) (primitive engine.Primitive, err error) {
pb := newPrimitiveBuilder(vschema, newJointab(sqlparser.GetBindvars(sel)))
if err := pb.processSelect(sel, nil); err != nil {
return nil, err
}
if err := pb.bldr.Wireup(pb.bldr, pb.jt); err != nil {
return nil, err
}
return pb.bldr.Primitive(), nil
}
// processSelect builds a primitive tree for the given query or subquery.
// The tree built by this function has the following general structure:
//
// The leaf nodes can be a route, vindexFunc or subquery. In the symtab,
// the tables map has columns that point to these leaf nodes. A subquery
// itself contains a builder tree, but it's opaque and is made to look
// like a table for the analysis of the current tree.
//
// The leaf nodes are usually tied together by join nodes. While the join
// nodes are built, they have ON clauses. Those are analyzed and pushed
// down into the leaf nodes as the tree is formed. Join nodes are formed
// during analysis of the FROM clause.
//
// During the WHERE clause analysis, the target leaf node is identified
// for each part, and the PushFilter function is used to push the condition
// down. The same strategy is used for the other clauses.
//
// So, a typical plan would either be a simple leaf node, or may consist
// of leaf nodes tied together by join nodes.
//
// If a query has aggregates that cannot be pushed down, an aggregator
// primitive is built. The current orderedAggregate primitive can only
// be built on top of a route. The orderedAggregate expects the rows
// to be ordered as they are returned. This work is performed by the
// underlying route. This means that a compatible ORDER BY clause
// can also be handled by this combination of primitives. In this case,
// the tree would consist of an orderedAggregate whose input is a route.
//
// If a query has an ORDER BY, but the route is a scatter, then the
// ordering is pushed down into the route itself. This results in a simple
// route primitive.
//
// The LIMIT clause is the last construct of a query. If it cannot be
// pushed into a route, then a primitve is created on top of any
// of the above trees to make it discard unwanted rows.
func (pb *primitiveBuilder) processSelect(sel *sqlparser.Select, outer *symtab) error {
if err := pb.processTableExprs(sel.From); err != nil {
return err
}
if rb, ok := pb.bldr.(*route); ok {
directives := sqlparser.ExtractCommentDirectives(sel.Comments)
rb.ERoute.QueryTimeout = queryTimeout(directives)
if rb.ERoute.TargetDestination != nil {
return errors.New("unsupported: SELECT with a target destination")
}
if directives.IsSet(sqlparser.DirectiveScatterErrorsAsWarnings) {
rb.ERoute.ScatterErrorsAsWarnings = true
}
}
// Set the outer symtab after processing of FROM clause.
// This is because correlation is not allowed there.
pb.st.Outer = outer
if sel.Where != nil {
if err := pb.pushFilter(sel.Where.Expr, sqlparser.WhereStr); err != nil {
return err
}
}
grouper, err := pb.checkAggregates(sel)
if err != nil {
return err
}
if err := pb.pushSelectExprs(sel, grouper); err != nil {
return err
}
if sel.Having != nil {
if err := pb.pushFilter(sel.Having.Expr, sqlparser.HavingStr); err != nil {
return err
}
}
if err := pb.pushOrderBy(sel.OrderBy); err != nil {
return err
}
if err := pb.pushLimit(sel.Limit); err != nil {
return err
}
pb.bldr.PushMisc(sel)
return nil
}
// pushFilter identifies the target route for the specified bool expr,
// pushes it down, and updates the route info if the new constraint improves
// the primitive. This function can push to a WHERE or HAVING clause.
func (pb *primitiveBuilder) pushFilter(boolExpr sqlparser.Expr, whereType string) error {
filters := splitAndExpression(nil, boolExpr)
reorderBySubquery(filters)
for _, filter := range filters {
pullouts, origin, expr, err := pb.findOrigin(filter)
if err != nil {
return err
}
// The returned expression may be complex. Resplit before pushing.
for _, subexpr := range splitAndExpression(nil, expr) {
if err := pb.bldr.PushFilter(pb, subexpr, whereType, origin); err != nil {
return err
}
}
pb.addPullouts(pullouts)
}
return nil
}
// reorderBySubquery reorders the filters by pushing subqueries
// to the end. This allows the non-subquery filters to be
// pushed first because they can potentially improve the routing
// plan, which can later allow a filter containing a subquery
// to successfully merge with the corresponding route.
func reorderBySubquery(filters []sqlparser.Expr) {
max := len(filters)
for i := 0; i < max; i++ {
if !hasSubquery(filters[i]) {
continue
}
saved := filters[i]
for j := i; j < len(filters)-1; j++ {
filters[j] = filters[j+1]
}
filters[len(filters)-1] = saved
max--
}
}
// addPullouts adds the pullout subqueries to the primitiveBuilder.
func (pb *primitiveBuilder) addPullouts(pullouts []*pulloutSubquery) {
for _, pullout := range pullouts {
pullout.setUnderlying(pb.bldr)
pb.bldr = pullout
}
}
// pushSelectExprs identifies the target route for the
// select expressions and pushes them down.
func (pb *primitiveBuilder) pushSelectExprs(sel *sqlparser.Select, grouper groupByHandler) error {
resultColumns, err := pb.pushSelectRoutes(sel.SelectExprs)
if err != nil {
return err
}
pb.st.SetResultColumns(resultColumns)
return pb.pushGroupBy(sel, grouper)
}
// pusheSelectRoutes is a convenience function that pushes all the select
// expressions and returns the list of resultColumns generated for it.
func (pb *primitiveBuilder) pushSelectRoutes(selectExprs sqlparser.SelectExprs) ([]*resultColumn, error) {
resultColumns := make([]*resultColumn, 0, len(selectExprs))
for _, node := range selectExprs {
switch node := node.(type) {
case *sqlparser.AliasedExpr:
pullouts, origin, expr, err := pb.findOrigin(node.Expr)
if err != nil {
return nil, err
}
node.Expr = expr
rc, _, err := pb.bldr.PushSelect(node, origin)
if err != nil {
return nil, err
}
resultColumns = append(resultColumns, rc)
pb.addPullouts(pullouts)
case *sqlparser.StarExpr:
var expanded bool
var err error
resultColumns, expanded, err = pb.expandStar(resultColumns, node)
if err != nil {
return nil, err
}
if expanded {
continue
}
// We'll allow select * for simple routes.
rb, ok := pb.bldr.(*route)
if !ok {
return nil, errors.New("unsupported: '*' expression in cross-shard query")
}
// Validate keyspace reference if any.
if !node.TableName.IsEmpty() {
if qual := node.TableName.Qualifier; !qual.IsEmpty() {
if qual.String() != rb.ERoute.Keyspace.Name {
return nil, fmt.Errorf("cannot resolve %s to keyspace %s", sqlparser.String(node), rb.ERoute.Keyspace.Name)
}
}
}
resultColumns = append(resultColumns, rb.PushAnonymous(node))
case sqlparser.Nextval:
rb, ok := pb.bldr.(*route)
if !ok {
// This code is unreachable because the parser doesn't allow joins for next val statements.
return nil, errors.New("unsupported: SELECT NEXT query in cross-shard query")
}
if err := rb.SetOpcode(engine.SelectNext); err != nil {
return nil, err
}
resultColumns = append(resultColumns, rb.PushAnonymous(node))
default:
panic(fmt.Sprintf("BUG: unexpceted select expression type: %T", node))
}
}
return resultColumns, nil
}
// expandStar expands a StarExpr and pushes the expanded
// expressions down if the tables have authoritative column lists.
// If not, it returns false.
// This function breaks the abstraction a bit: it directly sets the
// the Metadata for newly created expressions. In all other cases,
// the Metadata is set through a symtab Find.
func (pb *primitiveBuilder) expandStar(inrcs []*resultColumn, expr *sqlparser.StarExpr) (outrcs []*resultColumn, expanded bool, err error) {
tables := pb.st.AllTables()
if tables == nil {
// no table metadata available.
return inrcs, false, nil
}
if expr.TableName.IsEmpty() {
for _, t := range tables {
// All tables must have authoritative column lists.
if !t.isAuthoritative {
return inrcs, false, nil
}
}
singleTable := false
if len(tables) == 1 {
singleTable = true
}
for _, t := range tables {
for _, col := range t.columnNames {
var expr *sqlparser.AliasedExpr
if singleTable {
// If there's only one table, we use unqualifed column names.
expr = &sqlparser.AliasedExpr{
Expr: &sqlparser.ColName{
Metadata: t.columns[col.Lowered()],
Name: col,
},
}
} else {
// If a and b have id as their column, then
// select * from a join b should result in
// select a.id as id, b.id as id from a join b.
expr = &sqlparser.AliasedExpr{
Expr: &sqlparser.ColName{
Metadata: t.columns[col.Lowered()],
Name: col,
Qualifier: t.alias,
},
As: col,
}
}
rc, _, err := pb.bldr.PushSelect(expr, t.origin)
if err != nil {
// Unreachable because PushSelect won't fail on ColName.
return inrcs, false, err
}
inrcs = append(inrcs, rc)
}
}
return inrcs, true, nil
}
// Expression qualified with table name.
t, err := pb.st.FindTable(expr.TableName)
if err != nil {
return inrcs, false, err
}
if !t.isAuthoritative {
return inrcs, false, nil
}
for _, col := range t.columnNames {
expr := &sqlparser.AliasedExpr{
Expr: &sqlparser.ColName{
Metadata: t.columns[col.Lowered()],
Name: col,
Qualifier: expr.TableName,
},
}
rc, _, err := pb.bldr.PushSelect(expr, t.origin)
if err != nil {
// Unreachable because PushSelect won't fail on ColName.
return inrcs, false, err
}
inrcs = append(inrcs, rc)
}
return inrcs, true, nil
}
// queryTimeout returns DirectiveQueryTimeout value if set, otherwise returns 0.
func queryTimeout(d sqlparser.CommentDirectives) int {
if d == nil {
return 0
}
val, ok := d[sqlparser.DirectiveQueryTimeout]
if !ok {
return 0
}
intVal, ok := val.(int)
if ok {
return intVal
}
return 0
}