1318 lines
41 KiB
Go
1318 lines
41 KiB
Go
// Copyright 2019 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 rowcodec_test
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"hash/crc32"
|
|
"math"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pingcap/tidb/pkg/kv"
|
|
"github.com/pingcap/tidb/pkg/meta/model"
|
|
"github.com/pingcap/tidb/pkg/parser/mysql"
|
|
"github.com/pingcap/tidb/pkg/sessionctx/stmtctx"
|
|
"github.com/pingcap/tidb/pkg/tablecodec"
|
|
"github.com/pingcap/tidb/pkg/types"
|
|
"github.com/pingcap/tidb/pkg/util/chunk"
|
|
"github.com/pingcap/tidb/pkg/util/codec"
|
|
"github.com/pingcap/tidb/pkg/util/collate"
|
|
"github.com/pingcap/tidb/pkg/util/rowcodec"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type testData struct {
|
|
id int64
|
|
ft *types.FieldType
|
|
input types.Datum
|
|
output types.Datum
|
|
def *types.Datum
|
|
handle bool
|
|
}
|
|
|
|
func TestEncodeLargeSmallReuseBug(t *testing.T) {
|
|
// reuse one rowcodec.Encoder.
|
|
var encoder rowcodec.Encoder
|
|
colFt := types.NewFieldType(mysql.TypeString)
|
|
|
|
largeColID := int64(300)
|
|
b, err := encoder.Encode(nil, []int64{largeColID}, []types.Datum{types.NewBytesDatum([]byte(""))}, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
bDecoder := rowcodec.NewDatumMapDecoder([]rowcodec.ColInfo{
|
|
{
|
|
ID: largeColID,
|
|
Ft: colFt,
|
|
IsPKHandle: false,
|
|
},
|
|
}, nil)
|
|
_, err = bDecoder.DecodeToDatumMap(b, nil)
|
|
require.NoError(t, err)
|
|
|
|
colFt = types.NewFieldType(mysql.TypeLonglong)
|
|
smallColID := int64(1)
|
|
b, err = encoder.Encode(nil, []int64{smallColID}, []types.Datum{types.NewIntDatum(2)}, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
bDecoder = rowcodec.NewDatumMapDecoder([]rowcodec.ColInfo{
|
|
{
|
|
ID: smallColID,
|
|
Ft: colFt,
|
|
IsPKHandle: false,
|
|
},
|
|
}, nil)
|
|
m, err := bDecoder.DecodeToDatumMap(b, nil)
|
|
require.NoError(t, err)
|
|
|
|
v := m[smallColID]
|
|
require.Equal(t, int64(2), v.GetInt64())
|
|
}
|
|
|
|
func TestDecodeRowWithHandle(t *testing.T) {
|
|
handleID := int64(-1)
|
|
handleValue := int64(10000)
|
|
|
|
tests := []struct {
|
|
name string
|
|
testData []testData
|
|
}{
|
|
{
|
|
"signed int",
|
|
[]testData{
|
|
{
|
|
handleID,
|
|
types.NewFieldType(mysql.TypeLonglong),
|
|
types.NewIntDatum(handleValue),
|
|
types.NewIntDatum(handleValue),
|
|
nil,
|
|
true,
|
|
},
|
|
{
|
|
10,
|
|
types.NewFieldType(mysql.TypeLonglong),
|
|
types.NewIntDatum(1),
|
|
types.NewIntDatum(1),
|
|
nil,
|
|
false,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"unsigned int",
|
|
[]testData{
|
|
{
|
|
handleID,
|
|
withUnsigned(types.NewFieldType(mysql.TypeLonglong)),
|
|
types.NewUintDatum(uint64(handleValue)),
|
|
types.NewUintDatum(uint64(handleValue)), // decode as bytes will uint if unsigned.
|
|
nil,
|
|
true,
|
|
},
|
|
{
|
|
10,
|
|
types.NewFieldType(mysql.TypeLonglong),
|
|
types.NewIntDatum(1),
|
|
types.NewIntDatum(1),
|
|
nil,
|
|
false,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
td := test.testData
|
|
|
|
// transform test data into input.
|
|
colIDs := make([]int64, 0, len(td))
|
|
dts := make([]types.Datum, 0, len(td))
|
|
fts := make([]*types.FieldType, 0, len(td))
|
|
cols := make([]rowcodec.ColInfo, 0, len(td))
|
|
handleColFtMap := make(map[int64]*types.FieldType)
|
|
for _, d := range td {
|
|
if d.handle {
|
|
handleColFtMap[handleID] = d.ft
|
|
} else {
|
|
colIDs = append(colIDs, d.id)
|
|
dts = append(dts, d.input)
|
|
}
|
|
fts = append(fts, d.ft)
|
|
cols = append(cols, rowcodec.ColInfo{
|
|
ID: d.id,
|
|
IsPKHandle: d.handle,
|
|
Ft: d.ft,
|
|
})
|
|
}
|
|
|
|
// test encode input.
|
|
var encoder rowcodec.Encoder
|
|
newRow, err := encoder.Encode(time.UTC, colIDs, dts, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
// decode to datum map.
|
|
mDecoder := rowcodec.NewDatumMapDecoder(cols, time.UTC)
|
|
dm, err := mDecoder.DecodeToDatumMap(newRow, nil)
|
|
require.NoError(t, err)
|
|
|
|
dm, err = tablecodec.DecodeHandleToDatumMap(kv.IntHandle(handleValue), []int64{handleID}, handleColFtMap, time.UTC, dm)
|
|
require.NoError(t, err)
|
|
|
|
for _, d := range td {
|
|
dat, exists := dm[d.id]
|
|
require.True(t, exists)
|
|
require.Equal(t, d.input, dat)
|
|
}
|
|
|
|
// decode to chunk.
|
|
cDecoder := rowcodec.NewChunkDecoder(cols, []int64{-1}, nil, time.UTC)
|
|
chk := chunk.New(fts, 1, 1)
|
|
err = cDecoder.DecodeToChunk(newRow, 0, kv.IntHandle(handleValue), chk)
|
|
require.NoError(t, err)
|
|
|
|
chkRow := chk.GetRow(0)
|
|
cdt := chkRow.GetDatumRow(fts)
|
|
for i, d := range td {
|
|
dat := cdt[i]
|
|
if dat.Kind() == types.KindMysqlDecimal {
|
|
require.Equal(t, d.output.GetMysqlDecimal(), dat.GetMysqlDecimal())
|
|
} else {
|
|
require.Equal(t, d.output, dat)
|
|
}
|
|
}
|
|
|
|
// decode to old row bytes.
|
|
colOffset := make(map[int64]int)
|
|
for i, t := range td {
|
|
colOffset[t.id] = i
|
|
}
|
|
bDecoder := rowcodec.NewByteDecoder(cols, []int64{-1}, nil, nil)
|
|
oldRow, err := bDecoder.DecodeToBytes(colOffset, kv.IntHandle(handleValue), newRow, nil)
|
|
require.NoError(t, err)
|
|
|
|
for i, d := range td {
|
|
remain, dat, err := codec.DecodeOne(oldRow[i])
|
|
require.NoError(t, err)
|
|
require.Len(t, remain, 0)
|
|
if dat.Kind() == types.KindMysqlDecimal {
|
|
require.Equal(t, d.output.GetMysqlDecimal(), dat.GetMysqlDecimal())
|
|
} else {
|
|
require.Equal(t, d.output, dat)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEncodeKindNullDatum(t *testing.T) {
|
|
var encoder rowcodec.Encoder
|
|
colIDs := []int64{1, 2}
|
|
|
|
var nilDt types.Datum
|
|
nilDt.SetNull()
|
|
dts := []types.Datum{nilDt, types.NewIntDatum(2)}
|
|
ft := types.NewFieldType(mysql.TypeLonglong)
|
|
fts := []*types.FieldType{ft, ft}
|
|
newRow, err := encoder.Encode(time.UTC, colIDs, dts, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
cols := []rowcodec.ColInfo{{ID: 1, Ft: ft}, {ID: 2, Ft: ft}}
|
|
cDecoder := rowcodec.NewChunkDecoder(cols, []int64{-1}, nil, time.UTC)
|
|
chk := chunk.New(fts, 1, 1)
|
|
err = cDecoder.DecodeToChunk(newRow, 0, kv.IntHandle(-1), chk)
|
|
require.NoError(t, err)
|
|
|
|
chkRow := chk.GetRow(0)
|
|
cdt := chkRow.GetDatumRow(fts)
|
|
require.True(t, cdt[0].IsNull())
|
|
require.Equal(t, int64(2), cdt[1].GetInt64())
|
|
}
|
|
|
|
func TestDecodeDecimalFspNotMatch(t *testing.T) {
|
|
var encoder rowcodec.Encoder
|
|
colIDs := []int64{
|
|
1,
|
|
}
|
|
dec := withFrac(4)(withLen(6)(types.NewDecimalDatum(types.NewDecFromStringForTest("11.9900"))))
|
|
dts := []types.Datum{dec}
|
|
ft := types.NewFieldType(mysql.TypeNewDecimal)
|
|
ft.SetDecimal(4)
|
|
fts := []*types.FieldType{ft}
|
|
newRow, err := encoder.Encode(time.UTC, colIDs, dts, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
// decode to chunk.
|
|
ft = types.NewFieldType(mysql.TypeNewDecimal)
|
|
ft.SetDecimal(3)
|
|
cols := make([]rowcodec.ColInfo, 0)
|
|
cols = append(cols, rowcodec.ColInfo{
|
|
ID: 1,
|
|
Ft: ft,
|
|
})
|
|
cDecoder := rowcodec.NewChunkDecoder(cols, []int64{-1}, nil, time.UTC)
|
|
chk := chunk.New(fts, 1, 1)
|
|
err = cDecoder.DecodeToChunk(newRow, 0, kv.IntHandle(-1), chk)
|
|
require.NoError(t, err)
|
|
|
|
chkRow := chk.GetRow(0)
|
|
cdt := chkRow.GetDatumRow(fts)
|
|
dec = withFrac(3)(withLen(6)(types.NewDecimalDatum(types.NewDecFromStringForTest("11.990"))))
|
|
require.Equal(t, dec.GetMysqlDecimal().String(), cdt[0].GetMysqlDecimal().String())
|
|
}
|
|
|
|
func TestTypesNewRowCodec(t *testing.T) {
|
|
getJSONDatum := func(value string) types.Datum {
|
|
j, err := types.ParseBinaryJSONFromString(value)
|
|
require.NoError(t, err)
|
|
var d types.Datum
|
|
d.SetMysqlJSON(j)
|
|
return d
|
|
}
|
|
getSetDatum := func(name string, value uint64) types.Datum {
|
|
var d types.Datum
|
|
d.SetMysqlSet(types.Set{Name: name, Value: value}, mysql.DefaultCollationName)
|
|
return d
|
|
}
|
|
getTime := func(value string) types.Time {
|
|
d, err := types.ParseTime(types.DefaultStmtNoWarningContext, value, mysql.TypeTimestamp, 6)
|
|
require.NoError(t, err)
|
|
return d
|
|
}
|
|
|
|
blobTp := types.NewFieldType(mysql.TypeBlob)
|
|
blobTp.SetCollate(mysql.DefaultCollationName)
|
|
blobTp.SetFlen(types.UnspecifiedLength)
|
|
|
|
strTp := types.NewFieldType(mysql.TypeString)
|
|
strTp.SetCollate(mysql.DefaultCollationName)
|
|
|
|
enumTp := types.NewFieldType(mysql.TypeEnum)
|
|
enumTp.SetCollate(mysql.DefaultCollationName)
|
|
enumTp.SetFlen(collate.DefaultLen)
|
|
|
|
setTp := types.NewFieldType(mysql.TypeSet)
|
|
setTp.SetCollate(mysql.DefaultCollationName)
|
|
setTp.SetFlen(collate.DefaultLen)
|
|
|
|
varStrTp := types.NewFieldType(mysql.TypeVarString)
|
|
varStrTp.SetCollate(mysql.DefaultCollationName)
|
|
|
|
smallTestDataList := []testData{
|
|
{
|
|
1,
|
|
types.NewFieldType(mysql.TypeLonglong),
|
|
types.NewIntDatum(1),
|
|
types.NewIntDatum(1),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
22,
|
|
withUnsigned(types.NewFieldType(mysql.TypeShort)),
|
|
types.NewUintDatum(1),
|
|
types.NewUintDatum(1),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
3,
|
|
types.NewFieldType(mysql.TypeDouble),
|
|
types.NewFloat64Datum(2),
|
|
types.NewFloat64Datum(2),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
24,
|
|
blobTp,
|
|
types.NewStringDatum("abc"),
|
|
types.NewStringDatum("abc"),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
25,
|
|
strTp,
|
|
types.NewStringDatum("ab"),
|
|
types.NewBytesDatum([]byte("ab")),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
5,
|
|
withFsp(6)(types.NewFieldType(mysql.TypeTimestamp)),
|
|
types.NewTimeDatum(getTime("2011-11-10 11:11:11.999999")),
|
|
types.NewUintDatum(1840446893366133311),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
16,
|
|
withFsp(0)(types.NewFieldType(mysql.TypeDuration)),
|
|
types.NewDurationDatum(getDuration("4:00:00")),
|
|
types.NewIntDatum(14400000000000),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
8,
|
|
types.NewFieldType(mysql.TypeNewDecimal),
|
|
withFrac(4)(withLen(6)(types.NewDecimalDatum(types.NewDecFromStringForTest("11.9900")))),
|
|
withFrac(4)(withLen(6)(types.NewDecimalDatum(types.NewDecFromStringForTest("11.9900")))),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
12,
|
|
types.NewFieldType(mysql.TypeYear),
|
|
types.NewIntDatum(1999),
|
|
types.NewIntDatum(1999),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
9,
|
|
withEnumElems("y", "n")(enumTp),
|
|
types.NewMysqlEnumDatum(types.Enum{Name: "n", Value: 2}),
|
|
types.NewUintDatum(2),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
14,
|
|
types.NewFieldType(mysql.TypeJSON),
|
|
getJSONDatum(`{"a":2}`),
|
|
getJSONDatum(`{"a":2}`),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
11,
|
|
types.NewFieldType(mysql.TypeNull),
|
|
types.NewDatum(nil),
|
|
types.NewDatum(nil),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
2,
|
|
types.NewFieldType(mysql.TypeNull),
|
|
types.NewDatum(nil),
|
|
types.NewDatum(nil),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
100,
|
|
types.NewFieldType(mysql.TypeNull),
|
|
types.NewDatum(nil),
|
|
types.NewDatum(nil),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
116,
|
|
types.NewFieldType(mysql.TypeFloat),
|
|
types.NewFloat32Datum(6),
|
|
types.NewFloat64Datum(6),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
117,
|
|
withEnumElems("n1", "n2")(setTp),
|
|
getSetDatum("n1", 1),
|
|
types.NewUintDatum(1),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
118,
|
|
withFlen(24)(types.NewFieldType(mysql.TypeBit)), // 3 bit
|
|
types.NewMysqlBitDatum(types.NewBinaryLiteralFromUint(3223600, 3)),
|
|
types.NewUintDatum(3223600),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
119,
|
|
varStrTp,
|
|
types.NewStringDatum(""),
|
|
types.NewBytesDatum([]byte("")),
|
|
nil,
|
|
false,
|
|
},
|
|
}
|
|
|
|
largeColIDTestDataList := make([]testData, len(smallTestDataList))
|
|
copy(largeColIDTestDataList, smallTestDataList)
|
|
largeColIDTestDataList[0].id = 300
|
|
|
|
largeTestDataList := make([]testData, len(smallTestDataList))
|
|
copy(largeTestDataList, smallTestDataList)
|
|
largeTestDataList[3].input = types.NewStringDatum(strings.Repeat("a", math.MaxUint16+1))
|
|
largeTestDataList[3].output = types.NewStringDatum(strings.Repeat("a", math.MaxUint16+1))
|
|
|
|
var encoder rowcodec.Encoder
|
|
|
|
tests := []struct {
|
|
name string
|
|
testData []testData
|
|
}{
|
|
{
|
|
"small",
|
|
smallTestDataList,
|
|
},
|
|
{
|
|
"largeColID",
|
|
largeColIDTestDataList,
|
|
},
|
|
{
|
|
"largeData",
|
|
largeTestDataList,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
td := test.testData
|
|
|
|
// transform test data into input.
|
|
colIDs := make([]int64, 0, len(td))
|
|
dts := make([]types.Datum, 0, len(td))
|
|
fts := make([]*types.FieldType, 0, len(td))
|
|
cols := make([]rowcodec.ColInfo, 0, len(td))
|
|
for _, d := range td {
|
|
colIDs = append(colIDs, d.id)
|
|
dts = append(dts, d.input)
|
|
fts = append(fts, d.ft)
|
|
cols = append(cols, rowcodec.ColInfo{
|
|
ID: d.id,
|
|
IsPKHandle: d.handle,
|
|
Ft: d.ft,
|
|
})
|
|
}
|
|
|
|
// test encode input.
|
|
newRow, err := encoder.Encode(time.UTC, colIDs, dts, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
// decode to datum map.
|
|
mDecoder := rowcodec.NewDatumMapDecoder(cols, time.UTC)
|
|
dm, err := mDecoder.DecodeToDatumMap(newRow, nil)
|
|
require.NoError(t, err)
|
|
|
|
for _, d := range td {
|
|
dat, exists := dm[d.id]
|
|
require.True(t, exists)
|
|
require.Equal(t, d.input, dat)
|
|
}
|
|
|
|
// decode to chunk.
|
|
cDecoder := rowcodec.NewChunkDecoder(cols, []int64{-1}, nil, time.UTC)
|
|
chk := chunk.New(fts, 1, 1)
|
|
err = cDecoder.DecodeToChunk(newRow, 0, kv.IntHandle(-1), chk)
|
|
require.NoError(t, err)
|
|
|
|
chkRow := chk.GetRow(0)
|
|
cdt := chkRow.GetDatumRow(fts)
|
|
for i, d := range td {
|
|
dat := cdt[i]
|
|
if dat.Kind() == types.KindMysqlDecimal {
|
|
require.Equal(t, d.output.GetMysqlDecimal(), dat.GetMysqlDecimal())
|
|
} else {
|
|
require.Equal(t, d.input, dat)
|
|
}
|
|
}
|
|
|
|
// decode to old row bytes.
|
|
colOffset := make(map[int64]int)
|
|
for i, t := range td {
|
|
colOffset[t.id] = i
|
|
}
|
|
bDecoder := rowcodec.NewByteDecoder(cols, []int64{-1}, nil, nil)
|
|
oldRow, err := bDecoder.DecodeToBytes(colOffset, kv.IntHandle(-1), newRow, nil)
|
|
require.NoError(t, err)
|
|
|
|
for i, d := range td {
|
|
remain, dat, err := codec.DecodeOne(oldRow[i])
|
|
require.NoError(t, err)
|
|
require.Len(t, remain, 0)
|
|
if dat.Kind() == types.KindMysqlDecimal {
|
|
require.Equal(t, d.output.GetMysqlDecimal(), dat.GetMysqlDecimal())
|
|
} else if dat.Kind() == types.KindBytes {
|
|
require.Equal(t, d.output.GetBytes(), dat.GetBytes())
|
|
} else {
|
|
require.Equal(t, d.output, dat)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNilAndDefault(t *testing.T) {
|
|
td := []testData{
|
|
{
|
|
1,
|
|
types.NewFieldType(mysql.TypeLonglong),
|
|
types.NewIntDatum(1),
|
|
types.NewIntDatum(1),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
2,
|
|
withUnsigned(types.NewFieldType(mysql.TypeLonglong)),
|
|
types.NewUintDatum(1),
|
|
types.NewUintDatum(9),
|
|
getDatumPoint(types.NewUintDatum(9)),
|
|
false,
|
|
},
|
|
}
|
|
|
|
// transform test data into input.
|
|
colIDs := make([]int64, 0, len(td))
|
|
dts := make([]types.Datum, 0, len(td))
|
|
cols := make([]rowcodec.ColInfo, 0, len(td))
|
|
fts := make([]*types.FieldType, 0, len(td))
|
|
for i := range td {
|
|
d := td[i]
|
|
if d.def == nil {
|
|
colIDs = append(colIDs, d.id)
|
|
dts = append(dts, d.input)
|
|
}
|
|
fts = append(fts, d.ft)
|
|
cols = append(cols, rowcodec.ColInfo{
|
|
ID: d.id,
|
|
IsPKHandle: d.handle,
|
|
Ft: d.ft,
|
|
})
|
|
}
|
|
ddf := func(i int, chk *chunk.Chunk) error {
|
|
d := td[i]
|
|
if d.def == nil {
|
|
chk.AppendNull(i)
|
|
return nil
|
|
}
|
|
chk.AppendDatum(i, d.def)
|
|
return nil
|
|
}
|
|
bdf := func(i int) ([]byte, error) {
|
|
d := td[i]
|
|
if d.def == nil {
|
|
return nil, nil
|
|
}
|
|
return getOldDatumByte(*d.def), nil
|
|
}
|
|
|
|
// test encode input.
|
|
var encoder rowcodec.Encoder
|
|
newRow, err := encoder.Encode(time.UTC, colIDs, dts, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
// decode to datum map.
|
|
mDecoder := rowcodec.NewDatumMapDecoder(cols, time.UTC)
|
|
dm, err := mDecoder.DecodeToDatumMap(newRow, nil)
|
|
require.NoError(t, err)
|
|
|
|
for _, d := range td {
|
|
dat, exists := dm[d.id]
|
|
if d.def != nil {
|
|
// for datum should not fill default value.
|
|
require.False(t, exists)
|
|
} else {
|
|
require.True(t, exists)
|
|
require.Equal(t, d.output, dat)
|
|
}
|
|
}
|
|
|
|
// decode to chunk.
|
|
chk := chunk.New(fts, 1, 1)
|
|
cDecoder := rowcodec.NewChunkDecoder(cols, []int64{-1}, ddf, time.UTC)
|
|
err = cDecoder.DecodeToChunk(newRow, 0, kv.IntHandle(-1), chk)
|
|
require.NoError(t, err)
|
|
|
|
chkRow := chk.GetRow(0)
|
|
cdt := chkRow.GetDatumRow(fts)
|
|
for i, d := range td {
|
|
dat := cdt[i]
|
|
if dat.Kind() == types.KindMysqlDecimal {
|
|
require.Equal(t, d.output.GetMysqlDecimal(), dat.GetMysqlDecimal())
|
|
} else {
|
|
require.Equal(t, d.output, dat)
|
|
}
|
|
}
|
|
|
|
chk = chunk.New(fts, 1, 1)
|
|
cDecoder = rowcodec.NewChunkDecoder(cols, []int64{-1}, nil, time.UTC)
|
|
err = cDecoder.DecodeToChunk(newRow, 0, kv.IntHandle(-1), chk)
|
|
require.NoError(t, err)
|
|
|
|
chkRow = chk.GetRow(0)
|
|
cdt = chkRow.GetDatumRow(fts)
|
|
for i := range td {
|
|
if i == 0 {
|
|
continue
|
|
}
|
|
require.True(t, cdt[i].IsNull())
|
|
}
|
|
|
|
// decode to old row bytes.
|
|
colOffset := make(map[int64]int)
|
|
for i, t := range td {
|
|
colOffset[t.id] = i
|
|
}
|
|
bDecoder := rowcodec.NewByteDecoder(cols, []int64{-1}, bdf, time.UTC)
|
|
oldRow, err := bDecoder.DecodeToBytes(colOffset, kv.IntHandle(-1), newRow, nil)
|
|
require.NoError(t, err)
|
|
|
|
for i, d := range td {
|
|
remain, dat, err := codec.DecodeOne(oldRow[i])
|
|
require.NoError(t, err)
|
|
require.Len(t, remain, 0)
|
|
|
|
if dat.Kind() == types.KindMysqlDecimal {
|
|
require.Equal(t, d.output.GetMysqlDecimal(), dat.GetMysqlDecimal())
|
|
} else {
|
|
require.Equal(t, d.output, dat)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestVarintCompatibility(t *testing.T) {
|
|
td := []testData{
|
|
{
|
|
1,
|
|
types.NewFieldType(mysql.TypeLonglong),
|
|
types.NewIntDatum(1),
|
|
types.NewIntDatum(1),
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
2,
|
|
withUnsigned(types.NewFieldType(mysql.TypeLonglong)),
|
|
types.NewUintDatum(1),
|
|
types.NewUintDatum(1),
|
|
nil,
|
|
false,
|
|
},
|
|
}
|
|
|
|
// transform test data into input.
|
|
colIDs := make([]int64, 0, len(td))
|
|
dts := make([]types.Datum, 0, len(td))
|
|
cols := make([]rowcodec.ColInfo, 0, len(td))
|
|
for _, d := range td {
|
|
colIDs = append(colIDs, d.id)
|
|
dts = append(dts, d.input)
|
|
cols = append(cols, rowcodec.ColInfo{
|
|
ID: d.id,
|
|
IsPKHandle: d.handle,
|
|
Ft: d.ft,
|
|
})
|
|
}
|
|
|
|
// test encode input.
|
|
var encoder rowcodec.Encoder
|
|
newRow, err := encoder.Encode(time.UTC, colIDs, dts, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
decoder := rowcodec.NewByteDecoder(cols, []int64{-1}, nil, time.UTC)
|
|
// decode to old row bytes.
|
|
colOffset := make(map[int64]int)
|
|
for i, t := range td {
|
|
colOffset[t.id] = i
|
|
}
|
|
oldRow, err := decoder.DecodeToBytes(colOffset, kv.IntHandle(1), newRow, nil)
|
|
require.NoError(t, err)
|
|
|
|
for i, d := range td {
|
|
oldVarint, err := tablecodec.EncodeValue(nil, nil, d.output) // tablecodec will encode as varint/varuint
|
|
require.NoError(t, err)
|
|
require.Equal(t, oldRow[i], oldVarint)
|
|
}
|
|
}
|
|
|
|
func TestCodecUtil(t *testing.T) {
|
|
colIDs := []int64{1, 2, 3, 4}
|
|
tps := make([]*types.FieldType, 4)
|
|
for i := range 3 {
|
|
tps[i] = types.NewFieldType(mysql.TypeLonglong)
|
|
}
|
|
tps[3] = types.NewFieldType(mysql.TypeNull)
|
|
sc := stmtctx.NewStmtCtx()
|
|
oldRow, err := tablecodec.EncodeOldRow(sc.TimeZone(), types.MakeDatums(1, 2, 3, nil), colIDs, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
var (
|
|
rb rowcodec.Encoder
|
|
newRow []byte
|
|
)
|
|
newRow, err = rowcodec.EncodeFromOldRow(&rb, nil, oldRow, nil)
|
|
require.NoError(t, err)
|
|
require.True(t, rowcodec.IsNewFormat(newRow))
|
|
require.False(t, rowcodec.IsNewFormat(oldRow))
|
|
|
|
// test stringer for decoder.
|
|
var cols = make([]rowcodec.ColInfo, 0, len(tps))
|
|
for i, ft := range tps {
|
|
cols = append(cols, rowcodec.ColInfo{
|
|
ID: colIDs[i],
|
|
IsPKHandle: false,
|
|
Ft: ft,
|
|
})
|
|
}
|
|
d := rowcodec.NewDecoder(cols, []int64{-1}, nil)
|
|
|
|
// test ColumnIsNull
|
|
isNull, err := d.ColumnIsNull(newRow, 4, nil)
|
|
require.NoError(t, err)
|
|
require.True(t, isNull)
|
|
isNull, err = d.ColumnIsNull(newRow, 1, nil)
|
|
require.NoError(t, err)
|
|
require.False(t, isNull)
|
|
isNull, err = d.ColumnIsNull(newRow, 5, nil)
|
|
require.NoError(t, err)
|
|
require.True(t, isNull)
|
|
isNull, err = d.ColumnIsNull(newRow, 5, []byte{1})
|
|
require.NoError(t, err)
|
|
require.False(t, isNull)
|
|
|
|
// test isRowKey
|
|
require.False(t, rowcodec.IsRowKey([]byte{'b', 't'}))
|
|
require.False(t, rowcodec.IsRowKey([]byte{'t', 'r'}))
|
|
}
|
|
|
|
func TestOldRowCodec(t *testing.T) {
|
|
colIDs := []int64{1, 2, 3, 4}
|
|
tps := make([]*types.FieldType, 4)
|
|
for i := range 3 {
|
|
tps[i] = types.NewFieldType(mysql.TypeLonglong)
|
|
}
|
|
tps[3] = types.NewFieldType(mysql.TypeNull)
|
|
sc := stmtctx.NewStmtCtx()
|
|
oldRow, err := tablecodec.EncodeOldRow(sc.TimeZone(), types.MakeDatums(1, 2, 3, nil), colIDs, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
var (
|
|
rb rowcodec.Encoder
|
|
newRow []byte
|
|
)
|
|
newRow, err = rowcodec.EncodeFromOldRow(&rb, nil, oldRow, nil)
|
|
require.NoError(t, err)
|
|
|
|
cols := make([]rowcodec.ColInfo, len(tps))
|
|
for i, tp := range tps {
|
|
cols[i] = rowcodec.ColInfo{
|
|
ID: colIDs[i],
|
|
Ft: tp,
|
|
}
|
|
}
|
|
rd := rowcodec.NewChunkDecoder(cols, []int64{-1}, nil, time.Local)
|
|
chk := chunk.NewChunkWithCapacity(tps, 1)
|
|
err = rd.DecodeToChunk(newRow, 0, kv.IntHandle(-1), chk)
|
|
require.NoError(t, err)
|
|
row := chk.GetRow(0)
|
|
for i := range 3 {
|
|
require.Equal(t, int64(i+1), row.GetInt64(i))
|
|
}
|
|
}
|
|
|
|
func Test65535Bug(t *testing.T) {
|
|
colIds := []int64{1}
|
|
tps := make([]*types.FieldType, 1)
|
|
tps[0] = types.NewFieldType(mysql.TypeString)
|
|
text65535 := strings.Repeat("a", 65535)
|
|
encode := rowcodec.Encoder{}
|
|
bd, err := encode.Encode(time.UTC, colIds, []types.Datum{types.NewStringDatum(text65535)}, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
cols := make([]rowcodec.ColInfo, 1)
|
|
cols[0] = rowcodec.ColInfo{
|
|
ID: 1,
|
|
Ft: tps[0],
|
|
}
|
|
dc := rowcodec.NewDatumMapDecoder(cols, nil)
|
|
result, err := dc.DecodeToDatumMap(bd, nil)
|
|
require.NoError(t, err)
|
|
|
|
rs := result[1]
|
|
require.Equal(t, text65535, rs.GetString())
|
|
}
|
|
|
|
func TestColumnEncode(t *testing.T) {
|
|
encodeUint64 := func(v uint64) []byte {
|
|
return binary.LittleEndian.AppendUint64(nil, v)
|
|
}
|
|
encodeBytes := func(v []byte) []byte {
|
|
return append(binary.LittleEndian.AppendUint32(nil, uint32(len(v))), v...)
|
|
}
|
|
convertTZ := func(ts types.Time) types.Time {
|
|
require.NoError(t, ts.ConvertTimeZone(time.Local, time.UTC))
|
|
return ts
|
|
}
|
|
var (
|
|
buf = make([]byte, 0, 128)
|
|
intZero = 0
|
|
intPos = 42
|
|
intNeg = -2
|
|
i8Min = math.MinInt8
|
|
i16Min = math.MinInt16
|
|
i32Min = math.MinInt32
|
|
i64Min = math.MinInt64
|
|
i24Min = -1 << 23
|
|
ct = types.FromDate(2023, 1, 2, 3, 4, 5, 678)
|
|
dur = types.Duration{Duration: 123456*time.Microsecond + 7*time.Minute + 8*time.Hour, Fsp: 6}
|
|
decZero = types.NewDecFromStringForTest("0.000")
|
|
decPos = types.NewDecFromStringForTest("3.14")
|
|
decNeg = types.NewDecFromStringForTest("-1.2")
|
|
decMin = types.NewMaxOrMinDec(true, 12, 6)
|
|
decMax = types.NewMaxOrMinDec(false, 12, 6)
|
|
json1 = types.CreateBinaryJSON(nil)
|
|
json2 = types.CreateBinaryJSON(int64(42))
|
|
json3 = types.CreateBinaryJSON(map[string]any{"foo": "bar", "a": int64(42)})
|
|
)
|
|
|
|
for _, tt := range []struct {
|
|
name string
|
|
typ *types.FieldType
|
|
dat types.Datum
|
|
raw []byte
|
|
ok bool
|
|
}{
|
|
{"unspecified", types.NewFieldType(mysql.TypeUnspecified), types.NewDatum(1), nil, false},
|
|
{"wrong", types.NewFieldType(42), types.NewDatum(1), nil, false},
|
|
{"mismatch/timestamp", types.NewFieldType(mysql.TypeTimestamp), types.NewDatum(1), nil, false},
|
|
{"mismatch/datetime", types.NewFieldType(mysql.TypeDatetime), types.NewDatum(1), nil, false},
|
|
{"mismatch/date", types.NewFieldType(mysql.TypeDate), types.NewDatum(1), nil, false},
|
|
{"mismatch/newdate", types.NewFieldType(mysql.TypeNewDate), types.NewDatum(1), nil, false},
|
|
{"mismatch/decimal", types.NewFieldType(mysql.TypeNewDecimal), types.NewDatum(1), nil, false},
|
|
|
|
{"null", types.NewFieldType(mysql.TypeNull), types.NewDatum(1), nil, true},
|
|
{"geometry", types.NewFieldType(mysql.TypeGeometry), types.NewDatum(1), nil, true},
|
|
|
|
{"tinyint/zero", types.NewFieldType(mysql.TypeTiny), types.NewDatum(intZero), encodeUint64(uint64(intZero)), true},
|
|
{"tinyint/pos", types.NewFieldType(mysql.TypeTiny), types.NewDatum(intPos), encodeUint64(uint64(intPos)), true},
|
|
{"tinyint/neg", types.NewFieldType(mysql.TypeTiny), types.NewDatum(intNeg), encodeUint64(uint64(intNeg)), true},
|
|
{"tinyint/min/signed", types.NewFieldType(mysql.TypeTiny), types.NewDatum(i8Min), encodeUint64(uint64(i8Min)), true},
|
|
{"tinyint/max/signed", types.NewFieldType(mysql.TypeTiny), types.NewDatum(math.MaxInt8), encodeUint64(math.MaxInt8), true},
|
|
{"tinyint/max/unsigned", types.NewFieldType(mysql.TypeTiny), types.NewDatum(math.MaxUint8), encodeUint64(math.MaxUint8), true},
|
|
|
|
{"smallint/zero", types.NewFieldType(mysql.TypeShort), types.NewDatum(intZero), encodeUint64(uint64(intZero)), true},
|
|
{"smallint/pos", types.NewFieldType(mysql.TypeShort), types.NewDatum(intPos), encodeUint64(uint64(intPos)), true},
|
|
{"smallint/neg", types.NewFieldType(mysql.TypeShort), types.NewDatum(intNeg), encodeUint64(uint64(intNeg)), true},
|
|
{"smallint/min/signed", types.NewFieldType(mysql.TypeShort), types.NewDatum(i16Min), encodeUint64(uint64(i16Min)), true},
|
|
{"smallint/max/signed", types.NewFieldType(mysql.TypeShort), types.NewDatum(math.MaxInt16), encodeUint64(math.MaxInt16), true},
|
|
{"smallint/max/unsigned", types.NewFieldType(mysql.TypeShort), types.NewDatum(math.MaxUint16), encodeUint64(math.MaxUint16), true},
|
|
|
|
{"int/zero", types.NewFieldType(mysql.TypeLong), types.NewDatum(intZero), encodeUint64(uint64(intZero)), true},
|
|
{"int/pos", types.NewFieldType(mysql.TypeLong), types.NewDatum(intPos), encodeUint64(uint64(intPos)), true},
|
|
{"int/neg", types.NewFieldType(mysql.TypeLong), types.NewDatum(intNeg), encodeUint64(uint64(intNeg)), true},
|
|
{"int/min/signed", types.NewFieldType(mysql.TypeLong), types.NewDatum(i32Min), encodeUint64(uint64(i32Min)), true},
|
|
{"int/max/signed", types.NewFieldType(mysql.TypeLong), types.NewDatum(math.MaxInt32), encodeUint64(math.MaxInt32), true},
|
|
{"int/max/unsigned", types.NewFieldType(mysql.TypeLong), types.NewDatum(math.MaxUint32), encodeUint64(math.MaxUint32), true},
|
|
|
|
{"bigint/zero", types.NewFieldType(mysql.TypeLonglong), types.NewDatum(intZero), encodeUint64(uint64(intZero)), true},
|
|
{"bigint/pos", types.NewFieldType(mysql.TypeLonglong), types.NewDatum(intPos), encodeUint64(uint64(intPos)), true},
|
|
{"bigint/neg", types.NewFieldType(mysql.TypeLonglong), types.NewDatum(intNeg), encodeUint64(uint64(intNeg)), true},
|
|
{"bigint/min/signed", types.NewFieldType(mysql.TypeLonglong), types.NewDatum(i64Min), encodeUint64(uint64(i64Min)), true},
|
|
{"bigint/max/signed", types.NewFieldType(mysql.TypeLonglong), types.NewDatum(math.MaxInt64), encodeUint64(math.MaxInt64), true},
|
|
{"bigint/max/unsigned", types.NewFieldType(mysql.TypeLonglong), types.NewDatum(uint64(math.MaxUint64)), encodeUint64(math.MaxUint64), true},
|
|
|
|
{"mediumint/zero", types.NewFieldType(mysql.TypeInt24), types.NewDatum(intZero), encodeUint64(uint64(intZero)), true},
|
|
{"mediumint/pos", types.NewFieldType(mysql.TypeInt24), types.NewDatum(intPos), encodeUint64(uint64(intPos)), true},
|
|
{"mediumint/neg", types.NewFieldType(mysql.TypeInt24), types.NewDatum(intNeg), encodeUint64(uint64(intNeg)), true},
|
|
{"mediumint/min/signed", types.NewFieldType(mysql.TypeInt24), types.NewDatum(i24Min), encodeUint64(uint64(i24Min)), true},
|
|
{"mediumint/max/signed", types.NewFieldType(mysql.TypeInt24), types.NewDatum(1<<23 - 1), encodeUint64(1<<23 - 1), true},
|
|
{"mediumint/max/unsigned", types.NewFieldType(mysql.TypeInt24), types.NewDatum(1<<24 - 1), encodeUint64(1<<24 - 1), true},
|
|
|
|
{"year", types.NewFieldType(mysql.TypeYear), types.NewDatum(2023), encodeUint64(2023), true},
|
|
|
|
{"varchar", types.NewFieldType(mysql.TypeVarchar), types.NewDatum("foo"), encodeBytes([]byte("foo")), true},
|
|
{"varchar/empty", types.NewFieldType(mysql.TypeVarchar), types.NewDatum(""), encodeBytes([]byte{}), true},
|
|
{"varbinary", types.NewFieldType(mysql.TypeVarString), types.NewDatum([]byte("foo")), encodeBytes([]byte("foo")), true},
|
|
{"varbinary/empty", types.NewFieldType(mysql.TypeVarString), types.NewDatum([]byte("")), encodeBytes([]byte{}), true},
|
|
{"char", types.NewFieldType(mysql.TypeString), types.NewDatum("foo"), encodeBytes([]byte("foo")), true},
|
|
{"char/empty", types.NewFieldType(mysql.TypeString), types.NewDatum(""), encodeBytes([]byte{}), true},
|
|
{"binary", types.NewFieldType(mysql.TypeString), types.NewDatum([]byte("foo")), encodeBytes([]byte("foo")), true},
|
|
{"binary/empty", types.NewFieldType(mysql.TypeString), types.NewDatum([]byte("")), encodeBytes([]byte{}), true},
|
|
{"text", types.NewFieldType(mysql.TypeBlob), types.NewDatum("foo"), encodeBytes([]byte("foo")), true},
|
|
{"text/empty", types.NewFieldType(mysql.TypeBlob), types.NewDatum(""), encodeBytes([]byte{}), true},
|
|
{"blob", types.NewFieldType(mysql.TypeBlob), types.NewDatum([]byte("foo")), encodeBytes([]byte("foo")), true},
|
|
{"blob/empty", types.NewFieldType(mysql.TypeBlob), types.NewDatum([]byte("")), encodeBytes([]byte{}), true},
|
|
{"longtext", types.NewFieldType(mysql.TypeLongBlob), types.NewDatum("foo"), encodeBytes([]byte("foo")), true},
|
|
{"longtext/empty", types.NewFieldType(mysql.TypeLongBlob), types.NewDatum(""), encodeBytes([]byte{}), true},
|
|
{"longblob", types.NewFieldType(mysql.TypeLongBlob), types.NewDatum([]byte("foo")), encodeBytes([]byte("foo")), true},
|
|
{"longblob/empty", types.NewFieldType(mysql.TypeLongBlob), types.NewDatum([]byte("")), encodeBytes([]byte{}), true},
|
|
{"mediumtext", types.NewFieldType(mysql.TypeMediumBlob), types.NewDatum("foo"), encodeBytes([]byte("foo")), true},
|
|
{"mediumtext/empty", types.NewFieldType(mysql.TypeMediumBlob), types.NewDatum(""), encodeBytes([]byte{}), true},
|
|
{"mediumblob", types.NewFieldType(mysql.TypeMediumBlob), types.NewDatum([]byte("foo")), encodeBytes([]byte("foo")), true},
|
|
{"mediumblob/empty", types.NewFieldType(mysql.TypeMediumBlob), types.NewDatum([]byte("")), encodeBytes([]byte{}), true},
|
|
{"tinytext", types.NewFieldType(mysql.TypeTinyBlob), types.NewDatum("foo"), encodeBytes([]byte("foo")), true},
|
|
{"tinytext/empty", types.NewFieldType(mysql.TypeTinyBlob), types.NewDatum(""), encodeBytes([]byte{}), true},
|
|
{"tinyblob", types.NewFieldType(mysql.TypeTinyBlob), types.NewDatum([]byte("foo")), encodeBytes([]byte("foo")), true},
|
|
{"tinyblob/empty", types.NewFieldType(mysql.TypeTinyBlob), types.NewDatum([]byte("")), encodeBytes([]byte{}), true},
|
|
|
|
{"float", types.NewFieldType(mysql.TypeFloat), types.NewDatum(float32(3.14)), encodeUint64(math.Float64bits(float64(float32(3.14)))), true},
|
|
{"float/nan", types.NewFieldType(mysql.TypeFloat), types.NewDatum(float32(math.NaN())), encodeUint64(math.Float64bits(0)), true},
|
|
{"float/+inf", types.NewFieldType(mysql.TypeFloat), types.NewDatum(float32(math.Inf(1))), encodeUint64(math.Float64bits(0)), true},
|
|
{"float/-inf", types.NewFieldType(mysql.TypeFloat), types.NewDatum(float32(math.Inf(-1))), encodeUint64(math.Float64bits(0)), true},
|
|
{"double", types.NewFieldType(mysql.TypeDouble), types.NewDatum(float64(3.14)), encodeUint64(math.Float64bits(3.14)), true},
|
|
{"double/nan", types.NewFieldType(mysql.TypeDouble), types.NewDatum(math.NaN()), encodeUint64(math.Float64bits(0)), true},
|
|
{"double/+inf", types.NewFieldType(mysql.TypeDouble), types.NewDatum(math.Inf(1)), encodeUint64(math.Float64bits(0)), true},
|
|
{"double/-inf", types.NewFieldType(mysql.TypeDouble), types.NewDatum(math.Inf(-1)), encodeUint64(math.Float64bits(0)), true},
|
|
|
|
{"enum", types.NewFieldType(mysql.TypeEnum), types.NewDatum(0b010), encodeUint64(0b010), true},
|
|
{"set", types.NewFieldType(mysql.TypeSet), types.NewDatum(0b101), encodeUint64(0b101), true},
|
|
{"bit", types.NewFieldType(mysql.TypeBit), types.NewBinaryLiteralDatum([]byte{0x12, 0x34}), encodeUint64(0x1234), true},
|
|
{"bit/truncate", types.NewFieldType(mysql.TypeBit), types.NewBinaryLiteralDatum([]byte{0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0x12, 0x34, 0xff}), encodeUint64(math.MaxUint64), true},
|
|
|
|
{
|
|
"timestamp", types.NewFieldType(mysql.TypeTimestamp),
|
|
types.NewTimeDatum(types.NewTime(ct, mysql.TypeTimestamp, 3)),
|
|
encodeBytes([]byte(convertTZ(types.NewTime(ct, mysql.TypeTimestamp, 3)).String())),
|
|
true,
|
|
},
|
|
{
|
|
"timestamp/zero", types.NewFieldType(mysql.TypeTimestamp),
|
|
types.NewTimeDatum(types.ZeroTimestamp),
|
|
encodeBytes([]byte(convertTZ(types.ZeroTimestamp).String())),
|
|
true,
|
|
},
|
|
{
|
|
"timestamp/min", types.NewFieldType(mysql.TypeTimestamp),
|
|
types.NewTimeDatum(types.MinTimestamp),
|
|
encodeBytes([]byte(convertTZ(types.MinTimestamp).String())),
|
|
true,
|
|
},
|
|
{
|
|
"timestamp/max", types.NewFieldType(mysql.TypeTimestamp),
|
|
types.NewTimeDatum(types.MaxTimestamp),
|
|
encodeBytes([]byte(convertTZ(types.MaxTimestamp).String())),
|
|
true,
|
|
},
|
|
{
|
|
"datetime", types.NewFieldType(mysql.TypeDatetime),
|
|
types.NewTimeDatum(types.NewTime(ct, mysql.TypeDatetime, 3)),
|
|
encodeBytes([]byte(types.NewTime(ct, mysql.TypeDatetime, 3).String())),
|
|
true,
|
|
},
|
|
{
|
|
"datetime/zero", types.NewFieldType(mysql.TypeDatetime),
|
|
types.NewTimeDatum(types.ZeroDatetime),
|
|
encodeBytes([]byte(types.ZeroTimestamp.String())),
|
|
true,
|
|
},
|
|
{
|
|
"datetime/min", types.NewFieldType(mysql.TypeDatetime),
|
|
types.NewTimeDatum(types.NewTime(types.MinDatetime, mysql.TypeDatetime, 6)),
|
|
encodeBytes([]byte(types.NewTime(types.MinDatetime, mysql.TypeDatetime, 6).String())),
|
|
true,
|
|
},
|
|
{
|
|
"datetime/max", types.NewFieldType(mysql.TypeDatetime),
|
|
types.NewTimeDatum(types.NewTime(types.MaxDatetime, mysql.TypeDatetime, 6)),
|
|
encodeBytes([]byte(types.NewTime(types.MaxDatetime, mysql.TypeDatetime, 6).String())),
|
|
true,
|
|
},
|
|
{
|
|
"date", types.NewFieldType(mysql.TypeDate),
|
|
types.NewTimeDatum(types.NewTime(ct, mysql.TypeDate, 3)),
|
|
encodeBytes([]byte(types.NewTime(ct, mysql.TypeDate, 3).String())),
|
|
true,
|
|
},
|
|
{
|
|
"date/zero", types.NewFieldType(mysql.TypeDate),
|
|
types.NewTimeDatum(types.ZeroDate),
|
|
encodeBytes([]byte(types.ZeroDate.String())),
|
|
true,
|
|
},
|
|
{
|
|
"date/min",
|
|
types.NewFieldType(mysql.TypeDate),
|
|
types.NewTimeDatum(types.NewTime(types.MinDatetime, mysql.TypeDate, 6)),
|
|
encodeBytes([]byte(types.NewTime(types.MinDatetime, mysql.TypeDate, 6).String())),
|
|
true,
|
|
},
|
|
{
|
|
"date/max",
|
|
types.NewFieldType(mysql.TypeDate),
|
|
types.NewTimeDatum(types.NewTime(types.MaxDatetime, mysql.TypeDate, 6)),
|
|
encodeBytes([]byte(types.NewTime(types.MaxDatetime, mysql.TypeDate, 6).String())),
|
|
true,
|
|
},
|
|
{
|
|
"newdate", types.NewFieldType(mysql.TypeNewDate),
|
|
types.NewTimeDatum(types.NewTime(ct, mysql.TypeNewDate, 3)),
|
|
encodeBytes([]byte(types.NewTime(ct, mysql.TypeNewDate, 3).String())),
|
|
true,
|
|
},
|
|
{
|
|
"newdate/zero", types.NewFieldType(mysql.TypeNewDate),
|
|
types.NewTimeDatum(types.ZeroDate),
|
|
encodeBytes([]byte(types.ZeroDate.String())),
|
|
true,
|
|
},
|
|
{
|
|
"newdate/min",
|
|
types.NewFieldType(mysql.TypeNewDate),
|
|
types.NewTimeDatum(types.NewTime(types.MinDatetime, mysql.TypeNewDate, 6)),
|
|
encodeBytes([]byte(types.NewTime(types.MinDatetime, mysql.TypeNewDate, 6).String())),
|
|
true,
|
|
},
|
|
{
|
|
"newdate/max",
|
|
types.NewFieldType(mysql.TypeNewDate),
|
|
types.NewTimeDatum(types.NewTime(types.MaxDatetime, mysql.TypeNewDate, 6)),
|
|
encodeBytes([]byte(types.NewTime(types.MaxDatetime, mysql.TypeNewDate, 6).String())),
|
|
true,
|
|
},
|
|
|
|
{"time", types.NewFieldType(mysql.TypeDuration), types.NewDurationDatum(dur), encodeBytes([]byte(dur.String())), true},
|
|
{"time/zero", types.NewFieldType(mysql.TypeDuration), types.NewDurationDatum(types.ZeroDuration), encodeBytes([]byte(types.ZeroDuration.String())), true},
|
|
{"time/max", types.NewFieldType(mysql.TypeDuration), types.NewDurationDatum(types.MaxMySQLDuration(3)), encodeBytes([]byte(types.MaxMySQLDuration(3).String())), true},
|
|
|
|
{"decimal/zero", types.NewFieldType(mysql.TypeNewDecimal), types.NewDecimalDatum(decZero), encodeBytes([]byte(decZero.String())), true},
|
|
{"decimal/pos", types.NewFieldType(mysql.TypeNewDecimal), types.NewDecimalDatum(decPos), encodeBytes([]byte(decPos.String())), true},
|
|
{"decimal/neg", types.NewFieldType(mysql.TypeNewDecimal), types.NewDecimalDatum(decNeg), encodeBytes([]byte(decNeg.String())), true},
|
|
{"decimal/min", types.NewFieldType(mysql.TypeNewDecimal), types.NewDecimalDatum(decMin), encodeBytes([]byte(decMin.String())), true},
|
|
{"decimal/max", types.NewFieldType(mysql.TypeNewDecimal), types.NewDecimalDatum(decMax), encodeBytes([]byte(decMax.String())), true},
|
|
|
|
{"json/1", types.NewFieldType(mysql.TypeJSON), types.NewJSONDatum(json1), encodeBytes([]byte(json1.String())), true},
|
|
{"json/2", types.NewFieldType(mysql.TypeJSON), types.NewJSONDatum(json2), encodeBytes([]byte(json2.String())), true},
|
|
{"json/3", types.NewFieldType(mysql.TypeJSON), types.NewJSONDatum(json3), encodeBytes([]byte(json3.String())), true},
|
|
} {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
col := rowcodec.ColData{&model.ColumnInfo{FieldType: *tt.typ}, &tt.dat}
|
|
raw, err := col.Encode(time.Local, buf[:0])
|
|
if tt.ok {
|
|
require.NoError(t, err)
|
|
if len(tt.raw) == 0 {
|
|
require.Len(t, raw, 0)
|
|
} else {
|
|
require.Equal(t, tt.raw, raw)
|
|
}
|
|
} else {
|
|
require.Error(t, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("nulldatum", func(t *testing.T) {
|
|
for _, typ := range []byte{
|
|
mysql.TypeUnspecified,
|
|
mysql.TypeTiny,
|
|
mysql.TypeShort,
|
|
mysql.TypeLong,
|
|
mysql.TypeFloat,
|
|
mysql.TypeDouble,
|
|
mysql.TypeNull,
|
|
mysql.TypeTimestamp,
|
|
mysql.TypeLonglong,
|
|
mysql.TypeInt24,
|
|
mysql.TypeDate,
|
|
mysql.TypeDuration,
|
|
mysql.TypeDatetime,
|
|
mysql.TypeYear,
|
|
mysql.TypeNewDate,
|
|
mysql.TypeVarchar,
|
|
mysql.TypeBit,
|
|
mysql.TypeJSON,
|
|
mysql.TypeNewDecimal,
|
|
mysql.TypeEnum,
|
|
mysql.TypeSet,
|
|
mysql.TypeTinyBlob,
|
|
mysql.TypeMediumBlob,
|
|
mysql.TypeLongBlob,
|
|
mysql.TypeBlob,
|
|
mysql.TypeVarString,
|
|
mysql.TypeString,
|
|
mysql.TypeGeometry,
|
|
42, // wrong type
|
|
} {
|
|
ft := types.NewFieldType(typ)
|
|
dat := types.NewDatum(nil)
|
|
col := rowcodec.ColData{&model.ColumnInfo{FieldType: *ft}, &dat}
|
|
raw, err := col.Encode(time.Local, nil)
|
|
require.NoError(t, err)
|
|
require.Len(t, raw, 0)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRowChecksum(t *testing.T) {
|
|
typ1 := types.NewFieldType(mysql.TypeNull)
|
|
dat1 := types.NewDatum(nil)
|
|
col1 := rowcodec.ColData{&model.ColumnInfo{ID: 1, FieldType: *typ1}, &dat1}
|
|
typ2 := types.NewFieldType(mysql.TypeLong)
|
|
dat2 := types.NewDatum(42)
|
|
col2 := rowcodec.ColData{&model.ColumnInfo{ID: 2, FieldType: *typ2}, &dat2}
|
|
typ3 := types.NewFieldType(mysql.TypeVarchar)
|
|
dat3 := types.NewDatum("foobar")
|
|
col3 := rowcodec.ColData{&model.ColumnInfo{ID: 3, FieldType: *typ3}, &dat3}
|
|
typ4 := types.NewFieldType(mysql.TypeTimestamp)
|
|
dat4 := types.NewTimeDatum(types.NewTime(types.FromGoTime(time.Now()), mysql.TypeTimestamp, 6))
|
|
col4 := rowcodec.ColData{&model.ColumnInfo{ID: 4, FieldType: *typ4}, &dat4}
|
|
buf := make([]byte, 0, 64)
|
|
for _, tt := range []struct {
|
|
name string
|
|
cols []rowcodec.ColData
|
|
}{
|
|
{"nil", nil},
|
|
{"empty", []rowcodec.ColData{}},
|
|
{"nullonly", []rowcodec.ColData{col1}},
|
|
{"ordered", []rowcodec.ColData{col1, col2, col3, col4}},
|
|
{"unordered", []rowcodec.ColData{col3, col1, col4, col2}},
|
|
} {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
row := rowcodec.RowData{tt.cols, buf}
|
|
if !sort.IsSorted(row) {
|
|
sort.Sort(row)
|
|
}
|
|
checksum, err := row.Checksum(time.Local)
|
|
require.NoError(t, err)
|
|
raw, err := row.Encode(time.Local)
|
|
require.NoError(t, err)
|
|
require.Equal(t, crc32.ChecksumIEEE(raw), checksum)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEncodeDecodeRowWithChecksum(t *testing.T) {
|
|
enc := rowcodec.Encoder{}
|
|
|
|
raw, err := enc.Encode(time.UTC, nil, nil, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
dec := rowcodec.NewDatumMapDecoder([]rowcodec.ColInfo{}, time.UTC)
|
|
_, err = dec.DecodeToDatumMap(raw, nil)
|
|
require.NoError(t, err)
|
|
|
|
checksum, ok := dec.GetChecksum()
|
|
require.False(t, ok)
|
|
require.Zero(t, checksum)
|
|
|
|
rawChecksum := rowcodec.RawChecksum{
|
|
Handle: kv.IntHandle(1),
|
|
}
|
|
raw, err = enc.Encode(time.UTC, nil, nil, rawChecksum, nil)
|
|
require.NoError(t, err)
|
|
|
|
expected, ok := enc.GetChecksum()
|
|
require.True(t, ok)
|
|
require.NotZero(t, expected)
|
|
|
|
_, err = dec.DecodeToDatumMap(raw, nil)
|
|
require.NoError(t, err)
|
|
|
|
checksum, ok = dec.GetChecksum()
|
|
require.True(t, ok)
|
|
require.Equal(t, expected, checksum)
|
|
|
|
version := dec.ChecksumVersion()
|
|
require.Equal(t, 2, version)
|
|
}
|
|
|
|
var (
|
|
withUnsigned = func(ft *types.FieldType) *types.FieldType {
|
|
ft.AddFlag(mysql.UnsignedFlag)
|
|
return ft
|
|
}
|
|
withEnumElems = func(elem ...string) func(ft *types.FieldType) *types.FieldType {
|
|
return func(ft *types.FieldType) *types.FieldType {
|
|
ft.SetElems(elem)
|
|
return ft
|
|
}
|
|
}
|
|
withFsp = func(fsp int) func(ft *types.FieldType) *types.FieldType {
|
|
return func(ft *types.FieldType) *types.FieldType {
|
|
ft.SetDecimal(fsp)
|
|
return ft
|
|
}
|
|
}
|
|
withFlen = func(flen int) func(ft *types.FieldType) *types.FieldType {
|
|
return func(ft *types.FieldType) *types.FieldType {
|
|
ft.SetFlen(flen)
|
|
return ft
|
|
}
|
|
}
|
|
getDuration = func(value string) types.Duration {
|
|
dur, _, _ := types.ParseDuration(types.DefaultStmtNoWarningContext, value, 0)
|
|
return dur
|
|
}
|
|
getOldDatumByte = func(d types.Datum) []byte {
|
|
b, err := tablecodec.EncodeValue(nil, nil, d)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return b
|
|
}
|
|
getDatumPoint = func(d types.Datum) *types.Datum {
|
|
return &d
|
|
}
|
|
withFrac = func(f int) func(d types.Datum) types.Datum {
|
|
return func(d types.Datum) types.Datum {
|
|
d.SetFrac(f)
|
|
return d
|
|
}
|
|
}
|
|
withLen = func(l int) func(d types.Datum) types.Datum {
|
|
return func(d types.Datum) types.Datum {
|
|
d.SetLength(l)
|
|
return d
|
|
}
|
|
}
|
|
)
|
|
|
|
func TestDecodeWithCommitTS(t *testing.T) {
|
|
cols := []rowcodec.ColInfo{
|
|
{
|
|
ID: 1,
|
|
Ft: types.NewFieldType(mysql.TypeString),
|
|
},
|
|
{
|
|
ID: model.ExtraCommitTSID,
|
|
Ft: types.NewFieldType(mysql.TypeLonglong),
|
|
},
|
|
{
|
|
ID: 2,
|
|
Ft: types.NewFieldType(mysql.TypeString),
|
|
},
|
|
}
|
|
cols[1].Ft.SetFlag(mysql.UnsignedFlag)
|
|
|
|
var encoder rowcodec.Encoder
|
|
newRow, err := encoder.Encode(time.UTC, []int64{1, 2}, []types.Datum{
|
|
types.NewStringDatum("test1"),
|
|
types.NewStringDatum("test2"),
|
|
}, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
decoder := rowcodec.NewChunkDecoder(cols, []int64{-1}, nil, time.UTC)
|
|
chk := chunk.New([]*types.FieldType{cols[0].Ft, cols[1].Ft, cols[2].Ft}, 1, 1)
|
|
err = decoder.DecodeToChunk(newRow, 123456, nil, chk)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 1, chk.NumRows())
|
|
row := chk.GetRow(0)
|
|
require.Equal(t, "test1", row.GetString(0))
|
|
require.Equal(t, uint64(123456), row.GetUint64(1))
|
|
require.Equal(t, "test2", row.GetString(2))
|
|
}
|