From 107d7a70cede7ed6c66b2a8bf2936147d0a29de6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Jerman?= Date: Wed, 23 Nov 2022 16:07:19 +0100 Subject: [PATCH] Add support for in and nin ref handlers in reporter joins --- server/pkg/dal/def_link.go | 55 ++++++++++++++++++++++++++++------- server/pkg/dal/runner_gval.go | 53 +++++++++++++++++++++++++++++++-- server/pkg/dal/utils.go | 22 ++++++++++++-- server/pkg/expr/func_arr.go | 4 +-- 4 files changed, 117 insertions(+), 17 deletions(-) diff --git a/server/pkg/dal/def_link.go b/server/pkg/dal/def_link.go index 5fa4fe646..7612be693 100644 --- a/server/pkg/dal/def_link.go +++ b/server/pkg/dal/def_link.go @@ -219,24 +219,41 @@ func (def *Link) init(ctx context.Context, left, right Iterator) (exec *linkLeft } func (r *rowLink) SelectGVal(ctx context.Context, k string) (interface{}, error) { - return r.GetValue(k, 0) + // @todo this way of determining multivalue is not entirely ok but we can get away with it + // since we have special functions to work with those. + // We need to somehow inform this bit as to what fields are multi value. + row, counts := r.getGetter(k) + if row == nil { + return nil, nil + } + + cc := counts[k] + if cc == 0 { + return nil, nil + } + + if cc == 1 { + return row.GetValue(k, 0) + } + + out := make([]any, cc) + for i := uint(0); i < cc; i++ { + out[i], _ = row.GetValue(k, i) + } + return out, nil } func (r *rowLink) GetValue(name string, pos uint) (v any, err error) { - a := r.a.CountValues() + row, a := r.getGetter(name) + if row == nil { + return + } + if cc, ok := a[name]; ok { if pos >= cc { return nil, nil } - return r.a.GetValue(name, pos) - } - - b := r.b.CountValues() - if cc, ok := b[name]; ok { - if pos >= cc { - return nil, nil - } - return r.b.GetValue(name, pos) + return row.GetValue(name, pos) } return @@ -254,3 +271,19 @@ func (r *rowLink) CountValues() (out map[string]uint) { return } + +// getGetter returns the ValueGetter based on if the value is in a or b +// If neither, it returns nil +func (r *rowLink) getGetter(k string) (row ValueGetter, cc map[string]uint) { + cc = r.a.CountValues() + if _, ok := cc[k]; ok { + return r.a, cc + } + + cc = r.b.CountValues() + if _, ok := cc[k]; ok { + return r.b, cc + } + + return nil, nil +} diff --git a/server/pkg/dal/runner_gval.go b/server/pkg/dal/runner_gval.go index a7572bc31..b3e9118a7 100644 --- a/server/pkg/dal/runner_gval.go +++ b/server/pkg/dal/runner_gval.go @@ -3,9 +3,11 @@ package dal import ( "context" "fmt" + "reflect" "strings" "github.com/PaesslerAG/gval" + "github.com/cortezaproject/corteza/server/pkg/expr" "github.com/cortezaproject/corteza/server/pkg/gvalfnc" "github.com/cortezaproject/corteza/server/pkg/ql" ) @@ -125,6 +127,21 @@ var ( OutTypeUnknown: true, }, + "in": { + Handler: func(args ...string) string { + // The arguments must be reversed!! + return fmt.Sprintf("has(%s, %s)", args[1], args[0]) + }, + OutTypeUnknown: true, + }, + "nin": { + Handler: func(args ...string) string { + // The arguments must be reversed!! + return fmt.Sprintf("!has(%s, %s)", args[1], args[0]) + }, + OutTypeUnknown: true, + }, + // - strings "concat": { Handler: func(args ...string) string { @@ -311,7 +328,7 @@ func newRunnerGvalParsed(n *ql.ASTNode) (out *runnerGval, err error) { // @note the subset is limited to simplify the (eventual) offloading to the DB. // At some point, more functions will be supported, and the ones which can't // be offloaded will be performed in some exec. step. -func newGval(expr string) (gval.Evaluable, error) { +func newGval(e string) (gval.Evaluable, error) { return gval.Full( // Extra functions we'll need // @note don't bring in all of the expr. pkg functions as we'll need to @@ -328,7 +345,8 @@ func newGval(expr string) (gval.Evaluable, error) { gval.Function("int", gvalfnc.CastInt), gval.Function("string", gvalfnc.CastString), gval.Function("concat", gvalfnc.ConcatStrings), - ).NewEvaluable(expr) + gval.Function("has", arrHas), + ).NewEvaluable(e) } func newQlParser(onIdent ...ql.IdentHandler) *ql.Parser { @@ -409,3 +427,34 @@ func (c converterGval) refHandler(n *ql.ASTNode, args ...string) (out string, er } return refToGvalExp[r].Handler(args...), nil } + +// arrHas is a helper to assure the expr.Has always gets an array (or map) as +// the first argument +// +// @todo this is needed because how the ValueGetters returns multi-value fields so +// an edge case where a field would have [a] but here, it would be presented +// as a. +// This would become obsolete when we address the actual issue. +func arrHas(arr interface{}, vv ...interface{}) (b bool, err error) { + arr = expr.UntypedValue(arr) + + if isMap(arr) { + return expr.Has(arr, vv...) + } + + var ( + c = reflect.ValueOf(arr) + ) + + switch c.Kind() { + case reflect.Slice: + return expr.Has(arr, vv...) + + default: + return expr.Has([]any{arr}, vv...) + } +} + +func isMap(v interface{}) bool { + return reflect.TypeOf(v).Kind() == reflect.Map +} diff --git a/server/pkg/dal/utils.go b/server/pkg/dal/utils.go index 0f712624e..4c69524fd 100644 --- a/server/pkg/dal/utils.go +++ b/server/pkg/dal/utils.go @@ -57,8 +57,26 @@ func (r *Row) WithValue(name string, pos uint, v any) *Row { return r } -func (r Row) SelectGVal(ctx context.Context, k string) (interface{}, error) { - return r.GetValue(unwrapNestedGvalIdent(k), 0) +func (r Row) SelectGVal(ctx context.Context, wk string) (interface{}, error) { + // @todo this way of determining multivalue is not entirely ok but we can get away with it + // since we have special functions to work with those. + // We need to somehow inform this bit as to what fields are multi value. + + k := unwrapNestedGvalIdent(wk) + cc := r.CountValues()[k] + if cc == 0 { + return nil, nil + } + + if cc == 1 { + return r.GetValue(k, 0) + } + + out := make([]any, cc) + for i := uint(0); i < cc; i++ { + out[i], _ = r.GetValue(k, i) + } + return out, nil } // Reset clears out the row so the same instance can be reused where possible diff --git a/server/pkg/expr/func_arr.go b/server/pkg/expr/func_arr.go index 807b4cfcc..af72d2947 100644 --- a/server/pkg/expr/func_arr.go +++ b/server/pkg/expr/func_arr.go @@ -15,7 +15,7 @@ func ArrayFunctions() []gval.Language { gval.Function("pop", pop), gval.Function("shift", shift), gval.Function("count", count), - gval.Function("has", has), + gval.Function("has", Has), gval.Function("hasAll", hasAll), gval.Function("find", find), gval.Function("sort", sortSlice), @@ -157,7 +157,7 @@ func count(arr interface{}, v ...interface{}) (count int, err error) { } // has finds any occurrence of the values in slice or key in a map -func has(arr interface{}, vv ...interface{}) (b bool, err error) { +func Has(arr interface{}, vv ...interface{}) (b bool, err error) { arr = UntypedValue(arr) if isMap(arr) {