mysql_test.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  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. // without fill this should result in 4 buckets
  189. So(len(points), ShouldEqual, 4)
  190. dt := fromStart
  191. for i := 0; i < 2; i++ {
  192. aValue := points[i][0].Float64
  193. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  194. So(aValue, ShouldEqual, 15)
  195. So(aTime, ShouldEqual, dt)
  196. dt = dt.Add(5 * time.Minute)
  197. }
  198. // adjust for 10 minute gap between first and second set of points
  199. dt = dt.Add(10 * time.Minute)
  200. for i := 2; i < 4; i++ {
  201. aValue := points[i][0].Float64
  202. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  203. So(aValue, ShouldEqual, 20)
  204. So(aTime, ShouldEqual, dt)
  205. dt = dt.Add(5 * time.Minute)
  206. }
  207. })
  208. Convey("When doing a metric query using timeGroup with NULL fill enabled", func() {
  209. query := &tsdb.TsdbQuery{
  210. Queries: []*tsdb.Query{
  211. {
  212. Model: simplejson.NewFromAny(map[string]interface{}{
  213. "rawSql": "SELECT $__timeGroup(time, '5m', NULL) as time_sec, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
  214. "format": "time_series",
  215. }),
  216. RefId: "A",
  217. },
  218. },
  219. TimeRange: &tsdb.TimeRange{
  220. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  221. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  222. },
  223. }
  224. resp, err := endpoint.Query(nil, nil, query)
  225. So(err, ShouldBeNil)
  226. queryResult := resp.Results["A"]
  227. So(queryResult.Error, ShouldBeNil)
  228. points := queryResult.Series[0].Points
  229. So(len(points), ShouldEqual, 7)
  230. dt := fromStart
  231. for i := 0; i < 2; i++ {
  232. aValue := points[i][0].Float64
  233. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  234. So(aValue, ShouldEqual, 15)
  235. So(aTime, ShouldEqual, dt)
  236. dt = dt.Add(5 * time.Minute)
  237. }
  238. // check for NULL values inserted by fill
  239. So(points[2][0].Valid, ShouldBeFalse)
  240. So(points[3][0].Valid, ShouldBeFalse)
  241. // adjust for 10 minute gap between first and second set of points
  242. dt = dt.Add(10 * time.Minute)
  243. for i := 4; i < 6; i++ {
  244. aValue := points[i][0].Float64
  245. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  246. So(aValue, ShouldEqual, 20)
  247. So(aTime, ShouldEqual, dt)
  248. dt = dt.Add(5 * time.Minute)
  249. }
  250. // check for NULL values inserted by fill
  251. So(points[6][0].Valid, ShouldBeFalse)
  252. })
  253. Convey("When doing a metric query using timeGroup with float fill enabled", func() {
  254. query := &tsdb.TsdbQuery{
  255. Queries: []*tsdb.Query{
  256. {
  257. Model: simplejson.NewFromAny(map[string]interface{}{
  258. "rawSql": "SELECT $__timeGroup(time, '5m', 1.5) as time_sec, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
  259. "format": "time_series",
  260. }),
  261. RefId: "A",
  262. },
  263. },
  264. TimeRange: &tsdb.TimeRange{
  265. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  266. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  267. },
  268. }
  269. resp, err := endpoint.Query(nil, nil, query)
  270. So(err, ShouldBeNil)
  271. queryResult := resp.Results["A"]
  272. So(queryResult.Error, ShouldBeNil)
  273. points := queryResult.Series[0].Points
  274. So(points[3][0].Float64, ShouldEqual, 1.5)
  275. })
  276. })
  277. Convey("Given a table with metrics having multiple values and measurements", func() {
  278. type metric_values struct {
  279. Time time.Time `xorm:"datetime 'time' not null"`
  280. TimeNullable *time.Time `xorm:"datetime(6) 'timeNullable' null"`
  281. TimeInt64 int64 `xorm:"bigint(20) 'timeInt64' not null"`
  282. TimeInt64Nullable *int64 `xorm:"bigint(20) 'timeInt64Nullable' null"`
  283. TimeFloat64 float64 `xorm:"double 'timeFloat64' not null"`
  284. TimeFloat64Nullable *float64 `xorm:"double 'timeFloat64Nullable' null"`
  285. TimeInt32 int32 `xorm:"int(11) 'timeInt32' not null"`
  286. TimeInt32Nullable *int32 `xorm:"int(11) 'timeInt32Nullable' null"`
  287. TimeFloat32 float32 `xorm:"double 'timeFloat32' not null"`
  288. TimeFloat32Nullable *float32 `xorm:"double 'timeFloat32Nullable' null"`
  289. Measurement string
  290. ValueOne int64 `xorm:"integer 'valueOne'"`
  291. ValueTwo int64 `xorm:"integer 'valueTwo'"`
  292. }
  293. if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
  294. So(err, ShouldBeNil)
  295. sess.DropTable(metric_values{})
  296. }
  297. err := sess.CreateTable(metric_values{})
  298. So(err, ShouldBeNil)
  299. rand.Seed(time.Now().Unix())
  300. rnd := func(min, max int64) int64 {
  301. return rand.Int63n(max-min) + min
  302. }
  303. var tInitial time.Time
  304. series := []*metric_values{}
  305. for i, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
  306. if i == 0 {
  307. tInitial = t
  308. }
  309. tSeconds := t.Unix()
  310. tSecondsInt32 := int32(tSeconds)
  311. tSecondsFloat32 := float32(tSeconds)
  312. tMilliseconds := tSeconds * 1e3
  313. tMillisecondsFloat := float64(tMilliseconds)
  314. t2 := t
  315. first := metric_values{
  316. Time: t,
  317. TimeNullable: &t2,
  318. TimeInt64: tMilliseconds,
  319. TimeInt64Nullable: &(tMilliseconds),
  320. TimeFloat64: tMillisecondsFloat,
  321. TimeFloat64Nullable: &tMillisecondsFloat,
  322. TimeInt32: tSecondsInt32,
  323. TimeInt32Nullable: &tSecondsInt32,
  324. TimeFloat32: tSecondsFloat32,
  325. TimeFloat32Nullable: &tSecondsFloat32,
  326. Measurement: "Metric A",
  327. ValueOne: rnd(0, 100),
  328. ValueTwo: rnd(0, 100),
  329. }
  330. second := first
  331. second.Measurement = "Metric B"
  332. second.ValueOne = rnd(0, 100)
  333. second.ValueTwo = rnd(0, 100)
  334. series = append(series, &first)
  335. series = append(series, &second)
  336. }
  337. _, err = sess.InsertMulti(series)
  338. So(err, ShouldBeNil)
  339. Convey("When doing a metric query using time as time column should return metric with time in milliseconds", func() {
  340. query := &tsdb.TsdbQuery{
  341. Queries: []*tsdb.Query{
  342. {
  343. Model: simplejson.NewFromAny(map[string]interface{}{
  344. "rawSql": `SELECT time, valueOne FROM metric_values ORDER BY time LIMIT 1`,
  345. "format": "time_series",
  346. }),
  347. RefId: "A",
  348. },
  349. },
  350. }
  351. resp, err := endpoint.Query(nil, nil, query)
  352. So(err, ShouldBeNil)
  353. queryResult := resp.Results["A"]
  354. So(queryResult.Error, ShouldBeNil)
  355. So(len(queryResult.Series), ShouldEqual, 1)
  356. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  357. })
  358. Convey("When doing a metric query using time (nullable) as time column should return metric with time in milliseconds", func() {
  359. query := &tsdb.TsdbQuery{
  360. Queries: []*tsdb.Query{
  361. {
  362. Model: simplejson.NewFromAny(map[string]interface{}{
  363. "rawSql": `SELECT timeNullable as time, valueOne FROM metric_values ORDER BY time LIMIT 1`,
  364. "format": "time_series",
  365. }),
  366. RefId: "A",
  367. },
  368. },
  369. }
  370. resp, err := endpoint.Query(nil, nil, query)
  371. So(err, ShouldBeNil)
  372. queryResult := resp.Results["A"]
  373. So(queryResult.Error, ShouldBeNil)
  374. So(len(queryResult.Series), ShouldEqual, 1)
  375. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  376. })
  377. Convey("When doing a metric query using epoch (int64) as time column and value column (int64) should return metric with time in milliseconds", func() {
  378. query := &tsdb.TsdbQuery{
  379. Queries: []*tsdb.Query{
  380. {
  381. Model: simplejson.NewFromAny(map[string]interface{}{
  382. "rawSql": `SELECT timeInt64 as time, timeInt64 FROM metric_values ORDER BY time LIMIT 1`,
  383. "format": "time_series",
  384. }),
  385. RefId: "A",
  386. },
  387. },
  388. }
  389. resp, err := endpoint.Query(nil, nil, query)
  390. So(err, ShouldBeNil)
  391. queryResult := resp.Results["A"]
  392. So(queryResult.Error, ShouldBeNil)
  393. So(len(queryResult.Series), ShouldEqual, 1)
  394. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  395. })
  396. Convey("When doing a metric query using epoch (int64 nullable) as time column and value column (int64 nullable) should return metric with time in milliseconds", func() {
  397. query := &tsdb.TsdbQuery{
  398. Queries: []*tsdb.Query{
  399. {
  400. Model: simplejson.NewFromAny(map[string]interface{}{
  401. "rawSql": `SELECT timeInt64Nullable as time, timeInt64Nullable FROM metric_values ORDER BY time LIMIT 1`,
  402. "format": "time_series",
  403. }),
  404. RefId: "A",
  405. },
  406. },
  407. }
  408. resp, err := endpoint.Query(nil, nil, query)
  409. So(err, ShouldBeNil)
  410. queryResult := resp.Results["A"]
  411. So(queryResult.Error, ShouldBeNil)
  412. So(len(queryResult.Series), ShouldEqual, 1)
  413. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  414. })
  415. Convey("When doing a metric query using epoch (float64) as time column and value column (float64) should return metric with time in milliseconds", func() {
  416. query := &tsdb.TsdbQuery{
  417. Queries: []*tsdb.Query{
  418. {
  419. Model: simplejson.NewFromAny(map[string]interface{}{
  420. "rawSql": `SELECT timeFloat64 as time, timeFloat64 FROM metric_values ORDER BY time LIMIT 1`,
  421. "format": "time_series",
  422. }),
  423. RefId: "A",
  424. },
  425. },
  426. }
  427. resp, err := endpoint.Query(nil, nil, query)
  428. So(err, ShouldBeNil)
  429. queryResult := resp.Results["A"]
  430. So(queryResult.Error, ShouldBeNil)
  431. So(len(queryResult.Series), ShouldEqual, 1)
  432. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  433. })
  434. Convey("When doing a metric query using epoch (float64 nullable) as time column and value column (float64 nullable) should return metric with time in milliseconds", func() {
  435. query := &tsdb.TsdbQuery{
  436. Queries: []*tsdb.Query{
  437. {
  438. Model: simplejson.NewFromAny(map[string]interface{}{
  439. "rawSql": `SELECT timeFloat64Nullable as time, timeFloat64Nullable FROM metric_values ORDER BY time LIMIT 1`,
  440. "format": "time_series",
  441. }),
  442. RefId: "A",
  443. },
  444. },
  445. }
  446. resp, err := endpoint.Query(nil, nil, query)
  447. So(err, ShouldBeNil)
  448. queryResult := resp.Results["A"]
  449. So(queryResult.Error, ShouldBeNil)
  450. So(len(queryResult.Series), ShouldEqual, 1)
  451. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  452. })
  453. FocusConvey("When doing a metric query using epoch (int32) as time column and value column (int32) should return metric with time in milliseconds", func() {
  454. query := &tsdb.TsdbQuery{
  455. Queries: []*tsdb.Query{
  456. {
  457. Model: simplejson.NewFromAny(map[string]interface{}{
  458. "rawSql": `SELECT timeInt32 as time, timeInt32 FROM metric_values ORDER BY time LIMIT 1`,
  459. "format": "time_series",
  460. }),
  461. RefId: "A",
  462. },
  463. },
  464. }
  465. resp, err := endpoint.Query(nil, nil, query)
  466. So(err, ShouldBeNil)
  467. queryResult := resp.Results["A"]
  468. So(queryResult.Error, ShouldBeNil)
  469. So(len(queryResult.Series), ShouldEqual, 1)
  470. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  471. })
  472. Convey("When doing a metric query using epoch (int32 nullable) as time column and value column (int32 nullable) should return metric with time in milliseconds", func() {
  473. query := &tsdb.TsdbQuery{
  474. Queries: []*tsdb.Query{
  475. {
  476. Model: simplejson.NewFromAny(map[string]interface{}{
  477. "rawSql": `SELECT timeInt32Nullable as time, timeInt32Nullable FROM metric_values ORDER BY time LIMIT 1`,
  478. "format": "time_series",
  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.Series), ShouldEqual, 1)
  489. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  490. })
  491. Convey("When doing a metric query using epoch (float32) as time column and value column (float32) should return metric with time in milliseconds", func() {
  492. query := &tsdb.TsdbQuery{
  493. Queries: []*tsdb.Query{
  494. {
  495. Model: simplejson.NewFromAny(map[string]interface{}{
  496. "rawSql": `SELECT timeFloat32 as time, timeFloat32 FROM metric_values ORDER BY time LIMIT 1`,
  497. "format": "time_series",
  498. }),
  499. RefId: "A",
  500. },
  501. },
  502. }
  503. resp, err := endpoint.Query(nil, nil, query)
  504. So(err, ShouldBeNil)
  505. queryResult := resp.Results["A"]
  506. So(queryResult.Error, ShouldBeNil)
  507. So(len(queryResult.Series), ShouldEqual, 1)
  508. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
  509. })
  510. Convey("When doing a metric query using epoch (float32 nullable) as time column and value column (float32 nullable) should return metric with time in milliseconds", func() {
  511. query := &tsdb.TsdbQuery{
  512. Queries: []*tsdb.Query{
  513. {
  514. Model: simplejson.NewFromAny(map[string]interface{}{
  515. "rawSql": `SELECT timeFloat32Nullable as time, timeFloat32Nullable FROM metric_values ORDER BY time LIMIT 1`,
  516. "format": "time_series",
  517. }),
  518. RefId: "A",
  519. },
  520. },
  521. }
  522. resp, err := endpoint.Query(nil, nil, query)
  523. So(err, ShouldBeNil)
  524. queryResult := resp.Results["A"]
  525. So(queryResult.Error, ShouldBeNil)
  526. So(len(queryResult.Series), ShouldEqual, 1)
  527. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
  528. })
  529. Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
  530. query := &tsdb.TsdbQuery{
  531. Queries: []*tsdb.Query{
  532. {
  533. Model: simplejson.NewFromAny(map[string]interface{}{
  534. "rawSql": `SELECT $__time(time), CONCAT(measurement, ' - value one') as metric, valueOne FROM metric_values ORDER BY 1,2`,
  535. "format": "time_series",
  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.Series), ShouldEqual, 2)
  546. So(queryResult.Series[0].Name, ShouldEqual, "Metric A - value one")
  547. So(queryResult.Series[1].Name, ShouldEqual, "Metric B - value one")
  548. })
  549. Convey("When doing a metric query grouping by time should return correct series", func() {
  550. query := &tsdb.TsdbQuery{
  551. Queries: []*tsdb.Query{
  552. {
  553. Model: simplejson.NewFromAny(map[string]interface{}{
  554. "rawSql": `SELECT $__time(time), valueOne, valueTwo FROM metric_values ORDER BY 1`,
  555. "format": "time_series",
  556. }),
  557. RefId: "A",
  558. },
  559. },
  560. }
  561. resp, err := endpoint.Query(nil, nil, query)
  562. So(err, ShouldBeNil)
  563. queryResult := resp.Results["A"]
  564. So(queryResult.Error, ShouldBeNil)
  565. So(len(queryResult.Series), ShouldEqual, 2)
  566. So(queryResult.Series[0].Name, ShouldEqual, "valueOne")
  567. So(queryResult.Series[1].Name, ShouldEqual, "valueTwo")
  568. })
  569. })
  570. Convey("Given a table with event data", func() {
  571. type event struct {
  572. TimeSec int64
  573. Description string
  574. Tags string
  575. }
  576. if exist, err := sess.IsTableExist(event{}); err != nil || exist {
  577. So(err, ShouldBeNil)
  578. sess.DropTable(event{})
  579. }
  580. err := sess.CreateTable(event{})
  581. So(err, ShouldBeNil)
  582. events := []*event{}
  583. for _, t := range genTimeRangeByInterval(fromStart.Add(-20*time.Minute), 60*time.Minute, 25*time.Minute) {
  584. events = append(events, &event{
  585. TimeSec: t.Unix(),
  586. Description: "Someone deployed something",
  587. Tags: "deploy",
  588. })
  589. events = append(events, &event{
  590. TimeSec: t.Add(5 * time.Minute).Unix(),
  591. Description: "New support ticket registered",
  592. Tags: "ticket",
  593. })
  594. }
  595. for _, e := range events {
  596. _, err = sess.Insert(e)
  597. So(err, ShouldBeNil)
  598. }
  599. Convey("When doing an annotation query of deploy events should return expected result", func() {
  600. query := &tsdb.TsdbQuery{
  601. Queries: []*tsdb.Query{
  602. {
  603. Model: simplejson.NewFromAny(map[string]interface{}{
  604. "rawSql": `SELECT time_sec, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC`,
  605. "format": "table",
  606. }),
  607. RefId: "Deploys",
  608. },
  609. },
  610. TimeRange: &tsdb.TimeRange{
  611. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  612. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  613. },
  614. }
  615. resp, err := endpoint.Query(nil, nil, query)
  616. queryResult := resp.Results["Deploys"]
  617. So(err, ShouldBeNil)
  618. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  619. })
  620. Convey("When doing an annotation query of ticket events should return expected result", func() {
  621. query := &tsdb.TsdbQuery{
  622. Queries: []*tsdb.Query{
  623. {
  624. Model: simplejson.NewFromAny(map[string]interface{}{
  625. "rawSql": `SELECT time_sec, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC`,
  626. "format": "table",
  627. }),
  628. RefId: "Tickets",
  629. },
  630. },
  631. TimeRange: &tsdb.TimeRange{
  632. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  633. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  634. },
  635. }
  636. resp, err := endpoint.Query(nil, nil, query)
  637. queryResult := resp.Results["Tickets"]
  638. So(err, ShouldBeNil)
  639. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  640. })
  641. Convey("When doing an annotation query with a time column in datetime format", func() {
  642. dt := time.Date(2018, 3, 14, 21, 20, 6, 0, time.UTC)
  643. dtFormat := "2006-01-02 15:04:05.999999999"
  644. query := &tsdb.TsdbQuery{
  645. Queries: []*tsdb.Query{
  646. {
  647. Model: simplejson.NewFromAny(map[string]interface{}{
  648. "rawSql": fmt.Sprintf(`SELECT
  649. CAST('%s' as datetime) as time_sec,
  650. 'message' as text,
  651. 'tag1,tag2' as tags
  652. `, dt.Format(dtFormat)),
  653. "format": "table",
  654. }),
  655. RefId: "A",
  656. },
  657. },
  658. }
  659. resp, err := endpoint.Query(nil, nil, query)
  660. So(err, ShouldBeNil)
  661. queryResult := resp.Results["A"]
  662. So(queryResult.Error, ShouldBeNil)
  663. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  664. columns := queryResult.Tables[0].Rows[0]
  665. //Should be in milliseconds
  666. So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000))
  667. })
  668. Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
  669. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  670. query := &tsdb.TsdbQuery{
  671. Queries: []*tsdb.Query{
  672. {
  673. Model: simplejson.NewFromAny(map[string]interface{}{
  674. "rawSql": fmt.Sprintf(`SELECT
  675. %d as time_sec,
  676. 'message' as text,
  677. 'tag1,tag2' as tags
  678. `, dt.Unix()),
  679. "format": "table",
  680. }),
  681. RefId: "A",
  682. },
  683. },
  684. }
  685. resp, err := endpoint.Query(nil, nil, query)
  686. So(err, ShouldBeNil)
  687. queryResult := resp.Results["A"]
  688. So(queryResult.Error, ShouldBeNil)
  689. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  690. columns := queryResult.Tables[0].Rows[0]
  691. //Should be in milliseconds
  692. So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
  693. })
  694. Convey("When doing an annotation query with a time column in epoch second format (signed integer) should return ms", func() {
  695. dt := time.Date(2018, 3, 14, 21, 20, 6, 0, time.Local)
  696. query := &tsdb.TsdbQuery{
  697. Queries: []*tsdb.Query{
  698. {
  699. Model: simplejson.NewFromAny(map[string]interface{}{
  700. "rawSql": fmt.Sprintf(`SELECT
  701. CAST('%d' as signed integer) as time_sec,
  702. 'message' as text,
  703. 'tag1,tag2' as tags
  704. `, dt.Unix()),
  705. "format": "table",
  706. }),
  707. RefId: "A",
  708. },
  709. },
  710. }
  711. resp, err := endpoint.Query(nil, nil, query)
  712. So(err, ShouldBeNil)
  713. queryResult := resp.Results["A"]
  714. So(queryResult.Error, ShouldBeNil)
  715. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  716. columns := queryResult.Tables[0].Rows[0]
  717. //Should be in milliseconds
  718. So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
  719. })
  720. Convey("When doing an annotation query with a time column in epoch millisecond format should return ms", func() {
  721. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  722. query := &tsdb.TsdbQuery{
  723. Queries: []*tsdb.Query{
  724. {
  725. Model: simplejson.NewFromAny(map[string]interface{}{
  726. "rawSql": fmt.Sprintf(`SELECT
  727. %d as time_sec,
  728. 'message' as text,
  729. 'tag1,tag2' as tags
  730. `, dt.Unix()*1000),
  731. "format": "table",
  732. }),
  733. RefId: "A",
  734. },
  735. },
  736. }
  737. resp, err := endpoint.Query(nil, nil, query)
  738. So(err, ShouldBeNil)
  739. queryResult := resp.Results["A"]
  740. So(queryResult.Error, ShouldBeNil)
  741. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  742. columns := queryResult.Tables[0].Rows[0]
  743. //Should be in milliseconds
  744. So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
  745. })
  746. Convey("When doing an annotation query with a time column holding a unsigned integer null value should return nil", func() {
  747. query := &tsdb.TsdbQuery{
  748. Queries: []*tsdb.Query{
  749. {
  750. Model: simplejson.NewFromAny(map[string]interface{}{
  751. "rawSql": `SELECT
  752. cast(null as unsigned integer) as time_sec,
  753. 'message' as text,
  754. 'tag1,tag2' as tags
  755. `,
  756. "format": "table",
  757. }),
  758. RefId: "A",
  759. },
  760. },
  761. }
  762. resp, err := endpoint.Query(nil, nil, query)
  763. So(err, ShouldBeNil)
  764. queryResult := resp.Results["A"]
  765. So(queryResult.Error, ShouldBeNil)
  766. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  767. columns := queryResult.Tables[0].Rows[0]
  768. //Should be in milliseconds
  769. So(columns[0], ShouldBeNil)
  770. })
  771. Convey("When doing an annotation query with a time column holding a DATETIME null value should return nil", func() {
  772. query := &tsdb.TsdbQuery{
  773. Queries: []*tsdb.Query{
  774. {
  775. Model: simplejson.NewFromAny(map[string]interface{}{
  776. "rawSql": `SELECT
  777. cast(null as DATETIME) as time_sec,
  778. 'message' as text,
  779. 'tag1,tag2' as tags
  780. `,
  781. "format": "table",
  782. }),
  783. RefId: "A",
  784. },
  785. },
  786. }
  787. resp, err := endpoint.Query(nil, nil, query)
  788. So(err, ShouldBeNil)
  789. queryResult := resp.Results["A"]
  790. So(queryResult.Error, ShouldBeNil)
  791. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  792. columns := queryResult.Tables[0].Rows[0]
  793. //Should be in milliseconds
  794. So(columns[0], ShouldBeNil)
  795. })
  796. })
  797. })
  798. }
  799. func InitMySQLTestDB(t *testing.T) *xorm.Engine {
  800. x, err := xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, strings.Replace(sqlutil.TestDB_Mysql.ConnStr, "/grafana_tests", "/grafana_ds_tests", 1))
  801. if err != nil {
  802. t.Fatalf("Failed to init mysql db %v", err)
  803. }
  804. x.DatabaseTZ = time.UTC
  805. x.TZLocation = time.UTC
  806. // x.ShowSQL()
  807. return x
  808. }
  809. func genTimeRangeByInterval(from time.Time, duration time.Duration, interval time.Duration) []time.Time {
  810. durationSec := int64(duration.Seconds())
  811. intervalSec := int64(interval.Seconds())
  812. timeRange := []time.Time{}
  813. for i := int64(0); i < durationSec; i += intervalSec {
  814. timeRange = append(timeRange, from)
  815. from = from.Add(time.Duration(int64(time.Second) * intervalSec))
  816. }
  817. return timeRange
  818. }