summaryrefslogtreecommitdiff
path: root/internal/core/core.go
diff options
context:
space:
mode:
authorNeonXP <i@neonxp.dev>2023-01-04 18:44:58 +0300
committerNeonXP <i@neonxp.dev>2023-01-04 18:44:58 +0300
commit8716ac3e650075525cab7fb5caf1aa62b3efe55b (patch)
treef34dcb33400ef6bfd7f01b55a04f59784505c506 /internal/core/core.go
parente91712e388c530dd5bdfb46f028157a62a60b1e3 (diff)
rewriteHEADmaster
Diffstat (limited to 'internal/core/core.go')
-rw-r--r--internal/core/core.go203
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
+}