Ensure that fields are not removed when set to NULL

This commit is contained in:
Tobie Morgan Hitchcock 2018-04-27 00:40:36 +01:00
parent 9b03178dfd
commit eb62515a05
5 changed files with 50 additions and 33 deletions

View file

@ -108,7 +108,7 @@ func (d *document) delFld(ctx context.Context, met method) (err error) {
// Loop over the allowed keys // Loop over the allowed keys
for _, fd := range fds { 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{}{} keys[key] = struct{}{}
return return
}, fd.Name.ID) }, fd.Name.ID)
@ -291,9 +291,7 @@ func (d *document) mrgFld(ctx context.Context, met method) (err error) {
for _, fd := range fds { for _, fd := range fds {
err = d.current.Walk(func(key string, val interface{}) error { err = d.current.Walk(func(key string, val interface{}, exi bool) error {
vars := data.New()
var old = d.initial.Get(key).Data() 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 // Reset the variables
vars := data.New()
vars.Set(val, varKeyValue) vars.Set(val, varKeyValue)
vars.Set(val, varKeyAfter) vars.Set(val, varKeyAfter)
vars.Set(old, varKeyBefore) vars.Set(old, varKeyBefore)
@ -332,6 +331,7 @@ func (d *document) mrgFld(ctx context.Context, met method) (err error) {
// Reset the variables // Reset the variables
vars := data.New()
vars.Set(val, varKeyValue) vars.Set(val, varKeyValue)
vars.Set(val, varKeyAfter) vars.Set(val, varKeyAfter)
vars.Set(old, varKeyBefore) vars.Set(old, varKeyBefore)
@ -354,6 +354,7 @@ func (d *document) mrgFld(ctx context.Context, met method) (err error) {
// Reset the variables // Reset the variables
vars := data.New()
vars.Set(val, varKeyValue) vars.Set(val, varKeyValue)
vars.Set(val, varKeyAfter) vars.Set(val, varKeyAfter)
vars.Set(old, varKeyBefore) vars.Set(old, varKeyBefore)
@ -398,7 +399,11 @@ func (d *document) mrgFld(ctx context.Context, met method) (err error) {
switch val.(type) { switch val.(type) {
default: default:
d.current.Iff(val, key) if exi {
d.current.Set(val, key)
} else {
d.current.Iff(val, key)
}
case *sql.Void: case *sql.Void:
d.current.Del(key) d.current.Del(key)
} }

View file

@ -51,7 +51,7 @@ func (d *document) perms(ctx context.Context, doc *data.Doc) (err error) {
if fd.Perms != nil { 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 // We are checking the permissions of the field

View file

@ -242,7 +242,7 @@ func (d *document) yield(ctx context.Context, stm sql.Statement, output sql.Toke
break break
case *sql.Ident: 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) { switch res := val.(type) {
case []interface{}: case []interface{}:

View file

@ -47,6 +47,9 @@ type Fetcher func(key string, val interface{}, path []string) interface{}
// Iterator is used when iterating over items. // Iterator is used when iterating over items.
type Iterator func(key string, val interface{}) error 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. // New creates a new data object.
func New() *Doc { func New() *Doc {
return &Doc{data: map[string]interface{}{}} 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. // 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...) 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 { if len(path) == 0 {
return nil 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 m, ok := object.(map[string]interface{}); ok {
if object, ok = m[p]; !ok { if object, ok = m[p]; !ok {
return exec(d.join(prev, path), nil) return exec(d.join(prev, path), nil, false)
} }
continue continue
} }
@ -1051,7 +1054,7 @@ func (d *Doc) walk(exec Iterator, prev []string, path ...string) error {
c, i, r := d.what(p, a, choose) c, i, r := d.what(p, a, choose)
if r == one && len(c) == 0 { 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 { 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, prev...)
keep = append(keep, path[:k]...) keep = append(keep, path[:k]...)
keep = append(keep, fmt.Sprintf("[%d]", i[0])) keep = append(keep, fmt.Sprintf("[%d]", i[0]))
return exec(d.join(keep), c[0]) return exec(d.join(keep), c[0], true)
} else { } else {
var keep []string var keep []string
keep = append(keep, prev...) 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, prev...)
keep = append(keep, path[:k]...) keep = append(keep, path[:k]...)
keep = append(keep, fmt.Sprintf("[%d]", i[j])) 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 return err
} }
} else { } 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 // The current path item is not an object or an array
// but there are still other items in the search path. // 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)
} }

View file

@ -953,16 +953,18 @@ func TestOperations(t *testing.T) {
}) })
Convey("Can walk nil", t, func() { 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") doc.Set(tmp, "none")
So(exi, ShouldBeTrue)
return nil return nil
}) })
So(doc.Exists("none"), ShouldBeFalse) So(doc.Exists("none"), ShouldBeFalse)
}) })
Convey("Can walk array", t, func() { 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(key, ShouldResemble, "the.item.arrays")
So(exi, ShouldBeFalse)
doc.Set(tmp, key) doc.Set(tmp, key)
return nil return nil
}, "the.item.arrays") }, "the.item.arrays")
@ -970,98 +972,105 @@ func TestOperations(t *testing.T) {
}) })
Convey("Can walk array → *", t, func() { 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(key, ShouldBeIn, "the.item.arrays.[0]", "the.item.arrays.[1]", "the.item.arrays.[2]")
So(val, ShouldBeIn, tmp[0], tmp[1], tmp[2]) So(val, ShouldBeIn, tmp[0], tmp[1], tmp[2])
So(exi, ShouldBeTrue)
return nil return nil
}, "the.item.arrays.*") }, "the.item.arrays.*")
}) })
Convey("Can walk array → * → object", t, func() { 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(key, ShouldBeIn, "the.item.arrays.[0].test", "the.item.arrays.[1].test", "the.item.arrays.[2].test")
So(val, ShouldBeIn, "one", "two", "tre") So(val, ShouldBeIn, "one", "two", "tre")
So(exi, ShouldBeTrue)
return nil return nil
}, "the.item.arrays.*.test") }, "the.item.arrays.*.test")
}) })
Convey("Can walk array → first → object", t, func() { 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(key, ShouldResemble, "the.item.arrays.[0].test")
So(val, ShouldResemble, "one") So(val, ShouldResemble, "one")
So(exi, ShouldBeTrue)
return nil return nil
}, "the.item.arrays.first.test") }, "the.item.arrays.first.test")
}) })
Convey("Can walk array → last → object", t, func() { 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(key, ShouldResemble, "the.item.arrays.[2].test")
So(val, ShouldResemble, "tre") So(val, ShouldResemble, "tre")
So(exi, ShouldBeTrue)
return nil return nil
}, "the.item.arrays.last.test") }, "the.item.arrays.last.test")
}) })
Convey("Can walk array → 0 → value", t, func() { 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(key, ShouldResemble, "the.item.arrays.[0]")
So(val, ShouldResemble, map[string]interface{}{"test": "one"}) So(val, ShouldResemble, map[string]interface{}{"test": "one"})
So(exi, ShouldBeTrue)
return nil return nil
}, "the.item.arrays.0") }, "the.item.arrays.0")
}) })
Convey("Can walk array → 1 → value", t, func() { 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(key, ShouldResemble, "the.item.arrays.[1]")
So(val, ShouldResemble, map[string]interface{}{"test": "two"}) So(val, ShouldResemble, map[string]interface{}{"test": "two"})
So(exi, ShouldBeTrue)
return nil return nil
}, "the.item.arrays.1") }, "the.item.arrays.1")
}) })
Convey("Can walk array → 2 → value", t, func() { 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(key, ShouldResemble, "the.item.arrays.[2]")
So(val, ShouldResemble, map[string]interface{}{"test": "tre"}) So(val, ShouldResemble, map[string]interface{}{"test": "tre"})
So(exi, ShouldBeTrue)
return nil return nil
}, "the.item.arrays.2") }, "the.item.arrays.2")
}) })
Convey("Can walk array → 3 → value", t, func() { 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 return nil
}, "the.item.arrays.3") }, "the.item.arrays.3")
So(err, ShouldNotBeNil) So(err, ShouldBeNil)
}) })
Convey("Can walk array → 0 → value → value", t, func() { 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 return nil
}, "the.item.arrays.0.test.value") }, "the.item.arrays.0.test.value")
So(err, ShouldNotBeNil) So(err, ShouldBeNil)
}) })
Convey("Can walk array → 0 → value → value → value", t, func() { 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 return nil
}, "the.item.arrays.0.test.value.value") }, "the.item.arrays.0.test.value.value")
So(err, ShouldNotBeNil) So(err, ShouldBeNil)
}) })
Convey("Can force error from walk", t, func() { 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") return errors.New("Testing")
}, "the.item.something") }, "the.item.something")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
Convey("Can force error from walk array → *", t, func() { 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") return errors.New("Testing")
}, "the.item.arrays.*") }, "the.item.arrays.*")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
Convey("Can force error from walk array → * → value", t, func() { 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") return errors.New("Testing")
}, "the.item.arrays.*.test") }, "the.item.arrays.*.test")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)