diff --git a/pkg/tsdb/mssql/mssql_test.go b/pkg/tsdb/mssql/mssql_test.go index b13ff27f4ea..ce5866847ce 100644 --- a/pkg/tsdb/mssql/mssql_test.go +++ b/pkg/tsdb/mssql/mssql_test.go @@ -151,7 +151,7 @@ func TestMSSQL(t *testing.T) { require.Equal(t, 1, len(frames)) require.Equal(t, 24, len(frames[0].Fields)) - require.Equal(t, true, frames[0].Fields[0].At(0).(bool)) + require.Equal(t, true, *frames[0].Fields[0].At(0).(*bool)) require.Equal(t, int64(5), *frames[0].Fields[1].At(0).(*int64)) require.Equal(t, int64(20020), *frames[0].Fields[2].At(0).(*int64)) require.Equal(t, int64(980300), *frames[0].Fields[3].At(0).(*int64)) @@ -654,8 +654,8 @@ func TestMSSQL(t *testing.T) { require.Equal(t, 1, len(frames)) require.Equal(t, 3, len(frames[0].Fields)) - require.Equal(t, data.Labels{"metric": "Metric A - value one"}, frames[0].Fields[1].Labels) - require.Equal(t, data.Labels{"metric": "Metric B - value one"}, frames[0].Fields[2].Labels) + require.Equal(t, "Metric A - value one", frames[0].Fields[1].Name) + require.Equal(t, "Metric B - value one", frames[0].Fields[2].Name) }) t.Run("When doing a metric query grouping by time should return correct series", func(t *testing.T) { @@ -1203,6 +1203,32 @@ func TestMSSQL(t *testing.T) { // Should be in time.Time require.Nil(t, frames[0].Fields[0].At(0)) }) + + t.Run("When doing an annotation query with a time and timeend column should return two fields of type time", func(t *testing.T) { + query := plugins.DataQuery{ + Queries: []plugins.DataSubQuery{ + { + Model: simplejson.NewFromAny(map[string]interface{}{ + "rawSql": "SELECT 1631053772276 as time, 1631054012276 as timeend, '' as text, '' as tags", + "format": "table", + }), + RefID: "A", + }, + }, + } + + resp, err := endpoint.DataQuery(context.Background(), nil, query) + require.NoError(t, err) + queryResult := resp.Results["A"] + require.NoError(t, queryResult.Error) + + frames, _ := queryResult.Dataframes.Decoded() + require.Equal(t, 1, len(frames)) + require.Equal(t, 4, len(frames[0].Fields)) + + require.Equal(t, data.FieldTypeNullableTime, frames[0].Fields[0].Type()) + require.Equal(t, data.FieldTypeNullableTime, frames[0].Fields[1].Type()) + }) }) } diff --git a/pkg/tsdb/mysql/mysql_test.go b/pkg/tsdb/mysql/mysql_test.go index 2ffccddfece..2cc2815d152 100644 --- a/pkg/tsdb/mysql/mysql_test.go +++ b/pkg/tsdb/mysql/mysql_test.go @@ -1147,6 +1147,32 @@ func TestMySQL(t *testing.T) { //Should be in time.Time require.Nil(t, frames[0].Fields[0].At(0)) }) + + t.Run("When doing an annotation query with a time and timeend column should return two fields of type time", func(t *testing.T) { + query := plugins.DataQuery{ + Queries: []plugins.DataSubQuery{ + { + Model: simplejson.NewFromAny(map[string]interface{}{ + "rawSql": "SELECT 1631053772276 as time, 1631054012276 as timeend, '' as text, '' as tags", + "format": "table", + }), + RefID: "A", + }, + }, + } + + resp, err := exe.DataQuery(context.Background(), nil, query) + require.NoError(t, err) + queryResult := resp.Results["A"] + require.NoError(t, queryResult.Error) + + frames, _ := queryResult.Dataframes.Decoded() + require.Equal(t, 1, len(frames)) + require.Equal(t, 4, len(frames[0].Fields)) + + require.Equal(t, data.FieldTypeNullableTime, frames[0].Fields[0].Type()) + require.Equal(t, data.FieldTypeNullableTime, frames[0].Fields[1].Type()) + }) }) } diff --git a/pkg/tsdb/postgres/postgres_test.go b/pkg/tsdb/postgres/postgres_test.go index 79425cafdc5..5d68de3b36d 100644 --- a/pkg/tsdb/postgres/postgres_test.go +++ b/pkg/tsdb/postgres/postgres_test.go @@ -1217,6 +1217,32 @@ func TestPostgres(t *testing.T) { // Should be in time.Time assert.Nil(t, frames[0].Fields[0].At(0)) }) + + t.Run("When doing an annotation query with a time and timeend column should return two fields of type time", func(t *testing.T) { + query := plugins.DataQuery{ + Queries: []plugins.DataSubQuery{ + { + Model: simplejson.NewFromAny(map[string]interface{}{ + "rawSql": "SELECT 1631053772276 as time, 1631054012276 as timeend, '' as text, '' as tags", + "format": "table", + }), + RefID: "A", + }, + }, + } + + resp, err := exe.DataQuery(context.Background(), nil, query) + require.NoError(t, err) + queryResult := resp.Results["A"] + require.NoError(t, queryResult.Error) + + frames, _ := queryResult.Dataframes.Decoded() + require.Equal(t, 1, len(frames)) + require.Equal(t, 4, len(frames[0].Fields)) + + require.Equal(t, data.FieldTypeNullableTime, frames[0].Fields[0].Type()) + require.Equal(t, data.FieldTypeNullableTime, frames[0].Fields[1].Type()) + }) }) } diff --git a/pkg/tsdb/sqleng/sql_engine.go b/pkg/tsdb/sqleng/sql_engine.go index fed7612867e..94a098c0aaa 100644 --- a/pkg/tsdb/sqleng/sql_engine.go +++ b/pkg/tsdb/sqleng/sql_engine.go @@ -20,6 +20,7 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/tsdb/interval" + "github.com/grafana/grafana/pkg/util/errutil" "xorm.io/core" "xorm.io/xorm" ) @@ -270,11 +271,9 @@ func (e *dataPlugin) executeQuery(query plugins.DataSubQuery, wg *sync.WaitGroup return } - if qm.timeIndex != -1 { - if err := convertSQLTimeColumnToEpochMS(frame, qm.timeIndex); err != nil { - errAppendDebug("db convert time column failed", err, interpolatedQuery) - return - } + if err := convertSQLTimeColumnsToEpochMS(frame, qm); err != nil { + errAppendDebug("converting time columns failed", err, interpolatedQuery) + return } if qm.Format == dataQueryFormatSeries { @@ -375,6 +374,7 @@ func (e *dataPlugin) newProcessCfg(query plugins.DataSubQuery, queryContext plug columnNames: columnNames, rows: rows, timeIndex: -1, + timeEndIndex: -1, metricIndex: -1, metricPrefix: false, queryContext: queryContext, @@ -418,6 +418,12 @@ func (e *dataPlugin) newProcessCfg(query plugins.DataSubQuery, queryContext plug break } } + + if qm.Format == dataQueryFormatTable && col == "timeend" { + qm.timeEndIndex = i + continue + } + switch col { case "metric": qm.metricIndex = i @@ -456,6 +462,7 @@ type dataQueryModel struct { columnNames []string columnTypes []*sql.ColumnType timeIndex int + timeEndIndex int metricIndex int rows *core.Rows metricPrefix bool @@ -785,6 +792,22 @@ func convertNullableFloat32ToEpochMS(origin *data.Field, newField *data.Field) { } } +func convertSQLTimeColumnsToEpochMS(frame *data.Frame, qm *dataQueryModel) error { + if qm.timeIndex != -1 { + if err := convertSQLTimeColumnToEpochMS(frame, qm.timeIndex); err != nil { + return errutil.Wrap("failed to convert time column", err) + } + } + + if qm.timeEndIndex != -1 { + if err := convertSQLTimeColumnToEpochMS(frame, qm.timeEndIndex); err != nil { + return errutil.Wrap("failed to convert timeend column", err) + } + } + + return nil +} + // convertSQLTimeColumnToEpochMS converts column named time to unix timestamp in milliseconds // to make native datetime types and epoch dates work in annotation and table queries. func convertSQLTimeColumnToEpochMS(frame *data.Frame, timeIndex int) error {