Update item manipulation code
This commit is contained in:
parent
a0d3f6ec2a
commit
065b64c429
10 changed files with 1696 additions and 699 deletions
81
util/item/allow.go
Normal file
81
util/item/allow.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
// 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 item
|
||||
|
||||
import (
|
||||
"github.com/robertkrimen/otto"
|
||||
"github.com/yuin/gopher-lua"
|
||||
|
||||
"github.com/abcum/surreal/cnf"
|
||||
)
|
||||
|
||||
func (this *Doc) Allow(cond string) (val bool) {
|
||||
|
||||
this.getRules()
|
||||
|
||||
if rule, ok := this.rules[cond]; ok {
|
||||
|
||||
val = (rule.Rule == "ACCEPT")
|
||||
|
||||
if rule.Rule == "CUSTOM" {
|
||||
|
||||
if cnf.Settings.DB.Lang == "js" {
|
||||
|
||||
vm := otto.New()
|
||||
|
||||
vm.Set("doc", this.current.Copy())
|
||||
|
||||
ret, err := vm.Run("(function() { " + rule.Code + " })()")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if ret.IsDefined() {
|
||||
val, _ := ret.ToBoolean()
|
||||
return val
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if cnf.Settings.DB.Lang == "lua" {
|
||||
|
||||
vm := lua.NewState()
|
||||
defer vm.Close()
|
||||
|
||||
vm.SetGlobal("doc", toLUA(vm, this.current.Copy()))
|
||||
|
||||
if err := vm.DoString(rule.Code); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
ret := vm.Get(-1)
|
||||
|
||||
if lua.LVAsBool(ret) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
52
util/item/blaze.go
Normal file
52
util/item/blaze.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
// 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 item
|
||||
|
||||
import (
|
||||
"github.com/abcum/surreal/sql"
|
||||
"github.com/abcum/surreal/util/data"
|
||||
)
|
||||
|
||||
func (this *Doc) Blaze(ast *sql.SelectStatement) (res interface{}) {
|
||||
|
||||
doc := data.New()
|
||||
|
||||
for _, v := range ast.Expr {
|
||||
if _, ok := v.Expr.(*sql.All); ok {
|
||||
doc = data.Consume(this.current.Get("data").Data())
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range ast.Expr {
|
||||
switch e := v.Expr.(type) {
|
||||
default:
|
||||
doc.Set(e, v.Alias)
|
||||
case bool, int64, float64, string:
|
||||
doc.Set(e, v.Alias)
|
||||
case []interface{}, map[string]interface{}:
|
||||
doc.Set(e, v.Alias)
|
||||
case *sql.Null:
|
||||
doc.Set(nil, v.Alias)
|
||||
case *sql.Ident:
|
||||
doc.Set(this.current.Get("data", e.ID).Data(), v.Alias)
|
||||
case *sql.All:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return doc.Data()
|
||||
|
||||
}
|
642
util/item/check.go
Normal file
642
util/item/check.go
Normal file
|
@ -0,0 +1,642 @@
|
|||
// 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 item
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/abcum/surreal/sql"
|
||||
"github.com/abcum/surreal/util/data"
|
||||
)
|
||||
|
||||
func (this *Doc) Check(cond []sql.Expr) (val bool) {
|
||||
|
||||
for _, part := range cond {
|
||||
switch expr := part.(type) {
|
||||
case *sql.BinaryExpression:
|
||||
if !this.chkOne(expr) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) chkOne(expr *sql.BinaryExpression) (val bool) {
|
||||
|
||||
op := expr.Op
|
||||
lhs := getChkItem(this.current, expr.LHS)
|
||||
rhs := getChkItem(this.current, expr.RHS)
|
||||
|
||||
switch lhs.(type) {
|
||||
case bool, string, int64, float64, time.Time:
|
||||
switch rhs.(type) {
|
||||
case bool, string, int64, float64, time.Time:
|
||||
|
||||
if op == sql.EEQ {
|
||||
return lhs == rhs
|
||||
}
|
||||
|
||||
if op == sql.NEE {
|
||||
return lhs != rhs
|
||||
}
|
||||
|
||||
if op == sql.EQ && lhs == rhs {
|
||||
return true
|
||||
}
|
||||
|
||||
if op == sql.NEQ && lhs == rhs {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
switch l := expr.LHS.(type) {
|
||||
|
||||
case *sql.Ident:
|
||||
switch expr.RHS.(type) {
|
||||
|
||||
case *sql.Void:
|
||||
if op == sql.EQ {
|
||||
return this.current.Exists("data", l.ID) == false
|
||||
} else if op == sql.NEQ {
|
||||
return this.current.Exists("data", l.ID) == true
|
||||
}
|
||||
|
||||
case *sql.Null:
|
||||
if op == sql.EQ {
|
||||
return this.current.Exists("data", l.ID) == true && this.current.Get("data", l.ID).Data() == nil
|
||||
} else if op == sql.NEQ {
|
||||
return this.current.Exists("data", l.ID) == true && this.current.Get("data", l.ID).Data() != nil
|
||||
}
|
||||
|
||||
case *sql.Empty:
|
||||
if op == sql.EQ {
|
||||
return this.current.Exists("data", l.ID) == false || this.current.Get("data", l.ID).Data() == nil
|
||||
} else if op == sql.NEQ {
|
||||
return this.current.Exists("data", l.ID) == true && this.current.Get("data", l.ID).Data() != nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
switch r := expr.RHS.(type) {
|
||||
|
||||
case *sql.Ident:
|
||||
switch expr.LHS.(type) {
|
||||
|
||||
case *sql.Void:
|
||||
if op == sql.EQ {
|
||||
return this.current.Exists("data", r.ID) == false
|
||||
} else if op == sql.NEQ {
|
||||
return this.current.Exists("data", r.ID) == true
|
||||
}
|
||||
|
||||
case *sql.Null:
|
||||
if op == sql.EQ {
|
||||
return this.current.Exists("data", r.ID) == true && this.current.Get("data", r.ID).Data() == nil
|
||||
} else if op == sql.NEQ {
|
||||
return this.current.Exists("data", r.ID) == true && this.current.Get("data", r.ID).Data() != nil
|
||||
}
|
||||
|
||||
case *sql.Empty:
|
||||
if op == sql.EQ {
|
||||
return this.current.Exists("data", r.ID) == false || this.current.Get("data", r.ID).Data() == nil
|
||||
} else if op == sql.NEQ {
|
||||
return this.current.Exists("data", r.ID) == true && this.current.Get("data", r.ID).Data() != nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
switch l := lhs.(type) {
|
||||
|
||||
case nil:
|
||||
switch r := rhs.(type) {
|
||||
default:
|
||||
return op == sql.NEQ || op == sql.SNI || op == sql.NIS || op == sql.CONTAINSNONE
|
||||
case nil:
|
||||
return op == sql.EQ
|
||||
case []interface{}:
|
||||
return chkArrayR(op, l, r)
|
||||
case map[string]interface{}:
|
||||
return chkObject(op, r, l)
|
||||
}
|
||||
|
||||
case *sql.Void:
|
||||
switch r := rhs.(type) {
|
||||
default:
|
||||
return op == sql.NEQ || op == sql.SNI || op == sql.NIS || op == sql.CONTAINSNONE
|
||||
case *sql.Void:
|
||||
return op == sql.EQ
|
||||
case *sql.Empty:
|
||||
return op == sql.EQ
|
||||
case []interface{}:
|
||||
return chkArrayR(op, l, r)
|
||||
case map[string]interface{}:
|
||||
return chkObject(op, r, l)
|
||||
}
|
||||
|
||||
case *sql.Null:
|
||||
switch r := rhs.(type) {
|
||||
default:
|
||||
return op == sql.NEQ || op == sql.SNI || op == sql.NIS || op == sql.CONTAINSNONE
|
||||
case *sql.Null:
|
||||
return op == sql.EQ
|
||||
case *sql.Empty:
|
||||
return op == sql.EQ
|
||||
case []interface{}:
|
||||
return chkArrayR(op, l, r)
|
||||
case map[string]interface{}:
|
||||
return chkObject(op, r, l)
|
||||
}
|
||||
|
||||
case *sql.Empty:
|
||||
switch r := rhs.(type) {
|
||||
default:
|
||||
return op == sql.NEQ || op == sql.SNI || op == sql.NIS || op == sql.CONTAINSNONE
|
||||
case *sql.Null:
|
||||
return op == sql.EQ
|
||||
case *sql.Void:
|
||||
return op == sql.EQ
|
||||
case *sql.Empty:
|
||||
return op == sql.EQ
|
||||
case []interface{}:
|
||||
return chkArrayR(op, l, r)
|
||||
case map[string]interface{}:
|
||||
return chkObject(op, r, l)
|
||||
}
|
||||
|
||||
case bool:
|
||||
switch r := rhs.(type) {
|
||||
default:
|
||||
return op == sql.NEQ || op == sql.SNI || op == sql.NIS || op == sql.CONTAINSNONE
|
||||
case bool:
|
||||
return chkBool(op, l, r)
|
||||
case string:
|
||||
if b, err := strconv.ParseBool(r); err == nil {
|
||||
return chkBool(op, l, b)
|
||||
}
|
||||
case *regexp.Regexp:
|
||||
return chkRegex(op, strconv.FormatBool(l), r)
|
||||
case []interface{}:
|
||||
return chkArrayR(op, l, r)
|
||||
case map[string]interface{}:
|
||||
return chkObject(op, r, l)
|
||||
}
|
||||
|
||||
case string:
|
||||
switch r := rhs.(type) {
|
||||
default:
|
||||
return op == sql.NEQ || op == sql.SNI || op == sql.NIS || op == sql.CONTAINSNONE
|
||||
case bool:
|
||||
if b, err := strconv.ParseBool(l); err == nil {
|
||||
return chkBool(op, r, b)
|
||||
}
|
||||
case string:
|
||||
return chkString(op, l, r)
|
||||
case int64:
|
||||
if n, err := strconv.ParseInt(l, 10, 64); err == nil {
|
||||
return chkInt(op, r, n)
|
||||
}
|
||||
case float64:
|
||||
if n, err := strconv.ParseFloat(l, 64); err == nil {
|
||||
return chkFloat(op, r, n)
|
||||
}
|
||||
case time.Time:
|
||||
return chkString(op, l, r.String())
|
||||
case *regexp.Regexp:
|
||||
return chkRegex(op, l, r)
|
||||
case []interface{}:
|
||||
return chkArrayR(op, l, r)
|
||||
case map[string]interface{}:
|
||||
return chkObject(op, r, l)
|
||||
}
|
||||
|
||||
case int64:
|
||||
switch r := rhs.(type) {
|
||||
default:
|
||||
return op == sql.NEQ || op == sql.SNI || op == sql.NIS || op == sql.CONTAINSNONE
|
||||
case string:
|
||||
if n, err := strconv.ParseInt(r, 10, 64); err == nil {
|
||||
return chkInt(op, l, n)
|
||||
}
|
||||
case int64:
|
||||
return chkInt(op, l, r)
|
||||
case float64:
|
||||
return chkFloat(op, float64(l), r)
|
||||
case time.Time:
|
||||
return chkInt(op, l, r.UnixNano())
|
||||
case *regexp.Regexp:
|
||||
return chkRegex(op, strconv.FormatInt(l, 10), r)
|
||||
case []interface{}:
|
||||
return chkArrayR(op, l, r)
|
||||
case map[string]interface{}:
|
||||
return chkObject(op, r, l)
|
||||
}
|
||||
|
||||
case float64:
|
||||
switch r := rhs.(type) {
|
||||
default:
|
||||
return op == sql.NEQ || op == sql.SNI || op == sql.NIS || op == sql.CONTAINSNONE
|
||||
case string:
|
||||
if n, err := strconv.ParseFloat(r, 64); err == nil {
|
||||
return chkFloat(op, l, n)
|
||||
}
|
||||
case int64:
|
||||
return chkFloat(op, l, float64(r))
|
||||
case float64:
|
||||
return chkFloat(op, l, r)
|
||||
case time.Time:
|
||||
return chkFloat(op, l, float64(r.UnixNano()))
|
||||
case *regexp.Regexp:
|
||||
return chkRegex(op, strconv.FormatFloat(l, 'g', -1, 64), r)
|
||||
case []interface{}:
|
||||
return chkArrayR(op, l, r)
|
||||
case map[string]interface{}:
|
||||
return chkObject(op, r, l)
|
||||
}
|
||||
|
||||
case time.Time:
|
||||
switch r := rhs.(type) {
|
||||
default:
|
||||
return op == sql.NEQ || op == sql.SNI || op == sql.NIS || op == sql.CONTAINSNONE
|
||||
case string:
|
||||
return chkString(op, l.String(), r)
|
||||
case int64:
|
||||
return chkInt(op, l.UnixNano(), r)
|
||||
case float64:
|
||||
return chkFloat(op, float64(l.UnixNano()), r)
|
||||
case time.Time:
|
||||
return chkInt(op, l.UnixNano(), r.UnixNano())
|
||||
case *regexp.Regexp:
|
||||
return chkRegex(op, l.String(), r)
|
||||
case []interface{}:
|
||||
return chkArrayR(op, l, r)
|
||||
case map[string]interface{}:
|
||||
return chkObject(op, r, l)
|
||||
}
|
||||
|
||||
case []interface{}:
|
||||
switch r := rhs.(type) {
|
||||
default:
|
||||
return chkArrayL(op, l, r)
|
||||
case bool:
|
||||
return chkArrayL(op, l, r)
|
||||
case string:
|
||||
return chkArrayL(op, l, r)
|
||||
case int64:
|
||||
return chkArrayL(op, l, r)
|
||||
case float64:
|
||||
return chkArrayL(op, l, r)
|
||||
case time.Time:
|
||||
return chkArrayL(op, l, r)
|
||||
case *regexp.Regexp:
|
||||
return chkMatch(op, l, r)
|
||||
case []interface{}:
|
||||
return chkArray(op, l, r)
|
||||
case map[string]interface{}:
|
||||
return chkArrayL(op, l, r)
|
||||
}
|
||||
|
||||
case map[string]interface{}:
|
||||
switch r := rhs.(type) {
|
||||
default:
|
||||
return op == sql.NEQ || op == sql.SNI || op == sql.NIS || op == sql.CONTAINSNONE
|
||||
case []interface{}:
|
||||
return chkArrayR(op, l, r)
|
||||
case map[string]interface{}:
|
||||
return chkObject(op, l, r)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func chkVoid(op sql.Token, a, b bool) (val bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func chkNull(op sql.Token, a, b bool) (val bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func chkBool(op sql.Token, a, b bool) (val bool) {
|
||||
switch op {
|
||||
case sql.EQ:
|
||||
return a == b
|
||||
case sql.NEQ:
|
||||
return a != b
|
||||
case sql.SNI:
|
||||
return true
|
||||
case sql.NIS:
|
||||
return true
|
||||
case sql.CONTAINSNONE:
|
||||
return true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func chkString(op sql.Token, a, b string) (val bool) {
|
||||
switch op {
|
||||
case sql.EQ:
|
||||
return a == b
|
||||
case sql.NEQ:
|
||||
return a != b
|
||||
case sql.LT:
|
||||
return a < b
|
||||
case sql.LTE:
|
||||
return a <= b
|
||||
case sql.GT:
|
||||
return a > b
|
||||
case sql.GTE:
|
||||
return a >= b
|
||||
case sql.SNI:
|
||||
return true
|
||||
case sql.NIS:
|
||||
return true
|
||||
case sql.CONTAINSNONE:
|
||||
return true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func chkInt(op sql.Token, a, b int64) (val bool) {
|
||||
switch op {
|
||||
case sql.EQ:
|
||||
return a == b
|
||||
case sql.NEQ:
|
||||
return a != b
|
||||
case sql.LT:
|
||||
return a < b
|
||||
case sql.LTE:
|
||||
return a <= b
|
||||
case sql.GT:
|
||||
return a > b
|
||||
case sql.GTE:
|
||||
return a >= b
|
||||
case sql.SNI:
|
||||
return true
|
||||
case sql.NIS:
|
||||
return true
|
||||
case sql.CONTAINSNONE:
|
||||
return true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func chkFloat(op sql.Token, a, b float64) (val bool) {
|
||||
switch op {
|
||||
case sql.EQ:
|
||||
return a == b
|
||||
case sql.NEQ:
|
||||
return a != b
|
||||
case sql.LT:
|
||||
return a < b
|
||||
case sql.LTE:
|
||||
return a <= b
|
||||
case sql.GT:
|
||||
return a > b
|
||||
case sql.GTE:
|
||||
return a >= b
|
||||
case sql.SNI:
|
||||
return true
|
||||
case sql.NIS:
|
||||
return true
|
||||
case sql.CONTAINSNONE:
|
||||
return true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func chkRegex(op sql.Token, a string, r *regexp.Regexp) (val bool) {
|
||||
switch op {
|
||||
case sql.EQ:
|
||||
return r.MatchString(a) == true
|
||||
case sql.NEQ:
|
||||
return r.MatchString(a) == false
|
||||
case sql.ANY:
|
||||
return r.MatchString(a) == true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func chkObject(op sql.Token, m map[string]interface{}, i interface{}) (val bool) {
|
||||
switch op {
|
||||
case sql.EQ:
|
||||
if reflect.TypeOf(m) == reflect.TypeOf(i) && reflect.DeepEqual(m, i) == true {
|
||||
return true
|
||||
}
|
||||
case sql.NEQ:
|
||||
if reflect.TypeOf(m) != reflect.TypeOf(i) || reflect.DeepEqual(m, i) == false {
|
||||
return true
|
||||
}
|
||||
case sql.SNI:
|
||||
return true
|
||||
case sql.NIS:
|
||||
return true
|
||||
case sql.CONTAINSNONE:
|
||||
return true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func chkArrayL(op sql.Token, a []interface{}, i interface{}) (val bool) {
|
||||
switch op {
|
||||
case sql.EQ:
|
||||
return false
|
||||
case sql.NEQ:
|
||||
return true
|
||||
case sql.SIN:
|
||||
if _, ok := i.(*sql.Null); ok {
|
||||
return data.Consume(a).Contains(nil) == true
|
||||
} else {
|
||||
return data.Consume(a).Contains(i) == true
|
||||
}
|
||||
case sql.SNI:
|
||||
if _, ok := i.(*sql.Null); ok {
|
||||
return data.Consume(a).Contains(nil) == false
|
||||
} else {
|
||||
return data.Consume(a).Contains(i) == false
|
||||
}
|
||||
case sql.INS:
|
||||
return false
|
||||
case sql.NIS:
|
||||
return true
|
||||
case sql.CONTAINSNONE:
|
||||
return true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func chkArrayR(op sql.Token, i interface{}, a []interface{}) (val bool) {
|
||||
switch op {
|
||||
case sql.EQ:
|
||||
return false
|
||||
case sql.NEQ:
|
||||
return true
|
||||
case sql.SIN:
|
||||
return false
|
||||
case sql.SNI:
|
||||
return true
|
||||
case sql.INS:
|
||||
if _, ok := i.(*sql.Null); ok {
|
||||
return data.Consume(a).Contains(nil) == true
|
||||
} else {
|
||||
return data.Consume(a).Contains(i) == true
|
||||
}
|
||||
case sql.NIS:
|
||||
if _, ok := i.(*sql.Null); ok {
|
||||
return data.Consume(a).Contains(nil) == false
|
||||
} else {
|
||||
return data.Consume(a).Contains(i) == false
|
||||
}
|
||||
case sql.CONTAINSNONE:
|
||||
return true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func chkArray(op sql.Token, a []interface{}, b []interface{}) (val bool) {
|
||||
switch op {
|
||||
case sql.EQ:
|
||||
if reflect.TypeOf(a) == reflect.TypeOf(b) && reflect.DeepEqual(a, b) == true {
|
||||
return true
|
||||
}
|
||||
case sql.NEQ:
|
||||
if reflect.TypeOf(a) != reflect.TypeOf(b) || reflect.DeepEqual(a, b) == false {
|
||||
return true
|
||||
}
|
||||
case sql.SIN:
|
||||
return data.Consume(a).Contains(b) == true
|
||||
case sql.SNI:
|
||||
return data.Consume(a).Contains(b) == false
|
||||
case sql.INS:
|
||||
return data.Consume(b).Contains(a) == true
|
||||
case sql.NIS:
|
||||
return data.Consume(b).Contains(a) == false
|
||||
case sql.CONTAINSALL:
|
||||
for _, v := range b {
|
||||
if data.Consume(a).Contains(v) == false {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case sql.CONTAINSSOME:
|
||||
for _, v := range b {
|
||||
if data.Consume(a).Contains(v) == true {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case sql.CONTAINSNONE:
|
||||
for _, v := range b {
|
||||
if data.Consume(a).Contains(v) == true {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func chkMatch(op sql.Token, a []interface{}, r *regexp.Regexp) (val bool) {
|
||||
|
||||
for _, v := range a {
|
||||
|
||||
var s string
|
||||
|
||||
switch c := v.(type) {
|
||||
default:
|
||||
return false
|
||||
case string:
|
||||
s = c
|
||||
case bool:
|
||||
s = strconv.FormatBool(c)
|
||||
case int64:
|
||||
s = strconv.FormatInt(c, 10)
|
||||
case float64:
|
||||
s = strconv.FormatFloat(c, 'g', -1, 64)
|
||||
case time.Time:
|
||||
s = c.String()
|
||||
}
|
||||
|
||||
if op == sql.EQ {
|
||||
if chkRegex(sql.EQ, s, r) == false {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if op == sql.NEQ {
|
||||
if chkRegex(sql.EQ, s, r) == true {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if op == sql.ANY {
|
||||
if chkRegex(sql.EQ, s, r) == true {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
switch op {
|
||||
case sql.EQ:
|
||||
return true
|
||||
case sql.NEQ:
|
||||
return true
|
||||
case sql.ANY:
|
||||
return false
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func getChkItem(doc *data.Doc, expr sql.Expr) interface{} {
|
||||
|
||||
switch val := expr.(type) {
|
||||
default:
|
||||
return nil
|
||||
case time.Time:
|
||||
return val
|
||||
case *regexp.Regexp:
|
||||
return val
|
||||
case bool, int64, float64, string:
|
||||
return val
|
||||
case []interface{}, map[string]interface{}:
|
||||
return val
|
||||
case *sql.Void:
|
||||
return val
|
||||
case *sql.Null:
|
||||
return val
|
||||
case *sql.Empty:
|
||||
return val
|
||||
case *sql.Ident:
|
||||
return doc.Get("data", val.ID).Data()
|
||||
}
|
||||
|
||||
}
|
80
util/item/code.go
Normal file
80
util/item/code.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
// 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 item
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
func toLUA(L *lua.LState, value interface{}) lua.LValue {
|
||||
switch v := value.(type) {
|
||||
case bool:
|
||||
return lua.LBool(v)
|
||||
case int64:
|
||||
return lua.LNumber(v)
|
||||
case float64:
|
||||
return lua.LNumber(v)
|
||||
case string:
|
||||
return lua.LString(v)
|
||||
case time.Time:
|
||||
return lua.LNumber(v.Unix())
|
||||
case []interface{}:
|
||||
a := L.CreateTable(len(v), 0)
|
||||
for _, item := range v {
|
||||
a.Append(toLUA(L, item))
|
||||
}
|
||||
return a
|
||||
case map[string]interface{}:
|
||||
m := L.CreateTable(0, len(v))
|
||||
for key, item := range v {
|
||||
m.RawSetH(lua.LString(key), toLUA(L, item))
|
||||
}
|
||||
return m
|
||||
}
|
||||
return lua.LNil
|
||||
}
|
||||
|
||||
func frLUA(value lua.LValue) interface{} {
|
||||
switch v := value.(type) {
|
||||
case *lua.LNilType:
|
||||
return nil
|
||||
case lua.LBool:
|
||||
return bool(v)
|
||||
case lua.LString:
|
||||
return string(v)
|
||||
case lua.LNumber:
|
||||
return float64(v)
|
||||
case *lua.LTable:
|
||||
if c := v.MaxN(); c == 0 {
|
||||
m := make(map[string]interface{})
|
||||
v.ForEach(func(key, val lua.LValue) {
|
||||
str := fmt.Sprint(frLUA(key))
|
||||
m[str] = frLUA(val)
|
||||
})
|
||||
return m
|
||||
} else {
|
||||
a := make([]interface{}, 0, c)
|
||||
for i := 1; i <= c; i++ {
|
||||
a = append(a, frLUA(v.RawGetInt(i)))
|
||||
}
|
||||
return a
|
||||
}
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
423
util/item/each.go
Normal file
423
util/item/each.go
Normal file
|
@ -0,0 +1,423 @@
|
|||
// 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 item
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/robertkrimen/otto"
|
||||
"github.com/yuin/gopher-lua"
|
||||
|
||||
"github.com/abcum/surreal/cnf"
|
||||
"github.com/abcum/surreal/sql"
|
||||
"github.com/abcum/surreal/util/conv"
|
||||
"github.com/abcum/surreal/util/data"
|
||||
)
|
||||
|
||||
func each(fld *sql.DefineFieldStatement, initial *data.Doc, current *data.Doc) (err error) {
|
||||
|
||||
var e bool
|
||||
var i interface{}
|
||||
var c interface{}
|
||||
|
||||
i = initial.Get("data", fld.Name).Data()
|
||||
|
||||
if fld.Readonly && i != nil {
|
||||
current.Set(i, "data", fld.Name)
|
||||
return
|
||||
}
|
||||
|
||||
if fld.Code != "" {
|
||||
|
||||
if cnf.Settings.DB.Lang == "js" {
|
||||
|
||||
vm := otto.New()
|
||||
|
||||
vm.Set("doc", current.Copy())
|
||||
|
||||
ret, err := vm.Run("(function() { " + fld.Code + " })()")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Problem executing code: %v %v", fld.Code, err.Error())
|
||||
}
|
||||
|
||||
if ret.IsUndefined() {
|
||||
current.Del("data", fld.Name)
|
||||
} else {
|
||||
val, _ := ret.Export()
|
||||
current.Set(val, "data", fld.Name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if cnf.Settings.DB.Lang == "lua" {
|
||||
|
||||
vm := lua.NewState()
|
||||
defer vm.Close()
|
||||
|
||||
vm.SetGlobal("doc", toLUA(vm, current.Copy()))
|
||||
|
||||
if err := vm.DoString(fld.Code); err != nil {
|
||||
return fmt.Errorf("Problem executing code: %v %v", fld.Code, err.Error())
|
||||
}
|
||||
|
||||
ret := vm.Get(-1)
|
||||
|
||||
if ret == lua.LNil {
|
||||
current.Del("data", fld.Name)
|
||||
} else {
|
||||
current.Set(frLUA(ret), "data", fld.Name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
c = current.Get("data", fld.Name).Data()
|
||||
e = current.Exists("data", fld.Name)
|
||||
|
||||
if fld.Default != nil && e == false {
|
||||
switch val := fld.Default.(type) {
|
||||
case sql.Null, *sql.Null:
|
||||
current.Set(nil, "data", fld.Name)
|
||||
default:
|
||||
current.Set(fld.Default, "data", fld.Name)
|
||||
case sql.Ident:
|
||||
current.Set(current.Get("data", val.ID).Data(), "data", fld.Name)
|
||||
case *sql.Ident:
|
||||
current.Set(current.Get("data", val.ID).Data(), "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
c = current.Get("data", fld.Name).Data()
|
||||
e = current.Exists("data", fld.Name)
|
||||
|
||||
if fld.Notnull && e == true && c == nil {
|
||||
return fmt.Errorf("Field '%v' can't be null", fld.Name)
|
||||
}
|
||||
|
||||
if fld.Mandatory && e == false {
|
||||
return fmt.Errorf("Need to set field '%v'", fld.Name)
|
||||
}
|
||||
|
||||
if c != nil && fld.Type != "" {
|
||||
|
||||
switch fld.Type {
|
||||
|
||||
case "url":
|
||||
if val, err := conv.ConvertToUrl(c); err == nil {
|
||||
current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a URL", fld.Name)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "uuid":
|
||||
if val, err := conv.ConvertToUuid(c); err == nil {
|
||||
current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a UUID", fld.Name)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "color":
|
||||
if val, err := conv.ConvertToColor(c); err == nil {
|
||||
current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a HEX or RGB color", fld.Name)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "email":
|
||||
if val, err := conv.ConvertToEmail(c); err == nil {
|
||||
current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be an email address", fld.Name)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "phone":
|
||||
if val, err := conv.ConvertToPhone(c); err == nil {
|
||||
current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a phone number", fld.Name)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "array":
|
||||
if val, err := conv.ConvertToArray(c); err == nil {
|
||||
current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be an array", fld.Name)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "object":
|
||||
if val, err := conv.ConvertToObject(c); err == nil {
|
||||
current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be an object", fld.Name)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "domain":
|
||||
if val, err := conv.ConvertToDomain(c); err == nil {
|
||||
current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a domain name", fld.Name)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "base64":
|
||||
if val, err := conv.ConvertToBase64(c); err == nil {
|
||||
current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be base64 data", fld.Name)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "string":
|
||||
if val, err := conv.ConvertToString(c); err == nil {
|
||||
current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a string", fld.Name)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "number":
|
||||
if val, err := conv.ConvertToNumber(c); err == nil {
|
||||
current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a number", fld.Name)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "boolean":
|
||||
if val, err := conv.ConvertToBoolean(c); err == nil {
|
||||
current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a boolean", fld.Name)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "datetime":
|
||||
if val, err := conv.ConvertToDatetime(c); err == nil {
|
||||
current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a datetime", fld.Name)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "latitude":
|
||||
if val, err := conv.ConvertToLatitude(c); err == nil {
|
||||
current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a latitude value", fld.Name)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "longitude":
|
||||
if val, err := conv.ConvertToLongitude(c); err == nil {
|
||||
current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a longitude value", fld.Name)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "custom":
|
||||
|
||||
if val, err := conv.ConvertToOneOf(c, fld.Enum...); err == nil {
|
||||
current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be one of %v", fld.Name, fld.Enum)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if fld.Match != "" {
|
||||
|
||||
if reg, err := regexp.Compile(fld.Match); err != nil {
|
||||
return fmt.Errorf("Regular expression /%v/ is invalid", fld.Match)
|
||||
} else {
|
||||
if !reg.MatchString(fmt.Sprintf("%v", c)) {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to match the regular expression /%v/", fld.Name, fld.Match)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if fld.Min != 0 {
|
||||
|
||||
if c = current.Get("data", fld.Name).Data(); c != nil {
|
||||
|
||||
switch now := c.(type) {
|
||||
|
||||
case []interface{}:
|
||||
if len(now) < int(fld.Min) {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to have at least %v items", fld.Name, fld.Min)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case string:
|
||||
if len(now) < int(fld.Min) {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to have at least %v characters", fld.Name, fld.Min)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case float64:
|
||||
if now < fld.Min {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be >= %v", fld.Name, fld.Min)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if fld.Max != 0 {
|
||||
|
||||
if c = current.Get("data", fld.Name).Data(); c != nil {
|
||||
|
||||
switch now := c.(type) {
|
||||
|
||||
case []interface{}:
|
||||
if len(now) > int(fld.Max) {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to have %v or fewer items", fld.Name, fld.Max)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case string:
|
||||
if len(now) > int(fld.Max) {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to have %v or fewer characters", fld.Name, fld.Max)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case float64:
|
||||
if now > fld.Max {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be <= %v", fld.Name, fld.Max)
|
||||
} else {
|
||||
current.Iff(i, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
c = current.Get("data", fld.Name).Data()
|
||||
e = current.Exists("data", fld.Name)
|
||||
|
||||
if fld.Default != nil && e == false {
|
||||
switch val := fld.Default.(type) {
|
||||
case sql.Null, *sql.Null:
|
||||
current.Set(nil, "data", fld.Name)
|
||||
default:
|
||||
current.Set(fld.Default, "data", fld.Name)
|
||||
case sql.Ident:
|
||||
current.Set(current.Get("data", val.ID).Data(), "data", fld.Name)
|
||||
case *sql.Ident:
|
||||
current.Set(current.Get("data", val.ID).Data(), "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
c = current.Get("data", fld.Name).Data()
|
||||
e = current.Exists("data", fld.Name)
|
||||
|
||||
if fld.Notnull && e == true && c == nil {
|
||||
return fmt.Errorf("Field '%v' can't be null", fld.Name)
|
||||
}
|
||||
|
||||
if fld.Mandatory && e == false {
|
||||
return fmt.Errorf("Need to set field '%v'", fld.Name)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
20
util/item/erase.go
Normal file
20
util/item/erase.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
// 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 item
|
||||
|
||||
func (this *Doc) Erase() (err error) {
|
||||
this.current.Reset()
|
||||
return
|
||||
}
|
|
@ -16,56 +16,29 @@ package item
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"github.com/robertkrimen/otto"
|
||||
|
||||
"github.com/abcum/surreal/kvs"
|
||||
"github.com/abcum/surreal/sql"
|
||||
"github.com/abcum/surreal/util/conv"
|
||||
"github.com/abcum/surreal/util/data"
|
||||
"github.com/abcum/surreal/util/diff"
|
||||
"github.com/abcum/surreal/util/form"
|
||||
"github.com/abcum/surreal/util/keys"
|
||||
"github.com/abcum/surreal/util/pack"
|
||||
)
|
||||
|
||||
type field struct {
|
||||
Type string
|
||||
Name string
|
||||
Code string
|
||||
Enum []interface{}
|
||||
Min float64
|
||||
Max float64
|
||||
Match string
|
||||
Default interface{}
|
||||
Notnull bool
|
||||
Readonly bool
|
||||
Mandatory bool
|
||||
Validate bool
|
||||
}
|
||||
|
||||
type index struct {
|
||||
uniq bool
|
||||
name string
|
||||
code string
|
||||
cols []string
|
||||
}
|
||||
|
||||
type Doc struct {
|
||||
kv kvs.KV
|
||||
id string
|
||||
txn kvs.TX
|
||||
key *keys.Thing
|
||||
initial *data.Doc
|
||||
current *data.Doc
|
||||
fieldes []*field
|
||||
indexes []*index
|
||||
fields []*sql.DefineFieldStatement
|
||||
indexs []*sql.DefineIndexStatement
|
||||
rules map[string]*sql.DefineRulesStatement
|
||||
}
|
||||
|
||||
func New(kv kvs.KV, key *keys.Thing) (this *Doc) {
|
||||
func New(kv kvs.KV, txn kvs.TX, key *keys.Thing) (this *Doc) {
|
||||
|
||||
this = &Doc{kv: kv, key: key}
|
||||
this = &Doc{kv: kv, key: key, txn: txn}
|
||||
|
||||
if key == nil {
|
||||
this.key = &keys.Thing{}
|
||||
|
@ -78,8 +51,8 @@ func New(kv kvs.KV, key *keys.Thing) (this *Doc) {
|
|||
}
|
||||
|
||||
if kv.Exists() == true {
|
||||
this.initial = data.NewFromPACK(kv.Val())
|
||||
this.current = data.NewFromPACK(kv.Val())
|
||||
this.initial = data.New().Decode(kv.Val())
|
||||
this.current = data.New().Decode(kv.Val())
|
||||
}
|
||||
|
||||
if !this.current.Exists("meta") {
|
||||
|
@ -103,218 +76,21 @@ func New(kv kvs.KV, key *keys.Thing) (this *Doc) {
|
|||
|
||||
}
|
||||
|
||||
func (this *Doc) Allow(txn kvs.TX, cond string) (val bool) {
|
||||
func (this *Doc) getRules() {
|
||||
|
||||
switch cond {
|
||||
case "select":
|
||||
case "create":
|
||||
case "update":
|
||||
case "modify":
|
||||
case "delete":
|
||||
case "relate":
|
||||
}
|
||||
this.rules = make(map[string]*sql.DefineRulesStatement)
|
||||
|
||||
return true
|
||||
beg := &keys.RU{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, RU: keys.Prefix}
|
||||
end := &keys.RU{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, RU: keys.Suffix}
|
||||
rng, _ := this.txn.RGet(beg.Encode(), end.Encode(), 0)
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) Check(txn kvs.TX, cond []sql.Expr) (val bool) {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *Doc) Erase(txn kvs.TX, data []sql.Expr) (err error) {
|
||||
this.current.Reset()
|
||||
return
|
||||
}
|
||||
|
||||
func (this *Doc) Merge(txn kvs.TX, data []sql.Expr) (err error) {
|
||||
|
||||
now := time.Now()
|
||||
|
||||
for _, part := range data {
|
||||
|
||||
switch expr := part.(type) {
|
||||
case *sql.DiffExpression:
|
||||
this.mrgDpm(expr)
|
||||
case *sql.BinaryExpression:
|
||||
this.mrgOne(expr)
|
||||
case *sql.MergeExpression:
|
||||
this.mrgAny(expr)
|
||||
case *sql.ContentExpression:
|
||||
this.mrgAll(expr)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Set meta
|
||||
this.current.Set(this.key.TB, "meta", "table")
|
||||
this.current.Set(this.key.ID, "meta", "ident")
|
||||
|
||||
// Set time
|
||||
this.current.New(now, "time", "created")
|
||||
this.current.Set(now, "time", "updated")
|
||||
|
||||
// Set data
|
||||
this.current.Set(this.id, "id")
|
||||
this.current.Set(this.id, "data", "id")
|
||||
|
||||
// Set fields
|
||||
err = this.mrgFld(txn)
|
||||
|
||||
// Set data
|
||||
this.current.Set(this.id, "id")
|
||||
this.current.Set(this.id, "data", "id")
|
||||
|
||||
// Set time
|
||||
this.current.New(now, "time", "created")
|
||||
this.current.Set(now, "time", "updated")
|
||||
|
||||
// Set meta
|
||||
this.current.Set(this.key.TB, "meta", "table")
|
||||
this.current.Set(this.key.ID, "meta", "ident")
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) StartThing(txn kvs.TX) (err error) {
|
||||
|
||||
dkey := &keys.DB{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB}
|
||||
if err := txn.Put(dkey.Encode(), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tkey := &keys.TB{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB}
|
||||
if err := txn.Put(tkey.Encode(), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return txn.CPut(this.key.Encode(), this.current.ToPACK(), nil)
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) PurgeThing(txn kvs.TX) (err error) {
|
||||
|
||||
return txn.Del(this.key.Encode())
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) StoreThing(txn kvs.TX) (err error) {
|
||||
|
||||
dkey := &keys.DB{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB}
|
||||
if err := txn.Put(dkey.Encode(), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tkey := &keys.TB{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB}
|
||||
if err := txn.Put(tkey.Encode(), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return txn.CPut(this.key.Encode(), this.current.ToPACK(), this.kv.Val())
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) PurgePatch(txn kvs.TX) (err error) {
|
||||
|
||||
beg := &keys.Patch{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, ID: this.key.ID, AT: keys.StartOfTime}
|
||||
end := &keys.Patch{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, ID: this.key.ID, AT: keys.EndOfTime}
|
||||
return txn.RDel(beg.Encode(), end.Encode(), 0)
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) StorePatch(txn kvs.TX) (err error) {
|
||||
|
||||
key := &keys.Patch{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, ID: this.key.ID}
|
||||
return txn.CPut(key.Encode(), this.diff().ToPACK(), nil)
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) PurgeIndex(txn kvs.TX) (err error) {
|
||||
|
||||
for _, index := range this.indexes {
|
||||
|
||||
old := []interface{}{}
|
||||
|
||||
for _, col := range index.cols {
|
||||
old = append(old, this.initial.Get("data", col).Data())
|
||||
}
|
||||
|
||||
if index.uniq == true {
|
||||
key := &keys.Index{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.name, FD: old}
|
||||
txn.CDel(key.Encode(), []byte(this.id))
|
||||
}
|
||||
|
||||
if index.uniq == false {
|
||||
key := &keys.Point{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.name, FD: old, ID: this.key.ID}
|
||||
txn.CDel(key.Encode(), []byte(this.id))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) StoreIndex(txn kvs.TX) (err error) {
|
||||
|
||||
for _, index := range this.indexes {
|
||||
|
||||
old := []interface{}{}
|
||||
now := []interface{}{}
|
||||
|
||||
for _, col := range index.cols {
|
||||
old = append(old, this.initial.Get("data", col).Data())
|
||||
now = append(now, this.current.Get("data", col).Data())
|
||||
}
|
||||
|
||||
if index.uniq == true {
|
||||
oidx := &keys.Index{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.name, FD: old}
|
||||
txn.CDel(oidx.Encode(), []byte(this.id))
|
||||
nidx := &keys.Index{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.name, FD: now}
|
||||
if err = txn.CPut(nidx.Encode(), []byte(this.id), nil); err != nil {
|
||||
return fmt.Errorf("Duplicate entry %v in index '%s.%s'", now, this.key.TB, index.name)
|
||||
}
|
||||
}
|
||||
|
||||
if index.uniq == false {
|
||||
oidx := &keys.Point{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.name, FD: old, ID: this.key.ID}
|
||||
txn.CDel(oidx.Encode(), []byte(this.id))
|
||||
nidx := &keys.Point{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.name, FD: now, ID: this.key.ID}
|
||||
if err = txn.CPut(nidx.Encode(), []byte(this.id), nil); err != nil {
|
||||
return fmt.Errorf("Multiple items with id %s in index '%s.%s'", this.key.ID, this.key.TB, index.name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) Yield(output sql.Token, fallback sql.Token) (res interface{}) {
|
||||
|
||||
if output == 0 {
|
||||
output = fallback
|
||||
}
|
||||
|
||||
switch output {
|
||||
default:
|
||||
res = nil
|
||||
case sql.ID:
|
||||
res = fmt.Sprintf("@%v:%v", this.key.TB, this.key.ID)
|
||||
case sql.DIFF:
|
||||
res = this.diff().Data()
|
||||
case sql.FULL:
|
||||
res = this.current.Data()
|
||||
case sql.AFTER:
|
||||
res = this.current.Get("data").Data()
|
||||
case sql.BEFORE:
|
||||
res = this.initial.Get("data").Data()
|
||||
case sql.BOTH:
|
||||
res = map[string]interface{}{
|
||||
"After": this.current.Get("data").Data(),
|
||||
"Before": this.initial.Get("data").Data(),
|
||||
for _, kv := range rng {
|
||||
var rul sql.DefineRulesStatement
|
||||
key := new(keys.RU)
|
||||
key.Decode(kv.Key())
|
||||
if str, ok := key.RU.(string); ok {
|
||||
pack.FromPACK(kv.Val(), &rul)
|
||||
this.rules[str] = &rul
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -322,517 +98,179 @@ func (this *Doc) Yield(output sql.Token, fallback sql.Token) (res interface{}) {
|
|||
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// --------------------------------------------------
|
||||
// --------------------------------------------------
|
||||
// --------------------------------------------------
|
||||
// --------------------------------------------------
|
||||
|
||||
func (this *Doc) diff() *data.Doc {
|
||||
|
||||
differ := diff.New()
|
||||
diff, _ := differ.Compare(this.current.Get("data").ToJSON(), this.initial.Get("data").ToJSON())
|
||||
|
||||
format := form.NewDeltaFormatter()
|
||||
diffed, _ := format.Format(diff)
|
||||
|
||||
return data.NewFromJSON([]byte(diffed))
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) getFlds(txn kvs.TX) (out []*field) {
|
||||
func (this *Doc) getFields() {
|
||||
|
||||
beg := &keys.FD{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, FD: keys.Prefix}
|
||||
end := &keys.FD{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, FD: keys.Suffix}
|
||||
rng, _ := txn.RGet(beg.Encode(), end.Encode(), 0)
|
||||
rng, _ := this.txn.RGet(beg.Encode(), end.Encode(), 0)
|
||||
|
||||
for _, kv := range rng {
|
||||
|
||||
inf := data.NewFromPACK(kv.Val())
|
||||
|
||||
fld := &field{}
|
||||
|
||||
fld.Name, _ = inf.Get("name").Data().(string)
|
||||
fld.Type, _ = inf.Get("type").Data().(string)
|
||||
fld.Enum, _ = inf.Get("enum").Data().([]interface{})
|
||||
fld.Code, _ = inf.Get("code").Data().(string)
|
||||
fld.Min, _ = inf.Get("min").Data().(float64)
|
||||
fld.Max, _ = inf.Get("max").Data().(float64)
|
||||
fld.Match, _ = inf.Get("match").Data().(string)
|
||||
fld.Default = inf.Get("default").Data()
|
||||
fld.Notnull = inf.Get("notnull").Data().(bool)
|
||||
fld.Readonly = inf.Get("readonly").Data().(bool)
|
||||
fld.Mandatory = inf.Get("mandatory").Data().(bool)
|
||||
fld.Validate = inf.Get("validate").Data().(bool)
|
||||
|
||||
out = append(out, fld)
|
||||
|
||||
var fld sql.DefineFieldStatement
|
||||
pack.FromPACK(kv.Val(), &fld)
|
||||
this.fields = append(this.fields, &fld)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) getIdxs(txn kvs.TX) (out []*data.Doc) {
|
||||
func (this *Doc) getIndexs() {
|
||||
|
||||
beg := &keys.IX{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: keys.Prefix}
|
||||
end := &keys.IX{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: keys.Suffix}
|
||||
rng, _ := txn.RGet(beg.Encode(), end.Encode(), 0)
|
||||
rng, _ := this.txn.RGet(beg.Encode(), end.Encode(), 0)
|
||||
|
||||
for _, kv := range rng {
|
||||
idx := data.NewFromPACK(kv.Val())
|
||||
var idx sql.DefineIndexStatement
|
||||
pack.FromPACK(kv.Val(), &idx)
|
||||
this.indexs = append(this.indexs, &idx)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) StartThing() (err error) {
|
||||
|
||||
dkey := &keys.DB{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB}
|
||||
if err := this.txn.Put(dkey.Encode(), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tkey := &keys.TB{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB}
|
||||
if err := this.txn.Put(tkey.Encode(), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return this.txn.CPut(this.key.Encode(), this.current.Encode(), nil)
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) PurgeThing() (err error) {
|
||||
|
||||
return this.txn.Del(this.key.Encode())
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) StoreThing() (err error) {
|
||||
|
||||
dkey := &keys.DB{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB}
|
||||
if err := this.txn.Put(dkey.Encode(), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tkey := &keys.TB{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB}
|
||||
if err := this.txn.Put(tkey.Encode(), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return this.txn.CPut(this.key.Encode(), this.current.Encode(), this.kv.Val())
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) PurgePatch() (err error) {
|
||||
|
||||
beg := &keys.Patch{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, ID: this.key.ID, AT: keys.StartOfTime}
|
||||
end := &keys.Patch{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, ID: this.key.ID, AT: keys.EndOfTime}
|
||||
return this.txn.RDel(beg.Encode(), end.Encode(), 0)
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) StorePatch() (err error) {
|
||||
|
||||
key := &keys.Patch{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, ID: this.key.ID}
|
||||
return this.txn.CPut(key.Encode(), this.diff().Encode(), nil)
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) PurgeIndex() (err error) {
|
||||
|
||||
for _, index := range this.indexs {
|
||||
|
||||
if index.Uniq == true {
|
||||
for _, o := range buildIndex(index.Cols, this.initial) {
|
||||
key := &keys.Index{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.Name, FD: o}
|
||||
this.txn.CDel(key.Encode(), []byte(this.id))
|
||||
}
|
||||
}
|
||||
|
||||
if index.Uniq == false {
|
||||
for _, o := range buildIndex(index.Cols, this.initial) {
|
||||
key := &keys.Point{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.Name, FD: o, ID: this.key.ID}
|
||||
this.txn.CDel(key.Encode(), []byte(this.id))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) StoreIndex() (err error) {
|
||||
|
||||
for _, index := range this.indexs {
|
||||
|
||||
if index.Uniq == true {
|
||||
for _, o := range buildIndex(index.Cols, this.initial) {
|
||||
oidx := &keys.Index{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.Name, FD: o}
|
||||
this.txn.CDel(oidx.Encode(), []byte(this.id))
|
||||
}
|
||||
for _, n := range buildIndex(index.Cols, this.current) {
|
||||
nidx := &keys.Index{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.Name, FD: n}
|
||||
if err = this.txn.CPut(nidx.Encode(), []byte(this.id), nil); err != nil {
|
||||
return fmt.Errorf("Duplicate entry for %v in index '%s' on %s", n, index.Name, this.key.TB)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if index.Uniq == false {
|
||||
for _, o := range buildIndex(index.Cols, this.initial) {
|
||||
oidx := &keys.Point{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.Name, FD: o, ID: this.key.ID}
|
||||
this.txn.CDel(oidx.Encode(), []byte(this.id))
|
||||
}
|
||||
for _, n := range buildIndex(index.Cols, this.current) {
|
||||
nidx := &keys.Point{KV: this.key.KV, NS: this.key.NS, DB: this.key.DB, TB: this.key.TB, IX: index.Name, FD: n, ID: this.key.ID}
|
||||
if err = this.txn.CPut(nidx.Encode(), []byte(this.id), nil); err != nil {
|
||||
return fmt.Errorf("Multiple items with id %s in index '%s' on %s", this.key.ID, index.Name, this.key.TB)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func buildIndex(cols []string, item *data.Doc) (out [][]interface{}) {
|
||||
|
||||
if len(cols) == 0 {
|
||||
return [][]interface{}{nil}
|
||||
}
|
||||
|
||||
col, cols := cols[0], cols[1:]
|
||||
|
||||
sub := buildIndex(cols, item)
|
||||
|
||||
if arr, ok := item.Get("data", col).Data().([]interface{}); ok {
|
||||
for _, s := range sub {
|
||||
for _, a := range arr {
|
||||
idx := []interface{}{}
|
||||
idx = append(idx, a)
|
||||
idx = append(idx, s...)
|
||||
out = append(out, idx)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// --------------------------------------------------
|
||||
// --------------------------------------------------
|
||||
// --------------------------------------------------
|
||||
// --------------------------------------------------
|
||||
|
||||
func (this *Doc) mrgFld(txn kvs.TX) (err error) {
|
||||
|
||||
vm := otto.New()
|
||||
|
||||
vm.Set("doc", this.current.Data())
|
||||
vm.Set("data", this.current.Get("data").Data())
|
||||
vm.Set("meta", this.current.Get("meta").Data())
|
||||
vm.Set("time", this.current.Get("time").Data())
|
||||
|
||||
for _, fld := range this.getFlds(txn) {
|
||||
|
||||
var exists bool
|
||||
var initial interface{}
|
||||
var current interface{}
|
||||
|
||||
initial = this.initial.Get("data", fld.Name).Data()
|
||||
|
||||
if fld.Readonly && initial != nil {
|
||||
this.current.Set(initial, "data", fld.Name)
|
||||
return
|
||||
}
|
||||
|
||||
if fld.Code != "" {
|
||||
|
||||
ret, err := vm.Run("(function() { " + fld.Code + " })()")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Problem executing code: %v %v", fld.Code, err.Error())
|
||||
}
|
||||
|
||||
val, err := ret.Export()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Problem executing code: %v %v", fld.Code, err.Error())
|
||||
}
|
||||
|
||||
if ret.IsDefined() {
|
||||
this.current.Set(val, "data", fld.Name)
|
||||
}
|
||||
|
||||
if ret.IsUndefined() {
|
||||
this.current.Del("data", fld.Name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
current = this.current.Get("data", fld.Name).Data()
|
||||
exists = this.current.Exists("data", fld.Name)
|
||||
|
||||
if fld.Default != nil && exists == false {
|
||||
this.current.Set(fld.Default, "data", fld.Name)
|
||||
}
|
||||
|
||||
if fld.Notnull && exists == true && current == nil {
|
||||
return fmt.Errorf("Field '%v' can't be null", fld.Name)
|
||||
}
|
||||
|
||||
if fld.Mandatory && exists == false {
|
||||
return fmt.Errorf("Need to set field '%v'", fld.Name)
|
||||
}
|
||||
|
||||
if current != nil && fld.Type != "" {
|
||||
|
||||
switch fld.Type {
|
||||
|
||||
case "url":
|
||||
if val, err := conv.ConvertToUrl(current); err == nil {
|
||||
this.current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a URL", fld.Name)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
for _, s := range sub {
|
||||
idx := []interface{}{}
|
||||
idx = append(idx, item.Get("data", col).Data())
|
||||
idx = append(idx, s...)
|
||||
out = append(out, idx)
|
||||
}
|
||||
}
|
||||
|
||||
case "uuid":
|
||||
if val, err := conv.ConvertToUuid(current); err == nil {
|
||||
this.current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a UUID", fld.Name)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "color":
|
||||
if val, err := conv.ConvertToColor(current); err == nil {
|
||||
this.current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a HEX or RGB color", fld.Name)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "email":
|
||||
if val, err := conv.ConvertToEmail(current); err == nil {
|
||||
this.current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be an email address", fld.Name)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "phone":
|
||||
if val, err := conv.ConvertToPhone(current); err == nil {
|
||||
this.current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a phone number", fld.Name)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "array":
|
||||
if val, err := conv.ConvertToArray(current); err == nil {
|
||||
this.current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be an array", fld.Name)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "object":
|
||||
if val, err := conv.ConvertToObject(current); err == nil {
|
||||
this.current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be an object", fld.Name)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "domain":
|
||||
if val, err := conv.ConvertToDomain(current); err == nil {
|
||||
this.current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a domain name", fld.Name)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "base64":
|
||||
if val, err := conv.ConvertToBase64(current); err == nil {
|
||||
this.current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be base64 data", fld.Name)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "string":
|
||||
if val, err := conv.ConvertToString(current); err == nil {
|
||||
this.current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a string", fld.Name)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "number":
|
||||
if val, err := conv.ConvertToNumber(current); err == nil {
|
||||
this.current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a number", fld.Name)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "boolean":
|
||||
if val, err := conv.ConvertToBoolean(current); err == nil {
|
||||
this.current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a boolean", fld.Name)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "datetime":
|
||||
if val, err := conv.ConvertToDatetime(current); err == nil {
|
||||
this.current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a datetime", fld.Name)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "latitude":
|
||||
if val, err := conv.ConvertToLatitude(current); err == nil {
|
||||
this.current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a latitude value", fld.Name)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "longitude":
|
||||
if val, err := conv.ConvertToLongitude(current); err == nil {
|
||||
this.current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be a longitude value", fld.Name)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case "custom":
|
||||
|
||||
if val, err := conv.ConvertToOneOf(current, fld.Enum...); err == nil {
|
||||
this.current.Set(val, "data", fld.Name)
|
||||
} else {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be one of %v", fld.Name, fld.Enum)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if fld.Match != "" {
|
||||
|
||||
if reg, err := regexp.Compile(fld.Match); err != nil {
|
||||
return fmt.Errorf("Regular expression /%v/ is invalid", fld.Match)
|
||||
} else {
|
||||
if !reg.MatchString(fmt.Sprintf("%v", current)) {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to match the regular expression /%v/", fld.Name, fld.Match)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if fld.Min != 0 {
|
||||
|
||||
if current = this.current.Get("data", fld.Name).Data(); current != nil {
|
||||
|
||||
switch now := current.(type) {
|
||||
|
||||
case []interface{}:
|
||||
if len(now) < int(fld.Min) {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to have at least %v items", fld.Name, fld.Min)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case string:
|
||||
if len(now) < int(fld.Min) {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to have at least %v characters", fld.Name, fld.Min)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case float64:
|
||||
if now < fld.Min {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be >= %v", fld.Name, fld.Min)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if fld.Max != 0 {
|
||||
|
||||
if current = this.current.Get("data", fld.Name).Data(); current != nil {
|
||||
|
||||
switch now := current.(type) {
|
||||
|
||||
case []interface{}:
|
||||
if len(now) > int(fld.Max) {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to have %v or fewer items", fld.Name, fld.Max)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case string:
|
||||
if len(now) > int(fld.Max) {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to have %v or fewer characters", fld.Name, fld.Max)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case float64:
|
||||
if now > fld.Max {
|
||||
if fld.Validate {
|
||||
return fmt.Errorf("Field '%v' needs to be <= %v", fld.Name, fld.Max)
|
||||
} else {
|
||||
this.current.Try(initial, "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
current = this.current.Get("data", fld.Name).Data()
|
||||
exists = this.current.Exists("data", fld.Name)
|
||||
|
||||
if fld.Default != nil && exists == false {
|
||||
this.current.Set(fld.Default, "data", fld.Name)
|
||||
}
|
||||
|
||||
if fld.Notnull && exists == true && current == nil {
|
||||
return fmt.Errorf("Can't be null field '%v'", fld.Name)
|
||||
}
|
||||
|
||||
if fld.Mandatory && exists == false {
|
||||
return fmt.Errorf("Need to set field '%v'", fld.Name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) mrgAll(expr *sql.ContentExpression) {
|
||||
|
||||
val := data.Consume(expr.JSON)
|
||||
|
||||
this.current.Set(val.Data(), "data")
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) mrgAny(expr *sql.MergeExpression) {
|
||||
|
||||
lhs, _ := this.current.Get("data").Data().(map[string]interface{})
|
||||
rhs, _ := expr.JSON.(map[string]interface{})
|
||||
|
||||
err := mergo.MapWithOverwrite(&lhs, rhs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
this.current.Set(lhs, "data")
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) mrgDpm(expr *sql.DiffExpression) {
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) mrgOne(expr *sql.BinaryExpression) {
|
||||
|
||||
lhs := getDataItemLHS(this.current, expr.LHS)
|
||||
rhs := getDataItemRHS(this.current, expr.RHS)
|
||||
|
||||
if expr.Op == "=" {
|
||||
switch expr.RHS.(type) {
|
||||
default:
|
||||
this.current.Set(rhs, "data", lhs)
|
||||
case *sql.Void:
|
||||
this.current.Del("data", lhs)
|
||||
}
|
||||
}
|
||||
|
||||
if expr.Op == "+=" {
|
||||
this.current.ArrayAdd(rhs, "data", lhs)
|
||||
}
|
||||
|
||||
if expr.Op == "-=" {
|
||||
this.current.ArrayDel(rhs, "data", lhs)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// --------------------------------------------------
|
||||
// --------------------------------------------------
|
||||
// --------------------------------------------------
|
||||
// --------------------------------------------------
|
||||
|
||||
func getDataItemLHS(doc *data.Doc, expr sql.Expr) string {
|
||||
|
||||
switch val := expr.(type) {
|
||||
default:
|
||||
return ""
|
||||
case sql.Ident:
|
||||
return string(val)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getDataItemRHS(doc *data.Doc, expr sql.Expr) interface{} {
|
||||
|
||||
switch val := expr.(type) {
|
||||
default:
|
||||
return nil
|
||||
case time.Time:
|
||||
return val
|
||||
case bool, int64, float64, string:
|
||||
return val
|
||||
case []interface{}, map[string]interface{}:
|
||||
return val
|
||||
case sql.Ident:
|
||||
return doc.Get("data", string(val)).Data()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
181
util/item/merge.go
Normal file
181
util/item/merge.go
Normal file
|
@ -0,0 +1,181 @@
|
|||
// 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 item
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
|
||||
"github.com/abcum/surreal/sql"
|
||||
"github.com/abcum/surreal/util/data"
|
||||
)
|
||||
|
||||
func (this *Doc) Merge(data []sql.Expr) (err error) {
|
||||
|
||||
this.getFields()
|
||||
this.getIndexs()
|
||||
|
||||
// Set defaults
|
||||
this.defFld()
|
||||
|
||||
for _, part := range data {
|
||||
switch expr := part.(type) {
|
||||
case *sql.DiffExpression:
|
||||
this.mrgDpm(expr)
|
||||
case *sql.BinaryExpression:
|
||||
this.mrgOne(expr)
|
||||
case *sql.MergeExpression:
|
||||
this.mrgAny(expr)
|
||||
case *sql.ContentExpression:
|
||||
this.mrgAll(expr)
|
||||
}
|
||||
}
|
||||
|
||||
// Set data
|
||||
this.current.Set(this.id, "id")
|
||||
this.current.Set(this.id, "data", "id")
|
||||
|
||||
// Set time
|
||||
this.current.New(time.Now(), "time", "created")
|
||||
this.current.Set(time.Now(), "time", "updated")
|
||||
|
||||
// Set meta
|
||||
this.current.Set(this.key.TB, "meta", "table")
|
||||
this.current.Set(this.key.ID, "meta", "ident")
|
||||
|
||||
// Set fields
|
||||
err = this.mrgFld()
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) defFld() (err error) {
|
||||
|
||||
for _, fld := range this.fields {
|
||||
|
||||
e := this.current.Exists("data", fld.Name)
|
||||
|
||||
if fld.Default != nil && e == false {
|
||||
switch val := fld.Default.(type) {
|
||||
case sql.Null, *sql.Null:
|
||||
this.current.Set(nil, "data", fld.Name)
|
||||
default:
|
||||
this.current.Set(fld.Default, "data", fld.Name)
|
||||
case sql.Ident:
|
||||
this.current.Set(this.current.Get("data", val.ID).Data(), "data", fld.Name)
|
||||
case *sql.Ident:
|
||||
this.current.Set(this.current.Get("data", val.ID).Data(), "data", fld.Name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) mrgFld() (err error) {
|
||||
|
||||
for _, fld := range this.fields {
|
||||
if err = each(fld, this.initial, this.current); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) mrgAll(expr *sql.ContentExpression) {
|
||||
|
||||
val := data.Consume(expr.JSON)
|
||||
|
||||
this.current.Set(val.Data(), "data")
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) mrgAny(expr *sql.MergeExpression) {
|
||||
|
||||
lhs, _ := this.current.Get("data").Data().(map[string]interface{})
|
||||
rhs, _ := expr.JSON.(map[string]interface{})
|
||||
|
||||
err := mergo.MapWithOverwrite(&lhs, rhs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
this.current.Set(lhs, "data")
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) mrgDpm(expr *sql.DiffExpression) {
|
||||
|
||||
}
|
||||
|
||||
func (this *Doc) mrgOne(expr *sql.BinaryExpression) {
|
||||
|
||||
lhs := getMrgItemLHS(this.current, expr.LHS)
|
||||
rhs := getMrgItemRHS(this.current, expr.RHS)
|
||||
|
||||
if expr.Op == sql.EQ {
|
||||
switch expr.RHS.(type) {
|
||||
default:
|
||||
this.current.Set(rhs, "data", lhs)
|
||||
case *sql.Void:
|
||||
this.current.Del("data", lhs)
|
||||
}
|
||||
}
|
||||
|
||||
if expr.Op == sql.INC {
|
||||
this.current.Inc(rhs, "data", lhs)
|
||||
}
|
||||
|
||||
if expr.Op == sql.DEC {
|
||||
this.current.Dec(rhs, "data", lhs)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getMrgItemLHS(doc *data.Doc, expr sql.Expr) string {
|
||||
|
||||
switch val := expr.(type) {
|
||||
default:
|
||||
return ""
|
||||
case sql.Ident:
|
||||
return val.ID
|
||||
case *sql.Ident:
|
||||
return val.ID
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getMrgItemRHS(doc *data.Doc, expr sql.Expr) interface{} {
|
||||
|
||||
switch val := expr.(type) {
|
||||
default:
|
||||
return nil
|
||||
case time.Time:
|
||||
return val
|
||||
case bool, int64, float64, string:
|
||||
return val
|
||||
case []interface{}, map[string]interface{}:
|
||||
return val
|
||||
case *sql.Ident:
|
||||
return doc.Get("data", val.ID).Data()
|
||||
}
|
||||
|
||||
}
|
29
util/item/patch.go
Normal file
29
util/item/patch.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
// 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 item
|
||||
|
||||
import (
|
||||
"github.com/abcum/surreal/util/data"
|
||||
"github.com/abcum/surreal/util/json"
|
||||
)
|
||||
|
||||
func (this *Doc) diff() *data.Doc {
|
||||
|
||||
va := this.initial.Get("data").Data().(map[string]interface{})
|
||||
vb := this.current.Get("data").Data().(map[string]interface{})
|
||||
patch, _ := json.Diff(va, vb)
|
||||
return data.Consume(patch)
|
||||
|
||||
}
|
51
util/item/yield.go
Normal file
51
util/item/yield.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
// 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 item
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/abcum/surreal/sql"
|
||||
)
|
||||
|
||||
func (this *Doc) Yield(output sql.Token, fallback sql.Token) (res interface{}) {
|
||||
|
||||
if output == 0 {
|
||||
output = fallback
|
||||
}
|
||||
|
||||
switch output {
|
||||
default:
|
||||
res = nil
|
||||
case sql.ID:
|
||||
res = fmt.Sprintf("@%v:%v", this.key.TB, this.key.ID)
|
||||
case sql.DIFF:
|
||||
res = this.diff().Data()
|
||||
case sql.FULL:
|
||||
res = this.current.Data()
|
||||
case sql.AFTER:
|
||||
res = this.current.Get("data").Data()
|
||||
case sql.BEFORE:
|
||||
res = this.initial.Get("data").Data()
|
||||
case sql.BOTH:
|
||||
res = map[string]interface{}{
|
||||
"After": this.current.Get("data").Data(),
|
||||
"Before": this.initial.Get("data").Data(),
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
Loading…
Reference in a new issue