surrealpatch/db/util.go

355 lines
6.8 KiB
Go
Raw Normal View History

// 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 (
"fmt"
"time"
"github.com/imdario/mergo"
"github.com/robertkrimen/otto"
"github.com/abcum/surreal/err"
"github.com/abcum/surreal/kv"
"github.com/abcum/surreal/sql"
"github.com/abcum/surreal/util/json"
"github.com/abcum/surreal/util/keys"
"github.com/abcum/surreal/util/uuid"
"github.com/cockroachdb/cockroach/client"
)
func put(txn *client.Txn, key keys.Key, val interface{}) (err error) {
if rer := txn.Put(key.Encode(), val); rer != nil {
return &errors.DBError{Err: rer, Key: key}
}
return
}
// CPut conditionally puts an item into the backend store
func cput(txn *client.Txn, key keys.Key, val interface{}, was interface{}) (err error) {
if rer := txn.CPut(key.Encode(), val, was); rer != nil {
return &errors.ExistsError{Err: rer, Key: key}
}
return
}
func get(txn *client.Txn, key keys.Key) (res *kv.KV, err error) {
ckv, rer := txn.Get(key.Encode())
if rer != nil {
return nil, &errors.DBError{Err: rer, Key: key}
}
res = &kv.KV{
Exi: ckv.Exists(),
Key: []byte(ckv.Key.String()),
Val: ckv.ValueBytes(),
}
return
}
func rget(txn *client.Txn, beg, end keys.Key, max int64) (res []*kv.KV, err error) {
mul, rer := txn.Scan(beg.Encode(), end.Encode(), max)
if rer != nil {
return nil, &errors.DBError{Err: rer, Beg: beg, End: end}
}
for _, ckv := range mul {
res = append(res, &kv.KV{
Exi: ckv.Exists(),
Key: []byte(ckv.Key.String()),
Val: ckv.ValueBytes(),
})
}
return
}
func del(txn *client.Txn, key keys.Key) error {
if rer := txn.Del(key.Encode()); rer != nil {
return &errors.DBError{Err: rer, Key: key}
}
return nil
}
func rdel(txn *client.Txn, beg, end keys.Key) error {
if rer := txn.DelRange(beg.Encode(), end.Encode()); rer != nil {
return &errors.DBError{Err: rer, Beg: beg, End: end}
}
return nil
}
func new(txn *client.Txn, key *keys.Thing, kv *kv.KV) (old *json.Doc, doc *json.Doc, err error) {
if len(kv.Val) == 0 {
old, err = json.Setup()
doc, err = json.Setup()
if err != nil {
err = &errors.DataError{Err: err, Data: kv.Val}
return
}
}
if len(kv.Val) >= 1 {
old, err = json.Parse(kv.Val)
doc, err = json.Parse(kv.Val)
if err != nil {
err = &errors.DataError{Err: err, Data: kv.Val}
return
}
}
if !doc.Exists("meta") {
old.Object("meta")
doc.Object("meta")
}
if !doc.Exists("data") {
old.Object("data")
doc.Object("data")
}
if !doc.Exists("time") {
old.Object("time")
doc.Object("time")
}
return
}
func mrg(txn *client.Txn, key *keys.Thing, old, doc *json.Doc, data []sql.Expr) (err error) {
now := time.Now()
for _, part := range data {
switch expr := part.(type) {
case *sql.BinaryExpression:
updateOne(doc, expr)
case *sql.MergeExpression:
updateAny(doc, expr)
case *sql.ContentExpression:
updateAll(doc, expr)
}
}
if err = fld(txn, key, old, doc); err != nil {
return
}
// Set time
doc.New(now, "time", "created")
doc.Set(now, "time", "updated")
// Set meta
doc.Set(key.TB, "meta", "table")
doc.Set(key.ID, "meta", "ident")
// Set data
doc.Fmt("@%v:%v", key.TB, key.ID).Set("id")
doc.Fmt("@%v:%v", key.TB, key.ID).Set("data", "id")
return
}
func fld(txn *client.Txn, key keys.Key, old, doc *json.Doc) (err error) {
fld := "fullname"
vm := otto.New()
vm.Set("doc", doc.Data())
vm.Set("UUID", func(call otto.FunctionCall) otto.Value {
result, _ := vm.ToValue(uuid.NewV4())
return result
})
fnc := `
if (doc.data.firstname && doc.data.lastname) {
return doc.data.firstname + ' ' + doc.data.lastname;
} else if (doc.data.firstname) {
return doc.data.firstname;
} else if (doc.data.lastname) {
return doc.data.lastname;
} else {
return undefined
}
`
ret, err := vm.Run("(function() { " + fnc + " })()")
if err != nil {
return &errors.CodeError{Err: err, Name: fld, Code: fnc}
}
val, err := ret.Export()
if err != nil {
return &errors.CodeError{Err: err, Name: fld, Code: fnc}
}
if ret.IsDefined() {
doc.Set(val, "data", fld)
}
if ret.IsUndefined() {
doc.Del("data", fld)
}
return nil
}
func echo(key *keys.Thing, old *json.Doc, doc *json.Doc, dif interface{}, output sql.Token, fallback sql.Token) (res interface{}) {
if output == 0 {
output = fallback
}
switch output {
case sql.NONE:
res = nil
case sql.ID:
res = fmt.Sprintf("@%v:%v", key.TB, key.ID)
case sql.DIFF:
res = dif
case sql.FULL:
res = doc.Data()
case sql.AFTER:
res = doc.Search("data").Data()
case sql.BEFORE:
res = old.Search("data").Data()
case sql.BOTH:
res = map[string]interface{}{
"After": doc.Search("data").Data(),
"Before": old.Search("data").Data(),
}
}
return
}
func match(txn *client.Txn, key keys.Key, cond []sql.Expr) bool {
return true
}
func updateAll(doc *json.Doc, expr *sql.ContentExpression) {
val, err := json.Consume(expr.JSON.Val)
if err != nil {
return
}
doc.Set(val.Data(), "data")
}
func updateAny(doc *json.Doc, expr *sql.MergeExpression) {
lhs, _ := doc.Search("data").Data().(map[string]interface{})
rhs, _ := expr.JSON.Val.(map[string]interface{})
err := mergo.MapWithOverwrite(&lhs, rhs)
if err != nil {
return
}
doc.Set(lhs, "data")
}
func updateOne(doc *json.Doc, expr *sql.BinaryExpression) {
lhs := getDataItemLHS(doc, expr.LHS)
rhs := getDataItemRHS(doc, expr.RHS)
if expr.Op == "=" {
switch expr.RHS.(type) {
default:
doc.Set(rhs, "data", lhs)
case *sql.Void:
doc.Del("data", lhs)
}
}
if expr.Op == "+=" {
doc.ArrayAdd(rhs, "data", lhs)
}
if expr.Op == "-=" {
doc.ArrayDel(rhs, "data", lhs)
}
}
func getDataItemLHS(doc *json.Doc, expr sql.Expr) string {
switch part := expr.(type) {
default:
return ""
case *sql.IdentLiteral:
return part.Val
}
}
func getDataItemRHS(doc *json.Doc, expr sql.Expr) interface{} {
switch part := expr.(type) {
default:
return nil
case *sql.Null:
return nil
case *sql.JSONLiteral:
return part.Val
case *sql.ArrayLiteral:
return part.Val
case *sql.BytesLiteral:
return part.Val
case *sql.NumberLiteral:
return part.Val
case *sql.DoubleLiteral:
return part.Val
case *sql.StringLiteral:
return part.Val
case *sql.BooleanLiteral:
return part.Val
case *sql.DatetimeLiteral:
return part.Val
case *sql.IdentLiteral:
return doc.Search(part.Val).Data()
}
}