mysql_test.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930
  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. // Or from the commandline: GRAFANA_TEST_DB=mysql go test -v ./pkg/tsdb/mysql
  18. // The tests require a MySQL db named grafana_ds_tests and a user/password grafana/password
  19. // Use the docker/blocks/mysql_tests/docker-compose.yaml to spin up a
  20. // preconfigured MySQL server suitable for running these tests.
  21. // There is also a dashboard.json in same directory that you can import to Grafana
  22. // once you've created a datasource for the test server/database.
  23. func TestMySQL(t *testing.T) {
  24. // change to true to run the MySQL tests
  25. runMySqlTests := false
  26. // runMySqlTests := true
  27. if !(sqlstore.IsTestDbMySql() || runMySqlTests) {
  28. t.Skip()
  29. }
  30. Convey("MySQL", t, func() {
  31. x := InitMySQLTestDB(t)
  32. endpoint := &MysqlQueryEndpoint{
  33. sqlEngine: &tsdb.DefaultSqlEngine{
  34. MacroEngine: NewMysqlMacroEngine(),
  35. XormEngine: x,
  36. },
  37. log: log.New("tsdb.mysql"),
  38. }
  39. sess := x.NewSession()
  40. defer sess.Close()
  41. fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC)
  42. Convey("Given a table with different native data types", func() {
  43. if exists, err := sess.IsTableExist("mysql_types"); err != nil || exists {
  44. So(err, ShouldBeNil)
  45. sess.DropTable("mysql_types")
  46. }
  47. sql := "CREATE TABLE `mysql_types` ("
  48. sql += "`atinyint` tinyint(1) NOT NULL,"
  49. sql += "`avarchar` varchar(3) NOT NULL,"
  50. sql += "`achar` char(3),"
  51. sql += "`amediumint` mediumint NOT NULL,"
  52. sql += "`asmallint` smallint NOT NULL,"
  53. sql += "`abigint` bigint NOT NULL,"
  54. sql += "`aint` int(11) NOT NULL,"
  55. sql += "`adouble` double(10,2),"
  56. sql += "`anewdecimal` decimal(10,2),"
  57. sql += "`afloat` float(10,2) NOT NULL,"
  58. sql += "`atimestamp` timestamp NOT NULL,"
  59. sql += "`adatetime` datetime NOT NULL,"
  60. sql += "`atime` time NOT NULL,"
  61. sql += "`ayear` year," // Crashes xorm when running cleandb
  62. sql += "`abit` bit(1),"
  63. sql += "`atinytext` tinytext,"
  64. sql += "`atinyblob` tinyblob,"
  65. sql += "`atext` text,"
  66. sql += "`ablob` blob,"
  67. sql += "`amediumtext` mediumtext,"
  68. sql += "`amediumblob` mediumblob,"
  69. sql += "`alongtext` longtext,"
  70. sql += "`alongblob` longblob,"
  71. sql += "`aenum` enum('val1', 'val2'),"
  72. sql += "`aset` set('a', 'b', 'c', 'd'),"
  73. sql += "`adate` date,"
  74. sql += "`time_sec` datetime(6),"
  75. sql += "`aintnull` int(11),"
  76. sql += "`afloatnull` float(10,2),"
  77. sql += "`avarcharnull` varchar(3),"
  78. sql += "`adecimalnull` decimal(10,2)"
  79. sql += ") ENGINE=InnoDB DEFAULT CHARSET=latin1;"
  80. _, err := sess.Exec(sql)
  81. So(err, ShouldBeNil)
  82. sql = "INSERT INTO `mysql_types` "
  83. sql += "(`atinyint`, `avarchar`, `achar`, `amediumint`, `asmallint`, `abigint`, `aint`, `adouble`, "
  84. sql += "`anewdecimal`, `afloat`, `adatetime`, `atimestamp`, `atime`, `ayear`, `abit`, `atinytext`, "
  85. sql += "`atinyblob`, `atext`, `ablob`, `amediumtext`, `amediumblob`, `alongtext`, `alongblob`, "
  86. sql += "`aenum`, `aset`, `adate`, `time_sec`) "
  87. sql += "VALUES(1, 'abc', 'def', 1, 10, 100, 1420070400, 1.11, "
  88. sql += "2.22, 3.33, now(), current_timestamp(), '11:11:11', '2018', 1, 'tinytext', "
  89. sql += "'tinyblob', 'text', 'blob', 'mediumtext', 'mediumblob', 'longtext', 'longblob', "
  90. sql += "'val2', 'a,b', curdate(), '2018-01-01 00:01:01.123456');"
  91. _, err = sess.Exec(sql)
  92. So(err, ShouldBeNil)
  93. Convey("Query with Table format should map MySQL column types to Go types", func() {
  94. query := &tsdb.TsdbQuery{
  95. Queries: []*tsdb.Query{
  96. {
  97. Model: simplejson.NewFromAny(map[string]interface{}{
  98. "rawSql": "SELECT * FROM mysql_types",
  99. "format": "table",
  100. }),
  101. RefId: "A",
  102. },
  103. },
  104. }
  105. resp, err := endpoint.Query(nil, nil, query)
  106. So(err, ShouldBeNil)
  107. queryResult := resp.Results["A"]
  108. So(queryResult.Error, ShouldBeNil)
  109. column := queryResult.Tables[0].Rows[0]
  110. So(*column[0].(*int8), ShouldEqual, 1)
  111. So(column[1].(string), ShouldEqual, "abc")
  112. So(column[2].(string), ShouldEqual, "def")
  113. So(*column[3].(*int32), ShouldEqual, 1)
  114. So(*column[4].(*int16), ShouldEqual, 10)
  115. So(*column[5].(*int64), ShouldEqual, 100)
  116. So(*column[6].(*int32), ShouldEqual, 1420070400)
  117. So(column[7].(float64), ShouldEqual, 1.11)
  118. So(column[8].(float64), ShouldEqual, 2.22)
  119. So(*column[9].(*float32), ShouldEqual, 3.33)
  120. So(column[10].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now())
  121. So(column[11].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now())
  122. So(column[12].(string), ShouldEqual, "11:11:11")
  123. So(column[13].(int64), ShouldEqual, 2018)
  124. So(*column[14].(*[]byte), ShouldHaveSameTypeAs, []byte{1})
  125. So(column[15].(string), ShouldEqual, "tinytext")
  126. So(column[16].(string), ShouldEqual, "tinyblob")
  127. So(column[17].(string), ShouldEqual, "text")
  128. So(column[18].(string), ShouldEqual, "blob")
  129. So(column[19].(string), ShouldEqual, "mediumtext")
  130. So(column[20].(string), ShouldEqual, "mediumblob")
  131. So(column[21].(string), ShouldEqual, "longtext")
  132. So(column[22].(string), ShouldEqual, "longblob")
  133. So(column[23].(string), ShouldEqual, "val2")
  134. So(column[24].(string), ShouldEqual, "a,b")
  135. So(column[25].(time.Time).Format("2006-01-02T00:00:00Z"), ShouldEqual, time.Now().UTC().Format("2006-01-02T00:00:00Z"))
  136. So(column[26].(float64), ShouldEqual, float64(1.514764861123456*1e12))
  137. So(column[27], ShouldEqual, nil)
  138. So(column[28], ShouldEqual, nil)
  139. So(column[29], ShouldEqual, "")
  140. So(column[30], ShouldEqual, nil)
  141. })
  142. })
  143. Convey("Given a table with metrics that lacks data for some series ", func() {
  144. type metric struct {
  145. Time time.Time
  146. Value int64
  147. }
  148. if exist, err := sess.IsTableExist(metric{}); err != nil || exist {
  149. So(err, ShouldBeNil)
  150. sess.DropTable(metric{})
  151. }
  152. err := sess.CreateTable(metric{})
  153. So(err, ShouldBeNil)
  154. series := []*metric{}
  155. firstRange := genTimeRangeByInterval(fromStart, 10*time.Minute, 10*time.Second)
  156. secondRange := genTimeRangeByInterval(fromStart.Add(20*time.Minute), 10*time.Minute, 10*time.Second)
  157. for _, t := range firstRange {
  158. series = append(series, &metric{
  159. Time: t,
  160. Value: 15,
  161. })
  162. }
  163. for _, t := range secondRange {
  164. series = append(series, &metric{
  165. Time: t,
  166. Value: 20,
  167. })
  168. }
  169. _, err = sess.InsertMulti(series)
  170. So(err, ShouldBeNil)
  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 `xorm:"datetime 'time' not null"`
  275. TimeNullable *time.Time `xorm:"datetime(6) 'timeNullable' null"`
  276. TimeInt64 int64 `xorm:"bigint(20) 'timeInt64' not null"`
  277. TimeInt64Nullable *int64 `xorm:"bigint(20) 'timeInt64Nullable' null"`
  278. TimeFloat64 float64 `xorm:"double 'timeFloat64' not null"`
  279. TimeFloat64Nullable *float64 `xorm:"double 'timeFloat64Nullable' null"`
  280. TimeInt32 int32 `xorm:"int(11) 'timeInt32' not null"`
  281. TimeInt32Nullable *int32 `xorm:"int(11) 'timeInt32Nullable' null"`
  282. TimeFloat32 float32 `xorm:"double 'timeFloat32' not null"`
  283. TimeFloat32Nullable *float32 `xorm:"double 'timeFloat32Nullable' null"`
  284. Measurement string
  285. ValueOne int64 `xorm:"integer 'valueOne'"`
  286. ValueTwo int64 `xorm:"integer 'valueTwo'"`
  287. }
  288. if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
  289. So(err, ShouldBeNil)
  290. sess.DropTable(metric_values{})
  291. }
  292. err := sess.CreateTable(metric_values{})
  293. So(err, ShouldBeNil)
  294. rand.Seed(time.Now().Unix())
  295. rnd := func(min, max int64) int64 {
  296. return rand.Int63n(max-min) + min
  297. }
  298. var tInitial time.Time
  299. series := []*metric_values{}
  300. for i, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
  301. if i == 0 {
  302. tInitial = t
  303. }
  304. tSeconds := t.Unix()
  305. tSecondsInt32 := int32(tSeconds)
  306. tSecondsFloat32 := float32(tSeconds)
  307. tMilliseconds := tSeconds * 1e3
  308. tMillisecondsFloat := float64(tMilliseconds)
  309. t2 := t
  310. first := metric_values{
  311. Time: t,
  312. TimeNullable: &t2,
  313. TimeInt64: tMilliseconds,
  314. TimeInt64Nullable: &(tMilliseconds),
  315. TimeFloat64: tMillisecondsFloat,
  316. TimeFloat64Nullable: &tMillisecondsFloat,
  317. TimeInt32: tSecondsInt32,
  318. TimeInt32Nullable: &tSecondsInt32,
  319. TimeFloat32: tSecondsFloat32,
  320. TimeFloat32Nullable: &tSecondsFloat32,
  321. Measurement: "Metric A",
  322. ValueOne: rnd(0, 100),
  323. ValueTwo: rnd(0, 100),
  324. }
  325. second := first
  326. second.Measurement = "Metric B"
  327. second.ValueOne = rnd(0, 100)
  328. second.ValueTwo = rnd(0, 100)
  329. series = append(series, &first)
  330. series = append(series, &second)
  331. }
  332. _, err = sess.InsertMulti(series)
  333. So(err, ShouldBeNil)
  334. Convey("When doing a metric query using time as time column should return metric with time in milliseconds", func() {
  335. query := &tsdb.TsdbQuery{
  336. Queries: []*tsdb.Query{
  337. {
  338. Model: simplejson.NewFromAny(map[string]interface{}{
  339. "rawSql": `SELECT time, valueOne FROM metric_values ORDER BY time LIMIT 1`,
  340. "format": "time_series",
  341. }),
  342. RefId: "A",
  343. },
  344. },
  345. }
  346. resp, err := endpoint.Query(nil, nil, query)
  347. So(err, ShouldBeNil)
  348. queryResult := resp.Results["A"]
  349. So(queryResult.Error, ShouldBeNil)
  350. So(len(queryResult.Series), ShouldEqual, 1)
  351. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  352. })
  353. Convey("When doing a metric query using time (nullable) as time column should return metric with time in milliseconds", func() {
  354. query := &tsdb.TsdbQuery{
  355. Queries: []*tsdb.Query{
  356. {
  357. Model: simplejson.NewFromAny(map[string]interface{}{
  358. "rawSql": `SELECT timeNullable as time, valueOne FROM metric_values ORDER BY time LIMIT 1`,
  359. "format": "time_series",
  360. }),
  361. RefId: "A",
  362. },
  363. },
  364. }
  365. resp, err := endpoint.Query(nil, nil, query)
  366. So(err, ShouldBeNil)
  367. queryResult := resp.Results["A"]
  368. So(queryResult.Error, ShouldBeNil)
  369. So(len(queryResult.Series), ShouldEqual, 1)
  370. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  371. })
  372. Convey("When doing a metric query using epoch (int64) as time column should return metric with time in milliseconds", func() {
  373. query := &tsdb.TsdbQuery{
  374. Queries: []*tsdb.Query{
  375. {
  376. Model: simplejson.NewFromAny(map[string]interface{}{
  377. "rawSql": `SELECT timeInt64 as time, valueOne FROM metric_values ORDER BY time LIMIT 1`,
  378. "format": "time_series",
  379. }),
  380. RefId: "A",
  381. },
  382. },
  383. }
  384. resp, err := endpoint.Query(nil, nil, query)
  385. So(err, ShouldBeNil)
  386. queryResult := resp.Results["A"]
  387. So(queryResult.Error, ShouldBeNil)
  388. So(len(queryResult.Series), ShouldEqual, 1)
  389. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  390. })
  391. Convey("When doing a metric query using epoch (int64 nullable) as time column should return metric with time in milliseconds", func() {
  392. query := &tsdb.TsdbQuery{
  393. Queries: []*tsdb.Query{
  394. {
  395. Model: simplejson.NewFromAny(map[string]interface{}{
  396. "rawSql": `SELECT timeInt64Nullable as time, valueOne FROM metric_values ORDER BY time LIMIT 1`,
  397. "format": "time_series",
  398. }),
  399. RefId: "A",
  400. },
  401. },
  402. }
  403. resp, err := endpoint.Query(nil, nil, query)
  404. So(err, ShouldBeNil)
  405. queryResult := resp.Results["A"]
  406. So(queryResult.Error, ShouldBeNil)
  407. So(len(queryResult.Series), ShouldEqual, 1)
  408. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  409. })
  410. Convey("When doing a metric query using epoch (float64) as time column should return metric with time in milliseconds", func() {
  411. query := &tsdb.TsdbQuery{
  412. Queries: []*tsdb.Query{
  413. {
  414. Model: simplejson.NewFromAny(map[string]interface{}{
  415. "rawSql": `SELECT timeFloat64 as time, valueOne FROM metric_values ORDER BY time LIMIT 1`,
  416. "format": "time_series",
  417. }),
  418. RefId: "A",
  419. },
  420. },
  421. }
  422. resp, err := endpoint.Query(nil, nil, query)
  423. So(err, ShouldBeNil)
  424. queryResult := resp.Results["A"]
  425. So(queryResult.Error, ShouldBeNil)
  426. So(len(queryResult.Series), ShouldEqual, 1)
  427. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  428. })
  429. Convey("When doing a metric query using epoch (float64 nullable) as time column should return metric with time in milliseconds", func() {
  430. query := &tsdb.TsdbQuery{
  431. Queries: []*tsdb.Query{
  432. {
  433. Model: simplejson.NewFromAny(map[string]interface{}{
  434. "rawSql": `SELECT timeFloat64Nullable as time, valueOne FROM metric_values ORDER BY time LIMIT 1`,
  435. "format": "time_series",
  436. }),
  437. RefId: "A",
  438. },
  439. },
  440. }
  441. resp, err := endpoint.Query(nil, nil, query)
  442. So(err, ShouldBeNil)
  443. queryResult := resp.Results["A"]
  444. So(queryResult.Error, ShouldBeNil)
  445. So(len(queryResult.Series), ShouldEqual, 1)
  446. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  447. })
  448. Convey("When doing a metric query using epoch (int32) as time column should return metric with time in milliseconds", func() {
  449. query := &tsdb.TsdbQuery{
  450. Queries: []*tsdb.Query{
  451. {
  452. Model: simplejson.NewFromAny(map[string]interface{}{
  453. "rawSql": `SELECT timeInt32 as time, valueOne FROM metric_values ORDER BY time LIMIT 1`,
  454. "format": "time_series",
  455. }),
  456. RefId: "A",
  457. },
  458. },
  459. }
  460. resp, err := endpoint.Query(nil, nil, query)
  461. So(err, ShouldBeNil)
  462. queryResult := resp.Results["A"]
  463. So(queryResult.Error, ShouldBeNil)
  464. So(len(queryResult.Series), ShouldEqual, 1)
  465. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  466. })
  467. Convey("When doing a metric query using epoch (int32 nullable) as time column should return metric with time in milliseconds", func() {
  468. query := &tsdb.TsdbQuery{
  469. Queries: []*tsdb.Query{
  470. {
  471. Model: simplejson.NewFromAny(map[string]interface{}{
  472. "rawSql": `SELECT timeInt32Nullable as time, valueOne FROM metric_values ORDER BY time LIMIT 1`,
  473. "format": "time_series",
  474. }),
  475. RefId: "A",
  476. },
  477. },
  478. }
  479. resp, err := endpoint.Query(nil, nil, query)
  480. So(err, ShouldBeNil)
  481. queryResult := resp.Results["A"]
  482. So(queryResult.Error, ShouldBeNil)
  483. So(len(queryResult.Series), ShouldEqual, 1)
  484. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  485. })
  486. Convey("When doing a metric query using epoch (float32) as time column should return metric with time in milliseconds", func() {
  487. query := &tsdb.TsdbQuery{
  488. Queries: []*tsdb.Query{
  489. {
  490. Model: simplejson.NewFromAny(map[string]interface{}{
  491. "rawSql": `SELECT timeFloat32 as time, valueOne FROM metric_values ORDER BY time LIMIT 1`,
  492. "format": "time_series",
  493. }),
  494. RefId: "A",
  495. },
  496. },
  497. }
  498. resp, err := endpoint.Query(nil, nil, query)
  499. So(err, ShouldBeNil)
  500. queryResult := resp.Results["A"]
  501. So(queryResult.Error, ShouldBeNil)
  502. So(len(queryResult.Series), ShouldEqual, 1)
  503. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
  504. })
  505. Convey("When doing a metric query using epoch (float32 nullable) as time column should return metric with time in milliseconds", func() {
  506. query := &tsdb.TsdbQuery{
  507. Queries: []*tsdb.Query{
  508. {
  509. Model: simplejson.NewFromAny(map[string]interface{}{
  510. "rawSql": `SELECT timeFloat32Nullable as time, valueOne FROM metric_values ORDER BY time LIMIT 1`,
  511. "format": "time_series",
  512. }),
  513. RefId: "A",
  514. },
  515. },
  516. }
  517. resp, err := endpoint.Query(nil, nil, query)
  518. So(err, ShouldBeNil)
  519. queryResult := resp.Results["A"]
  520. So(queryResult.Error, ShouldBeNil)
  521. So(len(queryResult.Series), ShouldEqual, 1)
  522. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
  523. })
  524. Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
  525. query := &tsdb.TsdbQuery{
  526. Queries: []*tsdb.Query{
  527. {
  528. Model: simplejson.NewFromAny(map[string]interface{}{
  529. "rawSql": `SELECT $__time(time), CONCAT(measurement, ' - value one') as metric, valueOne FROM metric_values ORDER BY 1`,
  530. "format": "time_series",
  531. }),
  532. RefId: "A",
  533. },
  534. },
  535. }
  536. resp, err := endpoint.Query(nil, nil, query)
  537. So(err, ShouldBeNil)
  538. queryResult := resp.Results["A"]
  539. So(queryResult.Error, ShouldBeNil)
  540. So(len(queryResult.Series), ShouldEqual, 2)
  541. So(queryResult.Series[0].Name, ShouldEqual, "Metric B - value one")
  542. So(queryResult.Series[1].Name, ShouldEqual, "Metric A - value one")
  543. })
  544. Convey("When doing a metric query grouping by time should return correct series", func() {
  545. query := &tsdb.TsdbQuery{
  546. Queries: []*tsdb.Query{
  547. {
  548. Model: simplejson.NewFromAny(map[string]interface{}{
  549. "rawSql": `SELECT $__time(time), valueOne, valueTwo FROM metric_values ORDER BY 1`,
  550. "format": "time_series",
  551. }),
  552. RefId: "A",
  553. },
  554. },
  555. }
  556. resp, err := endpoint.Query(nil, nil, query)
  557. So(err, ShouldBeNil)
  558. queryResult := resp.Results["A"]
  559. So(queryResult.Error, ShouldBeNil)
  560. So(len(queryResult.Series), ShouldEqual, 2)
  561. So(queryResult.Series[0].Name, ShouldEqual, "valueOne")
  562. So(queryResult.Series[1].Name, ShouldEqual, "valueTwo")
  563. })
  564. })
  565. Convey("Given a table with event data", func() {
  566. type event struct {
  567. TimeSec int64
  568. Description string
  569. Tags string
  570. }
  571. if exist, err := sess.IsTableExist(event{}); err != nil || exist {
  572. So(err, ShouldBeNil)
  573. sess.DropTable(event{})
  574. }
  575. err := sess.CreateTable(event{})
  576. So(err, ShouldBeNil)
  577. events := []*event{}
  578. for _, t := range genTimeRangeByInterval(fromStart.Add(-20*time.Minute), 60*time.Minute, 25*time.Minute) {
  579. events = append(events, &event{
  580. TimeSec: t.Unix(),
  581. Description: "Someone deployed something",
  582. Tags: "deploy",
  583. })
  584. events = append(events, &event{
  585. TimeSec: t.Add(5 * time.Minute).Unix(),
  586. Description: "New support ticket registered",
  587. Tags: "ticket",
  588. })
  589. }
  590. for _, e := range events {
  591. _, err = sess.Insert(e)
  592. So(err, ShouldBeNil)
  593. }
  594. Convey("When doing an annotation query of deploy events should return expected result", func() {
  595. query := &tsdb.TsdbQuery{
  596. Queries: []*tsdb.Query{
  597. {
  598. Model: simplejson.NewFromAny(map[string]interface{}{
  599. "rawSql": `SELECT time_sec, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC`,
  600. "format": "table",
  601. }),
  602. RefId: "Deploys",
  603. },
  604. },
  605. TimeRange: &tsdb.TimeRange{
  606. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  607. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  608. },
  609. }
  610. resp, err := endpoint.Query(nil, nil, query)
  611. queryResult := resp.Results["Deploys"]
  612. So(err, ShouldBeNil)
  613. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  614. })
  615. Convey("When doing an annotation query of ticket events should return expected result", func() {
  616. query := &tsdb.TsdbQuery{
  617. Queries: []*tsdb.Query{
  618. {
  619. Model: simplejson.NewFromAny(map[string]interface{}{
  620. "rawSql": `SELECT time_sec, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC`,
  621. "format": "table",
  622. }),
  623. RefId: "Tickets",
  624. },
  625. },
  626. TimeRange: &tsdb.TimeRange{
  627. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  628. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  629. },
  630. }
  631. resp, err := endpoint.Query(nil, nil, query)
  632. queryResult := resp.Results["Tickets"]
  633. So(err, ShouldBeNil)
  634. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  635. })
  636. Convey("When doing an annotation query with a time column in datetime format", func() {
  637. dt := time.Date(2018, 3, 14, 21, 20, 6, 0, time.UTC)
  638. dtFormat := "2006-01-02 15:04:05.999999999"
  639. query := &tsdb.TsdbQuery{
  640. Queries: []*tsdb.Query{
  641. {
  642. Model: simplejson.NewFromAny(map[string]interface{}{
  643. "rawSql": fmt.Sprintf(`SELECT
  644. CAST('%s' as datetime) as time_sec,
  645. 'message' as text,
  646. 'tag1,tag2' as tags
  647. `, dt.Format(dtFormat)),
  648. "format": "table",
  649. }),
  650. RefId: "A",
  651. },
  652. },
  653. }
  654. resp, err := endpoint.Query(nil, nil, query)
  655. So(err, ShouldBeNil)
  656. queryResult := resp.Results["A"]
  657. So(queryResult.Error, ShouldBeNil)
  658. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  659. columns := queryResult.Tables[0].Rows[0]
  660. //Should be in milliseconds
  661. So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000))
  662. })
  663. Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
  664. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  665. query := &tsdb.TsdbQuery{
  666. Queries: []*tsdb.Query{
  667. {
  668. Model: simplejson.NewFromAny(map[string]interface{}{
  669. "rawSql": fmt.Sprintf(`SELECT
  670. %d as time_sec,
  671. 'message' as text,
  672. 'tag1,tag2' as tags
  673. `, dt.Unix()),
  674. "format": "table",
  675. }),
  676. RefId: "A",
  677. },
  678. },
  679. }
  680. resp, err := endpoint.Query(nil, nil, query)
  681. So(err, ShouldBeNil)
  682. queryResult := resp.Results["A"]
  683. So(queryResult.Error, ShouldBeNil)
  684. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  685. columns := queryResult.Tables[0].Rows[0]
  686. //Should be in milliseconds
  687. So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
  688. })
  689. Convey("When doing an annotation query with a time column in epoch second format (signed integer) should return ms", func() {
  690. dt := time.Date(2018, 3, 14, 21, 20, 6, 0, time.Local)
  691. query := &tsdb.TsdbQuery{
  692. Queries: []*tsdb.Query{
  693. {
  694. Model: simplejson.NewFromAny(map[string]interface{}{
  695. "rawSql": fmt.Sprintf(`SELECT
  696. CAST('%d' as signed integer) as time_sec,
  697. 'message' as text,
  698. 'tag1,tag2' as tags
  699. `, dt.Unix()),
  700. "format": "table",
  701. }),
  702. RefId: "A",
  703. },
  704. },
  705. }
  706. resp, err := endpoint.Query(nil, nil, query)
  707. So(err, ShouldBeNil)
  708. queryResult := resp.Results["A"]
  709. So(queryResult.Error, ShouldBeNil)
  710. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  711. columns := queryResult.Tables[0].Rows[0]
  712. //Should be in milliseconds
  713. So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
  714. })
  715. Convey("When doing an annotation query with a time column in epoch millisecond format should return ms", func() {
  716. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  717. query := &tsdb.TsdbQuery{
  718. Queries: []*tsdb.Query{
  719. {
  720. Model: simplejson.NewFromAny(map[string]interface{}{
  721. "rawSql": fmt.Sprintf(`SELECT
  722. %d as time_sec,
  723. 'message' as text,
  724. 'tag1,tag2' as tags
  725. `, dt.Unix()*1000),
  726. "format": "table",
  727. }),
  728. RefId: "A",
  729. },
  730. },
  731. }
  732. resp, err := endpoint.Query(nil, nil, query)
  733. So(err, ShouldBeNil)
  734. queryResult := resp.Results["A"]
  735. So(queryResult.Error, ShouldBeNil)
  736. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  737. columns := queryResult.Tables[0].Rows[0]
  738. //Should be in milliseconds
  739. So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
  740. })
  741. Convey("When doing an annotation query with a time column holding a unsigned integer null value should return nil", func() {
  742. query := &tsdb.TsdbQuery{
  743. Queries: []*tsdb.Query{
  744. {
  745. Model: simplejson.NewFromAny(map[string]interface{}{
  746. "rawSql": `SELECT
  747. cast(null as unsigned integer) as time_sec,
  748. 'message' as text,
  749. 'tag1,tag2' as tags
  750. `,
  751. "format": "table",
  752. }),
  753. RefId: "A",
  754. },
  755. },
  756. }
  757. resp, err := endpoint.Query(nil, nil, query)
  758. So(err, ShouldBeNil)
  759. queryResult := resp.Results["A"]
  760. So(queryResult.Error, ShouldBeNil)
  761. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  762. columns := queryResult.Tables[0].Rows[0]
  763. //Should be in milliseconds
  764. So(columns[0], ShouldBeNil)
  765. })
  766. Convey("When doing an annotation query with a time column holding a DATETIME null value should return nil", func() {
  767. query := &tsdb.TsdbQuery{
  768. Queries: []*tsdb.Query{
  769. {
  770. Model: simplejson.NewFromAny(map[string]interface{}{
  771. "rawSql": `SELECT
  772. cast(null as DATETIME) as time_sec,
  773. 'message' as text,
  774. 'tag1,tag2' as tags
  775. `,
  776. "format": "table",
  777. }),
  778. RefId: "A",
  779. },
  780. },
  781. }
  782. resp, err := endpoint.Query(nil, nil, query)
  783. So(err, ShouldBeNil)
  784. queryResult := resp.Results["A"]
  785. So(queryResult.Error, ShouldBeNil)
  786. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  787. columns := queryResult.Tables[0].Rows[0]
  788. //Should be in milliseconds
  789. So(columns[0], ShouldBeNil)
  790. })
  791. })
  792. })
  793. }
  794. func InitMySQLTestDB(t *testing.T) *xorm.Engine {
  795. x, err := xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, strings.Replace(sqlutil.TestDB_Mysql.ConnStr, "/grafana_tests", "/grafana_ds_tests", 1))
  796. if err != nil {
  797. t.Fatalf("Failed to init mysql db %v", err)
  798. }
  799. x.DatabaseTZ = time.UTC
  800. x.TZLocation = time.UTC
  801. // x.ShowSQL()
  802. return x
  803. }
  804. func genTimeRangeByInterval(from time.Time, duration time.Duration, interval time.Duration) []time.Time {
  805. durationSec := int64(duration.Seconds())
  806. intervalSec := int64(interval.Seconds())
  807. timeRange := []time.Time{}
  808. for i := int64(0); i < durationSec; i += intervalSec {
  809. timeRange = append(timeRange, from)
  810. from = from.Add(time.Duration(int64(time.Second) * intervalSec))
  811. }
  812. return timeRange
  813. }