From 0ea48ce4bd43862551186c440296cc267b03c948 Mon Sep 17 00:00:00 2001 From: Xiaoguang Sun Date: Wed, 20 Sep 2023 10:34:42 +0800 Subject: [PATCH] This is an automated cherry-pick of #46976 Signed-off-by: ti-chi-bot --- executor/test/showtest/show_test.go | 2152 ++++++++++++++++ executor/test/simpletest/simple_test.go | 1153 +++++++++ privilege/privileges/cache.go | 19 +- privilege/privileges/privileges.go | 2 +- privilege/privileges/privileges_test.go | 323 ++- server/tests/tidb_test.go | 3124 +++++++++++++++++++++++ 6 files changed, 6750 insertions(+), 23 deletions(-) create mode 100644 executor/test/showtest/show_test.go create mode 100644 executor/test/simpletest/simple_test.go create mode 100644 server/tests/tidb_test.go diff --git a/executor/test/showtest/show_test.go b/executor/test/showtest/show_test.go new file mode 100644 index 0000000000000..16fa29789684e --- /dev/null +++ b/executor/test/showtest/show_test.go @@ -0,0 +1,2152 @@ +// Copyright 2020 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 showtest + +import ( + "context" + "fmt" + "strings" + "testing" + "time" + + "github.com/pingcap/failpoint" + _ "github.com/pingcap/tidb/autoid_service" + "github.com/pingcap/tidb/executor" + "github.com/pingcap/tidb/infoschema" + "github.com/pingcap/tidb/parser/auth" + "github.com/pingcap/tidb/parser/model" + "github.com/pingcap/tidb/parser/mysql" + parsertypes "github.com/pingcap/tidb/parser/types" + plannercore "github.com/pingcap/tidb/planner/core" + "github.com/pingcap/tidb/privilege/privileges" + "github.com/pingcap/tidb/session" + "github.com/pingcap/tidb/sessionctx/variable" + "github.com/pingcap/tidb/testkit" + "github.com/pingcap/tidb/types" + "github.com/pingcap/tidb/util/dbterror/exeerrors" + "github.com/stretchr/testify/require" +) + +func TestShowHistogramsInFlight(t *testing.T) { + store := testkit.CreateMockStore(t) + + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + result := tk.MustQuery("show histograms_in_flight") + rows := result.Rows() + require.Len(t, rows, 1) + require.Equal(t, rows[0][0], "0") +} + +func TestShowOpenTables(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustQuery("show open tables") + tk.MustQuery("show open tables in test") +} + +func TestShowCreateViewDefiner(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + require.NoError(t, tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "%", AuthUsername: "root", AuthHostname: "%"}, nil, nil, nil)) + + tk.MustExec("use test") + tk.MustExec("create or replace view v1 as select 1") + tk.MustQuery("show create view v1").Check(testkit.RowsWithSep("|", "v1|CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`%` SQL SECURITY DEFINER VIEW `v1` (`1`) AS SELECT 1 AS `1`|utf8mb4|utf8mb4_bin")) + tk.MustExec("drop view v1") +} + +func TestShowCreateTable(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + tk.MustExec("use test") + tk.MustExec("drop table if exists t1") + tk.MustExec("create table t1(a int,b int)") + tk.MustExec("drop view if exists v1") + tk.MustExec("create or replace definer=`root`@`127.0.0.1` view v1 as select * from t1") + tk.MustQuery("show create table v1").Check(testkit.RowsWithSep("|", "v1|CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`127.0.0.1` SQL SECURITY DEFINER VIEW `v1` (`a`, `b`) AS SELECT `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` FROM `test`.`t1`|utf8mb4|utf8mb4_bin")) + tk.MustQuery("show create view v1").Check(testkit.RowsWithSep("|", "v1|CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`127.0.0.1` SQL SECURITY DEFINER VIEW `v1` (`a`, `b`) AS SELECT `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` FROM `test`.`t1`|utf8mb4|utf8mb4_bin")) + tk.MustExec("drop view v1") + tk.MustExec("drop table t1") + + tk.MustExec("drop view if exists v") + tk.MustExec("create or replace definer=`root`@`127.0.0.1` view v as select JSON_MERGE('{}', '{}') as col;") + tk.MustQuery("show create view v").Check(testkit.RowsWithSep("|", "v|CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`127.0.0.1` SQL SECURITY DEFINER VIEW `v` (`col`) AS SELECT JSON_MERGE(_UTF8MB4'{}', _UTF8MB4'{}') AS `col`|utf8mb4|utf8mb4_bin")) + tk.MustExec("drop view if exists v") + + tk.MustExec("drop table if exists t1") + tk.MustExec("create table t1(a int,b int)") + tk.MustExec("create or replace definer=`root`@`127.0.0.1` view v1 as select avg(a),t1.* from t1 group by a") + tk.MustQuery("show create view v1").Check(testkit.RowsWithSep("|", "v1|CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`127.0.0.1` SQL SECURITY DEFINER VIEW `v1` (`avg(a)`, `a`, `b`) AS SELECT AVG(`a`) AS `avg(a)`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` FROM `test`.`t1` GROUP BY `a`|utf8mb4|utf8mb4_bin")) + tk.MustExec("drop view v1") + tk.MustExec("create or replace definer=`root`@`127.0.0.1` view v1 as select a+b, t1.* , a as c from t1") + tk.MustQuery("show create view v1").Check(testkit.RowsWithSep("|", "v1|CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`127.0.0.1` SQL SECURITY DEFINER VIEW `v1` (`a+b`, `a`, `b`, `c`) AS SELECT `a`+`b` AS `a+b`,`test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`a` AS `c` FROM `test`.`t1`|utf8mb4|utf8mb4_bin")) + tk.MustExec("drop table t1") + tk.MustExec("drop view v1") + + // For issue #9211 + tk.MustExec("create table t(c int, b int as (c + 1))ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;") + tk.MustQuery("show create table `t`").Check(testkit.RowsWithSep("|", + ""+ + "t CREATE TABLE `t` (\n"+ + " `c` int(11) DEFAULT NULL,\n"+ + " `b` int(11) GENERATED ALWAYS AS (`c` + 1) VIRTUAL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + + tk.MustExec("drop table t") + tk.MustExec("create table t(c int, b int as (c + 1) not null)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;") + tk.MustQuery("show create table `t`").Check(testkit.RowsWithSep("|", + ""+ + "t CREATE TABLE `t` (\n"+ + " `c` int(11) DEFAULT NULL,\n"+ + " `b` int(11) GENERATED ALWAYS AS (`c` + 1) VIRTUAL NOT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + tk.MustExec("drop table t") + tk.MustExec("create table t ( a char(10) charset utf8 collate utf8_bin, b char(10) as (rtrim(a)));") + tk.MustQuery("show create table `t`").Check(testkit.RowsWithSep("|", + ""+ + "t CREATE TABLE `t` (\n"+ + " `a` char(10) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,\n"+ + " `b` char(10) GENERATED ALWAYS AS (rtrim(`a`)) VIRTUAL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + tk.MustExec("drop table t") + + tk.MustExec(`drop table if exists different_charset`) + tk.MustExec(`create table different_charset(ch1 varchar(10) charset utf8, ch2 varchar(10) charset binary);`) + tk.MustQuery(`show create table different_charset`).Check(testkit.RowsWithSep("|", + ""+ + "different_charset CREATE TABLE `different_charset` (\n"+ + " `ch1` varchar(10) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,\n"+ + " `ch2` varbinary(10) DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + + tk.MustExec("drop table if exists t") + tk.MustExec("create table `t` (\n" + + "`a` timestamp not null default current_timestamp,\n" + + "`b` timestamp(3) default current_timestamp(3),\n" + + "`c` datetime default current_timestamp,\n" + + "`d` datetime(4) default current_timestamp(4),\n" + + "`e` varchar(20) default 'cUrrent_tImestamp',\n" + + "`f` datetime(2) default current_timestamp(2) on update current_timestamp(2),\n" + + "`g` timestamp(2) default current_timestamp(2) on update current_timestamp(2),\n" + + "`h` date default current_date )") + tk.MustQuery("show create table `t`").Check(testkit.RowsWithSep("|", + ""+ + "t CREATE TABLE `t` (\n"+ + " `a` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n"+ + " `b` timestamp(3) DEFAULT CURRENT_TIMESTAMP(3),\n"+ + " `c` datetime DEFAULT CURRENT_TIMESTAMP,\n"+ + " `d` datetime(4) DEFAULT CURRENT_TIMESTAMP(4),\n"+ + " `e` varchar(20) DEFAULT 'cUrrent_tImestamp',\n"+ + " `f` datetime(2) DEFAULT CURRENT_TIMESTAMP(2) ON UPDATE CURRENT_TIMESTAMP(2),\n"+ + " `g` timestamp(2) DEFAULT CURRENT_TIMESTAMP(2) ON UPDATE CURRENT_TIMESTAMP(2),\n"+ + " `h` date DEFAULT CURRENT_DATE\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + tk.MustExec("drop table t") + + tk.MustExec("create table t (a int, b int) shard_row_id_bits = 4 pre_split_regions=3;") + tk.MustQuery("show create table `t`").Check(testkit.RowsWithSep("|", + ""+ + "t CREATE TABLE `t` (\n"+ + " `a` int(11) DEFAULT NULL,\n"+ + " `b` int(11) DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T! SHARD_ROW_ID_BITS=4 PRE_SPLIT_REGIONS=3 */", + )) + tk.MustExec("drop table t") + + // for issue #20446 + tk.MustExec("drop table if exists t1;") + tk.MustExec("create table t1(c int unsigned default 0);") + tk.MustQuery("show create table `t1`").Check(testkit.RowsWithSep("|", + ""+ + "t1 CREATE TABLE `t1` (\n"+ + " `c` int(10) unsigned DEFAULT '0'\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + tk.MustExec("drop table t1") + + tk.MustExec("CREATE TABLE `log` (" + + "`LOG_ID` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT," + + "`ROUND_ID` bigint(20) UNSIGNED NOT NULL," + + "`USER_ID` int(10) UNSIGNED NOT NULL," + + "`USER_IP` int(10) UNSIGNED DEFAULT NULL," + + "`END_TIME` datetime NOT NULL," + + "`USER_TYPE` int(11) DEFAULT NULL," + + "`APP_ID` int(11) DEFAULT NULL," + + "PRIMARY KEY (`LOG_ID`,`END_TIME`) NONCLUSTERED," + + "KEY `IDX_EndTime` (`END_TIME`)," + + "KEY `IDX_RoundId` (`ROUND_ID`)," + + "KEY `IDX_UserId_EndTime` (`USER_ID`,`END_TIME`)" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=505488 " + + "PARTITION BY RANGE ( month(`end_time`) ) (" + + "PARTITION `p1` VALUES LESS THAN (2)," + + "PARTITION `p2` VALUES LESS THAN (3)," + + "PARTITION `p3` VALUES LESS THAN (4)," + + "PARTITION `p4` VALUES LESS THAN (5)," + + "PARTITION `p5` VALUES LESS THAN (6)," + + "PARTITION `p6` VALUES LESS THAN (7)," + + "PARTITION `p7` VALUES LESS THAN (8)," + + "PARTITION `p8` VALUES LESS THAN (9)," + + "PARTITION `p9` VALUES LESS THAN (10)," + + "PARTITION `p10` VALUES LESS THAN (11)," + + "PARTITION `p11` VALUES LESS THAN (12)," + + "PARTITION `p12` VALUES LESS THAN (MAXVALUE))") + tk.MustQuery("show create table log").Check(testkit.RowsWithSep("|", + "log CREATE TABLE `log` (\n"+ + " `LOG_ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n"+ + " `ROUND_ID` bigint(20) unsigned NOT NULL,\n"+ + " `USER_ID` int(10) unsigned NOT NULL,\n"+ + " `USER_IP` int(10) unsigned DEFAULT NULL,\n"+ + " `END_TIME` datetime NOT NULL,\n"+ + " `USER_TYPE` int(11) DEFAULT NULL,\n"+ + " `APP_ID` int(11) DEFAULT NULL,\n"+ + " PRIMARY KEY (`LOG_ID`,`END_TIME`) /*T![clustered_index] NONCLUSTERED */,\n"+ + " KEY `IDX_EndTime` (`END_TIME`),\n"+ + " KEY `IDX_RoundId` (`ROUND_ID`),\n"+ + " KEY `IDX_UserId_EndTime` (`USER_ID`,`END_TIME`)\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=505488\n"+ + "PARTITION BY RANGE (MONTH(`end_time`))\n"+ + "(PARTITION `p1` VALUES LESS THAN (2),\n"+ + " PARTITION `p2` VALUES LESS THAN (3),\n"+ + " PARTITION `p3` VALUES LESS THAN (4),\n"+ + " PARTITION `p4` VALUES LESS THAN (5),\n"+ + " PARTITION `p5` VALUES LESS THAN (6),\n"+ + " PARTITION `p6` VALUES LESS THAN (7),\n"+ + " PARTITION `p7` VALUES LESS THAN (8),\n"+ + " PARTITION `p8` VALUES LESS THAN (9),\n"+ + " PARTITION `p9` VALUES LESS THAN (10),\n"+ + " PARTITION `p10` VALUES LESS THAN (11),\n"+ + " PARTITION `p11` VALUES LESS THAN (12),\n"+ + " PARTITION `p12` VALUES LESS THAN (MAXVALUE))")) + + // for issue #11831 + tk.MustExec("create table ttt4(a varchar(123) default null collate utf8mb4_unicode_ci)engine=innodb default charset=utf8mb4 collate=utf8mb4_unicode_ci;") + tk.MustQuery("show create table `ttt4`").Check(testkit.RowsWithSep("|", + ""+ + "ttt4 CREATE TABLE `ttt4` (\n"+ + " `a` varchar(123) COLLATE utf8mb4_unicode_ci DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", + )) + tk.MustExec("create table ttt5(a varchar(123) default null)engine=innodb default charset=utf8mb4 collate=utf8mb4_bin;") + tk.MustQuery("show create table `ttt5`").Check(testkit.RowsWithSep("|", + ""+ + "ttt5 CREATE TABLE `ttt5` (\n"+ + " `a` varchar(123) DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + + // for expression index + tk.MustExec("drop table if exists t;") + tk.MustExec("create table t(a int, b real);") + tk.MustExec("alter table t add index expr_idx((a*b+1));") + tk.MustQuery("show create table t;").Check(testkit.RowsWithSep("|", + ""+ + "t CREATE TABLE `t` (\n"+ + " `a` int(11) DEFAULT NULL,\n"+ + " `b` double DEFAULT NULL,\n"+ + " KEY `expr_idx` ((`a` * `b` + 1))\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + + // Fix issue #15175, show create table sequence_name. + tk.MustExec("drop sequence if exists seq") + tk.MustExec("create sequence seq") + tk.MustQuery("show create table seq;").Check(testkit.Rows("seq CREATE SEQUENCE `seq` start with 1 minvalue 1 maxvalue 9223372036854775806 increment by 1 cache 1000 nocycle ENGINE=InnoDB")) + + // Test for issue #15633, 'binary' collation should be ignored in the result of 'show create table'. + tk.MustExec(`drop table if exists binary_collate`) + tk.MustExec(`create table binary_collate(a varchar(10)) default collate=binary;`) + tk.MustQuery(`show create table binary_collate`).Check(testkit.RowsWithSep("|", + ""+ + "binary_collate CREATE TABLE `binary_collate` (\n"+ + " `a` varbinary(10) DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=binary", // binary collate is ignored + )) + tk.MustExec(`drop table if exists binary_collate`) + tk.MustExec(`create table binary_collate(a varchar(10)) default charset=binary collate=binary;`) + tk.MustQuery(`show create table binary_collate`).Check(testkit.RowsWithSep("|", + ""+ + "binary_collate CREATE TABLE `binary_collate` (\n"+ + " `a` varbinary(10) DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=binary", // binary collate is ignored + )) + tk.MustExec(`drop table if exists binary_collate`) + tk.MustExec(`create table binary_collate(a varchar(10)) default charset=utf8mb4 collate=utf8mb4_bin;`) + tk.MustQuery(`show create table binary_collate`).Check(testkit.RowsWithSep("|", + ""+ + "binary_collate CREATE TABLE `binary_collate` (\n"+ + " `a` varchar(10) DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", // non-binary collate is kept. + )) + // Test for issue #17 in bug competition, default num and sequence should be shown without quote. + tk.MustExec(`drop table if exists default_num`) + tk.MustExec("create table default_num(a int default 11)") + tk.MustQuery("show create table default_num").Check(testkit.RowsWithSep("|", + ""+ + "default_num CREATE TABLE `default_num` (\n"+ + " `a` int(11) DEFAULT '11'\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + tk.MustExec(`drop table if exists default_varchar`) + tk.MustExec("create table default_varchar(a varchar(10) default \"haha\")") + tk.MustQuery("show create table default_varchar").Check(testkit.RowsWithSep("|", + ""+ + "default_varchar CREATE TABLE `default_varchar` (\n"+ + " `a` varchar(10) DEFAULT 'haha'\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + tk.MustExec(`drop table if exists default_sequence`) + tk.MustExec("create table default_sequence(a int default nextval(seq))") + tk.MustQuery("show create table default_sequence").Check(testkit.RowsWithSep("|", + ""+ + "default_sequence CREATE TABLE `default_sequence` (\n"+ + " `a` int(11) DEFAULT nextval(`test`.`seq`)\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + + // set @@foreign_key_checks=0, + // This means that the child table can be created before the parent table. + // This behavior is required for mysqldump restores. + tk.MustExec("set @@foreign_key_checks=0") + tk.MustExec(`DROP TABLE IF EXISTS parent, child`) + tk.MustExec(`CREATE TABLE child (id INT NOT NULL PRIMARY KEY auto_increment, parent_id INT NOT NULL, INDEX par_ind (parent_id), CONSTRAINT child_ibfk_1 FOREIGN KEY (parent_id) REFERENCES parent(id))`) + tk.MustExec(`CREATE TABLE parent ( id INT NOT NULL PRIMARY KEY auto_increment )`) + tk.MustQuery(`show create table child`).Check(testkit.RowsWithSep("|", + ""+ + "child CREATE TABLE `child` (\n"+ + " `id` int(11) NOT NULL AUTO_INCREMENT,\n"+ + " `parent_id` int(11) NOT NULL,\n"+ + " PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,\n"+ + " KEY `par_ind` (`parent_id`),\n"+ + " CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `test`.`parent` (`id`)\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + + // Test Foreign keys + ON DELETE / ON UPDATE + tk.MustExec(`DROP TABLE child`) + tk.MustExec(`CREATE TABLE child (id INT NOT NULL PRIMARY KEY auto_increment, parent_id INT NOT NULL, INDEX par_ind (parent_id), CONSTRAINT child_ibfk_1 FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE RESTRICT ON UPDATE CASCADE)`) + tk.MustQuery(`show create table child`).Check(testkit.RowsWithSep("|", + ""+ + "child CREATE TABLE `child` (\n"+ + " `id` int(11) NOT NULL AUTO_INCREMENT,\n"+ + " `parent_id` int(11) NOT NULL,\n"+ + " PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,\n"+ + " KEY `par_ind` (`parent_id`),\n"+ + " CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `test`.`parent` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + + // Test Foreign key refer other database table. + tk.MustExec("create database test1") + tk.MustExec("create database test2") + tk.MustExec("create table test1.t1 (id int key, b int, index(b));") + tk.MustExec("create table test2.t2 (id int key, b int, foreign key fk(b) references test1.t1(id));") + tk.MustQuery("show create table test2.t2").Check(testkit.Rows("t2 CREATE TABLE `t2` (\n" + + " `id` int(11) NOT NULL,\n" + + " `b` int(11) DEFAULT NULL,\n" + + " PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,\n" + + " KEY `fk` (`b`),\n" + + " CONSTRAINT `fk` FOREIGN KEY (`b`) REFERENCES `test1`.`t1` (`id`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin")) + + // Test issue #20327 + tk.MustExec("drop table if exists t;") + tk.MustExec("create table t(a int, b char(10) as ('a'));") + result := tk.MustQuery("show create table t;").Rows()[0][1] + require.Regexp(t, `(?s).*GENERATED ALWAYS AS \(_utf8mb4'a'\).*`, result) + tk.MustExec("drop table if exists t;") + tk.MustExec("create table t(a int, b char(10) as (_utf8'a'));") + result = tk.MustQuery("show create table t;").Rows()[0][1] + require.Regexp(t, `(?s).*GENERATED ALWAYS AS \(_utf8'a'\).*`, result) + // Test show list partition table + tk.MustExec("set @@session.tidb_enable_list_partition = ON") + tk.MustExec(`DROP TABLE IF EXISTS t`) + tk.MustExec(`create table t (id int, name varchar(10), unique index idx (id)) partition by list (id) ( + partition p0 values in (3,5,6,9,17), + partition p1 values in (1,2,10,11,19,20), + partition p2 values in (4,12,13,14,18), + partition p3 values in (7,8,15,16,null) + );`) + tk.MustQuery(`show create table t`).Check(testkit.RowsWithSep("|", + "t CREATE TABLE `t` (\n"+ + " `id` int(11) DEFAULT NULL,\n"+ + " `name` varchar(10) DEFAULT NULL,\n"+ + " UNIQUE KEY `idx` (`id`)\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n"+ + "PARTITION BY LIST (`id`)\n"+ + "(PARTITION `p0` VALUES IN (3,5,6,9,17),\n"+ + " PARTITION `p1` VALUES IN (1,2,10,11,19,20),\n"+ + " PARTITION `p2` VALUES IN (4,12,13,14,18),\n"+ + " PARTITION `p3` VALUES IN (7,8,15,16,NULL))")) + // Test show list column partition table + tk.MustExec(`DROP TABLE IF EXISTS t`) + tk.MustExec(`create table t (id int, name varchar(10), unique index idx (id)) partition by list columns (id) ( + partition p0 values in (3,5,6,9,17), + partition p1 values in (1,2,10,11,19,20), + partition p2 values in (4,12,13,14,18), + partition p3 values in (7,8,15,16,null) + );`) + tk.MustQuery(`show create table t`).Check(testkit.RowsWithSep("|", + "t CREATE TABLE `t` (\n"+ + " `id` int(11) DEFAULT NULL,\n"+ + " `name` varchar(10) DEFAULT NULL,\n"+ + " UNIQUE KEY `idx` (`id`)\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n"+ + "PARTITION BY LIST COLUMNS(`id`)\n"+ + "(PARTITION `p0` VALUES IN (3,5,6,9,17),\n"+ + " PARTITION `p1` VALUES IN (1,2,10,11,19,20),\n"+ + " PARTITION `p2` VALUES IN (4,12,13,14,18),\n"+ + " PARTITION `p3` VALUES IN (7,8,15,16,NULL))")) + tk.MustExec(`DROP TABLE IF EXISTS t`) + tk.MustExec(`create table t (id int, name varchar(10), unique index idx (id, name)) partition by list columns (id, name) ( + partition p0 values in ((3, '1'), (5, '5')), + partition p1 values in ((1, '1')));`) + // The strings are single quoted in MySQL even if sql_mode doesn't contain ANSI_QUOTES. + tk.MustQuery(`show create table t`).Check(testkit.RowsWithSep("|", + "t CREATE TABLE `t` (\n"+ + " `id` int(11) DEFAULT NULL,\n"+ + " `name` varchar(10) DEFAULT NULL,\n"+ + " UNIQUE KEY `idx` (`id`,`name`)\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n"+ + "PARTITION BY LIST COLUMNS(`id`,`name`)\n"+ + "(PARTITION `p0` VALUES IN ((3,'1'),(5,'5')),\n"+ + " PARTITION `p1` VALUES IN ((1,'1')))")) + tk.MustExec(`DROP TABLE IF EXISTS t`) + tk.MustExec(`create table t (id int primary key, v varchar(255) not null, key idx_v (v) comment 'foo\'bar')`) + tk.MustQuery(`show create table t`).Check(testkit.RowsWithSep("|", + "t CREATE TABLE `t` (\n"+ + " `id` int(11) NOT NULL,\n"+ + " `v` varchar(255) NOT NULL,\n"+ + " PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,\n"+ + " KEY `idx_v` (`v`) COMMENT 'foo''bar'\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin")) + + // For issue #29922 + tk.MustExec("CREATE TABLE `thash` (\n `id` bigint unsigned NOT NULL,\n `data` varchar(255) DEFAULT NULL,\n PRIMARY KEY (`id`)\n)\nPARTITION BY HASH (`id`)\n(PARTITION pEven COMMENT = \"Even ids\",\n PARTITION pOdd COMMENT = \"Odd ids\");") + tk.MustQuery("show create table `thash`").Check(testkit.RowsWithSep("|", ""+ + "thash CREATE TABLE `thash` (\n"+ + " `id` bigint(20) unsigned NOT NULL,\n"+ + " `data` varchar(255) DEFAULT NULL,\n"+ + " PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n"+ + "PARTITION BY HASH (`id`)\n"+ + "(PARTITION `pEven` COMMENT 'Even ids',\n"+ + " PARTITION `pOdd` COMMENT 'Odd ids')", + )) + // empty edge case + tk.MustExec("drop table if exists `thash`") + tk.MustExec("CREATE TABLE `thash` (\n `id` bigint unsigned NOT NULL,\n `data` varchar(255) DEFAULT NULL,\n PRIMARY KEY (`id`)\n)\nPARTITION BY HASH (`id`);") + tk.MustQuery("show create table `thash`").Check(testkit.RowsWithSep("|", ""+ + "thash CREATE TABLE `thash` (\n"+ + " `id` bigint(20) unsigned NOT NULL,\n"+ + " `data` varchar(255) DEFAULT NULL,\n"+ + " PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n"+ + "PARTITION BY HASH (`id`) PARTITIONS 1", + )) + + // default value escape character '\\' display case + tk.MustExec("drop table if exists t;") + tk.MustExec("create table t(a int primary key, b varchar(20) default '\\\\');") + tk.MustQuery("show create table t;").Check(testkit.RowsWithSep("|", + ""+ + "t CREATE TABLE `t` (\n"+ + " `a` int(11) NOT NULL,\n"+ + " `b` varchar(20) DEFAULT '\\\\',\n"+ + " PRIMARY KEY (`a`) /*T![clustered_index] CLUSTERED */\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin")) + + tk.MustExec("drop table if exists t;") + tk.MustExec("create table t(" + + "a set('a', 'b') charset binary," + + "b enum('a', 'b') charset ascii);") + tk.MustQuery("show create table t;").Check(testkit.RowsWithSep("|", + ""+ + "t CREATE TABLE `t` (\n"+ + " `a` set('a','b') CHARACTER SET binary COLLATE binary DEFAULT NULL,\n"+ + " `b` enum('a','b') CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin")) + + tk.MustExec(`drop table if exists t`) + tk.MustExec(`create table t(a bit default (rand()))`) + tk.MustQuery(`show create table t`).Check(testkit.RowsWithSep("|", ""+ + "t CREATE TABLE `t` (\n"+ + " `a` bit(1) DEFAULT rand()\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin")) + + tk.MustExec(`drop table if exists t`) + err := tk.ExecToErr(`create table t (a varchar(255) character set ascii) partition by range columns (a) (partition p values less than (0xff))`) + require.ErrorContains(t, err, "[ddl:1654]Partition column values of incorrect type") + tk.MustExec(`create table t (a varchar(255) character set ascii) partition by range columns (a) (partition p values less than (0x7f))`) + tk.MustQuery(`show create table t`).Check(testkit.Rows( + "t CREATE TABLE `t` (\n" + + " `a` varchar(255) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n" + + "PARTITION BY RANGE COLUMNS(`a`)\n" + + "(PARTITION `p` VALUES LESS THAN (x'7f'))")) +} + +func TestShowCreateTablePlacement(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + defer tk.MustExec(`DROP TABLE IF EXISTS t`) + + // case for policy + tk.MustExec(`DROP TABLE IF EXISTS t`) + tk.MustExec("create placement policy x " + + "FOLLOWERS=2 " + + "CONSTRAINTS=\"[+disk=ssd]\" ") + defer tk.MustExec(`DROP PLACEMENT POLICY IF EXISTS x`) + tk.MustExec("create table t(a int)" + + "PLACEMENT POLICY=\"x\"") + tk.MustQuery(`show create table t`).Check(testkit.RowsWithSep("|", + "t CREATE TABLE `t` (\n"+ + " `a` int(11) DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin "+ + "/*T![placement] PLACEMENT POLICY=`x` */", + )) + + // case for policy with quotes + tk.MustExec(`DROP TABLE IF EXISTS t`) + tk.MustExec("create table t(a int)" + + "/*T![placement] PLACEMENT POLICY=\"x\" */") + tk.MustQuery(`show create table t`).Check(testkit.RowsWithSep("|", + "t CREATE TABLE `t` (\n"+ + " `a` int(11) DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin "+ + "/*T![placement] PLACEMENT POLICY=`x` */", + )) + + // Partitioned tables + tk.MustExec(`DROP TABLE IF EXISTS t`) + tk.MustExec("set @old_list_part = @@tidb_enable_list_partition") + defer tk.MustExec("set @@tidb_enable_list_partition = @old_list_part") + tk.MustExec("set tidb_enable_list_partition = 1") + tk.MustExec("create table t(a int, b varchar(255))" + + "/*T![placement] PLACEMENT POLICY=\"x\" */" + + "PARTITION BY LIST (a)\n" + + "(PARTITION pLow VALUES in (1,2,3,5,8) COMMENT 'a comment' placement policy 'x'," + + " PARTITION pMid VALUES in (9) COMMENT 'another comment'," + + "partition pMax values IN (10,11,12))") + tk.MustQuery(`show create table t`).Check(testkit.RowsWithSep("|", ""+ + "t CREATE TABLE `t` (\n"+ + " `a` int(11) DEFAULT NULL,\n"+ + " `b` varchar(255) DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![placement] PLACEMENT POLICY=`x` */\n"+ + "PARTITION BY LIST (`a`)\n"+ + "(PARTITION `pLow` VALUES IN (1,2,3,5,8) COMMENT 'a comment' /*T![placement] PLACEMENT POLICY=`x` */,\n"+ + " PARTITION `pMid` VALUES IN (9) COMMENT 'another comment',\n"+ + " PARTITION `pMax` VALUES IN (10,11,12))", + )) + + tk.MustExec(`DROP TABLE IF EXISTS t`) + tk.MustExec("create table t(a int, b varchar(255))" + + "PARTITION BY LIST COLUMNS (b)\n" + + "(PARTITION pLow VALUES in ('1','2','3','5','8') COMMENT 'a comment' placement policy 'x'," + + "partition pMax values IN ('10','11','12'))") + tk.MustQuery(`show create table t`).Check(testkit.RowsWithSep("|", ""+ + "t CREATE TABLE `t` (\n"+ + " `a` int(11) DEFAULT NULL,\n"+ + " `b` varchar(255) DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n"+ + "PARTITION BY LIST COLUMNS(`b`)\n"+ + "(PARTITION `pLow` VALUES IN ('1','2','3','5','8') COMMENT 'a comment' /*T![placement] PLACEMENT POLICY=`x` */,\n"+ + " PARTITION `pMax` VALUES IN ('10','11','12'))", + )) + + tk.MustExec(`DROP TABLE IF EXISTS t`) + tk.MustExec("create table t(a int, b varchar(255))" + + "PARTITION BY LIST COLUMNS (a,b)\n" + + "(PARTITION pLow VALUES in ((1,'1'),(2,'2'),(3,'3'),(5,'5'),(8,'8')) COMMENT 'a comment' placement policy 'x'," + + "partition pMax values IN ((10,'10'),(11,'11'),(12,'12')))") + tk.MustQuery(`show create table t`).Check(testkit.RowsWithSep("|", ""+ + "t CREATE TABLE `t` (\n"+ + " `a` int(11) DEFAULT NULL,\n"+ + " `b` varchar(255) DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n"+ + "PARTITION BY LIST COLUMNS(`a`,`b`)\n"+ + "(PARTITION `pLow` VALUES IN ((1,'1'),(2,'2'),(3,'3'),(5,'5'),(8,'8')) COMMENT 'a comment' /*T![placement] PLACEMENT POLICY=`x` */,\n"+ + " PARTITION `pMax` VALUES IN ((10,'10'),(11,'11'),(12,'12')))", + )) + + tk.MustExec(`DROP TABLE IF EXISTS t`) + tk.MustExec("create table t(a int, b varchar(255))" + + "PARTITION BY RANGE (a)\n" + + "(PARTITION pLow VALUES less than (1000000) COMMENT 'a comment' placement policy 'x'," + + "partition pMax values LESS THAN (MAXVALUE))") + tk.MustQuery(`show create table t`).Check(testkit.RowsWithSep("|", ""+ + "t CREATE TABLE `t` (\n"+ + " `a` int(11) DEFAULT NULL,\n"+ + " `b` varchar(255) DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n"+ + "PARTITION BY RANGE (`a`)\n"+ + "(PARTITION `pLow` VALUES LESS THAN (1000000) COMMENT 'a comment' /*T![placement] PLACEMENT POLICY=`x` */,\n"+ + " PARTITION `pMax` VALUES LESS THAN (MAXVALUE))", + )) + + tk.MustExec(`DROP TABLE IF EXISTS t`) + tk.MustExec("create table t(a int, b varchar(255))" + + "PARTITION BY RANGE COLUMNS (b)\n" + + "(PARTITION pLow VALUES less than ('1000000') COMMENT 'a comment' placement policy 'x'," + + "partition pMax values LESS THAN (MAXVALUE))") + tk.MustQuery(`show create table t`).Check(testkit.RowsWithSep("|", ""+ + "t CREATE TABLE `t` (\n"+ + " `a` int(11) DEFAULT NULL,\n"+ + " `b` varchar(255) DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n"+ + "PARTITION BY RANGE COLUMNS(`b`)\n"+ + "(PARTITION `pLow` VALUES LESS THAN ('1000000') COMMENT 'a comment' /*T![placement] PLACEMENT POLICY=`x` */,\n"+ + " PARTITION `pMax` VALUES LESS THAN (MAXVALUE))", + )) + + tk.MustExec(`DROP TABLE IF EXISTS t`) + + tk.MustExec("create table t(a int, b varchar(255))" + + "/*T![placement] PLACEMENT POLICY=\"x\" */" + + "PARTITION BY RANGE COLUMNS (a,b)\n" + + "(PARTITION pLow VALUES less than (1000000,'1000000') COMMENT 'a comment' placement policy 'x'," + + " PARTITION pMidLow VALUES less than (1000000,MAXVALUE) COMMENT 'another comment' placement policy 'x'," + + " PARTITION pMadMax VALUES less than (9000000,'1000000') COMMENT ='Not a comment' placement policy 'x'," + + "partition pMax values LESS THAN (MAXVALUE, 'Does not matter...'))") + tk.MustQuery("show warnings").Check(testkit.Rows()) + tk.MustExec(`insert into t values (1,'1')`) + tk.MustQuery("select * from t").Check(testkit.Rows("1 1")) + tk.MustQuery(`show create table t`).Check(testkit.Rows( + "t CREATE TABLE `t` (\n" + + " `a` int(11) DEFAULT NULL,\n" + + " `b` varchar(255) DEFAULT NULL\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![placement] PLACEMENT POLICY=`x` */\n" + + "PARTITION BY RANGE COLUMNS(`a`,`b`)\n" + + "(PARTITION `pLow` VALUES LESS THAN (1000000,'1000000') COMMENT 'a comment' /*T![placement] PLACEMENT POLICY=`x` */,\n" + + " PARTITION `pMidLow` VALUES LESS THAN (1000000,MAXVALUE) COMMENT 'another comment' /*T![placement] PLACEMENT POLICY=`x` */,\n" + + " PARTITION `pMadMax` VALUES LESS THAN (9000000,'1000000') COMMENT 'Not a comment' /*T![placement] PLACEMENT POLICY=`x` */,\n" + + " PARTITION `pMax` VALUES LESS THAN (MAXVALUE,'Does not matter...'))")) + + tk.MustExec(`DROP TABLE IF EXISTS t`) + tk.MustExec("create table t(a int, b varchar(255))" + + "/*T![placement] PLACEMENT POLICY=\"x\" */" + + "PARTITION BY HASH (a) PARTITIONS 2") + tk.MustQuery(`show create table t`).Check(testkit.RowsWithSep("|", ""+ + "t CREATE TABLE `t` (\n"+ + " `a` int(11) DEFAULT NULL,\n"+ + " `b` varchar(255) DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![placement] PLACEMENT POLICY=`x` */\n"+ + "PARTITION BY HASH (`a`) PARTITIONS 2", + )) + + tk.MustExec(`DROP TABLE IF EXISTS t`) + tk.MustExec("create table t(a int, b varchar(255))" + + "PARTITION BY HASH (a)\n" + + "(PARTITION pLow COMMENT 'a comment' placement policy 'x'," + + "partition pMax)") + tk.MustQuery(`show create table t`).Check(testkit.RowsWithSep("|", ""+ + "t CREATE TABLE `t` (\n"+ + " `a` int(11) DEFAULT NULL,\n"+ + " `b` varchar(255) DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n"+ + "PARTITION BY HASH (`a`)\n"+ + "(PARTITION `pLow` COMMENT 'a comment' /*T![placement] PLACEMENT POLICY=`x` */,\n"+ + " PARTITION `pMax`)", + )) + tk.MustExec(`DROP TABLE t`) +} + +func TestShowVisibility(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("create database showdatabase") + tk.MustExec("use showdatabase") + tk.MustExec("create table t1 (id int)") + tk.MustExec("create table t2 (id int)") + tk.MustExec(`create user 'show'@'%'`) + + tk1 := testkit.NewTestKit(t, store) + require.NoError(t, tk1.Session().Auth(&auth.UserIdentity{Username: "show", Hostname: "%"}, nil, nil, nil)) + + // No ShowDatabases privilege, this user would see nothing except INFORMATION_SCHEMA. + tk.MustQuery("show databases").Check(testkit.Rows("INFORMATION_SCHEMA")) + + // After grant, the user can see the database. + tk.MustExec(`grant select on showdatabase.t1 to 'show'@'%'`) + tk1.MustQuery("show databases").Check(testkit.Rows("INFORMATION_SCHEMA", "showdatabase")) + + // The user can see t1 but not t2. + tk1.MustExec("use showdatabase") + tk1.MustQuery("show tables").Check(testkit.Rows("t1")) + + // After revoke, show database result should be just except INFORMATION_SCHEMA. + tk.MustExec(`revoke select on showdatabase.t1 from 'show'@'%'`) + tk1.MustQuery("show databases").Check(testkit.Rows("INFORMATION_SCHEMA")) + + // Grant any global privilege would make show databases available. + tk.MustExec(`grant CREATE on *.* to 'show'@'%'`) + rows := tk1.MustQuery("show databases").Rows() + require.GreaterOrEqual(t, len(rows), 2) + + tk.MustExec(`drop user 'show'@'%'`) + tk.MustExec("drop database showdatabase") +} + +func TestShowDatabasesInfoSchemaFirst(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustQuery("show databases").Check(testkit.Rows("INFORMATION_SCHEMA")) + tk.MustExec(`create user 'show'@'%'`) + + tk.MustExec(`create database AAAA`) + tk.MustExec(`create database BBBB`) + tk.MustExec(`grant select on AAAA.* to 'show'@'%'`) + tk.MustExec(`grant select on BBBB.* to 'show'@'%'`) + + tk1 := testkit.NewTestKit(t, store) + require.NoError(t, tk1.Session().Auth(&auth.UserIdentity{Username: "show", Hostname: "%"}, nil, nil, nil)) + tk1.MustQuery("show databases").Check(testkit.Rows("INFORMATION_SCHEMA", "AAAA", "BBBB")) + + tk.MustExec(`drop user 'show'@'%'`) + tk.MustExec(`drop database AAAA`) + tk.MustExec(`drop database BBBB`) +} + +func TestShowWarnings(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + testSQL := `create table if not exists show_warnings (a int)` + tk.MustExec(testSQL) + tk.MustExec("set @@sql_mode=''") + tk.MustExec("insert show_warnings values ('a')") + require.Equal(t, uint16(1), tk.Session().GetSessionVars().StmtCtx.WarningCount()) + tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Warning|1366|Incorrect int value: 'a' for column 'a' at row 1")) + require.Equal(t, uint16(0), tk.Session().GetSessionVars().StmtCtx.WarningCount()) + tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Warning|1366|Incorrect int value: 'a' for column 'a' at row 1")) + require.Equal(t, uint16(0), tk.Session().GetSessionVars().StmtCtx.WarningCount()) + + // Test Warning level 'Error' + testSQL = `create table show_warnings (a int)` + _, _ = tk.Exec(testSQL) + // FIXME: Table 'test.show_warnings' already exists + require.Equal(t, uint16(1), tk.Session().GetSessionVars().StmtCtx.WarningCount()) + tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Error|1050|Table 'test.show_warnings' already exists")) + tk.MustQuery("select @@error_count").Check(testkit.RowsWithSep("|", "1")) + + // Test Warning level 'Note' + testSQL = `create table show_warnings_2 (a int)` + tk.MustExec(testSQL) + testSQL = `create table if not exists show_warnings_2 like show_warnings` + _, err := tk.Exec(testSQL) + require.NoError(t, err) + require.Equal(t, uint16(1), tk.Session().GetSessionVars().StmtCtx.WarningCount()) + tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Note|1050|Table 'test.show_warnings_2' already exists")) + tk.MustQuery("select @@warning_count").Check(testkit.RowsWithSep("|", "1")) + tk.MustQuery("select @@warning_count").Check(testkit.RowsWithSep("|", "0")) +} + +func TestShowErrors(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + testSQL := `create table if not exists show_errors (a int)` + tk.MustExec(testSQL) + testSQL = `create table show_errors (a int)` + // FIXME: 'test.show_errors' already exists + _, _ = tk.Exec(testSQL) + + tk.MustQuery("show errors").Check(testkit.RowsWithSep("|", "Error|1050|Table 'test.show_errors' already exists")) + + // eliminate previous errors + tk.MustExec("select 1") + _, _ = tk.Exec("create invalid") + tk.MustQuery("show errors").Check(testkit.RowsWithSep("|", "Error|1064|You have an error in your SQL syntax; check the manual that corresponds to your TiDB version for the right syntax to use line 1 column 14 near \"invalid\" ")) +} + +func TestShowWarningsForExprPushdown(t *testing.T) { + store, dom := testkit.CreateMockStoreAndDomain(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec(`set tidb_cost_model_version=2`) + tk.MustExec("set @@session.tidb_allow_tiflash_cop=ON") + + testSQL := `create table if not exists show_warnings_expr_pushdown (a int, value date)` + tk.MustExec(testSQL) + + // create tiflash replica + { + is := dom.InfoSchema() + db, exists := is.SchemaByName(model.NewCIStr("test")) + require.True(t, exists) + for _, tblInfo := range db.Tables { + if tblInfo.Name.L == "show_warnings_expr_pushdown" { + tblInfo.TiFlashReplica = &model.TiFlashReplicaInfo{ + Count: 1, + Available: true, + } + } + } + } + tk.MustExec("set tidb_allow_mpp=0") + tk.MustExec("explain select * from show_warnings_expr_pushdown t where md5(value) = '2020-01-01'") + require.Equal(t, uint16(1), tk.Session().GetSessionVars().StmtCtx.WarningCount()) + tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Warning|1105|Scalar function 'md5'(signature: MD5, return type: var_string(32)) is not supported to push down to tiflash now.")) + tk.MustExec("explain select /*+ read_from_storage(tiflash[show_warnings_expr_pushdown]) */ max(md5(value)) from show_warnings_expr_pushdown group by a") + require.Equal(t, uint16(2), tk.Session().GetSessionVars().StmtCtx.WarningCount()) + tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Warning|1105|Scalar function 'md5'(signature: MD5, return type: var_string(32)) is not supported to push down to tiflash now.", "Warning|1105|Aggregation can not be pushed to tiflash because arguments of AggFunc `max` contains unsupported exprs")) + tk.MustExec("explain select /*+ read_from_storage(tiflash[show_warnings_expr_pushdown]) */ max(a) from show_warnings_expr_pushdown group by md5(value)") + require.Equal(t, uint16(2), tk.Session().GetSessionVars().StmtCtx.WarningCount()) + tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Warning|1105|Scalar function 'md5'(signature: MD5, return type: var_string(32)) is not supported to push down to tiflash now.", "Warning|1105|Aggregation can not be pushed to tiflash because groupByItems contain unsupported exprs")) + tk.MustExec("set tidb_opt_distinct_agg_push_down=0") + tk.MustExec("explain select max(distinct a) from show_warnings_expr_pushdown group by value") + require.Equal(t, uint16(0), tk.Session().GetSessionVars().StmtCtx.WarningCount()) + // tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Warning|1105|Aggregation can not be pushed to storage layer in non-mpp mode because it contains agg function with distinct")) +} + +func TestShowGrantsPrivilege(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("create user show_grants") + tk.MustExec("show grants for show_grants") + tk1 := testkit.NewTestKit(t, store) + require.NoError(t, tk1.Session().Auth(&auth.UserIdentity{Username: "show_grants", Hostname: "%"}, nil, nil, nil)) + err := tk1.QueryToErr("show grants for root") + require.EqualError(t, exeerrors.ErrDBaccessDenied.GenWithStackByArgs("show_grants", "%", mysql.SystemDB), err.Error()) + // Test show grants for user with auth host name `%`. + tk2 := testkit.NewTestKit(t, store) + require.NoError(t, tk2.Session().Auth(&auth.UserIdentity{Username: "show_grants", Hostname: "127.0.0.1", AuthUsername: "show_grants", AuthHostname: "%"}, nil, nil, nil)) + tk2.MustQuery("show grants") +} + +func TestShowStatsPrivilege(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("create user show_stats") + tk1 := testkit.NewTestKit(t, store) + + require.NoError(t, tk1.Session().Auth(&auth.UserIdentity{Username: "show_stats", Hostname: "%"}, nil, nil, nil)) + e := "[planner:1142]SHOW command denied to user 'show_stats'@'%' for table" + err := tk1.ExecToErr("show stats_meta") + require.ErrorContains(t, err, e) + err = tk1.ExecToErr("SHOW STATS_BUCKETS") + require.ErrorContains(t, err, e) + err = tk1.ExecToErr("SHOW STATS_HISTOGRAMS") + require.ErrorContains(t, err, e) + + eqErr := plannercore.ErrDBaccessDenied.GenWithStackByArgs("show_stats", "%", mysql.SystemDB) + err = tk1.ExecToErr("SHOW STATS_HEALTHY") + require.EqualError(t, err, eqErr.Error()) + tk.MustExec("grant select on mysql.* to show_stats") + tk1.MustExec("show stats_meta") + tk1.MustExec("SHOW STATS_BUCKETS") + tk1.MustExec("SHOW STATS_HEALTHY") + tk1.MustExec("SHOW STATS_HISTOGRAMS") + + tk.MustExec("create user a@'%' identified by '';") + require.NoError(t, tk1.Session().Auth(&auth.UserIdentity{Username: "a", Hostname: "%"}, nil, nil, nil)) + tk.MustExec("grant select on mysql.stats_meta to a@'%';") + tk.MustExec("grant select on mysql.stats_buckets to a@'%';") + tk.MustExec("grant select on mysql.stats_histograms to a@'%';") + tk1.MustExec("show stats_meta") + tk1.MustExec("SHOW STATS_BUCKETS") + tk1.MustExec("SHOW STATS_HISTOGRAMS") +} + +func TestIssue18878(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + require.NoError(t, tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "127.0.0.1", AuthHostname: "%"}, nil, nil, nil)) + tk.MustQuery("select user()").Check(testkit.Rows("root@127.0.0.1")) + tk.MustQuery("show grants") + tk.MustQuery("select user()").Check(testkit.Rows("root@127.0.0.1")) + err := tk.QueryToErr("show grants for root@127.0.0.1") + require.Equal(t, privileges.ErrNonexistingGrant.FastGenByArgs("root", "127.0.0.1").Error(), err.Error()) + err = tk.QueryToErr("show grants for root@localhost") + require.Equal(t, privileges.ErrNonexistingGrant.FastGenByArgs("root", "localhost").Error(), err.Error()) + err = tk.QueryToErr("show grants for root@1.1.1.1") + require.Equal(t, privileges.ErrNonexistingGrant.FastGenByArgs("root", "1.1.1.1").Error(), err.Error()) + tk.MustExec("create user `show_grants`@`127.0.%`") + err = tk.QueryToErr("show grants for `show_grants`@`127.0.0.1`") + require.Equal(t, privileges.ErrNonexistingGrant.FastGenByArgs("show_grants", "127.0.0.1").Error(), err.Error()) + tk.MustQuery("show grants for `show_grants`@`127.0.%`") +} + +func TestIssue17794(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("CREATE USER 'root'@'8.8.%'") + require.NoError(t, tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "9.9.9.9", AuthHostname: "%"}, nil, nil, nil)) + + tk1 := testkit.NewTestKit(t, store) + require.NoError(t, tk1.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "8.8.8.8", AuthHostname: "8.8.%"}, nil, nil, nil)) + tk.MustQuery("show grants").Check(testkit.Rows("GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION")) + tk1.MustQuery("show grants").Check(testkit.Rows("GRANT USAGE ON *.* TO 'root'@'8.8.%'")) +} + +func TestIssue3641(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustGetErrCode("show tables;", mysql.ErrNoDB) + tk.MustGetErrCode("show tables;", mysql.ErrNoDB) +} + +func TestIssue10549(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("CREATE DATABASE newdb;") + tk.MustExec("CREATE ROLE 'app_developer';") + tk.MustExec("GRANT ALL ON newdb.* TO 'app_developer';") + tk.MustExec("CREATE USER 'dev';") + tk.MustExec("GRANT 'app_developer' TO 'dev';") + tk.MustExec("SET DEFAULT ROLE app_developer TO 'dev';") + + require.NoError(t, tk.Session().Auth(&auth.UserIdentity{Username: "dev", Hostname: "%", AuthUsername: "dev", AuthHostname: "%"}, nil, nil, nil)) + tk.MustQuery("SHOW DATABASES;").Check(testkit.Rows("INFORMATION_SCHEMA", "newdb")) + tk.MustQuery("SHOW GRANTS;").Check(testkit.Rows("GRANT USAGE ON *.* TO 'dev'@'%'", "GRANT ALL PRIVILEGES ON `newdb`.* TO 'dev'@'%'", "GRANT 'app_developer'@'%' TO 'dev'@'%'")) + tk.MustQuery("SHOW GRANTS FOR CURRENT_USER").Check(testkit.Rows("GRANT USAGE ON *.* TO 'dev'@'%'", "GRANT ALL PRIVILEGES ON `newdb`.* TO 'dev'@'%'", "GRANT 'app_developer'@'%' TO 'dev'@'%'")) + tk.MustQuery("SHOW GRANTS FOR dev").Check(testkit.Rows("GRANT USAGE ON *.* TO 'dev'@'%'", "GRANT 'app_developer'@'%' TO 'dev'@'%'")) +} + +func TestIssue11165(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("CREATE ROLE 'r_manager';") + tk.MustExec("CREATE USER 'manager'@'localhost';") + tk.MustExec("GRANT 'r_manager' TO 'manager'@'localhost';") + + require.NoError(t, tk.Session().Auth(&auth.UserIdentity{Username: "manager", Hostname: "localhost", AuthUsername: "manager", AuthHostname: "localhost"}, nil, nil, nil)) + tk.MustExec("SET DEFAULT ROLE ALL TO 'manager'@'localhost';") + tk.MustExec("SET DEFAULT ROLE NONE TO 'manager'@'localhost';") + tk.MustExec("SET DEFAULT ROLE 'r_manager' TO 'manager'@'localhost';") +} + +// TestShow2 is moved from session_test +func TestShow2(t *testing.T) { + store, dom := testkit.CreateMockStoreAndDomain(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + + tk.MustExec("set global autocommit=0") + tk1 := testkit.NewTestKit(t, store) + tk1.MustQuery("show global variables where variable_name = 'autocommit'").Check(testkit.Rows("autocommit OFF")) + tk.MustExec("set global autocommit = 1") + tk2 := testkit.NewTestKit(t, store) + tk2.MustQuery("show global variables where variable_name = 'autocommit'").Check(testkit.Rows("autocommit ON")) + + // TODO: Specifying the charset for national char/varchar should not be supported. + tk.MustExec("drop table if exists test_full_column") + tk.MustExec(`create table test_full_column( + c_int int, + c_float float, + c_bit bit, + c_bool bool, + c_char char(1) charset ascii collate ascii_bin, + c_nchar national char(1) charset ascii collate ascii_bin, + c_binary binary, + c_varchar varchar(1) charset ascii collate ascii_bin, + c_varchar_default varchar(20) charset ascii collate ascii_bin default 'cUrrent_tImestamp', + c_nvarchar national varchar(1) charset ascii collate ascii_bin, + c_varbinary varbinary(1), + c_year year, + c_date date, + c_time time, + c_datetime datetime, + c_datetime_default datetime default current_timestamp, + c_datetime_default_2 datetime(2) default current_timestamp(2), + c_timestamp timestamp, + c_timestamp_default timestamp default current_timestamp, + c_timestamp_default_3 timestamp(3) default current_timestamp(3), + c_timestamp_default_4 timestamp(3) default current_timestamp(3) on update current_timestamp(3), + c_date_default date default current_date, + c_date_default_2 date default (curdate()), + c_blob blob, + c_tinyblob tinyblob, + c_mediumblob mediumblob, + c_longblob longblob, + c_text text charset ascii collate ascii_bin, + c_tinytext tinytext charset ascii collate ascii_bin, + c_mediumtext mediumtext charset ascii collate ascii_bin, + c_longtext longtext charset ascii collate ascii_bin, + c_json json, + c_enum enum('1') charset ascii collate ascii_bin, + c_set set('1') charset ascii collate ascii_bin + );`) + + tk.MustQuery(`show full columns from test_full_column`).Check(testkit.Rows( + "" + + "c_int int(11) YES select,insert,update,references ]\n" + + "[c_float float YES select,insert,update,references ]\n" + + "[c_bit bit(1) YES select,insert,update,references ]\n" + + "[c_bool tinyint(1) YES select,insert,update,references ]\n" + + "[c_char char(1) ascii_bin YES select,insert,update,references ]\n" + + "[c_nchar char(1) ascii_bin YES select,insert,update,references ]\n" + + "[c_binary binary(1) YES select,insert,update,references ]\n" + + "[c_varchar varchar(1) ascii_bin YES select,insert,update,references ]\n" + + "[c_varchar_default varchar(20) ascii_bin YES cUrrent_tImestamp select,insert,update,references ]\n" + + "[c_nvarchar varchar(1) ascii_bin YES select,insert,update,references ]\n" + + "[c_varbinary varbinary(1) YES select,insert,update,references ]\n" + + "[c_year year(4) YES select,insert,update,references ]\n" + + "[c_date date YES select,insert,update,references ]\n" + + "[c_time time YES select,insert,update,references ]\n" + + "[c_datetime datetime YES select,insert,update,references ]\n" + + "[c_datetime_default datetime YES CURRENT_TIMESTAMP select,insert,update,references ]\n" + + "[c_datetime_default_2 datetime(2) YES CURRENT_TIMESTAMP(2) select,insert,update,references ]\n" + + "[c_timestamp timestamp YES select,insert,update,references ]\n" + + "[c_timestamp_default timestamp YES CURRENT_TIMESTAMP select,insert,update,references ]\n" + + "[c_timestamp_default_3 timestamp(3) YES CURRENT_TIMESTAMP(3) select,insert,update,references ]\n" + + "[c_timestamp_default_4 timestamp(3) YES CURRENT_TIMESTAMP(3) DEFAULT_GENERATED on update CURRENT_TIMESTAMP(3) select,insert,update,references ]\n" + + "[c_date_default date YES CURRENT_DATE select,insert,update,references ]\n" + + "[c_date_default_2 date YES CURRENT_DATE select,insert,update,references ]\n" + + "[c_blob blob YES select,insert,update,references ]\n" + + "[c_tinyblob tinyblob YES select,insert,update,references ]\n" + + "[c_mediumblob mediumblob YES select,insert,update,references ]\n" + + "[c_longblob longblob YES select,insert,update,references ]\n" + + "[c_text text ascii_bin YES select,insert,update,references ]\n" + + "[c_tinytext tinytext ascii_bin YES select,insert,update,references ]\n" + + "[c_mediumtext mediumtext ascii_bin YES select,insert,update,references ]\n" + + "[c_longtext longtext ascii_bin YES select,insert,update,references ]\n" + + "[c_json json YES select,insert,update,references ]\n" + + "[c_enum enum('1') ascii_bin YES select,insert,update,references ]\n" + + "[c_set set('1') ascii_bin YES select,insert,update,references ")) + + tk.MustExec("drop table if exists test_full_column") + + tk.MustExec("drop table if exists t") + tk.MustExec(`create table if not exists t (c int) comment '注释'`) + tk.MustExec("create or replace definer='root'@'localhost' view v as select * from t") + tk.MustQuery(`show columns from t`).Check(testkit.RowsWithSep(",", "c,int(11),YES,,,")) + tk.MustQuery(`describe t`).Check(testkit.RowsWithSep(",", "c,int(11),YES,,,")) + tk.MustQuery(`show columns from v`).Check(testkit.RowsWithSep(",", "c,int(11),YES,,,")) + tk.MustQuery(`describe v`).Check(testkit.RowsWithSep(",", "c,int(11),YES,,,")) + tk.MustQuery("show collation where Charset = 'utf8' and Collation = 'utf8_bin'").Check(testkit.RowsWithSep(",", "utf8_bin,utf8,83,Yes,Yes,1")) + tk.MustExec(`drop sequence if exists seq`) + tk.MustExec(`create sequence seq`) + tk.MustQuery("show tables").Check(testkit.Rows("seq", "t", "v")) + tk.MustQuery("show full tables").Check(testkit.Rows("seq SEQUENCE", "t BASE TABLE", "v VIEW")) + + // Bug 19427 + tk.MustQuery("SHOW FULL TABLES in INFORMATION_SCHEMA like 'VIEWS'").Check(testkit.Rows("VIEWS SYSTEM VIEW")) + tk.MustQuery("SHOW FULL TABLES in information_schema like 'VIEWS'").Check(testkit.Rows("VIEWS SYSTEM VIEW")) + tk.MustQuery("SHOW FULL TABLES in metrics_schema like 'uptime'").Check(testkit.Rows("uptime SYSTEM VIEW")) + + is := dom.InfoSchema() + tblInfo, err := is.TableByName(model.NewCIStr("test"), model.NewCIStr("t")) + require.NoError(t, err) + createTime := model.TSConvert2Time(tblInfo.Meta().UpdateTS).Format(time.DateTime) + + // The Hostname is the actual host + tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "192.168.0.1", AuthUsername: "root", AuthHostname: "%"}, nil, []byte("012345678901234567890"), nil) + + r := tk.MustQuery("show table status from test like 't'") + r.Check(testkit.Rows(fmt.Sprintf("t InnoDB 10 Compact 0 0 0 0 0 0 %s utf8mb4_bin 注释", createTime))) + + tk.MustQuery("show databases like 'test'").Check(testkit.Rows("test")) + + tk.MustExec(`grant all on *.* to 'root'@'%'`) + tk.MustQuery("show grants").Check(testkit.Rows(`GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION`)) + + tk.MustQuery("show grants for current_user()").Check(testkit.Rows(`GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION`)) + tk.MustQuery("show grants for current_user").Check(testkit.Rows(`GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION`)) +} + +func TestShowCreateUser(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + // Create a new user. + tk.MustExec(`CREATE USER 'test_show_create_user'@'%' IDENTIFIED BY 'root';`) + tk.MustQuery("show create user 'test_show_create_user'@'%'"). + Check(testkit.Rows(`CREATE USER 'test_show_create_user'@'%' IDENTIFIED WITH 'mysql_native_password' AS '*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT`)) + + tk.MustExec(`CREATE USER 'test_show_create_user'@'localhost' IDENTIFIED BY 'test';`) + tk.MustQuery("show create user 'test_show_create_user'@'localhost';"). + Check(testkit.Rows(`CREATE USER 'test_show_create_user'@'localhost' IDENTIFIED WITH 'mysql_native_password' AS '*94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT`)) + + // Case: the user exists but the host portion doesn't match + err := tk.QueryToErr("show create user 'test_show_create_user'@'asdf';") + require.Equal(t, exeerrors.ErrCannotUser.GenWithStackByArgs("SHOW CREATE USER", "'test_show_create_user'@'asdf'").Error(), err.Error()) + + // Case: a user that doesn't exist + err = tk.QueryToErr("show create user 'aaa'@'localhost';") + require.Equal(t, exeerrors.ErrCannotUser.GenWithStackByArgs("SHOW CREATE USER", "'aaa'@'localhost'").Error(), err.Error()) + + tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "127.0.0.1", AuthUsername: "root", AuthHostname: "%"}, nil, nil, nil) + tk.MustQuery("show create user current_user"). + Check(testkit.Rows("CREATE USER 'root'@'127.0.0.1' IDENTIFIED WITH 'mysql_native_password' AS '' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT")) + + tk.MustQuery("show create user current_user()"). + Check(testkit.Rows("CREATE USER 'root'@'127.0.0.1' IDENTIFIED WITH 'mysql_native_password' AS '' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT")) + + tk.MustExec("create user 'check_priv'") + + // "show create user" for other user requires the SELECT privilege on mysql database. + tk1 := testkit.NewTestKit(t, store) + tk1.MustExec("use mysql") + require.NoError(t, tk1.Session().Auth(&auth.UserIdentity{Username: "check_priv", Hostname: "127.0.0.1", AuthUsername: "test_show", AuthHostname: "asdf"}, nil, nil, nil)) + err = tk1.QueryToErr("show create user 'root'@'%'") + require.Error(t, err) + + // "show create user" for current user doesn't check privileges. + tk1.MustQuery("show create user current_user"). + Check(testkit.Rows("CREATE USER 'check_priv'@'127.0.0.1' IDENTIFIED WITH 'mysql_native_password' AS '' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT")) + + // Creating users with `IDENTIFIED WITH 'caching_sha2_password'`. + tk.MustExec("CREATE USER 'sha_test'@'%' IDENTIFIED WITH 'caching_sha2_password' BY 'temp_passwd'") + + // Compare only the start of the output as the salt changes every time. + rows := tk.MustQuery("SHOW CREATE USER 'sha_test'@'%'") + require.Equal(t, "CREATE USER 'sha_test'@'%' IDENTIFIED WITH 'caching_sha2_password' AS '$A$005$", rows.Rows()[0][0].(string)[:78]) + // Creating users with `IDENTIFIED WITH 'auth-socket'` + tk.MustExec("CREATE USER 'sock'@'%' IDENTIFIED WITH 'auth_socket'") + + // Compare only the start of the output as the salt changes every time. + rows = tk.MustQuery("SHOW CREATE USER 'sock'@'%'") + require.Equal(t, "CREATE USER 'sock'@'%' IDENTIFIED WITH 'auth_socket' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT", rows.Rows()[0][0].(string)) + tk.MustExec("CREATE USER 'sock2'@'%' IDENTIFIED WITH 'auth_socket' AS 'sock3'") + + // Compare only the start of the output as the salt changes every time. + rows = tk.MustQuery("SHOW CREATE USER 'sock2'@'%'") + require.Equal(t, "CREATE USER 'sock2'@'%' IDENTIFIED WITH 'auth_socket' AS 'sock3' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT", rows.Rows()[0][0].(string)) + + // Test ACCOUNT LOCK/UNLOCK. + tk.MustExec("CREATE USER 'lockness'@'%' IDENTIFIED BY 'monster' ACCOUNT LOCK") + rows = tk.MustQuery("SHOW CREATE USER 'lockness'@'%'") + require.Equal(t, "CREATE USER 'lockness'@'%' IDENTIFIED WITH 'mysql_native_password' AS '*BC05309E7FE12AFD4EBB9FFE7E488A6320F12FF3' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT LOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT", rows.Rows()[0][0].(string)) + + // Test COMMENT and ATTRIBUTE. + tk.MustExec("CREATE USER commentUser COMMENT '1234'") + tk.MustQuery("SHOW CREATE USER commentUser").Check(testkit.Rows(`CREATE USER 'commentUser'@'%' IDENTIFIED WITH 'mysql_native_password' AS '' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT ATTRIBUTE '{"comment": "1234"}'`)) + tk.MustExec(`CREATE USER attributeUser attribute '{"name": "Tom", "age": 19}'`) + tk.MustQuery("SHOW CREATE USER attributeUser").Check(testkit.Rows(`CREATE USER 'attributeUser'@'%' IDENTIFIED WITH 'mysql_native_password' AS '' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT ATTRIBUTE '{"age": 19, "name": "Tom"}'`)) + + // Creating users with IDENTIFIED WITH 'tidb_auth_token'. + tk.MustExec(`CREATE USER 'token_user'@'%' IDENTIFIED WITH 'tidb_auth_token' ATTRIBUTE '{"email": "user@pingcap.com"}'`) + tk.MustQuery("SHOW CREATE USER token_user").Check(testkit.Rows(`CREATE USER 'token_user'@'%' IDENTIFIED WITH 'tidb_auth_token' AS '' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT ATTRIBUTE '{"email": "user@pingcap.com"}'`)) + tk.MustExec(`ALTER USER 'token_user'@'%' REQUIRE token_issuer 'issuer-ABC'`) + tk.MustQuery("SHOW CREATE USER token_user").Check(testkit.Rows(`CREATE USER 'token_user'@'%' IDENTIFIED WITH 'tidb_auth_token' AS '' REQUIRE NONE token_issuer issuer-ABC PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT ATTRIBUTE '{"email": "user@pingcap.com"}'`)) + + // create users with password reuse. + tk.MustExec(`CREATE USER 'reuse_user'@'%' IDENTIFIED WITH 'tidb_auth_token' PASSWORD HISTORY 5 PASSWORD REUSE INTERVAL 3 DAY`) + tk.MustQuery("SHOW CREATE USER reuse_user").Check(testkit.Rows(`CREATE USER 'reuse_user'@'%' IDENTIFIED WITH 'tidb_auth_token' AS '' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY 5 PASSWORD REUSE INTERVAL 3 DAY`)) + tk.MustExec(`ALTER USER 'reuse_user'@'%' PASSWORD HISTORY 50`) + tk.MustQuery("SHOW CREATE USER reuse_user").Check(testkit.Rows(`CREATE USER 'reuse_user'@'%' IDENTIFIED WITH 'tidb_auth_token' AS '' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY 50 PASSWORD REUSE INTERVAL 3 DAY`)) + tk.MustExec(`ALTER USER 'reuse_user'@'%' PASSWORD REUSE INTERVAL 31 DAY`) + tk.MustQuery("SHOW CREATE USER reuse_user").Check(testkit.Rows(`CREATE USER 'reuse_user'@'%' IDENTIFIED WITH 'tidb_auth_token' AS '' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY 50 PASSWORD REUSE INTERVAL 31 DAY`)) + + tk.MustExec("CREATE USER 'jeffrey1'@'localhost' PASSWORD EXPIRE") + tk.MustQuery("SHOW CREATE USER 'jeffrey1'@'localhost'").Check(testkit.Rows(`CREATE USER 'jeffrey1'@'localhost' IDENTIFIED WITH 'mysql_native_password' AS '' REQUIRE NONE PASSWORD EXPIRE ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT`)) + tk.MustExec("CREATE USER 'jeffrey2'@'localhost' PASSWORD EXPIRE DEFAULT") + tk.MustQuery("SHOW CREATE USER 'jeffrey2'@'localhost'").Check(testkit.Rows(`CREATE USER 'jeffrey2'@'localhost' IDENTIFIED WITH 'mysql_native_password' AS '' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT`)) + tk.MustExec("CREATE USER 'jeffrey3'@'localhost' PASSWORD EXPIRE NEVER") + tk.MustQuery("SHOW CREATE USER 'jeffrey3'@'localhost'").Check(testkit.Rows(`CREATE USER 'jeffrey3'@'localhost' IDENTIFIED WITH 'mysql_native_password' AS '' REQUIRE NONE PASSWORD EXPIRE NEVER ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT`)) + tk.MustExec("CREATE USER 'jeffrey4'@'localhost' PASSWORD EXPIRE INTERVAL 180 DAY") + tk.MustQuery("SHOW CREATE USER 'jeffrey4'@'localhost'").Check(testkit.Rows(`CREATE USER 'jeffrey4'@'localhost' IDENTIFIED WITH 'mysql_native_password' AS '' REQUIRE NONE PASSWORD EXPIRE INTERVAL 180 DAY ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT`)) + + tk.MustExec("CREATE USER failed_login_user") + tk.MustQuery("SHOW CREATE USER failed_login_user").Check(testkit.Rows(`CREATE USER 'failed_login_user'@'%' IDENTIFIED WITH 'mysql_native_password' AS '' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT`)) + tk.MustExec("ALTER USER failed_login_user FAILED_LOGIN_ATTEMPTS 1 PASSWORD_LOCK_TIME 2") + tk.MustQuery("SHOW CREATE USER failed_login_user").Check(testkit.Rows(`CREATE USER 'failed_login_user'@'%' IDENTIFIED WITH 'mysql_native_password' AS '' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT FAILED_LOGIN_ATTEMPTS 1 PASSWORD_LOCK_TIME 2`)) + tk.MustExec("ALTER USER failed_login_user PASSWORD_LOCK_TIME UNBOUNDED") + tk.MustQuery("SHOW CREATE USER failed_login_user").Check(testkit.Rows(`CREATE USER 'failed_login_user'@'%' IDENTIFIED WITH 'mysql_native_password' AS '' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT FAILED_LOGIN_ATTEMPTS 1 PASSWORD_LOCK_TIME UNBOUNDED`)) + tk.MustExec("ALTER USER failed_login_user comment 'testcomment'") + tk.MustQuery("SHOW CREATE USER failed_login_user").Check(testkit.Rows(`CREATE USER 'failed_login_user'@'%' IDENTIFIED WITH 'mysql_native_password' AS '' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT FAILED_LOGIN_ATTEMPTS 1 PASSWORD_LOCK_TIME UNBOUNDED ATTRIBUTE '{"comment": "testcomment"}'`)) + tk.MustExec("ALTER USER failed_login_user ATTRIBUTE '{\"attribute\": \"testattribute\"}'") + tk.MustQuery("SHOW CREATE USER failed_login_user").Check(testkit.Rows(`CREATE USER 'failed_login_user'@'%' IDENTIFIED WITH 'mysql_native_password' AS '' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT FAILED_LOGIN_ATTEMPTS 1 PASSWORD_LOCK_TIME UNBOUNDED ATTRIBUTE '{"attribute": "testattribute", "comment": "testcomment"}'`)) +} + +func TestUnprivilegedShow(t *testing.T) { + store, dom := testkit.CreateMockStoreAndDomain(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("CREATE DATABASE testshow") + tk.MustExec("USE testshow") + tk.MustExec("CREATE TABLE t1 (a int)") + tk.MustExec("CREATE TABLE t2 (a int)") + + tk.MustExec(`CREATE USER 'lowprivuser'`) // no grants + + tk.Session().Auth(&auth.UserIdentity{Username: "lowprivuser", Hostname: "192.168.0.1", AuthUsername: "lowprivuser", AuthHostname: "%"}, nil, []byte("012345678901234567890"), nil) + rs, err := tk.Exec("SHOW TABLE STATUS FROM testshow") + require.NoError(t, err) + require.NotNil(t, rs) + + tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "192.168.0.1", AuthUsername: "root", AuthHostname: "%"}, nil, []byte("012345678901234567890"), nil) + tk.MustExec("GRANT ALL ON testshow.t1 TO 'lowprivuser'") + tk.Session().Auth(&auth.UserIdentity{Username: "lowprivuser", Hostname: "192.168.0.1", AuthUsername: "lowprivuser", AuthHostname: "%"}, nil, []byte("012345678901234567890"), nil) + + is := dom.InfoSchema() + tblInfo, err := is.TableByName(model.NewCIStr("testshow"), model.NewCIStr("t1")) + require.NoError(t, err) + createTime := model.TSConvert2Time(tblInfo.Meta().UpdateTS).Format(time.DateTime) + + tk.MustQuery("show table status from testshow").Check(testkit.Rows(fmt.Sprintf("t1 InnoDB 10 Compact 0 0 0 0 0 0 %s utf8mb4_bin ", createTime))) +} + +func TestCollation(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + + rs, err := tk.Exec("show collation;") + require.NoError(t, err) + fields := rs.Fields() + require.Equal(t, mysql.TypeVarchar, fields[0].Column.GetType()) + require.Equal(t, mysql.TypeVarchar, fields[1].Column.GetType()) + require.Equal(t, mysql.TypeLonglong, fields[2].Column.GetType()) + require.Equal(t, mysql.TypeVarchar, fields[3].Column.GetType()) + require.Equal(t, mysql.TypeVarchar, fields[4].Column.GetType()) + require.Equal(t, mysql.TypeLonglong, fields[5].Column.GetType()) +} + +func TestShowTableStatus(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 bigint);`) + + tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "192.168.0.1", AuthUsername: "root", AuthHostname: "%"}, nil, []byte("012345678901234567890"), nil) + + // It's not easy to test the result contents because every time the test runs, "Create_time" changed. + tk.MustExec("show table status;") + rs, err := tk.Exec("show table status;") + require.NoError(t, err) + require.NotNil(t, rs) + rows, err := session.GetRows4Test(context.Background(), tk.Session(), rs) + require.NoError(t, err) + err = rs.Close() + require.NoError(t, err) + require.Equal(t, 1, len(rows)) + + for i := range rows { + row := rows[i] + require.Equal(t, "t", row.GetString(0)) + require.Equal(t, "InnoDB", row.GetString(1)) + require.Equal(t, int64(10), row.GetInt64(2)) + require.Equal(t, "Compact", row.GetString(3)) + } + tk.MustExec(`drop table if exists tp;`) + tk.MustExec(`create table tp (a int) + partition by range(a) + ( partition p0 values less than (10), + partition p1 values less than (20), + partition p2 values less than (maxvalue) + );`) + rs, err = tk.Exec("show table status from test like 'tp';") + require.NoError(t, err) + rows, err = session.GetRows4Test(context.Background(), tk.Session(), rs) + require.NoError(t, err) + require.Equal(t, "partitioned", rows[0].GetString(16)) + + tk.MustExec("create database UPPER_CASE") + tk.MustExec("use UPPER_CASE") + tk.MustExec("create table t (i int)") + rs, err = tk.Exec("show table status") + require.NoError(t, err) + require.NotNil(t, rs) + rows, err = session.GetRows4Test(context.Background(), tk.Session(), rs) + require.NoError(t, err) + err = rs.Close() + require.NoError(t, err) + require.Equal(t, 1, len(rows)) + + tk.MustExec("use upper_case") + rs, err = tk.Exec("show table status") + require.NoError(t, err) + require.NotNil(t, rs) + rows, err = session.GetRows4Test(context.Background(), tk.Session(), rs) + require.NoError(t, err) + err = rs.Close() + require.NoError(t, err) + require.Equal(t, 1, len(rows)) + + tk.MustExec("drop database UPPER_CASE") +} + +func TestShowSlow(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + // The test result is volatile, because + // 1. Slow queries is stored in domain, which may be affected by other tests. + // 2. Collecting slow queries is a asynchronous process, check immediately may not get the expected result. + // 3. Make slow query like "select sleep(1)" would slow the CI. + // So, we just cover the code but do not check the result. + tk.MustQuery(`admin show slow recent 3`) + tk.MustQuery(`admin show slow top 3`) + tk.MustQuery(`admin show slow top internal 3`) + tk.MustQuery(`admin show slow top all 3`) +} + +func TestShowCreateTableAutoRandom(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + + // Basic show create table. + tk.MustExec("create table auto_random_tbl1 (a bigint primary key auto_random(3), b varchar(255))") + tk.MustQuery("show create table `auto_random_tbl1`").Check(testkit.RowsWithSep("|", + ""+ + "auto_random_tbl1 CREATE TABLE `auto_random_tbl1` (\n"+ + " `a` bigint(20) NOT NULL /*T![auto_rand] AUTO_RANDOM(3) */,\n"+ + " `b` varchar(255) DEFAULT NULL,\n"+ + " PRIMARY KEY (`a`) /*T![clustered_index] CLUSTERED */\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + + // Implicit auto_random value should be shown explicitly. + tk.MustExec("create table auto_random_tbl2 (a bigint auto_random primary key, b char)") + tk.MustQuery("show create table auto_random_tbl2").Check(testkit.RowsWithSep("|", + ""+ + "auto_random_tbl2 CREATE TABLE `auto_random_tbl2` (\n"+ + " `a` bigint(20) NOT NULL /*T![auto_rand] AUTO_RANDOM(5) */,\n"+ + " `b` char(1) DEFAULT NULL,\n"+ + " PRIMARY KEY (`a`) /*T![clustered_index] CLUSTERED */\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + + // Special version comment can be shown in TiDB with new version. + tk.MustExec("create table auto_random_tbl3 (a bigint /*T![auto_rand] auto_random */ primary key)") + tk.MustQuery("show create table auto_random_tbl3").Check(testkit.RowsWithSep("|", + ""+ + "auto_random_tbl3 CREATE TABLE `auto_random_tbl3` (\n"+ + " `a` bigint(20) NOT NULL /*T![auto_rand] AUTO_RANDOM(5) */,\n"+ + " PRIMARY KEY (`a`) /*T![clustered_index] CLUSTERED */\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + // Test show auto_random table option. + tk.MustExec("create table auto_random_tbl4 (a bigint primary key auto_random(5), b varchar(255)) auto_random_base = 100") + tk.MustQuery("show create table `auto_random_tbl4`").Check(testkit.RowsWithSep("|", + ""+ + "auto_random_tbl4 CREATE TABLE `auto_random_tbl4` (\n"+ + " `a` bigint(20) NOT NULL /*T![auto_rand] AUTO_RANDOM(5) */,\n"+ + " `b` varchar(255) DEFAULT NULL,\n"+ + " PRIMARY KEY (`a`) /*T![clustered_index] CLUSTERED */\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![auto_rand_base] AUTO_RANDOM_BASE=100 */", + )) + // Test implicit auto_random with auto_random table option. + tk.MustExec("create table auto_random_tbl5 (a bigint auto_random primary key, b char) auto_random_base 50") + tk.MustQuery("show create table auto_random_tbl5").Check(testkit.RowsWithSep("|", + ""+ + "auto_random_tbl5 CREATE TABLE `auto_random_tbl5` (\n"+ + " `a` bigint(20) NOT NULL /*T![auto_rand] AUTO_RANDOM(5) */,\n"+ + " `b` char(1) DEFAULT NULL,\n"+ + " PRIMARY KEY (`a`) /*T![clustered_index] CLUSTERED */\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![auto_rand_base] AUTO_RANDOM_BASE=50 */", + )) + // Test auto_random table option already with special comment. + tk.MustExec("create table auto_random_tbl6 (a bigint /*T![auto_rand] auto_random */ primary key) auto_random_base 200") + tk.MustQuery("show create table auto_random_tbl6").Check(testkit.RowsWithSep("|", + ""+ + "auto_random_tbl6 CREATE TABLE `auto_random_tbl6` (\n"+ + " `a` bigint(20) NOT NULL /*T![auto_rand] AUTO_RANDOM(5) */,\n"+ + " PRIMARY KEY (`a`) /*T![clustered_index] CLUSTERED */\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![auto_rand_base] AUTO_RANDOM_BASE=200 */", + )) + // Test auto_random table with range bits can be shown correctly. + tk.MustExec("create table auto_random_tbl7 (a bigint primary key auto_random(4, 32), b varchar(255));") + tk.MustQuery("show create table auto_random_tbl7").Check(testkit.RowsWithSep("|", + ""+ + "auto_random_tbl7 CREATE TABLE `auto_random_tbl7` (\n"+ + " `a` bigint(20) NOT NULL /*T![auto_rand] AUTO_RANDOM(4, 32) */,\n"+ + " `b` varchar(255) DEFAULT NULL,\n"+ + " PRIMARY KEY (`a`) /*T![clustered_index] CLUSTERED */\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) +} + +// TestAutoIdCache overrides testAutoRandomSuite to test auto id cache. +func TestAutoIdCache(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 auto_increment key) auto_id_cache = 10") + tk.MustQuery("show create table t").Check(testkit.RowsWithSep("|", + ""+ + "t CREATE TABLE `t` (\n"+ + " `a` int(11) NOT NULL AUTO_INCREMENT,\n"+ + " PRIMARY KEY (`a`) /*T![clustered_index] CLUSTERED */\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![auto_id_cache] AUTO_ID_CACHE=10 */", + )) + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(a int auto_increment unique, b int key) auto_id_cache 100") + tk.MustQuery("show create table t").Check(testkit.RowsWithSep("|", + ""+ + "t CREATE TABLE `t` (\n"+ + " `a` int(11) NOT NULL AUTO_INCREMENT,\n"+ + " `b` int(11) NOT NULL,\n"+ + " PRIMARY KEY (`b`) /*T![clustered_index] CLUSTERED */,\n"+ + " UNIQUE KEY `a` (`a`)\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![auto_id_cache] AUTO_ID_CACHE=100 */", + )) + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(a int key) auto_id_cache 5") + tk.MustQuery("show create table t").Check(testkit.RowsWithSep("|", + ""+ + "t CREATE TABLE `t` (\n"+ + " `a` int(11) NOT NULL,\n"+ + " PRIMARY KEY (`a`) /*T![clustered_index] CLUSTERED */\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![auto_id_cache] AUTO_ID_CACHE=5 */", + )) +} + +func TestShowCreateStmtIgnoreLocalTemporaryTables(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + + // SHOW CREATE VIEW ignores local temporary table with the same name + tk.MustExec("drop view if exists v1") + tk.MustExec("create view v1 as select 1") + tk.MustExec("create temporary table v1 (a int)") + tk.MustQuery("show create table v1").Check(testkit.RowsWithSep("|", + ""+ + "v1 CREATE TEMPORARY TABLE `v1` (\n"+ + " `a` int(11) DEFAULT NULL\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + tk.MustExec("drop view v1") + err := tk.ExecToErr("show create view v1") + require.True(t, infoschema.ErrTableNotExists.Equal(err)) + + // SHOW CREATE SEQUENCE ignores local temporary table with the same name + tk.MustExec("drop view if exists seq1") + tk.MustExec("create sequence seq1") + tk.MustExec("create temporary table seq1 (a int)") + tk.MustQuery("show create sequence seq1").Check(testkit.RowsWithSep("|", + "seq1 CREATE SEQUENCE `seq1` start with 1 minvalue 1 maxvalue 9223372036854775806 increment by 1 cache 1000 nocycle ENGINE=InnoDB", + )) + tk.MustExec("drop sequence seq1") + err = tk.ExecToErr("show create sequence seq1") + require.True(t, infoschema.ErrTableNotExists.Equal(err)) +} + +func TestAutoRandomBase(t *testing.T) { + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/meta/autoid/mockAutoIDChange", `return(true)`)) + defer func() { + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/meta/autoid/mockAutoIDChange")) + }() + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("set @@allow_auto_random_explicit_insert = true") + tk.MustExec("use test") + + tk.MustExec("drop table if exists t") + tk.MustExec("create table t (a bigint primary key auto_random(5), b int unique key auto_increment) auto_random_base = 100, auto_increment = 100") + tk.MustQuery("show create table t").Check(testkit.RowsWithSep("|", + ""+ + "t CREATE TABLE `t` (\n"+ + " `a` bigint(20) NOT NULL /*T![auto_rand] AUTO_RANDOM(5) */,\n"+ + " `b` int(11) NOT NULL AUTO_INCREMENT,\n"+ + " PRIMARY KEY (`a`) /*T![clustered_index] CLUSTERED */,\n"+ + " UNIQUE KEY `b` (`b`)\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=100 /*T![auto_rand_base] AUTO_RANDOM_BASE=100 */", + )) + + tk.MustExec("insert into t(`a`) values (1000)") + tk.MustQuery("show create table t").Check(testkit.RowsWithSep("|", + ""+ + "t CREATE TABLE `t` (\n"+ + " `a` bigint(20) NOT NULL /*T![auto_rand] AUTO_RANDOM(5) */,\n"+ + " `b` int(11) NOT NULL AUTO_INCREMENT,\n"+ + " PRIMARY KEY (`a`) /*T![clustered_index] CLUSTERED */,\n"+ + " UNIQUE KEY `b` (`b`)\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=5100 /*T![auto_rand_base] AUTO_RANDOM_BASE=6001 */", + )) +} + +func TestAutoRandomWithLargeSignedShowTableRegions(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("create database if not exists auto_random_db;") + defer tk.MustExec("drop database if exists auto_random_db;") + tk.MustExec("use auto_random_db;") + tk.MustExec("drop table if exists t;") + + tk.MustExec("create table t (a bigint unsigned auto_random primary key clustered);") + tk.MustExec("set @@global.tidb_scatter_region=1;") + // 18446744073709541615 is MaxUint64 - 10000. + // 18446744073709551615 is the MaxUint64. + tk.MustQuery("split table t between (18446744073709541615) and (18446744073709551615) regions 2;"). + Check(testkit.Rows("1 1")) + startKey := tk.MustQuery("show table t regions;").Rows()[1][1].(string) + idx := strings.Index(startKey, "_r_") + require.False(t, idx == -1) + require.Falsef(t, startKey[idx+3] == '-', "actual key: %s", startKey) +} + +func TestShowEscape(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + tk.MustExec("use test") + tk.MustExec("drop table if exists `t``abl\"e`") + tk.MustExec("create table `t``abl\"e`(`c``olum\"n` int(11) primary key)") + tk.MustQuery("show create table `t``abl\"e`").Check(testkit.RowsWithSep("|", + ""+ + "t`abl\"e CREATE TABLE `t``abl\"e` (\n"+ + " `c``olum\"n` int(11) NOT NULL,\n"+ + " PRIMARY KEY (`c``olum\"n`) /*T![clustered_index] CLUSTERED */\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + + // ANSI_QUOTES will change the SHOW output + tk.MustExec("set @old_sql_mode=@@sql_mode") + tk.MustExec("set sql_mode=ansi_quotes") + tk.MustQuery("show create table \"t`abl\"\"e\"").Check(testkit.RowsWithSep("|", + ""+ + "t`abl\"e CREATE TABLE \"t`abl\"\"e\" (\n"+ + " \"c`olum\"\"n\" int(11) NOT NULL,\n"+ + " PRIMARY KEY (\"c`olum\"\"n\") /*T![clustered_index] CLUSTERED */\n"+ + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", + )) + + tk.MustExec("rename table \"t`abl\"\"e\" to t") + tk.MustExec("set sql_mode=@old_sql_mode") +} + +func TestShowBuiltin(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + res := tk.MustQuery("show builtins;") + require.NotNil(t, res) + rows := res.Rows() + const builtinFuncNum = 291 + require.Equal(t, builtinFuncNum, len(rows)) + require.Equal(t, rows[0][0].(string), "abs") + require.Equal(t, rows[builtinFuncNum-1][0].(string), "yearweek") +} + +func TestShowClusterConfig(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + + var confItems [][]types.Datum + var confErr error + var confFunc executor.TestShowClusterConfigFunc = func() ([][]types.Datum, error) { + return confItems, confErr + } + tk.Session().SetValue(executor.TestShowClusterConfigKey, confFunc) + strs2Items := func(strs ...string) []types.Datum { + items := make([]types.Datum, 0, len(strs)) + for _, s := range strs { + items = append(items, types.NewStringDatum(s)) + } + return items + } + confItems = append(confItems, strs2Items("tidb", "127.0.0.1:1111", "log.level", "info")) + confItems = append(confItems, strs2Items("pd", "127.0.0.1:2222", "log.level", "info")) + confItems = append(confItems, strs2Items("tikv", "127.0.0.1:3333", "log.level", "info")) + tk.MustQuery("show config").Check(testkit.Rows( + "tidb 127.0.0.1:1111 log.level info", + "pd 127.0.0.1:2222 log.level info", + "tikv 127.0.0.1:3333 log.level info")) + tk.MustQuery("show config where type='tidb'").Check(testkit.Rows( + "tidb 127.0.0.1:1111 log.level info")) + tk.MustQuery("show config where type like '%ti%'").Check(testkit.Rows( + "tidb 127.0.0.1:1111 log.level info", + "tikv 127.0.0.1:3333 log.level info")) + + confErr = fmt.Errorf("something unknown error") + require.EqualError(t, tk.QueryToErr("show config"), confErr.Error()) +} + +func TestInvisibleCoprCacheConfig(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + rows := tk.MustQuery("show variables like '%config%'").Rows() + require.Equal(t, 1, len(rows)) + configValue := rows[0][1].(string) + coprCacheVal := + "\t\t\"copr-cache\": {\n" + + "\t\t\t\"capacity-mb\": 1000\n" + + "\t\t},\n" + require.Equal(t, true, strings.Contains(configValue, coprCacheVal)) +} + +func TestEnableGlobalKillConfig(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + rows := tk.MustQuery("show variables like '%config%'").Rows() + require.Equal(t, 1, len(rows)) + configValue := rows[0][1].(string) + globalKillVal := "\"enable-global-kill\": true" + require.True(t, strings.Contains(configValue, globalKillVal)) +} + +func TestShowCreateTableWithIntegerDisplayLengthWarnings(t *testing.T) { + parsertypes.TiDBStrictIntegerDisplayWidth = true + defer func() { + parsertypes.TiDBStrictIntegerDisplayWidth = false + }() + 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(2), b varchar(2))") + tk.MustQuery("show warnings").Check(testkit.Rows( + "Warning 1681 Integer display width is deprecated and will be removed in a future release.", + )) + tk.MustQuery("show create table t").Check(testkit.Rows("t CREATE TABLE `t` (\n" + + " `a` int DEFAULT NULL,\n" + + " `b` varchar(2) DEFAULT NULL\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin")) + + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(a bigint(10), b bigint)") + tk.MustQuery("show warnings").Check(testkit.Rows( + "Warning 1681 Integer display width is deprecated and will be removed in a future release.", + )) + tk.MustQuery("show create table t").Check(testkit.Rows("t CREATE TABLE `t` (\n" + + " `a` bigint DEFAULT NULL,\n" + + " `b` bigint DEFAULT NULL\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin")) + + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(a tinyint(5), b tinyint(2), c tinyint)") + // Here it will occur 2 warnings. + tk.MustQuery("show warnings").Check(testkit.Rows( + "Warning 1681 Integer display width is deprecated and will be removed in a future release.", + "Warning 1681 Integer display width is deprecated and will be removed in a future release.", + )) + tk.MustQuery("show create table t").Check(testkit.Rows("t CREATE TABLE `t` (\n" + + " `a` tinyint DEFAULT NULL,\n" + + " `b` tinyint DEFAULT NULL,\n" + + " `c` tinyint DEFAULT NULL\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin")) + + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(a smallint(5), b smallint)") + tk.MustQuery("show warnings").Check(testkit.Rows( + "Warning 1681 Integer display width is deprecated and will be removed in a future release.", + )) + tk.MustQuery("show create table t").Check(testkit.Rows("t CREATE TABLE `t` (\n" + + " `a` smallint DEFAULT NULL,\n" + + " `b` smallint DEFAULT NULL\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin")) + + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(a mediumint(5), b mediumint)") + tk.MustQuery("show warnings").Check(testkit.Rows( + "Warning 1681 Integer display width is deprecated and will be removed in a future release.", + )) + tk.MustQuery("show create table t").Check(testkit.Rows("t CREATE TABLE `t` (\n" + + " `a` mediumint DEFAULT NULL,\n" + + " `b` mediumint DEFAULT NULL\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin")) + + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(a int1(1), b int2(2), c int3, d int4, e int8)") + tk.MustQuery("show warnings").Check(testkit.Rows( + "Warning 1681 Integer display width is deprecated and will be removed in a future release.", + )) + tk.MustQuery("show create table t").Check(testkit.Rows("t CREATE TABLE `t` (\n" + + " `a` tinyint(1) DEFAULT NULL,\n" + + " `b` smallint DEFAULT NULL,\n" + + " `c` mediumint DEFAULT NULL,\n" + + " `d` int DEFAULT NULL,\n" + + " `e` bigint DEFAULT NULL\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin")) + + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(id int primary key, c1 bool, c2 int(10) zerofill)") + tk.MustQuery("show warnings").Check(testkit.Rows( + "Warning 1681 Integer display width is deprecated and will be removed in a future release.", + "Warning 1681 The ZEROFILL attribute is deprecated and will be removed in a future release. Use the LPAD function to zero-pad numbers, or store the formatted numbers in a CHAR column.", + )) + tk.MustQuery("show create table t").Check(testkit.Rows("t CREATE TABLE `t` (\n" + + " `id` int NOT NULL,\n" + + " `c1` tinyint(1) DEFAULT NULL,\n" + + " `c2` int(10) unsigned zerofill DEFAULT NULL,\n" + + " PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin")) +} + +func TestShowVar(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + var showSQL string + sessionVars := make([]string, 0, len(variable.GetSysVars())) + globalVars := make([]string, 0, len(variable.GetSysVars())) + for _, v := range variable.GetSysVars() { + if v.Scope == variable.ScopeSession { + sessionVars = append(sessionVars, v.Name) + } else { + globalVars = append(globalVars, v.Name) + } + } + + // When ScopeSession only. `show global variables` must return empty. + sessionVarsStr := strings.Join(sessionVars, "','") + showSQL = "show variables where variable_name in('" + sessionVarsStr + "')" + res := tk.MustQuery(showSQL) + require.Len(t, res.Rows(), len(sessionVars)) + showSQL = "show global variables where variable_name in('" + sessionVarsStr + "')" + res = tk.MustQuery(showSQL) + require.Len(t, res.Rows(), 0) + + globalVarsStr := strings.Join(globalVars, "','") + showSQL = "show variables where variable_name in('" + globalVarsStr + "')" + res = tk.MustQuery(showSQL) + require.Len(t, res.Rows(), len(globalVars)) + showSQL = "show global variables where variable_name in('" + globalVarsStr + "')" + res = tk.MustQuery(showSQL) + require.Len(t, res.Rows(), len(globalVars)) + + // Test versions' related variables + res = tk.MustQuery("show variables like 'version%'") + for _, row := range res.Rows() { + line := fmt.Sprint(row) + if strings.HasPrefix(line, "version ") { + require.Equal(t, mysql.ServerVersion, line[len("version "):]) + } else if strings.HasPrefix(line, "version_comment ") { + require.Equal(t, variable.GetSysVar(variable.VersionComment), line[len("version_comment "):]) + } + } + + // Test case insensitive case for show session variables + tk.MustExec("SET @@SQL_MODE='NO_BACKSLASH_ESCAPES'") + tk.MustQuery("SHOW SESSION VARIABLES like 'sql_mode'").Check( + testkit.RowsWithSep("|", "sql_mode|NO_BACKSLASH_ESCAPES")) + tk.MustQuery("SHOW SESSION VARIABLES like 'SQL_MODE'").Check( + testkit.RowsWithSep("|", "sql_mode|NO_BACKSLASH_ESCAPES")) +} + +func TestIssue19507(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("CREATE TABLE t2(a int primary key, b int unique, c int not null, unique index (c));") + tk.MustQuery("SHOW INDEX IN t2;").Check( + testkit.RowsWithSep("|", "t2|0|PRIMARY|1|a|A|0||||BTREE|||YES||YES", + "t2|0|c|1|c|A|0||||BTREE|||YES||NO", + "t2|0|b|1|b|A|0|||YES|BTREE|||YES||NO")) + + tk.MustExec("CREATE INDEX t2_b_c_index ON t2 (b, c);") + tk.MustExec("CREATE INDEX t2_c_b_index ON t2 (c, b);") + tk.MustQuery("SHOW INDEX IN t2;").Check( + testkit.RowsWithSep("|", "t2|0|PRIMARY|1|a|A|0||||BTREE|||YES||YES", + "t2|0|c|1|c|A|0||||BTREE|||YES||NO", + "t2|0|b|1|b|A|0|||YES|BTREE|||YES||NO", + "t2|1|t2_b_c_index|1|b|A|0|||YES|BTREE|||YES||NO", + "t2|1|t2_b_c_index|2|c|A|0||||BTREE|||YES||NO", + "t2|1|t2_c_b_index|1|c|A|0||||BTREE|||YES||NO", + "t2|1|t2_c_b_index|2|b|A|0|||YES|BTREE|||YES||NO")) +} + +// TestShowPerformanceSchema tests for Issue 19231 +func TestShowPerformanceSchema(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + // Ideally we should create a new performance_schema table here with indices that we run the tests on. + // However, its not possible to create a new performance_schema table since its a special in memory table. + // Instead the test below uses the default index on the table. + tk.MustQuery("SHOW INDEX FROM performance_schema.events_statements_summary_by_digest").Check( + testkit.Rows("events_statements_summary_by_digest 0 SCHEMA_NAME 1 SCHEMA_NAME A 0 YES BTREE YES NO", + "events_statements_summary_by_digest 0 SCHEMA_NAME 2 DIGEST A 0 YES BTREE YES NO")) +} + +func TestShowCreatePlacementPolicy(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("CREATE PLACEMENT POLICY xyz PRIMARY_REGION='us-east-1' REGIONS='us-east-1,us-east-2' FOLLOWERS=4") + tk.MustQuery("SHOW CREATE PLACEMENT POLICY xyz").Check(testkit.Rows("xyz CREATE PLACEMENT POLICY `xyz` PRIMARY_REGION=\"us-east-1\" REGIONS=\"us-east-1,us-east-2\" FOLLOWERS=4")) + // non existent policy + err := tk.QueryToErr("SHOW CREATE PLACEMENT POLICY doesnotexist") + require.Equal(t, infoschema.ErrPlacementPolicyNotExists.GenWithStackByArgs("doesnotexist").Error(), err.Error()) + // alter and try second example + tk.MustExec("ALTER PLACEMENT POLICY xyz FOLLOWERS=4") + tk.MustQuery("SHOW CREATE PLACEMENT POLICY xyz").Check(testkit.Rows("xyz CREATE PLACEMENT POLICY `xyz` FOLLOWERS=4")) + tk.MustExec("DROP PLACEMENT POLICY xyz") +} + +func TestShowTemporaryTable(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("create global temporary table t1 (id int) on commit delete rows") + tk.MustExec("create global temporary table t3 (i int primary key, j int) on commit delete rows") + // For issue https://github.com/pingcap/tidb/issues/24752 + tk.MustQuery("show create table t1").Check(testkit.Rows("t1 CREATE GLOBAL TEMPORARY TABLE `t1` (\n" + + " `id` int(11) DEFAULT NULL\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ON COMMIT DELETE ROWS")) + // No panic, fix issue https://github.com/pingcap/tidb/issues/24788 + expect := "CREATE GLOBAL TEMPORARY TABLE `t3` (\n" + + " `i` int(11) NOT NULL,\n" + + " `j` int(11) DEFAULT NULL,\n" + + " PRIMARY KEY (`i`) /*T![clustered_index] CLUSTERED */\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ON COMMIT DELETE ROWS" + tk.MustQuery("show create table t3").Check(testkit.Rows("t3 " + expect)) + + // Verify that the `show create table` result can be used to build the table. + createTable := strings.ReplaceAll(expect, "t3", "t4") + tk.MustExec(createTable) + + // Cover auto increment column. + tk.MustExec(`CREATE GLOBAL TEMPORARY TABLE t5 ( + id int(11) NOT NULL AUTO_INCREMENT, + b int(11) NOT NULL, + pad varbinary(255) DEFAULT NULL, + PRIMARY KEY (id), + KEY b (b)) ON COMMIT DELETE ROWS`) + expect = "CREATE GLOBAL TEMPORARY TABLE `t5` (\n" + + " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + + " `b` int(11) NOT NULL,\n" + + " `pad` varbinary(255) DEFAULT NULL,\n" + + " PRIMARY KEY (`id`) /*T![clustered_index] CLUSTERED */,\n" + + " KEY `b` (`b`)\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ON COMMIT DELETE ROWS" + tk.MustQuery("show create table t5").Check(testkit.Rows("t5 " + expect)) + + tk.MustExec("create temporary table t6 (i int primary key, j int)") + expect = "CREATE TEMPORARY TABLE `t6` (\n" + + " `i` int(11) NOT NULL,\n" + + " `j` int(11) DEFAULT NULL,\n" + + " PRIMARY KEY (`i`) /*T![clustered_index] CLUSTERED */\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin" + tk.MustQuery("show create table t6").Check(testkit.Rows("t6 " + expect)) + tk.MustExec("create temporary table t7 (i int primary key auto_increment, j int)") + defer func() { + tk.MustExec("commit;") + }() + tk.MustExec("begin;") + tk.MustExec("insert into t7 (j) values (14)") + tk.MustExec("insert into t7 (j) values (24)") + tk.MustQuery("select * from t7").Check(testkit.Rows("1 14", "2 24")) + expect = "CREATE TEMPORARY TABLE `t7` (\n" + + " `i` int(11) NOT NULL AUTO_INCREMENT,\n" + + " `j` int(11) DEFAULT NULL,\n" + + " PRIMARY KEY (`i`) /*T![clustered_index] CLUSTERED */\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=3" + tk.MustQuery("show create table t7").Check(testkit.Rows("t7 " + expect)) +} + +func TestShowCachedTable(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("create table t1 (id int)") + tk.MustExec("alter table t1 cache") + tk.MustQuery("show create table t1").Check( + testkit.Rows("t1 CREATE TABLE `t1` (\n" + + " `id` int(11) DEFAULT NULL\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /* CACHED ON */")) + tk.MustQuery("select create_options from information_schema.tables where table_schema = 'test' and table_name = 't1'").Check( + testkit.Rows("cached=on")) + + tk.MustExec("alter table t1 nocache") + tk.MustQuery("show create table t1").Check( + testkit.Rows("t1 CREATE TABLE `t1` (\n" + + " `id` int(11) DEFAULT NULL\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin")) + tk.MustQuery("select create_options from information_schema.tables where table_schema = 'test' and table_name = 't1'").Check( + testkit.Rows("")) +} + +func TestShowBindingCache(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)") + tk.MustExec(`set global tidb_mem_quota_binding_cache = 1`) + tk.MustQuery("select @@global.tidb_mem_quota_binding_cache").Check(testkit.Rows("1")) + tk.MustExec("admin reload bindings;") + res := tk.MustQuery("show global bindings") + require.Equal(t, 0, len(res.Rows())) + + tk.MustExec("create global binding for select * from t using select * from t") + res = tk.MustQuery("show global bindings") + require.Equal(t, 0, len(res.Rows())) + + tk.MustExec(`set global tidb_mem_quota_binding_cache = default`) + tk.MustQuery("select @@global.tidb_mem_quota_binding_cache").Check(testkit.Rows("67108864")) + tk.MustExec("admin reload bindings") + res = tk.MustQuery("show global bindings") + require.Equal(t, 1, len(res.Rows())) + + tk.MustExec("create global binding for select * from t where a > 1 using select * from t where a > 1") + res = tk.MustQuery("show global bindings") + require.Equal(t, 2, len(res.Rows())) +} + +func TestShowBindingCacheStatus(t *testing.T) { + store := testkit.CreateMockStore(t) + + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustQuery("show binding_cache status").Check(testkit.Rows( + "0 0 0 Bytes 64 MB")) + + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(a int, b int, index idx_a(a), index idx_b(b))") + result := tk.MustQuery("show global bindings") + rows := result.Rows() + require.Equal(t, len(rows), 0) + tk.MustExec("create global binding for select * from t using select * from t") + + result = tk.MustQuery("show global bindings") + rows = result.Rows() + require.Equal(t, len(rows), 1) + + tk.MustQuery("show binding_cache status").Check(testkit.Rows( + "1 1 159 Bytes 64 MB")) + + tk.MustExec(`set global tidb_mem_quota_binding_cache = 250`) + tk.MustQuery(`select @@global.tidb_mem_quota_binding_cache`).Check(testkit.Rows("250")) + tk.MustExec("admin reload bindings;") + tk.MustExec("create global binding for select * from t where a > 1 using select * from t where a > 1") + result = tk.MustQuery("show global bindings") + rows = result.Rows() + require.Equal(t, len(rows), 1) + tk.MustQuery("show binding_cache status").Check(testkit.Rows( + "1 2 187 Bytes 250 Bytes")) + + tk.MustExec("drop global binding for select * from t where a > 1") + result = tk.MustQuery("show global bindings") + rows = result.Rows() + require.Equal(t, len(rows), 0) + tk.MustQuery("show binding_cache status").Check(testkit.Rows( + "0 1 0 Bytes 250 Bytes")) + + tk.MustExec("admin reload bindings") + result = tk.MustQuery("show global bindings") + rows = result.Rows() + require.Equal(t, len(rows), 1) + tk.MustQuery("show binding_cache status").Check(testkit.Rows( + "1 1 159 Bytes 250 Bytes")) + + tk.MustExec("create global binding for select * from t using select * from t use index(idx_a)") + + result = tk.MustQuery("show global bindings") + rows = result.Rows() + require.Equal(t, len(rows), 1) + + tk.MustQuery("show binding_cache status").Check(testkit.Rows( + "1 1 198 Bytes 250 Bytes")) +} + +func TestShowDatabasesLike(t *testing.T) { + store := testkit.CreateMockStore(t) + + tk := testkit.NewTestKit(t, store) + require.NoError(t, tk.Session().Auth(&auth.UserIdentity{ + Username: "root", Hostname: "%"}, nil, nil, nil)) + + tk.MustExec("DROP DATABASE IF EXISTS `TEST_$1`") + tk.MustExec("DROP DATABASE IF EXISTS `test_$2`") + tk.MustExec("CREATE DATABASE `TEST_$1`;") + tk.MustExec("CREATE DATABASE `test_$2`;") + + tk.MustQuery("SHOW DATABASES LIKE 'TEST_%'").Check(testkit.Rows("TEST_$1", "test_$2")) + tk.MustQuery("SHOW DATABASES LIKE 'test_%'").Check(testkit.Rows("TEST_$1", "test_$2")) +} + +func TestShowTableStatusLike(t *testing.T) { + store := testkit.CreateMockStore(t) + + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("DROP table IF EXISTS `T1`") + tk.MustExec("CREATE table `T1` (a int);") + rows := tk.MustQuery("SHOW table status LIKE 't1'").Rows() + require.Equal(t, "T1", rows[0][0]) + + tk.MustExec("DROP table IF EXISTS `Li_1`") + tk.MustExec("DROP table IF EXISTS `li_2`") + + tk.MustExec("CREATE table `Li_1` (a int);") + tk.MustExec("CREATE table `li_2` (a int);") + + rows = tk.MustQuery("SHOW table status LIKE 'li%'").Rows() + require.Equal(t, "Li_1", rows[0][0]) + require.Equal(t, "li_2", rows[1][0]) +} + +func TestShowCollationsLike(t *testing.T) { + store := testkit.CreateMockStore(t) + + tk := testkit.NewTestKit(t, store) + require.NoError(t, tk.Session().Auth(&auth.UserIdentity{ + Username: "root", Hostname: "%"}, nil, nil, nil)) + tk.MustQuery("SHOW COLLATION LIKE 'UTF8MB4_BI%'").Check(testkit.Rows("utf8mb4_bin utf8mb4 46 Yes Yes 1")) + tk.MustQuery("SHOW COLLATION LIKE 'utf8mb4_bi%'").Check(testkit.Rows("utf8mb4_bin utf8mb4 46 Yes Yes 1")) +} + +func TestShowViewWithWindowFunction(t *testing.T) { + store := testkit.CreateMockStore(t) + + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("CREATE TABLE `test1` (`id` int(0) NOT NULL,`num` int(0) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;") + tk.MustExec("create or replace view test1_v as(select id,row_number() over (partition by num) from test1);") + tk.MustQuery("desc test1_v;").Check(testkit.Rows("id int(0) NO ", "row_number() over (partition by num) bigint(21) YES ")) +} + +func TestShowLimitReturnRow(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 t1(a int, b int, c int, d int, index idx_a(a), index idx_b(b))") + tk.MustExec("create table t2(a int, b int, c int, d int, index idx_a(a), index idx_b(b))") + tk.MustExec("INSERT INTO t1 VALUES(1,2,3,4)") + tk.MustExec("INSERT INTO t1 VALUES(4,3,1,2)") + tk.MustExec("SET @@sql_select_limit=1") + tk.MustExec("PREPARE stmt FROM \"SHOW COLUMNS FROM t1\"") + result := tk.MustQuery("EXECUTE stmt") + rows := result.Rows() + require.Equal(t, len(rows), 1) + + tk.MustExec("PREPARE stmt FROM \"select * FROM t1\"") + result = tk.MustQuery("EXECUTE stmt") + rows = result.Rows() + require.Equal(t, len(rows), 1) + + // Test case for other scenarios. + result = tk.MustQuery("SHOW ENGINES") + rows = result.Rows() + require.Equal(t, len(rows), 1) + + tk.MustQuery("SHOW DATABASES like '%SCHEMA'").Check(testkit.RowsWithSep("|", "INFORMATION_SCHEMA")) + + tk.MustQuery("SHOW TABLES where tables_in_test='t2'").Check(testkit.RowsWithSep("|", "t2")) + + result = tk.MustQuery("SHOW TABLE STATUS where name='t2'") + rows = result.Rows() + require.Equal(t, rows[0][0], "t2") + + tk.MustQuery("SHOW COLUMNS FROM t1 where Field ='d'").Check(testkit.RowsWithSep("|", ""+ + "d int(11) YES ")) + + tk.MustQuery("Show Charset where charset='gbk'").Check(testkit.RowsWithSep("|", ""+ + "gbk Chinese Internal Code Specification gbk_chinese_ci 2")) + + tk.MustQuery("Show Variables where variable_name ='max_allowed_packet'").Check(testkit.RowsWithSep("|", ""+ + "max_allowed_packet 67108864")) + + result = tk.MustQuery("SHOW status where variable_name ='server_id'") + rows = result.Rows() + require.Equal(t, rows[0][0], "server_id") + + tk.MustQuery("Show Collation where collation='utf8_bin'").Check(testkit.RowsWithSep("|", ""+ + "utf8_bin utf8 83 Yes Yes 1")) + + result = tk.MustQuery("show index from t1 where key_name='idx_b'") + rows = result.Rows() + require.Equal(t, rows[0][2], "idx_b") +} + +func TestShowTTLOption(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(created_at datetime) ttl = `created_at` + INTERVAL 100 YEAR") + tk.MustQuery("show create table t").Check(testkit.Rows("t CREATE TABLE `t` (\n `created_at` datetime DEFAULT NULL\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![ttl] TTL=`created_at` + INTERVAL 100 YEAR */ /*T![ttl] TTL_ENABLE='ON' */ /*T![ttl] TTL_JOB_INTERVAL='1h' */")) + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(created_at datetime) ttl = `created_at` + INTERVAL 100 YEAR ttl_enable = 'OFF'") + tk.MustQuery("show create table t").Check(testkit.Rows("t CREATE TABLE `t` (\n `created_at` datetime DEFAULT NULL\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![ttl] TTL=`created_at` + INTERVAL 100 YEAR */ /*T![ttl] TTL_ENABLE='OFF' */ /*T![ttl] TTL_JOB_INTERVAL='1h' */")) + + tk.MustExec("drop table if exists t") + tk.MustExec("create table t (created_at datetime) TTL = created_at + INTERVAL 3.14159 HOUR_MINUTE") + tk.MustQuery("show create table t").Check(testkit.Rows("t CREATE TABLE `t` (\n `created_at` datetime DEFAULT NULL\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![ttl] TTL=`created_at` + INTERVAL 3.14159 HOUR_MINUTE */ /*T![ttl] TTL_ENABLE='ON' */ /*T![ttl] TTL_JOB_INTERVAL='1h' */")) + + tk.MustExec("drop table if exists t") + tk.MustExec("create table t (created_at datetime) TTL = created_at + INTERVAL \"15:20\" HOUR_MINUTE") + tk.MustQuery("show create table t").Check(testkit.Rows("t CREATE TABLE `t` (\n `created_at` datetime DEFAULT NULL\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![ttl] TTL=`created_at` + INTERVAL _utf8mb4'15:20' HOUR_MINUTE */ /*T![ttl] TTL_ENABLE='ON' */ /*T![ttl] TTL_JOB_INTERVAL='1h' */")) + + tk.MustExec("drop table if exists t") + tk.MustExec("create table t (created_at datetime) TTL = created_at + INTERVAL 100 YEAR TTL_JOB_INTERVAL = '1d'") + tk.MustQuery("show create table t").Check(testkit.Rows("t CREATE TABLE `t` (\n `created_at` datetime DEFAULT NULL\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin /*T![ttl] TTL=`created_at` + INTERVAL 100 YEAR */ /*T![ttl] TTL_ENABLE='ON' */ /*T![ttl] TTL_JOB_INTERVAL='1d' */")) +} + +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 TestShowPasswordVariable(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + tk.MustExec("SET GLOBAL authentication_ldap_sasl_bind_root_pwd = ''") + rs, err := tk.Exec("show variables like 'authentication_ldap_sasl_bind_root_pwd'") + require.NoError(t, err) + require.Equal(t, tk.ResultSetToResult(rs, "").Rows()[0][1], "") + rs, err = tk.Exec("SELECT current_value FROM information_schema.variables_info WHERE VARIABLE_NAME LIKE 'authentication_ldap_sasl_bind_root_pwd'") + require.NoError(t, err) + require.Equal(t, tk.ResultSetToResult(rs, "").Rows()[0][0], "") + + tk.MustExec("SET GLOBAL authentication_ldap_sasl_bind_root_pwd = password") + defer func() { + tk.MustExec("SET GLOBAL authentication_ldap_sasl_bind_root_pwd = ''") + }() + rs, err = tk.Exec("show variables like 'authentication_ldap_sasl_bind_root_pwd'") + require.NoError(t, err) + require.Equal(t, tk.ResultSetToResult(rs, "").Rows()[0][1], variable.MaskPwd) + rs, err = tk.Exec("SELECT current_value FROM information_schema.variables_info WHERE VARIABLE_NAME LIKE 'authentication_ldap_sasl_bind_root_pwd'") + require.NoError(t, err) + require.Equal(t, tk.ResultSetToResult(rs, "").Rows()[0][0], variable.MaskPwd) + + tk.MustExec("SET GLOBAL authentication_ldap_simple_bind_root_pwd = ''") + rs, err = tk.Exec("show variables like 'authentication_ldap_simple_bind_root_pwd'") + require.NoError(t, err) + require.Equal(t, tk.ResultSetToResult(rs, "").Rows()[0][1], "") + rs, err = tk.Exec("SELECT current_value FROM information_schema.variables_info WHERE VARIABLE_NAME LIKE 'authentication_ldap_simple_bind_root_pwd'") + require.NoError(t, err) + require.Equal(t, tk.ResultSetToResult(rs, "").Rows()[0][0], "") + + tk.MustExec("SET GLOBAL authentication_ldap_simple_bind_root_pwd = password") + defer func() { + tk.MustExec("SET GLOBAL authentication_ldap_simple_bind_root_pwd = ''") + }() + + rs, err = tk.Exec("show variables like 'authentication_ldap_simple_bind_root_pwd'") + require.NoError(t, err) + require.Equal(t, tk.ResultSetToResult(rs, "").Rows()[0][1], variable.MaskPwd) + rs, err = tk.Exec("SELECT current_value FROM information_schema.variables_info WHERE VARIABLE_NAME LIKE 'authentication_ldap_simple_bind_root_pwd'") + require.NoError(t, err) + require.Equal(t, tk.ResultSetToResult(rs, "").Rows()[0][0], variable.MaskPwd) +} diff --git a/executor/test/simpletest/simple_test.go b/executor/test/simpletest/simple_test.go new file mode 100644 index 0000000000000..badb1821f88b0 --- /dev/null +++ b/executor/test/simpletest/simple_test.go @@ -0,0 +1,1153 @@ +// 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 simpletest + +import ( + "context" + "strconv" + "testing" + + "github.com/pingcap/errors" + "github.com/pingcap/tidb/config" + "github.com/pingcap/tidb/errno" + "github.com/pingcap/tidb/parser/auth" + "github.com/pingcap/tidb/parser/model" + "github.com/pingcap/tidb/parser/mysql" + "github.com/pingcap/tidb/parser/terror" + "github.com/pingcap/tidb/planner/core" + "github.com/pingcap/tidb/session" + "github.com/pingcap/tidb/sessionctx" + "github.com/pingcap/tidb/statistics/handle" + "github.com/pingcap/tidb/store/mockstore" + "github.com/pingcap/tidb/testkit" + "github.com/pingcap/tidb/util/dbterror/exeerrors" + "github.com/stretchr/testify/require" + "go.opencensus.io/stats/view" +) + +func TestFlushTables(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + tk.MustExec("FLUSH TABLES") + err := tk.ExecToErr("FLUSH TABLES WITH READ LOCK") + require.Error(t, err) +} + +func TestUseDB(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("USE test") + err := tk.ExecToErr("USE ``") + require.Truef(t, terror.ErrorEqual(core.ErrNoDB, err), "err %v", err) +} + +func TestStmtAutoNewTxn(t *testing.T) { + store := testkit.CreateMockStore(t) + // Some statements are like DDL, they commit the previous txn automically. + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + + // Fix issue https://github.com/pingcap/tidb/issues/10705 + tk.MustExec("begin") + tk.MustExec("create user 'xxx'@'%';") + tk.MustExec("grant all privileges on *.* to 'xxx'@'%';") + + tk.MustExec("create table auto_new (id int)") + tk.MustExec("begin") + tk.MustExec("insert into auto_new values (1)") + tk.MustExec("revoke all privileges on *.* from 'xxx'@'%'") + tk.MustExec("rollback") // insert statement has already committed + tk.MustQuery("select * from auto_new").Check(testkit.Rows("1")) + + // Test the behavior when autocommit is false. + tk.MustExec("set autocommit = 0") + tk.MustExec("insert into auto_new values (2)") + tk.MustExec("create user 'yyy'@'%'") + tk.MustExec("rollback") + tk.MustQuery("select * from auto_new").Check(testkit.Rows("1", "2")) + + tk.MustExec("drop user 'yyy'@'%'") + tk.MustExec("insert into auto_new values (3)") + tk.MustExec("rollback") + tk.MustQuery("select * from auto_new").Check(testkit.Rows("1", "2")) +} + +func TestIssue9111(t *testing.T) { + store := testkit.CreateMockStore(t) + // CREATE USER / DROP USER fails if admin doesn't have insert privilege on `mysql.user` table. + tk := testkit.NewTestKit(t, store) + tk.MustExec("create user 'user_admin'@'localhost';") + tk.MustExec("grant create user on *.* to 'user_admin'@'localhost';") + + // Create a new session. + se, err := session.CreateSession4Test(store) + require.NoError(t, err) + defer se.Close() + require.NoError(t, se.Auth(&auth.UserIdentity{Username: "user_admin", Hostname: "localhost"}, nil, nil, nil)) + + ctx := context.Background() + _, err = se.Execute(ctx, `create user test_create_user`) + require.NoError(t, err) + _, err = se.Execute(ctx, `drop user test_create_user`) + require.NoError(t, err) + + tk.MustExec("revoke create user on *.* from 'user_admin'@'localhost';") + tk.MustExec("grant insert, delete on mysql.user to 'user_admin'@'localhost';") + + _, err = se.Execute(ctx, `create user test_create_user`) + require.NoError(t, err) + _, err = se.Execute(ctx, `drop user test_create_user`) + require.NoError(t, err) + + _, err = se.Execute(ctx, `create role test_create_user`) + require.NoError(t, err) + _, err = se.Execute(ctx, `drop role test_create_user`) + require.NoError(t, err) + + tk.MustExec("drop user 'user_admin'@'localhost';") +} + +func TestRoleAtomic(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + tk.MustExec("create role r2;") + err := tk.ExecToErr("create role r1, r2, r3") + require.Error(t, err) + // Check atomic create role. + result := tk.MustQuery(`SELECT user FROM mysql.User WHERE user in ('r1', 'r2', 'r3')`) + result.Check(testkit.Rows("r2")) + // Check atomic drop role. + err = tk.ExecToErr("drop role r1, r2, r3") + require.Error(t, err) + result = tk.MustQuery(`SELECT user FROM mysql.User WHERE user in ('r1', 'r2', 'r3')`) + result.Check(testkit.Rows("r2")) + tk.MustExec("drop role r2;") +} + +func TestExtendedStatsPrivileges(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)") + tk.MustExec("create user 'u1'@'%'") + se, err := session.CreateSession4Test(store) + require.NoError(t, err) + defer se.Close() + require.NoError(t, se.Auth(&auth.UserIdentity{Username: "u1", Hostname: "%"}, nil, nil, nil)) + ctx := context.Background() + _, err = se.Execute(ctx, "set session tidb_enable_extended_stats = on") + require.NoError(t, err) + _, err = se.Execute(ctx, "alter table test.t add stats_extended s1 correlation(a,b)") + require.Error(t, err) + require.Equal(t, "[planner:1142]ALTER command denied to user 'u1'@'%' for table 't'", err.Error()) + tk.MustExec("grant alter on test.* to 'u1'@'%'") + _, err = se.Execute(ctx, "alter table test.t add stats_extended s1 correlation(a,b)") + require.Error(t, err) + require.Equal(t, "[planner:1142]ADD STATS_EXTENDED command denied to user 'u1'@'%' for table 't'", err.Error()) + tk.MustExec("grant select on test.* to 'u1'@'%'") + _, err = se.Execute(ctx, "alter table test.t add stats_extended s1 correlation(a,b)") + require.Error(t, err) + require.Equal(t, "[planner:1142]ADD STATS_EXTENDED command denied to user 'u1'@'%' for table 'stats_extended'", err.Error()) + tk.MustExec("grant insert on mysql.stats_extended to 'u1'@'%'") + _, err = se.Execute(ctx, "alter table test.t add stats_extended s1 correlation(a,b)") + require.NoError(t, err) + + _, err = se.Execute(ctx, "use test") + require.NoError(t, err) + _, err = se.Execute(ctx, "alter table t drop stats_extended s1") + require.Error(t, err) + require.Equal(t, "[planner:1142]DROP STATS_EXTENDED command denied to user 'u1'@'%' for table 'stats_extended'", err.Error()) + tk.MustExec("grant update on mysql.stats_extended to 'u1'@'%'") + _, err = se.Execute(ctx, "alter table t drop stats_extended s1") + require.NoError(t, err) + tk.MustExec("drop user 'u1'@'%'") +} + +func TestIssue17247(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("create user 'issue17247'") + tk.MustExec("grant CREATE USER on *.* to 'issue17247'") + + tk1 := testkit.NewTestKit(t, store) + tk1.MustExec("use test") + require.NoError(t, tk1.Session().Auth(&auth.UserIdentity{Username: "issue17247", Hostname: "%"}, nil, nil, nil)) + tk1.MustExec("ALTER USER USER() IDENTIFIED BY 'xxx'") + tk1.MustExec("ALTER USER CURRENT_USER() IDENTIFIED BY 'yyy'") + tk1.MustExec("ALTER USER CURRENT_USER IDENTIFIED BY 'zzz'") + tk.MustExec("ALTER USER 'issue17247'@'%' IDENTIFIED BY 'kkk'") + tk.MustExec("ALTER USER 'issue17247'@'%' IDENTIFIED BY PASSWORD '*B50FBDB37F1256824274912F2A1CE648082C3F1F'") + // Wrong grammar + _, err := tk1.Exec("ALTER USER USER() IDENTIFIED BY PASSWORD '*B50FBDB37F1256824274912F2A1CE648082C3F1F'") + require.Error(t, err) +} + +// Close issue #23649. +// See https://github.com/pingcap/tidb/issues/23649 +func TestIssue23649(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("DROP USER IF EXISTS issue23649;") + tk.MustExec("CREATE USER issue23649;") + err := tk.ExecToErr("GRANT bogusrole to issue23649;") + require.Equal(t, "[executor:3523]Unknown authorization ID `bogusrole`@`%`", err.Error()) + err = tk.ExecToErr("GRANT bogusrole to nonexisting;") + require.Equal(t, "[executor:3523]Unknown authorization ID `bogusrole`@`%`", err.Error()) +} + +func TestSetCurrentUserPwd(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("CREATE USER issue28534;") + defer func() { + tk.MustExec("DROP USER IF EXISTS issue28534;") + }() + + require.NoError(t, tk.Session().Auth(&auth.UserIdentity{Username: "issue28534", Hostname: "localhost", CurrentUser: true, AuthUsername: "issue28534", AuthHostname: "%"}, nil, nil, nil)) + tk.MustExec(`SET PASSWORD FOR CURRENT_USER() = "43582eussi"`) + + require.NoError(t, tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil, nil)) + result := tk.MustQuery(`SELECT authentication_string FROM mysql.User WHERE User="issue28534"`) + result.Check(testkit.Rows(auth.EncodePassword("43582eussi"))) +} + +func TestShowGrantsAfterDropRole(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("CREATE USER u29473") + defer tk.MustExec("DROP USER IF EXISTS u29473") + + tk.MustExec("CREATE ROLE r29473") + tk.MustExec("GRANT r29473 TO u29473") + tk.MustExec("GRANT CREATE USER ON *.* TO u29473") + + tk.Session().Auth(&auth.UserIdentity{Username: "u29473", Hostname: "%"}, nil, nil, nil) + tk.MustExec("SET ROLE r29473") + tk.MustExec("DROP ROLE r29473") + tk.MustQuery("SHOW GRANTS").Check(testkit.Rows("GRANT CREATE USER ON *.* TO 'u29473'@'%'")) +} + +func TestPrivilegesAfterDropUser(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("create table t1(id int, v int)") + defer tk.MustExec("drop table t1") + + tk.MustExec("CREATE USER u1 require ssl") + defer tk.MustExec("DROP USER IF EXISTS u1") + + tk.MustExec("GRANT CREATE ON test.* TO u1") + tk.MustExec("GRANT UPDATE ON test.t1 TO u1") + tk.MustExec("GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO u1") + tk.MustExec("GRANT SELECT(v), UPDATE(v) on test.t1 TO u1") + + tk.MustQuery("SELECT COUNT(1) FROM mysql.global_grants WHERE USER='u1' AND HOST='%'").Check(testkit.Rows("1")) + tk.MustQuery("SELECT COUNT(1) FROM mysql.global_priv WHERE USER='u1' AND HOST='%'").Check(testkit.Rows("1")) + tk.MustQuery("SELECT COUNT(1) FROM mysql.tables_priv WHERE USER='u1' AND HOST='%'").Check(testkit.Rows("1")) + tk.MustQuery("SELECT COUNT(1) FROM mysql.columns_priv WHERE USER='u1' AND HOST='%'").Check(testkit.Rows("1")) + tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil, nil) + tk.MustQuery("SHOW GRANTS FOR u1").Check(testkit.Rows( + "GRANT USAGE ON *.* TO 'u1'@'%'", + "GRANT CREATE ON `test`.* TO 'u1'@'%'", + "GRANT UPDATE ON `test`.`t1` TO 'u1'@'%'", + "GRANT SELECT(v), UPDATE(v) ON `test`.`t1` TO 'u1'@'%'", + "GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO 'u1'@'%'", + )) + + tk.MustExec("DROP USER u1") + err := tk.QueryToErr("SHOW GRANTS FOR u1") + require.Equal(t, "[privilege:1141]There is no such grant defined for user 'u1' on host '%'", err.Error()) + tk.MustQuery("SELECT * FROM mysql.global_grants WHERE USER='u1' AND HOST='%'").Check(testkit.Rows()) + tk.MustQuery("SELECT * FROM mysql.global_priv WHERE USER='u1' AND HOST='%'").Check(testkit.Rows()) + tk.MustQuery("SELECT * FROM mysql.tables_priv WHERE USER='u1' AND HOST='%'").Check(testkit.Rows()) + tk.MustQuery("SELECT * FROM mysql.columns_priv WHERE USER='u1' AND HOST='%'").Check(testkit.Rows()) +} + +func TestDropRoleAfterRevoke(t *testing.T) { + store := testkit.CreateMockStore(t) + // issue 29781 + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test;") + tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil, nil) + + tk.MustExec("create role r1, r2, r3;") + defer tk.MustExec("drop role if exists r1, r2, r3;") + tk.MustExec("grant r1,r2,r3 to current_user();") + tk.MustExec("set role all;") + tk.MustExec("revoke r1, r3 from root;") + tk.MustExec("drop role r1;") +} + +func TestUserWithSetNames(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test;") + tk.MustExec("set names gbk;") + + tk.MustExec("drop user if exists '\xd2\xbb'@'localhost';") + tk.MustExec("create user '\xd2\xbb'@'localhost' IDENTIFIED BY '\xd2\xbb';") + + result := tk.MustQuery("SELECT authentication_string FROM mysql.User WHERE User='\xd2\xbb' and Host='localhost';") + result.Check(testkit.Rows(auth.EncodePassword("一"))) + + tk.MustExec("ALTER USER '\xd2\xbb'@'localhost' IDENTIFIED BY '\xd2\xbb\xd2\xbb';") + result = tk.MustQuery("SELECT authentication_string FROM mysql.User WHERE User='\xd2\xbb' and Host='localhost';") + result.Check(testkit.Rows(auth.EncodePassword("一一"))) + + tk.MustExec("RENAME USER '\xd2\xbb'@'localhost' to '\xd2\xbb'") + + tk.MustExec("drop user '\xd2\xbb';") +} + +func TestStatementsCauseImplicitCommit(t *testing.T) { + // Test some of the implicit commit statements. + // See https://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test;") + tk.MustExec("create table ic (id int primary key)") + + cases := []string{ + "create table xx (id int)", + "create user 'xx'@'127.0.0.1'", + "grant SELECT on test.ic to 'xx'@'127.0.0.1'", + "flush privileges", + "analyze table ic", + } + for i, sql := range cases { + tk.MustExec("begin") + tk.MustExec("insert into ic values (?)", i) + tk.MustExec(sql) + tk.MustQuery("select * from ic where id = ?", i).Check(testkit.Rows(strconv.FormatInt(int64(i), 10))) + // Clean up data + tk.MustExec("delete from ic") + } +} + +func TestDo(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("do 1, @a:=1") + tk.MustQuery("select @a").Check(testkit.Rows("1")) + + tk.MustExec("use test") + tk.MustExec("create table t (i int)") + tk.MustExec("insert into t values (1)") + tk2 := testkit.NewTestKit(t, store) + tk2.MustExec("use test") + tk.MustQuery("select * from t").Check(testkit.Rows("1")) + tk.MustExec("do @a := (select * from t where i = 1)") + tk2.MustExec("insert into t values (2)") + tk.MustQuery("select * from t").Check(testkit.Rows("1", "2")) +} + +func TestDoWithAggFunc(t *testing.T) { + store := testkit.CreateMockStore(t) + + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("DO sum(1)") + tk.MustExec("DO avg(@e+@f)") + tk.MustExec("DO GROUP_CONCAT(NULLIF(ELT(1, @e), 2.0) ORDER BY 1)") +} + +func TestSetRoleAllCorner(t *testing.T) { + store := testkit.CreateMockStore(t) + // For user with no role, `SET ROLE ALL` should active + // a empty slice, rather than nil. + tk := testkit.NewTestKit(t, store) + tk.MustExec("create user set_role_all") + se, err := session.CreateSession4Test(store) + require.NoError(t, err) + defer se.Close() + require.NoError(t, se.Auth(&auth.UserIdentity{Username: "set_role_all", Hostname: "localhost"}, nil, nil, nil)) + ctx := context.Background() + _, err = se.Execute(ctx, `set role all`) + require.NoError(t, err) + _, err = se.Execute(ctx, `select current_role`) + require.NoError(t, err) +} + +func TestCreateRole(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("create user testCreateRole;") + tk.MustExec("grant CREATE USER on *.* to testCreateRole;") + se, err := session.CreateSession4Test(store) + require.NoError(t, err) + defer se.Close() + require.NoError(t, se.Auth(&auth.UserIdentity{Username: "testCreateRole", Hostname: "localhost"}, nil, nil, nil)) + + ctx := context.Background() + _, err = se.Execute(ctx, `create role test_create_role;`) + require.NoError(t, err) + tk.MustExec("revoke CREATE USER on *.* from testCreateRole;") + tk.MustExec("drop role test_create_role;") + tk.MustExec("grant CREATE ROLE on *.* to testCreateRole;") + _, err = se.Execute(ctx, `create role test_create_role;`) + require.NoError(t, err) + tk.MustExec("drop role test_create_role;") + _, err = se.Execute(ctx, `create user test_create_role;`) + require.Error(t, err) + tk.MustExec("drop user testCreateRole;") +} + +func TestDropRole(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("create user testCreateRole;") + tk.MustExec("create user test_create_role;") + tk.MustExec("grant CREATE USER on *.* to testCreateRole;") + se, err := session.CreateSession4Test(store) + require.NoError(t, err) + defer se.Close() + require.NoError(t, se.Auth(&auth.UserIdentity{Username: "testCreateRole", Hostname: "localhost"}, nil, nil, nil)) + + ctx := context.Background() + _, err = se.Execute(ctx, `drop role test_create_role;`) + require.NoError(t, err) + tk.MustExec("revoke CREATE USER on *.* from testCreateRole;") + tk.MustExec("create role test_create_role;") + tk.MustExec("grant DROP ROLE on *.* to testCreateRole;") + _, err = se.Execute(ctx, `drop role test_create_role;`) + require.NoError(t, err) + tk.MustExec("create user test_create_role;") + _, err = se.Execute(ctx, `drop user test_create_role;`) + require.Error(t, err) + tk.MustExec("drop user testCreateRole;") + tk.MustExec("drop user test_create_role;") +} + +func TestTransaction(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("begin") + ctx := tk.Session() + require.True(t, inTxn(ctx)) + tk.MustExec("commit") + require.False(t, inTxn(ctx)) + tk.MustExec("begin") + require.True(t, inTxn(ctx)) + tk.MustExec("rollback") + require.False(t, inTxn(ctx)) + + // Test that begin implicitly commits previous transaction. + tk.MustExec("use test") + tk.MustExec("create table txn (a int)") + tk.MustExec("begin") + tk.MustExec("insert txn values (1)") + tk.MustExec("begin") + tk.MustExec("rollback") + tk.MustQuery("select * from txn").Check(testkit.Rows("1")) + + // Test that DDL implicitly commits previous transaction. + tk.MustExec("begin") + tk.MustExec("insert txn values (2)") + tk.MustExec("create table txn2 (a int)") + tk.MustExec("rollback") + tk.MustQuery("select * from txn").Check(testkit.Rows("1", "2")) +} + +func inTxn(ctx sessionctx.Context) bool { + return (ctx.GetSessionVars().Status & mysql.ServerStatusInTrans) > 0 +} + +func TestIssue33144(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + //Create role + tk.MustExec("create role 'r1' ;") + + sessionVars := tk.Session().GetSessionVars() + sessionVars.User = &auth.UserIdentity{Username: "root", Hostname: "localhost", AuthUsername: "root", AuthHostname: "%"} + + //Grant role to current_user() + tk.MustExec("grant 'r1' to current_user();") + //Revoke role from current_user() + tk.MustExec("revoke 'r1' from current_user();") + + //Grant role to current_user(),current_user() + tk.MustExec("grant 'r1' to current_user(),current_user();") + //Revoke role from current_user(),current_user() + tk.MustExec("revoke 'r1' from current_user(),current_user();") + + //Drop role + tk.MustExec("drop role 'r1' ;") +} + +func TestRole(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + // Make sure user test not in mysql.User. + result := tk.MustQuery(`SELECT authentication_string FROM mysql.User WHERE User="test" and Host="localhost"`) + result.Check(nil) + + // Test for DROP ROLE. + createRoleSQL := `CREATE ROLE 'test'@'localhost';` + tk.MustExec(createRoleSQL) + // Make sure user test in mysql.User. + result = tk.MustQuery(`SELECT authentication_string FROM mysql.User WHERE User="test" and Host="localhost"`) + result.Check(testkit.Rows(auth.EncodePassword(""))) + // Insert relation into mysql.role_edges + tk.MustExec("insert into mysql.role_edges (FROM_HOST,FROM_USER,TO_HOST,TO_USER) values ('localhost','test','%','root')") + tk.MustExec("insert into mysql.role_edges (FROM_HOST,FROM_USER,TO_HOST,TO_USER) values ('localhost','test1','localhost','test1')") + // Insert relation into mysql.default_roles + tk.MustExec("insert into mysql.default_roles (HOST,USER,DEFAULT_ROLE_HOST,DEFAULT_ROLE_USER) values ('%','root','localhost','test')") + tk.MustExec("insert into mysql.default_roles (HOST,USER,DEFAULT_ROLE_HOST,DEFAULT_ROLE_USER) values ('localhost','test','%','test1')") + + dropUserSQL := `DROP ROLE IF EXISTS 'test'@'localhost' ;` + err := tk.ExecToErr(dropUserSQL) + require.NoError(t, err) + + result = tk.MustQuery(`SELECT authentication_string FROM mysql.User WHERE User="test" and Host="localhost"`) + result.Check(nil) + result = tk.MustQuery(`SELECT * FROM mysql.role_edges WHERE TO_USER="test" and TO_HOST="localhost"`) + result.Check(nil) + result = tk.MustQuery(`SELECT * FROM mysql.role_edges WHERE FROM_USER="test" and FROM_HOST="localhost"`) + result.Check(nil) + result = tk.MustQuery(`SELECT * FROM mysql.default_roles WHERE USER="test" and HOST="localhost"`) + result.Check(nil) + result = tk.MustQuery(`SELECT * FROM mysql.default_roles WHERE DEFAULT_ROLE_USER="test" and DEFAULT_ROLE_HOST="localhost"`) + result.Check(nil) + + // Test for GRANT ROLE + createRoleSQL = `CREATE ROLE 'r_1'@'localhost', 'r_2'@'localhost', 'r_3'@'localhost';` + tk.MustExec(createRoleSQL) + grantRoleSQL := `GRANT 'r_1'@'localhost' TO 'r_2'@'localhost';` + tk.MustExec(grantRoleSQL) + result = tk.MustQuery(`SELECT TO_USER FROM mysql.role_edges WHERE FROM_USER="r_1" and FROM_HOST="localhost"`) + result.Check(testkit.Rows("r_2")) + + grantRoleSQL = `GRANT 'r_1'@'localhost' TO 'r_3'@'localhost', 'r_4'@'localhost';` + err = tk.ExecToErr(grantRoleSQL) + require.Error(t, err) + + // Test grant role for current_user(); + sessionVars := tk.Session().GetSessionVars() + originUser := sessionVars.User + sessionVars.User = &auth.UserIdentity{Username: "root", Hostname: "localhost", AuthUsername: "root", AuthHostname: "%"} + tk.MustExec("grant 'r_1'@'localhost' to current_user();") + tk.MustExec("revoke 'r_1'@'localhost' from 'root'@'%';") + sessionVars.User = originUser + + result = tk.MustQuery(`SELECT FROM_USER FROM mysql.role_edges WHERE TO_USER="r_3" and TO_HOST="localhost"`) + result.Check(nil) + + dropRoleSQL := `DROP ROLE IF EXISTS 'r_1'@'localhost' ;` + tk.MustExec(dropRoleSQL) + dropRoleSQL = `DROP ROLE IF EXISTS 'r_2'@'localhost' ;` + tk.MustExec(dropRoleSQL) + dropRoleSQL = `DROP ROLE IF EXISTS 'r_3'@'localhost' ;` + tk.MustExec(dropRoleSQL) + + // Test for revoke role + createRoleSQL = `CREATE ROLE 'test'@'localhost', r_1, r_2;` + tk.MustExec(createRoleSQL) + tk.MustExec("insert into mysql.role_edges (FROM_HOST,FROM_USER,TO_HOST,TO_USER) values ('localhost','test','%','root')") + tk.MustExec("insert into mysql.role_edges (FROM_HOST,FROM_USER,TO_HOST,TO_USER) values ('%','r_1','%','root')") + tk.MustExec("insert into mysql.role_edges (FROM_HOST,FROM_USER,TO_HOST,TO_USER) values ('%','r_2','%','root')") + tk.MustExec("flush privileges") + tk.MustExec("SET DEFAULT ROLE r_1, r_2 TO root") + err = tk.ExecToErr("revoke test@localhost, r_1 from root;") + require.NoError(t, err) + err = tk.ExecToErr("revoke `r_2`@`%` from root, u_2;") + require.Error(t, err) + err = tk.ExecToErr("revoke `r_2`@`%` from root;") + require.NoError(t, err) + err = tk.ExecToErr("revoke `r_1`@`%` from root;") + require.NoError(t, err) + result = tk.MustQuery(`SELECT * FROM mysql.default_roles WHERE DEFAULT_ROLE_USER="test" and DEFAULT_ROLE_HOST="localhost"`) + result.Check(nil) + result = tk.MustQuery(`SELECT * FROM mysql.default_roles WHERE USER="root" and HOST="%"`) + result.Check(nil) + dropRoleSQL = `DROP ROLE 'test'@'localhost', r_1, r_2;` + tk.MustExec(dropRoleSQL) + + ctx := tk.Session().(sessionctx.Context) + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "test1", Hostname: "localhost"} + require.NotNil(t, tk.ExecToErr("SET ROLE role1, role2")) + tk.MustExec("SET ROLE ALL") + tk.MustExec("SET ROLE ALL EXCEPT role1, role2") + tk.MustExec("SET ROLE DEFAULT") + tk.MustExec("SET ROLE NONE") +} + +func TestRoleAdmin(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("CREATE USER 'testRoleAdmin';") + tk.MustExec("CREATE ROLE 'targetRole';") + + // Create a new session. + se, err := session.CreateSession4Test(store) + require.NoError(t, err) + defer se.Close() + require.NoError(t, se.Auth(&auth.UserIdentity{Username: "testRoleAdmin", Hostname: "localhost"}, nil, nil, nil)) + + ctx := context.Background() + _, err = se.Execute(ctx, "GRANT `targetRole` TO `testRoleAdmin`;") + require.Error(t, err) + + tk.MustExec("GRANT SUPER ON *.* TO `testRoleAdmin`;") + _, err = se.Execute(ctx, "GRANT `targetRole` TO `testRoleAdmin`;") + require.NoError(t, err) + _, err = se.Execute(ctx, "REVOKE `targetRole` FROM `testRoleAdmin`;") + require.NoError(t, err) + + tk.MustExec("DROP USER 'testRoleAdmin';") + tk.MustExec("DROP ROLE 'targetRole';") +} + +func TestDefaultRole(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + createRoleSQL := `CREATE ROLE r_1, r_2, r_3, u_1;` + tk.MustExec(createRoleSQL) + + tk.MustExec("insert into mysql.role_edges (FROM_HOST,FROM_USER,TO_HOST,TO_USER) values ('%','r_1','%','u_1')") + tk.MustExec("insert into mysql.role_edges (FROM_HOST,FROM_USER,TO_HOST,TO_USER) values ('%','r_2','%','u_1')") + + tk.MustExec("flush privileges;") + + setRoleSQL := `SET DEFAULT ROLE r_3 TO u_1;` + err := tk.ExecToErr(setRoleSQL) + require.Error(t, err) + + setRoleSQL = `SET DEFAULT ROLE r_1 TO u_1000;` + err = tk.ExecToErr(setRoleSQL) + require.Error(t, err) + + setRoleSQL = `SET DEFAULT ROLE r_1, r_3 TO u_1;` + err = tk.ExecToErr(setRoleSQL) + require.Error(t, err) + + setRoleSQL = `SET DEFAULT ROLE r_1 TO u_1;` + err = tk.ExecToErr(setRoleSQL) + require.NoError(t, err) + result := tk.MustQuery(`SELECT DEFAULT_ROLE_USER FROM mysql.default_roles WHERE USER="u_1"`) + result.Check(testkit.Rows("r_1")) + setRoleSQL = `SET DEFAULT ROLE r_2 TO u_1;` + err = tk.ExecToErr(setRoleSQL) + require.NoError(t, err) + result = tk.MustQuery(`SELECT DEFAULT_ROLE_USER FROM mysql.default_roles WHERE USER="u_1"`) + result.Check(testkit.Rows("r_2")) + + setRoleSQL = `SET DEFAULT ROLE ALL TO u_1;` + err = tk.ExecToErr(setRoleSQL) + require.NoError(t, err) + result = tk.MustQuery(`SELECT DEFAULT_ROLE_USER FROM mysql.default_roles WHERE USER="u_1"`) + result.Check(testkit.Rows("r_1", "r_2")) + + setRoleSQL = `SET DEFAULT ROLE NONE TO u_1;` + err = tk.ExecToErr(setRoleSQL) + require.NoError(t, err) + result = tk.MustQuery(`SELECT DEFAULT_ROLE_USER FROM mysql.default_roles WHERE USER="u_1"`) + result.Check(nil) + + dropRoleSQL := `DROP USER r_1, r_2, r_3, u_1;` + tk.MustExec(dropRoleSQL) +} + +func TestSetDefaultRoleAll(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("create user test_all;") + se, err := session.CreateSession4Test(store) + require.NoError(t, err) + defer se.Close() + require.NoError(t, se.Auth(&auth.UserIdentity{Username: "test_all", Hostname: "localhost"}, nil, nil, nil)) + + ctx := context.Background() + _, err = se.Execute(ctx, "set default role all to test_all;") + require.NoError(t, err) +} + +func TestUser(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + // Make sure user test not in mysql.User. + result := tk.MustQuery(`SELECT authentication_string FROM mysql.User WHERE User="test" and Host="localhost"`) + result.Check(nil) + // Create user test. + createUserSQL := `CREATE USER 'test'@'localhost' IDENTIFIED BY '123';` + tk.MustExec(createUserSQL) + // Make sure user test in mysql.User. + result = tk.MustQuery(`SELECT authentication_string FROM mysql.User WHERE User="test" and Host="localhost"`) + result.Check(testkit.Rows(auth.EncodePassword("123"))) + // Create duplicate user with IfNotExists will be success. + createUserSQL = `CREATE USER IF NOT EXISTS 'test'@'localhost' IDENTIFIED BY '123';` + tk.MustExec(createUserSQL) + + // Create duplicate user without IfNotExists will cause error. + createUserSQL = `CREATE USER 'test'@'localhost' IDENTIFIED BY '123';` + tk.MustGetErrCode(createUserSQL, mysql.ErrCannotUser) + createUserSQL = `CREATE USER IF NOT EXISTS 'test'@'localhost' IDENTIFIED BY '123';` + tk.MustExec(createUserSQL) + tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Note|3163|User 'test'@'localhost' already exists.")) + dropUserSQL := `DROP USER IF EXISTS 'test'@'localhost' ;` + tk.MustExec(dropUserSQL) + // Create user test. + createUserSQL = `CREATE USER 'test1'@'localhost';` + tk.MustExec(createUserSQL) + // Make sure user test in mysql.User. + result = tk.MustQuery(`SELECT authentication_string FROM mysql.User WHERE User="test1" and Host="localhost"`) + result.Check(testkit.Rows(auth.EncodePassword(""))) + dropUserSQL = `DROP USER IF EXISTS 'test1'@'localhost' ;` + tk.MustExec(dropUserSQL) + + // Test create/alter user with `tidb_auth_token` + tk.MustExec(`CREATE USER token_user IDENTIFIED WITH 'tidb_auth_token' REQUIRE token_issuer 'issuer-abc'`) + tk.MustQuery(`SELECT plugin, token_issuer FROM mysql.user WHERE user = 'token_user'`).Check(testkit.Rows("tidb_auth_token issuer-abc")) + tk.MustExec(`ALTER USER token_user REQUIRE token_issuer 'issuer-123'`) + tk.MustQuery(`SELECT plugin, token_issuer FROM mysql.user WHERE user = 'token_user'`).Check(testkit.Rows("tidb_auth_token issuer-123")) + tk.MustExec(`ALTER USER token_user IDENTIFIED WITH 'tidb_auth_token'`) + tk.MustExec(`CREATE USER token_user1 IDENTIFIED WITH 'tidb_auth_token'`) + tk.MustQuery(`show warnings`).Check(testkit.RowsWithSep("|", "Warning|1105|TOKEN_ISSUER is needed for 'tidb_auth_token' user, please use 'alter user' to declare it")) + tk.MustExec(`CREATE USER temp_user IDENTIFIED WITH 'mysql_native_password' BY '1234' REQUIRE token_issuer 'issuer-abc'`) + tk.MustQuery(`show warnings`).Check(testkit.RowsWithSep("|", "Warning|1105|TOKEN_ISSUER is not needed for 'mysql_native_password' user")) + tk.MustExec(`ALTER USER temp_user IDENTIFIED WITH 'tidb_auth_token' REQUIRE token_issuer 'issuer-abc'`) + tk.MustQuery(`show warnings`).Check(testkit.Rows()) + tk.MustExec(`ALTER USER temp_user IDENTIFIED WITH 'mysql_native_password' REQUIRE token_issuer 'issuer-abc'`) + tk.MustQuery(`show warnings`).Check(testkit.RowsWithSep("|", "Warning|1105|TOKEN_ISSUER is not needed for the auth plugin")) + tk.MustExec(`ALTER USER temp_user IDENTIFIED WITH 'tidb_auth_token'`) + tk.MustQuery(`show warnings`).Check(testkit.RowsWithSep("|", "Warning|1105|Auth plugin 'tidb_auth_plugin' needs TOKEN_ISSUER")) + tk.MustExec(`ALTER USER token_user REQUIRE SSL`) + tk.MustQuery(`show warnings`).Check(testkit.Rows()) + tk.MustExec(`ALTER USER token_user IDENTIFIED WITH 'mysql_native_password' BY '1234'`) + tk.MustQuery(`show warnings`).Check(testkit.Rows()) + tk.MustExec(`ALTER USER token_user IDENTIFIED WITH 'tidb_auth_token' REQUIRE token_issuer 'issuer-abc'`) + tk.MustQuery(`show warnings`).Check(testkit.Rows()) + + // Test alter user. + createUserSQL = `CREATE USER 'test1'@'localhost' IDENTIFIED BY '123', 'test2'@'localhost' IDENTIFIED BY '123', 'test3'@'localhost' IDENTIFIED BY '123', 'test4'@'localhost' IDENTIFIED BY '123';` + tk.MustExec(createUserSQL) + alterUserSQL := `ALTER USER 'test1'@'localhost' IDENTIFIED BY '111';` + tk.MustExec(alterUserSQL) + result = tk.MustQuery(`SELECT authentication_string FROM mysql.User WHERE User="test1" and Host="localhost"`) + result.Check(testkit.Rows(auth.EncodePassword("111"))) + alterUserSQL = `ALTER USER 'test_not_exist'@'localhost' IDENTIFIED BY '111';` + tk.MustGetErrCode(alterUserSQL, mysql.ErrCannotUser) + alterUserSQL = `ALTER USER 'test1'@'localhost' IDENTIFIED BY '222', 'test_not_exist'@'localhost' IDENTIFIED BY '111';` + tk.MustGetErrCode(alterUserSQL, mysql.ErrCannotUser) + result = tk.MustQuery(`SELECT authentication_string FROM mysql.User WHERE User="test1" and Host="localhost"`) + result.Check(testkit.Rows(auth.EncodePassword("111"))) + alterUserSQL = `ALTER USER 'test4'@'localhost' IDENTIFIED WITH 'auth_socket';` + tk.MustExec(alterUserSQL) + result = tk.MustQuery(`SELECT plugin FROM mysql.User WHERE User="test4" and Host="localhost"`) + result.Check(testkit.Rows("auth_socket")) + + alterUserSQL = `ALTER USER IF EXISTS 'test2'@'localhost' IDENTIFIED BY '222', 'test_not_exist'@'localhost' IDENTIFIED BY '1';` + tk.MustExec(alterUserSQL) + tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Note|3162|User 'test_not_exist'@'localhost' does not exist.")) + result = tk.MustQuery(`SELECT authentication_string FROM mysql.User WHERE User="test2" and Host="localhost"`) + result.Check(testkit.Rows(auth.EncodePassword("222"))) + alterUserSQL = `ALTER USER IF EXISTS'test_not_exist'@'localhost' IDENTIFIED BY '1', 'test3'@'localhost' IDENTIFIED BY '333';` + tk.MustExec(alterUserSQL) + tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Note|3162|User 'test_not_exist'@'localhost' does not exist.")) + result = tk.MustQuery(`SELECT authentication_string FROM mysql.User WHERE User="test3" and Host="localhost"`) + result.Check(testkit.Rows(auth.EncodePassword("333"))) + + // Test alter user user(). + alterUserSQL = `ALTER USER USER() IDENTIFIED BY '1';` + err := tk.ExecToErr(alterUserSQL) + require.Truef(t, terror.ErrorEqual(err, errors.New("Session user is empty")), "err %v", err) + sess, err := session.CreateSession4Test(store) + require.NoError(t, err) + tk.SetSession(sess) + ctx := tk.Session().(sessionctx.Context) + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "test1", Hostname: "localhost", AuthHostname: "localhost"} + tk.MustExec(alterUserSQL) + result = tk.MustQuery(`SELECT authentication_string FROM mysql.User WHERE User="test1" and Host="localhost"`) + result.Check(testkit.Rows(auth.EncodePassword("1"))) + dropUserSQL = `DROP USER 'test1'@'localhost', 'test2'@'localhost', 'test3'@'localhost';` + tk.MustExec(dropUserSQL) + + // Test drop user if exists. + createUserSQL = `CREATE USER 'test1'@'localhost', 'test3'@'localhost';` + tk.MustExec(createUserSQL) + dropUserSQL = `DROP USER IF EXISTS 'test1'@'localhost', 'test2'@'localhost', 'test3'@'localhost' ;` + tk.MustExec(dropUserSQL) + tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Note|3162|User test2@localhost does not exist.")) + + // Test negative cases without IF EXISTS. + createUserSQL = `CREATE USER 'test1'@'localhost', 'test3'@'localhost';` + tk.MustExec(createUserSQL) + dropUserSQL = `DROP USER 'test1'@'localhost', 'test2'@'localhost', 'test3'@'localhost';` + tk.MustGetErrCode(dropUserSQL, mysql.ErrCannotUser) + dropUserSQL = `DROP USER 'test3'@'localhost';` + tk.MustExec(dropUserSQL) + dropUserSQL = `DROP USER 'test1'@'localhost';` + tk.MustExec(dropUserSQL) + // Test positive cases without IF EXISTS. + createUserSQL = `CREATE USER 'test1'@'localhost', 'test3'@'localhost';` + tk.MustExec(createUserSQL) + dropUserSQL = `DROP USER 'test1'@'localhost', 'test3'@'localhost';` + tk.MustExec(dropUserSQL) + + // Test 'identified by password' + createUserSQL = `CREATE USER 'test1'@'localhost' identified by password 'xxx';` + err = tk.ExecToErr(createUserSQL) + require.Truef(t, terror.ErrorEqual(exeerrors.ErrPasswordFormat, err), "err %v", err) + createUserSQL = `CREATE USER 'test1'@'localhost' identified by password '*3D56A309CD04FA2EEF181462E59011F075C89548';` + tk.MustExec(createUserSQL) + dropUserSQL = `DROP USER 'test1'@'localhost';` + tk.MustExec(dropUserSQL) + + // Test drop user meet error + err = tk.ExecToErr(dropUserSQL) + require.Truef(t, terror.ErrorEqual(err, exeerrors.ErrCannotUser.GenWithStackByArgs("DROP USER", "")), "err %v", err) + + createUserSQL = `CREATE USER 'test1'@'localhost'` + tk.MustExec(createUserSQL) + createUserSQL = `CREATE USER 'test2'@'localhost'` + tk.MustExec(createUserSQL) + + dropUserSQL = `DROP USER 'test1'@'localhost', 'test2'@'localhost', 'test3'@'localhost';` + err = tk.ExecToErr(dropUserSQL) + require.Truef(t, terror.ErrorEqual(err, exeerrors.ErrCannotUser.GenWithStackByArgs("DROP USER", "")), "err %v", err) + + // Close issue #17639 + dropUserSQL = `DROP USER if exists test3@'%'` + tk.MustExec(dropUserSQL) + createUserSQL = `create user test3@'%' IDENTIFIED WITH 'mysql_native_password' AS '*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9';` + tk.MustExec(createUserSQL) + querySQL := `select authentication_string from mysql.user where user="test3" ;` + tk.MustQuery(querySQL).Check(testkit.Rows("*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9")) + alterUserSQL = `alter user test3@'%' IDENTIFIED WITH 'mysql_native_password' AS '*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9';` + tk.MustExec(alterUserSQL) + tk.MustQuery(querySQL).Check(testkit.Rows("*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9")) + + createUserSQL = `create user userA@LOCALHOST;` + tk.MustExec(createUserSQL) + querySQL = `select user,host from mysql.user where user = 'userA';` + tk.MustQuery(querySQL).Check(testkit.Rows("userA localhost")) + + createUserSQL = `create user userB@DEMO.com;` + tk.MustExec(createUserSQL) + querySQL = `select user,host from mysql.user where user = 'userB';` + tk.MustQuery(querySQL).Check(testkit.Rows("userB demo.com")) + + createUserSQL = `create user userC@localhost;` + tk.MustExec(createUserSQL) + renameUserSQL := `rename user 'userC'@'localhost' to 'userD'@'Demo.com';` + tk.MustExec(renameUserSQL) + querySQL = `select user,host from mysql.user where user = 'userD';` + tk.MustQuery(querySQL).Check(testkit.Rows("userD demo.com")) + + createUserSQL = `create user foo@localhost identified with 'foobar';` + err = tk.ExecToErr(createUserSQL) + require.Truef(t, terror.ErrorEqual(err, exeerrors.ErrPluginIsNotLoaded), "err %v", err) + + tk.MustExec(`create user joan;`) + tk.MustExec(`create user sally;`) + tk.MustExec(`create role engineering;`) + tk.MustExec(`create role consultants;`) + tk.MustExec(`create role qa;`) + tk.MustExec(`grant engineering to joan;`) + tk.MustExec(`grant engineering to sally;`) + tk.MustExec(`grant engineering, consultants to joan, sally;`) + tk.MustExec(`grant qa to consultants;`) + tk.MustExec("CREATE ROLE `engineering`@`US`;") + tk.MustExec("create role `engineering`@`INDIA`;") + tk.MustExec("grant `engineering`@`US` TO `engineering`@`INDIA`;") + + tk.MustQuery("select user,host from mysql.user where user='engineering' and host = 'india'"). + Check(testkit.Rows("engineering india")) + tk.MustQuery("select user,host from mysql.user where user='engineering' and host = 'us'"). + Check(testkit.Rows("engineering us")) + + tk.MustExec("drop role engineering@INDIA;") + tk.MustExec("drop role engineering@US;") + + tk.MustQuery("select user from mysql.user where user='engineering' and host = 'india'").Check(testkit.Rows()) + tk.MustQuery("select user from mysql.user where user='engineering' and host = 'us'").Check(testkit.Rows()) +} + +func TestSetPwd(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + createUserSQL := `CREATE USER 'testpwd'@'localhost' IDENTIFIED BY '';` + tk.MustExec(createUserSQL) + result := tk.MustQuery(`SELECT authentication_string FROM mysql.User WHERE User="testpwd" and Host="localhost"`) + result.Check(testkit.Rows("")) + + // set password for + tk.MustExec(`SET PASSWORD FOR 'testpwd'@'localhost' = 'password';`) + result = tk.MustQuery(`SELECT authentication_string FROM mysql.User WHERE User="testpwd" and Host="localhost"`) + result.Check(testkit.Rows(auth.EncodePassword("password"))) + + tk.MustExec(`CREATE USER 'testpwdsock'@'localhost' IDENTIFIED WITH 'auth_socket';`) + tk.MustExec(`SET PASSWORD FOR 'testpwdsock'@'localhost' = 'password';`) + result = tk.MustQuery("show warnings") + result.Check(testkit.Rows("Note 1699 SET PASSWORD has no significance for user 'testpwdsock'@'localhost' as authentication plugin does not support it.")) + + // set password + setPwdSQL := `SET PASSWORD = 'pwd'` + // Session user is empty. + err := tk.ExecToErr(setPwdSQL) + require.Error(t, err) + sess, err := session.CreateSession4Test(store) + require.NoError(t, err) + tk.SetSession(sess) + ctx := tk.Session().(sessionctx.Context) + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "testpwd1", Hostname: "localhost", AuthUsername: "testpwd1", AuthHostname: "localhost"} + // Session user doesn't exist. + err = tk.ExecToErr(setPwdSQL) + require.Truef(t, terror.ErrorEqual(err, exeerrors.ErrPasswordNoMatch), "err %v", err) + // normal + ctx.GetSessionVars().User = &auth.UserIdentity{Username: "testpwd", Hostname: "localhost", AuthUsername: "testpwd", AuthHostname: "localhost"} + tk.MustExec(setPwdSQL) + result = tk.MustQuery(`SELECT authentication_string FROM mysql.User WHERE User="testpwd" and Host="localhost"`) + result.Check(testkit.Rows(auth.EncodePassword("pwd"))) +} + +func TestFlushPrivileges(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + tk.MustExec(`CREATE USER 'testflush'@'localhost' IDENTIFIED BY '';`) + tk.MustExec(`UPDATE mysql.User SET Select_priv='Y' WHERE User="testflush" and Host="localhost"`) + + // Create a new session. + se, err := session.CreateSession4Test(store) + require.NoError(t, err) + defer se.Close() + require.NoError(t, se.Auth(&auth.UserIdentity{Username: "testflush", Hostname: "localhost"}, nil, nil, nil)) + + ctx := context.Background() + // Before flush. + _, err = se.Execute(ctx, `SELECT authentication_string FROM mysql.User WHERE User="testflush" and Host="localhost"`) + require.Error(t, err) + + tk.MustExec("FLUSH PRIVILEGES") + + // After flush. + _, err = se.Execute(ctx, `SELECT authentication_string FROM mysql.User WHERE User="testflush" and Host="localhost"`) + require.NoError(t, err) +} + +func TestFlushPrivilegesPanic(t *testing.T) { + defer view.Stop() + // Run in a separate suite because this test need to set SkipGrantTable config. + store, err := mockstore.NewMockStore() + require.NoError(t, err) + defer func() { + err := store.Close() + require.NoError(t, err) + }() + + defer config.RestoreFunc()() + config.UpdateGlobal(func(conf *config.Config) { + conf.Security.SkipGrantTable = true + }) + + dom, err := session.BootstrapSession(store) + require.NoError(t, err) + defer dom.Close() + + tk := testkit.NewTestKit(t, store) + tk.MustExec("FLUSH PRIVILEGES") +} + +func TestDropPartitionStats(t *testing.T) { + store, dom := testkit.CreateMockStoreAndDomain(t) + // Use the testSerialSuite to fix the unstable test + tk := testkit.NewTestKit(t, store) + tk.MustExec(`create database if not exists test_drop_gstats`) + tk.MustExec("use test_drop_gstats") + tk.MustExec("drop table if exists test_drop_gstats;") + tk.MustExec(`create table test_drop_gstats ( + a int, + key(a) +) +partition by range (a) ( + partition p0 values less than (10), + partition p1 values less than (20), + partition global values less than (30) +)`) + tk.MustExec("set @@tidb_analyze_version = 2") + tk.MustExec("set @@tidb_partition_prune_mode='dynamic'") + tk.MustExec("insert into test_drop_gstats values (1), (5), (11), (15), (21), (25)") + require.Nil(t, dom.StatsHandle().DumpStatsDeltaToKV(handle.DumpAll)) + + checkPartitionStats := func(names ...string) { + rs := tk.MustQuery("show stats_meta").Rows() + require.Equal(t, len(names), len(rs)) + for i := range names { + require.Equal(t, names[i], rs[i][2].(string)) + } + } + + tk.MustExec("analyze table test_drop_gstats") + checkPartitionStats("global", "p0", "p1", "global") + + tk.MustExec("drop stats test_drop_gstats partition p0") + tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Warning|1681|'DROP STATS ... PARTITION ...' is deprecated and will be removed in a future release.")) + checkPartitionStats("global", "p1", "global") + + err := tk.ExecToErr("drop stats test_drop_gstats partition abcde") + require.Error(t, err) + require.Equal(t, "can not found the specified partition name abcde in the table definition", err.Error()) + + tk.MustExec("drop stats test_drop_gstats partition global") + checkPartitionStats("global", "p1") + + tk.MustExec("drop stats test_drop_gstats global") + tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Warning|1287|'DROP STATS ... GLOBAL' is deprecated and will be removed in a future release. Please use DROP STATS ... instead")) + checkPartitionStats("p1") + + tk.MustExec("analyze table test_drop_gstats") + checkPartitionStats("global", "p0", "p1", "global") + + tk.MustExec("drop stats test_drop_gstats partition p0, p1, global") + checkPartitionStats("global") + + tk.MustExec("analyze table test_drop_gstats") + checkPartitionStats("global", "p0", "p1", "global") + + tk.MustExec("drop stats test_drop_gstats") + checkPartitionStats() +} + +func TestDropStats(t *testing.T) { + store, dom := testkit.CreateMockStoreAndDomain(t) + testKit := testkit.NewTestKit(t, store) + testKit.MustExec("use test") + testKit.MustExec("create table t (c1 int, c2 int)") + is := dom.InfoSchema() + tbl, err := is.TableByName(model.NewCIStr("test"), model.NewCIStr("t")) + require.NoError(t, err) + tableInfo := tbl.Meta() + h := dom.StatsHandle() + h.Clear() + testKit.MustExec("analyze table t") + statsTbl := h.GetTableStats(tableInfo) + require.False(t, statsTbl.Pseudo) + + testKit.MustExec("drop stats t") + require.Nil(t, h.Update(is)) + statsTbl = h.GetTableStats(tableInfo) + require.True(t, statsTbl.Pseudo) + + testKit.MustExec("analyze table t") + statsTbl = h.GetTableStats(tableInfo) + require.False(t, statsTbl.Pseudo) + + h.SetLease(1) + testKit.MustExec("drop stats t") + require.Nil(t, h.Update(is)) + statsTbl = h.GetTableStats(tableInfo) + require.True(t, statsTbl.Pseudo) + h.SetLease(0) +} + +func TestDropStatsForMultipleTable(t *testing.T) { + store, dom := testkit.CreateMockStoreAndDomain(t) + testKit := testkit.NewTestKit(t, store) + testKit.MustExec("use test") + testKit.MustExec("create table t1 (c1 int, c2 int)") + testKit.MustExec("create table t2 (c1 int, c2 int)") + + is := dom.InfoSchema() + tbl1, err := is.TableByName(model.NewCIStr("test"), model.NewCIStr("t1")) + require.NoError(t, err) + tableInfo1 := tbl1.Meta() + + tbl2, err := is.TableByName(model.NewCIStr("test"), model.NewCIStr("t2")) + require.NoError(t, err) + tableInfo2 := tbl2.Meta() + + h := dom.StatsHandle() + h.Clear() + testKit.MustExec("analyze table t1, t2") + statsTbl1 := h.GetTableStats(tableInfo1) + require.False(t, statsTbl1.Pseudo) + statsTbl2 := h.GetTableStats(tableInfo2) + require.False(t, statsTbl2.Pseudo) + + testKit.MustExec("drop stats t1, t2") + require.Nil(t, h.Update(is)) + statsTbl1 = h.GetTableStats(tableInfo1) + require.True(t, statsTbl1.Pseudo) + statsTbl2 = h.GetTableStats(tableInfo2) + require.True(t, statsTbl2.Pseudo) + + testKit.MustExec("analyze table t1, t2") + statsTbl1 = h.GetTableStats(tableInfo1) + require.False(t, statsTbl1.Pseudo) + statsTbl2 = h.GetTableStats(tableInfo2) + require.False(t, statsTbl2.Pseudo) + + h.SetLease(1) + testKit.MustExec("drop stats t1, t2") + require.Nil(t, h.Update(is)) + statsTbl1 = h.GetTableStats(tableInfo1) + require.True(t, statsTbl1.Pseudo) + statsTbl2 = h.GetTableStats(tableInfo2) + require.True(t, statsTbl2.Pseudo) + h.SetLease(0) +} + +func TestCreateUserWithLDAP(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + tk.MustExec("CREATE USER 'bob'@'localhost' IDENTIFIED WITH authentication_ldap_simple AS 'uid=bob,ou=People,dc=example,dc=com'") + tk.MustQuery("SELECT Host, User, authentication_string, plugin FROM mysql.User WHERE User = 'bob'").Check(testkit.Rows("localhost bob uid=bob,ou=People,dc=example,dc=com authentication_ldap_simple")) + + tk.MustExec("CREATE USER 'bob2'@'localhost' IDENTIFIED WITH authentication_ldap_sasl AS 'uid=bob2,ou=People,dc=example,dc=com'") + tk.MustQuery("SELECT Host, User, authentication_string, plugin FROM mysql.User WHERE User = 'bob2'").Check(testkit.Rows("localhost bob2 uid=bob2,ou=People,dc=example,dc=com authentication_ldap_sasl")) +} + +func TestAlterUserWithLDAP(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + // case 1: alter from a LDAP user to LDAP user + tk.MustExec("CREATE USER 'bob'@'localhost' IDENTIFIED WITH authentication_ldap_simple AS 'uid=bob,ou=People,dc=example,dc=com'") + tk.MustQuery("SELECT Host, User, authentication_string, plugin FROM mysql.User WHERE User = 'bob'").Check(testkit.Rows("localhost bob uid=bob,ou=People,dc=example,dc=com authentication_ldap_simple")) + tk.MustExec("ALTER USER 'bob'@'localhost' IDENTIFIED WITH authentication_ldap_sasl AS 'uid=bob,ou=Manager,dc=example,dc=com'") + tk.MustQuery("SELECT Host, User, authentication_string, plugin FROM mysql.User WHERE User = 'bob'").Check(testkit.Rows("localhost bob uid=bob,ou=Manager,dc=example,dc=com authentication_ldap_sasl")) + + // case 2: should ignore the password history + tk.MustExec("ALTER USER 'bob'@'localhost' PASSWORD HISTORY 5\n") + tk.MustExec("ALTER USER 'bob'@'localhost' IDENTIFIED WITH authentication_ldap_sasl AS 'uid=bob,ou=People,dc=example,dc=com'") + tk.MustExec("ALTER USER 'bob'@'localhost' IDENTIFIED WITH authentication_ldap_sasl AS 'uid=bob,ou=Manager,dc=example,dc=com'") + tk.MustExec("ALTER USER 'bob'@'localhost' IDENTIFIED WITH authentication_ldap_sasl AS 'uid=bob,ou=People,dc=example,dc=com'") + tk.MustExec("ALTER USER 'bob'@'localhost' IDENTIFIED WITH authentication_ldap_sasl AS 'uid=bob,ou=Manager,dc=example,dc=com'") +} + +func TestIssue44098(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + tk.MustExec("set global validate_password.enable = 1") + tk.MustExec("create user u1 identified with 'tidb_auth_token'") + tk.MustExec("create user u2 identified with 'auth_socket'") + tk.MustExec("create user u3 identified with 'authentication_ldap_simple'") + tk.MustExec("create user u4 identified with 'authentication_ldap_sasl'") + tk.MustGetErrCode("create user u5 identified with 'mysql_native_password'", errno.ErrNotValidPassword) + tk.MustGetErrCode("create user u5 identified with 'caching_sha2_password'", errno.ErrNotValidPassword) + tk.MustGetErrCode("create user u5 identified with 'tidb_sm3_password'", errno.ErrNotValidPassword) + tk.MustGetErrCode("create user u5 identified with 'mysql_clear_password'", errno.ErrPluginIsNotLoaded) + tk.MustGetErrCode("create user u5 identified with 'tidb_session_token'", errno.ErrPluginIsNotLoaded) +} diff --git a/privilege/privileges/cache.go b/privilege/privileges/cache.go index fb6be9d0362e8..5e34bf8724318 100644 --- a/privilege/privileges/cache.go +++ b/privilege/privileges/cache.go @@ -1163,8 +1163,13 @@ func (p *MySQLPrivilege) DBIsVisible(user, host, db string) bool { return false } +<<<<<<< HEAD func (p *MySQLPrivilege) showGrants(user, host string, roles []*auth.RoleIdentity) []string { var gs []string +======= +func (p *MySQLPrivilege) showGrants(ctx sessionctx.Context, user, host string, roles []*auth.RoleIdentity) []string { + var gs []string //nolint: prealloc +>>>>>>> 6271d1c80b2 (executor: Escape object in show grants output (#46976)) var sortFromIdx int var hasGlobalGrant = false // Some privileges may granted from role inheritance. @@ -1244,7 +1249,10 @@ func (p *MySQLPrivilege) showGrants(user, host string, roles []*auth.RoleIdentit } } } + + sqlMode := ctx.GetSessionVars().SQLMode for dbName, priv := range dbPrivTable { + dbName = stringutil.Escape(dbName, sqlMode) g := dbPrivToString(priv) if len(g) > 0 { var s string @@ -1267,7 +1275,7 @@ func (p *MySQLPrivilege) showGrants(user, host string, roles []*auth.RoleIdentit sortFromIdx = len(gs) tablePrivTable := make(map[string]mysql.PrivilegeType) for _, record := range p.TablesPriv { - recordKey := record.DB + "." + record.TableName + recordKey := stringutil.Escape(record.DB, sqlMode) + "." + stringutil.Escape(record.TableName, sqlMode) if user == record.User && host == record.Host { if _, ok := dbPrivTable[record.DB]; ok { tablePrivTable[recordKey] |= record.TablePriv @@ -1311,9 +1319,9 @@ func (p *MySQLPrivilege) showGrants(user, host string, roles []*auth.RoleIdentit columnPrivTable := make(map[string]privOnColumns) for i := range p.ColumnsPriv { record := p.ColumnsPriv[i] - if !collectColumnGrant(&record, user, host, columnPrivTable) { + if !collectColumnGrant(&record, user, host, columnPrivTable, sqlMode) { for _, r := range allRoles { - collectColumnGrant(&record, r.Username, r.Hostname, columnPrivTable) + collectColumnGrant(&record, r.Username, r.Hostname, columnPrivTable, sqlMode) } } } @@ -1424,9 +1432,10 @@ func privOnColumnsToString(p privOnColumns) string { return buf.String() } -func collectColumnGrant(record *columnsPrivRecord, user, host string, columnPrivTable map[string]privOnColumns) bool { +func collectColumnGrant(record *columnsPrivRecord, user, host string, columnPrivTable map[string]privOnColumns, sqlMode mysql.SQLMode) bool { if record.baseRecord.match(user, host) { - recordKey := record.DB + "." + record.TableName + recordKey := stringutil.Escape(record.DB, sqlMode) + "." + stringutil.Escape(record.TableName, sqlMode) + privColumns, ok := columnPrivTable[recordKey] if !ok { privColumns = make(map[mysql.PrivilegeType]columnStrs) diff --git a/privilege/privileges/privileges.go b/privilege/privileges/privileges.go index 7b499bdd64100..6902acbd65248 100644 --- a/privilege/privileges/privileges.go +++ b/privilege/privileges/privileges.go @@ -577,7 +577,7 @@ func (p *UserPrivileges) ShowGrants(ctx sessionctx.Context, user *auth.UserIdent u = user.AuthUsername h = user.AuthHostname } - grants = mysqlPrivilege.showGrants(u, h, roles) + grants = mysqlPrivilege.showGrants(ctx, u, h, roles) if len(grants) == 0 { err = ErrNonexistingGrant.GenWithStackByArgs(u, h) } diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index a3b8cd9b9869e..32baf85839c82 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -270,14 +270,20 @@ func TestShowGrants(t *testing.T) { require.NoError(t, err) require.Len(t, gs, 2) expected := []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, +<<<<<<< HEAD `GRANT SELECT ON test.* TO 'show'@'localhost'`} require.True(t, testutil.CompareUnorderedStringSlice(gs, expected)) +======= + "GRANT SELECT ON `test`.* TO 'show'@'localhost'"} + require.True(t, testutil.CompareUnorderedStringSlice(gs, expected), fmt.Sprintf("gs: %v, expected: %v", gs, expected)) +>>>>>>> 6271d1c80b2 (executor: Escape object in show grants output (#46976)) mustExec(t, se, `GRANT Index ON test1.* TO 'show'@'localhost';`) gs, err = pc.ShowGrants(se, &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) require.NoError(t, err) require.Len(t, gs, 3) expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, +<<<<<<< HEAD `GRANT SELECT ON test.* TO 'show'@'localhost'`, `GRANT INDEX ON test1.* TO 'show'@'localhost'`} require.True(t, testutil.CompareUnorderedStringSlice(gs, expected)) @@ -290,6 +296,30 @@ func TestShowGrants(t *testing.T) { `GRANT SELECT ON test.* TO 'show'@'localhost'`, `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`} require.True(t, testutil.CompareUnorderedStringSlice(gs, expected)) +======= + "GRANT SELECT ON `test`.* TO 'show'@'localhost'", + "GRANT INDEX ON `test1`.* TO 'show'@'localhost'"} + require.True(t, testutil.CompareUnorderedStringSlice(gs, expected), fmt.Sprintf("gs: %v, expected: %v", gs, expected)) + + // Add another db privilege to the same db and test again. + tk.MustExec(`GRANT Delete ON test1.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(tk.Session(), &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + require.NoError(t, err) + require.Len(t, gs, 3) + expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + "GRANT SELECT ON `test`.* TO 'show'@'localhost'", + "GRANT DELETE,INDEX ON `test1`.* TO 'show'@'localhost'"} + require.True(t, testutil.CompareUnorderedStringSlice(gs, expected), fmt.Sprintf("gs: %v, expected: %v", gs, expected)) + + tk.MustExec(`GRANT ALL ON test1.* TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(tk.Session(), &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + require.NoError(t, err) + require.Len(t, gs, 3) + expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + "GRANT SELECT ON `test`.* TO 'show'@'localhost'", + "GRANT ALL PRIVILEGES ON `test1`.* TO 'show'@'localhost'"} + require.True(t, testutil.CompareUnorderedStringSlice(gs, expected), fmt.Sprintf("gs: %v, expected: %v", gs, expected)) +>>>>>>> 6271d1c80b2 (executor: Escape object in show grants output (#46976)) // Add table scope privileges mustExec(t, se, `GRANT Update ON test.test TO 'show'@'localhost';`) @@ -297,10 +327,37 @@ func TestShowGrants(t *testing.T) { require.NoError(t, err) require.Len(t, gs, 4) expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, +<<<<<<< HEAD `GRANT SELECT ON test.* TO 'show'@'localhost'`, `GRANT ALL PRIVILEGES ON test1.* TO 'show'@'localhost'`, `GRANT UPDATE ON test.test TO 'show'@'localhost'`} require.True(t, testutil.CompareUnorderedStringSlice(gs, expected)) +======= + "GRANT SELECT ON `test`.* TO 'show'@'localhost'", + "GRANT ALL PRIVILEGES ON `test1`.* TO 'show'@'localhost'", + "GRANT UPDATE ON `test`.`test` TO 'show'@'localhost'"} + require.True(t, testutil.CompareUnorderedStringSlice(gs, expected), fmt.Sprintf("gs: %v, expected: %v", gs, expected)) + + // Revoke the db privilege of `test` and test again. See issue #30855. + tk.MustExec(`REVOKE SELECT ON test.* FROM 'show'@'localhost'`) + gs, err = pc.ShowGrants(tk.Session(), &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + require.NoError(t, err) + require.Len(t, gs, 3) + expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + "GRANT ALL PRIVILEGES ON `test1`.* TO 'show'@'localhost'", + "GRANT UPDATE ON `test`.`test` TO 'show'@'localhost'"} + require.True(t, testutil.CompareUnorderedStringSlice(gs, expected), fmt.Sprintf("gs: %v, expected: %v", gs, expected)) + + // Add another table privilege and test again. + tk.MustExec(`GRANT Select ON test.test TO 'show'@'localhost';`) + gs, err = pc.ShowGrants(tk.Session(), &auth.UserIdentity{Username: "show", Hostname: "localhost"}, nil) + require.NoError(t, err) + require.Len(t, gs, 3) + expected = []string{`GRANT ALL PRIVILEGES ON *.* TO 'show'@'localhost'`, + "GRANT ALL PRIVILEGES ON `test1`.* TO 'show'@'localhost'", + "GRANT SELECT,UPDATE ON `test`.`test` TO 'show'@'localhost'"} + require.True(t, testutil.CompareUnorderedStringSlice(gs, expected), fmt.Sprintf("gs: %v, expected: %v", gs, expected)) +>>>>>>> 6271d1c80b2 (executor: Escape object in show grants output (#46976)) // Expected behavior: Usage still exists after revoking all privileges mustExec(t, se, `REVOKE ALL PRIVILEGES ON *.* FROM 'show'@'localhost'`) @@ -405,7 +462,7 @@ func TestShowColumnGrants(t *testing.T) { pc := privilege.GetPrivilegeManager(se) gs, err := pc.ShowGrants(se, &auth.UserIdentity{Username: "column", Hostname: "%"}, nil) require.NoError(t, err) - require.Equal(t, "GRANT USAGE ON *.* TO 'column'@'%' GRANT SELECT(a), INSERT(c), UPDATE(a, b) ON test.column_table TO 'column'@'%'", strings.Join(gs, " ")) + require.Equal(t, "GRANT USAGE ON *.* TO 'column'@'%' GRANT SELECT(a), INSERT(c), UPDATE(a, b) ON `test`.`column_table` TO 'column'@'%'", strings.Join(gs, " ")) } func TestDropTablePrivileges(t *testing.T) { @@ -2226,7 +2283,7 @@ func TestGrantOptionAndRevoke(t *testing.T) { Hostname: "localhost", }, nil, nil) - tk.MustQuery(`SHOW GRANTS FOR u1`).Check(testkit.Rows("GRANT SELECT ON *.* TO 'u1'@'%' WITH GRANT OPTION", "GRANT UPDATE,DELETE ON db.* TO 'u1'@'%'")) + tk.MustQuery(`SHOW GRANTS FOR u1`).Check(testkit.Rows("GRANT SELECT ON *.* TO 'u1'@'%' WITH GRANT OPTION", "GRANT UPDATE,DELETE ON `db`.* TO 'u1'@'%'")) tk.MustExec("GRANT SELECT ON d1.* to u2") tk.MustExec("GRANT SELECT ON d2.* to u2 WITH GRANT OPTION") @@ -2234,18 +2291,18 @@ func TestGrantOptionAndRevoke(t *testing.T) { tk.MustExec("GRANT SELECT ON d4.* to u2") tk.MustExec("GRANT SELECT ON d5.* to u2") tk.MustQuery(`SHOW GRANTS FOR u2;`).Sort().Check(testkit.Rows( - "GRANT SELECT ON d1.* TO 'u2'@'%'", - "GRANT SELECT ON d2.* TO 'u2'@'%' WITH GRANT OPTION", - "GRANT SELECT ON d3.* TO 'u2'@'%'", - "GRANT SELECT ON d4.* TO 'u2'@'%'", - "GRANT SELECT ON d5.* TO 'u2'@'%'", + "GRANT SELECT ON `d1`.* TO 'u2'@'%'", + "GRANT SELECT ON `d2`.* TO 'u2'@'%' WITH GRANT OPTION", + "GRANT SELECT ON `d3`.* TO 'u2'@'%'", + "GRANT SELECT ON `d4`.* TO 'u2'@'%'", + "GRANT SELECT ON `d5`.* TO 'u2'@'%'", "GRANT USAGE ON *.* TO 'u2'@'%'", )) tk.MustExec("grant all on hchwang.* to u3 with grant option") - tk.MustQuery(`SHOW GRANTS FOR u3;`).Check(testkit.Rows("GRANT USAGE ON *.* TO 'u3'@'%'", "GRANT ALL PRIVILEGES ON hchwang.* TO 'u3'@'%' WITH GRANT OPTION")) + tk.MustQuery(`SHOW GRANTS FOR u3;`).Check(testkit.Rows("GRANT USAGE ON *.* TO 'u3'@'%'", "GRANT ALL PRIVILEGES ON `hchwang`.* TO 'u3'@'%' WITH GRANT OPTION")) tk.MustExec("revoke all on hchwang.* from u3") - tk.MustQuery(`SHOW GRANTS FOR u3;`).Check(testkit.Rows("GRANT USAGE ON *.* TO 'u3'@'%'", "GRANT USAGE ON hchwang.* TO 'u3'@'%' WITH GRANT OPTION")) + tk.MustQuery(`SHOW GRANTS FOR u3;`).Check(testkit.Rows("GRANT USAGE ON *.* TO 'u3'@'%'", "GRANT USAGE ON `hchwang`.* TO 'u3'@'%' WITH GRANT OPTION")) // Same again but with column privileges. @@ -2255,8 +2312,8 @@ func TestGrantOptionAndRevoke(t *testing.T) { tk.MustExec("revoke all on test.testgrant from u3") tk.MustQuery(`SHOW GRANTS FOR u3`).Sort().Check(testkit.Rows( "GRANT USAGE ON *.* TO 'u3'@'%'", - "GRANT USAGE ON hchwang.* TO 'u3'@'%' WITH GRANT OPTION", - "GRANT USAGE ON test.testgrant TO 'u3'@'%' WITH GRANT OPTION", + "GRANT USAGE ON `hchwang`.* TO 'u3'@'%' WITH GRANT OPTION", + "GRANT USAGE ON `test`.`testgrant` TO 'u3'@'%' WITH GRANT OPTION", )) } func setUpTest(t *testing.T, store kv.Storage, dbName string) { @@ -2306,8 +2363,8 @@ func TestGrantReferences(t *testing.T) { }, nil, nil) tk.MustQuery("SHOW GRANTS FOR referencesUser").Check(testkit.Rows( `GRANT REFERENCES ON *.* TO 'referencesUser'@'%'`, - `GRANT REFERENCES ON reftestdb.* TO 'referencesUser'@'%'`, - `GRANT REFERENCES ON reftestdb.reftest TO 'referencesUser'@'%'`)) + "GRANT REFERENCES ON `reftestdb`.* TO 'referencesUser'@'%'", + "GRANT REFERENCES ON `reftestdb`.`reftest` TO 'referencesUser'@'%'")) tk.MustExec("DROP USER referencesUser") tk.MustExec("DROP SCHEMA reftestdb") } @@ -2462,7 +2519,7 @@ func TestGrantLockTables(t *testing.T) { }, nil, nil) tk.MustQuery("SHOW GRANTS FOR lock_tables_user").Check(testkit.Rows( `GRANT LOCK TABLES ON *.* TO 'lock_tables_user'@'%'`, - `GRANT LOCK TABLES ON lock_tables_db.* TO 'lock_tables_user'@'%'`)) + "GRANT LOCK TABLES ON `lock_tables_db`.* TO 'lock_tables_user'@'%'")) tk.MustExec("DROP USER lock_tables_user") tk.MustExec("DROP DATABASE lock_tables_db") } @@ -2504,16 +2561,28 @@ func TestShowGrantsForCurrentUserUsingRole(t *testing.T) { tk.MustQuery("SHOW GRANTS FOR current_user() USING otherrole;").Check(testkit.Rows( "GRANT USAGE ON *.* TO 'joe'@'%'", +<<<<<<< HEAD "GRANT SELECT ON test.* TO 'joe'@'%'", "GRANT UPDATE ON role.* TO 'joe'@'%'", "GRANT DELETE ON mysql.user TO 'joe'@'%'", +======= + "GRANT SELECT ON `test`.* TO 'joe'@'%'", + "GRANT UPDATE ON `role`.* TO 'joe'@'%'", + "GRANT SELECT,DELETE ON `mysql`.`user` TO 'joe'@'%'", +>>>>>>> 6271d1c80b2 (executor: Escape object in show grants output (#46976)) "GRANT 'admins'@'%', 'engineering'@'%', 'otherrole'@'%' TO 'joe'@'%'", )) tk.MustQuery("SHOW GRANTS FOR joe USING otherrole;").Check(testkit.Rows( "GRANT USAGE ON *.* TO 'joe'@'%'", +<<<<<<< HEAD "GRANT SELECT ON test.* TO 'joe'@'%'", "GRANT UPDATE ON role.* TO 'joe'@'%'", "GRANT DELETE ON mysql.user TO 'joe'@'%'", +======= + "GRANT SELECT ON `test`.* TO 'joe'@'%'", + "GRANT UPDATE ON `role`.* TO 'joe'@'%'", + "GRANT SELECT,DELETE ON `mysql`.`user` TO 'joe'@'%'", +>>>>>>> 6271d1c80b2 (executor: Escape object in show grants output (#46976)) "GRANT 'admins'@'%', 'engineering'@'%', 'otherrole'@'%' TO 'joe'@'%'", )) @@ -2650,7 +2719,7 @@ func TestGrantCreateTmpTables(t *testing.T) { }, nil, nil) tk.MustQuery("SHOW GRANTS FOR u1").Check(testkit.Rows( `GRANT CREATE TEMPORARY TABLES ON *.* TO 'u1'@'%'`, - `GRANT CREATE TEMPORARY TABLES ON create_tmp_table_db.* TO 'u1'@'%'`)) + "GRANT CREATE TEMPORARY TABLES ON `create_tmp_table_db`.* TO 'u1'@'%'")) tk.MustExec("DROP USER u1") tk.MustExec("DROP DATABASE create_tmp_table_db") } @@ -2846,7 +2915,7 @@ func TestGrantEvent(t *testing.T) { }, nil, nil) tk.MustQuery("SHOW GRANTS FOR u1").Check(testkit.Rows( `GRANT EVENT ON *.* TO 'u1'@'%'`, - `GRANT EVENT ON event_db.* TO 'u1'@'%'`)) + "GRANT EVENT ON `event_db`.* TO 'u1'@'%'")) tk.MustExec("DROP USER u1") tk.MustExec("DROP DATABASE event_db") } @@ -2871,7 +2940,7 @@ func TestGrantRoutine(t *testing.T) { }, nil, nil) tk.MustQuery("SHOW GRANTS FOR u1").Check(testkit.Rows( `GRANT CREATE ROUTINE,ALTER ROUTINE ON *.* TO 'u1'@'%'`, - `GRANT CREATE ROUTINE,ALTER ROUTINE ON routine_db.* TO 'u1'@'%'`)) + "GRANT CREATE ROUTINE,ALTER ROUTINE ON `routine_db`.* TO 'u1'@'%'")) tk.MustExec("DROP USER u1") tk.MustExec("DROP DATABASE routine_db") } @@ -2986,3 +3055,223 @@ func TestIssue37488(t *testing.T) { tk.MustQuery("select current_user()").Check(testkit.Rows("dba_test@192.168.%")) tk.MustExec("DROP TABLE IF EXISTS a;") // succ } +<<<<<<< HEAD +======= + +func TestCheckPasswordExpired(t *testing.T) { + sessionVars := variable.NewSessionVars(nil) + sessionVars.GlobalVarsAccessor = variable.NewMockGlobalAccessor4Tests() + record := privileges.NewUserRecord("%", "root") + userPrivilege := privileges.NewUserPrivileges(privileges.NewHandle(), nil) + + record.PasswordExpired = true + _, err := userPrivilege.CheckPasswordExpired(sessionVars, &record) + require.ErrorContains(t, err, "Your password has expired. To log in you must change it using a client that supports expired passwords") + + record.PasswordExpired = false + err = sessionVars.GlobalVarsAccessor.SetGlobalSysVar(context.Background(), variable.DefaultPasswordLifetime, "2") + require.NoError(t, err) + // use default_password_lifetime + record.PasswordLifeTime = -1 + record.PasswordLastChanged = time.Now().AddDate(0, 0, -2) + time.Sleep(time.Second) + _, err = userPrivilege.CheckPasswordExpired(sessionVars, &record) + require.ErrorContains(t, err, "Your password has expired. To log in you must change it using a client that supports expired passwords") + record.PasswordLastChanged = time.Now().AddDate(0, 0, -1) + _, err = userPrivilege.CheckPasswordExpired(sessionVars, &record) + require.NoError(t, err) + + // never expire + record.PasswordLifeTime = 0 + record.PasswordLastChanged = time.Now().AddDate(0, 0, -10) + _, err = userPrivilege.CheckPasswordExpired(sessionVars, &record) + require.NoError(t, err) + + // expire with the specified time + record.PasswordLifeTime = 3 + record.PasswordLastChanged = time.Now().AddDate(0, 0, -3) + time.Sleep(time.Second) + _, err = userPrivilege.CheckPasswordExpired(sessionVars, &record) + require.ErrorContains(t, err, "Your password has expired. To log in you must change it using a client that supports expired passwords") + record.PasswordLastChanged = time.Now().AddDate(0, 0, -2) + _, err = userPrivilege.CheckPasswordExpired(sessionVars, &record) + require.NoError(t, err) +} + +func TestPasswordExpireWithoutSandBoxMode(t *testing.T) { + store := createStoreAndPrepareDB(t) + rootTk := testkit.NewTestKit(t, store) + rootTk.MustExec(`CREATE USER 'testuser'@'localhost' PASSWORD EXPIRE`) + + // PASSWORD EXPIRE + user := &auth.UserIdentity{Username: "testuser", Hostname: "localhost"} + tk := testkit.NewTestKit(t, store) + err := tk.Session().Auth(user, nil, nil, nil) + require.ErrorContains(t, err, "Your password has expired") + + // PASSWORD EXPIRE NEVER + rootTk.MustExec(`ALTER USER 'testuser'@'localhost' IDENTIFIED BY '' PASSWORD EXPIRE NEVER`) + err = tk.Session().Auth(user, nil, nil, nil) + require.NoError(t, err) + + // PASSWORD EXPIRE INTERVAL N DAY + rootTk.MustExec(`ALTER USER 'testuser'@'localhost' PASSWORD EXPIRE INTERVAL 2 DAY`) + rootTk.MustExec(`UPDATE mysql.user SET password_last_changed = (now() - INTERVAL 1 DAY) where user='testuser'`) + rootTk.MustExec(`FLUSH PRIVILEGES`) + err = tk.Session().Auth(user, nil, nil, nil) + require.NoError(t, err) + rootTk.MustExec(`UPDATE mysql.user SET password_last_changed = (now() - INTERVAL 2 DAY) where user='testuser'`) + rootTk.MustExec(`FLUSH PRIVILEGES`) + time.Sleep(2 * time.Second) + err = tk.Session().Auth(user, nil, nil, nil) + require.ErrorContains(t, err, "Your password has expired") + + // PASSWORD EXPIRE DEFAULT + rootTk.MustExec(`ALTER USER 'testuser'@'localhost' PASSWORD EXPIRE DEFAULT`) + rootTk.MustExec(`SET GLOBAL default_password_lifetime = 2`) + err = tk.Session().Auth(user, nil, nil, nil) + require.ErrorContains(t, err, "Your password has expired") + rootTk.MustExec(`SET GLOBAL default_password_lifetime = 3`) + err = tk.Session().Auth(user, nil, nil, nil) + require.NoError(t, err) +} + +func TestPasswordExpireWithSandBoxMode(t *testing.T) { + store := createStoreAndPrepareDB(t) + rootTk := testkit.NewTestKit(t, store) + rootTk.MustExec(`CREATE USER 'testuser'@'localhost' PASSWORD EXPIRE`) + variable.IsSandBoxModeEnabled.Store(true) + + // PASSWORD EXPIRE + user := &auth.UserIdentity{Username: "testuser", Hostname: "localhost"} + tk := testkit.NewTestKit(t, store) + err := tk.Session().Auth(user, nil, nil, nil) + require.NoError(t, err) + require.True(t, tk.Session().InSandBoxMode()) + tk.Session().DisableSandBoxMode() + + // PASSWORD EXPIRE NEVER + rootTk.MustExec(`ALTER USER 'testuser'@'localhost' IDENTIFIED BY '' PASSWORD EXPIRE NEVER`) + err = tk.Session().Auth(user, nil, nil, nil) + require.NoError(t, err) + require.False(t, tk.Session().InSandBoxMode()) + + // PASSWORD EXPIRE INTERVAL N DAY + rootTk.MustExec(`ALTER USER 'testuser'@'localhost' PASSWORD EXPIRE INTERVAL 2 DAY`) + rootTk.MustExec(`UPDATE mysql.user SET password_last_changed = (now() - INTERVAL 1 DAY) where user='testuser'`) + rootTk.MustExec(`FLUSH PRIVILEGES`) + err = tk.Session().Auth(user, nil, nil, nil) + require.NoError(t, err) + require.False(t, tk.Session().InSandBoxMode()) + rootTk.MustExec(`UPDATE mysql.user SET password_last_changed = (now() - INTERVAL 2 DAY) where user='testuser'`) + rootTk.MustExec(`FLUSH PRIVILEGES`) + time.Sleep(2 * time.Second) + err = tk.Session().Auth(user, nil, nil, nil) + require.NoError(t, err) + require.True(t, tk.Session().InSandBoxMode()) + tk.Session().DisableSandBoxMode() + + // PASSWORD EXPIRE DEFAULT + rootTk.MustExec(`ALTER USER 'testuser'@'localhost' PASSWORD EXPIRE DEFAULT`) + rootTk.MustExec(`SET GLOBAL default_password_lifetime = 2`) + err = tk.Session().Auth(user, nil, nil, nil) + require.NoError(t, err) + require.True(t, tk.Session().InSandBoxMode()) + tk.Session().DisableSandBoxMode() + rootTk.MustExec(`SET GLOBAL default_password_lifetime = 3`) + err = tk.Session().Auth(user, nil, nil, nil) + require.NoError(t, err) + require.False(t, tk.Session().InSandBoxMode()) +} + +func TestVerificationInfoWithSessionTokenPlugin(t *testing.T) { + // prepare signing certs + tempDir := t.TempDir() + certPath := filepath.Join(tempDir, "test1_cert.pem") + keyPath := filepath.Join(tempDir, "test1_key.pem") + err := util.CreateCertificates(certPath, keyPath, 4096, x509.RSA, x509.UnknownSignatureAlgorithm) + require.NoError(t, err) + sessionstates.SetKeyPath(keyPath) + sessionstates.SetCertPath(certPath) + + // prepare user + store := createStoreAndPrepareDB(t) + rootTk := testkit.NewTestKit(t, store) + rootTk.MustExec(`CREATE USER 'testuser'@'localhost' PASSWORD EXPIRE`) + // prepare session token + token, err := sessionstates.CreateSessionToken("testuser") + require.NoError(t, err) + tokenBytes, err := json.Marshal(token) + require.NoError(t, err) + + // Test password expiration without sandbox. + user := &auth.UserIdentity{Username: "testuser", Hostname: "localhost", AuthPlugin: mysql.AuthTiDBSessionToken} + tk := testkit.NewTestKit(t, store) + err = tk.Session().Auth(user, tokenBytes, nil, nil) + require.NoError(t, err) + require.False(t, tk.Session().InSandBoxMode()) + + // Test password expiration with sandbox. + variable.IsSandBoxModeEnabled.Store(true) + err = tk.Session().Auth(user, tokenBytes, nil, nil) + require.NoError(t, err) + require.False(t, tk.Session().InSandBoxMode()) + + // Enable resource group. + variable.EnableResourceControl.Store(true) + err = tk.Session().Auth(user, tokenBytes, nil, nil) + require.NoError(t, err) + require.Equal(t, "default", tk.Session().GetSessionVars().ResourceGroupName) + + // Non-default resource group. + rootTk.MustExec("CREATE RESOURCE GROUP rg1 RU_PER_SEC = 999") + rootTk.MustExec(`ALTER USER 'testuser'@'localhost' RESOURCE GROUP rg1`) + err = tk.Session().Auth(user, tokenBytes, nil, nil) + require.NoError(t, err) + require.Equal(t, "rg1", tk.Session().GetSessionVars().ResourceGroupName) + + // Wrong token + err = tk.Session().Auth(user, nil, nil, nil) + require.ErrorContains(t, err, "Access denied") +} + +func TestNilHandleInConnectionVerification(t *testing.T) { + config.GetGlobalConfig().Security.SkipGrantTable = true + privileges.SkipWithGrant = true + defer func() { + config.GetGlobalConfig().Security.SkipGrantTable = false + privileges.SkipWithGrant = false + }() + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + require.NoError(t, tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: `%`}, nil, nil, nil)) +} + +func testShowGrantsSQLMode(t *testing.T, tk *testkit.TestKit, expected []string) { + pc := privilege.GetPrivilegeManager(tk.Session()) + gs, err := pc.ShowGrants(tk.Session(), &auth.UserIdentity{Username: "show_sql_mode", Hostname: "localhost"}, nil) + require.NoError(t, err) + require.Len(t, gs, 2) + require.True(t, testutil.CompareUnorderedStringSlice(gs, expected), fmt.Sprintf("gs: %v, expected: %v", gs, expected)) +} + +func TestShowGrantsSQLMode(t *testing.T) { + store := createStoreAndPrepareDB(t) + + tk := testkit.NewTestKit(t, store) + ctx, _ := tk.Session().(sessionctx.Context) + tk.MustExec(`CREATE USER 'show_sql_mode'@'localhost' identified by '123';`) + tk.MustExec(`GRANT Select ON test.* TO 'show_sql_mode'@'localhost';`) + + testShowGrantsSQLMode(t, tk, []string{ + "GRANT USAGE ON *.* TO 'show_sql_mode'@'localhost'", + "GRANT SELECT ON `test`.* TO 'show_sql_mode'@'localhost'", + }) + + ctx.GetSessionVars().SQLMode = mysql.SetSQLMode(ctx.GetSessionVars().SQLMode, mysql.ModeANSIQuotes) + testShowGrantsSQLMode(t, tk, []string{ + "GRANT USAGE ON *.* TO 'show_sql_mode'@'localhost'", + "GRANT SELECT ON \"test\".* TO 'show_sql_mode'@'localhost'", + }) +} +>>>>>>> 6271d1c80b2 (executor: Escape object in show grants output (#46976)) diff --git a/server/tests/tidb_test.go b/server/tests/tidb_test.go new file mode 100644 index 0000000000000..984c58b4c152d --- /dev/null +++ b/server/tests/tidb_test.go @@ -0,0 +1,3124 @@ +// Copyright 2015 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 tests + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "database/sql" + "encoding/binary" + "encoding/pem" + "fmt" + "io" + "math/big" + "net" + "net/http" + "os" + "path/filepath" + "strings" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/go-sql-driver/mysql" + "github.com/pingcap/errors" + "github.com/pingcap/failpoint" + "github.com/pingcap/tidb/config" + ddlutil "github.com/pingcap/tidb/ddl/util" + "github.com/pingcap/tidb/domain" + "github.com/pingcap/tidb/extension" + "github.com/pingcap/tidb/kv" + "github.com/pingcap/tidb/parser" + "github.com/pingcap/tidb/parser/ast" + "github.com/pingcap/tidb/parser/auth" + tmysql "github.com/pingcap/tidb/parser/mysql" + server2 "github.com/pingcap/tidb/server" + "github.com/pingcap/tidb/server/internal/column" + "github.com/pingcap/tidb/server/internal/resultset" + "github.com/pingcap/tidb/server/internal/testserverclient" + "github.com/pingcap/tidb/server/internal/testutil" + util2 "github.com/pingcap/tidb/server/internal/util" + "github.com/pingcap/tidb/session" + "github.com/pingcap/tidb/sessionctx/variable" + "github.com/pingcap/tidb/store/mockstore" + "github.com/pingcap/tidb/store/mockstore/unistore" + "github.com/pingcap/tidb/testkit" + "github.com/pingcap/tidb/util" + "github.com/pingcap/tidb/util/cpuprofile" + "github.com/pingcap/tidb/util/plancodec" + "github.com/pingcap/tidb/util/resourcegrouptag" + "github.com/pingcap/tidb/util/topsql" + "github.com/pingcap/tidb/util/topsql/collector" + mockTopSQLTraceCPU "github.com/pingcap/tidb/util/topsql/collector/mock" + topsqlstate "github.com/pingcap/tidb/util/topsql/state" + "github.com/pingcap/tidb/util/topsql/stmtstats" + "github.com/stretchr/testify/require" + "github.com/tikv/client-go/v2/tikvrpc" + "go.opencensus.io/stats/view" +) + +type tidbTestSuite struct { + *testserverclient.TestServerClient + tidbdrv *server2.TiDBDriver + server *server2.Server + domain *domain.Domain + store kv.Storage +} + +func createTidbTestSuite(t *testing.T) *tidbTestSuite { + cfg := util2.NewTestConfig() + cfg.Port = 0 + cfg.Status.ReportStatus = true + cfg.Status.StatusPort = 0 + cfg.Status.RecordDBLabel = true + cfg.Performance.TCPKeepAlive = true + return createTidbTestSuiteWithCfg(t, cfg) +} + +func createTidbTestSuiteWithCfg(t *testing.T, cfg *config.Config) *tidbTestSuite { + ts := &tidbTestSuite{TestServerClient: testserverclient.NewTestServerClient()} + + // setup tidbTestSuite + var err error + ts.store, err = mockstore.NewMockStore() + session.DisableStats4Test() + require.NoError(t, err) + ts.domain, err = session.BootstrapSession(ts.store) + require.NoError(t, err) + ts.tidbdrv = server2.NewTiDBDriver(ts.store) + + server, err := server2.NewServer(cfg, ts.tidbdrv) + require.NoError(t, err) + ts.Port = testutil.GetPortFromTCPAddr(server.ListenAddr()) + ts.StatusPort = testutil.GetPortFromTCPAddr(server.StatusListenerAddr()) + ts.server = server + ts.server.SetDomain(ts.domain) + ts.domain.InfoSyncer().SetSessionManager(ts.server) + go func() { + err := ts.server.Run() + require.NoError(t, err) + }() + ts.WaitUntilServerOnline() + + t.Cleanup(func() { + if ts.domain != nil { + ts.domain.Close() + } + if ts.server != nil { + ts.server.Close() + } + if ts.store != nil { + require.NoError(t, ts.store.Close()) + } + view.Stop() + }) + return ts +} + +type tidbTestTopSQLSuite struct { + *tidbTestSuite +} + +func createTidbTestTopSQLSuite(t *testing.T) *tidbTestTopSQLSuite { + base := createTidbTestSuite(t) + + ts := &tidbTestTopSQLSuite{base} + + // Initialize global variable for top-sql test. + db, err := sql.Open("mysql", ts.GetDSN()) + require.NoError(t, err) + defer func() { + err := db.Close() + require.NoError(t, err) + }() + + dbt := testkit.NewDBTestKit(t, db) + topsqlstate.GlobalState.PrecisionSeconds.Store(1) + topsqlstate.GlobalState.ReportIntervalSeconds.Store(2) + dbt.MustExec("set @@global.tidb_top_sql_max_time_series_count=5;") + + require.NoError(t, cpuprofile.StartCPUProfiler()) + t.Cleanup(func() { + cpuprofile.StopCPUProfiler() + topsqlstate.GlobalState.PrecisionSeconds.Store(topsqlstate.DefTiDBTopSQLPrecisionSeconds) + topsqlstate.GlobalState.ReportIntervalSeconds.Store(topsqlstate.DefTiDBTopSQLReportIntervalSeconds) + view.Stop() + }) + return ts +} + +func TestRegression(t *testing.T) { + ts := createTidbTestSuite(t) + if testserverclient.Regression { + ts.RunTestRegression(t, nil, "Regression") + } +} + +func TestUint64(t *testing.T) { + ts := createTidbTestSuite(t) + ts.RunTestPrepareResultFieldType(t) +} + +func TestSpecialType(t *testing.T) { + ts := createTidbTestSuite(t) + ts.RunTestSpecialType(t) +} + +func TestPreparedString(t *testing.T) { + ts := createTidbTestSuite(t) + + ts.RunTestPreparedString(t) +} + +func TestPreparedTimestamp(t *testing.T) { + ts := createTidbTestSuite(t) + + ts.RunTestPreparedTimestamp(t) +} + +func TestConcurrentUpdate(t *testing.T) { + ts := createTidbTestSuite(t) + + ts.RunTestConcurrentUpdate(t) +} + +func TestErrorCode(t *testing.T) { + ts := createTidbTestSuite(t) + + ts.RunTestErrorCode(t) +} + +func TestAuth(t *testing.T) { + ts := createTidbTestSuite(t) + + ts.RunTestAuth(t) + ts.RunTestIssue3682(t) + ts.RunTestAccountLock(t) +} + +func TestIssues(t *testing.T) { + ts := createTidbTestSuite(t) + + ts.RunTestIssue3662(t) + ts.RunTestIssue3680(t) + ts.RunTestIssue22646(t) +} + +func TestDBNameEscape(t *testing.T) { + ts := createTidbTestSuite(t) + ts.RunTestDBNameEscape(t) +} + +func TestResultFieldTableIsNull(t *testing.T) { + ts := createTidbTestSuite(t) + + ts.RunTestResultFieldTableIsNull(t) +} + +func TestStatusAPI(t *testing.T) { + ts := createTidbTestSuite(t) + + ts.RunTestStatusAPI(t) +} + +func TestStatusPort(t *testing.T) { + ts := createTidbTestSuite(t) + + cfg := util2.NewTestConfig() + cfg.Port = 0 + cfg.Status.ReportStatus = true + cfg.Status.StatusPort = ts.StatusPort + cfg.Performance.TCPKeepAlive = true + + server, err := server2.NewServer(cfg, ts.tidbdrv) + require.Error(t, err) + require.Nil(t, server) +} + +func TestStatusAPIWithTLS(t *testing.T) { + ts := createTidbTestSuite(t) + + dir := t.TempDir() + + fileName := func(file string) string { + return filepath.Join(dir, file) + } + + caCert, caKey, err := generateCert(0, "TiDB CA 2", nil, nil, fileName("ca-key-2.pem"), fileName("ca-cert-2.pem")) + require.NoError(t, err) + _, _, err = generateCert(1, "tidb-server-2", caCert, caKey, fileName("server-key-2.pem"), fileName("server-cert-2.pem")) + require.NoError(t, err) + + cli := testserverclient.NewTestServerClient() + cli.StatusScheme = "https" + cfg := util2.NewTestConfig() + cfg.Port = cli.Port + cfg.Status.StatusPort = cli.StatusPort + cfg.Security.ClusterSSLCA = fileName("ca-cert-2.pem") + cfg.Security.ClusterSSLCert = fileName("server-cert-2.pem") + cfg.Security.ClusterSSLKey = fileName("server-key-2.pem") + server, err := server2.NewServer(cfg, ts.tidbdrv) + require.NoError(t, err) + cli.Port = testutil.GetPortFromTCPAddr(server.ListenAddr()) + cli.StatusPort = testutil.GetPortFromTCPAddr(server.StatusListenerAddr()) + go func() { + err := server.Run() + require.NoError(t, err) + }() + time.Sleep(time.Millisecond * 100) + + // https connection should work. + ts.RunTestStatusAPI(t) + + // but plain http connection should fail. + cli.StatusScheme = "http" + //nolint:bodyclose + _, err = cli.FetchStatus("/status") + require.Error(t, err) + + server.Close() +} + +func TestStatusAPIWithTLSCNCheck(t *testing.T) { + ts := createTidbTestSuite(t) + + dir := t.TempDir() + + caPath := filepath.Join(dir, "ca-cert-cn.pem") + serverKeyPath := filepath.Join(dir, "server-key-cn.pem") + serverCertPath := filepath.Join(dir, "server-cert-cn.pem") + client1KeyPath := filepath.Join(dir, "client-key-cn-check-a.pem") + client1CertPath := filepath.Join(dir, "client-cert-cn-check-a.pem") + client2KeyPath := filepath.Join(dir, "client-key-cn-check-b.pem") + client2CertPath := filepath.Join(dir, "client-cert-cn-check-b.pem") + + caCert, caKey, err := generateCert(0, "TiDB CA CN CHECK", nil, nil, filepath.Join(dir, "ca-key-cn.pem"), caPath) + require.NoError(t, err) + _, _, err = generateCert(1, "tidb-server-cn-check", caCert, caKey, serverKeyPath, serverCertPath) + require.NoError(t, err) + _, _, err = generateCert(2, "tidb-client-cn-check-a", caCert, caKey, client1KeyPath, client1CertPath, func(c *x509.Certificate) { + c.Subject.CommonName = "tidb-client-1" + }) + require.NoError(t, err) + _, _, err = generateCert(3, "tidb-client-cn-check-b", caCert, caKey, client2KeyPath, client2CertPath, func(c *x509.Certificate) { + c.Subject.CommonName = "tidb-client-2" + }) + require.NoError(t, err) + + cli := testserverclient.NewTestServerClient() + cli.StatusScheme = "https" + cfg := util2.NewTestConfig() + cfg.Port = cli.Port + cfg.Status.StatusPort = cli.StatusPort + cfg.Security.ClusterSSLCA = caPath + cfg.Security.ClusterSSLCert = serverCertPath + cfg.Security.ClusterSSLKey = serverKeyPath + cfg.Security.ClusterVerifyCN = []string{"tidb-client-2"} + server, err := server2.NewServer(cfg, ts.tidbdrv) + require.NoError(t, err) + + cli.Port = testutil.GetPortFromTCPAddr(server.ListenAddr()) + cli.StatusPort = testutil.GetPortFromTCPAddr(server.StatusListenerAddr()) + go func() { + err := server.Run() + require.NoError(t, err) + }() + defer server.Close() + time.Sleep(time.Millisecond * 100) + + hc := newTLSHttpClient(t, caPath, + client1CertPath, + client1KeyPath, + ) + //nolint:bodyclose + _, err = hc.Get(cli.StatusURL("/status")) + require.Error(t, err) + + hc = newTLSHttpClient(t, caPath, + client2CertPath, + client2KeyPath, + ) + resp, err := hc.Get(cli.StatusURL("/status")) + require.NoError(t, err) + require.Nil(t, resp.Body.Close()) +} + +func newTLSHttpClient(t *testing.T, caFile, certFile, keyFile string) *http.Client { + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + require.NoError(t, err) + caCert, err := os.ReadFile(caFile) + require.NoError(t, err) + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: caCertPool, + InsecureSkipVerify: true, + } + tlsConfig.BuildNameToCertificate() + return &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}} +} + +func TestMultiStatements(t *testing.T) { + ts := createTidbTestSuite(t) + + ts.RunFailedTestMultiStatements(t) + ts.RunTestMultiStatements(t) +} + +func TestSocketForwarding(t *testing.T) { + tempDir := t.TempDir() + socketFile := tempDir + "/tidbtest.sock" // Unix Socket does not work on Windows, so '/' should be OK + + ts := createTidbTestSuite(t) + + cli := testserverclient.NewTestServerClient() + cfg := util2.NewTestConfig() + cfg.Socket = socketFile + cfg.Port = cli.Port + os.Remove(cfg.Socket) + cfg.Status.ReportStatus = false + + server, err := server2.NewServer(cfg, ts.tidbdrv) + require.NoError(t, err) + server.SetDomain(ts.domain) + cli.Port = testutil.GetPortFromTCPAddr(server.ListenAddr()) + go func() { + err := server.Run() + require.NoError(t, err) + }() + time.Sleep(time.Millisecond * 100) + defer server.Close() + + cli.RunTestRegression(t, func(config *mysql.Config) { + config.User = "root" + config.Net = "unix" + config.Addr = socketFile + config.DBName = "test" + config.Params = map[string]string{"sql_mode": "'STRICT_ALL_TABLES'"} + }, "SocketRegression") +} + +func TestSocket(t *testing.T) { + tempDir := t.TempDir() + socketFile := tempDir + "/tidbtest.sock" // Unix Socket does not work on Windows, so '/' should be OK + + cfg := util2.NewTestConfig() + cfg.Socket = socketFile + cfg.Port = 0 + os.Remove(cfg.Socket) + cfg.Host = "" + cfg.Status.ReportStatus = false + + ts := createTidbTestSuite(t) + + server, err := server2.NewServer(cfg, ts.tidbdrv) + require.NoError(t, err) + server.SetDomain(ts.domain) + go func() { + err := server.Run() + require.NoError(t, err) + }() + time.Sleep(time.Millisecond * 100) + defer server.Close() + + confFunc := func(config *mysql.Config) { + config.User = "root" + config.Net = "unix" + config.Addr = socketFile + config.DBName = "test" + config.Params = map[string]string{"sql_mode": "STRICT_ALL_TABLES"} + } + // a fake server client, config is override, just used to run tests + cli := testserverclient.NewTestServerClient() + cli.WaitUntilCustomServerCanConnect(confFunc) + cli.RunTestRegression(t, confFunc, "SocketRegression") +} + +func TestSocketAndIp(t *testing.T) { + tempDir := t.TempDir() + socketFile := tempDir + "/tidbtest.sock" // Unix Socket does not work on Windows, so '/' should be OK + + cli := testserverclient.NewTestServerClient() + cfg := util2.NewTestConfig() + cfg.Socket = socketFile + cfg.Port = cli.Port + cfg.Status.ReportStatus = false + + ts := createTidbTestSuite(t) + + server, err := server2.NewServer(cfg, ts.tidbdrv) + require.NoError(t, err) + server.SetDomain(ts.domain) + cli.Port = testutil.GetPortFromTCPAddr(server.ListenAddr()) + go func() { + err := server.Run() + require.NoError(t, err) + }() + cli.WaitUntilServerCanConnect() + defer server.Close() + + // Test with Socket connection + Setup user1@% for all host access + cli.Port = testutil.GetPortFromTCPAddr(server.ListenAddr()) + defer func() { + cli.RunTests(t, func(config *mysql.Config) { + config.User = "root" + }, + func(dbt *testkit.DBTestKit) { + dbt.MustExec("DROP USER IF EXISTS 'user1'@'%'") + dbt.MustExec("DROP USER IF EXISTS 'user1'@'localhost'") + dbt.MustExec("DROP USER IF EXISTS 'user1'@'127.0.0.1'") + }) + }() + cli.RunTests(t, func(config *mysql.Config) { + config.User = "root" + config.Net = "unix" + config.Addr = socketFile + config.DBName = "test" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("select user()") + cli.CheckRows(t, rows, "root@localhost") + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION") + dbt.MustQuery("CREATE USER user1@'%'") + dbt.MustQuery("GRANT SELECT ON test.* TO user1@'%'") + }) + // Test with Network interface connection with all hosts + cli.RunTests(t, func(config *mysql.Config) { + config.User = "user1" + config.DBName = "test" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("select user()") + // NOTICE: this is not compatible with MySQL! (MySQL would report user1@localhost also for 127.0.0.1) + cli.CheckRows(t, rows, "user1@127.0.0.1") + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT USAGE ON *.* TO 'user1'@'%'\nGRANT SELECT ON `test`.* TO 'user1'@'%'") + rows = dbt.MustQuery("select host from information_schema.processlist where user = 'user1'") + records := cli.Rows(t, rows) + require.Contains(t, records[0], ":", "Missing : in is.processlist") + }) + // Test with unix domain socket file connection with all hosts + cli.RunTests(t, func(config *mysql.Config) { + config.Net = "unix" + config.Addr = socketFile + config.User = "user1" + config.DBName = "test" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("select user()") + cli.CheckRows(t, rows, "user1@localhost") + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT USAGE ON *.* TO 'user1'@'%'\nGRANT SELECT ON `test`.* TO 'user1'@'%'") + }) + + // Setup user1@127.0.0.1 for loop back network interface access + cli.RunTests(t, func(config *mysql.Config) { + config.User = "root" + config.DBName = "test" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("select user()") + // NOTICE: this is not compatible with MySQL! (MySQL would report user1@localhost also for 127.0.0.1) + cli.CheckRows(t, rows, "root@127.0.0.1") + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION") + dbt.MustQuery("CREATE USER user1@127.0.0.1") + dbt.MustQuery("GRANT SELECT,INSERT ON test.* TO user1@'127.0.0.1'") + }) + // Test with Network interface connection with all hosts + cli.RunTests(t, func(config *mysql.Config) { + config.User = "user1" + config.DBName = "test" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("select user()") + // NOTICE: this is not compatible with MySQL! (MySQL would report user1@localhost also for 127.0.0.1) + cli.CheckRows(t, rows, "user1@127.0.0.1") + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT USAGE ON *.* TO 'user1'@'127.0.0.1'\nGRANT SELECT,INSERT ON `test`.* TO 'user1'@'127.0.0.1'") + }) + // Test with unix domain socket file connection with all hosts + cli.RunTests(t, func(config *mysql.Config) { + config.Net = "unix" + config.Addr = socketFile + config.User = "user1" + config.DBName = "test" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("select user()") + cli.CheckRows(t, rows, "user1@localhost") + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT USAGE ON *.* TO 'user1'@'%'\nGRANT SELECT ON `test`.* TO 'user1'@'%'") + }) + + // Setup user1@localhost for socket (and if MySQL compatible; loop back network interface access) + cli.RunTests(t, func(config *mysql.Config) { + config.Net = "unix" + config.Addr = socketFile + config.User = "root" + config.DBName = "test" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("select user()") + cli.CheckRows(t, rows, "root@localhost") + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION") + dbt.MustExec("CREATE USER user1@localhost") + dbt.MustExec("GRANT SELECT,INSERT,UPDATE,DELETE ON test.* TO user1@localhost") + }) + // Test with Network interface connection with all hosts + cli.RunTests(t, func(config *mysql.Config) { + config.User = "user1" + config.DBName = "test" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("select user()") + // NOTICE: this is not compatible with MySQL! (MySQL would report user1@localhost also for 127.0.0.1) + cli.CheckRows(t, rows, "user1@127.0.0.1") + require.NoError(t, rows.Close()) + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT USAGE ON *.* TO 'user1'@'127.0.0.1'\nGRANT SELECT,INSERT ON `test`.* TO 'user1'@'127.0.0.1'") + require.NoError(t, rows.Close()) + }) + // Test with unix domain socket file connection with all hosts + cli.RunTests(t, func(config *mysql.Config) { + config.Net = "unix" + config.Addr = socketFile + config.User = "user1" + config.DBName = "test" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("select user()") + cli.CheckRows(t, rows, "user1@localhost") + require.NoError(t, rows.Close()) + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT USAGE ON *.* TO 'user1'@'localhost'\nGRANT SELECT,INSERT,UPDATE,DELETE ON `test`.* TO 'user1'@'localhost'") + require.NoError(t, rows.Close()) + }) +} + +// TestOnlySocket for server configuration without network interface for mysql clients +func TestOnlySocket(t *testing.T) { + tempDir := t.TempDir() + socketFile := tempDir + "/tidbtest.sock" // Unix Socket does not work on Windows, so '/' should be OK + + cli := testserverclient.NewTestServerClient() + cfg := util2.NewTestConfig() + cfg.Socket = socketFile + cfg.Host = "" // No network interface listening for mysql traffic + cfg.Status.ReportStatus = false + + ts := createTidbTestSuite(t) + + server, err := server2.NewServer(cfg, ts.tidbdrv) + require.NoError(t, err) + server.SetDomain(ts.domain) + go func() { + err := server.Run() + require.NoError(t, err) + }() + time.Sleep(time.Millisecond * 100) + defer server.Close() + require.Nil(t, server.Listener()) + require.NotNil(t, server.Socket()) + + // Test with Socket connection + Setup user1@% for all host access + defer func() { + cli.RunTests(t, func(config *mysql.Config) { + config.User = "root" + config.Net = "unix" + config.Addr = socketFile + }, + func(dbt *testkit.DBTestKit) { + dbt.MustExec("DROP USER IF EXISTS 'user1'@'%'") + dbt.MustExec("DROP USER IF EXISTS 'user1'@'localhost'") + dbt.MustExec("DROP USER IF EXISTS 'user1'@'127.0.0.1'") + }) + }() + cli.RunTests(t, func(config *mysql.Config) { + config.User = "root" + config.Net = "unix" + config.Addr = socketFile + config.DBName = "test" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("select user()") + cli.CheckRows(t, rows, "root@localhost") + require.NoError(t, rows.Close()) + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION") + require.NoError(t, rows.Close()) + dbt.MustExec("CREATE USER user1@'%'") + dbt.MustExec("GRANT SELECT ON test.* TO user1@'%'") + }) + // Test with Network interface connection with all hosts, should fail since server not configured + db, err := sql.Open("mysql", cli.GetDSN(func(config *mysql.Config) { + config.User = "root" + config.DBName = "test" + })) + require.NoErrorf(t, err, "Open failed") + err = db.Ping() + require.Errorf(t, err, "Connect succeeded when not configured!?!") + db.Close() + db, err = sql.Open("mysql", cli.GetDSN(func(config *mysql.Config) { + config.User = "user1" + config.DBName = "test" + })) + require.NoErrorf(t, err, "Open failed") + err = db.Ping() + require.Errorf(t, err, "Connect succeeded when not configured!?!") + db.Close() + // Test with unix domain socket file connection with all hosts + cli.RunTests(t, func(config *mysql.Config) { + config.Net = "unix" + config.Addr = socketFile + config.User = "user1" + config.DBName = "test" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("select user()") + cli.CheckRows(t, rows, "user1@localhost") + require.NoError(t, rows.Close()) + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT USAGE ON *.* TO 'user1'@'%'\nGRANT SELECT ON `test`.* TO 'user1'@'%'") + require.NoError(t, rows.Close()) + }) + + // Setup user1@127.0.0.1 for loop back network interface access + cli.RunTests(t, func(config *mysql.Config) { + config.Net = "unix" + config.Addr = socketFile + config.User = "root" + config.DBName = "test" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("select user()") + // NOTICE: this is not compatible with MySQL! (MySQL would report user1@localhost also for 127.0.0.1) + cli.CheckRows(t, rows, "root@localhost") + require.NoError(t, rows.Close()) + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION") + require.NoError(t, rows.Close()) + dbt.MustExec("CREATE USER user1@127.0.0.1") + dbt.MustExec("GRANT SELECT,INSERT ON test.* TO user1@'127.0.0.1'") + }) + // Test with unix domain socket file connection with all hosts + cli.RunTests(t, func(config *mysql.Config) { + config.Net = "unix" + config.Addr = socketFile + config.User = "user1" + config.DBName = "test" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("select user()") + cli.CheckRows(t, rows, "user1@localhost") + require.NoError(t, rows.Close()) + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT USAGE ON *.* TO 'user1'@'%'\nGRANT SELECT ON `test`.* TO 'user1'@'%'") + require.NoError(t, rows.Close()) + }) + + // Setup user1@localhost for socket (and if MySQL compatible; loop back network interface access) + cli.RunTests(t, func(config *mysql.Config) { + config.Net = "unix" + config.Addr = socketFile + config.User = "root" + config.DBName = "test" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("select user()") + cli.CheckRows(t, rows, "root@localhost") + require.NoError(t, rows.Close()) + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION") + require.NoError(t, rows.Close()) + dbt.MustExec("CREATE USER user1@localhost") + dbt.MustExec("GRANT SELECT,INSERT,UPDATE,DELETE ON test.* TO user1@localhost") + }) + // Test with unix domain socket file connection with all hosts + cli.RunTests(t, func(config *mysql.Config) { + config.Net = "unix" + config.Addr = socketFile + config.User = "user1" + config.DBName = "test" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("select user()") + cli.CheckRows(t, rows, "user1@localhost") + require.NoError(t, rows.Close()) + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT USAGE ON *.* TO 'user1'@'localhost'\nGRANT SELECT,INSERT,UPDATE,DELETE ON `test`.* TO 'user1'@'localhost'") + require.NoError(t, rows.Close()) + }) +} + +// generateCert generates a private key and a certificate in PEM format based on parameters. +// If parentCert and parentCertKey is specified, the new certificate will be signed by the parentCert. +// Otherwise, the new certificate will be self-signed and is a CA. +func generateCert(sn int, commonName string, parentCert *x509.Certificate, parentCertKey *rsa.PrivateKey, outKeyFile string, outCertFile string, opts ...func(c *x509.Certificate)) (*x509.Certificate, *rsa.PrivateKey, error) { + privateKey, err := rsa.GenerateKey(rand.Reader, 528) + if err != nil { + return nil, nil, errors.Trace(err) + } + notBefore := time.Now().Add(-10 * time.Minute).UTC() + notAfter := notBefore.Add(1 * time.Hour).UTC() + + template := x509.Certificate{ + SerialNumber: big.NewInt(int64(sn)), + Subject: pkix.Name{CommonName: commonName, Names: []pkix.AttributeTypeAndValue{util.MockPkixAttribute(util.CommonName, commonName)}}, + DNSNames: []string{commonName}, + NotBefore: notBefore, + NotAfter: notAfter, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + BasicConstraintsValid: true, + } + for _, opt := range opts { + opt(&template) + } + + var parent *x509.Certificate + var priv *rsa.PrivateKey + + if parentCert == nil || parentCertKey == nil { + template.IsCA = true + template.KeyUsage |= x509.KeyUsageCertSign + parent = &template + priv = privateKey + } else { + parent = parentCert + priv = parentCertKey + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, parent, &privateKey.PublicKey, priv) + if err != nil { + return nil, nil, errors.Trace(err) + } + + cert, err := x509.ParseCertificate(derBytes) + if err != nil { + return nil, nil, errors.Trace(err) + } + + certOut, err := os.Create(outCertFile) + if err != nil { + return nil, nil, errors.Trace(err) + } + err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + if err != nil { + return nil, nil, errors.Trace(err) + } + err = certOut.Close() + if err != nil { + return nil, nil, errors.Trace(err) + } + + keyOut, err := os.OpenFile(outKeyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return nil, nil, errors.Trace(err) + } + err = pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}) + if err != nil { + return nil, nil, errors.Trace(err) + } + err = keyOut.Close() + if err != nil { + return nil, nil, errors.Trace(err) + } + + return cert, privateKey, nil +} + +// registerTLSConfig registers a mysql client TLS config. +// See https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig for details. +func registerTLSConfig(configName string, caCertPath string, clientCertPath string, clientKeyPath string, serverName string, verifyServer bool) error { + rootCertPool := x509.NewCertPool() + data, err := os.ReadFile(caCertPath) + if err != nil { + return err + } + if ok := rootCertPool.AppendCertsFromPEM(data); !ok { + return errors.New("Failed to append PEM") + } + clientCert := make([]tls.Certificate, 0, 1) + certs, err := tls.LoadX509KeyPair(clientCertPath, clientKeyPath) + if err != nil { + return err + } + clientCert = append(clientCert, certs) + tlsConfig := &tls.Config{ + RootCAs: rootCertPool, + Certificates: clientCert, + ServerName: serverName, + InsecureSkipVerify: !verifyServer, + } + return mysql.RegisterTLSConfig(configName, tlsConfig) +} + +func TestSystemTimeZone(t *testing.T) { + ts := createTidbTestSuite(t) + + tk := testkit.NewTestKit(t, ts.store) + cfg := util2.NewTestConfig() + cfg.Port, cfg.Status.StatusPort = 0, 0 + cfg.Status.ReportStatus = false + server, err := server2.NewServer(cfg, ts.tidbdrv) + require.NoError(t, err) + defer server.Close() + + tz1 := tk.MustQuery("select variable_value from mysql.tidb where variable_name = 'system_tz'").Rows() + tk.MustQuery("select @@system_time_zone").Check(tz1) +} + +func TestInternalSessionTxnStartTS(t *testing.T) { + ts := createTidbTestSuite(t) + + se, err := session.CreateSession4Test(ts.store) + require.NoError(t, err) + + _, err = se.Execute(context.Background(), "set global tidb_enable_metadata_lock=0") + require.NoError(t, err) + + count := 10 + stmts := make([]ast.StmtNode, count) + for i := 0; i < count; i++ { + stmt, err := session.ParseWithParams4Test(context.Background(), se, "select * from mysql.user limit 1") + require.NoError(t, err) + stmts[i] = stmt + } + // Test an issue that sysSessionPool doesn't call session's Close, cause + // asyncGetTSWorker goroutine leak. + var wg util.WaitGroupWrapper + for i := 0; i < count; i++ { + s := stmts[i] + wg.Run(func() { + _, _, err := session.ExecRestrictedStmt4Test(context.Background(), se, s) + require.NoError(t, err) + }) + } + + wg.Wait() +} + +func TestClientWithCollation(t *testing.T) { + ts := createTidbTestSuite(t) + + ts.RunTestClientWithCollation(t) +} + +func TestCreateTableFlen(t *testing.T) { + ts := createTidbTestSuite(t) + + // issue #4540 + qctx, err := ts.tidbdrv.OpenCtx(uint64(0), 0, uint8(tmysql.DefaultCollationID), "test", nil, nil) + require.NoError(t, err) + _, err = Execute(context.Background(), qctx, "use test;") + require.NoError(t, err) + + ctx := context.Background() + testSQL := "CREATE TABLE `t1` (" + + "`a` char(36) NOT NULL," + + "`b` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP," + + "`c` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP," + + "`d` varchar(50) DEFAULT ''," + + "`e` char(36) NOT NULL DEFAULT ''," + + "`f` char(36) NOT NULL DEFAULT ''," + + "`g` char(1) NOT NULL DEFAULT 'N'," + + "`h` varchar(100) NOT NULL," + + "`i` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP," + + "`j` varchar(10) DEFAULT ''," + + "`k` varchar(10) DEFAULT ''," + + "`l` varchar(20) DEFAULT ''," + + "`m` varchar(20) DEFAULT ''," + + "`n` varchar(30) DEFAULT ''," + + "`o` varchar(100) DEFAULT ''," + + "`p` varchar(50) DEFAULT ''," + + "`q` varchar(50) DEFAULT ''," + + "`r` varchar(100) DEFAULT ''," + + "`s` varchar(20) DEFAULT ''," + + "`t` varchar(50) DEFAULT ''," + + "`u` varchar(100) DEFAULT ''," + + "`v` varchar(50) DEFAULT ''," + + "`w` varchar(300) NOT NULL," + + "`x` varchar(250) DEFAULT ''," + + "`y` decimal(20)," + + "`z` decimal(20, 4)," + + "PRIMARY KEY (`a`)" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin" + _, err = Execute(ctx, qctx, testSQL) + require.NoError(t, err) + rs, err := Execute(ctx, qctx, "show create table t1") + require.NoError(t, err) + req := rs.NewChunk(nil) + err = rs.Next(ctx, req) + require.NoError(t, err) + cols := rs.Columns() + require.NoError(t, err) + require.Len(t, cols, 2) + require.Equal(t, 5*tmysql.MaxBytesOfCharacter, int(cols[0].ColumnLength)) + require.Equal(t, len(req.GetRow(0).GetString(1))*tmysql.MaxBytesOfCharacter, int(cols[1].ColumnLength)) + + // for issue#5246 + rs, err = Execute(ctx, qctx, "select y, z from t1") + require.NoError(t, err) + cols = rs.Columns() + require.Len(t, cols, 2) + require.Equal(t, 21, int(cols[0].ColumnLength)) + require.Equal(t, 22, int(cols[1].ColumnLength)) + rs.Close() +} + +func Execute(ctx context.Context, qc *server2.TiDBContext, sql string) (resultset.ResultSet, error) { + stmts, err := qc.Parse(ctx, sql) + if err != nil { + return nil, err + } + if len(stmts) != 1 { + panic("wrong input for Execute: " + sql) + } + return qc.ExecuteStmt(ctx, stmts[0]) +} + +func TestShowTablesFlen(t *testing.T) { + ts := createTidbTestSuite(t) + + qctx, err := ts.tidbdrv.OpenCtx(uint64(0), 0, uint8(tmysql.DefaultCollationID), "test", nil, nil) + require.NoError(t, err) + ctx := context.Background() + _, err = Execute(ctx, qctx, "use test;") + require.NoError(t, err) + + testSQL := "create table abcdefghijklmnopqrstuvwxyz (i int)" + _, err = Execute(ctx, qctx, testSQL) + require.NoError(t, err) + rs, err := Execute(ctx, qctx, "show tables") + require.NoError(t, err) + req := rs.NewChunk(nil) + err = rs.Next(ctx, req) + require.NoError(t, err) + cols := rs.Columns() + require.NoError(t, err) + require.Len(t, cols, 1) + require.Equal(t, 26*tmysql.MaxBytesOfCharacter, int(cols[0].ColumnLength)) +} + +func checkColNames(t *testing.T, columns []*column.Info, names ...string) { + for i, name := range names { + require.Equal(t, name, columns[i].Name) + require.Equal(t, name, columns[i].OrgName) + } +} + +func TestFieldList(t *testing.T) { + ts := createTidbTestSuite(t) + + qctx, err := ts.tidbdrv.OpenCtx(uint64(0), 0, uint8(tmysql.DefaultCollationID), "test", nil, nil) + require.NoError(t, err) + _, err = Execute(context.Background(), qctx, "use test;") + require.NoError(t, err) + + ctx := context.Background() + testSQL := `create table t ( + c_bit bit(10), + c_int_d int, + c_bigint_d bigint, + c_float_d float, + c_double_d double, + c_decimal decimal(6, 3), + c_datetime datetime(2), + c_time time(3), + c_date date, + c_timestamp timestamp(4) DEFAULT CURRENT_TIMESTAMP(4), + c_char char(20), + c_varchar varchar(20), + c_text_d text, + c_binary binary(20), + c_blob_d blob, + c_set set('a', 'b', 'c'), + c_enum enum('a', 'b', 'c'), + c_json JSON, + c_year year + )` + _, err = Execute(ctx, qctx, testSQL) + require.NoError(t, err) + colInfos, err := qctx.FieldList("t") + require.NoError(t, err) + require.Len(t, colInfos, 19) + + checkColNames(t, colInfos, "c_bit", "c_int_d", "c_bigint_d", "c_float_d", + "c_double_d", "c_decimal", "c_datetime", "c_time", "c_date", "c_timestamp", + "c_char", "c_varchar", "c_text_d", "c_binary", "c_blob_d", "c_set", "c_enum", + "c_json", "c_year") + + for _, cols := range colInfos { + require.Equal(t, "test", cols.Schema) + } + + for _, cols := range colInfos { + require.Equal(t, "t", cols.Table) + } + + for i, col := range colInfos { + switch i { + case 10, 11, 12, 15, 16: + // c_char char(20), c_varchar varchar(20), c_text_d text, + // c_set set('a', 'b', 'c'), c_enum enum('a', 'b', 'c') + require.Equalf(t, uint16(tmysql.CharsetNameToID(tmysql.DefaultCharset)), col.Charset, "index %d", i) + continue + } + + require.Equalf(t, uint16(tmysql.CharsetNameToID("binary")), col.Charset, "index %d", i) + } + + // c_decimal decimal(6, 3) + require.Equal(t, uint8(3), colInfos[5].Decimal) + + // for issue#10513 + tooLongColumnAsName := "COALESCE(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)" + columnAsName := tooLongColumnAsName[:tmysql.MaxAliasIdentifierLen] + + rs, err := Execute(ctx, qctx, "select "+tooLongColumnAsName) + require.NoError(t, err) + cols := rs.Columns() + require.Equal(t, "", cols[0].OrgName) + require.Equal(t, columnAsName, cols[0].Name) + rs.Close() + + rs, err = Execute(ctx, qctx, "select c_bit as '"+tooLongColumnAsName+"' from t") + require.NoError(t, err) + cols = rs.Columns() + require.Equal(t, "c_bit", cols[0].OrgName) + require.Equal(t, columnAsName, cols[0].Name) + rs.Close() +} + +func TestClientErrors(t *testing.T) { + ts := createTidbTestSuite(t) + ts.RunTestInfoschemaClientErrors(t) +} + +func TestInitConnect(t *testing.T) { + ts := createTidbTestSuite(t) + ts.RunTestInitConnect(t) +} + +func TestSumAvg(t *testing.T) { + ts := createTidbTestSuite(t) + ts.RunTestSumAvg(t) +} + +func TestNullFlag(t *testing.T) { + ts := createTidbTestSuite(t) + + qctx, err := ts.tidbdrv.OpenCtx(uint64(0), 0, uint8(tmysql.DefaultCollationID), "test", nil, nil) + require.NoError(t, err) + + ctx := context.Background() + { + // issue #9689 + rs, err := Execute(ctx, qctx, "select 1") + require.NoError(t, err) + cols := rs.Columns() + require.Len(t, cols, 1) + expectFlag := uint16(tmysql.NotNullFlag | tmysql.BinaryFlag) + require.Equal(t, expectFlag, column.DumpFlag(cols[0].Type, cols[0].Flag)) + rs.Close() + } + + { + // issue #19025 + rs, err := Execute(ctx, qctx, "select convert('{}', JSON)") + require.NoError(t, err) + cols := rs.Columns() + require.Len(t, cols, 1) + expectFlag := uint16(tmysql.BinaryFlag) + require.Equal(t, expectFlag, column.DumpFlag(cols[0].Type, cols[0].Flag)) + rs.Close() + } + + { + // issue #18488 + _, err := Execute(ctx, qctx, "use test") + require.NoError(t, err) + _, err = Execute(ctx, qctx, "CREATE TABLE `test` (`iD` bigint(20) NOT NULL, `INT_TEST` int(11) DEFAULT NULL);") + require.NoError(t, err) + rs, err := Execute(ctx, qctx, `SELECT id + int_test as res FROM test GROUP BY res ORDER BY res;`) + require.NoError(t, err) + cols := rs.Columns() + require.Len(t, cols, 1) + expectFlag := uint16(tmysql.BinaryFlag) + require.Equal(t, expectFlag, column.DumpFlag(cols[0].Type, cols[0].Flag)) + rs.Close() + } + + { + rs, err := Execute(ctx, qctx, "select if(1, null, 1) ;") + require.NoError(t, err) + cols := rs.Columns() + require.Len(t, cols, 1) + expectFlag := uint16(tmysql.BinaryFlag) + require.Equal(t, expectFlag, column.DumpFlag(cols[0].Type, cols[0].Flag)) + rs.Close() + } + { + rs, err := Execute(ctx, qctx, "select CASE 1 WHEN 2 THEN 1 END ;") + require.NoError(t, err) + cols := rs.Columns() + require.Len(t, cols, 1) + expectFlag := uint16(tmysql.BinaryFlag) + require.Equal(t, expectFlag, column.DumpFlag(cols[0].Type, cols[0].Flag)) + rs.Close() + } + { + rs, err := Execute(ctx, qctx, "select NULL;") + require.NoError(t, err) + cols := rs.Columns() + require.Len(t, cols, 1) + expectFlag := uint16(tmysql.BinaryFlag) + require.Equal(t, expectFlag, column.DumpFlag(cols[0].Type, cols[0].Flag)) + rs.Close() + } +} + +func TestNO_DEFAULT_VALUEFlag(t *testing.T) { + ts := createTidbTestSuite(t) + + // issue #21465 + qctx, err := ts.tidbdrv.OpenCtx(uint64(0), 0, uint8(tmysql.DefaultCollationID), "test", nil, nil) + require.NoError(t, err) + + ctx := context.Background() + _, err = Execute(ctx, qctx, "use test") + require.NoError(t, err) + _, err = Execute(ctx, qctx, "drop table if exists t") + require.NoError(t, err) + _, err = Execute(ctx, qctx, "create table t(c1 int key, c2 int);") + require.NoError(t, err) + rs, err := Execute(ctx, qctx, "select c1 from t;") + require.NoError(t, err) + defer rs.Close() + cols := rs.Columns() + require.Len(t, cols, 1) + expectFlag := uint16(tmysql.NotNullFlag | tmysql.PriKeyFlag | tmysql.NoDefaultValueFlag) + require.Equal(t, expectFlag, column.DumpFlag(cols[0].Type, cols[0].Flag)) +} + +func TestGracefulShutdown(t *testing.T) { + ts := createTidbTestSuite(t) + + cli := testserverclient.NewTestServerClient() + cfg := util2.NewTestConfig() + cfg.GracefulWaitBeforeShutdown = 2 // wait before shutdown + cfg.Port = 0 + cfg.Status.StatusPort = 0 + cfg.Status.ReportStatus = true + cfg.Performance.TCPKeepAlive = true + server, err := server2.NewServer(cfg, ts.tidbdrv) + require.NoError(t, err) + require.NotNil(t, server) + cli.Port = testutil.GetPortFromTCPAddr(server.ListenAddr()) + cli.StatusPort = testutil.GetPortFromTCPAddr(server.StatusListenerAddr()) + go func() { + err := server.Run() + require.NoError(t, err) + }() + time.Sleep(time.Millisecond * 100) + + resp, err := cli.FetchStatus("/status") // server is up + require.NoError(t, err) + require.Nil(t, resp.Body.Close()) + + go server.Close() + time.Sleep(time.Millisecond * 500) + + resp, _ = cli.FetchStatus("/status") // should return 5xx code + require.Equal(t, 500, resp.StatusCode) + require.Nil(t, resp.Body.Close()) + + time.Sleep(time.Second * 2) + + //nolint:bodyclose + _, err = cli.FetchStatus("/status") // Status is gone + require.Error(t, err) + require.Regexp(t, "connect: connection refused$", err.Error()) +} + +func TestPessimisticInsertSelectForUpdate(t *testing.T) { + ts := createTidbTestSuite(t) + + qctx, err := ts.tidbdrv.OpenCtx(uint64(0), 0, uint8(tmysql.DefaultCollationID), "test", nil, nil) + require.NoError(t, err) + defer qctx.Close() + ctx := context.Background() + _, err = Execute(ctx, qctx, "use test;") + require.NoError(t, err) + _, err = Execute(ctx, qctx, "drop table if exists t1, t2") + require.NoError(t, err) + _, err = Execute(ctx, qctx, "create table t1 (id int)") + require.NoError(t, err) + _, err = Execute(ctx, qctx, "create table t2 (id int)") + require.NoError(t, err) + _, err = Execute(ctx, qctx, "insert into t1 select 1") + require.NoError(t, err) + _, err = Execute(ctx, qctx, "begin pessimistic") + require.NoError(t, err) + rs, err := Execute(ctx, qctx, "INSERT INTO t2 (id) select id from t1 where id = 1 for update") + require.NoError(t, err) + require.Nil(t, rs) // should be no delay +} + +func TestTopSQLCatchRunningSQL(t *testing.T) { + ts := createTidbTestTopSQLSuite(t) + + db, err := sql.Open("mysql", ts.GetDSN()) + require.NoError(t, err) + defer func() { + require.NoError(t, db.Close()) + }() + + dbt := testkit.NewDBTestKit(t, db) + dbt.MustExec("drop database if exists topsql") + dbt.MustExec("create database topsql") + dbt.MustExec("use topsql;") + dbt.MustExec("create table t (a int, b int);") + + for i := 0; i < 5000; i++ { + dbt.MustExec(fmt.Sprintf("insert into t values (%v, %v)", i, i)) + } + + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/util/topsql/mockHighLoadForEachPlan", `return(true)`)) + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/domain/skipLoadSysVarCacheLoop", `return(true)`)) + defer func() { + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/util/topsql/mockHighLoadForEachPlan")) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/domain/skipLoadSysVarCacheLoop")) + }() + + mc := mockTopSQLTraceCPU.NewTopSQLCollector() + topsql.SetupTopSQLForTest(mc) + sqlCPUCollector := collector.NewSQLCPUCollector(mc) + sqlCPUCollector.Start() + defer sqlCPUCollector.Stop() + + query := "select count(*) from t as t0 join t as t1 on t0.a != t1.a;" + needEnableTopSQL := int64(0) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + for { + select { + case <-ctx.Done(): + return + default: + } + if atomic.LoadInt64(&needEnableTopSQL) == 1 { + time.Sleep(2 * time.Millisecond) + topsqlstate.EnableTopSQL() + atomic.StoreInt64(&needEnableTopSQL, 0) + } + time.Sleep(time.Millisecond) + } + }() + execFn := func(db *sql.DB) { + dbt := testkit.NewDBTestKit(t, db) + atomic.StoreInt64(&needEnableTopSQL, 1) + mustQuery(t, dbt, query) + topsqlstate.DisableTopSQL() + } + check := func() { + require.NoError(t, ctx.Err()) + stats := mc.GetSQLStatsBySQLWithRetry(query, true) + require.Greaterf(t, len(stats), 0, query) + } + ts.testCase(t, mc, execFn, check) + cancel() + wg.Wait() +} + +func TestTopSQLCPUProfile(t *testing.T) { + ts := createTidbTestTopSQLSuite(t) + + db, err := sql.Open("mysql", ts.GetDSN()) + require.NoError(t, err) + defer func() { + require.NoError(t, db.Close()) + }() + + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/util/topsql/mockHighLoadForEachSQL", `return(true)`)) + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/util/topsql/mockHighLoadForEachPlan", `return(true)`)) + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/domain/skipLoadSysVarCacheLoop", `return(true)`)) + defer func() { + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/util/topsql/mockHighLoadForEachSQL")) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/util/topsql/mockHighLoadForEachPlan")) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/domain/skipLoadSysVarCacheLoop")) + }() + + topsqlstate.EnableTopSQL() + defer topsqlstate.DisableTopSQL() + + mc := mockTopSQLTraceCPU.NewTopSQLCollector() + topsql.SetupTopSQLForTest(mc) + sqlCPUCollector := collector.NewSQLCPUCollector(mc) + sqlCPUCollector.Start() + defer sqlCPUCollector.Stop() + + dbt := testkit.NewDBTestKit(t, db) + dbt.MustExec("drop database if exists topsql") + dbt.MustExec("create database topsql") + dbt.MustExec("use topsql;") + dbt.MustExec("create table t (a int auto_increment, b int, unique index idx(a));") + dbt.MustExec("create table t1 (a int auto_increment, b int, unique index idx(a));") + dbt.MustExec("create table t2 (a int auto_increment, b int, unique index idx(a));") + dbt.MustExec("set @@global.tidb_txn_mode = 'pessimistic'") + + checkFn := func(sql, planRegexp string) { + stats := mc.GetSQLStatsBySQLWithRetry(sql, len(planRegexp) > 0) + // since 1 sql may has many plan, check `len(stats) > 0` instead of `len(stats) == 1`. + require.Greaterf(t, len(stats), 0, "sql: "+sql) + + for _, s := range stats { + sqlStr := mc.GetSQL(s.SQLDigest) + encodedPlan := mc.GetPlan(s.PlanDigest) + // Normalize the user SQL before check. + normalizedSQL := parser.Normalize(sql) + require.Equalf(t, normalizedSQL, sqlStr, "sql: %v", sql) + // decode plan before check. + normalizedPlan, err := plancodec.DecodeNormalizedPlan(encodedPlan) + require.NoError(t, err) + // remove '\n' '\t' before do regexp match. + normalizedPlan = strings.Replace(normalizedPlan, "\n", " ", -1) + normalizedPlan = strings.Replace(normalizedPlan, "\t", " ", -1) + require.Regexpf(t, planRegexp, normalizedPlan, "sql: %v", sql) + } + } + + // Test case 1: DML query: insert/update/replace/delete/select + cases1 := []struct { + sql string + planRegexp string + }{ + {sql: "insert into t () values (),(),(),(),(),(),();", planRegexp: ""}, + {sql: "insert into t (b) values (1),(1),(1),(1),(1),(1),(1),(1);", planRegexp: ""}, + {sql: "update t set b=a where b is null limit 1;", planRegexp: ".*Limit.*TableReader.*"}, + {sql: "delete from t where b = a limit 2;", planRegexp: ".*Limit.*TableReader.*"}, + {sql: "replace into t (b) values (1),(1),(1),(1),(1),(1),(1),(1);", planRegexp: ""}, + {sql: "select * from t use index(idx) where a<10;", planRegexp: ".*IndexLookUp.*"}, + {sql: "select * from t ignore index(idx) where a>1000000000;", planRegexp: ".*TableReader.*"}, + {sql: "select /*+ HASH_JOIN(t1, t2) */ * from t t1 join t t2 on t1.a=t2.a where t1.b is not null;", planRegexp: ".*HashJoin.*"}, + {sql: "select /*+ INL_HASH_JOIN(t1, t2) */ * from t t1 join t t2 on t2.a=t1.a where t1.b is not null;", planRegexp: ".*IndexHashJoin.*"}, + {sql: "select * from t where a=1;", planRegexp: ".*Point_Get.*"}, + {sql: "select * from t where a in (1,2,3,4)", planRegexp: ".*Batch_Point_Get.*"}, + } + execFn := func(db *sql.DB) { + dbt := testkit.NewDBTestKit(t, db) + for _, ca := range cases1 { + sqlStr := ca.sql + if strings.HasPrefix(sqlStr, "select") { + mustQuery(t, dbt, sqlStr) + } else { + dbt.MustExec(sqlStr) + } + } + } + check := func() { + for _, ca := range cases1 { + checkFn(ca.sql, ca.planRegexp) + } + } + ts.testCase(t, mc, execFn, check) + + // Test case 2: prepare/execute sql + cases2 := []struct { + prepare string + args []interface{} + planRegexp string + }{ + {prepare: "insert into t1 (b) values (?);", args: []interface{}{1}, planRegexp: ""}, + {prepare: "replace into t1 (b) values (?);", args: []interface{}{1}, planRegexp: ""}, + {prepare: "update t1 set b=a where b is null limit ?;", args: []interface{}{1}, planRegexp: ".*Limit.*TableReader.*"}, + {prepare: "delete from t1 where b = a limit ?;", args: []interface{}{1}, planRegexp: ".*Limit.*TableReader.*"}, + {prepare: "replace into t1 (b) values (?);", args: []interface{}{1}, planRegexp: ""}, + {prepare: "select * from t1 use index(idx) where a?;", args: []interface{}{1000000000}, planRegexp: ".*TableReader.*"}, + {prepare: "select /*+ HASH_JOIN(t1, t2) */ * from t1 t1 join t1 t2 on t1.a=t2.a where t1.b is not null;", args: nil, planRegexp: ".*HashJoin.*"}, + {prepare: "select /*+ INL_HASH_JOIN(t1, t2) */ * from t1 t1 join t1 t2 on t2.a=t1.a where t1.b is not null;", args: nil, planRegexp: ".*IndexHashJoin.*"}, + {prepare: "select * from t1 where a=?;", args: []interface{}{1}, planRegexp: ".*Point_Get.*"}, + {prepare: "select * from t1 where a in (?,?,?,?)", args: []interface{}{1, 2, 3, 4}, planRegexp: ".*Batch_Point_Get.*"}, + } + execFn = func(db *sql.DB) { + dbt := testkit.NewDBTestKit(t, db) + for _, ca := range cases2 { + prepare, args := ca.prepare, ca.args + stmt := dbt.MustPrepare(prepare) + if strings.HasPrefix(prepare, "select") { + rows, err := stmt.Query(args...) + require.NoError(t, err) + for rows.Next() { + } + require.NoError(t, rows.Close()) + } else { + _, err = stmt.Exec(args...) + require.NoError(t, err) + } + } + } + check = func() { + for _, ca := range cases2 { + checkFn(ca.prepare, ca.planRegexp) + } + } + ts.testCase(t, mc, execFn, check) + + // Test case 3: prepare, execute stmt using @val... + cases3 := []struct { + prepare string + args []interface{} + planRegexp string + }{ + {prepare: "insert into t2 (b) values (?);", args: []interface{}{1}, planRegexp: ""}, + {prepare: "update t2 set b=a where b is null limit ?;", args: []interface{}{1}, planRegexp: ".*Limit.*TableReader.*"}, + {prepare: "delete from t2 where b = a limit ?;", args: []interface{}{1}, planRegexp: ".*Limit.*TableReader.*"}, + {prepare: "replace into t2 (b) values (?);", args: []interface{}{1}, planRegexp: ""}, + {prepare: "select * from t2 use index(idx) where a?;", args: []interface{}{1000000000}, planRegexp: ".*TableReader.*"}, + {prepare: "select /*+ HASH_JOIN(t1, t2) */ * from t2 t1 join t2 t2 on t1.a=t2.a where t1.b is not null;", args: nil, planRegexp: ".*HashJoin.*"}, + {prepare: "select /*+ INL_HASH_JOIN(t1, t2) */ * from t2 t1 join t2 t2 on t2.a=t1.a where t1.b is not null;", args: nil, planRegexp: ".*IndexHashJoin.*"}, + {prepare: "select * from t2 where a=?;", args: []interface{}{1}, planRegexp: ".*Point_Get.*"}, + {prepare: "select * from t2 where a in (?,?,?,?)", args: []interface{}{1, 2, 3, 4}, planRegexp: ".*Batch_Point_Get.*"}, + } + execFn = func(db *sql.DB) { + dbt := testkit.NewDBTestKit(t, db) + for _, ca := range cases3 { + prepare, args := ca.prepare, ca.args + dbt.MustExec(fmt.Sprintf("prepare stmt from '%v'", prepare)) + + var params []string + for i := range args { + param := 'a' + i + dbt.MustExec(fmt.Sprintf("set @%c=%v", param, args[i])) + params = append(params, fmt.Sprintf("@%c", param)) + } + + sqlStr := "execute stmt" + if len(params) > 0 { + sqlStr += " using " + sqlStr += strings.Join(params, ",") + } + if strings.HasPrefix(prepare, "select") { + mustQuery(t, dbt, sqlStr) + } else { + dbt.MustExec(sqlStr) + } + } + } + check = func() { + for _, ca := range cases3 { + checkFn(ca.prepare, ca.planRegexp) + } + } + ts.testCase(t, mc, execFn, check) + + // Test case for other statements + cases4 := []struct { + sql string + plan string + isQuery bool + }{ + {"begin", "", false}, + {"insert into t () values (),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),()", "", false}, + {"commit", "", false}, + {"analyze table t", "", false}, + {"explain analyze select sum(a+b) from t", ".*TableReader.*", true}, + {"trace select sum(b*a), sum(a+b) from t", "", true}, + {"set global tidb_stmt_summary_history_size=5;", "", false}, + } + execFn = func(db *sql.DB) { + dbt := testkit.NewDBTestKit(t, db) + for _, ca := range cases4 { + if ca.isQuery { + mustQuery(t, dbt, ca.sql) + } else { + dbt.MustExec(ca.sql) + } + } + } + check = func() { + for _, ca := range cases4 { + checkFn(ca.sql, ca.plan) + } + // check for internal SQL. + checkFn("replace into mysql.global_variables (variable_name,variable_value) values ('tidb_stmt_summary_history_size', '5')", "") + } + ts.testCase(t, mc, execFn, check) + + // Test case for multi-statement. + cases5 := []string{ + "delete from t limit 1;", + "update t set b=1 where b is null limit 1;", + "select sum(a+b*2) from t;", + } + multiStatement5 := strings.Join(cases5, "") + execFn = func(db *sql.DB) { + dbt := testkit.NewDBTestKit(t, db) + dbt.MustExec("SET tidb_multi_statement_mode='ON'") + dbt.MustExec(multiStatement5) + } + check = func() { + for _, sqlStr := range cases5 { + checkFn(sqlStr, ".*TableReader.*") + } + } + ts.testCase(t, mc, execFn, check) + + // Test case for multi-statement, but first statements execute failed + cases6 := []string{ + "delete from t_not_exist;", + "update t set a=1 where a is null limit 1;", + } + multiStatement6 := strings.Join(cases6, "") + execFn = func(db *sql.DB) { + dbt := testkit.NewDBTestKit(t, db) + dbt.MustExec("SET tidb_multi_statement_mode='ON'") + _, err := db.Exec(multiStatement6) + require.NotNil(t, err) + require.Equal(t, "Error 1146: Table 'topsql.t_not_exist' doesn't exist", err.Error()) + } + check = func() { + for i := 1; i < len(cases6); i++ { + sqlStr := cases6[i] + stats := mc.GetSQLStatsBySQL(sqlStr, false) + require.Equal(t, 0, len(stats), sqlStr) + } + } + ts.testCase(t, mc, execFn, check) + + // Test case for multi-statement, the first statements execute success but the second statement execute failed. + cases7 := []string{ + "update t set a=1 where a <0 limit 1;", + "delete from t_not_exist;", + } + multiStatement7 := strings.Join(cases7, "") + execFn = func(db *sql.DB) { + dbt := testkit.NewDBTestKit(t, db) + dbt.MustExec("SET tidb_multi_statement_mode='ON'") + _, err = db.Exec(multiStatement7) + require.NotNil(t, err) + require.Equal(t, "Error 1146 (42S02): Table 'topsql.t_not_exist' doesn't exist", err.Error()) + } + check = func() { + checkFn(cases7[0], "") // the first statement execute success, should have topsql data. + } + ts.testCase(t, mc, execFn, check) + + // Test case for statement with wrong syntax. + wrongSyntaxSQL := "select * froms t" + execFn = func(db *sql.DB) { + _, err = db.Exec(wrongSyntaxSQL) + require.NotNil(t, err) + require.Regexp(t, "Error 1064: You have an error in your SQL syntax...", err.Error()) + } + check = func() { + stats := mc.GetSQLStatsBySQL(wrongSyntaxSQL, false) + require.Equal(t, 0, len(stats), wrongSyntaxSQL) + } + ts.testCase(t, mc, execFn, check) + + // Test case for high cost of plan optimize. + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/planner/mockHighLoadForOptimize", "return")) + selectSQL := "select sum(a+b), count(distinct b) from t where a+b >0" + updateSQL := "update t set a=a+100 where a > 10000000" + selectInPlanSQL := "select * from t where exists (select 1 from t1 where t1.a = 1);" + execFn = func(db *sql.DB) { + dbt := testkit.NewDBTestKit(t, db) + mustQuery(t, dbt, selectSQL) + dbt.MustExec(updateSQL) + mustQuery(t, dbt, selectInPlanSQL) + } + check = func() { + checkFn(selectSQL, "") + checkFn(updateSQL, "") + selectCPUTime := mc.GetSQLCPUTimeBySQL(selectSQL) + updateCPUTime := mc.GetSQLCPUTimeBySQL(updateSQL) + require.Less(t, updateCPUTime, selectCPUTime) + checkFn(selectInPlanSQL, "") + } + ts.testCase(t, mc, execFn, check) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/planner/mockHighLoadForOptimize")) + + // Test case for DDL execute failed but should still have CPU data. + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/ddl/mockHighLoadForAddIndex", "return")) + dbt.MustExec(fmt.Sprintf("insert into t values (%v,%v), (%v, %v);", 2000, 1, 2001, 1)) + addIndexStr := "alter table t add unique index idx_b (b)" + execFn = func(db *sql.DB) { + dbt := testkit.NewDBTestKit(t, db) + dbt.MustExec("alter table t drop index if exists idx_b") + _, err := db.Exec(addIndexStr) + require.NotNil(t, err) + require.Equal(t, "Error 1062 (23000): Duplicate entry '1' for key 't.idx_b'", err.Error()) + } + check = func() { + checkFn(addIndexStr, "") + } + ts.testCase(t, mc, execFn, check) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/ddl/mockHighLoadForAddIndex")) + + // Test case for execute failed cause by storage error. + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/store/copr/handleTaskOnceError", `return(true)`)) + execFailedQuery := "select * from t where a*b < 1000" + execFn = func(db *sql.DB) { + _, err = db.Query(execFailedQuery) + require.NotNil(t, err) + require.Equal(t, "Error 1105 (HY000): mock handleTaskOnce error", err.Error()) + } + check = func() { + checkFn(execFailedQuery, "") + } + ts.testCase(t, mc, execFn, check) + require.NoError(t, failpoint.Disable("github.com/pingcap/tidb/store/copr/handleTaskOnceError")) +} + +func (ts *tidbTestTopSQLSuite) testCase(t *testing.T, mc *mockTopSQLTraceCPU.TopSQLCollector, execFn func(db *sql.DB), checkFn func()) { + var wg sync.WaitGroup + ctx, cancel := context.WithCancel(context.Background()) + wg.Add(1) + go func() { + defer wg.Done() + ts.loopExec(ctx, t, execFn) + }() + + checkFn() + cancel() + wg.Wait() + mc.Reset() +} + +func mustQuery(t *testing.T, dbt *testkit.DBTestKit, query string) { + rows := dbt.MustQuery(query) + for rows.Next() { + } + err := rows.Close() + require.NoError(t, err) +} + +type mockCollector struct { + f func(data stmtstats.StatementStatsMap) +} + +func newMockCollector(f func(data stmtstats.StatementStatsMap)) stmtstats.Collector { + return &mockCollector{f: f} +} + +func (c *mockCollector) CollectStmtStatsMap(data stmtstats.StatementStatsMap) { + c.f(data) +} + +func waitCollected(ch chan struct{}) { + select { + case <-ch: + case <-time.After(time.Second * 3): + } +} + +func TestTopSQLStatementStats(t *testing.T) { + ts, total, tagChecker, collectedNotifyCh := setupForTestTopSQLStatementStats(t) + + const ExecCountPerSQL = 2 + // Test for CRUD. + cases1 := []string{ + "insert into t values (%d, sleep(0.1))", + "update t set a = %[1]d + 1000 where a = %[1]d and sleep(0.1);", + "select a from t where b = %d and sleep(0.1);", + "select a from t where a = %d and sleep(0.1);", // test for point-get + "delete from t where a = %d and sleep(0.1);", + "insert into t values (%d, sleep(0.1)) on duplicate key update b = b+1", + } + var wg sync.WaitGroup + sqlDigests := map[stmtstats.BinaryDigest]string{} + for i, ca := range cases1 { + sqlStr := fmt.Sprintf(ca, i) + _, digest := parser.NormalizeDigest(sqlStr) + sqlDigests[stmtstats.BinaryDigest(digest.Bytes())] = sqlStr + } + wg.Add(1) + go func() { + defer wg.Done() + for _, ca := range cases1 { + db, err := sql.Open("mysql", ts.GetDSN()) + require.NoError(t, err) + dbt := testkit.NewDBTestKit(t, db) + dbt.MustExec("use stmtstats;") + for n := 0; n < ExecCountPerSQL; n++ { + sqlStr := fmt.Sprintf(ca, n) + if strings.HasPrefix(strings.ToLower(sqlStr), "select") { + mustQuery(t, dbt, sqlStr) + } else { + dbt.MustExec(sqlStr) + } + } + err = db.Close() + require.NoError(t, err) + } + }() + + // Test for prepare stmt/execute stmt + cases2 := []struct { + prepare string + execStmt string + setSQLsGen func(idx int) []string + execSQL string + }{ + { + prepare: "prepare stmt from 'insert into t2 values (?, sleep(?))';", + execStmt: "insert into t2 values (1, sleep(0.1))", + setSQLsGen: func(idx int) []string { + return []string{fmt.Sprintf("set @a=%v", idx), "set @b=0.1"} + }, + execSQL: "execute stmt using @a, @b;", + }, + { + prepare: "prepare stmt from 'update t2 set a = a + 1000 where a = ? and sleep(?);';", + execStmt: "update t2 set a = a + 1000 where a = 1 and sleep(0.1);", + setSQLsGen: func(idx int) []string { + return []string{fmt.Sprintf("set @a=%v", idx), "set @b=0.1"} + }, + execSQL: "execute stmt using @a, @b;", + }, + { + // test for point-get + prepare: "prepare stmt from 'select a, sleep(?) from t2 where a = ?';", + execStmt: "select a, sleep(?) from t2 where a = ?", + setSQLsGen: func(idx int) []string { + return []string{"set @a=0.1", fmt.Sprintf("set @b=%v", idx)} + }, + execSQL: "execute stmt using @a, @b;", + }, + { + prepare: "prepare stmt from 'select a, sleep(?) from t2 where b = ?';", + execStmt: "select a, sleep(?) from t2 where b = ?", + setSQLsGen: func(idx int) []string { + return []string{"set @a=0.1", fmt.Sprintf("set @b=%v", idx)} + }, + execSQL: "execute stmt using @a, @b;", + }, + { + prepare: "prepare stmt from 'delete from t2 where sleep(?) and a = ?';", + execStmt: "delete from t2 where sleep(0.1) and a = 1", + setSQLsGen: func(idx int) []string { + return []string{"set @a=0.1", fmt.Sprintf("set @b=%v", idx)} + }, + execSQL: "execute stmt using @a, @b;", + }, + { + prepare: "prepare stmt from 'insert into t2 values (?, sleep(?)) on duplicate key update b = b+1';", + execStmt: "insert into t2 values (1, sleep(0.1)) on duplicate key update b = b+1", + setSQLsGen: func(idx int) []string { + return []string{fmt.Sprintf("set @a=%v", idx), "set @b=0.1"} + }, + execSQL: "execute stmt using @a, @b;", + }, + { + prepare: "prepare stmt from 'set global tidb_enable_top_sql = (? = sleep(?))';", + execStmt: "set global tidb_enable_top_sql = (0 = sleep(0.1))", + setSQLsGen: func(idx int) []string { + return []string{"set @a=0", "set @b=0.1"} + }, + execSQL: "execute stmt using @a, @b;", + }, + } + for _, ca := range cases2 { + _, digest := parser.NormalizeDigest(ca.execStmt) + sqlDigests[stmtstats.BinaryDigest(digest.Bytes())] = ca.execStmt + } + wg.Add(1) + go func() { + defer wg.Done() + for _, ca := range cases2 { + db, err := sql.Open("mysql", ts.GetDSN()) + require.NoError(t, err) + dbt := testkit.NewDBTestKit(t, db) + dbt.MustExec("use stmtstats;") + // prepare stmt + dbt.MustExec(ca.prepare) + for n := 0; n < ExecCountPerSQL; n++ { + setSQLs := ca.setSQLsGen(n) + for _, setSQL := range setSQLs { + dbt.MustExec(setSQL) + } + if strings.HasPrefix(strings.ToLower(ca.execStmt), "select") { + mustQuery(t, dbt, ca.execSQL) + } else { + dbt.MustExec(ca.execSQL) + } + } + err = db.Close() + require.NoError(t, err) + } + }() + + // Test for prepare by db client prepare/exec interface. + cases3 := []struct { + prepare string + execStmt string + argsGen func(idx int) []interface{} + }{ + { + prepare: "insert into t3 values (?, sleep(?))", + argsGen: func(idx int) []interface{} { + return []interface{}{idx, 0.1} + }, + }, + { + prepare: "update t3 set a = a + 1000 where a = ? and sleep(?)", + argsGen: func(idx int) []interface{} { + return []interface{}{idx, 0.1} + }, + }, + { + // test for point-get + prepare: "select a, sleep(?) from t3 where a = ?", + argsGen: func(idx int) []interface{} { + return []interface{}{0.1, idx} + }, + }, + { + prepare: "select a, sleep(?) from t3 where b = ?", + argsGen: func(idx int) []interface{} { + return []interface{}{0.1, idx} + }, + }, + { + prepare: "delete from t3 where sleep(?) and a = ?", + argsGen: func(idx int) []interface{} { + return []interface{}{0.1, idx} + }, + }, + { + prepare: "insert into t3 values (?, sleep(?)) on duplicate key update b = b+1", + argsGen: func(idx int) []interface{} { + return []interface{}{idx, 0.1} + }, + }, + { + prepare: "set global tidb_enable_1pc = (? = sleep(?))", + argsGen: func(idx int) []interface{} { + return []interface{}{0, 0.1} + }, + }, + } + for _, ca := range cases3 { + _, digest := parser.NormalizeDigest(ca.prepare) + sqlDigests[stmtstats.BinaryDigest(digest.Bytes())] = ca.prepare + } + wg.Add(1) + go func() { + defer wg.Done() + for _, ca := range cases3 { + db, err := sql.Open("mysql", ts.GetDSN()) + require.NoError(t, err) + dbt := testkit.NewDBTestKit(t, db) + dbt.MustExec("use stmtstats;") + // prepare stmt + stmt, err := db.Prepare(ca.prepare) + require.NoError(t, err) + for n := 0; n < ExecCountPerSQL; n++ { + args := ca.argsGen(n) + if strings.HasPrefix(strings.ToLower(ca.prepare), "select") { + row, err := stmt.Query(args...) + require.NoError(t, err) + err = row.Close() + require.NoError(t, err) + } else { + _, err := stmt.Exec(args...) + require.NoError(t, err) + } + } + err = db.Close() + require.NoError(t, err) + } + }() + + wg.Wait() + // Wait for collect. + waitCollected(collectedNotifyCh) + + found := 0 + for digest, item := range total { + if sqlStr, ok := sqlDigests[digest.SQLDigest]; ok { + found++ + require.Equal(t, uint64(ExecCountPerSQL), item.ExecCount, sqlStr) + require.Equal(t, uint64(ExecCountPerSQL), item.DurationCount, sqlStr) + require.True(t, item.SumDurationNs > uint64(time.Millisecond*100*ExecCountPerSQL), sqlStr) + require.True(t, item.SumDurationNs < uint64(time.Millisecond*300*ExecCountPerSQL), sqlStr) + if strings.HasPrefix(sqlStr, "set global") { + // set global statement use internal SQL to change global variable, so itself doesn't have KV request. + continue + } + var kvSum uint64 + for _, kvCount := range item.KvStatsItem.KvExecCount { + kvSum += kvCount + } + require.Equal(t, uint64(ExecCountPerSQL), kvSum) + tagChecker.checkExist(t, digest.SQLDigest, sqlStr) + } + } + require.Equal(t, len(sqlDigests), found) + require.Equal(t, 20, found) +} + +type resourceTagChecker struct { + sync.Mutex + sqlDigest2Reqs map[stmtstats.BinaryDigest]map[tikvrpc.CmdType]struct{} +} + +func (c *resourceTagChecker) checkExist(t *testing.T, digest stmtstats.BinaryDigest, sqlStr string) { + if strings.HasPrefix(sqlStr, "set global") { + // `set global` statement will use another internal sql to execute, so `set global` statement won't + // send RPC request. + return + } + if strings.HasPrefix(sqlStr, "trace") { + // `trace` statement will use another internal sql to execute, so remove the `trace` prefix before check. + _, sqlDigest := parser.NormalizeDigest(strings.TrimPrefix(sqlStr, "trace")) + digest = stmtstats.BinaryDigest(sqlDigest.Bytes()) + } + + c.Lock() + defer c.Unlock() + _, ok := c.sqlDigest2Reqs[digest] + require.True(t, ok, sqlStr) +} + +func (c *resourceTagChecker) checkReqExist(t *testing.T, digest stmtstats.BinaryDigest, sqlStr string, reqs ...tikvrpc.CmdType) { + if len(reqs) == 0 { + return + } + c.Lock() + defer c.Unlock() + reqMap, ok := c.sqlDigest2Reqs[digest] + require.True(t, ok, sqlStr) + for _, req := range reqs { + _, ok := reqMap[req] + require.True(t, ok, fmt.Sprintf("sql: %v, expect: %v, got: %v", sqlStr, reqs, reqMap)) + } +} + +func setupForTestTopSQLStatementStats(t *testing.T) (*tidbTestSuite, stmtstats.StatementStatsMap, *resourceTagChecker, chan struct{}) { + // Prepare stmt stats. + stmtstats.SetupAggregator() + + // Register stmt stats collector. + var mu sync.Mutex + collectedNotifyCh := make(chan struct{}) + total := stmtstats.StatementStatsMap{} + mockCollector := newMockCollector(func(data stmtstats.StatementStatsMap) { + mu.Lock() + defer mu.Unlock() + total.Merge(data) + select { + case collectedNotifyCh <- struct{}{}: + default: + } + }) + stmtstats.RegisterCollector(mockCollector) + + ts := createTidbTestSuite(t) + + db, err := sql.Open("mysql", ts.GetDSN()) + require.NoError(t, err) + defer func() { + err := db.Close() + require.NoError(t, err) + }() + + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/domain/skipLoadSysVarCacheLoop", `return(true)`)) + require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/store/mockstore/unistore/unistoreRPCClientSendHook", `return(true)`)) + + dbt := testkit.NewDBTestKit(t, db) + dbt.MustExec("drop database if exists stmtstats") + dbt.MustExec("create database stmtstats") + dbt.MustExec("use stmtstats;") + dbt.MustExec("create table t (a int, b int, unique index idx(a));") + dbt.MustExec("create table t2 (a int, b int, unique index idx(a));") + dbt.MustExec("create table t3 (a int, b int, unique index idx(a));") + + // Enable TopSQL + topsqlstate.EnableTopSQL() + config.UpdateGlobal(func(conf *config.Config) { + conf.TopSQL.ReceiverAddress = "mock-agent" + }) + + tagChecker := &resourceTagChecker{ + sqlDigest2Reqs: make(map[stmtstats.BinaryDigest]map[tikvrpc.CmdType]struct{}), + } + unistoreRPCClientSendHook := func(req *tikvrpc.Request) { + tag := req.GetResourceGroupTag() + if len(tag) == 0 || ddlutil.IsInternalResourceGroupTaggerForTopSQL(tag) { + // Ignore for internal background request. + return + } + sqlDigest, err := resourcegrouptag.DecodeResourceGroupTag(tag) + require.NoError(t, err) + tagChecker.Lock() + defer tagChecker.Unlock() + + reqMap, ok := tagChecker.sqlDigest2Reqs[stmtstats.BinaryDigest(sqlDigest)] + if !ok { + reqMap = make(map[tikvrpc.CmdType]struct{}) + } + reqMap[req.Type] = struct{}{} + tagChecker.sqlDigest2Reqs[stmtstats.BinaryDigest(sqlDigest)] = reqMap + } + unistore.UnistoreRPCClientSendHook.Store(&unistoreRPCClientSendHook) + + t.Cleanup(func() { + stmtstats.UnregisterCollector(mockCollector) + err = failpoint.Disable("github.com/pingcap/tidb/domain/skipLoadSysVarCacheLoop") + require.NoError(t, err) + err = failpoint.Disable("github.com/pingcap/tidb/store/mockstore/unistore/unistoreRPCClientSendHook") + require.NoError(t, err) + stmtstats.CloseAggregator() + view.Stop() + }) + + return ts, total, tagChecker, collectedNotifyCh +} + +func TestTopSQLStatementStats2(t *testing.T) { + ts, total, tagChecker, collectedNotifyCh := setupForTestTopSQLStatementStats(t) + + const ExecCountPerSQL = 3 + sqlDigests := map[stmtstats.BinaryDigest]string{} + + // Test case for other statements + cases4 := []struct { + sql string + plan string + isQuery bool + }{ + {"insert into t () values (),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),()", "", false}, + {"analyze table t", "", false}, + {"explain analyze select sum(a+b) from t", ".*TableReader.*", true}, + {"trace select sum(b*a), sum(a+b) from t", "", true}, + {"set global tidb_stmt_summary_history_size=5;", "", false}, + {"select * from stmtstats.t where exists (select 1 from stmtstats.t2 where t2.a = 1);", ".*TableReader.*", true}, + } + executeCaseFn := func(execFn func(db *sql.DB)) { + db, err := sql.Open("mysql", ts.GetDSN()) + require.NoError(t, err) + dbt := testkit.NewDBTestKit(t, db) + dbt.MustExec("use stmtstats;") + require.NoError(t, err) + + for n := 0; n < ExecCountPerSQL; n++ { + execFn(db) + } + err = db.Close() + require.NoError(t, err) + } + execFn := func(db *sql.DB) { + dbt := testkit.NewDBTestKit(t, db) + for _, ca := range cases4 { + if ca.isQuery { + mustQuery(t, dbt, ca.sql) + } else { + dbt.MustExec(ca.sql) + } + } + } + for _, ca := range cases4 { + _, digest := parser.NormalizeDigest(ca.sql) + sqlDigests[stmtstats.BinaryDigest(digest.Bytes())] = ca.sql + } + executeCaseFn(execFn) + + // Test case for multi-statement. + cases5 := []string{ + "delete from t limit 1;", + "update t set b=1 where b is null limit 1;", + "select sum(a+b*2) from t;", + } + multiStatement5 := strings.Join(cases5, "") + // Test case for multi-statement, but first statements execute failed + cases6 := []string{ + "delete from t6_not_exist;", + "update t set a=1 where a is null limit 1;", + } + multiStatement6 := strings.Join(cases6, "") + // Test case for multi-statement, the first statements execute success but the second statement execute failed. + cases7 := []string{ + "update t set a=1 where a <0 limit 1;", + "delete from t7_not_exist;", + } + // Test case for DDL. + cases8 := []string{ + "create table if not exists t10 (a int, b int)", + "alter table t drop index if exists idx_b", + "alter table t add index idx_b (b)", + } + multiStatement7 := strings.Join(cases7, "") + execFn = func(db *sql.DB) { + dbt := testkit.NewDBTestKit(t, db) + dbt.MustExec("SET tidb_multi_statement_mode='ON'") + dbt.MustExec(multiStatement5) + + _, err := db.Exec(multiStatement6) + require.NotNil(t, err) + require.Equal(t, "Error 1146 (42S02): Table 'stmtstats.t6_not_exist' doesn't exist", err.Error()) + + _, err = db.Exec(multiStatement7) + require.NotNil(t, err) + require.Equal(t, "Error 1146 (42S02): Table 'stmtstats.t7_not_exist' doesn't exist", err.Error()) + + for _, ca := range cases8 { + dbt.MustExec(ca) + } + } + executeCaseFn(execFn) + sqlStrs := append([]string{}, cases5...) + sqlStrs = append(sqlStrs, cases7[0]) + sqlStrs = append(sqlStrs, cases8...) + for _, sqlStr := range sqlStrs { + _, digest := parser.NormalizeDigest(sqlStr) + sqlDigests[stmtstats.BinaryDigest(digest.Bytes())] = sqlStr + } + + // Wait for collect. + waitCollected(collectedNotifyCh) + + foundMap := map[stmtstats.BinaryDigest]string{} + for digest, item := range total { + if sqlStr, ok := sqlDigests[digest.SQLDigest]; ok { + require.Equal(t, uint64(ExecCountPerSQL), item.ExecCount, sqlStr) + require.True(t, item.SumDurationNs > 1, sqlStr) + foundMap[digest.SQLDigest] = sqlStr + tagChecker.checkExist(t, digest.SQLDigest, sqlStr) + // The special check uses to test the issue #33202. + if strings.Contains(strings.ToLower(sqlStr), "add index") { + tagChecker.checkReqExist(t, digest.SQLDigest, sqlStr, tikvrpc.CmdScan) + } + } + } + require.Equal(t, len(sqlDigests), len(foundMap), fmt.Sprintf("%v !=\n %v", sqlDigests, foundMap)) +} + +func TestTopSQLStatementStats3(t *testing.T) { + ts, total, tagChecker, collectedNotifyCh := setupForTestTopSQLStatementStats(t) + + err := failpoint.Enable("github.com/pingcap/tidb/executor/mockSleepInTableReaderNext", "return(2000)") + require.NoError(t, err) + defer func() { + _ = failpoint.Disable("github.com/pingcap/tidb/executor/mockSleepInTableReaderNext") + }() + + cases := []string{ + "select count(a+b) from stmtstats.t", + "select * from stmtstats.t where b is null", + "update stmtstats.t set b = 1 limit 10", + "delete from stmtstats.t limit 1", + } + var wg sync.WaitGroup + sqlDigests := map[stmtstats.BinaryDigest]string{} + for _, ca := range cases { + wg.Add(1) + go func(sqlStr string) { + defer wg.Done() + db, err := sql.Open("mysql", ts.GetDSN()) + require.NoError(t, err) + dbt := testkit.NewDBTestKit(t, db) + require.NoError(t, err) + if strings.HasPrefix(sqlStr, "select") { + mustQuery(t, dbt, sqlStr) + } else { + dbt.MustExec(sqlStr) + } + err = db.Close() + require.NoError(t, err) + }(ca) + _, digest := parser.NormalizeDigest(ca) + sqlDigests[stmtstats.BinaryDigest(digest.Bytes())] = ca + } + // Wait for collect. + waitCollected(collectedNotifyCh) + + foundMap := map[stmtstats.BinaryDigest]string{} + for digest, item := range total { + if sqlStr, ok := sqlDigests[digest.SQLDigest]; ok { + // since the SQL doesn't execute finish, the ExecCount should be recorded, + // but the DurationCount and SumDurationNs should be 0. + require.Equal(t, uint64(1), item.ExecCount, sqlStr) + require.Equal(t, uint64(0), item.DurationCount, sqlStr) + require.Equal(t, uint64(0), item.SumDurationNs, sqlStr) + foundMap[digest.SQLDigest] = sqlStr + } + } + + // wait sql execute finish. + wg.Wait() + // Wait for collect. + waitCollected(collectedNotifyCh) + + for digest, item := range total { + if sqlStr, ok := sqlDigests[digest.SQLDigest]; ok { + require.Equal(t, uint64(1), item.ExecCount, sqlStr) + require.Equal(t, uint64(1), item.DurationCount, sqlStr) + require.Less(t, uint64(0), item.SumDurationNs, sqlStr) + foundMap[digest.SQLDigest] = sqlStr + tagChecker.checkExist(t, digest.SQLDigest, sqlStr) + } + } +} + +func TestTopSQLStatementStats4(t *testing.T) { + ts, total, tagChecker, collectedNotifyCh := setupForTestTopSQLStatementStats(t) + + err := failpoint.Enable("github.com/pingcap/tidb/executor/mockSleepInTableReaderNext", "return(2000)") + require.NoError(t, err) + defer func() { + _ = failpoint.Disable("github.com/pingcap/tidb/executor/mockSleepInTableReaderNext") + }() + + cases := []struct { + prepare string + sql string + args []interface{} + }{ + {prepare: "select count(a+b) from stmtstats.t", sql: "select count(a+b) from stmtstats.t"}, + {prepare: "select * from stmtstats.t where b is null", sql: "select * from stmtstats.t where b is null"}, + {prepare: "update stmtstats.t set b = ? limit ?", sql: "update stmtstats.t set b = 1 limit 10", args: []interface{}{1, 10}}, + {prepare: "delete from stmtstats.t limit ?", sql: "delete from stmtstats.t limit 1", args: []interface{}{1}}, + } + var wg sync.WaitGroup + sqlDigests := map[stmtstats.BinaryDigest]string{} + for _, ca := range cases { + wg.Add(1) + go func(prepare string, args []interface{}) { + defer wg.Done() + db, err := sql.Open("mysql", ts.GetDSN()) + require.NoError(t, err) + stmt, err := db.Prepare(prepare) + require.NoError(t, err) + if strings.HasPrefix(prepare, "select") { + rows, err := stmt.Query(args...) + require.NoError(t, err) + for rows.Next() { + } + err = rows.Close() + require.NoError(t, err) + } else { + _, err := stmt.Exec(args...) + require.NoError(t, err) + } + err = db.Close() + require.NoError(t, err) + }(ca.prepare, ca.args) + _, digest := parser.NormalizeDigest(ca.sql) + sqlDigests[stmtstats.BinaryDigest(digest.Bytes())] = ca.sql + } + // Wait for collect. + waitCollected(collectedNotifyCh) + + foundMap := map[stmtstats.BinaryDigest]string{} + for digest, item := range total { + if sqlStr, ok := sqlDigests[digest.SQLDigest]; ok { + // since the SQL doesn't execute finish, the ExecCount should be recorded, + // but the DurationCount and SumDurationNs should be 0. + require.Equal(t, uint64(1), item.ExecCount, sqlStr) + require.Equal(t, uint64(0), item.DurationCount, sqlStr) + require.Equal(t, uint64(0), item.SumDurationNs, sqlStr) + foundMap[digest.SQLDigest] = sqlStr + } + } + + // wait sql execute finish. + wg.Wait() + // Wait for collect. + waitCollected(collectedNotifyCh) + + for digest, item := range total { + if sqlStr, ok := sqlDigests[digest.SQLDigest]; ok { + require.Equal(t, uint64(1), item.ExecCount, sqlStr) + require.Equal(t, uint64(1), item.DurationCount, sqlStr) + require.Less(t, uint64(0), item.SumDurationNs, sqlStr) + foundMap[digest.SQLDigest] = sqlStr + tagChecker.checkExist(t, digest.SQLDigest, sqlStr) + } + } +} + +func TestTopSQLResourceTag(t *testing.T) { + ts, _, tagChecker, _ := setupForTestTopSQLStatementStats(t) + defer func() { + topsqlstate.DisableTopSQL() + }() + + loadDataFile, err := os.CreateTemp("", "load_data_test0.csv") + require.NoError(t, err) + defer func() { + path := loadDataFile.Name() + err = loadDataFile.Close() + require.NoError(t, err) + err = os.Remove(path) + require.NoError(t, err) + }() + _, err = loadDataFile.WriteString( + "31 31\n" + + "32 32\n" + + "33 33\n") + require.NoError(t, err) + + // Test case for other statements + cases := []struct { + sql string + isQuery bool + reqs []tikvrpc.CmdType + }{ + // Test for curd. + {"insert into t values (1,1), (3,3)", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit}}, + {"insert into t values (1,2) on duplicate key update a = 2", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit, tikvrpc.CmdBatchGet}}, + {"update t set b=b+1 where a=3", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit, tikvrpc.CmdGet}}, + {"update t set b=b+1 where a>1", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit, tikvrpc.CmdCop}}, + {"delete from t where a=3", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit, tikvrpc.CmdGet}}, + {"delete from t where a>1", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit, tikvrpc.CmdCop}}, + {"insert ignore into t values (2,2), (3,3)", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit, tikvrpc.CmdBatchGet}}, + {"select * from t where a in (1,2,3,4)", true, []tikvrpc.CmdType{tikvrpc.CmdBatchGet}}, + {"select * from t where a = 1", true, []tikvrpc.CmdType{tikvrpc.CmdGet}}, + {"select * from t where b > 0", true, []tikvrpc.CmdType{tikvrpc.CmdCop}}, + {"replace into t values (2,2), (4,4)", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit, tikvrpc.CmdBatchGet}}, + + // Test for DDL + {"create database test_db0", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit}}, + {"create table test_db0.test_t0 (a int, b int, index idx(a))", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit}}, + {"create table test_db0.test_t1 (a int, b int, index idx(a))", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit}}, + {"alter table test_db0.test_t0 add column c int", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit}}, + {"drop table test_db0.test_t0", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit}}, + {"drop database test_db0", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit}}, + {"alter table t modify column b double", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit, tikvrpc.CmdScan, tikvrpc.CmdCop}}, + {"alter table t add index idx2 (b,a)", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit, tikvrpc.CmdScan, tikvrpc.CmdCop}}, + {"alter table t drop index idx2", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit}}, + + // Test for transaction + {"begin", false, nil}, + {"insert into t2 values (10,10), (11,11)", false, nil}, + {"insert ignore into t2 values (20,20), (21,21)", false, []tikvrpc.CmdType{tikvrpc.CmdBatchGet}}, + {"commit", false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit}}, + + // Test for other statements. + {"set @@global.tidb_enable_1pc = 1", false, nil}, + {fmt.Sprintf("load data local infile %q into table t2", loadDataFile.Name()), false, []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit, tikvrpc.CmdBatchGet}}, + {"admin check table t", false, nil}, + {"admin check index t idx", false, nil}, + {"admin recover index t idx", false, []tikvrpc.CmdType{tikvrpc.CmdBatchGet}}, + {"admin cleanup index t idx", false, []tikvrpc.CmdType{tikvrpc.CmdBatchGet}}, + } + + internalCases := []struct { + sql string + reqs []tikvrpc.CmdType + }{ + {"replace into mysql.global_variables (variable_name,variable_value) values ('tidb_enable_1pc', '1')", []tikvrpc.CmdType{tikvrpc.CmdPrewrite, tikvrpc.CmdCommit, tikvrpc.CmdBatchGet}}, + {"select /*+ read_from_storage(tikv[`stmtstats`.`t`]) */ bit_xor(crc32(md5(concat_ws(0x2, `_tidb_rowid`, `a`)))), ((cast(crc32(md5(concat_ws(0x2, `_tidb_rowid`))) as signed) - 0) div 1 % 1024), count(*) from `stmtstats`.`t` use index() where 0 = 0 group by ((cast(crc32(md5(concat_ws(0x2, `_tidb_rowid`))) as signed) - 0) div 1 % 1024)", []tikvrpc.CmdType{tikvrpc.CmdCop}}, + {"select bit_xor(crc32(md5(concat_ws(0x2, `_tidb_rowid`, `a`)))), ((cast(crc32(md5(concat_ws(0x2, `_tidb_rowid`))) as signed) - 0) div 1 % 1024), count(*) from `stmtstats`.`t` use index(`idx`) where 0 = 0 group by ((cast(crc32(md5(concat_ws(0x2, `_tidb_rowid`))) as signed) - 0) div 1 % 1024)", []tikvrpc.CmdType{tikvrpc.CmdCop}}, + {"select /*+ read_from_storage(tikv[`stmtstats`.`t`]) */ bit_xor(crc32(md5(concat_ws(0x2, `_tidb_rowid`, `a`)))), ((cast(crc32(md5(concat_ws(0x2, `_tidb_rowid`))) as signed) - 0) div 1 % 1024), count(*) from `stmtstats`.`t` use index() where 0 = 0 group by ((cast(crc32(md5(concat_ws(0x2, `_tidb_rowid`))) as signed) - 0) div 1 % 1024)", []tikvrpc.CmdType{tikvrpc.CmdCop}}, + {"select bit_xor(crc32(md5(concat_ws(0x2, `_tidb_rowid`, `a`)))), ((cast(crc32(md5(concat_ws(0x2, `_tidb_rowid`))) as signed) - 0) div 1 % 1024), count(*) from `stmtstats`.`t` use index(`idx`) where 0 = 0 group by ((cast(crc32(md5(concat_ws(0x2, `_tidb_rowid`))) as signed) - 0) div 1 % 1024)", []tikvrpc.CmdType{tikvrpc.CmdCop}}, + } + executeCaseFn := func(execFn func(db *sql.DB)) { + dsn := ts.GetDSN(func(config *mysql.Config) { + config.AllowAllFiles = true + config.Params["sql_mode"] = "''" + }) + db, err := sql.Open("mysql", dsn) + require.NoError(t, err) + dbt := testkit.NewDBTestKit(t, db) + dbt.MustExec("use stmtstats;") + require.NoError(t, err) + + execFn(db) + err = db.Close() + require.NoError(t, err) + } + execFn := func(db *sql.DB) { + dbt := testkit.NewDBTestKit(t, db) + for _, ca := range cases { + if ca.isQuery { + mustQuery(t, dbt, ca.sql) + } else { + dbt.MustExec(ca.sql) + } + } + } + executeCaseFn(execFn) + + for _, ca := range cases { + _, digest := parser.NormalizeDigest(ca.sql) + tagChecker.checkReqExist(t, stmtstats.BinaryDigest(digest.Bytes()), ca.sql, ca.reqs...) + } + for _, ca := range internalCases { + _, digest := parser.NormalizeDigest(ca.sql) + tagChecker.checkReqExist(t, stmtstats.BinaryDigest(digest.Bytes()), ca.sql, ca.reqs...) + } +} + +func (ts *tidbTestTopSQLSuite) loopExec(ctx context.Context, t *testing.T, fn func(db *sql.DB)) { + db, err := sql.Open("mysql", ts.GetDSN()) + require.NoError(t, err, "Error connecting") + defer func() { + err := db.Close() + require.NoError(t, err) + }() + dbt := testkit.NewDBTestKit(t, db) + dbt.MustExec("use topsql;") + for { + select { + case <-ctx.Done(): + return + default: + } + fn(db) + } +} + +func TestLocalhostClientMapping(t *testing.T) { + tempDir := t.TempDir() + socketFile := tempDir + "/tidbtest.sock" // Unix Socket does not work on Windows, so '/' should be OK + + cli := testserverclient.NewTestServerClient() + cfg := util2.NewTestConfig() + cfg.Socket = socketFile + cfg.Port = cli.Port + cfg.Status.ReportStatus = false + + ts := createTidbTestSuite(t) + + server, err := server2.NewServer(cfg, ts.tidbdrv) + require.NoError(t, err) + server.SetDomain(ts.domain) + cli.Port = testutil.GetPortFromTCPAddr(server.ListenAddr()) + go func() { + err := server.Run() + require.NoError(t, err) + }() + defer server.Close() + cli.WaitUntilServerCanConnect() + + cli.Port = testutil.GetPortFromTCPAddr(server.ListenAddr()) + // Create a db connection for root + db, err := sql.Open("mysql", cli.GetDSN(func(config *mysql.Config) { + config.User = "root" + config.Net = "unix" + config.DBName = "test" + config.Addr = socketFile + })) + require.NoErrorf(t, err, "Open failed") + err = db.Ping() + require.NoErrorf(t, err, "Ping failed") + defer db.Close() + dbt := testkit.NewDBTestKit(t, db) + rows := dbt.MustQuery("select user()") + cli.CheckRows(t, rows, "root@localhost") + require.NoError(t, rows.Close()) + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION") + require.NoError(t, rows.Close()) + + dbt.MustExec("CREATE USER 'localhostuser'@'localhost'") + dbt.MustExec("CREATE USER 'localhostuser'@'%'") + defer func() { + dbt.MustExec("DROP USER IF EXISTS 'localhostuser'@'%'") + dbt.MustExec("DROP USER IF EXISTS 'localhostuser'@'localhost'") + dbt.MustExec("DROP USER IF EXISTS 'localhostuser'@'127.0.0.1'") + }() + + dbt.MustExec("GRANT SELECT ON test.* TO 'localhostuser'@'%'") + dbt.MustExec("GRANT SELECT,UPDATE ON test.* TO 'localhostuser'@'localhost'") + + // Test with loopback interface - Should get access to localhostuser@localhost! + cli.RunTests(t, func(config *mysql.Config) { + config.User = "localhostuser" + config.DBName = "test" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("select user()") + // NOTICE: this is not compatible with MySQL! (MySQL would report localhostuser@localhost also for 127.0.0.1) + cli.CheckRows(t, rows, "localhostuser@127.0.0.1") + require.NoError(t, rows.Close()) + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT USAGE ON *.* TO 'localhostuser'@'localhost'\nGRANT SELECT,UPDATE ON `test`.* TO 'localhostuser'@'localhost'") + require.NoError(t, rows.Close()) + }) + + dbt.MustExec("DROP USER IF EXISTS 'localhostuser'@'localhost'") + dbt.MustExec("CREATE USER 'localhostuser'@'127.0.0.1'") + dbt.MustExec("GRANT SELECT,UPDATE ON test.* TO 'localhostuser'@'127.0.0.1'") + // Test with unix domain socket file connection - Should get access to '%' + cli.RunTests(t, func(config *mysql.Config) { + config.Net = "unix" + config.Addr = socketFile + config.User = "localhostuser" + config.DBName = "test" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("select user()") + cli.CheckRows(t, rows, "localhostuser@localhost") + require.NoError(t, rows.Close()) + rows = dbt.MustQuery("show grants") + cli.CheckRows(t, rows, "GRANT USAGE ON *.* TO 'localhostuser'@'%'\nGRANT SELECT ON `test`.* TO 'localhostuser'@'%'") + require.NoError(t, rows.Close()) + }) + + // Test if only localhost exists + dbt.MustExec("DROP USER 'localhostuser'@'%'") + dbSocket, err := sql.Open("mysql", cli.GetDSN(func(config *mysql.Config) { + config.User = "localhostuser" + config.Net = "unix" + config.DBName = "test" + config.Addr = socketFile + })) + require.NoErrorf(t, err, "Open failed") + defer dbSocket.Close() + err = dbSocket.Ping() + require.Errorf(t, err, "Connection successful without matching host for unix domain socket!") +} + +func TestRcReadCheckTS(t *testing.T) { + ts := createTidbTestSuite(t) + + db, err := sql.Open("mysql", ts.GetDSN()) + require.NoError(t, err) + defer func() { + err := db.Close() + require.NoError(t, err) + }() + + db2, err := sql.Open("mysql", ts.GetDSN()) + require.NoError(t, err) + defer func() { + err := db2.Close() + require.NoError(t, err) + }() + tk2 := testkit.NewDBTestKit(t, db2) + tk2.MustExec("set @@tidb_enable_async_commit = 0") + tk2.MustExec("set @@tidb_enable_1pc = 0") + + cli := testserverclient.NewTestServerClient() + + tk := testkit.NewDBTestKit(t, db) + tk.MustExec("drop table if exists t1") + tk.MustExec("create table t1(c1 int key, c2 int)") + tk.MustExec("insert into t1 values(1, 10), (2, 20), (3, 30)") + + tk.MustExec(`set tidb_rc_read_check_ts = 'on';`) + tk.MustExec(`set tx_isolation = 'READ-COMMITTED';`) + tk.MustExec("begin pessimistic") + // Test point get retry. + rows := tk.MustQuery("select * from t1 where c1 = 1") + cli.CheckRows(t, rows, "1 10") + tk2.MustExec("update t1 set c2 = c2 + 1") + rows = tk.MustQuery("select * from t1 where c1 = 1") + cli.CheckRows(t, rows, "1 11") + // Test batch point get retry. + rows = tk.MustQuery("select * from t1 where c1 in (1, 3)") + cli.CheckRows(t, rows, "1 11", "3 31") + tk2.MustExec("update t1 set c2 = c2 + 1") + rows = tk.MustQuery("select * from t1 where c1 in (1, 3)") + cli.CheckRows(t, rows, "1 12", "3 32") + // Test scan retry. + rows = tk.MustQuery("select * from t1") + cli.CheckRows(t, rows, "1 12", "2 22", "3 32") + tk2.MustExec("update t1 set c2 = c2 + 1") + rows = tk.MustQuery("select * from t1") + cli.CheckRows(t, rows, "1 13", "2 23", "3 33") + // Test reverse scan retry. + rows = tk.MustQuery("select * from t1 order by c1 desc") + cli.CheckRows(t, rows, "3 33", "2 23", "1 13") + tk2.MustExec("update t1 set c2 = c2 + 1") + rows = tk.MustQuery("select * from t1 order by c1 desc") + cli.CheckRows(t, rows, "3 34", "2 24", "1 14") + + // Test retry caused by ongoing prewrite lock. + // As the `defaultLockTTL` is 3s and it's difficult to change it here, the lock + // test is implemented in the uft test cases. +} + +type connEventLogs struct { + sync.Mutex + types []extension.ConnEventTp + infos []extension.ConnEventInfo +} + +func (l *connEventLogs) add(tp extension.ConnEventTp, info *extension.ConnEventInfo) { + l.Lock() + defer l.Unlock() + l.types = append(l.types, tp) + l.infos = append(l.infos, *info) +} + +func (l *connEventLogs) reset() { + l.Lock() + defer l.Unlock() + l.types = l.types[:0] + l.infos = l.infos[:0] +} + +func (l *connEventLogs) check(fn func()) { + l.Lock() + defer l.Unlock() + fn() +} + +func (l *connEventLogs) waitEvent(tp extension.ConnEventTp) error { + totalSleep := 0 + for { + l.Lock() + if l.types[len(l.types)-1] == tp { + l.Unlock() + return nil + } + l.Unlock() + if totalSleep >= 10000 { + break + } + time.Sleep(time.Millisecond * 100) + totalSleep += 100 + } + return errors.New("timeout") +} + +func TestExtensionConnEvent(t *testing.T) { + defer extension.Reset() + extension.Reset() + + logs := &connEventLogs{} + require.NoError(t, extension.Register("test", extension.WithSessionHandlerFactory(func() *extension.SessionHandler { + return &extension.SessionHandler{ + OnConnectionEvent: logs.add, + } + }))) + require.NoError(t, extension.Setup()) + + ts := createTidbTestSuite(t) + // createTidbTestSuite create an inner connection, so wait the previous connection closed + require.NoError(t, logs.waitEvent(extension.ConnDisconnected)) + + // test for login success + logs.reset() + db, err := sql.Open("mysql", ts.GetDSN()) + require.NoError(t, err) + defer func() { + require.NoError(t, db.Close()) + }() + + conn, err := db.Conn(context.Background()) + require.NoError(t, err) + defer func() { + _ = conn.Close() + }() + + var expectedConn2 variable.ConnectionInfo + require.NoError(t, logs.waitEvent(extension.ConnHandshakeAccepted)) + logs.check(func() { + require.Equal(t, []extension.ConnEventTp{ + extension.ConnConnected, + extension.ConnHandshakeAccepted, + }, logs.types) + conn1 := logs.infos[0] + require.Equal(t, "127.0.0.1", conn1.ClientIP) + require.Equal(t, "127.0.0.1", conn1.ServerIP) + require.Empty(t, conn1.User) + require.Empty(t, conn1.DB) + require.Equal(t, int(ts.Port), conn1.ServerPort) + require.NotEqual(t, conn1.ServerPort, conn1.ClientPort) + require.NotEmpty(t, conn1.ConnectionID) + require.Nil(t, conn1.ActiveRoles) + require.NoError(t, conn1.Error) + require.Empty(t, conn1.SessionAlias) + + expectedConn2 = *(conn1.ConnectionInfo) + expectedConn2.User = "root" + expectedConn2.DB = "test" + require.Equal(t, []*auth.RoleIdentity{}, logs.infos[1].ActiveRoles) + require.Nil(t, logs.infos[1].Error) + require.Equal(t, expectedConn2, *(logs.infos[1].ConnectionInfo)) + require.Empty(t, logs.infos[1].SessionAlias) + }) + + _, err = conn.ExecContext(context.TODO(), "create role r1@'%'") + require.NoError(t, err) + _, err = conn.ExecContext(context.TODO(), "grant r1 TO root") + require.NoError(t, err) + _, err = conn.ExecContext(context.TODO(), "set role all") + require.NoError(t, err) + _, err = conn.ExecContext(context.TODO(), "set @@tidb_session_alias='alias123'") + require.NoError(t, err) + + require.NoError(t, conn.Close()) + require.NoError(t, db.Close()) + require.NoError(t, logs.waitEvent(extension.ConnDisconnected)) + logs.check(func() { + require.Equal(t, 3, len(logs.infos)) + require.Equal(t, 1, len(logs.infos[2].ActiveRoles)) + require.Equal(t, auth.RoleIdentity{ + Username: "r1", + Hostname: "%", + }, *logs.infos[2].ActiveRoles[0]) + require.Nil(t, logs.infos[2].Error) + require.Equal(t, expectedConn2, *(logs.infos[2].ConnectionInfo)) + require.Equal(t, "alias123", logs.infos[2].SessionAlias) + }) + + // test for login failed + logs.reset() + cfg := mysql.NewConfig() + cfg.User = "noexist" + cfg.Net = "tcp" + cfg.Addr = fmt.Sprintf("127.0.0.1:%d", ts.Port) + cfg.DBName = "test" + + db, err = sql.Open("mysql", cfg.FormatDSN()) + require.NoError(t, err) + defer func() { + require.NoError(t, db.Close()) + }() + + _, err = db.Conn(context.Background()) + require.Error(t, err) + require.NoError(t, logs.waitEvent(extension.ConnDisconnected)) + logs.check(func() { + require.Equal(t, []extension.ConnEventTp{ + extension.ConnConnected, + extension.ConnHandshakeRejected, + extension.ConnDisconnected, + }, logs.types) + conn1 := logs.infos[0] + require.Equal(t, "127.0.0.1", conn1.ClientIP) + require.Equal(t, "127.0.0.1", conn1.ServerIP) + require.Empty(t, conn1.User) + require.Empty(t, conn1.DB) + require.Equal(t, int(ts.Port), conn1.ServerPort) + require.NotEqual(t, conn1.ServerPort, conn1.ClientPort) + require.NotEmpty(t, conn1.ConnectionID) + require.Nil(t, conn1.ActiveRoles) + require.NoError(t, conn1.Error) + require.Empty(t, conn1.SessionAlias) + + expectedConn2 = *(conn1.ConnectionInfo) + expectedConn2.User = "noexist" + expectedConn2.DB = "test" + require.Equal(t, []*auth.RoleIdentity{}, logs.infos[1].ActiveRoles) + require.EqualError(t, logs.infos[1].Error, "[server:1045]Access denied for user 'noexist'@'127.0.0.1' (using password: NO)") + require.Equal(t, expectedConn2, *(logs.infos[1].ConnectionInfo)) + require.Empty(t, logs.infos[2].SessionAlias) + }) +} + +func TestSandBoxMode(t *testing.T) { + ts := createTidbTestSuite(t) + qctx, err := ts.tidbdrv.OpenCtx(uint64(0), 0, uint8(tmysql.DefaultCollationID), "test", nil, nil) + require.NoError(t, err) + _, err = Execute(context.Background(), qctx, "create user testuser;") + require.NoError(t, err) + qctx.Session.GetSessionVars().User = &auth.UserIdentity{Username: "testuser", AuthUsername: "testuser", AuthHostname: "%"} + + alterPwdStmts := []string{ + "set password = '1234';", + "alter user testuser identified by '1234';", + "alter user current_user() identified by '1234';", + } + + for _, alterPwdStmt := range alterPwdStmts { + require.False(t, qctx.Session.InSandBoxMode()) + _, err = Execute(context.Background(), qctx, "select 1;") + require.NoError(t, err) + + qctx.Session.EnableSandBoxMode() + require.True(t, qctx.Session.InSandBoxMode()) + _, err = Execute(context.Background(), qctx, "select 1;") + require.Error(t, err) + _, err = Execute(context.Background(), qctx, "alter user testuser identified with 'mysql_native_password';") + require.Error(t, err) + _, err = Execute(context.Background(), qctx, alterPwdStmt) + require.NoError(t, err) + _, err = Execute(context.Background(), qctx, "select 1;") + require.NoError(t, err) + } +} + +// See: https://github.com/pingcap/tidb/issues/40979 +// Reusing memory of `chunk.Chunk` may cause some systems variable's memory value to be modified unexpectedly. +func TestChunkReuseCorruptSysVarString(t *testing.T) { + ts := createTidbTestSuite(t) + + db, err := sql.Open("mysql", ts.GetDSN()) + require.NoError(t, err) + defer func() { + require.NoError(t, db.Close()) + }() + + conn, err := db.Conn(context.Background()) + require.NoError(t, err) + defer func() { + require.NoError(t, conn.Close()) + }() + + rs, err := conn.QueryContext(context.Background(), "show tables in test") + ts.Rows(t, rs) + require.NoError(t, err) + + _, err = conn.ExecContext(context.Background(), "set @@time_zone=(select 'Asia/Shanghai')") + require.NoError(t, err) + + rs, err = conn.QueryContext(context.Background(), "select TIDB_TABLE_ID from information_schema.tables where TABLE_SCHEMA='aaaa'") + ts.Rows(t, rs) + require.NoError(t, err) + + rs, err = conn.QueryContext(context.Background(), "select @@time_zone") + require.NoError(t, err) + defer func() { + require.NoError(t, rs.Close()) + }() + + rows := ts.Rows(t, rs) + require.Equal(t, 1, len(rows)) + require.Equal(t, "Asia/Shanghai", rows[0]) +} + +type mockProxyProtocolProxy struct { + frontend string + backend string + clientAddr string + backendIsSock bool + ln net.Listener + run atomic.Bool +} + +func newMockProxyProtocolProxy(frontend, backend, clientAddr string, backendIsSock bool) *mockProxyProtocolProxy { + return &mockProxyProtocolProxy{ + frontend: frontend, + backend: backend, + clientAddr: clientAddr, + backendIsSock: backendIsSock, + ln: nil, + } +} + +func (p *mockProxyProtocolProxy) ListenAddr() net.Addr { + return p.ln.Addr() +} + +func (p *mockProxyProtocolProxy) Run() (err error) { + p.run.Store(true) + p.ln, err = net.Listen("tcp", p.frontend) + if err != nil { + return err + } + for p.run.Load() { + conn, err := p.ln.Accept() + if err != nil { + break + } + go p.onConn(conn) + } + return nil +} + +func (p *mockProxyProtocolProxy) Close() error { + p.run.Store(false) + if p.ln != nil { + return p.ln.Close() + } + return nil +} + +func (p *mockProxyProtocolProxy) connectToBackend() (net.Conn, error) { + if p.backendIsSock { + return net.Dial("unix", p.backend) + } + return net.Dial("tcp", p.backend) +} + +func (p *mockProxyProtocolProxy) onConn(conn net.Conn) { + bconn, err := p.connectToBackend() + if err != nil { + conn.Close() + fmt.Println(err) + } + defer bconn.Close() + ppHeader := p.generateProxyProtocolHeaderV2("tcp4", p.clientAddr, p.frontend) + bconn.Write(ppHeader) + p.proxyPipe(conn, bconn) +} + +func (p *mockProxyProtocolProxy) proxyPipe(p1, p2 io.ReadWriteCloser) { + defer p1.Close() + defer p2.Close() + + // start proxy + p1die := make(chan struct{}) + go func() { io.Copy(p1, p2); close(p1die) }() + + p2die := make(chan struct{}) + go func() { io.Copy(p2, p1); close(p2die) }() + + // wait for proxy termination + select { + case <-p1die: + case <-p2die: + } +} + +func (p *mockProxyProtocolProxy) generateProxyProtocolHeaderV2(network, srcAddr, dstAddr string) []byte { + var ( + proxyProtocolV2Sig = []byte{0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A} + v2CmdPos = 12 + v2FamlyPos = 13 + ) + saddr, _ := net.ResolveTCPAddr(network, srcAddr) + daddr, _ := net.ResolveTCPAddr(network, dstAddr) + buffer := make([]byte, 1024) + copy(buffer, proxyProtocolV2Sig) + // Command + buffer[v2CmdPos] = 0x21 + // Famly + if network == "tcp4" { + buffer[v2FamlyPos] = 0x11 + binary.BigEndian.PutUint16(buffer[14:14+2], 12) + copy(buffer[16:16+4], []byte(saddr.IP.To4())) + copy(buffer[20:20+4], []byte(daddr.IP.To4())) + binary.BigEndian.PutUint16(buffer[24:24+2], uint16(saddr.Port)) + binary.BigEndian.PutUint16(buffer[26:26+2], uint16(saddr.Port)) + return buffer[0:28] + } else if network == "tcp6" { + buffer[v2FamlyPos] = 0x21 + binary.BigEndian.PutUint16(buffer[14:14+2], 36) + copy(buffer[16:16+16], []byte(saddr.IP.To16())) + copy(buffer[32:32+16], []byte(daddr.IP.To16())) + binary.BigEndian.PutUint16(buffer[48:48+2], uint16(saddr.Port)) + binary.BigEndian.PutUint16(buffer[50:50+2], uint16(saddr.Port)) + return buffer[0:52] + } + return buffer +} + +func TestProxyProtocolWithIpFallbackable(t *testing.T) { + cfg := util2.NewTestConfig() + cfg.Port = 4999 + cfg.Status.ReportStatus = false + // Setup proxy protocol config + cfg.ProxyProtocol.Networks = "*" + cfg.ProxyProtocol.Fallbackable = true + + ts := createTidbTestSuite(t) + + // Prepare Server + server, err := server2.NewServer(cfg, ts.tidbdrv) + require.NoError(t, err) + server.SetDomain(ts.domain) + go func() { + err := server.Run() + require.NoError(t, err) + }() + time.Sleep(time.Millisecond * 100) + defer func() { + server.Close() + }() + + require.NotNil(t, server.Listener()) + require.Nil(t, server.Socket()) + + // Prepare Proxy + ppProxy := newMockProxyProtocolProxy("127.0.0.1:5000", "127.0.0.1:4999", "192.168.1.2:60055", false) + go func() { + ppProxy.Run() + }() + time.Sleep(time.Millisecond * 100) + defer func() { + ppProxy.Close() + }() + + cli := testserverclient.NewTestServerClient() + cli.Port = testutil.GetPortFromTCPAddr(ppProxy.ListenAddr()) + cli.WaitUntilServerCanConnect() + + cli.RunTests(t, + func(config *mysql.Config) { + config.User = "root" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("SHOW PROCESSLIST;") + records := cli.Rows(t, rows) + require.Contains(t, records[0], "192.168.1.2:60055") + }, + ) + + cli2 := testserverclient.NewTestServerClient() + cli2.Port = 4999 + cli2.RunTests(t, + func(config *mysql.Config) { + config.User = "root" + }, + func(dbt *testkit.DBTestKit) { + rows := dbt.MustQuery("SHOW PROCESSLIST;") + records := cli.Rows(t, rows) + require.Contains(t, records[0], "127.0.0.1:") + }, + ) +} + +func TestProxyProtocolWithIpNoFallbackable(t *testing.T) { + cfg := util2.NewTestConfig() + cfg.Port = 0 + cfg.Status.ReportStatus = false + // Setup proxy protocol config + cfg.ProxyProtocol.Networks = "*" + cfg.ProxyProtocol.Fallbackable = false + + ts := createTidbTestSuite(t) + + // Prepare Server + server, err := server2.NewServer(cfg, ts.tidbdrv) + require.NoError(t, err) + server.SetDomain(ts.domain) + go func() { + err := server.Run() + require.NoError(t, err) + }() + time.Sleep(time.Millisecond * 1000) + defer func() { + server.Close() + }() + + require.NotNil(t, server.Listener()) + require.Nil(t, server.Socket()) + + cli := testserverclient.NewTestServerClient() + cli.Port = testutil.GetPortFromTCPAddr(server.ListenAddr()) + dsn := cli.GetDSN(func(config *mysql.Config) { + config.User = "root" + config.DBName = "test" + }) + db, err := sql.Open("mysql", dsn) + require.Nil(t, err) + err = db.Ping() + require.NotNil(t, err) + db.Close() +}