Skip to content

Commit

Permalink
Binary representation of UInt indexes should sort properly. (#93)
Browse files Browse the repository at this point in the history
  • Loading branch information
jakedt authored Mar 12, 2021
1 parent 335f02e commit 542a580
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 29 deletions.
34 changes: 25 additions & 9 deletions index.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"math/bits"
"reflect"
"strings"
)
Expand Down Expand Up @@ -380,8 +381,7 @@ func (u *UintFieldIndex) FromObject(obj interface{}) (bool, []byte, error) {

// Get the value and encode it
val := fv.Uint()
buf := make([]byte, size)
binary.PutUvarint(buf, val)
buf := encodeUInt(val, size)

return true, buf, nil
}
Expand All @@ -403,26 +403,42 @@ func (u *UintFieldIndex) FromArgs(args ...interface{}) ([]byte, error) {
}

val := v.Uint()
buf := make([]byte, size)
binary.PutUvarint(buf, val)
buf := encodeUInt(val, size)

return buf, nil
}

func encodeUInt(val uint64, size int) []byte {
buf := make([]byte, size)

switch size {
case 1:
buf[0] = uint8(val)
case 2:
binary.BigEndian.PutUint16(buf, uint16(val))
case 4:
binary.BigEndian.PutUint32(buf, uint32(val))
case 8:
binary.BigEndian.PutUint64(buf, val)
}

return buf
}

// IsUintType returns whether the passed type is a type of uint and the number
// of bytes needed to encode the type.
func IsUintType(k reflect.Kind) (size int, okay bool) {
switch k {
case reflect.Uint:
return binary.MaxVarintLen64, true
return bits.UintSize / 8, true
case reflect.Uint8:
return 2, true
return 1, true
case reflect.Uint16:
return binary.MaxVarintLen16, true
return 2, true
case reflect.Uint32:
return binary.MaxVarintLen32, true
return 4, true
case reflect.Uint64:
return binary.MaxVarintLen64, true
return 8, true
default:
return 0, false
}
Expand Down
92 changes: 72 additions & 20 deletions index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -753,16 +753,16 @@ func TestIntFieldIndex_FromArgs(t *testing.T) {
func TestUintFieldIndex_FromObject(t *testing.T) {
obj := testObj()

euint := make([]byte, 10)
euint8 := make([]byte, 2)
euint16 := make([]byte, 3)
euint32 := make([]byte, 5)
euint64 := make([]byte, 10)
binary.PutUvarint(euint, uint64(obj.Uint))
binary.PutUvarint(euint8, uint64(obj.Uint8))
binary.PutUvarint(euint16, uint64(obj.Uint16))
binary.PutUvarint(euint32, uint64(obj.Uint32))
binary.PutUvarint(euint64, obj.Uint64)
euint := make([]byte, 8)
euint8 := make([]byte, 1)
euint16 := make([]byte, 2)
euint32 := make([]byte, 4)
euint64 := make([]byte, 8)
binary.BigEndian.PutUint64(euint, uint64(obj.Uint))
euint8[0] = obj.Uint8
binary.BigEndian.PutUint16(euint16, obj.Uint16)
binary.BigEndian.PutUint32(euint32, obj.Uint32)
binary.BigEndian.PutUint64(euint64, obj.Uint64)

cases := []struct {
Field string
Expand Down Expand Up @@ -846,16 +846,16 @@ func TestUintFieldIndex_FromArgs(t *testing.T) {
}

obj := testObj()
euint := make([]byte, 10)
euint8 := make([]byte, 2)
euint16 := make([]byte, 3)
euint32 := make([]byte, 5)
euint64 := make([]byte, 10)
binary.PutUvarint(euint, uint64(obj.Uint))
binary.PutUvarint(euint8, uint64(obj.Uint8))
binary.PutUvarint(euint16, uint64(obj.Uint16))
binary.PutUvarint(euint32, uint64(obj.Uint32))
binary.PutUvarint(euint64, obj.Uint64)
euint := make([]byte, 8)
euint8 := make([]byte, 1)
euint16 := make([]byte, 2)
euint32 := make([]byte, 4)
euint64 := make([]byte, 8)
binary.BigEndian.PutUint64(euint, uint64(obj.Uint))
euint8[0] = obj.Uint8
binary.BigEndian.PutUint16(euint16, obj.Uint16)
binary.BigEndian.PutUint32(euint32, obj.Uint32)
binary.BigEndian.PutUint64(euint64, obj.Uint64)

val, err := indexer.FromArgs(obj.Uint)
if err != nil {
Expand Down Expand Up @@ -898,6 +898,58 @@ func TestUintFieldIndex_FromArgs(t *testing.T) {
}
}

func TestUIntFieldIndexSortability(t *testing.T) {
testCases := []struct {
u8l uint8
u8r uint8
u16l uint16
u16r uint16
u32l uint32
u32r uint32
u64l uint64
u64r uint64
ul uint
ur uint
expected int
name string
}{
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "zero"},
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, "small eq"},
{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, -1, "small lt"},
{2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, "small gt"},
{100, 200, 1000, 2000, 1000000000, 2000000000, 10000000000, 20000000000, 1000000000, 2000000000, -1, "large lt"},
{100, 99, 1000, 999, 1000000000, 999999999, 10000000000, 9999999999, 1000000000, 999999999, 1, "large gt"},
{127, 128, 255, 256, 65535, 65536, 4294967295, 4294967296, 65535, 65536, -1, "edge conditions"},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
compareEncoded(t, tc.u8l, tc.u8r, tc.expected)
compareEncoded(t, tc.u16l, tc.u16r, tc.expected)
compareEncoded(t, tc.u32l, tc.u32r, tc.expected)
compareEncoded(t, tc.u64l, tc.u64r, tc.expected)
compareEncoded(t, tc.ul, tc.ur, tc.expected)
})
}
}

func compareEncoded(t *testing.T, l interface{}, r interface{}, expected int) {
indexer := UintFieldIndex{"Foo"}

lBytes, err := indexer.FromArgs(l)
if err != nil {
t.Fatalf("unable to encode: %d", l)
}
rBytes, err := indexer.FromArgs(r)
if err != nil {
t.Fatalf("unable to encode: %d", r)
}

if bytes.Compare(lBytes, rBytes) != expected {
t.Fatalf("Compare(%#v, %#v) != %d", lBytes, rBytes, expected)
}
}

func TestBoolFieldIndex_FromObject(t *testing.T) {
obj := testObj()
indexer := BoolFieldIndex{Field: "Bool"}
Expand Down

0 comments on commit 542a580

Please sign in to comment.