// 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 chunk

import (
	"fmt"
	"math/rand"
	"testing"
	"time"
	"unsafe"

	"github.com/pingcap/tidb/pkg/parser/mysql"
	"github.com/pingcap/tidb/pkg/types"
	"github.com/stretchr/testify/require"
)

func TestColumnCopy(t *testing.T) {
	col := newFixedLenColumn(8, 10)
	for i := range 10 {
		col.AppendInt64(int64(i))
	}

	c1 := col.CopyConstruct(nil)
	require.Equal(t, col, c1)

	c2 := newFixedLenColumn(8, 10)
	c2 = col.CopyConstruct(c2)
	require.Equal(t, col, c2)
}

func TestColumnCopyReconstructFixedLen(t *testing.T) {
	col := NewColumn(types.NewFieldType(mysql.TypeLonglong), 1024)
	results := make([]int64, 0, 1024)
	nulls := make([]bool, 0, 1024)
	sel := make([]int, 0, 1024)
	for i := range 1024 {
		if rand.Intn(10) < 6 {
			sel = append(sel, i)
		}

		if rand.Intn(10) < 2 {
			col.AppendNull()
			nulls = append(nulls, true)
			results = append(results, 0)
			continue
		}

		v := rand.Int63()
		col.AppendInt64(v)
		results = append(results, v)
		nulls = append(nulls, false)
	}

	col = col.CopyReconstruct(sel, nil)
	nullCnt := 0
	for n, i := range sel {
		if nulls[i] {
			nullCnt++
			require.True(t, col.IsNull(n))
		} else {
			require.Equal(t, results[i], col.GetInt64(n))
		}
	}
	require.Equal(t, col.nullCount(), nullCnt)
	require.Len(t, sel, col.length)

	for i := range 128 {
		if i%2 == 0 {
			col.AppendNull()
		} else {
			col.AppendInt64(int64(i * i * i))
		}
	}

	require.Len(t, sel, col.length-128)
	require.Equal(t, nullCnt+128/2, col.nullCount())
	for i := range 128 {
		if i%2 == 0 {
			require.True(t, col.IsNull(len(sel)+i))
		} else {
			require.Equal(t, int64(i*i*i), col.GetInt64(len(sel)+i))
			require.False(t, col.IsNull(len(sel)+i))
		}
	}
}

func TestColumnCopyReconstructVarLen(t *testing.T) {
	col := NewColumn(types.NewFieldType(mysql.TypeVarString), 1024)
	results := make([]string, 0, 1024)
	nulls := make([]bool, 0, 1024)
	sel := make([]int, 0, 1024)
	for i := range 1024 {
		if rand.Intn(10) < 6 {
			sel = append(sel, i)
		}

		if rand.Intn(10) < 2 {
			col.AppendNull()
			nulls = append(nulls, true)
			results = append(results, "")
			continue
		}

		v := fmt.Sprintf("%v", rand.Int63())
		col.AppendString(v)
		results = append(results, v)
		nulls = append(nulls, false)
	}

	col = col.CopyReconstruct(sel, nil)
	nullCnt := 0
	for n, i := range sel {
		if nulls[i] {
			nullCnt++
			require.True(t, col.IsNull(n))
		} else {
			require.Equal(t, results[i], col.GetString(n))
		}
	}
	require.Equal(t, col.nullCount(), nullCnt)
	require.Len(t, sel, col.length)

	for i := range 128 {
		if i%2 == 0 {
			col.AppendNull()
		} else {
			col.AppendString(fmt.Sprintf("%v", i*i*i))
		}
	}

	require.Len(t, sel, col.length-128)
	require.Equal(t, nullCnt+128/2, col.nullCount())
	for i := range 128 {
		if i%2 == 0 {
			require.True(t, col.IsNull(len(sel)+i))
		} else {
			require.Equal(t, fmt.Sprintf("%v", i*i*i), col.GetString(len(sel)+i))
			require.False(t, col.IsNull(len(sel)+i))
		}
	}
}

func TestLargeStringColumnOffset(t *testing.T) {
	numRows := 1
	col := newVarLenColumn(numRows)
	// The max-length of a string field can be 6M, a typical batch size for Chunk is 1024, which is 1K.
	// That is to say, the memory offset of a string column can be 6GB, which exceeds int32
	col.offsets[0] = 6 << 30
	require.Equal(t, int64(6<<30), col.offsets[0]) // test no overflow.
}

func TestI64Column(t *testing.T) {
	chk := NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeLonglong)}, 1024)
	col := chk.Column(0)
	for i := range 1024 {
		col.AppendInt64(int64(i))
	}

	i64s := col.Int64s()
	for i := range 1024 {
		require.Equal(t, int64(i), i64s[i])
		i64s[i]++
	}

	it := NewIterator4Chunk(chk)
	var i int
	for row := it.Begin(); row != it.End(); row = it.Next() {
		require.Equal(t, int64(i+1), row.GetInt64(0))
		require.Equal(t, int64(i+1), col.GetInt64(i))
		i++
	}
}

func TestF64Column(t *testing.T) {
	chk := NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeDouble)}, 1024)
	col := chk.Column(0)
	for i := range 1024 {
		col.AppendFloat64(float64(i))
	}

	f64s := col.Float64s()
	for i := range 1024 {
		require.Equal(t, float64(i), f64s[i])
		f64s[i] /= 2
	}

	it := NewIterator4Chunk(chk)
	var i int64
	for row := it.Begin(); row != it.End(); row = it.Next() {
		require.Equal(t, float64(i)/2, row.GetFloat64(0))
		require.Equal(t, float64(i)/2, col.GetFloat64(int(i)))
		i++
	}
}

func TestF32Column(t *testing.T) {
	chk := NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeFloat)}, 1024)
	col := chk.Column(0)
	for i := range 1024 {
		col.AppendFloat32(float32(i))
	}

	f32s := col.Float32s()
	for i := range 1024 {
		require.Equal(t, float32(i), f32s[i])
		f32s[i] /= 2
	}

	it := NewIterator4Chunk(chk)
	var i int64
	for row := it.Begin(); row != it.End(); row = it.Next() {
		require.Equal(t, float32(i)/2, row.GetFloat32(0))
		require.Equal(t, float32(i)/2, col.GetFloat32(int(i)))
		i++
	}
}

func TestDurationSliceColumn(t *testing.T) {
	chk := NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeDuration)}, 1024)
	col := chk.Column(0)
	for i := range 1024 {
		col.AppendDuration(types.Duration{Duration: time.Duration(i)})
	}

	ds := col.GoDurations()
	for i := range 1024 {
		require.Equal(t, time.Duration(i), ds[i])
		d := types.Duration{Duration: ds[i]}
		d, _ = d.Add(d)
		ds[i] = d.Duration
	}

	it := NewIterator4Chunk(chk)
	var i int64
	for row := it.Begin(); row != it.End(); row = it.Next() {
		require.Equal(t, time.Duration(i)*2, row.GetDuration(0, 0).Duration)
		require.Equal(t, time.Duration(i)*2, col.GetDuration(int(i), 0).Duration)
		i++
	}
}

func TestMyDecimal(t *testing.T) {
	chk := NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeNewDecimal)}, 1024)
	col := chk.Column(0)
	for i := range 1024 {
		d := new(types.MyDecimal)
		err := d.FromFloat64(float64(i) * 1.1)
		require.NoError(t, err)
		col.AppendMyDecimal(d)
	}

	ds := col.Decimals()
	for i := range 1024 {
		d := new(types.MyDecimal)
		err := d.FromFloat64(float64(i) * 1.1)
		require.NoError(t, err)
		require.Zero(t, d.Compare(&ds[i]))

		types.DecimalAdd(&ds[i], d, &ds[i])
		require.NoError(t, err)
	}

	it := NewIterator4Chunk(chk)
	var i int64
	for row := it.Begin(); row != it.End(); row = it.Next() {
		d := new(types.MyDecimal)
		err := d.FromFloat64(float64(i) * 1.1 * 2)
		require.NoError(t, err)

		delta := new(types.MyDecimal)
		err = types.DecimalSub(d, row.GetMyDecimal(0), delta)
		require.NoError(t, err)

		fDelta, err := delta.ToFloat64()
		require.NoError(t, err)
		require.InDelta(t, 0, fDelta, 0.0001)

		i++
	}
}

func TestStringColumn(t *testing.T) {
	chk := NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeVarString)}, 1024)
	col := chk.Column(0)
	for i := range 1024 {
		col.AppendString(fmt.Sprintf("%v", i*i))
	}

	it := NewIterator4Chunk(chk)
	var i int
	for row := it.Begin(); row != it.End(); row = it.Next() {
		require.Equal(t, fmt.Sprintf("%v", i*i), row.GetString(0))
		require.Equal(t, fmt.Sprintf("%v", i*i), col.GetString(i))
		i++
	}
}

func TestSetColumn(t *testing.T) {
	chk := NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeSet)}, 1024)
	col := chk.Column(0)
	for i := range 1024 {
		col.AppendSet(types.Set{Name: fmt.Sprintf("%v", i), Value: uint64(i)})
	}

	it := NewIterator4Chunk(chk)
	var i int
	for row := it.Begin(); row != it.End(); row = it.Next() {
		s1 := col.GetSet(i)
		s2 := row.GetSet(0)
		require.Equal(t, s2.Name, s1.Name)
		require.Equal(t, s2.Value, s1.Value)
		require.Equal(t, fmt.Sprintf("%v", i), s1.Name)
		require.Equal(t, uint64(i), s1.Value)
		i++
	}
}

func TestJSONColumn(t *testing.T) {
	chk := NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeJSON)}, 1024)
	col := chk.Column(0)
	for i := range 1024 {
		j := new(types.BinaryJSON)
		err := j.UnmarshalJSON([]byte(fmt.Sprintf(`{"%v":%v}`, i, i)))
		require.NoError(t, err)
		col.AppendJSON(*j)
	}

	it := NewIterator4Chunk(chk)
	var i int
	for row := it.Begin(); row != it.End(); row = it.Next() {
		j1 := col.GetJSON(i)
		j2 := row.GetJSON(0)
		require.Equal(t, j2.String(), j1.String())
		i++
	}
}

func TestTimeColumn(t *testing.T) {
	chk := NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeDatetime)}, 1024)
	col := chk.Column(0)
	for range 1024 {
		col.AppendTime(types.CurrentTime(mysql.TypeDatetime))
		time.Sleep(time.Millisecond / 10)
	}

	it := NewIterator4Chunk(chk)
	ts := col.Times()
	var i int
	for row := it.Begin(); row != it.End(); row = it.Next() {
		j1 := col.GetTime(i)
		j2 := row.GetTime(0)
		j3 := ts[i]
		require.Zero(t, j1.Compare(j2))
		require.Zero(t, j1.Compare(j3))
		i++
	}
}

func TestDurationColumn(t *testing.T) {
	chk := NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeDuration)}, 1024)
	col := chk.Column(0)
	for i := range 1024 {
		col.AppendDuration(types.Duration{Duration: time.Second * time.Duration(i)})
	}

	it := NewIterator4Chunk(chk)
	var i int
	for row := it.Begin(); row != it.End(); row = it.Next() {
		j1 := col.GetDuration(i, 0)
		j2 := row.GetDuration(0, 0)
		require.Zero(t, j1.Compare(j2))
		i++
	}
}

func TestEnumColumn(t *testing.T) {
	chk := NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeEnum)}, 1024)
	col := chk.Column(0)
	for i := range 1024 {
		col.AppendEnum(types.Enum{Name: fmt.Sprintf("%v", i), Value: uint64(i)})
	}

	it := NewIterator4Chunk(chk)
	var i int
	for row := it.Begin(); row != it.End(); row = it.Next() {
		s1 := col.GetEnum(i)
		s2 := row.GetEnum(0)
		require.Equal(t, s2.Name, s1.Name)
		require.Equal(t, s2.Value, s1.Value)
		require.Equal(t, fmt.Sprintf("%v", i), s1.Name)
		require.Equal(t, uint64(i), s1.Value)
		i++
	}
}

func TestNullsColumn(t *testing.T) {
	chk := NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeLonglong)}, 1024)
	col := chk.Column(0)
	for i := range 1024 {
		if i%2 == 0 {
			col.AppendNull()
			continue
		}
		col.AppendInt64(int64(i))
	}

	it := NewIterator4Chunk(chk)
	var i int
	for row := it.Begin(); row != it.End(); row = it.Next() {
		if i%2 == 0 {
			require.True(t, row.IsNull(0))
			require.True(t, col.IsNull(i))
		} else {
			require.Equal(t, int64(i), row.GetInt64(0))
		}
		i++
	}
}

func TestReconstructFixedLen(t *testing.T) {
	col := NewColumn(types.NewFieldType(mysql.TypeLonglong), 1024)
	results := make([]int64, 0, 1024)
	nulls := make([]bool, 0, 1024)
	sel := make([]int, 0, 1024)
	for i := range 1024 {
		if rand.Intn(10) < 6 {
			sel = append(sel, i)
		}

		if rand.Intn(10) < 2 {
			col.AppendNull()
			nulls = append(nulls, true)
			results = append(results, 0)
			continue
		}

		v := rand.Int63()
		col.AppendInt64(v)
		results = append(results, v)
		nulls = append(nulls, false)
	}

	col.reconstruct(sel)
	nullCnt := 0
	for n, i := range sel {
		if nulls[i] {
			nullCnt++
			require.True(t, col.IsNull(n))
		} else {
			require.Equal(t, results[i], col.GetInt64(n))
		}
	}
	require.Equal(t, col.nullCount(), nullCnt)
	require.Len(t, sel, col.length)

	for i := range 128 {
		if i%2 == 0 {
			col.AppendNull()
		} else {
			col.AppendInt64(int64(i * i * i))
		}
	}

	require.Len(t, sel, col.length-128)
	require.Equal(t, nullCnt+128/2, col.nullCount())
	for i := range 128 {
		if i%2 == 0 {
			require.True(t, col.IsNull(len(sel)+i))
		} else {
			require.Equal(t, int64(i*i*i), col.GetInt64(len(sel)+i))
			require.False(t, col.IsNull(len(sel)+i))
		}
	}
}

func TestReconstructVarLen(t *testing.T) {
	col := NewColumn(types.NewFieldType(mysql.TypeVarString), 1024)
	results := make([]string, 0, 1024)
	nulls := make([]bool, 0, 1024)
	sel := make([]int, 0, 1024)
	for i := range 1024 {
		if rand.Intn(10) < 6 {
			sel = append(sel, i)
		}

		if rand.Intn(10) < 2 {
			col.AppendNull()
			nulls = append(nulls, true)
			results = append(results, "")
			continue
		}

		v := fmt.Sprintf("%v", rand.Int63())
		col.AppendString(v)
		results = append(results, v)
		nulls = append(nulls, false)
	}

	col.reconstruct(sel)
	nullCnt := 0
	for n, i := range sel {
		if nulls[i] {
			nullCnt++
			require.True(t, col.IsNull(n))
		} else {
			require.Equal(t, results[i], col.GetString(n))
		}
	}
	require.Equal(t, col.nullCount(), nullCnt)
	require.Len(t, sel, col.length)

	for i := range 128 {
		if i%2 == 0 {
			col.AppendNull()
		} else {
			col.AppendString(fmt.Sprintf("%v", i*i*i))
		}
	}

	require.Len(t, sel, col.length-128)
	require.Equal(t, nullCnt+128/2, col.nullCount())
	for i := range 128 {
		if i%2 == 0 {
			require.True(t, col.IsNull(len(sel)+i))
		} else {
			require.Equal(t, fmt.Sprintf("%v", i*i*i), col.GetString(len(sel)+i))
			require.False(t, col.IsNull(len(sel)+i))
		}
	}
}

func TestPreAllocInt64(t *testing.T) {
	col := NewColumn(types.NewFieldType(mysql.TypeLonglong), 128)
	col.ResizeInt64(256, true)
	i64s := col.Int64s()
	require.Equal(t, 256, len(i64s))
	for i := range 256 {
		require.True(t, col.IsNull(i))
	}
	col.AppendInt64(2333)
	require.False(t, col.IsNull(256))
	require.Equal(t, 257, len(col.Int64s()))
	require.Equal(t, int64(2333), col.Int64s()[256])
}

func TestPreAllocUint64(t *testing.T) {
	tll := types.NewFieldType(mysql.TypeLonglong)
	tll.AddFlag(mysql.UnsignedFlag)
	col := NewColumn(tll, 128)
	col.ResizeUint64(256, true)
	u64s := col.Uint64s()
	require.Equal(t, 256, len(u64s))
	for i := range 256 {
		require.True(t, col.IsNull(i))
	}
	col.AppendUint64(2333)
	require.False(t, col.IsNull(256))
	require.Equal(t, 257, len(col.Uint64s()))
	require.Equal(t, uint64(2333), col.Uint64s()[256])
}

func TestPreAllocFloat32(t *testing.T) {
	col := newFixedLenColumn(sizeFloat32, 128)
	col.ResizeFloat32(256, true)
	f32s := col.Float32s()
	require.Equal(t, 256, len(f32s))
	for i := range 256 {
		require.True(t, col.IsNull(i))
	}
	col.AppendFloat32(2333)
	require.False(t, col.IsNull(256))
	require.Equal(t, 257, len(col.Float32s()))
	require.Equal(t, float32(2333), col.Float32s()[256])
}

func TestPreAllocFloat64(t *testing.T) {
	col := newFixedLenColumn(sizeFloat64, 128)
	col.ResizeFloat64(256, true)
	f64s := col.Float64s()
	require.Equal(t, 256, len(f64s))
	for i := range 256 {
		require.True(t, col.IsNull(i))
	}
	col.AppendFloat64(2333)
	require.False(t, col.IsNull(256))
	require.Equal(t, 257, len(col.Float64s()))
	require.Equal(t, float64(2333), col.Float64s()[256])
}

func TestPreAllocDecimal(t *testing.T) {
	col := newFixedLenColumn(sizeMyDecimal, 128)
	col.ResizeDecimal(256, true)
	ds := col.Decimals()
	require.Equal(t, 256, len(ds))
	for i := range 256 {
		require.True(t, col.IsNull(i))
	}
	col.AppendMyDecimal(new(types.MyDecimal))
	require.False(t, col.IsNull(256))
	require.Equal(t, 257, len(col.Float64s()))
}

func TestPreAllocTime(t *testing.T) {
	col := newFixedLenColumn(sizeTime, 128)
	col.ResizeTime(256, true)
	ds := col.Times()
	require.Equal(t, 256, len(ds))
	for i := range 256 {
		require.True(t, col.IsNull(i))
	}
	col.AppendTime(types.ZeroDatetime)
	require.False(t, col.IsNull(256))
	require.Equal(t, 257, len(col.Times()))
}

func TestNull(t *testing.T) {
	col := newFixedLenColumn(sizeFloat64, 32)
	col.ResizeFloat64(1024, true)
	require.Equal(t, 1024, col.nullCount())

	notNulls := make(map[int]struct{})
	for range 512 {
		idx := rand.Intn(1024)
		notNulls[idx] = struct{}{}
		col.SetNull(idx, false)
	}

	require.Equal(t, 1024-len(notNulls), col.nullCount())
	for idx := range notNulls {
		require.False(t, col.IsNull(idx))
	}

	col.ResizeFloat64(8, true)
	col.SetNulls(0, 8, true)
	col.SetNull(7, false)
	require.Equal(t, 7, col.nullCount())

	col.ResizeFloat64(8, true)
	col.SetNulls(0, 8, true)
	require.Equal(t, 8, col.nullCount())

	col.ResizeFloat64(9, true)
	col.SetNulls(0, 9, true)
	col.SetNull(8, false)
	require.Equal(t, 8, col.nullCount())
}

func TestSetNulls(t *testing.T) {
	col := newFixedLenColumn(sizeFloat64, 32)
	col.ResizeFloat64(1024, true)
	require.Equal(t, 1024, col.nullCount())

	col.SetNulls(0, 1024, false)
	require.Zero(t, col.nullCount())

	nullMap := make(map[int]struct{})
	for range 100 {
		begin := rand.Intn(1024)
		l := rand.Intn(37)
		end := begin + l
		if end > 1024 {
			end = 1024
		}
		for i := begin; i < end; i++ {
			nullMap[i] = struct{}{}
		}
		col.SetNulls(begin, end, true)

		require.Len(t, nullMap, col.nullCount())
		for k := range nullMap {
			require.True(t, col.IsNull(k))
		}
	}
}

func TestResizeReserve(t *testing.T) {
	cI64s := newFixedLenColumn(sizeInt64, 0)
	require.Zero(t, cI64s.length)
	for range 100 {
		n := rand.Intn(1024)
		cI64s.ResizeInt64(n, true)
		require.Equal(t, n, cI64s.length)
		require.Equal(t, n, len(cI64s.Int64s()))
	}
	cI64s.ResizeInt64(0, true)
	require.Zero(t, cI64s.length)
	require.Zero(t, len(cI64s.Int64s()))

	cStrs := newVarLenColumn(0)
	for range 100 {
		n := rand.Intn(1024)
		cStrs.ReserveString(n)
		require.Zero(t, cStrs.length)
	}
	cStrs.ReserveString(0)
	require.Zero(t, cStrs.length)
}

func TestGetRaw(t *testing.T) {
	chk := NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeFloat)}, 1024)
	col := chk.Column(0)
	for i := range 1024 {
		col.AppendFloat32(float32(i))
	}
	it := NewIterator4Chunk(chk)
	var i int
	for row := it.Begin(); row != it.End(); row = it.Next() {
		f := float32(i)
		b := (*[unsafe.Sizeof(f)]byte)(unsafe.Pointer(&f))[:]
		require.Equal(t, b, row.GetRaw(0))
		require.Equal(t, b, col.GetRaw(i))
		i++
	}

	chk = NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeVarString)}, 1024)
	col = chk.Column(0)
	for i := range 1024 {
		col.AppendString(fmt.Sprint(i))
	}
	it = NewIterator4Chunk(chk)
	i = 0
	for row := it.Begin(); row != it.End(); row = it.Next() {
		require.Equal(t, []byte(fmt.Sprint(i)), row.GetRaw(0))
		require.Equal(t, []byte(fmt.Sprint(i)), col.GetRaw(i))
		i++
	}
}

func TestResize(t *testing.T) {
	col := NewColumn(types.NewFieldType(mysql.TypeLonglong), 1024)
	for i := range 1024 {
		col.AppendInt64(int64(i))
	}
	col.ResizeInt64(1024, false)
	for i := range 1024 {
		require.Equal(t, int64(0), col.Int64s()[i])
	}

	col = NewColumn(types.NewFieldType(mysql.TypeFloat), 1024)
	for i := range 1024 {
		col.AppendFloat32(float32(i))
	}
	col.ResizeFloat32(1024, false)
	for i := range 1024 {
		require.Equal(t, float32(0), col.Float32s()[i])
	}

	col = NewColumn(types.NewFieldType(mysql.TypeDouble), 1024)
	for i := range 1024 {
		col.AppendFloat64(float64(i))
	}
	col.ResizeFloat64(1024, false)
	for i := range 1024 {
		require.Equal(t, float64(0), col.Float64s()[i])
	}

	col = NewColumn(types.NewFieldType(mysql.TypeNewDecimal), 1024)
	for i := range 1024 {
		col.AppendMyDecimal(new(types.MyDecimal).FromInt(int64(i)))
	}
	col.ResizeDecimal(1024, false)
	for i := range 1024 {
		var d types.MyDecimal
		require.Equal(t, d, col.Decimals()[i])
	}

	col = NewColumn(types.NewFieldType(mysql.TypeDuration), 1024)
	for i := range 1024 {
		col.AppendDuration(types.Duration{Duration: time.Duration(i), Fsp: i})
	}
	col.ResizeGoDuration(1024, false)
	for i := range 1024 {
		require.Equal(t, time.Duration(0), col.GoDurations()[i])
	}

	col = NewColumn(types.NewFieldType(mysql.TypeDatetime), 1024)
	for range 1024 {
		gt := types.FromDate(rand.Intn(2200), rand.Intn(10)+1, rand.Intn(20)+1, rand.Intn(12), rand.Intn(60), rand.Intn(60), rand.Intn(1000000))
		t := types.NewTime(gt, 0, 0)
		col.AppendTime(t)
	}
	col.ResizeTime(1024, false)
	for i := range 1024 {
		var time types.Time
		require.Equal(t, time, col.Times()[i])
	}
}

func BenchmarkDurationRow(b *testing.B) {
	chk1 := NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeDuration)}, 1024)
	col1 := chk1.Column(0)
	for i := range 1024 {
		col1.AppendDuration(types.Duration{Duration: time.Second * time.Duration(i)})
	}
	chk2 := chk1.CopyConstruct()
	result := chk1.CopyConstruct()

	b.ResetTimer()
	for k := 0; k < b.N; k++ {
		result.Reset()
		it1 := NewIterator4Chunk(chk1)
		it2 := NewIterator4Chunk(chk2)
		for r1, r2 := it1.Begin(), it2.Begin(); r1 != it1.End() && r2 != it2.End(); r1, r2 = it1.Next(), it2.Next() {
			d1 := r1.GetDuration(0, 0)
			d2 := r2.GetDuration(0, 0)
			r, err := d1.Add(d2)
			if err != nil {
				b.Fatal(err)
			}
			result.AppendDuration(0, r)
		}
	}
}

func BenchmarkDurationVec(b *testing.B) {
	chk := NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeDuration)}, 1024)
	col1 := chk.Column(0)
	for i := range 1024 {
		col1.AppendDuration(types.Duration{Duration: time.Second * time.Duration(i)})
	}
	col2 := col1.CopyConstruct(nil)
	result := col1.CopyConstruct(nil)

	ds1 := col1.GoDurations()
	ds2 := col2.GoDurations()
	rs := result.GoDurations()

	b.ResetTimer()
	for k := 0; k < b.N; k++ {
		result.ResizeGoDuration(1024, true)
		for i := range 1024 {
			d1 := types.Duration{Duration: ds1[i]}
			d2 := types.Duration{Duration: ds2[i]}
			r, err := d1.Add(d2)
			if err != nil {
				b.Fatal(err)
			}
			rs[i] = r.Duration
		}
	}
}

func BenchmarkTimeRow(b *testing.B) {
	chk1 := NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeDate)}, 1024)
	col1 := chk1.Column(0)
	for range 1024 {
		col1.AppendTime(types.ZeroDate)
	}
	chk2 := chk1.CopyConstruct()
	result := chk1.CopyConstruct()

	b.ResetTimer()
	for k := 0; k < b.N; k++ {
		result.Reset()
		it1 := NewIterator4Chunk(chk1)
		it2 := NewIterator4Chunk(chk2)
		for r1, r2 := it1.Begin(), it2.Begin(); r1 != it1.End() && r2 != it2.End(); r1, r2 = it1.Next(), it2.Next() {
			d1 := r1.GetTime(0)
			d2 := r2.GetTime(0)
			if r := d1.Compare(d2); r > 0 {
				result.AppendTime(0, d1)
			} else {
				result.AppendTime(0, d2)
			}
		}
	}
}

func BenchmarkTimeVec(b *testing.B) {
	chk := NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeDate)}, 1024)
	col1 := chk.Column(0)
	for range 1024 {
		col1.AppendTime(types.ZeroDate)
	}
	col2 := col1.CopyConstruct(nil)
	result := col1.CopyConstruct(nil)

	ds1 := col1.Times()
	ds2 := col2.Times()
	rs := result.Times()

	b.ResetTimer()
	for k := 0; k < b.N; k++ {
		result.ResizeTime(1024, true)
		for i := range 1024 {
			if r := ds1[i].Compare(ds2[i]); r > 0 {
				rs[i] = ds1[i]
			} else {
				rs[i] = ds2[i]
			}
		}
	}
}

func genNullCols(n int) []*Column {
	cols := make([]*Column, n)
	for i := range cols {
		cols[i] = NewColumn(types.NewFieldType(mysql.TypeLonglong), 1024)
		cols[i].ResizeInt64(1024, false)
		for j := range 1024 {
			if rand.Intn(10) < 5 {
				cols[i].SetNull(j, true)
			}
		}
	}
	return cols
}

func TestVectorizedNulls(t *testing.T) {
	for range 256 {
		cols := genNullCols(4)
		lCol, rCol := cols[0], cols[1]
		vecResult, rowResult := cols[2], cols[3]
		vecResult.SetNulls(0, 1024, false)
		rowResult.SetNulls(0, 1024, false)
		vecResult.MergeNulls(lCol, rCol)
		for i := range 1024 {
			rowResult.SetNull(i, lCol.IsNull(i) || rCol.IsNull(i))
		}

		for i := range 1024 {
			require.Equal(t, vecResult.IsNull(i), rowResult.IsNull(i))
		}
	}
}

func TestResetColumn(t *testing.T) {
	col0 := NewColumn(types.NewFieldType(mysql.TypeVarString), 0)
	col1 := NewColumn(types.NewFieldType(mysql.TypeLonglong), 0)

	// using col0.reset() here will cause panic since it doesn't reset the elemBuf field which
	// is used by MergeNulls.
	col0.Reset(types.ETInt)
	col0.MergeNulls(col1)

	col := NewColumn(types.NewFieldType(mysql.TypeDatetime), 0)
	col.Reset(types.ETDuration)
	col.AppendDuration(types.Duration{})
	// using col.reset() above will let this assertion fail since the length of initialized elemBuf
	// is sizeTime.
	require.Equal(t, sizeGoDuration, len(col.data))
}

func BenchmarkMergeNullsVectorized(b *testing.B) {
	cols := genNullCols(3)
	b.ResetTimer()
	for range b.N {
		cols[0].MergeNulls(cols[1:]...)
	}
}

func BenchmarkMergeNullsNonVectorized(b *testing.B) {
	cols := genNullCols(3)
	b.ResetTimer()
	for range b.N {
		for i := range 1024 {
			cols[0].SetNull(i, cols[1].IsNull(i) || cols[2].IsNull(i))
		}
	}
}

func TestColumnResizeInt64(t *testing.T) {
	var col = NewColumn(types.NewFieldType(mysql.TypeLonglong), 2)
	col.AppendUint64(11)
	col.AppendUint64(11)

	col.ResizeInt64(4, false)
	require.Equal(t, col.nullBitmap, []byte{0b1111})
	col.AppendUint64(11)
	require.Equal(t, col.nullBitmap, []byte{0b11111})
	col.AppendNull()
	require.Equal(t, col.nullBitmap, []byte{0b011111})

	col.ResizeUint64(11, false)
	require.Equal(t, col.nullBitmap, []byte{0b11111111, 0b111})

	col.ResizeUint64(7, true)
	require.Equal(t, col.nullBitmap, []byte{0})

	col.AppendUint64(32)
	col.AppendUint64(32)
	require.Equal(t, col.nullBitmap, []byte{0b10000000, 0b1})
}
