// Copyright 2022 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 executor_test import ( "context" "fmt" "net" "os" "strconv" "testing" "time" "github.com/pingcap/tidb/config" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/expression" "github.com/pingcap/tidb/parser" "github.com/pingcap/tidb/parser/auth" "github.com/pingcap/tidb/parser/mysql" "github.com/pingcap/tidb/server" "github.com/pingcap/tidb/testkit" "github.com/pingcap/tidb/util" "github.com/stretchr/testify/require" "google.golang.org/grpc" ) func createRPCServer(t *testing.T, dom *domain.Domain) *grpc.Server { sm := &testkit.MockSessionManager{} sm.PS = append(sm.PS, &util.ProcessInfo{ ID: 1, User: "root", Host: "127.0.0.1", Command: mysql.ComQuery, }) lis, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) srv := server.NewRPCServer(config.GetGlobalConfig(), dom, sm) port := lis.Addr().(*net.TCPAddr).Port go func() { err = srv.Serve(lis) require.NoError(t, err) }() config.UpdateGlobal(func(conf *config.Config) { conf.Status.StatusPort = uint(port) }) return srv } func TestClusterTableSlowQuery(t *testing.T) { store, dom := testkit.CreateMockStoreAndDomain(t) srv := createRPCServer(t, dom) defer srv.Stop() logData0 := "" logData1 := ` # Time: 2020-02-15T18:00:01.000000+08:00 select 1; # Time: 2020-02-15T19:00:05.000000+08:00 select 2;` logData2 := ` # Time: 2020-02-16T18:00:01.000000+08:00 select 3; # Time: 2020-02-16T18:00:05.000000+08:00 select 4;` logData3 := ` # Time: 2020-02-16T19:00:00.000000+08:00 select 5; # Time: 2020-02-17T18:00:05.000000+08:00 select 6;` logData4 := ` # Time: 2020-05-14T19:03:54.314615176+08:00 select 7;` logData := []string{logData0, logData1, logData2, logData3, logData4} fileName0 := "tidb-slow-2020-02-14T19-04-05.01.log" fileName1 := "tidb-slow-2020-02-15T19-04-05.01.log" fileName2 := "tidb-slow-2020-02-16T19-04-05.01.log" fileName3 := "tidb-slow-2020-02-17T18-00-05.01.log" fileName4 := "tidb-slow.log" fileNames := []string{fileName0, fileName1, fileName2, fileName3, fileName4} prepareLogs(t, logData, fileNames) defer func() { removeFiles(t, fileNames) }() tk := testkit.NewTestKit(t, store) loc, err := time.LoadLocation("Asia/Shanghai") require.NoError(t, err) tk.Session().GetSessionVars().TimeZone = loc tk.MustExec("use information_schema") cases := []struct { prepareSQL string sql string result []string }{ { sql: "select count(*),min(time),max(time) from %s where time > '2019-01-26 21:51:00' and time < now()", result: []string{"7|2020-02-15 18:00:01.000000|2020-05-14 19:03:54.314615"}, }, { sql: "select count(*),min(time),max(time) from %s where time > '2020-02-15 19:00:00' and time < '2020-02-16 18:00:02'", result: []string{"2|2020-02-15 19:00:05.000000|2020-02-16 18:00:01.000000"}, }, { sql: "select count(*),min(time),max(time) from %s where time > '2020-02-16 18:00:02' and time < '2020-02-17 17:00:00'", result: []string{"2|2020-02-16 18:00:05.000000|2020-02-16 19:00:00.000000"}, }, { sql: "select count(*),min(time),max(time) from %s where time > '2020-02-16 18:00:02' and time < '2020-02-17 20:00:00'", result: []string{"3|2020-02-16 18:00:05.000000|2020-02-17 18:00:05.000000"}, }, { sql: "select count(*),min(time),max(time) from %s", result: []string{"1|2020-05-14 19:03:54.314615|2020-05-14 19:03:54.314615"}, }, { sql: "select count(*),min(time) from %s where time > '2020-02-16 20:00:00'", result: []string{"1|2020-02-17 18:00:05.000000"}, }, { sql: "select count(*) from %s where time > '2020-02-17 20:00:00'", result: []string{"0"}, }, { sql: "select query from %s where time > '2019-01-26 21:51:00' and time < now()", result: []string{"select 1;", "select 2;", "select 3;", "select 4;", "select 5;", "select 6;", "select 7;"}, }, // Test for different timezone. { prepareSQL: "set @@time_zone = '+00:00'", sql: "select time from %s where time = '2020-02-17 10:00:05.000000'", result: []string{"2020-02-17 10:00:05.000000"}, }, { prepareSQL: "set @@time_zone = '+02:00'", sql: "select time from %s where time = '2020-02-17 12:00:05.000000'", result: []string{"2020-02-17 12:00:05.000000"}, }, // Test for issue 17224 { prepareSQL: "set @@time_zone = '+08:00'", sql: "select time from %s where time = '2020-05-14 19:03:54.314615'", result: []string{"2020-05-14 19:03:54.314615"}, }, } for _, cas := range cases { if len(cas.prepareSQL) > 0 { tk.MustExec(cas.prepareSQL) } sql := fmt.Sprintf(cas.sql, "slow_query") tk.MustQuery(sql).Check(testkit.RowsWithSep("|", cas.result...)) sql = fmt.Sprintf(cas.sql, "cluster_slow_query") tk.MustQuery(sql).Check(testkit.RowsWithSep("|", cas.result...)) } } func TestIssue20236(t *testing.T) { store, dom := testkit.CreateMockStoreAndDomain(t) srv := createRPCServer(t, dom) defer srv.Stop() logData0 := "" logData1 := ` # Time: 2020-02-15T18:00:01.000000+08:00 select 1; # Time: 2020-02-15T19:00:05.000000+08:00 select 2; # Time: 2020-02-15T20:00:05.000000+08:00` logData2 := `select 3; # Time: 2020-02-16T18:00:01.000000+08:00 select 4; # Time: 2020-02-16T18:00:05.000000+08:00 select 5;` logData3 := ` # Time: 2020-02-16T19:00:00.000000+08:00 select 6; # Time: 2020-02-17T18:00:05.000000+08:00 select 7; # Time: 2020-02-17T19:00:00.000000+08:00` logData4 := `select 8; # Time: 2020-02-17T20:00:00.000000+08:00 select 9 # Time: 2020-05-14T19:03:54.314615176+08:00 select 10;` logData := []string{logData0, logData1, logData2, logData3, logData4} fileName0 := "tidb-slow-2020-02-14T19-04-05.01.log" fileName1 := "tidb-slow-2020-02-15T19-04-05.01.log" fileName2 := "tidb-slow-2020-02-16T19-04-05.01.log" fileName3 := "tidb-slow-2020-02-17T18-00-05.01.log" fileName4 := "tidb-slow.log" fileNames := []string{fileName0, fileName1, fileName2, fileName3, fileName4} prepareLogs(t, logData, fileNames) defer func() { removeFiles(t, fileNames) }() tk := testkit.NewTestKit(t, store) loc, err := time.LoadLocation("Asia/Shanghai") require.NoError(t, err) tk.Session().GetSessionVars().TimeZone = loc tk.MustExec("use information_schema") cases := []struct { prepareSQL string sql string result []string }{ { prepareSQL: "set @@time_zone = '+08:00'", sql: "select time from cluster_slow_query where time > '2020-02-17 12:00:05.000000' and time < '2020-05-14 20:00:00.000000'", result: []string{"2020-02-17 18:00:05.000000", "2020-02-17 19:00:00.000000", "2020-05-14 19:03:54.314615"}, }, { prepareSQL: "set @@time_zone = '+08:00'", sql: "select time from cluster_slow_query where time > '2020-02-17 12:00:05.000000' and time < '2020-05-14 20:00:00.000000' order by time desc", result: []string{"2020-05-14 19:03:54.314615", "2020-02-17 19:00:00.000000", "2020-02-17 18:00:05.000000"}, }, { prepareSQL: "set @@time_zone = '+08:00'", sql: "select time from cluster_slow_query where (time > '2020-02-15 18:00:00' and time < '2020-02-15 20:01:00') or (time > '2020-02-17 18:00:00' and time < '2020-05-14 20:00:00') order by time", result: []string{"2020-02-15 18:00:01.000000", "2020-02-15 19:00:05.000000", "2020-02-17 18:00:05.000000", "2020-02-17 19:00:00.000000", "2020-05-14 19:03:54.314615"}, }, { prepareSQL: "set @@time_zone = '+08:00'", sql: "select time from cluster_slow_query where (time > '2020-02-15 18:00:00' and time < '2020-02-15 20:01:00') or (time > '2020-02-17 18:00:00' and time < '2020-05-14 20:00:00') order by time desc", result: []string{"2020-05-14 19:03:54.314615", "2020-02-17 19:00:00.000000", "2020-02-17 18:00:05.000000", "2020-02-15 19:00:05.000000", "2020-02-15 18:00:01.000000"}, }, { prepareSQL: "set @@time_zone = '+08:00'", sql: "select count(*) from cluster_slow_query where time > '2020-02-15 18:00:00.000000' and time < '2020-05-14 20:00:00.000000' order by time desc", result: []string{"9"}, }, { prepareSQL: "set @@time_zone = '+08:00'", sql: "select count(*) from cluster_slow_query where (time > '2020-02-16 18:00:00' and time < '2020-05-14 20:00:00') or (time > '2020-02-17 18:00:00' and time < '2020-05-17 20:00:00')", result: []string{"6"}, }, { prepareSQL: "set @@time_zone = '+08:00'", sql: "select count(*) from cluster_slow_query where time > '2020-02-16 18:00:00.000000' and time < '2020-02-17 20:00:00.000000' order by time desc", result: []string{"5"}, }, { prepareSQL: "set @@time_zone = '+08:00'", sql: "select time from cluster_slow_query where time > '2020-02-16 18:00:00.000000' and time < '2020-05-14 20:00:00.000000' order by time desc limit 3", result: []string{"2020-05-14 19:03:54.314615", "2020-02-17 19:00:00.000000", "2020-02-17 18:00:05.000000"}, }, } for _, cas := range cases { if len(cas.prepareSQL) > 0 { tk.MustExec(cas.prepareSQL) } tk.MustQuery(cas.sql).Check(testkit.RowsWithSep("|", cas.result...)) } } func TestSQLDigestTextRetriever(t *testing.T) { store, dom := testkit.CreateMockStoreAndDomain(t) srv := createRPCServer(t, dom) defer srv.Stop() tkInit := testkit.NewTestKit(t, store) tkInit.MustExec("use test") tkInit.MustExec("set global tidb_enable_stmt_summary = 1") tkInit.MustQuery("select @@global.tidb_enable_stmt_summary").Check(testkit.Rows("1")) tkInit.MustExec("drop table if exists test_sql_digest_text_retriever") tkInit.MustExec("create table test_sql_digest_text_retriever (id int primary key, v int)") tk := testkit.NewTestKit(t, store) tk.MustExec("use test") require.NoError(t, tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil)) tk.MustExec("insert into test_sql_digest_text_retriever values (1, 1)") insertNormalized, insertDigest := parser.NormalizeDigest("insert into test_sql_digest_text_retriever values (1, 1)") _, updateDigest := parser.NormalizeDigest("update test_sql_digest_text_retriever set v = v + 1 where id = 1") r := &expression.SQLDigestTextRetriever{ SQLDigestsMap: map[string]string{ insertDigest.String(): "", updateDigest.String(): "", }, } err := r.RetrieveLocal(context.Background(), tk.Session()) require.NoError(t, err) require.Equal(t, insertNormalized, r.SQLDigestsMap[insertDigest.String()]) require.Equal(t, "", r.SQLDigestsMap[updateDigest.String()]) } func TestFunctionDecodeSQLDigests(t *testing.T) { store, dom := testkit.CreateMockStoreAndDomain(t) srv := createRPCServer(t, dom) defer srv.Stop() tk := testkit.NewTestKit(t, store) tk.MustExec("use test") require.NoError(t, tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil)) tk.MustExec("set global tidb_enable_stmt_summary = 1") tk.MustQuery("select @@global.tidb_enable_stmt_summary").Check(testkit.Rows("1")) tk.MustExec("drop table if exists test_func_decode_sql_digests") tk.MustExec("create table test_func_decode_sql_digests(id int primary key, v int)") q1 := "begin" norm1, digest1 := parser.NormalizeDigest(q1) q2 := "select @@tidb_current_ts" norm2, digest2 := parser.NormalizeDigest(q2) q3 := "select id, v from test_func_decode_sql_digests where id = 1 for update" norm3, digest3 := parser.NormalizeDigest(q3) // TIDB_DECODE_SQL_DIGESTS function doesn't actually do "decoding", instead it queries `statements_summary` and it's // variations for the corresponding statements. // Execute the statements so that the queries will be saved into statements_summary table. tk.MustExec(q1) // Save the ts to query the transaction from tidb_trx. ts, err := strconv.ParseUint(tk.MustQuery(q2).Rows()[0][0].(string), 10, 64) require.NoError(t, err) require.Greater(t, ts, uint64(0)) tk.MustExec(q3) tk.MustExec("rollback") // Test statements truncating. decoded := fmt.Sprintf(`["%s","%s","%s"]`, norm1, norm2, norm3) digests := fmt.Sprintf(`["%s","%s","%s"]`, digest1, digest2, digest3) tk.MustQuery("select tidb_decode_sql_digests(?, 0)", digests).Check(testkit.Rows(decoded)) // The three queries are shorter than truncate length, equal to truncate length and longer than truncate length respectively. tk.MustQuery("select tidb_decode_sql_digests(?, ?)", digests, len(norm2)).Check(testkit.Rows( "[\"begin\",\"select @@tidb_current_ts\",\"select `id` , `v` from `...\"]")) // Empty array. tk.MustQuery("select tidb_decode_sql_digests('[]')").Check(testkit.Rows("[]")) // NULL tk.MustQuery("select tidb_decode_sql_digests(null)").Check(testkit.Rows("")) // Array containing wrong types and not-existing digests (maps to null). tk.MustQuery("select tidb_decode_sql_digests(?)", fmt.Sprintf(`["%s",1,null,"%s",{"a":1},[2],"%s","","abcde"]`, digest1, digest2, digest3)). Check(testkit.Rows(fmt.Sprintf(`["%s",null,null,"%s",null,null,"%s",null,null]`, norm1, norm2, norm3))) // Not JSON array (throws warnings) tk.MustQuery(`select tidb_decode_sql_digests('{"a":1}')`).Check(testkit.Rows("")) tk.MustQuery(`show warnings`).Check(testkit.Rows(`Warning 1210 The argument can't be unmarshalled as JSON array: '{"a":1}'`)) tk.MustQuery(`select tidb_decode_sql_digests('aabbccdd')`).Check(testkit.Rows("")) tk.MustQuery(`show warnings`).Check(testkit.Rows(`Warning 1210 The argument can't be unmarshalled as JSON array: 'aabbccdd'`)) // Invalid argument count. tk.MustGetErrCode("select tidb_decode_sql_digests('a', 1, 2)", 1582) tk.MustGetErrCode("select tidb_decode_sql_digests()", 1582) } func TestFunctionDecodeSQLDigestsPrivilege(t *testing.T) { store, dom := testkit.CreateMockStoreAndDomain(t) srv := createRPCServer(t, dom) defer srv.Stop() dropUserTk := testkit.NewTestKit(t, store) require.NoError(t, dropUserTk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil)) tk := testkit.NewTestKit(t, store) require.NoError(t, tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil)) tk.MustExec("create user 'testuser'@'localhost'") defer dropUserTk.MustExec("drop user 'testuser'@'localhost'") require.NoError(t, tk.Session().Auth(&auth.UserIdentity{Username: "testuser", Hostname: "localhost"}, nil, nil)) tk.MustGetErrMsg("select tidb_decode_sql_digests('[\"aa\"]')", "[expression:1227]Access denied; you need (at least one of) the PROCESS privilege(s) for this operation") tk = testkit.NewTestKit(t, store) require.NoError(t, tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil)) tk.MustExec("create user 'testuser2'@'localhost'") defer dropUserTk.MustExec("drop user 'testuser2'@'localhost'") tk.MustExec("grant process on *.* to 'testuser2'@'localhost'") require.NoError(t, tk.Session().Auth(&auth.UserIdentity{Username: "testuser2", Hostname: "localhost"}, nil, nil)) tk.MustExec("select tidb_decode_sql_digests('[\"aa\"]')") } func prepareLogs(t *testing.T, logData []string, fileNames []string) { for i, log := range logData { f, err := os.OpenFile(fileNames[i], os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) require.NoError(t, err) _, err = f.Write([]byte(log)) require.NoError(t, err) require.NoError(t, f.Close()) } } func removeFiles(t *testing.T, fileNames []string) { for _, fileName := range fileNames { require.NoError(t, os.Remove(fileName)) } }