From ec128e34bb51ae4531f1870e7fb95321180529b5 Mon Sep 17 00:00:00 2001 From: Jake Moshenko Date: Tue, 9 Mar 2021 18:02:58 -0500 Subject: [PATCH] Binary representation of UInt indexes should sort properly. --- index.go | 34 ++++++++++++++----- index_test.go | 92 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 97 insertions(+), 29 deletions(-) diff --git a/index.go b/index.go index 41c392b..3b87d94 100644 --- a/index.go +++ b/index.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "errors" "fmt" + "math/bits" "reflect" "strings" ) @@ -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 } @@ -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 } diff --git a/index_test.go b/index_test.go index d21e170..56b9cd5 100644 --- a/index_test.go +++ b/index_test.go @@ -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 @@ -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 { @@ -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"}