Files
tidb/pkg/util/schemacmp/table_test.go

518 lines
17 KiB
Go

// 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 schemacmp_test
import (
"strings"
"testing"
"github.com/pingcap/errors"
"github.com/pingcap/tidb/pkg/ddl"
"github.com/pingcap/tidb/pkg/parser"
"github.com/pingcap/tidb/pkg/parser/ast"
"github.com/pingcap/tidb/pkg/parser/model"
"github.com/pingcap/tidb/pkg/parser/mysql"
_ "github.com/pingcap/tidb/pkg/planner"
"github.com/pingcap/tidb/pkg/sessionctx"
"github.com/pingcap/tidb/pkg/util/mock"
"github.com/pingcap/tidb/pkg/util/schemacmp"
"github.com/stretchr/testify/require"
)
func toTableInfo(parser *parser.Parser, sctx sessionctx.Context, createTableStmt string) (*model.TableInfo, error) {
node, err := parser.ParseOneStmt(createTableStmt, "", "")
if err != nil {
return nil, err
}
createStmtNode, ok := node.(*ast.CreateTableStmt)
if !ok {
return nil, errors.New("not a create table statement")
}
return ddl.MockTableInfo(sctx, createStmtNode, 1)
}
func checkDecodeFieldTypes(t *testing.T, info *model.TableInfo, tt schemacmp.Table) {
fieldTyps := schemacmp.DecodeColumnFieldTypes(tt)
require.Len(t, fieldTyps, len(info.Columns))
for _, col := range info.Columns {
typ, ok := fieldTyps[col.Name.O]
require.True(t, ok)
require.Equal(t, *typ, col.FieldType)
}
}
func TestJoinSchemas(t *testing.T) {
p := parser.New()
sctx := mock.NewContext()
testCases := []struct {
name string
a string
b string
cmp int
cmpErr string
join string
joinErr string
}{
{
name: "DM_002/1",
a: "CREATE TABLE tb1 (col1 INT)",
b: "CREATE TABLE tb2 (col1 INT, new_col1 INT)",
cmp: -1,
join: "CREATE TABLE tb3 (col1 INT, new_col1 INT)",
},
{
name: "DM_002/1/unordered",
a: "CREATE TABLE tb1 (col1 INT)",
b: "CREATE TABLE tb2 (new_col1 INT, col1 INT)",
cmp: -1,
join: "CREATE TABLE tb3 (new_col1 INT, col1 INT)",
},
{
name: "DM_002/2",
a: "CREATE TABLE tb1 (col1 INT, new_col1 INT)",
b: "CREATE TABLE tb2 (col1 INT, new_col1 INT)",
cmp: 0,
join: "CREATE TABLE tb3 (col1 INT, new_col1 INT)",
},
{
name: "DM_002/2/unordered",
a: "CREATE TABLE tb1 (col1 INT, new_col1 INT)",
b: "CREATE TABLE tb2 (new_col1 INT, col1 INT)",
cmp: 0,
join: "CREATE TABLE tb3 (col1 INT, new_col1 INT)",
},
{
name: "DM_010",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), new_col1 INT, new_col2 INT)",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10))",
cmp: 1,
join: "CREATE TABLE tb3 (a INT, b VARCHAR(10), new_col1 INT, new_col2 INT)",
},
{
name: "DM_011",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), new_col1 INT, new_col2 INT)",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10), new_col1 FLOAT)",
cmpErr: `.*"new_col1".*incompatible mysql type.*`,
joinErr: `.*"new_col1".*incompatible mysql type.*`,
},
{
name: "DM_014",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), new_col1 INT)",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10), new_col2 INT)",
cmpErr: `.*combining contradicting orders.*`,
join: "CREATE TABLE tb3 (a INT, b VARCHAR(10), new_col1 INT, new_col2 INT)",
},
{
name: "DM_031/VARCHAR",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), new_col1 INT)",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10), new_col1 VARCHAR(10))",
cmpErr: `.*"new_col1".*incompatible mysql type.*`,
joinErr: `.*"new_col1".*incompatible mysql type.*`,
},
{
name: "DM_031/TEXT",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), new_col1 INT)",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10), new_col1 TEXT)",
cmpErr: `.*"new_col1".*incompatible mysql type.*`,
joinErr: `.*"new_col1".*incompatible mysql type.*`,
},
{
name: "DM_031/JSON",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), new_col1 INT)",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10), new_col1 JSON)",
cmpErr: `.*"new_col1".*incompatible mysql type.*`,
joinErr: `.*"new_col1".*incompatible mysql type.*`,
},
{
name: "DM_033",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), c FLOAT NOT NULL)",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10))",
cmpErr: `.*"c": column with no default value cannot be missing`,
join: "CREATE TABLE tb3 (a INT, b VARCHAR(10), c FLOAT NOT NULL DEFAULT 0)",
},
{
name: "DM_034",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), new_col1 INT UNIQUE AUTO_INCREMENT)",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10))",
cmpErr: `.*combining contradicting orders.*`,
joinErr: `.*"new_col1".*auto type but not defined as a key`,
},
{
name: "DM_035",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), col1 INT, col2 INT)",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10), col2 INT, col1 INT)",
cmp: 0,
join: "CREATE TABLE tb3 (a INT, b VARCHAR(10), col1 INT, col2 INT)",
},
{
name: "DM_037",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), col1 INT DEFAULT 0)",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10), col1 INT DEFAULT -1)",
cmpErr: `.*"col1".*distinct singletons.*`,
joinErr: `.*"col1".*distinct singletons.*`,
},
{
name: "DM_039/1",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), col1 VARCHAR(10) CHARSET utf8 COLLATE utf8_bin)",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10))",
cmp: 1,
join: "CREATE TABLE tb3 (a INT, b VARCHAR(10), col1 VARCHAR(10) CHARSET utf8 COLLATE utf8_bin)",
},
{
name: "DM_039/2",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), col1 VARCHAR(10) CHARSET utf8 COLLATE utf8_bin)",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10), col1 VARCHAR(10) CHARSET utf8 COLLATE utf8_bin)",
cmp: 0,
join: "CREATE TABLE tb3 (a INT, b VARCHAR(10), col1 VARCHAR(10) CHARSET utf8 COLLATE utf8_bin)",
},
{
name: "DM_040",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), col1 VARCHAR(10) CHARSET utf8 COLLATE utf8_bin)",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10), col1 VARCHAR(10) CHARSET utf8mb4 COLLATE utf8mb4_bin)",
cmpErr: `.*"col1".*distinct singletons.*`,
joinErr: `.*"col1".*distinct singletons.*`,
},
{
name: "DM_041/1",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), new_col1 INT AS (a + 1))",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10))",
cmp: 1,
join: "CREATE TABLE tb1 (a INT, b VARCHAR(10), new_col1 INT AS (a + 1))",
},
{
name: "DM_041/2",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), new_col1 INT AS (a + 1))",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10), new_col1 INT AS (a + 1))",
cmp: 0,
join: "CREATE TABLE tb1 (a INT, b VARCHAR(10), new_col1 INT AS (a + 1))",
},
{
name: "DM_042",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), new_col1 INT AS (a + 1) STORED)",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10))",
cmp: 1,
join: "CREATE TABLE tb1 (a INT, b VARCHAR(10), new_col1 INT AS (a + 1) STORED)",
},
{
name: "DM_043",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), new_col1 INT AS (a + 1))",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10), new_col1 INT AS (a + 2))",
cmpErr: `.*"new_col1".*distinct singletons.*`,
joinErr: `.*"new_col1".*distinct singletons.*`,
},
{
name: "DM_044",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), new_col1 INT AS (a + 1) VIRTUAL)",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10), new_col1 INT AS (a + 1) STORED)",
cmpErr: `.*"new_col1".*distinct singletons.*`,
joinErr: `.*"new_col1".*distinct singletons.*`,
},
{
name: "DM_052",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10))",
b: "CREATE TABLE tb2 (c BIGINT, b VARCHAR(10))",
cmpErr: `.*combining contradicting orders.*`,
join: `CREATE TABLE tb3 (a INT, b VARCHAR(10), c BIGINT)`,
},
{
name: "DM_053",
a: "CREATE TABLE tb1 (c BIGINT, b VARCHAR(10))",
b: "CREATE TABLE tb2 (c DOUBLE, b VARCHAR(10))",
cmpErr: `.*"c".*incompatible mysql type.*`,
joinErr: `.*"c".*incompatible mysql type.*`,
},
{
name: "DM_055",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10))",
b: "CREATE TABLE tb2 (a BIGINT, b VARCHAR(10))",
cmp: -1,
join: "CREATE TABLE tb2 (a BIGINT, b VARCHAR(10))",
},
{
name: "DM_057",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10))",
b: "CREATE TABLE tb2 (c INT DEFAULT 1, b VARCHAR(10))",
cmpErr: `.*combining contradicting orders.*`,
join: "CREATE TABLE tb3 (a INT, b VARCHAR(10), c INT DEFAULT 1)",
},
{
name: "DM_061",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10))",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10) CHARSET utf8)",
cmpErr: `.*"b".*distinct singletons.*`,
joinErr: `.*"b".*distinct singletons.*`,
},
{
name: "DM_066",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10))",
b: "CREATE TABLE tb2 (a INT DEFAULT 1, b VARCHAR(10))",
cmp: -1,
join: "CREATE TABLE tb3 (a INT DEFAULT 1, b VARCHAR(10))",
},
// { // these table options are somehow ignored by the parser.
// name: "DM_074",
// a: "CREATE TABLE tbl1 (a INT, b VARCHAR(10)) CHARSET utf8 COLLATE utf8_bin",
// b: "CREATE TABLE tbl2 (a INT, b VARCHAR(10)) CHARSET utf8mb4 COLLATE utf8mb4_bin",
// joinErr: `.*distinct singletons.*`,
// },
{
name: "DM_078",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10))",
b: "CREATE TABLE tb2 (a INT PRIMARY KEY, b VARCHAR(10))",
cmp: 1,
join: "CREATE TABLE tb3 (a INT, b VARCHAR(10))",
},
{
name: "DM_080/1",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), UNIQUE KEY idx_a(a), UNIQUE KEY idx_b(b), UNIQUE KEY idx_ab(a, b))",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10))",
cmp: -1,
join: "CREATE TABLE tb3 (a INT, b VARCHAR(10))",
},
{
name: "DM_080/2",
a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), UNIQUE KEY idx_a(a), UNIQUE KEY idx_b(b), UNIQUE KEY idx_ab(a, b))",
b: "CREATE TABLE tb2 (a INT, b VARCHAR(10), UNIQUE KEY idx_a(a), UNIQUE KEY idx_b(b))",
cmp: -1,
join: "CREATE TABLE tb3 (a INT, b VARCHAR(10), UNIQUE KEY idx_a(a), UNIQUE KEY idx_b(b))",
},
// { // index visibility is not visible in IndexInfo yet.
// name: "DM_086",
// a: "CREATE TABLE tb1 (a INT, b VARCHAR(10), UNIQUE KEY a(a) VISIBLE)",
// b: "CREATE TABLE tb2 (a INT, b VARCHAR(10), UNIQUE KEY a(a) INVISIBLE)",
// joinErr: `.*distinct singletons.*`,
// },
{
name: "Different index components",
a: "CREATE TABLE tbl1 (a INT, b INT, KEY i(a))",
b: "CREATE TABLE tbl2 (a INT, b INT, KEY i(b))",
cmpErr: `.*combining contradicting orders.*`,
join: "CREATE TABLE tbl3 (a INT, b INT)",
},
{
name: "Different index order",
a: "CREATE TABLE tbl1 (a INT, b INT, KEY i(a, b))",
b: "CREATE TABLE tbl2 (a INT, b INT, KEY i(b, a))",
cmpErr: `.*combining contradicting orders.*`,
join: "CREATE TABLE tbl3 (a INT, b INT)",
},
{
name: "Different index length",
a: "CREATE TABLE tbl1 (a TEXT, KEY i(a(14)))",
b: "CREATE TABLE tbl2 (a TEXT, KEY i(a(15)))",
cmpErr: `.*distinct singletons.*`,
join: "CREATE TABLE tbl3 (a TEXT)",
},
{
name: "Cannot drop key tied to AUTO_INC column",
a: "CREATE TABLE tbl1(a INT AUTO_INCREMENT, b INT, KEY i(a))",
b: "CREATE TABLE tbl2(a INT AUTO_INCREMENT, b INT, KEY i(a, b))",
cmpErr: `.*distinct singletons.*`,
joinErr: `.*"a".*auto type but not defined as a key`,
},
{
name: "not-null column with special types",
a: `CREATE TABLE tbl1(
a1 INT NOT NULL,
b1 DECIMAL NOT NULL,
c1 VARCHAR(20) NOT NULL,
d1 DATETIME(3) NOT NULL,
e1 ENUM('abc', 'def') NOT NULL
)`,
b: `CREATE TABLE tbl2(
a2 TIME NOT NULL,
b2 DATE NOT NULL,
c2 BINARY(50) NOT NULL,
d2 YEAR(4) NOT NULL,
e2 SET('abc', 'def') NOT NULL
)`,
cmpErr: `.*column with no default value cannot be missing`,
join: `CREATE TABLE tbl3(
a1 INT NOT NULL DEFAULT 0,
b1 DECIMAL NOT NULL DEFAULT 0,
c1 VARCHAR(20) NOT NULL DEFAULT '',
d1 DATETIME(3) NOT NULL DEFAULT '0000-00-00 00:00:00',
e1 ENUM('abc', 'def') NOT NULL DEFAULT 'abc',
a2 TIME NOT NULL DEFAULT '00:00:00',
b2 DATE NOT NULL DEFAULT '0000-00-00',
c2 BINARY(50) NOT NULL DEFAULT '',
d2 YEAR(4) NOT NULL DEFAULT '0000',
e2 SET('abc', 'def') NOT NULL DEFAULT ''
)`,
},
{
name: "test case 2020-03-17",
a: `CREATE TABLE bar (id INT PRIMARY KEY)`,
b: `CREATE TABLE bar (id INT PRIMARY KEY, c1 INT)`,
cmp: -1,
join: `CREATE TABLE bar (id INT PRIMARY KEY, c1 INT)`,
},
{
name: "test case 2020-03-17-alt",
a: `CREATE TABLE bar (id VARCHAR(10) PRIMARY KEY)`,
b: `CREATE TABLE bar (id VARCHAR(10) PRIMARY KEY, c1 INT)`,
cmp: -1,
join: `CREATE TABLE bar (id VARCHAR(10) PRIMARY KEY, c1 INT)`,
},
{
name: "test case 2020-03-17-alt-2",
a: `CREATE TABLE bar (id INT PRIMARY KEY)`,
b: `CREATE TABLE bar (id INT, c1 INT)`,
cmp: -1,
join: `CREATE TABLE bar (id INT, c1 INT)`,
},
{
name: "test case 2020-03-17-alt-3",
a: `CREATE TABLE bar (id1 INT PRIMARY KEY, id2 INT)`,
b: `CREATE TABLE bar (id1 INT, id2 INT PRIMARY KEY)`,
cmpErr: `.*combining contradicting orders.*`,
join: `CREATE TABLE bar (id1 INT, id2 INT)`,
},
{
name: "test case 2020-04-28-blob",
a: "CREATE TABLE tb1 (a BLOB, b VARCHAR(10))",
b: "CREATE TABLE tb2 (a LONGBLOB, b VARCHAR(10))",
cmp: -1,
join: "CREATE TABLE tb2 (a LONGBLOB, b VARCHAR(10))",
},
{
name: "join equal single primary key",
a: "CREATE TABLE t(a INT, b INT, PRIMARY KEY(a))",
b: "CREATE TABLE t(a INT, b INT, PRIMARY KEY(a))",
cmp: 0,
join: "CREATE TABLE t(a INT, b INT, PRIMARY KEY(a))",
},
{
name: "join equal composite primary key",
a: "CREATE TABLE t(a INT, b INT, c INT, PRIMARY KEY(a, b))",
b: "CREATE TABLE t(a INT, b INT, c INT, PRIMARY KEY(a, b))",
cmp: 0,
join: "CREATE TABLE t(a INT, b INT, c INT, PRIMARY KEY(a, b))",
},
{
name: "join equal single index",
a: "CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, INDEX idx_b(b))",
b: "CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, INDEX idx_b(b))",
cmp: 0,
join: "CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, INDEX idx_b(b))",
},
{
name: "join equal unique index",
a: "CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, UNIQUE KEY uni_b(b))",
b: "CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, UNIQUE KEY uni_b(b))",
cmp: 0,
join: "CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, UNIQUE KEY uni_b(b))",
},
{
name: "join equal composite index",
a: "CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, INDEX idx_bc(b, c))",
b: "CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, INDEX idx_bc(b, c))",
cmp: 0,
join: "CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, INDEX idx_bc(b, c))",
},
{
name: "join equal composite unique index",
a: "CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, UNIQUE INDEX idx_bc(b, c))",
b: "CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, UNIQUE INDEX idx_bc(b, c))",
cmp: 0,
join: "CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, UNIQUE INDEX idx_bc(b, c))",
},
}
for _, tc := range testCases {
tia, err := toTableInfo(p, sctx, tc.a)
require.NoError(t, err)
tib, err := toTableInfo(p, sctx, tc.b)
require.NoError(t, err)
a := schemacmp.Encode(tia)
b := schemacmp.Encode(tib)
checkDecodeFieldTypes(t, tia, a)
checkDecodeFieldTypes(t, tib, b)
var j schemacmp.Table
if len(tc.joinErr) == 0 {
tij, err := toTableInfo(p, sctx, tc.join)
require.NoError(t, err)
j = schemacmp.Encode(tij)
}
cmp, err := a.Compare(b)
if len(tc.cmpErr) != 0 {
require.Regexp(t, tc.cmpErr, err)
} else {
require.NoError(t, err)
require.Equal(t, tc.cmp, cmp)
}
cmp, err = b.Compare(a)
if len(tc.cmpErr) != 0 {
require.Regexp(t, tc.cmpErr, err)
} else {
require.NoError(t, err)
require.Equal(t, cmp, -tc.cmp)
}
joined, err := a.Join(b)
if len(tc.joinErr) != 0 {
if err == nil {
t.Log("a = ", a)
t.Log("b = ", b)
t.Log("j = ", joined)
}
require.Regexp(t, tc.joinErr, err)
} else {
require.NoError(t, err)
require.Equal(t, joined, j)
}
joined, err = b.Join(a)
if len(tc.joinErr) != 0 {
require.Regexp(t, tc.joinErr, err)
} else {
require.NoError(t, err)
require.Equal(t, joined, j)
cmp, err = joined.Compare(a)
require.NoError(t, err)
require.GreaterOrEqual(t, cmp, 0)
cmp, err = joined.Compare(b)
require.NoError(t, err)
require.GreaterOrEqual(t, cmp, 0)
}
}
}
func TestTableString(t *testing.T) {
p := parser.New()
sctx := mock.NewContext()
ti, err := toTableInfo(p, sctx, "CREATE TABLE tb (a INT, b INT)")
require.NoError(t, err)
charsets := []string{"", mysql.DefaultCharset}
collates := []string{"", mysql.DefaultCollationName}
for _, charset := range charsets {
for _, collate := range collates {
ti.Charset = charset
ti.Collate = collate
sql := strings.ToLower(schemacmp.Encode(ti).String())
require.Equal(t, charset != "", strings.Contains(sql, "charset"))
require.Equal(t, collate != "", strings.Contains(sql, "collate"))
_, err := toTableInfo(p, sctx, sql)
require.NoError(t, err)
}
}
}