mssql_test.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876
  1. package mssql
  2. import (
  3. "fmt"
  4. "math/rand"
  5. "strings"
  6. "testing"
  7. "time"
  8. "github.com/go-xorm/xorm"
  9. "github.com/grafana/grafana/pkg/components/simplejson"
  10. "github.com/grafana/grafana/pkg/log"
  11. "github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
  12. "github.com/grafana/grafana/pkg/tsdb"
  13. . "github.com/smartystreets/goconvey/convey"
  14. )
  15. // To run this test, remove the Skip from SkipConvey
  16. // and set up a MSSQL db named grafanatest and a user/password grafana/Password!
  17. // Use the docker/blocks/mssql_tests/docker-compose.yaml to spin up a
  18. // preconfigured MSSQL server suitable for running these tests.
  19. // Thers's also a dashboard.json in same directory that you can import to Grafana
  20. // once you've created a datasource for the test server/database.
  21. // If needed, change the variable below to the IP address of the database.
  22. var serverIP string = "localhost"
  23. func TestMSSQL(t *testing.T) {
  24. SkipConvey("MSSQL", t, func() {
  25. x := InitMSSQLTestDB(t)
  26. endpoint := &MssqlQueryEndpoint{
  27. sqlEngine: &tsdb.DefaultSqlEngine{
  28. MacroEngine: NewMssqlMacroEngine(),
  29. XormEngine: x,
  30. },
  31. log: log.New("tsdb.mssql"),
  32. }
  33. sess := x.NewSession()
  34. defer sess.Close()
  35. fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local)
  36. Convey("Given a table with different native data types", func() {
  37. sql := `
  38. IF OBJECT_ID('dbo.[mssql_types]', 'U') IS NOT NULL
  39. DROP TABLE dbo.[mssql_types]
  40. CREATE TABLE [mssql_types] (
  41. c_bit bit,
  42. c_tinyint tinyint,
  43. c_smallint smallint,
  44. c_int int,
  45. c_bigint bigint,
  46. c_money money,
  47. c_smallmoney smallmoney,
  48. c_numeric numeric(10,5),
  49. c_real real,
  50. c_decimal decimal(10,2),
  51. c_float float,
  52. c_char char(10),
  53. c_varchar varchar(10),
  54. c_text text,
  55. c_nchar nchar(12),
  56. c_nvarchar nvarchar(12),
  57. c_ntext ntext,
  58. c_datetime datetime,
  59. c_datetime2 datetime2,
  60. c_smalldatetime smalldatetime,
  61. c_date date,
  62. c_time time,
  63. c_datetimeoffset datetimeoffset
  64. )
  65. `
  66. _, err := sess.Exec(sql)
  67. So(err, ShouldBeNil)
  68. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  69. dtFormat := "2006-01-02 15:04:05.999999999"
  70. d := dt.Format(dtFormat)
  71. dt2 := time.Date(2018, 3, 14, 21, 20, 6, 8896406e2, time.UTC)
  72. dt2Format := "2006-01-02 15:04:05.999999999 -07:00"
  73. d2 := dt2.Format(dt2Format)
  74. sql = fmt.Sprintf(`
  75. INSERT INTO [mssql_types]
  76. SELECT
  77. 1, 5, 20020, 980300, 1420070400, '$20000.15', '£2.15', 12345.12,
  78. 1.11, 2.22, 3.33,
  79. 'char10', 'varchar10', 'text',
  80. N'☺nchar12☺', N'☺nvarchar12☺', N'☺text☺',
  81. 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')
  82. `, d, d2, d, d, d, d2)
  83. _, err = sess.Exec(sql)
  84. So(err, ShouldBeNil)
  85. Convey("When doing a table query should map MSSQL column types to Go types", func() {
  86. query := &tsdb.TsdbQuery{
  87. Queries: []*tsdb.Query{
  88. {
  89. Model: simplejson.NewFromAny(map[string]interface{}{
  90. "rawSql": "SELECT * FROM mssql_types",
  91. "format": "table",
  92. }),
  93. RefId: "A",
  94. },
  95. },
  96. }
  97. resp, err := endpoint.Query(nil, nil, query)
  98. queryResult := resp.Results["A"]
  99. So(err, ShouldBeNil)
  100. column := queryResult.Tables[0].Rows[0]
  101. So(column[0].(bool), ShouldEqual, true)
  102. So(column[1].(int64), ShouldEqual, 5)
  103. So(column[2].(int64), ShouldEqual, 20020)
  104. So(column[3].(int64), ShouldEqual, 980300)
  105. So(column[4].(int64), ShouldEqual, 1420070400)
  106. So(column[5].(float64), ShouldEqual, 20000.15)
  107. So(column[6].(float64), ShouldEqual, 2.15)
  108. So(column[7].(float64), ShouldEqual, 12345.12)
  109. So(column[8].(float64), ShouldEqual, 1.1100000143051147)
  110. So(column[9].(float64), ShouldEqual, 2.22)
  111. So(column[10].(float64), ShouldEqual, 3.33)
  112. So(column[11].(string), ShouldEqual, "char10 ")
  113. So(column[12].(string), ShouldEqual, "varchar10")
  114. So(column[13].(string), ShouldEqual, "text")
  115. So(column[14].(string), ShouldEqual, "☺nchar12☺ ")
  116. So(column[15].(string), ShouldEqual, "☺nvarchar12☺")
  117. So(column[16].(string), ShouldEqual, "☺text☺")
  118. So(column[17].(time.Time), ShouldEqual, dt)
  119. So(column[18].(time.Time), ShouldEqual, dt2)
  120. So(column[19].(time.Time), ShouldEqual, dt.Truncate(time.Minute))
  121. So(column[20].(time.Time), ShouldEqual, dt.Truncate(24*time.Hour))
  122. So(column[21].(time.Time), ShouldEqual, time.Date(1, 1, 1, dt.Hour(), dt.Minute(), dt.Second(), dt.Nanosecond(), time.UTC))
  123. So(column[22].(time.Time), ShouldEqual, dt2.In(time.FixedZone("UTC", int(-7*time.Hour))))
  124. })
  125. })
  126. Convey("Given a table with metrics that lacks data for some series ", func() {
  127. sql := `
  128. IF OBJECT_ID('dbo.[metric]', 'U') IS NOT NULL
  129. DROP TABLE dbo.[metric]
  130. CREATE TABLE [metric] (
  131. time datetime,
  132. value int
  133. )
  134. `
  135. _, err := sess.Exec(sql)
  136. So(err, ShouldBeNil)
  137. type metric struct {
  138. Time time.Time
  139. Value int64
  140. }
  141. series := []*metric{}
  142. firstRange := genTimeRangeByInterval(fromStart, 10*time.Minute, 10*time.Second)
  143. secondRange := genTimeRangeByInterval(fromStart.Add(20*time.Minute), 10*time.Minute, 10*time.Second)
  144. for _, t := range firstRange {
  145. series = append(series, &metric{
  146. Time: t,
  147. Value: 15,
  148. })
  149. }
  150. for _, t := range secondRange {
  151. series = append(series, &metric{
  152. Time: t,
  153. Value: 20,
  154. })
  155. }
  156. for _, s := range series {
  157. _, err = sess.Insert(s)
  158. So(err, ShouldBeNil)
  159. }
  160. Convey("When doing a metric query using timeGroup", func() {
  161. query := &tsdb.TsdbQuery{
  162. Queries: []*tsdb.Query{
  163. {
  164. Model: simplejson.NewFromAny(map[string]interface{}{
  165. "rawSql": "SELECT $__timeGroup(time, '5m') AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1",
  166. "format": "time_series",
  167. }),
  168. RefId: "A",
  169. },
  170. },
  171. }
  172. resp, err := endpoint.Query(nil, nil, query)
  173. So(err, ShouldBeNil)
  174. queryResult := resp.Results["A"]
  175. So(queryResult.Error, ShouldBeNil)
  176. points := queryResult.Series[0].Points
  177. So(len(points), ShouldEqual, 6)
  178. dt := fromStart
  179. for i := 0; i < 3; i++ {
  180. aValue := points[i][0].Float64
  181. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  182. So(aValue, ShouldEqual, 15)
  183. So(aTime, ShouldEqual, dt)
  184. dt = dt.Add(5 * time.Minute)
  185. }
  186. // adjust for 5 minute gap
  187. dt = dt.Add(5 * time.Minute)
  188. for i := 3; i < 6; i++ {
  189. aValue := points[i][0].Float64
  190. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  191. So(aValue, ShouldEqual, 20)
  192. So(aTime, ShouldEqual, dt)
  193. dt = dt.Add(5 * time.Minute)
  194. }
  195. })
  196. Convey("When doing a metric query using timeGroup with NULL fill enabled", func() {
  197. query := &tsdb.TsdbQuery{
  198. Queries: []*tsdb.Query{
  199. {
  200. Model: simplejson.NewFromAny(map[string]interface{}{
  201. "rawSql": "SELECT $__timeGroup(time, '5m', NULL) AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1",
  202. "format": "time_series",
  203. }),
  204. RefId: "A",
  205. },
  206. },
  207. TimeRange: &tsdb.TimeRange{
  208. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  209. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  210. },
  211. }
  212. resp, err := endpoint.Query(nil, nil, query)
  213. So(err, ShouldBeNil)
  214. queryResult := resp.Results["A"]
  215. So(queryResult.Error, ShouldBeNil)
  216. points := queryResult.Series[0].Points
  217. So(len(points), ShouldEqual, 7)
  218. dt := fromStart
  219. for i := 0; i < 3; i++ {
  220. aValue := points[i][0].Float64
  221. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  222. So(aValue, ShouldEqual, 15)
  223. So(aTime, ShouldEqual, dt)
  224. dt = dt.Add(5 * time.Minute)
  225. }
  226. So(points[3][0].Valid, ShouldBeFalse)
  227. // adjust for 5 minute gap
  228. dt = dt.Add(5 * time.Minute)
  229. for i := 4; i < 7; i++ {
  230. aValue := points[i][0].Float64
  231. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  232. So(aValue, ShouldEqual, 20)
  233. So(aTime, ShouldEqual, dt)
  234. dt = dt.Add(5 * time.Minute)
  235. }
  236. })
  237. Convey("When doing a metric query using timeGroup with float fill enabled", func() {
  238. query := &tsdb.TsdbQuery{
  239. Queries: []*tsdb.Query{
  240. {
  241. Model: simplejson.NewFromAny(map[string]interface{}{
  242. "rawSql": "SELECT $__timeGroup(time, '5m', 1.5) AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1",
  243. "format": "time_series",
  244. }),
  245. RefId: "A",
  246. },
  247. },
  248. TimeRange: &tsdb.TimeRange{
  249. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  250. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  251. },
  252. }
  253. resp, err := endpoint.Query(nil, nil, query)
  254. So(err, ShouldBeNil)
  255. queryResult := resp.Results["A"]
  256. So(queryResult.Error, ShouldBeNil)
  257. points := queryResult.Series[0].Points
  258. So(points[3][0].Float64, ShouldEqual, 1.5)
  259. })
  260. })
  261. Convey("Given a table with metrics having multiple values and measurements", func() {
  262. type metric_values struct {
  263. Time time.Time
  264. Measurement string
  265. ValueOne int64 `xorm:"integer 'valueOne'"`
  266. ValueTwo int64 `xorm:"integer 'valueTwo'"`
  267. }
  268. if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
  269. So(err, ShouldBeNil)
  270. sess.DropTable(metric_values{})
  271. }
  272. err := sess.CreateTable(metric_values{})
  273. So(err, ShouldBeNil)
  274. rand.Seed(time.Now().Unix())
  275. rnd := func(min, max int64) int64 {
  276. return rand.Int63n(max-min) + min
  277. }
  278. series := []*metric_values{}
  279. for _, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
  280. series = append(series, &metric_values{
  281. Time: t,
  282. Measurement: "Metric A",
  283. ValueOne: rnd(0, 100),
  284. ValueTwo: rnd(0, 100),
  285. })
  286. series = append(series, &metric_values{
  287. Time: t,
  288. Measurement: "Metric B",
  289. ValueOne: rnd(0, 100),
  290. ValueTwo: rnd(0, 100),
  291. })
  292. }
  293. for _, s := range series {
  294. _, err = sess.Insert(s)
  295. So(err, ShouldBeNil)
  296. }
  297. Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
  298. query := &tsdb.TsdbQuery{
  299. Queries: []*tsdb.Query{
  300. {
  301. Model: simplejson.NewFromAny(map[string]interface{}{
  302. "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values ORDER BY 1",
  303. "format": "time_series",
  304. }),
  305. RefId: "A",
  306. },
  307. },
  308. }
  309. resp, err := endpoint.Query(nil, nil, query)
  310. So(err, ShouldBeNil)
  311. queryResult := resp.Results["A"]
  312. So(queryResult.Error, ShouldBeNil)
  313. So(len(queryResult.Series), ShouldEqual, 2)
  314. So(queryResult.Series[0].Name, ShouldEqual, "Metric A - value one")
  315. So(queryResult.Series[1].Name, ShouldEqual, "Metric B - value one")
  316. })
  317. Convey("When doing a metric query grouping by time should return correct series", func() {
  318. query := &tsdb.TsdbQuery{
  319. Queries: []*tsdb.Query{
  320. {
  321. Model: simplejson.NewFromAny(map[string]interface{}{
  322. "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values ORDER BY 1",
  323. "format": "time_series",
  324. }),
  325. RefId: "A",
  326. },
  327. },
  328. }
  329. resp, err := endpoint.Query(nil, nil, query)
  330. So(err, ShouldBeNil)
  331. queryResult := resp.Results["A"]
  332. So(queryResult.Error, ShouldBeNil)
  333. So(len(queryResult.Series), ShouldEqual, 2)
  334. So(queryResult.Series[0].Name, ShouldEqual, "valueOne")
  335. So(queryResult.Series[1].Name, ShouldEqual, "valueTwo")
  336. })
  337. Convey("Given a stored procedure that takes @from and @to in epoch time", func() {
  338. sql := `
  339. IF object_id('sp_test_epoch') IS NOT NULL
  340. DROP PROCEDURE sp_test_epoch
  341. `
  342. _, err := sess.Exec(sql)
  343. So(err, ShouldBeNil)
  344. sql = `
  345. CREATE PROCEDURE sp_test_epoch(
  346. @from int,
  347. @to int,
  348. @interval nvarchar(50) = '5m',
  349. @metric nvarchar(200) = 'ALL'
  350. ) AS
  351. BEGIN
  352. DECLARE @dInterval int
  353. SELECT @dInterval = 300
  354. IF @interval = '10m'
  355. SELECT @dInterval = 600
  356. SELECT
  357. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
  358. measurement + ' - value one' as metric,
  359. avg(valueOne) as value
  360. FROM
  361. metric_values
  362. WHERE
  363. time BETWEEN DATEADD(s, @from, '1970-01-01') AND DATEADD(s, @to, '1970-01-01') AND
  364. (@metric = 'ALL' OR measurement = @metric)
  365. GROUP BY
  366. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
  367. measurement
  368. UNION ALL
  369. SELECT
  370. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
  371. measurement + ' - value two' as metric,
  372. avg(valueTwo) as value
  373. FROM
  374. metric_values
  375. WHERE
  376. time BETWEEN DATEADD(s, @from, '1970-01-01') AND DATEADD(s, @to, '1970-01-01') AND
  377. (@metric = 'ALL' OR measurement = @metric)
  378. GROUP BY
  379. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
  380. measurement
  381. ORDER BY 1
  382. END
  383. `
  384. _, err = sess.Exec(sql)
  385. So(err, ShouldBeNil)
  386. Convey("When doing a metric query using stored procedure should return correct result", func() {
  387. query := &tsdb.TsdbQuery{
  388. Queries: []*tsdb.Query{
  389. {
  390. Model: simplejson.NewFromAny(map[string]interface{}{
  391. "rawSql": `DECLARE
  392. @from int = $__unixEpochFrom(),
  393. @to int = $__unixEpochTo()
  394. EXEC dbo.sp_test_epoch @from, @to`,
  395. "format": "time_series",
  396. }),
  397. RefId: "A",
  398. },
  399. },
  400. TimeRange: &tsdb.TimeRange{
  401. From: "1521117000000",
  402. To: "1521122100000",
  403. },
  404. }
  405. resp, err := endpoint.Query(nil, nil, query)
  406. queryResult := resp.Results["A"]
  407. So(err, ShouldBeNil)
  408. fmt.Println("query", "sql", queryResult.Meta)
  409. So(queryResult.Error, ShouldBeNil)
  410. So(len(queryResult.Series), ShouldEqual, 4)
  411. So(queryResult.Series[0].Name, ShouldEqual, "Metric A - value one")
  412. So(queryResult.Series[1].Name, ShouldEqual, "Metric B - value one")
  413. So(queryResult.Series[2].Name, ShouldEqual, "Metric A - value two")
  414. So(queryResult.Series[3].Name, ShouldEqual, "Metric B - value two")
  415. })
  416. })
  417. Convey("Given a stored procedure that takes @from and @to in datetime", func() {
  418. sql := `
  419. IF object_id('sp_test_datetime') IS NOT NULL
  420. DROP PROCEDURE sp_test_datetime
  421. `
  422. _, err := sess.Exec(sql)
  423. So(err, ShouldBeNil)
  424. sql = `
  425. CREATE PROCEDURE sp_test_datetime(
  426. @from datetime,
  427. @to datetime,
  428. @interval nvarchar(50) = '5m',
  429. @metric nvarchar(200) = 'ALL'
  430. ) AS
  431. BEGIN
  432. DECLARE @dInterval int
  433. SELECT @dInterval = 300
  434. IF @interval = '10m'
  435. SELECT @dInterval = 600
  436. SELECT
  437. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
  438. measurement + ' - value one' as metric,
  439. avg(valueOne) as value
  440. FROM
  441. metric_values
  442. WHERE
  443. time BETWEEN @from AND @to AND
  444. (@metric = 'ALL' OR measurement = @metric)
  445. GROUP BY
  446. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
  447. measurement
  448. UNION ALL
  449. SELECT
  450. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
  451. measurement + ' - value two' as metric,
  452. avg(valueTwo) as value
  453. FROM
  454. metric_values
  455. WHERE
  456. time BETWEEN @from AND @to AND
  457. (@metric = 'ALL' OR measurement = @metric)
  458. GROUP BY
  459. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
  460. measurement
  461. ORDER BY 1
  462. END
  463. `
  464. _, err = sess.Exec(sql)
  465. So(err, ShouldBeNil)
  466. Convey("When doing a metric query using stored procedure should return correct result", func() {
  467. query := &tsdb.TsdbQuery{
  468. Queries: []*tsdb.Query{
  469. {
  470. Model: simplejson.NewFromAny(map[string]interface{}{
  471. "rawSql": `DECLARE
  472. @from int = $__unixEpochFrom(),
  473. @to int = $__unixEpochTo()
  474. EXEC dbo.sp_test_epoch @from, @to`,
  475. "format": "time_series",
  476. }),
  477. RefId: "A",
  478. },
  479. },
  480. TimeRange: &tsdb.TimeRange{
  481. From: "1521117000000",
  482. To: "1521122100000",
  483. },
  484. }
  485. resp, err := endpoint.Query(nil, nil, query)
  486. queryResult := resp.Results["A"]
  487. So(err, ShouldBeNil)
  488. So(queryResult.Error, ShouldBeNil)
  489. So(len(queryResult.Series), ShouldEqual, 4)
  490. So(queryResult.Series[0].Name, ShouldEqual, "Metric A - value one")
  491. So(queryResult.Series[1].Name, ShouldEqual, "Metric B - value one")
  492. So(queryResult.Series[2].Name, ShouldEqual, "Metric A - value two")
  493. So(queryResult.Series[3].Name, ShouldEqual, "Metric B - value two")
  494. })
  495. })
  496. })
  497. Convey("Given a table with event data", func() {
  498. sql := `
  499. IF OBJECT_ID('dbo.[event]', 'U') IS NOT NULL
  500. DROP TABLE dbo.[event]
  501. CREATE TABLE [event] (
  502. time_sec int,
  503. description nvarchar(100),
  504. tags nvarchar(100),
  505. )
  506. `
  507. _, err := sess.Exec(sql)
  508. So(err, ShouldBeNil)
  509. type event struct {
  510. TimeSec int64
  511. Description string
  512. Tags string
  513. }
  514. events := []*event{}
  515. for _, t := range genTimeRangeByInterval(fromStart.Add(-20*time.Minute), 60*time.Minute, 25*time.Minute) {
  516. events = append(events, &event{
  517. TimeSec: t.Unix(),
  518. Description: "Someone deployed something",
  519. Tags: "deploy",
  520. })
  521. events = append(events, &event{
  522. TimeSec: t.Add(5 * time.Minute).Unix(),
  523. Description: "New support ticket registered",
  524. Tags: "ticket",
  525. })
  526. }
  527. for _, e := range events {
  528. sql = fmt.Sprintf(`
  529. INSERT [event] (time_sec, description, tags)
  530. VALUES(%d, '%s', '%s')
  531. `, e.TimeSec, e.Description, e.Tags)
  532. _, err = sess.Exec(sql)
  533. So(err, ShouldBeNil)
  534. }
  535. Convey("When doing an annotation query of deploy events should return expected result", func() {
  536. query := &tsdb.TsdbQuery{
  537. Queries: []*tsdb.Query{
  538. {
  539. Model: simplejson.NewFromAny(map[string]interface{}{
  540. "rawSql": "SELECT time_sec as time, description as [text], tags FROM [event] WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC",
  541. "format": "table",
  542. }),
  543. RefId: "Deploys",
  544. },
  545. },
  546. TimeRange: &tsdb.TimeRange{
  547. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  548. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  549. },
  550. }
  551. resp, err := endpoint.Query(nil, nil, query)
  552. queryResult := resp.Results["Deploys"]
  553. So(err, ShouldBeNil)
  554. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  555. })
  556. Convey("When doing an annotation query of ticket events should return expected result", func() {
  557. query := &tsdb.TsdbQuery{
  558. Queries: []*tsdb.Query{
  559. {
  560. Model: simplejson.NewFromAny(map[string]interface{}{
  561. "rawSql": "SELECT time_sec as time, description as [text], tags FROM [event] WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC",
  562. "format": "table",
  563. }),
  564. RefId: "Tickets",
  565. },
  566. },
  567. TimeRange: &tsdb.TimeRange{
  568. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  569. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  570. },
  571. }
  572. resp, err := endpoint.Query(nil, nil, query)
  573. queryResult := resp.Results["Tickets"]
  574. So(err, ShouldBeNil)
  575. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  576. })
  577. Convey("When doing an annotation query with a time column in datetime format", func() {
  578. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  579. dtFormat := "2006-01-02 15:04:05.999999999"
  580. query := &tsdb.TsdbQuery{
  581. Queries: []*tsdb.Query{
  582. {
  583. Model: simplejson.NewFromAny(map[string]interface{}{
  584. "rawSql": fmt.Sprintf(`SELECT
  585. CAST('%s' AS DATETIME) as time,
  586. 'message' as text,
  587. 'tag1,tag2' as tags
  588. `, dt.Format(dtFormat)),
  589. "format": "table",
  590. }),
  591. RefId: "A",
  592. },
  593. },
  594. }
  595. resp, err := endpoint.Query(nil, nil, query)
  596. So(err, ShouldBeNil)
  597. queryResult := resp.Results["A"]
  598. So(queryResult.Error, ShouldBeNil)
  599. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  600. columns := queryResult.Tables[0].Rows[0]
  601. //Should be in milliseconds
  602. So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000))
  603. })
  604. Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
  605. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  606. query := &tsdb.TsdbQuery{
  607. Queries: []*tsdb.Query{
  608. {
  609. Model: simplejson.NewFromAny(map[string]interface{}{
  610. "rawSql": fmt.Sprintf(`SELECT
  611. %d as time,
  612. 'message' as text,
  613. 'tag1,tag2' as tags
  614. `, dt.Unix()),
  615. "format": "table",
  616. }),
  617. RefId: "A",
  618. },
  619. },
  620. }
  621. resp, err := endpoint.Query(nil, nil, query)
  622. So(err, ShouldBeNil)
  623. queryResult := resp.Results["A"]
  624. So(queryResult.Error, ShouldBeNil)
  625. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  626. columns := queryResult.Tables[0].Rows[0]
  627. //Should be in milliseconds
  628. So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
  629. })
  630. Convey("When doing an annotation query with a time column in epoch second format (int) should return ms", func() {
  631. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  632. query := &tsdb.TsdbQuery{
  633. Queries: []*tsdb.Query{
  634. {
  635. Model: simplejson.NewFromAny(map[string]interface{}{
  636. "rawSql": fmt.Sprintf(`SELECT
  637. cast(%d as int) as time,
  638. 'message' as text,
  639. 'tag1,tag2' as tags
  640. `, dt.Unix()),
  641. "format": "table",
  642. }),
  643. RefId: "A",
  644. },
  645. },
  646. }
  647. resp, err := endpoint.Query(nil, nil, query)
  648. So(err, ShouldBeNil)
  649. queryResult := resp.Results["A"]
  650. So(queryResult.Error, ShouldBeNil)
  651. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  652. columns := queryResult.Tables[0].Rows[0]
  653. //Should be in milliseconds
  654. So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
  655. })
  656. Convey("When doing an annotation query with a time column in epoch millisecond format should return ms", func() {
  657. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  658. query := &tsdb.TsdbQuery{
  659. Queries: []*tsdb.Query{
  660. {
  661. Model: simplejson.NewFromAny(map[string]interface{}{
  662. "rawSql": fmt.Sprintf(`SELECT
  663. %d as time,
  664. 'message' as text,
  665. 'tag1,tag2' as tags
  666. `, dt.Unix()*1000),
  667. "format": "table",
  668. }),
  669. RefId: "A",
  670. },
  671. },
  672. }
  673. resp, err := endpoint.Query(nil, nil, query)
  674. So(err, ShouldBeNil)
  675. queryResult := resp.Results["A"]
  676. So(queryResult.Error, ShouldBeNil)
  677. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  678. columns := queryResult.Tables[0].Rows[0]
  679. //Should be in milliseconds
  680. So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000))
  681. })
  682. Convey("When doing an annotation query with a time column holding a bigint null value should return nil", func() {
  683. query := &tsdb.TsdbQuery{
  684. Queries: []*tsdb.Query{
  685. {
  686. Model: simplejson.NewFromAny(map[string]interface{}{
  687. "rawSql": `SELECT
  688. cast(null as bigint) as time,
  689. 'message' as text,
  690. 'tag1,tag2' as tags
  691. `,
  692. "format": "table",
  693. }),
  694. RefId: "A",
  695. },
  696. },
  697. }
  698. resp, err := endpoint.Query(nil, nil, query)
  699. So(err, ShouldBeNil)
  700. queryResult := resp.Results["A"]
  701. So(queryResult.Error, ShouldBeNil)
  702. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  703. columns := queryResult.Tables[0].Rows[0]
  704. //Should be in milliseconds
  705. So(columns[0], ShouldBeNil)
  706. })
  707. Convey("When doing an annotation query with a time column holding a datetime null value should return nil", func() {
  708. query := &tsdb.TsdbQuery{
  709. Queries: []*tsdb.Query{
  710. {
  711. Model: simplejson.NewFromAny(map[string]interface{}{
  712. "rawSql": `SELECT
  713. cast(null as datetime) as time,
  714. 'message' as text,
  715. 'tag1,tag2' as tags
  716. `,
  717. "format": "table",
  718. }),
  719. RefId: "A",
  720. },
  721. },
  722. }
  723. resp, err := endpoint.Query(nil, nil, query)
  724. So(err, ShouldBeNil)
  725. queryResult := resp.Results["A"]
  726. So(queryResult.Error, ShouldBeNil)
  727. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  728. columns := queryResult.Tables[0].Rows[0]
  729. //Should be in milliseconds
  730. So(columns[0], ShouldBeNil)
  731. })
  732. })
  733. })
  734. }
  735. func InitMSSQLTestDB(t *testing.T) *xorm.Engine {
  736. x, err := xorm.NewEngine(sqlutil.TestDB_Mssql.DriverName, strings.Replace(sqlutil.TestDB_Mssql.ConnStr, "localhost", serverIP, 1))
  737. x.DatabaseTZ = time.UTC
  738. x.TZLocation = time.UTC
  739. // x.ShowSQL()
  740. if err != nil {
  741. t.Fatalf("Failed to init mssql db %v", err)
  742. }
  743. return x
  744. }
  745. func genTimeRangeByInterval(from time.Time, duration time.Duration, interval time.Duration) []time.Time {
  746. durationSec := int64(duration.Seconds())
  747. intervalSec := int64(interval.Seconds())
  748. timeRange := []time.Time{}
  749. for i := int64(0); i < durationSec; i += intervalSec {
  750. timeRange = append(timeRange, from)
  751. from = from.Add(time.Duration(int64(time.Second) * intervalSec))
  752. }
  753. return timeRange
  754. }