diff --git a/pkg/bindinfo/BUILD.bazel b/pkg/bindinfo/BUILD.bazel index 653e4a8ed7030..2eccea1b4c8b3 100644 --- a/pkg/bindinfo/BUILD.bazel +++ b/pkg/bindinfo/BUILD.bazel @@ -51,11 +51,11 @@ go_test( "bind_cache_test.go", "binding_match_test.go", "capture_test.go", + "fuzzy_binding_test.go", "global_handle_test.go", "main_test.go", "optimize_test.go", "session_handle_test.go", - "universal_binding_test.go", ], embed = [":bindinfo"], flaky = True, diff --git a/pkg/bindinfo/bind_record.go b/pkg/bindinfo/bind_record.go index a93579e49ef30..4bbeea99d38fe 100644 --- a/pkg/bindinfo/bind_record.go +++ b/pkg/bindinfo/bind_record.go @@ -20,6 +20,7 @@ import ( "github.com/pingcap/tidb/pkg/metrics" "github.com/pingcap/tidb/pkg/parser" + "github.com/pingcap/tidb/pkg/parser/ast" "github.com/pingcap/tidb/pkg/sessionctx" "github.com/pingcap/tidb/pkg/types" "github.com/pingcap/tidb/pkg/util/hack" @@ -85,6 +86,9 @@ type Binding struct { PlanDigest string // Type indicates the type of this binding, currently only 2 types: "" for normal and "u" for universal bindings. Type string + + // TableNames records all schema and table names in this binding statement, which are used for fuzzy matching. + TableNames []*ast.TableName `json:"-"` } func (b *Binding) isSame(rb *Binding) bool { @@ -188,7 +192,12 @@ func (br *BindRecord) prepareHints(sctx sessionctx.Context) error { continue } dbName := br.Db - if bind.Type == TypeUniversal { + bindingStmt, err := p.ParseOneStmt(bind.BindSQL, bind.Charset, bind.Collation) + if err != nil { + return err + } + isFuzzy := isFuzzyBinding(bindingStmt) + if isFuzzy { dbName = "*" // ues '*' for universal bindings } @@ -196,7 +205,7 @@ func (br *BindRecord) prepareHints(sctx sessionctx.Context) error { if err != nil { return err } - if sctx != nil && bind.Type == TypeNormal { + if sctx != nil && bind.Type == TypeNormal && !isFuzzy { paramChecker := ¶mMarkerChecker{} stmt.Accept(paramChecker) if !paramChecker.hasParamMarker { diff --git a/pkg/bindinfo/binding_match.go b/pkg/bindinfo/binding_match.go index b0e9ae6d85967..b8159db06109b 100644 --- a/pkg/bindinfo/binding_match.go +++ b/pkg/bindinfo/binding_match.go @@ -52,14 +52,14 @@ func getBindRecord(ctx sessionctx.Context, stmt ast.StmtNode) (*BindRecord, stri } // the priority: session normal > session universal > global normal > global universal sessionHandle := ctx.Value(SessionBindInfoKeyType).(SessionBindingHandle) - if bindRecord, err := sessionHandle.MatchSessionBinding(ctx.GetSessionVars().CurrentDB, stmt); err == nil && bindRecord != nil && bindRecord.HasEnabledBinding() { + if bindRecord, err := sessionHandle.MatchSessionBinding(ctx, stmt); err == nil && bindRecord != nil && bindRecord.HasEnabledBinding() { return bindRecord, metrics.ScopeSession, nil } globalHandle := GetGlobalBindingHandle(ctx) if globalHandle == nil { return nil, "", nil } - if bindRecord, err := globalHandle.MatchGlobalBinding(ctx.GetSessionVars().CurrentDB, stmt); err == nil && bindRecord != nil && bindRecord.HasEnabledBinding() { + if bindRecord, err := globalHandle.MatchGlobalBinding(ctx, stmt); err == nil && bindRecord != nil && bindRecord.HasEnabledBinding() { return bindRecord, metrics.ScopeGlobal, nil } return nil, "", nil @@ -77,11 +77,17 @@ func eraseLastSemicolon(stmt ast.StmtNode) { // For normal bindings, DB name will be completed automatically: // // e.g. `select * from t where a in (1, 2, 3)` --> `select * from test.t where a in (...)` -func normalizeStmt(stmtNode ast.StmtNode, specifiedDB string) (stmt ast.StmtNode, normalizedStmt, sqlDigest string, err error) { +func normalizeStmt(stmtNode ast.StmtNode, specifiedDB string, fuzzy bool) (stmt ast.StmtNode, normalizedStmt, sqlDigest string, err error) { normalize := func(n ast.StmtNode) (normalizedStmt, sqlDigest string) { eraseLastSemicolon(n) var digest *parser.Digest - normalizedStmt, digest = parser.NormalizeDigestForBinding(utilparser.RestoreWithDefaultDB(n, specifiedDB, n.Text())) + var normalizedSQL string + if !fuzzy { + normalizedSQL = utilparser.RestoreWithDefaultDB(n, specifiedDB, n.Text()) + } else { + normalizedSQL = utilparser.RestoreWithoutDB(n) + } + normalizedStmt, digest = parser.NormalizeDigestForBinding(normalizedSQL) return normalizedStmt, digest.String() } @@ -131,6 +137,37 @@ func normalizeStmt(stmtNode ast.StmtNode, specifiedDB string) (stmt ast.StmtNode return nil, "", "", nil } +func fuzzyMatchBindingTableName(currentDB string, stmtTableNames, bindingTableNames []*ast.TableName) (numWildcards int, matched bool) { + if len(stmtTableNames) != len(bindingTableNames) { + return 0, false + } + for i := range stmtTableNames { + if stmtTableNames[i].Name.L != bindingTableNames[i].Name.L { + return 0, false + } + if bindingTableNames[i].Schema.L == "*" { + numWildcards++ + } + if bindingTableNames[i].Schema.L == stmtTableNames[i].Schema.L || // exactly same, or + (stmtTableNames[i].Schema.L == "" && bindingTableNames[i].Schema.L == currentDB) || // equal to the current DB, or + bindingTableNames[i].Schema.L == "*" { // fuzzy match successfully + continue + } + return 0, false + } + return numWildcards, true +} + +// isFuzzyBinding checks whether the stmtNode is a fuzzy binding. +func isFuzzyBinding(stmt ast.Node) bool { + for _, t := range CollectTableNames(stmt) { + if t.Schema.L == "*" { + return true + } + } + return false +} + // CollectTableNames gets all table names from ast.Node. // This function is mainly for binding fuzzy matching. // ** the return is read-only. diff --git a/pkg/bindinfo/capture_test.go b/pkg/bindinfo/capture_test.go index 8b47755ece39f..eab225b6a93a1 100644 --- a/pkg/bindinfo/capture_test.go +++ b/pkg/bindinfo/capture_test.go @@ -329,7 +329,7 @@ func TestBindingSource(t *testing.T) { tk.MustExec("create global binding for select * from t where a > 10 using select * from t ignore index(idx_a) where a > 10") bindHandle := dom.BindHandle() stmt, _, _ := internal.UtilNormalizeWithDefaultDB(t, "select * from t where a > ?") - bindData, err := bindHandle.MatchGlobalBinding("test", stmt) + bindData, err := bindHandle.MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.NotNil(t, bindData) require.Equal(t, "select * from `test` . `t` where `a` > ?", bindData.OriginalSQL) @@ -350,7 +350,7 @@ func TestBindingSource(t *testing.T) { tk.MustExec("admin capture bindings") bindHandle.CaptureBaselines() stmt, _, _ = internal.UtilNormalizeWithDefaultDB(t, "select * from t where a < ?") - bindData, err = bindHandle.MatchGlobalBinding("test", stmt) + bindData, err = bindHandle.MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.NotNil(t, bindData) require.Equal(t, "select * from `test` . `t` where `a` < ?", bindData.OriginalSQL) diff --git a/pkg/bindinfo/universal_binding_test.go b/pkg/bindinfo/fuzzy_binding_test.go similarity index 64% rename from pkg/bindinfo/universal_binding_test.go rename to pkg/bindinfo/fuzzy_binding_test.go index a1f1752d123a3..beefde59aae04 100644 --- a/pkg/bindinfo/universal_binding_test.go +++ b/pkg/bindinfo/fuzzy_binding_test.go @@ -52,8 +52,7 @@ func removeAllBindings(tk *testkit.TestKit, global bool) { tk.MustQuery(fmt.Sprintf("show %v bindings", scope)).Check(testkit.Rows()) // empty } -func TestUniversalBindingBasic(t *testing.T) { - t.Skip("skip it temporarily") +func TestFuzzyBindingBasic(t *testing.T) { store := testkit.CreateMockStore(t) tk1 := testkit.NewTestKit(t, store) @@ -66,28 +65,22 @@ func TestUniversalBindingBasic(t *testing.T) { tk1.MustExec(`use test2`) tk1.MustExec(`create table t (a int, b int, c int, d int, e int, key(a), key(b), key(c), key(d), key(e))`) - for _, hasForStmt := range []bool{true, false} { - for _, scope := range []string{"", "session", "global"} { - tk := testkit.NewTestKit(t, store) - for _, idx := range []string{"a", "b", "c", "d", "e"} { - tk.MustExec("use test") - forStmt := "for select * from t" - if !hasForStmt { - forStmt = "" - } - tk.MustExec(fmt.Sprintf(`create %v universal binding %v using select /*+ use_index(t, %v) */ * from t`, scope, forStmt, idx)) - for _, useDB := range []string{"test", "test1", "test2"} { - tk.MustExec("use " + useDB) - for _, testDB := range []string{"", "test.", "test1.", "test2."} { - tk.MustExec(`set @@tidb_opt_enable_fuzzy_binding=1`) // enabled - require.True(t, tk.MustUseIndex(fmt.Sprintf("select * from %vt", testDB), idx)) - tk.MustQuery(`select @@last_plan_from_binding`).Check(testkit.Rows("1")) - require.True(t, tk.MustUseIndex(fmt.Sprintf("select * from %vt", testDB), idx)) - tk.MustQuery(`show warnings`).Check(testkit.Rows()) // no warning - tk.MustExec(`set @@tidb_opt_enable_fuzzy_binding=0`) // disabled - tk.MustQuery(fmt.Sprintf("select * from %vt", testDB)) - tk.MustQuery(`select @@last_plan_from_binding`).Check(testkit.Rows("0")) - } + for _, scope := range []string{"", "session", "global"} { + tk := testkit.NewTestKit(t, store) + for _, idx := range []string{"a", "b", "c", "d", "e"} { + tk.MustExec("use test") + tk.MustExec(fmt.Sprintf(`create %v binding using select /*+ use_index(t, %v) */ * from *.t`, scope, idx)) + for _, useDB := range []string{"test", "test1", "test2"} { + tk.MustExec("use " + useDB) + for _, testDB := range []string{"", "test.", "test1.", "test2."} { + tk.MustExec(`set @@tidb_opt_enable_fuzzy_binding=1`) // enabled + require.True(t, tk.MustUseIndex(fmt.Sprintf("select * from %vt", testDB), idx)) + tk.MustQuery(`select @@last_plan_from_binding`).Check(testkit.Rows("1")) + require.True(t, tk.MustUseIndex(fmt.Sprintf("select * from %vt", testDB), idx)) + tk.MustQuery(`show warnings`).Check(testkit.Rows()) // no warning + tk.MustExec(`set @@tidb_opt_enable_fuzzy_binding=0`) // disabled + tk.MustQuery(fmt.Sprintf("select * from %vt", testDB)) + tk.MustQuery(`select @@last_plan_from_binding`).Check(testkit.Rows("0")) } } } @@ -164,8 +157,7 @@ func TestUniversalBindingPriority(t *testing.T) { tk.MustQuery(`select @@last_plan_from_binding`).Check(testkit.Rows("1")) } -func TestCreateUpdateUniversalBinding(t *testing.T) { - t.Skip("skip it temporarily") +func TestCreateUpdateFuzzyBinding(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) @@ -173,32 +165,31 @@ func TestCreateUpdateUniversalBinding(t *testing.T) { tk.MustExec(`create table t (a int)`) // drop/show/update binding for sql digest can work for global universal bindings - tk.MustExec(`create global universal binding using select * from t`) + tk.MustExec(`create global binding using select * from *.t`) require.Equal(t, showBinding(tk, "show global bindings"), - [][]interface{}{{"select * from `t`", "SELECT * FROM `t`", "", "enabled", "manual", "e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7"}}) + [][]interface{}{{"select * from `*` . `t`", "SELECT * FROM `*`.`t`", "", "enabled", "manual", "a17da0a38af0f1d75229c5cd064d5222a610c5e5ef59436be5da1564c16f1013"}}) require.Equal(t, showBinding(tk, "show global bindings"), [][]interface{}{ - {"select * from `t`", "SELECT * FROM `t`", "", "enabled", "manual", "e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7"}}) - tk.MustExec(`set binding disabled for sql digest 'e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7'`) + {"select * from `*` . `t`", "SELECT * FROM `*`.`t`", "", "enabled", "manual", "a17da0a38af0f1d75229c5cd064d5222a610c5e5ef59436be5da1564c16f1013"}}) + tk.MustExec(`set binding disabled for sql digest 'a17da0a38af0f1d75229c5cd064d5222a610c5e5ef59436be5da1564c16f1013'`) require.Equal(t, showBinding(tk, "show global bindings"), [][]interface{}{ - {"select * from `t`", "SELECT * FROM `t`", "", "disabled", "manual", "e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7"}}) - tk.MustExec(`set binding enabled for sql digest 'e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7'`) + {"select * from `*` . `t`", "SELECT * FROM `*`.`t`", "", "disabled", "manual", "a17da0a38af0f1d75229c5cd064d5222a610c5e5ef59436be5da1564c16f1013"}}) + tk.MustExec(`set binding enabled for sql digest 'a17da0a38af0f1d75229c5cd064d5222a610c5e5ef59436be5da1564c16f1013'`) require.Equal(t, showBinding(tk, "show global bindings"), [][]interface{}{ - {"select * from `t`", "SELECT * FROM `t`", "", "enabled", "manual", "e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7"}}) - tk.MustExec(`drop global binding for sql digest 'e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7'`) + {"select * from `*` . `t`", "SELECT * FROM `*`.`t`", "", "enabled", "manual", "a17da0a38af0f1d75229c5cd064d5222a610c5e5ef59436be5da1564c16f1013"}}) + tk.MustExec(`drop global binding for sql digest 'a17da0a38af0f1d75229c5cd064d5222a610c5e5ef59436be5da1564c16f1013'`) require.Equal(t, showBinding(tk, "show global bindings"), [][]interface{}{}) // drop/show/update binding for sql digest can work for session universal bindings - tk.MustExec(`create session universal binding using select * from t`) + tk.MustExec(`create session binding using select * from *.t`) require.Equal(t, showBinding(tk, "show session bindings"), - [][]interface{}{{"select * from `t`", "SELECT * FROM `t`", "", "enabled", "manual", "e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7"}}) + [][]interface{}{{"select * from `*` . `t`", "SELECT * FROM `*`.`t`", "", "enabled", "manual", "a17da0a38af0f1d75229c5cd064d5222a610c5e5ef59436be5da1564c16f1013"}}) require.Equal(t, showBinding(tk, "show session bindings"), [][]interface{}{ - {"select * from `t`", "SELECT * FROM `t`", "", "enabled", "manual", "e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7"}}) - tk.MustExec(`drop session binding for sql digest 'e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7'`) + {"select * from `*` . `t`", "SELECT * FROM `*`.`t`", "", "enabled", "manual", "a17da0a38af0f1d75229c5cd064d5222a610c5e5ef59436be5da1564c16f1013"}}) + tk.MustExec(`drop session binding for sql digest 'a17da0a38af0f1d75229c5cd064d5222a610c5e5ef59436be5da1564c16f1013'`) require.Equal(t, showBinding(tk, "show session bindings"), [][]interface{}{}) } -func TestUniversalBindingSwitch(t *testing.T) { - t.Skip("skip it temporarily") +func TestFuzzyBindingSwitch(t *testing.T) { store := testkit.CreateMockStore(t) tk1 := testkit.NewTestKit(t, store) @@ -208,7 +199,7 @@ func TestUniversalBindingSwitch(t *testing.T) { // switch can work for both global and session universal bindings // test for session bindings - tk1.MustExec(`create session universal binding using select /*+ use_index(t, b) */ * from t`) + tk1.MustExec(`create session binding using select /*+ use_index(t, b) */ * from *.t`) tk1.MustExec(`use test1`) tk1.MustQuery(`select * from test.t`).Check(testkit.Rows()) tk1.MustQuery(`select @@last_plan_from_binding`).Check(testkit.Rows("0")) @@ -222,7 +213,7 @@ func TestUniversalBindingSwitch(t *testing.T) { // test for global bindings tk2 := testkit.NewTestKit(t, store) tk2.MustExec(`use test1`) - tk2.MustExec(`create global universal binding using select /*+ use_index(t, b) */ * from t`) + tk2.MustExec(`create global binding using select /*+ use_index(t, b) */ * from *.t`) tk2.MustQuery(`select * from test.t`).Check(testkit.Rows()) tk2.MustQuery(`select @@last_plan_from_binding`).Check(testkit.Rows("0")) tk2.MustExec(`set @@tidb_opt_enable_fuzzy_binding=1`) @@ -287,20 +278,19 @@ func TestUniversalBindingDBInHints(t *testing.T) { require.Equal(t, rs[2][1], "SELECT /*+ use_index(`t` `a`)*/ * FROM `t`") } -func TestUniversalBindingGC(t *testing.T) { - t.Skip("skip it temporarily") +func TestFuzzyBindingGC(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec(`use test`) tk.MustExec(`create table t (a int, b int, c int, d int, key(a), key(b), key(c), key(d))`) - tk.MustExec(`create global universal binding using select /*+ use_index(t, b) */ * from t`) + tk.MustExec(`create global binding using select /*+ use_index(t, b) */ * from *.t`) require.Equal(t, showBinding(tk, "show global bindings"), - [][]interface{}{{"select * from `t`", "SELECT /*+ use_index(`t` `b`)*/ * FROM `t`", "", "enabled", "manual", "e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7"}}) - tk.MustExec(`drop global binding for sql digest 'e5796985ccafe2f71126ed6c0ac939ffa015a8c0744a24b7aee6d587103fd2f7'`) + [][]interface{}{{"select * from `*` . `t`", "SELECT /*+ use_index(`t` `b`)*/ * FROM `*`.`t`", "", "enabled", "manual", "a17da0a38af0f1d75229c5cd064d5222a610c5e5ef59436be5da1564c16f1013"}}) + tk.MustExec(`drop global binding for sql digest 'a17da0a38af0f1d75229c5cd064d5222a610c5e5ef59436be5da1564c16f1013'`) require.Equal(t, showBinding(tk, "show global bindings"), [][]interface{}{}) // empty tk.MustQuery(`select bind_sql, status from mysql.bind_info where source != 'builtin'`).Check( - testkit.Rows("SELECT /*+ use_index(`t` `b`)*/ * FROM `t` deleted")) // status=deleted + testkit.Rows("SELECT /*+ use_index(`t` `b`)*/ * FROM `*`.`t` deleted")) // status=deleted updateTime := time.Now().Add(-(15 * bindinfo.Lease)) updateTimeStr := types.NewTime(types.FromGoTime(updateTime), mysql.TypeTimestamp, 3).String() @@ -310,8 +300,7 @@ func TestUniversalBindingGC(t *testing.T) { tk.MustQuery(`select bind_sql, status from mysql.bind_info where source != 'builtin'`).Check(testkit.Rows()) // empty after GC } -func TestUniversalBindingHints(t *testing.T) { - t.Skip("skip it temporarily") +func TestFuzzyBindingHints(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec(`use test`) @@ -330,126 +319,126 @@ func TestUniversalBindingHints(t *testing.T) { qTemplate string }{ // use index - {`create universal binding using select /*+ use_index(t1, c) */ * from t1 where a=1`, + {`create global binding using select /*+ use_index(t1, c) */ * from *.t1 where a=1`, `select * from %st1 where a=1000`}, - {`create universal binding using select /*+ use_index(t1, c) */ * from t1 where d<1`, + {`create global binding using select /*+ use_index(t1, c) */ * from *.t1 where d<1`, `select * from %st1 where d<10000`}, - {`create universal binding using select /*+ use_index(t1, c) */ * from t1, t2 where t1.d<1`, + {`create global binding using select /*+ use_index(t1, c) */ * from *.t1, *.t2 where t1.d<1`, `select * from %st1, t2 where t1.d<100`}, - {`create universal binding using select /*+ use_index(t1, c) */ * from t1, t2 where t1.d<1`, + {`create global binding using select /*+ use_index(t1, c) */ * from *.t1, *.t2 where t1.d<1`, `select * from t1, %st2 where t1.d<100`}, - {`create universal binding using select /*+ use_index(t1, c), use_index(t2, a) */ * from t1, t2 where t1.d<1`, + {`create global binding using select /*+ use_index(t1, c), use_index(t2, a) */ * from *.t1, *.t2 where t1.d<1`, `select * from %st1, t2 where t1.d<100`}, - {`create universal binding using select /*+ use_index(t1, c), use_index(t2, a) */ * from t1, t2 where t1.d<1`, + {`create global binding using select /*+ use_index(t1, c), use_index(t2, a) */ * from *.t1, *.t2 where t1.d<1`, `select * from t1, %st2 where t1.d<100`}, - {`create universal binding using select /*+ use_index(t1, c), use_index(t2, a) */ * from t1, t2, t3 where t1.d<1`, + {`create global binding using select /*+ use_index(t1, c), use_index(t2, a) */ * from *.t1, *.t2, *.t3 where t1.d<1`, `select * from %st1, t2, t3 where t1.d<100`}, - {`create universal binding using select /*+ use_index(t1, c), use_index(t2, a) */ * from t1, t2, t3 where t1.d<1`, + {`create global binding using select /*+ use_index(t1, c), use_index(t2, a) */ * from *.t1, *.t2, *.t3 where t1.d<1`, `select * from t1, t2, %st3 where t1.d<100`}, // ignore index - {`create universal binding using select /*+ ignore_index(t1, b) */ * from t1 where b=1`, + {`create global binding using select /*+ ignore_index(t1, b) */ * from *.t1 where b=1`, `select * from %st1 where b=1000`}, - {`create universal binding using select /*+ ignore_index(t1, b) */ * from t1 where b>1`, + {`create global binding using select /*+ ignore_index(t1, b) */ * from *.t1 where b>1`, `select * from %st1 where b>1000`}, - {`create universal binding using select /*+ ignore_index(t1, b) */ * from t1 where b in (1,2)`, + {`create global binding using select /*+ ignore_index(t1, b) */ * from *.t1 where b in (1,2)`, `select * from %st1 where b in (1)`}, - {`create universal binding using select /*+ ignore_index(t1, b) */ * from t1 where b in (1,2)`, + {`create global binding using select /*+ ignore_index(t1, b) */ * from *.t1 where b in (1,2)`, `select * from %st1 where b in (1,2,3,4,5)`}, // order index hint - {`create universal binding using select /*+ order_index(t1, a) */ a from t1 where a<10 order by a limit 10`, + {`create global binding using select /*+ order_index(t1, a) */ a from *.t1 where a<10 order by a limit 10`, `select a from %st1 where a<10000 order by a limit 10`}, - {`create universal binding using select /*+ order_index(t1, b) */ b from t1 where b>10 order by b limit 1111`, + {`create global binding using select /*+ order_index(t1, b) */ b from *.t1 where b>10 order by b limit 1111`, `select b from %st1 where b>2 order by b limit 10`}, // no order index hint - {`create universal binding using select /*+ no_order_index(t1, c) */ c from t1 where c<10 order by c limit 10`, + {`create global binding using select /*+ no_order_index(t1, c) */ c from *.t1 where c<10 order by c limit 10`, `select c from %st1 where c<10000 order by c limit 10`}, - {`create universal binding using select /*+ no_order_index(t1, d) */ d from t1 where d>10 order by d limit 1111`, + {`create global binding using select /*+ no_order_index(t1, d) */ d from *.t1 where d>10 order by d limit 1111`, `select d from %st1 where d>2 order by d limit 10`}, // agg hint - {`create universal binding using select /*+ hash_agg() */ count(*) from t1 group by a`, + {`create global binding using select /*+ hash_agg() */ count(*) from *.t1 group by a`, `select count(*) from %st1 group by a`}, - {`create universal binding using select /*+ stream_agg() */ count(*) from t1 group by b`, + {`create global binding using select /*+ stream_agg() */ count(*) from *.t1 group by b`, `select count(*) from %st1 group by b`}, // to_cop hint - {`create universal binding using select /*+ agg_to_cop() */ sum(a) from t1`, + {`create global binding using select /*+ agg_to_cop() */ sum(a) from *.t1`, `select sum(a) from %st1`}, - {`create universal binding using select /*+ limit_to_cop() */ a from t1 limit 10`, + {`create global binding using select /*+ limit_to_cop() */ a from *.t1 limit 10`, `select a from %st1 limit 101`}, // index merge hint - {`create universal binding using select /*+ use_index_merge(t1, c, d) */ * from t1 where c=1 or d=1`, + {`create global binding using select /*+ use_index_merge(t1, c, d) */ * from *.t1 where c=1 or d=1`, `select * from %st1 where c=1000 or d=1000`}, - {`create universal binding using select /*+ no_index_merge() */ * from t1 where a=1 or b=1`, + {`create global binding using select /*+ no_index_merge() */ * from *.t1 where a=1 or b=1`, `select * from %st1 where a=1000 or b=1000`}, // join type hint - {`create universal binding using select /*+ hash_join(t1) */ * from t1, t2 where t1.a=t2.a`, + {`create global binding using select /*+ hash_join(t1) */ * from *.t1, *.t2 where t1.a=t2.a`, `select * from %st1, t2 where t1.a=t2.a`}, - {`create universal binding using select /*+ hash_join(t2) */ * from t1, t2 where t1.a=t2.a`, + {`create global binding using select /*+ hash_join(t2) */ * from *.t1, *.t2 where t1.a=t2.a`, `select * from t1, %st2 where t1.a=t2.a`}, - {`create universal binding using select /*+ hash_join(t2) */ * from t1, t2, t3 where t1.a=t2.a and t3.b=t2.b`, + {`create global binding using select /*+ hash_join(t2) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`, `select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`}, - {`create universal binding using select /*+ hash_join_build(t1) */ * from t1, t2 where t1.a=t2.a`, + {`create global binding using select /*+ hash_join_build(t1) */ * from *.t1, *.t2 where t1.a=t2.a`, `select * from t1, %st2 where t1.a=t2.a`}, - {`create universal binding using select /*+ hash_join_probe(t1) */ * from t1, t2 where t1.a=t2.a`, + {`create global binding using select /*+ hash_join_probe(t1) */ * from *.t1, *.t2 where t1.a=t2.a`, `select * from t1, %st2 where t1.a=t2.a`}, - {`create universal binding using select /*+ merge_join(t1) */ * from t1, t2 where t1.a=t2.a`, + {`create global binding using select /*+ merge_join(t1) */ * from *.t1, *.t2 where t1.a=t2.a`, `select * from %st1, t2 where t1.a=t2.a`}, - {`create universal binding using select /*+ merge_join(t2) */ * from t1, t2 where t1.a=t2.a`, + {`create global binding using select /*+ merge_join(t2) */ * from *.t1, *.t2 where t1.a=t2.a`, `select * from t1, %st2 where t1.a=t2.a`}, - {`create universal binding using select /*+ merge_join(t2) */ * from t1, t2, t3 where t1.a=t2.a and t3.b=t2.b`, + {`create global binding using select /*+ merge_join(t2) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`, `select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`}, - {`create universal binding using select /*+ inl_join(t1) */ * from t1, t2 where t1.a=t2.a`, + {`create global binding using select /*+ inl_join(t1) */ * from *.t1, *.t2 where t1.a=t2.a`, `select * from %st1, t2 where t1.a=t2.a`}, - {`create universal binding using select /*+ inl_join(t2) */ * from t1, t2 where t1.a=t2.a`, + {`create global binding using select /*+ inl_join(t2) */ * from *.t1, *.t2 where t1.a=t2.a`, `select * from t1, %st2 where t1.a=t2.a`}, - {`create universal binding using select /*+ inl_join(t2) */ * from t1, t2, t3 where t1.a=t2.a and t3.b=t2.b`, + {`create global binding using select /*+ inl_join(t2) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`, `select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`}, // no join type hint - {`create universal binding using select /*+ no_hash_join(t1) */ * from t1, t2 where t1.b=t2.b`, + {`create global binding using select /*+ no_hash_join(t1) */ * from *.t1, *.t2 where t1.b=t2.b`, `select * from %st1, t2 where t1.b=t2.b`}, - {`create universal binding using select /*+ no_hash_join(t2) */ * from t1, t2 where t1.c=t2.c`, + {`create global binding using select /*+ no_hash_join(t2) */ * from *.t1, *.t2 where t1.c=t2.c`, `select * from t1, %st2 where t1.c=t2.c`}, - {`create universal binding using select /*+ no_hash_join(t2) */ * from t1, t2, t3 where t1.a=t2.a and t3.b=t2.b`, + {`create global binding using select /*+ no_hash_join(t2) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`, `select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`}, - {`create universal binding using select /*+ no_merge_join(t1) */ * from t1, t2 where t1.b=t2.b`, + {`create global binding using select /*+ no_merge_join(t1) */ * from *.t1, *.t2 where t1.b=t2.b`, `select * from %st1, t2 where t1.b=t2.b`}, - {`create universal binding using select /*+ no_merge_join(t2) */ * from t1, t2 where t1.c=t2.c`, + {`create global binding using select /*+ no_merge_join(t2) */ * from *.t1, *.t2 where t1.c=t2.c`, `select * from t1, %st2 where t1.c=t2.c`}, - {`create universal binding using select /*+ no_merge_join(t2) */ * from t1, t2, t3 where t1.a=t2.a and t3.b=t2.b`, + {`create global binding using select /*+ no_merge_join(t2) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`, `select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`}, - {`create universal binding using select /*+ no_index_join(t1) */ * from t1, t2 where t1.b=t2.b`, + {`create global binding using select /*+ no_index_join(t1) */ * from *.t1, *.t2 where t1.b=t2.b`, `select * from %st1, t2 where t1.b=t2.b`}, - {`create universal binding using select /*+ no_index_join(t2) */ * from t1, t2 where t1.c=t2.c`, + {`create global binding using select /*+ no_index_join(t2) */ * from *.t1, *.t2 where t1.c=t2.c`, `select * from t1, %st2 where t1.c=t2.c`}, - {`create universal binding using select /*+ no_index_join(t2) */ * from t1, t2, t3 where t1.a=t2.a and t3.b=t2.b`, + {`create global binding using select /*+ no_index_join(t2) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`, `select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`}, // join order hint - {`create universal binding using select /*+ leading(t2) */ * from t1, t2 where t1.b=t2.b`, + {`create global binding using select /*+ leading(t2) */ * from *.t1, *.t2 where t1.b=t2.b`, `select * from %st1, t2 where t1.b=t2.b`}, - {`create universal binding using select /*+ leading(t2) */ * from t1, t2 where t1.c=t2.c`, + {`create global binding using select /*+ leading(t2) */ * from *.t1, *.t2 where t1.c=t2.c`, `select * from t1, %st2 where t1.c=t2.c`}, - {`create universal binding using select /*+ leading(t2, t1) */ * from t1, t2 where t1.c=t2.c`, + {`create global binding using select /*+ leading(t2, t1) */ * from *.t1, *.t2 where t1.c=t2.c`, `select * from t1, %st2 where t1.c=t2.c`}, - {`create universal binding using select /*+ leading(t1, t2) */ * from t1, t2 where t1.c=t2.c`, + {`create global binding using select /*+ leading(t1, t2) */ * from *.t1, *.t2 where t1.c=t2.c`, `select * from t1, %st2 where t1.c=t2.c`}, - {`create universal binding using select /*+ leading(t1) */ * from t1, t2, t3 where t1.a=t2.a and t3.b=t2.b`, + {`create global binding using select /*+ leading(t1) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`, `select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`}, - {`create universal binding using select /*+ leading(t2) */ * from t1, t2, t3 where t1.a=t2.a and t3.b=t2.b`, + {`create global binding using select /*+ leading(t2) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`, `select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`}, - {`create universal binding using select /*+ leading(t2,t3) */ * from t1, t2, t3 where t1.a=t2.a and t3.b=t2.b`, + {`create global binding using select /*+ leading(t2,t3) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`, `select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`}, - {`create universal binding using select /*+ leading(t2,t3,t1) */ * from t1, t2, t3 where t1.a=t2.a and t3.b=t2.b`, + {`create global binding using select /*+ leading(t2,t3,t1) */ * from *.t1, *.t2, *.t3 where t1.a=t2.a and t3.b=t2.b`, `select * from t1, %st2, t3 where t1.a=t2.a and t3.b=t2.b`}, } { - removeAllBindings(tk, false) + removeAllBindings(tk, true) tk.MustExec(c.binding) for _, currentDB := range []string{"db1", "db2", "db3"} { tk.MustExec(`use ` + currentDB) diff --git a/pkg/bindinfo/global_handle.go b/pkg/bindinfo/global_handle.go index cd5a8621f3e3b..69b7b6e784aa0 100644 --- a/pkg/bindinfo/global_handle.go +++ b/pkg/bindinfo/global_handle.go @@ -46,7 +46,7 @@ type GlobalBindingHandle interface { // Methods for create, get, drop global sql bindings. // MatchGlobalBinding returns the matched binding for this statement. - MatchGlobalBinding(currentDB string, stmt ast.StmtNode) (*BindRecord, error) + MatchGlobalBinding(sctx sessionctx.Context, stmt ast.StmtNode) (*BindRecord, error) // GetAllGlobalBindings returns all bind records in cache. GetAllGlobalBindings() (bindRecords []*BindRecord) @@ -112,6 +112,11 @@ type globalBindingHandle struct { bindingCache atomic.Pointer[bindCache] + // fuzzyDigestMap is used to support fuzzy matching. + // fuzzyDigest is the digest calculated after eliminating all DB names, e.g. `select * from test.t` -> `select * from t` -> fuzzyDigest. + // exactDigest is the digest where all DB names are kept, e.g. `select * from test.t` -> exactDigest. + fuzzyDigestMap atomic.Value // map[string][]string fuzzyDigest --> exactDigests + // lastTaskTime records the last update time for the global sql bind cache. // This value is used to avoid reload duplicated bindings from storage. lastUpdateTime atomic.Value @@ -165,6 +170,33 @@ func (h *globalBindingHandle) setCache(c *bindCache) { h.bindingCache.Store(c) } +func (h *globalBindingHandle) getFuzzyDigestMap() map[string][]string { + return h.fuzzyDigestMap.Load().(map[string][]string) +} + +func (h *globalBindingHandle) setFuzzyDigestMap(m map[string][]string) { + h.fuzzyDigestMap.Store(m) +} + +func buildFuzzyDigestMap(bindRecords []*BindRecord) map[string][]string { + m := make(map[string][]string) + p := parser.New() + for _, bindRecord := range bindRecords { + for _, binding := range bindRecord.Bindings { + stmt, err := p.ParseOneStmt(binding.BindSQL, binding.Charset, binding.Collation) + if err != nil { + logutil.BgLogger().Warn("parse bindSQL failed", zap.String("bindSQL", binding.BindSQL), zap.Error(err)) + p = parser.New() + continue + } + sqlWithoutDB := utilparser.RestoreWithoutDB(stmt) + _, fuzzyDigest := parser.NormalizeDigestForBinding(sqlWithoutDB) + m[fuzzyDigest.String()] = append(m[fuzzyDigest.String()], binding.SQLDigest) + } + } + return m +} + // Reset is to reset the BindHandle and clean old info. func (h *globalBindingHandle) Reset() { h.lastUpdateTime.Store(types.ZeroTimestamp) @@ -220,6 +252,7 @@ func (h *globalBindingHandle) LoadFromStorageToCache(fullLoad bool) (err error) defer func() { h.setLastUpdateTime(lastUpdateTime) h.setCache(newCache) // TODO: update it in place + h.setFuzzyDigestMap(buildFuzzyDigestMap(newCache.GetAllBindings())) }() for _, row := range rows { @@ -488,17 +521,42 @@ func (h *globalBindingHandle) Size() int { } // MatchGlobalBinding returns the matched binding for this statement. -func (h *globalBindingHandle) MatchGlobalBinding(currentDB string, stmt ast.StmtNode) (*BindRecord, error) { +func (h *globalBindingHandle) MatchGlobalBinding(sctx sessionctx.Context, stmt ast.StmtNode) (*BindRecord, error) { bindingCache := h.getCache() if bindingCache.Size() == 0 { return nil, nil } - // TODO: support fuzzy matching. - _, _, sqlDigest, err := normalizeStmt(stmt, currentDB) + fuzzyDigestMap := h.getFuzzyDigestMap() + if len(fuzzyDigestMap) == 0 { + return nil, nil + } + + _, _, fuzzDigest, err := normalizeStmt(stmt, sctx.GetSessionVars().CurrentDB, true) if err != nil { return nil, err } - return bindingCache.GetBinding(sqlDigest), nil + + tableNames := CollectTableNames(stmt) + var bestBinding *BindRecord + leastWildcards := len(tableNames) + 1 + for _, exactDigest := range fuzzyDigestMap[fuzzDigest] { + sqlDigest := exactDigest + if bindRecord := bindingCache.GetBinding(sqlDigest); bindRecord != nil { + for _, binding := range bindRecord.Bindings { + numWildcards, matched := fuzzyMatchBindingTableName(sctx.GetSessionVars().CurrentDB, tableNames, binding.TableNames) + if matched && numWildcards > 0 && sctx != nil && !sctx.GetSessionVars().EnableFuzzyBinding { + continue // fuzzy binding is disabled, skip this binding + } + if matched && numWildcards < leastWildcards { + bestBinding = bindRecord + leastWildcards = numWildcards + break + } + } + } + } + + return bestBinding, nil } // GetAllGlobalBindings returns all bind records in cache. @@ -534,17 +592,27 @@ func newBindRecord(sctx sessionctx.Context, row chunk.Row) (string, *BindRecord, if defaultDB == "" { bindingType = TypeUniversal } + + bindSQL := row.GetString(1) + charset, collation := row.GetString(6), row.GetString(7) + stmt, err := parser.New().ParseOneStmt(bindSQL, charset, collation) + if err != nil { + return "", nil, err + } + tableNames := CollectTableNames(stmt) + binding := Binding{ - BindSQL: row.GetString(1), + BindSQL: bindSQL, Status: status, CreateTime: row.GetTime(4), UpdateTime: row.GetTime(5), - Charset: row.GetString(6), - Collation: row.GetString(7), + Charset: charset, + Collation: collation, Source: row.GetString(8), SQLDigest: row.GetString(9), PlanDigest: row.GetString(10), Type: bindingType, + TableNames: tableNames, } bindRecord := &BindRecord{ OriginalSQL: row.GetString(0), @@ -553,7 +621,7 @@ func newBindRecord(sctx sessionctx.Context, row chunk.Row) (string, *BindRecord, } sqlDigest := parser.DigestNormalized(bindRecord.OriginalSQL) sctx.GetSessionVars().CurrentDB = bindRecord.Db - err := bindRecord.prepareHints(sctx) + err = bindRecord.prepareHints(sctx) return sqlDigest.String(), bindRecord, err } diff --git a/pkg/bindinfo/global_handle_test.go b/pkg/bindinfo/global_handle_test.go index d4b24eb983b6a..34421df77206b 100644 --- a/pkg/bindinfo/global_handle_test.go +++ b/pkg/bindinfo/global_handle_test.go @@ -69,7 +69,7 @@ func TestBindingLastUpdateTime(t *testing.T) { require.NoError(t, err) stmt, err := parser.New().ParseOneStmt("select * from test . t0", "", "") require.NoError(t, err) - bindData, err := bindHandle.MatchGlobalBinding("test", stmt) + bindData, err := bindHandle.MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.Equal(t, 1, len(bindData.Bindings)) bind := bindData.Bindings[0] @@ -125,7 +125,8 @@ func TestBindParse(t *testing.T) { charset := "utf8mb4" collation := "utf8mb4_bin" source := bindinfo.Manual - mockDigest := "0f644e22c38ecc71d4592c52df127df7f86b6ca7f7c0ee899113b794578f9396" + _, digest := parser.NormalizeDigestForBinding(originSQL) + mockDigest := digest.String() sql := fmt.Sprintf(`INSERT INTO mysql.bind_info(original_sql,bind_sql,default_db,status,create_time,update_time,charset,collation,source, sql_digest, plan_digest) VALUES ('%s', '%s', '%s', '%s', NOW(), NOW(),'%s', '%s', '%s', '%s', '%s')`, originSQL, bindSQL, defaultDb, status, charset, collation, source, mockDigest, mockDigest) tk.MustExec(sql) @@ -136,7 +137,7 @@ func TestBindParse(t *testing.T) { stmt, err := parser.New().ParseOneStmt("select * from test . t", "", "") require.NoError(t, err) - bindData, err := bindHandle.MatchGlobalBinding("test", stmt) + bindData, err := bindHandle.MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.NotNil(t, bindData) require.Equal(t, "select * from `test` . `t`", bindData.OriginalSQL) @@ -437,7 +438,7 @@ func TestGlobalBinding(t *testing.T) { stmt, _, _ := internal.UtilNormalizeWithDefaultDB(t, testSQL.querySQL) - bindData, err := dom.BindHandle().MatchGlobalBinding("test", stmt) + bindData, err := dom.BindHandle().MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.NotNil(t, bindData) require.Equal(t, testSQL.originSQL, bindData.OriginalSQL) @@ -471,7 +472,7 @@ func TestGlobalBinding(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, bindHandle.Size()) - bindData, err = dom.BindHandle().MatchGlobalBinding("test", stmt) + bindData, err = dom.BindHandle().MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.NotNil(t, bindData) require.Equal(t, testSQL.originSQL, bindData.OriginalSQL) @@ -487,7 +488,7 @@ func TestGlobalBinding(t *testing.T) { _, err = tk.Exec("drop global " + testSQL.dropSQL) require.Equal(t, uint64(1), tk.Session().AffectedRows()) require.NoError(t, err) - bindData, err = dom.BindHandle().MatchGlobalBinding("test", stmt) + bindData, err = dom.BindHandle().MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.Nil(t, bindData) @@ -496,7 +497,7 @@ func TestGlobalBinding(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, bindHandle.Size()) - bindData, err = dom.BindHandle().MatchGlobalBinding("test", stmt) + bindData, err = dom.BindHandle().MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.Nil(t, bindData) diff --git a/pkg/bindinfo/session_handle.go b/pkg/bindinfo/session_handle.go index aa5aec8f1914f..54492b48d6b18 100644 --- a/pkg/bindinfo/session_handle.go +++ b/pkg/bindinfo/session_handle.go @@ -42,7 +42,7 @@ type SessionBindingHandle interface { DropSessionBinding(sqlDigest string) error // MatchSessionBinding returns the matched binding for this statement. - MatchSessionBinding(currentDB string, stmt ast.StmtNode) (*BindRecord, error) + MatchSessionBinding(sctx sessionctx.Context, stmt ast.StmtNode) (*BindRecord, error) // GetAllSessionBindings return all bindings. GetAllSessionBindings() (bindRecords []*BindRecord) @@ -105,16 +105,49 @@ func (h *sessionBindingHandle) DropSessionBinding(sqlDigest string) error { } // MatchSessionBinding returns the matched binding for this statement. -func (h *sessionBindingHandle) MatchSessionBinding(currentDB string, stmt ast.StmtNode) (*BindRecord, error) { +func (h *sessionBindingHandle) MatchSessionBinding(sctx sessionctx.Context, stmt ast.StmtNode) (*BindRecord, error) { if h.ch.Size() == 0 { return nil, nil } - // TODO: support fuzzy matching. - _, _, sqlDigest, err := normalizeStmt(stmt, currentDB) + + _, _, fuzzDigest, err := normalizeStmt(stmt, sctx.GetSessionVars().CurrentDB, true) if err != nil { return nil, err } - return h.ch.GetBinding(sqlDigest), nil + + // The current implementation is simplistic, but session binding is only for test purpose, so + // there shouldn't be many session bindings, and to keep it simple, this implementation is acceptable. + tableNames := CollectTableNames(stmt) + var bestBinding *BindRecord + leastWildcards := len(tableNames) + 1 + bindRecords := h.ch.GetAllBindings() + for _, bindRecord := range bindRecords { + for _, binding := range bindRecord.Bindings { + bindingStmt, err := parser.New().ParseOneStmt(binding.BindSQL, binding.Charset, binding.Collation) + if err != nil { + return nil, err + } + _, _, bindingFuzzyDigest, err := normalizeStmt(bindingStmt, sctx.GetSessionVars().CurrentDB, true) + if err != nil { + return nil, err + } + if bindingFuzzyDigest != fuzzDigest { + continue + } + bindingTableNames := CollectTableNames(bindingStmt) + + numWildcards, matched := fuzzyMatchBindingTableName(sctx.GetSessionVars().CurrentDB, tableNames, bindingTableNames) + if matched && numWildcards > 0 && sctx != nil && !sctx.GetSessionVars().EnableFuzzyBinding { + continue // fuzzy binding is disabled, skip this binding + } + if matched && numWildcards < leastWildcards { + bestBinding = bindRecord + leastWildcards = numWildcards + break + } + } + } + return bestBinding, nil } // GetAllSessionBindings return all session bind info. diff --git a/pkg/bindinfo/session_handle_test.go b/pkg/bindinfo/session_handle_test.go index bcfb87ef3650e..65a32dd4b9403 100644 --- a/pkg/bindinfo/session_handle_test.go +++ b/pkg/bindinfo/session_handle_test.go @@ -112,7 +112,7 @@ func TestSessionBinding(t *testing.T) { handle := tk.Session().Value(bindinfo.SessionBindInfoKeyType).(bindinfo.SessionBindingHandle) stmt, err := parser.New().ParseOneStmt(testSQL.originSQL, "", "") require.NoError(t, err) - bindData, err := handle.MatchSessionBinding("test", stmt) + bindData, err := handle.MatchSessionBinding(tk.Session(), stmt) require.NoError(t, err) require.NotNil(t, bindData) require.Equal(t, testSQL.originSQL, bindData.OriginalSQL) @@ -150,7 +150,7 @@ func TestSessionBinding(t *testing.T) { _, err = tk.Exec("drop session " + testSQL.dropSQL) require.NoError(t, err) - bindData, err = handle.MatchSessionBinding("test", stmt) + bindData, err = handle.MatchSessionBinding(tk.Session(), stmt) require.NoError(t, err) require.Nil(t, bindData) // dropped } diff --git a/pkg/bindinfo/tests/bind_test.go b/pkg/bindinfo/tests/bind_test.go index 7197fa1635259..f9734f16d8177 100644 --- a/pkg/bindinfo/tests/bind_test.go +++ b/pkg/bindinfo/tests/bind_test.go @@ -320,7 +320,7 @@ func TestBindingSymbolList(t *testing.T) { stmt, err := parser.New().ParseOneStmt("select a, b from test . t where a = 1 limit 0, 1", "", "") require.NoError(t, err) - bindData, err := dom.BindHandle().MatchGlobalBinding("test", stmt) + bindData, err := dom.BindHandle().MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.NotNil(t, bindData) require.Equal(t, "select `a` , `b` from `test` . `t` where `a` = ? limit ...", bindData.OriginalSQL) @@ -366,7 +366,7 @@ func TestBindingInListWithSingleLiteral(t *testing.T) { stmt, err := parser.New().ParseOneStmt("select a, b from test . t where a in (1)", "", "") require.NoError(t, err) - bindData, err := dom.BindHandle().MatchGlobalBinding("test", stmt) + bindData, err := dom.BindHandle().MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.NotNil(t, bindData) require.Equal(t, "select `a` , `b` from `test` . `t` where `a` in ( ... )", bindData.OriginalSQL) @@ -403,7 +403,7 @@ func TestBestPlanInBaselines(t *testing.T) { stmt, _, _ := internal.UtilNormalizeWithDefaultDB(t, "select a, b from t where a = 1 limit 0, 1") - bindData, err := dom.BindHandle().MatchGlobalBinding("test", stmt) + bindData, err := dom.BindHandle().MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.NotNil(t, bindData) require.Equal(t, "select `a` , `b` from `test` . `t` where `a` = ? limit ...", bindData.OriginalSQL) @@ -438,7 +438,7 @@ func TestErrorBind(t *testing.T) { stmt, err := parser.New().ParseOneStmt("select * from test . t where i > ?", "", "") require.NoError(t, err) - bindData, err := dom.BindHandle().MatchGlobalBinding("test", stmt) + bindData, err := dom.BindHandle().MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.NotNil(t, bindData) require.Equal(t, "select * from `test` . `t` where `i` > ?", bindData.OriginalSQL) @@ -497,7 +497,7 @@ func TestHintsSetID(t *testing.T) { // Verify the added Binding contains ID with restored query block. stmt, err := parser.New().ParseOneStmt("select * from t where a > ?", "", "") require.NoError(t, err) - bindData, err := bindHandle.MatchGlobalBinding("test", stmt) + bindData, err := bindHandle.MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.NotNil(t, bindData) require.Equal(t, "select * from `test` . `t` where `a` > ?", bindData.OriginalSQL) @@ -507,7 +507,7 @@ func TestHintsSetID(t *testing.T) { internal.UtilCleanBindingEnv(tk, dom) tk.MustExec("create global binding for select * from t where a > 10 using select /*+ use_index(t, idx_a) */ * from t where a > 10") - bindData, err = bindHandle.MatchGlobalBinding("test", stmt) + bindData, err = bindHandle.MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.NotNil(t, bindData) require.Equal(t, "select * from `test` . `t` where `a` > ?", bindData.OriginalSQL) @@ -517,7 +517,7 @@ func TestHintsSetID(t *testing.T) { internal.UtilCleanBindingEnv(tk, dom) tk.MustExec("create global binding for select * from t where a > 10 using select /*+ use_index(@sel_1 t, idx_a) */ * from t where a > 10") - bindData, err = bindHandle.MatchGlobalBinding("test", stmt) + bindData, err = bindHandle.MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.NotNil(t, bindData) require.Equal(t, "select * from `test` . `t` where `a` > ?", bindData.OriginalSQL) @@ -527,7 +527,7 @@ func TestHintsSetID(t *testing.T) { internal.UtilCleanBindingEnv(tk, dom) tk.MustExec("create global binding for select * from t where a > 10 using select /*+ use_index(@qb1 t, idx_a) qb_name(qb1) */ * from t where a > 10") - bindData, err = bindHandle.MatchGlobalBinding("test", stmt) + bindData, err = bindHandle.MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.NotNil(t, bindData) require.Equal(t, "select * from `test` . `t` where `a` > ?", bindData.OriginalSQL) @@ -537,7 +537,7 @@ func TestHintsSetID(t *testing.T) { internal.UtilCleanBindingEnv(tk, dom) tk.MustExec("create global binding for select * from t where a > 10 using select /*+ use_index(T, IDX_A) */ * from t where a > 10") - bindData, err = bindHandle.MatchGlobalBinding("test", stmt) + bindData, err = bindHandle.MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.NotNil(t, bindData) require.Equal(t, "select * from `test` . `t` where `a` > ?", bindData.OriginalSQL) @@ -549,7 +549,7 @@ func TestHintsSetID(t *testing.T) { err = tk.ExecToErr("create global binding for select * from t using select /*+ non_exist_hint() */ * from t") require.True(t, terror.ErrorEqual(err, parser.ErrParse)) tk.MustExec("create global binding for select * from t where a > 10 using select * from t where a > 10") - bindData, err = bindHandle.MatchGlobalBinding("test", stmt) + bindData, err = bindHandle.MatchGlobalBinding(tk.Session(), stmt) require.NoError(t, err) require.NotNil(t, bindData) require.Equal(t, "select * from `test` . `t` where `a` > ?", bindData.OriginalSQL) diff --git a/pkg/planner/core/plan_cache.go b/pkg/planner/core/plan_cache.go index 612448e07f37c..88a933a41a1a5 100644 --- a/pkg/planner/core/plan_cache.go +++ b/pkg/planner/core/plan_cache.go @@ -801,7 +801,7 @@ func GetBindSQL4PlanCache(sctx sessionctx.Context, stmt *PlanCacheStmt) (string, return "", ignore } sessionHandle := sctx.Value(bindinfo.SessionBindInfoKeyType).(bindinfo.SessionBindingHandle) - bindRecord, _ := sessionHandle.MatchSessionBinding(sctx.GetSessionVars().CurrentDB, stmt.PreparedAst.Stmt) + bindRecord, _ := sessionHandle.MatchSessionBinding(sctx, stmt.PreparedAst.Stmt) if bindRecord != nil { enabledBinding := bindRecord.FindEnabledBinding() if enabledBinding != nil { @@ -813,7 +813,7 @@ func GetBindSQL4PlanCache(sctx sessionctx.Context, stmt *PlanCacheStmt) (string, if globalHandle == nil { return "", ignore } - bindRecord, _ = globalHandle.MatchGlobalBinding(sctx.GetSessionVars().CurrentDB, stmt.PreparedAst.Stmt) + bindRecord, _ = globalHandle.MatchGlobalBinding(sctx, stmt.PreparedAst.Stmt) if bindRecord != nil { enabledBinding := bindRecord.FindEnabledBinding() if enabledBinding != nil { diff --git a/pkg/session/BUILD.bazel b/pkg/session/BUILD.bazel index b62b822e8faf4..806bd6f76bfd7 100644 --- a/pkg/session/BUILD.bazel +++ b/pkg/session/BUILD.bazel @@ -142,6 +142,7 @@ go_test( "//pkg/expression", "//pkg/kv", "//pkg/meta", + "//pkg/parser", "//pkg/parser/ast", "//pkg/parser/auth", "//pkg/session/types", diff --git a/pkg/session/bootstrap_test.go b/pkg/session/bootstrap_test.go index b9b58569780e4..9d20f9b3f3f7d 100644 --- a/pkg/session/bootstrap_test.go +++ b/pkg/session/bootstrap_test.go @@ -26,6 +26,7 @@ import ( "github.com/pingcap/tidb/pkg/ddl" "github.com/pingcap/tidb/pkg/domain" "github.com/pingcap/tidb/pkg/meta" + "github.com/pingcap/tidb/pkg/parser" "github.com/pingcap/tidb/pkg/parser/auth" sessiontypes "github.com/pingcap/tidb/pkg/session/types" "github.com/pingcap/tidb/pkg/sessionctx" @@ -1949,9 +1950,12 @@ func TestTiDBBindingInListToVer175(t *testing.T) { // create some bindings at version174 MustExec(t, seV174, "use test") MustExec(t, seV174, "create table t (a int, b int, c int, key(c))") - MustExec(t, seV174, "insert into mysql.bind_info values ('select * from `test` . `t` where `a` in ( ... )', 'SELECT /*+ use_index(`t` `c`)*/ * FROM `test`.`t` WHERE `a` IN (1,2,3)', 'test', 'enabled', '2023-09-13 14:41:38.319', '2023-09-13 14:41:35.319', 'utf8', 'utf8_general_ci', 'manual', '', '')") - MustExec(t, seV174, "insert into mysql.bind_info values ('select * from `test` . `t` where `a` in ( ? )', 'SELECT /*+ use_index(`t` `c`)*/ * FROM `test`.`t` WHERE `a` IN (1)', 'test', 'enabled', '2023-09-13 14:41:38.319', '2023-09-13 14:41:36.319', 'utf8', 'utf8_general_ci', 'manual', '', '')") - MustExec(t, seV174, "insert into mysql.bind_info values ('select * from `test` . `t` where `a` in ( ? ) and `b` in ( ... )', 'SELECT /*+ use_index(`t` `c`)*/ * FROM `test`.`t` WHERE `a` IN (1) AND `b` IN (1,2,3)', 'test', 'enabled', '2023-09-13 14:41:37.319', '2023-09-13 14:41:38.319', 'utf8', 'utf8_general_ci', 'manual', '', '')") + _, digest := parser.NormalizeDigestForBinding("SELECT * FROM `test`.`t` WHERE `a` IN (1,2,3)") + MustExec(t, seV174, fmt.Sprintf("insert into mysql.bind_info values ('select * from `test` . `t` where `a` in ( ... )', 'SELECT /*+ use_index(`t` `c`)*/ * FROM `test`.`t` WHERE `a` IN (1,2,3)', 'test', 'enabled', '2023-09-13 14:41:38.319', '2023-09-13 14:41:35.319', 'utf8', 'utf8_general_ci', 'manual', '%s', '')", digest.String())) + _, digest = parser.NormalizeDigestForBinding("SELECT * FROM `test`.`t` WHERE `a` IN (1)") + MustExec(t, seV174, fmt.Sprintf("insert into mysql.bind_info values ('select * from `test` . `t` where `a` in ( ? )', 'SELECT /*+ use_index(`t` `c`)*/ * FROM `test`.`t` WHERE `a` IN (1)', 'test', 'enabled', '2023-09-13 14:41:38.319', '2023-09-13 14:41:36.319', 'utf8', 'utf8_general_ci', 'manual', '%s', '')", digest.String())) + _, digest = parser.NormalizeDigestForBinding("SELECT * FROM `test`.`t` WHERE `a` IN (1) AND `b` IN (1,2,3)") + MustExec(t, seV174, fmt.Sprintf("insert into mysql.bind_info values ('select * from `test` . `t` where `a` in ( ? ) and `b` in ( ... )', 'SELECT /*+ use_index(`t` `c`)*/ * FROM `test`.`t` WHERE `a` IN (1) AND `b` IN (1,2,3)', 'test', 'enabled', '2023-09-13 14:41:37.319', '2023-09-13 14:41:38.319', 'utf8', 'utf8_general_ci', 'manual', '%s', '')", digest.String())) showBindings := func(s sessiontypes.Session) (records []string) { MustExec(t, s, "admin reload bindings")