442 lines
12 KiB
Go
442 lines
12 KiB
Go
// 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,
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package model
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pingcap/tidb/parser/charset"
|
|
"github.com/pingcap/tidb/parser/mysql"
|
|
"github.com/pingcap/tidb/parser/types"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestT(t *testing.T) {
|
|
abc := NewCIStr("aBC")
|
|
require.Equal(t, "aBC", abc.O)
|
|
require.Equal(t, "abc", abc.L)
|
|
require.Equal(t, "aBC", abc.String())
|
|
}
|
|
|
|
func TestModelBasic(t *testing.T) {
|
|
column := &ColumnInfo{
|
|
ID: 1,
|
|
Name: NewCIStr("c"),
|
|
Offset: 0,
|
|
DefaultValue: 0,
|
|
FieldType: *types.NewFieldType(0),
|
|
Hidden: true,
|
|
}
|
|
column.Flag |= mysql.PriKeyFlag
|
|
|
|
index := &IndexInfo{
|
|
Name: NewCIStr("key"),
|
|
Table: NewCIStr("t"),
|
|
Columns: []*IndexColumn{
|
|
{
|
|
Name: NewCIStr("c"),
|
|
Offset: 0,
|
|
Length: 10,
|
|
}},
|
|
Unique: true,
|
|
Primary: true,
|
|
}
|
|
|
|
fk := &FKInfo{
|
|
RefCols: []CIStr{NewCIStr("a")},
|
|
Cols: []CIStr{NewCIStr("a")},
|
|
}
|
|
|
|
seq := &SequenceInfo{
|
|
Increment: 1,
|
|
MinValue: 1,
|
|
MaxValue: 100,
|
|
}
|
|
|
|
table := &TableInfo{
|
|
ID: 1,
|
|
Name: NewCIStr("t"),
|
|
Charset: "utf8",
|
|
Collate: "utf8_bin",
|
|
Columns: []*ColumnInfo{column},
|
|
Indices: []*IndexInfo{index},
|
|
ForeignKeys: []*FKInfo{fk},
|
|
PKIsHandle: true,
|
|
}
|
|
|
|
table2 := &TableInfo{
|
|
ID: 2,
|
|
Name: NewCIStr("s"),
|
|
Sequence: seq,
|
|
}
|
|
|
|
dbInfo := &DBInfo{
|
|
ID: 1,
|
|
Name: NewCIStr("test"),
|
|
Charset: "utf8",
|
|
Collate: "utf8_bin",
|
|
Tables: []*TableInfo{table},
|
|
}
|
|
|
|
n := dbInfo.Clone()
|
|
require.Equal(t, dbInfo, n)
|
|
|
|
pkName := table.GetPkName()
|
|
require.Equal(t, NewCIStr("c"), pkName)
|
|
newColumn := table.GetPkColInfo()
|
|
require.Equal(t, true, newColumn.Hidden)
|
|
require.Equal(t, column, newColumn)
|
|
inIdx := table.ColumnIsInIndex(column)
|
|
require.Equal(t, true, inIdx)
|
|
tp := IndexTypeBtree
|
|
require.Equal(t, "BTREE", tp.String())
|
|
tp = IndexTypeHash
|
|
require.Equal(t, "HASH", tp.String())
|
|
tp = 1e5
|
|
require.Equal(t, "", tp.String())
|
|
has := index.HasPrefixIndex()
|
|
require.Equal(t, true, has)
|
|
require.Equal(t, TSConvert2Time(table.UpdateTS), table.GetUpdateTime())
|
|
require.True(t, table2.IsSequence())
|
|
require.False(t, table2.IsBaseTable())
|
|
|
|
// Corner cases
|
|
column.Flag ^= mysql.PriKeyFlag
|
|
pkName = table.GetPkName()
|
|
require.Equal(t, NewCIStr(""), pkName)
|
|
newColumn = table.GetPkColInfo()
|
|
require.Nil(t, newColumn)
|
|
anCol := &ColumnInfo{
|
|
Name: NewCIStr("d"),
|
|
}
|
|
exIdx := table.ColumnIsInIndex(anCol)
|
|
require.Equal(t, false, exIdx)
|
|
anIndex := &IndexInfo{
|
|
Columns: []*IndexColumn{},
|
|
}
|
|
no := anIndex.HasPrefixIndex()
|
|
require.Equal(t, false, no)
|
|
|
|
extraPK := NewExtraHandleColInfo()
|
|
require.Equal(t, mysql.NotNullFlag|mysql.PriKeyFlag, extraPK.Flag)
|
|
require.Equal(t, charset.CharsetBin, extraPK.Charset)
|
|
require.Equal(t, charset.CollationBin, extraPK.Collate)
|
|
}
|
|
|
|
func TestJobStartTime(t *testing.T) {
|
|
job := &Job{
|
|
ID: 123,
|
|
BinlogInfo: &HistoryInfo{},
|
|
}
|
|
require.Equal(t, TSConvert2Time(job.StartTS), time.Unix(0, 0))
|
|
require.Equal(t, fmt.Sprintf("ID:123, Type:none, State:none, SchemaState:queueing, SchemaID:0, TableID:0, RowCount:0, ArgLen:0, start time: %s, Err:<nil>, ErrCount:0, SnapshotVersion:0", time.Unix(0, 0)), job.String())
|
|
}
|
|
|
|
func TestJobCodec(t *testing.T) {
|
|
type A struct {
|
|
Name string
|
|
}
|
|
tzName, tzOffset := time.Now().In(time.UTC).Zone()
|
|
job := &Job{
|
|
ID: 1,
|
|
TableID: 2,
|
|
SchemaID: 1,
|
|
BinlogInfo: &HistoryInfo{},
|
|
Args: []interface{}{NewCIStr("a"), A{Name: "abc"}},
|
|
ReorgMeta: &DDLReorgMeta{
|
|
Location: &TimeZone{Name: tzName, Offset: tzOffset},
|
|
},
|
|
}
|
|
job.BinlogInfo.AddDBInfo(123, &DBInfo{ID: 1, Name: NewCIStr("test_history_db")})
|
|
job.BinlogInfo.AddTableInfo(123, &TableInfo{ID: 1, Name: NewCIStr("test_history_tbl")})
|
|
|
|
// Test IsDependentOn.
|
|
// job: table ID is 2
|
|
// job1: table ID is 2
|
|
var err error
|
|
job1 := &Job{
|
|
ID: 2,
|
|
TableID: 2,
|
|
SchemaID: 1,
|
|
Type: ActionRenameTable,
|
|
BinlogInfo: &HistoryInfo{},
|
|
Args: []interface{}{int64(3), NewCIStr("new_table_name")},
|
|
}
|
|
job1.RawArgs, err = json.Marshal(job1.Args)
|
|
require.NoError(t, err)
|
|
isDependent, err := job.IsDependentOn(job1)
|
|
require.NoError(t, err)
|
|
require.True(t, isDependent)
|
|
// job1: rename table, old schema ID is 3
|
|
// job2: create schema, schema ID is 3
|
|
job2 := &Job{
|
|
ID: 3,
|
|
TableID: 3,
|
|
SchemaID: 3,
|
|
Type: ActionCreateSchema,
|
|
BinlogInfo: &HistoryInfo{},
|
|
}
|
|
isDependent, err = job2.IsDependentOn(job1)
|
|
require.NoError(t, err)
|
|
require.True(t, isDependent)
|
|
|
|
require.Equal(t, false, job.IsCancelled())
|
|
b, err := job.Encode(false)
|
|
require.NoError(t, err)
|
|
newJob := &Job{}
|
|
err = newJob.Decode(b)
|
|
require.NoError(t, err)
|
|
require.Equal(t, job.BinlogInfo, newJob.BinlogInfo)
|
|
name := CIStr{}
|
|
a := A{}
|
|
err = newJob.DecodeArgs(&name, &a)
|
|
require.NoError(t, err)
|
|
require.Equal(t, NewCIStr(""), name)
|
|
require.Equal(t, A{Name: ""}, a)
|
|
require.Greater(t, len(newJob.String()), 0)
|
|
require.Equal(t, newJob.ReorgMeta.Location.Name, tzName)
|
|
require.Equal(t, newJob.ReorgMeta.Location.Offset, tzOffset)
|
|
|
|
job.BinlogInfo.Clean()
|
|
b1, err := job.Encode(true)
|
|
require.NoError(t, err)
|
|
newJob = &Job{}
|
|
err = newJob.Decode(b1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, &HistoryInfo{}, newJob.BinlogInfo)
|
|
name = CIStr{}
|
|
a = A{}
|
|
err = newJob.DecodeArgs(&name, &a)
|
|
require.NoError(t, err)
|
|
require.Equal(t, NewCIStr("a"), name)
|
|
require.Equal(t, A{Name: "abc"}, a)
|
|
require.Greater(t, len(newJob.String()), 0)
|
|
|
|
b2, err := job.Encode(true)
|
|
require.NoError(t, err)
|
|
newJob = &Job{}
|
|
err = newJob.Decode(b2)
|
|
require.NoError(t, err)
|
|
name = CIStr{}
|
|
// Don't decode to a here.
|
|
err = newJob.DecodeArgs(&name)
|
|
require.NoError(t, err)
|
|
require.Equal(t, NewCIStr("a"), name)
|
|
require.Greater(t, len(newJob.String()), 0)
|
|
|
|
job.State = JobStateDone
|
|
require.True(t, job.IsDone())
|
|
require.True(t, job.IsFinished())
|
|
require.False(t, job.IsRunning())
|
|
require.False(t, job.IsSynced())
|
|
require.False(t, job.IsRollbackDone())
|
|
job.SetRowCount(3)
|
|
require.Equal(t, int64(3), job.GetRowCount())
|
|
}
|
|
|
|
func TestState(t *testing.T) {
|
|
schemaTbl := []SchemaState{
|
|
StateDeleteOnly,
|
|
StateWriteOnly,
|
|
StateWriteReorganization,
|
|
StateDeleteReorganization,
|
|
StatePublic,
|
|
StateGlobalTxnOnly,
|
|
}
|
|
|
|
for _, state := range schemaTbl {
|
|
require.Greater(t, len(state.String()), 0)
|
|
}
|
|
|
|
jobTbl := []JobState{
|
|
JobStateRunning,
|
|
JobStateDone,
|
|
JobStateCancelled,
|
|
JobStateRollingback,
|
|
JobStateRollbackDone,
|
|
JobStateSynced,
|
|
}
|
|
|
|
for _, state := range jobTbl {
|
|
require.Greater(t, len(state.String()), 0)
|
|
}
|
|
}
|
|
|
|
func TestString(t *testing.T) {
|
|
acts := []struct {
|
|
act ActionType
|
|
result string
|
|
}{
|
|
{ActionNone, "none"},
|
|
{ActionAddForeignKey, "add foreign key"},
|
|
{ActionDropForeignKey, "drop foreign key"},
|
|
{ActionTruncateTable, "truncate table"},
|
|
{ActionModifyColumn, "modify column"},
|
|
{ActionRenameTable, "rename table"},
|
|
{ActionRenameTables, "rename tables"},
|
|
{ActionSetDefaultValue, "set default value"},
|
|
{ActionCreateSchema, "create schema"},
|
|
{ActionDropSchema, "drop schema"},
|
|
{ActionCreateTable, "create table"},
|
|
{ActionDropTable, "drop table"},
|
|
{ActionAddIndex, "add index"},
|
|
{ActionDropIndex, "drop index"},
|
|
{ActionAddColumn, "add column"},
|
|
{ActionAddColumns, "add multi-columns"},
|
|
{ActionDropColumn, "drop column"},
|
|
{ActionDropColumns, "drop multi-columns"},
|
|
{ActionModifySchemaCharsetAndCollate, "modify schema charset and collate"},
|
|
{ActionDropIndexes, "drop multi-indexes"},
|
|
{ActionAlterTablePlacement, "alter table placement"},
|
|
{ActionAlterTablePartitionPlacement, "alter table partition placement"},
|
|
{ActionAlterNoCacheTable, "alter table nocache"},
|
|
}
|
|
|
|
for _, v := range acts {
|
|
str := v.act.String()
|
|
require.Equal(t, v.result, str)
|
|
}
|
|
}
|
|
|
|
func TestUnmarshalCIStr(t *testing.T) {
|
|
var ci CIStr
|
|
|
|
// Test unmarshal CIStr from a single string.
|
|
str := "aaBB"
|
|
buf, err := json.Marshal(str)
|
|
require.NoError(t, err)
|
|
require.NoError(t, ci.UnmarshalJSON(buf))
|
|
require.Equal(t, str, ci.O)
|
|
require.Equal(t, "aabb", ci.L)
|
|
|
|
buf, err = json.Marshal(ci)
|
|
require.NoError(t, err)
|
|
require.Equal(t, `{"O":"aaBB","L":"aabb"}`, string(buf))
|
|
require.NoError(t, ci.UnmarshalJSON(buf))
|
|
require.Equal(t, str, ci.O)
|
|
require.Equal(t, "aabb", ci.L)
|
|
}
|
|
|
|
func TestDefaultValue(t *testing.T) {
|
|
srcCol := &ColumnInfo{
|
|
ID: 1,
|
|
}
|
|
randPlainStr := "random_plain_string"
|
|
|
|
oldPlainCol := srcCol.Clone()
|
|
oldPlainCol.Name = NewCIStr("oldPlainCol")
|
|
oldPlainCol.FieldType = *types.NewFieldType(mysql.TypeLong)
|
|
oldPlainCol.DefaultValue = randPlainStr
|
|
oldPlainCol.OriginDefaultValue = randPlainStr
|
|
|
|
newPlainCol := srcCol.Clone()
|
|
newPlainCol.Name = NewCIStr("newPlainCol")
|
|
newPlainCol.FieldType = *types.NewFieldType(mysql.TypeLong)
|
|
err := newPlainCol.SetDefaultValue(1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, newPlainCol.GetDefaultValue())
|
|
err = newPlainCol.SetDefaultValue(randPlainStr)
|
|
require.NoError(t, err)
|
|
require.Equal(t, randPlainStr, newPlainCol.GetDefaultValue())
|
|
|
|
randBitStr := string([]byte{25, 185})
|
|
|
|
oldBitCol := srcCol.Clone()
|
|
oldBitCol.Name = NewCIStr("oldBitCol")
|
|
oldBitCol.FieldType = *types.NewFieldType(mysql.TypeBit)
|
|
oldBitCol.DefaultValue = randBitStr
|
|
oldBitCol.OriginDefaultValue = randBitStr
|
|
|
|
newBitCol := srcCol.Clone()
|
|
newBitCol.Name = NewCIStr("newBitCol")
|
|
newBitCol.FieldType = *types.NewFieldType(mysql.TypeBit)
|
|
err = newBitCol.SetDefaultValue(1)
|
|
// Only string type is allowed in BIT column.
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "Invalid default value")
|
|
require.Equal(t, 1, newBitCol.GetDefaultValue())
|
|
err = newBitCol.SetDefaultValue(randBitStr)
|
|
require.NoError(t, err)
|
|
require.Equal(t, randBitStr, newBitCol.GetDefaultValue())
|
|
|
|
nullBitCol := srcCol.Clone()
|
|
nullBitCol.Name = NewCIStr("nullBitCol")
|
|
nullBitCol.FieldType = *types.NewFieldType(mysql.TypeBit)
|
|
err = nullBitCol.SetOriginDefaultValue(nil)
|
|
require.NoError(t, err)
|
|
require.Nil(t, nullBitCol.GetOriginDefaultValue())
|
|
|
|
testCases := []struct {
|
|
col *ColumnInfo
|
|
isConsistent bool
|
|
}{
|
|
{oldPlainCol, true},
|
|
{oldBitCol, false},
|
|
{newPlainCol, true},
|
|
{newBitCol, true},
|
|
{nullBitCol, true},
|
|
}
|
|
for _, tc := range testCases {
|
|
col, isConsistent := tc.col, tc.isConsistent
|
|
comment := fmt.Sprintf("%s assertion failed", col.Name.O)
|
|
bytes, err := json.Marshal(col)
|
|
require.NoError(t, err, comment)
|
|
var newCol ColumnInfo
|
|
err = json.Unmarshal(bytes, &newCol)
|
|
require.NoError(t, err, comment)
|
|
if isConsistent {
|
|
require.Equal(t, newCol.GetDefaultValue(), col.GetDefaultValue())
|
|
require.Equal(t, newCol.GetOriginDefaultValue(), col.GetOriginDefaultValue())
|
|
} else {
|
|
require.False(t, col.DefaultValue == newCol.DefaultValue, comment)
|
|
require.False(t, col.DefaultValue == newCol.DefaultValue, comment)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPlacementSettingsString(t *testing.T) {
|
|
settings := &PlacementSettings{
|
|
PrimaryRegion: "us-east-1",
|
|
Regions: "us-east-1,us-east-2",
|
|
Schedule: "EVEN",
|
|
}
|
|
require.Equal(t, "PRIMARY_REGION=\"us-east-1\" REGIONS=\"us-east-1,us-east-2\" SCHEDULE=\"EVEN\"", settings.String())
|
|
|
|
settings = &PlacementSettings{
|
|
LeaderConstraints: "[+region=bj]",
|
|
}
|
|
require.Equal(t, "LEADER_CONSTRAINTS=\"[+region=bj]\"", settings.String())
|
|
|
|
settings = &PlacementSettings{
|
|
Voters: 1,
|
|
VoterConstraints: "[+region=us-east-1]",
|
|
Followers: 2,
|
|
FollowerConstraints: "[+disk=ssd]",
|
|
Learners: 3,
|
|
LearnerConstraints: "[+region=us-east-2]",
|
|
}
|
|
require.Equal(t, "VOTERS=1 VOTER_CONSTRAINTS=\"[+region=us-east-1]\" FOLLOWERS=2 FOLLOWER_CONSTRAINTS=\"[+disk=ssd]\" LEARNERS=3 LEARNER_CONSTRAINTS=\"[+region=us-east-2]\"", settings.String())
|
|
|
|
settings = &PlacementSettings{
|
|
Voters: 3,
|
|
Followers: 2,
|
|
Learners: 1,
|
|
Constraints: "{+us-east-1:1,+us-east-2:1}",
|
|
}
|
|
require.Equal(t, "CONSTRAINTS=\"{+us-east-1:1,+us-east-2:1}\" VOTERS=3 FOLLOWERS=2 LEARNERS=1", settings.String())
|
|
}
|