mysql_test.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  1. package mysql
  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"
  12. "github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
  13. "github.com/grafana/grafana/pkg/tsdb"
  14. . "github.com/smartystreets/goconvey/convey"
  15. )
  16. // To run this test, set runMySqlTests=true
  17. // and set up a MySQL db named grafana_ds_tests and a user/password grafana/password
  18. // Use the docker/blocks/mysql_tests/docker-compose.yaml to spin up a
  19. // preconfigured MySQL server suitable for running these tests.
  20. // Thers's also a dashboard.json in same directory that you can import to Grafana
  21. // once you've created a datasource for the test server/database.
  22. func TestMySQL(t *testing.T) {
  23. // change to true to run the MySQL tests
  24. runMySqlTests := false
  25. // runMySqlTests := true
  26. if !(sqlstore.IsTestDbMySql() || runMySqlTests) {
  27. t.Skip()
  28. }
  29. Convey("MySQL", t, func() {
  30. x := InitMySQLTestDB(t)
  31. endpoint := &MysqlQueryEndpoint{
  32. sqlEngine: &tsdb.DefaultSqlEngine{
  33. MacroEngine: NewMysqlMacroEngine(),
  34. XormEngine: x,
  35. },
  36. log: log.New("tsdb.mysql"),
  37. }
  38. sess := x.NewSession()
  39. defer sess.Close()
  40. fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC)
  41. Convey("Given a table with different native data types", func() {
  42. if exists, err := sess.IsTableExist("mysql_types"); err != nil || exists {
  43. So(err, ShouldBeNil)
  44. sess.DropTable("mysql_types")
  45. }
  46. sql := "CREATE TABLE `mysql_types` ("
  47. sql += "`atinyint` tinyint(1) NOT NULL,"
  48. sql += "`avarchar` varchar(3) NOT NULL,"
  49. sql += "`achar` char(3),"
  50. sql += "`amediumint` mediumint NOT NULL,"
  51. sql += "`asmallint` smallint NOT NULL,"
  52. sql += "`abigint` bigint NOT NULL,"
  53. sql += "`aint` int(11) NOT NULL,"
  54. sql += "`adouble` double(10,2),"
  55. sql += "`anewdecimal` decimal(10,2),"
  56. sql += "`afloat` float(10,2) NOT NULL,"
  57. sql += "`atimestamp` timestamp NOT NULL,"
  58. sql += "`adatetime` datetime NOT NULL,"
  59. sql += "`atime` time NOT NULL,"
  60. sql += "`ayear` year," // Crashes xorm when running cleandb
  61. sql += "`abit` bit(1),"
  62. sql += "`atinytext` tinytext,"
  63. sql += "`atinyblob` tinyblob,"
  64. sql += "`atext` text,"
  65. sql += "`ablob` blob,"
  66. sql += "`amediumtext` mediumtext,"
  67. sql += "`amediumblob` mediumblob,"
  68. sql += "`alongtext` longtext,"
  69. sql += "`alongblob` longblob,"
  70. sql += "`aenum` enum('val1', 'val2'),"
  71. sql += "`aset` set('a', 'b', 'c', 'd'),"
  72. sql += "`adate` date,"
  73. sql += "`time_sec` datetime(6),"
  74. sql += "`aintnull` int(11),"
  75. sql += "`afloatnull` float(10,2),"
  76. sql += "`avarcharnull` varchar(3),"
  77. sql += "`adecimalnull` decimal(10,2)"
  78. sql += ") ENGINE=InnoDB DEFAULT CHARSET=latin1;"
  79. _, err := sess.Exec(sql)
  80. So(err, ShouldBeNil)
  81. sql = "INSERT INTO `mysql_types` "
  82. sql += "(`atinyint`, `avarchar`, `achar`, `amediumint`, `asmallint`, `abigint`, `aint`, `adouble`, "
  83. sql += "`anewdecimal`, `afloat`, `adatetime`, `atimestamp`, `atime`, `ayear`, `abit`, `atinytext`, "
  84. sql += "`atinyblob`, `atext`, `ablob`, `amediumtext`, `amediumblob`, `alongtext`, `alongblob`, "
  85. sql += "`aenum`, `aset`, `adate`, `time_sec`) "
  86. sql += "VALUES(1, 'abc', 'def', 1, 10, 100, 1420070400, 1.11, "
  87. sql += "2.22, 3.33, now(), current_timestamp(), '11:11:11', '2018', 1, 'tinytext', "
  88. sql += "'tinyblob', 'text', 'blob', 'mediumtext', 'mediumblob', 'longtext', 'longblob', "
  89. sql += "'val2', 'a,b', curdate(), '2018-01-01 00:01:01.123456');"
  90. _, err = sess.Exec(sql)
  91. So(err, ShouldBeNil)
  92. Convey("Query with Table format should map MySQL column types to Go types", func() {
  93. query := &tsdb.TsdbQuery{
  94. Queries: []*tsdb.Query{
  95. {
  96. Model: simplejson.NewFromAny(map[string]interface{}{
  97. "rawSql": "SELECT * FROM mysql_types",
  98. "format": "table",
  99. }),
  100. RefId: "A",
  101. },
  102. },
  103. }
  104. resp, err := endpoint.Query(nil, nil, query)
  105. So(err, ShouldBeNil)
  106. queryResult := resp.Results["A"]
  107. So(queryResult.Error, ShouldBeNil)
  108. column := queryResult.Tables[0].Rows[0]
  109. So(*column[0].(*int8), ShouldEqual, 1)
  110. So(column[1].(string), ShouldEqual, "abc")
  111. So(column[2].(string), ShouldEqual, "def")
  112. So(*column[3].(*int32), ShouldEqual, 1)
  113. So(*column[4].(*int16), ShouldEqual, 10)
  114. So(*column[5].(*int64), ShouldEqual, 100)
  115. So(*column[6].(*int32), ShouldEqual, 1420070400)
  116. So(column[7].(float64), ShouldEqual, 1.11)
  117. So(column[8].(float64), ShouldEqual, 2.22)
  118. So(*column[9].(*float32), ShouldEqual, 3.33)
  119. So(column[10].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now())
  120. So(column[11].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now())
  121. So(column[12].(string), ShouldEqual, "11:11:11")
  122. So(column[13].(int64), ShouldEqual, 2018)
  123. So(*column[14].(*[]byte), ShouldHaveSameTypeAs, []byte{1})
  124. So(column[15].(string), ShouldEqual, "tinytext")
  125. So(column[16].(string), ShouldEqual, "tinyblob")
  126. So(column[17].(string), ShouldEqual, "text")
  127. So(column[18].(string), ShouldEqual, "blob")
  128. So(column[19].(string), ShouldEqual, "mediumtext")
  129. So(column[20].(string), ShouldEqual, "mediumblob")
  130. So(column[21].(string), ShouldEqual, "longtext")
  131. So(column[22].(string), ShouldEqual, "longblob")
  132. So(column[23].(string), ShouldEqual, "val2")
  133. So(column[24].(string), ShouldEqual, "a,b")
  134. So(column[25].(time.Time).Format("2006-01-02T00:00:00Z"), ShouldEqual, time.Now().UTC().Format("2006-01-02T00:00:00Z"))
  135. So(column[27], ShouldEqual, nil)
  136. So(column[28], ShouldEqual, nil)
  137. So(column[29], ShouldEqual, "")
  138. So(column[30], ShouldEqual, nil)
  139. })
  140. })
  141. Convey("Given a table with metrics that lacks data for some series ", func() {
  142. type metric struct {
  143. Time time.Time
  144. Value int64
  145. }
  146. if exist, err := sess.IsTableExist(metric{}); err != nil || exist {
  147. So(err, ShouldBeNil)
  148. sess.DropTable(metric{})
  149. }
  150. err := sess.CreateTable(metric{})
  151. So(err, ShouldBeNil)
  152. series := []*metric{}
  153. firstRange := genTimeRangeByInterval(fromStart, 10*time.Minute, 10*time.Second)
  154. secondRange := genTimeRangeByInterval(fromStart.Add(20*time.Minute), 10*time.Minute, 10*time.Second)
  155. for _, t := range firstRange {
  156. series = append(series, &metric{
  157. Time: t,
  158. Value: 15,
  159. })
  160. }
  161. for _, t := range secondRange {
  162. series = append(series, &metric{
  163. Time: t,
  164. Value: 20,
  165. })
  166. }
  167. for _, s := range series {
  168. _, err = sess.Insert(s)
  169. So(err, ShouldBeNil)
  170. }
  171. Convey("When doing a metric query using timeGroup", func() {
  172. query := &tsdb.TsdbQuery{
  173. Queries: []*tsdb.Query{
  174. {
  175. Model: simplejson.NewFromAny(map[string]interface{}{
  176. "rawSql": "SELECT $__timeGroup(time, '5m') as time_sec, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
  177. "format": "time_series",
  178. }),
  179. RefId: "A",
  180. },
  181. },
  182. }
  183. resp, err := endpoint.Query(nil, nil, query)
  184. So(err, ShouldBeNil)
  185. queryResult := resp.Results["A"]
  186. So(queryResult.Error, ShouldBeNil)
  187. points := queryResult.Series[0].Points
  188. So(len(points), ShouldEqual, 6)
  189. dt := fromStart
  190. for i := 0; i < 3; i++ {
  191. aValue := points[i][0].Float64
  192. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  193. So(aValue, ShouldEqual, 15)
  194. So(aTime, ShouldEqual, dt)
  195. dt = dt.Add(5 * time.Minute)
  196. }
  197. // adjust for 5 minute gap
  198. dt = dt.Add(5 * time.Minute)
  199. for i := 3; i < 6; i++ {
  200. aValue := points[i][0].Float64
  201. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  202. So(aValue, ShouldEqual, 20)
  203. So(aTime, ShouldEqual, dt)
  204. dt = dt.Add(5 * time.Minute)
  205. }
  206. })
  207. Convey("When doing a metric query using timeGroup with NULL fill enabled", func() {
  208. query := &tsdb.TsdbQuery{
  209. Queries: []*tsdb.Query{
  210. {
  211. Model: simplejson.NewFromAny(map[string]interface{}{
  212. "rawSql": "SELECT $__timeGroup(time, '5m', NULL) as time_sec, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
  213. "format": "time_series",
  214. }),
  215. RefId: "A",
  216. },
  217. },
  218. TimeRange: &tsdb.TimeRange{
  219. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  220. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  221. },
  222. }
  223. resp, err := endpoint.Query(nil, nil, query)
  224. So(err, ShouldBeNil)
  225. queryResult := resp.Results["A"]
  226. So(queryResult.Error, ShouldBeNil)
  227. points := queryResult.Series[0].Points
  228. So(len(points), ShouldEqual, 7)
  229. dt := fromStart
  230. for i := 0; i < 3; i++ {
  231. aValue := points[i][0].Float64
  232. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  233. So(aValue, ShouldEqual, 15)
  234. So(aTime, ShouldEqual, dt)
  235. dt = dt.Add(5 * time.Minute)
  236. }
  237. So(points[3][0].Valid, ShouldBeFalse)
  238. // adjust for 5 minute gap
  239. dt = dt.Add(5 * time.Minute)
  240. for i := 4; i < 7; i++ {
  241. aValue := points[i][0].Float64
  242. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  243. So(aValue, ShouldEqual, 20)
  244. So(aTime, ShouldEqual, dt)
  245. dt = dt.Add(5 * time.Minute)
  246. }
  247. })
  248. Convey("When doing a metric query using timeGroup with float fill enabled", func() {
  249. query := &tsdb.TsdbQuery{
  250. Queries: []*tsdb.Query{
  251. {
  252. Model: simplejson.NewFromAny(map[string]interface{}{
  253. "rawSql": "SELECT $__timeGroup(time, '5m', 1.5) as time_sec, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
  254. "format": "time_series",
  255. }),
  256. RefId: "A",
  257. },
  258. },
  259. TimeRange: &tsdb.TimeRange{
  260. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  261. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  262. },
  263. }
  264. resp, err := endpoint.Query(nil, nil, query)
  265. So(err, ShouldBeNil)
  266. queryResult := resp.Results["A"]
  267. So(queryResult.Error, ShouldBeNil)
  268. points := queryResult.Series[0].Points
  269. So(points[3][0].Float64, ShouldEqual, 1.5)
  270. })
  271. })
  272. Convey("Given a table with metrics having multiple values and measurements", func() {
  273. type metric_values struct {
  274. Time time.Time
  275. Measurement string
  276. ValueOne int64 `xorm:"integer 'valueOne'"`
  277. ValueTwo int64 `xorm:"integer 'valueTwo'"`
  278. }
  279. if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
  280. So(err, ShouldBeNil)
  281. sess.DropTable(metric_values{})
  282. }
  283. err := sess.CreateTable(metric_values{})
  284. So(err, ShouldBeNil)
  285. rand.Seed(time.Now().Unix())
  286. rnd := func(min, max int64) int64 {
  287. return rand.Int63n(max-min) + min
  288. }
  289. series := []*metric_values{}
  290. for _, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
  291. series = append(series, &metric_values{
  292. Time: t,
  293. Measurement: "Metric A",
  294. ValueOne: rnd(0, 100),
  295. ValueTwo: rnd(0, 100),
  296. })
  297. series = append(series, &metric_values{
  298. Time: t,
  299. Measurement: "Metric B",
  300. ValueOne: rnd(0, 100),
  301. ValueTwo: rnd(0, 100),
  302. })
  303. }
  304. for _, s := range series {
  305. _, err := sess.Insert(s)
  306. So(err, ShouldBeNil)
  307. }
  308. Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
  309. query := &tsdb.TsdbQuery{
  310. Queries: []*tsdb.Query{
  311. {
  312. Model: simplejson.NewFromAny(map[string]interface{}{
  313. "rawSql": `SELECT $__time(time), CONCAT(measurement, ' - value one') as metric, valueOne FROM metric_values ORDER BY 1`,
  314. "format": "time_series",
  315. }),
  316. RefId: "A",
  317. },
  318. },
  319. }
  320. resp, err := endpoint.Query(nil, nil, query)
  321. So(err, ShouldBeNil)
  322. queryResult := resp.Results["A"]
  323. So(queryResult.Error, ShouldBeNil)
  324. So(len(queryResult.Series), ShouldEqual, 2)
  325. So(queryResult.Series[0].Name, ShouldEqual, "Metric B - value one")
  326. So(queryResult.Series[1].Name, ShouldEqual, "Metric A - value one")
  327. })
  328. Convey("When doing a metric query grouping by time should return correct series", func() {
  329. query := &tsdb.TsdbQuery{
  330. Queries: []*tsdb.Query{
  331. {
  332. Model: simplejson.NewFromAny(map[string]interface{}{
  333. "rawSql": `SELECT $__time(time), valueOne, valueTwo FROM metric_values ORDER BY 1`,
  334. "format": "time_series",
  335. }),
  336. RefId: "A",
  337. },
  338. },
  339. }
  340. resp, err := endpoint.Query(nil, nil, query)
  341. So(err, ShouldBeNil)
  342. queryResult := resp.Results["A"]
  343. So(queryResult.Error, ShouldBeNil)
  344. So(len(queryResult.Series), ShouldEqual, 2)
  345. So(queryResult.Series[0].Name, ShouldEqual, "valueOne")
  346. So(queryResult.Series[1].Name, ShouldEqual, "valueTwo")
  347. })
  348. })
  349. Convey("Given a table with event data", func() {
  350. type event struct {
  351. TimeSec int64
  352. Description string
  353. Tags string
  354. }
  355. if exist, err := sess.IsTableExist(event{}); err != nil || exist {
  356. So(err, ShouldBeNil)
  357. sess.DropTable(event{})
  358. }
  359. err := sess.CreateTable(event{})
  360. So(err, ShouldBeNil)
  361. events := []*event{}
  362. for _, t := range genTimeRangeByInterval(fromStart.Add(-20*time.Minute), 60*time.Minute, 25*time.Minute) {
  363. events = append(events, &event{
  364. TimeSec: t.Unix(),
  365. Description: "Someone deployed something",
  366. Tags: "deploy",
  367. })
  368. events = append(events, &event{
  369. TimeSec: t.Add(5 * time.Minute).Unix(),
  370. Description: "New support ticket registered",
  371. Tags: "ticket",
  372. })
  373. }
  374. for _, e := range events {
  375. _, err = sess.Insert(e)
  376. So(err, ShouldBeNil)
  377. }
  378. Convey("When doing an annotation query of deploy events should return expected result", func() {
  379. query := &tsdb.TsdbQuery{
  380. Queries: []*tsdb.Query{
  381. {
  382. Model: simplejson.NewFromAny(map[string]interface{}{
  383. "rawSql": `SELECT time_sec, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC`,
  384. "format": "table",
  385. }),
  386. RefId: "Deploys",
  387. },
  388. },
  389. TimeRange: &tsdb.TimeRange{
  390. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  391. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  392. },
  393. }
  394. resp, err := endpoint.Query(nil, nil, query)
  395. queryResult := resp.Results["Deploys"]
  396. So(err, ShouldBeNil)
  397. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  398. })
  399. Convey("When doing an annotation query of ticket events should return expected result", func() {
  400. query := &tsdb.TsdbQuery{
  401. Queries: []*tsdb.Query{
  402. {
  403. Model: simplejson.NewFromAny(map[string]interface{}{
  404. "rawSql": `SELECT time_sec, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC`,
  405. "format": "table",
  406. }),
  407. RefId: "Tickets",
  408. },
  409. },
  410. TimeRange: &tsdb.TimeRange{
  411. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  412. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  413. },
  414. }
  415. resp, err := endpoint.Query(nil, nil, query)
  416. queryResult := resp.Results["Tickets"]
  417. So(err, ShouldBeNil)
  418. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  419. })
  420. Convey("When doing an annotation query with a time column in datetime format", func() {
  421. dt := time.Date(2018, 3, 14, 21, 20, 6, 0, time.UTC)
  422. dtFormat := "2006-01-02 15:04:05.999999999"
  423. query := &tsdb.TsdbQuery{
  424. Queries: []*tsdb.Query{
  425. {
  426. Model: simplejson.NewFromAny(map[string]interface{}{
  427. "rawSql": fmt.Sprintf(`SELECT
  428. CAST('%s' as datetime) as time_sec,
  429. 'message' as text,
  430. 'tag1,tag2' as tags
  431. `, dt.Format(dtFormat)),
  432. "format": "table",
  433. }),
  434. RefId: "A",
  435. },
  436. },
  437. }
  438. resp, err := endpoint.Query(nil, nil, query)
  439. So(err, ShouldBeNil)
  440. queryResult := resp.Results["A"]
  441. So(queryResult.Error, ShouldBeNil)
  442. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  443. columns := queryResult.Tables[0].Rows[0]
  444. //Should be in milliseconds
  445. So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000))
  446. })
  447. Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
  448. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  449. query := &tsdb.TsdbQuery{
  450. Queries: []*tsdb.Query{
  451. {
  452. Model: simplejson.NewFromAny(map[string]interface{}{
  453. "rawSql": fmt.Sprintf(`SELECT
  454. %d as time_sec,
  455. 'message' as text,
  456. 'tag1,tag2' as tags
  457. `, dt.Unix()),
  458. "format": "table",
  459. }),
  460. RefId: "A",
  461. },
  462. },
  463. }
  464. resp, err := endpoint.Query(nil, nil, query)
  465. So(err, ShouldBeNil)
  466. queryResult := resp.Results["A"]
  467. So(queryResult.Error, ShouldBeNil)
  468. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  469. columns := queryResult.Tables[0].Rows[0]
  470. //Should be in milliseconds
  471. So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
  472. })
  473. Convey("When doing an annotation query with a time column in epoch second format (signed integer) should return ms", func() {
  474. dt := time.Date(2018, 3, 14, 21, 20, 6, 0, time.Local)
  475. query := &tsdb.TsdbQuery{
  476. Queries: []*tsdb.Query{
  477. {
  478. Model: simplejson.NewFromAny(map[string]interface{}{
  479. "rawSql": fmt.Sprintf(`SELECT
  480. CAST('%d' as signed integer) as time_sec,
  481. 'message' as text,
  482. 'tag1,tag2' as tags
  483. `, dt.Unix()),
  484. "format": "table",
  485. }),
  486. RefId: "A",
  487. },
  488. },
  489. }
  490. resp, err := endpoint.Query(nil, nil, query)
  491. So(err, ShouldBeNil)
  492. queryResult := resp.Results["A"]
  493. So(queryResult.Error, ShouldBeNil)
  494. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  495. columns := queryResult.Tables[0].Rows[0]
  496. //Should be in milliseconds
  497. So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
  498. })
  499. Convey("When doing an annotation query with a time column in epoch millisecond format should return ms", func() {
  500. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  501. query := &tsdb.TsdbQuery{
  502. Queries: []*tsdb.Query{
  503. {
  504. Model: simplejson.NewFromAny(map[string]interface{}{
  505. "rawSql": fmt.Sprintf(`SELECT
  506. %d as time_sec,
  507. 'message' as text,
  508. 'tag1,tag2' as tags
  509. `, dt.Unix()*1000),
  510. "format": "table",
  511. }),
  512. RefId: "A",
  513. },
  514. },
  515. }
  516. resp, err := endpoint.Query(nil, nil, query)
  517. So(err, ShouldBeNil)
  518. queryResult := resp.Results["A"]
  519. So(queryResult.Error, ShouldBeNil)
  520. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  521. columns := queryResult.Tables[0].Rows[0]
  522. //Should be in milliseconds
  523. So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
  524. })
  525. Convey("When doing an annotation query with a time column holding a unsigned integer null value should return nil", func() {
  526. query := &tsdb.TsdbQuery{
  527. Queries: []*tsdb.Query{
  528. {
  529. Model: simplejson.NewFromAny(map[string]interface{}{
  530. "rawSql": `SELECT
  531. cast(null as unsigned integer) as time_sec,
  532. 'message' as text,
  533. 'tag1,tag2' as tags
  534. `,
  535. "format": "table",
  536. }),
  537. RefId: "A",
  538. },
  539. },
  540. }
  541. resp, err := endpoint.Query(nil, nil, query)
  542. So(err, ShouldBeNil)
  543. queryResult := resp.Results["A"]
  544. So(queryResult.Error, ShouldBeNil)
  545. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  546. columns := queryResult.Tables[0].Rows[0]
  547. //Should be in milliseconds
  548. So(columns[0], ShouldBeNil)
  549. })
  550. Convey("When doing an annotation query with a time column holding a DATETIME null value should return nil", func() {
  551. query := &tsdb.TsdbQuery{
  552. Queries: []*tsdb.Query{
  553. {
  554. Model: simplejson.NewFromAny(map[string]interface{}{
  555. "rawSql": `SELECT
  556. cast(null as DATETIME) as time_sec,
  557. 'message' as text,
  558. 'tag1,tag2' as tags
  559. `,
  560. "format": "table",
  561. }),
  562. RefId: "A",
  563. },
  564. },
  565. }
  566. resp, err := endpoint.Query(nil, nil, query)
  567. So(err, ShouldBeNil)
  568. queryResult := resp.Results["A"]
  569. So(queryResult.Error, ShouldBeNil)
  570. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  571. columns := queryResult.Tables[0].Rows[0]
  572. //Should be in milliseconds
  573. So(columns[0], ShouldBeNil)
  574. })
  575. })
  576. })
  577. }
  578. func InitMySQLTestDB(t *testing.T) *xorm.Engine {
  579. x, err := xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, strings.Replace(sqlutil.TestDB_Mysql.ConnStr, "/grafana_tests", "/grafana_ds_tests", 1))
  580. if err != nil {
  581. t.Fatalf("Failed to init mysql db %v", err)
  582. }
  583. x.DatabaseTZ = time.UTC
  584. x.TZLocation = time.UTC
  585. // x.ShowSQL()
  586. return x
  587. }
  588. func genTimeRangeByInterval(from time.Time, duration time.Duration, interval time.Duration) []time.Time {
  589. durationSec := int64(duration.Seconds())
  590. intervalSec := int64(interval.Seconds())
  591. timeRange := []time.Time{}
  592. for i := int64(0); i < durationSec; i += intervalSec {
  593. timeRange = append(timeRange, from)
  594. from = from.Add(time.Duration(int64(time.Second) * intervalSec))
  595. }
  596. return timeRange
  597. }