Skip to content

Commit

Permalink
Experiment/implement basic querying (#1)
Browse files Browse the repository at this point in the history
* feat: define basic query primative with internal tests

* make filters be a defined type

* initial work on implementing basic filters

* implement half working multi filter system

* fix: ensure filters match correct field values

* docs: add comment for fix

* refactor: rename eval to predicate

* fix: out of order exclusion/removal of found entries

* fix: bug whereby a single filter auto excludes everything

* test: add more test cases

* optimise: ignore loading of entries for already excluded full structs

* refactor: move per entry item logic into own func

* refactor: simplify loading of item data into entry

* refactor: give target built struct index a better name

* benchmark: define single benchmark test

* benchmark: define new benchmark testing running query against hundreds of records

* feat: implement filter predicate to take multiple values to match against

* test: ensure single predicate with multi values works

* benchmark: alternate color which will match filter in stored values

* example: update simple example to remove query stuff

* feat: make owner UUID used for querying an optional param

* example: provide example for running simple single predicate query
  • Loading branch information
tauraamui authored Jul 26, 2023
1 parent fda46cf commit f80cf11
Show file tree
Hide file tree
Showing 11 changed files with 700 additions and 30 deletions.
21 changes: 21 additions & 0 deletions v2/entry.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kvs

import (
"bytes"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -201,6 +202,26 @@ func assignUint32(data uint32, dest any) error {
return errors.New("struct field ID is not of type uint32")
}

func CompareBytesToAny(a []byte, i interface{}) bool {
switch v := i.(type) {
case []byte:
return bytes.Equal(a, v)
case string:
return string(a) == v
default:
val := reflect.ValueOf(i)
if val.Kind() == reflect.Ptr {
val = reflect.Indirect(val)
}
newVal := reflect.New(val.Type())
err := json.Unmarshal(a, newVal.Interface())
if err != nil {
return false
}
return reflect.DeepEqual(newVal.Elem().Interface(), i)
}
}

func convertFromBytes(data []byte, i interface{}) error {
// Check that the destination argument is a pointer.
if reflect.TypeOf(i).Kind() != reflect.Ptr {
Expand Down
27 changes: 26 additions & 1 deletion v2/entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (

"github.com/google/uuid"
"github.com/matryer/is"
"github.com/tauraamui/kvs"
"github.com/tauraamui/kvs/v2"
)

func TestEntryStoreValuesInTable(t *testing.T) {
Expand Down Expand Up @@ -195,3 +195,28 @@ func TestSequences(t *testing.T) {
is.NoErr(err) // error occurred when aquiring next iter value
is.Equal(id, uint64(2))
}

func TestCompareStringWithBytes(t *testing.T) {
is := is.New(t)

input := []byte("hello")
is.True(kvs.CompareBytesToAny(input, "hello"))
}

func TestCompareBytesWithBytes(t *testing.T) {
is := is.New(t)

input := []byte("{\"A\":5,\"B\":\"hello\"}")
is.True(kvs.CompareBytesToAny(input, input))
}

func TestCompareBytesWithStruct(t *testing.T) {
is := is.New(t)

type TestStruct struct {
A int
B string
}
input := []byte("{\"A\":5,\"B\":\"hello\"}")
is.True(kvs.CompareBytesToAny(input, TestStruct{A: 5, B: "hello"}))
}
75 changes: 75 additions & 0 deletions v2/example/hierarchy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package main

import (
"fmt"

"github.com/google/uuid"
"github.com/tauraamui/kvs/v2"
"github.com/tauraamui/kvs/v2/storage"
)

type SmallChild struct {
ID uint32 `mdb:"ignore"`
UUID kvs.UUID
HungryMetric uint32
Norished bool
}

type Cake struct {
ID uint32 `mdb:"ignore"`
UUID kvs.UUID
Type string
Calories int
}

func (b Cake) TableName() string { return "cakes" }

type Candle struct {
Cake kvs.UUID
ID uint32 `mdb:"ignore"`
Lit bool
}

func (b Candle) TableName() string { return "candles" }

func hierarchy() {
db, err := kvs.NewMemKVDB()
if err != nil {
panic(err)
}
defer db.Close()

store := storage.New(db)
defer store.Close()

child := SmallChild{
UUID: uuid.New(),
HungryMetric: 100,
}

disguistingVeganCake := Cake{UUID: uuid.New(), Type: "INEDIBLE", Calories: -38}
healthyishCarrotCake := Cake{UUID: uuid.New(), Type: "CARROT", Calories: 280}
redVelvetCake := Cake{UUID: uuid.New(), Type: "RED_VELVET", Calories: 410}

store.Save(child.UUID, &disguistingVeganCake)
store.Save(child.UUID, &healthyishCarrotCake)
store.Save(child.UUID, &redVelvetCake)

store.Save(disguistingVeganCake.UUID, &Candle{Cake: disguistingVeganCake.UUID, Lit: true})
store.Save(healthyishCarrotCake.UUID, &Candle{Cake: healthyishCarrotCake.UUID, Lit: true})
store.Save(redVelvetCake.UUID, &Candle{Cake: redVelvetCake.UUID, Lit: true})

bs, err := storage.LoadAll[Cake](store, child.UUID)
for _, cake := range bs {
fmt.Printf("ROWID: %d, %+v\n", cake.ID, cake)

candles, err := storage.LoadAll[Cake](store, cake.UUID)
if err != nil {
panic(err)
}

for _, candle := range candles {
fmt.Printf("ROWID: %d, %+v\n", candle.ID, candle)
}
}
}
62 changes: 62 additions & 0 deletions v2/example/queries.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

import (
"fmt"

"github.com/google/uuid"
"github.com/tauraamui/kvs/v2"
"github.com/tauraamui/kvs/v2/query"
"github.com/tauraamui/kvs/v2/storage"
)

type HotAirBalloon struct {
ID uint32 `mdb:"ignore"`
UUID kvs.UUID
Flying bool
MaxCap int
}

func (b HotAirBalloon) TableName() string { return "hotairballoons" }

type Passenger struct {
ID uint32 `mdb:"ignore"`
FirstName string
Surname string
Married bool
Age int
}

func (p Passenger) TableName() string { return "passengers" }

func queries() {
db, err := kvs.NewMemKVDB()
if err != nil {
panic(err)
}
defer db.Close()

store := storage.New(db)
defer store.Close()

blimp := HotAirBalloon{
UUID: uuid.New(),
Flying: false,
MaxCap: 5,
}

store.Save(kvs.RootOwner{}, &blimp)
store.Save(blimp.UUID, &Passenger{FirstName: "Brian", Surname: "Hax", Age: 3})
store.Save(blimp.UUID, &Passenger{FirstName: "Amy", Surname: "Hax", Age: 26, Married: true})
store.Save(blimp.UUID, &Passenger{FirstName: "Mark", Surname: "West", Age: 58})
store.Save(blimp.UUID, &Passenger{FirstName: "Rory", Surname: "Hax", Age: 27, Married: true})

haxFamalam, err := query.Run[Passenger](store, blimp.UUID, query.New().Filter("surname").Eq("Hax"))
if err != nil {
panic(err)
}

for _, p := range haxFamalam {
fmt.Printf("ROWID: %d, %+v\n", p.ID, p)
}

}
36 changes: 36 additions & 0 deletions v2/example/simple.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"fmt"

"github.com/tauraamui/kvs/v2"
"github.com/tauraamui/kvs/v2/storage"
)

type Balloon struct {
ID uint32 `mdb:"ignore"`
Color string
Size int
}

func (b Balloon) TableName() string { return "balloons" }

func simple() {
db, err := kvs.NewMemKVDB()
if err != nil {
panic(err)
}
defer db.Close()

store := storage.New(db)
defer store.Close()

store.Save(kvs.RootOwner{}, &Balloon{Color: "RED", Size: 695})
store.Save(kvs.RootOwner{}, &Balloon{Color: "WHITE", Size: 366})

bs, err := storage.LoadAll[Balloon](store, kvs.RootOwner{})
for _, balloon := range bs {
fmt.Printf("ROWID: %d, %+v\n", balloon.ID, balloon)
}

}
71 changes: 71 additions & 0 deletions v2/query/benchmark_query_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package query_test

import (
"testing"

"github.com/tauraamui/kvs/v2"
"github.com/tauraamui/kvs/v2/query"
"github.com/tauraamui/kvs/v2/storage"
)

func BenchmarkQueryWithSingleFilterWithTwoRecords(b *testing.B) {
db, err := kvs.NewMemKVDB()
if err != nil {
b.Fatal(err)
}
defer db.Close()

store := storage.New(db)
defer store.Close()

store.Save(kvs.RootOwner{}, &Balloon{Color: "WHITE", Size: 366})
store.Save(kvs.RootOwner{}, &Balloon{Color: "RED", Size: 695})

b.ResetTimer()
for i := 0; i < b.N; i++ {
query.Run[Balloon](store, kvs.RootOwner{}, query.New().Filter("color").Eq("WHITE"))
}
}

func BenchmarkQueryWithMultiFilterWithTwoRecords(b *testing.B) {
db, err := kvs.NewMemKVDB()
if err != nil {
b.Fatal(err)
}
defer db.Close()

store := storage.New(db)
defer store.Close()

store.Save(kvs.RootOwner{}, &Balloon{Color: "WHITE", Size: 366})
store.Save(kvs.RootOwner{}, &Balloon{Color: "RED", Size: 695})

b.ResetTimer()
for i := 0; i < b.N; i++ {
query.Run[Balloon](store, kvs.RootOwner{}, query.New().Filter("color").Eq("WHITE").Filter("size").Eq(306))
}
}

func BenchmarkQueryWithMultiFilterWithFiveHunderedRecordsWithMatchingFilter(b *testing.B) {
db, err := kvs.NewMemKVDB()
if err != nil {
b.Fatal(err)
}
defer db.Close()

store := storage.New(db)
defer store.Close()

for i := 0; i < 500; i++ {
color := "RED"
if i%2 == 0 {
color = "BLUE"
}
store.Save(kvs.RootOwner{}, &Balloon{Color: color, Size: i})
}

b.ResetTimer()
for i := 0; i < b.N; i++ {
query.Run[Balloon](store, kvs.RootOwner{}, query.New().Filter("color").Eq("RED").Filter("size").Eq(306, 422, 211))
}
}
Loading

0 comments on commit f80cf11

Please sign in to comment.