mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
adding support for sgl native time datatypes
This commit is contained in:
parent
dd7ab43d09
commit
6e7a067857
@ -31,7 +31,6 @@ Example:
|
||||
|
||||
```sql
|
||||
CREATE USER grafanareader WITH PASSWORD 'password'
|
||||
|
||||
GRANT SELECT ON dbo.YourTable3 TO grafanareader
|
||||
```
|
||||
|
||||
@ -43,7 +42,9 @@ To simplify syntax and to allow for dynamic parts, like date range filters, the
|
||||
|
||||
Macro example | Description
|
||||
------------ | -------------
|
||||
*$__time(dateColumn)* | Will be replaced by an expression to convert a DATETIME column type to unix timestamp and rename the it to `time_sec`. For example, *DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), dateColumn) ) as time_sec*
|
||||
*$__time(dateColumn)* | Will rename the column to `time`. For example, *dateColumn AS time*.
|
||||
*$__utcTime(dateColumn)* | Will be replaced by an expression to convert a DATETIME column type to UTC depending on the server's local timeoffset and rename it to `time`. For example, *DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), dateColumn) ) AS time*
|
||||
*$__timeEpoch(dateColumn)* | Will be replaced by an expression to convert a DATETIME column type to unix timestamp and rename the it to `time`. For example, *DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), dateColumn) ) AS time*
|
||||
*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *dateColumn >= DATEADD(s, 1494410783+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01') AND dateColumn <= DATEADD(s, 1494497183+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')*
|
||||
*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *DATEADD(second, 1494410783+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')*
|
||||
*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *DATEADD(second, 1494497183+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')*
|
||||
@ -77,7 +78,7 @@ FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_NAME = 'mssql_types';
|
||||
```
|
||||
|
||||
You can control the name of the Table panel columns by using regular `as ` SQL column selection syntax.
|
||||
You can control the name of the Table panel columns by using regular `AS ` SQL column selection syntax.
|
||||
|
||||
The resulting table panel:
|
||||
|
||||
@ -85,23 +86,48 @@ The resulting table panel:
|
||||
|
||||
### Time series queries
|
||||
|
||||
If you set `Format as` to `Time series`, for use in Graph panel for example, then the query must follow these rules:
|
||||
- Must be a column named `time_sec` representing a unix epoch in seconds.
|
||||
- Must be a column named `value` representing the time series value.
|
||||
- Must be a column named `metric` representing the time series name.
|
||||
If you set `Format as` to `Time series`, for use in Graph panel for example, then the query must must have a column named `time` that returns either a sql datetime or any numeric datatype representing unix epoch in seconds. You may return a column named `metric` that is used as metric name for the value column. Any column except `time` and `metric` is treated as a value column. If you ommit the `metric` column, tha name of the value column will be the metric name. You may select multiple value columns, each will have its name as metric. If you select multiple value columns along with a `metric` column, the names ("MetircName - ColumnName") will be combined to make the metric name.
|
||||
|
||||
Example with `metric` column
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
MIN(DATEDIFF(second,{d '1970-01-01'},[time_date_time])) as [time_sec],
|
||||
MAX([value_double]) as [value],
|
||||
[time_date_time] as [time],
|
||||
[value_double] as [value],
|
||||
[metric1] as [metric]
|
||||
FROM [test_data]
|
||||
WHERE $__timeFilter([time_date_time])
|
||||
GROUP BY metric1, DATEDIFF(second,{d '1970-01-01'},[time_date_time])/300
|
||||
ORDER BY [time_date_time]
|
||||
```
|
||||
|
||||
Example with multiple `value` culumns
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
[time_date_time] as [time],
|
||||
[value_double1] as [metric_name1],
|
||||
[value_int2] as [metric_name2]
|
||||
FROM [test_data]
|
||||
WHERE $__timeFilter([time_date_time])
|
||||
ORDER BY [time_date_time]
|
||||
```
|
||||
|
||||
Example with multiple `value` culumns combined with a `metric` column
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
[time_date_time] as [time],
|
||||
[value_double1] as [value1],
|
||||
[value_int2] as [value2],
|
||||
[metric_col] as [metric]
|
||||
FROM [test_data]
|
||||
WHERE $__timeFilter([time_date_time])
|
||||
ORDER BY [time_date_time]
|
||||
```
|
||||
The result of the above query would look something like the below
|
||||
|
||||

|
||||
|
||||
Currently, there is no support for a dynamic group by time based on time range & panel width.
|
||||
This is something we plan to add.
|
||||
|
||||
@ -138,7 +164,7 @@ You can also create nested variables. For example if you had another variable na
|
||||
the hosts variable only show hosts from the current selected region with a query like this (if `region` is a multi-value variable then use the `IN` comparison operator rather than `=` to match against multiple values):
|
||||
|
||||
```sql
|
||||
SELECT hostname FROM host WHERE region IN($region)
|
||||
SELECT hostname FROM host WHERE region IN ($region)
|
||||
```
|
||||
|
||||
### Using Variables in Queries
|
||||
@ -155,7 +181,7 @@ There are two syntaxes:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
atimestamp time_sec,
|
||||
atimestamp time,
|
||||
aint value
|
||||
FROM table
|
||||
WHERE $__timeFilter(atimestamp) and hostname in($hostname)
|
||||
@ -166,7 +192,7 @@ ORDER BY atimestamp
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
atimestamp as time_sec,
|
||||
atimestamp as time,
|
||||
aint as value
|
||||
FROM table
|
||||
WHERE $__timeFilter(atimestamp) and hostname in([[hostname]])
|
||||
@ -181,7 +207,7 @@ An example query:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time_column) ) as [time_sec],
|
||||
DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time_column) ) as [time],
|
||||
metric1 as [text],
|
||||
convert(varvhar, metric1) + ',' + convert(varchar, metric2) as [tags]
|
||||
FROM
|
||||
@ -192,7 +218,7 @@ WHERE
|
||||
|
||||
Name | Description
|
||||
------------ | -------------
|
||||
time_sec | The name of the date/time field.
|
||||
time | The name of the date/time field. could be in a native sql time datatype
|
||||
text | Event description field.
|
||||
tags | Optional field name to use for event tags as a comma separated string.
|
||||
|
||||
|
@ -64,7 +64,17 @@ func (m *MsSqlMacroEngine) evaluateMacro(name string, args []string) (string, er
|
||||
if len(args) == 0 {
|
||||
return "", fmt.Errorf("missing time column argument for macro %v", name)
|
||||
}
|
||||
return fmt.Sprintf("DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), %s) ) as time_sec", args[0]), nil
|
||||
return fmt.Sprintf("%s AS time", args[0]), nil
|
||||
case "__utcTime":
|
||||
if len(args) == 0 {
|
||||
return "", fmt.Errorf("missing time column argument for macro %v", name)
|
||||
}
|
||||
return fmt.Sprintf("DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), %s) AS time", args[0]), nil
|
||||
case "__timeEpoch":
|
||||
if len(args) == 0 {
|
||||
return "", fmt.Errorf("missing time column argument for macro %v", name)
|
||||
}
|
||||
return fmt.Sprintf("DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), %s) ) AS time", args[0]), nil
|
||||
case "__timeFilter":
|
||||
if len(args) == 0 {
|
||||
return "", fmt.Errorf("missing time column argument for macro %v", name)
|
||||
|
@ -16,14 +16,28 @@ func TestMacroEngine(t *testing.T) {
|
||||
sql, err := engine.Interpolate(nil, "select $__time(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, "select DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time_column) ) as time_sec")
|
||||
So(sql, ShouldEqual, "select time_column AS time")
|
||||
})
|
||||
|
||||
Convey("interpolate __time function wrapped in aggregation", func() {
|
||||
sql, err := engine.Interpolate(nil, "select min($__time(time_column))")
|
||||
Convey("interpolate __utcTime function", func() {
|
||||
sql, err := engine.Interpolate(nil, "select $__utcTime(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, "select min(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time_column) ) as time_sec)")
|
||||
So(sql, ShouldEqual, "select DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time_column) AS time")
|
||||
})
|
||||
|
||||
Convey("interpolate __timeEpoch function", func() {
|
||||
sql, err := engine.Interpolate(nil, "select $__timeEpoch(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, "select DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time_column) ) AS time")
|
||||
})
|
||||
|
||||
Convey("interpolate __timeEpoch function wrapped in aggregation", func() {
|
||||
sql, err := engine.Interpolate(nil, "select min($__timeEpoch(time_column))")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, "select min(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time_column) ) AS time)")
|
||||
})
|
||||
|
||||
Convey("interpolate __timeFilter function", func() {
|
||||
|
@ -5,10 +5,9 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
_ "time"
|
||||
"time"
|
||||
|
||||
_ "github.com/denisenkom/go-mssqldb"
|
||||
"github.com/go-xorm/core"
|
||||
@ -69,6 +68,10 @@ func (e MssqlQueryEndpoint) transformToTable(query *tsdb.Query, rows *core.Rows,
|
||||
return err
|
||||
}
|
||||
|
||||
rowLimit := 1000000
|
||||
rowCount := 0
|
||||
timeIndex := -1
|
||||
|
||||
table := &tsdb.Table{
|
||||
Columns: make([]tsdb.TableColumn, columnCount),
|
||||
Rows: make([]tsdb.RowValues, 0),
|
||||
@ -76,6 +79,12 @@ func (e MssqlQueryEndpoint) transformToTable(query *tsdb.Query, rows *core.Rows,
|
||||
|
||||
for i, name := range columnNames {
|
||||
table.Columns[i].Text = name
|
||||
|
||||
// check if there is a column named time
|
||||
switch name {
|
||||
case "time":
|
||||
timeIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
columnTypes, err := rows.ColumnTypes()
|
||||
@ -83,9 +92,6 @@ func (e MssqlQueryEndpoint) transformToTable(query *tsdb.Query, rows *core.Rows,
|
||||
return err
|
||||
}
|
||||
|
||||
rowLimit := 1000000
|
||||
rowCount := 0
|
||||
|
||||
for ; rows.Next(); rowCount++ {
|
||||
if rowCount > rowLimit {
|
||||
return fmt.Errorf("MsSQL query row limit exceeded, limit %d", rowLimit)
|
||||
@ -96,6 +102,15 @@ func (e MssqlQueryEndpoint) transformToTable(query *tsdb.Query, rows *core.Rows,
|
||||
return err
|
||||
}
|
||||
|
||||
// convert column named time to unix timestamp to make
|
||||
// native datetime mssql types work in annotation queries
|
||||
if timeIndex != -1 {
|
||||
switch value := values[timeIndex].(type) {
|
||||
case time.Time:
|
||||
values[timeIndex] = float64(value.Unix())
|
||||
}
|
||||
}
|
||||
|
||||
table.Rows = append(table.Rows, values)
|
||||
}
|
||||
|
||||
@ -123,42 +138,107 @@ func (e MssqlQueryEndpoint) getTypedRowData(types []*sql.ColumnType, rows *core.
|
||||
func (e MssqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core.Rows, result *tsdb.QueryResult) error {
|
||||
pointsBySeries := make(map[string]*tsdb.TimeSeries)
|
||||
seriesByQueryOrder := list.New()
|
||||
columnNames, err := rows.Columns()
|
||||
|
||||
columnNames, err := rows.Columns()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rowData := NewStringStringScan(columnNames)
|
||||
rowLimit := 1000
|
||||
columnTypes, err := rows.ColumnTypes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rowLimit := 1000000
|
||||
rowCount := 0
|
||||
timeIndex := -1
|
||||
metricIndex := -1
|
||||
|
||||
// check columns of resultset: a column named time is mandatory
|
||||
// the first text column is treated as metric name unless a column named metric is present
|
||||
for i, col := range columnNames {
|
||||
switch col {
|
||||
case "time":
|
||||
timeIndex = i
|
||||
case "metric":
|
||||
metricIndex = i
|
||||
default:
|
||||
if metricIndex == -1 {
|
||||
switch columnTypes[i].DatabaseTypeName() {
|
||||
case "VARCHAR", "CHAR", "NVARCHAR", "NCHAR":
|
||||
metricIndex = i
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if timeIndex == -1 {
|
||||
return fmt.Errorf("Found no column named time")
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var timestamp float64
|
||||
var value null.Float
|
||||
var metricColVal string
|
||||
var metric string
|
||||
|
||||
for ; rows.Next(); rowCount++ {
|
||||
if rowCount > rowLimit {
|
||||
return fmt.Errorf("MsSQL query row limit exceeded, limit %d", rowLimit)
|
||||
return fmt.Errorf("MSSQL query row limit exceeded, limit %d", rowLimit)
|
||||
}
|
||||
|
||||
err := rowData.Update(rows.Rows)
|
||||
values, err := e.getTypedRowData(columnTypes, rows)
|
||||
if err != nil {
|
||||
e.log.Error("MsSQL response parsing", "error", err)
|
||||
return fmt.Errorf("MsSQL response parsing error %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if rowData.metric == "" {
|
||||
rowData.metric = "Unknown"
|
||||
switch columnValue := values[timeIndex].(type) {
|
||||
case int64:
|
||||
timestamp = float64(columnValue * 1000)
|
||||
case float64:
|
||||
timestamp = columnValue * 1000
|
||||
case time.Time:
|
||||
timestamp = (float64(columnValue.Unix()) * 1000) + float64(columnValue.Nanosecond()/1e6) // in case someone is trying to map times beyond 2262 :D
|
||||
default:
|
||||
return fmt.Errorf("Invalid type for column time, must be of type timestamp or unix timestamp")
|
||||
}
|
||||
|
||||
if !rowData.time.Valid {
|
||||
return fmt.Errorf("Found row with no time value")
|
||||
if metricIndex >= 0 {
|
||||
if columnValue, ok := values[metricIndex].(string); ok == true {
|
||||
metricColVal = columnValue
|
||||
} else {
|
||||
return fmt.Errorf("Column metric must be of type CHAR, VARCHAR, NCHAR or NVARCHAR. metric column name: %s type: %s but datatype is %T", columnNames[metricIndex], columnTypes[metricIndex].DatabaseTypeName(), values[metricIndex])
|
||||
}
|
||||
}
|
||||
|
||||
if series, exist := pointsBySeries[rowData.metric]; exist {
|
||||
series.Points = append(series.Points, tsdb.TimePoint{rowData.value, rowData.time})
|
||||
} else {
|
||||
series := &tsdb.TimeSeries{Name: rowData.metric}
|
||||
series.Points = append(series.Points, tsdb.TimePoint{rowData.value, rowData.time})
|
||||
pointsBySeries[rowData.metric] = series
|
||||
seriesByQueryOrder.PushBack(rowData.metric)
|
||||
for i, col := range columnNames {
|
||||
if i == timeIndex || i == metricIndex {
|
||||
continue
|
||||
}
|
||||
|
||||
switch columnValue := values[i].(type) {
|
||||
case int64:
|
||||
value = null.FloatFrom(float64(columnValue))
|
||||
case float64:
|
||||
value = null.FloatFrom(columnValue)
|
||||
case nil:
|
||||
value.Valid = false
|
||||
default:
|
||||
return fmt.Errorf("Value column must have numeric datatype, column: %s type: %T value: %v", col, columnValue, columnValue)
|
||||
}
|
||||
|
||||
// construct the metric name
|
||||
// if there is more than 3 columns (more than one value) and there is
|
||||
// a metric column, join them to make the metric name
|
||||
if metricIndex == -1 {
|
||||
metric = col
|
||||
} else if len(columnNames) > 3 {
|
||||
metric = metricColVal + " - " + col
|
||||
} else {
|
||||
metric = metricColVal
|
||||
}
|
||||
|
||||
e.appendTimePoint(pointsBySeries, seriesByQueryOrder, metric, timestamp, value)
|
||||
rowCount++
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,61 +251,14 @@ func (e MssqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core.
|
||||
return nil
|
||||
}
|
||||
|
||||
type stringStringScan struct {
|
||||
rowPtrs []interface{}
|
||||
rowValues []string
|
||||
columnNames []string
|
||||
columnCount int
|
||||
|
||||
time null.Float
|
||||
value null.Float
|
||||
metric string
|
||||
}
|
||||
|
||||
func NewStringStringScan(columnNames []string) *stringStringScan {
|
||||
s := &stringStringScan{
|
||||
columnCount: len(columnNames),
|
||||
columnNames: columnNames,
|
||||
rowPtrs: make([]interface{}, len(columnNames)),
|
||||
rowValues: make([]string, len(columnNames)),
|
||||
}
|
||||
|
||||
for i := 0; i < s.columnCount; i++ {
|
||||
s.rowPtrs[i] = new(sql.RawBytes)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *stringStringScan) Update(rows *sql.Rows) error {
|
||||
if err := rows.Scan(s.rowPtrs...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.time = null.FloatFromPtr(nil)
|
||||
s.value = null.FloatFromPtr(nil)
|
||||
|
||||
for i := 0; i < s.columnCount; i++ {
|
||||
if rb, ok := s.rowPtrs[i].(*sql.RawBytes); ok {
|
||||
s.rowValues[i] = string(*rb)
|
||||
|
||||
switch s.columnNames[i] {
|
||||
case "time_sec":
|
||||
if sec, err := strconv.ParseInt(s.rowValues[i], 10, 64); err == nil {
|
||||
s.time = null.FloatFrom(float64(sec * 1000))
|
||||
}
|
||||
case "value":
|
||||
if value, err := strconv.ParseFloat(s.rowValues[i], 64); err == nil {
|
||||
s.value = null.FloatFrom(value)
|
||||
}
|
||||
case "metric":
|
||||
s.metric = s.rowValues[i]
|
||||
}
|
||||
|
||||
*rb = nil // reset pointer to discard current value to avoid a bug
|
||||
} else {
|
||||
return fmt.Errorf("Cannot convert index %d column %s to type *sql.RawBytes", i, s.columnNames[i])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
func (e MssqlQueryEndpoint) appendTimePoint(pointsBySeries map[string]*tsdb.TimeSeries, seriesByQueryOrder *list.List, metric string, timestamp float64, value null.Float) {
|
||||
if series, exist := pointsBySeries[metric]; exist {
|
||||
series.Points = append(series.Points, tsdb.TimePoint{value, null.FloatFrom(timestamp)})
|
||||
} else {
|
||||
series := &tsdb.TimeSeries{Name: metric}
|
||||
series.Points = append(series.Points, tsdb.TimePoint{value, null.FloatFrom(timestamp)})
|
||||
pointsBySeries[metric] = series
|
||||
seriesByQueryOrder.PushBack(metric)
|
||||
}
|
||||
e.log.Debug("Rows", "metric", metric, "time", timestamp, "value", value)
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import (
|
||||
var serverIP string = "10.20.30.40"
|
||||
|
||||
func TestMSSQL(t *testing.T) {
|
||||
//SkipConvey("MSSQL", t, func() {
|
||||
SkipConvey("MSSQL", t, func() {
|
||||
x := InitMSSQLTestDB(t)
|
||||
|
||||
@ -52,7 +51,8 @@ func TestMSSQL(t *testing.T) {
|
||||
sql += "afloat float, "
|
||||
sql += "adatetime datetime, "
|
||||
sql += "adate date, "
|
||||
sql += "atime time) "
|
||||
sql += "atime time, "
|
||||
sql += "adatetimeoffset datetimeoffset) "
|
||||
_, err := sess.Exec(sql)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -60,11 +60,11 @@ func TestMSSQL(t *testing.T) {
|
||||
sql += "(abit, atinyint, asmallint, aint, abigint, "
|
||||
sql += "avarchar, achar, anewvarchar, anewchar, "
|
||||
sql += "areal, anewdecimal, afloat, "
|
||||
sql += "adatetime, adate, atime ) "
|
||||
sql += "adatetime, adate, atime, adatetimeoffset ) "
|
||||
sql += "VALUES(1, 5, 20020, 980300, 1420070400, "
|
||||
sql += "'abc', 'def', 'hi varchar', 'I am only char', "
|
||||
sql += "1.11, 2.22, 3.33, "
|
||||
sql += "GETUTCDATE(), CAST(GETUTCDATE() AS DATE), CAST(GETUTCDATE() AS TIME) );"
|
||||
sql += "GETUTCDATE(), CAST(GETUTCDATE() AS DATE), CAST(GETUTCDATE() AS TIME), SWITCHOFFSET(SYSDATETIMEOFFSET(), '-07:00') );"
|
||||
_, err = sess.Exec(sql)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -98,12 +98,13 @@ func TestMSSQL(t *testing.T) {
|
||||
So(column[8].(string), ShouldEqual, "I am only char")
|
||||
|
||||
So(column[9].(float64), ShouldEqual, 1.1100000143051147) // MSSQL dose not have precision for "real" datatype
|
||||
// fiix me: MSSQL driver puts the decimal inside an array of chars. and the test fails despite the values are correct.
|
||||
// fix me: MSSQL driver puts the decimal inside an array of chars. and the test fails despite the values are correct.
|
||||
//So(column[10].([]uint8), ShouldEqual, []uint8{'2', '.', '2', '2'})
|
||||
So(column[11].(float64), ShouldEqual, 3.33)
|
||||
So(column[12].(time.Time), ShouldHappenWithin, time.Duration(15*time.Second), time.Now().UTC())
|
||||
So(column[13].(time.Time), ShouldHappenWithin, time.Duration(15*time.Second), time.Now().UTC().Truncate(24*time.Hour))
|
||||
So(column[14].(time.Time), ShouldHappenWithin, time.Duration(15*time.Second), time.Date(1, time.January, 1, time.Now().UTC().Hour(), time.Now().UTC().Minute(), time.Now().UTC().Second(), 0, time.UTC))
|
||||
So(column[12].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now().UTC())
|
||||
So(column[13].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now().UTC().Truncate(24*time.Hour)) // ShouldEqual dose not work here !!?
|
||||
So(column[14].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Date(1, time.January, 1, time.Now().UTC().Hour(), time.Now().UTC().Minute(), time.Now().UTC().Second(), 0, time.UTC))
|
||||
So(column[15].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now().UTC())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ class MssqlConfigCtrl {
|
||||
}
|
||||
|
||||
const defaultQuery = `SELECT TOP 100
|
||||
DATEDIFF(second, {d '1970-01-01'}, DATEADD(second,DATEDIFF(second,GETDATE(),GETUTCDATE()),<time_column>) ) as time_sec,
|
||||
$__utcTime(<time_column>),
|
||||
<text_column> as text,
|
||||
<tags_column> as tags
|
||||
FROM <table name>
|
||||
|
@ -18,16 +18,17 @@
|
||||
|
||||
<div class="gf-form" ng-show="ctrl.showHelp">
|
||||
<pre class="gf-form-pre alert alert-info"><h6>Annotation Query Format</h6>
|
||||
An annotation is an event that is overlayed on top of graphs. The query can have up to four columns per row, the time_sec column is mandatory. Annotation rendering is expensive so it is important to limit the number of rows returned.
|
||||
An annotation is an event that is overlayed on top of graphs. The query can have up to four columns per row, the <b>time</b> column is mandatory. Annotation rendering is expensive so it is important to limit the number of rows returned.
|
||||
|
||||
- column with alias: <b>time_sec</b> for the annotation event. Format is UTC in seconds, use the below to convert a datetime column to UTC unix time stamp:
|
||||
DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), column_name) )
|
||||
- column with alias: <b>time</b> for the annotation event time (in UTC), as a unix time stamp or any sql native date datatype.
|
||||
- column with alias: <b>text</b> for the annotation text
|
||||
- column with alias: <b>tags</b> for annotation tags. This is a comma separated string of tags e.g. 'tag1,tag2'
|
||||
|
||||
|
||||
Macros:
|
||||
- $__time(column) -> DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), column) ) as time_sec
|
||||
- $__time(column) -> column AS time
|
||||
- $__utcTime(column) -> DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), column) AS time
|
||||
- $__timeEpoch(column) -> DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), column) ) AS time
|
||||
- $__timeFilter(column) -> column > DATEADD(s, 1492750877+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01') AND column < DATEADD(s, 1492750877+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')
|
||||
- $__unixEpochFilter(column) -> column > 1492750877 AND column < 1492750877
|
||||
|
||||
|
@ -38,16 +38,18 @@
|
||||
|
||||
<div class="gf-form" ng-show="ctrl.showHelp">
|
||||
<pre class="gf-form-pre alert alert-info">Time series:
|
||||
- return column named time_sec (UTC in seconds), use the below to convert a datetime column to UTC unix time stamp:
|
||||
DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), column_name) )
|
||||
- return column named value for the time point value
|
||||
- return column named metric to represent the series name
|
||||
- return column named time (in UTC), as a unix time stamp or any sql native date datatype. you can use the macros below.
|
||||
- optional: return column named metric to represent the series names.
|
||||
- any other columns returned will be the time point values.
|
||||
- if multiple value columns are present and a metric column is provided. the series name will be the combination of "MetricName - ValueColumnName".
|
||||
|
||||
Table:
|
||||
- return any set of columns
|
||||
|
||||
Macros:
|
||||
- $__time(column) -> DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), column) ) as time_sec
|
||||
- $__time(column) -> column AS time
|
||||
- $__utcTime(column) -> DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), column) AS time
|
||||
- $__timeEpoch(column) -> DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), column) ) AS time
|
||||
- $__timeFilter(column) -> column > DATEADD(s, 1492750877+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01') AND column < DATEADD(s, 1492750877+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')
|
||||
- $__unixEpochFilter(column) -> column > 1492750877 AND column < 1492750877
|
||||
|
||||
|
@ -16,7 +16,7 @@ export interface QueryMeta {
|
||||
|
||||
|
||||
const defaultQuery = `SELECT
|
||||
DATEDIFF(second, {d '1970-01-01'}, DATEADD(second,DATEDIFF(second,GETDATE(),GETUTCDATE()),<time_column>)) as time_sec,
|
||||
$__utcTime(<time_column>),
|
||||
<value column> as value,
|
||||
<series name column> as metric
|
||||
FROM <table name>
|
||||
|
@ -110,7 +110,7 @@ export default class ResponseParser {
|
||||
let tagsColumnIndex = -1;
|
||||
|
||||
for (let i = 0; i < table.columns.length; i++) {
|
||||
if (table.columns[i].text === 'time_sec') {
|
||||
if (table.columns[i].text === 'time') {
|
||||
timeColumnIndex = i;
|
||||
} else if (table.columns[i].text === 'title') {
|
||||
return this.$q.reject({message: 'The title column for annotations is deprecated, now only a column named text is returned'});
|
||||
@ -122,7 +122,7 @@ export default class ResponseParser {
|
||||
}
|
||||
|
||||
if (timeColumnIndex === -1) {
|
||||
return this.$q.reject({message: 'Missing mandatory time column (with time_sec column alias) in annotation query.'});
|
||||
return this.$q.reject({message: 'Missing mandatory time column (with time column alias) in annotation query.'});
|
||||
}
|
||||
|
||||
const list = [];
|
||||
|
@ -28,7 +28,7 @@ describe('MSSQLDatasource', function() {
|
||||
const options = {
|
||||
annotation: {
|
||||
name: annotationName,
|
||||
rawQuery: 'select time_sec, text, tags from table;'
|
||||
rawQuery: 'select time, text, tags from table;'
|
||||
},
|
||||
range: {
|
||||
from: moment(1432288354),
|
||||
@ -42,7 +42,7 @@ describe('MSSQLDatasource', function() {
|
||||
refId: annotationName,
|
||||
tables: [
|
||||
{
|
||||
columns: [{text: 'time_sec'}, {text: 'text'}, {text: 'tags'}],
|
||||
columns: [{text: 'time'}, {text: 'text'}, {text: 'tags'}],
|
||||
rows: [
|
||||
[1432288355, 'some text', 'TagA,TagB'],
|
||||
[1432288390, 'some text2', ' TagB , TagC'],
|
||||
|
Loading…
Reference in New Issue
Block a user