// Copyright 2023 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 partitiontest import ( "fmt" "testing" "github.com/pingcap/failpoint" "github.com/pingcap/tidb/testkit" "github.com/stretchr/testify/require" ) func TestPartitionedTableReplace(t *testing.T) { failpoint.Enable("github.com/pingcap/tidb/planner/core/forceDynamicPrune", `return(true)`) defer failpoint.Disable("github.com/pingcap/tidb/planner/core/forceDynamicPrune") store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec("use test") testSQL := `drop table if exists replace_test; create table replace_test (id int PRIMARY KEY AUTO_INCREMENT, c1 int, c2 int, c3 int default 1) partition by range (id) ( PARTITION p0 VALUES LESS THAN (3), PARTITION p1 VALUES LESS THAN (5), PARTITION p2 VALUES LESS THAN (7), PARTITION p3 VALUES LESS THAN (9));` tk.MustExec(testSQL) testSQL = `replace replace_test (c1) values (1),(2),(NULL);` tk.MustExec(testSQL) require.Equal(t, tk.Session().LastMessage(), "Records: 3 Duplicates: 0 Warnings: 0") errReplaceSQL := `replace replace_test (c1) values ();` tk.MustExec("begin") err := tk.ExecToErr(errReplaceSQL) require.Error(t, err) tk.MustExec("rollback") errReplaceSQL = `replace replace_test (c1, c2) values (1,2),(1);` tk.MustExec("begin") err = tk.ExecToErr(errReplaceSQL) require.Error(t, err) tk.MustExec("rollback") errReplaceSQL = `replace replace_test (xxx) values (3);` tk.MustExec("begin") err = tk.ExecToErr(errReplaceSQL) require.Error(t, err) tk.MustExec("rollback") errReplaceSQL = `replace replace_test_xxx (c1) values ();` tk.MustExec("begin") err = tk.ExecToErr(errReplaceSQL) require.Error(t, err) tk.MustExec("rollback") replaceSetSQL := `replace replace_test set c1 = 3;` tk.MustExec(replaceSetSQL) require.Empty(t, tk.Session().LastMessage()) errReplaceSetSQL := `replace replace_test set c1 = 4, c1 = 5;` tk.MustExec("begin") err = tk.ExecToErr(errReplaceSetSQL) require.Error(t, err) tk.MustExec("rollback") errReplaceSetSQL = `replace replace_test set xxx = 6;` tk.MustExec("begin") err = tk.ExecToErr(errReplaceSetSQL) require.Error(t, err) tk.MustExec("rollback") tk.MustExec(`drop table if exists replace_test_1`) tk.MustExec(`create table replace_test_1 (id int, c1 int) partition by range (id) ( PARTITION p0 VALUES LESS THAN (4), PARTITION p1 VALUES LESS THAN (6), PARTITION p2 VALUES LESS THAN (8), PARTITION p3 VALUES LESS THAN (10), PARTITION p4 VALUES LESS THAN (100))`) tk.MustExec(`replace replace_test_1 select id, c1 from replace_test;`) require.Equal(t, tk.Session().LastMessage(), "Records: 4 Duplicates: 0 Warnings: 0") tk.MustExec(`drop table if exists replace_test_2`) tk.MustExec(`create table replace_test_2 (id int, c1 int) partition by range (id) ( PARTITION p0 VALUES LESS THAN (10), PARTITION p1 VALUES LESS THAN (50), PARTITION p2 VALUES LESS THAN (100), PARTITION p3 VALUES LESS THAN (300))`) tk.MustExec(`replace replace_test_1 select id, c1 from replace_test union select id * 10, c1 * 10 from replace_test;`) require.Equal(t, tk.Session().LastMessage(), "Records: 8 Duplicates: 0 Warnings: 0") errReplaceSelectSQL := `replace replace_test_1 select c1 from replace_test;` tk.MustExec("begin") err = tk.ExecToErr(errReplaceSelectSQL) require.Error(t, err) tk.MustExec("rollback") tk.MustExec(`drop table if exists replace_test_3`) replaceUniqueIndexSQL := `create table replace_test_3 (c1 int, c2 int, UNIQUE INDEX (c2)) partition by range (c2) ( PARTITION p0 VALUES LESS THAN (4), PARTITION p1 VALUES LESS THAN (7), PARTITION p2 VALUES LESS THAN (11))` tk.MustExec(replaceUniqueIndexSQL) replaceUniqueIndexSQL = `replace into replace_test_3 set c2=8;` tk.MustExec(replaceUniqueIndexSQL) replaceUniqueIndexSQL = `replace into replace_test_3 set c2=8;` tk.MustExec(replaceUniqueIndexSQL) require.Equal(t, int64(1), int64(tk.Session().AffectedRows())) require.Empty(t, tk.Session().LastMessage()) replaceUniqueIndexSQL = `replace into replace_test_3 set c1=8, c2=8;` tk.MustExec(replaceUniqueIndexSQL) require.Equal(t, int64(2), int64(tk.Session().AffectedRows())) require.Empty(t, tk.Session().LastMessage()) replaceUniqueIndexSQL = `replace into replace_test_3 set c2=NULL;` tk.MustExec(replaceUniqueIndexSQL) replaceUniqueIndexSQL = `replace into replace_test_3 set c2=NULL;` tk.MustExec(replaceUniqueIndexSQL) require.Equal(t, int64(1), int64(tk.Session().AffectedRows())) require.Empty(t, tk.Session().LastMessage()) replaceUniqueIndexSQL = `create table replace_test_4 (c1 int, c2 int, c3 int, UNIQUE INDEX (c1, c2)) partition by range (c1) ( PARTITION p0 VALUES LESS THAN (4), PARTITION p1 VALUES LESS THAN (7), PARTITION p2 VALUES LESS THAN (11));` tk.MustExec(`drop table if exists replace_test_4`) tk.MustExec(replaceUniqueIndexSQL) replaceUniqueIndexSQL = `replace into replace_test_4 set c2=NULL;` tk.MustExec(replaceUniqueIndexSQL) replaceUniqueIndexSQL = `replace into replace_test_4 set c2=NULL;` tk.MustExec(replaceUniqueIndexSQL) require.Equal(t, int64(1), int64(tk.Session().AffectedRows())) replacePrimaryKeySQL := `create table replace_test_5 (c1 int, c2 int, c3 int, PRIMARY KEY (c1, c2)) partition by range (c2) ( PARTITION p0 VALUES LESS THAN (4), PARTITION p1 VALUES LESS THAN (7), PARTITION p2 VALUES LESS THAN (11));` tk.MustExec(replacePrimaryKeySQL) replacePrimaryKeySQL = `replace into replace_test_5 set c1=1, c2=2;` tk.MustExec(replacePrimaryKeySQL) replacePrimaryKeySQL = `replace into replace_test_5 set c1=1, c2=2;` tk.MustExec(replacePrimaryKeySQL) require.Equal(t, int64(1), int64(tk.Session().AffectedRows())) issue989SQL := `CREATE TABLE tIssue989 (a int, b int, KEY(a), UNIQUE KEY(b)) partition by range (b) ( PARTITION p1 VALUES LESS THAN (100), PARTITION p2 VALUES LESS THAN (200))` tk.MustExec(issue989SQL) issue989SQL = `insert into tIssue989 (a, b) values (1, 2);` tk.MustExec(issue989SQL) issue989SQL = `replace into tIssue989(a, b) values (111, 2);` tk.MustExec(issue989SQL) r := tk.MustQuery("select * from tIssue989;") r.Check(testkit.Rows("111 2")) } func TestHashPartitionedTableReplace(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec("use test") tk.MustExec("set @@session.tidb_enable_table_partition = '1';") tk.MustExec("drop table if exists replace_test;") testSQL := `create table replace_test (id int PRIMARY KEY AUTO_INCREMENT, c1 int, c2 int, c3 int default 1) partition by hash(id) partitions 4;` tk.MustExec(testSQL) testSQL = `replace replace_test (c1) values (1),(2),(NULL);` tk.MustExec(testSQL) errReplaceSQL := `replace replace_test (c1) values ();` tk.MustExec("begin") err := tk.ExecToErr(errReplaceSQL) require.Error(t, err) tk.MustExec("rollback") errReplaceSQL = `replace replace_test (c1, c2) values (1,2),(1);` tk.MustExec("begin") err = tk.ExecToErr(errReplaceSQL) require.Error(t, err) tk.MustExec("rollback") errReplaceSQL = `replace replace_test (xxx) values (3);` tk.MustExec("begin") err = tk.ExecToErr(errReplaceSQL) require.Error(t, err) tk.MustExec("rollback") errReplaceSQL = `replace replace_test_xxx (c1) values ();` tk.MustExec("begin") err = tk.ExecToErr(errReplaceSQL) require.Error(t, err) tk.MustExec("rollback") errReplaceSetSQL := `replace replace_test set c1 = 4, c1 = 5;` tk.MustExec("begin") err = tk.ExecToErr(errReplaceSetSQL) require.Error(t, err) tk.MustExec("rollback") errReplaceSetSQL = `replace replace_test set xxx = 6;` tk.MustExec("begin") err = tk.ExecToErr(errReplaceSetSQL) require.Error(t, err) tk.MustExec("rollback") tk.MustExec(`replace replace_test set c1 = 3;`) tk.MustExec(`replace replace_test set c1 = 4;`) tk.MustExec(`replace replace_test set c1 = 5;`) tk.MustExec(`replace replace_test set c1 = 6;`) tk.MustExec(`replace replace_test set c1 = 7;`) tk.MustExec(`drop table if exists replace_test_1`) tk.MustExec(`create table replace_test_1 (id int, c1 int) partition by hash(id) partitions 5;`) tk.MustExec(`replace replace_test_1 select id, c1 from replace_test;`) tk.MustExec(`drop table if exists replace_test_2`) tk.MustExec(`create table replace_test_2 (id int, c1 int) partition by hash(id) partitions 6;`) tk.MustExec(`replace replace_test_1 select id, c1 from replace_test union select id * 10, c1 * 10 from replace_test;`) errReplaceSelectSQL := `replace replace_test_1 select c1 from replace_test;` tk.MustExec("begin") err = tk.ExecToErr(errReplaceSelectSQL) require.Error(t, err) tk.MustExec("rollback") tk.MustExec(`drop table if exists replace_test_3`) replaceUniqueIndexSQL := `create table replace_test_3 (c1 int, c2 int, UNIQUE INDEX (c2)) partition by hash(c2) partitions 7;` tk.MustExec(replaceUniqueIndexSQL) tk.MustExec(`replace into replace_test_3 set c2=8;`) tk.MustExec(`replace into replace_test_3 set c2=8;`) require.Equal(t, int64(1), int64(tk.Session().AffectedRows())) tk.MustExec(`replace into replace_test_3 set c1=8, c2=8;`) require.Equal(t, int64(2), int64(tk.Session().AffectedRows())) tk.MustExec(`replace into replace_test_3 set c2=NULL;`) tk.MustExec(`replace into replace_test_3 set c2=NULL;`) require.Equal(t, int64(1), int64(tk.Session().AffectedRows())) for i := 0; i < 100; i++ { sql := fmt.Sprintf("replace into replace_test_3 set c2=%d;", i) tk.MustExec(sql) } result := tk.MustQuery("select count(*) from replace_test_3") result.Check(testkit.Rows("102")) replaceUniqueIndexSQL = `create table replace_test_4 (c1 int, c2 int, c3 int, UNIQUE INDEX (c1, c2)) partition by hash(c1) partitions 8;` tk.MustExec(`drop table if exists replace_test_4`) tk.MustExec(replaceUniqueIndexSQL) replaceUniqueIndexSQL = `replace into replace_test_4 set c2=NULL;` tk.MustExec(replaceUniqueIndexSQL) replaceUniqueIndexSQL = `replace into replace_test_4 set c2=NULL;` tk.MustExec(replaceUniqueIndexSQL) require.Equal(t, int64(1), int64(tk.Session().AffectedRows())) replacePrimaryKeySQL := `create table replace_test_5 (c1 int, c2 int, c3 int, PRIMARY KEY (c1, c2)) partition by hash (c2) partitions 9;` tk.MustExec(replacePrimaryKeySQL) replacePrimaryKeySQL = `replace into replace_test_5 set c1=1, c2=2;` tk.MustExec(replacePrimaryKeySQL) replacePrimaryKeySQL = `replace into replace_test_5 set c1=1, c2=2;` tk.MustExec(replacePrimaryKeySQL) require.Equal(t, int64(1), int64(tk.Session().AffectedRows())) issue989SQL := `CREATE TABLE tIssue989 (a int, b int, KEY(a), UNIQUE KEY(b)) partition by hash (b) partitions 10;` tk.MustExec(issue989SQL) issue989SQL = `insert into tIssue989 (a, b) values (1, 2);` tk.MustExec(issue989SQL) issue989SQL = `replace into tIssue989(a, b) values (111, 2);` tk.MustExec(issue989SQL) r := tk.MustQuery("select * from tIssue989;") r.Check(testkit.Rows("111 2")) } func TestPartitionedTableUpdate(t *testing.T) { failpoint.Enable("github.com/pingcap/tidb/planner/core/forceDynamicPrune", `return(true)`) defer failpoint.Disable("github.com/pingcap/tidb/planner/core/forceDynamicPrune") 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 (id int not null default 1, name varchar(255)) PARTITION BY RANGE ( id ) ( PARTITION p0 VALUES LESS THAN (6), PARTITION p1 VALUES LESS THAN (11), PARTITION p2 VALUES LESS THAN (16), PARTITION p3 VALUES LESS THAN (21))`) tk.MustExec(`insert INTO t VALUES (1, "hello");`) tk.CheckExecResult(1, 0) tk.MustExec(`insert INTO t VALUES (7, "hello");`) tk.CheckExecResult(1, 0) // update non partition column tk.MustExec(`UPDATE t SET name = "abc" where id > 0;`) tk.CheckExecResult(2, 0) require.Equal(t, tk.Session().LastMessage(), "Rows matched: 2 Changed: 2 Warnings: 0") r := tk.MustQuery(`SELECT * from t order by id limit 2;`) r.Check(testkit.Rows("1 abc", "7 abc")) // update partition column tk.MustExec(`update t set id = id + 1`) tk.CheckExecResult(2, 0) require.Equal(t, tk.Session().LastMessage(), "Rows matched: 2 Changed: 2 Warnings: 0") r = tk.MustQuery(`SELECT * from t order by id limit 2;`) r.Check(testkit.Rows("2 abc", "8 abc")) // update partition column, old and new record locates on different partitions tk.MustExec(`update t set id = 20 where id = 8`) tk.CheckExecResult(1, 0) require.Equal(t, tk.Session().LastMessage(), "Rows matched: 1 Changed: 1 Warnings: 0") r = tk.MustQuery(`SELECT * from t order by id limit 2;`) r.Check(testkit.Rows("2 abc", "20 abc")) // table option is auto-increment tk.MustExec("drop table if exists t;") tk.MustExec(`create table t (id int not null auto_increment, name varchar(255), primary key(id)) PARTITION BY RANGE ( id ) ( PARTITION p0 VALUES LESS THAN (6), PARTITION p1 VALUES LESS THAN (11), PARTITION p2 VALUES LESS THAN (16), PARTITION p3 VALUES LESS THAN (21))`) tk.MustExec("insert into t(name) values ('aa')") tk.MustExec("update t set id = 8 where name = 'aa'") require.Equal(t, tk.Session().LastMessage(), "Rows matched: 1 Changed: 1 Warnings: 0") tk.MustExec("insert into t(name) values ('bb')") r = tk.MustQuery("select * from t;") r.Check(testkit.Rows("8 aa", "9 bb")) err := tk.ExecToErr("update t set id = null where name = 'aa'") require.EqualError(t, err, "[table:1048]Column 'id' cannot be null") // Test that in a transaction, when a constraint failed in an update statement, the record is not inserted. tk.MustExec("drop table if exists t;") tk.MustExec(`create table t (id int, name int unique) PARTITION BY RANGE ( name ) ( PARTITION p0 VALUES LESS THAN (6), PARTITION p1 VALUES LESS THAN (11), PARTITION p2 VALUES LESS THAN (16), PARTITION p3 VALUES LESS THAN (21))`) tk.MustExec("insert t values (1, 1), (2, 2);") err = tk.ExecToErr("update t set name = 1 where id = 2") require.Error(t, err) tk.MustQuery("select * from t").Check(testkit.Rows("1 1", "2 2")) // test update ignore for pimary key tk.MustExec("drop table if exists t;") tk.MustExec(`create table t(a bigint, primary key (a)) PARTITION BY RANGE (a) ( PARTITION p0 VALUES LESS THAN (6), PARTITION p1 VALUES LESS THAN (11))`) tk.MustExec("insert into t values (5)") tk.MustExec("insert into t values (7)") err = tk.ExecToErr("update ignore t set a = 5 where a = 7;") require.NoError(t, err) require.Equal(t, tk.Session().LastMessage(), "Rows matched: 1 Changed: 0 Warnings: 1") r = tk.MustQuery("SHOW WARNINGS;") r.Check(testkit.Rows("Warning 1062 Duplicate entry '5' for key 't.PRIMARY'")) tk.MustQuery("select * from t order by a").Check(testkit.Rows("5", "7")) // test update ignore for truncate as warning err = tk.ExecToErr("update ignore t set a = 1 where a = (select '2a')") require.NoError(t, err) r = tk.MustQuery("SHOW WARNINGS;") r.Check(testkit.Rows("Warning 1292 Truncated incorrect DOUBLE value: '2a'", "Warning 1292 Truncated incorrect DOUBLE value: '2a'")) // test update ignore for unique key tk.MustExec("drop table if exists t;") tk.MustExec(`create table t(a bigint, unique key I_uniq (a)) PARTITION BY RANGE (a) ( PARTITION p0 VALUES LESS THAN (6), PARTITION p1 VALUES LESS THAN (11))`) tk.MustExec("insert into t values (5)") tk.MustExec("insert into t values (7)") err = tk.ExecToErr("update ignore t set a = 5 where a = 7;") require.NoError(t, err) require.Equal(t, tk.Session().LastMessage(), "Rows matched: 1 Changed: 0 Warnings: 1") r = tk.MustQuery("SHOW WARNINGS;") r.Check(testkit.Rows("Warning 1062 Duplicate entry '5' for key 't.I_uniq'")) tk.MustQuery("select * from t order by a").Check(testkit.Rows("5", "7")) } func TestPartitionedTableDelete(t *testing.T) { failpoint.Enable("github.com/pingcap/tidb/planner/core/forceDynamicPrune", `return(true)`) defer failpoint.Disable("github.com/pingcap/tidb/planner/core/forceDynamicPrune") createTable := `CREATE TABLE test.t (id int not null default 1, name varchar(255), index(id)) PARTITION BY RANGE ( id ) ( PARTITION p0 VALUES LESS THAN (6), PARTITION p1 VALUES LESS THAN (11), PARTITION p2 VALUES LESS THAN (16), PARTITION p3 VALUES LESS THAN (21))` store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec("use test") tk.MustExec("drop table if exists t") tk.MustExec(createTable) for i := 1; i < 21; i++ { tk.MustExec(fmt.Sprintf(`insert into t values (%d, "hello")`, i)) } tk.MustExec(`delete from t where id = 2 limit 1;`) tk.CheckExecResult(1, 0) // Test delete with false condition tk.MustExec(`delete from t where 0;`) tk.CheckExecResult(0, 0) tk.MustExec("insert into t values (2, 'abc')") tk.MustExec(`delete from t where t.id = 2 limit 1`) tk.CheckExecResult(1, 0) // Test delete ignore tk.MustExec("insert into t values (2, 'abc')") err := tk.ExecToErr("delete from t where id = (select '2a')") require.Error(t, err) err = tk.ExecToErr("delete ignore from t where id = (select '2a')") require.NoError(t, err) tk.CheckExecResult(1, 0) r := tk.MustQuery("SHOW WARNINGS;") r.Check(testkit.Rows("Warning 1292 Truncated incorrect DOUBLE value: '2a'", "Warning 1292 Truncated incorrect DOUBLE value: '2a'")) // Test delete without using index, involve multiple partitions. tk.MustExec("delete from t ignore index(id) where id >= 13 and id <= 17") tk.CheckExecResult(5, 0) tk.MustExec("admin check table t") tk.MustExec(`delete from t;`) tk.CheckExecResult(14, 0) // Fix that partitioned table should not use PointGetPlan. tk.MustExec(`create table t1 (c1 bigint, c2 bigint, c3 bigint, primary key(c1)) partition by range (c1) (partition p0 values less than (3440))`) tk.MustExec("insert into t1 values (379, 379, 379)") tk.MustExec("delete from t1 where c1 = 379") tk.CheckExecResult(1, 0) tk.MustExec(`drop table t1;`) } func TestPartitionOnMissing(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) tk.MustExec("create schema OnMissing") tk.MustExec("use OnMissing") tk.MustExec(`set global tidb_partition_prune_mode='dynamic'`) tk.MustExec(`set session tidb_partition_prune_mode='dynamic'`) tk.MustExec(`CREATE TABLE tt1 ( id INT NOT NULL, listid INT, name varchar(10), primary key (listid) clustered ) PARTITION BY LIST (listid) ( PARTITION p1 VALUES IN (1), PARTITION p2 VALUES IN (2), PARTITION p3 VALUES IN (3), PARTITION p4 VALUES IN (4) )`) tk.MustExec(`CREATE TABLE tt2 ( id INT NOT NULL, listid INT )`) tk.MustExec(`create index idx_listid on tt1(id,listid)`) tk.MustExec(`create index idx_listid on tt2(listid)`) tk.MustExec(`insert into tt1 values(1,1,1)`) tk.MustExec(`insert into tt1 values(2,2,2)`) tk.MustExec(`insert into tt1 values(3,3,3)`) tk.MustExec(`insert into tt1 values(4,4,4)`) tk.MustExec(`insert into tt2 values(1,1)`) tk.MustExec(`insert into tt2 values(2,2)`) tk.MustExec(`insert into tt2 values(3,3)`) tk.MustExec(`insert into tt2 values(4,4)`) tk.MustExec(`insert into tt2 values(5,5)`) tk.MustExec(`analyze table tt1`) tk.MustExec(`analyze table tt2`) tk.MustQuery(`select /*+ inl_join(tt1)*/ count(*) from tt2 left join tt1 on tt1.listid=tt2.listid and tt1.id=tt2.id`).Check(testkit.Rows("5")) tk.MustQuery(`select /*+ inl_join(tt1)*/ count(*) from tt2 left join tt1 on tt1.listid=tt2.listid`).Check(testkit.Rows("5")) tk.MustQuery(`explain format = 'brief' select /*+ inl_join(tt1)*/ count(*) from tt2 left join tt1 on tt1.listid=tt2.listid`).Check(testkit.Rows(""+ "StreamAgg 1.00 root funcs:count(1)->Column#7", "└─IndexJoin 5.00 root left outer join, inner:TableReader, outer key:onmissing.tt2.listid, inner key:onmissing.tt1.listid, equal cond:eq(onmissing.tt2.listid, onmissing.tt1.listid)", " ├─IndexReader(Build) 5.00 root index:IndexFullScan", " │ └─IndexFullScan 5.00 cop[tikv] table:tt2, index:idx_listid(listid) keep order:false", " └─TableReader(Probe) 4.00 root partition:all data:TableRangeScan", " └─TableRangeScan 4.00 cop[tikv] table:tt1 range: decided by [onmissing.tt2.listid], keep order:false")) }