Skip to content

Commit

Permalink
Fix Int indexes to make them sortable. (#114)
Browse files Browse the repository at this point in the history
  • Loading branch information
jakedt authored Dec 15, 2021
1 parent 422653b commit fcf5d84
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 61 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/hashicorp/go-memdb

go 1.12
go 1.13

require (
github.com/hashicorp/go-immutable-radix v1.3.0
Expand Down
1 change: 0 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrh
github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
47 changes: 36 additions & 11 deletions index.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"encoding/hex"
"errors"
"fmt"
"math/bits"
"reflect"
"strconv"
"strings"
)

Expand Down Expand Up @@ -308,8 +308,7 @@ func (i *IntFieldIndex) FromObject(obj interface{}) (bool, []byte, error) {

// Get the value and encode it
val := fv.Int()
buf := make([]byte, size)
binary.PutVarint(buf, val)
buf := encodeInt(val, size)

return true, buf, nil
}
Expand All @@ -331,26 +330,50 @@ func (i *IntFieldIndex) FromArgs(args ...interface{}) ([]byte, error) {
}

val := v.Int()
buf := make([]byte, size)
binary.PutVarint(buf, val)
buf := encodeInt(val, size)

return buf, nil
}

func encodeInt(val int64, size int) []byte {
buf := make([]byte, size)

// This bit flips the sign bit on any sized signed twos-complement integer,
// which when truncated to a uint of the same size will bias the value such
// that the maximum negative int becomes 0, and the maximum positive int
// becomes the maximum positive uint.
scaled := val ^ int64(-1<<(size*8-1))

switch size {
case 1:
buf[0] = uint8(scaled)
case 2:
binary.BigEndian.PutUint16(buf, uint16(scaled))
case 4:
binary.BigEndian.PutUint32(buf, uint32(scaled))
case 8:
binary.BigEndian.PutUint64(buf, uint64(scaled))
default:
panic(fmt.Sprintf("unsupported int size parameter: %d", size))
}

return buf
}

// IsIntType returns whether the passed type is a type of int and the number
// of bytes needed to encode the type.
func IsIntType(k reflect.Kind) (size int, okay bool) {
switch k {
case reflect.Int:
return binary.MaxVarintLen64, true
return strconv.IntSize / 8, true
case reflect.Int8:
return 2, true
return 1, true
case reflect.Int16:
return binary.MaxVarintLen16, true
return 2, true
case reflect.Int32:
return binary.MaxVarintLen32, true
return 4, true
case reflect.Int64:
return binary.MaxVarintLen64, true
return 8, true
default:
return 0, false
}
Expand Down Expand Up @@ -420,6 +443,8 @@ func encodeUInt(val uint64, size int) []byte {
binary.BigEndian.PutUint32(buf, uint32(val))
case 8:
binary.BigEndian.PutUint64(buf, val)
default:
panic(fmt.Sprintf("unsupported uint size parameter: %d", size))
}

return buf
Expand All @@ -430,7 +455,7 @@ func encodeUInt(val uint64, size int) []byte {
func IsUintType(k reflect.Kind) (size int, okay bool) {
switch k {
case reflect.Uint:
return bits.UintSize / 8, true
return strconv.IntSize / 8, true
case reflect.Uint8:
return 1, true
case reflect.Uint16:
Expand Down
127 changes: 79 additions & 48 deletions index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,6 @@ func TestUUIDFieldIndex_PrefixFromArgs(t *testing.T) {
if !bytes.Equal(uuidBuf[:9], val) {
t.Fatalf("foo")
}

}

func BenchmarkUUIDFieldIndex_parseString(b *testing.B) {
Expand Down Expand Up @@ -609,16 +608,16 @@ func generateUUID() ([]byte, string) {
func TestIntFieldIndex_FromObject(t *testing.T) {
obj := testObj()

eint := make([]byte, 10)
eint8 := make([]byte, 2)
eint16 := make([]byte, 3)
eint32 := make([]byte, 5)
eint64 := make([]byte, 10)
binary.PutVarint(eint, int64(obj.Int))
binary.PutVarint(eint8, int64(obj.Int8))
binary.PutVarint(eint16, int64(obj.Int16))
binary.PutVarint(eint32, int64(obj.Int32))
binary.PutVarint(eint64, obj.Int64)
eint := make([]byte, 8)
eint8 := make([]byte, 1)
eint16 := make([]byte, 2)
eint32 := make([]byte, 4)
eint64 := make([]byte, 8)
binary.BigEndian.PutUint64(eint, 1<<63+1)
eint8[0] = 0
binary.BigEndian.PutUint16(eint16, 0)
binary.BigEndian.PutUint32(eint32, 0)
binary.BigEndian.PutUint64(eint64, 0)

cases := []struct {
Field string
Expand Down Expand Up @@ -659,7 +658,6 @@ func TestIntFieldIndex_FromObject(t *testing.T) {
t.Run(c.Field, func(t *testing.T) {
indexer := IntFieldIndex{c.Field}
ok, val, err := indexer.FromObject(obj)

if err != nil {
if ok {
t.Fatalf("okay and error")
Expand All @@ -679,7 +677,6 @@ func TestIntFieldIndex_FromObject(t *testing.T) {
if !bytes.Equal(val, c.Expected) {
t.Fatalf("bad: %#v %#v", val, c.Expected)
}

})
}
}
Expand All @@ -702,16 +699,16 @@ func TestIntFieldIndex_FromArgs(t *testing.T) {
}

obj := testObj()
eint := make([]byte, 10)
eint8 := make([]byte, 2)
eint16 := make([]byte, 3)
eint32 := make([]byte, 5)
eint64 := make([]byte, 10)
binary.PutVarint(eint, int64(obj.Int))
binary.PutVarint(eint8, int64(obj.Int8))
binary.PutVarint(eint16, int64(obj.Int16))
binary.PutVarint(eint32, int64(obj.Int32))
binary.PutVarint(eint64, obj.Int64)
eint := make([]byte, 8)
eint8 := make([]byte, 1)
eint16 := make([]byte, 2)
eint32 := make([]byte, 4)
eint64 := make([]byte, 8)
binary.BigEndian.PutUint64(eint, 1<<63+1)
eint8[0] = 0
binary.BigEndian.PutUint16(eint16, 0)
binary.BigEndian.PutUint32(eint32, 0)
binary.BigEndian.PutUint64(eint64, 0)

val, err := indexer.FromArgs(obj.Int)
if err != nil {
Expand Down Expand Up @@ -754,6 +751,46 @@ func TestIntFieldIndex_FromArgs(t *testing.T) {
}
}

func TestIntFieldIndexSortability(t *testing.T) {
testCases := []struct {
i8l int8
i8r int8
i16l int16
i16r int16
i32l int32
i32r int32
i64l int64
i64r int64
il int
ir int
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"},
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, "small neg eq"},
{-2, -1, -2, -1, -2, -1, -2, -1, -2, -1, -1, "small neg lt"},
{-1, -2, -1, -2, -1, -2, -1, -2, -1, -2, 1, "small neg gt"},
{-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, "neg vs pos"},
{-128, 127, -32768, 32767, -2147483648, 2147483647, -9223372036854775808, 9223372036854775807, -9223372036854775808, 9223372036854775807, -1, "max conditions"},
{100, 127, 1000, 2000, 1000000000, 2000000000, 10000000000, 20000000000, 1000000000, 2000000000, -1, "large lt"},
{100, 99, 1000, 999, 1000000000, 999999999, 10000000000, 9999999999, 1000000000, 999999999, 1, "large gt"},
{126, 127, 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, &IntFieldIndex{"Foo"}, tc.i8l, tc.i8r, tc.expected)
compareEncoded(t, &IntFieldIndex{"Foo"}, tc.i16l, tc.i16r, tc.expected)
compareEncoded(t, &IntFieldIndex{"Foo"}, tc.i32l, tc.i32r, tc.expected)
compareEncoded(t, &IntFieldIndex{"Foo"}, tc.i64l, tc.i64r, tc.expected)
compareEncoded(t, &IntFieldIndex{"Foo"}, tc.il, tc.ir, tc.expected)
})
}
}

func TestUintFieldIndex_FromObject(t *testing.T) {
obj := testObj()

Expand Down Expand Up @@ -807,7 +844,6 @@ func TestUintFieldIndex_FromObject(t *testing.T) {
t.Run(c.Field, func(t *testing.T) {
indexer := UintFieldIndex{c.Field}
ok, val, err := indexer.FromObject(obj)

if err != nil {
if ok {
t.Fatalf("okay and error")
Expand All @@ -827,7 +863,6 @@ func TestUintFieldIndex_FromObject(t *testing.T) {
if !bytes.Equal(val, c.Expected) {
t.Fatalf("bad: %#v %#v", val, c.Expected)
}

})
}
}
Expand Down Expand Up @@ -928,25 +963,23 @@ func TestUIntFieldIndexSortability(t *testing.T) {

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)
compareEncoded(t, &UintFieldIndex{"Foo"}, tc.u8l, tc.u8r, tc.expected)
compareEncoded(t, &UintFieldIndex{"Foo"}, tc.u16l, tc.u16r, tc.expected)
compareEncoded(t, &UintFieldIndex{"Foo"}, tc.u32l, tc.u32r, tc.expected)
compareEncoded(t, &UintFieldIndex{"Foo"}, tc.u64l, tc.u64r, tc.expected)
compareEncoded(t, &UintFieldIndex{"Foo"}, tc.ul, tc.ur, tc.expected)
})
}
}

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

func compareEncoded(t *testing.T, indexer Indexer, l interface{}, r interface{}, expected int) {
lBytes, err := indexer.FromArgs(l)
if err != nil {
t.Fatalf("unable to encode: %d", l)
t.Fatalf("unable to encode %d: %s", l, err)
}
rBytes, err := indexer.FromArgs(r)
if err != nil {
t.Fatalf("unable to encode: %d", r)
t.Fatalf("unable to encode %d: %s", r, err)
}

if bytes.Compare(lBytes, rBytes) != expected {
Expand Down Expand Up @@ -1113,21 +1146,19 @@ func TestFieldSetIndex_FromArgs(t *testing.T) {
}
}

var (
// A conditional that checks if TestObject.Bar == 42
conditional = func(obj interface{}) (bool, error) {
test, ok := obj.(*TestObject)
if !ok {
return false, fmt.Errorf("Expect only TestObj types")
}

if test.Bar != 42 {
return false, nil
}
// A conditional that checks if TestObject.Bar == 42
var conditional = func(obj interface{}) (bool, error) {
test, ok := obj.(*TestObject)
if !ok {
return false, fmt.Errorf("Expect only TestObj types")
}

return true, nil
if test.Bar != 42 {
return false, nil
}
)

return true, nil
}

func TestConditionalIndex_FromObject(t *testing.T) {
obj := testObj()
Expand Down

0 comments on commit fcf5d84

Please sign in to comment.