* ttl: forbid create/alter a table with TTL options when pk contains float/double column * format * update * update * update Co-authored-by: Ti Chi Robot <ti-community-prow-bot@tidb.io>
818 lines
25 KiB
Go
818 lines
25 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 cache_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"sort"
|
|
"testing"
|
|
|
|
"github.com/pingcap/kvproto/pkg/metapb"
|
|
"github.com/pingcap/tidb/infoschema"
|
|
"github.com/pingcap/tidb/kv"
|
|
"github.com/pingcap/tidb/parser/model"
|
|
"github.com/pingcap/tidb/store/helper"
|
|
"github.com/pingcap/tidb/tablecodec"
|
|
"github.com/pingcap/tidb/testkit"
|
|
"github.com/pingcap/tidb/ttl/cache"
|
|
"github.com/pingcap/tidb/types"
|
|
"github.com/pingcap/tidb/util/codec"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/tikv/client-go/v2/tikv"
|
|
pd "github.com/tikv/pd/client"
|
|
)
|
|
|
|
func newMockRegion(regionID uint64, startKey []byte, endKey []byte) *pd.Region {
|
|
leader := &metapb.Peer{
|
|
Id: regionID,
|
|
StoreId: 1,
|
|
Role: metapb.PeerRole_Voter,
|
|
}
|
|
|
|
return &pd.Region{
|
|
Meta: &metapb.Region{
|
|
Id: regionID,
|
|
StartKey: startKey,
|
|
EndKey: endKey,
|
|
Peers: []*metapb.Peer{leader},
|
|
},
|
|
Leader: leader,
|
|
}
|
|
}
|
|
|
|
type mockPDClient struct {
|
|
t *testing.T
|
|
pd.Client
|
|
regions []*pd.Region
|
|
regionsSorted bool
|
|
}
|
|
|
|
func (c *mockPDClient) ScanRegions(_ context.Context, key, endKey []byte, limit int) ([]*pd.Region, error) {
|
|
if len(c.regions) == 0 {
|
|
return []*pd.Region{newMockRegion(1, []byte{}, []byte{0xFF, 0xFF})}, nil
|
|
}
|
|
|
|
if !c.regionsSorted {
|
|
sort.Slice(c.regions, func(i, j int) bool {
|
|
return kv.Key(c.regions[i].Meta.StartKey).Cmp(c.regions[j].Meta.StartKey) < 0
|
|
})
|
|
c.regionsSorted = true
|
|
}
|
|
|
|
regions := []*pd.Region{newMockRegion(1, []byte{}, c.regions[0].Meta.StartKey)}
|
|
regions = append(regions, c.regions...)
|
|
regions = append(regions, newMockRegion(2, c.regions[len(c.regions)-1].Meta.EndKey, []byte{0xFF, 0xFF, 0xFF}))
|
|
|
|
result := make([]*pd.Region, 0)
|
|
for _, r := range regions {
|
|
if kv.Key(r.Meta.StartKey).Cmp(endKey) >= 0 {
|
|
continue
|
|
}
|
|
|
|
if kv.Key(r.Meta.EndKey).Cmp(key) <= 0 {
|
|
continue
|
|
}
|
|
|
|
if len(result) >= limit {
|
|
break
|
|
}
|
|
|
|
result = append(result, r)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (c *mockPDClient) GetStore(_ context.Context, storeID uint64) (*metapb.Store, error) {
|
|
return &metapb.Store{
|
|
Id: storeID,
|
|
Address: fmt.Sprintf("127.0.0.%d", storeID),
|
|
}, nil
|
|
}
|
|
|
|
type mockTiKVStore struct {
|
|
t *testing.T
|
|
helper.Storage
|
|
pdClient *mockPDClient
|
|
cache *tikv.RegionCache
|
|
nextRegionID uint64
|
|
}
|
|
|
|
func newMockTiKVStore(t *testing.T) *mockTiKVStore {
|
|
pdClient := &mockPDClient{t: t}
|
|
s := &mockTiKVStore{
|
|
t: t,
|
|
pdClient: pdClient,
|
|
cache: tikv.NewRegionCache(pdClient),
|
|
nextRegionID: 1000,
|
|
}
|
|
s.refreshCache()
|
|
t.Cleanup(func() {
|
|
s.cache.Close()
|
|
})
|
|
return s
|
|
}
|
|
|
|
func (s *mockTiKVStore) addFullTableRegion(tableID ...int64) *mockTiKVStore {
|
|
prefix1 := tablecodec.GenTablePrefix(tableID[0])
|
|
prefix2 := tablecodec.GenTablePrefix(tableID[len(tableID)-1])
|
|
return s.addRegion(prefix1, prefix2.PrefixNext())
|
|
}
|
|
|
|
func (s *mockTiKVStore) addRegionBeginWithTablePrefix(tableID int64, handle kv.Handle) *mockTiKVStore {
|
|
start := tablecodec.GenTablePrefix(tableID)
|
|
end := tablecodec.EncodeRowKeyWithHandle(tableID, handle)
|
|
return s.addRegion(start, end)
|
|
}
|
|
|
|
func (s *mockTiKVStore) addRegionEndWithTablePrefix(handle kv.Handle, tableID int64) *mockTiKVStore {
|
|
start := tablecodec.EncodeRowKeyWithHandle(tableID, handle)
|
|
end := tablecodec.GenTablePrefix(tableID + 1)
|
|
return s.addRegion(start, end)
|
|
}
|
|
|
|
func (s *mockTiKVStore) addRegionWithTablePrefix(tableID int64, start kv.Handle, end kv.Handle) *mockTiKVStore {
|
|
startKey := tablecodec.EncodeRowKeyWithHandle(tableID, start)
|
|
endKey := tablecodec.EncodeRowKeyWithHandle(tableID, end)
|
|
return s.addRegion(startKey, endKey)
|
|
}
|
|
|
|
func (s *mockTiKVStore) addRegion(key, endKey []byte) *mockTiKVStore {
|
|
require.True(s.t, kv.Key(endKey).Cmp(key) > 0)
|
|
if len(s.pdClient.regions) > 0 {
|
|
lastRegion := s.pdClient.regions[len(s.pdClient.regions)-1]
|
|
require.True(s.t, kv.Key(endKey).Cmp(lastRegion.Meta.EndKey) >= 0)
|
|
}
|
|
|
|
regionID := s.nextRegionID
|
|
s.nextRegionID++
|
|
leader := &metapb.Peer{
|
|
Id: regionID,
|
|
StoreId: 1,
|
|
Role: metapb.PeerRole_Voter,
|
|
}
|
|
|
|
s.pdClient.regions = append(s.pdClient.regions, &pd.Region{
|
|
Meta: &metapb.Region{
|
|
Id: regionID,
|
|
StartKey: key,
|
|
EndKey: endKey,
|
|
Peers: []*metapb.Peer{leader},
|
|
},
|
|
Leader: leader,
|
|
})
|
|
|
|
s.pdClient.regionsSorted = false
|
|
s.refreshCache()
|
|
return s
|
|
}
|
|
|
|
func (s *mockTiKVStore) refreshCache() {
|
|
_, err := s.cache.LoadRegionsInKeyRange(
|
|
tikv.NewBackofferWithVars(context.Background(), 1000, nil),
|
|
[]byte{},
|
|
[]byte{0xFF},
|
|
)
|
|
require.NoError(s.t, err)
|
|
}
|
|
|
|
func (s *mockTiKVStore) batchAddIntHandleRegions(tblID int64, regionCnt int, regionSize int, offset int64) (end kv.IntHandle) {
|
|
for i := 0; i < regionCnt; i++ {
|
|
start := kv.IntHandle(offset + int64(i*regionSize))
|
|
end = kv.IntHandle(start.IntValue() + int64(regionSize))
|
|
s.addRegionWithTablePrefix(tblID, start, end)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *mockTiKVStore) clearRegions() {
|
|
s.pdClient.regions = nil
|
|
s.cache.Close()
|
|
s.cache = tikv.NewRegionCache(s.pdClient)
|
|
s.refreshCache()
|
|
}
|
|
|
|
func (s *mockTiKVStore) newCommonHandle(ds ...types.Datum) *kv.CommonHandle {
|
|
encoded, err := codec.EncodeKey(nil, nil, ds...)
|
|
require.NoError(s.t, err)
|
|
h, err := kv.NewCommonHandle(encoded)
|
|
require.NoError(s.t, err)
|
|
return h
|
|
}
|
|
|
|
func (s *mockTiKVStore) GetRegionCache() *tikv.RegionCache {
|
|
return s.cache
|
|
}
|
|
|
|
func bytesHandle(t *testing.T, data []byte) kv.Handle {
|
|
encoded, err := codec.EncodeKey(nil, nil, types.NewBytesDatum(data))
|
|
require.NoError(t, err)
|
|
h, err := kv.NewCommonHandle(encoded)
|
|
require.NoError(t, err)
|
|
return h
|
|
}
|
|
|
|
func createTTLTable(t *testing.T, tk *testkit.TestKit, name string, option string) *cache.PhysicalTable {
|
|
if option == "" {
|
|
return createTTLTableWithSQL(t, tk, name, fmt.Sprintf("create table test.%s(t timestamp) TTL = `t` + interval 1 day", name))
|
|
}
|
|
|
|
return createTTLTableWithSQL(t, tk, name, fmt.Sprintf("create table test.%s(id %s primary key, t timestamp) TTL = `t` + interval 1 day", name, option))
|
|
}
|
|
|
|
func create2PKTTLTable(t *testing.T, tk *testkit.TestKit, name string, option string) *cache.PhysicalTable {
|
|
return createTTLTableWithSQL(t, tk, name, fmt.Sprintf("create table test.%s(id %s, id2 int, t timestamp, primary key(id, id2)) TTL = `t` + interval 1 day", name, option))
|
|
}
|
|
|
|
func createTTLTableWithSQL(t *testing.T, tk *testkit.TestKit, name string, sql string) *cache.PhysicalTable {
|
|
tk.MustExec(sql)
|
|
is, ok := tk.Session().GetDomainInfoSchema().(infoschema.InfoSchema)
|
|
require.True(t, ok)
|
|
tbl, err := is.TableByName(model.NewCIStr("test"), model.NewCIStr(name))
|
|
require.NoError(t, err)
|
|
ttlTbl, err := cache.NewPhysicalTable(model.NewCIStr("test"), tbl.Meta(), model.NewCIStr(""))
|
|
require.NoError(t, err)
|
|
return ttlTbl
|
|
}
|
|
|
|
func checkRange(t *testing.T, r cache.ScanRange, start, end types.Datum) {
|
|
if start.IsNull() {
|
|
require.Nil(t, r.Start)
|
|
} else {
|
|
require.Equal(t, 1, len(r.Start))
|
|
require.Equal(t, start.Kind(), r.Start[0].Kind())
|
|
require.Equal(t, start.GetValue(), r.Start[0].GetValue())
|
|
}
|
|
|
|
if end.IsNull() {
|
|
require.Nil(t, r.End)
|
|
} else {
|
|
require.Equal(t, 1, len(r.End))
|
|
require.Equal(t, end.Kind(), r.End[0].Kind())
|
|
require.Equal(t, end.GetValue(), r.End[0].GetValue())
|
|
}
|
|
}
|
|
|
|
func TestSplitTTLScanRangesWithSignedInt(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
tk := testkit.NewTestKit(t, store)
|
|
|
|
tbls := []*cache.PhysicalTable{
|
|
createTTLTable(t, tk, "t1", "tinyint"),
|
|
createTTLTable(t, tk, "t2", "smallint"),
|
|
createTTLTable(t, tk, "t3", "mediumint"),
|
|
createTTLTable(t, tk, "t4", "int"),
|
|
createTTLTable(t, tk, "t5", "bigint"),
|
|
createTTLTable(t, tk, "t6", ""), // no clustered
|
|
create2PKTTLTable(t, tk, "t7", "tinyint"),
|
|
}
|
|
|
|
tikvStore := newMockTiKVStore(t)
|
|
for _, tbl := range tbls {
|
|
// test only one region
|
|
tikvStore.clearRegions()
|
|
ranges, err := tbl.SplitScanRanges(context.TODO(), tikvStore, 4)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(ranges))
|
|
checkRange(t, ranges[0], types.Datum{}, types.Datum{})
|
|
|
|
// test share regions with other table
|
|
tikvStore.clearRegions()
|
|
tikvStore.addRegion(
|
|
tablecodec.GenTablePrefix(tbl.ID-1),
|
|
tablecodec.GenTablePrefix(tbl.ID+1),
|
|
)
|
|
ranges, err = tbl.SplitScanRanges(context.TODO(), tikvStore, 4)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(ranges))
|
|
checkRange(t, ranges[0], types.Datum{}, types.Datum{})
|
|
|
|
// test one table has multiple regions
|
|
tikvStore.clearRegions()
|
|
tikvStore.addRegionBeginWithTablePrefix(tbl.ID, kv.IntHandle(0))
|
|
end := tikvStore.batchAddIntHandleRegions(tbl.ID, 8, 100, 0)
|
|
tikvStore.addRegionEndWithTablePrefix(end, tbl.ID)
|
|
ranges, err = tbl.SplitScanRanges(context.TODO(), tikvStore, 4)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 4, len(ranges))
|
|
checkRange(t, ranges[0], types.Datum{}, types.NewIntDatum(200))
|
|
checkRange(t, ranges[1], types.NewIntDatum(200), types.NewIntDatum(500))
|
|
checkRange(t, ranges[2], types.NewIntDatum(500), types.NewIntDatum(700))
|
|
checkRange(t, ranges[3], types.NewIntDatum(700), types.Datum{})
|
|
|
|
// test one table has multiple regions and one table region across 0
|
|
tikvStore.clearRegions()
|
|
tikvStore.addRegionBeginWithTablePrefix(tbl.ID, kv.IntHandle(-350))
|
|
end = tikvStore.batchAddIntHandleRegions(tbl.ID, 8, 100, -350)
|
|
tikvStore.addRegionEndWithTablePrefix(end, tbl.ID)
|
|
ranges, err = tbl.SplitScanRanges(context.TODO(), tikvStore, 5)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 5, len(ranges))
|
|
checkRange(t, ranges[0], types.Datum{}, types.NewIntDatum(-250))
|
|
checkRange(t, ranges[1], types.NewIntDatum(-250), types.NewIntDatum(-50))
|
|
checkRange(t, ranges[2], types.NewIntDatum(-50), types.NewIntDatum(150))
|
|
checkRange(t, ranges[3], types.NewIntDatum(150), types.NewIntDatum(350))
|
|
checkRange(t, ranges[4], types.NewIntDatum(350), types.Datum{})
|
|
}
|
|
}
|
|
|
|
func TestSplitTTLScanRangesWithUnsignedInt(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
tk := testkit.NewTestKit(t, store)
|
|
|
|
tbls := []*cache.PhysicalTable{
|
|
createTTLTable(t, tk, "t1", "tinyint unsigned"),
|
|
createTTLTable(t, tk, "t2", "smallint unsigned"),
|
|
createTTLTable(t, tk, "t3", "mediumint unsigned"),
|
|
createTTLTable(t, tk, "t4", "int unsigned"),
|
|
createTTLTable(t, tk, "t5", "bigint unsigned"),
|
|
create2PKTTLTable(t, tk, "t6", "tinyint unsigned"),
|
|
}
|
|
|
|
tikvStore := newMockTiKVStore(t)
|
|
for _, tbl := range tbls {
|
|
// test only one region
|
|
tikvStore.clearRegions()
|
|
ranges, err := tbl.SplitScanRanges(context.TODO(), tikvStore, 4)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(ranges))
|
|
checkRange(t, ranges[0], types.Datum{}, types.Datum{})
|
|
|
|
// test share regions with other table
|
|
tikvStore.clearRegions()
|
|
tikvStore.addRegion(
|
|
tablecodec.GenTablePrefix(tbl.ID-1),
|
|
tablecodec.GenTablePrefix(tbl.ID+1),
|
|
)
|
|
ranges, err = tbl.SplitScanRanges(context.TODO(), tikvStore, 4)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(ranges))
|
|
checkRange(t, ranges[0], types.Datum{}, types.Datum{})
|
|
|
|
// test one table has multiple regions: [MinInt64, a) [a, b) [b, 0) [0, c) [c, d) [d, MaxInt64]
|
|
tikvStore.clearRegions()
|
|
tikvStore.addRegionBeginWithTablePrefix(tbl.ID, kv.IntHandle(-200))
|
|
end := tikvStore.batchAddIntHandleRegions(tbl.ID, 4, 100, -200)
|
|
tikvStore.addRegionEndWithTablePrefix(end, tbl.ID)
|
|
ranges, err = tbl.SplitScanRanges(context.TODO(), tikvStore, 6)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 6, len(ranges))
|
|
checkRange(t, ranges[0], types.NewUintDatum(uint64(math.MaxInt64)+1), types.NewUintDatum(uint64(math.MaxUint64)-199))
|
|
checkRange(t, ranges[1], types.NewUintDatum(uint64(math.MaxUint64)-199), types.NewUintDatum(uint64(math.MaxUint64)-99))
|
|
checkRange(t, ranges[2], types.NewUintDatum(uint64(math.MaxUint64)-99), types.Datum{})
|
|
checkRange(t, ranges[3], types.Datum{}, types.NewUintDatum(100))
|
|
checkRange(t, ranges[4], types.NewUintDatum(100), types.NewUintDatum(200))
|
|
checkRange(t, ranges[5], types.NewUintDatum(200), types.NewUintDatum(uint64(math.MaxInt64)+1))
|
|
|
|
// test one table has multiple regions: [MinInt64, a) [a, b) [b, c) [c, d) [d, MaxInt64], b < 0 < c
|
|
tikvStore.clearRegions()
|
|
tikvStore.addRegionBeginWithTablePrefix(tbl.ID, kv.IntHandle(-150))
|
|
end = tikvStore.batchAddIntHandleRegions(tbl.ID, 3, 100, -150)
|
|
tikvStore.addRegionEndWithTablePrefix(end, tbl.ID)
|
|
ranges, err = tbl.SplitScanRanges(context.TODO(), tikvStore, 5)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 6, len(ranges))
|
|
checkRange(t, ranges[0], types.NewUintDatum(uint64(math.MaxInt64)+1), types.NewUintDatum(uint64(math.MaxUint64)-149))
|
|
checkRange(t, ranges[1], types.NewUintDatum(uint64(math.MaxUint64)-149), types.NewUintDatum(uint64(math.MaxUint64)-49))
|
|
checkRange(t, ranges[2], types.NewUintDatum(uint64(math.MaxUint64)-49), types.Datum{})
|
|
checkRange(t, ranges[3], types.Datum{}, types.NewUintDatum(50))
|
|
checkRange(t, ranges[4], types.NewUintDatum(50), types.NewUintDatum(150))
|
|
checkRange(t, ranges[5], types.NewUintDatum(150), types.NewUintDatum(uint64(math.MaxInt64)+1))
|
|
}
|
|
}
|
|
|
|
func TestSplitTTLScanRangesWithBytes(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
tk := testkit.NewTestKit(t, store)
|
|
|
|
tbls := []*cache.PhysicalTable{
|
|
createTTLTable(t, tk, "t1", "binary(32)"),
|
|
createTTLTable(t, tk, "t2", "char(32) CHARACTER SET BINARY"),
|
|
createTTLTable(t, tk, "t3", "varchar(32) CHARACTER SET BINARY"),
|
|
createTTLTable(t, tk, "t4", "bit(32)"),
|
|
create2PKTTLTable(t, tk, "t5", "binary(32)"),
|
|
}
|
|
|
|
tikvStore := newMockTiKVStore(t)
|
|
for _, tbl := range tbls {
|
|
// test only one region
|
|
tikvStore.clearRegions()
|
|
ranges, err := tbl.SplitScanRanges(context.TODO(), tikvStore, 4)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(ranges))
|
|
checkRange(t, ranges[0], types.Datum{}, types.Datum{})
|
|
|
|
// test share regions with other table
|
|
tikvStore.clearRegions()
|
|
tikvStore.addRegion(
|
|
tablecodec.GenTablePrefix(tbl.ID-1),
|
|
tablecodec.GenTablePrefix(tbl.ID+1),
|
|
)
|
|
ranges, err = tbl.SplitScanRanges(context.TODO(), tikvStore, 4)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(ranges))
|
|
checkRange(t, ranges[0], types.Datum{}, types.Datum{})
|
|
|
|
// test one table has multiple regions
|
|
tikvStore.clearRegions()
|
|
tikvStore.addRegionBeginWithTablePrefix(tbl.ID, bytesHandle(t, []byte{1, 2, 3}))
|
|
tikvStore.addRegionWithTablePrefix(tbl.ID, bytesHandle(t, []byte{1, 2, 3}), bytesHandle(t, []byte{1, 2, 3, 4}))
|
|
tikvStore.addRegionWithTablePrefix(tbl.ID, bytesHandle(t, []byte{1, 2, 3, 4}), bytesHandle(t, []byte{1, 2, 3, 4, 5}))
|
|
tikvStore.addRegionWithTablePrefix(tbl.ID, bytesHandle(t, []byte{1, 2, 3, 4, 5}), bytesHandle(t, []byte{1, 2, 4}))
|
|
tikvStore.addRegionWithTablePrefix(tbl.ID, bytesHandle(t, []byte{1, 2, 4}), bytesHandle(t, []byte{1, 2, 5}))
|
|
tikvStore.addRegionEndWithTablePrefix(bytesHandle(t, []byte{1, 2, 5}), tbl.ID)
|
|
ranges, err = tbl.SplitScanRanges(context.TODO(), tikvStore, 4)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 4, len(ranges))
|
|
checkRange(t, ranges[0], types.Datum{}, types.NewBytesDatum([]byte{1, 2, 3, 4}))
|
|
checkRange(t, ranges[1], types.NewBytesDatum([]byte{1, 2, 3, 4}), types.NewBytesDatum([]byte{1, 2, 4}))
|
|
checkRange(t, ranges[2], types.NewBytesDatum([]byte{1, 2, 4}), types.NewBytesDatum([]byte{1, 2, 5}))
|
|
checkRange(t, ranges[3], types.NewBytesDatum([]byte{1, 2, 5}), types.Datum{})
|
|
}
|
|
}
|
|
|
|
func TestNoTTLSplitSupportTables(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
tk := testkit.NewTestKit(t, store)
|
|
|
|
tbls := []*cache.PhysicalTable{
|
|
createTTLTable(t, tk, "t1", "char(32) CHARACTER SET UTF8MB4"),
|
|
createTTLTable(t, tk, "t2", "varchar(32) CHARACTER SET UTF8MB4"),
|
|
createTTLTable(t, tk, "t4", "decimal(32, 2)"),
|
|
create2PKTTLTable(t, tk, "t5", "char(32) CHARACTER SET UTF8MB4"),
|
|
}
|
|
|
|
tikvStore := newMockTiKVStore(t)
|
|
for _, tbl := range tbls {
|
|
// test only one region
|
|
tikvStore.clearRegions()
|
|
ranges, err := tbl.SplitScanRanges(context.TODO(), tikvStore, 4)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(ranges))
|
|
checkRange(t, ranges[0], types.Datum{}, types.Datum{})
|
|
|
|
// test share regions with other table
|
|
tikvStore.clearRegions()
|
|
tikvStore.addRegion(
|
|
tablecodec.GenTablePrefix(tbl.ID-1),
|
|
tablecodec.GenTablePrefix(tbl.ID+1),
|
|
)
|
|
ranges, err = tbl.SplitScanRanges(context.TODO(), tikvStore, 4)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(ranges))
|
|
checkRange(t, ranges[0], types.Datum{}, types.Datum{})
|
|
|
|
// test one table has multiple regions
|
|
tikvStore.clearRegions()
|
|
tikvStore.addRegionBeginWithTablePrefix(tbl.ID, bytesHandle(t, []byte{1, 2, 3}))
|
|
tikvStore.addRegionWithTablePrefix(tbl.ID, bytesHandle(t, []byte{1, 2, 3}), bytesHandle(t, []byte{1, 2, 3, 4}))
|
|
tikvStore.addRegionEndWithTablePrefix(bytesHandle(t, []byte{1, 2, 3, 4}), tbl.ID)
|
|
ranges, err = tbl.SplitScanRanges(context.TODO(), tikvStore, 3)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(ranges))
|
|
checkRange(t, ranges[0], types.Datum{}, types.Datum{})
|
|
}
|
|
}
|
|
|
|
func TestGetNextBytesHandleDatum(t *testing.T) {
|
|
tblID := int64(7)
|
|
buildHandleBytes := func(data []byte) []byte {
|
|
handleBytes, err := codec.EncodeKey(nil, nil, types.NewBytesDatum(data))
|
|
require.NoError(t, err)
|
|
return handleBytes
|
|
}
|
|
|
|
buildRowKey := func(handleBytes []byte) kv.Key {
|
|
return tablecodec.EncodeRowKey(tblID, handleBytes)
|
|
}
|
|
|
|
buildBytesRowKey := func(data []byte) kv.Key {
|
|
return buildRowKey(buildHandleBytes(data))
|
|
}
|
|
|
|
binaryDataStartPos := len(tablecodec.GenTableRecordPrefix(tblID)) + 1
|
|
cases := []struct {
|
|
key interface{}
|
|
result []byte
|
|
isNull bool
|
|
}{
|
|
{
|
|
key: buildBytesRowKey([]byte{}),
|
|
result: []byte{},
|
|
},
|
|
{
|
|
key: buildBytesRowKey([]byte{1, 2, 3}),
|
|
result: []byte{1, 2, 3},
|
|
},
|
|
{
|
|
key: buildBytesRowKey([]byte{1, 2, 3, 0}),
|
|
result: []byte{1, 2, 3, 0},
|
|
},
|
|
{
|
|
key: buildBytesRowKey([]byte{1, 2, 3, 4, 5, 6, 7, 8}),
|
|
result: []byte{1, 2, 3, 4, 5, 6, 7, 8},
|
|
},
|
|
{
|
|
key: buildBytesRowKey([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9}),
|
|
result: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
|
},
|
|
{
|
|
key: buildBytesRowKey([]byte{1, 2, 3, 4, 5, 6, 7, 8, 0}),
|
|
result: []byte{1, 2, 3, 4, 5, 6, 7, 8, 0},
|
|
},
|
|
{
|
|
key: append(buildBytesRowKey([]byte{1, 2, 3, 4, 5, 6, 7, 8, 0}), 0),
|
|
result: []byte{1, 2, 3, 4, 5, 6, 7, 8, 0, 0},
|
|
},
|
|
{
|
|
key: append(buildBytesRowKey([]byte{1, 2, 3, 4, 5, 6, 7, 8, 0}), 1),
|
|
result: []byte{1, 2, 3, 4, 5, 6, 7, 8, 0, 0},
|
|
},
|
|
{
|
|
key: []byte{},
|
|
result: []byte{},
|
|
},
|
|
{
|
|
key: tablecodec.GenTableRecordPrefix(tblID),
|
|
result: []byte{},
|
|
},
|
|
{
|
|
key: tablecodec.GenTableRecordPrefix(tblID - 1),
|
|
result: []byte{},
|
|
},
|
|
{
|
|
key: tablecodec.GenTablePrefix(tblID).PrefixNext(),
|
|
isNull: true,
|
|
},
|
|
{
|
|
key: buildRowKey([]byte{0}),
|
|
result: []byte{},
|
|
},
|
|
{
|
|
key: buildRowKey([]byte{1}),
|
|
result: []byte{},
|
|
},
|
|
{
|
|
key: buildRowKey([]byte{2}),
|
|
isNull: true,
|
|
},
|
|
{
|
|
// recordPrefix + bytesFlag + [0]
|
|
key: buildBytesRowKey([]byte{})[:binaryDataStartPos+1],
|
|
result: []byte{},
|
|
},
|
|
{
|
|
// recordPrefix + bytesFlag + [0, 0, 0, 0, 0, 0, 0, 0]
|
|
key: buildBytesRowKey([]byte{})[:binaryDataStartPos+8],
|
|
result: []byte{},
|
|
},
|
|
{
|
|
// recordPrefix + bytesFlag + [1]
|
|
key: buildBytesRowKey([]byte{1, 2, 3})[:binaryDataStartPos+1],
|
|
result: []byte{1},
|
|
},
|
|
{
|
|
// recordPrefix + bytesFlag + [1, 2, 3]
|
|
key: buildBytesRowKey([]byte{1, 2, 3})[:binaryDataStartPos+3],
|
|
result: []byte{1, 2, 3},
|
|
},
|
|
{
|
|
// recordPrefix + bytesFlag + [1, 2, 3, 0]
|
|
key: buildBytesRowKey([]byte{1, 2, 3})[:binaryDataStartPos+4],
|
|
result: []byte{1, 2, 3},
|
|
},
|
|
{
|
|
// recordPrefix + bytesFlag + [1, 2, 3, 0, 0, 0, 0, 0, 247]
|
|
key: func() []byte {
|
|
bs := buildBytesRowKey([]byte{1, 2, 3})
|
|
bs[len(bs)-1] = 247
|
|
return bs
|
|
},
|
|
result: []byte{1, 2, 3},
|
|
},
|
|
{
|
|
// recordPrefix + bytesFlag + [1, 2, 3, 0, 0, 0, 0, 0, 0]
|
|
key: func() []byte {
|
|
bs := buildBytesRowKey([]byte{1, 2, 3})
|
|
bs[len(bs)-1] = 0
|
|
return bs
|
|
},
|
|
result: []byte{1, 2, 3},
|
|
},
|
|
{
|
|
// recordPrefix + bytesFlag + [1, 2, 3, 4, 5, 6, 7, 8, 254, 9, 0, 0, 0, 0, 0, 0, 0, 248]
|
|
key: func() []byte {
|
|
bs := buildBytesRowKey([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
|
bs[len(bs)-10] = 254
|
|
return bs
|
|
},
|
|
result: []byte{1, 2, 3, 4, 5, 6, 7, 8},
|
|
},
|
|
{
|
|
// recordPrefix + bytesFlag + [1, 2, 3, 4, 5, 6, 7, 0, 254, 9, 0, 0, 0, 0, 0, 0, 0, 248]
|
|
key: func() []byte {
|
|
bs := buildBytesRowKey([]byte{1, 2, 3, 4, 5, 6, 7, 0, 9})
|
|
bs[len(bs)-10] = 254
|
|
return bs
|
|
},
|
|
result: []byte{1, 2, 3, 4, 5, 6, 7, 0},
|
|
},
|
|
{
|
|
// recordPrefix + bytesFlag + [1, 2, 3, 4, 5, 6, 7, 0, 253, 9, 0, 0, 0, 0, 0, 0, 0, 248]
|
|
key: func() []byte {
|
|
bs := buildBytesRowKey([]byte{1, 2, 3, 4, 5, 6, 7, 0, 9})
|
|
bs[len(bs)-10] = 253
|
|
return bs
|
|
},
|
|
result: []byte{1, 2, 3, 4, 5, 6, 7},
|
|
},
|
|
{
|
|
// recordPrefix + bytesFlag + [1, 2, 3, 4, 5, 6, 7, 8, 255, 9, 0, 0, 0, 0, 0, 0, 0, 247]
|
|
key: func() []byte {
|
|
bs := buildBytesRowKey([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
|
bs[len(bs)-1] = 247
|
|
return bs
|
|
},
|
|
result: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
|
},
|
|
{
|
|
// recordPrefix + bytesFlag + [1, 2, 3, 4, 5, 6, 7, 8, 255, 9, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
key: func() []byte {
|
|
bs := buildBytesRowKey([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
|
bs[len(bs)-1] = 0
|
|
return bs
|
|
},
|
|
result: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
|
},
|
|
{
|
|
// recordPrefix + bytesFlag + [1, 2, 3, 4, 5, 6, 7, 8, 255, 9, 0, 0, 0, 0, 0, 0, 0]
|
|
key: func() []byte {
|
|
bs := buildBytesRowKey([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9})
|
|
bs = bs[:len(bs)-1]
|
|
return bs
|
|
},
|
|
result: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
|
|
},
|
|
{
|
|
// recordPrefix + bytesFlag + [1, 2, 3, 4, 5, 6, 7, 8, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
key: func() []byte {
|
|
bs := buildBytesRowKey([]byte{1, 2, 3, 4, 5, 6, 7, 8})
|
|
bs = bs[:len(bs)-1]
|
|
return bs
|
|
},
|
|
result: []byte{1, 2, 3, 4, 5, 6, 7, 8},
|
|
},
|
|
{
|
|
// recordPrefix + bytesFlag + [1, 2, 3, 4, 5, 6, 7, 8, 255, 0, 0, 0, 0, 0, 0, 0, 0, 246]
|
|
key: func() []byte {
|
|
bs := buildBytesRowKey([]byte{1, 2, 3, 4, 5, 6, 7, 8})
|
|
bs = bs[:len(bs)-1]
|
|
return bs
|
|
},
|
|
result: []byte{1, 2, 3, 4, 5, 6, 7, 8},
|
|
},
|
|
}
|
|
|
|
for i, c := range cases {
|
|
var key kv.Key
|
|
switch k := c.key.(type) {
|
|
case kv.Key:
|
|
key = k
|
|
case []byte:
|
|
key = k
|
|
case func() []byte:
|
|
key = k()
|
|
case func() kv.Key:
|
|
key = k()
|
|
default:
|
|
require.FailNow(t, "%d", i)
|
|
}
|
|
|
|
d := cache.GetNextBytesHandleDatum(key, tablecodec.GenTableRecordPrefix(tblID))
|
|
if c.isNull {
|
|
require.True(t, d.IsNull(), i)
|
|
} else {
|
|
require.Equal(t, types.KindBytes, d.Kind(), i)
|
|
require.Equal(t, c.result, d.GetBytes(), i)
|
|
}
|
|
}
|
|
}
|
|
func TestGetNextIntHandle(t *testing.T) {
|
|
tblID := int64(7)
|
|
cases := []struct {
|
|
key interface{}
|
|
result int64
|
|
isNull bool
|
|
}{
|
|
{
|
|
key: tablecodec.EncodeRowKeyWithHandle(tblID, kv.IntHandle(0)),
|
|
result: 0,
|
|
},
|
|
{
|
|
key: tablecodec.EncodeRowKeyWithHandle(tblID, kv.IntHandle(3)),
|
|
result: 3,
|
|
},
|
|
{
|
|
key: tablecodec.EncodeRowKeyWithHandle(tblID, kv.IntHandle(math.MaxInt64)),
|
|
result: math.MaxInt64,
|
|
},
|
|
{
|
|
key: tablecodec.EncodeRowKeyWithHandle(tblID, kv.IntHandle(math.MinInt64)),
|
|
result: math.MinInt64,
|
|
},
|
|
{
|
|
key: append(tablecodec.EncodeRowKeyWithHandle(tblID, kv.IntHandle(7)), 0),
|
|
result: 8,
|
|
},
|
|
{
|
|
key: append(tablecodec.EncodeRowKeyWithHandle(tblID, kv.IntHandle(math.MaxInt64)), 0),
|
|
isNull: true,
|
|
},
|
|
{
|
|
key: append(tablecodec.EncodeRowKeyWithHandle(tblID, kv.IntHandle(math.MinInt64)), 0),
|
|
result: math.MinInt64 + 1,
|
|
},
|
|
{
|
|
key: []byte{},
|
|
result: math.MinInt64,
|
|
},
|
|
{
|
|
key: tablecodec.GenTableRecordPrefix(tblID),
|
|
result: math.MinInt64,
|
|
},
|
|
{
|
|
key: tablecodec.GenTableRecordPrefix(tblID - 1),
|
|
result: math.MinInt64,
|
|
},
|
|
{
|
|
key: tablecodec.GenTablePrefix(tblID).PrefixNext(),
|
|
isNull: true,
|
|
},
|
|
{
|
|
key: tablecodec.EncodeRowKey(tblID, []byte{0}),
|
|
result: codec.DecodeCmpUintToInt(0),
|
|
},
|
|
{
|
|
key: tablecodec.EncodeRowKey(tblID, []byte{0, 1, 2, 3}),
|
|
result: codec.DecodeCmpUintToInt(0x0001020300000000),
|
|
},
|
|
{
|
|
key: tablecodec.EncodeRowKey(tblID, []byte{8, 1, 2, 3}),
|
|
result: codec.DecodeCmpUintToInt(0x0801020300000000),
|
|
},
|
|
{
|
|
key: tablecodec.EncodeRowKey(tblID, []byte{0, 1, 2, 3, 4, 5, 6, 7, 0}),
|
|
result: codec.DecodeCmpUintToInt(0x0001020304050607) + 1,
|
|
},
|
|
{
|
|
key: tablecodec.EncodeRowKey(tblID, []byte{8, 1, 2, 3, 4, 5, 6, 7, 0}),
|
|
result: codec.DecodeCmpUintToInt(0x0801020304050607) + 1,
|
|
},
|
|
{
|
|
key: tablecodec.EncodeRowKey(tblID, []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}),
|
|
result: math.MaxInt64,
|
|
},
|
|
{
|
|
key: tablecodec.EncodeRowKey(tblID, []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0}),
|
|
isNull: true,
|
|
},
|
|
}
|
|
|
|
for i, c := range cases {
|
|
var key kv.Key
|
|
switch k := c.key.(type) {
|
|
case kv.Key:
|
|
key = k
|
|
case []byte:
|
|
key = k
|
|
case func() []byte:
|
|
key = k()
|
|
case func() kv.Key:
|
|
key = k()
|
|
default:
|
|
require.FailNow(t, "%d", i)
|
|
}
|
|
|
|
v := cache.GetNextIntHandle(key, tablecodec.GenTableRecordPrefix(tblID))
|
|
if c.isNull {
|
|
require.Nil(t, v, i)
|
|
} else {
|
|
require.IsType(t, kv.IntHandle(0), v, i)
|
|
require.Equal(t, c.result, v.IntValue())
|
|
}
|
|
}
|
|
}
|