226 lines
5.2 KiB
Go
226 lines
5.2 KiB
Go
package kvstore_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"github.com/TeaOSLab/EdgeNode/internal/utils/kvstore"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func openIsolatedTable[T any](t *testing.T, tableName string, encoder kvstore.ValueEncoder[T]) *kvstore.Table[T] {
|
|
storeName := fmt.Sprintf("test-%d", time.Now().UnixNano())
|
|
|
|
store, err := kvstore.OpenStore(storeName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
_ = store.Close()
|
|
_ = kvstore.RemoveStore(store.Path())
|
|
})
|
|
|
|
db, err := store.NewDB("db1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
table, err := kvstore.NewTable[T](tableName, encoder)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
db.AddTable(table)
|
|
return table
|
|
}
|
|
|
|
func TestQuery_FieldKeyIsStableAcrossPaging(t *testing.T) {
|
|
table := openIsolatedTable[*testCachedItem](t, "cache_items", &testCacheItemEncoder[*testCachedItem]{})
|
|
|
|
err := table.AddFields("expiresAt")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for i, key := range []string{"a1", "a2", "a3"} {
|
|
err = table.Set(key, &testCachedItem{
|
|
Hash: key,
|
|
URL: "https://example.com/" + key,
|
|
ExpiresAt: int64(i + 1),
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
var firstFieldKey []byte
|
|
var firstFieldKeySnapshot []byte
|
|
var count int
|
|
|
|
err = table.Query().
|
|
FieldAsc("expiresAt").
|
|
Limit(2).
|
|
FindAll(func(tx *kvstore.Tx[*testCachedItem], item kvstore.Item[*testCachedItem]) (bool, error) {
|
|
switch count {
|
|
case 0:
|
|
firstFieldKey = item.FieldKey
|
|
firstFieldKeySnapshot = append([]byte(nil), item.FieldKey...)
|
|
case 1:
|
|
if !bytes.Equal(firstFieldKey, firstFieldKeySnapshot) {
|
|
t.Fatalf("field key mutated during iteration: got %q want %q", firstFieldKey, firstFieldKeySnapshot)
|
|
}
|
|
}
|
|
count++
|
|
return true, nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var keys []string
|
|
err = table.Query().
|
|
FieldAsc("expiresAt").
|
|
FieldOffset(firstFieldKey).
|
|
Limit(2).
|
|
FindAll(func(tx *kvstore.Tx[*testCachedItem], item kvstore.Item[*testCachedItem]) (bool, error) {
|
|
keys = append(keys, item.Key)
|
|
return true, nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(keys) != 2 || keys[0] != "a2" || keys[1] != "a3" {
|
|
t.Fatalf("unexpected paged keys: %v", keys)
|
|
}
|
|
}
|
|
|
|
func TestQuery_FindAll_StringPanicReturnsError(t *testing.T) {
|
|
table := openIsolatedTable[string](t, "users", kvstore.NewStringValueEncoder[string]())
|
|
|
|
err := table.Set("a1", "value-1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = table.Query().
|
|
Limit(1).
|
|
FindAll(func(tx *kvstore.Tx[string], item kvstore.Item[string]) (bool, error) {
|
|
panic("boom")
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "execute query failed: boom") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestQuery_ReusesFreshTableTransactionEachRun(t *testing.T) {
|
|
table := openIsolatedTable[string](t, "users", kvstore.NewStringValueEncoder[string]())
|
|
|
|
for _, key := range []string{"a1", "a2", "a3"} {
|
|
err := table.Set(key, "value-"+key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
query := table.Query().Limit(1)
|
|
for i := 0; i < 2; i++ {
|
|
var keys []string
|
|
err := query.FindAll(func(tx *kvstore.Tx[string], item kvstore.Item[string]) (bool, error) {
|
|
keys = append(keys, item.Key)
|
|
return true, nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(keys) != 1 || keys[0] != "a1" {
|
|
t.Fatalf("unexpected result on run %d: %v", i, keys)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestQuery_UsesExistingTxWithoutClosingIt(t *testing.T) {
|
|
table := openIsolatedTable[string](t, "users", kvstore.NewStringValueEncoder[string]())
|
|
|
|
for _, key := range []string{"a1", "a2", "a3"} {
|
|
err := table.Set(key, "value-"+key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
err := table.ReadTx(func(tx *kvstore.Tx[string]) error {
|
|
var keys []string
|
|
err := tx.Query().
|
|
Limit(2).
|
|
FindAll(func(queryTx *kvstore.Tx[string], item kvstore.Item[string]) (bool, error) {
|
|
if queryTx != tx {
|
|
return false, fmt.Errorf("query did not reuse the current tx")
|
|
}
|
|
keys = append(keys, item.Key)
|
|
return true, nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(keys) != 2 {
|
|
return fmt.Errorf("unexpected query size: %d", len(keys))
|
|
}
|
|
|
|
_, err = tx.Get("a1")
|
|
return err
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestDB_InspectProvidesStableBuffers(t *testing.T) {
|
|
table := openIsolatedTable[string](t, "users", kvstore.NewStringValueEncoder[string]())
|
|
|
|
for _, pair := range []struct {
|
|
key string
|
|
value string
|
|
}{
|
|
{key: "a1", value: "value-1"},
|
|
{key: "a2", value: "value-2"},
|
|
} {
|
|
err := table.Set(pair.key, pair.value)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
var firstKey []byte
|
|
var firstValue []byte
|
|
var firstKeySnapshot []byte
|
|
var firstValueSnapshot []byte
|
|
var count int
|
|
|
|
err := table.DB().Inspect(func(key []byte, value []byte) {
|
|
switch count {
|
|
case 0:
|
|
firstKey = key
|
|
firstValue = value
|
|
firstKeySnapshot = append([]byte(nil), key...)
|
|
firstValueSnapshot = append([]byte(nil), value...)
|
|
case 1:
|
|
if !bytes.Equal(firstKey, firstKeySnapshot) {
|
|
t.Fatalf("inspect key mutated after next iteration: got %q want %q", firstKey, firstKeySnapshot)
|
|
}
|
|
if !bytes.Equal(firstValue, firstValueSnapshot) {
|
|
t.Fatalf("inspect value mutated after next iteration: got %q want %q", firstValue, firstValueSnapshot)
|
|
}
|
|
}
|
|
count++
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|