tile38/internal/field/list_test.go
tidwall cf7f49fd9b Add field path queries for json and comparison operators
It's now possible to query a JSON field using a GJSON path.

   SET fleet truck1 FIELD props '{"speed":58,"name":"Andy"}' POINT 33 -112

You can then use the GJSON type path to return the objects that match the WHERE.

   SCAN fleet WHERE props.speed 50 inf
   SCAN fleet WHERE props.name Andy Andy

Included in this commit is support for '==', '<', '>', '<=', '>=', and '!='.
The previous queries could be written like:

    SCAN fleet WHERE props.speed > 50
    SCAN fleet WHERE props.name == Andy
2022-10-20 11:17:01 -07:00

233 lines
5.6 KiB
Go

package field
import (
"fmt"
"math/rand"
"testing"
"time"
"github.com/tidwall/assert"
"github.com/tidwall/btree"
)
func TestList(t *testing.T) {
var fields List
fields = fields.Set(Make("hello", "123"))
assert.Assert(fields.Len() == 1)
// println(fields.Weight())
// assert.Assert(fields.Weight() == 16)
fields = fields.Set(Make("jello", "456"))
assert.Assert(fields.Len() == 2)
// assert.Assert(fields.Weight() == 32)
value := fields.Get("jello")
assert.Assert(value.Value().Data() == "456")
assert.Assert(value.Value().JSON() == "456")
assert.Assert(value.Value().Num() == 456)
value = fields.Get("nello")
assert.Assert(value.Name() == "")
assert.Assert(value.Value().IsZero())
fields = fields.Set(Make("jello", "789"))
assert.Assert(fields.Len() == 2)
// assert.Assert(fields.Weight() == 32)
fields = fields.Set(Make("nello", "0"))
assert.Assert(fields.Len() == 2)
// assert.Assert(fields.Weight() == 32)
fields = fields.Set(Make("jello", "789"))
assert.Assert(fields.Len() == 2)
// assert.Assert(fields.Weight() == 32)
fields = fields.Set(Make("jello", "0"))
assert.Assert(fields.Len() == 1)
// assert.Assert(fields.Weight() == 16)
fields = fields.Set(Make("nello", "012"))
fields = fields.Set(Make("hello", "456"))
fields = fields.Set(Make("fello", "123"))
fields = fields.Set(Make("jello", "789"))
var names string
var datas string
var nums float64
fields.Scan(func(f Field) bool {
names += f.Name()
datas += f.Value().Data()
nums += f.Value().Num()
return true
})
assert.Assert(names == "fellohellojellonello")
assert.Assert(datas == "123456789012")
assert.Assert(nums == 1380)
names = ""
datas = ""
nums = 0
fields.Scan(func(f Field) bool {
names += f.Name()
datas += f.Value().Data()
nums += f.Value().Num()
return false
})
assert.Assert(names == "fello")
assert.Assert(datas == "123")
assert.Assert(nums == 123)
}
func randStr(n int) string {
b := make([]byte, n)
rand.Read(b)
for i := 0; i < n; i++ {
b[i] = 'a' + b[i]%26
}
return string(b)
}
func randVal(n int) string {
switch rand.Intn(10) {
case 0:
return "null"
case 1:
return "true"
case 2:
return "false"
case 3:
return `{"a":"` + randStr(n) + `"}`
case 4:
return `["` + randStr(n) + `"]`
case 5:
return `"` + randStr(n) + `"`
case 6:
return randStr(n)
default:
return fmt.Sprintf("%f", rand.Float64()*360)
}
}
func TestRandom(t *testing.T) {
seed := time.Now().UnixNano()
// seed = 1663607868546669000
rand.Seed(seed)
start := time.Now()
var total int
for time.Since(start) < time.Second*2 {
N := rand.Intn(500)
var org []Field
var tr btree.Map[string, Field]
var fields List
for i := 0; i < N; i++ {
name := randStr(rand.Intn(10))
value := randVal(rand.Intn(10))
field := Make(name, value)
org = append(org, field)
fields = fields.Set(field)
v := fields.Get(name)
// println(name, v.Value().Data(), field.Value().Data())
if !v.Value().Equals(field.Value()) {
t.Fatalf("seed: %d, expected true", seed)
}
tr.Set(name, field)
if fields.Len() != tr.Len() {
t.Fatalf("seed: %d, expected %d, got %d",
seed, tr.Len(), fields.Len())
}
}
comp := func() {
var all []Field
fields.Scan(func(f Field) bool {
all = append(all, f)
return true
})
if len(all) != fields.Len() {
t.Fatalf("seed: %d, expected %d, got %d",
seed, fields.Len(), len(all))
}
if fields.Len() != tr.Len() {
t.Fatalf("seed: %d, expected %d, got %d",
seed, tr.Len(), fields.Len())
}
var i int
tr.Scan(func(name string, f Field) bool {
if name != f.Name() || all[i].Name() != f.Name() {
t.Fatalf("seed: %d, out of order", seed)
}
i++
return true
})
}
comp()
rand.Shuffle(len(org), func(i, j int) {
org[i], org[j] = org[j], org[i]
})
for _, f := range org {
comp()
tr.Delete(f.Name())
fields = fields.Set(Make(f.Name(), "0"))
if fields.Len() != tr.Len() {
t.Fatalf("seed: %d, expected %d, got %d",
seed, tr.Len(), fields.Len())
}
comp()
}
total++
}
}
func TestJSONGet(t *testing.T) {
var list List
list = list.Set(Make("hello", "world"))
list = list.Set(Make("hello", `"world"`))
list = list.Set(Make("jello", "planet"))
list = list.Set(Make("telly", `{"a":[1,2,3],"b":null,"c":true,"d":false}`))
list = list.Set(Make("belly", `{"a":{"b":{"c":"fancy"}}}`))
json := list.String()
exp := `{"belly":{"a":{"b":{"c":"fancy"}}},"hello":"world","jello":` +
`"planet","telly":{"a":[1,2,3],"b":null,"c":true,"d":false}}`
if json != exp {
t.Fatalf("expected '%s', got '%s'", exp, json)
}
data := list.Get("hello").Value().Data()
if data != "world" {
t.Fatalf("expected '%s', got '%s'", "world", data)
}
data = list.Get("telly").Value().Data()
if data != `{"a":[1,2,3],"b":null,"c":true,"d":false}` {
t.Fatalf("expected '%s', got '%s'",
`{"a":[1,2,3],"b":null,"c":true,"d":false}`, data)
}
data = list.Get("belly").Value().Data()
if data != `{"a":{"b":{"c":"fancy"}}}` {
t.Fatalf("expected '%s', got '%s'",
`{"a":{"b":{"c":"fancy"}}}`, data)
}
data = list.Get("belly.a").Value().Data()
if data != `{"b":{"c":"fancy"}}` {
t.Fatalf("expected '%s', got '%s'",
`{"b":{"c":"fancy"}}`, data)
}
data = list.Get("belly.a.b").Value().Data()
if data != `{"c":"fancy"}` {
t.Fatalf("expected '%s', got '%s'",
`{"c":"fancy"}`, data)
}
data = list.Get("belly.a.b.c").Value().Data()
if data != `fancy` {
t.Fatalf("expected '%s', got '%s'",
`fancy`, data)
}
// Tile38 defaults non-existent fields to zero.
data = list.Get("belly.a.b.c.d").Value().Data()
if data != `0` {
t.Fatalf("expected '%s', got '%s'",
`0`, data)
}
}