Files
tidb/pkg/meta/meta_test.go

1371 lines
38 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,
// 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 meta_test
import (
"context"
"encoding/json"
"fmt"
"regexp"
"sort"
"strconv"
"testing"
"time"
"github.com/pingcap/errors"
"github.com/pingcap/tidb/pkg/config/kerneltype"
"github.com/pingcap/tidb/pkg/ddl"
"github.com/pingcap/tidb/pkg/dxf/framework/schstatus"
infoschemacontext "github.com/pingcap/tidb/pkg/infoschema/context"
"github.com/pingcap/tidb/pkg/kv"
"github.com/pingcap/tidb/pkg/meta"
"github.com/pingcap/tidb/pkg/meta/metadef"
"github.com/pingcap/tidb/pkg/meta/model"
"github.com/pingcap/tidb/pkg/parser"
"github.com/pingcap/tidb/pkg/parser/ast"
"github.com/pingcap/tidb/pkg/parser/terror"
_ "github.com/pingcap/tidb/pkg/planner/core"
"github.com/pingcap/tidb/pkg/session"
"github.com/pingcap/tidb/pkg/store/mockstore"
"github.com/pingcap/tidb/pkg/util"
"github.com/pingcap/tidb/pkg/util/dbterror"
"github.com/pingcap/tidb/pkg/util/intest"
"github.com/pingcap/tidb/pkg/util/mock"
"github.com/stretchr/testify/require"
)
func TestPlacementPolicy(t *testing.T) {
store, err := mockstore.NewMockStore(mockstore.WithStoreType(mockstore.EmbedUnistore))
require.NoError(t, err)
defer func() {
err := store.Close()
require.NoError(t, err)
}()
txn, err := store.Begin()
require.NoError(t, err)
// test the independent policy ID allocation.
m := meta.NewMutator(txn)
// test the meta storage of placemnt policy.
policy := &model.PolicyInfo{
ID: 1,
Name: ast.NewCIStr("aa"),
PlacementSettings: &model.PlacementSettings{
PrimaryRegion: "my primary",
Regions: "my regions",
Learners: 1,
Followers: 2,
Voters: 3,
Schedule: "even",
Constraints: "+disk=ssd",
LearnerConstraints: "+zone=shanghai",
},
}
err = m.CreatePolicy(policy)
require.NoError(t, err)
require.Equal(t, policy.ID, int64(1))
err = m.CreatePolicy(policy)
require.NotNil(t, err)
require.True(t, meta.ErrPolicyExists.Equal(err))
val, err := m.GetPolicy(1)
require.NoError(t, err)
require.Equal(t, policy, val)
// mock updating the placement policy.
policy.Name = ast.NewCIStr("bb")
policy.LearnerConstraints = "+zone=nanjing"
err = m.UpdatePolicy(policy)
require.NoError(t, err)
val, err = m.GetPolicy(1)
require.NoError(t, err)
require.Equal(t, policy, val)
ps, err := m.ListPolicies()
require.NoError(t, err)
require.Equal(t, []*model.PolicyInfo{policy}, ps)
err = txn.Commit(context.Background())
require.NoError(t, err)
// fetch the stored value after committing.
txn, err = store.Begin()
require.NoError(t, err)
m = meta.NewMutator(txn)
val, err = m.GetPolicy(1)
require.NoError(t, err)
require.Equal(t, policy, val)
err = txn.Commit(context.Background())
require.NoError(t, err)
}
func TestResourceGroup(t *testing.T) {
store, err := mockstore.NewMockStore()
require.NoError(t, err)
defer func() {
require.NoError(t, store.Close())
}()
txn, err := store.Begin()
require.NoError(t, err)
// test the independent policy ID allocation.
m := meta.NewMutator(txn)
groups, err := m.ListResourceGroups()
require.NoError(t, err)
require.Equal(t, len(groups), 1)
require.Equal(t, groups[0], meta.DefaultGroupMeta4Test())
groupID := int64(2)
checkResourceGroup := func(ru uint64) {
rg, err := m.GetResourceGroup(groupID)
require.NoError(t, err)
require.Equal(t, rg.RURate, ru)
}
rg := &model.ResourceGroupInfo{
ID: groupID,
Name: ast.NewCIStr("aa"),
ResourceGroupSettings: &model.ResourceGroupSettings{
RURate: 100,
},
}
require.NoError(t, m.AddResourceGroup(rg))
checkResourceGroup(100)
groups, err = m.ListResourceGroups()
require.NoError(t, err)
require.Equal(t, len(groups), 2)
rg.RURate = 200
require.NoError(t, m.UpdateResourceGroup(rg))
checkResourceGroup(200)
m.DropResourceGroup(groupID)
_, err = m.GetResourceGroup(groupID)
require.Error(t, err)
}
func TestMeta(t *testing.T) {
store, err := mockstore.NewMockStore(mockstore.WithStoreType(mockstore.EmbedUnistore))
require.NoError(t, err)
defer func() {
err := store.Close()
require.NoError(t, err)
}()
txn, err := store.Begin()
require.NoError(t, err)
m := meta.NewMutator(txn)
n, err := m.GenGlobalID()
require.NoError(t, err)
require.Equal(t, int64(1), n)
n, err = m.GetGlobalID()
require.NoError(t, err)
require.Equal(t, int64(1), n)
var wg util.WaitGroupWrapper
wg.Run(func() {
ids, err := m.GenGlobalIDs(3)
require.NoError(t, err)
anyMatch(t, ids, []int64{2, 3, 4}, []int64{6, 7, 8})
})
wg.Run(func() {
ids, err := m.GenGlobalIDs(4)
require.NoError(t, err)
anyMatch(t, ids, []int64{5, 6, 7, 8}, []int64{2, 3, 4, 5})
})
wg.Wait()
n, err = m.GetSchemaVersion()
require.NoError(t, err)
require.Equal(t, int64(0), n)
n, err = m.GenSchemaVersion()
require.NoError(t, err)
require.Equal(t, int64(1), n)
n, err = m.GetSchemaVersion()
require.NoError(t, err)
require.Equal(t, int64(1), n)
dbInfo := &model.DBInfo{
ID: 1,
Name: ast.NewCIStr("a"),
}
err = m.CreateDatabase(dbInfo)
require.NoError(t, err)
err = m.CreateDatabase(dbInfo)
require.NotNil(t, err)
require.True(t, meta.ErrDBExists.Equal(err))
v, err := m.GetDatabase(1)
require.NoError(t, err)
require.Equal(t, dbInfo, v)
dbInfo.Name = ast.NewCIStr("aa")
err = m.UpdateDatabase(dbInfo)
require.NoError(t, err)
v, err = m.GetDatabase(1)
require.NoError(t, err)
require.Equal(t, dbInfo, v)
dbs, err := m.ListDatabases()
require.NoError(t, err)
require.Equal(t, []*model.DBInfo{dbInfo}, dbs)
tbInfo := &model.TableInfo{
ID: 1,
Name: ast.NewCIStr("t"),
DBID: dbInfo.ID,
}
err = m.CreateTableOrView(1, tbInfo)
require.NoError(t, err)
n, err = m.GetAutoIDAccessors(1, 1).RowID().Inc(10)
require.NoError(t, err)
require.Equal(t, int64(10), n)
n, err = m.GetAutoIDAccessors(1, 1).RowID().Get()
require.NoError(t, err)
require.Equal(t, int64(10), n)
err = m.CreateTableOrView(1, tbInfo)
require.NotNil(t, err)
require.True(t, meta.ErrTableExists.Equal(err))
tbInfo.Name = ast.NewCIStr("tt")
err = m.UpdateTable(1, tbInfo)
require.NoError(t, err)
table, err := m.GetTable(1, 1)
require.NoError(t, err)
require.Equal(t, tbInfo, table)
tblExist, err := m.CheckTableExists(1, 1)
require.NoError(t, err)
require.Equal(t, true, tblExist)
table, err = m.GetTable(1, 2)
require.NoError(t, err)
require.Nil(t, table)
tblExist, err = m.CheckTableExists(1, 2)
require.NoError(t, err)
require.Equal(t, false, tblExist)
tbInfo2 := &model.TableInfo{
ID: 2,
Name: ast.NewCIStr("bb"),
DBID: dbInfo.ID,
}
err = m.CreateTableOrView(1, tbInfo2)
require.NoError(t, err)
tblName := &model.TableNameInfo{ID: tbInfo.ID, Name: tbInfo.Name}
tblName2 := &model.TableNameInfo{ID: tbInfo2.ID, Name: tbInfo2.Name}
tableNames, err := m.ListSimpleTables(1)
require.NoError(t, err)
require.Equal(t, []*model.TableNameInfo{tblName, tblName2}, tableNames)
tables, err := m.ListTables(context.Background(), 1)
require.NoError(t, err)
require.Equal(t, []*model.TableInfo{tbInfo, tbInfo2}, tables)
{
idx := 0
err := m.IterTables(1, func(info *model.TableInfo) error {
require.Less(t, idx, 2)
if idx == 0 {
require.Equal(t, tbInfo, info)
idx += 1
} else {
require.Equal(t, tbInfo2, info)
}
return nil
})
require.NoError(t, err)
}
// Generate an auto id.
n, err = m.GetAutoIDAccessors(1, 2).RowID().Inc(10)
require.NoError(t, err)
require.Equal(t, int64(10), n)
// Make sure the auto id key-value entry is there.
n, err = m.GetAutoIDAccessors(1, 2).RowID().Get()
require.NoError(t, err)
require.Equal(t, int64(10), n)
err = m.DropTableOrView(1, tbInfo2.ID)
require.NoError(t, err)
err = m.GetAutoIDAccessors(1, tbInfo2.ID).Del()
require.NoError(t, err)
// Make sure auto id key-value entry is gone.
n, err = m.GetAutoIDAccessors(1, 2).RowID().Get()
require.NoError(t, err)
require.Equal(t, int64(0), n)
tableNames, err = m.ListSimpleTables(1)
require.NoError(t, err)
require.Equal(t, []*model.TableNameInfo{tblName}, tableNames)
tables, err = m.ListTables(context.Background(), 1)
require.NoError(t, err)
require.Equal(t, []*model.TableInfo{tbInfo}, tables)
{
idx := 0
err := m.IterTables(1, func(info *model.TableInfo) error {
require.Less(t, idx, 1)
require.Equal(t, tbInfo, info)
idx += 1
return nil
})
require.NoError(t, err)
}
// Test case for drop a table without delete auto id key-value entry.
tid := int64(100)
tbInfo100 := &model.TableInfo{
ID: tid,
Name: ast.NewCIStr("t_rename"),
}
// Create table.
err = m.CreateTableOrView(1, tbInfo100)
require.NoError(t, err)
// Update auto ID.
currentDBID := int64(1)
n, err = m.GetAutoIDAccessors(currentDBID, tid).RowID().Inc(10)
require.NoError(t, err)
require.Equal(t, int64(10), n)
// Test to update non-existing auto ID.
// The table ID doesn't exist.
// We can no longer test for non-existing ids.
nonExistentID := int64(1234)
_, err = m.GetAutoIDAccessors(currentDBID, nonExistentID).RowID().Inc(10)
require.NoError(t, err)
//require.True(t, meta.ErrTableNotExists.Equal(err))
// Test to update non-existing auto ID.
// The current database ID doesn't exist.
// We can no longer test for non-existing ids.
currentDBID = nonExistentID
_, err = m.GetAutoIDAccessors(currentDBID, tid).RowID().Inc(10)
require.NoError(t, err)
//require.True(t, meta.ErrDBNotExists.Equal(err))
// Test case for CreateTableAndSetAutoID.
tbInfo3 := &model.TableInfo{
ID: 3,
Name: ast.NewCIStr("tbl3"),
}
err = m.CreateTableAndSetAutoID(1, tbInfo3, model.AutoIDGroup{RowID: 123, IncrementID: 0})
require.NoError(t, err)
id, err := m.GetAutoIDAccessors(1, tbInfo3.ID).RowID().Get()
require.NoError(t, err)
require.Equal(t, int64(123), id)
// Test case for GenAutoTableIDKeyValue.
key, val := m.GenAutoTableIDKeyValue(1, tbInfo3.ID, 1234)
require.Equal(t, []byte(strconv.FormatInt(1234, 10)), val)
require.Equal(t, []byte{0x6d, 0x44, 0x42, 0x3a, 0x31, 0x0, 0x0, 0x0, 0x0, 0xfb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x68, 0x54, 0x49, 0x44, 0x3a, 0x33, 0x0, 0x0, 0x0, 0xfc}, key)
err = m.DropDatabase(1)
require.NoError(t, err)
err = m.DropDatabase(currentDBID)
require.NoError(t, err)
dbs, err = m.ListDatabases()
require.NoError(t, err)
require.Len(t, dbs, 0)
bootstrapVer, err := m.GetBootstrapVersion()
require.NoError(t, err)
require.Equal(t, int64(0), bootstrapVer)
err = m.FinishBootstrap(int64(1))
require.NoError(t, err)
bootstrapVer, err = m.GetBootstrapVersion()
require.NoError(t, err)
require.Equal(t, int64(1), bootstrapVer)
// Test case for meta.FinishBootstrap with a version.
err = m.FinishBootstrap(int64(10))
require.NoError(t, err)
bootstrapVer, err = m.GetBootstrapVersion()
require.NoError(t, err)
require.Equal(t, int64(10), bootstrapVer)
// Test case for SchemaDiff.
schemaDiff := &model.SchemaDiff{
Version: 100,
SchemaID: 1,
Type: model.ActionTruncateTable,
TableID: 2,
OldTableID: 3,
}
err = m.SetSchemaDiff(schemaDiff)
require.NoError(t, err)
readDiff, err := m.GetSchemaDiff(schemaDiff.Version)
require.NoError(t, err)
require.Equal(t, schemaDiff, readDiff)
// Test for BDR role
role, err := m.GetBDRRole()
require.NoError(t, err)
require.Len(t, role, 0)
require.NoError(t, m.SetBDRRole(string(ast.BDRRolePrimary)))
role, err = m.GetBDRRole()
require.NoError(t, err)
require.Equal(t, string(ast.BDRRolePrimary), role)
require.NoError(t, m.ClearBDRRole())
role, err = m.GetBDRRole()
require.NoError(t, err)
require.Len(t, role, 0)
err = txn.Commit(context.Background())
require.NoError(t, err)
// Test for DDLJobHistoryKey.
key = meta.DDLJobHistoryKey(m, 888)
require.Equal(t, []byte{0x6d, 0x44, 0x44, 0x4c, 0x4a, 0x6f, 0x62, 0x48, 0x69, 0xff, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x0, 0x0, 0x0, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x78, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf7}, key)
}
func TestSnapshot(t *testing.T) {
store, err := mockstore.NewMockStore(mockstore.WithStoreType(mockstore.EmbedUnistore))
require.NoError(t, err)
defer func() {
err := store.Close()
require.NoError(t, err)
}()
txn, _ := store.Begin()
m := meta.NewMutator(txn)
_, err = m.GenGlobalID()
require.NoError(t, err)
n, _ := m.GetGlobalID()
require.Equal(t, int64(1), n)
err = txn.Commit(context.Background())
require.NoError(t, err)
ver1, _ := store.CurrentVersion(kv.GlobalTxnScope)
time.Sleep(time.Millisecond)
txn, _ = store.Begin()
m = meta.NewMutator(txn)
_, err = m.GenGlobalID()
require.NoError(t, err)
n, _ = m.GetGlobalID()
require.Equal(t, int64(2), n)
err = txn.Commit(context.Background())
require.NoError(t, err)
snapshot := store.GetSnapshot(ver1)
snapMeta := meta.NewReader(snapshot)
n, _ = snapMeta.GetGlobalID()
require.Equal(t, int64(1), n)
}
func TestElement(t *testing.T) {
checkElement := func(key []byte, resErr error) {
e := &meta.Element{ID: 123, TypeKey: key}
eBytes := e.EncodeElement()
resE, err := meta.DecodeElement(eBytes)
if resErr == nil {
require.NoError(t, err)
require.Equal(t, resE, e)
} else {
require.EqualError(t, err, resErr.Error())
}
}
key := []byte("_col")
checkElement(key, errors.Errorf(`invalid encoded element key prefix "_col\x00"`))
checkElement(meta.IndexElementKey, nil)
checkElement(meta.ColumnElementKey, nil)
key = []byte("inexistent")
checkElement(key, errors.Errorf("invalid encoded element key prefix %q", key[:5]))
_, err := meta.DecodeElement([]byte("_col"))
require.EqualError(t, err, `invalid encoded element "_col" length 4`)
_, err = meta.DecodeElement(meta.ColumnElementKey)
require.EqualError(t, err, `invalid encoded element "_col_" length 5`)
}
func BenchmarkGenGlobalIDs(b *testing.B) {
store, err := mockstore.NewMockStore(mockstore.WithStoreType(mockstore.EmbedUnistore))
require.NoError(b, err)
defer func() {
err := store.Close()
require.NoError(b, err)
}()
txn, err := store.Begin()
require.NoError(b, err)
defer func() {
err := txn.Rollback()
require.NoError(b, err)
}()
m := meta.NewMutator(txn)
b.ResetTimer()
var ids []int64
for i := 0; i < b.N; i++ {
ids, _ = m.GenGlobalIDs(10)
}
require.Len(b, ids, 10)
require.Equal(b, int64(b.N)*10, ids[9])
}
func BenchmarkGenGlobalIDOneByOne(b *testing.B) {
store, err := mockstore.NewMockStore()
require.NoError(b, err)
defer func() {
err := store.Close()
require.NoError(b, err)
}()
txn, err := store.Begin()
require.NoError(b, err)
defer func() {
err := txn.Rollback()
require.NoError(b, err)
}()
m := meta.NewMutator(txn)
b.ResetTimer()
var id int64
for i := 0; i < b.N; i++ {
for range 10 {
id, _ = m.GenGlobalID()
}
}
require.Equal(b, int64(b.N)*10, id)
}
func anyMatch(t *testing.T, ids []int64, candidates ...[]int64) {
comment := fmt.Sprintf("ids %v cannot match any of %v", ids, candidates)
for _, candidate := range candidates {
if match(ids, candidate) {
return
}
}
require.FailNow(t, comment)
}
func match(ids, candidate []int64) bool {
if len(ids) != len(candidate) {
return false
}
for i, v := range candidate {
if ids[i] != v {
return false
}
}
return true
}
func TestDBKey(b *testing.T) {
var dbID int64 = 10
dbKey := meta.DBkey(dbID)
require.True(b, meta.IsDBkey(dbKey))
parseID, err := meta.ParseDBKey(dbKey)
require.NoError(b, err)
require.Equal(b, dbID, parseID)
}
func TestTableKey(b *testing.T) {
var tableID int64 = 10
tableKey := meta.TableKey(tableID)
require.True(b, meta.IsTableKey(tableKey))
parseID, err := meta.ParseTableKey(tableKey)
require.NoError(b, err)
require.Equal(b, tableID, parseID)
}
func TestAutoTableIDKey(b *testing.T) {
var tableID int64 = 10
tableKey := meta.AutoTableIDKey(tableID)
require.True(b, meta.IsAutoTableIDKey(tableKey))
id, err := meta.ParseAutoTableIDKey(tableKey)
require.NoError(b, err)
require.Equal(b, tableID, id)
}
func TestAutoRandomTableIDKey(b *testing.T) {
var tableID int64 = 10
key := meta.AutoRandomTableIDKey(tableID)
require.True(b, meta.IsAutoRandomTableIDKey(key))
id, err := meta.ParseAutoRandomTableIDKey(key)
require.NoError(b, err)
require.Equal(b, tableID, id)
}
func TestIterDatabases(t *testing.T) {
// Create a new mock store and a transaction
store, err := mockstore.NewMockStore(mockstore.WithStoreType(mockstore.EmbedUnistore))
require.NoError(t, err)
defer func() {
require.NoError(t, store.Close())
}()
txn, err := store.Begin()
require.NoError(t, err)
m := meta.NewMutator(txn)
// Prepare multiple databases
db1 := &model.DBInfo{ID: 1, Name: ast.NewCIStr("db1")}
db2 := &model.DBInfo{ID: 2, Name: ast.NewCIStr("db2")}
db3 := &model.DBInfo{ID: 3, Name: ast.NewCIStr("db3")}
require.NoError(t, m.CreateDatabase(db1))
require.NoError(t, m.CreateDatabase(db2))
require.NoError(t, m.CreateDatabase(db3))
// Iterate all databases and collect names
var names []string
err = m.IterDatabases(func(info *model.DBInfo) error {
names = append(names, info.Name.O)
return nil
})
require.NoError(t, err)
require.Len(t, names, 3)
sort.Strings(names)
require.Equal(t, []string{"db1", "db2", "db3"}, names)
// Verify early stop behavior by returning a sentinel error from the callback
count := 0
sentinel := errors.New("stop")
err = m.IterDatabases(func(info *model.DBInfo) error {
count++
if count == 2 {
return sentinel
}
return nil
})
require.Error(t, err)
require.True(t, errors.ErrorEqual(err, sentinel))
require.Equal(t, 2, count)
// Commit the transaction to ensure no side effects remain
require.NoError(t, txn.Commit(context.Background()))
}
func TestSequenceKey(b *testing.T) {
var tableID int64 = 10
key := meta.SequenceKey(tableID)
require.True(b, meta.IsSequenceKey(key))
id, err := meta.ParseSequenceKey(key)
require.NoError(b, err)
require.Equal(b, tableID, id)
}
func TestCreateMySQLDatabase(t *testing.T) {
store, err := mockstore.NewMockStore()
require.NoError(t, err)
defer func() {
require.NoError(t, store.Close())
}()
txn, err := store.Begin()
require.NoError(t, err)
m := meta.NewMutator(txn)
dbID, err := m.CreateMySQLDatabaseIfNotExists()
require.NoError(t, err)
if kerneltype.IsNextGen() {
require.Equal(t, metadef.SystemDatabaseID, dbID)
} else {
require.EqualValues(t, 1, dbID)
}
anotherDBID, err := m.CreateMySQLDatabaseIfNotExists()
require.NoError(t, err)
require.Equal(t, dbID, anotherDBID)
err = txn.Rollback()
require.NoError(t, err)
}
func TestIsTableInfoMustLoad(t *testing.T) {
tableInfo := &model.TableInfo{
TTLInfo: &model.TTLInfo{IntervalExprStr: "1", IntervalTimeUnit: int(ast.TimeUnitDay), JobInterval: "1h"},
State: model.StatePublic,
}
b, err := json.Marshal(tableInfo)
require.NoError(t, err)
require.True(t, meta.IsTableInfoMustLoad(b))
tableInfo = &model.TableInfo{
TiFlashReplica: &model.TiFlashReplicaInfo{Count: 1},
State: model.StatePublic,
}
b, err = json.Marshal(tableInfo)
require.NoError(t, err)
require.True(t, meta.IsTableInfoMustLoad(b))
tableInfo = &model.TableInfo{
PlacementPolicyRef: &model.PolicyRefInfo{ID: 1},
State: model.StatePublic,
}
b, err = json.Marshal(tableInfo)
require.NoError(t, err)
require.True(t, meta.IsTableInfoMustLoad(b))
tableInfo = &model.TableInfo{
Partition: &model.PartitionInfo{Expr: "a"},
State: model.StatePublic,
}
b, err = json.Marshal(tableInfo)
require.NoError(t, err)
require.True(t, meta.IsTableInfoMustLoad(b))
tableInfo = &model.TableInfo{
Lock: &model.TableLockInfo{State: model.TableLockStatePreLock},
State: model.StatePublic,
}
b, err = json.Marshal(tableInfo)
require.NoError(t, err)
require.True(t, meta.IsTableInfoMustLoad(b))
tableInfo = &model.TableInfo{
ForeignKeys: []*model.FKInfo{{ID: 1}},
State: model.StatePublic,
}
b, err = json.Marshal(tableInfo)
require.NoError(t, err)
require.True(t, meta.IsTableInfoMustLoad(b))
tableInfo = tableInfo.Clone()
b, err = json.Marshal(tableInfo)
require.NoError(t, err)
require.True(t, meta.IsTableInfoMustLoad(b))
tableInfo.ForeignKeys = nil
tableInfo = tableInfo.Clone()
b, err = json.Marshal(tableInfo)
require.NoError(t, err)
require.False(t, meta.IsTableInfoMustLoad(b))
tableInfo.ForeignKeys = make([]*model.FKInfo, 0)
tableInfo = tableInfo.Clone()
b, err = json.Marshal(tableInfo)
require.NoError(t, err)
require.False(t, meta.IsTableInfoMustLoad(b))
tableInfo.ForeignKeys = nil
b, err = json.Marshal(tableInfo)
require.NoError(t, err)
require.False(t, meta.IsTableInfoMustLoad(b))
tableInfo.ForeignKeys = make([]*model.FKInfo, 0)
b, err = json.Marshal(tableInfo)
require.NoError(t, err)
require.False(t, meta.IsTableInfoMustLoad(b))
tableInfo = &model.TableInfo{
TempTableType: model.TempTableGlobal,
State: model.StatePublic,
}
b, err = json.Marshal(tableInfo)
require.NoError(t, err)
require.True(t, meta.IsTableInfoMustLoad(b))
tableInfo = &model.TableInfo{
ID: 123,
State: model.StatePublic,
}
b, err = json.Marshal(tableInfo)
require.NoError(t, err)
require.False(t, meta.IsTableInfoMustLoad(b))
tableInfo = &model.TableInfo{
State: model.StatePublic,
}
b, err = json.Marshal(tableInfo)
require.NoError(t, err)
require.False(t, meta.IsTableInfoMustLoad(b))
}
func TestIsTableInfoMustLoadSubStringsOrder(t *testing.T) {
// The order matter!
// IsTableInfoMustLoad relies on the order of the json marshal result,
// or the internal of the json marshal in other words.
// This test cover the invariance, if Go std library changes, we can catch it.
tableInfo := &model.TableInfo{}
b, err := json.Marshal(tableInfo)
require.NoError(t, err)
expect := `{"id":0,"name":{"O":"","L":""},"charset":"","collate":"","cols":null,"index_info":null,"constraint_info":null,"fk_info":null,"state":0,"pk_is_handle":false,"is_common_handle":false,"common_handle_version":0,"comment":"","auto_inc_id":0,"auto_id_cache":0,"auto_rand_id":0,"max_col_id":0,"max_idx_id":0,"max_fk_id":0,"max_cst_id":0,"update_timestamp":0,"ShardRowIDBits":0,"max_shard_row_id_bits":0,"auto_random_bits":0,"auto_random_range_bits":0,"pre_split_regions":0,"partition":null,"compression":"","view":null,"sequence":null,"Lock":null,"version":0,"tiflash_replica":null,"is_columnar":false,"temp_table_type":0,"cache_table_status":0,"policy_ref_info":null,"stats_options":null,"exchange_partition_info":null,"ttl_info":null,"revision":0}`
require.Equal(t, expect, string(b))
}
func TestTableNameExtract(t *testing.T) {
var tbl model.TableInfo
tbl.Name = ast.NewCIStr(`a`)
b, err := json.Marshal(tbl)
require.NoError(t, err)
nameLRegex := regexp.MustCompile(meta.NameExtractRegexp)
nameLMatch := nameLRegex.FindStringSubmatch(string(b))
require.Len(t, nameLMatch, 2)
require.Equal(t, "a", nameLMatch[1])
tbl.Name = ast.NewCIStr(`"a"`)
b, err = json.Marshal(tbl)
require.NoError(t, err)
nameLMatch = nameLRegex.FindStringSubmatch(string(b))
require.Len(t, nameLMatch, 2)
require.Equal(t, `"a"`, meta.Unescape(nameLMatch[1]))
tbl.Name = ast.NewCIStr(`""a"`)
b, err = json.Marshal(tbl)
require.NoError(t, err)
nameLMatch = nameLRegex.FindStringSubmatch(string(b))
require.Len(t, nameLMatch, 2)
require.Equal(t, `""a"`, meta.Unescape(nameLMatch[1]))
tbl.Name = ast.NewCIStr(`"\"a"`)
b, err = json.Marshal(tbl)
require.NoError(t, err)
nameLMatch = nameLRegex.FindStringSubmatch(string(b))
require.Len(t, nameLMatch, 2)
require.Equal(t, `"\"a"`, meta.Unescape(nameLMatch[1]))
tbl.Name = ast.NewCIStr(`"\"啊"`)
b, err = json.Marshal(tbl)
require.NoError(t, err)
nameLMatch = nameLRegex.FindStringSubmatch(string(b))
require.Len(t, nameLMatch, 2)
require.Equal(t, `"\"啊"`, meta.Unescape(nameLMatch[1]))
}
func TestNameExtractFromJob(t *testing.T) {
type extractTestCase struct {
schemaName string
tableName string
}
var job model.Job
// Inject some table_name and schema_name into other fields of json
job.Error = dbterror.ClassDDL.Synthesize(terror.CodeUnknown, `test error, "table_name":"aaa", "schema_name":"bbb"`)
job.Warning = dbterror.ClassDDL.Synthesize(terror.CodeUnknown, `test warning, "table_name":"ccc", "schema_name":"ddd"`)
job.Query = `create table test.t1(id int) comment 'create table, table_name:"eee", schema_name:"fff"'`
var testCases = []extractTestCase{
// Normal string
{"", "schema_name"},
{"table_name", ""},
// String with quota
{`"quota_schema_name"`, `"quota_table_name"`},
{`"single_quota`, `""triple_quota"`},
{"\"schema_name\"", "\"table_name\""},
// String with slash
{"\\", "\\\\"},
// Unicode
{"中文1", "中文2"},
{"😋", "😭"},
// Other interpunction
{"comma,1", "dot.3"},
// Put it together
{`"combine:\\\",你好\\`, `"schema_name:1️⃣","table_name:2️⃣"`},
}
for _, tc := range testCases {
job.SchemaName = tc.schemaName
job.TableName = tc.tableName
b, err := job.Encode(true)
require.NoError(t, err)
schemaName, tableName, err := meta.ExtractSchemaAndTableNameFromJob(b)
require.NoError(t, err)
require.Equal(t, tc.schemaName, schemaName)
require.Equal(t, tc.tableName, tableName)
}
}
var benchCases = [][2]string{
{"narrow", `CREATE TABLE t (c INT PRIMARY KEY);`},
{"wide", `
CREATE TABLE t (
c BIGINT PRIMARY KEY AUTO_RANDOM,
c2 TINYINT,
c3 BLOB,
c4 VARCHAR(255) DEFAULT 'ohsdfihusdfihusdfiuh',
c5 FLOAT,
c6 BIGINT UNSIGNED,
c7 DECIMAL(10, 2),
c8 CHAR(10),
c9 TEXT,
c10 DATE,
c11 TIME,
c12 TIMESTAMP,
c13 DATETIME,
INDEX idx(c2),
INDEX idx2(c4, c5),
INDEX idx3(c6, c2),
UNIQUE INDEX idx4(c12),
INDEX idx5((c + c2))
);`},
}
func BenchmarkIsTableInfoMustLoad(b *testing.B) {
for _, benchCase := range benchCases {
b.Run(benchCase[0], func(b *testing.B) {
benchIsTableInfoMustLoad(b, benchCase[1])
})
}
}
func getTableInfoJSON(b *testing.B, sql string) []byte {
p := parser.New()
stmt, err := p.ParseOneStmt(sql, "", "")
require.NoError(b, err)
se := mock.NewContext()
tblInfo, err := ddl.MockTableInfo(se, stmt.(*ast.CreateTableStmt), 1)
require.NoError(b, err)
data, err := json.Marshal(tblInfo)
require.NoError(b, err)
return data
}
func benchIsTableInfoMustLoad(b *testing.B, sql string) {
data := getTableInfoJSON(b, sql)
b.ResetTimer()
for i := 0; i < b.N; i++ {
got := meta.IsTableInfoMustLoad(data)
intest.Assert(!got)
}
}
func BenchmarkTableNameInfo(b *testing.B) {
for _, benchCase := range benchCases {
b.Run(benchCase[0]+"-json", func(b *testing.B) {
benchJSONTableNameInfo(b, benchCase[1])
})
b.Run(benchCase[0]+"-fastjson", func(b *testing.B) {
benchFastJSONTableNameInfo(b, benchCase[1])
})
}
}
func benchJSONTableNameInfo(b *testing.B, sql string) {
data := getTableInfoJSON(b, sql)
b.ResetTimer()
for i := 0; i < b.N; i++ {
tbInfo := &model.TableNameInfo{}
err := json.Unmarshal(data, tbInfo)
intest.Assert(tbInfo.ID == 1)
intest.Assert(tbInfo.Name.L == "t")
intest.AssertNoError(err)
}
}
func benchFastJSONTableNameInfo(b *testing.B, sql string) {
data := getTableInfoJSON(b, sql)
b.ResetTimer()
for i := 0; i < b.N; i++ {
tbInfo, err := meta.FastUnmarshalTableNameInfo(data)
intest.AssertNoError(err)
intest.Assert(tbInfo.ID == 1)
intest.Assert(tbInfo.Name.L == "t")
}
}
func TestInfoSchemaV2SpecialAttributeCorrectnessAfterBootstrap(t *testing.T) {
store, err := mockstore.NewMockStore()
require.NoError(t, err)
defer func() {
require.NoError(t, store.Close())
}()
// create database
dbInfo := &model.DBInfo{
ID: 10001,
Name: ast.NewCIStr("sc"),
State: model.StatePublic,
}
// create table with special attributes
tblInfo := &model.TableInfo{
ID: 10002,
Name: ast.NewCIStr("cs"),
State: model.StatePublic,
Partition: &model.PartitionInfo{
Definitions: []model.PartitionDefinition{
{ID: 11, Name: ast.NewCIStr("p1")},
{ID: 22, Name: ast.NewCIStr("p2")},
},
Enable: true,
},
TiFlashReplica: &model.TiFlashReplicaInfo{
Count: 0,
LocationLabels: []string{"a,b,c"},
Available: true,
},
Lock: &model.TableLockInfo{
Tp: ast.TableLockRead,
State: model.TableLockStatePreLock,
TS: 0,
},
PlacementPolicyRef: &model.PolicyRefInfo{
ID: 1,
Name: ast.NewCIStr("r1"),
},
TTLInfo: &model.TTLInfo{
IntervalExprStr: "1",
IntervalTimeUnit: int(ast.TimeUnitDay),
Enable: true,
JobInterval: "1h",
},
}
ctx := kv.WithInternalSourceType(context.Background(), kv.InternalTxnDDL)
err = kv.RunInNewTxn(ctx, store, true, func(ctx context.Context, txn kv.Transaction) error {
err := meta.NewMutator(txn).CreateDatabase(dbInfo)
require.NoError(t, err)
err = meta.NewMutator(txn).CreateTableOrView(dbInfo.ID, tblInfo)
require.NoError(t, err)
return errors.Trace(err)
})
require.NoError(t, err)
// bootstrap
dom, err := session.BootstrapSession(store)
require.NoError(t, err)
defer dom.Close()
// verify partition info correctness
tblInfoRes := dom.InfoSchema().ListTablesWithSpecialAttribute(infoschemacontext.PartitionAttribute)
require.Equal(t, len(tblInfoRes[0].TableInfos), 1)
require.Equal(t, tblInfo.Partition, tblInfoRes[0].TableInfos[0].Partition)
// tiflash replica info
tblInfoRes = dom.InfoSchema().ListTablesWithSpecialAttribute(infoschemacontext.TiFlashAttribute)
require.Equal(t, len(tblInfoRes[0].TableInfos), 1)
require.Equal(t, tblInfo.TiFlashReplica, tblInfoRes[0].TableInfos[0].TiFlashReplica)
// lock info
tblInfoRes = dom.InfoSchema().ListTablesWithSpecialAttribute(infoschemacontext.TableLockAttribute)
require.Equal(t, len(tblInfoRes[0].TableInfos), 1)
require.Equal(t, tblInfo.Lock, tblInfoRes[0].TableInfos[0].Lock)
// placement policy
tblInfoRes = dom.InfoSchema().ListTablesWithSpecialAttribute(infoschemacontext.PlacementPolicyAttribute)
require.Equal(t, len(tblInfoRes[0].TableInfos), 1)
require.Equal(t, tblInfo.PlacementPolicyRef, tblInfoRes[0].TableInfos[0].PlacementPolicyRef)
// ttl info
tblInfoRes = dom.InfoSchema().ListTablesWithSpecialAttribute(infoschemacontext.TTLAttribute)
require.Equal(t, len(tblInfoRes[0].TableInfos), 1)
require.Equal(t, tblInfo.TTLInfo, tblInfoRes[0].TableInfos[0].TTLInfo)
}
func TestInfoSchemaV2DataFieldsCorrectnessAfterBootstrap(t *testing.T) {
store, err := mockstore.NewMockStore()
require.NoError(t, err)
defer func() {
require.NoError(t, store.Close())
}()
// create database
dbInfo := &model.DBInfo{
ID: 10001,
Name: ast.NewCIStr("sc"),
Charset: "utf8",
Collate: "utf8_general_ci",
State: model.StatePublic,
}
// create table with partition info
tblInfo := &model.TableInfo{
ID: 10002,
Name: ast.NewCIStr("cs"),
Charset: "latin1",
Collate: "latin1_bin",
State: model.StatePublic,
Partition: &model.PartitionInfo{
Definitions: []model.PartitionDefinition{
{ID: 1, Name: ast.NewCIStr("p1")},
},
Enable: true,
},
}
ctx := kv.WithInternalSourceType(context.Background(), kv.InternalTxnDDL)
err = kv.RunInNewTxn(ctx, store, true, func(ctx context.Context, txn kv.Transaction) error {
err := meta.NewMutator(txn).CreateDatabase(dbInfo)
require.NoError(t, err)
err = meta.NewMutator(txn).CreateTableOrView(dbInfo.ID, tblInfo)
require.NoError(t, err)
return errors.Trace(err)
})
require.NoError(t, err)
// bootstrap
dom, err := session.BootstrapSession(store)
require.NoError(t, err)
defer dom.Close()
is := dom.InfoSchema()
//byID, traverse byID and load from store
tbl, ok := is.TableByID(context.Background(), 10002)
require.True(t, ok)
require.Equal(t, tbl.Meta().ID, tblInfo.ID)
//byName, traverse byName and load from store,
tbl, err = is.TableByName(context.Background(), ast.NewCIStr("sc"), ast.NewCIStr("cs"))
require.NoError(t, err)
require.Equal(t, tbl.Meta().ID, tblInfo.ID)
//tableCache, table info exists in cache now, just use id to seek
tbl, ok = is.TableByID(context.Background(), 10002)
require.True(t, ok)
require.Equal(t, tbl.Meta().ID, tblInfo.ID)
//schemaMap, traverse schemaMap find dbInfo
db, ok := is.SchemaByName(ast.NewCIStr("sc"))
require.True(t, ok)
require.Equal(t, db.ID, dbInfo.ID)
//schemaID2Name, traverse schemaID2Name find dbInfo
db, ok = is.SchemaByID(dbInfo.ID)
require.True(t, ok)
require.Equal(t, db.ID, dbInfo.ID)
//pid2tid, traverse pid2tid find tblInfo, dbInfo and partition info
tbl, ok = is.TableByID(context.Background(), 10002)
require.True(t, ok)
require.Equal(t, len(tbl.Meta().GetPartitionInfo().Definitions), 1)
pid := tbl.Meta().GetPartitionInfo().Definitions[0].ID
tbl, db, pDef := is.FindTableByPartitionID(pid)
require.NotNil(t, tbl)
require.NotNil(t, db)
require.NotNil(t, pDef)
}
func TestInfoSchemaMiscFieldsCorrectnessAfterBootstrap(t *testing.T) {
store, err := mockstore.NewMockStore()
require.NoError(t, err)
defer func() {
require.NoError(t, store.Close())
}()
dbInfo := &model.DBInfo{
ID: 10001,
Name: ast.NewCIStr("sc"),
State: model.StatePublic,
}
policy := &model.PolicyInfo{
ID: 2,
Name: ast.NewCIStr("policy_1"),
PlacementSettings: &model.PlacementSettings{
PrimaryRegion: "r1",
Regions: "r1,r2",
},
}
group := &model.ResourceGroupInfo{
ID: 3,
Name: ast.NewCIStr("groupName_1"),
}
tblInfo := &model.TableInfo{
ID: 10002,
Name: ast.NewCIStr("cs"),
State: model.StatePublic,
ForeignKeys: []*model.FKInfo{{
ID: 1,
Name: ast.NewCIStr("fk_1"),
RefSchema: ast.NewCIStr("t1"),
RefTable: ast.NewCIStr("parent"),
Version: 1,
RefCols: []ast.CIStr{ast.NewCIStr("id")},
}},
PlacementPolicyRef: &model.PolicyRefInfo{
ID: policy.ID,
Name: policy.Name,
},
}
tblInfo1 := &model.TableInfo{
ID: 10003,
Name: ast.NewCIStr("cs"),
State: model.StatePublic,
TempTableType: model.TempTableLocal,
}
ctx := kv.WithInternalSourceType(context.Background(), kv.InternalTxnDDL)
err = kv.RunInNewTxn(ctx, store, true, func(ctx context.Context, txn kv.Transaction) error {
m := meta.NewMutator(txn)
err := m.CreatePolicy(policy)
require.NoError(t, err)
err = m.AddResourceGroup(group)
require.NoError(t, err)
err = m.CreateDatabase(dbInfo)
require.NoError(t, err)
err = m.CreateTableOrView(dbInfo.ID, tblInfo)
require.NoError(t, err)
err = m.CreateTableOrView(dbInfo.ID, tblInfo1)
require.NoError(t, err)
return errors.Trace(err)
})
require.NoError(t, err)
// bootstrap
dom, err := session.BootstrapSession(store)
require.NoError(t, err)
defer dom.Close()
is := dom.InfoSchema()
tbl, ok := is.TableByID(context.Background(), 10002)
require.True(t, ok)
require.Equal(t, tbl.Meta().ID, tblInfo.ID)
// placement policy
policy1 := is.AllPlacementPolicies()
require.Equal(t, len(policy1), 1)
require.Equal(t, policy1[0].Name, policy.Name)
// resource group
group1 := is.AllResourceGroups()
require.Equal(t, len(group1), 2)
sort.Slice(group1, func(i, j int) bool {
return group1[i].Name.L < group1[j].Name.L
})
require.Equal(t, group1[1].Name, group.Name)
// referred foreign key
referredFk := is.GetTableReferredForeignKeys(tblInfo.ForeignKeys[0].RefSchema.L, tblInfo.ForeignKeys[0].RefTable.L)
require.Equal(t, len(referredFk), 1)
require.Equal(t, referredFk[0].ChildFKName, tblInfo.ForeignKeys[0].Name)
// temp table
require.True(t, is.HasTemporaryTable())
}
func TestIsDatabaseExist(t *testing.T) {
store, err := mockstore.NewMockStore()
require.NoError(t, err)
defer func() {
require.NoError(t, store.Close())
}()
ctx := kv.WithInternalSourceType(context.Background(), kv.InternalTxnDDL)
require.NoError(t, kv.RunInNewTxn(ctx, store, true, func(ctx context.Context, txn kv.Transaction) error {
m := meta.NewMutator(txn)
exist, err := m.IsDatabaseExist(123)
require.NoError(t, err)
require.False(t, exist)
require.NoError(t, m.CreateSysDatabaseByID("aaa", 123))
exist, err = m.IsDatabaseExist(123)
require.NoError(t, err)
require.True(t, exist)
return nil
}))
}
func TestBootTableVersion(t *testing.T) {
store, err := mockstore.NewMockStore()
require.NoError(t, err)
defer func() {
require.NoError(t, store.Close())
}()
ctx := kv.WithInternalSourceType(context.Background(), kv.InternalTxnDDL)
require.NoError(t, kv.RunInNewTxn(ctx, store, true, func(ctx context.Context, txn kv.Transaction) error {
m := meta.NewMutator(txn)
ver, err := m.GetNextGenBootTableVersion()
require.NoError(t, err)
require.EqualValues(t, meta.InitNextGenBootTableVersion, ver)
require.NoError(t, m.SetNextGenBootTableVersion(meta.BaseNextGenBootTableVersion))
ver, err = m.GetNextGenBootTableVersion()
require.NoError(t, err)
require.EqualValues(t, meta.BaseNextGenBootTableVersion, ver)
// make sure we use correct key
ddlVer, err := m.GetDDLTableVersion()
require.NoError(t, err)
require.EqualValues(t, meta.InitDDLTableVersion, ddlVer)
return nil
}))
}
func TestCreateSysDatabaseByIDIfNotExists(t *testing.T) {
store, err := mockstore.NewMockStore()
require.NoError(t, err)
defer func() {
require.NoError(t, store.Close())
}()
ctx := kv.WithInternalSourceType(context.Background(), kv.InternalTxnDDL)
require.NoError(t, kv.RunInNewTxn(ctx, store, true, func(ctx context.Context, txn kv.Transaction) error {
m := meta.NewMutator(txn)
err = m.CreateSysDatabaseByIDIfNotExists("aaa", 123)
require.NoError(t, err)
exist, err := m.IsDatabaseExist(123)
require.NoError(t, err)
require.True(t, exist)
err = m.CreateSysDatabaseByIDIfNotExists("aaa", 123)
require.NoError(t, err)
return nil
}))
}
func TestSetGetDXFScheduleTuneFactors(t *testing.T) {
if kerneltype.IsClassic() {
t.Skip("only for next-gen")
}
store, err := mockstore.NewMockStore()
require.NoError(t, err)
defer func() {
require.NoError(t, store.Close())
}()
ctx := kv.WithInternalSourceType(context.Background(), kv.InternalDistTask)
// not set yet
require.NoError(t, kv.RunInNewTxn(ctx, store, true, func(ctx context.Context, txn kv.Transaction) error {
m := meta.NewMutator(txn)
factors, err := m.GetDXFScheduleTuneFactors(store.GetKeyspace())
require.NoError(t, err)
require.Nil(t, factors)
return nil
}))
// set it
factors := &schstatus.TTLTuneFactors{
TTLInfo: schstatus.TTLInfo{TTL: time.Hour},
TuneFactors: schstatus.TuneFactors{AmplifyFactor: 1.5},
}
require.NoError(t, kv.RunInNewTxn(ctx, store, true, func(ctx context.Context, txn kv.Transaction) error {
m := meta.NewMutator(txn)
err := m.SetDXFScheduleTuneFactors(store.GetKeyspace(), factors)
require.NoError(t, err)
return nil
}))
require.NoError(t, kv.RunInNewTxn(ctx, store, true, func(ctx context.Context, txn kv.Transaction) error {
m := meta.NewMutator(txn)
got, err := m.GetDXFScheduleTuneFactors(store.GetKeyspace())
require.NoError(t, err)
require.EqualValues(t, factors, got)
return nil
}))
}