From 99885da94fd0b19ff7c842ae814d27020ddde27a Mon Sep 17 00:00:00 2001 From: Bel LaPointe Date: Thu, 23 Jul 2020 19:49:31 -0600 Subject: [PATCH] Impl new driver even if i lose arr operations --- storage/driver/boltdb.go | 120 ++++++++++++++++++++++++++++- storage/driver/boltdb_test.go | 138 +++++++++++++++++++++++++++++++++- storage/driver/mon.go | 2 +- 3 files changed, 253 insertions(+), 7 deletions(-) diff --git a/storage/driver/boltdb.go b/storage/driver/boltdb.go index a78f436..23e5dfa 100644 --- a/storage/driver/boltdb.go +++ b/storage/driver/boltdb.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "local/dndex/config" + "local/dndex/storage/entity" "os" "regexp" @@ -13,6 +14,10 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) +var ( + errModifiedReserved = errors.New("cannot modify reserved field " + entity.Name) +) + type BoltDB struct { db *bolt.DB } @@ -74,12 +79,70 @@ func (bdb *BoltDB) Find(_ context.Context, namespace string, filter interface{}) return ch, err } -func (bdb *BoltDB) Update(context.Context, string, interface{}, interface{}) error { - return errors.New("not impl") +func (bdb *BoltDB) Update(ctx context.Context, namespace string, filter, operator interface{}) error { + b, err := bson.Marshal(filter) + if err != nil { + return err + } + m := bson.M{} + if err := bson.Unmarshal(b, &m); err != nil { + return err + } + return bdb.db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(namespace)) + if bucket == nil { + return nil + } + cursor := bucket.Cursor() + for k, v := cursor.First(); k != nil && v != nil; k, v = cursor.Next() { + n := bson.M{} + if err := bson.Unmarshal(v, &n); err != nil { + return err + } + if matches(n, m) { + n, err := apply(n, operator) + if err != nil { + return err + } + v, err := bson.Marshal(n) + if err != nil { + return err + } + if err := bucket.Put(k, v); err != nil { + return err + } + } + } + return nil + }) } -func (bdb *BoltDB) Insert(context.Context, string, interface{}) error { - return errors.New("not impl") +func (bdb *BoltDB) Insert(ctx context.Context, namespace string, doc interface{}) error { + b, err := bson.Marshal(doc) + if err != nil { + return err + } + m := bson.M{} + if err := bson.Unmarshal(b, &m); err != nil { + return err + } + if _, ok := m[entity.Name]; !ok { + return errors.New("primary key required to insert: did not find " + entity.Name) + } else if _, ok := m[entity.Name].(string); !ok { + return errors.New("primary key must be a string") + } + return bdb.db.Update(func(tx *bolt.Tx) error { + bucket, err := tx.CreateBucketIfNotExists([]byte(namespace)) + if err != nil { + return err + } + k := []byte(m[entity.Name].(string)) + v := bucket.Get(k) + if len(v) > 0 { + return errors.New("cannot insert: collision on primary key") + } + return bucket.Put(k, b) + }) } func (bdb *BoltDB) Delete(_ context.Context, namespace string, filter interface{}) error { @@ -170,3 +233,52 @@ func matches(doc, filter bson.M) bool { } return true } + +func apply(doc bson.M, operator interface{}) (bson.M, error) { + b, err := bson.Marshal(operator) + if err != nil { + return nil, err + } + op := bson.M{} + if err := bson.Unmarshal(b, &op); err != nil { + return nil, err + } + if _, ok := op[entity.Name]; ok { + return nil, errModifiedReserved + } + for k, v := range op { + operateBM, ok := v.(bson.M) + if !ok { + operateM, mok := v.(map[string]interface{}) + ok = mok + if ok { + operateBM = bson.M(operateM) + } + } + if !ok { + return nil, fmt.Errorf("invalid apply operand: %s:%T", k, v) + } + var op func(bson.M, bson.M) (bson.M, error) + switch k { + case "$set": + op = applySet + default: + return nil, errors.New("cannot apply operation " + k) + } + doc, err = op(doc, operateBM) + if err != nil { + return nil, err + } + } + return doc, nil +} + +func applySet(doc, operator bson.M) (bson.M, error) { + for k, v := range operator { + if k == entity.Name { + return nil, errModifiedReserved + } + doc[k] = v + } + return doc, nil +} diff --git a/storage/driver/boltdb_test.go b/storage/driver/boltdb_test.go index 1ba04ec..1d58b6d 100644 --- a/storage/driver/boltdb_test.go +++ b/storage/driver/boltdb_test.go @@ -2,6 +2,7 @@ package driver import ( "context" + "fmt" "io/ioutil" "local/dndex/storage/entity" "local/dndex/storage/operator" @@ -152,11 +153,144 @@ func TestBoltDBFind(t *testing.T) { } func TestBoltDBUpdate(t *testing.T) { - t.Fatal("not impl") + bdb, can := tempBoltDB(t) + defer can() + + ch, err := bdb.Find(context.TODO(), testNS, map[string]string{}) + if err != nil { + t.Fatal(err) + } + ones := make([]entity.One, testN) + i := 0 + for j := range ch { + var o entity.One + if err := bson.Unmarshal(j, &o); err != nil { + t.Fatal(err) + } + ones[i] = o + i++ + } + + if err := bdb.Update(context.TODO(), testNS, ones[0].Query(), operator.Set{Key: entity.Name, Value: "NEWNAME"}); err == nil { + t.Fatal(err) + } + + if err := bdb.Update(context.TODO(), testNS, ones[0].Query(), operator.Set{Key: entity.Title, Value: "NEWTITLE"}); err != nil { + t.Fatal(err) + } + + if n, err := bdb.count(context.TODO(), testNS, map[string]string{}); err != nil { + t.Fatal(err) + } else if n != testN { + t.Fatal(n) + } + + if n, err := bdb.count(context.TODO(), testNS, map[string]string{entity.Title: "NEWTITLE"}); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(n) + } + + ch, err = bdb.Find(context.TODO(), testNS, ones[0].Query()) + if err != nil { + t.Fatal(err) + } + i = 0 + for j := range ch { + i++ + o := entity.One{} + if err := bson.Unmarshal(j, &o); err != nil { + t.Fatal(err) + } + if fmt.Sprint(ones[0]) == fmt.Sprint(o) { + t.Fatal(ones[0], o) + } + ones[0].Title = "" + o.Title = "" + if fmt.Sprint(ones[0]) == fmt.Sprint(o) { + t.Fatal(ones[0], o) + } + ones[0].Modified = 0 + o.Modified = 0 + if fmt.Sprint(ones[0]) != fmt.Sprint(o) { + t.Fatalf("after removing fields that should differ, still not the same:\n%+v\n%+v", ones[0], o) + } + } + if i != 1 { + t.Fatal(i) + } } func TestBoltDBInsert(t *testing.T) { - t.Fatal("not impl") + bdb, can := tempBoltDB(t) + defer can() + + ch, err := bdb.Find(context.TODO(), testNS, map[string]string{}) + if err != nil { + t.Fatal(err) + } + ones := make([]entity.One, testN) + i := 0 + for j := range ch { + var o entity.One + if err := bson.Unmarshal(j, &o); err != nil { + t.Fatal(err) + } + ones[i] = o + i++ + } + + if err := bdb.Insert(context.TODO(), testNS, ones[0]); err == nil { + t.Fatal("could insert colliding object:", err) + } + + ones[0].Name = "NEWNAME" + if err := bdb.Insert(context.TODO(), testNS, ones[0]); err != nil { + t.Fatal("could not insert object with new Name:", err) + } + + if n, err := bdb.count(context.TODO(), testNS, ones[0].Query()); err != nil { + t.Fatal(err) + } else if n != 1 { + t.Fatal(err) + } + + ch, err = bdb.Find(context.TODO(), testNS, ones[0].Query()) + if err != nil { + t.Fatal(err) + } + for i := range ch { + o := entity.One{} + if err := bson.Unmarshal(i, &o); err != nil { + t.Fatal(err) + } + if fmt.Sprint(o) == fmt.Sprint(ones[0]) { + t.Fatal(o, ones[0]) + } + o.Modified = 0 + for k := range ones[0].Connections { + if _, ok := o.Connections[k]; !ok { + t.Fatalf("db had fewer connections than real: %s", k) + } + } + for k := range o.Connections { + if _, ok := ones[0].Connections[k]; !ok { + t.Fatalf("db had more connections than real: %s", k) + } + c := o.Connections[k] + c.Modified = 0 + o.Connections[k] = c + + c = ones[0].Connections[k] + c.Modified = 0 + ones[0].Connections[k] = c + } + o.Modified = 0 + ones[0].Modified = 0 + if fmt.Sprint(o) != fmt.Sprint(ones[0]) { + t.Fatalf("objects should match after removing modify:\n%+v\n%+v", o, ones[0]) + } + } } func TestBoltDBDelete(t *testing.T) { diff --git a/storage/driver/mon.go b/storage/driver/mon.go index b70a8e3..8548997 100644 --- a/storage/driver/mon.go +++ b/storage/driver/mon.go @@ -60,7 +60,7 @@ func (m Mongo) page(ctx context.Context, cursor *mongo.Cursor) chan bson.Raw { func (m Mongo) Update(ctx context.Context, namespace string, filter, apply interface{}) error { c := m.client.Database(m.db).Collection(namespace) - _, err := c.UpdateOne(ctx, filter, apply) + _, err := c.UpdateMany(ctx, filter, apply) return err }