update dependencies (#6267)
Signed-off-by: hongming <coder.scala@gmail.com>
This commit is contained in:
314
vendor/github.com/open-policy-agent/opa/storage/inmem/ast.go
generated
vendored
Normal file
314
vendor/github.com/open-policy-agent/opa/storage/inmem/ast.go
generated
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
// Copyright 2024 The OPA Authors. All rights reserved.
|
||||
// Use of this source code is governed by an Apache2
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package inmem
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/storage"
|
||||
"github.com/open-policy-agent/opa/storage/internal/errors"
|
||||
"github.com/open-policy-agent/opa/storage/internal/ptr"
|
||||
)
|
||||
|
||||
type updateAST struct {
|
||||
path storage.Path // data path modified by update
|
||||
remove bool // indicates whether update removes the value at path
|
||||
value ast.Value // value to add/replace at path (ignored if remove is true)
|
||||
}
|
||||
|
||||
func (u *updateAST) Path() storage.Path {
|
||||
return u.path
|
||||
}
|
||||
|
||||
func (u *updateAST) Remove() bool {
|
||||
return u.remove
|
||||
}
|
||||
|
||||
func (u *updateAST) Set(v interface{}) {
|
||||
if v, ok := v.(ast.Value); ok {
|
||||
u.value = v
|
||||
} else {
|
||||
panic("illegal value type") // FIXME: do conversion?
|
||||
}
|
||||
}
|
||||
|
||||
func (u *updateAST) Value() interface{} {
|
||||
return u.value
|
||||
}
|
||||
|
||||
func (u *updateAST) Relative(path storage.Path) dataUpdate {
|
||||
cpy := *u
|
||||
cpy.path = cpy.path[len(path):]
|
||||
return &cpy
|
||||
}
|
||||
|
||||
func (u *updateAST) Apply(v interface{}) interface{} {
|
||||
if len(u.path) == 0 {
|
||||
return u.value
|
||||
}
|
||||
|
||||
data, ok := v.(ast.Value)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("illegal value type %T, expected ast.Value", v))
|
||||
}
|
||||
|
||||
if u.remove {
|
||||
newV, err := removeInAst(data, u.path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return newV
|
||||
}
|
||||
|
||||
// If we're not removing, we're replacing (adds are turned into replaces during updateAST creation).
|
||||
newV, err := setInAst(data, u.path, u.value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return newV
|
||||
}
|
||||
|
||||
func newUpdateAST(data interface{}, op storage.PatchOp, path storage.Path, idx int, value ast.Value) (*updateAST, error) {
|
||||
|
||||
switch data.(type) {
|
||||
case ast.Null, ast.Boolean, ast.Number, ast.String:
|
||||
return nil, errors.NewNotFoundError(path)
|
||||
}
|
||||
|
||||
switch data := data.(type) {
|
||||
case ast.Object:
|
||||
return newUpdateObjectAST(data, op, path, idx, value)
|
||||
|
||||
case *ast.Array:
|
||||
return newUpdateArrayAST(data, op, path, idx, value)
|
||||
}
|
||||
|
||||
return nil, &storage.Error{
|
||||
Code: storage.InternalErr,
|
||||
Message: "invalid data value encountered",
|
||||
}
|
||||
}
|
||||
|
||||
func newUpdateArrayAST(data *ast.Array, op storage.PatchOp, path storage.Path, idx int, value ast.Value) (*updateAST, error) {
|
||||
|
||||
if idx == len(path)-1 {
|
||||
if path[idx] == "-" || path[idx] == strconv.Itoa(data.Len()) {
|
||||
if op != storage.AddOp {
|
||||
return nil, invalidPatchError("%v: invalid patch path", path)
|
||||
}
|
||||
|
||||
cpy := data.Copy()
|
||||
cpy = cpy.Append(ast.NewTerm(value))
|
||||
return &updateAST{path[:len(path)-1], false, cpy}, nil
|
||||
}
|
||||
|
||||
pos, err := ptr.ValidateASTArrayIndex(data, path[idx], path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch op {
|
||||
case storage.AddOp:
|
||||
var results []*ast.Term
|
||||
for i := 0; i < data.Len(); i++ {
|
||||
if i == pos {
|
||||
results = append(results, ast.NewTerm(value))
|
||||
}
|
||||
results = append(results, data.Elem(i))
|
||||
}
|
||||
|
||||
return &updateAST{path[:len(path)-1], false, ast.NewArray(results...)}, nil
|
||||
|
||||
case storage.RemoveOp:
|
||||
var results []*ast.Term
|
||||
for i := 0; i < data.Len(); i++ {
|
||||
if i != pos {
|
||||
results = append(results, data.Elem(i))
|
||||
}
|
||||
}
|
||||
return &updateAST{path[:len(path)-1], false, ast.NewArray(results...)}, nil
|
||||
|
||||
default:
|
||||
var results []*ast.Term
|
||||
for i := 0; i < data.Len(); i++ {
|
||||
if i == pos {
|
||||
results = append(results, ast.NewTerm(value))
|
||||
} else {
|
||||
results = append(results, data.Elem(i))
|
||||
}
|
||||
}
|
||||
|
||||
return &updateAST{path[:len(path)-1], false, ast.NewArray(results...)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
pos, err := ptr.ValidateASTArrayIndex(data, path[idx], path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newUpdateAST(data.Elem(pos).Value, op, path, idx+1, value)
|
||||
}
|
||||
|
||||
func newUpdateObjectAST(data ast.Object, op storage.PatchOp, path storage.Path, idx int, value ast.Value) (*updateAST, error) {
|
||||
key := ast.StringTerm(path[idx])
|
||||
val := data.Get(key)
|
||||
|
||||
if idx == len(path)-1 {
|
||||
switch op {
|
||||
case storage.ReplaceOp, storage.RemoveOp:
|
||||
if val == nil {
|
||||
return nil, errors.NewNotFoundError(path)
|
||||
}
|
||||
}
|
||||
return &updateAST{path, op == storage.RemoveOp, value}, nil
|
||||
}
|
||||
|
||||
if val != nil {
|
||||
return newUpdateAST(val.Value, op, path, idx+1, value)
|
||||
}
|
||||
|
||||
return nil, errors.NewNotFoundError(path)
|
||||
}
|
||||
|
||||
func interfaceToValue(v interface{}) (ast.Value, error) {
|
||||
if v, ok := v.(ast.Value); ok {
|
||||
return v, nil
|
||||
}
|
||||
return ast.InterfaceToValue(v)
|
||||
}
|
||||
|
||||
// setInAst updates the value in the AST at the given path with the given value.
|
||||
// Values can only be replaced in arrays, not added.
|
||||
// Values for new keys can be added to objects
|
||||
func setInAst(data ast.Value, path storage.Path, value ast.Value) (ast.Value, error) {
|
||||
if len(path) == 0 {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
switch data := data.(type) {
|
||||
case ast.Object:
|
||||
return setInAstObject(data, path, value)
|
||||
case *ast.Array:
|
||||
return setInAstArray(data, path, value)
|
||||
default:
|
||||
return nil, fmt.Errorf("illegal value type %T, expected ast.Object or ast.Array", data)
|
||||
}
|
||||
}
|
||||
|
||||
func setInAstObject(obj ast.Object, path storage.Path, value ast.Value) (ast.Value, error) {
|
||||
key := ast.StringTerm(path[0])
|
||||
|
||||
if len(path) == 1 {
|
||||
obj.Insert(key, ast.NewTerm(value))
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
child := obj.Get(key)
|
||||
newChild, err := setInAst(child.Value, path[1:], value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj.Insert(key, ast.NewTerm(newChild))
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func setInAstArray(arr *ast.Array, path storage.Path, value ast.Value) (ast.Value, error) {
|
||||
idx, err := strconv.Atoi(path[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("illegal array index %v: %v", path[0], err)
|
||||
}
|
||||
|
||||
if idx < 0 || idx >= arr.Len() {
|
||||
return arr, nil
|
||||
}
|
||||
|
||||
if len(path) == 1 {
|
||||
arr.Set(idx, ast.NewTerm(value))
|
||||
return arr, nil
|
||||
}
|
||||
|
||||
child := arr.Elem(idx)
|
||||
newChild, err := setInAst(child.Value, path[1:], value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arr.Set(idx, ast.NewTerm(newChild))
|
||||
return arr, nil
|
||||
}
|
||||
|
||||
func removeInAst(value ast.Value, path storage.Path) (ast.Value, error) {
|
||||
if len(path) == 0 {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
switch value := value.(type) {
|
||||
case ast.Object:
|
||||
return removeInAstObject(value, path)
|
||||
case *ast.Array:
|
||||
return removeInAstArray(value, path)
|
||||
default:
|
||||
return nil, fmt.Errorf("illegal value type %T, expected ast.Object or ast.Array", value)
|
||||
}
|
||||
}
|
||||
|
||||
func removeInAstObject(obj ast.Object, path storage.Path) (ast.Value, error) {
|
||||
key := ast.StringTerm(path[0])
|
||||
|
||||
if len(path) == 1 {
|
||||
var items [][2]*ast.Term
|
||||
// Note: possibly expensive operation for large data.
|
||||
obj.Foreach(func(k *ast.Term, v *ast.Term) {
|
||||
if k.Equal(key) {
|
||||
return
|
||||
}
|
||||
items = append(items, [2]*ast.Term{k, v})
|
||||
})
|
||||
return ast.NewObject(items...), nil
|
||||
}
|
||||
|
||||
if child := obj.Get(key); child != nil {
|
||||
updatedChild, err := removeInAst(child.Value, path[1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj.Insert(key, ast.NewTerm(updatedChild))
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func removeInAstArray(arr *ast.Array, path storage.Path) (ast.Value, error) {
|
||||
idx, err := strconv.Atoi(path[0])
|
||||
if err != nil {
|
||||
// We expect the path to be valid at this point.
|
||||
return arr, nil
|
||||
}
|
||||
|
||||
if idx < 0 || idx >= arr.Len() {
|
||||
return arr, err
|
||||
}
|
||||
|
||||
if len(path) == 1 {
|
||||
var elems []*ast.Term
|
||||
// Note: possibly expensive operation for large data.
|
||||
for i := 0; i < arr.Len(); i++ {
|
||||
if i == idx {
|
||||
continue
|
||||
}
|
||||
elems = append(elems, arr.Elem(i))
|
||||
}
|
||||
return ast.NewArray(elems...), nil
|
||||
}
|
||||
|
||||
updatedChild, err := removeInAst(arr.Elem(idx).Value, path[1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arr.Set(idx, ast.NewTerm(updatedChild))
|
||||
return arr, nil
|
||||
}
|
||||
66
vendor/github.com/open-policy-agent/opa/storage/inmem/inmem.go
generated
vendored
66
vendor/github.com/open-policy-agent/opa/storage/inmem/inmem.go
generated
vendored
@@ -24,6 +24,7 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/internal/merge"
|
||||
"github.com/open-policy-agent/opa/storage"
|
||||
"github.com/open-policy-agent/opa/util"
|
||||
@@ -37,16 +38,22 @@ func New() storage.Store {
|
||||
// NewWithOpts returns an empty in-memory store, with extra options passed.
|
||||
func NewWithOpts(opts ...Opt) storage.Store {
|
||||
s := &store{
|
||||
data: map[string]interface{}{},
|
||||
triggers: map[*handle]storage.TriggerConfig{},
|
||||
policies: map[string][]byte{},
|
||||
roundTripOnWrite: true,
|
||||
triggers: map[*handle]storage.TriggerConfig{},
|
||||
policies: map[string][]byte{},
|
||||
roundTripOnWrite: true,
|
||||
returnASTValuesOnRead: false,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(s)
|
||||
}
|
||||
|
||||
if s.returnASTValuesOnRead {
|
||||
s.data = ast.NewObject()
|
||||
} else {
|
||||
s.data = map[string]interface{}{}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -55,7 +62,7 @@ func NewFromObject(data map[string]interface{}) storage.Store {
|
||||
return NewFromObjectWithOpts(data)
|
||||
}
|
||||
|
||||
// NewFromObject returns a new in-memory store from the supplied data object, with the
|
||||
// NewFromObjectWithOpts returns a new in-memory store from the supplied data object, with the
|
||||
// options passed.
|
||||
func NewFromObjectWithOpts(data map[string]interface{}, opts ...Opt) storage.Store {
|
||||
db := NewWithOpts(opts...)
|
||||
@@ -94,13 +101,18 @@ type store struct {
|
||||
rmu sync.RWMutex // reader-writer lock
|
||||
wmu sync.Mutex // writer lock
|
||||
xid uint64 // last generated transaction id
|
||||
data map[string]interface{} // raw data
|
||||
data interface{} // raw or AST data
|
||||
policies map[string][]byte // raw policies
|
||||
triggers map[*handle]storage.TriggerConfig // registered triggers
|
||||
|
||||
// roundTripOnWrite, if true, means that every call to Write round trips the
|
||||
// data through JSON before adding the data to the store. Defaults to true.
|
||||
roundTripOnWrite bool
|
||||
|
||||
// returnASTValuesOnRead, if true, means that the store will eagerly convert data to AST values,
|
||||
// and return them on Read.
|
||||
// FIXME: naming(?)
|
||||
returnASTValuesOnRead bool
|
||||
}
|
||||
|
||||
type handle struct {
|
||||
@@ -295,7 +307,13 @@ func (db *store) Read(_ context.Context, txn storage.Transaction, path storage.P
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return underlying.Read(path)
|
||||
|
||||
v, err := underlying.Read(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (db *store) Write(_ context.Context, txn storage.Transaction, op storage.PatchOp, path storage.Path, value interface{}) error {
|
||||
@@ -327,11 +345,45 @@ func (h *handle) Unregister(_ context.Context, txn storage.Transaction) {
|
||||
}
|
||||
|
||||
func (db *store) runOnCommitTriggers(ctx context.Context, txn storage.Transaction, event storage.TriggerEvent) {
|
||||
if db.returnASTValuesOnRead && len(db.triggers) > 0 {
|
||||
// FIXME: Not very performant for large data.
|
||||
|
||||
dataEvents := make([]storage.DataEvent, 0, len(event.Data))
|
||||
|
||||
for _, dataEvent := range event.Data {
|
||||
if astData, ok := dataEvent.Data.(ast.Value); ok {
|
||||
jsn, err := ast.ValueToInterface(astData, illegalResolver{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dataEvents = append(dataEvents, storage.DataEvent{
|
||||
Path: dataEvent.Path,
|
||||
Data: jsn,
|
||||
Removed: dataEvent.Removed,
|
||||
})
|
||||
} else {
|
||||
dataEvents = append(dataEvents, dataEvent)
|
||||
}
|
||||
}
|
||||
|
||||
event = storage.TriggerEvent{
|
||||
Policy: event.Policy,
|
||||
Data: dataEvents,
|
||||
Context: event.Context,
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range db.triggers {
|
||||
t.OnCommit(ctx, txn, event)
|
||||
}
|
||||
}
|
||||
|
||||
type illegalResolver struct{}
|
||||
|
||||
func (illegalResolver) Resolve(ref ast.Ref) (interface{}, error) {
|
||||
return nil, fmt.Errorf("illegal value: %v", ref)
|
||||
}
|
||||
|
||||
func (db *store) underlying(txn storage.Transaction) (*transaction, error) {
|
||||
underlying, ok := txn.(*transaction)
|
||||
if !ok {
|
||||
|
||||
12
vendor/github.com/open-policy-agent/opa/storage/inmem/opts.go
generated
vendored
12
vendor/github.com/open-policy-agent/opa/storage/inmem/opts.go
generated
vendored
@@ -23,3 +23,15 @@ func OptRoundTripOnWrite(enabled bool) Opt {
|
||||
s.roundTripOnWrite = enabled
|
||||
}
|
||||
}
|
||||
|
||||
// OptReturnASTValuesOnRead sets whether data values added to the store should be
|
||||
// eagerly converted to AST values, which are then returned on read.
|
||||
//
|
||||
// When enabled, this feature does not sanity check data before converting it to AST values,
|
||||
// which may result in panics if the data is not valid. Callers should ensure that passed data
|
||||
// can be serialized to AST values; otherwise, it's recommended to also enable OptRoundTripOnWrite.
|
||||
func OptReturnASTValuesOnRead(enabled bool) Opt {
|
||||
return func(s *store) {
|
||||
s.returnASTValuesOnRead = enabled
|
||||
}
|
||||
}
|
||||
|
||||
195
vendor/github.com/open-policy-agent/opa/storage/inmem/txn.go
generated
vendored
195
vendor/github.com/open-policy-agent/opa/storage/inmem/txn.go
generated
vendored
@@ -9,6 +9,7 @@ import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/internal/deepcopy"
|
||||
"github.com/open-policy-agent/opa/storage"
|
||||
"github.com/open-policy-agent/opa/storage/internal/errors"
|
||||
@@ -76,13 +77,13 @@ func (txn *transaction) Write(op storage.PatchOp, path storage.Path, value inter
|
||||
}
|
||||
|
||||
for curr := txn.updates.Front(); curr != nil; {
|
||||
update := curr.Value.(*update)
|
||||
update := curr.Value.(dataUpdate)
|
||||
|
||||
// Check if new update masks existing update exactly. In this case, the
|
||||
// existing update can be removed and no other updates have to be
|
||||
// visited (because no two updates overlap.)
|
||||
if update.path.Equal(path) {
|
||||
if update.remove {
|
||||
if update.Path().Equal(path) {
|
||||
if update.Remove() {
|
||||
if op != storage.AddOp {
|
||||
return errors.NewNotFoundError(path)
|
||||
}
|
||||
@@ -94,7 +95,7 @@ func (txn *transaction) Write(op storage.PatchOp, path storage.Path, value inter
|
||||
// Check if new update masks existing update. In this case, the
|
||||
// existing update has to be removed but other updates may overlap, so
|
||||
// we must continue.
|
||||
if update.path.HasPrefix(path) {
|
||||
if update.Path().HasPrefix(path) {
|
||||
remove := curr
|
||||
curr = curr.Next()
|
||||
txn.updates.Remove(remove)
|
||||
@@ -103,23 +104,23 @@ func (txn *transaction) Write(op storage.PatchOp, path storage.Path, value inter
|
||||
|
||||
// Check if new update modifies existing update. In this case, the
|
||||
// existing update is mutated.
|
||||
if path.HasPrefix(update.path) {
|
||||
if update.remove {
|
||||
if path.HasPrefix(update.Path()) {
|
||||
if update.Remove() {
|
||||
return errors.NewNotFoundError(path)
|
||||
}
|
||||
suffix := path[len(update.path):]
|
||||
newUpdate, err := newUpdate(update.value, op, suffix, 0, value)
|
||||
suffix := path[len(update.Path()):]
|
||||
newUpdate, err := txn.db.newUpdate(update.Value(), op, suffix, 0, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
update.value = newUpdate.Apply(update.value)
|
||||
update.Set(newUpdate.Apply(update.Value()))
|
||||
return nil
|
||||
}
|
||||
|
||||
curr = curr.Next()
|
||||
}
|
||||
|
||||
update, err := newUpdate(txn.db.data, op, path, 0, value)
|
||||
update, err := txn.db.newUpdate(txn.db.data, op, path, 0, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -132,72 +133,115 @@ func (txn *transaction) updateRoot(op storage.PatchOp, value interface{}) error
|
||||
if op == storage.RemoveOp {
|
||||
return invalidPatchError(rootCannotBeRemovedMsg)
|
||||
}
|
||||
if _, ok := value.(map[string]interface{}); !ok {
|
||||
return invalidPatchError(rootMustBeObjectMsg)
|
||||
|
||||
var update any
|
||||
if txn.db.returnASTValuesOnRead {
|
||||
valueAST, err := interfaceToValue(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := valueAST.(ast.Object); !ok {
|
||||
return invalidPatchError(rootMustBeObjectMsg)
|
||||
}
|
||||
|
||||
update = &updateAST{
|
||||
path: storage.Path{},
|
||||
remove: false,
|
||||
value: valueAST,
|
||||
}
|
||||
} else {
|
||||
if _, ok := value.(map[string]interface{}); !ok {
|
||||
return invalidPatchError(rootMustBeObjectMsg)
|
||||
}
|
||||
|
||||
update = &updateRaw{
|
||||
path: storage.Path{},
|
||||
remove: false,
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
txn.updates.Init()
|
||||
txn.updates.PushFront(&update{
|
||||
path: storage.Path{},
|
||||
remove: false,
|
||||
value: value,
|
||||
})
|
||||
txn.updates.PushFront(update)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txn *transaction) Commit() (result storage.TriggerEvent) {
|
||||
result.Context = txn.context
|
||||
for curr := txn.updates.Front(); curr != nil; curr = curr.Next() {
|
||||
action := curr.Value.(*update)
|
||||
updated := action.Apply(txn.db.data)
|
||||
txn.db.data = updated.(map[string]interface{})
|
||||
action := curr.Value.(dataUpdate)
|
||||
txn.db.data = action.Apply(txn.db.data)
|
||||
|
||||
result.Data = append(result.Data, storage.DataEvent{
|
||||
Path: action.path,
|
||||
Data: action.value,
|
||||
Removed: action.remove,
|
||||
Path: action.Path(),
|
||||
Data: action.Value(),
|
||||
Removed: action.Remove(),
|
||||
})
|
||||
}
|
||||
for id, update := range txn.policies {
|
||||
if update.remove {
|
||||
for id, upd := range txn.policies {
|
||||
if upd.remove {
|
||||
delete(txn.db.policies, id)
|
||||
} else {
|
||||
txn.db.policies[id] = update.value
|
||||
txn.db.policies[id] = upd.value
|
||||
}
|
||||
|
||||
result.Policy = append(result.Policy, storage.PolicyEvent{
|
||||
ID: id,
|
||||
Data: update.value,
|
||||
Removed: update.remove,
|
||||
Data: upd.value,
|
||||
Removed: upd.remove,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func pointer(v interface{}, path storage.Path) (interface{}, error) {
|
||||
if v, ok := v.(ast.Value); ok {
|
||||
return ptr.ValuePtr(v, path)
|
||||
}
|
||||
return ptr.Ptr(v, path)
|
||||
}
|
||||
|
||||
func deepcpy(v interface{}) interface{} {
|
||||
if v, ok := v.(ast.Value); ok {
|
||||
var cpy ast.Value
|
||||
|
||||
switch data := v.(type) {
|
||||
case ast.Object:
|
||||
cpy = data.Copy()
|
||||
case *ast.Array:
|
||||
cpy = data.Copy()
|
||||
}
|
||||
|
||||
return cpy
|
||||
}
|
||||
return deepcopy.DeepCopy(v)
|
||||
}
|
||||
|
||||
func (txn *transaction) Read(path storage.Path) (interface{}, error) {
|
||||
|
||||
if !txn.write {
|
||||
return ptr.Ptr(txn.db.data, path)
|
||||
return pointer(txn.db.data, path)
|
||||
}
|
||||
|
||||
merge := []*update{}
|
||||
var merge []dataUpdate
|
||||
|
||||
for curr := txn.updates.Front(); curr != nil; curr = curr.Next() {
|
||||
|
||||
update := curr.Value.(*update)
|
||||
upd := curr.Value.(dataUpdate)
|
||||
|
||||
if path.HasPrefix(update.path) {
|
||||
if update.remove {
|
||||
if path.HasPrefix(upd.Path()) {
|
||||
if upd.Remove() {
|
||||
return nil, errors.NewNotFoundError(path)
|
||||
}
|
||||
return ptr.Ptr(update.value, path[len(update.path):])
|
||||
return pointer(upd.Value(), path[len(upd.Path()):])
|
||||
}
|
||||
|
||||
if update.path.HasPrefix(path) {
|
||||
merge = append(merge, update)
|
||||
if upd.Path().HasPrefix(path) {
|
||||
merge = append(merge, upd)
|
||||
}
|
||||
}
|
||||
|
||||
data, err := ptr.Ptr(txn.db.data, path)
|
||||
data, err := pointer(txn.db.data, path)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -207,7 +251,7 @@ func (txn *transaction) Read(path storage.Path) (interface{}, error) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
cpy := deepcopy.DeepCopy(data)
|
||||
cpy := deepcpy(data)
|
||||
|
||||
for _, update := range merge {
|
||||
cpy = update.Relative(path).Apply(cpy)
|
||||
@@ -266,15 +310,44 @@ func (txn *transaction) DeletePolicy(id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type dataUpdate interface {
|
||||
Path() storage.Path
|
||||
Remove() bool
|
||||
Apply(interface{}) interface{}
|
||||
Relative(path storage.Path) dataUpdate
|
||||
Set(interface{})
|
||||
Value() interface{}
|
||||
}
|
||||
|
||||
// update contains state associated with an update to be applied to the
|
||||
// in-memory data store.
|
||||
type update struct {
|
||||
type updateRaw struct {
|
||||
path storage.Path // data path modified by update
|
||||
remove bool // indicates whether update removes the value at path
|
||||
value interface{} // value to add/replace at path (ignored if remove is true)
|
||||
}
|
||||
|
||||
func newUpdate(data interface{}, op storage.PatchOp, path storage.Path, idx int, value interface{}) (*update, error) {
|
||||
func (db *store) newUpdate(data interface{}, op storage.PatchOp, path storage.Path, idx int, value interface{}) (dataUpdate, error) {
|
||||
if db.returnASTValuesOnRead {
|
||||
astData, err := interfaceToValue(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
astValue, err := interfaceToValue(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newUpdateAST(astData, op, path, idx, astValue)
|
||||
}
|
||||
return newUpdateRaw(data, op, path, idx, value)
|
||||
}
|
||||
|
||||
func newUpdateRaw(data interface{}, op storage.PatchOp, path storage.Path, idx int, value interface{}) (dataUpdate, error) {
|
||||
|
||||
switch data.(type) {
|
||||
case nil, bool, json.Number, string:
|
||||
return nil, errors.NewNotFoundError(path)
|
||||
}
|
||||
|
||||
switch data := data.(type) {
|
||||
case map[string]interface{}:
|
||||
@@ -282,9 +355,6 @@ func newUpdate(data interface{}, op storage.PatchOp, path storage.Path, idx int,
|
||||
|
||||
case []interface{}:
|
||||
return newUpdateArray(data, op, path, idx, value)
|
||||
|
||||
case nil, bool, json.Number, string:
|
||||
return nil, errors.NewNotFoundError(path)
|
||||
}
|
||||
|
||||
return nil, &storage.Error{
|
||||
@@ -293,7 +363,7 @@ func newUpdate(data interface{}, op storage.PatchOp, path storage.Path, idx int,
|
||||
}
|
||||
}
|
||||
|
||||
func newUpdateArray(data []interface{}, op storage.PatchOp, path storage.Path, idx int, value interface{}) (*update, error) {
|
||||
func newUpdateArray(data []interface{}, op storage.PatchOp, path storage.Path, idx int, value interface{}) (dataUpdate, error) {
|
||||
|
||||
if idx == len(path)-1 {
|
||||
if path[idx] == "-" || path[idx] == strconv.Itoa(len(data)) {
|
||||
@@ -303,7 +373,7 @@ func newUpdateArray(data []interface{}, op storage.PatchOp, path storage.Path, i
|
||||
cpy := make([]interface{}, len(data)+1)
|
||||
copy(cpy, data)
|
||||
cpy[len(data)] = value
|
||||
return &update{path[:len(path)-1], false, cpy}, nil
|
||||
return &updateRaw{path[:len(path)-1], false, cpy}, nil
|
||||
}
|
||||
|
||||
pos, err := ptr.ValidateArrayIndex(data, path[idx], path)
|
||||
@@ -317,19 +387,19 @@ func newUpdateArray(data []interface{}, op storage.PatchOp, path storage.Path, i
|
||||
copy(cpy[:pos], data[:pos])
|
||||
copy(cpy[pos+1:], data[pos:])
|
||||
cpy[pos] = value
|
||||
return &update{path[:len(path)-1], false, cpy}, nil
|
||||
return &updateRaw{path[:len(path)-1], false, cpy}, nil
|
||||
|
||||
case storage.RemoveOp:
|
||||
cpy := make([]interface{}, len(data)-1)
|
||||
copy(cpy[:pos], data[:pos])
|
||||
copy(cpy[pos:], data[pos+1:])
|
||||
return &update{path[:len(path)-1], false, cpy}, nil
|
||||
return &updateRaw{path[:len(path)-1], false, cpy}, nil
|
||||
|
||||
default:
|
||||
cpy := make([]interface{}, len(data))
|
||||
copy(cpy, data)
|
||||
cpy[pos] = value
|
||||
return &update{path[:len(path)-1], false, cpy}, nil
|
||||
return &updateRaw{path[:len(path)-1], false, cpy}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,10 +408,10 @@ func newUpdateArray(data []interface{}, op storage.PatchOp, path storage.Path, i
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newUpdate(data[pos], op, path, idx+1, value)
|
||||
return newUpdateRaw(data[pos], op, path, idx+1, value)
|
||||
}
|
||||
|
||||
func newUpdateObject(data map[string]interface{}, op storage.PatchOp, path storage.Path, idx int, value interface{}) (*update, error) {
|
||||
func newUpdateObject(data map[string]interface{}, op storage.PatchOp, path storage.Path, idx int, value interface{}) (dataUpdate, error) {
|
||||
|
||||
if idx == len(path)-1 {
|
||||
switch op {
|
||||
@@ -350,16 +420,25 @@ func newUpdateObject(data map[string]interface{}, op storage.PatchOp, path stora
|
||||
return nil, errors.NewNotFoundError(path)
|
||||
}
|
||||
}
|
||||
return &update{path, op == storage.RemoveOp, value}, nil
|
||||
return &updateRaw{path, op == storage.RemoveOp, value}, nil
|
||||
}
|
||||
|
||||
if data, ok := data[path[idx]]; ok {
|
||||
return newUpdate(data, op, path, idx+1, value)
|
||||
return newUpdateRaw(data, op, path, idx+1, value)
|
||||
}
|
||||
|
||||
return nil, errors.NewNotFoundError(path)
|
||||
}
|
||||
func (u *update) Apply(data interface{}) interface{} {
|
||||
|
||||
func (u *updateRaw) Remove() bool {
|
||||
return u.remove
|
||||
}
|
||||
|
||||
func (u *updateRaw) Path() storage.Path {
|
||||
return u.path
|
||||
}
|
||||
|
||||
func (u *updateRaw) Apply(data interface{}) interface{} {
|
||||
if len(u.path) == 0 {
|
||||
return u.value
|
||||
}
|
||||
@@ -389,7 +468,15 @@ func (u *update) Apply(data interface{}) interface{} {
|
||||
return data
|
||||
}
|
||||
|
||||
func (u *update) Relative(path storage.Path) *update {
|
||||
func (u *updateRaw) Set(v interface{}) {
|
||||
u.value = v
|
||||
}
|
||||
|
||||
func (u *updateRaw) Value() interface{} {
|
||||
return u.value
|
||||
}
|
||||
|
||||
func (u *updateRaw) Relative(path storage.Path) dataUpdate {
|
||||
cpy := *u
|
||||
cpy.path = cpy.path[len(path):]
|
||||
return &cpy
|
||||
|
||||
Reference in New Issue
Block a user