Add key lookup support for tables in Go (#7644)

* Add support for key lookup for tables in Go

* Run clang format

* Run go fmt on tests

* Remove TODO in tests

* Update LookupByKey API

* Update LookupByKey API

* Don't use resolvePointer in expectEq

* Use generated getters instead of reading values directly from buffer

* Fix typo

Co-authored-by: Derek Bailey <derekbailey@google.com>
This commit is contained in:
Michael Le
2022-11-22 14:08:19 -08:00
committed by GitHub
parent 1cba8b2b49
commit 60975d6f7e
10 changed files with 365 additions and 7 deletions

View File

@@ -4,7 +4,6 @@ package Example
import (
"strconv"
flatbuffers "github.com/google/flatbuffers/go"
MyGame__Example2 "MyGame/Example2"

View File

@@ -4,7 +4,6 @@ package Example
import (
"strconv"
flatbuffers "github.com/google/flatbuffers/go"
)

View File

@@ -4,7 +4,6 @@ package Example
import (
"strconv"
flatbuffers "github.com/google/flatbuffers/go"
MyGame__Example2 "MyGame/Example2"

View File

@@ -3,8 +3,8 @@
package Example
import (
"bytes"
"math"
flatbuffers "github.com/google/flatbuffers/go"
MyGame "MyGame"
@@ -568,6 +568,38 @@ func (rcv *Monster) Name() []byte {
return nil
}
func MonsterKeyCompare(o1, o2 flatbuffers.UOffsetT, buf []byte) bool {
obj1 := &Monster{}
obj2 := &Monster{}
obj1.Init(buf, flatbuffers.UOffsetT(len(buf)) - o1)
obj2.Init(buf, flatbuffers.UOffsetT(len(buf)) - o2)
return string(obj1.Name()) < string(obj2.Name())
}
func (rcv *Monster) LookupByKey(key string, vectorLocation flatbuffers.UOffsetT, buf []byte) bool {
span := flatbuffers.GetUOffsetT(buf[vectorLocation - 4:])
start := flatbuffers.UOffsetT(0)
for span != 0 {
middle := span / 2
tableOffset := flatbuffers.GetIndirectOffset(buf, vectorLocation+ 4 * (start + middle))
obj := &Monster{}
obj.Init(buf, tableOffset)
bKey := []byte(key)
comp := bytes.Compare(obj.Name(), bKey)
if comp > 0 {
span = middle
} else if comp < 0 {
middle += 1
start += middle
span -= middle
} else {
rcv.Init(buf, tableOffset)
return true
}
}
return false
}
func (rcv *Monster) Inventory(j int) byte {
o := flatbuffers.UOffsetT(rcv._tab.Offset(14))
if o != 0 {
@@ -685,6 +717,15 @@ func (rcv *Monster) Testarrayoftables(obj *Monster, j int) bool {
return false
}
func (rcv *Monster) TestarrayoftablesByKey(obj *Monster, key string) bool{
o := flatbuffers.UOffsetT(rcv._tab.Offset(26))
if o != 0 {
x := rcv._tab.Vector(o)
return obj.LookupByKey(key, x, rcv._tab.Bytes)
}
return false
}
func (rcv *Monster) TestarrayoftablesLength() int {
o := flatbuffers.UOffsetT(rcv._tab.Offset(26))
if o != 0 {
@@ -1091,6 +1132,15 @@ func (rcv *Monster) VectorOfReferrables(obj *Referrable, j int) bool {
return false
}
func (rcv *Monster) VectorOfReferrablesByKey(obj *Referrable, key uint64) bool{
o := flatbuffers.UOffsetT(rcv._tab.Offset(74))
if o != 0 {
x := rcv._tab.Vector(o)
return obj.LookupByKey(key, x, rcv._tab.Bytes)
}
return false
}
func (rcv *Monster) VectorOfReferrablesLength() int {
o := flatbuffers.UOffsetT(rcv._tab.Offset(74))
if o != 0 {
@@ -1149,6 +1199,15 @@ func (rcv *Monster) VectorOfStrongReferrables(obj *Referrable, j int) bool {
return false
}
func (rcv *Monster) VectorOfStrongReferrablesByKey(obj *Referrable, key uint64) bool{
o := flatbuffers.UOffsetT(rcv._tab.Offset(80))
if o != 0 {
x := rcv._tab.Vector(o)
return obj.LookupByKey(key, x, rcv._tab.Bytes)
}
return false
}
func (rcv *Monster) VectorOfStrongReferrablesLength() int {
o := flatbuffers.UOffsetT(rcv._tab.Offset(80))
if o != 0 {
@@ -1367,6 +1426,15 @@ func (rcv *Monster) ScalarKeySortedTables(obj *Stat, j int) bool {
return false
}
func (rcv *Monster) ScalarKeySortedTablesByKey(obj *Stat, key uint16) bool{
o := flatbuffers.UOffsetT(rcv._tab.Offset(104))
if o != 0 {
x := rcv._tab.Vector(o)
return obj.LookupByKey(key, x, rcv._tab.Bytes)
}
return false
}
func (rcv *Monster) ScalarKeySortedTablesLength() int {
o := flatbuffers.UOffsetT(rcv._tab.Offset(104))
if o != 0 {

View File

@@ -67,6 +67,43 @@ func (rcv *Referrable) MutateId(n uint64) bool {
return rcv._tab.MutateUint64Slot(4, n)
}
func ReferrableKeyCompare(o1, o2 flatbuffers.UOffsetT, buf []byte) bool {
obj1 := &Referrable{}
obj2 := &Referrable{}
obj1.Init(buf, flatbuffers.UOffsetT(len(buf)) - o1)
obj2.Init(buf, flatbuffers.UOffsetT(len(buf)) - o2)
return obj1.Id() < obj2.Id()
}
func (rcv *Referrable) LookupByKey(key uint64, vectorLocation flatbuffers.UOffsetT, buf []byte) bool {
span := flatbuffers.GetUOffsetT(buf[vectorLocation - 4:])
start := flatbuffers.UOffsetT(0)
for span != 0 {
middle := span / 2
tableOffset := flatbuffers.GetIndirectOffset(buf, vectorLocation+ 4 * (start + middle))
obj := &Referrable{}
obj.Init(buf, tableOffset)
val := obj.Id()
comp := 0
if val > key {
comp = 1
} else if val < key {
comp = -1
}
if comp > 0 {
span = middle
} else if comp < 0 {
middle += 1
start += middle
span -= middle
} else {
rcv.Init(buf, tableOffset)
return true
}
}
return false
}
func ReferrableStart(builder *flatbuffers.Builder) {
builder.StartObject(1)
}

View File

@@ -94,6 +94,43 @@ func (rcv *Stat) MutateCount(n uint16) bool {
return rcv._tab.MutateUint16Slot(8, n)
}
func StatKeyCompare(o1, o2 flatbuffers.UOffsetT, buf []byte) bool {
obj1 := &Stat{}
obj2 := &Stat{}
obj1.Init(buf, flatbuffers.UOffsetT(len(buf)) - o1)
obj2.Init(buf, flatbuffers.UOffsetT(len(buf)) - o2)
return obj1.Count() < obj2.Count()
}
func (rcv *Stat) LookupByKey(key uint16, vectorLocation flatbuffers.UOffsetT, buf []byte) bool {
span := flatbuffers.GetUOffsetT(buf[vectorLocation - 4:])
start := flatbuffers.UOffsetT(0)
for span != 0 {
middle := span / 2
tableOffset := flatbuffers.GetIndirectOffset(buf, vectorLocation+ 4 * (start + middle))
obj := &Stat{}
obj.Init(buf, tableOffset)
val := obj.Count()
comp := 0
if val > key {
comp = 1
} else if val < key {
comp = -1
}
if comp > 0 {
span = middle
} else if comp < 0 {
middle += 1
start += middle
span -= middle
} else {
rcv.Init(buf, tableOffset)
return true
}
}
return false
}
func StatStart(builder *flatbuffers.Builder) {
builder.StartObject(3)
}

View File

@@ -186,9 +186,12 @@ func TestAll(t *testing.T) {
// Check size-prefixed flatbuffers
CheckSizePrefixedBuffer(t.Fatalf)
// Check that optional scalars work
// Check that optional scalars works
CheckOptionalScalars(t.Fatalf)
// Check that getting vector element by key works
CheckByKey(t.Fatalf)
// If the filename of the FlatBuffers file generated by the Java test
// is given, check that Go code can read it, and that Go code
// generates an identical buffer when used to create the example data:
@@ -2215,6 +2218,78 @@ func CheckOptionalScalars(fail func(string, ...interface{})) {
expectEq("defaultEnum", obj.DefaultEnum, optional_scalars.OptionalByteTwo)
}
func CheckByKey(fail func(string, ...interface{})) {
expectEq := func(what string, a, b interface{}) {
if a != b {
fail(FailString("Lookup by key: "+what, b, a))
}
}
b := flatbuffers.NewBuilder(0)
name := b.CreateString("Boss")
slime := &example.MonsterT{Name: "Slime"}
pig := &example.MonsterT{Name: "Pig"}
slimeBoss := &example.MonsterT{Name: "SlimeBoss"}
mushroom := &example.MonsterT{Name: "Mushroom"}
ironPig := &example.MonsterT{Name: "Iron Pig"}
monsterOffsets := make([]flatbuffers.UOffsetT, 5)
monsterOffsets[0] = slime.Pack(b)
monsterOffsets[1] = pig.Pack(b)
monsterOffsets[2] = slimeBoss.Pack(b)
monsterOffsets[3] = mushroom.Pack(b)
monsterOffsets[4] = ironPig.Pack(b)
testarrayoftables := b.CreateVectorOfSortedTables(monsterOffsets, example.MonsterKeyCompare)
str := &example.StatT{Id: "Strength", Count: 42}
luk := &example.StatT{Id: "Luck", Count: 51}
hp := &example.StatT{Id: "Health", Count: 12}
// Test default count value of 0
mp := &example.StatT{Id: "Mana"}
statOffsets := make([]flatbuffers.UOffsetT, 4)
statOffsets[0] = str.Pack(b)
statOffsets[1] = luk.Pack(b)
statOffsets[2] = hp.Pack(b)
statOffsets[3] = mp.Pack(b)
scalarKeySortedTablesOffset := b.CreateVectorOfSortedTables(statOffsets, example.StatKeyCompare)
example.MonsterStart(b)
example.MonsterAddName(b, name)
example.MonsterAddTestarrayoftables(b, testarrayoftables)
example.MonsterAddScalarKeySortedTables(b, scalarKeySortedTablesOffset)
moff := example.MonsterEnd(b)
b.Finish(moff)
monster := example.GetRootAsMonster(b.Bytes, b.Head())
slimeMon := &example.Monster{}
monster.TestarrayoftablesByKey(slimeMon, slime.Name)
mushroomMon := &example.Monster{}
monster.TestarrayoftablesByKey(mushroomMon, mushroom.Name)
slimeBossMon := &example.Monster{}
monster.TestarrayoftablesByKey(slimeBossMon, slimeBoss.Name)
strStat := &example.Stat{}
monster.ScalarKeySortedTablesByKey(strStat, str.Count)
lukStat := &example.Stat{}
monster.ScalarKeySortedTablesByKey(lukStat, luk.Count)
mpStat := &example.Stat{}
monster.ScalarKeySortedTablesByKey(mpStat, mp.Count)
expectEq("Boss name", string(monster.Name()), "Boss")
expectEq("Slime name", string(slimeMon.Name()), slime.Name)
expectEq("Mushroom name", string(mushroomMon.Name()), mushroom.Name)
expectEq("SlimeBoss name", string(slimeBossMon.Name()), slimeBoss.Name)
expectEq("Strength Id", string(strStat.Id()), str.Id)
expectEq("Strength Count", strStat.Count(), str.Count)
expectEq("Luck Id", string(lukStat.Id()), luk.Id)
expectEq("Luck Count", lukStat.Count(), luk.Count)
expectEq("Mana Id", string(mpStat.Id()), mp.Id)
// Use default count value as key
expectEq("Mana Count", mpStat.Count(), uint16(0))
}
// BenchmarkVtableDeduplication measures the speed of vtable deduplication
// by creating prePop vtables, then populating b.N objects with a
// different single vtable.