From 6e35021d6b1c2cd2975ba805feb2d204e2f0ca6a Mon Sep 17 00:00:00 2001 From: AJ Roetker Date: Mon, 14 Oct 2024 09:32:46 -0700 Subject: [PATCH] Add boundary calc for slices --- expr/builtins/list_map.go | 73 ++++++++++++++++++--------------------- value/coerce.go | 42 ++++++++++++---------- vm/datemath.go | 39 ++++++++++++++++++--- vm/vm.go | 4 +-- 4 files changed, 93 insertions(+), 65 deletions(-) diff --git a/expr/builtins/list_map.go b/expr/builtins/list_map.go index 2b1a0940..7df0293c 100644 --- a/expr/builtins/list_map.go +++ b/expr/builtins/list_map.go @@ -15,9 +15,8 @@ var _ = u.EMPTY // len length of array types // -// len([1,2,3]) => 3, true -// len(not_a_field) => -- NilInt, false -// +// len([1,2,3]) => 3, true +// len(not_a_field) => -- NilInt, false type Length struct{} // Type is IntType @@ -63,13 +62,12 @@ func lenEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool) { // ArrayIndex array.index choose the nth element of an array // -// // given context input of -// "items" = [1,2,3] -// -// array.index(items, 1) => 1, true -// array.index(items, 5) => nil, false -// array.index(items, -1) => 3, true +// // given context input of +// "items" = [1,2,3] // +// array.index(items, 1) => 1, true +// array.index(items, 5) => nil, false +// array.index(items, -1) => 3, true type ArrayIndex struct{} // Type unknown - returns single value from SliceValue array @@ -108,12 +106,12 @@ func arrayIndexEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool // array.slice slice element m -> n of a slice. First arg must be a slice. // -// // given context of -// "items" = [1,2,3,4,5] +// // given context of +// "items" = [1,2,3,4,5] // -// array.slice(items, 1, 3) => [2,3], true -// array.slice(items, 2) => [3,4,5], true -// array.slice(items, -2) => [4,5], true +// array.slice(items, 1, 3) => [2,3], true +// array.slice(items, 2) => [3,4,5], true +// array.slice(items, -2) => [4,5], true type ArraySlice struct{} // Type Unknown for Array Slice @@ -208,8 +206,7 @@ func arraySliceEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool // Map Create a map from two values. If the right side value is nil // then does not evaluate. // -// map(left, right) => map[string]value{left:right} -// +// map(left, right) => map[string]value{left:right} type MapFunc struct{} // Type is MapValueType @@ -236,9 +233,8 @@ func mapEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool) { // MapTime() Create a map[string]time of each key // -// maptime(field) => map[string]time{field_value:message_timestamp} -// maptime(field, timestamp) => map[string]time{field_value:timestamp} -// +// maptime(field) => map[string]time{field_value:message_timestamp} +// maptime(field, timestamp) => map[string]time{field_value:timestamp} type MapTime struct{} // Type MapTime @@ -268,9 +264,9 @@ func mapTimeEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool) { } k = strings.ToLower(kitem.ToString()) - var ok bool - ts, ok = value.ValueToTime(args[1]) - if !ok { + var err error + ts, err = value.ValueToTime(args[1]) + if err != nil { return value.EmptyMapTimeValue, false } } @@ -282,13 +278,13 @@ func mapTimeEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool) { // - May pass as many match strings as you want. // - Must match on Prefix of key. // -// given input context of: -// {"score_value":24,"event_click":true, "tag_apple": "apple", "label_orange": "orange"} +// given input context of: +// {"score_value":24,"event_click":true, "tag_apple": "apple", "label_orange": "orange"} // -// match("score_") => {"value":24} -// match("amount_") => false -// match("event_") => {"click":true} -// match("label_","tag_") => {"apple":"apple","orange":"orange"} +// match("score_") => {"value":24} +// match("amount_") => false +// match("event_") => {"click":true} +// match("label_","tag_") => {"apple":"apple","orange":"orange"} type Match struct{} // Type is MapValueType @@ -328,11 +324,10 @@ func matchEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool) { // MapKeys: Take a map and extract array of keys // -// //given input: -// {"tag.1":"news","tag.2":"sports"} -// -// mapkeys(match("tag.")) => []string{"news","sports"} +// //given input: +// {"tag.1":"news","tag.2":"sports"} // +// mapkeys(match("tag.")) => []string{"news","sports"} type MapKeys struct{} // Type []string aka strings @@ -368,11 +363,10 @@ func mapKeysEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool) { // MapValues: Take a map and extract array of values // -// // given input: -// {"tag.1":"news","tag.2":"sports"} -// -// mapvalue(match("tag.")) => []string{"1","2"} +// // given input: +// {"tag.1":"news","tag.2":"sports"} // +// mapvalue(match("tag.")) => []string{"1","2"} type MapValues struct{} // Type strings aka []string @@ -409,11 +403,10 @@ func mapValuesEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool) // MapInvert: Take a map and invert key/values // -// // given input: -// tags = {"1":"news","2":"sports"} -// -// mapinvert(tags) => map[string]string{"news":"1","sports":"2"} +// // given input: +// tags = {"1":"news","2":"sports"} // +// mapinvert(tags) => map[string]string{"news":"1","sports":"2"} type MapInvert struct{} // Type MapValue diff --git a/value/coerce.go b/value/coerce.go index 04626ea6..7b4de1a0 100644 --- a/value/coerce.go +++ b/value/coerce.go @@ -79,8 +79,8 @@ func Cast(valType ValueType, val Value) (Value, error) { } case TimeType: - t, ok := ValueToTime(val) - if ok { + t, err := ValueToTime(val) + if err != nil { return NewTimeValue(t), nil } case StringType: @@ -312,55 +312,61 @@ func ValueToInt64(val Value) (int64, bool) { // StringToTimeAnchor Convert a string type to a time if possible. // If "now-3d" then use date-anchoring ie if prefix = 'now'. -func StringToTimeAnchor(val string, anchor time.Time) (time.Time, bool) { +func StringToTimeAnchor(val string, anchor time.Time) (time.Time, error) { if len(val) > 3 && strings.ToLower(val[:3]) == "now" { // Is date math t, err := datemath.EvalAnchor(anchor, val) if err != nil { - return time.Time{}, false + return time.Time{}, fmt.Errorf("failed to eval datemath anchor: %w", err) } - return t, true + return t, nil } t, err := dateparse.ParseAny(val) - if err == nil { - return t, true + if err != nil { + return t, fmt.Errorf("failed to parse time %v: %w", val, err) } - return time.Time{}, false + return t, nil } // ValueToTime Convert a value type to a time if possible -func ValueToTime(val Value) (time.Time, bool) { +func ValueToTime(val Value) (time.Time, error) { return ValueToTimeAnchor(val, time.Now()) } // ValueToTimeAnchor given a value, and a time anchor, conver to time. // use "now-3d" anchoring if has prefix "now". -func ValueToTimeAnchor(val Value, anchor time.Time) (time.Time, bool) { +func ValueToTimeAnchor(val Value, anchor time.Time) (time.Time, error) { switch v := val.(type) { case TimeValue: - return v.Val(), true - case StringValue: - return StringToTimeAnchor(v.Val(), anchor) + return v.Val(), nil case StringsValue: vals := v.Val() if len(vals) < 1 { - return time.Time{}, false + return time.Time{}, fmt.Errorf("empty string slice") } return StringToTimeAnchor(vals[0], anchor) + case SliceValue: + vals := v.SliceValue() + if len(vals) < 1 { + return time.Time{}, fmt.Errorf("empty slice") + } + return time.Time{}, fmt.Errorf("slice values") + case StringValue: + return StringToTimeAnchor(v.Val(), anchor) case IntValue, NumberValue: t, err := dateparse.ParseIn(v.ToString(), time.Local) if err != nil { - return time.Time{}, false + return t, fmt.Errorf("parsing number: %w", err) } if t.Year() < 1800 || t.Year() > 2120 { - return t, false + return t, fmt.Errorf("year outside of time bounds 1800, 2120: %w", err) } - return t, true + return t, nil default: //u.Warnf("un-handled type to time? %#v", val) } - return time.Time{}, false + return time.Time{}, fmt.Errorf("unhandled type to time: %#v", val) } // StringToFloat64 converts a string to a float diff --git a/vm/datemath.go b/vm/datemath.go index 909a15a3..9fd066d0 100644 --- a/vm/datemath.go +++ b/vm/datemath.go @@ -62,17 +62,46 @@ func compareBoundaries(currBoundary, newBoundary time.Time) time.Time { return currBoundary } func evalBoundary(anchorTime, currBoundary time.Time, lhv value.Value, op lex.TokenType, val string) (time.Time, error) { - ct, ok := value.ValueToTime(lhv) - if !ok { - return currBoundary, fmt.Errorf("Could not convert %T: %v to time.Time", lhv, lhv) - } - // Given Anchor Time At calculate Relative Time Rt rt, err := datemath.EvalAnchor(anchorTime, val) if err != nil { return currBoundary, err } + ct, err := value.ValueToTime(lhv) + if err != nil && strings.Contains(err.Error(), "slice values") { + switch op { + case lex.TokenGT, lex.TokenGE: + bt := currBoundary + for _, val := range lhv.(value.SliceValue).SliceValue() { + ct, err := value.ValueToTime(val) + if err != nil { + return time.Time{}, fmt.Errorf("converting slice value: %w", err) + } + if rt.Before(ct) { + bt = compareBoundaries(currBoundary, anchorTime.Add(ct.Sub(rt))) + } + } + return bt, nil + case lex.TokenLT, lex.TokenLE: + bt := currBoundary + for _, val := range lhv.(value.SliceValue).SliceValue() { + ct, err := value.ValueToTime(val) + if err != nil { + return time.Time{}, fmt.Errorf("converting slice value: %w", err) + } + if !ct.Before(rt) { + bt = compareBoundaries(bt, anchorTime.Add(ct.Sub(rt))) + } + } + return bt, nil + default: + return currBoundary, nil + } + } else if err != nil { + return currBoundary, fmt.Errorf("Could not convert %T: %v to time.Time %w", lhv, lhv, err) + } + // Ct = Comparison time, left hand side of expression // At = Anchor Time // Rt = Relative time result of Anchor Time offset by datemath "now-3d" diff --git a/vm/vm.go b/vm/vm.go index b4b708fb..cdfcabfe 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -619,8 +619,8 @@ func evalBinary(ctx expr.EvalContext, node *expr.BinaryNode, depth int, visitedI n := operateNumbers(node.Operator, at.NumberValue(), bt) return n, true case value.TimeValue: - lht, ok := value.ValueToTime(at) - if !ok { + lht, err := value.ValueToTime(at) + if err != nil { return value.BoolValueFalse, false } return operateTime(node.Operator.T, lht, bt.Val())