diff options
author | NeonXP <i@neonxp.dev> | 2023-01-04 18:44:58 +0300 |
---|---|---|
committer | NeonXP <i@neonxp.dev> | 2023-01-04 18:44:58 +0300 |
commit | 8716ac3e650075525cab7fb5caf1aa62b3efe55b (patch) | |
tree | f34dcb33400ef6bfd7f01b55a04f59784505c506 /internal/core/core.go | |
parent | e91712e388c530dd5bdfb46f028157a62a60b1e3 (diff) |
Diffstat (limited to 'internal/core/core.go')
-rw-r--r-- | internal/core/core.go | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/internal/core/core.go b/internal/core/core.go new file mode 100644 index 0000000..34718bd --- /dev/null +++ b/internal/core/core.go @@ -0,0 +1,203 @@ +package core + +import ( + "context" + "fmt" + "strconv" + "sync" + + "github.com/dgraph-io/badger/v3" + "github.com/vmihailenco/msgpack/v5" + "go.neonxp.dev/djson/internal/command" + "go.neonxp.dev/djson/internal/node" + "go.neonxp.dev/json" +) + +type Core struct { + storage *badger.DB + root *node.Node + mu sync.RWMutex + json *json.JSON +} + +func New(storage *badger.DB) *Core { + return &Core{ + storage: storage, + root: nil, + json: json.New(node.Factory), + } +} + +func (c *Core) Init(ctx context.Context) error { + return c.storage.View(func(txn *badger.Txn) error { + opts := badger.DefaultIteratorOptions + opts.PrefetchSize = 10 + it := txn.NewIterator(opts) + defer it.Close() + for it.Rewind(); it.Valid(); it.Next() { + item := it.Item() + err := item.Value(func(v []byte) error { + mut := &command.Mutation{} + if err := msgpack.Unmarshal(v, mut); err != nil { + return err + } + return c.apply(ctx, mut) + }) + if err != nil { + return err + } + } + return nil + }) +} + +func (c *Core) Apply(ctx context.Context, mutation *command.Mutation) error { + return c.storage.Update(func(txn *badger.Txn) error { + if err := c.apply(ctx, mutation); err != nil { + return err + } + mb, err := msgpack.Marshal(mutation) + if err != nil { + return err + } + + e := &badger.Entry{ + Key: mutation.ID, + Value: mb, + } + + return txn.SetEntry(e) + }) +} + +func (c *Core) apply(ctx context.Context, mutation *command.Mutation) error { + c.mu.Lock() + defer c.mu.Unlock() + + switch mutation.Type { + case command.Create: + n, err := c.json.Unmarshal(mutation.Data) + if err != nil { + return err + } + if err := c.create(mutation.Path, n.(*node.Node)); err != nil { + return err + } + case command.Merge: + n, err := c.json.Unmarshal(mutation.Data) + if err != nil { + return err + } + if err := c.merge(mutation.Path, n.(*node.Node)); err != nil { + return err + } + case command.Remove: + if err := c.remove(mutation.Path); err != nil { + return err + } + } + return nil +} + +func (c *Core) Query(ctx context.Context, query []string) (json.Node, error) { + return json.Query(c.root, query) +} + +func (c *Core) create(path []string, n *node.Node) error { + if len(path) == 0 { + c.root = n + return nil + } + + path, last := path[:len(path)-1], path[len(path)-1] + parent, err := json.Query(c.root, path) + if err != nil { + return fmt.Errorf("parent node not found") + } + + parentNode, ok := parent.(*node.Node) + if !ok { + return fmt.Errorf("invalid node") + } + + switch parentNode.Type { + case json.ArrayType: + if last == "[]" { + parentNode.Append(n) + return nil + } + idx, err := strconv.Atoi(last) + if err != nil { + return fmt.Errorf("cant use %s as array index", last) + } + if idx < 0 || idx >= parentNode.Len() { + return fmt.Errorf("index %d out of bounds [0, %d]", idx, parentNode.Len()-1) + } + parentNode.SetByIndex(idx, n) + case json.ObjectType: + parentNode.SetKeyValue(last, n) + default: + return fmt.Errorf("cant add node to node of type %s", parentNode.Type) + } + return nil +} + +func (c *Core) merge(path []string, n *node.Node) error { + parent, err := json.Query(c.root, path) + if err != nil { + return fmt.Errorf("parent node not found") + } + + parentNode, ok := parent.(*node.Node) + if !ok { + return fmt.Errorf("invalid node") + } + + if n.Type != parentNode.Type { + return fmt.Errorf("merging nodes must be same type") + } + + switch n.Type { + case json.ObjectType: + parentNode.Merge(n) + case json.ArrayType: + for i := 0; i < n.Len(); i++ { + parentNode.Append(n.Index(i)) + } + default: + return fmt.Errorf("can merge only objects or arrays") + } + return nil +} + +func (c *Core) remove(path []string) error { + if len(path) == 0 { + c.root = nil + return nil + } + path, last := path[:len(path)-1], path[len(path)-1] + parent, err := json.Query(c.root, path) + if err != nil { + return fmt.Errorf("parent node not found") + } + parentNode, ok := parent.(*node.Node) + if !ok { + return fmt.Errorf("invalid node") + } + switch parentNode.Type { + case json.ObjectType: + parentNode.RemoveByKey(last) + case json.ArrayType: + idx, err := strconv.Atoi(last) + if err != nil { + return fmt.Errorf("cant use %s as array index", last) + } + if idx < 0 || idx >= parentNode.Len() { + return fmt.Errorf("index %d out of bounds [0, %d]", idx, parentNode.Len()-1) + } + parentNode.RemoveByIndex(idx) + default: + return fmt.Errorf("can remove children only from object or array") + } + return nil +} |