Skip to content

Commit ae8d61e

Browse files
systayfrouioui
andauthored
expression rewriting: enable more rewrites and limit CNF rewrites (#14560)
Co-authored-by: Florent Poinsard <florent.poinsard@outlook.fr>
1 parent 4f08558 commit ae8d61e

File tree

4 files changed

+65
-26
lines changed

4 files changed

+65
-26
lines changed

go/vt/sqlparser/predicate_rewriting.go

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,26 @@ import (
2020
"vitess.io/vitess/go/vt/log"
2121
)
2222

23+
// This is the number of OR expressions in a predicate that will disable the CNF
24+
// rewrite because we don't want to send large queries to MySQL
25+
const CNFOrLimit = 5
26+
2327
// RewritePredicate walks the input AST and rewrites any boolean logic into a simpler form
2428
// This simpler form is CNF plus logic for extracting predicates from OR, plus logic for turning ORs into IN
2529
// Note: In order to re-plan, we need to empty the accumulated metadata in the AST,
2630
// so ColName.Metadata will be nil:ed out as part of this rewrite
2731
func RewritePredicate(ast SQLNode) SQLNode {
32+
count := 0
33+
_ = Walk(func(node SQLNode) (bool, error) {
34+
if _, isExpr := node.(*OrExpr); isExpr {
35+
count++
36+
}
37+
38+
return true, nil
39+
}, ast)
40+
41+
allowCNF := count < CNFOrLimit
42+
2843
for {
2944
printExpr(ast)
3045
exprChanged := false
@@ -37,7 +52,7 @@ func RewritePredicate(ast SQLNode) SQLNode {
3752
return true
3853
}
3954

40-
rewritten, state := simplifyExpression(e)
55+
rewritten, state := simplifyExpression(e, allowCNF)
4156
if ch, isChange := state.(changed); isChange {
4257
printRule(ch.rule, ch.exprMatched)
4358
exprChanged = true
@@ -52,12 +67,12 @@ func RewritePredicate(ast SQLNode) SQLNode {
5267
}
5368
}
5469

55-
func simplifyExpression(expr Expr) (Expr, rewriteState) {
70+
func simplifyExpression(expr Expr, allowCNF bool) (Expr, rewriteState) {
5671
switch expr := expr.(type) {
5772
case *NotExpr:
5873
return simplifyNot(expr)
5974
case *OrExpr:
60-
return simplifyOr(expr)
75+
return simplifyOr(expr, allowCNF)
6176
case *XorExpr:
6277
return simplifyXor(expr)
6378
case *AndExpr:
@@ -113,55 +128,66 @@ func ExtractINFromOR(expr *OrExpr) []Expr {
113128
return uniquefy(ins)
114129
}
115130

116-
func simplifyOr(expr *OrExpr) (Expr, rewriteState) {
131+
func simplifyOr(expr *OrExpr, allowCNF bool) (Expr, rewriteState) {
117132
or := expr
118133

119134
// first we search for ANDs and see how they can be simplified
120135
land, lok := or.Left.(*AndExpr)
121136
rand, rok := or.Right.(*AndExpr)
122-
switch {
123-
case lok && rok:
137+
138+
if lok && rok {
124139
// (<> AND <>) OR (<> AND <>)
125140
var a, b, c Expr
126141
var change changed
127142
switch {
128143
case Equals.Expr(land.Left, rand.Left):
129144
change = newChange("(A and B) or (A and C) => A AND (B OR C)", f(expr))
130145
a, b, c = land.Left, land.Right, rand.Right
146+
return &AndExpr{Left: a, Right: &OrExpr{Left: b, Right: c}}, change
131147
case Equals.Expr(land.Left, rand.Right):
132148
change = newChange("(A and B) or (C and A) => A AND (B OR C)", f(expr))
133149
a, b, c = land.Left, land.Right, rand.Left
150+
return &AndExpr{Left: a, Right: &OrExpr{Left: b, Right: c}}, change
134151
case Equals.Expr(land.Right, rand.Left):
135152
change = newChange("(B and A) or (A and C) => A AND (B OR C)", f(expr))
136153
a, b, c = land.Right, land.Left, rand.Right
154+
return &AndExpr{Left: a, Right: &OrExpr{Left: b, Right: c}}, change
137155
case Equals.Expr(land.Right, rand.Right):
138156
change = newChange("(B and A) or (C and A) => A AND (B OR C)", f(expr))
139157
a, b, c = land.Right, land.Left, rand.Left
140-
default:
141-
return expr, noChange{}
158+
return &AndExpr{Left: a, Right: &OrExpr{Left: b, Right: c}}, change
142159
}
143-
return &AndExpr{Left: a, Right: &OrExpr{Left: b, Right: c}}, change
144-
case lok:
145-
// (<> AND <>) OR <>
160+
}
161+
162+
// (<> AND <>) OR <>
163+
if lok {
146164
// Simplification
147165
if Equals.Expr(or.Right, land.Left) || Equals.Expr(or.Right, land.Right) {
148166
return or.Right, newChange("(A AND B) OR A => A", f(expr))
149167
}
150-
// Distribution Law
151-
return &AndExpr{Left: &OrExpr{Left: land.Left, Right: or.Right}, Right: &OrExpr{Left: land.Right, Right: or.Right}},
152-
newChange("(A AND B) OR C => (A OR C) AND (B OR C)", f(expr))
153-
case rok:
154-
// <> OR (<> AND <>)
168+
169+
if allowCNF {
170+
// Distribution Law
171+
return &AndExpr{Left: &OrExpr{Left: land.Left, Right: or.Right}, Right: &OrExpr{Left: land.Right, Right: or.Right}},
172+
newChange("(A AND B) OR C => (A OR C) AND (B OR C)", f(expr))
173+
}
174+
}
175+
176+
// <> OR (<> AND <>)
177+
if rok {
155178
// Simplification
156179
if Equals.Expr(or.Left, rand.Left) || Equals.Expr(or.Left, rand.Right) {
157180
return or.Left, newChange("A OR (A AND B) => A", f(expr))
158181
}
159-
// Distribution Law
160-
return &AndExpr{
161-
Left: &OrExpr{Left: or.Left, Right: rand.Left},
162-
Right: &OrExpr{Left: or.Left, Right: rand.Right},
163-
},
164-
newChange("C OR (A AND B) => (C OR A) AND (C OR B)", f(expr))
182+
183+
if allowCNF {
184+
// Distribution Law
185+
return &AndExpr{
186+
Left: &OrExpr{Left: or.Left, Right: rand.Left},
187+
Right: &OrExpr{Left: or.Left, Right: rand.Right},
188+
},
189+
newChange("C OR (A AND B) => (C OR A) AND (C OR B)", f(expr))
190+
}
165191
}
166192

167193
// next, we want to try to turn multiple ORs into an IN when possible
@@ -257,7 +283,6 @@ func simplifyAnd(expr *AndExpr) (Expr, rewriteState) {
257283
and := expr
258284
if or, ok := and.Left.(*OrExpr); ok {
259285
// Simplification
260-
261286
if Equals.Expr(or.Left, and.Right) {
262287
return and.Right, newChange("(A OR B) AND A => A", f(expr))
263288
}

go/vt/sqlparser/predicate_rewriting_test.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func TestSimplifyExpression(in *testing.T) {
9191
expr, err := ParseExpr(tc.in)
9292
require.NoError(t, err)
9393

94-
expr, didRewrite := simplifyExpression(expr)
94+
expr, didRewrite := simplifyExpression(expr, true)
9595
assert.True(t, didRewrite.changed())
9696
assert.Equal(t, tc.expected, String(expr))
9797
})
@@ -129,6 +129,17 @@ func TestRewritePredicate(in *testing.T) {
129129
}, {
130130
in: "A and (B or A)",
131131
expected: "A",
132+
}, {
133+
in: "(a = 1 and b = 41) or (a = 2 and b = 42)",
134+
// this might look weird, but it allows the planner to either a or b in a vindex operation
135+
expected: "a in (1, 2) and (a = 1 or b = 42) and ((b = 41 or a = 2) and b in (41, 42))",
136+
}, {
137+
in: "(a = 1 and b = 41) or (a = 2 and b = 42) or (a = 3 and b = 43)",
138+
expected: "a in (1, 2, 3) and (a in (1, 2) or b = 43) and ((a = 1 or b = 42 or a = 3) and (a = 1 or b = 42 or b = 43)) and ((b = 41 or a = 2 or a = 3) and (b = 41 or a = 2 or b = 43) and ((b in (41, 42) or a = 3) and b in (41, 42, 43)))",
139+
}, {
140+
// this has too many OR expressions in it, so we don't even try the CNF rewriting
141+
in: "a = 1 and b = 41 or a = 2 and b = 42 or a = 3 and b = 43 or a = 4 and b = 44 or a = 5 and b = 45 or a = 6 and b = 46",
142+
expected: "a = 1 and b = 41 or a = 2 and b = 42 or a = 3 and b = 43 or a = 4 and b = 44 or a = 5 and b = 45 or a = 6 and b = 46",
132143
}}
133144

134145
for _, tc := range tests {
@@ -164,6 +175,9 @@ func TestExtractINFromOR(in *testing.T) {
164175
}, {
165176
in: "(a in (1, 5) and B or C and a in (5, 7))",
166177
expected: "a in (1, 5, 7)",
178+
}, {
179+
in: "(a = 5 and b = 1 or b = 2 and a = 6 or b = 3 and a = 4)",
180+
expected: "<nil>",
167181
}}
168182

169183
for _, tc := range tests {

go/vt/vtgate/executor_select_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1275,7 +1275,7 @@ func TestSelectINFromOR(t *testing.T) {
12751275
_, err := executorExec(ctx, executor, session, "select 1 from user where id = 1 and name = 'apa' or id = 2 and name = 'toto'", nil)
12761276
require.NoError(t, err)
12771277
wantQueries := []*querypb.BoundQuery{{
1278-
Sql: "select 1 from `user` where id = 1 and `name` = 'apa' or id = 2 and `name` = 'toto'",
1278+
Sql: "select 1 from `user` where id in ::__vals and (id = 1 or `name` = 'toto') and (`name` = 'apa' or id = 2) and `name` in ('apa', 'toto')",
12791279
BindVariables: map[string]*querypb.BindVariable{
12801280
"__vals": sqltypes.TestBindVariable([]any{int64(1), int64(2)}),
12811281
},

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4110,7 +4110,7 @@
41104110
"Sharded": true
41114111
},
41124112
"FieldQuery": "select id from `user` where 1 != 1",
4113-
"Query": "select id from `user` where id = 5 and `name` = 'foo' or id = 12 and `name` = 'bar'",
4113+
"Query": "select id from `user` where id in ::__vals and (id = 5 or `name` = 'bar') and (`name` = 'foo' or id = 12) and `name` in ('foo', 'bar')",
41144114
"Table": "`user`",
41154115
"Values": [
41164116
"(5, 12)"

0 commit comments

Comments
 (0)