From 9fc3b360a43e9f0295f7366e9ea635a43521b341 Mon Sep 17 00:00:00 2001 From: Victor Marin <36818606+mdvictor@users.noreply.github.com> Date: Fri, 3 Feb 2023 17:23:46 +0200 Subject: [PATCH] MSSQL: Add support for macro function calls (#62742) * MSSQL: Add support for macro function calls * Add tests --- pkg/tsdb/mssql/macros.go | 28 ++++++- pkg/tsdb/mssql/macros_test.go | 142 ++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 1 deletion(-) diff --git a/pkg/tsdb/mssql/macros.go b/pkg/tsdb/mssql/macros.go index 9a137ce496d..a0c060c5b14 100644 --- a/pkg/tsdb/mssql/macros.go +++ b/pkg/tsdb/mssql/macros.go @@ -13,6 +13,7 @@ import ( const rsIdentifier = `([_a-zA-Z0-9]+)` const sExpr = `\$` + rsIdentifier + `\(([^\)]*)\)` +const argFuncExpr = `[\(|,|\s]([_a-zA-Z0-9]+\([^\(]*?\))` type msSQLMacroEngine struct { *sqleng.SQLMacroEngineBase @@ -22,16 +23,41 @@ func newMssqlMacroEngine() sqleng.SQLMacroEngine { return &msSQLMacroEngine{SQLMacroEngineBase: sqleng.NewSQLMacroEngineBase()} } +func replaceFunctionCallArgsWithPlaceholders(sql string) (string, map[string]string) { + rExp, _ := regexp.Compile(argFuncExpr) + functionCalls := rExp.FindAllString(sql, -1) + replacedArgs := make(map[string]string) + + if len(functionCalls) > 0 { + sql = strings.Replace(sql, "\n", "", -1) + + for i, functionCall := range functionCalls { + key := fmt.Sprintf("$%d", i) + formattedVal := functionCall[1:] + sql = strings.Replace(sql, formattedVal, key, 1) + replacedArgs[key] = formattedVal + } + + return sql, replacedArgs + } + + return sql, nil +} + func (m *msSQLMacroEngine) Interpolate(query *backend.DataQuery, timeRange backend.TimeRange, sql string) (string, error) { // TODO: Return any error rExp, _ := regexp.Compile(sExpr) var macroError error + formattedSql, fctCallArgsMap := replaceFunctionCallArgsWithPlaceholders(sql) - sql = m.ReplaceAllStringSubmatchFunc(rExp, sql, func(groups []string) string { + sql = m.ReplaceAllStringSubmatchFunc(rExp, formattedSql, func(groups []string) string { args := strings.Split(groups[2], ",") for i, arg := range args { args[i] = strings.Trim(arg, " ") + if fctCallArgsMap != nil && fctCallArgsMap[args[i]] != "" { + args[i] = strings.Replace(args[i], args[i], fctCallArgsMap[args[i]], 1) + } } res, err := m.evaluateMacro(timeRange, query, groups[1], args) if err != nil && macroError == nil { diff --git a/pkg/tsdb/mssql/macros_test.go b/pkg/tsdb/mssql/macros_test.go index 76b2a8c0b23..5e14a9a0b6d 100644 --- a/pkg/tsdb/mssql/macros_test.go +++ b/pkg/tsdb/mssql/macros_test.go @@ -207,6 +207,148 @@ func TestMacroEngine(t *testing.T) { require.Equal(t, fmt.Sprintf("select time_column >= %d AND time_column <= %d", from.UnixNano(), to.UnixNano()), sql) }) + + t.Run("interpolate __timeGroup with function calls as params successfully", func(t *testing.T) { + //various queries with function calls as params tested with different spacings + sqls := []struct { + query string + expected string + }{ + { + query: "select $__timeGroup(test, 5m)", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', test)/300)*300", + }, + { + query: "select $__timeGroup(try_convert(timestamp, time), 5m)", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300", + }, + { + query: "select $__timeGroup(func(), 5m)", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', func())/300)*300", + }, + { + query: "select $__timeGroup( try_convert(timestamp, time), 5m)", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300", + }, + { + query: "select $__timeGroup( \n try_convert(timestamp, time), 5m)", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300", + }, + { + query: "select $__timeGroup( \n\n\ntry_convert(timestamp, time), 5m)", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300", + }, + { + query: "select $__timeGroup( try_convert(timestamp, time) , 5m)", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300", + }, + { + query: "select $__timeGroup( try_convert(timestamp, time) ,5m)", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300", + }, + { + query: "select $__timeGroup(try_convert(timestamp, time),5m)", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300", + }, + { + query: "select $__timeGroup(try_convert(timestamp, time), 5m)", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300", + }, + { + query: "select $__timeGroup(try_convert(timestamp, time), 5m)", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300", + }, + } + + for _, sql := range sqls { + actual, err := engine.Interpolate(query, timeRange, sql.query) + require.Nil(t, err) + require.Equal(t, sql.expected, actual) + } + }) + + t.Run("interpolate macros with function calls as params successfully", func(t *testing.T) { + filterInterpolated := fmt.Sprintf("BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339)) + unixEpochFilter := fmt.Sprintf("select try_convert(timestamp, time) >= %d AND try_convert(timestamp, time) <= %d", from.Unix(), to.Unix()) + unixEphocNanoFilter := fmt.Sprintf("select try_convert(timestamp, time) >= %d AND try_convert(timestamp, time) <= %d", from.UnixNano(), to.UnixNano()) + + //queries with macros and fct calls as params. fct calls are tested with various params and spaces + sqls := []struct { + query string + expected string + }{ + { + query: "select $__timeGroup(try_convert(timestamp, time), 5m) from test where $__timeFilter(time)", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300 from test where time " + filterInterpolated, + }, + { + query: "select $__timeGroup(try_convert(timestamp, time), 5m) from test where $__timeFilter(func(time))", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300 from test where func(time) " + filterInterpolated, + }, + { + query: "select $__timeGroup(try_convert(timestamp, time), 5m) from test where $__timeFilter(func(time));", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300 from test where func(time) " + filterInterpolated + ";", + }, + { + query: "select $__timeGroup(try_convert(timestamp, time), 5m) from test where $__timeFilter(func(time, var2));", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300 from test where func(time, var2) " + filterInterpolated + ";", + }, + { + query: "select $__timeGroup(try_convert(timestamp, time), 5m), $__timeGroup(func2( var1 , var2, var3 ), 15m) from test where $__timeFilter(func(time, var2));", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/300)*300, FLOOR(DATEDIFF(second, '1970-01-01', func2( var1 , var2, var3 ))/900)*900 from test where func(time, var2) " + filterInterpolated + ";", + }, + { + query: "select $__timeGroupAlias(try_convert(), 5m)", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert())/300)*300 AS [time]", + }, + { + query: "select $__timeEpoch(try_convert(timestamp, time))", + expected: "select DATEDIFF(second, '1970-01-01', try_convert(timestamp, time)) AS time", + }, + { + query: "select $__time(try_convert(timestamp, time))", + expected: "select try_convert(timestamp, time) AS time", + }, + { + query: "select $__timeGroup(try_convert(timestamp, time), 15m, NULL)", + expected: "select FLOOR(DATEDIFF(second, '1970-01-01', try_convert(timestamp, time))/900)*900", + }, + { + query: "select $__unixEpochFilter(try_convert(timestamp, time))", + expected: unixEpochFilter, + }, + { + query: "select $__unixEpochNanoFilter(try_convert(timestamp, time))", + expected: unixEphocNanoFilter, + }, + { + query: "select $__unixEpochGroup(try_convert(timestamp, time), 15m)", + expected: "select FLOOR(try_convert(timestamp, time)/900)*900", + }, + { + query: "select $__unixEpochGroupAlias(try_convert(timestamp, time), 15m)", + expected: "select FLOOR(try_convert(timestamp, time)/900)*900 AS [time]", + }, + } + + for _, sql := range sqls { + actual, err := engine.Interpolate(query, timeRange, sql.query) + require.Nil(t, err) + require.Equal(t, sql.expected, actual) + } + }) + + t.Run("interpolate macros with function calls as params unsuccessfully", func(t *testing.T) { + sqls := []string{ + "select $__timeGroup(func1(func2()), 5m)", //cannot go beyond 1st level of nested function calls + "select $__timeGroup(func1(), func2())", //second param must be interval + } + + for _, sql := range sqls { + _, err := engine.Interpolate(query, timeRange, sql) + require.NotNil(t, err) + } + }) }) }