From 46c7246bf2b9666a1140ec168bc0ff9e71f58011 Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Mon, 25 Dec 2023 00:01:58 +0530 Subject: [PATCH 01/10] initial implementation of engine primitive delete multi table Signed-off-by: Harshit Gangal --- go/vt/vtgate/engine/delete_multi.go | 88 +++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 go/vt/vtgate/engine/delete_multi.go diff --git a/go/vt/vtgate/engine/delete_multi.go b/go/vt/vtgate/engine/delete_multi.go new file mode 100644 index 00000000000..9256afe4644 --- /dev/null +++ b/go/vt/vtgate/engine/delete_multi.go @@ -0,0 +1,88 @@ +/* +Copyright 2023 The Vitess Authors. + +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 engine + +import ( + "context" + + topodatapb "vitess.io/vitess/go/vt/proto/topodata" + "vitess.io/vitess/go/vt/vterrors" + + "vitess.io/vitess/go/sqltypes" + querypb "vitess.io/vitess/go/vt/proto/query" +) + +var _ Primitive = (*DeleteMulti)(nil) + +const DM_VALS = "dm_vals" + +// DeleteMulti represents the instructions to perform a delete. +type DeleteMulti struct { + Delete Primitive + Input Primitive + + txNeeded +} + +func (del *DeleteMulti) RouteType() string { + return "DELETEMULTI" +} + +func (del *DeleteMulti) GetKeyspaceName() string { + return del.Input.GetKeyspaceName() +} + +func (del *DeleteMulti) GetTableName() string { + return del.Input.GetTableName() +} + +func (del *DeleteMulti) Inputs() ([]Primitive, []map[string]any) { + return []Primitive{del.Input, del.Delete}, nil +} + +// TryExecute performs a non-streaming exec. +func (del *DeleteMulti) TryExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, _ bool) (*sqltypes.Result, error) { + inputRes, err := vcursor.ExecutePrimitive(ctx, del.Input, bindVars, false) + if err != nil { + return nil, err + } + // TODO: range over select rows to create delete where condition bind variable. + for range inputRes.Rows { + } + return vcursor.ExecutePrimitive(ctx, del.Delete, bindVars, false) +} + +// TryStreamExecute performs a streaming exec. +func (del *DeleteMulti) TryStreamExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool, callback func(*sqltypes.Result) error) error { + res, err := del.TryExecute(ctx, vcursor, bindVars, wantfields) + if err != nil { + return err + } + return callback(res) +} + +// GetFields fetches the field info. +func (del *DeleteMulti) GetFields(context.Context, VCursor, map[string]*querypb.BindVariable) (*sqltypes.Result, error) { + return nil, vterrors.VT13001("BUG: unreachable code for MULTI DELETE") +} + +func (del *DeleteMulti) description() PrimitiveDescription { + return PrimitiveDescription{ + OperatorType: "DeleteMulti", + TargetTabletType: topodatapb.TabletType_PRIMARY, + } +} From 0d7e9765c265a5b3c78938d026a96f14c7fa5534 Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Mon, 25 Dec 2023 00:02:50 +0530 Subject: [PATCH 02/10] feat: planning of delete multi table Signed-off-by: Harshit Gangal --- go/vt/vtgate/planbuilder/delete_multi.go | 38 +++++ .../planbuilder/operator_transformers.go | 26 +++- go/vt/vtgate/planbuilder/operators/delete.go | 8 +- .../planbuilder/operators/delete_multi.go | 55 +++++++ .../planbuilder/operators/query_planning.go | 52 ++++++- .../planbuilder/operators/route_planning.go | 26 ++-- go/vt/vtgate/planbuilder/plan_test.go | 2 + .../planbuilder/testdata/dml_cases.json | 134 ++++++++++++++++++ .../testdata/unsupported_cases.json | 10 -- 9 files changed, 322 insertions(+), 29 deletions(-) create mode 100644 go/vt/vtgate/planbuilder/delete_multi.go create mode 100644 go/vt/vtgate/planbuilder/operators/delete_multi.go diff --git a/go/vt/vtgate/planbuilder/delete_multi.go b/go/vt/vtgate/planbuilder/delete_multi.go new file mode 100644 index 00000000000..9365835d089 --- /dev/null +++ b/go/vt/vtgate/planbuilder/delete_multi.go @@ -0,0 +1,38 @@ +/* +Copyright 2023 The Vitess Authors. + +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 ( + "vitess.io/vitess/go/vt/vtgate/engine" +) + +type deleteMulti struct { + input logicalPlan + delete logicalPlan +} + +var _ logicalPlan = (*deleteMulti)(nil) + +// Primitive implements the logicalPlan interface +func (d *deleteMulti) Primitive() engine.Primitive { + inp := d.input.Primitive() + del := d.delete.Primitive() + return &engine.DeleteMulti{ + Delete: del, + Input: inp, + } +} diff --git a/go/vt/vtgate/planbuilder/operator_transformers.go b/go/vt/vtgate/planbuilder/operator_transformers.go index 65012e68e02..6891b885a81 100644 --- a/go/vt/vtgate/planbuilder/operator_transformers.go +++ b/go/vt/vtgate/planbuilder/operator_transformers.go @@ -73,11 +73,29 @@ func transformToLogicalPlan(ctx *plancontext.PlanningContext, op operators.Opera return transformHashJoin(ctx, op) case *operators.Sequential: return transformSequential(ctx, op) + case *operators.DeleteMulti: + return transformDeleteMulti(ctx, op) } return nil, vterrors.VT13001(fmt.Sprintf("unknown type encountered: %T (transformToLogicalPlan)", op)) } +func transformDeleteMulti(ctx *plancontext.PlanningContext, op *operators.DeleteMulti) (logicalPlan, error) { + input, err := transformToLogicalPlan(ctx, op.Source) + if err != nil { + return nil, err + } + + del, err := transformToLogicalPlan(ctx, op.Delete) + if err != nil { + return nil, err + } + return &deleteMulti{ + input: input, + delete: del, + }, nil +} + func transformUpsert(ctx *plancontext.PlanningContext, op *operators.Upsert) (logicalPlan, error) { u := &upsert{} for _, source := range op.Sources { @@ -698,11 +716,15 @@ func buildDeleteLogicalPlan(ctx *plancontext.PlanningContext, rb *operators.Rout Query: generateQuery(stmt), TableNames: []string{vtable.Name.String()}, Vindexes: vtable.Owned, - OwnedVindexQuery: del.OwnedVindexQuery, RoutingParameters: rp, } - transformDMLPlan(vtable, edml, rb.Routing, del.OwnedVindexQuery != "") + hasLookupVindex := del.OwnedVindexQuery != nil + if hasLookupVindex { + edml.OwnedVindexQuery = sqlparser.String(del.OwnedVindexQuery) + } + + transformDMLPlan(vtable, edml, rb.Routing, hasLookupVindex) e := &engine.Delete{ DML: edml, diff --git a/go/vt/vtgate/planbuilder/operators/delete.go b/go/vt/vtgate/planbuilder/operators/delete.go index f9934306f38..337e6a5af97 100644 --- a/go/vt/vtgate/planbuilder/operators/delete.go +++ b/go/vt/vtgate/planbuilder/operators/delete.go @@ -28,7 +28,7 @@ import ( type Delete struct { Target TargetTable - OwnedVindexQuery string + OwnedVindexQuery *sqlparser.Select OrderBy sqlparser.OrderBy Limit *sqlparser.Limit Ignore bool @@ -62,7 +62,7 @@ func (d *Delete) Inputs() []Operator { func (d *Delete) SetInputs(inputs []Operator) { if len(inputs) != 1 { - panic(vterrors.VT13001("unexpected number of inputs to Delete operator")) + panic(vterrors.VT13001("unexpected number of inputs for Delete operator")) } d.Source = inputs[0] } @@ -104,7 +104,7 @@ func createOperatorFromDelete(ctx *plancontext.PlanningContext, deleteStmt *sqlp childFks := ctx.SemTable.GetChildForeignKeysList() // If there are no foreign key constraints, then we don't need to do anything. if len(childFks) == 0 { - return + return op } // If the delete statement has a limit, we don't support it yet. if delClone.Limit != nil { @@ -137,7 +137,7 @@ func createDeleteOperator(ctx *plancontext.PlanningContext, del *sqlparser.Delet vTbl = updateQueryGraphWithSource(ctx, op, tblID, vTbl) } - var ovq string + var ovq *sqlparser.Select if vTbl.Keyspace.Sharded && vTbl.Type == vindexes.TypeTable { primaryVindex, _ := getVindexInformation(tblID, vTbl) ate := tblInfo.GetAliasedTableExpr() diff --git a/go/vt/vtgate/planbuilder/operators/delete_multi.go b/go/vt/vtgate/planbuilder/operators/delete_multi.go new file mode 100644 index 00000000000..00006418962 --- /dev/null +++ b/go/vt/vtgate/planbuilder/operators/delete_multi.go @@ -0,0 +1,55 @@ +/* +Copyright 2023 The Vitess Authors. + +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 operators + +import "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" + +type DeleteMulti struct { + Source Operator + Delete Operator + + noColumns + noPredicates +} + +func (d *DeleteMulti) Clone(inputs []Operator) Operator { + newD := *d + newD.SetInputs(inputs) + return &newD +} + +func (d *DeleteMulti) Inputs() []Operator { + return []Operator{d.Source, d.Delete} +} + +func (d *DeleteMulti) SetInputs(inputs []Operator) { + if len(inputs) != 2 { + panic("unexpected number of inputs for DeleteMulti operator") + } + d.Source = inputs[0] + d.Delete = inputs[1] +} + +func (d *DeleteMulti) ShortDescription() string { + return "" +} + +func (d *DeleteMulti) GetOrdering(ctx *plancontext.PlanningContext) []OrderBy { + return nil +} + +var _ Operator = (*DeleteMulti)(nil) diff --git a/go/vt/vtgate/planbuilder/operators/query_planning.go b/go/vt/vtgate/planbuilder/operators/query_planning.go index b2d51c2935e..87ea9279672 100644 --- a/go/vt/vtgate/planbuilder/operators/query_planning.go +++ b/go/vt/vtgate/planbuilder/operators/query_planning.go @@ -22,6 +22,7 @@ import ( "vitess.io/vitess/go/vt/sqlparser" "vitess.io/vitess/go/vt/vterrors" + "vitess.io/vitess/go/vt/vtgate/engine" "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" "vitess.io/vitess/go/vt/vtgate/semantics" ) @@ -127,7 +128,56 @@ func tryPushDelete(in *Delete) (Operator, *ApplyResult) { } return Swap(in, src, "pushed delete under route") case *ApplyJoin: - panic(vterrors.VT12001("multi shard DELETE with join table references")) + if len(in.Target.VTable.PrimaryKey) == 0 { + panic(vterrors.VT09015()) + } + dm := &DeleteMulti{} + var selExprs sqlparser.SelectExprs + var leftComp sqlparser.ValTuple + for _, col := range in.Target.VTable.PrimaryKey { + colName := sqlparser.NewColNameWithQualifier(col.String(), in.Target.Name) + selExprs = append(selExprs, sqlparser.NewAliasedExpr(colName, "")) + leftComp = append(leftComp, colName) + } + + sel := &sqlparser.Select{ + From: nil, + SelectExprs: selExprs, + OrderBy: in.OrderBy, + Limit: in.Limit, + Lock: sqlparser.ForUpdateLock, + } + dm.Source = newHorizon(src, sel) + + var targetTable *Table + _ = Visit(src, func(operator Operator) error { + if tbl, ok := operator.(*Table); ok && tbl.QTable.ID == in.Target.ID { + targetTable = tbl + return io.EOF + } + return nil + }) + if targetTable == nil { + panic(vterrors.VT13001("target DELETE table not ")) + } + compExpr := sqlparser.NewComparisonExpr(sqlparser.InOp, leftComp, sqlparser.ListArg(engine.DM_VALS), nil) + targetQT := targetTable.QTable + qt := &QueryTable{ + ID: targetQT.ID, + Alias: sqlparser.CloneRefOfAliasedTableExpr(targetQT.Alias), + Table: sqlparser.CloneTableName(targetQT.Table), + Predicates: []sqlparser.Expr{compExpr}, + } + + qg := &QueryGraph{Tables: []*QueryTable{qt}} + in.Source = qg + + if in.OwnedVindexQuery != nil { + in.OwnedVindexQuery.Where = sqlparser.NewWhere(sqlparser.WhereClause, compExpr) + } + dm.Delete = in + + return dm, Rewrote("Delete Multi on top of Delete and ApplyJoin") } return in, nil diff --git a/go/vt/vtgate/planbuilder/operators/route_planning.go b/go/vt/vtgate/planbuilder/operators/route_planning.go index 07dbab3bc90..7e2f3c021cf 100644 --- a/go/vt/vtgate/planbuilder/operators/route_planning.go +++ b/go/vt/vtgate/planbuilder/operators/route_planning.go @@ -124,23 +124,25 @@ func buildVindexTableForDML( return vindexTable, routing } -func generateOwnedVindexQuery(tblExpr sqlparser.TableExpr, del *sqlparser.Delete, table *vindexes.Table, ksidCols []sqlparser.IdentifierCI) string { - buf := sqlparser.NewTrackedBuffer(nil) - for idx, col := range ksidCols { - if idx == 0 { - buf.Myprintf("select %v", col) - } else { - buf.Myprintf(", %v", col) - } +func generateOwnedVindexQuery(tblExpr sqlparser.TableExpr, del *sqlparser.Delete, table *vindexes.Table, ksidCols []sqlparser.IdentifierCI) *sqlparser.Select { + var selExprs sqlparser.SelectExprs + for _, col := range ksidCols { + selExprs = append(selExprs, sqlparser.NewAliasedExpr(sqlparser.NewColName(col.String()), "")) } for _, cv := range table.Owned { - for _, column := range cv.Columns { - buf.Myprintf(", %v", column) + for _, col := range cv.Columns { + selExprs = append(selExprs, sqlparser.NewAliasedExpr(sqlparser.NewColName(col.String()), "")) } } sqlparser.RemoveKeyspaceInTables(tblExpr) - buf.Myprintf(" from %v%v%v%v for update", tblExpr, del.Where, del.OrderBy, del.Limit) - return buf.String() + return &sqlparser.Select{ + SelectExprs: selExprs, + From: sqlparser.TableExprs{tblExpr}, + Where: del.Where, + OrderBy: del.OrderBy, + Limit: del.Limit, + Lock: sqlparser.ForUpdateLock, + } } func getUpdateVindexInformation( diff --git a/go/vt/vtgate/planbuilder/plan_test.go b/go/vt/vtgate/planbuilder/plan_test.go index 6ebd71dcf1b..34100211049 100644 --- a/go/vt/vtgate/planbuilder/plan_test.go +++ b/go/vt/vtgate/planbuilder/plan_test.go @@ -62,6 +62,7 @@ func TestPlan(t *testing.T) { TestBuilder: TestBuilder, } testOutputTempDir := makeTestOutput(t) + addPKs(t, vschemaWrapper.V, "user", []string{"user"}) // You will notice that some tests expect user.Id instead of user.id. // This is because we now pre-create vindex columns in the symbol @@ -266,6 +267,7 @@ func TestOne(t *testing.T) { lv := loadSchema(t, "vschemas/schema.json", true) setFks(t, lv) + addPKs(t, lv, "user", []string{"user"}) vschema := &vschemawrapper.VSchemaWrapper{ V: lv, TestBuilder: TestBuilder, diff --git a/go/vt/vtgate/planbuilder/testdata/dml_cases.json b/go/vt/vtgate/planbuilder/testdata/dml_cases.json index 5ca6b034d24..a63ddc4aee2 100644 --- a/go/vt/vtgate/planbuilder/testdata/dml_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/dml_cases.json @@ -5047,5 +5047,139 @@ "user.user" ] } + }, + { + "comment": "multi delete multi table", + "query": "delete user from user join user_extra on user.id = user_extra.id where user.name = 'foo'", + "plan": { + "QueryType": "DELETE", + "Original": "delete user from user join user_extra on user.id = user_extra.id where user.name = 'foo'", + "Instructions": { + "OperatorType": "DeleteMulti", + "TargetTabletType": "PRIMARY", + "Inputs": [ + { + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "L:0", + "JoinVars": { + "user_extra_id": 1 + }, + "TableName": "user_extra_`user`", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select `user`.id, user_extra.id from user_extra where 1 != 1", + "Query": "select `user`.id, user_extra.id from user_extra", + "Table": "user_extra" + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select 1 from `user` where 1 != 1", + "Query": "select 1 from `user` where `user`.`name` = 'foo' and `user`.id = :user_extra_id", + "Table": "`user`", + "Values": [ + ":user_extra_id" + ], + "Vindex": "user_index" + } + ] + }, + { + "OperatorType": "Delete", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "TargetTabletType": "PRIMARY", + "KsidLength": 1, + "KsidVindex": "user_index", + "OwnedVindexQuery": "select Id, `Name`, Costly from `user` where (`user`.id) in ::dm_vals for update", + "Query": "delete from `user` where (`user`.id) in ::dm_vals", + "Table": "user" + } + ] + }, + "TablesUsed": [ + "user.user", + "user.user_extra" + ] + } + }, + { + "comment": "multi delete multi table with alias", + "query": "delete u from user u join music m on u.col = m.col", + "plan": { + "QueryType": "DELETE", + "Original": "delete u from user u join music m on u.col = m.col", + "Instructions": { + "OperatorType": "DeleteMulti", + "TargetTabletType": "PRIMARY", + "Inputs": [ + { + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "L:0", + "JoinVars": { + "u_col": 1 + }, + "TableName": "`user`_music", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select u.id, u.col from `user` as u where 1 != 1", + "Query": "select u.id, u.col from `user` as u", + "Table": "`user`" + }, + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select 1 from music as m where 1 != 1", + "Query": "select 1 from music as m where m.col = :u_col", + "Table": "music" + } + ] + }, + { + "OperatorType": "Delete", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "TargetTabletType": "PRIMARY", + "KsidLength": 1, + "KsidVindex": "user_index", + "OwnedVindexQuery": "select Id, `Name`, Costly from `user` as u where (u.id) in ::dm_vals for update", + "Query": "delete from `user` as u where (u.id) in ::dm_vals", + "Table": "user" + } + ] + }, + "TablesUsed": [ + "user.music", + "user.user" + ] + } } ] diff --git a/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json b/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json index 3765ae9b278..f78e2749899 100644 --- a/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json @@ -394,16 +394,6 @@ "query": "delete u, r from user u join ref_with_source r on u.col = r.col", "plan": "VT12001: unsupported: multi-table DELETE statement in a sharded keyspace" }, - { - "comment": "multi delete multi table", - "query": "delete user from user join user_extra on user.id = user_extra.id where user.name = 'foo'", - "plan": "VT12001: unsupported: multi shard DELETE with join table references" - }, - { - "comment": "multi delete multi table with alias", - "query": "delete u from user u join music m on u.col = m.col", - "plan": "VT12001: unsupported: multi shard DELETE with join table references" - }, { "comment": "reference table delete with join", "query": "delete r from user u join ref_with_source r on u.col = r.col", From b3e9ba04e8d3edaf575abfb9269852df1d9cf7d5 Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Wed, 27 Dec 2023 21:42:45 +0530 Subject: [PATCH 03/10] delete multi engine to use input values to send bindvars, added unit tests Signed-off-by: Harshit Gangal --- go/vt/vtgate/engine/delete_multi.go | 12 +++- go/vt/vtgate/engine/delete_multi_test.go | 70 ++++++++++++++++++++++++ go/vt/vtgate/executor_dml_test.go | 61 +++++++++++++++++++++ go/vt/vtgate/executor_framework_test.go | 4 +- 4 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 go/vt/vtgate/engine/delete_multi_test.go diff --git a/go/vt/vtgate/engine/delete_multi.go b/go/vt/vtgate/engine/delete_multi.go index 9256afe4644..acb2e68f130 100644 --- a/go/vt/vtgate/engine/delete_multi.go +++ b/go/vt/vtgate/engine/delete_multi.go @@ -60,10 +60,16 @@ func (del *DeleteMulti) TryExecute(ctx context.Context, vcursor VCursor, bindVar if err != nil { return nil, err } - // TODO: range over select rows to create delete where condition bind variable. - for range inputRes.Rows { + + bv := &querypb.BindVariable{ + Type: querypb.Type_TUPLE, + } + for _, row := range inputRes.Rows { + bv.Values = append(bv.Values, sqltypes.TupleToProto(row)) } - return vcursor.ExecutePrimitive(ctx, del.Delete, bindVars, false) + return vcursor.ExecutePrimitive(ctx, del.Delete, map[string]*querypb.BindVariable{ + DM_VALS: bv, + }, false) } // TryStreamExecute performs a streaming exec. diff --git a/go/vt/vtgate/engine/delete_multi_test.go b/go/vt/vtgate/engine/delete_multi_test.go new file mode 100644 index 00000000000..d91c23f2d33 --- /dev/null +++ b/go/vt/vtgate/engine/delete_multi_test.go @@ -0,0 +1,70 @@ +/* +Copyright 2023 The Vitess Authors. + +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 engine + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "vitess.io/vitess/go/sqltypes" + "vitess.io/vitess/go/vt/vtgate/vindexes" +) + +func TestDeleteMulti(t *testing.T) { + input := &fakePrimitive{results: []*sqltypes.Result{ + sqltypes.MakeTestResult(sqltypes.MakeTestFields("id", "int64"), "1", "2", "3"), + }} + + del := &DeleteMulti{ + Input: input, + Delete: &Delete{ + DML: &DML{ + RoutingParameters: &RoutingParameters{ + Opcode: Scatter, + Keyspace: &vindexes.Keyspace{ + Name: "ks", + Sharded: true, + }, + }, + Query: "dummy_delete", + }, + }, + } + + vc := newDMLTestVCursor("-20", "20-") + _, err := del.TryExecute(context.Background(), vc, nil, false) + require.NoError(t, err) + vc.ExpectLog(t, []string{ + `ResolveDestinations ks [] Destinations:DestinationAllShards()`, + `ExecuteMultiShard ` + + `ks.-20: dummy_delete {dm_vals: type:TUPLE values:{type:TUPLE value:"\x89\x02\x011"} values:{type:TUPLE value:"\x89\x02\x012"} values:{type:TUPLE value:"\x89\x02\x013"}} ` + + `ks.20-: dummy_delete {dm_vals: type:TUPLE values:{type:TUPLE value:"\x89\x02\x011"} values:{type:TUPLE value:"\x89\x02\x012"} values:{type:TUPLE value:"\x89\x02\x013"}} true false`, + }) + + vc.Rewind() + input.rewind() + err = del.TryStreamExecute(context.Background(), vc, nil, false, func(result *sqltypes.Result) error { return nil }) + require.NoError(t, err) + vc.ExpectLog(t, []string{ + `ResolveDestinations ks [] Destinations:DestinationAllShards()`, + `ExecuteMultiShard ` + + `ks.-20: dummy_delete {dm_vals: type:TUPLE values:{type:TUPLE value:"\x89\x02\x011"} values:{type:TUPLE value:"\x89\x02\x012"} values:{type:TUPLE value:"\x89\x02\x013"}} ` + + `ks.20-: dummy_delete {dm_vals: type:TUPLE values:{type:TUPLE value:"\x89\x02\x011"} values:{type:TUPLE value:"\x89\x02\x012"} values:{type:TUPLE value:"\x89\x02\x013"}} true false`, + }) +} diff --git a/go/vt/vtgate/executor_dml_test.go b/go/vt/vtgate/executor_dml_test.go index 4ef598d2e61..48425c860cc 100644 --- a/go/vt/vtgate/executor_dml_test.go +++ b/go/vt/vtgate/executor_dml_test.go @@ -3017,3 +3017,64 @@ func TestInsertReference(t *testing.T) { _, err = executorExec(ctx, executor, session, "insert into TestExecutor.zip_detail(id, status) values (1, 'CLOSED')", nil) require.NoError(t, err) // Gen4 planner can redirect the query to correct source for update when reference table is involved. } + +func TestDeleteMulti(t *testing.T) { + executor, sbc1, sbc2, sbclookup, ctx := createExecutorEnv(t) + executor.vschema.Keyspaces["TestExecutor"].Tables["user"].PrimaryKey = sqlparser.Columns{sqlparser.NewIdentifierCI("id")} + + logChan := executor.queryLogger.Subscribe("TestDeleteMulti") + defer executor.queryLogger.Unsubscribe(logChan) + + session := &vtgatepb.Session{TargetString: "@primary"} + _, err := executorExec(ctx, executor, session, "delete user from user join music on user.col = music.col where music.user_id = 1", nil) + require.NoError(t, err) + + var dmlVals []*querypb.Value + for i := 0; i < 8; i++ { + dmlVals = append(dmlVals, sqltypes.TupleToProto([]sqltypes.Value{sqltypes.TestValue(sqltypes.Int32, "1")})) + } + + bq := &querypb.BoundQuery{ + Sql: "select 1 from music where music.user_id = 1 and music.col = :user_col", + BindVariables: map[string]*querypb.BindVariable{"user_col": sqltypes.StringBindVariable("foo")}, + } + wantQueries := []*querypb.BoundQuery{ + {Sql: "select `user`.id, `user`.col from `user`", BindVariables: map[string]*querypb.BindVariable{}}, + bq, bq, bq, bq, bq, bq, bq, bq, + {Sql: "select Id, `name` from `user` where (`user`.id) in ::dm_vals for update", BindVariables: map[string]*querypb.BindVariable{"dm_vals": {Type: querypb.Type_TUPLE, Values: dmlVals}}}, + {Sql: "delete from `user` where (`user`.id) in ::dm_vals", BindVariables: map[string]*querypb.BindVariable{"dm_vals": {Type: querypb.Type_TUPLE, Values: dmlVals}}}} + assertQueries(t, sbc1, wantQueries) + + wantQueries = []*querypb.BoundQuery{ + {Sql: "select `user`.id, `user`.col from `user`", BindVariables: map[string]*querypb.BindVariable{}}, + {Sql: "select Id, `name` from `user` where (`user`.id) in ::dm_vals for update", BindVariables: map[string]*querypb.BindVariable{"dm_vals": {Type: querypb.Type_TUPLE, Values: dmlVals}}}, + {Sql: "delete from `user` where (`user`.id) in ::dm_vals", BindVariables: map[string]*querypb.BindVariable{"dm_vals": {Type: querypb.Type_TUPLE, Values: dmlVals}}}, + } + assertQueries(t, sbc2, wantQueries) + + bq = &querypb.BoundQuery{ + Sql: "delete from name_user_map where `name` = :name and user_id = :user_id", + BindVariables: map[string]*querypb.BindVariable{ + "name": sqltypes.StringBindVariable("foo"), + "user_id": sqltypes.Uint64BindVariable(1), + }} + wantQueries = []*querypb.BoundQuery{ + bq, bq, bq, bq, bq, bq, bq, bq, + } + assertQueries(t, sbclookup, wantQueries) + + testQueryLog(t, executor, logChan, "MarkSavepoint", "SAVEPOINT", "savepoint s1", 8) + testQueryLog(t, executor, logChan, "VindexDelete", "DELETE", "delete from name_user_map where `name` = :name and user_id = :user_id", 1) + testQueryLog(t, executor, logChan, "VindexDelete", "DELETE", "delete from name_user_map where `name` = :name and user_id = :user_id", 1) + testQueryLog(t, executor, logChan, "VindexDelete", "DELETE", "delete from name_user_map where `name` = :name and user_id = :user_id", 1) + testQueryLog(t, executor, logChan, "VindexDelete", "DELETE", "delete from name_user_map where `name` = :name and user_id = :user_id", 1) + testQueryLog(t, executor, logChan, "VindexDelete", "DELETE", "delete from name_user_map where `name` = :name and user_id = :user_id", 1) + testQueryLog(t, executor, logChan, "VindexDelete", "DELETE", "delete from name_user_map where `name` = :name and user_id = :user_id", 1) + testQueryLog(t, executor, logChan, "VindexDelete", "DELETE", "delete from name_user_map where `name` = :name and user_id = :user_id", 1) + testQueryLog(t, executor, logChan, "VindexDelete", "DELETE", "delete from name_user_map where `name` = :name and user_id = :user_id", 1) + // select `user`.id, `user`.col from `user` - 8 shard + // select 1 from music where music.user_id = 1 and music.col = :user_col - 8 shards + // select Id, `name` from `user` where (`user`.id) in ::dm_vals for update - 8 shards + // delete from `user` where (`user`.id) in ::dm_vals - 8 shards + testQueryLog(t, executor, logChan, "TestExecute", "DELETE", "delete `user` from `user` join music on `user`.col = music.col where music.user_id = 1", 32) +} diff --git a/go/vt/vtgate/executor_framework_test.go b/go/vt/vtgate/executor_framework_test.go index 831e133770a..01a5aebc6d5 100644 --- a/go/vt/vtgate/executor_framework_test.go +++ b/go/vt/vtgate/executor_framework_test.go @@ -368,8 +368,8 @@ func assertQueries(t *testing.T, sbc *sandboxconn.SandboxConn, wantQueries []*qu } got := query.Sql expected := wantQueries[idx].Sql - utils.MustMatch(t, expected, got) - utils.MustMatch(t, wantQueries[idx].BindVariables, query.BindVariables) + utils.MustMatch(t, expected, got, fmt.Sprintf("query did not match on index: %d", idx)) + utils.MustMatch(t, wantQueries[idx].BindVariables, query.BindVariables, fmt.Sprintf("bind variables did not match on index: %d", idx)) idx++ } } From c96016567743ff0e2251d5820124174900366072 Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Thu, 28 Dec 2023 13:34:59 +0530 Subject: [PATCH 04/10] update sizegen Signed-off-by: Harshit Gangal --- go/vt/vtgate/engine/cached_size.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/go/vt/vtgate/engine/cached_size.go b/go/vt/vtgate/engine/cached_size.go index 93c5e97bd89..19a329047c1 100644 --- a/go/vt/vtgate/engine/cached_size.go +++ b/go/vt/vtgate/engine/cached_size.go @@ -189,6 +189,24 @@ func (cached *Delete) CachedSize(alloc bool) int64 { size += cached.DML.CachedSize(true) return size } +func (cached *DeleteMulti) CachedSize(alloc bool) int64 { + if cached == nil { + return int64(0) + } + size := int64(0) + if alloc { + size += int64(32) + } + // field Delete vitess.io/vitess/go/vt/vtgate/engine.Primitive + if cc, ok := cached.Delete.(cachedObject); ok { + size += cc.CachedSize(true) + } + // field Input vitess.io/vitess/go/vt/vtgate/engine.Primitive + if cc, ok := cached.Input.(cachedObject); ok { + size += cc.CachedSize(true) + } + return size +} func (cached *Distinct) CachedSize(alloc bool) int64 { if cached == nil { return int64(0) From 77500fbeeb237fec0086ee2a09f922b2ca7ffa94 Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Thu, 28 Dec 2023 15:45:59 +0530 Subject: [PATCH 05/10] added summary note Signed-off-by: Harshit Gangal --- changelog/19.0/19.0.0/summary.md | 46 +++++++++++++++++++------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/changelog/19.0/19.0.0/summary.md b/changelog/19.0/19.0.0/summary.md index eafea7b163e..c20ceadfd94 100644 --- a/changelog/19.0/19.0.0/summary.md +++ b/changelog/19.0/19.0.0/summary.md @@ -11,14 +11,14 @@ - **[New Stats](#new-stats)** - [Stream Consolidations](#stream-consolidations) - [Build Version in `/debug/vars`](#build-version-in-debug-vars) - - **[VTGate](#vtgate)** + - **[Planned Reparent Shard](#planned-reparent-shard)** + - [`--tolerable-replication-lag` Sub-flag](#tolerable-repl-lag) + - **[Query Compatibility](#query-compatibility)** + - [Multi Table Delete Support](#multi-table-delete) + - [`SHOW VSCHEMA KEYSPACES` Query](#show-vschema-keyspaces) - [`FOREIGN_KEY_CHECKS` is now a Vitess Aware Variable](#fk-checks-vitess-aware) - **[Vttestserver](#vttestserver)** - [`--vtcombo-bind-host` flag](#vtcombo-bind-host) - - **[Query Compatibility](#query-compatibility)** - - [`SHOW VSCHEMA KEYSPACES` Query](#show-vschema-keyspaces) - - **[Planned Reparent Shard](#planned-reparent-shard)** - - [`--tolerable-replication-lag` Sub-flag](#tolerable-repl-lag) ## Major Changes @@ -29,6 +29,7 @@ upgrading to v19. Vitess will however, continue to support importing from MySQL 5.7 into Vitess even in v19. + ### Deprecations and Deletions - The `MYSQL_FLAVOR` environment variable is now removed from all Docker Images. @@ -39,6 +40,7 @@ Vitess will however, continue to support importing from MySQL 5.7 into Vitess ev `--vreplication_healthcheck_topology_refresh`, `--vreplication_healthcheck_retry_delay`, and `--vreplication_healthcheck_timeout`. - The `--vreplication_tablet_type` flag is now deprecated and ignored. + ### Docker #### New MySQL Image @@ -48,7 +50,8 @@ This lightweight image is a replacement of `vitess/lite` to only run `mysqld`. Several tags are available to let you choose what version of MySQL you want to use: `vitess/mysql:8.0.30`, `vitess/mysql:8.0.34`. -### new stats + +### New Stats #### Stream Consolidations @@ -58,27 +61,34 @@ Prior to 19.0 VTTablet reported how much time non-streaming executions spend wai The build version (e.g., `19.0.0-SNAPSHOT`) has been added to `/debug/vars`, allowing users to programmatically inspect Vitess components' build version at runtime. -### VTGate -#### `FOREIGN_KEY_CHECKS` is now a Vitess Aware Variable +### Planned Reparent Shard -When VTGate receives a query to change the `FOREIGN_KEY_CHECKS` value for a session, instead of sending the value down to MySQL, VTGate now keeps track of the value and changes the queries by adding `SET_VAR(FOREIGN_KEY_CHECKS=On/Off)` style query optimizer hints wherever required. +#### `--tolerable-replication-lag` Sub-flag -### Vttestserver +A new sub-flag `--tolerable-replication-lag` has been added to the command `PlannedReparentShard` that allows users to specify the amount of replication lag that is considered acceptable for a tablet to be eligible for promotion when Vitess makes the choice of a new primary. +This feature is opt-in and not specifying this sub-flag makes Vitess ignore the replication lag entirely. -#### `--vtcombo-bind-host` flag +A new flag in VTOrc with the same name has been added to control the behaviour of the PlannedReparentShard calls that VTOrc issues. -A new flag `--vtcombo-bind-host` has been added to vttestserver that allows the users to configure the bind host that vtcombo uses. This is especially useful when running vttestserver as a docker image and you want to run vtctld commands and look at the vtcombo `/debug/status` dashboard. ### Query Compatibility +#### Multi Table Delete Support + +Support is added for sharded multi-table delete with target on single table using multiple table join. + +Example: `Delete t1 from t1 join t2 on t1.id = t2.id join t3 on t1.col = t3.col where t3.foo = 5 and t2.bar = 7` + +More details about how it works is available in [MySQL Docs](https://dev.mysql.com/doc/refman/8.0/en/delete.html) + #### `SHOW VSCHEMA KEYSPACES` Query A SQL query, `SHOW VSCHEMA KEYSPACES` is now supported in Vitess. This query prints the vschema information for all the keyspaces. It is useful for seeing the foreign key mode, whether the keyspace is sharded, and if there is an error in the VSchema for the keyspace. -An example output of the query looks like - +An example output of the query looks like - ```sql mysql> show vschema keyspaces; +----------+---------+-------------+---------+ @@ -90,12 +100,12 @@ mysql> show vschema keyspaces; 2 rows in set (0.01 sec) ``` -### Planned Reparent Shard +#### `FOREIGN_KEY_CHECKS` is now a Vitess Aware Variable -#### `--tolerable-replication-lag` Sub-flag +When VTGate receives a query to change the `FOREIGN_KEY_CHECKS` value for a session, instead of sending the value down to MySQL, VTGate now keeps track of the value and changes the queries by adding `SET_VAR(FOREIGN_KEY_CHECKS=On/Off)` style query optimizer hints wherever required. -A new sub-flag `--tolerable-replication-lag` has been added to the command `PlannedReparentShard` that allows users to specify the amount of replication lag that is considered acceptable for a tablet to be eligible for promotion when Vitess makes the choice of a new primary. -This feature is opt-in and not specifying this sub-flag makes Vitess ignore the replication lag entirely. +### Vttestserver -A new flag in VTOrc with the same name has been added to control the behaviour of the PlannedReparentShard calls that VTOrc issues. +#### `--vtcombo-bind-host` flag +A new flag `--vtcombo-bind-host` has been added to vttestserver that allows the users to configure the bind host that vtcombo uses. This is especially useful when running vttestserver as a docker image and you want to run vtctld commands and look at the vtcombo `/debug/status` dashboard. \ No newline at end of file From 95266d664df451ffcb72b0632c0c4b9c6df874af Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Thu, 28 Dec 2023 18:26:38 +0530 Subject: [PATCH 06/10] assign dependency for new columns added to have correct push down Signed-off-by: Harshit Gangal --- .../planbuilder/operators/query_planning.go | 5 +- .../planbuilder/testdata/dml_cases.json | 77 +++++++++++++++++-- 2 files changed, 74 insertions(+), 8 deletions(-) diff --git a/go/vt/vtgate/planbuilder/operators/query_planning.go b/go/vt/vtgate/planbuilder/operators/query_planning.go index 87ea9279672..823a7a23e88 100644 --- a/go/vt/vtgate/planbuilder/operators/query_planning.go +++ b/go/vt/vtgate/planbuilder/operators/query_planning.go @@ -99,7 +99,7 @@ func runRewriters(ctx *plancontext.PlanningContext, root Operator) Operator { case *LockAndComment: return pushLockAndComment(in) case *Delete: - return tryPushDelete(in) + return tryPushDelete(ctx, in) default: return in, NoRewrite } @@ -108,7 +108,7 @@ func runRewriters(ctx *plancontext.PlanningContext, root Operator) Operator { return FixedPointBottomUp(root, TableID, visitor, stopAtRoute) } -func tryPushDelete(in *Delete) (Operator, *ApplyResult) { +func tryPushDelete(ctx *plancontext.PlanningContext, in *Delete) (Operator, *ApplyResult) { switch src := in.Source.(type) { case *Route: if in.Limit != nil && !src.IsSingleShardOrByDestination() { @@ -138,6 +138,7 @@ func tryPushDelete(in *Delete) (Operator, *ApplyResult) { colName := sqlparser.NewColNameWithQualifier(col.String(), in.Target.Name) selExprs = append(selExprs, sqlparser.NewAliasedExpr(colName, "")) leftComp = append(leftComp, colName) + ctx.SemTable.Recursive[colName] = in.Target.ID } sel := &sqlparser.Select{ diff --git a/go/vt/vtgate/planbuilder/testdata/dml_cases.json b/go/vt/vtgate/planbuilder/testdata/dml_cases.json index a63ddc4aee2..55498b50e6e 100644 --- a/go/vt/vtgate/planbuilder/testdata/dml_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/dml_cases.json @@ -5061,9 +5061,9 @@ { "OperatorType": "Join", "Variant": "Join", - "JoinColumnIndexes": "L:0", + "JoinColumnIndexes": "R:0", "JoinVars": { - "user_extra_id": 1 + "user_extra_id": 0 }, "TableName": "user_extra_`user`", "Inputs": [ @@ -5074,8 +5074,8 @@ "Name": "user", "Sharded": true }, - "FieldQuery": "select `user`.id, user_extra.id from user_extra where 1 != 1", - "Query": "select `user`.id, user_extra.id from user_extra", + "FieldQuery": "select user_extra.id from user_extra where 1 != 1", + "Query": "select user_extra.id from user_extra", "Table": "user_extra" }, { @@ -5085,8 +5085,8 @@ "Name": "user", "Sharded": true }, - "FieldQuery": "select 1 from `user` where 1 != 1", - "Query": "select 1 from `user` where `user`.`name` = 'foo' and `user`.id = :user_extra_id", + "FieldQuery": "select `user`.id from `user` where 1 != 1", + "Query": "select `user`.id from `user` where `user`.`name` = 'foo' and `user`.id = :user_extra_id", "Table": "`user`", "Values": [ ":user_extra_id" @@ -5181,5 +5181,70 @@ "user.user" ] } + }, + { + "comment": "reverse the join order for delete", + "query": "delete u from music m join user u where u.col = m.col and m.foo = 42", + "plan": { + "QueryType": "DELETE", + "Original": "delete u from music m join user u where u.col = m.col and m.foo = 42", + "Instructions": { + "OperatorType": "DeleteMulti", + "TargetTabletType": "PRIMARY", + "Inputs": [ + { + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "R:0", + "JoinVars": { + "m_col": 0 + }, + "TableName": "music_`user`", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select m.col from music as m where 1 != 1", + "Query": "select m.col from music as m where m.foo = 42", + "Table": "music" + }, + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select u.id from `user` as u where 1 != 1", + "Query": "select u.id from `user` as u where u.col = :m_col", + "Table": "`user`" + } + ] + }, + { + "OperatorType": "Delete", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "TargetTabletType": "PRIMARY", + "KsidLength": 1, + "KsidVindex": "user_index", + "OwnedVindexQuery": "select Id, `Name`, Costly from `user` as u where (u.id) in ::dm_vals for update", + "Query": "delete from `user` as u where (u.id) in ::dm_vals", + "Table": "user" + } + ] + }, + "TablesUsed": [ + "user.music", + "user.user" + ] + } } ] From 1163262a1e9ea3b2cf3c1fa9a419ef9b93fd8608 Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Thu, 28 Dec 2023 21:27:21 +0530 Subject: [PATCH 07/10] modify vindex query for column alias and table expressions based on if multi table query is merged or not Signed-off-by: Harshit Gangal --- .../endtoend/vtgate/queries/dml/dml_test.go | 39 +++++++++++++++++++ go/vt/sqlparser/ast_funcs.go | 8 ++++ go/vt/vtgate/planbuilder/operators/delete.go | 24 ++++++------ .../planbuilder/operators/query_planning.go | 2 +- .../planbuilder/operators/route_planning.go | 19 ++++++--- .../planbuilder/testdata/dml_cases.json | 36 ++++++++++++++--- 6 files changed, 106 insertions(+), 22 deletions(-) diff --git a/go/test/endtoend/vtgate/queries/dml/dml_test.go b/go/test/endtoend/vtgate/queries/dml/dml_test.go index 52a64acaa56..0210bbb0cba 100644 --- a/go/test/endtoend/vtgate/queries/dml/dml_test.go +++ b/go/test/endtoend/vtgate/queries/dml/dml_test.go @@ -20,6 +20,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "vitess.io/vitess/go/test/endtoend/utils" ) func TestMultiEqual(t *testing.T) { @@ -39,3 +41,40 @@ func TestMultiEqual(t *testing.T) { qr = mcmp.Exec("delete from user_tbl where (id, region_id) in ((1,1), (2,4))") assert.EqualValues(t, 1, qr.RowsAffected) } + +// TestMultiTableDelete executed multi-table delete queries +func TestMultiTableDelete(t *testing.T) { + utils.SkipIfBinaryIsBelowVersion(t, 19, "vtgate") + + mcmp, closer := start(t) + defer closer() + + // initial rows + mcmp.Exec("insert into order_tbl(region_id, oid, cust_no) values (1,1,4), (1,2,2), (2,3,5), (2,4,55)") + mcmp.Exec("insert into oevent_tbl(oid, ename) values (1,'a'), (2,'b'), (3,'a'), (4,'c')") + + // check rows + mcmp.AssertMatches(`select region_id, oid, cust_no from order_tbl order by oid`, + `[[INT64(1) INT64(1) INT64(4)] [INT64(1) INT64(2) INT64(2)] [INT64(2) INT64(3) INT64(5)] [INT64(2) INT64(4) INT64(55)]]`) + mcmp.AssertMatches(`select oid, ename from oevent_tbl order by oid`, + `[[INT64(1) VARCHAR("a")] [INT64(2) VARCHAR("b")] [INT64(3) VARCHAR("a")] [INT64(4) VARCHAR("c")]]`) + + // multi table delete + qr := mcmp.Exec(`delete o from order_tbl o join oevent_tbl ev where o.oid = ev.oid and ev.ename = 'a'`) + assert.EqualValues(t, 2, qr.RowsAffected) + + // check rows + mcmp.AssertMatches(`select region_id, oid, cust_no from order_tbl order by oid`, + `[[INT64(1) INT64(2) INT64(2)] [INT64(2) INT64(4) INT64(55)]]`) + mcmp.AssertMatches(`select oid, ename from oevent_tbl order by oid`, + `[[INT64(1) VARCHAR("a")] [INT64(2) VARCHAR("b")] [INT64(3) VARCHAR("a")] [INT64(4) VARCHAR("c")]]`) + + qr = mcmp.Exec(`delete o from order_tbl o join oevent_tbl ev where o.cust_no = ev.oid`) + assert.EqualValues(t, 1, qr.RowsAffected) + + // check rows + mcmp.AssertMatches(`select region_id, oid, cust_no from order_tbl order by oid`, + `[[INT64(2) INT64(4) INT64(55)]]`) + mcmp.AssertMatches(`select oid, ename from oevent_tbl order by oid`, + `[[INT64(1) VARCHAR("a")] [INT64(2) VARCHAR("b")] [INT64(3) VARCHAR("a")] [INT64(4) VARCHAR("c")]]`) +} diff --git a/go/vt/sqlparser/ast_funcs.go b/go/vt/sqlparser/ast_funcs.go index 1de529c973b..54195ed435c 100644 --- a/go/vt/sqlparser/ast_funcs.go +++ b/go/vt/sqlparser/ast_funcs.go @@ -2551,3 +2551,11 @@ func (node *Delete) isSingleAliasExpr() bool { _, isAliasExpr := node.TableExprs[0].(*AliasedTableExpr) return isAliasExpr } + +func (node TableExprs) MultiTable() bool { + if len(node) > 1 { + return true + } + _, singleTbl := node[0].(*AliasedTableExpr) + return !singleTbl +} diff --git a/go/vt/vtgate/planbuilder/operators/delete.go b/go/vt/vtgate/planbuilder/operators/delete.go index 337e6a5af97..8761b55a82d 100644 --- a/go/vt/vtgate/planbuilder/operators/delete.go +++ b/go/vt/vtgate/planbuilder/operators/delete.go @@ -137,26 +137,28 @@ func createDeleteOperator(ctx *plancontext.PlanningContext, del *sqlparser.Delet vTbl = updateQueryGraphWithSource(ctx, op, tblID, vTbl) } + name, err := tblInfo.Name() + if err != nil { + panic(err) + } + + targetTbl := TargetTable{ + ID: tblID, + VTable: vTbl, + Name: name, + } + var ovq *sqlparser.Select if vTbl.Keyspace.Sharded && vTbl.Type == vindexes.TypeTable { primaryVindex, _ := getVindexInformation(tblID, vTbl) ate := tblInfo.GetAliasedTableExpr() if len(vTbl.Owned) > 0 { - ovq = generateOwnedVindexQuery(ate, del, vTbl, primaryVindex.Columns) + ovq = generateOwnedVindexQuery(ate, del, targetTbl, primaryVindex.Columns) } } - name, err := tblInfo.Name() - if err != nil { - panic(err) - } - return &Delete{ - Target: TargetTable{ - ID: tblID, - VTable: vTbl, - Name: name, - }, + Target: targetTbl, Source: op, Ignore: bool(del.Ignore), Limit: del.Limit, diff --git a/go/vt/vtgate/planbuilder/operators/query_planning.go b/go/vt/vtgate/planbuilder/operators/query_planning.go index 823a7a23e88..e6cf77b04fc 100644 --- a/go/vt/vtgate/planbuilder/operators/query_planning.go +++ b/go/vt/vtgate/planbuilder/operators/query_planning.go @@ -142,7 +142,6 @@ func tryPushDelete(ctx *plancontext.PlanningContext, in *Delete) (Operator, *App } sel := &sqlparser.Select{ - From: nil, SelectExprs: selExprs, OrderBy: in.OrderBy, Limit: in.Limit, @@ -174,6 +173,7 @@ func tryPushDelete(ctx *plancontext.PlanningContext, in *Delete) (Operator, *App in.Source = qg if in.OwnedVindexQuery != nil { + in.OwnedVindexQuery.From = sqlparser.TableExprs{targetQT.Alias} in.OwnedVindexQuery.Where = sqlparser.NewWhere(sqlparser.WhereClause, compExpr) } dm.Delete = in diff --git a/go/vt/vtgate/planbuilder/operators/route_planning.go b/go/vt/vtgate/planbuilder/operators/route_planning.go index 7e2f3c021cf..bae8ed33625 100644 --- a/go/vt/vtgate/planbuilder/operators/route_planning.go +++ b/go/vt/vtgate/planbuilder/operators/route_planning.go @@ -124,20 +124,22 @@ func buildVindexTableForDML( return vindexTable, routing } -func generateOwnedVindexQuery(tblExpr sqlparser.TableExpr, del *sqlparser.Delete, table *vindexes.Table, ksidCols []sqlparser.IdentifierCI) *sqlparser.Select { +func generateOwnedVindexQuery(tblExpr sqlparser.TableExpr, del *sqlparser.Delete, table TargetTable, ksidCols []sqlparser.IdentifierCI) *sqlparser.Select { var selExprs sqlparser.SelectExprs for _, col := range ksidCols { - selExprs = append(selExprs, sqlparser.NewAliasedExpr(sqlparser.NewColName(col.String()), "")) + colName := makeColName(col, table, del.TableExprs.MultiTable()) + selExprs = append(selExprs, sqlparser.NewAliasedExpr(colName, "")) } - for _, cv := range table.Owned { + for _, cv := range table.VTable.Owned { for _, col := range cv.Columns { - selExprs = append(selExprs, sqlparser.NewAliasedExpr(sqlparser.NewColName(col.String()), "")) + colName := makeColName(col, table, del.TableExprs.MultiTable()) + selExprs = append(selExprs, sqlparser.NewAliasedExpr(colName, "")) } } sqlparser.RemoveKeyspaceInTables(tblExpr) return &sqlparser.Select{ SelectExprs: selExprs, - From: sqlparser.TableExprs{tblExpr}, + From: del.TableExprs, Where: del.Where, OrderBy: del.OrderBy, Limit: del.Limit, @@ -145,6 +147,13 @@ func generateOwnedVindexQuery(tblExpr sqlparser.TableExpr, del *sqlparser.Delete } } +func makeColName(col sqlparser.IdentifierCI, table TargetTable, isMultiTbl bool) *sqlparser.ColName { + if isMultiTbl { + return sqlparser.NewColNameWithQualifier(col.String(), table.Name) + } + return sqlparser.NewColName(col.String()) +} + func getUpdateVindexInformation( ctx *plancontext.PlanningContext, updStmt *sqlparser.Update, diff --git a/go/vt/vtgate/planbuilder/testdata/dml_cases.json b/go/vt/vtgate/planbuilder/testdata/dml_cases.json index 55498b50e6e..ffdf89c09f2 100644 --- a/go/vt/vtgate/planbuilder/testdata/dml_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/dml_cases.json @@ -5012,7 +5012,7 @@ "TargetTabletType": "PRIMARY", "KsidLength": 1, "KsidVindex": "user_index", - "OwnedVindexQuery": "select Id, `Name`, Costly from `user` as u for update", + "OwnedVindexQuery": "select u.Id, u.`Name`, u.Costly from `user` as u join ref_with_source as r on u.col = r.col for update", "Query": "delete u from `user` as u, ref_with_source as r where u.col = r.col", "Table": "user" }, @@ -5038,7 +5038,7 @@ "TargetTabletType": "PRIMARY", "KsidLength": 1, "KsidVindex": "user_index", - "OwnedVindexQuery": "select Id, `Name`, Costly from `user` as u for update", + "OwnedVindexQuery": "select u.Id, u.`Name`, u.Costly from `user` as u join music as m on u.id = m.user_id for update", "Query": "delete u from `user` as u, music as m where u.id = m.user_id", "Table": "user" }, @@ -5105,7 +5105,7 @@ "TargetTabletType": "PRIMARY", "KsidLength": 1, "KsidVindex": "user_index", - "OwnedVindexQuery": "select Id, `Name`, Costly from `user` where (`user`.id) in ::dm_vals for update", + "OwnedVindexQuery": "select `user`.Id, `user`.`Name`, `user`.Costly from `user` where (`user`.id) in ::dm_vals for update", "Query": "delete from `user` where (`user`.id) in ::dm_vals", "Table": "user" } @@ -5170,7 +5170,7 @@ "TargetTabletType": "PRIMARY", "KsidLength": 1, "KsidVindex": "user_index", - "OwnedVindexQuery": "select Id, `Name`, Costly from `user` as u where (u.id) in ::dm_vals for update", + "OwnedVindexQuery": "select u.Id, u.`Name`, u.Costly from `user` as u where (u.id) in ::dm_vals for update", "Query": "delete from `user` as u where (u.id) in ::dm_vals", "Table": "user" } @@ -5235,7 +5235,7 @@ "TargetTabletType": "PRIMARY", "KsidLength": 1, "KsidVindex": "user_index", - "OwnedVindexQuery": "select Id, `Name`, Costly from `user` as u where (u.id) in ::dm_vals for update", + "OwnedVindexQuery": "select u.Id, u.`Name`, u.Costly from `user` as u where (u.id) in ::dm_vals for update", "Query": "delete from `user` as u where (u.id) in ::dm_vals", "Table": "user" } @@ -5246,5 +5246,31 @@ "user.user" ] } + }, + { + "comment": "multi table delete with join on vindex column", + "query": "delete u from user u join music m where u.id = m.user_id and m.foo = 42", + "plan": { + "QueryType": "DELETE", + "Original": "delete u from user u join music m where u.id = m.user_id and m.foo = 42", + "Instructions": { + "OperatorType": "Delete", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "TargetTabletType": "PRIMARY", + "KsidLength": 1, + "KsidVindex": "user_index", + "OwnedVindexQuery": "select u.Id, u.`Name`, u.Costly from `user` as u join music as m where u.id = m.user_id and m.foo = 42 for update", + "Query": "delete u from `user` as u, music as m where m.foo = 42 and u.id = m.user_id", + "Table": "user" + }, + "TablesUsed": [ + "user.music", + "user.user" + ] + } } ] From 363feafa80823f459f4a116986350ec690b3fec7 Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Fri, 29 Dec 2023 00:05:25 +0530 Subject: [PATCH 08/10] update test expectation Signed-off-by: Harshit Gangal --- go/vt/vtgate/executor_dml_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go/vt/vtgate/executor_dml_test.go b/go/vt/vtgate/executor_dml_test.go index 48425c860cc..3d48e96dabc 100644 --- a/go/vt/vtgate/executor_dml_test.go +++ b/go/vt/vtgate/executor_dml_test.go @@ -3041,13 +3041,13 @@ func TestDeleteMulti(t *testing.T) { wantQueries := []*querypb.BoundQuery{ {Sql: "select `user`.id, `user`.col from `user`", BindVariables: map[string]*querypb.BindVariable{}}, bq, bq, bq, bq, bq, bq, bq, bq, - {Sql: "select Id, `name` from `user` where (`user`.id) in ::dm_vals for update", BindVariables: map[string]*querypb.BindVariable{"dm_vals": {Type: querypb.Type_TUPLE, Values: dmlVals}}}, + {Sql: "select `user`.Id, `user`.`name` from `user` where (`user`.id) in ::dm_vals for update", BindVariables: map[string]*querypb.BindVariable{"dm_vals": {Type: querypb.Type_TUPLE, Values: dmlVals}}}, {Sql: "delete from `user` where (`user`.id) in ::dm_vals", BindVariables: map[string]*querypb.BindVariable{"dm_vals": {Type: querypb.Type_TUPLE, Values: dmlVals}}}} assertQueries(t, sbc1, wantQueries) wantQueries = []*querypb.BoundQuery{ {Sql: "select `user`.id, `user`.col from `user`", BindVariables: map[string]*querypb.BindVariable{}}, - {Sql: "select Id, `name` from `user` where (`user`.id) in ::dm_vals for update", BindVariables: map[string]*querypb.BindVariable{"dm_vals": {Type: querypb.Type_TUPLE, Values: dmlVals}}}, + {Sql: "select `user`.Id, `user`.`name` from `user` where (`user`.id) in ::dm_vals for update", BindVariables: map[string]*querypb.BindVariable{"dm_vals": {Type: querypb.Type_TUPLE, Values: dmlVals}}}, {Sql: "delete from `user` where (`user`.id) in ::dm_vals", BindVariables: map[string]*querypb.BindVariable{"dm_vals": {Type: querypb.Type_TUPLE, Values: dmlVals}}}, } assertQueries(t, sbc2, wantQueries) From d58b4aa1e8fb682b83e7acaea63cdeb2067b7319 Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Wed, 3 Jan 2024 13:27:00 +0530 Subject: [PATCH 09/10] added additional test Signed-off-by: Harshit Gangal --- go/vt/vtgate/planbuilder/plan_test.go | 4 +- .../planbuilder/testdata/dml_cases.json | 132 ++++++++++++++++++ 2 files changed, 134 insertions(+), 2 deletions(-) diff --git a/go/vt/vtgate/planbuilder/plan_test.go b/go/vt/vtgate/planbuilder/plan_test.go index 34100211049..7bf16178b75 100644 --- a/go/vt/vtgate/planbuilder/plan_test.go +++ b/go/vt/vtgate/planbuilder/plan_test.go @@ -62,7 +62,7 @@ func TestPlan(t *testing.T) { TestBuilder: TestBuilder, } testOutputTempDir := makeTestOutput(t) - addPKs(t, vschemaWrapper.V, "user", []string{"user"}) + addPKs(t, vschemaWrapper.V, "user", []string{"user", "music"}) // You will notice that some tests expect user.Id instead of user.id. // This is because we now pre-create vindex columns in the symbol @@ -267,7 +267,7 @@ func TestOne(t *testing.T) { lv := loadSchema(t, "vschemas/schema.json", true) setFks(t, lv) - addPKs(t, lv, "user", []string{"user"}) + addPKs(t, lv, "user", []string{"user", "music"}) vschema := &vschemawrapper.VSchemaWrapper{ V: lv, TestBuilder: TestBuilder, diff --git a/go/vt/vtgate/planbuilder/testdata/dml_cases.json b/go/vt/vtgate/planbuilder/testdata/dml_cases.json index ffdf89c09f2..36bd0943bce 100644 --- a/go/vt/vtgate/planbuilder/testdata/dml_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/dml_cases.json @@ -5272,5 +5272,137 @@ "user.user" ] } + }, + { + "comment": "delete 3 way join with sharding key and primary key same", + "query": "delete u from user u join music m on u.col = m.col join user_extra ue on m.user_id = ue.user_id where ue.foo = 20 and u.col = 30 and m.bar = 40", + "plan": { + "QueryType": "DELETE", + "Original": "delete u from user u join music m on u.col = m.col join user_extra ue on m.user_id = ue.user_id where ue.foo = 20 and u.col = 30 and m.bar = 40", + "Instructions": { + "OperatorType": "DeleteMulti", + "TargetTabletType": "PRIMARY", + "Inputs": [ + { + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "L:0", + "JoinVars": { + "u_col": 1 + }, + "TableName": "`user`_music, user_extra", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select u.id, u.col from `user` as u where 1 != 1", + "Query": "select u.id, u.col from `user` as u where u.col = 30", + "Table": "`user`" + }, + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select 1 from music as m, user_extra as ue where 1 != 1", + "Query": "select 1 from music as m, user_extra as ue where m.bar = 40 and m.col = :u_col and ue.foo = 20 and m.user_id = ue.user_id", + "Table": "music, user_extra" + } + ] + }, + { + "OperatorType": "Delete", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "TargetTabletType": "PRIMARY", + "KsidLength": 1, + "KsidVindex": "user_index", + "OwnedVindexQuery": "select u.Id, u.`Name`, u.Costly from `user` as u where (u.id) in ::dm_vals for update", + "Query": "delete from `user` as u where (u.id) in ::dm_vals", + "Table": "user" + } + ] + }, + "TablesUsed": [ + "user.music", + "user.user", + "user.user_extra" + ] + } + }, + { + "comment": "delete 3 way join with sharding key and primary key different", + "query": "delete m from user u join music m on u.col = m.col join user_extra ue on m.user_id = ue.user_id where ue.foo = 20 and u.col = 30 and m.bar = 40", + "plan": { + "QueryType": "DELETE", + "Original": "delete m from user u join music m on u.col = m.col join user_extra ue on m.user_id = ue.user_id where ue.foo = 20 and u.col = 30 and m.bar = 40", + "Instructions": { + "OperatorType": "DeleteMulti", + "TargetTabletType": "PRIMARY", + "Inputs": [ + { + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "R:0", + "JoinVars": { + "u_col": 0 + }, + "TableName": "`user`_music, user_extra", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select u.col from `user` as u where 1 != 1", + "Query": "select u.col from `user` as u where u.col = 30", + "Table": "`user`" + }, + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select m.id from music as m, user_extra as ue where 1 != 1", + "Query": "select m.id from music as m, user_extra as ue where m.bar = 40 and m.col = :u_col and ue.foo = 20 and m.user_id = ue.user_id", + "Table": "music, user_extra" + } + ] + }, + { + "OperatorType": "Delete", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "TargetTabletType": "PRIMARY", + "KsidLength": 1, + "KsidVindex": "user_index", + "OwnedVindexQuery": "select m.user_id, m.id from music as m where (m.id) in ::dm_vals for update", + "Query": "delete from music as m where (m.id) in ::dm_vals", + "Table": "music" + } + ] + }, + "TablesUsed": [ + "user.music", + "user.user", + "user.user_extra" + ] + } } ] From 5ea9ea50d6a376c3a3aa1276bbd17930637dd46b Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Wed, 3 Jan 2024 20:29:45 +0530 Subject: [PATCH 10/10] refactor: little refactor and address review comments Signed-off-by: Harshit Gangal --- go/vt/vtgate/engine/delete_multi.go | 2 +- .../planbuilder/operators/query_planning.go | 133 +++++++++--------- 2 files changed, 71 insertions(+), 64 deletions(-) diff --git a/go/vt/vtgate/engine/delete_multi.go b/go/vt/vtgate/engine/delete_multi.go index acb2e68f130..d38eeed56af 100644 --- a/go/vt/vtgate/engine/delete_multi.go +++ b/go/vt/vtgate/engine/delete_multi.go @@ -83,7 +83,7 @@ func (del *DeleteMulti) TryStreamExecute(ctx context.Context, vcursor VCursor, b // GetFields fetches the field info. func (del *DeleteMulti) GetFields(context.Context, VCursor, map[string]*querypb.BindVariable) (*sqltypes.Result, error) { - return nil, vterrors.VT13001("BUG: unreachable code for MULTI DELETE") + return nil, vterrors.VT13001("unreachable code for MULTI DELETE") } func (del *DeleteMulti) description() PrimitiveDescription { diff --git a/go/vt/vtgate/planbuilder/operators/query_planning.go b/go/vt/vtgate/planbuilder/operators/query_planning.go index e6cf77b04fc..8137ec502a4 100644 --- a/go/vt/vtgate/planbuilder/operators/query_planning.go +++ b/go/vt/vtgate/planbuilder/operators/query_planning.go @@ -111,77 +111,84 @@ func runRewriters(ctx *plancontext.PlanningContext, root Operator) Operator { func tryPushDelete(ctx *plancontext.PlanningContext, in *Delete) (Operator, *ApplyResult) { switch src := in.Source.(type) { case *Route: - if in.Limit != nil && !src.IsSingleShardOrByDestination() { - panic(vterrors.VT12001("multi shard DELETE with LIMIT")) - } - - switch r := src.Routing.(type) { - case *SequenceRouting: - // Sequences are just unsharded routes - src.Routing = &AnyShardRouting{ - keyspace: r.keyspace, - } - case *AnyShardRouting: - // References would have an unsharded source - // Alternates are not required. - r.Alternates = nil - } - return Swap(in, src, "pushed delete under route") + return pushDeleteUnderRoute(in, src) case *ApplyJoin: - if len(in.Target.VTable.PrimaryKey) == 0 { - panic(vterrors.VT09015()) - } - dm := &DeleteMulti{} - var selExprs sqlparser.SelectExprs - var leftComp sqlparser.ValTuple - for _, col := range in.Target.VTable.PrimaryKey { - colName := sqlparser.NewColNameWithQualifier(col.String(), in.Target.Name) - selExprs = append(selExprs, sqlparser.NewAliasedExpr(colName, "")) - leftComp = append(leftComp, colName) - ctx.SemTable.Recursive[colName] = in.Target.ID - } - - sel := &sqlparser.Select{ - SelectExprs: selExprs, - OrderBy: in.OrderBy, - Limit: in.Limit, - Lock: sqlparser.ForUpdateLock, - } - dm.Source = newHorizon(src, sel) - - var targetTable *Table - _ = Visit(src, func(operator Operator) error { - if tbl, ok := operator.(*Table); ok && tbl.QTable.ID == in.Target.ID { - targetTable = tbl - return io.EOF - } - return nil - }) - if targetTable == nil { - panic(vterrors.VT13001("target DELETE table not ")) - } - compExpr := sqlparser.NewComparisonExpr(sqlparser.InOp, leftComp, sqlparser.ListArg(engine.DM_VALS), nil) - targetQT := targetTable.QTable - qt := &QueryTable{ - ID: targetQT.ID, - Alias: sqlparser.CloneRefOfAliasedTableExpr(targetQT.Alias), - Table: sqlparser.CloneTableName(targetQT.Table), - Predicates: []sqlparser.Expr{compExpr}, + return pushDeleteUnderJoin(ctx, in, src) + } + return in, nil +} + +func pushDeleteUnderRoute(in *Delete, src *Route) (Operator, *ApplyResult) { + if in.Limit != nil && !src.IsSingleShardOrByDestination() { + panic(vterrors.VT12001("multi shard DELETE with LIMIT")) + } + + switch r := src.Routing.(type) { + case *SequenceRouting: + // Sequences are just unsharded routes + src.Routing = &AnyShardRouting{ + keyspace: r.keyspace, } + case *AnyShardRouting: + // References would have an unsharded source + // Alternates are not required. + r.Alternates = nil + } + return Swap(in, src, "pushed delete under route") +} - qg := &QueryGraph{Tables: []*QueryTable{qt}} - in.Source = qg +func pushDeleteUnderJoin(ctx *plancontext.PlanningContext, in *Delete, src Operator) (Operator, *ApplyResult) { + if len(in.Target.VTable.PrimaryKey) == 0 { + panic(vterrors.VT09015()) + } + dm := &DeleteMulti{} + var selExprs sqlparser.SelectExprs + var leftComp sqlparser.ValTuple + for _, col := range in.Target.VTable.PrimaryKey { + colName := sqlparser.NewColNameWithQualifier(col.String(), in.Target.Name) + selExprs = append(selExprs, sqlparser.NewAliasedExpr(colName, "")) + leftComp = append(leftComp, colName) + ctx.SemTable.Recursive[colName] = in.Target.ID + } - if in.OwnedVindexQuery != nil { - in.OwnedVindexQuery.From = sqlparser.TableExprs{targetQT.Alias} - in.OwnedVindexQuery.Where = sqlparser.NewWhere(sqlparser.WhereClause, compExpr) + sel := &sqlparser.Select{ + SelectExprs: selExprs, + OrderBy: in.OrderBy, + Limit: in.Limit, + Lock: sqlparser.ForUpdateLock, + } + dm.Source = newHorizon(src, sel) + + var targetTable *Table + _ = Visit(src, func(operator Operator) error { + if tbl, ok := operator.(*Table); ok && tbl.QTable.ID == in.Target.ID { + targetTable = tbl + return io.EOF } - dm.Delete = in + return nil + }) + if targetTable == nil { + panic(vterrors.VT13001("target DELETE table not found")) + } + compExpr := sqlparser.NewComparisonExpr(sqlparser.InOp, leftComp, sqlparser.ListArg(engine.DM_VALS), nil) + targetQT := targetTable.QTable + qt := &QueryTable{ + ID: targetQT.ID, + Alias: sqlparser.CloneRefOfAliasedTableExpr(targetQT.Alias), + Table: sqlparser.CloneTableName(targetQT.Table), + Predicates: []sqlparser.Expr{compExpr}, + } - return dm, Rewrote("Delete Multi on top of Delete and ApplyJoin") + qg := &QueryGraph{Tables: []*QueryTable{qt}} + in.Source = qg + + if in.OwnedVindexQuery != nil { + in.OwnedVindexQuery.From = sqlparser.TableExprs{targetQT.Alias} + in.OwnedVindexQuery.Where = sqlparser.NewWhere(sqlparser.WhereClause, compExpr) } + dm.Delete = in - return in, nil + return dm, Rewrote("Delete Multi on top of Delete and ApplyJoin") } func pushLockAndComment(l *LockAndComment) (Operator, *ApplyResult) {