diff --git a/parser/ast/dml.go b/parser/ast/dml.go index 3264d318e843a..14119d4cf3eae 100644 --- a/parser/ast/dml.go +++ b/parser/ast/dml.go @@ -282,6 +282,11 @@ type TableName struct { TableSample *TableSample // AS OF is used to see the data as it was at a specific point in time. AsOf *AsOfClause + // IsAlias is true if this table name is an alias. + // sometime, we need to distinguish the table name is an alias or not. + // for example ```delete tt1 from t1 tt1,(select max(id) id from t2)tt2 where tt1.id<=tt2.id``` + // ```tt1``` is a alias name. so we need to set IsAlias to true and restore the table name without database name. + IsAlias bool } func (*TableName) resultSet() {} @@ -293,7 +298,7 @@ func (n *TableName) restoreName(ctx *format.RestoreCtx) { if n.Schema.String() != "" { ctx.WriteName(n.Schema.String()) ctx.WritePlain(".") - } else if ctx.DefaultDB != "" { + } else if ctx.DefaultDB != "" && !n.IsAlias { // Try CTE, for a CTE table name, we shouldn't write the database name. if !ctx.IsCTETableName(n.Name.L) { ctx.WriteName(ctx.DefaultDB) diff --git a/planner/core/preprocess.go b/planner/core/preprocess.go index 7bd0c99176f6b..c513bec4d9d35 100644 --- a/planner/core/preprocess.go +++ b/planner/core/preprocess.go @@ -543,9 +543,17 @@ func (p *preprocessor) checkBindGrammar(originNode, hintedNode ast.StmtNode, def tn.TableInfo = tableInfo tn.DBInfo = dbInfo } +<<<<<<< HEAD:planner/core/preprocess.go originSQL := parser.Normalize(utilparser.RestoreWithDefaultDB(originNode, defaultDB, originNode.Text())) hintedSQL := parser.Normalize(utilparser.RestoreWithDefaultDB(hintedNode, defaultDB, hintedNode.Text())) +======= + aliasChecker := &aliasChecker{} + originNode.Accept(aliasChecker) + hintedNode.Accept(aliasChecker) + originSQL := parser.NormalizeForBinding(utilparser.RestoreWithDefaultDB(originNode, defaultDB, originNode.Text()), false) + hintedSQL := parser.NormalizeForBinding(utilparser.RestoreWithDefaultDB(hintedNode, defaultDB, hintedNode.Text()), false) +>>>>>>> 4fded5f4384 (planner: fix cannot binding for deleting multi table with alias (#57255)):pkg/planner/core/preprocess.go if originSQL != hintedSQL { p.err = errors.Errorf("hinted sql and origin sql don't match when hinted sql erase the hint info, after erase hint info, originSQL:%s, hintedSQL:%s", originSQL, hintedSQL) } @@ -1931,3 +1939,59 @@ func (p *preprocessor) skipLockMDL() bool { // skip lock mdl for ANALYZE statement. return p.flag&inAnalyze > 0 } + +// aliasChecker is used to check the alias of the table in delete statement. +// +// for example: delete tt1 from t1 tt1,(select max(id) id from t2)tt2 where tt1.id<=tt2.id +// `delete tt1` will be transformed to `delete current_database.t1` by default. +// because `tt1` cannot be used as alias in delete statement. +// so we have to set `tt1` as alias by aliasChecker. +type aliasChecker struct{} + +func (*aliasChecker) Enter(in ast.Node) (ast.Node, bool) { + if deleteStmt, ok := in.(*ast.DeleteStmt); ok { + // 1. check the tableRefs of deleteStmt to find the alias + var aliases []*pmodel.CIStr + if deleteStmt.TableRefs != nil && deleteStmt.TableRefs.TableRefs != nil { + tableRefs := deleteStmt.TableRefs.TableRefs + if val := getTableRefsAlias(tableRefs.Left); val != nil { + aliases = append(aliases, val) + } + if val := getTableRefsAlias(tableRefs.Right); val != nil { + aliases = append(aliases, val) + } + } + // 2. check the Tables to tag the alias + if deleteStmt.Tables != nil && deleteStmt.Tables.Tables != nil { + for _, table := range deleteStmt.Tables.Tables { + if table.Schema.String() != "" { + continue + } + for _, alias := range aliases { + if table.Name.L == alias.L { + table.IsAlias = true + break + } + } + } + } + return in, true + } + return in, false +} + +func getTableRefsAlias(tableRefs ast.ResultSetNode) *pmodel.CIStr { + switch v := tableRefs.(type) { + case *ast.Join: + if v.Left != nil { + return getTableRefsAlias(v.Left) + } + case *ast.TableSource: + return &v.AsName + } + return nil +} + +func (*aliasChecker) Leave(in ast.Node) (ast.Node, bool) { + return in, true +} diff --git a/planner/core/preprocess_test.go b/planner/core/preprocess_test.go index 661df73289f6f..6a7f5a3c5a302 100644 --- a/planner/core/preprocess_test.go +++ b/planner/core/preprocess_test.go @@ -423,3 +423,14 @@ func TestPreprocessCTE(t *testing.T) { require.Equal(t, tc.after, rs.String()) } } + +func TestPreprocessDeleteFromWithAlias(t *testing.T) { + // https://github.com/pingcap/tidb/issues/56726 + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("create table t1(id int);") + tk.MustExec(" create table t2(id int);") + tk.MustExec("delete tt1 from t1 tt1,(select max(id) id from t2)tt2 where tt1.id<=tt2.id;") + tk.MustExec("create global binding for delete tt1 from t1 tt1,(select max(id) id from t2)tt2 where tt1.id<=tt2.id using delete /*+ MAX_EXECUTION_TIME(10)*/ tt1 from t1 tt1,(select max(id) id from t2)tt2 where tt1.id<=tt2.id;") +}