commit 5348e19880454920c387b3c1191716898cc5e79a
parent b588301738ed0626da50abb85dcc6b916598676f
Author: lash <dev@holbrook.no>
Date: Sat, 2 Nov 2024 20:05:56 +0000
Add dumper db implementation for gdbm
Diffstat:
8 files changed, 231 insertions(+), 0 deletions(-)
diff --git a/db/db.go b/db/db.go
@@ -75,6 +75,7 @@ type Db interface {
SetLanguage(*lang.Language)
// Prefix returns the current active datatype prefix
Prefix() uint8
+ Dump(context.Context, []byte) (*Dumper, error)
}
type LookupKey struct {
diff --git a/db/dump.go b/db/dump.go
@@ -0,0 +1,42 @@
+package db
+
+import (
+ "context"
+)
+
+type DumperFunc func(ctx context.Context) ([]byte, []byte)
+
+type Dumper struct {
+ fn DumperFunc
+ k []byte
+ v []byte
+ nexted bool
+}
+
+func NewDumper(fn DumperFunc) *Dumper {
+ return &Dumper{
+ fn: fn,
+ }
+}
+
+func(d *Dumper) WithFirst(k []byte, v []byte) *Dumper {
+ if d.nexted {
+ panic("already started")
+ }
+ d.k = k
+ d.v = v
+ d.nexted = true
+ return d
+}
+
+func(d *Dumper) Next(ctx context.Context) ([]byte, []byte) {
+ d.nexted = true
+ k := d.k
+ v := d.v
+ if k == nil {
+ return nil, nil
+ }
+ d.k, d.v = d.fn(ctx)
+ logg.TraceCtxf(ctx, "next value is", "k", d.k, "v", d.v)
+ return k, v
+}
diff --git a/db/fs/dump.go b/db/fs/dump.go
@@ -0,0 +1,12 @@
+package fs
+
+import (
+ "context"
+ "errors"
+
+ "git.defalsify.org/vise.git/db"
+)
+
+func(fdb *fsDb) Dump(ctx context.Context, key []byte) (*db.Dumper, error) {
+ return nil, errors.New("unimplemented")
+}
diff --git a/db/gdbm/dump.go b/db/gdbm/dump.go
@@ -0,0 +1,84 @@
+package gdbm
+
+import (
+ "bytes"
+ "context"
+ "errors"
+
+ gdbm "github.com/graygnuorg/go-gdbm"
+
+ "git.defalsify.org/vise.git/db"
+)
+
+func(gdb *gdbmDb) Dump(ctx context.Context, key []byte) (*db.Dumper, error) {
+ gdb.it = gdb.conn.Iterator()
+ for true {
+ k, err := gdb.it()
+ if err != nil {
+ if errors.Is(err, gdbm.ErrItemNotFound) {
+ err = db.NewErrNotFound(key)
+ }
+ gdb.it = nil
+ return nil, err
+ }
+ logg.TraceCtxf(ctx, "dump trace", "k", k)
+ if !bytes.HasPrefix(k[1:], key) {
+ continue
+ }
+ v, err := gdb.Get(ctx, k[1:])
+ if err != nil {
+ gdb.it = nil
+ return nil, err
+ }
+ gdb.itBase = key
+ return db.NewDumper(gdb.dumpFunc).WithFirst(k, v), nil
+ }
+ gdb.it = nil
+ return nil, db.NewErrNotFound(key)
+}
+
+func(gdb *gdbmDb) dumpFunc(ctx context.Context) ([]byte, []byte) {
+ var k []byte
+ var match bool
+ var err error
+
+ for true {
+ k, err = gdb.it()
+ if err != nil {
+ gdb.it = nil
+ return nil, nil
+ }
+ if bytes.HasPrefix(k[1:], gdb.itBase) {
+ match = true
+ break
+ }
+ }
+ if !match {
+ gdb.it = nil
+ return nil, nil
+ }
+ v, err := gdb.Get(ctx, k[1:])
+ if err != nil {
+ return nil, nil
+ }
+ return k, v
+}
+
+//func(gdb *gdbmDb) After(ctx context.Context, keyPart []byte) ([]byte, []byte) {
+// if keyPart == nil {
+// gdb.it = gdb.conn.Iterator()
+// return nil, nil
+// }
+// k, err := gdb.it()
+// if err != nil {
+// if !errors.Is(err, gdbm.ErrItemNotFound) {
+// panic(err)
+// }
+// gdb.it = gdb.conn.Iterator()
+// }
+// v, err := gdb.Get(ctx, k)
+// if err != nil {
+// panic(err)
+// }
+// return k, v
+//}
diff --git a/db/gdbm/dump_test.go b/db/gdbm/dump_test.go
@@ -0,0 +1,65 @@
+package gdbm
+
+import (
+ "bytes"
+ "context"
+ "io/ioutil"
+ "testing"
+
+ "git.defalsify.org/vise.git/db"
+)
+
+func TestDumpGdbm(t *testing.T) {
+ ctx := context.Background()
+
+ store := NewGdbmDb()
+ f, err := ioutil.TempFile("", "vise-db-gdbm-*")
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = store.Connect(ctx, f.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ store.SetPrefix(db.DATATYPE_USERDATA)
+ err = store.Put(ctx, []byte("bar"), []byte("inky"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = store.Put(ctx, []byte("foobar"), []byte("pinky"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = store.Put(ctx, []byte("foobarbaz"), []byte("blinky"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = store.Put(ctx, []byte("xyzzy"), []byte("clyde"))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ o, err := store.Dump(ctx, []byte("foo"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ k, v := o.Next(ctx)
+ if !bytes.Equal(k, append([]byte{db.DATATYPE_USERDATA}, []byte("foobar")...)) {
+ t.Fatalf("expected key 'foobar', got %s", k)
+ }
+ if !bytes.Equal(v, []byte("pinky")) {
+ t.Fatalf("expected val 'pinky', got %s", v)
+ }
+ k, v = o.Next(ctx)
+ if !bytes.Equal(k, append([]byte{db.DATATYPE_USERDATA}, []byte("foobarbaz")...)) {
+ t.Fatalf("expected key 'foobarbaz', got %s", k)
+ }
+ if !bytes.Equal(v, []byte("blinky")) {
+ t.Fatalf("expected val 'blinky', got %s", v)
+ }
+ k, v = o.Next(ctx)
+ if k != nil {
+ t.Fatalf("expected nil, got %s", k)
+ }
+}
diff --git a/db/gdbm/gdbm.go b/db/gdbm/gdbm.go
@@ -17,6 +17,8 @@ type gdbmDb struct {
conn *gdbm.Database
readOnly bool
prefix uint8
+ it gdbm.DatabaseIterator
+ itBase []byte
}
// Creates a new gdbm backed Db implementation.
@@ -118,6 +120,7 @@ func(gdb *gdbmDb) Get(ctx context.Context, key []byte) ([]byte, error) {
return v, nil
}
v, err = gdb.conn.Fetch(lk.Default)
+ logg.TraceCtxf(ctx, "gdbm get", "key", key, "lk", lk.Default)
if err != nil {
if errors.Is(gdbm.ErrItemNotFound, err) {
return nil, db.NewErrNotFound(key)
diff --git a/db/mem/dump.go b/db/mem/dump.go
@@ -0,0 +1,12 @@
+package mem
+
+import (
+ "context"
+ "errors"
+
+ "git.defalsify.org/vise.git/db"
+)
+
+func(mdb *memDb) Dump(ctx context.Context, key []byte) (*db.Dumper, error) {
+ return nil, errors.New("unimplemented")
+}
diff --git a/db/postgres/dump.go b/db/postgres/dump.go
@@ -0,0 +1,12 @@
+package postgres
+
+import (
+ "context"
+ "errors"
+
+ "git.defalsify.org/vise.git/db"
+)
+
+func(pdb *pgDb) Dump(ctx context.Context, key []byte) (*db.Dumper, error) {
+ return nil, errors.New("unimplemented")
+}