Commit 34da0711 by Sven Klemm Committed by Daniel Lee

add __timeGroup macro for mysql (#9596)

* add __timeGroup macro for mysql

* put example __timeGroup query in frontend help

* do __timeGroup interval parsing in go similar to mysql

* ignore whitespace around interval
parent 6aa0f350
...@@ -3,6 +3,8 @@ package mysql ...@@ -3,6 +3,8 @@ package mysql
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"strings"
"time"
"github.com/grafana/grafana/pkg/tsdb" "github.com/grafana/grafana/pkg/tsdb"
) )
...@@ -25,7 +27,7 @@ func (m *MySqlMacroEngine) Interpolate(timeRange *tsdb.TimeRange, sql string) (s ...@@ -25,7 +27,7 @@ func (m *MySqlMacroEngine) Interpolate(timeRange *tsdb.TimeRange, sql string) (s
var macroError error var macroError error
sql = replaceAllStringSubmatchFunc(rExp, sql, func(groups []string) string { sql = replaceAllStringSubmatchFunc(rExp, sql, func(groups []string) string {
res, err := m.evaluateMacro(groups[1], groups[2:]) res, err := m.evaluateMacro(groups[1], strings.Split(groups[2], ","))
if err != nil && macroError == nil { if err != nil && macroError == nil {
macroError = err macroError = err
return "macro_error()" return "macro_error()"
...@@ -73,6 +75,15 @@ func (m *MySqlMacroEngine) evaluateMacro(name string, args []string) (string, er ...@@ -73,6 +75,15 @@ func (m *MySqlMacroEngine) evaluateMacro(name string, args []string) (string, er
return fmt.Sprintf("FROM_UNIXTIME(%d)", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil return fmt.Sprintf("FROM_UNIXTIME(%d)", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil
case "__timeTo": case "__timeTo":
return fmt.Sprintf("FROM_UNIXTIME(%d)", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil return fmt.Sprintf("FROM_UNIXTIME(%d)", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil
case "__timeGroup":
if len(args) != 2 {
return "", fmt.Errorf("macro %v needs time column and interval", name)
}
interval, err := time.ParseDuration(strings.Trim(args[1], `'" `))
if err != nil {
return "", fmt.Errorf("error parsing interval %v", args[1])
}
return fmt.Sprintf("cast(cast(UNIX_TIMESTAMP(%s)/(%.0f) as signed)*%.0f as signed)", args[0], interval.Seconds(), interval.Seconds()), nil
case "__unixEpochFilter": case "__unixEpochFilter":
if len(args) == 0 { if len(args) == 0 {
return "", fmt.Errorf("missing time column argument for macro %v", name) return "", fmt.Errorf("missing time column argument for macro %v", name)
......
...@@ -40,6 +40,14 @@ func TestMacroEngine(t *testing.T) { ...@@ -40,6 +40,14 @@ func TestMacroEngine(t *testing.T) {
So(sql, ShouldEqual, "select FROM_UNIXTIME(18446744066914186738)") So(sql, ShouldEqual, "select FROM_UNIXTIME(18446744066914186738)")
}) })
Convey("interpolate __timeGroup function", func() {
sql, err := engine.Interpolate(timeRange, "GROUP BY $__timeGroup(time_column,'5m')")
So(err, ShouldBeNil)
So(sql, ShouldEqual, "GROUP BY cast(cast(UNIX_TIMESTAMP(time_column)/(300) as signed)*300 as signed)")
})
Convey("interpolate __timeTo function", func() { Convey("interpolate __timeTo function", func() {
sql, err := engine.Interpolate(timeRange, "select $__timeTo(time_column)") sql, err := engine.Interpolate(timeRange, "select $__timeTo(time_column)")
So(err, ShouldBeNil) So(err, ShouldBeNil)
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"regexp" "regexp"
"strings" "strings"
"time"
"github.com/grafana/grafana/pkg/tsdb" "github.com/grafana/grafana/pkg/tsdb"
) )
...@@ -80,10 +81,14 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string, ...@@ -80,10 +81,14 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string,
case "__timeTo": case "__timeTo":
return fmt.Sprintf("to_timestamp(%d)", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil return fmt.Sprintf("to_timestamp(%d)", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil
case "__timeGroup": case "__timeGroup":
if len(args) < 2 { if len(args) != 2 {
return "", fmt.Errorf("macro %v needs time column and interval", name) return "", fmt.Errorf("macro %v needs time column and interval", name)
} }
return fmt.Sprintf("(extract(epoch from \"%s\")/extract(epoch from %s::interval))::int*extract(epoch from %s::interval)", args[0], args[1], args[1]), nil interval, err := time.ParseDuration(strings.Trim(args[1], `' `))
if err != nil {
return "", fmt.Errorf("error parsing interval %v", args[1])
}
return fmt.Sprintf("(extract(epoch from \"%s\")/%v)::bigint*%v", args[0], interval.Seconds(), interval.Seconds()), nil
case "__unixEpochFilter": case "__unixEpochFilter":
if len(args) == 0 { if len(args) == 0 {
return "", fmt.Errorf("missing time column argument for macro %v", name) return "", fmt.Errorf("missing time column argument for macro %v", name)
......
...@@ -45,7 +45,7 @@ func TestMacroEngine(t *testing.T) { ...@@ -45,7 +45,7 @@ func TestMacroEngine(t *testing.T) {
sql, err := engine.Interpolate(timeRange, "GROUP BY $__timeGroup(time_column,'5m')") sql, err := engine.Interpolate(timeRange, "GROUP BY $__timeGroup(time_column,'5m')")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(sql, ShouldEqual, "GROUP BY (extract(epoch from \"time_column\")/extract(epoch from '5m'::interval))::int*extract(epoch from '5m'::interval)") So(sql, ShouldEqual, "GROUP BY (extract(epoch from \"time_column\")/300)::bigint*300")
}) })
Convey("interpolate __timeTo function", func() { Convey("interpolate __timeTo function", func() {
......
...@@ -49,7 +49,15 @@ Macros: ...@@ -49,7 +49,15 @@ Macros:
- $__time(column) -&gt; UNIX_TIMESTAMP(column) as time_sec - $__time(column) -&gt; UNIX_TIMESTAMP(column) as time_sec
- $__timeFilter(column) -&gt; UNIX_TIMESTAMP(time_date_time) &ge; 1492750877 AND UNIX_TIMESTAMP(time_date_time) &le; 1492750877 - $__timeFilter(column) -&gt; UNIX_TIMESTAMP(time_date_time) &ge; 1492750877 AND UNIX_TIMESTAMP(time_date_time) &le; 1492750877
- $__unixEpochFilter(column) -&gt; time_unix_epoch &gt; 1492750877 AND time_unix_epoch &lt; 1492750877 - $__unixEpochFilter(column) -&gt; time_unix_epoch &gt; 1492750877 AND time_unix_epoch &lt; 1492750877
- $__timeGroup(column,'5m') -&gt; (extract(epoch from "dateColumn")/extract(epoch from '5m'::interval))::int - $__timeGroup(column,'5m') -&gt; cast(cast(UNIX_TIMESTAMP(column)/(300) as signed)*300 as signed)
Example of group by and order by with $__timeGroup:
SELECT
$__timeGroup(timestamp_col, '1h') AS time,
sum(value_double) as value
FROM yourtable
GROUP BY 1
ORDER BY 1
Or build your own conditionals using these macros which just return the values: Or build your own conditionals using these macros which just return the values:
- $__timeFrom() -&gt; FROM_UNIXTIME(1492750877) - $__timeFrom() -&gt; FROM_UNIXTIME(1492750877)
......
...@@ -50,17 +50,15 @@ Macros: ...@@ -50,17 +50,15 @@ Macros:
- $__timeEpoch -&gt; extract(epoch from column) as "time" - $__timeEpoch -&gt; extract(epoch from column) as "time"
- $__timeFilter(column) -&gt; column &ge; to_timestamp(1492750877) AND column &le; to_timestamp(1492750877) - $__timeFilter(column) -&gt; column &ge; to_timestamp(1492750877) AND column &le; to_timestamp(1492750877)
- $__unixEpochFilter(column) -&gt; column &gt; 1492750877 AND column &lt; 1492750877 - $__unixEpochFilter(column) -&gt; column &gt; 1492750877 AND column &lt; 1492750877
- $__timeGroup(column,'5m') -&gt; (extract(epoch from "dateColumn")/extract(epoch from '5m'::interval))::int
To group by time use $__timeGroup:
-&gt; (extract(epoch from column)/extract(epoch from column::interval))::int
Example of group by and order by with $__timeGroup: Example of group by and order by with $__timeGroup:
SELECT SELECT
min(date_time_col) AS time_sec, $__timeGroup(date_time_col, '1h') AS time,
sum(value_double) as value sum(value) as value
FROM yourtable FROM yourtable
group by $__timeGroup(date_time_col, '1h') GROUP BY time
order by $__timeGroup(date_time_col, '1h') ASC ORDER BY time
Or build your own conditionals using these macros which just return the values: Or build your own conditionals using these macros which just return the values:
- $__timeFrom() -&gt; to_timestamp(1492750877) - $__timeFrom() -&gt; to_timestamp(1492750877)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment