diff --git a/util/item/allow.go b/util/item/allow.go new file mode 100644 index 00000000..fac2022f --- /dev/null +++ b/util/item/allow.go @@ -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 + +} diff --git a/util/item/blaze.go b/util/item/blaze.go new file mode 100644 index 00000000..fe666f0d --- /dev/null +++ b/util/item/blaze.go @@ -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() + +} diff --git a/util/item/check.go b/util/item/check.go new file mode 100644 index 00000000..4959c33f --- /dev/null +++ b/util/item/check.go @@ -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() + } + +} diff --git a/util/item/code.go b/util/item/code.go new file mode 100644 index 00000000..3af5607a --- /dev/null +++ b/util/item/code.go @@ -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 + } +} diff --git a/util/item/each.go b/util/item/each.go new file mode 100644 index 00000000..53401b21 --- /dev/null +++ b/util/item/each.go @@ -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 + +} diff --git a/util/item/erase.go b/util/item/erase.go new file mode 100644 index 00000000..b41408f4 --- /dev/null +++ b/util/item/erase.go @@ -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 +} diff --git a/util/item/item.go b/util/item/item.go index b5ae8e0a..bda1cc46 100644 --- a/util/item/item.go +++ b/util/item/item.go @@ -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,429 +98,107 @@ 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()) - out = append(out, idx) + var idx sql.DefineIndexStatement + pack.FromPACK(kv.Val(), &idx) + this.indexs = append(this.indexs, &idx) } return } -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- -// -------------------------------------------------- +func (this *Doc) StartThing() (err error) { -func (this *Doc) mrgFld(txn kvs.TX) (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 + } - vm := otto.New() + 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 + } - 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()) + return this.txn.CPut(this.key.Encode(), this.current.Encode(), nil) - for _, fld := range this.getFlds(txn) { +} - var exists bool - var initial interface{} - var current interface{} +func (this *Doc) PurgeThing() (err error) { - initial = this.initial.Get("data", fld.Name).Data() + return this.txn.Del(this.key.Encode()) - if fld.Readonly && initial != nil { - this.current.Set(initial, "data", fld.Name) - return - } +} - if fld.Code != "" { +func (this *Doc) StoreThing() (err error) { - ret, err := vm.Run("(function() { " + fld.Code + " })()") - if err != nil { - return fmt.Errorf("Problem executing code: %v %v", fld.Code, 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)) } + } - val, err := ret.Export() - if err != nil { - return fmt.Errorf("Problem executing code: %v %v", fld.Code, err.Error()) + 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)) } - - 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) - } - } - - 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) } } @@ -753,86 +207,70 @@ func (this *Doc) mrgFld(txn kvs.TX) (err error) { } -func (this *Doc) mrgAll(expr *sql.ContentExpression) { +func (this *Doc) StoreIndex() (err error) { - val := data.Consume(expr.JSON) + for _, index := range this.indexs { - this.current.Set(val.Data(), "data") + 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) + } + } + } -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") + return } -func (this *Doc) mrgDpm(expr *sql.DiffExpression) { +func buildIndex(cols []string, item *data.Doc) (out [][]interface{}) { -} + if len(cols) == 0 { + return [][]interface{}{nil} + } -func (this *Doc) mrgOne(expr *sql.BinaryExpression) { + col, cols := cols[0], cols[1:] - lhs := getDataItemLHS(this.current, expr.LHS) - rhs := getDataItemRHS(this.current, expr.RHS) + sub := buildIndex(cols, item) - if expr.Op == "=" { - switch expr.RHS.(type) { - default: - this.current.Set(rhs, "data", lhs) - case *sql.Void: - this.current.Del("data", lhs) + 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) + } + } + } else { + for _, s := range sub { + idx := []interface{}{} + idx = append(idx, item.Get("data", col).Data()) + idx = append(idx, s...) + out = append(out, idx) } } - 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() - } + return } diff --git a/util/item/merge.go b/util/item/merge.go new file mode 100644 index 00000000..3d04eea2 --- /dev/null +++ b/util/item/merge.go @@ -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() + } + +} diff --git a/util/item/patch.go b/util/item/patch.go new file mode 100644 index 00000000..1dbe4eb2 --- /dev/null +++ b/util/item/patch.go @@ -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) + +} diff --git a/util/item/yield.go b/util/item/yield.go new file mode 100644 index 00000000..0c851e50 --- /dev/null +++ b/util/item/yield.go @@ -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 + +}