971 lines
48 KiB
Go
971 lines
48 KiB
Go
// Copyright 2021 PingCAP, Inc.
|
|
//
|
|
// 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 bindinfo_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ngaut/pools"
|
|
"github.com/pingcap/failpoint"
|
|
"github.com/pingcap/tidb/pkg/bindinfo"
|
|
"github.com/pingcap/tidb/pkg/parser"
|
|
"github.com/pingcap/tidb/pkg/parser/ast"
|
|
"github.com/pingcap/tidb/pkg/parser/terror"
|
|
"github.com/pingcap/tidb/pkg/session/sessionapi"
|
|
"github.com/pingcap/tidb/pkg/testkit"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestBindingCache(t *testing.T) {
|
|
store, dom := testkit.CreateMockStoreAndDomain(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t(a int, b int, index idx(a))")
|
|
tk.MustExec("create global binding for select * from t using select * from t use index(idx);")
|
|
tk.MustExec("create database tmp")
|
|
tk.MustExec("use tmp")
|
|
tk.MustExec("create table t(a int, b int, index idx(a))")
|
|
tk.MustExec("create global binding for select * from t using select * from t use index(idx);")
|
|
|
|
require.Nil(t, dom.BindingHandle().LoadFromStorageToCache(false, false))
|
|
require.Nil(t, dom.BindingHandle().LoadFromStorageToCache(false, false))
|
|
res := tk.MustQuery("show global bindings")
|
|
require.Equal(t, 2, len(res.Rows()))
|
|
|
|
tk.MustExec("drop global binding for select * from t;")
|
|
require.Nil(t, dom.BindingHandle().LoadFromStorageToCache(false, false))
|
|
require.Equal(t, 1, len(dom.BindingHandle().GetAllBindings()))
|
|
}
|
|
|
|
func TestBindingLastUpdateTime(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t0;")
|
|
tk.MustExec("create table t0(a int, key(a));")
|
|
tk.MustExec("create global binding for select * from t0 using select * from t0 use index(a);")
|
|
tk.MustExec("admin reload bindings;")
|
|
|
|
bindHandle := bindinfo.NewBindingHandle(&mockSessionPool{tk.Session()})
|
|
err := bindHandle.LoadFromStorageToCache(true, false)
|
|
require.NoError(t, err)
|
|
stmt, err := parser.New().ParseOneStmt("select * from test . t0", "", "")
|
|
require.NoError(t, err)
|
|
|
|
_, noDBDigest := bindinfo.NormalizeStmtForBinding(stmt, "", true)
|
|
binding, matched := bindHandle.MatchingBinding(tk.Session(), noDBDigest, bindinfo.CollectTableNames(stmt))
|
|
require.True(t, matched)
|
|
updateTime := binding.UpdateTime.String()
|
|
|
|
rows1 := tk.MustQuery("show status like 'last_plan_binding_update_time';").Rows()
|
|
updateTime1 := rows1[0][1]
|
|
require.Equal(t, updateTime, updateTime1)
|
|
|
|
rows2 := tk.MustQuery("show session status like 'last_plan_binding_update_time';").Rows()
|
|
updateTime2 := rows2[0][1]
|
|
require.Equal(t, updateTime, updateTime2)
|
|
tk.MustQuery(`show global status like 'last_plan_binding_update_time';`).Check(testkit.Rows())
|
|
}
|
|
|
|
func TestBindingLastUpdateTimeWithInvalidBind(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
|
|
rows0 := tk.MustQuery("show status like 'last_plan_binding_update_time';").Rows()
|
|
updateTime0 := rows0[0][1]
|
|
require.Equal(t, updateTime0, "0000-00-00 00:00:00")
|
|
|
|
tk.MustExec("insert into mysql.bind_info (original_sql, bind_sql, default_db, status, create_time, update_time, charset, collation, source, sql_digest, plan_digest) values('select * from `test` . `t`', 'invalid_binding', 'test', 'enabled', '2000-01-01 09:00:00', '2000-01-01 09:00:00', '', '','" +
|
|
bindinfo.SourceManual + "', '', '')")
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("admin reload bindings;")
|
|
|
|
rows2 := tk.MustQuery("show global bindings").Rows()
|
|
require.Len(t, rows2, 0)
|
|
}
|
|
|
|
func TestBindParse(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
|
|
tk.MustExec("use test")
|
|
tk.MustExec("create table t(i int)")
|
|
tk.MustExec("create index index_t on t(i)")
|
|
|
|
originSQL := "select * from `test` . `t`"
|
|
bindSQL := "select * from `test` . `t` use index(index_t)"
|
|
defaultDb := "test"
|
|
status := bindinfo.StatusEnabled
|
|
charset := "utf8mb4"
|
|
collation := "utf8mb4_bin"
|
|
source := bindinfo.SourceManual
|
|
_, 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)
|
|
bindHandle := bindinfo.NewBindingHandle(&mockSessionPool{tk.Session()})
|
|
err := bindHandle.LoadFromStorageToCache(true, false)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(bindHandle.GetAllBindings()))
|
|
|
|
stmt, err := parser.New().ParseOneStmt("select * from test . t", "", "")
|
|
require.NoError(t, err)
|
|
_, noDBDigest := bindinfo.NormalizeStmtForBinding(stmt, "", true)
|
|
binding, matched := bindHandle.MatchingBinding(tk.Session(), noDBDigest, bindinfo.CollectTableNames(stmt))
|
|
require.True(t, matched)
|
|
require.Equal(t, "select * from `test` . `t`", binding.OriginalSQL)
|
|
require.Equal(t, "select * from `test` . `t` use index(index_t)", binding.BindSQL)
|
|
require.Equal(t, "test", binding.Db)
|
|
require.Equal(t, bindinfo.StatusEnabled, binding.Status)
|
|
require.Equal(t, "utf8mb4", binding.Charset)
|
|
require.Equal(t, "utf8mb4_bin", binding.Collation)
|
|
require.NotNil(t, binding.CreateTime)
|
|
require.NotNil(t, binding.UpdateTime)
|
|
|
|
dur, err := binding.UpdateTime.GoTime(time.Local)
|
|
require.NoError(t, err)
|
|
require.GreaterOrEqual(t, int64(time.Since(dur)), int64(0))
|
|
|
|
// Test fields with quotes or slashes.
|
|
sql = `CREATE GLOBAL BINDING FOR select * from t where i BETWEEN "a" and "b" USING select * from t use index(index_t) where i BETWEEN "a\nb\rc\td\0e" and 'x'`
|
|
tk.MustExec(sql)
|
|
tk.MustExec(`DROP global binding for select * from t use index(idx) where i BETWEEN "a\nb\rc\td\0e" and "x"`)
|
|
|
|
// Test SetOprStmt.
|
|
tk.MustExec(`create binding for select * from t union all select * from t using select * from t use index(index_t) union all select * from t use index()`)
|
|
tk.MustExec(`drop binding for select * from t union all select * from t using select * from t use index(index_t) union all select * from t use index()`)
|
|
tk.MustExec(`create binding for select * from t INTERSECT select * from t using select * from t use index(index_t) INTERSECT select * from t use index()`)
|
|
tk.MustExec(`drop binding for select * from t INTERSECT select * from t using select * from t use index(index_t) INTERSECT select * from t use index()`)
|
|
tk.MustExec(`create binding for select * from t EXCEPT select * from t using select * from t use index(index_t) EXCEPT select * from t use index()`)
|
|
tk.MustExec(`drop binding for select * from t EXCEPT select * from t using select * from t use index(index_t) EXCEPT select * from t use index()`)
|
|
tk.MustExec(`create binding for (select * from t) union all (select * from t) using (select * from t use index(index_t)) union all (select * from t use index())`)
|
|
tk.MustExec(`drop binding for (select * from t) union all (select * from t) using (select * from t use index(index_t)) union all (select * from t use index())`)
|
|
|
|
// Test Update / Delete.
|
|
tk.MustExec("create table t1(a int, b int, c int, key(b), key(c))")
|
|
tk.MustExec("create table t2(a int, b int, c int, key(b), key(c))")
|
|
tk.MustExec("create binding for delete from t1 where b = 1 and c > 1 using delete /*+ use_index(t1, c) */ from t1 where b = 1 and c > 1")
|
|
tk.MustExec("drop binding for delete from t1 where b = 1 and c > 1 using delete /*+ use_index(t1, c) */ from t1 where b = 1 and c > 1")
|
|
tk.MustExec("create binding for delete t1, t2 from t1 inner join t2 on t1.b = t2.b where t1.c = 1 using delete /*+ hash_join(t1, t2), use_index(t1, c) */ t1, t2 from t1 inner join t2 on t1.b = t2.b where t1.c = 1")
|
|
tk.MustExec("drop binding for delete t1, t2 from t1 inner join t2 on t1.b = t2.b where t1.c = 1 using delete /*+ hash_join(t1, t2), use_index(t1, c) */ t1, t2 from t1 inner join t2 on t1.b = t2.b where t1.c = 1")
|
|
tk.MustExec("create binding for update t1 set a = 1 where b = 1 and c > 1 using update /*+ use_index(t1, c) */ t1 set a = 1 where b = 1 and c > 1")
|
|
tk.MustExec("drop binding for update t1 set a = 1 where b = 1 and c > 1 using update /*+ use_index(t1, c) */ t1 set a = 1 where b = 1 and c > 1")
|
|
tk.MustExec("create binding for update t1, t2 set t1.a = 1 where t1.b = t2.b using update /*+ inl_join(t1) */ t1, t2 set t1.a = 1 where t1.b = t2.b")
|
|
tk.MustExec("drop binding for update t1, t2 set t1.a = 1 where t1.b = t2.b using update /*+ inl_join(t1) */ t1, t2 set t1.a = 1 where t1.b = t2.b")
|
|
// Test Insert / Replace.
|
|
tk.MustExec("create binding for insert into t1 select * from t2 where t2.b = 1 and t2.c > 1 using insert into t1 select /*+ use_index(t2,c) */ * from t2 where t2.b = 1 and t2.c > 1")
|
|
tk.MustExec("drop binding for insert into t1 select * from t2 where t2.b = 1 and t2.c > 1 using insert into t1 select /*+ use_index(t2,c) */ * from t2 where t2.b = 1 and t2.c > 1")
|
|
tk.MustExec("create binding for replace into t1 select * from t2 where t2.b = 1 and t2.c > 1 using replace into t1 select /*+ use_index(t2,c) */ * from t2 where t2.b = 1 and t2.c > 1")
|
|
tk.MustExec("drop binding for replace into t1 select * from t2 where t2.b = 1 and t2.c > 1 using replace into t1 select /*+ use_index(t2,c) */ * from t2 where t2.b = 1 and t2.c > 1")
|
|
err = tk.ExecToErr("create binding for insert into t1 values(1,1,1) using insert into t1 values(1,1,1)")
|
|
require.Equal(t, "create binding only supports INSERT / REPLACE INTO SELECT", err.Error())
|
|
err = tk.ExecToErr("create binding for replace into t1 values(1,1,1) using replace into t1 values(1,1,1)")
|
|
require.Equal(t, "create binding only supports INSERT / REPLACE INTO SELECT", err.Error())
|
|
|
|
// Test errors.
|
|
tk.MustExec(`drop table if exists t1`)
|
|
tk.MustExec("create table t1(i int, s varchar(20))")
|
|
_, err = tk.Exec("create global binding for select * from t using select * from t1 use index for join(index_t)")
|
|
require.NotNil(t, err, "err %v", err)
|
|
}
|
|
|
|
func TestSetBindingStatus(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t(a int, index idx_a(a))")
|
|
tk.MustQuery("show global bindings").Check(testkit.Rows())
|
|
tk.MustExec("create global binding for select * from t where a > 10 using select /*+ USE_INDEX(t, idx_a) */ * from t where a > 10")
|
|
rows := tk.MustQuery("show global bindings").Rows()
|
|
require.Len(t, rows, 1)
|
|
require.Equal(t, bindinfo.StatusEnabled, rows[0][3])
|
|
tk.MustExec("select * from t where a > 10")
|
|
tk.MustQuery("select @@last_plan_from_binding").Check(testkit.Rows("1"))
|
|
|
|
tk.MustExec("set binding disabled for select * from t where a > 10")
|
|
rows = tk.MustQuery("show global bindings").Rows()
|
|
require.Len(t, rows, 1)
|
|
require.Equal(t, bindinfo.StatusDisabled, rows[0][3])
|
|
tk.MustExec("select * from t where a > 10")
|
|
tk.MustQuery("select @@last_plan_from_binding").Check(testkit.Rows("0"))
|
|
|
|
tk.MustExec("set binding enabled for select * from t where a > 10")
|
|
rows = tk.MustQuery("show global bindings").Rows()
|
|
require.Len(t, rows, 1)
|
|
require.Equal(t, bindinfo.StatusEnabled, rows[0][3])
|
|
|
|
tk.MustExec("set binding disabled for select * from t where a > 10")
|
|
tk.MustExec("create global binding for select * from t where a > 10 using select * from t where a > 10")
|
|
rows = tk.MustQuery("show global bindings").Rows()
|
|
require.Len(t, rows, 1)
|
|
require.Equal(t, bindinfo.StatusEnabled, rows[0][3])
|
|
tk.MustExec("select * from t where a > 10")
|
|
tk.MustQuery("select @@last_plan_from_binding").Check(testkit.Rows("1"))
|
|
|
|
tk.MustExec("set binding disabled for select * from t where a > 10 using select * from t where a > 10")
|
|
rows = tk.MustQuery("show global bindings").Rows()
|
|
require.Len(t, rows, 1)
|
|
require.Equal(t, bindinfo.StatusDisabled, rows[0][3])
|
|
tk.MustExec("select * from t where a > 10")
|
|
tk.MustQuery("select @@last_plan_from_binding").Check(testkit.Rows("0"))
|
|
|
|
tk.MustExec("set binding enabled for select * from t where a > 10 using select * from t where a > 10")
|
|
rows = tk.MustQuery("show global bindings").Rows()
|
|
require.Len(t, rows, 1)
|
|
require.Equal(t, bindinfo.StatusEnabled, rows[0][3])
|
|
|
|
tk.MustExec("set binding disabled for select * from t where a > 10 using select * from t where a > 10")
|
|
tk.MustExec("drop global binding for select * from t where a > 10 using select * from t where a > 10")
|
|
rows = tk.MustQuery("show global bindings").Rows()
|
|
require.Len(t, rows, 0)
|
|
}
|
|
|
|
func TestSetBindingStatusWithoutBindingInCache(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t(a int, index idx_a(a))")
|
|
utilCleanBindingEnv(tk)
|
|
tk.MustQuery("show global bindings").Check(testkit.Rows())
|
|
|
|
// Simulate creating bindings on other machines
|
|
_, sqlDigest := parser.NormalizeDigestForBinding("select * from `test` . `t` where `a` > ?")
|
|
tk.MustExec("insert into mysql.bind_info (original_sql, bind_sql, default_db, status, create_time, update_time, charset, collation, source, sql_digest, plan_digest) values('select * from `test` . `t` where `a` > ?', 'SELECT /*+ USE_INDEX(`t` `idx_a`)*/ * FROM `test`.`t` WHERE `a` > 10', 'test', 'enabled', '2000-01-02 09:00:00', '2000-01-02 09:00:00', '', '','" +
|
|
bindinfo.SourceManual + "', '" + sqlDigest.String() + "', '')")
|
|
tk.MustExec("set binding disabled for select * from t where a > 10")
|
|
tk.MustExec("admin reload bindings")
|
|
rows := tk.MustQuery("show global bindings").Rows()
|
|
require.Len(t, rows, 1)
|
|
require.Equal(t, bindinfo.StatusDisabled, rows[0][3])
|
|
|
|
// clear the mysql.bind_info
|
|
utilCleanBindingEnv(tk)
|
|
|
|
// Simulate creating bindings on other machines
|
|
tk.MustExec("insert into mysql.bind_info (original_sql, bind_sql, default_db, status, create_time, update_time, charset, collation, source, sql_digest, plan_digest) values('select * from `test` . `t` where `a` > ?', 'SELECT * FROM `test`.`t` WHERE `a` > 10', 'test', 'disabled', '2000-01-02 09:00:00', '2000-01-02 09:00:00', '', '','" +
|
|
bindinfo.SourceManual + "', '" + sqlDigest.String() + "', '')")
|
|
tk.MustExec("set binding enabled for select * from t where a > 10")
|
|
tk.MustExec("admin reload bindings")
|
|
rows = tk.MustQuery("show global bindings").Rows()
|
|
require.Len(t, rows, 1)
|
|
require.Equal(t, bindinfo.StatusEnabled, rows[0][3])
|
|
|
|
utilCleanBindingEnv(tk)
|
|
}
|
|
|
|
var testSQLs = []struct {
|
|
createSQL string
|
|
overlaySQL string
|
|
querySQL string
|
|
originSQL string
|
|
bindSQL string
|
|
dropSQL string
|
|
memoryUsage float64
|
|
}{
|
|
{
|
|
createSQL: "binding for select * from t where i>100 using select * from t use index(index_t) where i>100",
|
|
overlaySQL: "binding for select * from t where i>99 using select * from t use index(index_t) where i>99",
|
|
querySQL: "select * from t where i > 30.0",
|
|
originSQL: "select * from `test` . `t` where `i` > ?",
|
|
bindSQL: "SELECT * FROM `test`.`t` USE INDEX (`index_t`) WHERE `i` > 99",
|
|
dropSQL: "binding for select * from t where i>100",
|
|
memoryUsage: float64(167),
|
|
},
|
|
{
|
|
createSQL: "binding for select * from t union all select * from t using select * from t use index(index_t) union all select * from t use index()",
|
|
overlaySQL: "",
|
|
querySQL: "select * from t union all select * from t",
|
|
originSQL: "select * from `test` . `t` union all select * from `test` . `t`",
|
|
bindSQL: "SELECT * FROM `test`.`t` USE INDEX (`index_t`) UNION ALL SELECT * FROM `test`.`t` USE INDEX ()",
|
|
dropSQL: "binding for select * from t union all select * from t",
|
|
memoryUsage: float64(237),
|
|
},
|
|
{
|
|
createSQL: "binding for (select * from t) union all (select * from t) using (select * from t use index(index_t)) union all (select * from t use index())",
|
|
overlaySQL: "",
|
|
querySQL: "(select * from t) union all (select * from t)",
|
|
originSQL: "( select * from `test` . `t` ) union all ( select * from `test` . `t` )",
|
|
bindSQL: "(SELECT * FROM `test`.`t` USE INDEX (`index_t`)) UNION ALL (SELECT * FROM `test`.`t` USE INDEX ())",
|
|
dropSQL: "binding for (select * from t) union all (select * from t)",
|
|
memoryUsage: float64(249),
|
|
},
|
|
{
|
|
createSQL: "binding for select * from t intersect select * from t using select * from t use index(index_t) intersect select * from t use index()",
|
|
overlaySQL: "",
|
|
querySQL: "select * from t intersect select * from t",
|
|
originSQL: "select * from `test` . `t` intersect select * from `test` . `t`",
|
|
bindSQL: "SELECT * FROM `test`.`t` USE INDEX (`index_t`) INTERSECT SELECT * FROM `test`.`t` USE INDEX ()",
|
|
dropSQL: "binding for select * from t intersect select * from t",
|
|
memoryUsage: float64(237),
|
|
},
|
|
{
|
|
createSQL: "binding for select * from t except select * from t using select * from t use index(index_t) except select * from t use index()",
|
|
overlaySQL: "",
|
|
querySQL: "select * from t except select * from t",
|
|
originSQL: "select * from `test` . `t` except select * from `test` . `t`",
|
|
bindSQL: "SELECT * FROM `test`.`t` USE INDEX (`index_t`) EXCEPT SELECT * FROM `test`.`t` USE INDEX ()",
|
|
dropSQL: "binding for select * from t except select * from t",
|
|
memoryUsage: float64(231),
|
|
},
|
|
{
|
|
createSQL: "binding for select * from t using select /*+ use_index(t,index_t)*/ * from t",
|
|
overlaySQL: "",
|
|
querySQL: "select * from t ",
|
|
originSQL: "select * from `test` . `t`",
|
|
bindSQL: "SELECT /*+ use_index(`t` `index_t`)*/ * FROM `test`.`t`",
|
|
dropSQL: "binding for select * from t",
|
|
memoryUsage: float64(166),
|
|
},
|
|
{
|
|
createSQL: "binding for delete from t where i = 1 using delete /*+ use_index(t,index_t) */ from t where i = 1",
|
|
overlaySQL: "",
|
|
querySQL: "delete from t where i = 2",
|
|
originSQL: "delete from `test` . `t` where `i` = ?",
|
|
bindSQL: "DELETE /*+ use_index(`t` `index_t`)*/ FROM `test`.`t` WHERE `i` = 1",
|
|
dropSQL: "binding for delete from t where i = 1",
|
|
memoryUsage: float64(190),
|
|
},
|
|
{
|
|
createSQL: "binding for delete t, t1 from t inner join t1 on t.s = t1.s where t.i = 1 using delete /*+ use_index(t,index_t), hash_join(t,t1) */ t, t1 from t inner join t1 on t.s = t1.s where t.i = 1",
|
|
overlaySQL: "",
|
|
querySQL: "delete t, t1 from t inner join t1 on t.s = t1.s where t.i = 2",
|
|
originSQL: "delete `test` . `t` , `test` . `t1` from `test` . `t` join `test` . `t1` on `t` . `s` = `t1` . `s` where `t` . `i` = ?",
|
|
bindSQL: "DELETE /*+ use_index(`t` `index_t`) hash_join(`t`, `t1`)*/ `test`.`t`,`test`.`t1` FROM `test`.`t` JOIN `test`.`t1` ON `t`.`s` = `t1`.`s` WHERE `t`.`i` = 1",
|
|
dropSQL: "binding for delete t, t1 from t inner join t1 on t.s = t1.s where t.i = 1",
|
|
memoryUsage: float64(402),
|
|
},
|
|
{
|
|
createSQL: "binding for update t set s = 'a' where i = 1 using update /*+ use_index(t,index_t) */ t set s = 'a' where i = 1",
|
|
overlaySQL: "",
|
|
querySQL: "update t set s='b' where i=2",
|
|
originSQL: "update `test` . `t` set `s` = ? where `i` = ?",
|
|
bindSQL: "UPDATE /*+ use_index(`t` `index_t`)*/ `test`.`t` SET `s`='a' WHERE `i` = 1",
|
|
dropSQL: "binding for update t set s = 'a' where i = 1",
|
|
memoryUsage: float64(204),
|
|
},
|
|
{
|
|
createSQL: "binding for update t, t1 set t.s = 'a' where t.i = t1.i using update /*+ inl_join(t1) */ t, t1 set t.s = 'a' where t.i = t1.i",
|
|
overlaySQL: "",
|
|
querySQL: "update t , t1 set t.s='b' where t.i=t1.i",
|
|
originSQL: "update ( `test` . `t` ) join `test` . `t1` set `t` . `s` = ? where `t` . `i` = `t1` . `i`",
|
|
bindSQL: "UPDATE /*+ inl_join(`t1`)*/ (`test`.`t`) JOIN `test`.`t1` SET `t`.`s`='a' WHERE `t`.`i` = `t1`.`i`",
|
|
dropSQL: "binding for update t, t1 set t.s = 'a' where t.i = t1.i",
|
|
memoryUsage: float64(262),
|
|
},
|
|
{
|
|
createSQL: "binding for insert into t1 select * from t where t.i = 1 using insert into t1 select /*+ use_index(t,index_t) */ * from t where t.i = 1",
|
|
overlaySQL: "",
|
|
querySQL: "insert into t1 select * from t where t.i = 2",
|
|
originSQL: "insert into `test` . `t1` select * from `test` . `t` where `t` . `i` = ?",
|
|
bindSQL: "INSERT INTO `test`.`t1` SELECT /*+ use_index(`t` `index_t`)*/ * FROM `test`.`t` WHERE `t`.`i` = 1",
|
|
dropSQL: "binding for insert into t1 select * from t where t.i = 1",
|
|
memoryUsage: float64(254),
|
|
},
|
|
{
|
|
createSQL: "binding for replace into t1 select * from t where t.i = 1 using replace into t1 select /*+ use_index(t,index_t) */ * from t where t.i = 1",
|
|
overlaySQL: "",
|
|
querySQL: "replace into t1 select * from t where t.i = 2",
|
|
originSQL: "replace into `test` . `t1` select * from `test` . `t` where `t` . `i` = ?",
|
|
bindSQL: "REPLACE INTO `test`.`t1` SELECT /*+ use_index(`t` `index_t`)*/ * FROM `test`.`t` WHERE `t`.`i` = 1",
|
|
dropSQL: "binding for replace into t1 select * from t where t.i = 1",
|
|
memoryUsage: float64(256),
|
|
},
|
|
}
|
|
|
|
func TestLoadBindingTimeLag(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
tk := testkit.NewTestKit(t, store)
|
|
|
|
tk.MustExec("use test")
|
|
tk.MustExec(`create table t (a int)`)
|
|
tk.MustExec(`create global binding using select * from t`)
|
|
numBindings := len(tk.MustQuery(`show global bindings`).Rows())
|
|
require.Equal(t, 1, numBindings)
|
|
|
|
tk.Session().SetValue(bindinfo.TestTimeLagInLoadingBinding, 5*time.Second)
|
|
tk.MustExec(`create global binding using select * from t where a < 1`)
|
|
numBindings = len(tk.MustQuery(`show global bindings`).Rows())
|
|
require.Equal(t, 2, numBindings)
|
|
|
|
tk.Session().SetValue(bindinfo.TestTimeLagInLoadingBinding, 15*time.Second)
|
|
tk.MustExec(`create global binding using select * from t where a > 1`)
|
|
numBindings = len(tk.MustQuery(`show global bindings`).Rows())
|
|
require.Equal(t, 2, numBindings) // can't see the latest one since the time lag tolerance is only 10s
|
|
}
|
|
|
|
func TestGlobalBinding(t *testing.T) {
|
|
store, dom := testkit.CreateMockStoreAndDomain(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
|
|
for _, testSQL := range testSQLs {
|
|
utilCleanBindingEnv(tk)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("drop table if exists t1")
|
|
tk.MustExec("create table t(i int, s varchar(20))")
|
|
tk.MustExec("create table t1(i int, s varchar(20))")
|
|
tk.MustExec("create index index_t on t(i,s)")
|
|
|
|
_, err := tk.Exec("create global " + testSQL.createSQL)
|
|
require.NoErrorf(t, err, "testSQL: %+v", testSQL)
|
|
|
|
if testSQL.overlaySQL != "" {
|
|
_, err = tk.Exec("create global " + testSQL.overlaySQL)
|
|
require.NoErrorf(t, err, "testSQL: %+v", testSQL)
|
|
}
|
|
|
|
stmt, _, _ := utilNormalizeWithDefaultDB(t, testSQL.querySQL)
|
|
|
|
_, noDBDigest := bindinfo.NormalizeStmtForBinding(stmt, "", true)
|
|
binding, matched := dom.BindingHandle().MatchingBinding(tk.Session(), noDBDigest, bindinfo.CollectTableNames(stmt))
|
|
require.Truef(t, matched, "testSQL %+v", testSQL)
|
|
require.Equalf(t, testSQL.originSQL, binding.OriginalSQL, "testSQL %+v", testSQL)
|
|
require.Equalf(t, testSQL.bindSQL, binding.BindSQL, "testSQL %+v", testSQL)
|
|
require.Equalf(t, "test", binding.Db, "testSQL %+v", testSQL)
|
|
require.Equalf(t, bindinfo.StatusEnabled, binding.Status, "testSQL %+v", testSQL)
|
|
require.NotNilf(t, binding.Charset, "testSQL %+v", testSQL)
|
|
require.NotNilf(t, binding.Collation, "testSQL %+v", testSQL)
|
|
require.NotNilf(t, binding.CreateTime, "testSQL %+v", testSQL)
|
|
require.NotNilf(t, binding.UpdateTime, "testSQL %+v", testSQL)
|
|
|
|
rs, err := tk.Exec("show global bindings")
|
|
require.NoErrorf(t, err, "testSQL %+v", testSQL)
|
|
chk := rs.NewChunk(nil)
|
|
err = rs.Next(context.TODO(), chk)
|
|
require.NoErrorf(t, err, "testSQL %+v", testSQL)
|
|
require.Equalf(t, 1, chk.NumRows(), "testSQL %+v", testSQL)
|
|
row := chk.GetRow(0)
|
|
require.Equalf(t, testSQL.originSQL, row.GetString(0), "testSQL %+v", testSQL)
|
|
require.Equalf(t, testSQL.bindSQL, row.GetString(1), "testSQL %+v", testSQL)
|
|
require.Equalf(t, "test", row.GetString(2), "testSQL %+v", testSQL)
|
|
require.Equalf(t, bindinfo.StatusEnabled, row.GetString(3), "testSQL %+v", testSQL)
|
|
require.NotNilf(t, row.GetTime(4), "testSQL %+v", testSQL)
|
|
require.NotNilf(t, row.GetTime(5), "testSQL %+v", testSQL)
|
|
require.NotNilf(t, row.GetString(6), "testSQL %+v", testSQL)
|
|
require.NotNilf(t, row.GetString(7), "testSQL %+v", testSQL)
|
|
|
|
bindHandle := bindinfo.NewBindingHandle(&mockSessionPool{tk.Session()})
|
|
err = bindHandle.LoadFromStorageToCache(true, false)
|
|
require.NoErrorf(t, err, "testSQL %+v", testSQL)
|
|
require.Equalf(t, 1, len(bindHandle.GetAllBindings()), "testSQL %+v", testSQL)
|
|
|
|
_, noDBDigest = bindinfo.NormalizeStmtForBinding(stmt, "", true)
|
|
binding, matched = dom.BindingHandle().MatchingBinding(tk.Session(), noDBDigest, bindinfo.CollectTableNames(stmt))
|
|
require.Truef(t, matched, "testSQL %+v", testSQL)
|
|
require.Equalf(t, testSQL.originSQL, binding.OriginalSQL, "testSQL %+v", testSQL)
|
|
require.Equalf(t, testSQL.bindSQL, binding.BindSQL, "testSQL %+v", testSQL)
|
|
require.Equalf(t, "test", binding.Db, "testSQL %+v", testSQL)
|
|
require.Equalf(t, bindinfo.StatusEnabled, binding.Status, "testSQL %+v", testSQL)
|
|
require.NotNilf(t, binding.Charset, "testSQL %+v", testSQL)
|
|
require.NotNilf(t, binding.Collation, "testSQL %+v", testSQL)
|
|
require.NotNilf(t, binding.CreateTime, "testSQL %+v", testSQL)
|
|
require.NotNilf(t, binding.UpdateTime, "testSQL %+v", testSQL)
|
|
|
|
_, err = tk.Exec("drop global " + testSQL.dropSQL)
|
|
require.Equalf(t, uint64(1), tk.Session().AffectedRows(), "testSQL %+v", testSQL)
|
|
require.NoErrorf(t, err, "testSQL %+v", testSQL)
|
|
_, noDBDigest = bindinfo.NormalizeStmtForBinding(stmt, "", true)
|
|
_, matched = dom.BindingHandle().MatchingBinding(tk.Session(), noDBDigest, bindinfo.CollectTableNames(stmt))
|
|
require.Falsef(t, matched, "testSQL %+v", testSQL) // dropped
|
|
bindHandle = bindinfo.NewBindingHandle(&mockSessionPool{tk.Session()})
|
|
err = bindHandle.LoadFromStorageToCache(true, false)
|
|
require.NoErrorf(t, err, "testSQL %+v", testSQL)
|
|
require.Equalf(t, 0, len(bindHandle.GetAllBindings()), "testSQL %+v", testSQL)
|
|
|
|
_, noDBDigest = bindinfo.NormalizeStmtForBinding(stmt, "", true)
|
|
_, matched = dom.BindingHandle().MatchingBinding(tk.Session(), noDBDigest, bindinfo.CollectTableNames(stmt))
|
|
require.Falsef(t, matched, "testSQL %+v", testSQL) // dropped
|
|
|
|
rs, err = tk.Exec("show global bindings")
|
|
require.NoErrorf(t, err, "testSQL %+v", testSQL)
|
|
chk = rs.NewChunk(nil)
|
|
err = rs.Next(context.TODO(), chk)
|
|
require.NoErrorf(t, err, "testSQL %+v", testSQL)
|
|
require.Equalf(t, 0, chk.NumRows(), "testSQL %+v", testSQL)
|
|
|
|
_, err = tk.Exec("delete from mysql.bind_info where source != 'builtin'")
|
|
require.NoErrorf(t, err, "testSQL %+v", testSQL)
|
|
}
|
|
}
|
|
|
|
func TestOutdatedInfoSchema(t *testing.T) {
|
|
store, dom := testkit.CreateMockStoreAndDomain(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t(a int, b int, index idx(a))")
|
|
tk.MustExec("create global binding for select * from t using select * from t use index(idx)")
|
|
require.Nil(t, dom.BindingHandle().LoadFromStorageToCache(false, false))
|
|
utilCleanBindingEnv(tk)
|
|
tk.MustExec("create global binding for select * from t using select * from t use index(idx)")
|
|
}
|
|
|
|
func TestReloadBindings(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
tk := testkit.NewTestKit(t, store)
|
|
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t(a int, b int, index idx(a))")
|
|
tk.MustExec("create global binding for select * from t using select * from t use index(idx)")
|
|
rows := tk.MustQuery("show global bindings").Rows()
|
|
require.Equal(t, 1, len(rows))
|
|
rows = tk.MustQuery("select * from mysql.bind_info where source != 'builtin'").Rows()
|
|
require.Equal(t, 1, len(rows))
|
|
tk.MustExec(`drop global binding for select * from t`)
|
|
rows = tk.MustQuery("show global bindings").Rows()
|
|
require.Equal(t, 0, len(rows))
|
|
}
|
|
|
|
func TestSetVarFixControlWithBinding(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
|
|
tk.MustExec(`create table t(id int, a varchar(100), b int, c int, index idx_ab(a, b))`)
|
|
tk.MustQuery(`explain format='brief' select * from t where c = 10 and (a = 'xx' or (a = 'kk' and b = 1))`).Check(
|
|
testkit.Rows(
|
|
`IndexLookUp 1.00 root `,
|
|
`├─IndexRangeScan(Build) 10.10 cop[tikv] table:t, index:idx_ab(a, b) range:["kk" 1,"kk" 1], ["xx","xx"], keep order:false, stats:pseudo`,
|
|
`└─Selection(Probe) 1.00 cop[tikv] eq(test.t.c, 10)`,
|
|
` └─TableRowIDScan 10.10 cop[tikv] table:t keep order:false, stats:pseudo`))
|
|
|
|
tk.MustExec(`create global binding using select /*+ set_var(tidb_opt_fix_control='44389:ON') */ * from t where c = 10 and (a = 'xx' or (a = 'kk' and b = 1))`)
|
|
tk.MustQuery(`show warnings`).Check(testkit.Rows()) // no warning
|
|
|
|
// the fix control can take effect
|
|
tk.MustQuery(`explain format='brief' select * from t where c = 10 and (a = 'xx' or (a = 'kk' and b = 1))`).Check(
|
|
testkit.Rows(`IndexLookUp 1.00 root `,
|
|
`├─IndexRangeScan(Build) 10.10 cop[tikv] table:t, index:idx_ab(a, b) range:["kk" 1,"kk" 1], ["xx","xx"], keep order:false, stats:pseudo`,
|
|
`└─Selection(Probe) 1.00 cop[tikv] eq(test.t.c, 10)`,
|
|
` └─TableRowIDScan 10.10 cop[tikv] table:t keep order:false, stats:pseudo`))
|
|
tk.MustQuery(`select @@last_plan_from_binding`).Check(testkit.Rows("1"))
|
|
}
|
|
|
|
func TestRemoveDuplicatedPseudoBinding(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
|
|
checkPseudoBinding := func(num int) {
|
|
tk.MustQuery(fmt.Sprintf("select count(1) from mysql.bind_info where original_sql='%s'",
|
|
bindinfo.BuiltinPseudoSQL4BindLock)).Check(testkit.Rows(fmt.Sprintf("%d", num)))
|
|
}
|
|
insertPseudoBinding := func() {
|
|
tk.MustExec(fmt.Sprintf(`INSERT INTO mysql.bind_info(original_sql, bind_sql, default_db, status, create_time, update_time, charset, collation, source)
|
|
VALUES ('%v', '%v', "mysql", '%v', "2000-01-01 00:00:00", "2000-01-01 00:00:00", "", "", '%v')`,
|
|
bindinfo.BuiltinPseudoSQL4BindLock, bindinfo.BuiltinPseudoSQL4BindLock, bindinfo.StatusBuiltin, bindinfo.StatusBuiltin))
|
|
}
|
|
removeDuplicated := func() {
|
|
tk.MustExec(bindinfo.StmtRemoveDuplicatedPseudoBinding)
|
|
}
|
|
|
|
checkPseudoBinding(1)
|
|
insertPseudoBinding()
|
|
checkPseudoBinding(2)
|
|
removeDuplicated()
|
|
checkPseudoBinding(1)
|
|
|
|
insertPseudoBinding()
|
|
insertPseudoBinding()
|
|
insertPseudoBinding()
|
|
checkPseudoBinding(4)
|
|
removeDuplicated()
|
|
checkPseudoBinding(1)
|
|
removeDuplicated()
|
|
checkPseudoBinding(1)
|
|
}
|
|
|
|
type mockSessionPool struct {
|
|
se sessionapi.Session
|
|
}
|
|
|
|
func (p *mockSessionPool) Get() (pools.Resource, error) {
|
|
return p.se, nil
|
|
}
|
|
|
|
func (p *mockSessionPool) Put(pools.Resource) {}
|
|
|
|
func (p *mockSessionPool) Destroy(pools.Resource) {}
|
|
|
|
func (p *mockSessionPool) Close() {}
|
|
|
|
func TestShowBindingDigestField(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
tk := testkit.NewTestKit(t, store)
|
|
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t1, t2")
|
|
tk.MustExec("create table t1(id int, key(id))")
|
|
tk.MustExec("create table t2(id int, key(id))")
|
|
tk.MustExec("create binding for select * from t1, t2 where t1.id = t2.id using select /*+ merge_join(t1, t2)*/ * from t1, t2 where t1.id = t2.id")
|
|
result := tk.MustQuery("show bindings;")
|
|
rows := result.Rows()[0]
|
|
require.Equal(t, len(rows), 11)
|
|
require.Equal(t, rows[9], "ac1ceb4eb5c01f7c03e29b7d0d6ab567e563f4c93164184cde218f20d07fd77c")
|
|
tk.MustExec("drop binding for select * from t1, t2 where t1.id = t2.id")
|
|
result = tk.MustQuery("show bindings;")
|
|
require.Equal(t, len(result.Rows()), 0)
|
|
|
|
tk.MustExec("create global binding for select * from t1, t2 where t1.id = t2.id using select /*+ merge_join(t1, t2)*/ * from t1, t2 where t1.id = t2.id")
|
|
result = tk.MustQuery("show global bindings;")
|
|
rows = result.Rows()[0]
|
|
require.Equal(t, len(rows), 11)
|
|
require.Equal(t, rows[9], "ac1ceb4eb5c01f7c03e29b7d0d6ab567e563f4c93164184cde218f20d07fd77c")
|
|
tk.MustExec("drop global binding for select * from t1, t2 where t1.id = t2.id")
|
|
result = tk.MustQuery("show global bindings;")
|
|
require.Equal(t, len(result.Rows()), 0)
|
|
}
|
|
|
|
func TestIssue64558(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec(`use test`)
|
|
tk.MustExec(`create table t (a int)`)
|
|
tk.MustExec(`create global binding using select * from t`)
|
|
sqlDigest := tk.MustQuery(`show global bindings`).Rows()[0][9].(string)
|
|
tk.MustExec(fmt.Sprintf(`set binding disabled for sql digest '%s'`, sqlDigest))
|
|
tk.MustQuery(`show warnings`).Check(testkit.Rows()) // no warning
|
|
}
|
|
|
|
func TestOptimizeOnlyOnce(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t(a int, b int, index idxa(a))")
|
|
tk.MustExec("create global binding for select * from t using select * from t use index(idxa)")
|
|
require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/pkg/planner/checkOptimizeCountOne", "return(\"select * from t\")"))
|
|
defer func() {
|
|
require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/pkg/planner/checkOptimizeCountOne"))
|
|
}()
|
|
tk.MustQuery("select * from t").Check(testkit.Rows())
|
|
}
|
|
|
|
func TestNormalizeStmtForBinding(t *testing.T) {
|
|
tests := []struct {
|
|
sql string
|
|
normalized string
|
|
digest string
|
|
}{
|
|
{"select 1 from b where (x,y) in ((1, 3), ('3', 1))", "select ? from `b` where row ( `x` , `y` ) in ( ... )", "ab6c607d118c24030807f8d1c7c846ec23e3b752fd88ed763bb8e26fbfa56a83"},
|
|
{"select 1 from b where (x,y) in ((1, 3), ('3', 1), (2, 3))", "select ? from `b` where row ( `x` , `y` ) in ( ... )", "ab6c607d118c24030807f8d1c7c846ec23e3b752fd88ed763bb8e26fbfa56a83"},
|
|
{"select 1 from b where (x,y) in ((1, 3), ('3', 1), (2, 3),('x', 'y'))", "select ? from `b` where row ( `x` , `y` ) in ( ... )", "ab6c607d118c24030807f8d1c7c846ec23e3b752fd88ed763bb8e26fbfa56a83"},
|
|
{"select 1 from b where (x,y) in ((1, 3), ('3', 1), (2, 3),('x', 'y'),('x', 'y'))", "select ? from `b` where row ( `x` , `y` ) in ( ... )", "ab6c607d118c24030807f8d1c7c846ec23e3b752fd88ed763bb8e26fbfa56a83"},
|
|
{"select 1 from b where (x) in ((1), ('3'), (2),('x'),('x'))", "select ? from `b` where ( `x` ) in ( ( ... ) )", "03e6e1eb3d76b69363922ff269284b359ca73351001ba0e82d3221c740a6a14c"},
|
|
{"select 1 from b where (x) in ((1), ('3'), (2),('x'))", "select ? from `b` where ( `x` ) in ( ( ... ) )", "03e6e1eb3d76b69363922ff269284b359ca73351001ba0e82d3221c740a6a14c"},
|
|
}
|
|
for _, test := range tests {
|
|
stmt, _, _ := utilNormalizeWithDefaultDB(t, test.sql)
|
|
n, digest := bindinfo.NormalizeStmtForBinding(stmt, "", true)
|
|
require.Equalf(t, test.normalized, n, "sql: %s", test.sql)
|
|
require.Equalf(t, test.digest, digest, "sql: %s", test.sql)
|
|
}
|
|
}
|
|
|
|
func TestHintsSetID(t *testing.T) {
|
|
store, dom := testkit.CreateMockStoreAndDomain(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t(a int, index idx_a(a))")
|
|
tk.MustExec("create global binding for select * from t where a > 10 using select /*+ use_index(test.t, idx_a) */ * from t where a > 10")
|
|
// Verify the added Binding contains ID with restored query block.
|
|
stmt, err := parser.New().ParseOneStmt("select * from t where a > ?", "", "")
|
|
require.NoError(t, err)
|
|
_, noDBDigest := bindinfo.NormalizeStmtForBinding(stmt, "", true)
|
|
binding, matched := dom.BindingHandle().MatchingBinding(tk.Session(), noDBDigest, bindinfo.CollectTableNames(stmt))
|
|
require.True(t, matched)
|
|
require.Equal(t, "select * from `test` . `t` where `a` > ?", binding.OriginalSQL)
|
|
require.Equal(t, "use_index(@`sel_1` `test`.`t` `idx_a`)", binding.ID)
|
|
|
|
utilCleanBindingEnv(tk)
|
|
tk.MustExec("create global binding for select * from t where a > 10 using select /*+ use_index(t, idx_a) */ * from t where a > 10")
|
|
_, noDBDigest = bindinfo.NormalizeStmtForBinding(stmt, "", true)
|
|
binding, matched = dom.BindingHandle().MatchingBinding(tk.Session(), noDBDigest, bindinfo.CollectTableNames(stmt))
|
|
require.True(t, matched)
|
|
require.Equal(t, "select * from `test` . `t` where `a` > ?", binding.OriginalSQL)
|
|
require.Equal(t, "use_index(@`sel_1` `test`.`t` `idx_a`)", binding.ID)
|
|
|
|
utilCleanBindingEnv(tk)
|
|
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")
|
|
_, noDBDigest = bindinfo.NormalizeStmtForBinding(stmt, "", true)
|
|
binding, matched = dom.BindingHandle().MatchingBinding(tk.Session(), noDBDigest, bindinfo.CollectTableNames(stmt))
|
|
require.True(t, matched)
|
|
require.Equal(t, "select * from `test` . `t` where `a` > ?", binding.OriginalSQL)
|
|
require.Equal(t, "use_index(@`sel_1` `test`.`t` `idx_a`)", binding.ID)
|
|
|
|
utilCleanBindingEnv(tk)
|
|
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")
|
|
_, noDBDigest = bindinfo.NormalizeStmtForBinding(stmt, "", true)
|
|
binding, matched = dom.BindingHandle().MatchingBinding(tk.Session(), noDBDigest, bindinfo.CollectTableNames(stmt))
|
|
require.True(t, matched)
|
|
require.Equal(t, "select * from `test` . `t` where `a` > ?", binding.OriginalSQL)
|
|
require.Equal(t, "use_index(@`sel_1` `test`.`t` `idx_a`)", binding.ID)
|
|
|
|
utilCleanBindingEnv(tk)
|
|
tk.MustExec("create global binding for select * from t where a > 10 using select /*+ use_index(T, IDX_A) */ * from t where a > 10")
|
|
_, noDBDigest = bindinfo.NormalizeStmtForBinding(stmt, "", true)
|
|
binding, matched = dom.BindingHandle().MatchingBinding(tk.Session(), noDBDigest, bindinfo.CollectTableNames(stmt))
|
|
require.True(t, matched)
|
|
require.Equal(t, "select * from `test` . `t` where `a` > ?", binding.OriginalSQL)
|
|
require.Equal(t, "use_index(@`sel_1` `test`.`t` `idx_a`)", binding.ID)
|
|
|
|
utilCleanBindingEnv(tk)
|
|
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")
|
|
_, noDBDigest = bindinfo.NormalizeStmtForBinding(stmt, "", true)
|
|
binding, matched = dom.BindingHandle().MatchingBinding(tk.Session(), noDBDigest, bindinfo.CollectTableNames(stmt))
|
|
require.True(t, matched)
|
|
require.Equal(t, "select * from `test` . `t` where `a` > ?", binding.OriginalSQL)
|
|
}
|
|
|
|
func TestErrorBind(t *testing.T) {
|
|
store, dom := testkit.CreateMockStoreAndDomain(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
tk.MustContainErrMsg("create global binding for select * xxx", "You have an error in your SQL syntax")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("drop table if exists t1")
|
|
tk.MustExec("create table t(i int, s varchar(20))")
|
|
tk.MustExec("create table t1(i int, s varchar(20))")
|
|
tk.MustExec("create index index_t on t(i,s)")
|
|
|
|
_, err := tk.Exec("create global binding for select * from t where i>100 using select * from t use index(index_t) where i>100")
|
|
require.NoError(t, err, "err %v", err)
|
|
|
|
stmt, err := parser.New().ParseOneStmt("select * from test . t where i > ?", "", "")
|
|
require.NoError(t, err)
|
|
_, noDBDigest := bindinfo.NormalizeStmtForBinding(stmt, "", true)
|
|
binding, matched := dom.BindingHandle().MatchingBinding(tk.Session(), noDBDigest, bindinfo.CollectTableNames(stmt))
|
|
require.True(t, matched)
|
|
require.Equal(t, "select * from `test` . `t` where `i` > ?", binding.OriginalSQL)
|
|
require.Equal(t, "SELECT * FROM `test`.`t` USE INDEX (`index_t`) WHERE `i` > 100", binding.BindSQL)
|
|
require.Equal(t, "test", binding.Db)
|
|
require.Equal(t, bindinfo.StatusEnabled, binding.Status)
|
|
require.NotNil(t, binding.Charset)
|
|
require.NotNil(t, binding.Collation)
|
|
require.NotNil(t, binding.CreateTime)
|
|
require.NotNil(t, binding.UpdateTime)
|
|
|
|
tk.MustExec("drop index index_t on t")
|
|
require.Equal(t, 1, len(tk.MustQuery(`show global bindings`).Rows()))
|
|
tk.MustQuery("select * from t where i > 10")
|
|
}
|
|
|
|
func TestBestPlanInBaselines(t *testing.T) {
|
|
store, dom := testkit.CreateMockStoreAndDomain(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t(a int, b int, INDEX ia (a), INDEX ib (b));")
|
|
tk.MustExec("insert into t value(1, 1);")
|
|
|
|
// before binding
|
|
tk.MustQuery("select a, b from t where a = 3 limit 1, 100")
|
|
require.Equal(t, "t:ia", tk.Session().GetSessionVars().StmtCtx.IndexNames[0])
|
|
tk.MustUseIndex("select a, b from t where a = 3 limit 1, 100", "ia(a)")
|
|
|
|
tk.MustQuery("select a, b from t where b = 3 limit 1, 100")
|
|
require.Equal(t, "t:ib", tk.Session().GetSessionVars().StmtCtx.IndexNames[0])
|
|
tk.MustUseIndex("select a, b from t where b = 3 limit 1, 100", "ib(b)")
|
|
|
|
tk.MustExec(`create global binding for select a, b from t where a = 1 limit 0, 1 using select /*+ use_index(@sel_1 test.t ia) */ a, b from t where a = 1 limit 0, 1`)
|
|
tk.MustExec(`create global binding for select a, b from t where b = 1 limit 0, 1 using select /*+ use_index(@sel_1 test.t ib) */ a, b from t where b = 1 limit 0, 1`)
|
|
|
|
stmt, _, _ := utilNormalizeWithDefaultDB(t, "select a, b from t where a = 1 limit 0, 1")
|
|
|
|
_, noDBDigest := bindinfo.NormalizeStmtForBinding(stmt, "", true)
|
|
binding, matched := dom.BindingHandle().MatchingBinding(tk.Session(), noDBDigest, bindinfo.CollectTableNames(stmt))
|
|
require.True(t, matched)
|
|
require.Equal(t, "select `a` , `b` from `test` . `t` where `a` = ? limit ...", binding.OriginalSQL)
|
|
require.Equal(t, "SELECT /*+ use_index(@`sel_1` `test`.`t` `ia`)*/ `a`,`b` FROM `test`.`t` WHERE `a` = 1 LIMIT 0,1", binding.BindSQL)
|
|
require.Equal(t, "test", binding.Db)
|
|
require.Equal(t, bindinfo.StatusEnabled, binding.Status)
|
|
|
|
tk.MustQuery("select a, b from t where a = 3 limit 1, 10")
|
|
require.Equal(t, "t:ia", tk.Session().GetSessionVars().StmtCtx.IndexNames[0])
|
|
tk.MustUseIndex("select a, b from t where a = 3 limit 1, 100", "ia(a)")
|
|
|
|
tk.MustQuery("select a, b from t where b = 3 limit 1, 100")
|
|
require.Equal(t, "t:ib", tk.Session().GetSessionVars().StmtCtx.IndexNames[0])
|
|
tk.MustUseIndex("select a, b from t where b = 3 limit 1, 100", "ib(b)")
|
|
}
|
|
|
|
// TestBindingSymbolList tests sql with "?, ?, ?, ?", fixes #13871
|
|
func TestBindingSymbolList(t *testing.T) {
|
|
store, dom := testkit.CreateMockStoreAndDomain(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t(a int, b int, INDEX ia (a), INDEX ib (b));")
|
|
tk.MustExec("insert into t value(1, 1);")
|
|
|
|
// before binding
|
|
tk.MustQuery("select a, b from t where a = 3 limit 1, 100")
|
|
require.Equal(t, "t:ia", tk.Session().GetSessionVars().StmtCtx.IndexNames[0])
|
|
tk.MustUseIndex("select a, b from t where a = 3 limit 1, 100", "ia(a)")
|
|
|
|
tk.MustExec(`create global binding for select a, b from t where a = 1 limit 0, 1 using select a, b from t use index (ib) where a = 1 limit 0, 1`)
|
|
|
|
// after binding
|
|
tk.MustQuery("select a, b from t where a = 3 limit 1, 100")
|
|
require.Equal(t, "t:ib", tk.Session().GetSessionVars().StmtCtx.IndexNames[0])
|
|
tk.MustUseIndex("select a, b from t where a = 3 limit 1, 100", "ib(b)")
|
|
|
|
// Normalize
|
|
stmt, err := parser.New().ParseOneStmt("select a, b from test . t where a = 1 limit 0, 1", "", "")
|
|
require.NoError(t, err)
|
|
|
|
_, noDBDigest := bindinfo.NormalizeStmtForBinding(stmt, "", true)
|
|
binding, matched := dom.BindingHandle().MatchingBinding(tk.Session(), noDBDigest, bindinfo.CollectTableNames(stmt))
|
|
require.True(t, matched)
|
|
require.Equal(t, "select `a` , `b` from `test` . `t` where `a` = ? limit ...", binding.OriginalSQL)
|
|
require.Equal(t, "SELECT `a`,`b` FROM `test`.`t` USE INDEX (`ib`) WHERE `a` = 1 LIMIT 0,1", binding.BindSQL)
|
|
require.Equal(t, "test", binding.Db)
|
|
require.Equal(t, bindinfo.StatusEnabled, binding.Status)
|
|
require.NotNil(t, binding.Charset)
|
|
require.NotNil(t, binding.Collation)
|
|
require.NotNil(t, binding.CreateTime)
|
|
require.NotNil(t, binding.UpdateTime)
|
|
}
|
|
|
|
func TestBindingQueryInList(t *testing.T) {
|
|
store, dom := testkit.CreateMockStoreAndDomain(t)
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec(`create table t (a int)`)
|
|
|
|
inList := []string{"(1)", "(1, 2)", "(1, 2, 3)"}
|
|
for _, bindingInList := range inList {
|
|
tk.MustExec(`create global binding using select * from t where a in ` + bindingInList)
|
|
require.NoErrorf(t, dom.BindingHandle().LoadFromStorageToCache(true, false), "bindingInList: %+v", bindingInList)
|
|
require.Equalf(t, 1, len(tk.MustQuery(`show global bindings`).Rows()), "bindingInList: %+v", bindingInList)
|
|
|
|
for _, queryInList := range inList {
|
|
tk.MustQuery(`select * from t where a in ` + queryInList)
|
|
tk.MustQuery(`select @@last_plan_from_binding`).Check(testkit.Rows("1"))
|
|
}
|
|
|
|
tk.MustExec(`drop global binding for select * from t where a in ` + bindingInList)
|
|
require.NoErrorf(t, dom.BindingHandle().LoadFromStorageToCache(true, false), "bindingInList: %+v", bindingInList)
|
|
require.Equalf(t, 0, len(tk.MustQuery(`show global bindings`).Rows()), "bindingInList: %+v", bindingInList)
|
|
}
|
|
}
|
|
|
|
func TestIssue64070(t *testing.T) {
|
|
store, _ := testkit.CreateMockStoreAndDomain(t)
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec(`set tidb_opt_enable_fuzzy_binding=true`)
|
|
tk.MustExec(`create table tttt (a int)`)
|
|
tk.MustExec(`create global binding using select * from test.tttt`)
|
|
sqlDigest := tk.MustQuery(`select sql_digest from mysql.bind_info where bind_sql like "%tttt%"`).Rows()[0][0].(string)
|
|
tk.MustExec(fmt.Sprintf(`SET BINDING DISABLED FOR SQL DIGEST '%v'`, sqlDigest)) // disable this binding
|
|
tk.MustExec(`create global binding using select * from *.tttt`)
|
|
tk.MustQuery(`select bind_sql, status from mysql.bind_info where source != "builtin" order by bind_sql`).Check(testkit.Rows(
|
|
"SELECT * FROM `*`.`tttt` enabled", // enabled cross-db binding v.s. disabled normal binding
|
|
"SELECT * FROM `test`.`tttt` disabled"))
|
|
tk.MustQuery(`select * from tttt`)
|
|
tk.MustQuery(`select @@last_plan_from_binding`).Check(testkit.Rows("1")) // use the cross-db binding
|
|
}
|
|
|
|
// TestBindingInListWithSingleLiteral tests sql with "IN (Lit)", fixes #44298
|
|
func TestBindingInListWithSingleLiteral(t *testing.T) {
|
|
store, dom := testkit.CreateMockStoreAndDomain(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t(a int, b int, INDEX ia (a), INDEX ib (b));")
|
|
tk.MustExec("insert into t value(1, 1);")
|
|
|
|
// GIVEN
|
|
sqlcmd := "select a, b from t where a in (1)"
|
|
bindingStmt := `create global binding for select a, b from t where a in (1, 2, 3) using select a, b from t use index (ib) where a in (1, 2, 3)`
|
|
|
|
// before binding
|
|
tk.MustQuery(sqlcmd)
|
|
require.Equal(t, "t:ia", tk.Session().GetSessionVars().StmtCtx.IndexNames[0])
|
|
tk.MustUseIndex(sqlcmd, "ia(a)")
|
|
|
|
tk.MustExec(bindingStmt)
|
|
|
|
// after binding
|
|
tk.MustQuery(sqlcmd)
|
|
require.Equal(t, "t:ib", tk.Session().GetSessionVars().StmtCtx.IndexNames[0])
|
|
tk.MustUseIndex(sqlcmd, "ib(b)")
|
|
|
|
tk.MustQuery("select @@last_plan_from_binding").Check(testkit.Rows("1"))
|
|
|
|
// Normalize
|
|
stmt, err := parser.New().ParseOneStmt("select a, b from test . t where a in (1)", "", "")
|
|
require.NoError(t, err)
|
|
|
|
_, noDBDigest := bindinfo.NormalizeStmtForBinding(stmt, "", true)
|
|
binding, matched := dom.BindingHandle().MatchingBinding(tk.Session(), noDBDigest, bindinfo.CollectTableNames(stmt))
|
|
require.True(t, matched)
|
|
require.Equal(t, "select `a` , `b` from `test` . `t` where `a` in ( ... )", binding.OriginalSQL)
|
|
require.Equal(t, "SELECT `a`,`b` FROM `test`.`t` USE INDEX (`ib`) WHERE `a` IN (1,2,3)", binding.BindSQL)
|
|
require.Equal(t, "test", binding.Db)
|
|
require.Equal(t, bindinfo.StatusEnabled, binding.Status)
|
|
require.NotNil(t, binding.Charset)
|
|
require.NotNil(t, binding.Collation)
|
|
require.NotNil(t, binding.CreateTime)
|
|
require.NotNil(t, binding.UpdateTime)
|
|
}
|
|
|
|
func utilCleanBindingEnv(tk *testkit.TestKit) {
|
|
tk.MustExec("update mysql.bind_info set status='deleted' where source != 'builtin'")
|
|
tk.MustExec(`admin reload bindings`)
|
|
tk.MustExec("delete from mysql.bind_info where source != 'builtin'")
|
|
tk.MustExec(`admin reload bindings`)
|
|
}
|
|
|
|
func utilNormalizeWithDefaultDB(t *testing.T, sql string) (stmt ast.StmtNode, normalized, digest string) {
|
|
testParser := parser.New()
|
|
stmt, err := testParser.ParseOneStmt(sql, "", "")
|
|
require.NoError(t, err)
|
|
normalized, digestResult := parser.NormalizeDigestForBinding(bindinfo.RestoreDBForBinding(stmt, "test"))
|
|
return stmt, normalized, digestResult.String()
|
|
}
|