From daba1a0740c996959b98c0f5049b60626b4864e2 Mon Sep 17 00:00:00 2001 From: Harshit Gangal Date: Fri, 16 Feb 2024 21:57:06 +0530 Subject: [PATCH] Delete with subquery support (#15219) Signed-off-by: Harshit Gangal --- changelog/20.0/20.0.0/summary.md | 8 + .../endtoend/vtgate/queries/dml/dml_test.go | 89 ++++++ go/vt/vtgate/planbuilder/delete.go | 14 - .../planbuilder/operator_transformers.go | 4 + go/vt/vtgate/planbuilder/operators/delete.go | 46 +-- .../planbuilder/operators/dml_planning.go | 4 - go/vt/vtgate/planbuilder/operators/update.go | 15 +- go/vt/vtgate/planbuilder/plan_test.go | 1 + .../planbuilder/testdata/dml_cases.json | 293 +++++++++++++++++- .../testdata/unsupported_cases.json | 20 -- 10 files changed, 427 insertions(+), 67 deletions(-) diff --git a/changelog/20.0/20.0.0/summary.md b/changelog/20.0/20.0.0/summary.md index b30ec93d400..54179429bd5 100644 --- a/changelog/20.0/20.0.0/summary.md +++ b/changelog/20.0/20.0.0/summary.md @@ -7,6 +7,7 @@ - [Vindex Hints](#vindex-hints) - [Update with Limit Support](#update-limit) - [Update with Multi Table Support](#multi-table-update) + - [Delete with Subquery Support](#delete-subquery) - **[Flag changes](#flag-changes)** - [`pprof-http` default change](#pprof-http-default) - **[Minor Changes](#minor-changes)** @@ -45,6 +46,13 @@ Example: `update t1 join t2 on t1.id = t2.id join t3 on t1.col = t3.col set t1.b More details about how it works is available in [MySQL Docs](https://dev.mysql.com/doc/refman/8.0/en/update.html) +#### Delete with Subquery Support + +Support is added for sharded table delete with subquery + +Example: `delete from t1 where id in (select col from t2 where foo = 32 and bar = 43)` + + ### Flag Changes #### `pprof-http` Default Change diff --git a/go/test/endtoend/vtgate/queries/dml/dml_test.go b/go/test/endtoend/vtgate/queries/dml/dml_test.go index f23320a3fe9..561d73f44d5 100644 --- a/go/test/endtoend/vtgate/queries/dml/dml_test.go +++ b/go/test/endtoend/vtgate/queries/dml/dml_test.go @@ -204,3 +204,92 @@ func TestUpdateWithLimit(t *testing.T) { require.EqualValues(t, 0, qr.RowsAffected) } + +// TestMultiTableUpdate executed multi-table update queries +func TestMultiTableUpdate(t *testing.T) { + utils.SkipIfBinaryIsBelowVersion(t, 20, "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(`update order_tbl o join oevent_tbl ev on o.oid = ev.oid set ev.ename = 'a' where ev.oid > 3`) + assert.EqualValues(t, 1, qr.RowsAffected) + + // 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("a")]]`) + + qr = mcmp.Exec(`update order_tbl o, oevent_tbl ev set ev.ename = 'xyz' where o.cust_no = ev.oid`) + 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(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("xyz")] [INT64(3) VARCHAR("a")] [INT64(4) VARCHAR("xyz")]]`) +} + +// TestDeleteWithSubquery executed delete queries with subqueries +func TestDeleteWithSubquery(t *testing.T) { + utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate") + + mcmp, closer := start(t) + defer closer() + + // initial rows + mcmp.Exec("insert into s_tbl(id, num) values (1,10), (2,10), (3,10), (4,20), (5,5), (6,15), (7,17), (8,80)") + mcmp.Exec("insert into order_tbl(region_id, oid, cust_no) values (1,1,4), (1,2,2), (2,3,5), (2,4,55)") + + // check rows + mcmp.AssertMatches(`select id, num from s_tbl order by id`, + `[[INT64(1) INT64(10)] [INT64(2) INT64(10)] [INT64(3) INT64(10)] [INT64(4) INT64(20)] [INT64(5) INT64(5)] [INT64(6) INT64(15)] [INT64(7) INT64(17)] [INT64(8) INT64(80)]]`) + 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)]]`) + + // delete with subquery on s_tbl + qr := mcmp.Exec(`delete from s_tbl where id in (select oid from order_tbl)`) + require.EqualValues(t, 4, qr.RowsAffected) + + // check rows + mcmp.AssertMatches(`select id, num from s_tbl order by id`, + `[[INT64(5) INT64(5)] [INT64(6) INT64(15)] [INT64(7) INT64(17)] [INT64(8) INT64(80)]]`) + 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)]]`) + + // delete with subquery on order_tbl + qr = mcmp.Exec(`delete from order_tbl where cust_no > (select num from s_tbl where id = 7)`) + require.EqualValues(t, 1, qr.RowsAffected) + + // check rows + mcmp.AssertMatches(`select id, num from s_tbl order by id`, + `[[INT64(5) INT64(5)] [INT64(6) INT64(15)] [INT64(7) INT64(17)] [INT64(8) INT64(80)]]`) + 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)]]`) + + // delete with subquery from same table (fails on mysql) - subquery get's merged so fails for vitess + _, err := mcmp.ExecAllowAndCompareError(`delete from s_tbl where id in (select id from s_tbl)`) + require.ErrorContains(t, err, "You can't specify target table 's_tbl' for update in FROM clause (errno 1093) (sqlstate HY000)") + + // delete with subquery from same table (fails on mysql) - subquery not merged so passes for vitess + qr = utils.Exec(t, mcmp.VtConn, `delete from order_tbl where region_id in (select cust_no from order_tbl)`) + require.EqualValues(t, 1, qr.RowsAffected) + + // check rows + utils.AssertMatches(t, mcmp.VtConn, `select id, num from s_tbl order by id`, + `[[INT64(5) INT64(5)] [INT64(6) INT64(15)] [INT64(7) INT64(17)] [INT64(8) INT64(80)]]`) + utils.AssertMatches(t, mcmp.VtConn, `select region_id, oid, cust_no from order_tbl order by oid`, + `[[INT64(1) INT64(1) INT64(4)] [INT64(1) INT64(2) INT64(2)]]`) +} diff --git a/go/vt/vtgate/planbuilder/delete.go b/go/vt/vtgate/planbuilder/delete.go index 3fb4937ff97..a613c158ab7 100644 --- a/go/vt/vtgate/planbuilder/delete.go +++ b/go/vt/vtgate/planbuilder/delete.go @@ -144,19 +144,5 @@ func checkIfDeleteSupported(del *sqlparser.Delete, semTable *semantics.SemTable) return vterrors.VT12001("multi-table DELETE statement with multi-target") } - err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { - switch node.(type) { - case *sqlparser.Subquery, *sqlparser.DerivedTable: - // We have a subquery, so we must fail the planning. - // If this subquery and the table expression were all belonging to the same unsharded keyspace, - // we would have already created a plan for them before doing these checks. - return false, vterrors.VT12001("subqueries in DML") - } - return true, nil - }, del) - if err != nil { - return err - } - return nil } diff --git a/go/vt/vtgate/planbuilder/operator_transformers.go b/go/vt/vtgate/planbuilder/operator_transformers.go index 674849ffc61..577e585351d 100644 --- a/go/vt/vtgate/planbuilder/operator_transformers.go +++ b/go/vt/vtgate/planbuilder/operator_transformers.go @@ -688,6 +688,8 @@ func buildUpdateLogicalPlan( var vindexes []*vindexes.ColumnVindex vQuery := "" if len(upd.ChangedVindexValues) > 0 { + upd.OwnedVindexQuery.From = stmt.GetFrom() + upd.OwnedVindexQuery.Where = stmt.Where vQuery = sqlparser.String(upd.OwnedVindexQuery) vindexes = upd.Target.VTable.ColumnVindexes if upd.OwnedVindexQuery.Limit != nil && len(upd.OwnedVindexQuery.OrderBy) == 0 { @@ -709,6 +711,8 @@ func buildDeleteLogicalPlan(ctx *plancontext.PlanningContext, rb *operators.Rout var vindexes []*vindexes.ColumnVindex vQuery := "" if del.OwnedVindexQuery != nil { + del.OwnedVindexQuery.From = stmt.GetFrom() + del.OwnedVindexQuery.Where = stmt.Where vQuery = sqlparser.String(del.OwnedVindexQuery) vindexes = del.Target.VTable.Owned } diff --git a/go/vt/vtgate/planbuilder/operators/delete.go b/go/vt/vtgate/planbuilder/operators/delete.go index 7a497d5511c..c22da14c35f 100644 --- a/go/vt/vtgate/planbuilder/operators/delete.go +++ b/go/vt/vtgate/planbuilder/operators/delete.go @@ -74,7 +74,15 @@ func (d *Delete) GetOrdering(*plancontext.PlanningContext) []OrderBy { func (d *Delete) ShortDescription() string { ovq := "" if d.OwnedVindexQuery != nil { - ovq = " vindexQuery:%s" + sqlparser.String(d.OwnedVindexQuery) + var cols, orderby, limit string + cols = fmt.Sprintf("COLUMNS: [%s]", sqlparser.String(d.OwnedVindexQuery.SelectExprs)) + if len(d.OwnedVindexQuery.OrderBy) > 0 { + orderby = fmt.Sprintf(" ORDERBY: [%s]", sqlparser.String(d.OwnedVindexQuery.OrderBy)) + } + if d.OwnedVindexQuery.Limit != nil { + limit = fmt.Sprintf(" LIMIT: [%s]", sqlparser.String(d.OwnedVindexQuery.Limit)) + } + ovq = fmt.Sprintf(" vindexQuery(%s%s%s)", cols, orderby, limit) } return fmt.Sprintf("%s.%s%s", d.Target.VTable.Keyspace.Name, d.Target.VTable.Name.String(), ovq) } @@ -90,8 +98,8 @@ func createOperatorFromDelete(ctx *plancontext.PlanningContext, deleteStmt *sqlp } delClone := sqlparser.CloneRefOfDelete(deleteStmt) - delOp := createDeleteOperator(ctx, deleteStmt) - op = delOp + var vTbl *vindexes.Table + op, vTbl = createDeleteOperator(ctx, deleteStmt) if deleteStmt.Comments != nil { op = &LockAndComment{ @@ -105,7 +113,7 @@ func createOperatorFromDelete(ctx *plancontext.PlanningContext, deleteStmt *sqlp return op } - return createFkCascadeOpForDelete(ctx, op, delClone, childFks, delOp.Target.VTable) + return createFkCascadeOpForDelete(ctx, op, delClone, childFks, vTbl) } func deleteWithInputPlanningRequired(childFks []vindexes.ChildFKInfo, deleteStmt *sqlparser.Delete) bool { @@ -170,11 +178,12 @@ func deleteWithInputPlanningForFk(ctx *plancontext.PlanningContext, del *sqlpars } } -func createDeleteOperator(ctx *plancontext.PlanningContext, del *sqlparser.Delete) *Delete { +func createDeleteOperator(ctx *plancontext.PlanningContext, del *sqlparser.Delete) (Operator, *vindexes.Table) { op := crossJoin(ctx, del.TableExprs) + sqc := &SubQueryBuilder{} if del.Where != nil { - op = addWherePredicates(ctx, del.Where.Expr, op) + op = addWherePredsToSubQueryBuilder(ctx, del.Where.Expr, op, sqc) } target := del.Targets[0] @@ -207,9 +216,8 @@ func createDeleteOperator(ctx *plancontext.PlanningContext, del *sqlparser.Delet 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, targetTbl, primaryVindex.Columns) + ovq = generateOwnedVindexQuery(del, targetTbl, primaryVindex.Columns) } } @@ -222,21 +230,18 @@ func createDeleteOperator(ctx *plancontext.PlanningContext, del *sqlparser.Delet }, } - if del.Limit == nil { - return delOp - } - - addOrdering(ctx, del.OrderBy, delOp) - - delOp.Source = &Limit{ - Source: delOp.Source, - AST: del.Limit, + if del.Limit != nil { + addOrdering(ctx, del.OrderBy, delOp) + delOp.Source = &Limit{ + Source: delOp.Source, + AST: del.Limit, + } } - return delOp + return sqc.getRootOperator(delOp, nil), vTbl } -func generateOwnedVindexQuery(tblExpr sqlparser.TableExpr, del *sqlparser.Delete, table TargetTable, ksidCols []sqlparser.IdentifierCI) *sqlparser.Select { +func generateOwnedVindexQuery(del *sqlparser.Delete, table TargetTable, ksidCols []sqlparser.IdentifierCI) *sqlparser.Select { var selExprs sqlparser.SelectExprs for _, col := range ksidCols { colName := makeColName(col, table, sqlparser.MultiTable(del.TableExprs)) @@ -248,11 +253,8 @@ func generateOwnedVindexQuery(tblExpr sqlparser.TableExpr, del *sqlparser.Delete selExprs = append(selExprs, aeWrap(colName)) } } - sqlparser.RemoveKeyspaceInTables(tblExpr) return &sqlparser.Select{ SelectExprs: selExprs, - From: del.TableExprs, - Where: del.Where, OrderBy: del.OrderBy, Limit: del.Limit, Lock: sqlparser.ForUpdateLock, diff --git a/go/vt/vtgate/planbuilder/operators/dml_planning.go b/go/vt/vtgate/planbuilder/operators/dml_planning.go index ed777428381..6f71b41162e 100644 --- a/go/vt/vtgate/planbuilder/operators/dml_planning.go +++ b/go/vt/vtgate/planbuilder/operators/dml_planning.go @@ -109,7 +109,6 @@ func buildChangedVindexesValues( ctx *plancontext.PlanningContext, update *sqlparser.Update, table *vindexes.Table, - ate *sqlparser.AliasedTableExpr, ksidCols []sqlparser.IdentifierCI, assignments []SetExpr, ) (vv map[string]*engine.VindexValues, ownedVindexQuery *sqlparser.Select, subQueriesArgOnChangedVindex []string) { @@ -145,11 +144,8 @@ func buildChangedVindexesValues( return nil, nil, nil } // generate rest of the owned vindex query. - tblExpr := sqlparser.NewAliasedTableExpr(sqlparser.TableName{Name: table.Name}, ate.As.String()) ovq := &sqlparser.Select{ - From: []sqlparser.TableExpr{tblExpr}, SelectExprs: selExprs, - Where: update.Where, OrderBy: update.OrderBy, Limit: update.Limit, Lock: sqlparser.ForUpdateLock, diff --git a/go/vt/vtgate/planbuilder/operators/update.go b/go/vt/vtgate/planbuilder/operators/update.go index 5fc7dc0d4a7..cf55f91fddd 100644 --- a/go/vt/vtgate/planbuilder/operators/update.go +++ b/go/vt/vtgate/planbuilder/operators/update.go @@ -97,7 +97,15 @@ func (u *Update) TablesUsed() []string { func (u *Update) ShortDescription() string { ovq := "" if u.OwnedVindexQuery != nil { - ovq = " vindexQuery:%s" + sqlparser.String(u.OwnedVindexQuery) + var cols, orderby, limit string + cols = fmt.Sprintf("COLUMNS: [%s]", sqlparser.String(u.OwnedVindexQuery.SelectExprs)) + if len(u.OwnedVindexQuery.OrderBy) > 0 { + orderby = fmt.Sprintf(" ORDERBY: [%s]", sqlparser.String(u.OwnedVindexQuery.OrderBy)) + } + if u.OwnedVindexQuery.Limit != nil { + limit = fmt.Sprintf(" LIMIT: [%s]", sqlparser.String(u.OwnedVindexQuery.Limit)) + } + ovq = fmt.Sprintf(" vindexQuery(%s%s%s)", cols, orderby, limit) } return fmt.Sprintf("%s.%s%s", u.Target.VTable.Keyspace.Name, u.Target.VTable.Name.String(), ovq) } @@ -217,7 +225,7 @@ func createUpdateOperator(ctx *plancontext.PlanningContext, updStmt *sqlparser.U Name: name, } - _, cvv, ovq, subQueriesArgOnChangedVindex := getUpdateVindexInformation(ctx, updStmt, targetTbl, tblInfo.GetAliasedTableExpr(), assignments) + _, cvv, ovq, subQueriesArgOnChangedVindex := getUpdateVindexInformation(ctx, updStmt, targetTbl, assignments) updOp := &Update{ DMLCommon: &DMLCommon{ @@ -249,7 +257,6 @@ func getUpdateVindexInformation( ctx *plancontext.PlanningContext, updStmt *sqlparser.Update, table TargetTable, - ate *sqlparser.AliasedTableExpr, assignments []SetExpr, ) ([]*VindexPlusPredicates, map[string]*engine.VindexValues, *sqlparser.Select, []string) { if !table.VTable.Keyspace.Sharded { @@ -257,7 +264,7 @@ func getUpdateVindexInformation( } primaryVindex, vindexAndPredicates := getVindexInformation(table.ID, table.VTable) - changedVindexValues, ownedVindexQuery, subQueriesArgOnChangedVindex := buildChangedVindexesValues(ctx, updStmt, table.VTable, ate, primaryVindex.Columns, assignments) + changedVindexValues, ownedVindexQuery, subQueriesArgOnChangedVindex := buildChangedVindexesValues(ctx, updStmt, table.VTable, primaryVindex.Columns, assignments) return vindexAndPredicates, changedVindexValues, ownedVindexQuery, subQueriesArgOnChangedVindex } diff --git a/go/vt/vtgate/planbuilder/plan_test.go b/go/vt/vtgate/planbuilder/plan_test.go index 8d339eb52ee..e7c18af1d4c 100644 --- a/go/vt/vtgate/planbuilder/plan_test.go +++ b/go/vt/vtgate/planbuilder/plan_test.go @@ -279,6 +279,7 @@ func TestOne(t *testing.T) { lv := loadSchema(t, "vschemas/schema.json", true) setFks(t, lv) addPKs(t, lv, "user", []string{"user", "music"}) + addPKs(t, lv, "main", []string{"unsharded"}) 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 18ba14db06e..6fbc31eb84d 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 u.Id, u.`Name`, u.Costly from `user` as u join ref_with_source as r on u.col = r.col for update", + "OwnedVindexQuery": "select u.Id, u.`Name`, u.Costly from `user` as u, ref_with_source as r where 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 u.Id, u.`Name`, u.Costly from `user` as u join music as m on u.id = m.user_id for update", + "OwnedVindexQuery": "select u.Id, u.`Name`, u.Costly from `user` as u, music as m where 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" }, @@ -5284,7 +5284,7 @@ "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", + "OwnedVindexQuery": "select u.Id, u.`Name`, u.Costly from `user` as u, music as m where m.foo = 42 and u.id = m.user_id for update", "Query": "delete u from `user` as u, music as m where m.foo = 42 and u.id = m.user_id", "Table": "user" }, @@ -5901,5 +5901,292 @@ "user.music" ] } + }, + { + "comment": "sharded subquery in sharded delete", + "query": "delete from user where id = (select id from music where user_id = 1)", + "plan": { + "QueryType": "DELETE", + "Original": "delete from user where id = (select id from music where user_id = 1)", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutValue", + "PulloutVars": [ + "__sq1" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from music where 1 != 1", + "Query": "select id from music where user_id = 1", + "Table": "music", + "Values": [ + "1" + ], + "Vindex": "user_index" + }, + { + "InputName": "Outer", + "OperatorType": "Delete", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "TargetTabletType": "PRIMARY", + "KsidLength": 1, + "KsidVindex": "user_index", + "OwnedVindexQuery": "select Id, `Name`, Costly from `user` where id = :__sq1 for update", + "Query": "delete from `user` where id = :__sq1", + "Table": "user", + "Values": [ + ":__sq1" + ], + "Vindex": "user_index" + } + ] + }, + "TablesUsed": [ + "user.music", + "user.user" + ] + } + }, + { + "comment": "unsharded subquery in sharded delete", + "query": "delete from user where col = (select id from unsharded)", + "plan": { + "QueryType": "DELETE", + "Original": "delete from user where col = (select id from unsharded)", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutValue", + "PulloutVars": [ + "__sq1" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select id from unsharded where 1 != 1", + "Query": "select id from unsharded", + "Table": "unsharded" + }, + { + "InputName": "Outer", + "OperatorType": "Delete", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "TargetTabletType": "PRIMARY", + "KsidLength": 1, + "KsidVindex": "user_index", + "OwnedVindexQuery": "select Id, `Name`, Costly from `user` where col = :__sq1 for update", + "Query": "delete from `user` where col = :__sq1", + "Table": "user" + } + ] + }, + "TablesUsed": [ + "main.unsharded", + "user.user" + ] + } + }, + { + "comment": "sharded subquery in unsharded delete", + "query": "delete from unsharded where col = (select id from user)", + "plan": { + "QueryType": "DELETE", + "Original": "delete from unsharded where col = (select id from user)", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutValue", + "PulloutVars": [ + "__sq1" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from `user` where 1 != 1", + "Query": "select id from `user`", + "Table": "`user`" + }, + { + "InputName": "Outer", + "OperatorType": "Delete", + "Variant": "Unsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "Query": "delete from unsharded where col = :__sq1", + "Table": "unsharded" + } + ] + }, + "TablesUsed": [ + "main.unsharded", + "user.user" + ] + } + }, + { + "comment": "sharded subquery in unsharded subquery in unsharded delete", + "query": "delete from unsharded where col = (select id from unsharded where id = (select id from user))", + "plan": { + "QueryType": "DELETE", + "Original": "delete from unsharded where col = (select id from unsharded where id = (select id from user))", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutValue", + "PulloutVars": [ + "__sq1" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutValue", + "PulloutVars": [ + "__sq2" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from `user` where 1 != 1", + "Query": "select id from `user`", + "Table": "`user`" + }, + { + "InputName": "Outer", + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select id from unsharded where 1 != 1", + "Query": "select id from unsharded where id = :__sq2", + "Table": "unsharded" + } + ] + }, + { + "InputName": "Outer", + "OperatorType": "Delete", + "Variant": "Unsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "Query": "delete from unsharded where col = :__sq1", + "Table": "unsharded" + } + ] + }, + "TablesUsed": [ + "main.unsharded", + "user.user" + ] + } + }, + { + "comment": "sharded join unsharded subquery in unsharded delete", + "query": "delete from unsharded where col = (select id from unsharded join user on unsharded.id = user.id)", + "plan": { + "QueryType": "DELETE", + "Original": "delete from unsharded where col = (select id from unsharded join user on unsharded.id = user.id)", + "Instructions": { + "OperatorType": "UncorrelatedSubquery", + "Variant": "PulloutValue", + "PulloutVars": [ + "__sq1" + ], + "Inputs": [ + { + "InputName": "SubQuery", + "OperatorType": "Join", + "Variant": "Join", + "JoinColumnIndexes": "R:0", + "JoinVars": { + "unsharded_id": 0 + }, + "TableName": "unsharded_`user`", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select unsharded.id from unsharded where 1 != 1", + "Query": "select unsharded.id from unsharded", + "Table": "unsharded" + }, + { + "OperatorType": "Route", + "Variant": "EqualUnique", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select id from `user` where 1 != 1", + "Query": "select id from `user` where `user`.id = :unsharded_id", + "Table": "`user`", + "Values": [ + ":unsharded_id" + ], + "Vindex": "user_index" + } + ] + }, + { + "InputName": "Outer", + "OperatorType": "Delete", + "Variant": "Unsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "Query": "delete from unsharded where col = :__sq1", + "Table": "unsharded" + } + ] + }, + "TablesUsed": [ + "main.unsharded", + "user.user" + ] + } } ] diff --git a/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json b/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json index 4214a396499..5c8bcad0c57 100644 --- a/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/unsupported_cases.json @@ -19,26 +19,6 @@ "query": "select id from user group by id, (select id from user_extra)", "plan": "VT12001: unsupported: subqueries in GROUP BY" }, - { - "comment": "subqueries in delete", - "query": "delete from user where col = (select id from unsharded)", - "plan": "VT12001: unsupported: subqueries in DML" - }, - { - "comment": "sharded subqueries in unsharded delete", - "query": "delete from unsharded where col = (select id from user)", - "plan": "VT12001: unsupported: subqueries in DML" - }, - { - "comment": "sharded subquery in unsharded subquery in unsharded delete", - "query": "delete from unsharded where col = (select id from unsharded where id = (select id from user))", - "plan": "VT12001: unsupported: subqueries in DML" - }, - { - "comment": "sharded join unsharded subqueries in unsharded delete", - "query": "delete from unsharded where col = (select id from unsharded join user on unsharded.id = user.id)", - "plan": "VT12001: unsupported: subqueries in DML" - }, { "comment": "update changes primary vindex column", "query": "update user set id = 1 where id = 1",