From 08cf03f98b07ef599e30af99e4eab4dfef5fcfef Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Thu, 16 Nov 2017 20:31:20 +0000 Subject: [PATCH] =?UTF-8?q?Add=20=E2=80=98fncs=E2=80=99=20package=20for=20?= =?UTF-8?q?all=20sql=20function=20commands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- util/fncs/args.go | 237 ++++++++++++++++++++++++++++ util/fncs/args_test.go | 167 ++++++++++++++++++++ util/fncs/batch.go | 27 ++++ util/fncs/batch_test.go | 36 +++++ util/fncs/bcrypt.go | 40 +++++ util/fncs/bcrypt_test.go | 41 +++++ util/fncs/count.go | 49 ++++++ util/fncs/count_test.go | 57 +++++++ util/fncs/email.go | 43 ++++++ util/fncs/email_test.go | 60 ++++++++ util/fncs/fnc.go | 325 +++++++++++++++++++++++++++++++++++++++ util/fncs/fnc_test.go | 32 ++++ util/fncs/geo.go | 115 ++++++++++++++ util/fncs/geo_test.go | 90 +++++++++++ util/fncs/get.go | 33 ++++ util/fncs/get_test.go | 56 +++++++ util/fncs/hash.go | 56 +++++++ util/fncs/hash_test.go | 50 ++++++ util/fncs/http.go | 210 +++++++++++++++++++++++++ util/fncs/http_test.go | 158 +++++++++++++++++++ util/fncs/if.go | 26 ++++ util/fncs/if_test.go | 41 +++++ util/fncs/json.go | 32 ++++ util/fncs/json_test.go | 48 ++++++ util/fncs/math.go | 163 ++++++++++++++++++++ util/fncs/math_test.go | 264 +++++++++++++++++++++++++++++++ util/fncs/model.go | 46 ++++++ util/fncs/model_test.go | 55 +++++++ util/fncs/rand.go | 207 +++++++++++++++++++++++++ util/fncs/rand_test.go | 243 +++++++++++++++++++++++++++++ util/fncs/scrypt.go | 40 +++++ util/fncs/scrypt_test.go | 41 +++++ util/fncs/sets.go | 104 +++++++++++++ util/fncs/sets_test.go | 62 ++++++++ util/fncs/string.go | 180 ++++++++++++++++++++++ util/fncs/string_test.go | 171 ++++++++++++++++++++ util/fncs/table.go | 26 ++++ util/fncs/table_test.go | 36 +++++ util/fncs/thing.go | 26 ++++ util/fncs/thing_test.go | 36 +++++ util/fncs/time.go | 156 +++++++++++++++++++ util/fncs/time_test.go | 234 ++++++++++++++++++++++++++++ 42 files changed, 4119 insertions(+) create mode 100644 util/fncs/args.go create mode 100644 util/fncs/args_test.go create mode 100644 util/fncs/batch.go create mode 100644 util/fncs/batch_test.go create mode 100644 util/fncs/bcrypt.go create mode 100644 util/fncs/bcrypt_test.go create mode 100644 util/fncs/count.go create mode 100644 util/fncs/count_test.go create mode 100644 util/fncs/email.go create mode 100644 util/fncs/email_test.go create mode 100644 util/fncs/fnc.go create mode 100644 util/fncs/fnc_test.go create mode 100644 util/fncs/geo.go create mode 100644 util/fncs/geo_test.go create mode 100644 util/fncs/get.go create mode 100644 util/fncs/get_test.go create mode 100644 util/fncs/hash.go create mode 100644 util/fncs/hash_test.go create mode 100644 util/fncs/http.go create mode 100644 util/fncs/http_test.go create mode 100644 util/fncs/if.go create mode 100644 util/fncs/if_test.go create mode 100644 util/fncs/json.go create mode 100644 util/fncs/json_test.go create mode 100644 util/fncs/math.go create mode 100644 util/fncs/math_test.go create mode 100644 util/fncs/model.go create mode 100644 util/fncs/model_test.go create mode 100644 util/fncs/rand.go create mode 100644 util/fncs/rand_test.go create mode 100644 util/fncs/scrypt.go create mode 100644 util/fncs/scrypt_test.go create mode 100644 util/fncs/sets.go create mode 100644 util/fncs/sets_test.go create mode 100644 util/fncs/string.go create mode 100644 util/fncs/string_test.go create mode 100644 util/fncs/table.go create mode 100644 util/fncs/table_test.go create mode 100644 util/fncs/thing.go create mode 100644 util/fncs/thing_test.go create mode 100644 util/fncs/time.go create mode 100644 util/fncs/time_test.go diff --git a/util/fncs/args.go b/util/fncs/args.go new file mode 100644 index 00000000..715a8afd --- /dev/null +++ b/util/fncs/args.go @@ -0,0 +1,237 @@ +// 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 fncs + +import ( + "fmt" + "math" + "strconv" + "time" + + "github.com/abcum/surreal/sql" +) + +var defaultTime = time.Unix(0, 0) + +func outputFloat(val float64) (interface{}, error) { + switch { + case math.IsNaN(val): + return nil, nil + case math.IsInf(val, 0): + return nil, nil + default: + return val, nil + } +} + +func ensureString(val interface{}) (out string, ok bool) { + switch val := val.(type) { + case string: + return val, true + case nil: + return "", true + default: + return fmt.Sprint(val), true + } +} + +func ensureBytes(val interface{}) (out []byte, ok bool) { + switch val := val.(type) { + case []byte: + return val, true + case string: + return []byte(val), true + case nil: + return []byte(""), true + default: + return []byte(fmt.Sprint(val)), true + } +} + +func ensureBool(val interface{}) (out bool, ok bool) { + switch val := val.(type) { + case bool: + return val, true + case string: + if val, err := strconv.ParseBool(val); err == nil { + return val, true + } + } + return false, false +} + +func ensureInt(val interface{}) (out int64, ok bool) { + switch val := val.(type) { + case int: + return int64(val), true + case int64: + return int64(val), true + case uint: + return int64(val), true + case uint64: + return int64(val), true + case float32: + return int64(val), true + case float64: + return int64(val), true + case string: + if val, err := strconv.ParseFloat(val, 64); err == nil { + return int64(val), true + } + } + return 0, false +} + +func ensureFloat(val interface{}) (out float64, ok bool) { + switch val := val.(type) { + case int: + return float64(val), true + case int64: + return float64(val), true + case uint: + return float64(val), true + case uint64: + return float64(val), true + case float32: + return float64(val), true + case float64: + return float64(val), true + case string: + if val, err := strconv.ParseFloat(val, 64); err == nil { + return float64(val), true + } + } + return 0, false +} + +func ensureTime(val interface{}) (out time.Time, ok bool) { + switch val := val.(type) { + case time.Time: + return val, true + } + return defaultTime, false +} + +func ensurePoint(val interface{}) (out *sql.Point, ok bool) { + switch val := val.(type) { + case *sql.Point: + return val, true + } + return nil, false +} + +func ensureCircle(val interface{}) (out *sql.Circle, ok bool) { + switch val := val.(type) { + case *sql.Circle: + return val, true + } + return nil, false +} + +func ensurePolygon(val interface{}) (out *sql.Polygon, ok bool) { + switch val := val.(type) { + case *sql.Polygon: + return val, true + } + return nil, false +} + +func ensureGeometry(val interface{}) (out sql.Expr, ok bool) { + switch val := val.(type) { + case *sql.Point: + return val, true + case *sql.Circle: + return val, true + case *sql.Polygon: + return val, true + } + return nil, false +} + +func ensureDuration(val interface{}) (out time.Duration, ok bool) { + switch val := val.(type) { + case time.Duration: + return val, true + } + return 0, false +} + +func ensureSlice(args interface{}) (out []interface{}, ok bool) { + if i, ok := args.([]interface{}); ok { + out = i + } else { + out = []interface{}{args} + } + return out, true +} + +func ensureObject(args interface{}) (out map[string]interface{}, ok bool) { + if i, ok := args.(map[string]interface{}); ok { + out = i + } else { + out = map[string]interface{}{} + } + return out, true +} + +func ensureInts(args interface{}) (out []int64) { + arr, _ := ensureSlice(args) + for _, val := range arr { + switch val := val.(type) { + case int: + out = append(out, int64(val)) + case int64: + out = append(out, int64(val)) + case uint: + out = append(out, int64(val)) + case uint64: + out = append(out, int64(val)) + case float32: + out = append(out, int64(val)) + case float64: + out = append(out, int64(val)) + case string: + if val, err := strconv.ParseFloat(val, 64); err == nil { + out = append(out, int64(val)) + } + } + } + return +} + +func ensureFloats(args interface{}) (out []float64) { + arr, _ := ensureSlice(args) + for _, val := range arr { + switch val := val.(type) { + case int: + out = append(out, float64(val)) + case int64: + out = append(out, float64(val)) + case uint: + out = append(out, float64(val)) + case uint64: + out = append(out, float64(val)) + case float32: + out = append(out, float64(val)) + case float64: + out = append(out, float64(val)) + case string: + if val, err := strconv.ParseFloat(val, 64); err == nil { + out = append(out, float64(val)) + } + } + } + return +} diff --git a/util/fncs/args_test.go b/util/fncs/args_test.go new file mode 100644 index 00000000..c2a06b49 --- /dev/null +++ b/util/fncs/args_test.go @@ -0,0 +1,167 @@ +// 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 fncs + +import ( + "testing" + "time" + + "github.com/abcum/surreal/sql" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestArgs(t *testing.T) { + + now := time.Now() + + args := []interface{}{ + int(1), + int64(1), + uint(1), + uint64(1), + float32(1.5), + float64(1.5), + string("1"), + string("1.5"), + string("test"), + []byte("test"), + map[string]interface{}{"test": true}, + } + + good := args[0:8] + + oops := args[8:11] + + Convey("An argument will convert to string if possible", t, func() { + for _, arg := range args { + val, ok := ensureString(arg) + So(ok, ShouldEqual, true) + So(val, ShouldHaveSameTypeAs, string("")) + } + }) + + Convey("An argument will convert to []byte if possible", t, func() { + for _, arg := range args { + val, ok := ensureBytes(arg) + So(ok, ShouldEqual, true) + So(val, ShouldHaveSameTypeAs, []byte("")) + } + }) + + Convey("An argument will convert to int64 if possible", t, func() { + for _, arg := range good { + val, ok := ensureInt(arg) + So(ok, ShouldEqual, true) + So(val, ShouldHaveSameTypeAs, int64(0)) + } + for _, arg := range oops { + val, ok := ensureInt(arg) + So(ok, ShouldEqual, false) + So(val, ShouldHaveSameTypeAs, int64(0)) + } + }) + + Convey("An argument will convert to float64 if possible", t, func() { + for _, arg := range good { + val, ok := ensureFloat(arg) + So(ok, ShouldEqual, true) + So(val, ShouldHaveSameTypeAs, float64(0)) + } + for _, arg := range oops { + val, ok := ensureFloat(arg) + So(ok, ShouldEqual, false) + So(val, ShouldHaveSameTypeAs, float64(0)) + } + }) + + Convey("An argument will convert to time.Time if possible", t, func() { + res, ok := ensureTime("test") + So(ok, ShouldEqual, false) + So(res, ShouldEqual, time.Unix(0, 0)) + one, ok := ensureTime(now) + So(ok, ShouldEqual, true) + So(one, ShouldEqual, now) + }) + + Convey("An argument will convert to *sql.Point if possible", t, func() { + res, ok := ensurePoint("test") + So(ok, ShouldEqual, false) + So(res, ShouldEqual, nil) + one, ok := ensurePoint(&sql.Point{}) + So(ok, ShouldEqual, true) + So(one, ShouldResemble, &sql.Point{}) + }) + + Convey("An argument will convert to *sql.Circle if possible", t, func() { + res, ok := ensureCircle("test") + So(ok, ShouldEqual, false) + So(res, ShouldEqual, nil) + one, ok := ensureCircle(&sql.Circle{}) + So(ok, ShouldEqual, true) + So(one, ShouldResemble, &sql.Circle{}) + }) + + Convey("An argument will convert to *sql.Polygon if possible", t, func() { + res, ok := ensurePolygon("test") + So(ok, ShouldEqual, false) + So(res, ShouldEqual, nil) + one, ok := ensurePolygon(&sql.Polygon{}) + So(ok, ShouldEqual, true) + So(one, ShouldResemble, &sql.Polygon{}) + }) + + Convey("An argument will convert to *sql.Polygon if possible", t, func() { + res, ok := ensureGeometry("test") + So(ok, ShouldEqual, false) + So(res, ShouldEqual, nil) + one, ok := ensureGeometry(&sql.Point{}) + So(ok, ShouldEqual, true) + So(one, ShouldResemble, &sql.Point{}) + two, ok := ensureGeometry(&sql.Circle{}) + So(ok, ShouldEqual, true) + So(two, ShouldResemble, &sql.Circle{}) + tre, ok := ensureGeometry(&sql.Polygon{}) + So(ok, ShouldEqual, true) + So(tre, ShouldResemble, &sql.Polygon{}) + }) + + Convey("Arguments are converted to []int64 if possible", t, func() { + res := ensureInts(args) + So(res, ShouldHaveSameTypeAs, []int64{}) + So(len(res), ShouldEqual, 8) + }) + + Convey("Arguments are converted to []float64 if possible", t, func() { + res := ensureFloats(args) + So(res, ShouldHaveSameTypeAs, []float64{}) + So(len(res), ShouldEqual, 8) + }) + + Convey("Arguments are converted to []interface{} if possible", t, func() { + for _, arg := range args { + res, _ := ensureSlice(arg) + So(res, ShouldHaveSameTypeAs, []interface{}{}) + } + }) + + Convey("Arguments are converted to map[string]interface{} if possible", t, func() { + for _, arg := range args { + res, _ := ensureObject(arg) + So(res, ShouldHaveSameTypeAs, map[string]interface{}{}) + } + }) + +} diff --git a/util/fncs/batch.go b/util/fncs/batch.go new file mode 100644 index 00000000..f82ffe2e --- /dev/null +++ b/util/fncs/batch.go @@ -0,0 +1,27 @@ +// 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 fncs + +import ( + "context" + + "github.com/abcum/surreal/sql" +) + +func batch(ctx context.Context, args ...interface{}) (*sql.Batch, error) { + tb, _ := ensureString(args[0]) + id, _ := ensureSlice(args[1]) + return sql.NewBatch(tb, id), nil +} diff --git a/util/fncs/batch_test.go b/util/fncs/batch_test.go new file mode 100644 index 00000000..fc197267 --- /dev/null +++ b/util/fncs/batch_test.go @@ -0,0 +1,36 @@ +// 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 fncs + +import ( + "context" + "testing" + + "github.com/abcum/surreal/sql" + . "github.com/smartystreets/goconvey/convey" +) + +func TestBatch(t *testing.T) { + + var res interface{} + + Convey("batch() works properly", t, func() { + res, _ = Run(context.Background(), "batch", 1, []interface{}{1, 2, 3}) + So(res, ShouldResemble, sql.NewBatch("1", []interface{}{1, 2, 3})) + res, _ = Run(context.Background(), "batch", "test", []interface{}{1, 2, 3}) + So(res, ShouldResemble, sql.NewBatch("test", []interface{}{1, 2, 3})) + }) + +} diff --git a/util/fncs/bcrypt.go b/util/fncs/bcrypt.go new file mode 100644 index 00000000..436aba05 --- /dev/null +++ b/util/fncs/bcrypt.go @@ -0,0 +1,40 @@ +// 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 fncs + +import ( + "context" + + "golang.org/x/crypto/bcrypt" +) + +func bcryptCompare(ctx context.Context, args ...interface{}) (bool, error) { + if h, ok := ensureString(args[0]); ok { + if s, ok := ensureString(args[1]); ok { + e := bcrypt.CompareHashAndPassword([]byte(h), []byte(s)) + if e == nil { + return true, nil + } + } + } + return false, nil +} + +func bcryptGenerate(ctx context.Context, args ...interface{}) ([]byte, error) { + s, _ := ensureString(args[0]) + p := []byte(s) + o := bcrypt.DefaultCost + return bcrypt.GenerateFromPassword(p, o) +} diff --git a/util/fncs/bcrypt_test.go b/util/fncs/bcrypt_test.go new file mode 100644 index 00000000..f503a431 --- /dev/null +++ b/util/fncs/bcrypt_test.go @@ -0,0 +1,41 @@ +// 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 fncs + +import ( + "context" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestBcrypt(t *testing.T) { + + Convey("bcrypt.compare(a, b) works properly", t, func() { + res, _ := Run(context.Background(), "bcrypt.compare", "$2y$10$XPMT7nWucHJK113jjomfJ.xa64/jhH7sYrRJ9/0Q2CjzBTGwejUx.", "test") + So(res, ShouldEqual, true) + }) + + Convey("bcrypt.compare(a, b) errors properly", t, func() { + res, _ := Run(context.Background(), "bcrypt.compare", "$2y$10$XPMT7nWucHJK113jjomfJ.xa64/jhH7sYrRJ9/0Q2CjzBTGwejUx.", "wrong") + So(res, ShouldEqual, false) + }) + + Convey("bcrypt.generate(a) works properly", t, func() { + res, _ := Run(context.Background(), "bcrypt.generate", "test") + So(res, ShouldHaveSameTypeAs, []byte("test")) + }) + +} diff --git a/util/fncs/count.go b/util/fncs/count.go new file mode 100644 index 00000000..e96139f8 --- /dev/null +++ b/util/fncs/count.go @@ -0,0 +1,49 @@ +// 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 fncs + +import ( + "context" +) + +func count(ctx context.Context, args ...interface{}) (out float64, err error) { + arr, _ := ensureSlice(args[0]) + for _, v := range arr { + if v != nil { + out++ + } + } + return +} + +func countIf(ctx context.Context, args ...interface{}) (out float64, err error) { + arr, _ := ensureSlice(args[0]) + for _, v := range arr { + if v == args[1] { + out++ + } + } + return +} + +func countNot(ctx context.Context, args ...interface{}) (out float64, err error) { + arr, _ := ensureSlice(args[0]) + for _, v := range arr { + if v != args[1] { + out++ + } + } + return +} diff --git a/util/fncs/count_test.go b/util/fncs/count_test.go new file mode 100644 index 00000000..598798c0 --- /dev/null +++ b/util/fncs/count_test.go @@ -0,0 +1,57 @@ +// 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 fncs + +import ( + "context" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestCount(t *testing.T) { + + var res interface{} + + var test = []interface{}{int(1), int(2), nil, float64(3.5), float64(4.5), float64(3.5)} + + Convey("count() works properly", t, func() { + res, _ = Run(context.Background(), "count", "test") + So(res, ShouldEqual, 1) + res, _ = Run(context.Background(), "count", 10) + So(res, ShouldEqual, 1) + res, _ = Run(context.Background(), "count", test) + So(res, ShouldEqual, 5) + }) + + Convey("count.if() works properly", t, func() { + res, _ = Run(context.Background(), "count.if", "test", 3.5) + So(res, ShouldEqual, 0) + res, _ = Run(context.Background(), "count.if", 10, 3.5) + So(res, ShouldEqual, 0) + res, _ = Run(context.Background(), "count.if", test, 3.5) + So(res, ShouldEqual, 2) + }) + + Convey("count.not() works properly", t, func() { + res, _ = Run(context.Background(), "count.not", "test", 3.5) + So(res, ShouldEqual, 1) + res, _ = Run(context.Background(), "count.not", 10, 3.5) + So(res, ShouldEqual, 1) + res, _ = Run(context.Background(), "count.not", test, 3.5) + So(res, ShouldEqual, 4) + }) + +} diff --git a/util/fncs/email.go b/util/fncs/email.go new file mode 100644 index 00000000..2cbcc87c --- /dev/null +++ b/util/fncs/email.go @@ -0,0 +1,43 @@ +// 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 fncs + +import ( + "context" + "strings" + + "github.com/asaskevich/govalidator" +) + +func emailUser(ctx context.Context, args ...interface{}) (string, error) { + v, _ := ensureString(args[0]) + if p := strings.LastIndexByte(v, '@'); p > 0 { + return v[:p], nil + } + return "", nil +} + +func emailDomain(ctx context.Context, args ...interface{}) (string, error) { + v, _ := ensureString(args[0]) + if p := strings.LastIndexByte(v, '@'); p > 0 { + return v[p+1:], nil + } + return "", nil +} + +func emailValid(ctx context.Context, args ...interface{}) (bool, error) { + v, _ := ensureString(args[0]) + return govalidator.IsEmail(v), nil +} diff --git a/util/fncs/email_test.go b/util/fncs/email_test.go new file mode 100644 index 00000000..bc83b334 --- /dev/null +++ b/util/fncs/email_test.go @@ -0,0 +1,60 @@ +// 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 fncs + +import ( + "context" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestEmail(t *testing.T) { + + Convey("email.user(a) works properly", t, func() { + res, _ := Run(context.Background(), "email.user", "tobie@abcum.com") + So(res, ShouldHaveSameTypeAs, "test") + So(res, ShouldEqual, "tobie") + }) + + Convey("email.user(a) errors properly", t, func() { + res, _ := Run(context.Background(), "email.user", "test") + So(res, ShouldHaveSameTypeAs, "test") + So(res, ShouldEqual, "") + }) + + Convey("email.domain(a) works properly", t, func() { + res, _ := Run(context.Background(), "email.domain", "tobie@abcum.com") + So(res, ShouldHaveSameTypeAs, "test") + So(res, ShouldEqual, "abcum.com") + }) + + Convey("email.domain(a) errors properly", t, func() { + res, _ := Run(context.Background(), "email.domain", "test") + So(res, ShouldHaveSameTypeAs, "test") + So(res, ShouldEqual, "") + }) + + Convey("email.valid(a) works properly", t, func() { + res, _ := Run(context.Background(), "email.valid", "tobie@abcum.com") + So(res, ShouldEqual, true) + }) + + Convey("email.valid(a) errors properly", t, func() { + res, _ := Run(context.Background(), "email.valid", "test") + So(res, ShouldEqual, false) + }) + +} diff --git a/util/fncs/fnc.go b/util/fncs/fnc.go new file mode 100644 index 00000000..95d1d162 --- /dev/null +++ b/util/fncs/fnc.go @@ -0,0 +1,325 @@ +// 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 fncs + +import "context" + +func Run(ctx context.Context, name string, args ...interface{}) (interface{}, error) { + + switch name { + + case "batch": + return batch(ctx, args...) + case "difference": + return difference(ctx, args...) + case "distinct": + return distinct(ctx, args...) + case "get": + return get(ctx, args...) + case "if": + return ifel(ctx, args...) + case "intersect": + return intersect(ctx, args...) + case "model": + return model(ctx, args...) + case "table": + return table(ctx, args...) + case "thing": + return thing(ctx, args...) + case "union": + return union(ctx, args...) + + // Count implementation + case "count": + return count(ctx, args...) + case "count.if": + return countIf(ctx, args...) + case "count.not": + return countNot(ctx, args...) + + // Json implementation + case "json.decode": + return jsonDecode(ctx, args...) + case "json.encode": + return jsonEncode(ctx, args...) + + // Geo implementation + case "geo.point": + return geoPoint(ctx, args...) + case "geo.circle": + return geoCircle(ctx, args...) + case "geo.polygon": + return geoPolygon(ctx, args...) + case "geo.distance": + return geoDistance(ctx, args...) + case "geo.inside": + return geoInside(ctx, args...) + case "geo.intersects": + return geoIntersects(ctx, args...) + case "geo.hash.decode": + return geoHashDecode(ctx, args...) + case "geo.hash.encode": + return geoHashEncode(ctx, args...) + + // Http implementation + case "http.head": + return httpHead(ctx, args...) + case "http.get": + return httpGet(ctx, args...) + case "http.put": + return httpPut(ctx, args...) + case "http.post": + return httpPost(ctx, args...) + case "http.patch": + return httpPatch(ctx, args...) + case "http.delete": + return httpDelete(ctx, args...) + case "http.async.head": + return httpAsyncHead(ctx, args...) + case "http.async.get": + return httpAsyncGet(ctx, args...) + case "http.async.put": + return httpAsyncPut(ctx, args...) + case "http.async.post": + return httpAsyncPost(ctx, args...) + case "http.async.patch": + return httpAsyncPatch(ctx, args...) + case "http.async.delete": + return httpAsyncDelete(ctx, args...) + + // Math implementation + case "math.abs", "abs": + return mathAbs(ctx, args...) + case "math.bottom", "bottom": + return mathBottom(ctx, args...) + case "math.ceil", "ceil": + return mathCeil(ctx, args...) + case "math.correlation", "correlation": + return mathCorrelation(ctx, args...) + case "math.covariance", "covariance": + return mathCovariance(ctx, args...) + case "math.floor", "floor": + return mathFloor(ctx, args...) + case "math.geometricmean", "geometricmean": + return mathGeometricmean(ctx, args...) + case "math.harmonicmean", "harmonicmean": + return mathHarmonicmean(ctx, args...) + case "math.interquartile", "interquartile": + return mathInterquartile(ctx, args...) + case "math.max", "max": + return mathMax(ctx, args...) + case "math.mean", "mean": + return mathMean(ctx, args...) + case "math.median", "median": + return mathMedian(ctx, args...) + case "math.midhinge", "midhinge": + return mathMidhinge(ctx, args...) + case "math.min", "min": + return mathMin(ctx, args...) + case "math.mode", "mode": + return mathMode(ctx, args...) + case "math.percentile", "percentile": + return mathPercentile(ctx, args...) + case "math.round", "round": + return mathRound(ctx, args...) + case "math.sample", "sample": + return mathSample(ctx, args...) + case "math.spread", "spread": + return mathSpread(ctx, args...) + case "math.stddev", "stddev": + return mathStddev(ctx, args...) + case "math.sum", "sum": + return mathSum(ctx, args...) + case "math.top", "top": + return mathTop(ctx, args...) + case "math.trimean", "trimean": + return mathTrimean(ctx, args...) + case "math.variance", "variance": + return mathVariance(ctx, args...) + + // String implementation + case "string.concat": + return stringConcat(ctx, args...) + case "string.contains": + return stringContains(ctx, args...) + case "string.endsWith": + return stringEndsWith(ctx, args...) + case "string.format": + return stringFormat(ctx, args...) + case "string.includes": + return stringIncludes(ctx, args...) + case "string.join": + return stringJoin(ctx, args...) + case "string.length": + return stringLength(ctx, args...) + case "string.levenshtein": + return stringLevenshtein(ctx, args...) + case "string.lowercase": + return stringLowercase(ctx, args...) + case "string.repeat": + return stringRepeat(ctx, args...) + case "string.replace": + return stringReplace(ctx, args...) + case "string.reverse": + return stringReverse(ctx, args...) + case "string.search": + return stringSearch(ctx, args...) + case "string.slice": + return stringSlice(ctx, args...) + case "string.split": + return stringSplit(ctx, args...) + case "string.startsWith": + return stringStartsWith(ctx, args...) + case "string.substr": + return stringSubstr(ctx, args...) + case "string.trim": + return stringTrim(ctx, args...) + case "string.uppercase": + return stringUppercase(ctx, args...) + case "string.words": + return stringWords(ctx, args...) + + // Hash implementation + case "hash.md5": + return hashMd5(ctx, args...) + case "hash.sha1": + return hashSha1(ctx, args...) + case "hash.sha256": + return hashSha256(ctx, args...) + case "hash.sha512": + return hashSha512(ctx, args...) + + // Time implementation + case "time.now": + return timeNow(ctx, args...) + case "time.add": + return timeAdd(ctx, args...) + case "time.age": + return timeAge(ctx, args...) + case "time.floor": + return timeFloor(ctx, args...) + case "time.round": + return timeRound(ctx, args...) + case "time.day": + return timeDay(ctx, args...) + case "time.hour": + return timeHour(ctx, args...) + case "time.mins": + return timeMins(ctx, args...) + case "time.month": + return timeMonth(ctx, args...) + case "time.nano": + return timeNano(ctx, args...) + case "time.secs": + return timeSecs(ctx, args...) + case "time.unix": + return timeUnix(ctx, args...) + case "time.year": + return timeYear(ctx, args...) + + // Email implementation + case "email.user": + return emailUser(ctx, args...) + case "email.domain": + return emailDomain(ctx, args...) + case "email.valid": + return emailValid(ctx, args...) + + // Bcrypt implementation + case "bcrypt.compare": + return bcryptCompare(ctx, args...) + case "bcrypt.generate": + return bcryptGenerate(ctx, args...) + + // Scrypt implementation + case "scrypt.compare": + return scryptCompare(ctx, args...) + case "scrypt.generate": + return scryptGenerate(ctx, args...) + + // Rand implementation + case "rand": + return rand(ctx, args...) + case "uuid": + return randUuid(ctx, args...) + case "rand.bool": + return randBool(ctx, args...) + case "rand.uuid": + return randUuid(ctx, args...) + case "rand.enum": + return randEnum(ctx, args...) + case "rand.time": + return randTime(ctx, args...) + case "rand.string": + return randString(ctx, args...) + case "rand.integer": + return randInteger(ctx, args...) + case "rand.decimal": + return randDecimal(ctx, args...) + case "rand.word": + return randWord(ctx, args...) + case "rand.sentence": + return randSentence(ctx, args...) + case "rand.paragraph": + return randParagraph(ctx, args...) + case "rand.person.email": + return randPersonEmail(ctx, args...) + case "rand.person.phone": + return randPersonPhone(ctx, args...) + case "rand.person.fullname": + return randPersonFullname(ctx, args...) + case "rand.person.firstname": + return randPersonFirstname(ctx, args...) + case "rand.person.lastname": + return randPersonLastname(ctx, args...) + case "rand.person.username": + return randPersonUsername(ctx, args...) + case "rand.person.jobtitle": + return randPersonJobtitle(ctx, args...) + case "rand.company.name": + return randCompanyName(ctx, args...) + case "rand.company.industry": + return randCompanyIndustry(ctx, args...) + case "rand.location.name": + return randLocationName(ctx, args...) + case "rand.location.address": + return randLocationAddress(ctx, args...) + case "rand.location.street": + return randLocationStreet(ctx, args...) + case "rand.location.city": + return randLocationCity(ctx, args...) + case "rand.location.state": + return randLocationState(ctx, args...) + case "rand.location.county": + return randLocationCounty(ctx, args...) + case "rand.location.zipcode": + return randLocationZipcode(ctx, args...) + case "rand.location.postcode": + return randLocationPostcode(ctx, args...) + case "rand.location.country": + return randLocationCountry(ctx, args...) + case "rand.location.altitude": + return randLocationAltitude(ctx, args...) + case "rand.location.latitude": + return randLocationLatitude(ctx, args...) + case "rand.location.longitude": + return randLocationLongitude(ctx, args...) + + default: + return nil, nil // Should never get here + + } + +} diff --git a/util/fncs/fnc_test.go b/util/fncs/fnc_test.go new file mode 100644 index 00000000..0d798c5a --- /dev/null +++ b/util/fncs/fnc_test.go @@ -0,0 +1,32 @@ +// 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 fncs + +import ( + "context" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestNil(t *testing.T) { + + Convey("invalid.function() returns nil", t, func() { + res, _ := Run(context.Background(), "invalid.function") + So(res, ShouldHaveSameTypeAs, nil) + So(res, ShouldEqual, nil) + }) + +} diff --git a/util/fncs/geo.go b/util/fncs/geo.go new file mode 100644 index 00000000..5eb1b00d --- /dev/null +++ b/util/fncs/geo.go @@ -0,0 +1,115 @@ +// 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 fncs + +import ( + "context" + + "github.com/abcum/surreal/sql" + "github.com/abcum/surreal/util/geof" +) + +func geoPoint(ctx context.Context, args ...interface{}) (*sql.Point, error) { + switch len(args) { + case 1: + if p, ok := ensurePoint(args[0]); ok { + return p, nil + } + if p := ensureFloats(args[0]); len(p) == 2 { + return sql.NewPoint(p[0], p[1]), nil + } + case 2: + if lat, ok := ensureFloat(args[0]); ok { + if lng, ok := ensureFloat(args[1]); ok { + return sql.NewPoint(lat, lng), nil + } + } + } + return nil, nil +} + +func geoCircle(ctx context.Context, args ...interface{}) (*sql.Circle, error) { + if cen, ok := ensurePoint(args[0]); ok { + if rad, ok := ensureFloat(args[1]); ok { + return sql.NewCircle(cen, rad), nil + } + } + if val := ensureFloats(args[0]); len(val) == 2 { + cen, _ := geoPoint(ctx, val[0], val[1]) + if rad, ok := ensureFloat(args[1]); ok { + return sql.NewCircle(cen, rad), nil + } + } + return nil, nil +} + +func geoPolygon(ctx context.Context, args ...interface{}) (*sql.Polygon, error) { + switch len(args) { + case 0, 1, 2: + // Not enough arguments, so just ignore + default: + var pnts sql.Points + for _, a := range args { + if p, _ := geoPoint(ctx, a); p != nil { + pnts = append(pnts, p) + } else { + return nil, nil + } + } + return sql.NewPolygon(pnts...), nil + } + return nil, nil +} + +func geoDistance(ctx context.Context, args ...interface{}) (float64, error) { + if pnt, ok := ensurePoint(args[0]); ok { + if frm, ok := ensurePoint(args[1]); ok { + return geof.Haversine(pnt, frm), nil + } + } + return -1, nil +} + +func geoInside(ctx context.Context, args ...interface{}) (bool, error) { + if a, ok := ensureGeometry(args[0]); ok { + if b, ok := ensureGeometry(args[1]); ok { + return geof.Inside(a, b), nil + } + } + return false, nil +} + +func geoIntersects(ctx context.Context, args ...interface{}) (bool, error) { + if a, ok := ensureGeometry(args[0]); ok { + if b, ok := ensureGeometry(args[1]); ok { + return geof.Intersects(a, b), nil + } + } + return false, nil +} + +func geoHashDecode(ctx context.Context, args ...interface{}) (*sql.Point, error) { + s, _ := ensureString(args[0]) + return geof.GeohashDecode(s), nil +} + +func geoHashEncode(ctx context.Context, args ...interface{}) (string, error) { + if pnt, ok := ensurePoint(args[0]); ok { + if acc, ok := ensureInt(args[1]); ok { + return geof.GeohashEncode(pnt, acc), nil + } + } + return "", nil +} diff --git a/util/fncs/geo_test.go b/util/fncs/geo_test.go new file mode 100644 index 00000000..55b0774b --- /dev/null +++ b/util/fncs/geo_test.go @@ -0,0 +1,90 @@ +// 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 fncs + +import ( + "context" + "testing" + + "github.com/abcum/surreal/sql" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestGeo(t *testing.T) { + + var res interface{} + + Convey("geo.point(a, b) works properly", t, func() { + res, _ = Run(context.Background(), "geo.point", "test", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "geo.point", &sql.Point{38.898556, -77.037852}) + So(res, ShouldResemble, &sql.Point{38.898556, -77.037852}) + res, _ = Run(context.Background(), "geo.point", []interface{}{38.898556, -77.037852}) + So(res, ShouldResemble, &sql.Point{38.898556, -77.037852}) + res, _ = Run(context.Background(), "geo.point", 38.898556, -77.037852) + So(res, ShouldResemble, &sql.Point{38.898556, -77.037852}) + }) + + Convey("geo.circle(a, b) works properly", t, func() { + res, _ = Run(context.Background(), "geo.circle", "test", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "geo.circle", &sql.Point{38.898556, -77.037852}, 100) + So(res, ShouldResemble, &sql.Circle{&sql.Point{38.898556, -77.037852}, 100}) + res, _ = Run(context.Background(), "geo.circle", []interface{}{38.898556, -77.037852}, 100) + So(res, ShouldResemble, &sql.Circle{&sql.Point{38.898556, -77.037852}, 100}) + }) + + Convey("geo.polygon(a, b) works properly", t, func() { + res, _ = Run(context.Background(), "geo.polygon", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "geo.polygon", "test", "test", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "geo.polygon", "test", &sql.Point{}, &sql.Point{}, &sql.Point{}) + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "geo.polygon", &sql.Point{}, &sql.Point{}, &sql.Point{}, &sql.Point{}) + So(res, ShouldResemble, &sql.Polygon{sql.Points{&sql.Point{}, &sql.Point{}, &sql.Point{}, &sql.Point{}}}) + }) + + Convey("geo.distance(a, b, c, d) works properly", t, func() { + res, _ = Run(context.Background(), "geo.distance", &sql.Point{38.898556, -77.037852}, &sql.Point{38.897147, -77.043934}) + So(res, ShouldEqual, 549.1557912048178) + }) + + Convey("geo.distance(a, b, c, d) errors properly", t, func() { + res, _ = Run(context.Background(), "geo.distance", &sql.Point{38.898556, -77.037852}, "null") + So(res, ShouldEqual, -1) + }) + + Convey("geo.hash.decode(a) works properly", t, func() { + res, _ = Run(context.Background(), "geo.hash.decode", "dqcjq") + So(res, ShouldResemble, &sql.Point{38.91357421875, -77.05810546875}) + res, _ = Run(context.Background(), "geo.hash.decode", "dqcjqcq8x") + So(res, ShouldResemble, &sql.Point{38.9230709412368, -77.06750950572314}) + }) + + Convey("geo.hash.encode(a, b, c) works properly", t, func() { + res, _ = Run(context.Background(), "geo.hash.encode", &sql.Point{38.898556, -77.037852}, 5) + So(res, ShouldEqual, "dqcjq") + res, _ = Run(context.Background(), "geo.hash.encode", &sql.Point{38.898556, -77.037852}, 9) + So(res, ShouldEqual, "dqcjqcq8x") + }) + + Convey("geo.hash.encode(a, b, c) errors properly", t, func() { + res, _ = Run(context.Background(), "geo.hash.encode", 0, 0, "null") + So(res, ShouldEqual, "") + }) + +} diff --git a/util/fncs/get.go b/util/fncs/get.go new file mode 100644 index 00000000..5a4fb6f2 --- /dev/null +++ b/util/fncs/get.go @@ -0,0 +1,33 @@ +// 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 fncs + +import ( + "context" + + "github.com/abcum/surreal/sql" + "github.com/abcum/surreal/util/data" +) + +func get(ctx context.Context, args ...interface{}) (interface{}, error) { + doc := data.Consume(args[0]) + switch fld := args[1].(type) { + case *sql.Value: + return doc.Get(fld.ID).Data(), nil + case string: + return doc.Get(fld).Data(), nil + } + return nil, nil +} diff --git a/util/fncs/get_test.go b/util/fncs/get_test.go new file mode 100644 index 00000000..07b92860 --- /dev/null +++ b/util/fncs/get_test.go @@ -0,0 +1,56 @@ +// 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 fncs + +import ( + "context" + "testing" + + "github.com/abcum/surreal/sql" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestGet(t *testing.T) { + + var res interface{} + + var doc = map[string]interface{}{ + "object": map[string]interface{}{ + "test": "ok", + }, + "string": "test", + "bool": true, + "int": 13, + } + + Convey("get(a, b) works properly", t, func() { + res, _ = Run(context.Background(), "get", doc, &sql.Value{"object.test"}) + So(res, ShouldEqual, "ok") + res, _ = Run(context.Background(), "get", doc, "object.test") + So(res, ShouldEqual, "ok") + res, _ = Run(context.Background(), "get", doc, "string") + So(res, ShouldEqual, "test") + res, _ = Run(context.Background(), "get", doc, "bool") + So(res, ShouldEqual, true) + res, _ = Run(context.Background(), "get", doc, "int") + So(res, ShouldEqual, 13) + res, _ = Run(context.Background(), "get", doc, "err") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "get", doc, nil) + So(res, ShouldEqual, nil) + }) + +} diff --git a/util/fncs/hash.go b/util/fncs/hash.go new file mode 100644 index 00000000..39453b49 --- /dev/null +++ b/util/fncs/hash.go @@ -0,0 +1,56 @@ +// 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 fncs + +import ( + "context" + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/hex" +) + +func hashMd5(ctx context.Context, args ...interface{}) (string, error) { + v, _ := ensureString(args[0]) + h := md5.New() + h.Write([]byte(v)) + s := h.Sum(nil) + return hex.EncodeToString(s), nil +} + +func hashSha1(ctx context.Context, args ...interface{}) (string, error) { + v, _ := ensureString(args[0]) + h := sha1.New() + h.Write([]byte(v)) + s := h.Sum(nil) + return hex.EncodeToString(s), nil +} + +func hashSha256(ctx context.Context, args ...interface{}) (string, error) { + v, _ := ensureString(args[0]) + h := sha256.New() + h.Write([]byte(v)) + s := h.Sum(nil) + return hex.EncodeToString(s), nil +} + +func hashSha512(ctx context.Context, args ...interface{}) (string, error) { + v, _ := ensureString(args[0]) + h := sha512.New() + h.Write([]byte(v)) + s := h.Sum(nil) + return hex.EncodeToString(s), nil +} diff --git a/util/fncs/hash_test.go b/util/fncs/hash_test.go new file mode 100644 index 00000000..1f6051dc --- /dev/null +++ b/util/fncs/hash_test.go @@ -0,0 +1,50 @@ +// 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 fncs + +import ( + "context" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestHash(t *testing.T) { + + Convey("hash.md5(a) works properly", t, func() { + res, _ := Run(context.Background(), "hash.md5", "test") + So(res, ShouldHaveSameTypeAs, "test") + So(res, ShouldEqual, "098f6bcd4621d373cade4e832627b4f6") + }) + + Convey("hash.sha1(a) works properly", t, func() { + res, _ := Run(context.Background(), "hash.sha1", "test") + So(res, ShouldHaveSameTypeAs, "test") + So(res, ShouldEqual, "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3") + }) + + Convey("hash.sha256(a) works properly", t, func() { + res, _ := Run(context.Background(), "hash.sha256", "test") + So(res, ShouldHaveSameTypeAs, "test") + So(res, ShouldEqual, "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08") + }) + + Convey("hash.sha512(a) works properly", t, func() { + res, _ := Run(context.Background(), "hash.sha512", "test") + So(res, ShouldHaveSameTypeAs, "test") + So(res, ShouldEqual, "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff") + }) + +} diff --git a/util/fncs/http.go b/util/fncs/http.go new file mode 100644 index 00000000..b2831635 --- /dev/null +++ b/util/fncs/http.go @@ -0,0 +1,210 @@ +// 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 fncs + +import ( + "bytes" + "context" + "errors" + "io" + "io/ioutil" + "net/http" + "time" + + "encoding/json" + + "golang.org/x/net/context/ctxhttp" + + "github.com/abcum/surreal/util/build" + "github.com/abcum/surreal/util/hook" +) + +type opts map[string]interface{} + +var version = build.GetInfo().Ver + +var httpResponseError = errors.New("HTTP response error") + +func httpHead(ctx context.Context, args ...interface{}) (interface{}, error) { + return runSync(ctx, "HEAD", args...) +} + +func httpGet(ctx context.Context, args ...interface{}) (interface{}, error) { + return runSync(ctx, "GET", args...) +} + +func httpPut(ctx context.Context, args ...interface{}) (interface{}, error) { + return runSync(ctx, "PUT", args...) +} + +func httpPost(ctx context.Context, args ...interface{}) (interface{}, error) { + return runSync(ctx, "POST", args...) +} + +func httpPatch(ctx context.Context, args ...interface{}) (interface{}, error) { + return runSync(ctx, "PATCH", args...) +} + +func httpDelete(ctx context.Context, args ...interface{}) (interface{}, error) { + return runSync(ctx, "DELETE", args...) +} + +func httpAsyncHead(ctx context.Context, args ...interface{}) (interface{}, error) { + return runAsync(ctx, "HEAD", args...) +} + +func httpAsyncGet(ctx context.Context, args ...interface{}) (interface{}, error) { + return runAsync(ctx, "GET", args...) +} + +func httpAsyncPut(ctx context.Context, args ...interface{}) (interface{}, error) { + return runAsync(ctx, "PUT", args...) +} + +func httpAsyncPost(ctx context.Context, args ...interface{}) (interface{}, error) { + return runAsync(ctx, "POST", args...) +} + +func httpAsyncPatch(ctx context.Context, args ...interface{}) (interface{}, error) { + return runAsync(ctx, "PATCH", args...) +} + +func httpAsyncDelete(ctx context.Context, args ...interface{}) (interface{}, error) { + return runAsync(ctx, "DELETE", args...) +} + +func runSync(ctx context.Context, met string, args ...interface{}) (interface{}, error) { + url, bdy, opt := httpCnf(met, args...) + out, _ := httpRes(ctx, met, url, bdy, opt) + return out, nil +} + +func runAsync(ctx context.Context, met string, args ...interface{}) (interface{}, error) { + url, bdy, opt := httpCnf(met, args...) + ctx = context.Background() + go hook.NewBackoff(5, 5, 10*time.Second).Run(ctx, func() error { + return httpErr(httpReq(ctx, met, url, bdy, opt)) + }) + return nil, nil +} + +func httpCnf(met string, args ...interface{}) (url string, bdy io.Reader, opt opts) { + var bit []byte + switch met { + case "HEAD", "GET", "DELETE": + switch len(args) { + case 1: + url, _ = ensureString(args[0]) + case 2: + url, _ = ensureString(args[0]) + opt, _ = ensureObject(args[1]) + } + case "PUT", "POST", "PATCH": + switch len(args) { + case 1: + url, _ = ensureString(args[0]) + case 2: + url, _ = ensureString(args[0]) + switch v := args[1].(type) { + case []interface{}, map[string]interface{}: + bit, _ = json.Marshal(v) + default: + bit, _ = ensureBytes(v) + } + case 3: + url, _ = ensureString(args[0]) + switch v := args[1].(type) { + case []interface{}, map[string]interface{}: + bit, _ = json.Marshal(v) + default: + bit, _ = ensureBytes(v) + } + opt, _ = ensureObject(args[2]) + } + } + return url, bytes.NewReader(bit), opt +} + +func httpReq(ctx context.Context, met, url string, body io.Reader, conf opts) (*http.Response, error) { + + cli := new(http.Client) + + req, err := http.NewRequest(met, url, body) + if err != nil { + return nil, err + } + + req.Header.Set("User-Agent", "SurrealDB HTTP/"+version) + + if val, ok := conf["auth"]; ok { + if opt, ok := ensureObject(val); ok { + user, _ := ensureString(opt["user"]) + pass, _ := ensureString(opt["pass"]) + req.SetBasicAuth(user, pass) + } + } + + if val, ok := conf["head"]; ok { + if opt, ok := ensureObject(val); ok { + for key, v := range opt { + head, _ := ensureString(v) + req.Header.Set(key, head) + } + } + } + + res, err := ctxhttp.Do(ctx, cli, req) + if err != nil { + return nil, err + } + + return res, nil + +} + +func httpRes(ctx context.Context, met, url string, body io.Reader, conf opts) (interface{}, error) { + + var out interface{} + + res, err := httpReq(ctx, met, url, body, conf) + if err != nil { + return nil, err + } + + bdy, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + + err = json.Unmarshal(bdy, &out) + if err != nil { + if len(bdy) != 0 { + return bdy, nil + } + } + + return out, nil + +} + +func httpErr(res *http.Response, err error) error { + if err != nil { + return err + } + if res.StatusCode >= 500 { + return httpResponseError + } + return nil +} diff --git a/util/fncs/http_test.go b/util/fncs/http_test.go new file mode 100644 index 00000000..8a1c8bfb --- /dev/null +++ b/util/fncs/http_test.go @@ -0,0 +1,158 @@ +// 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 fncs + +import ( + "context" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestHttp(t *testing.T) { + + var res interface{} + var jsn = `{"test":true, "temp":"text"}` + var obj = map[string]interface{}{"temp": "text", "test": true} + var txt = `` + var opt = map[string]interface{}{ + "auth": map[string]interface{}{"user": "u", "pass": "p"}, + "head": map[string]interface{}{"x-test": true}, + } + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "HEAD", "DELETE": + // Return nothing + case "GET": + fmt.Fprint(w, txt) + case "PUT", "POST", "PATCH": + io.Copy(w, r.Body) + } + })) + + defer srv.Close() + + Convey("http.head(a) works properly", t, func() { + res, _ = Run(context.Background(), "http.head", srv.URL) + So(res, ShouldResemble, nil) + }) + + Convey("http.get(a) works properly", t, func() { + res, _ = Run(context.Background(), "http.get", srv.URL) + So(res, ShouldResemble, []byte(txt)) + res, _ = Run(context.Background(), "http.get", srv.URL, opt) + So(res, ShouldResemble, []byte(txt)) + }) + + Convey("http.put(a) works properly", t, func() { + res, _ = Run(context.Background(), "http.put", srv.URL) + So(res, ShouldResemble, nil) + res, _ = Run(context.Background(), "http.put", srv.URL, txt) + So(res, ShouldResemble, []byte(txt)) + res, _ = Run(context.Background(), "http.put", srv.URL, jsn) + So(res, ShouldResemble, obj) + res, _ = Run(context.Background(), "http.put", srv.URL, jsn, opt) + So(res, ShouldResemble, obj) + }) + + Convey("http.post(a) works properly", t, func() { + res, _ = Run(context.Background(), "http.post", srv.URL) + So(res, ShouldResemble, nil) + res, _ = Run(context.Background(), "http.post", srv.URL, txt) + So(res, ShouldResemble, []byte(txt)) + res, _ = Run(context.Background(), "http.post", srv.URL, jsn) + So(res, ShouldResemble, obj) + res, _ = Run(context.Background(), "http.post", srv.URL, jsn, opt) + So(res, ShouldResemble, obj) + }) + + Convey("http.patch(a) works properly", t, func() { + res, _ = Run(context.Background(), "http.patch", srv.URL) + So(res, ShouldResemble, nil) + res, _ = Run(context.Background(), "http.patch", srv.URL, txt) + So(res, ShouldResemble, []byte(txt)) + res, _ = Run(context.Background(), "http.patch", srv.URL, jsn) + So(res, ShouldResemble, obj) + res, _ = Run(context.Background(), "http.patch", srv.URL, jsn, opt) + So(res, ShouldResemble, obj) + }) + + Convey("http.delete(a) works properly", t, func() { + res, _ = Run(context.Background(), "http.delete", srv.URL) + So(res, ShouldResemble, nil) + res, _ = Run(context.Background(), "http.delete", srv.URL, opt) + So(res, ShouldResemble, nil) + }) + + Convey("http.async.head(a) works properly", t, func() { + res, _ = Run(context.Background(), "http.async.head", srv.URL) + So(res, ShouldResemble, nil) + res, _ = Run(context.Background(), "http.async.head", srv.URL, opt) + So(res, ShouldResemble, nil) + }) + + Convey("http.async.get(a) works properly", t, func() { + res, _ = Run(context.Background(), "http.async.get", srv.URL) + So(res, ShouldResemble, nil) + res, _ = Run(context.Background(), "http.async.get", srv.URL, opt) + So(res, ShouldResemble, nil) + }) + + Convey("http.async.put(a) works properly", t, func() { + res, _ = Run(context.Background(), "http.async.put", srv.URL) + So(res, ShouldResemble, nil) + res, _ = Run(context.Background(), "http.async.put", srv.URL, txt) + So(res, ShouldResemble, nil) + res, _ = Run(context.Background(), "http.async.put", srv.URL, jsn) + So(res, ShouldResemble, nil) + res, _ = Run(context.Background(), "http.async.put", srv.URL, jsn, opt) + So(res, ShouldResemble, nil) + }) + + Convey("http.async.post(a) works properly", t, func() { + res, _ = Run(context.Background(), "http.async.post", srv.URL) + So(res, ShouldResemble, nil) + res, _ = Run(context.Background(), "http.async.post", srv.URL, txt) + So(res, ShouldResemble, nil) + res, _ = Run(context.Background(), "http.async.post", srv.URL, jsn) + So(res, ShouldResemble, nil) + res, _ = Run(context.Background(), "http.async.post", srv.URL, jsn, opt) + So(res, ShouldResemble, nil) + }) + + Convey("http.async.patch(a) works properly", t, func() { + res, _ = Run(context.Background(), "http.async.patch", srv.URL) + So(res, ShouldResemble, nil) + res, _ = Run(context.Background(), "http.async.patch", srv.URL, txt) + So(res, ShouldResemble, nil) + res, _ = Run(context.Background(), "http.async.patch", srv.URL, jsn) + So(res, ShouldResemble, nil) + res, _ = Run(context.Background(), "http.async.patch", srv.URL, jsn, opt) + So(res, ShouldResemble, nil) + }) + + Convey("http.async.delete(a) works properly", t, func() { + res, _ = Run(context.Background(), "http.async.delete", srv.URL) + So(res, ShouldResemble, nil) + res, _ = Run(context.Background(), "http.async.delete", srv.URL, opt) + So(res, ShouldResemble, nil) + }) + +} diff --git a/util/fncs/if.go b/util/fncs/if.go new file mode 100644 index 00000000..78ae314b --- /dev/null +++ b/util/fncs/if.go @@ -0,0 +1,26 @@ +// 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 fncs + +import ( + "context" +) + +func ifel(ctx context.Context, args ...interface{}) (interface{}, error) { + if val, ok := ensureBool(args[0]); ok && val { + return args[1], nil + } + return args[2], nil +} diff --git a/util/fncs/if_test.go b/util/fncs/if_test.go new file mode 100644 index 00000000..91558df6 --- /dev/null +++ b/util/fncs/if_test.go @@ -0,0 +1,41 @@ +// 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 fncs + +import ( + "context" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestIf(t *testing.T) { + + var res interface{} + + Convey("if(a, b, c) works properly", t, func() { + res, _ = Run(context.Background(), "if", true, "yes", "no") + So(res, ShouldEqual, "yes") + res, _ = Run(context.Background(), "if", "true", "yes", "no") + So(res, ShouldEqual, "yes") + res, _ = Run(context.Background(), "if", false, "yes", "no") + So(res, ShouldEqual, "no") + res, _ = Run(context.Background(), "if", "false", "yes", "no") + So(res, ShouldEqual, "no") + res, _ = Run(context.Background(), "if", "something", "yes", "no") + So(res, ShouldEqual, "no") + }) + +} diff --git a/util/fncs/json.go b/util/fncs/json.go new file mode 100644 index 00000000..37d93214 --- /dev/null +++ b/util/fncs/json.go @@ -0,0 +1,32 @@ +// 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 fncs + +import ( + "context" + + "encoding/json" +) + +func jsonDecode(ctx context.Context, args ...interface{}) (out interface{}, err error) { + bit, _ := ensureBytes(args[0]) + json.Unmarshal(bit, &out) + return +} + +func jsonEncode(ctx context.Context, args ...interface{}) (interface{}, error) { + bit, _ := json.Marshal(args[0]) + return bit, nil +} diff --git a/util/fncs/json_test.go b/util/fncs/json_test.go new file mode 100644 index 00000000..568f5e7b --- /dev/null +++ b/util/fncs/json_test.go @@ -0,0 +1,48 @@ +// 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 fncs + +import ( + "context" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestJson(t *testing.T) { + + var res interface{} + + Convey("json.decode(a) works properly", t, func() { + res, _ = Run(context.Background(), "json.decode", "true") + So(res, ShouldResemble, true) + res, _ = Run(context.Background(), "json.decode", "13579") + So(res, ShouldResemble, float64(13579)) + res, _ = Run(context.Background(), "json.decode", `{"test":true}`) + So(res, ShouldResemble, map[string]interface{}{ + "test": true, + }) + }) + + Convey("json.encode(a) works properly", t, func() { + res, _ = Run(context.Background(), "json.encode", true) + So(res, ShouldResemble, []byte("true")) + res, _ = Run(context.Background(), "json.encode", 13579) + So(res, ShouldResemble, []byte("13579")) + res, _ = Run(context.Background(), "json.encode", map[string]interface{}{"test": true}) + So(res, ShouldResemble, []byte(`{"test":true}`)) + }) + +} diff --git a/util/fncs/math.go b/util/fncs/math.go new file mode 100644 index 00000000..4a676181 --- /dev/null +++ b/util/fncs/math.go @@ -0,0 +1,163 @@ +// 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 fncs + +import ( + "context" + + "github.com/abcum/surreal/util/math" +) + +func mathAbs(ctx context.Context, args ...interface{}) (out interface{}, err error) { + if val, ok := ensureFloat(args[0]); ok { + return outputFloat(math.Abs(val)) + } + return +} + +func mathBottom(ctx context.Context, args ...interface{}) (out interface{}, err error) { + vals := ensureFloats(args[0]) + if take, ok := ensureInt(args[1]); ok { + return math.Bottom(vals, int(take)), nil + } + return +} + +func mathCeil(ctx context.Context, args ...interface{}) (out interface{}, err error) { + if val, ok := ensureFloat(args[0]); ok { + return outputFloat(math.Ceil(val)) + } + return +} + +func mathCorrelation(ctx context.Context, args ...interface{}) (out interface{}, err error) { + a := ensureFloats(args[0]) + b := ensureFloats(args[1]) + return outputFloat(math.Correlation(a, b)) +} + +func mathCovariance(ctx context.Context, args ...interface{}) (out interface{}, err error) { + a := ensureFloats(args[0]) + b := ensureFloats(args[1]) + return outputFloat(math.Covariance(a, b)) +} + +func mathFloor(ctx context.Context, args ...interface{}) (out interface{}, err error) { + if val, ok := ensureFloat(args[0]); ok { + return outputFloat(math.Floor(val)) + } + return +} + +func mathGeometricmean(ctx context.Context, args ...interface{}) (out interface{}, err error) { + vals := ensureFloats(args[0]) + return outputFloat(math.GeometricMean(vals)) +} + +func mathHarmonicmean(ctx context.Context, args ...interface{}) (out interface{}, err error) { + vals := ensureFloats(args[0]) + return outputFloat(math.HarmonicMean(vals)) +} + +func mathInterquartile(ctx context.Context, args ...interface{}) (out interface{}, err error) { + vals := ensureFloats(args[0]) + return outputFloat(math.InterQuartileRange(vals)) +} + +func mathMax(ctx context.Context, args ...interface{}) (out interface{}, err error) { + vals := ensureFloats(args[0]) + return outputFloat(math.Max(vals)) +} + +func mathMean(ctx context.Context, args ...interface{}) (out interface{}, err error) { + vals := ensureFloats(args[0]) + return outputFloat(math.Mean(vals)) +} + +func mathMedian(ctx context.Context, args ...interface{}) (out interface{}, err error) { + vals := ensureFloats(args[0]) + return outputFloat(math.Median(vals)) +} + +func mathMidhinge(ctx context.Context, args ...interface{}) (out interface{}, err error) { + vals := ensureFloats(args[0]) + return outputFloat(math.Midhinge(vals)) +} + +func mathMin(ctx context.Context, args ...interface{}) (out interface{}, err error) { + vals := ensureFloats(args[0]) + return outputFloat(math.Min(vals)) +} + +func mathMode(ctx context.Context, args ...interface{}) (out []float64, err error) { + vals := ensureFloats(args[0]) + return math.Mode(vals), nil +} + +func mathPercentile(ctx context.Context, args ...interface{}) (out interface{}, err error) { + vals := ensureFloats(args[0]) + if perc, ok := ensureFloat(args[1]); ok { + return outputFloat(math.Percentile(vals, perc)) + } + return +} + +func mathRound(ctx context.Context, args ...interface{}) (out interface{}, err error) { + if val, ok := ensureFloat(args[0]); ok { + return outputFloat(math.Round(val)) + } + return +} + +func mathSample(ctx context.Context, args ...interface{}) (out interface{}, err error) { + vals := ensureFloats(args[0]) + if take, ok := ensureInt(args[1]); ok { + return math.Sample(vals, int(take)), nil + } + return +} + +func mathSpread(ctx context.Context, args ...interface{}) (out interface{}, err error) { + vals := ensureFloats(args[0]) + return outputFloat(math.Spread(vals)) +} + +func mathStddev(ctx context.Context, args ...interface{}) (out interface{}, err error) { + vals := ensureFloats(args[0]) + return outputFloat(math.PopulationStandardDeviation(vals)) +} + +func mathSum(ctx context.Context, args ...interface{}) (out interface{}, err error) { + vals := ensureFloats(args[0]) + return outputFloat(math.Sum(vals)) +} + +func mathTop(ctx context.Context, args ...interface{}) (out interface{}, err error) { + vals := ensureFloats(args[0]) + if take, ok := ensureInt(args[1]); ok { + return math.Top(vals, int(take)), nil + } + return +} + +func mathTrimean(ctx context.Context, args ...interface{}) (out interface{}, err error) { + vals := ensureFloats(args[0]) + return outputFloat(math.Trimean(vals)) +} + +func mathVariance(ctx context.Context, args ...interface{}) (out interface{}, err error) { + vals := ensureFloats(args[0]) + return outputFloat(math.PopulationVariance(vals)) +} diff --git a/util/fncs/math_test.go b/util/fncs/math_test.go new file mode 100644 index 00000000..9590c349 --- /dev/null +++ b/util/fncs/math_test.go @@ -0,0 +1,264 @@ +// 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 fncs + +import ( + "context" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestMath(t *testing.T) { + + var res interface{} + + var test = []interface{}{int(1), int64(3), float32(4.5), float64(3.5)} + var testA = []interface{}{int(1), int64(3), float32(4.5), float64(3.5)} + var testB = []interface{}{int(5), int64(9), float32(2.5), float64(6.5)} + var testC = []interface{}{int(5), int64(5), float32(2.5), float64(6.5)} + + Convey("math.abs() works properly", t, func() { + res, _ = Run(context.Background(), "math.abs", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.abs", 10) + So(res, ShouldEqual, 10) + res, _ = Run(context.Background(), "math.abs", -1.5) + So(res, ShouldEqual, 1.5) + }) + + Convey("math.bottom() works properly", t, func() { + res, _ = Run(context.Background(), "math.bottom", "test", 2) + So(res, ShouldHaveLength, 0) + res, _ = Run(context.Background(), "math.bottom", testC, "oops") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.bottom", testC, 2) + So(res, ShouldHaveSameTypeAs, []float64{}) + So(res, ShouldContain, 2.5) + So(res, ShouldContain, 5.0) + res, _ = Run(context.Background(), "math.bottom", 13, 2) + So(res, ShouldHaveSameTypeAs, []float64{}) + So(res, ShouldContain, 13.0) + }) + + Convey("math.ceil() works properly", t, func() { + res, _ = Run(context.Background(), "math.ceil", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.ceil", 10) + So(res, ShouldEqual, 10) + res, _ = Run(context.Background(), "math.ceil", 1.5) + So(res, ShouldEqual, 2) + }) + + Convey("math.correlation() works properly", t, func() { + res, _ = Run(context.Background(), "math.correlation", "test", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.correlation", 1234, 1234) + So(res, ShouldEqual, 0) + res, _ = Run(context.Background(), "math.correlation", testA, testB) + So(res, ShouldEqual, -0.24945922497781908) + }) + + Convey("math.covariance() works properly", t, func() { + res, _ = Run(context.Background(), "math.covariance", "test", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.covariance", 1234, 1234) + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.covariance", testA, testB) + So(res, ShouldEqual, -1) + }) + + Convey("math.floor() works properly", t, func() { + res, _ = Run(context.Background(), "math.floor", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.floor", 10) + So(res, ShouldEqual, 10) + res, _ = Run(context.Background(), "math.floor", 1.5) + So(res, ShouldEqual, 1) + }) + + Convey("math.geometricmean() works properly", t, func() { + res, _ = Run(context.Background(), "math.geometricmean", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.geometricmean", 10) + So(res, ShouldEqual, 10) + res, _ = Run(context.Background(), "math.geometricmean", test) + So(res, ShouldEqual, 2.621805397514041) + }) + + Convey("math.harmonicmean() works properly", t, func() { + res, _ = Run(context.Background(), "math.harmonicmean", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.harmonicmean", 10) + So(res, ShouldEqual, 10) + res, _ = Run(context.Background(), "math.harmonicmean", test) + So(res, ShouldEqual, 2.172413793103449) + }) + + Convey("math.interquartile() works properly", t, func() { + res, _ = Run(context.Background(), "math.interquartile", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.interquartile", 10) + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.interquartile", test) + So(res, ShouldEqual, 2) + }) + + Convey("math.max() works properly", t, func() { + res, _ = Run(context.Background(), "math.max", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.max", 10) + So(res, ShouldEqual, 10) + res, _ = Run(context.Background(), "math.max", test) + So(res, ShouldEqual, 4.5) + }) + + Convey("math.mean() works properly", t, func() { + res, _ = Run(context.Background(), "math.mean", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.mean", 10) + So(res, ShouldEqual, 10) + res, _ = Run(context.Background(), "math.mean", testA, testB) + So(res, ShouldEqual, 3) + }) + + Convey("math.median() works properly", t, func() { + res, _ = Run(context.Background(), "math.median", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.median", 10) + So(res, ShouldEqual, 10) + res, _ = Run(context.Background(), "math.median", testA, testB) + So(res, ShouldEqual, 3.25) + }) + + Convey("math.midhinge() works properly", t, func() { + res, _ = Run(context.Background(), "math.midhinge", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.midhinge", 10) + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.midhinge", test) + So(res, ShouldEqual, 3) + }) + + Convey("math.min() works properly", t, func() { + res, _ = Run(context.Background(), "math.min", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.min", 10) + So(res, ShouldEqual, 10) + res, _ = Run(context.Background(), "math.min", test) + So(res, ShouldEqual, 1) + }) + + Convey("math.mode() works properly", t, func() { + res, _ = Run(context.Background(), "math.mode", "test") + So(res, ShouldHaveLength, 0) + res, _ = Run(context.Background(), "math.mode", testC) + So(res, ShouldResemble, []float64{5}) + res, _ = Run(context.Background(), "math.mode", 1) + So(res, ShouldResemble, []float64{1}) + }) + + Convey("math.percentile() works properly", t, func() { + res, _ = Run(context.Background(), "math.percentile", "test", 90) + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.percentile", 10, "oops") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.percentile", 10, 90) + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.percentile", test, 90) + So(res, ShouldEqual, 4) + }) + + Convey("math.round() works properly", t, func() { + res, _ = Run(context.Background(), "math.round", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.round", 1.4) + So(res, ShouldEqual, 1) + res, _ = Run(context.Background(), "math.round", 1.5) + So(res, ShouldEqual, 2) + }) + + Convey("math.sample() works properly", t, func() { + res, _ = Run(context.Background(), "math.sample", "test", 3) + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.sample", 10, "oops") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.sample", 10, 3) + So(res, ShouldResemble, []float64{10}) + res, _ = Run(context.Background(), "math.sample", test, 3) + So(res, ShouldHaveLength, 3) + }) + + Convey("math.spread() works properly", t, func() { + res, _ = Run(context.Background(), "math.spread", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.spread", 10) + So(res, ShouldEqual, 0) + res, _ = Run(context.Background(), "math.spread", test) + So(res, ShouldEqual, 4.5) + }) + + Convey("math.stddev() works properly", t, func() { + res, _ = Run(context.Background(), "math.stddev", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.stddev", 10) + So(res, ShouldEqual, 0) + res, _ = Run(context.Background(), "math.stddev", test) + So(res, ShouldEqual, 1.2747548783981961) + }) + + Convey("math.sum() works properly", t, func() { + res, _ = Run(context.Background(), "math.sum", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.sum", 1234) + So(res, ShouldEqual, 1234) + res, _ = Run(context.Background(), "math.sum", []interface{}{int(1), int64(3), float32(4.5), float64(3.5)}) + So(res, ShouldEqual, 12) + }) + + Convey("math.top() works properly", t, func() { + res, _ = Run(context.Background(), "math.top", "test", 2) + So(res, ShouldHaveSameTypeAs, []float64{}) + So(res, ShouldHaveLength, 0) + res, _ = Run(context.Background(), "math.top", testC, "oops") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.top", testC, 2) + So(res, ShouldHaveSameTypeAs, []float64{}) + So(res, ShouldContain, 6.5) + So(res, ShouldContain, 5.0) + res, _ = Run(context.Background(), "math.top", 13, 2) + So(res, ShouldHaveSameTypeAs, []float64{}) + So(res, ShouldContain, 13.0) + }) + + Convey("math.trimean() works properly", t, func() { + res, _ = Run(context.Background(), "math.trimean", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.trimean", 10) + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.trimean", test) + So(res, ShouldEqual, 3.125) + }) + + Convey("math.variance() works properly", t, func() { + res, _ = Run(context.Background(), "math.variance", "test") + So(res, ShouldEqual, nil) + res, _ = Run(context.Background(), "math.variance", 10) + So(res, ShouldEqual, 0) + res, _ = Run(context.Background(), "math.variance", test) + So(res, ShouldEqual, 1.625) + }) + +} diff --git a/util/fncs/model.go b/util/fncs/model.go new file mode 100644 index 00000000..34d3c17b --- /dev/null +++ b/util/fncs/model.go @@ -0,0 +1,46 @@ +// 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 fncs + +import ( + "context" + + "github.com/abcum/surreal/sql" +) + +func model(ctx context.Context, args ...interface{}) (*sql.Model, error) { + tb, _ := ensureString(args[0]) + switch len(args) { + case 2: + if max, ok := ensureFloat(args[1]); ok { + return sql.NewModel(tb, 0, 0, max), nil + } + case 3: + if min, ok := ensureFloat(args[1]); ok { + if max, ok := ensureFloat(args[2]); ok { + return sql.NewModel(tb, min, 1, max), nil + } + } + case 4: + if min, ok := ensureFloat(args[1]); ok { + if inc, ok := ensureFloat(args[2]); ok { + if max, ok := ensureFloat(args[3]); ok { + return sql.NewModel(tb, min, inc, max), nil + } + } + } + } + return nil, nil +} diff --git a/util/fncs/model_test.go b/util/fncs/model_test.go new file mode 100644 index 00000000..e0fb5dc1 --- /dev/null +++ b/util/fncs/model_test.go @@ -0,0 +1,55 @@ +// 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 fncs + +import ( + "context" + "testing" + + "github.com/abcum/surreal/sql" + . "github.com/smartystreets/goconvey/convey" +) + +func TestModel(t *testing.T) { + + var res interface{} + + Convey("model(a) errors properly", t, func() { + res, _ = Run(context.Background(), "model", "test") + So(res, ShouldBeNil) + }) + + Convey("model(a, b) works properly", t, func() { + res, _ = Run(context.Background(), "model", 1, 10) + So(res, ShouldResemble, sql.NewModel("1", 0, 0, 10)) + res, _ = Run(context.Background(), "model", "test", 10) + So(res, ShouldResemble, sql.NewModel("test", 0, 0, 10)) + }) + + Convey("model(a, b, c) works properly", t, func() { + res, _ = Run(context.Background(), "model", 1, 10, 20) + So(res, ShouldResemble, sql.NewModel("1", 10, 1, 20)) + res, _ = Run(context.Background(), "model", "test", 10, 20) + So(res, ShouldResemble, sql.NewModel("test", 10, 1, 20)) + }) + + Convey("model(a, b, c, d) works properly", t, func() { + res, _ = Run(context.Background(), "model", 1, 10, 0.5, 20) + So(res, ShouldResemble, sql.NewModel("1", 10, 0.5, 20)) + res, _ = Run(context.Background(), "model", "test", 10, 0.5, 20) + So(res, ShouldResemble, sql.NewModel("test", 10, 0.5, 20)) + }) + +} diff --git a/util/fncs/rand.go b/util/fncs/rand.go new file mode 100644 index 00000000..4973f3ac --- /dev/null +++ b/util/fncs/rand.go @@ -0,0 +1,207 @@ +// 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 fncs + +import ( + "context" + "time" + + "github.com/abcum/surreal/util/fake" +) + +func rand(ctx context.Context, args ...interface{}) (float64, error) { + return fake.DecimalBetween(0, 1), nil +} + +func randBool(ctx context.Context, args ...interface{}) (bool, error) { + return fake.Bool(), nil +} + +func randUuid(ctx context.Context, args ...interface{}) (string, error) { + return fake.Uuid(), nil +} + +func randEnum(ctx context.Context, args ...interface{}) (interface{}, error) { + switch len(args) { + case 0: + return nil, nil + default: + return args[fake.IntegerBetween(0, len(args))], nil + } +} + +func randTime(ctx context.Context, args ...interface{}) (time.Time, error) { + switch len(args) { + case 2: + if b, ok := ensureTime(args[0]); ok { + if e, ok := ensureTime(args[1]); ok { + return fake.TimeBetween(b, e), nil + } + } + } + return fake.Time(), nil +} + +func randString(ctx context.Context, args ...interface{}) (string, error) { + switch len(args) { + case 1: + if l, ok := ensureInt(args[0]); ok { + return fake.StringLength(int(l)), nil + } + case 2: + if b, ok := ensureInt(args[0]); ok { + if e, ok := ensureInt(args[1]); ok { + return fake.StringBetween(int(b), int(e)), nil + } + } + } + return fake.String(), nil +} + +func randInteger(ctx context.Context, args ...interface{}) (float64, error) { + switch len(args) { + case 2: + if b, ok := ensureInt(args[0]); ok { + if e, ok := ensureInt(args[1]); ok { + return float64(fake.IntegerBetween(int(b), int(e))), nil + } + } + } + return float64(fake.Integer()), nil +} + +func randDecimal(ctx context.Context, args ...interface{}) (float64, error) { + switch len(args) { + case 2: + if b, ok := ensureFloat(args[0]); ok { + if e, ok := ensureFloat(args[1]); ok { + return fake.DecimalBetween(b, e), nil + } + } + } + return fake.Decimal(), nil +} + +func randWord(ctx context.Context, args ...interface{}) (string, error) { + return fake.Word(), nil +} + +func randSentence(ctx context.Context, args ...interface{}) (string, error) { + switch len(args) { + case 2: + if b, ok := ensureInt(args[0]); ok { + if e, ok := ensureInt(args[1]); ok { + return fake.SentenceBetween(int(b), int(e)), nil + } + } + } + return fake.Sentence(), nil +} + +func randParagraph(ctx context.Context, args ...interface{}) (string, error) { + switch len(args) { + case 2: + if b, ok := ensureInt(args[0]); ok { + if e, ok := ensureInt(args[1]); ok { + return fake.ParagraphBetween(int(b), int(e)), nil + } + } + } + return fake.Paragraph(), nil +} + +func randPersonEmail(ctx context.Context, args ...interface{}) (string, error) { + return fake.PersonEmail(), nil +} + +func randPersonPhone(ctx context.Context, args ...interface{}) (string, error) { + return fake.PersonPhone(), nil +} + +func randPersonFullname(ctx context.Context, args ...interface{}) (string, error) { + return fake.PersonFullname(), nil +} + +func randPersonFirstname(ctx context.Context, args ...interface{}) (string, error) { + return fake.PersonFirstname(), nil +} + +func randPersonLastname(ctx context.Context, args ...interface{}) (string, error) { + return fake.PersonLastname(), nil +} + +func randPersonUsername(ctx context.Context, args ...interface{}) (string, error) { + return fake.PersonUsername(), nil +} + +func randPersonJobtitle(ctx context.Context, args ...interface{}) (string, error) { + return fake.PersonJobtitle(), nil +} + +func randCompanyName(ctx context.Context, args ...interface{}) (string, error) { + return fake.CompanyName(), nil +} + +func randCompanyIndustry(ctx context.Context, args ...interface{}) (string, error) { + return fake.CompanyIndustry(), nil +} + +func randLocationName(ctx context.Context, args ...interface{}) (string, error) { + return fake.LocationName(), nil +} + +func randLocationAddress(ctx context.Context, args ...interface{}) (string, error) { + return fake.LocationAddress(), nil +} + +func randLocationStreet(ctx context.Context, args ...interface{}) (string, error) { + return fake.LocationStreet(), nil +} + +func randLocationCity(ctx context.Context, args ...interface{}) (string, error) { + return fake.LocationCity(), nil +} + +func randLocationState(ctx context.Context, args ...interface{}) (string, error) { + return fake.LocationState(), nil +} + +func randLocationCounty(ctx context.Context, args ...interface{}) (string, error) { + return fake.LocationCounty(), nil +} + +func randLocationZipcode(ctx context.Context, args ...interface{}) (string, error) { + return fake.LocationZipcode(), nil +} + +func randLocationPostcode(ctx context.Context, args ...interface{}) (string, error) { + return fake.LocationPostcode(), nil +} + +func randLocationCountry(ctx context.Context, args ...interface{}) (string, error) { + return fake.LocationCountry(), nil +} + +func randLocationAltitude(ctx context.Context, args ...interface{}) (float64, error) { + return fake.LocationAltitude(), nil +} + +func randLocationLatitude(ctx context.Context, args ...interface{}) (float64, error) { + return fake.LocationLatitude(), nil +} + +func randLocationLongitude(ctx context.Context, args ...interface{}) (float64, error) { + return fake.LocationLongitude(), nil +} diff --git a/util/fncs/rand_test.go b/util/fncs/rand_test.go new file mode 100644 index 00000000..c51cbe4d --- /dev/null +++ b/util/fncs/rand_test.go @@ -0,0 +1,243 @@ +// 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 fncs + +import ( + "context" + "testing" + "time" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestRand(t *testing.T) { + + Convey("rand() works properly", t, func() { + res, _ := Run(context.Background(), "rand") + So(res, ShouldHaveSameTypeAs, 36.0) + }) + + Convey("uuid() works properly", t, func() { + res, _ := Run(context.Background(), "uuid") + So(res, ShouldHaveSameTypeAs, "test") + So(res, ShouldHaveLength, 36) + }) + + Convey("rand.bool() works properly", t, func() { + res, _ := Run(context.Background(), "rand.bool") + So(res, ShouldHaveSameTypeAs, true) + }) + + Convey("rand.uuid() works properly", t, func() { + res, _ := Run(context.Background(), "rand.uuid") + So(res, ShouldHaveSameTypeAs, "test") + So(res, ShouldHaveLength, 36) + }) + + Convey("rand.enum() works properly", t, func() { + res, _ := Run(context.Background(), "rand.enum") + So(res, ShouldHaveSameTypeAs, nil) + }) + + Convey("rand.enum(a,b,c) works properly", t, func() { + res, _ := Run(context.Background(), "rand.enum", "one", "two", "tre") + So(res, ShouldHaveSameTypeAs, "test") + So(res, ShouldBeIn, []interface{}{"one", "two", "tre"}) + }) + + Convey("rand.time() works properly", t, func() { + res, _ := Run(context.Background(), "rand.time") + So(res, ShouldHaveSameTypeAs, time.Now()) + }) + + Convey("rand.time(a,b) works properly", t, func() { + d, _ := time.ParseDuration("24h") + now := time.Now() + res, _ := Run(context.Background(), "rand.time", now, now.Add(d)) + So(res, ShouldHaveSameTypeAs, time.Now()) + So(res.(time.Time).UnixNano(), ShouldBeBetween, now.UnixNano(), now.Add(d).UnixNano()) + }) + + Convey("rand.string() works properly", t, func() { + res, _ := Run(context.Background(), "rand.string") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.string(a) works properly", t, func() { + res, _ := Run(context.Background(), "rand.string", int64(12)) + So(res, ShouldHaveSameTypeAs, "test") + So(res, ShouldHaveLength, 12) + }) + + Convey("rand.string(a,b) works properly", t, func() { + res, _ := Run(context.Background(), "rand.string", int64(12), int64(16)) + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.integer() works properly", t, func() { + res, _ := Run(context.Background(), "rand.integer") + So(res, ShouldHaveSameTypeAs, float64(0)) + }) + + Convey("rand.integer(a,b) works properly", t, func() { + res, _ := Run(context.Background(), "rand.integer", int64(12), int64(16)) + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldBeBetween, 11, 17) + }) + + Convey("rand.decimal() works properly", t, func() { + res, _ := Run(context.Background(), "rand.decimal") + So(res, ShouldHaveSameTypeAs, float64(0)) + }) + + Convey("rand.decimal(a,b) works properly", t, func() { + res, _ := Run(context.Background(), "rand.decimal", int64(12), int64(16)) + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldBeBetween, 11, 17) + }) + + Convey("rand.word() works properly", t, func() { + res, _ := Run(context.Background(), "rand.word") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.sentence() works properly", t, func() { + res, _ := Run(context.Background(), "rand.sentence") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.sentence(a,b) works properly", t, func() { + res, _ := Run(context.Background(), "rand.sentence", int64(12), int64(16)) + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.paragraph() works properly", t, func() { + res, _ := Run(context.Background(), "rand.paragraph") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.paragraph(a,b) works properly", t, func() { + res, _ := Run(context.Background(), "rand.paragraph", int64(12), int64(16)) + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.person.email works properly", t, func() { + res, _ := Run(context.Background(), "rand.person.email") + So(res, ShouldHaveSameTypeAs, "test") + So(res, ShouldContainSubstring, "@") + }) + + Convey("rand.person.phone works properly", t, func() { + res, _ := Run(context.Background(), "rand.person.phone") + So(res, ShouldHaveSameTypeAs, "test") + So(res, ShouldContainSubstring, " ") + }) + + Convey("rand.person.fullname works properly", t, func() { + res, _ := Run(context.Background(), "rand.person.fullname") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.person.firstname works properly", t, func() { + res, _ := Run(context.Background(), "rand.person.firstname") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.person.lastname works properly", t, func() { + res, _ := Run(context.Background(), "rand.person.lastname") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.person.username works properly", t, func() { + res, _ := Run(context.Background(), "rand.person.username") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.person.jobtitle works properly", t, func() { + res, _ := Run(context.Background(), "rand.person.jobtitle") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.company.name works properly", t, func() { + res, _ := Run(context.Background(), "rand.company.name") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.company.industry works properly", t, func() { + res, _ := Run(context.Background(), "rand.company.industry") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.location.name works properly", t, func() { + res, _ := Run(context.Background(), "rand.location.name") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.location.address works properly", t, func() { + res, _ := Run(context.Background(), "rand.location.address") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.location.street works properly", t, func() { + res, _ := Run(context.Background(), "rand.location.street") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.location.city works properly", t, func() { + res, _ := Run(context.Background(), "rand.location.city") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.location.state works properly", t, func() { + res, _ := Run(context.Background(), "rand.location.state") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.location.county works properly", t, func() { + res, _ := Run(context.Background(), "rand.location.county") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.location.zipcode works properly", t, func() { + res, _ := Run(context.Background(), "rand.location.zipcode") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.location.postcode works properly", t, func() { + res, _ := Run(context.Background(), "rand.location.postcode") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.location.country works properly", t, func() { + res, _ := Run(context.Background(), "rand.location.country") + So(res, ShouldHaveSameTypeAs, "test") + }) + + Convey("rand.location.altitude works properly", t, func() { + res, _ := Run(context.Background(), "rand.location.altitude") + So(res, ShouldHaveSameTypeAs, float64(0)) + }) + + Convey("rand.location.latitude works properly", t, func() { + res, _ := Run(context.Background(), "rand.location.latitude") + So(res, ShouldHaveSameTypeAs, float64(0)) + }) + + Convey("rand.location.longitude works properly", t, func() { + res, _ := Run(context.Background(), "rand.location.longitude") + So(res, ShouldHaveSameTypeAs, float64(0)) + }) + +} diff --git a/util/fncs/scrypt.go b/util/fncs/scrypt.go new file mode 100644 index 00000000..2cdc0bab --- /dev/null +++ b/util/fncs/scrypt.go @@ -0,0 +1,40 @@ +// 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 fncs + +import ( + "context" + + "github.com/elithrar/simple-scrypt" +) + +func scryptCompare(ctx context.Context, args ...interface{}) (bool, error) { + if h, ok := ensureString(args[0]); ok { + if s, ok := ensureString(args[1]); ok { + e := scrypt.CompareHashAndPassword([]byte(h), []byte(s)) + if e == nil { + return true, nil + } + } + } + return false, nil +} + +func scryptGenerate(ctx context.Context, args ...interface{}) ([]byte, error) { + s, _ := ensureString(args[0]) + p := []byte(s) + o := scrypt.DefaultParams + return scrypt.GenerateFromPassword(p, o) +} diff --git a/util/fncs/scrypt_test.go b/util/fncs/scrypt_test.go new file mode 100644 index 00000000..5f6f63e9 --- /dev/null +++ b/util/fncs/scrypt_test.go @@ -0,0 +1,41 @@ +// 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 fncs + +import ( + "context" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestScrypt(t *testing.T) { + + Convey("scrypt.compare(a, b) works properly", t, func() { + res, _ := Run(context.Background(), "scrypt.compare", "16384$8$1$8065a3ec98903c86d950840721ef945b$1f23d0d2ff5528f033161fd21ce84911f9332ac9878953139ad30b6a8c2959f2", "test") + So(res, ShouldEqual, true) + }) + + Convey("scrypt.compare(a, b) errors properly", t, func() { + res, _ := Run(context.Background(), "scrypt.compare", "16384$8$1$8065a3ec98903c86d950840721ef945b$1f23d0d2ff5528f033161fd21ce84911f9332ac9878953139ad30b6a8c2959f2", "wrong") + So(res, ShouldEqual, false) + }) + + Convey("scrypt.generate(a) works properly", t, func() { + res, _ := Run(context.Background(), "scrypt.generate", "test") + So(res, ShouldHaveSameTypeAs, []byte("test")) + }) + +} diff --git a/util/fncs/sets.go b/util/fncs/sets.go new file mode 100644 index 00000000..0e6089c8 --- /dev/null +++ b/util/fncs/sets.go @@ -0,0 +1,104 @@ +// 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 fncs + +import ( + "context" +) + +func difference(ctx context.Context, args ...interface{}) ([]interface{}, error) { + + d := make([]interface{}, 0) + c := make(map[interface{}]int) + + for _, x := range args { + a, _ := ensureSlice(x) + for _, v := range a { + c[v] += 1 + } + } + + for k, b := range c { + if b == 1 { + d = append(d, k) + } + } + + return d, nil + +} + +func distinct(ctx context.Context, args ...interface{}) (interface{}, error) { + + d := make([]interface{}, 0) + c := make(map[interface{}]bool) + + for _, x := range args { + a, _ := ensureSlice(x) + for _, v := range a { + c[v] = true + } + } + + for k := range c { + d = append(d, k) + } + + return d, nil + +} + +func intersect(ctx context.Context, args ...interface{}) ([]interface{}, error) { + + l := len(args) + d := make([]interface{}, 0) + c := make(map[interface{}]int) + + for _, x := range args { + a, _ := ensureSlice(x) + for _, v := range a { + c[v] += 1 + } + } + + for k, b := range c { + if b == l { + d = append(d, k) + } + } + + return d, nil + +} + +func union(ctx context.Context, args ...interface{}) ([]interface{}, error) { + + d := make([]interface{}, 0) + c := make(map[interface{}]bool) + + for _, x := range args { + a, _ := ensureSlice(x) + for _, v := range a { + c[v] = true + } + } + + for k := range c { + d = append(d, k) + } + + return d, nil + +} diff --git a/util/fncs/sets_test.go b/util/fncs/sets_test.go new file mode 100644 index 00000000..3ec89767 --- /dev/null +++ b/util/fncs/sets_test.go @@ -0,0 +1,62 @@ +// 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 fncs + +import ( + "context" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestSets(t *testing.T) { + + Convey("difference(a, b, c) works properly", t, func() { + res, _ := Run(context.Background(), "difference", []interface{}{"one"}, []interface{}{"one", "two"}, []interface{}{"one", "two", "tre"}) + So(res, ShouldHaveLength, 1) + So(res, ShouldContain, "tre") + }) + + Convey("distinct(a) works properly", t, func() { + res, _ := Run(context.Background(), "distinct", []interface{}{"one", "two", "two", "tre", "tre", "tre"}) + So(res, ShouldHaveLength, 3) + So(res, ShouldContain, "one") + So(res, ShouldContain, "two") + So(res, ShouldContain, "tre") + }) + + Convey("distinct(a, b, c) works properly", t, func() { + res, _ := Run(context.Background(), "distinct", []interface{}{"one"}, []interface{}{"one", "two"}, []interface{}{"one", "two", "tre"}) + So(res, ShouldHaveLength, 3) + So(res, ShouldContain, "one") + So(res, ShouldContain, "two") + So(res, ShouldContain, "tre") + }) + + Convey("intersect(a, b, c) works properly", t, func() { + res, _ := Run(context.Background(), "intersect", []interface{}{"one"}, []interface{}{"one", "two"}, []interface{}{"one", "two", "tre"}) + So(res, ShouldHaveLength, 1) + So(res, ShouldContain, "one") + }) + + Convey("union(a, b, c) works properly", t, func() { + res, _ := Run(context.Background(), "union", []interface{}{"one"}, []interface{}{"one", "two"}, []interface{}{"one", "two", "tre"}) + So(res, ShouldHaveLength, 3) + So(res, ShouldContain, "one") + So(res, ShouldContain, "two") + So(res, ShouldContain, "tre") + }) + +} diff --git a/util/fncs/string.go b/util/fncs/string.go new file mode 100644 index 00000000..cfe57762 --- /dev/null +++ b/util/fncs/string.go @@ -0,0 +1,180 @@ +// 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 fncs + +import ( + "context" + "fmt" + "strings" + + "github.com/abcum/surreal/util/ints" + "github.com/abcum/surreal/util/text" +) + +func stringConcat(ctx context.Context, args ...interface{}) (str string, err error) { + for _, v := range args { + str = str + fmt.Sprint(v) + } + return +} + +func stringContains(ctx context.Context, args ...interface{}) (bool, error) { + s, _ := ensureString(args[0]) + p, _ := ensureString(args[1]) + return strings.Contains(s, p), nil +} + +func stringEndsWith(ctx context.Context, args ...interface{}) (bool, error) { + s, _ := ensureString(args[0]) + p, _ := ensureString(args[1]) + return strings.HasSuffix(s, p), nil +} + +func stringFormat(ctx context.Context, args ...interface{}) (str string, err error) { + switch len(args) { + case 0, 1: + // Not enough arguments, so just ignore + default: + s, _ := ensureString(args[0]) + str = fmt.Sprintf(s, args[1:]...) + } + return +} + +func stringIncludes(ctx context.Context, args ...interface{}) (bool, error) { + s, _ := ensureString(args[0]) + p, _ := ensureString(args[1]) + return strings.Contains(s, p), nil +} + +func stringJoin(ctx context.Context, args ...interface{}) (str string, err error) { + switch len(args) { + case 0, 1: + // Not enough arguments, so just ignore + default: + var a []string + j, _ := ensureString(args[0]) + for _, v := range args[1:] { + if v != nil { + a = append(a, fmt.Sprint(v)) + } + } + str = strings.Join(a, j) + } + return +} + +func stringLength(ctx context.Context, args ...interface{}) (float64, error) { + s, _ := ensureString(args[0]) + return float64(len(s)), nil +} + +func stringLevenshtein(ctx context.Context, args ...interface{}) (float64, error) { + s, _ := ensureString(args[0]) + c, _ := ensureString(args[1]) + return float64(text.Levenshtein(s, c)), nil +} + +func stringLowercase(ctx context.Context, args ...interface{}) (string, error) { + s, _ := ensureString(args[0]) + return strings.ToLower(s), nil +} + +func stringRepeat(ctx context.Context, args ...interface{}) (string, error) { + s, _ := ensureString(args[0]) + if c, ok := ensureInt(args[1]); ok { + return strings.Repeat(s, int(c)), nil + } + return s, nil +} + +func stringReplace(ctx context.Context, args ...interface{}) (string, error) { + s, _ := ensureString(args[0]) + o, _ := ensureString(args[1]) + n, _ := ensureString(args[2]) + return strings.Replace(s, o, n, -1), nil +} + +func stringReverse(ctx context.Context, args ...interface{}) (string, error) { + s, _ := ensureString(args[0]) + r := []rune(s) + for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 { + r[i], r[j] = r[j], r[i] + } + return string(r), nil +} + +func stringSearch(ctx context.Context, args ...interface{}) (float64, error) { + s, _ := ensureString(args[0]) + p, _ := ensureString(args[1]) + return float64(strings.Index(s, p)), nil +} + +func stringSlice(ctx context.Context, args ...interface{}) (string, error) { + s, _ := ensureString(args[0]) + b, bk := ensureInt(args[1]) + e, ek := ensureInt(args[2]) + f := ints.Min(len(s), int(b+e)) + if bk && ek { + return s[b:f], nil + } else if bk { + return s[b:], nil + } else if ek { + return s[:f], nil + } + return s, nil +} + +func stringSplit(ctx context.Context, args ...interface{}) ([]string, error) { + s, _ := ensureString(args[0]) + p, _ := ensureString(args[1]) + return strings.Split(s, p), nil +} + +func stringStartsWith(ctx context.Context, args ...interface{}) (bool, error) { + s, _ := ensureString(args[0]) + p, _ := ensureString(args[1]) + return strings.HasPrefix(s, p), nil +} + +func stringSubstr(ctx context.Context, args ...interface{}) (string, error) { + s, _ := ensureString(args[0]) + b, bk := ensureInt(args[1]) + e, ek := ensureInt(args[2]) + f := ints.Min(len(s), int(e)) + if bk && ek { + return s[b:f], nil + } else if bk { + return s[b:], nil + } else if ek { + return s[:f], nil + } + return s, nil +} + +func stringTrim(ctx context.Context, args ...interface{}) (string, error) { + s, _ := ensureString(args[0]) + return strings.TrimSpace(s), nil +} + +func stringUppercase(ctx context.Context, args ...interface{}) (string, error) { + s, _ := ensureString(args[0]) + return strings.ToUpper(s), nil +} + +func stringWords(ctx context.Context, args ...interface{}) ([]string, error) { + s, _ := ensureString(args[0]) + return strings.Fields(s), nil +} diff --git a/util/fncs/string_test.go b/util/fncs/string_test.go new file mode 100644 index 00000000..743b1e7c --- /dev/null +++ b/util/fncs/string_test.go @@ -0,0 +1,171 @@ +// 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 fncs + +import ( + "context" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestString(t *testing.T) { + + var res interface{} + + var test = "This IS a test" + var spac = " This IS a test " + + Convey("string.concat() works properly", t, func() { + res, _ = Run(context.Background(), "string.concat", nil, 1, 1.5, "2", true, false) + So(res, ShouldEqual, "11.52truefalse") + }) + + Convey("string.contains() works properly", t, func() { + res, _ = Run(context.Background(), "string.contains", test, "done") + So(res, ShouldEqual, false) + res, _ = Run(context.Background(), "string.contains", test, "test") + So(res, ShouldEqual, true) + }) + + Convey("string.endsWith() works properly", t, func() { + res, _ = Run(context.Background(), "string.endsWith", test, "done") + So(res, ShouldEqual, false) + res, _ = Run(context.Background(), "string.endsWith", test, "test") + So(res, ShouldEqual, true) + }) + + Convey("string.format() works properly", t, func() { + res, _ = Run(context.Background(), "string.format", "%.9d", 1) + So(res, ShouldEqual, "000000001") + }) + + Convey("string.format() errors properly", t, func() { + res, _ = Run(context.Background(), "string.format", "%.9d") + So(res, ShouldEqual, "") + }) + + Convey("string.includes() works properly", t, func() { + res, _ = Run(context.Background(), "string.includes", test, "done") + So(res, ShouldEqual, false) + res, _ = Run(context.Background(), "string.includes", test, "test") + So(res, ShouldEqual, true) + }) + + Convey("string.join() works properly", t, func() { + res, _ = Run(context.Background(), "string.join") + So(res, ShouldEqual, "") + res, _ = Run(context.Background(), "string.join", ",") + So(res, ShouldEqual, "") + res, _ = Run(context.Background(), "string.join", ",", nil, 1, 1.5, "2", true, false) + So(res, ShouldEqual, "1,1.5,2,true,false") + }) + + Convey("string.length() works properly", t, func() { + res, _ = Run(context.Background(), "string.length", test) + So(res, ShouldEqual, 14) + }) + + Convey("string.levenshtein() works properly", t, func() { + res, _ = Run(context.Background(), "string.levenshtein", "test", "test") + So(res, ShouldEqual, 0) + res, _ = Run(context.Background(), "string.levenshtein", "lawn", "flaw") + So(res, ShouldEqual, 2) + res, _ = Run(context.Background(), "string.levenshtein", "test", "done") + So(res, ShouldEqual, 4) + }) + + Convey("string.lowercase() works properly", t, func() { + res, _ = Run(context.Background(), "string.lowercase", test) + So(res, ShouldEqual, "this is a test") + }) + + Convey("string.repeat(a, b) works properly", t, func() { + res, _ = Run(context.Background(), "string.repeat", test, 2) + So(res, ShouldEqual, test+test) + }) + + Convey("string.repeat(a, b) errors properly", t, func() { + res, _ = Run(context.Background(), "string.repeat", test, "test") + So(res, ShouldEqual, test) + }) + + Convey("string.replace() works properly", t, func() { + res, _ = Run(context.Background(), "string.replace", test, "test", "note") + So(res, ShouldEqual, "This IS a note") + }) + + Convey("string.reverse() works properly", t, func() { + res, _ = Run(context.Background(), "string.reverse", test, "test") + So(res, ShouldEqual, "tset a SI sihT") + }) + + Convey("string.search() works properly", t, func() { + res, _ = Run(context.Background(), "string.search", test, "done") + So(res, ShouldEqual, -1) + res, _ = Run(context.Background(), "string.search", test, "test") + So(res, ShouldEqual, 10) + }) + + Convey("string.slice() works properly", t, func() { + res, _ = Run(context.Background(), "string.slice", test, "a", "b") + So(res, ShouldEqual, test) + res, _ = Run(context.Background(), "string.slice", test, "2", "b") + So(res, ShouldEqual, test[2:]) + res, _ = Run(context.Background(), "string.slice", test, "a", "2") + So(res, ShouldEqual, test[:2]) + res, _ = Run(context.Background(), "string.slice", test, "2", "4") + So(res, ShouldEqual, test[2:4+2]) + }) + + Convey("string.split() works properly", t, func() { + res, _ = Run(context.Background(), "string.split", test, " ") + So(res, ShouldResemble, []string{"This", "IS", "a", "test"}) + }) + + Convey("string.startsWith() works properly", t, func() { + res, _ = Run(context.Background(), "string.startsWith", test, "this") + So(res, ShouldEqual, false) + res, _ = Run(context.Background(), "string.startsWith", test, "This") + So(res, ShouldEqual, true) + }) + + Convey("string.substr() works properly", t, func() { + res, _ = Run(context.Background(), "string.substr", test, "a", "b") + So(res, ShouldEqual, test) + res, _ = Run(context.Background(), "string.substr", test, "2", "b") + So(res, ShouldEqual, test[2:]) + res, _ = Run(context.Background(), "string.substr", test, "a", "2") + So(res, ShouldEqual, test[:2]) + res, _ = Run(context.Background(), "string.substr", test, "2", "4") + So(res, ShouldEqual, test[2:4]) + }) + + Convey("string.trim() works properly", t, func() { + res, _ = Run(context.Background(), "string.trim", spac) + So(res, ShouldEqual, test) + }) + + Convey("string.uppercase() works properly", t, func() { + res, _ = Run(context.Background(), "string.uppercase", test) + So(res, ShouldEqual, "THIS IS A TEST") + }) + + Convey("string.words() works properly", t, func() { + res, _ = Run(context.Background(), "string.words", test) + So(res, ShouldResemble, []string{"This", "IS", "a", "test"}) + }) + +} diff --git a/util/fncs/table.go b/util/fncs/table.go new file mode 100644 index 00000000..8cccdaf2 --- /dev/null +++ b/util/fncs/table.go @@ -0,0 +1,26 @@ +// 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 fncs + +import ( + "context" + + "github.com/abcum/surreal/sql" +) + +func table(ctx context.Context, args ...interface{}) (*sql.Table, error) { + tb, _ := ensureString(args[0]) + return sql.NewTable(tb), nil +} diff --git a/util/fncs/table_test.go b/util/fncs/table_test.go new file mode 100644 index 00000000..270d0355 --- /dev/null +++ b/util/fncs/table_test.go @@ -0,0 +1,36 @@ +// 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 fncs + +import ( + "context" + "testing" + + "github.com/abcum/surreal/sql" + . "github.com/smartystreets/goconvey/convey" +) + +func TestTable(t *testing.T) { + + var res interface{} + + Convey("table() works properly", t, func() { + res, _ = Run(context.Background(), "table", 1) + So(res, ShouldResemble, sql.NewTable("1")) + res, _ = Run(context.Background(), "table", "test") + So(res, ShouldResemble, sql.NewTable("test")) + }) + +} diff --git a/util/fncs/thing.go b/util/fncs/thing.go new file mode 100644 index 00000000..0cd2e4c5 --- /dev/null +++ b/util/fncs/thing.go @@ -0,0 +1,26 @@ +// 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 fncs + +import ( + "context" + + "github.com/abcum/surreal/sql" +) + +func thing(ctx context.Context, args ...interface{}) (*sql.Thing, error) { + tb, _ := ensureString(args[0]) + return sql.NewThing(tb, args[1]), nil +} diff --git a/util/fncs/thing_test.go b/util/fncs/thing_test.go new file mode 100644 index 00000000..8d6251b5 --- /dev/null +++ b/util/fncs/thing_test.go @@ -0,0 +1,36 @@ +// 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 fncs + +import ( + "context" + "testing" + + "github.com/abcum/surreal/sql" + . "github.com/smartystreets/goconvey/convey" +) + +func TestThing(t *testing.T) { + + var res interface{} + + Convey("thing() works properly", t, func() { + res, _ = Run(context.Background(), "thing", 1, 1) + So(res, ShouldResemble, sql.NewThing("1", 1)) + res, _ = Run(context.Background(), "thing", "test", 1) + So(res, ShouldResemble, sql.NewThing("test", 1)) + }) + +} diff --git a/util/fncs/time.go b/util/fncs/time.go new file mode 100644 index 00000000..6763b9a3 --- /dev/null +++ b/util/fncs/time.go @@ -0,0 +1,156 @@ +// 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 fncs + +import ( + "context" + "time" +) + +func timeNow(ctx context.Context, args ...interface{}) (time.Time, error) { + return time.Now(), nil +} + +func timeAdd(ctx context.Context, args ...interface{}) (time.Time, error) { + if t, ok := ensureTime(args[0]); ok { + if d, ok := ensureDuration(args[1]); ok { + return t.Add(d), nil + } + } + return time.Unix(0, 0), nil +} + +func timeAge(ctx context.Context, args ...interface{}) (time.Time, error) { + if t, ok := ensureTime(args[0]); ok { + if d, ok := ensureDuration(args[1]); ok { + return t.Add(-d), nil + } + } + return time.Unix(0, 0), nil +} + +func timeFloor(ctx context.Context, args ...interface{}) (interface{}, error) { + if t, ok := ensureTime(args[0]); ok { + if d, ok := ensureDuration(args[1]); ok { + return t.Truncate(d), nil + } + } + return nil, nil +} + +func timeRound(ctx context.Context, args ...interface{}) (interface{}, error) { + if t, ok := ensureTime(args[0]); ok { + if d, ok := ensureDuration(args[1]); ok { + return t.Round(d), nil + } + } + return nil, nil +} + +func timeDay(ctx context.Context, args ...interface{}) (float64, error) { + switch len(args) { + case 0: + return float64(time.Now().Day()), nil + case 1: + if v, ok := ensureTime(args[0]); ok { + return float64(v.Day()), nil + } + } + return 0, nil +} + +func timeHour(ctx context.Context, args ...interface{}) (float64, error) { + switch len(args) { + case 0: + return float64(time.Now().Hour()), nil + case 1: + if v, ok := ensureTime(args[0]); ok { + return float64(v.Hour()), nil + } + } + return 0, nil +} + +func timeMins(ctx context.Context, args ...interface{}) (float64, error) { + switch len(args) { + case 0: + return float64(time.Now().Minute()), nil + case 1: + if v, ok := ensureTime(args[0]); ok { + return float64(v.Minute()), nil + } + } + return 0, nil +} + +func timeMonth(ctx context.Context, args ...interface{}) (float64, error) { + switch len(args) { + case 0: + return float64(time.Now().Month()), nil + case 1: + if v, ok := ensureTime(args[0]); ok { + return float64(v.Month()), nil + } + } + return 0, nil +} + +func timeNano(ctx context.Context, args ...interface{}) (float64, error) { + switch len(args) { + case 0: + return float64(time.Now().UnixNano()), nil + case 1: + if v, ok := ensureTime(args[0]); ok { + return float64(v.UnixNano()), nil + } + } + return 0, nil +} + +func timeSecs(ctx context.Context, args ...interface{}) (float64, error) { + switch len(args) { + case 0: + return float64(time.Now().Second()), nil + case 1: + if v, ok := ensureTime(args[0]); ok { + return float64(v.Second()), nil + } + } + return 0, nil +} + +func timeUnix(ctx context.Context, args ...interface{}) (float64, error) { + switch len(args) { + case 0: + return float64(time.Now().Unix()), nil + case 1: + if v, ok := ensureTime(args[0]); ok { + return float64(v.Unix()), nil + } + } + return 0, nil +} + +func timeYear(ctx context.Context, args ...interface{}) (float64, error) { + switch len(args) { + case 0: + return float64(time.Now().Year()), nil + case 1: + if v, ok := ensureTime(args[0]); ok { + return float64(v.Year()), nil + } + } + return 0, nil +} diff --git a/util/fncs/time_test.go b/util/fncs/time_test.go new file mode 100644 index 00000000..99f584ac --- /dev/null +++ b/util/fncs/time_test.go @@ -0,0 +1,234 @@ +// 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 fncs + +import ( + "context" + "testing" + "time" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestTime(t *testing.T) { + + now := time.Now() + org := time.Unix(0, 0) + dur := 24 * time.Hour + old, _ := time.Parse(time.RFC822Z, time.RFC822Z) + old = old.UTC() + rnd, _ := time.Parse("2006-01-02", "2006-01-03") + rnd = rnd.UTC() + trc, _ := time.Parse("2006-01-02", "2006-01-02") + trc = trc.UTC() + + Convey("time.now() works properly", t, func() { + res, _ := Run(context.Background(), "time.now") + So(res, ShouldHaveSameTypeAs, now) + }) + + Convey("time.add(a, b) works properly", t, func() { + dur, _ := time.ParseDuration("1h") + res, _ := Run(context.Background(), "time.add", now, dur) + So(res, ShouldHaveSameTypeAs, now) + So(res, ShouldHappenAfter, now) + }) + + Convey("time.add(a, b) errors properly", t, func() { + res, _ := Run(context.Background(), "time.add", now, nil) + So(res, ShouldHaveSameTypeAs, org) + So(res, ShouldEqual, org) + }) + + Convey("time.age(a, b) errors properly", t, func() { + dur, _ := time.ParseDuration("1h") + res, _ := Run(context.Background(), "time.age", now, dur) + So(res, ShouldHaveSameTypeAs, now) + So(res, ShouldHappenBefore, now) + }) + + Convey("time.age(a, b) errors properly", t, func() { + res, _ := Run(context.Background(), "time.age", now, nil) + So(res, ShouldHaveSameTypeAs, org) + So(res, ShouldEqual, org) + }) + + Convey("time.floor(a,b) works properly", t, func() { + res, _ := Run(context.Background(), "time.floor", old, dur) + So(res, ShouldHaveSameTypeAs, org) + So(res, ShouldEqual, trc) + }) + + Convey("time.floor(a,b) errors properly", t, func() { + res, _ := Run(context.Background(), "time.floor", "one", "two") + So(res, ShouldEqual, nil) + }) + + Convey("time.round(a,b) works properly", t, func() { + res, _ := Run(context.Background(), "time.round", old, dur) + So(res, ShouldHaveSameTypeAs, org) + So(res, ShouldEqual, rnd) + }) + + Convey("time.round(a,b) errors properly", t, func() { + res, _ := Run(context.Background(), "time.round", "one", "two") + So(res, ShouldEqual, nil) + }) + + Convey("time.day() works properly", t, func() { + res, _ := Run(context.Background(), "time.day") + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, now.Day()) + }) + + Convey("time.day(a) works properly", t, func() { + res, _ := Run(context.Background(), "time.day", old) + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, 2) + }) + + Convey("time.day(a,b,c) errors properly", t, func() { + res, _ := Run(context.Background(), "time.day", "one", "two") + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, 0) + }) + + Convey("time.hour() works properly", t, func() { + res, _ := Run(context.Background(), "time.hour") + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, now.Hour()) + }) + + Convey("time.hour(a) works properly", t, func() { + res, _ := Run(context.Background(), "time.hour", old) + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, 22) + }) + + Convey("time.hour(a,b,c) errors properly", t, func() { + res, _ := Run(context.Background(), "time.hour", "one", "two") + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, 0) + }) + + Convey("time.mins() works properly", t, func() { + res, _ := Run(context.Background(), "time.mins") + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, now.Minute()) + }) + + Convey("time.mins(a) works properly", t, func() { + res, _ := Run(context.Background(), "time.mins", old) + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, 4) + }) + + Convey("time.mins(a,b,c) errors properly", t, func() { + res, _ := Run(context.Background(), "time.mins", "one", "two") + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, 0) + }) + + Convey("time.month() works properly", t, func() { + res, _ := Run(context.Background(), "time.month") + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, now.Month()) + }) + + Convey("time.month(a) works properly", t, func() { + res, _ := Run(context.Background(), "time.month", old) + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, 1) + }) + + Convey("time.month(a,b,c) errors properly", t, func() { + res, _ := Run(context.Background(), "time.month", "one", "two") + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, 0) + }) + + Convey("time.nano() works properly", t, func() { + res, _ := Run(context.Background(), "time.nano") + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldBeGreaterThanOrEqualTo, now.UnixNano()) + }) + + Convey("time.nano(a) works properly", t, func() { + res, _ := Run(context.Background(), "time.nano", old) + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, 1136239440000000000) + }) + + Convey("time.nano(a,b,c) errors properly", t, func() { + res, _ := Run(context.Background(), "time.nano", "one", "two") + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, 0) + }) + + Convey("time.secs() works properly", t, func() { + res, _ := Run(context.Background(), "time.secs") + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, now.Second()) + }) + + Convey("time.secs(a) works properly", t, func() { + res, _ := Run(context.Background(), "time.secs", old) + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, 0) + }) + + Convey("time.secs(a,b,c) errors properly", t, func() { + res, _ := Run(context.Background(), "time.secs", "one", "two") + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, 0) + }) + + Convey("time.unix() works properly", t, func() { + res, _ := Run(context.Background(), "time.unix") + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldBeGreaterThanOrEqualTo, now.Unix()) + }) + + Convey("time.unix(a) works properly", t, func() { + res, _ := Run(context.Background(), "time.unix", old) + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, 1136239440) + }) + + Convey("time.unix(a,b,c) errors properly", t, func() { + res, _ := Run(context.Background(), "time.unix", "one", "two") + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, 0) + }) + + Convey("time.year() works properly", t, func() { + res, _ := Run(context.Background(), "time.year") + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, now.Year()) + }) + + Convey("time.year(a) works properly", t, func() { + res, _ := Run(context.Background(), "time.year", old) + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, 2006) + }) + + Convey("time.year(a,b,c) errors properly", t, func() { + res, _ := Run(context.Background(), "time.year", "one", "two") + So(res, ShouldHaveSameTypeAs, float64(0)) + So(res, ShouldEqual, 0) + }) + +}