mysql_test.go 35 KB

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