Skip to content

Commit f56e64a

Browse files
frouiouisystay
andauthored
[release-15.0] Make sure to not push down expressions when not possible (vitessio#12607) (vitessio#12647)
* [gen4 planner] Make sure to not push down expressions when not possible (vitessio#12607) * Fix random aggregation to not select Null column * stop pushing down projections that should be evaluated at the vtgate level * undo changes to AggregateRandom * clean up code * fix executor test mock Signed-off-by: Florent Poinsard <florent.poinsard@outlook.fr> Signed-off-by: Andres Taylor <andres@planetscale.com> * Fix schema error Signed-off-by: Florent Poinsard <florent.poinsard@outlook.fr> --------- Signed-off-by: Florent Poinsard <florent.poinsard@outlook.fr> Signed-off-by: Andres Taylor <andres@planetscale.com> Co-authored-by: Andres Taylor <andres@planetscale.com>
1 parent f0cfda7 commit f56e64a

File tree

8 files changed

+222
-22
lines changed

8 files changed

+222
-22
lines changed

go/test/endtoend/vtgate/queries/aggregation/aggregation_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func start(t *testing.T) (utils.MySQLCompare, func()) {
3333
deleteAll := func() {
3434
_, _ = utils.ExecAllowError(t, mcmp.VtConn, "set workload = oltp")
3535

36-
tables := []string{"t9", "aggr_test", "t3", "t7_xxhash", "aggr_test_dates", "t7_xxhash_idx", "t1", "t2"}
36+
tables := []string{"t9", "aggr_test", "t3", "t7_xxhash", "aggr_test_dates", "t7_xxhash_idx", "t1", "t2", "t11"}
3737
for _, table := range tables {
3838
_, _ = mcmp.ExecAndIgnore("delete from " + table)
3939
}
@@ -427,3 +427,13 @@ func TestScalarAggregate(t *testing.T) {
427427
mcmp.Exec("insert into aggr_test(id, val1, val2) values(1,'a',1), (2,'A',1), (3,'b',1), (4,'c',3), (5,'c',4)")
428428
mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(distinct val1) from aggr_test", `[[INT64(3)]]`)
429429
}
430+
431+
func TestAggregationRandomOnAnAggregatedValue(t *testing.T) {
432+
mcmp, closer := start(t)
433+
defer closer()
434+
435+
mcmp.Exec("insert into t11(k, a, b) values (0, 100, 10), (10, 200, 20);")
436+
437+
mcmp.AssertMatchesNoOrder("select /*vt+ PLANNER=gen4 */ A.a, A.b, (A.a / A.b) as d from (select sum(a) as a, sum(b) as b from t11 where a = 100) A;",
438+
`[[DECIMAL(100) DECIMAL(10) DECIMAL(10.0000)]]`)
439+
}

go/test/endtoend/vtgate/queries/aggregation/schema.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,8 @@ CREATE TABLE t2 (
7070
PRIMARY KEY (id)
7171
) ENGINE InnoDB;
7272

73+
CREATE TABLE t11 (
74+
k BIGINT PRIMARY KEY,
75+
a INT,
76+
b INT
77+
);

go/test/endtoend/vtgate/queries/aggregation/vschema.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@
123123
"name": "hash"
124124
}
125125
]
126+
},
127+
"t11": {
128+
"column_vindexes": [
129+
{
130+
"column": "k",
131+
"name": "hash"
132+
}
133+
]
126134
}
127135
}
128136
}

go/vt/vtgate/executor_select_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3744,6 +3744,40 @@ func TestSelectHexAndBit(t *testing.T) {
37443744
require.Equal(t, `[[UINT64(10) UINT64(10) UINT64(10) UINT64(10)]]`, fmt.Sprintf("%v", qr.Rows))
37453745
}
37463746

3747+
func TestSelectAggregationRandom(t *testing.T) {
3748+
cell := "aa"
3749+
hc := discovery.NewFakeHealthCheck(nil)
3750+
createSandbox(KsTestSharded).VSchema = executorVSchema
3751+
getSandbox(KsTestUnsharded).VSchema = unshardedVSchema
3752+
serv := newSandboxForCells([]string{cell})
3753+
resolver := newTestResolver(hc, serv, cell)
3754+
shards := []string{"-20", "20-40", "40-60", "60-80", "80-a0", "a0-c0", "c0-e0", "e0-"}
3755+
var conns []*sandboxconn.SandboxConn
3756+
for _, shard := range shards {
3757+
sbc := hc.AddTestTablet(cell, shard, 1, KsTestSharded, shard, topodatapb.TabletType_PRIMARY, true, 1, nil)
3758+
conns = append(conns, sbc)
3759+
3760+
sbc.SetResults([]*sqltypes.Result{sqltypes.MakeTestResult(
3761+
sqltypes.MakeTestFields("a|b", "int64|int64"),
3762+
"null|null",
3763+
)})
3764+
}
3765+
3766+
conns[0].SetResults([]*sqltypes.Result{sqltypes.MakeTestResult(
3767+
sqltypes.MakeTestFields("a|b", "int64|int64"),
3768+
"10|1",
3769+
)})
3770+
3771+
executor := createExecutor(serv, cell, resolver)
3772+
executor.pv = querypb.ExecuteOptions_Gen4
3773+
session := NewAutocommitSession(&vtgatepb.Session{})
3774+
3775+
rs, err := executor.Execute(context.Background(), "TestSelectCFC", session,
3776+
"select /*vt+ PLANNER=gen4 */ A.a, A.b, (A.a / A.b) as c from (select sum(a) as a, sum(b) as b from user) A", nil)
3777+
require.NoError(t, err)
3778+
assert.Equal(t, `[[INT64(10) INT64(1) DECIMAL(10.0000)]]`, fmt.Sprintf("%v", rs.Rows))
3779+
}
3780+
37473781
func TestMain(m *testing.M) {
37483782
_flag.ParseFlagsForTest()
37493783
os.Exit(m.Run())

go/vt/vtgate/planbuilder/abstract/queryprojection.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"strings"
2323

2424
"vitess.io/vitess/go/vt/vtgate/engine"
25+
"vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext"
26+
"vitess.io/vitess/go/vt/vtgate/semantics"
2527

2628
vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
2729
"vitess.io/vitess/go/vt/sqlparser"
@@ -575,6 +577,85 @@ func (qp *QueryProjection) GetColumnCount() int {
575577
return len(qp.SelectExprs) - qp.AddedColumn
576578
}
577579

580+
// NeedsProjecting returns true if we have projections that need to be evaluated at the vtgate level
581+
// and can't be pushed down to MySQL
582+
func (qp *QueryProjection) NeedsProjecting(
583+
ctx *plancontext.PlanningContext,
584+
pusher func(expr *sqlparser.AliasedExpr) (int, error),
585+
) (needsVtGateEval bool, expressions []sqlparser.Expr, colNames []string, err error) {
586+
for _, se := range qp.SelectExprs {
587+
var ae *sqlparser.AliasedExpr
588+
ae, err = se.GetAliasedExpr()
589+
if err != nil {
590+
return false, nil, nil, err
591+
}
592+
593+
expr := ae.Expr
594+
colNames = append(colNames, ae.ColumnName())
595+
596+
if _, isCol := expr.(*sqlparser.ColName); isCol {
597+
offset, err := pusher(ae)
598+
if err != nil {
599+
return false, nil, nil, err
600+
}
601+
expressions = append(expressions, sqlparser.NewOffset(offset, expr))
602+
continue
603+
}
604+
605+
rExpr := sqlparser.Rewrite(sqlparser.CloneExpr(expr), func(cursor *sqlparser.Cursor) bool {
606+
col, isCol := cursor.Node().(*sqlparser.ColName)
607+
if !isCol {
608+
return true
609+
}
610+
var tableInfo semantics.TableInfo
611+
tableInfo, err = ctx.SemTable.TableInfoForExpr(col)
612+
if err != nil {
613+
return true
614+
}
615+
_, isDT := tableInfo.(*semantics.DerivedTable)
616+
if !isDT {
617+
return true
618+
}
619+
620+
var rewritten sqlparser.Expr
621+
rewritten, err = semantics.RewriteDerivedTableExpression(col, tableInfo)
622+
if err != nil {
623+
return false
624+
}
625+
if sqlparser.ContainsAggregation(rewritten) {
626+
offset, tErr := pusher(&sqlparser.AliasedExpr{Expr: col})
627+
if tErr != nil {
628+
err = tErr
629+
return false
630+
}
631+
632+
cursor.Replace(sqlparser.NewOffset(offset, col))
633+
}
634+
return true
635+
}, nil).(sqlparser.Expr)
636+
637+
if err != nil {
638+
return
639+
}
640+
641+
if !sqlparser.EqualsExpr(rExpr, expr) {
642+
// if we changed the expression, it means that we have to evaluate the rest at the vtgate level
643+
expressions = append(expressions, rExpr)
644+
needsVtGateEval = true
645+
continue
646+
}
647+
648+
// we did not need to push any parts of this expression down. Let's check if we can push all of it
649+
offset, err := pusher(ae)
650+
if err != nil {
651+
return false, nil, nil, err
652+
}
653+
expressions = append(expressions, sqlparser.NewOffset(offset, expr))
654+
}
655+
656+
return
657+
}
658+
578659
func checkForInvalidGroupingExpressions(expr sqlparser.Expr) error {
579660
return sqlparser.Walk(func(node sqlparser.SQLNode) (bool, error) {
580661
if _, isAggregate := node.(sqlparser.AggrFunc); isAggregate {

go/vt/vtgate/planbuilder/gen4_planner.go

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -222,13 +222,13 @@ func newBuildSelectPlan(
222222
return nil, nil, err
223223
}
224224

225-
plan = optimizePlan(plan)
226-
227225
plan, err = planHorizon(ctx, plan, selStmt, true)
228226
if err != nil {
229227
return nil, nil, err
230228
}
231229

230+
optimizePlan(plan)
231+
232232
sel, isSel := selStmt.(*sqlparser.Select)
233233
if isSel {
234234
if err := setMiscFunc(plan, sel); err != nil {
@@ -249,25 +249,25 @@ func newBuildSelectPlan(
249249
}
250250

251251
// optimizePlan removes unnecessary simpleProjections that have been created while planning
252-
func optimizePlan(plan logicalPlan) logicalPlan {
253-
newPlan, _ := visit(plan, func(plan logicalPlan) (bool, logicalPlan, error) {
254-
this, ok := plan.(*simpleProjection)
255-
if !ok {
256-
return true, plan, nil
257-
}
252+
func optimizePlan(plan logicalPlan) {
253+
for _, lp := range plan.Inputs() {
254+
optimizePlan(lp)
255+
}
258256

259-
input, ok := this.input.(*simpleProjection)
260-
if !ok {
261-
return true, plan, nil
262-
}
257+
this, ok := plan.(*simpleProjection)
258+
if !ok {
259+
return
260+
}
263261

264-
for i, col := range this.eSimpleProj.Cols {
265-
this.eSimpleProj.Cols[i] = input.eSimpleProj.Cols[col]
266-
}
267-
this.input = input.input
268-
return true, this, nil
269-
})
270-
return newPlan
262+
input, ok := this.input.(*simpleProjection)
263+
if !ok {
264+
return
265+
}
266+
267+
for i, col := range this.eSimpleProj.Cols {
268+
this.eSimpleProj.Cols[i] = input.eSimpleProj.Cols[col]
269+
}
270+
this.input = input.input
271271
}
272272

273273
func gen4UpdateStmtPlanner(

go/vt/vtgate/planbuilder/horizon_planning.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ func (hp *horizonPlanning) planHorizon(ctx *plancontext.PlanningContext, plan lo
5959
// a simpleProjection. We create a new Route that contains the derived table in the
6060
// FROM clause. Meaning that, when we push expressions to the select list of this
6161
// new Route, we do not want them to rewrite them.
62-
if _, isSimpleProj := plan.(*simpleProjection); isSimpleProj {
62+
sp, derivedTable := plan.(*simpleProjection)
63+
if derivedTable {
6364
oldRewriteDerivedExpr := ctx.RewriteDerivedExpr
6465
defer func() {
6566
ctx.RewriteDerivedExpr = oldRewriteDerivedExpr
@@ -74,10 +75,11 @@ func (hp *horizonPlanning) planHorizon(ctx *plancontext.PlanningContext, plan lo
7475
}
7576

7677
needsOrdering := len(hp.qp.OrderExprs) > 0
77-
canShortcut := isRoute && hp.sel.Having == nil && !needsOrdering
7878

7979
// If we still have a HAVING clause, it's because it could not be pushed to the WHERE,
8080
// so it probably has aggregations
81+
canShortcut := isRoute && hp.sel.Having == nil && !needsOrdering
82+
8183
switch {
8284
case hp.qp.NeedsAggregation() || hp.sel.Having != nil:
8385
plan, err = hp.planAggregations(ctx, plan)
@@ -91,6 +93,26 @@ func (hp *horizonPlanning) planHorizon(ctx *plancontext.PlanningContext, plan lo
9193
if err != nil {
9294
return nil, err
9395
}
96+
case derivedTable:
97+
pusher := func(ae *sqlparser.AliasedExpr) (int, error) {
98+
offset, _, err := pushProjection(ctx, ae, sp.input, true, true, false)
99+
return offset, err
100+
}
101+
needsVtGate, projections, colNames, err := hp.qp.NeedsProjecting(ctx, pusher)
102+
if err != nil {
103+
return nil, err
104+
}
105+
if !needsVtGate {
106+
break
107+
}
108+
109+
// there were some expressions we could not push down entirely,
110+
// so replace the simpleProjection with a real projection
111+
plan = &projection{
112+
source: sp.input,
113+
columns: projections,
114+
columnNames: colNames,
115+
}
94116
default:
95117
err = pushProjections(ctx, plan, hp.qp.SelectExprs)
96118
if err != nil {

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4956,5 +4956,45 @@
49564956
"user.user_extra"
49574957
]
49584958
}
4959+
},
4960+
{
4961+
"comment": "Aggregations from derived table used in arithmetic outside derived table",
4962+
"query": "select A.a, A.b, (A.a / A.b) as d from (select sum(a) as a, sum(b) as b from user) A",
4963+
"v3-plan": "unsupported: expression on results of a cross-shard subquery",
4964+
"gen4-plan": {
4965+
"QueryType": "SELECT",
4966+
"Original": "select A.a, A.b, (A.a / A.b) as d from (select sum(a) as a, sum(b) as b from user) A",
4967+
"Instructions": {
4968+
"OperatorType": "Projection",
4969+
"Expressions": [
4970+
"[COLUMN 0] as a",
4971+
"[COLUMN 1] as b",
4972+
"[COLUMN 0] / [COLUMN 1] as d"
4973+
],
4974+
"Inputs": [
4975+
{
4976+
"OperatorType": "Aggregate",
4977+
"Variant": "Scalar",
4978+
"Aggregates": "sum(0) AS a, sum(1) AS b",
4979+
"Inputs": [
4980+
{
4981+
"OperatorType": "Route",
4982+
"Variant": "Scatter",
4983+
"Keyspace": {
4984+
"Name": "user",
4985+
"Sharded": true
4986+
},
4987+
"FieldQuery": "select sum(a) as a, sum(b) as b from `user` where 1 != 1",
4988+
"Query": "select sum(a) as a, sum(b) as b from `user`",
4989+
"Table": "`user`"
4990+
}
4991+
]
4992+
}
4993+
]
4994+
},
4995+
"TablesUsed": [
4996+
"user.user"
4997+
]
4998+
}
49594999
}
49605000
]

0 commit comments

Comments
 (0)