Files
chaisql/internal/encoding/helpers_test.go
2025-09-21 11:28:55 +05:30

296 lines
11 KiB
Go

package encoding_test
import (
"encoding/binary"
"fmt"
"math"
"strings"
"testing"
"github.com/chaisql/chai/internal/encoding"
"github.com/chaisql/chai/internal/environment"
"github.com/chaisql/chai/internal/testutil"
"github.com/chaisql/chai/internal/tree"
"github.com/chaisql/chai/internal/types"
"github.com/stretchr/testify/require"
)
func TestCompare(t *testing.T) {
tests := []struct {
k1, k2 string
cmp int
}{
// null
{`(null)`, `(null)`, 0},
// booleans
{`(true)`, `(true)`, 0},
{`(false)`, `(true)`, -1},
{`(true)`, `(false)`, 1},
{`(false)`, `(false)`, 0},
// ints
{`(1)`, `(1)`, 0},
{`(1)`, `(2)`, -1},
{`(2)`, `(1)`, 1},
{`(1000000000)`, `(1)`, 33},
{`(254)`, `(255)`, -1}, // 2x uint8
{`(255)`, `(254)`, 1}, // 2x uint8
{`(10000)`, `(10001)`, -1}, // 2x uint16
{`(10001)`, `(10000)`, 1}, // 2x uint16
{`(1000000)`, `(1000001)`, -1}, // 2x uint32
{`(1000001)`, `(1000000)`, 1}, // 2x uint32
{`(1000000000000000)`, `(1000000000000001)`, -1}, // 2x uint64
{`(1000000000000001)`, `(1000000000000000)`, 1}, // 2x uint64
{`(-126)`, `(-127)`, 1}, // 2x int8
{`(-127)`, `(-126)`, -1}, // 2x int8
{`(-10000)`, `(-10001)`, 1}, // 2x int16
{`(-10001)`, `(-10000)`, -1}, // 2x int16
{`(-1000000)`, `(-1000001)`, 1}, // 2x int32
{`(-1000001)`, `(-1000000)`, -1}, // 2x int32
{`(-1000000000000000)`, `(-1000000000000001)`, 1}, // 2x int64
{`(-1000000000000001)`, `(-1000000000000000)`, -1}, // 2x int64
{`(-1)`, `(1)`, -2}, // neg fixint < fixuint
{`(1)`, `(31)`, -30}, // neg fixint < fixuint
{`(-127)`, `(1)`, -34}, // int8 < fixuint
{`(-10000)`, `(1)`, -35}, // int16 < fixuint
{`(-1000000)`, `(1)`, -36}, // int32 < fixuint
{`(-1000000000000000)`, `(1)`, -37}, // int64 < fixuint
{`(-127)`, `(255)`, -65}, // int8 < uint8
{`(-60000)`, `(255)`, -67}, // int16 < uint8
{`(-1000000)`, `(255)`, -67}, // int32 < uint8
{`(-1000000000000000)`, `(255)`, -68}, // int64 < uint8
// floats
{`(1.0)`, `(1.0)`, 0},
{`(1.1)`, `(1.0)`, 1},
{`(1.0)`, `(1.1)`, -1},
{`(-1.0)`, `(-1.1)`, 1},
// doubles
{`(1e50)`, `(1e50)`, 0},
{`(1e51)`, `(1e50)`, 1},
{`(1e50)`, `(1e51)`, -1},
{`(-1e50)`, `(-1e51)`, 1},
// floats and doubles
{`(1.0)`, `(1e50)`, -1},
{`(1e50)`, `(1.0)`, 1},
// text
{`('a')`, `('a')`, 0},
{`('b')`, `('a')`, 1},
{`('a')`, `('b')`, -1},
{`('a')`, `('aa')`, -1},
{`('aaaa')`, `('aab')`, -1},
// bytea
{`('\xaa')`, `('\xaa')`, 0},
{`('\xab')`, `('\xaa')`, 1},
{`('\xaa')`, `('\xab')`, -1},
{`('\xaa')`, `('\xaaaa')`, -1},
// different types
{`(null)`, `(true)`, -4},
{`(true)`, `(1)`, -43},
{`(1)`, `(1.0)`, -41},
{`(1.0)`, `('a')`, -8},
{`('a')`, `('\x00')`, -5},
// consecutive values
{`(1, 2, 3)`, `(1, 2, 3)`, 0},
{`(1, 2, 3)`, `(1, 2, 4)`, -1},
{`(1, 2, 3)`, `(1, 2, 3, 4)`, -1},
// consecutive mixed values
{`(1, true, 3.4)`, `(1, true, 3.4)`, 0},
}
for _, test := range tests {
t.Run(fmt.Sprintf("Compare(%s, %s)", test.k1, test.k2), func(t *testing.T) {
a1, err := testutil.ParseExprList(t, test.k1).EvalAll(&environment.Environment{})
require.NoError(t, err)
k1 := mustNewKey(t, 0, 0, a1...)
a2, err := testutil.ParseExprList(t, test.k2).EvalAll(&environment.Environment{})
require.NoError(t, err)
k2 := mustNewKey(t, 0, 0, a2...)
require.Equal(t, test.cmp, encoding.Compare(k1, k2))
// compare abbreviated keys
// prepend namespace
kk1 := mustNewKey(t, 1, 0, a1...)
kk2 := mustNewKey(t, 1, 0, a2...)
cmp := int64(encoding.AbbreviatedKey(kk1) - encoding.AbbreviatedKey(kk2))
if test.cmp < 0 {
require.False(t, cmp > 0)
} else if test.cmp > 0 {
require.False(t, cmp < 0)
} else {
require.True(t, cmp == 0)
}
})
}
}
func TestCompareOrder(t *testing.T) {
tests := []struct {
k1, k2 string
cmp int
order []bool
}{
// null
{`(null)`, `(null)`, 0, []bool{false}},
{`(null)`, `(null)`, 0, []bool{true}},
{`(1)`, `(2)`, -1, []bool{false}},
{`(1)`, `(2)`, 1, []bool{true}},
{`(60)`, `(30)`, 2, []bool{false}},
{`(60)`, `(30)`, -2, []bool{true}},
{`(30, 'hello')`, `(30, 'bye')`, 1, []bool{true, false}},
{`(30, 'hello')`, `(30, 'bye')`, -1, []bool{true, true}},
}
for _, test := range tests {
t.Run(fmt.Sprintf("CompareOrder(%s, %s)", test.k1, test.k2), func(t *testing.T) {
a1, err := testutil.ParseExprList(t, test.k1).EvalAll(&environment.Environment{})
require.NoError(t, err)
order := tree.SortOrder(0)
for i := range a1 {
if test.order[i] {
order = order.SetDesc(i)
}
}
k1 := mustNewKey(t, 0, order, a1...)
a2, err := testutil.ParseExprList(t, test.k2).EvalAll(&environment.Environment{})
require.NoError(t, err)
k2 := mustNewKey(t, 0, order, a2...)
require.Equal(t, test.cmp, encoding.Compare(k1, k2))
// compare abbreviated keys
// prepend namespace
kk1 := mustNewKey(t, 1, order, a1...)
kk2 := mustNewKey(t, 1, order, a2...)
cmp := int64(encoding.AbbreviatedKey(kk1) - encoding.AbbreviatedKey(kk2))
if test.cmp < 0 {
require.False(t, cmp > 0)
} else if test.cmp > 0 {
require.False(t, cmp < 0)
} else {
require.True(t, cmp == 0)
}
})
}
}
func TestAbbreviatedKey(t *testing.T) {
i64 := int64(-5000000000)
i32 := int32(-60000000)
i16 := int16(-10000)
i8 := int8(-127)
tests := []struct {
k string
want uint64
}{
// namespace only
{`(1)`, 0b_0000000000000001_00000000_0000000000000000000000000000000000000000},
{`(400)`, 0b_0000000110010000_00000000_0000000000000000000000000000000000000000},
{`(1000000)`, 0b_1111111111111111_00000000_0000000000000000000000000000000000000000}, // > 1 << 16
// null
{`(1, null)`, 1<<48 | uint64(encoding.NullValue)<<40},
// bool
{`(1, false)`, 1<<48 | uint64(encoding.FalseValue)<<40},
{`(1, true)`, 1<<48 | uint64(encoding.TrueValue)<<40},
// int
{`(1, 1)`, 1<<48 | (uint64(encoding.IntSmallValue)+32+1)<<40}, // positive int -> small value
{`(1, -10)`, 1<<48 | (uint64(encoding.IntSmallValue)+32-10)<<40}, // negative int -> small value
{`(1, 31)`, 1<<48 | (uint64(encoding.IntSmallValue)+32+31)<<40}, // positive int -> small value
{`(1, 100)`, 1<<48 | uint64(encoding.Uint8Value)<<40 | 100}, // positive int -> uint8
{`(1, 128)`, 1<<48 | uint64(encoding.Uint8Value)<<40 | 128}, // positive int -> uint8
{`(1, 255)`, 1<<48 | uint64(encoding.Uint8Value)<<40 | 255}, // positive int -> uint8
{`(1, 256)`, 1<<48 | uint64(encoding.Uint16Value)<<40 | 256}, // positive int -> uint16
{`(1, 999)`, 1<<48 | uint64(encoding.Uint16Value)<<40 | 999}, // positive int -> uint16
{`(1, -5000000000)`, 1<<48 | uint64(encoding.Int64Value)<<40 | (uint64(i64)+math.MaxInt64+1)>>24}, // int64
{`(1, -60000000)`, 1<<48 | uint64(encoding.Int32Value)<<40 | (uint64(i32) + math.MaxInt32 + 1)}, // int32
{`(1, -10000)`, 1<<48 | uint64(encoding.Int16Value)<<40 | (uint64(i16) + math.MaxInt16 + 1)}, // int16
{`(1, -127)`, 1<<48 | uint64(encoding.Int8Value)<<40 | (uint64(i8) + math.MaxInt8 + 1)}, // int8
{`(1, 255)`, 1<<48 | uint64(encoding.Uint8Value)<<40 | 255}, // uint8
{`(1, 50000)`, 1<<48 | uint64(encoding.Uint16Value)<<40 | 50000}, // uint16
{`(1, 500000)`, 1<<48 | uint64(encoding.Uint32Value)<<40 | 500000}, // uint32
{`(1, 5000000000)`, 1<<48 | uint64(encoding.Uint64Value)<<40 | 5000000000>>24}, // uint64
// float / double
{`(1, 1.0)`, 1<<48 | uint64(encoding.Float64Value)<<40 | uint64(math.Float64bits(1)^(1<<63))>>24},
{`(1, -1.0)`, 1<<48 | uint64(encoding.Float64Value)<<40 | uint64(math.Float64bits(-1)^(1<<64-1))>>24},
{`(1, 1e50)`, 1<<48 | uint64(encoding.Float64Value)<<40 | uint64(math.Float64bits(1e50)^1<<63)>>24},
{`(1, -1e50)`, 1<<48 | uint64(encoding.Float64Value)<<40 | uint64(math.Float64bits(-1e50)^(1<<64-1))>>24},
// text
{`(1, 'abc')`, 1<<48 | uint64(encoding.TextValue)<<40 | uint64('a')<<32 | uint64('b')<<24 | uint64('c')<<16},
{`(1, 'abcdefghijkl')`, 1<<48 | uint64(encoding.TextValue)<<40 | uint64('a')<<32 | uint64('b')<<24 | uint64('c')<<16 | uint64('d')<<8 | uint64('e')},
{`(1, 'abcdefghijkl` + strings.Repeat("m", 100) + `')`, 1<<48 | uint64(encoding.TextValue)<<40 | uint64('a')<<32 | uint64('b')<<24 | uint64('c')<<16 | uint64('d')<<8 | uint64('e')},
{`(1, 'abcdefghijkl` + strings.Repeat("m", 10000) + `')`, 1<<48 | uint64(encoding.TextValue)<<40 | uint64('a')<<32 | uint64('b')<<24 | uint64('c')<<16 | uint64('d')<<8 | uint64('e')},
// bytea
{`(1, '\xab')`, 1<<48 | uint64(encoding.ByteaValue)<<40 | uint64(0xab)<<32},
{`(1, '\xabcdefabcdef')`, 1<<48 | uint64(encoding.ByteaValue)<<40 | uint64(0xab)<<32 | uint64(0xcd)<<24 | uint64(0xef)<<16 | uint64(0xab)<<8 | uint64(0xcd)},
{`(1, '\xabcdefabcdef` + strings.Repeat("c", 100) + `')`, 1<<48 | uint64(encoding.ByteaValue)<<40 | uint64(0xab)<<32 | uint64(0xcd)<<24 | uint64(0xef)<<16 | uint64(0xab)<<8 | uint64(0xcd)},
{`(1, '\xabcdefabcdef` + strings.Repeat("c", 1000) + `')`, 1<<48 | uint64(encoding.ByteaValue)<<40 | uint64(0xab)<<32 | uint64(0xcd)<<24 | uint64(0xef)<<16 | uint64(0xab)<<8 | uint64(0xcd)},
}
for _, test := range tests {
t.Run(fmt.Sprintf("AbbreviatedKey(%s)", test.k), func(t *testing.T) {
a, err := testutil.ParseExprList(t, test.k).EvalAll(&environment.Environment{})
require.NoError(t, err)
k := mustNewKey(t, 0, 0, a...)
require.Equal(t, test.want, encoding.AbbreviatedKey(k))
})
}
}
func TestSeparator(t *testing.T) {
tests := []struct {
k1, k2 string
}{
{`(1, 1)`, `(1, 2)`},
{`(1, 1)`, `(1, 3)`},
}
for _, test := range tests {
t.Run(fmt.Sprintf("Separator(%v, %v)", test.k1, test.k2), func(t *testing.T) {
v1, err := testutil.ParseExprList(t, test.k1).EvalAll(&environment.Environment{})
require.NoError(t, err)
v2, err := testutil.ParseExprList(t, test.k2).EvalAll(&environment.Environment{})
require.NoError(t, err)
k1 := mustNewKey(t, 0, 0, v1...)
k2 := mustNewKey(t, 0, 0, v2...)
sep := encoding.Separator(nil, k1, k2)
require.LessOrEqual(t, encoding.Compare(k1, sep), 0)
require.Less(t, encoding.Compare(sep, k2), 0)
})
}
}
func makeUvarint(n int) []byte {
var buf [10]byte
i := binary.PutUvarint(buf[:], uint64(n))
return buf[:i]
}
func mustNewKey(t testing.TB, namespace tree.Namespace, order tree.SortOrder, values ...types.Value) []byte {
k := tree.NewKey(values...)
b, err := k.Encode(namespace, order)
require.NoError(t, err)
return b
}