ca6d0d86fd
When performing a range query on an array when deleting, the items which were supposed to be removed, ended up being the ones which were kept. Not the ramining array items are kept instead when deleting.
1056 lines
21 KiB
Go
1056 lines
21 KiB
Go
// Copyright © 2016 Abcum Ltd
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package data
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"encoding/json"
|
|
|
|
"github.com/abcum/surreal/util/deep"
|
|
"github.com/abcum/surreal/util/pack"
|
|
)
|
|
|
|
const (
|
|
one int8 = iota
|
|
many
|
|
)
|
|
|
|
const (
|
|
choose int8 = iota
|
|
remove
|
|
)
|
|
|
|
// Doc holds a reference to the core data object, or a selected path.
|
|
type Doc struct {
|
|
data interface{}
|
|
}
|
|
|
|
// New creates a new data object.
|
|
func New() *Doc {
|
|
return &Doc{data: map[string]interface{}{}}
|
|
}
|
|
|
|
// Consume converts a GO interface into a data object.
|
|
func Consume(input interface{}) *Doc {
|
|
return &Doc{data: input}
|
|
}
|
|
|
|
// Data returns the internal data object as an interface.
|
|
func (d *Doc) Data() interface{} {
|
|
return d.data
|
|
}
|
|
|
|
// Copy returns a duplicated copy of the internal data object.
|
|
func (d *Doc) Copy() (i interface{}) {
|
|
return deep.Copy(d.data)
|
|
}
|
|
|
|
// Encode encodes the data object to a byte slice.
|
|
func (d *Doc) Encode() (dst []byte) {
|
|
dst = pack.Encode(d.data)
|
|
return
|
|
}
|
|
|
|
// Decode decodes the byte slice into a data object.
|
|
func (d *Doc) Decode(src []byte) *Doc {
|
|
pack.Decode(src, &d.data)
|
|
return d
|
|
}
|
|
|
|
func (d *Doc) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(d.Data())
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
|
|
func (d *Doc) path(path ...string) (paths []string) {
|
|
|
|
for _, p := range path {
|
|
for j, i, o := 0, 0, false; i < len(p); i++ {
|
|
switch {
|
|
case i == len(p)-1:
|
|
if len(p[j:]) > 0 {
|
|
paths = append(paths, p[j:])
|
|
}
|
|
case p[i] == '.':
|
|
if len(p[j:i]) > 0 {
|
|
paths = append(paths, p[j:i])
|
|
}
|
|
j, i = i+1, i+0
|
|
case p[i] == '[':
|
|
if len(p[j:i]) > 0 {
|
|
paths = append(paths, p[j:i])
|
|
}
|
|
j, i, o = i, i+1, true
|
|
case p[i] == ']' && o:
|
|
if len(p[j:i+1]) > 0 {
|
|
paths = append(paths, p[j:i+1])
|
|
}
|
|
j, i, o = i+1, i+0, false
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
func min(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
func max(a, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
func trim(s string) string {
|
|
if s[0] == '[' && s[len(s)-1] == ']' {
|
|
return s[1 : len(s)-1]
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (d *Doc) what(p string, a []interface{}, t int8) (o []interface{}, i []int, r int8) {
|
|
|
|
p = trim(p)
|
|
|
|
i = []int{}
|
|
|
|
o = []interface{}{}
|
|
|
|
// If there are no items in the
|
|
// original array, then return
|
|
// an empty array immediately.
|
|
|
|
if len(a) == 0 {
|
|
if strings.ContainsAny(p, ":*") {
|
|
return o, i, many
|
|
} else {
|
|
return o, i, one
|
|
}
|
|
}
|
|
|
|
// If the array index is a star
|
|
// or a colon only, then return
|
|
// the full array immediately
|
|
|
|
if p == "*" || p == ":" {
|
|
switch t {
|
|
case choose:
|
|
for k := range a {
|
|
i = append(i, k)
|
|
}
|
|
return a, i, many
|
|
case remove:
|
|
for k := range a {
|
|
i = append(i, k)
|
|
}
|
|
return o, i, many
|
|
}
|
|
}
|
|
|
|
// Split the specified array index
|
|
// by colons, so that we can get
|
|
// the specified array items.
|
|
|
|
c := strings.Count(p, ":")
|
|
|
|
if c == 0 {
|
|
|
|
switch p {
|
|
case "0", "first":
|
|
if t == choose {
|
|
i = append(i, 0)
|
|
o = append(o, a[0])
|
|
} else {
|
|
for k := range a[1:] {
|
|
i = append(i, k)
|
|
}
|
|
o = append(o, a[1:]...)
|
|
}
|
|
case "$", "last":
|
|
if t == choose {
|
|
i = append(i, len(a)-1)
|
|
o = append(o, a[len(a)-1])
|
|
} else {
|
|
for k := range a[:len(a)-1] {
|
|
i = append(i, k)
|
|
}
|
|
o = append(o, a[:len(a)-1]...)
|
|
}
|
|
default:
|
|
if z, e := strconv.Atoi(p); e == nil {
|
|
if len(a) > z {
|
|
if t == choose {
|
|
i = append(i, z)
|
|
o = append(o, a[z])
|
|
} else {
|
|
for k := range append(a[:z], a[z+1:]...) {
|
|
i = append(i, k)
|
|
}
|
|
o = append(o, append(a[:z], a[z+1:]...)...)
|
|
}
|
|
} else {
|
|
if t == remove {
|
|
for k := range a {
|
|
i = append(i, k)
|
|
}
|
|
o = append(o, a[:]...)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return o, i, one
|
|
|
|
}
|
|
|
|
if c == 1 {
|
|
|
|
var e error
|
|
var s, f int
|
|
|
|
b := []int{0, len(a)}
|
|
x := strings.Split(p, ":")
|
|
|
|
for k := range x {
|
|
switch x[k] {
|
|
case "":
|
|
case "0", "first":
|
|
b[k] = 0
|
|
case "$", "last":
|
|
b[k] = len(a)
|
|
default:
|
|
if b[k], e = strconv.Atoi(x[k]); e != nil {
|
|
return nil, nil, many
|
|
}
|
|
}
|
|
}
|
|
|
|
s = b[0]
|
|
s = max(s, 0)
|
|
s = min(s, len(a))
|
|
|
|
f = b[1]
|
|
f = max(f, 0)
|
|
f = min(f, len(a))
|
|
|
|
if t == choose {
|
|
for k, v := range a[s:f] {
|
|
i = append(i, k)
|
|
o = append(o, v)
|
|
}
|
|
} else {
|
|
for k, v := range append(a[:s], a[f+1:]...) {
|
|
i = append(i, k)
|
|
o = append(o, v)
|
|
}
|
|
}
|
|
|
|
return o, i, many
|
|
|
|
}
|
|
|
|
return nil, nil, many
|
|
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
|
|
// Reset empties and resets the data at the specified path.
|
|
func (d *Doc) Reset(path ...string) (*Doc, error) {
|
|
return d.Set(nil, path...)
|
|
}
|
|
|
|
// Valid checks whether the value at the specified path is nil.
|
|
func (d *Doc) Valid(path ...string) bool {
|
|
if !d.Exists(path...) {
|
|
return false
|
|
}
|
|
return d.Get(path...).Data() != nil
|
|
}
|
|
|
|
// Array sets the specified path to an array.
|
|
func (d *Doc) Array(path ...string) (*Doc, error) {
|
|
return d.Set([]interface{}{}, path...)
|
|
}
|
|
|
|
// Object sets the specified path to an object.
|
|
func (d *Doc) Object(path ...string) (*Doc, error) {
|
|
return d.Set(map[string]interface{}{}, path...)
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
|
|
// New sets the value at the specified path if it does not exist.
|
|
func (d *Doc) New(value interface{}, path ...string) (*Doc, error) {
|
|
if !d.Exists(path...) {
|
|
return d.Set(value, path...)
|
|
}
|
|
return d.Get(path...), nil
|
|
}
|
|
|
|
// Iff sets the value at the specified path if it is not nil, or deleted it.
|
|
func (d *Doc) Iff(value interface{}, path ...string) (*Doc, error) {
|
|
if value != nil {
|
|
return d.Set(value, path...)
|
|
}
|
|
return &Doc{data: nil}, d.Del(path...)
|
|
}
|
|
|
|
// Keys retrieves the object keys at the specified path.
|
|
func (d *Doc) Keys(path ...string) *Doc {
|
|
|
|
path = d.path(path...)
|
|
|
|
out := []interface{}{}
|
|
|
|
if m, ok := d.Get(path...).Data().(map[string]interface{}); ok {
|
|
for k := range m {
|
|
out = append(out, k)
|
|
}
|
|
}
|
|
|
|
return &Doc{data: out}
|
|
|
|
}
|
|
|
|
// Vals retrieves the object values at the specified path.
|
|
func (d *Doc) Vals(path ...string) *Doc {
|
|
|
|
path = d.path(path...)
|
|
|
|
out := []interface{}{}
|
|
|
|
if m, ok := d.Get(path...).Data().(map[string]interface{}); ok {
|
|
for _, v := range m {
|
|
out = append(out, v)
|
|
}
|
|
}
|
|
|
|
return &Doc{data: out}
|
|
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
|
|
// Exists checks whether the specified path exists.
|
|
func (d *Doc) Exists(path ...string) bool {
|
|
|
|
path = d.path(path...)
|
|
|
|
// If the value found at the current
|
|
// path part is undefined, then just
|
|
// return false immediately
|
|
|
|
if d.data == nil {
|
|
return false
|
|
}
|
|
|
|
// Define the temporary object so
|
|
// that we can loop over and traverse
|
|
// down the path parts of the data
|
|
|
|
object := d.data
|
|
|
|
// Loop over each part of the path
|
|
// whilst detecting if the data at
|
|
// the current path is an {} or []
|
|
|
|
for k, p := range path {
|
|
|
|
// If the value found at the current
|
|
// path part is an object, then move
|
|
// to the next part of the path
|
|
|
|
if m, ok := object.(map[string]interface{}); ok {
|
|
if object, ok = m[trim(p)]; !ok {
|
|
return false
|
|
}
|
|
continue
|
|
}
|
|
|
|
// If the value found at the current
|
|
// path part is an array, then perform
|
|
// the query on the specified items
|
|
|
|
if a, ok := object.([]interface{}); ok {
|
|
|
|
if p == "length" {
|
|
return len(a) > 0
|
|
}
|
|
|
|
c, _, r := d.what(p, a, choose)
|
|
|
|
if len(c) == 0 {
|
|
return false
|
|
}
|
|
|
|
if r == one {
|
|
return Consume(c[0]).Exists(path[k+1:]...)
|
|
}
|
|
|
|
if r == many {
|
|
for _, v := range c {
|
|
if !Consume(v).Exists(path[k+1:]...) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
|
|
// Get gets the value or values at a specified path.
|
|
func (d *Doc) Get(path ...string) *Doc {
|
|
|
|
path = d.path(path...)
|
|
|
|
// If the value found at the current
|
|
// path part is undefined, then just
|
|
// return false immediately
|
|
|
|
if d.data == nil {
|
|
return &Doc{data: nil}
|
|
}
|
|
|
|
// Define the temporary object so
|
|
// that we can loop over and traverse
|
|
// down the path parts of the data
|
|
|
|
object := d.data
|
|
|
|
// Loop over each part of the path
|
|
// whilst detecting if the data at
|
|
// the current path is an {} or []
|
|
|
|
for k, p := range path {
|
|
|
|
// If the value found at the current
|
|
// path part is an object, then move
|
|
// to the next part of the path
|
|
|
|
if m, ok := object.(map[string]interface{}); ok {
|
|
object = m[trim(p)]
|
|
continue
|
|
}
|
|
|
|
// If the value found at the current
|
|
// path part is an array, then perform
|
|
// the query on the specified items
|
|
|
|
if a, ok := object.([]interface{}); ok {
|
|
|
|
if p == "length" {
|
|
return &Doc{data: len(a)}
|
|
}
|
|
|
|
c, _, r := d.what(p, a, choose)
|
|
|
|
if len(c) == 0 {
|
|
return &Doc{data: nil}
|
|
}
|
|
|
|
if r == one {
|
|
return Consume(c[0]).Get(path[k+1:]...)
|
|
}
|
|
|
|
if r == many {
|
|
out := []interface{}{}
|
|
for _, v := range c {
|
|
res := Consume(v).Get(path[k+1:]...)
|
|
if res.data != nil {
|
|
out = append(out, res.data)
|
|
}
|
|
}
|
|
return &Doc{data: out}
|
|
}
|
|
|
|
}
|
|
|
|
return &Doc{data: nil}
|
|
|
|
}
|
|
|
|
return &Doc{data: object}
|
|
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
|
|
// Set sets the value or values at a specified path.
|
|
func (d *Doc) Set(value interface{}, path ...string) (*Doc, error) {
|
|
|
|
path = d.path(path...)
|
|
|
|
if len(path) == 0 {
|
|
d.data = value
|
|
return d, nil
|
|
}
|
|
|
|
// If the value found at the current
|
|
// path part is undefined, then ensure
|
|
// that it is an object
|
|
|
|
if d.data == nil {
|
|
d.data = map[string]interface{}{}
|
|
}
|
|
|
|
// Define the temporary object so
|
|
// that we can loop over and traverse
|
|
// down the path parts of the data
|
|
|
|
object := d.data
|
|
|
|
// Loop over each part of the path
|
|
// whilst detecting if the data at
|
|
// the current path is an {} or []
|
|
|
|
for k, p := range path {
|
|
|
|
// If the value found at the current
|
|
// path part is an object, then move
|
|
// to the next part of the path
|
|
|
|
if m, ok := object.(map[string]interface{}); ok {
|
|
if k == len(path)-1 {
|
|
m[trim(p)] = value
|
|
} else if m[p] == nil {
|
|
m[trim(p)] = map[string]interface{}{}
|
|
}
|
|
object = m[trim(p)]
|
|
continue
|
|
}
|
|
|
|
// If the value found at the current
|
|
// path part is an array, then perform
|
|
// the query on the specified items
|
|
|
|
if a, ok := object.([]interface{}); ok {
|
|
|
|
c, i, r := d.what(p, a, choose)
|
|
|
|
if len(c) == 0 {
|
|
return &Doc{data: nil}, nil
|
|
}
|
|
|
|
if r == one {
|
|
if k == len(path)-1 {
|
|
a[i[0]] = value
|
|
object = a[i[0]]
|
|
} else {
|
|
return Consume(a[i[0]]).Set(value, path[k+1:]...)
|
|
}
|
|
}
|
|
|
|
if r == many {
|
|
out := []interface{}{}
|
|
for j, v := range c {
|
|
if k == len(path)-1 {
|
|
a[i[j]] = value
|
|
out = append(out, value)
|
|
} else {
|
|
res, _ := Consume(v).Set(value, path[k+1:]...)
|
|
if res.data != nil {
|
|
out = append(out, res.data)
|
|
}
|
|
}
|
|
}
|
|
return &Doc{data: out}, nil
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &Doc{data: object}, nil
|
|
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
|
|
// Del deletes the value or values at a specified path.
|
|
func (d *Doc) Del(path ...string) error {
|
|
|
|
path = d.path(path...)
|
|
|
|
// If the value found at the current
|
|
// path part is undefined, then return
|
|
// a not an object error
|
|
|
|
if d.data == nil {
|
|
return fmt.Errorf("Item is not an object")
|
|
}
|
|
|
|
// Define the temporary object so
|
|
// that we can loop over and traverse
|
|
// down the path parts of the data
|
|
|
|
object := d.data
|
|
|
|
// Loop over each part of the path
|
|
// whilst detecting if the data at
|
|
// the current path is an {} or []
|
|
|
|
for k, p := range path {
|
|
|
|
// If the value found at the current
|
|
// path part is an object, then move
|
|
// to the next part of the path
|
|
|
|
if m, ok := object.(map[string]interface{}); ok {
|
|
if k == len(path)-1 {
|
|
delete(m, trim(p))
|
|
} else if m[trim(p)] == nil {
|
|
return fmt.Errorf("Item at path %s is not an object", path)
|
|
}
|
|
object = m[trim(p)]
|
|
continue
|
|
}
|
|
|
|
// If the value found at the current
|
|
// path part is an array, then perform
|
|
// the query on the specified items
|
|
|
|
if a, ok := object.([]interface{}); ok {
|
|
|
|
var r int8
|
|
var c []interface{}
|
|
|
|
if k == len(path)-1 {
|
|
c, _, r = d.what(p, a, remove)
|
|
} else {
|
|
c, _, r = d.what(p, a, choose)
|
|
}
|
|
|
|
if r == one {
|
|
if k == len(path)-1 {
|
|
d.Set(c, path[:len(path)-1]...)
|
|
} else {
|
|
if len(c) != 0 {
|
|
return Consume(c[0]).Del(path[k+1:]...)
|
|
}
|
|
}
|
|
}
|
|
|
|
if r == many {
|
|
if k == len(path)-1 {
|
|
d.Set(c, path[:len(path)-1]...)
|
|
} else {
|
|
for _, v := range c {
|
|
Consume(v).Del(path[k+1:]...)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
|
|
// ArrayDel appends an item or an array of items to an array at the specified path.
|
|
func (d *Doc) ArrayAdd(value interface{}, path ...string) (*Doc, error) {
|
|
|
|
a, ok := d.Get(path...).Data().([]interface{})
|
|
if !ok {
|
|
return &Doc{data: nil}, fmt.Errorf("Not an array")
|
|
}
|
|
|
|
if values, ok := value.([]interface{}); ok {
|
|
outer:
|
|
for _, value := range values {
|
|
for _, v := range a {
|
|
if reflect.DeepEqual(v, value) {
|
|
continue outer
|
|
}
|
|
}
|
|
a = append(a, value)
|
|
}
|
|
} else {
|
|
for _, v := range a {
|
|
if reflect.DeepEqual(v, value) {
|
|
return nil, nil
|
|
}
|
|
}
|
|
a = append(a, value)
|
|
}
|
|
|
|
return d.Set(a, path...)
|
|
|
|
}
|
|
|
|
// ArrayDel deletes an item or an array of items from an array at the specified path.
|
|
func (d *Doc) ArrayDel(value interface{}, path ...string) (*Doc, error) {
|
|
|
|
a, ok := d.Get(path...).Data().([]interface{})
|
|
if !ok {
|
|
return &Doc{data: nil}, fmt.Errorf("Not an array")
|
|
}
|
|
|
|
if values, ok := value.([]interface{}); ok {
|
|
for _, value := range values {
|
|
for i := len(a) - 1; i >= 0; i-- {
|
|
v := a[i]
|
|
if reflect.DeepEqual(v, value) {
|
|
copy(a[i:], a[i+1:])
|
|
a[len(a)-1] = nil
|
|
a = a[:len(a)-1]
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
for i := len(a) - 1; i >= 0; i-- {
|
|
v := a[i]
|
|
if reflect.DeepEqual(v, value) {
|
|
copy(a[i:], a[i+1:])
|
|
a[len(a)-1] = nil
|
|
a = a[:len(a)-1]
|
|
}
|
|
}
|
|
}
|
|
|
|
return d.Set(a, path...)
|
|
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
|
|
// Contains checks whether the value exists within the array at the specified path.
|
|
func (d *Doc) Contains(value interface{}, path ...string) bool {
|
|
|
|
a, ok := d.Get(path...).Data().([]interface{})
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
for _, v := range a {
|
|
if reflect.DeepEqual(v, value) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
|
|
// Inc increments an item, or appends an item to an array at the specified path.
|
|
func (d *Doc) Inc(value interface{}, path ...string) (*Doc, error) {
|
|
|
|
switch cur := d.Get(path...).Data().(type) {
|
|
case nil:
|
|
switch inc := value.(type) {
|
|
case int64:
|
|
return d.Set(0+inc, path...)
|
|
case float64:
|
|
return d.Set(0+inc, path...)
|
|
}
|
|
case int64:
|
|
switch inc := value.(type) {
|
|
case int64:
|
|
return d.Set(cur+inc, path...)
|
|
case float64:
|
|
return d.Set(float64(cur)+inc, path...)
|
|
}
|
|
case float64:
|
|
switch inc := value.(type) {
|
|
case int64:
|
|
return d.Set(cur+float64(inc), path...)
|
|
case float64:
|
|
return d.Set(cur+inc, path...)
|
|
}
|
|
case []interface{}:
|
|
return d.ArrayAdd(value, path...)
|
|
}
|
|
|
|
return &Doc{data: nil}, fmt.Errorf("Not possible to increment.")
|
|
|
|
}
|
|
|
|
// Dec decrements an item, or removes an item from an array at the specified path.
|
|
func (d *Doc) Dec(value interface{}, path ...string) (*Doc, error) {
|
|
|
|
switch cur := d.Get(path...).Data().(type) {
|
|
case nil:
|
|
switch inc := value.(type) {
|
|
case int64:
|
|
return d.Set(0-inc, path...)
|
|
case float64:
|
|
return d.Set(0-inc, path...)
|
|
}
|
|
case int64:
|
|
switch inc := value.(type) {
|
|
case int64:
|
|
return d.Set(cur-inc, path...)
|
|
case float64:
|
|
return d.Set(float64(cur)-inc, path...)
|
|
}
|
|
case float64:
|
|
switch inc := value.(type) {
|
|
case int64:
|
|
return d.Set(cur-float64(inc), path...)
|
|
case float64:
|
|
return d.Set(cur-inc, path...)
|
|
}
|
|
case []interface{}:
|
|
return d.ArrayDel(value, path...)
|
|
}
|
|
|
|
return &Doc{data: nil}, fmt.Errorf("Not possible to decrement.")
|
|
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
|
|
func (d *Doc) Diff(n *Doc) map[string]interface{} {
|
|
|
|
var initial = make(map[string]interface{})
|
|
var current = make(map[string]interface{})
|
|
var changes = make(map[string]interface{})
|
|
|
|
d.Each(func(key string, val interface{}) error {
|
|
initial[key] = val
|
|
return nil
|
|
})
|
|
|
|
n.Each(func(key string, val interface{}) error {
|
|
current[key] = val
|
|
return nil
|
|
})
|
|
|
|
for k, v := range current {
|
|
if o, ok := initial[k]; ok {
|
|
if reflect.DeepEqual(o, v) {
|
|
continue
|
|
}
|
|
}
|
|
changes[k] = v
|
|
}
|
|
|
|
for k := range initial {
|
|
if _, ok := current[k]; !ok {
|
|
changes[k] = nil
|
|
}
|
|
}
|
|
|
|
return changes
|
|
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
|
|
type Iterator func(key string, val interface{}) error
|
|
|
|
func (d *Doc) join(parts ...[]string) string {
|
|
var path []string
|
|
for _, part := range parts {
|
|
path = append(path, part...)
|
|
}
|
|
return strings.Join(path, ".")
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
|
|
// Each loops through the values in the data doc.
|
|
func (d *Doc) Each(exec Iterator) error {
|
|
|
|
return d.each(exec, nil)
|
|
|
|
}
|
|
|
|
func (d *Doc) each(exec Iterator, prev []string) error {
|
|
|
|
if d.data == nil {
|
|
return nil
|
|
}
|
|
|
|
// Define the temporary object so
|
|
// that we can loop over and traverse
|
|
// down the path parts of the data
|
|
|
|
object := d.data
|
|
|
|
// If the value found at the current
|
|
// path part is an object, then move
|
|
// to the next part of the path
|
|
|
|
if m, ok := object.(map[string]interface{}); ok {
|
|
exec(d.join(prev), make(map[string]interface{}))
|
|
for k, v := range m {
|
|
var keep []string
|
|
keep = append(keep, prev...)
|
|
keep = append(keep, k)
|
|
Consume(v).each(exec, keep)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// If the value found at the current
|
|
// path part is an array, then perform
|
|
// the query on the specified items
|
|
|
|
if a, ok := object.([]interface{}); ok {
|
|
exec(d.join(prev), make([]interface{}, len(a)))
|
|
for i, v := range a {
|
|
var keep []string
|
|
keep = append(keep, prev...)
|
|
keep = append(keep, fmt.Sprintf("[%d]", i))
|
|
Consume(v).each(exec, keep)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
return exec(d.join(prev), object)
|
|
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
|
|
// Walk walks the value or values at a specified path.
|
|
func (d *Doc) Walk(exec Iterator, path ...string) error {
|
|
|
|
path = d.path(path...)
|
|
|
|
return d.walk(exec, nil, path...)
|
|
|
|
}
|
|
|
|
func (d *Doc) walk(exec Iterator, prev []string, path ...string) error {
|
|
|
|
if len(path) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// If the value found at the current
|
|
// path part is undefined, then ensure
|
|
// that it is an object
|
|
|
|
if d.data == nil {
|
|
d.data = map[string]interface{}{}
|
|
}
|
|
|
|
// Define the temporary object so
|
|
// that we can loop over and traverse
|
|
// down the path parts of the data
|
|
|
|
object := d.data
|
|
|
|
// Loop over each part of the path
|
|
// whilst detecting if the data at
|
|
// the current path is an {} or []
|
|
|
|
for k, p := range path {
|
|
|
|
// If the value found at the current
|
|
// path part is an object, then move
|
|
// to the next part of the path
|
|
|
|
if m, ok := object.(map[string]interface{}); ok {
|
|
if object, ok = m[trim(p)]; !ok {
|
|
return exec(d.join(prev, path), nil)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// If the value found at the current
|
|
// path part is an array, then perform
|
|
// the query on the specified items
|
|
|
|
if a, ok := object.([]interface{}); ok {
|
|
|
|
c, i, r := d.what(p, a, choose)
|
|
|
|
if len(c) == 0 {
|
|
return fmt.Errorf("No item with index %s in array, using path %s", p, path)
|
|
}
|
|
|
|
if r == one {
|
|
if k == len(path)-1 {
|
|
var keep []string
|
|
keep = append(keep, prev...)
|
|
keep = append(keep, path[:k]...)
|
|
keep = append(keep, fmt.Sprintf("[%d]", i[0]))
|
|
return exec(d.join(keep), c[0])
|
|
} else {
|
|
var keep []string
|
|
keep = append(keep, prev...)
|
|
keep = append(keep, path[:k]...)
|
|
keep = append(keep, fmt.Sprintf("[%d]", i[0]))
|
|
return Consume(c[0]).walk(exec, keep, path[k+1:]...)
|
|
}
|
|
}
|
|
|
|
if r == many {
|
|
for j, v := range c {
|
|
if k == len(path)-1 {
|
|
var keep []string
|
|
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 {
|
|
return err
|
|
}
|
|
} else {
|
|
var keep []string
|
|
keep = append(keep, prev...)
|
|
keep = append(keep, path[:k]...)
|
|
keep = append(keep, fmt.Sprintf("[%d]", i[j]))
|
|
if err := Consume(v).walk(exec, keep, path[k+1:]...); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
}
|
|
|
|
// 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 exec(d.join(prev, path), object)
|
|
|
|
}
|