Skip to content

Commit af8f73c

Browse files
[release-18.0] Avoid rewriting unsharded queries and split semantic analysis in two (#15217) (#15229)
Signed-off-by: Andres Taylor <andres@planetscale.com> Signed-off-by: Florent Poinsard <35779988+frouioui@users.noreply.github.com> Co-authored-by: Florent Poinsard <35779988+frouioui@users.noreply.github.com>
1 parent c678773 commit af8f73c

File tree

12 files changed

+529
-347
lines changed

12 files changed

+529
-347
lines changed

go/vt/vtgate/planbuilder/testdata/ddl_cases.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@
260260
"Name": "main",
261261
"Sharded": false
262262
},
263-
"Query": "create view view_a as select col1, col2 from (select col1, col2 from unsharded where id = 1 union select col1, col2 from unsharded where id = 3) as a"
263+
"Query": "create view view_a as select * from (select col1, col2 from unsharded where id = 1 union select col1, col2 from unsharded where id = 3) as a"
264264
},
265265
"TablesUsed": [
266266
"main.view_a"

go/vt/vtgate/planbuilder/testdata/filter_cases.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4062,7 +4062,7 @@
40624062
"Sharded": false
40634063
},
40644064
"FieldQuery": "select col + 2 as a from unsharded where 1 != 1",
4065-
"Query": "select col + 2 as a from unsharded having col + 2 = 42",
4065+
"Query": "select col + 2 as a from unsharded having a = 42",
40664066
"Table": "unsharded"
40674067
},
40684068
"TablesUsed": [

go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,7 +1132,7 @@
11321132
"Sharded": false
11331133
},
11341134
"FieldQuery": "select 1 from u_tbl8 left join u_tbl9 on u_tbl9.col9 = 'foo' where 1 != 1",
1135-
"Query": "select 1 from u_tbl8 left join u_tbl9 on u_tbl9.col9 = 'foo' where (u_tbl8.col8) in ::fkc_vals and u_tbl9.col9 is null limit 1 lock in share mode",
1135+
"Query": "select 1 from u_tbl8 left join u_tbl9 on u_tbl9.col9 = 'foo' where u_tbl9.col9 is null and (u_tbl8.col8) in ::fkc_vals limit 1 lock in share mode",
11361136
"Table": "u_tbl8, u_tbl9"
11371137
},
11381138
{
@@ -1208,7 +1208,7 @@
12081208
"Sharded": false
12091209
},
12101210
"FieldQuery": "select 1 from u_tbl4 left join u_tbl3 on u_tbl3.col3 = 'foo' where 1 != 1",
1211-
"Query": "select 1 from u_tbl4 left join u_tbl3 on u_tbl3.col3 = 'foo' where (u_tbl4.col4) in ::fkc_vals and u_tbl3.col3 is null limit 1 lock in share mode",
1211+
"Query": "select 1 from u_tbl4 left join u_tbl3 on u_tbl3.col3 = 'foo' where u_tbl3.col3 is null and (u_tbl4.col4) in ::fkc_vals limit 1 lock in share mode",
12121212
"Table": "u_tbl3, u_tbl4"
12131213
},
12141214
{
@@ -1220,7 +1220,7 @@
12201220
"Sharded": false
12211221
},
12221222
"FieldQuery": "select 1 from u_tbl4, u_tbl9 where 1 != 1",
1223-
"Query": "select 1 from u_tbl4, u_tbl9 where (u_tbl4.col4) in ::fkc_vals and (u_tbl9.col9) not in (('foo')) and u_tbl4.col4 = u_tbl9.col9 limit 1 lock in share mode",
1223+
"Query": "select 1 from u_tbl4, u_tbl9 where u_tbl4.col4 = u_tbl9.col9 and (u_tbl4.col4) in ::fkc_vals and ('foo' is null or (u_tbl9.col9) not in (('foo'))) and u_tbl4.col4 = u_tbl9.col9 and (u_tbl4.col4) in ::fkc_vals and ('foo' is null or (u_tbl9.col9) not in (('foo'))) limit 1 lock in share mode",
12241224
"Table": "u_tbl4, u_tbl9"
12251225
},
12261226
{
@@ -1297,7 +1297,7 @@
12971297
"Sharded": false
12981298
},
12991299
"FieldQuery": "select 1 from u_tbl4 left join u_tbl3 on u_tbl3.col3 = :v1 where 1 != 1",
1300-
"Query": "select 1 from u_tbl4 left join u_tbl3 on u_tbl3.col3 = :v1 where (u_tbl4.col4) in ::fkc_vals and u_tbl3.col3 is null limit 1 lock in share mode",
1300+
"Query": "select 1 from u_tbl4 left join u_tbl3 on u_tbl3.col3 = :v1 where u_tbl3.col3 is null and (u_tbl4.col4) in ::fkc_vals limit 1 lock in share mode",
13011301
"Table": "u_tbl3, u_tbl4"
13021302
},
13031303
{
@@ -1309,7 +1309,7 @@
13091309
"Sharded": false
13101310
},
13111311
"FieldQuery": "select 1 from u_tbl4, u_tbl9 where 1 != 1",
1312-
"Query": "select 1 from u_tbl4, u_tbl9 where (u_tbl4.col4) in ::fkc_vals and (:v1 is null or (u_tbl9.col9) not in ((:v1))) and u_tbl4.col4 = u_tbl9.col9 limit 1 lock in share mode",
1312+
"Query": "select 1 from u_tbl4, u_tbl9 where u_tbl4.col4 = u_tbl9.col9 and (u_tbl4.col4) in ::fkc_vals and (:v1 is null or (u_tbl9.col9) not in ((:v1))) and u_tbl4.col4 = u_tbl9.col9 and (u_tbl4.col4) in ::fkc_vals and (:v1 is null or (u_tbl9.col9) not in ((:v1))) limit 1 lock in share mode",
13131313
"Table": "u_tbl4, u_tbl9"
13141314
},
13151315
{

go/vt/vtgate/planbuilder/testdata/from_cases.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -711,8 +711,8 @@
711711
"Name": "main",
712712
"Sharded": false
713713
},
714-
"FieldQuery": "select m1.col from unsharded as m1 join unsharded as m2 where 1 != 1",
715-
"Query": "select m1.col from unsharded as m1 join unsharded as m2",
714+
"FieldQuery": "select m1.col from unsharded as m1 straight_join unsharded as m2 where 1 != 1",
715+
"Query": "select m1.col from unsharded as m1 straight_join unsharded as m2",
716716
"Table": "unsharded"
717717
},
718718
"TablesUsed": [
@@ -3989,8 +3989,8 @@
39893989
"Name": "main",
39903990
"Sharded": false
39913991
},
3992-
"FieldQuery": "select A.col1 as col1, A.col2 as col2, B.col2 as col2 from unsharded_authoritative as A left join unsharded_authoritative as B on A.col1 = B.col1 where 1 != 1",
3993-
"Query": "select A.col1 as col1, A.col2 as col2, B.col2 as col2 from unsharded_authoritative as A left join unsharded_authoritative as B on A.col1 = B.col1",
3992+
"FieldQuery": "select * from unsharded_authoritative as A left join unsharded_authoritative as B using (col1) where 1 != 1",
3993+
"Query": "select * from unsharded_authoritative as A left join unsharded_authoritative as B using (col1)",
39943994
"Table": "unsharded_authoritative"
39953995
},
39963996
"TablesUsed": [

go/vt/vtgate/planbuilder/testdata/select_cases.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,8 +1428,8 @@
14281428
"Name": "main",
14291429
"Sharded": false
14301430
},
1431-
"FieldQuery": "select col1, col2 from (select col1, col2 from unsharded where 1 != 1 union select col1, col2 from unsharded where 1 != 1) as a where 1 != 1",
1432-
"Query": "select col1, col2 from (select col1, col2 from unsharded where id = 1 union select col1, col2 from unsharded where id = 3) as a",
1431+
"FieldQuery": "select * from (select col1, col2 from unsharded where 1 != 1 union select col1, col2 from unsharded where 1 != 1) as a where 1 != 1",
1432+
"Query": "select * from (select col1, col2 from unsharded where id = 1 union select col1, col2 from unsharded where id = 3) as a",
14331433
"Table": "unsharded"
14341434
},
14351435
"TablesUsed": [
@@ -2544,7 +2544,7 @@
25442544
"Sharded": false
25452545
},
25462546
"FieldQuery": "select 1 from (select col, count(*) as a from unsharded where 1 != 1 group by col) as f left join unsharded as u on f.col = u.id where 1 != 1",
2547-
"Query": "select 1 from (select col, count(*) as a from unsharded group by col having count(*) > 0 limit 0, 12) as f left join unsharded as u on f.col = u.id",
2547+
"Query": "select 1 from (select col, count(*) as a from unsharded group by col having a > 0 limit 0, 12) as f left join unsharded as u on f.col = u.id",
25482548
"Table": "unsharded"
25492549
},
25502550
"TablesUsed": [

go/vt/vtgate/planbuilder/testdata/union_cases.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -882,7 +882,7 @@
882882
"Sharded": false
883883
},
884884
"FieldQuery": "(select 1 from unsharded where 1 != 1 union select 1 from unsharded where 1 != 1 union all select 1 from unsharded where 1 != 1) union select 1 from unsharded where 1 != 1 union all select 1 from unsharded where 1 != 1",
885-
"Query": "(select 1 from unsharded union select 1 from unsharded union all select 1 from unsharded order by `1` asc) union select 1 from unsharded union all select 1 from unsharded order by `1` asc",
885+
"Query": "(select 1 from unsharded union select 1 from unsharded union all select 1 from unsharded order by 1 asc) union select 1 from unsharded union all select 1 from unsharded order by 1 asc",
886886
"Table": "unsharded"
887887
},
888888
"TablesUsed": [

go/vt/vtgate/semantics/analyzer.go

Lines changed: 89 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,47 +25,60 @@ import (
2525
// analyzer controls the flow of the analysis.
2626
// It starts the tree walking and controls which part of the analysis sees which parts of the tree
2727
type analyzer struct {
28-
scoper *scoper
29-
tables *tableCollector
30-
binder *binder
31-
typer *typer
32-
rewriter *earlyRewriter
33-
sig QuerySignature
28+
scoper *scoper
29+
earlyTables *earlyTableCollector
30+
tables *tableCollector
31+
binder *binder
32+
typer *typer
33+
rewriter *earlyRewriter
34+
sig QuerySignature
35+
si SchemaInformation
36+
currentDb string
3437

3538
err error
3639
inProjection int
3740

38-
projErr error
39-
unshardedErr error
40-
warning string
41+
projErr error
42+
unshardedErr error
43+
warning string
44+
singleUnshardedKeyspace bool
45+
fullAnalysis bool
4146
}
4247

4348
// newAnalyzer create the semantic analyzer
44-
func newAnalyzer(dbName string, si SchemaInformation) *analyzer {
49+
func newAnalyzer(dbName string, si SchemaInformation, fullAnalysis bool) *analyzer {
4550
// TODO dependencies between these components are a little tangled. We should try to clean up
4651
s := newScoper()
4752
a := &analyzer{
48-
scoper: s,
49-
tables: newTableCollector(s, si, dbName),
50-
typer: newTyper(),
53+
scoper: s,
54+
earlyTables: newEarlyTableCollector(si, dbName),
55+
typer: newTyper(),
56+
si: si,
57+
currentDb: dbName,
58+
fullAnalysis: fullAnalysis,
5159
}
5260
s.org = a
53-
a.tables.org = a
61+
return a
62+
}
5463

55-
b := newBinder(s, a, a.tables, a.typer)
56-
a.binder = b
64+
func (a *analyzer) lateInit() {
65+
a.tables = a.earlyTables.newTableCollector(a.scoper, a)
66+
a.binder = newBinder(a.scoper, a, a.tables, a.typer)
67+
a.scoper.binder = a.binder
5768
a.rewriter = &earlyRewriter{
58-
scoper: s,
59-
binder: b,
69+
scoper: a.scoper,
70+
binder: a.binder,
6071
expandedColumns: map[sqlparser.TableName][]*sqlparser.ColName{},
6172
}
62-
s.binder = b
63-
return a
6473
}
6574

6675
// Analyze analyzes the parsed query.
6776
func Analyze(statement sqlparser.Statement, currentDb string, si SchemaInformation) (*SemTable, error) {
68-
analyzer := newAnalyzer(currentDb, newSchemaInfo(si))
77+
return analyseAndGetSemTable(statement, currentDb, si, false)
78+
}
79+
80+
func analyseAndGetSemTable(statement sqlparser.Statement, currentDb string, si SchemaInformation, fullAnalysis bool) (*SemTable, error) {
81+
analyzer := newAnalyzer(currentDb, newSchemaInfo(si), fullAnalysis)
6982

7083
// Analysis for initial scope
7184
err := analyzer.analyze(statement)
@@ -79,7 +92,7 @@ func Analyze(statement sqlparser.Statement, currentDb string, si SchemaInformati
7992

8093
// AnalyzeStrict analyzes the parsed query, and fails the analysis for any possible errors
8194
func AnalyzeStrict(statement sqlparser.Statement, currentDb string, si SchemaInformation) (*SemTable, error) {
82-
st, err := Analyze(statement, currentDb, si)
95+
st, err := analyseAndGetSemTable(statement, currentDb, si, true)
8396
if err != nil {
8497
return nil, err
8598
}
@@ -103,6 +116,27 @@ func (a *analyzer) newSemTable(
103116
if isCommented {
104117
comments = commentedStmt.GetParsedComments()
105118
}
119+
120+
if a.singleUnshardedKeyspace {
121+
return &SemTable{
122+
Tables: a.earlyTables.Tables,
123+
Comments: comments,
124+
Warning: a.warning,
125+
Collation: coll,
126+
ExprTypes: map[sqlparser.Expr]Type{},
127+
NotSingleRouteErr: a.projErr,
128+
NotUnshardedErr: a.unshardedErr,
129+
Recursive: ExprDependencies{},
130+
Direct: ExprDependencies{},
131+
ColumnEqualities: map[columnName][]sqlparser.Expr{},
132+
ExpandedColumns: map[sqlparser.TableName][]*sqlparser.ColName{},
133+
columns: map[*sqlparser.Union]sqlparser.SelectExprs{},
134+
comparator: nil,
135+
StatementIDs: a.scoper.statementIDs,
136+
QuerySignature: QuerySignature{},
137+
}, nil
138+
}
139+
106140
columns := map[*sqlparser.Union]sqlparser.SelectExprs{}
107141
for union, info := range a.tables.unionInfo {
108142
columns[union] = info.exprs
@@ -280,10 +314,43 @@ func (a *analyzer) depsForExpr(expr sqlparser.Expr) (direct, recursive TableSet,
280314
}
281315

282316
func (a *analyzer) analyze(statement sqlparser.Statement) error {
317+
_ = sqlparser.Rewrite(statement, nil, a.earlyUp)
318+
if a.err != nil {
319+
return a.err
320+
}
321+
322+
if a.canShortCut(statement) {
323+
return nil
324+
}
325+
326+
a.lateInit()
327+
283328
_ = sqlparser.Rewrite(statement, a.analyzeDown, a.analyzeUp)
284329
return a.err
285330
}
286331

332+
// canShortCut checks if we are dealing with a single unsharded keyspace and no tables that have managed foreign keys
333+
// if so, we can stop the analyzer early
334+
func (a *analyzer) canShortCut(statement sqlparser.Statement) bool {
335+
if a.fullAnalysis {
336+
return false
337+
}
338+
ks, _ := singleUnshardedKeyspace(a.earlyTables.Tables)
339+
if ks == nil {
340+
return false
341+
}
342+
343+
a.singleUnshardedKeyspace = !sqlparser.IsDMLStatement(statement)
344+
return a.singleUnshardedKeyspace
345+
}
346+
347+
// earlyUp collects tables in the query, so we can check
348+
// if this a single unsharded query we are dealing with
349+
func (a *analyzer) earlyUp(cursor *sqlparser.Cursor) bool {
350+
a.earlyTables.up(cursor)
351+
return true
352+
}
353+
287354
func (a *analyzer) shouldContinue() bool {
288355
return a.err == nil
289356
}

0 commit comments

Comments
 (0)