Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

types: fix creating partition tables fail in ANSI_QUOTES mode #35379

Merged
merged 12 commits into from
Jun 21, 2022
74 changes: 67 additions & 7 deletions ddl/db_partition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1283,15 +1283,15 @@ func TestAlterTableAddPartitionByListColumns(t *testing.T) {
require.Equal(t, "id", part.Columns[0].O)
require.Equal(t, "name", part.Columns[1].O)
require.Len(t, part.Definitions, 5)
require.Equal(t, [][]string{{"1", `"a"`}, {"2", `"b"`}}, part.Definitions[0].InValues)
require.Equal(t, [][]string{{"1", `'a'`}, {"2", `'b'`}}, part.Definitions[0].InValues)
require.Equal(t, model.NewCIStr("p0"), part.Definitions[0].Name)
require.Equal(t, [][]string{{"3", `"a"`}, {"4", `"b"`}}, part.Definitions[1].InValues)
require.Equal(t, [][]string{{"3", `'a'`}, {"4", `'b'`}}, part.Definitions[1].InValues)
require.Equal(t, model.NewCIStr("p1"), part.Definitions[1].Name)
require.Equal(t, [][]string{{"5", `NULL`}}, part.Definitions[2].InValues)
require.Equal(t, model.NewCIStr("p3"), part.Definitions[2].Name)
require.Equal(t, [][]string{{"7", `"a"`}}, part.Definitions[3].InValues)
require.Equal(t, [][]string{{"7", `'a'`}}, part.Definitions[3].InValues)
require.Equal(t, model.NewCIStr("p4"), part.Definitions[3].Name)
require.Equal(t, [][]string{{"8", `"a"`}}, part.Definitions[4].InValues)
require.Equal(t, [][]string{{"8", `'a'`}}, part.Definitions[4].InValues)
require.Equal(t, model.NewCIStr("p5"), part.Definitions[4].Name)

errorCases := []struct {
Expand Down Expand Up @@ -1387,9 +1387,9 @@ func TestAlterTableDropPartitionByListColumns(t *testing.T) {
require.Equal(t, "id", part.Columns[0].O)
require.Equal(t, "name", part.Columns[1].O)
require.Len(t, part.Definitions, 2)
require.Equal(t, [][]string{{"1", `"a"`}, {"2", `"b"`}}, part.Definitions[0].InValues)
require.Equal(t, [][]string{{"1", `'a'`}, {"2", `'b'`}}, part.Definitions[0].InValues)
require.Equal(t, model.NewCIStr("p0"), part.Definitions[0].Name)
require.Equal(t, [][]string{{"5", `"a"`}, {"NULL", "NULL"}}, part.Definitions[1].InValues)
require.Equal(t, [][]string{{"5", `'a'`}, {"NULL", "NULL"}}, part.Definitions[1].InValues)
require.Equal(t, model.NewCIStr("p3"), part.Definitions[1].Name)

sql := "alter table t drop partition p10;"
Expand Down Expand Up @@ -1454,7 +1454,7 @@ func TestAlterTableTruncatePartitionByListColumns(t *testing.T) {
part := tbl.Meta().Partition
require.True(t, part.Type == model.PartitionTypeList)
require.Len(t, part.Definitions, 3)
require.Equal(t, [][]string{{"3", `"a"`}, {"4", `"b"`}}, part.Definitions[1].InValues)
require.Equal(t, [][]string{{"3", `'a'`}, {"4", `'b'`}}, part.Definitions[1].InValues)
require.Equal(t, model.NewCIStr("p1"), part.Definitions[1].Name)
require.False(t, part.Definitions[1].ID == oldTbl.Meta().Partition.Definitions[1].ID)

Expand Down Expand Up @@ -3615,3 +3615,63 @@ func TestDuplicatePartitionNames(t *testing.T) {
"(PARTITION `p2` VALUES IN (2),\n" +
" PARTITION `p3` VALUES IN (3))"))
}

func TestPartitionTableWithAnsiQuotes(t *testing.T) {
store, clean := testkit.CreateMockStore(t)
defer clean()
tk := testkit.NewTestKit(t, store)
tk.MustExec("create database partitionWithAnsiQuotes")
defer tk.MustExec("drop database partitionWithAnsiQuotes")
tk.MustExec("use partitionWithAnsiQuotes")
tk.MustExec("SET SESSION sql_mode='ANSI_QUOTES'")

// Test single quotes.
tk.MustExec(`create table t(created_at datetime) PARTITION BY RANGE COLUMNS(created_at) (
PARTITION p0 VALUES LESS THAN ('2021-12-01 00:00:00'),
PARTITION p1 VALUES LESS THAN ('2022-01-01 00:00:00'))`)
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\n" +
"PARTITION BY RANGE COLUMNS(\"created_at\")\n" +
"(PARTITION \"p0\" VALUES LESS THAN ('2021-12-01 00:00:00'),\n" +
" PARTITION \"p1\" VALUES LESS THAN ('2022-01-01 00:00:00'))"))
tk.MustExec("drop table t")

// Test expression with single quotes.
tk.MustExec(`create table t(created_at timestamp) PARTITION BY RANGE (unix_timestamp(created_at)) (
PARTITION p0 VALUES LESS THAN (unix_timestamp('2021-12-01 00:00:00')),
PARTITION p1 VALUES LESS THAN (unix_timestamp('2022-01-01 00:00:00')))`)
// FIXME: should be "created_at" instead of `created_at`, see #35389.
tk.MustQuery("show create table t").Check(testkit.Rows("t CREATE TABLE \"t\" (\n" +
" \"created_at\" timestamp NULL DEFAULT NULL\n" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n" +
"PARTITION BY RANGE (UNIX_TIMESTAMP(`created_at`))\n" +
"(PARTITION \"p0\" VALUES LESS THAN (1638288000),\n" +
" PARTITION \"p1\" VALUES LESS THAN (1640966400))"))
tk.MustExec("drop table t")

// Test values in.
tk.MustExec(`CREATE TABLE t (a int DEFAULT NULL, b varchar(255) DEFAULT NULL) PARTITION BY LIST COLUMNS(a,b) (
PARTITION p0 VALUES IN ((1,'1'),(2,'2')),
PARTITION p1 VALUES IN ((10,'10'),(11,'11')))`)
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\n" +
"PARTITION BY LIST COLUMNS(\"a\",\"b\")\n" +
"(PARTITION \"p0\" VALUES IN ((1,'1'),(2,'2')),\n" +
" PARTITION \"p1\" VALUES IN ((10,'10'),(11,'11')))"))
tk.MustExec("drop table t")

// Test escaped characters in single quotes.
tk.MustExec(`CREATE TABLE t (a varchar(255) DEFAULT NULL) PARTITION BY LIST COLUMNS(a) (
PARTITION p0 VALUES IN ('\'','\'\'',''''''''),
PARTITION p1 VALUES IN ('""','\\','\\\'\t\n'))`)
tk.MustQuery("show create table t").Check(testkit.Rows("t CREATE TABLE \"t\" (\n" +
" \"a\" varchar(255) DEFAULT NULL\n" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n" +
"PARTITION BY LIST COLUMNS(\"a\")\n" +
"(PARTITION \"p0\" VALUES IN ('''','''''',''''''''),\n" +
" PARTITION \"p1\" VALUES IN ('\"\"','\\\\','\\\\''\t\n'))"))
tk.MustExec("drop table t")
}
mjonss marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion ddl/partition.go
Original file line number Diff line number Diff line change
Expand Up @@ -1657,7 +1657,7 @@ func buildCheckSQLForRangeExprPartition(pi *model.PartitionInfo, index int, sche
}

func trimQuotation(str string) string {
return strings.Trim(str, "\"")
return strings.Trim(str, "'")
}

func buildCheckSQLForRangeColumnsPartition(pi *model.PartitionInfo, index int, schemaName, tableName model.CIStr) (string, []interface{}) {
Expand Down
15 changes: 8 additions & 7 deletions executor/showtest/show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,15 +404,16 @@ func TestShowCreateTable(t *testing.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\")))"))
"(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("|",
Expand Down Expand Up @@ -525,8 +526,8 @@ func TestShowCreateTablePlacement(t *testing.T) {
" `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\"))",
"(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`)
Expand All @@ -540,8 +541,8 @@ func TestShowCreateTablePlacement(t *testing.T) {
" `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\")))",
"(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`)
Expand Down Expand Up @@ -570,7 +571,7 @@ func TestShowCreateTablePlacement(t *testing.T) {
" `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 `pLow` VALUES LESS THAN ('1000000') COMMENT 'a comment' /*T![placement] PLACEMENT POLICY=`x` */,\n"+
" PARTITION `pMax` VALUES LESS THAN (MAXVALUE))",
))

Expand Down
6 changes: 5 additions & 1 deletion types/parser_driver/value_expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,11 @@ func (n *ValueExpr) Format(w io.Writer) {
case types.KindFloat64:
s = strconv.FormatFloat(n.GetFloat64(), 'e', -1, 64)
case types.KindString, types.KindBytes:
s = strconv.Quote(n.GetString())
// If sql_mode='ANSI_QUOTES', strings with double-quotes will be taken as an identifier.
// See #35281.
s = strings.ReplaceAll(n.GetString(), "\\", "\\\\")
s = strings.ReplaceAll(s, `'`, `''`)
s = fmt.Sprintf("'%s'", s)
case types.KindMysqlDecimal:
s = n.GetMysqlDecimal().String()
case types.KindBinaryLiteral:
Expand Down
31 changes: 31 additions & 0 deletions types/parser_driver/value_expr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,34 @@ func TestValueExprRestore(t *testing.T) {
})
}
}

func TestValueExprFormat(t *testing.T) {
tests := []struct {
datum types.Datum
expect string
}{
{types.NewDatum(nil), "NULL"},
{types.NewIntDatum(1), "1"},
{types.NewIntDatum(-1), "-1"},
{types.NewUintDatum(1), "1"},
{types.NewFloat32Datum(1.1), "1.1e+00"},
{types.NewFloat64Datum(1.1), "1.1e+00"},
{types.NewStringDatum("test `s't\"r."), "'test `s''t\"r.'"},
{types.NewBytesDatum([]byte("test `s't\"r.")), "'test `s''t\"r.'"},
{types.NewBinaryLiteralDatum([]byte("test `s't\"r.")), "b'11101000110010101110011011101000010000001100000011100110010011101110100001000100111001000101110'"},
{types.NewDecimalDatum(types.NewDecFromInt(321)), "321"},
{types.NewStringDatum("\\"), "'\\\\'"},
{types.NewStringDatum("''"), "''''''"},
{types.NewStringDatum("\\''\t\n"), "'\\\\''''\t\n'"},
}

for _, test := range tests {
test := test
t.Run(test.expect, func(t *testing.T) {
var sb strings.Builder
expr := &ValueExpr{Datum: test.datum}
expr.Format(&sb)
require.Equalf(t, test.expect, sb.String(), "datum: %#v", test.datum)
})
}
}