From eb62515a0514768509135ca52a22609b6d1d3e0e Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Fri, 27 Apr 2018 00:40:36 +0100 Subject: [PATCH] Ensure that fields are not removed when set to NULL --- db/merge.go | 15 +++++++++----- db/perms.go | 2 +- db/yield.go | 2 +- util/data/data.go | 19 ++++++++++-------- util/data/data_test.go | 45 +++++++++++++++++++++++++----------------- 5 files changed, 50 insertions(+), 33 deletions(-) diff --git a/db/merge.go b/db/merge.go index 0712d0fd..ba8ba6d8 100644 --- a/db/merge.go +++ b/db/merge.go @@ -108,7 +108,7 @@ func (d *document) delFld(ctx context.Context, met method) (err error) { // Loop over the allowed keys for _, fd := range fds { - d.current.Walk(func(key string, val interface{}) (err error) { + d.current.Walk(func(key string, val interface{}, ok bool) (err error) { keys[key] = struct{}{} return }, fd.Name.ID) @@ -291,9 +291,7 @@ func (d *document) mrgFld(ctx context.Context, met method) (err error) { for _, fd := range fds { - err = d.current.Walk(func(key string, val interface{}) error { - - vars := data.New() + err = d.current.Walk(func(key string, val interface{}, exi bool) error { var old = d.initial.Get(key).Data() @@ -313,6 +311,7 @@ func (d *document) mrgFld(ctx context.Context, met method) (err error) { // Reset the variables + vars := data.New() vars.Set(val, varKeyValue) vars.Set(val, varKeyAfter) vars.Set(old, varKeyBefore) @@ -332,6 +331,7 @@ func (d *document) mrgFld(ctx context.Context, met method) (err error) { // Reset the variables + vars := data.New() vars.Set(val, varKeyValue) vars.Set(val, varKeyAfter) vars.Set(old, varKeyBefore) @@ -354,6 +354,7 @@ func (d *document) mrgFld(ctx context.Context, met method) (err error) { // Reset the variables + vars := data.New() vars.Set(val, varKeyValue) vars.Set(val, varKeyAfter) vars.Set(old, varKeyBefore) @@ -398,7 +399,11 @@ func (d *document) mrgFld(ctx context.Context, met method) (err error) { switch val.(type) { default: - d.current.Iff(val, key) + if exi { + d.current.Set(val, key) + } else { + d.current.Iff(val, key) + } case *sql.Void: d.current.Del(key) } diff --git a/db/perms.go b/db/perms.go index 4e6ed3b6..f5d6322a 100644 --- a/db/perms.go +++ b/db/perms.go @@ -51,7 +51,7 @@ func (d *document) perms(ctx context.Context, doc *data.Doc) (err error) { if fd.Perms != nil { - err = doc.Walk(func(key string, val interface{}) error { + err = doc.Walk(func(key string, val interface{}, exi bool) error { // We are checking the permissions of the field diff --git a/db/yield.go b/db/yield.go index 76f80b6c..1a250b5e 100644 --- a/db/yield.go +++ b/db/yield.go @@ -242,7 +242,7 @@ func (d *document) yield(ctx context.Context, stm sql.Statement, output sql.Toke break case *sql.Ident: - out.Walk(func(key string, val interface{}) error { + out.Walk(func(key string, val interface{}, exi bool) error { switch res := val.(type) { case []interface{}: diff --git a/util/data/data.go b/util/data/data.go index b3ae6886..6acbcfdb 100644 --- a/util/data/data.go +++ b/util/data/data.go @@ -47,6 +47,9 @@ type Fetcher func(key string, val interface{}, path []string) interface{} // Iterator is used when iterating over items. type Iterator func(key string, val interface{}) error +// Walker is used when walking over items. +type Walker func(key string, val interface{}, exi bool) error + // New creates a new data object. func New() *Doc { return &Doc{data: map[string]interface{}{}} @@ -995,7 +998,7 @@ func (d *Doc) each(exec Iterator, prev []string) error { // -------------------------------------------------------------------------------- // Walk walks the value or values at a specified path. -func (d *Doc) Walk(exec Iterator, path ...string) error { +func (d *Doc) Walk(exec Walker, path ...string) error { path = d.path(path...) @@ -1003,7 +1006,7 @@ func (d *Doc) Walk(exec Iterator, path ...string) error { } -func (d *Doc) walk(exec Iterator, prev []string, path ...string) error { +func (d *Doc) walk(exec Walker, prev []string, path ...string) error { if len(path) == 0 { return nil @@ -1037,7 +1040,7 @@ func (d *Doc) walk(exec Iterator, prev []string, path ...string) error { if m, ok := object.(map[string]interface{}); ok { if object, ok = m[p]; !ok { - return exec(d.join(prev, path), nil) + return exec(d.join(prev, path), nil, false) } continue } @@ -1051,7 +1054,7 @@ func (d *Doc) walk(exec Iterator, prev []string, path ...string) error { c, i, r := d.what(p, a, choose) if r == one && len(c) == 0 { - return fmt.Errorf("No item with index %s in array, using path %s", p, path) + return nil } if r == one { @@ -1060,7 +1063,7 @@ func (d *Doc) walk(exec Iterator, prev []string, path ...string) error { keep = append(keep, prev...) keep = append(keep, path[:k]...) keep = append(keep, fmt.Sprintf("[%d]", i[0])) - return exec(d.join(keep), c[0]) + return exec(d.join(keep), c[0], true) } else { var keep []string keep = append(keep, prev...) @@ -1077,7 +1080,7 @@ func (d *Doc) walk(exec Iterator, prev []string, path ...string) error { keep = append(keep, prev...) keep = append(keep, path[:k]...) keep = append(keep, fmt.Sprintf("[%d]", i[j])) - if err := exec(d.join(keep), v); err != nil { + if err := exec(d.join(keep), v, true); err != nil { return err } } else { @@ -1098,10 +1101,10 @@ func (d *Doc) walk(exec Iterator, prev []string, path ...string) error { // The current path item is not an object or an array // but there are still other items in the search path. - return fmt.Errorf("Can not get path %s from %v", path, object) + return nil } - return exec(d.join(prev, path), object) + return exec(d.join(prev, path), object, true) } diff --git a/util/data/data_test.go b/util/data/data_test.go index 3bdee96e..d69f12a4 100644 --- a/util/data/data_test.go +++ b/util/data/data_test.go @@ -953,16 +953,18 @@ func TestOperations(t *testing.T) { }) Convey("Can walk nil", t, func() { - doc.Walk(func(key string, val interface{}) error { + doc.Walk(func(key string, val interface{}, exi bool) error { doc.Set(tmp, "none") + So(exi, ShouldBeTrue) return nil }) So(doc.Exists("none"), ShouldBeFalse) }) Convey("Can walk array", t, func() { - doc.Walk(func(key string, val interface{}) error { + doc.Walk(func(key string, val interface{}, exi bool) error { So(key, ShouldResemble, "the.item.arrays") + So(exi, ShouldBeFalse) doc.Set(tmp, key) return nil }, "the.item.arrays") @@ -970,98 +972,105 @@ func TestOperations(t *testing.T) { }) Convey("Can walk array → *", t, func() { - doc.Walk(func(key string, val interface{}) error { + doc.Walk(func(key string, val interface{}, exi bool) error { So(key, ShouldBeIn, "the.item.arrays.[0]", "the.item.arrays.[1]", "the.item.arrays.[2]") So(val, ShouldBeIn, tmp[0], tmp[1], tmp[2]) + So(exi, ShouldBeTrue) return nil }, "the.item.arrays.*") }) Convey("Can walk array → * → object", t, func() { - doc.Walk(func(key string, val interface{}) error { + doc.Walk(func(key string, val interface{}, exi bool) error { So(key, ShouldBeIn, "the.item.arrays.[0].test", "the.item.arrays.[1].test", "the.item.arrays.[2].test") So(val, ShouldBeIn, "one", "two", "tre") + So(exi, ShouldBeTrue) return nil }, "the.item.arrays.*.test") }) Convey("Can walk array → first → object", t, func() { - doc.Walk(func(key string, val interface{}) error { + doc.Walk(func(key string, val interface{}, exi bool) error { So(key, ShouldResemble, "the.item.arrays.[0].test") So(val, ShouldResemble, "one") + So(exi, ShouldBeTrue) return nil }, "the.item.arrays.first.test") }) Convey("Can walk array → last → object", t, func() { - doc.Walk(func(key string, val interface{}) error { + doc.Walk(func(key string, val interface{}, exi bool) error { So(key, ShouldResemble, "the.item.arrays.[2].test") So(val, ShouldResemble, "tre") + So(exi, ShouldBeTrue) return nil }, "the.item.arrays.last.test") }) Convey("Can walk array → 0 → value", t, func() { - doc.Walk(func(key string, val interface{}) error { + doc.Walk(func(key string, val interface{}, exi bool) error { So(key, ShouldResemble, "the.item.arrays.[0]") So(val, ShouldResemble, map[string]interface{}{"test": "one"}) + So(exi, ShouldBeTrue) return nil }, "the.item.arrays.0") }) Convey("Can walk array → 1 → value", t, func() { - doc.Walk(func(key string, val interface{}) error { + doc.Walk(func(key string, val interface{}, exi bool) error { So(key, ShouldResemble, "the.item.arrays.[1]") So(val, ShouldResemble, map[string]interface{}{"test": "two"}) + So(exi, ShouldBeTrue) return nil }, "the.item.arrays.1") }) Convey("Can walk array → 2 → value", t, func() { - doc.Walk(func(key string, val interface{}) error { + doc.Walk(func(key string, val interface{}, exi bool) error { So(key, ShouldResemble, "the.item.arrays.[2]") So(val, ShouldResemble, map[string]interface{}{"test": "tre"}) + So(exi, ShouldBeTrue) return nil }, "the.item.arrays.2") }) Convey("Can walk array → 3 → value", t, func() { - err := doc.Walk(func(key string, val interface{}) error { + err := doc.Walk(func(key string, val interface{}, exi bool) error { return nil }, "the.item.arrays.3") - So(err, ShouldNotBeNil) + So(err, ShouldBeNil) }) Convey("Can walk array → 0 → value → value", t, func() { - err := doc.Walk(func(key string, val interface{}) error { + err := doc.Walk(func(key string, val interface{}, exi bool) error { return nil }, "the.item.arrays.0.test.value") - So(err, ShouldNotBeNil) + So(err, ShouldBeNil) }) Convey("Can walk array → 0 → value → value → value", t, func() { - err := doc.Walk(func(key string, val interface{}) error { + err := doc.Walk(func(key string, val interface{}, exi bool) error { return nil }, "the.item.arrays.0.test.value.value") - So(err, ShouldNotBeNil) + So(err, ShouldBeNil) }) Convey("Can force error from walk", t, func() { - err := doc.Walk(func(key string, val interface{}) error { + err := doc.Walk(func(key string, val interface{}, exi bool) error { return errors.New("Testing") }, "the.item.something") So(err, ShouldNotBeNil) }) Convey("Can force error from walk array → *", t, func() { - err := doc.Walk(func(key string, val interface{}) error { + err := doc.Walk(func(key string, val interface{}, exi bool) error { return errors.New("Testing") }, "the.item.arrays.*") So(err, ShouldNotBeNil) }) Convey("Can force error from walk array → * → value", t, func() { - err := doc.Walk(func(key string, val interface{}) error { + err := doc.Walk(func(key string, val interface{}, exi bool) error { return errors.New("Testing") }, "the.item.arrays.*.test") So(err, ShouldNotBeNil)