surrealpatch/db/merge.go
Tobie Morgan Hitchcock d0d566bbd9 Improve database speed
2019-11-20 13:20:27 +00:00

444 lines
8.4 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 db
import (
"sort"
"context"
"github.com/abcum/surreal/cnf"
"github.com/abcum/surreal/sql"
"github.com/abcum/surreal/util/conv"
"github.com/abcum/surreal/util/data"
"github.com/abcum/surreal/util/diff"
)
var main = map[string]struct{}{
"id": {},
"meta": {},
"meta.tb": {},
"meta.id": {},
}
func (d *document) merge(ctx context.Context, met method, data sql.Expr) (err error) {
if err = d.defDoc(ctx, met); err != nil {
return
}
if err = d.defFld(ctx, met); err != nil {
return
}
switch expr := data.(type) {
case *sql.DataExpression:
if err = d.mrgSet(ctx, met, expr); err != nil {
return err
}
case *sql.DiffExpression:
if err = d.mrgDpm(ctx, met, expr); err != nil {
return err
}
case *sql.MergeExpression:
if err = d.mrgAny(ctx, met, expr); err != nil {
return err
}
case *sql.ContentExpression:
if err = d.mrgAll(ctx, met, expr); err != nil {
return err
}
}
if err = d.defFld(ctx, met); err != nil {
return
}
if err = d.mrgFld(ctx, met); err != nil {
return
}
if err = d.defFld(ctx, met); err != nil {
return
}
if err = d.delFld(ctx, met); err != nil {
return
}
d.changed = d.hasChanged(ctx)
return
}
func (d *document) defDoc(ctx context.Context, met method) (err error) {
d.current = d.current.Copy()
return
}
func (d *document) defFld(ctx context.Context, met method) (err error) {
switch d.i.vir {
case true:
d.current.Set(d.id, "id")
d.current.Set(d.id.TB, "meta.tb")
d.current.Set(d.id.ID, "meta.id")
case false:
d.current.Del("meta")
d.current.Set(d.id, "id")
d.current.Set(d.id.TB, "meta.tb")
d.current.Set(d.id.ID, "meta.id")
}
return
}
func (d *document) delFld(ctx context.Context, met method) (err error) {
tb, err := d.i.e.tx.GetTB(ctx, d.key.NS, d.key.DB, d.key.TB)
if err != nil {
return err
}
if tb.Full {
var keys = map[string]struct{}{}
// Get the defined fields
fds, err := d.i.e.tx.AllFD(ctx, d.key.NS, d.key.DB, d.key.TB)
if err != nil {
return err
}
// Loop over the allowed keys
for _, fd := range fds {
d.current.Walk(func(key string, val interface{}, ok bool) (err error) {
keys[key] = struct{}{}
return
}, fd.Name.VA)
}
// Delete any keys which aren't allowed
d.current.Each(func(key string, val interface{}) (err error) {
if _, ok := main[key]; !ok {
if _, ok := keys[key]; !ok {
d.current.Del(key)
}
}
return
})
}
return
}
func (d *document) mrgAll(ctx context.Context, met method, expr *sql.ContentExpression) (err error) {
var obj map[string]interface{}
switch v := expr.Data.(type) {
case map[string]interface{}:
obj = v
case *sql.Param:
p, err := d.i.e.fetch(ctx, v, d.current)
if err != nil {
return err
}
switch v := p.(type) {
case map[string]interface{}:
obj = v
}
}
d.current.Reset()
for k, v := range obj {
d.current.Set(v, k)
}
return
}
func (d *document) mrgAny(ctx context.Context, met method, expr *sql.MergeExpression) (err error) {
var obj map[string]interface{}
switch v := expr.Data.(type) {
case map[string]interface{}:
obj = v
case *sql.Param:
p, err := d.i.e.fetch(ctx, v, d.current)
if err != nil {
return err
}
switch v := p.(type) {
case map[string]interface{}:
obj = v
}
}
for k, v := range obj {
d.current.Set(v, k)
}
return
}
func (d *document) mrgDpm(ctx context.Context, met method, expr *sql.DiffExpression) (err error) {
var obj []interface{}
var old map[string]interface{}
var now map[string]interface{}
switch v := expr.Data.(type) {
case []interface{}:
obj = v
case *sql.Param:
p, err := d.i.e.fetch(ctx, v, d.current)
if err != nil {
return err
}
switch v := p.(type) {
case []interface{}:
obj = v
}
}
old = d.current.Data().(map[string]interface{})
now = diff.Patch(old, obj)
d.current = data.Consume(now)
return
}
func (d *document) mrgSet(ctx context.Context, met method, expr *sql.DataExpression) (err error) {
for _, v := range expr.Data {
if i, ok := v.LHS.(*sql.Ident); ok {
n, err := d.i.e.fetch(ctx, v.RHS, d.current)
if err != nil {
return err
}
switch v.Op {
case sql.EQ:
switch n.(type) {
default:
d.current.Set(n, i.VA)
case *sql.Void:
d.current.Del(i.VA)
}
case sql.INC:
d.current.Inc(n, i.VA)
case sql.DEC:
d.current.Dec(n, i.VA)
}
}
}
return
}
func (d *document) mrgFld(ctx context.Context, met method) (err error) {
fds, err := d.i.e.tx.AllFD(ctx, d.key.NS, d.key.DB, d.key.TB)
if err != nil {
return err
}
// Sort the fields according to their
// priority so that fields which depend
// on another field can be processed
// after that field in a specific order.
sort.Slice(fds, func(i, j int) bool {
return fds[i].Priority < fds[j].Priority
})
// Loop through each field and check to
// see if it might be a specific type.
// This is because when updating records
// using json, there is no specific type
// for a 'datetime' and 'record'.
d.current.Each(func(key string, val interface{}) (err error) {
if val, ok := conv.MightBe(val); ok {
d.current.Iff(val, key)
}
return nil
})
// Loop over each of the defined fields
// and process them fully, applying the
// VALUE and ASSERT clauses sequentially.
for _, fd := range fds {
err = d.current.Walk(func(key string, val interface{}, exi bool) error {
var old = d.initial.Get(key).Data()
// Ensure object and arrays are set
val = conv.MustBe(fd.Type, val)
// Ensure the field is the correct type
if val != nil {
if now, err := conv.ConvertTo(fd.Type, fd.Kind, val); err != nil {
val = nil
} else {
val = now
}
}
// We are setting the value of the field
if fd.Value != nil && d.i.e.opts.fields {
// Reset the variables
vars := data.New()
vars.Set(val, varKeyValue)
vars.Set(val, varKeyAfter)
vars.Set(old, varKeyBefore)
ctx = context.WithValue(ctx, ctxKeySpec, vars)
if now, err := d.i.e.fetch(ctx, fd.Value, d.current); err != nil {
return err
} else {
val = now
}
}
// We are checking the value of the field
if fd.Assert != nil && d.i.e.opts.fields {
// Reset the variables
vars := data.New()
vars.Set(val, varKeyValue)
vars.Set(val, varKeyAfter)
vars.Set(old, varKeyBefore)
ctx = context.WithValue(ctx, ctxKeySpec, vars)
if chk, err := d.i.e.fetch(ctx, fd.Assert, d.current); err != nil {
return err
} else if chk, ok := chk.(bool); ok && !chk {
return &FieldError{field: key, found: val, check: fd.Assert}
}
}
// We are checking the permissions of the field
if fd.Perms != nil && perm(ctx) > cnf.AuthDB {
// Reset the variables
vars := data.New()
vars.Set(val, varKeyValue)
vars.Set(val, varKeyAfter)
vars.Set(old, varKeyBefore)
ctx = context.WithValue(ctx, ctxKeySpec, vars)
switch p := fd.Perms.(type) {
case *sql.PermExpression:
switch met {
case _CREATE:
if v, err := d.i.e.fetch(ctx, p.Create, d.current); err != nil {
return err
} else {
if b, ok := v.(bool); !ok || !b {
val = old
}
}
case _UPDATE:
if v, err := d.i.e.fetch(ctx, p.Update, d.current); err != nil {
return err
} else {
if b, ok := v.(bool); !ok || !b {
val = old
}
}
case _DELETE:
if v, err := d.i.e.fetch(ctx, p.Delete, d.current); err != nil {
return err
} else {
if b, ok := v.(bool); !ok || !b {
val = old
}
}
}
}
}
// We are setting the value of the field
switch val.(type) {
default:
if exi {
d.current.Set(val, key)
} else {
d.current.Iff(val, key)
}
case *sql.Void:
d.current.Del(key)
}
return nil
}, fd.Name.VA)
if err != nil {
return
}
}
return
}