mysql_test.go 31 KB

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