mysql_test.go 21 KB

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