Files
tidb/ttl/cache/split_test.go
王超 caffd8d6c5 ttl: forbid creating/altering a table with TTL options when pk contains float/double column (#40487)
* 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>
2023-01-11 16:22:33 +08:00

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())
}
}
}