diff --git a/pkg/tsdb/mssql/mssql_test.go b/pkg/tsdb/mssql/mssql_test.go index a6752a0a87f..f837245daa3 100644 --- a/pkg/tsdb/mssql/mssql_test.go +++ b/pkg/tsdb/mssql/mssql_test.go @@ -2,6 +2,7 @@ package mssql import ( "fmt" + "math/rand" "strings" "testing" "time" @@ -15,8 +16,10 @@ import ( ) // To run this test, remove the Skip from SkipConvey -// and set up a MSSQL db named grafana_tests and a user/password grafana/Password! -// and set the variable below to the IP address of the database +// and set up a MSSQL db named grafanatest and a user/password grafana/Password! +// Use the docker/blocks/mssql_tests/docker-compose.yaml to spin up a +// preconfigured MSSQL server suitable for running these tests. +// If needed, change the variable below to the IP address of the database. var serverIP string = "localhost" func TestMSSQL(t *testing.T) { @@ -90,7 +93,7 @@ func TestMSSQL(t *testing.T) { 1.11, 2.22, 3.33, 'char10', 'varchar10', 'text', N'☺nchar12☺', N'☺nvarchar12☺', N'☺text☺', - CAST('%s' AS DATETIME), CAST('%s' AS DATETIME2), CAST('%s' AS SMALLDATETIME), CAST('%s' AS DATE), CAST('%s' AS TIME), SWITCHOFFSET(CAST('%s' AS DATETIMEOFFSET), '-07:00') + CAST('%s' AS DATETIME), CAST('%s' AS DATETIME2), CAST('%s' AS SMALLDATETIME), CAST('%s' AS DATE), CAST('%s' AS TIME), SWITCHOFFSET(CAST('%s' AS DATETIMEOFFSET), '-07:00') `, d, d2, d, d, d, d2) _, err = sess.Exec(sql) @@ -146,14 +149,13 @@ func TestMSSQL(t *testing.T) { }) }) - Convey("Given a table with metrics", func() { + Convey("Given a table with metrics that lacks data for some series ", func() { sql := ` IF OBJECT_ID('dbo.[metric]', 'U') IS NOT NULL DROP TABLE dbo.[metric] CREATE TABLE [metric] ( time datetime, - measurement nvarchar(100), value int ) ` @@ -162,39 +164,34 @@ func TestMSSQL(t *testing.T) { So(err, ShouldBeNil) type metric struct { - Time time.Time - Measurement string - Value int64 + Time time.Time + Value int64 } series := []*metric{} - - fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC) firstRange := genTimeRangeByInterval(fromStart, 10*time.Minute, 10*time.Second) secondRange := genTimeRangeByInterval(fromStart.Add(20*time.Minute), 10*time.Minute, 10*time.Second) for _, t := range firstRange { series = append(series, &metric{ - Time: t, - Measurement: "test", - Value: 15, + Time: t, + Value: 15, }) } for _, t := range secondRange { series = append(series, &metric{ - Time: t, - Measurement: "test", - Value: 20, + Time: t, + Value: 20, }) } dtFormat := "2006-01-02 15:04:05.999999999" for _, s := range series { sql = fmt.Sprintf(` - INSERT INTO metric (time, measurement, value) - VALUES(CAST('%s' AS DATETIME), '%s', %d) - `, s.Time.Format(dtFormat), s.Measurement, s.Value) + INSERT INTO metric (time, value) + VALUES(CAST('%s' AS DATETIME), %d) + `, s.Time.Format(dtFormat), s.Value) _, err = sess.Exec(sql) So(err, ShouldBeNil) @@ -205,7 +202,7 @@ func TestMSSQL(t *testing.T) { Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": "SELECT $__timeGroup(time, '5m') AS time, measurement as metric, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m'), measurement ORDER BY 1", + "rawSql": "SELECT $__timeGroup(time, '5m') AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1", "format": "time_series", }), RefId: "A", @@ -237,7 +234,7 @@ func TestMSSQL(t *testing.T) { Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": "SELECT $__timeGroup(time, '5m', NULL) AS time, measurement as metric, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m'), measurement ORDER BY 1", + "rawSql": "SELECT $__timeGroup(time, '5m', NULL) AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1", "format": "time_series", }), RefId: "A", @@ -284,7 +281,7 @@ func TestMSSQL(t *testing.T) { Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": "SELECT $__timeGroup(time, '5m', 1.5) AS time, measurement as metric, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m'), measurement ORDER BY 1", + "rawSql": "SELECT $__timeGroup(time, '5m', 1.5) AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1", "format": "time_series", }), RefId: "A", @@ -306,6 +303,202 @@ func TestMSSQL(t *testing.T) { So(points[6][0].Float64, ShouldEqual, 1.5) }) }) + + Convey("Given a table with metrics having multiple values and measurements", func() { + sql := ` + IF OBJECT_ID('dbo.[metric_values]', 'U') IS NOT NULL + DROP TABLE dbo.[metric_values] + + CREATE TABLE [metric_values] ( + time datetime, + measurement nvarchar(100), + valueOne int, + valueTwo int, + ) + ` + + _, err := sess.Exec(sql) + So(err, ShouldBeNil) + + type metricValues struct { + Time time.Time + Measurement string + ValueOne int64 + ValueTwo int64 + } + + rand.Seed(time.Now().Unix()) + rnd := func(min, max int64) int64 { + return rand.Int63n(max-min) + min + } + + series := []*metricValues{} + for _, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) { + series = append(series, &metricValues{ + Time: t, + Measurement: "Metric A", + ValueOne: rnd(0, 100), + ValueTwo: rnd(0, 100), + }) + series = append(series, &metricValues{ + Time: t, + Measurement: "Metric B", + ValueOne: rnd(0, 100), + ValueTwo: rnd(0, 100), + }) + } + + dtFormat := "2006-01-02 15:04:05" + for _, s := range series { + sql = fmt.Sprintf(` + INSERT metric_values (time, measurement, valueOne, valueTwo) + VALUES(CAST('%s' AS DATETIME), '%s', %d, %d) + `, s.Time.Format(dtFormat), s.Measurement, s.ValueOne, s.ValueTwo) + + _, err = sess.Exec(sql) + So(err, ShouldBeNil) + } + + Convey("When doing a metric query grouping by time and select metric column should return correct series", func() { + query := &tsdb.TsdbQuery{ + Queries: []*tsdb.Query{ + { + Model: simplejson.NewFromAny(map[string]interface{}{ + "rawSql": "SELECT $__timeEpoch(time), measurement as metric, valueOne, valueTwo FROM metric_values ORDER BY 1", + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + queryResult := resp.Results["A"] + So(err, ShouldBeNil) + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 4) + So(queryResult.Series[0].Name, ShouldEqual, "Metric A - valueOne") + So(queryResult.Series[1].Name, ShouldEqual, "Metric A - valueTwo") + So(queryResult.Series[2].Name, ShouldEqual, "Metric B - valueOne") + So(queryResult.Series[3].Name, ShouldEqual, "Metric B - valueTwo") + }) + + Convey("When doing a metric query grouping by time should return correct series", func() { + query := &tsdb.TsdbQuery{ + Queries: []*tsdb.Query{ + { + Model: simplejson.NewFromAny(map[string]interface{}{ + "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values ORDER BY 1", + "format": "time_series", + }), + RefId: "A", + }, + }, + } + + resp, err := endpoint.Query(nil, nil, query) + queryResult := resp.Results["A"] + So(err, ShouldBeNil) + So(queryResult.Error, ShouldBeNil) + + So(len(queryResult.Series), ShouldEqual, 2) + So(queryResult.Series[0].Name, ShouldEqual, "valueOne") + So(queryResult.Series[1].Name, ShouldEqual, "valueTwo") + }) + }) + + Convey("Given a table with event data", func() { + sql := ` + IF OBJECT_ID('dbo.[event]', 'U') IS NOT NULL + DROP TABLE dbo.[event] + + CREATE TABLE [event] ( + time_sec bigint, + description nvarchar(100), + tags nvarchar(100), + ) + ` + + _, err := sess.Exec(sql) + So(err, ShouldBeNil) + + type event struct { + TimeSec int64 + Description string + Tags string + } + + events := []*event{} + for _, t := range genTimeRangeByInterval(fromStart.Add(-20*time.Minute), 60*time.Minute, 25*time.Minute) { + events = append(events, &event{ + TimeSec: t.Unix(), + Description: "Someone deployed something", + Tags: "deploy", + }) + events = append(events, &event{ + TimeSec: t.Add(5 * time.Minute).Unix(), + Description: "New support ticket registered", + Tags: "ticket", + }) + } + + for _, e := range events { + sql = fmt.Sprintf(` + INSERT [event] (time_sec, description, tags) + VALUES(%d, '%s', '%s') + `, e.TimeSec, e.Description, e.Tags) + + _, err = sess.Exec(sql) + So(err, ShouldBeNil) + } + + Convey("When doing an annotation query of deploy events should return expected result", func() { + query := &tsdb.TsdbQuery{ + Queries: []*tsdb.Query{ + { + Model: simplejson.NewFromAny(map[string]interface{}{ + "rawSql": "SELECT time_sec as time, description as [text], tags FROM [event] WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC", + "format": "table", + }), + RefId: "Deploys", + }, + }, + TimeRange: &tsdb.TimeRange{ + From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000), + To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000), + }, + } + + resp, err := endpoint.Query(nil, nil, query) + queryResult := resp.Results["Deploys"] + So(err, ShouldBeNil) + So(len(queryResult.Tables[0].Rows), ShouldEqual, 3) + }) + + Convey("When doing an annotation query of ticket events should return expected result", func() { + query := &tsdb.TsdbQuery{ + Queries: []*tsdb.Query{ + { + Model: simplejson.NewFromAny(map[string]interface{}{ + "rawSql": "SELECT time_sec as time, description as [text], tags FROM [event] WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC", + "format": "table", + }), + RefId: "Tickets", + }, + }, + TimeRange: &tsdb.TimeRange{ + From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000), + To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000), + }, + } + + resp, err := endpoint.Query(nil, nil, query) + queryResult := resp.Results["Tickets"] + So(err, ShouldBeNil) + So(len(queryResult.Tables[0].Rows), ShouldEqual, 3) + }) + }) }) }