// Convert to Integer: Best attempt at converting to integer // // toint("5") => 5 // toint("5.75") => 5 // toint("5,555") => 5555 // toint("$5") => 5 // toint("5,555.00") => 5555 // func ToInt(ctx expr.EvalContext, item value.Value) (value.IntValue, bool) { iv, ok := value.ToInt64(reflect.ValueOf(item.Value())) if !ok { return value.NewIntValue(0), false } return value.NewIntValue(iv), true }
// Not: urnary negation function // // eq(item,5) func NotFunc(ctx expr.EvalContext, item value.Value) (value.BoolValue, bool) { boolVal, ok := value.ToBool(item.Rv()) if ok { return value.NewBoolValue(!boolVal), true } return value.BoolValueFalse, false }
// Convert to Number: Best attempt at converting to integer // // tonumber("5") => 5.0 // tonumber("5.75") => 5.75 // tonumber("5,555") => 5555 // tonumber("$5") => 5.00 // tonumber("5,555.00") => 5555 // func ToNumber(ctx expr.EvalContext, item value.Value) (value.NumberValue, bool) { fv, ok := value.ToFloat64(reflect.ValueOf(item.Value())) if !ok { return value.NewNumberValue(0), false } return value.NewNumberValue(fv), true }
// Extract host from a String (must be urlish), doesn't do much/any validation func Qs(ctx expr.EvalContext, urlItem, keyItem value.Value) (value.StringValue, bool) { val, ok := value.ToString(urlItem.Rv()) if !ok { return value.EmptyStringValue, false } if val == "" { return value.EmptyStringValue, false } urlstr := strings.ToLower(val) if len(urlstr) < 8 { return value.EmptyStringValue, false } keyVal, ok := value.ToString(keyItem.Rv()) if !ok { return value.EmptyStringValue, false } if keyVal == "" { return value.EmptyStringValue, false } if !strings.HasPrefix(urlstr, "http") { urlstr = "http://" + urlstr } if urlParsed, err := url.Parse(urlstr); err == nil { //u.Infof("url.parse: %#v", urlParsed) qsval, ok := urlParsed.Query()[keyVal] if !ok { return value.EmptyStringValue, false } if len(qsval) > 0 { return value.NewStringValue(qsval[0]), true } } return value.EmptyStringValue, false }
// Count func CountFunc(ctx EvalContext, val value.Value) (value.IntValue, bool) { if val.Err() || val.Nil() { return value.NewIntValue(0), false } //u.Infof("??? vals=[%v]", val.Value()) return value.NewIntValue(1), true }
// Equal function? returns true if items are equal // // eq(item,5) func Eq(ctx expr.EvalContext, itemA, itemB value.Value) (value.BoolValue, bool) { //return BoolValue(itemA == itemB) //rvb := value.CoerceTo(itemA.Rv(), itemB.Rv()) //u.Infof("Eq(): a:%T b:%T %v=%v?", itemA, itemB, itemA.Value(), rvb) //u.Infof("Eq()2: %T %T", itemA.Rv(), rvb) return value.NewBoolValue(reflect.DeepEqual(itemA.Value(), itemB.Value())), true }
// String lower function // must be able to convert to string // func Lower(ctx expr.EvalContext, item value.Value) (value.StringValue, bool) { val, ok := value.ToString(item.Rv()) if !ok { return value.EmptyStringValue, false } return value.NewStringValue(strings.ToLower(val)), true }
// <= Less Than or Equal // Must be able to convert items to Floats or else not ok // func LeFunc(ctx expr.EvalContext, lv, rv value.Value) (value.BoolValue, bool) { left, _ := value.ToFloat64(lv.Rv()) right, _ := value.ToFloat64(rv.Rv()) if math.IsNaN(left) || math.IsNaN(right) { return value.BoolValueFalse, false } return value.NewBoolValue(left <= right), true }
// < Less Than // Must be able to convert items to Floats or else not ok // func LtFunc(ctx expr.EvalContext, lv, rv value.Value) (value.BoolValue, bool) { left := value.ToFloat64(lv.Rv()) right := value.ToFloat64(rv.Rv()) if left == math.NaN() || right == math.NaN() { return value.BoolValueFalse, false } return value.NewBoolValue(left < right), true }
// Example of a custom Function, that we are adding into the Expression VM // // select // user_id AS theuserid, email, item_count * 2, reg_date // FROM stdio // WHERE email_is_valid(email) func EmailIsValid(ctx expr.EvalContext, email value.Value) (value.BoolValue, bool) { emailstr, ok := value.ToString(email.Rv()) if !ok || emailstr == "" { return value.BoolValueFalse, true } if _, err := mail.ParseAddress(emailstr); err == nil { return value.BoolValueTrue, true } return value.BoolValueFalse, true }
// todate func ToDate(ctx expr.EvalContext, item value.Value) (value.TimeValue, bool) { dateStr, ok := value.ToString(item.Rv()) if !ok { return value.TimeZeroValue, false } //u.Infof("v=%v %v ", v, item.Rv()) if t, err := dateparse.ParseAny(dateStr); err == nil { return value.NewTimeValue(t), true } return value.TimeZeroValue, false }
// Sqrt // // sqrt(4) => 2, true // sqrt(9) => 3, true // sqrt(not_number) => 0, false // func SqrtFunc(ctx expr.EvalContext, val value.Value) (value.NumberValue, bool) { nv, ok := val.(value.NumericValue) if !ok { return value.NewNumberValue(math.NaN()), false } if val.Err() || val.Nil() { return value.NewNumberValue(0), false } fv := nv.Float() fv = math.Sqrt(fv) //u.Infof("??? vals=[%v]", val.Value()) return value.NewNumberValue(fv), true }
// Convert to Integer: Best attempt at converting to integer // // toint("5") => 5, true // toint("5.75") => 5, true // toint("5,555") => 5555, true // toint("$5") => 5, true // toint("5,555.00") => 5555, true // func ToInt(ctx expr.EvalContext, item value.Value) (value.IntValue, bool) { switch itemT := item.(type) { case value.TimeValue: iv := itemT.Val().UnixNano() / 1e6 // Milliseconds return value.NewIntValue(iv), true } iv, ok := value.ToInt64(reflect.ValueOf(item.Value())) if !ok { return value.NewIntValue(0), false } return value.NewIntValue(iv), true }
// totimestamp: convert to date, then to unix Seconds // func ToTimestamp(ctx expr.EvalContext, item value.Value) (value.IntValue, bool) { dateStr, ok := value.ToString(item.Rv()) if !ok { return value.NewIntValue(0), false } if t, err := dateparse.ParseAny(dateStr); err == nil { //u.Infof("v=%v %v unix=%v", item, item.Rv(), t.Unix()) return value.NewIntValue(int64(t.Unix())), true } return value.NewIntValue(0), false }
// String contains // Will first convert to string, so may get unexpected results // func ContainsFunc(ctx expr.EvalContext, lv, rv value.Value) (value.BoolValue, bool) { left, leftOk := value.ToString(lv.Rv()) right, rightOk := value.ToString(rv.Rv()) if !leftOk || !rightOk { return value.BoolValueFalse, false } //u.Infof("Contains(%v, %v)", left, right) if left == "" || right == "" { return value.BoolValueFalse, false } if strings.Contains(left, right) { return value.BoolValueTrue, true } return value.BoolValueFalse, true }
// Split a string, accepts an optional with parameter // // split(item, ",") // func SplitFunc(ctx expr.EvalContext, input value.Value, splitByV value.StringValue) (value.StringsValue, bool) { sv, ok := value.ToString(input.Rv()) splitBy, splitByOk := value.ToString(splitByV.Rv()) if !ok || !splitByOk { return value.NewStringsValue(make([]string, 0)), false } if sv == "" { return value.NewStringsValue(make([]string, 0)), false } if splitBy == "" { return value.NewStringsValue(make([]string, 0)), false } vals := strings.Split(sv, splitBy) return value.NewStringsValue(vals), true }
// emailname a string, parses email // // emailname("Bob <*****@*****.**>") => Bob // func EmailNameFunc(ctx expr.EvalContext, item value.Value) (value.StringValue, bool) { val, ok := value.ToString(item.Rv()) if !ok { return value.EmptyStringValue, false } if val == "" { return value.EmptyStringValue, false } if len(val) < 6 { return value.EmptyStringValue, false } if em, err := mail.ParseAddress(val); err == nil { return value.NewStringValue(em.Name), true } return value.EmptyStringValue, false }
func Yy(ctx expr.EvalContext, item value.Value) (value.IntValue, bool) { //u.Info("yy: %T", item) val, ok := value.ToString(item.Rv()) if !ok || val == "" { return value.NewIntValue(0), false } //u.Infof("v=%v %v ", val, item.Rv()) if t, err := dateparse.ParseAny(val); err == nil { yy := t.Year() if yy >= 2000 { yy = yy - 2000 } else if yy >= 1900 { yy = yy - 1900 } //u.Infof("Yy = %v yy = %v", item, yy) return value.NewIntValue(int64(yy)), true } return value.NewIntValue(0), false }
// email a string, parses email // // email("Bob <*****@*****.**>") => [email protected] // func EmailDomainFunc(ctx expr.EvalContext, item value.Value) (value.StringValue, bool) { val, ok := value.ToString(item.Rv()) if !ok { return value.EmptyStringValue, false } if val == "" { return value.EmptyStringValue, false } if len(val) < 6 { return value.EmptyStringValue, false } if em, err := mail.ParseAddress(strings.ToLower(val)); err == nil { parts := strings.SplitN(strings.ToLower(em.Address), "@", 2) if len(parts) == 2 { return value.NewStringValue(parts[1]), true } } return value.EmptyStringValue, false }
// urlminusqs removes a specific query parameter and its value from a url // // urlminusqs("http://www.lytics.io/?q1=google&q2=123", "q1") => "http://www.lytics.io/?q2=123", true // func UrlMinusQs(ctx expr.EvalContext, urlItem, keyItem value.Value) (value.StringValue, bool) { val := "" switch itemT := urlItem.(type) { case value.StringValue: val = itemT.Val() case value.StringsValue: if len(itemT.Val()) == 0 { return value.EmptyStringValue, false } val = itemT.Val()[0] } if val == "" { return value.EmptyStringValue, false } if !strings.HasPrefix(val, "http") { val = "http://" + val } keyVal, ok := value.ToString(keyItem.Rv()) if !ok { return value.EmptyStringValue, false } if keyVal == "" { return value.EmptyStringValue, false } if up, err := url.Parse(val); err == nil { qsval := up.Query() _, ok := qsval[keyVal] if !ok { return value.NewStringValue(fmt.Sprintf("%s://%s%s?%s", up.Scheme, up.Host, up.Path, up.RawQuery)), true } qsval.Del(keyVal) up.RawQuery = qsval.Encode() if up.RawQuery == "" { return value.NewStringValue(fmt.Sprintf("%s://%s%s", up.Scheme, up.Host, up.Path)), true } return value.NewStringValue(fmt.Sprintf("%s://%s%s?%s", up.Scheme, up.Host, up.Path, up.RawQuery)), true } return value.EmptyStringValue, false }
// Extract url path from a String (must be urlish), doesn't do much/any validation func UrlPath(ctx expr.EvalContext, item value.Value) (value.StringValue, bool) { val, ok := value.ToString(item.Rv()) if !ok { return value.EmptyStringValue, false } if val == "" { return value.EmptyStringValue, false } urlstr := strings.ToLower(val) if len(urlstr) < 8 { return value.EmptyStringValue, false } if !strings.HasPrefix(urlstr, "http") { urlstr = "http://" + urlstr } if urlParsed, err := url.Parse(urlstr); err == nil { //u.Infof("url.parse: %#v", urlParsed) return value.NewStringValue(urlParsed.Path), true } return value.EmptyStringValue, false }
// HashSha512Func Hash a value to SHA512 string // // hash.sha512("/blog/index.html") => abc345xyz // func HashSha512Func(ctx expr.EvalContext, arg value.Value) (value.StringValue, bool) { if arg.Err() || arg.Nil() { return value.EmptyStringValue, true } hasher := sha512.New() hasher.Write([]byte(arg.ToString())) return value.NewStringValue(hex.EncodeToString(hasher.Sum(nil))), true }
func checkval(t *testing.T, r expr.ContextReader, key string, expected value.Value) { val, ok := r.Get(key) assert.Tf(t, ok, "expected key:%s =%v", key, expected.Value()) if val == nil { t.Errorf("not value for %v", key) } else { assert.Equalf(t, expected.Value(), val.Value(), "%s expected: %v got:%v", key, expected.Value(), val.Value()) } }
// Count: This should be renamed Increment // and in general is a horrible, horrible function that needs to be replaced // with occurences of value, ignores the value and ensures it is non null // // count(anyvalue) => 1, true // count(not_number) => -- 0, false // func CountFunc(ctx expr.EvalContext, val value.Value) (value.IntValue, bool) { if val.Err() || val.Nil() { return value.NewIntValue(0), false } return value.NewIntValue(1), true }
func ToInt(ctx expr.EvalContext, item value.Value) (value.IntValue, bool) { iv, _ := value.ToInt64(reflect.ValueOf(item.Value())) return value.NewIntValue(iv), true //return IntValue(2) }
// FilterFunc Filter out Values that match specified list of match filter criteria // // - Operates on MapValue (map[string]interface{}), StringsValue ([]string), or string // - takes N Filter Criteria // - supports Matching: "filter*" // matches "filter_x", "filterstuff" // // -- Filter a map of values by key to remove certain keys // filter(match("topic_"),key_to_filter, key2_to_filter) => {"goodkey": 22}, true // // -- Filter out VALUES (not keys) from a list of []string{} for a specific value // filter(split("apples,oranges",","),"ora*") => ["apples"], true // // -- Filter out values for single strings // filter("apples","app*") => []string{}, true // func FilterFunc(ctx expr.EvalContext, val value.Value, filterVals ...value.Value) (value.Value, bool) { filters := FiltersFromArgs(filterVals) //u.Debugf("Filter(): %T:%v filters:%v", val, val, filters) switch val := val.(type) { case value.MapValue: mv := make(map[string]interface{}) for rowKey, v := range val.Val() { filteredOut := false for _, filter := range filters { if strings.Contains(filter, "*") { match, _ := glob.Match(filter, rowKey) if match { filteredOut = true break } } else { if strings.HasPrefix(rowKey, filter) && v != nil { filteredOut = true break } } } if !filteredOut { mv[rowKey] = v.Value() } } return value.NewMapValue(mv), true case value.StringValue: anyMatches := false for _, filter := range filters { if strings.Contains(filter, "*") { match, _ := glob.Match(filter, val.Val()) if match { anyMatches = true break } } else { if strings.HasPrefix(val.Val(), filter) { anyMatches = true break } } } if anyMatches { return value.NilValueVal, true } return val, true case value.StringsValue: lv := make([]string, 0, val.Len()) for _, sv := range val.Val() { filteredOut := false for _, filter := range filters { if strings.Contains(filter, "*") { match, _ := glob.Match(filter, sv) if match { filteredOut = true break } } else { if strings.HasPrefix(sv, filter) && sv != "" { filteredOut = true break } } } if !filteredOut { lv = append(lv, sv) } } return value.NewStringsValue(lv), true default: u.Warnf("unsuported key type: %T %v", val, val) } //u.Warnf("could not find key: %T %v", item, item) return nil, false }
// Pow func PowFunc(ctx EvalContext, val, toPower value.Value) (value.NumberValue, bool) { //Pow(x, y float64) float64 //u.Infof("powFunc: %T:%v %T:%v ", val, val.Value(), toPower, toPower.Value()) if val.Err() || val.Nil() { return value.NewNumberValue(0), false } if toPower.Err() || toPower.Nil() { return value.NewNumberValue(0), false } fv, _ := value.ToFloat64(val.Rv()) pow, _ := value.ToFloat64(toPower.Rv()) if math.IsNaN(fv) || math.IsNaN(pow) { return value.NewNumberValue(0), false } fv = math.Pow(fv, pow) //u.Infof("pow ??? vals=[%v]", fv, pow) return value.NewNumberValue(fv), true }
// Equal function? returns true if items are equal // // eq(item,5) func Eq(e *State, itemA, itemB value.Value) (value.BoolValue, bool) { //return BoolValue(itemA == itemB) rvb := value.CoerceTo(itemA.Rv(), itemB.Rv()) //u.Infof("Eq(): a:%T b:%T %v=%v?", itemA, itemB, itemA.Value(), rvb) return value.NewBoolValue(reflect.DeepEqual(itemA.Rv(), rvb)), true }
func ToInt(e *State, item value.Value) (value.IntValue, bool) { iv, _ := value.ToInt64(reflect.ValueOf(item.Value())) return value.NewIntValue(iv), true //return IntValue(2) }
// 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} // func MapFunc(ctx expr.EvalContext, lv, rv value.Value) (value.MapValue, bool) { if lv.Err() || rv.Err() { return value.EmptyMapValue, false } if lv.Nil() || rv.Nil() { return value.EmptyMapValue, false } return value.NewMapValue(map[string]interface{}{lv.ToString(): rv.Value()}), true }