postgres_test.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862
  1. package postgres
  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/lib/pq"
  15. . "github.com/smartystreets/goconvey/convey"
  16. )
  17. // To run this test, set runMySqlTests=true
  18. // and set up a PostgreSQL db named grafanadstest and a user/password grafanatest/grafanatest!
  19. // Use the docker/blocks/postgres_tests/docker-compose.yaml to spin up a
  20. // preconfigured Postgres server suitable for running these tests.
  21. // Thers's 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 TestPostgres(t *testing.T) {
  24. // change to true to run the MySQL tests
  25. runPostgresTests := false
  26. // runPostgresTests := true
  27. if !(sqlstore.IsTestDbPostgres() || runPostgresTests) {
  28. t.Skip()
  29. }
  30. Convey("PostgreSQL", t, func() {
  31. x := InitPostgresTestDB(t)
  32. endpoint := &PostgresQueryEndpoint{
  33. sqlEngine: &tsdb.DefaultSqlEngine{
  34. MacroEngine: NewPostgresMacroEngine(),
  35. XormEngine: x,
  36. },
  37. log: log.New("tsdb.postgres"),
  38. }
  39. sess := x.NewSession()
  40. defer sess.Close()
  41. fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local)
  42. Convey("Given a table with different native data types", func() {
  43. sql := `
  44. DROP TABLE IF EXISTS postgres_types;
  45. CREATE TABLE postgres_types(
  46. c00_smallint smallint,
  47. c01_integer integer,
  48. c02_bigint bigint,
  49. c03_real real,
  50. c04_double double precision,
  51. c05_decimal decimal(10,2),
  52. c06_numeric numeric(10,2),
  53. c07_char char(10),
  54. c08_varchar varchar(10),
  55. c09_text text,
  56. c10_timestamp timestamp without time zone,
  57. c11_timestamptz timestamp with time zone,
  58. c12_date date,
  59. c13_time time without time zone,
  60. c14_timetz time with time zone,
  61. c15_interval interval
  62. );
  63. `
  64. _, err := sess.Exec(sql)
  65. So(err, ShouldBeNil)
  66. sql = `
  67. INSERT INTO postgres_types VALUES(
  68. 1,2,3,
  69. 4.5,6.7,1.1,1.2,
  70. 'char10','varchar10','text',
  71. now(),now(),now(),now(),now(),'15m'::interval
  72. );
  73. `
  74. _, err = sess.Exec(sql)
  75. So(err, ShouldBeNil)
  76. Convey("When doing a table query should map Postgres column types to Go types", func() {
  77. query := &tsdb.TsdbQuery{
  78. Queries: []*tsdb.Query{
  79. {
  80. Model: simplejson.NewFromAny(map[string]interface{}{
  81. "rawSql": "SELECT * FROM postgres_types",
  82. "format": "table",
  83. }),
  84. RefId: "A",
  85. },
  86. },
  87. }
  88. resp, err := endpoint.Query(nil, nil, query)
  89. So(err, ShouldBeNil)
  90. queryResult := resp.Results["A"]
  91. So(queryResult.Error, ShouldBeNil)
  92. column := queryResult.Tables[0].Rows[0]
  93. So(column[0].(int64), ShouldEqual, 1)
  94. So(column[1].(int64), ShouldEqual, 2)
  95. So(column[2].(int64), ShouldEqual, 3)
  96. So(column[3].(float64), ShouldEqual, 4.5)
  97. So(column[4].(float64), ShouldEqual, 6.7)
  98. So(column[5].(float64), ShouldEqual, 1.1)
  99. So(column[6].(float64), ShouldEqual, 1.2)
  100. So(column[7].(string), ShouldEqual, "char10 ")
  101. So(column[8].(string), ShouldEqual, "varchar10")
  102. So(column[9].(string), ShouldEqual, "text")
  103. So(column[10].(time.Time), ShouldHaveSameTypeAs, time.Now())
  104. So(column[11].(time.Time), ShouldHaveSameTypeAs, time.Now())
  105. So(column[12].(time.Time), ShouldHaveSameTypeAs, time.Now())
  106. So(column[13].(time.Time), ShouldHaveSameTypeAs, time.Now())
  107. So(column[14].(time.Time), ShouldHaveSameTypeAs, time.Now())
  108. So(column[15].(string), ShouldEqual, "00:15:00")
  109. })
  110. })
  111. Convey("Given a table with metrics that lacks data for some series ", func() {
  112. sql := `
  113. DROP TABLE IF EXISTS metric;
  114. CREATE TABLE metric (
  115. time timestamp,
  116. value integer
  117. )
  118. `
  119. _, err := sess.Exec(sql)
  120. So(err, ShouldBeNil)
  121. type metric struct {
  122. Time time.Time
  123. Value int64
  124. }
  125. series := []*metric{}
  126. firstRange := genTimeRangeByInterval(fromStart, 10*time.Minute, 10*time.Second)
  127. secondRange := genTimeRangeByInterval(fromStart.Add(20*time.Minute), 10*time.Minute, 10*time.Second)
  128. for _, t := range firstRange {
  129. series = append(series, &metric{
  130. Time: t,
  131. Value: 15,
  132. })
  133. }
  134. for _, t := range secondRange {
  135. series = append(series, &metric{
  136. Time: t,
  137. Value: 20,
  138. })
  139. }
  140. _, err = sess.InsertMulti(series)
  141. So(err, ShouldBeNil)
  142. Convey("When doing a metric query using timeGroup", func() {
  143. query := &tsdb.TsdbQuery{
  144. Queries: []*tsdb.Query{
  145. {
  146. Model: simplejson.NewFromAny(map[string]interface{}{
  147. "rawSql": "SELECT $__timeGroup(time, '5m'), avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
  148. "format": "time_series",
  149. }),
  150. RefId: "A",
  151. },
  152. },
  153. }
  154. resp, err := endpoint.Query(nil, nil, query)
  155. So(err, ShouldBeNil)
  156. queryResult := resp.Results["A"]
  157. So(queryResult.Error, ShouldBeNil)
  158. points := queryResult.Series[0].Points
  159. So(len(points), ShouldEqual, 6)
  160. dt := fromStart
  161. for i := 0; i < 3; i++ {
  162. aValue := points[i][0].Float64
  163. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  164. So(aValue, ShouldEqual, 15)
  165. So(aTime, ShouldEqual, dt)
  166. dt = dt.Add(5 * time.Minute)
  167. }
  168. // adjust for 5 minute gap
  169. dt = dt.Add(5 * time.Minute)
  170. for i := 3; i < 6; i++ {
  171. aValue := points[i][0].Float64
  172. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  173. So(aValue, ShouldEqual, 20)
  174. So(aTime, ShouldEqual, dt)
  175. dt = dt.Add(5 * time.Minute)
  176. }
  177. })
  178. Convey("When doing a metric query using timeGroup with NULL fill enabled", func() {
  179. query := &tsdb.TsdbQuery{
  180. Queries: []*tsdb.Query{
  181. {
  182. Model: simplejson.NewFromAny(map[string]interface{}{
  183. "rawSql": "SELECT $__timeGroup(time, '5m', NULL), avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
  184. "format": "time_series",
  185. }),
  186. RefId: "A",
  187. },
  188. },
  189. TimeRange: &tsdb.TimeRange{
  190. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  191. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  192. },
  193. }
  194. resp, err := endpoint.Query(nil, nil, query)
  195. So(err, ShouldBeNil)
  196. queryResult := resp.Results["A"]
  197. So(queryResult.Error, ShouldBeNil)
  198. points := queryResult.Series[0].Points
  199. So(len(points), ShouldEqual, 7)
  200. dt := fromStart
  201. for i := 0; i < 3; i++ {
  202. aValue := points[i][0].Float64
  203. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  204. So(aValue, ShouldEqual, 15)
  205. So(aTime, ShouldEqual, dt)
  206. dt = dt.Add(5 * time.Minute)
  207. }
  208. So(points[3][0].Valid, ShouldBeFalse)
  209. // adjust for 5 minute gap
  210. dt = dt.Add(5 * time.Minute)
  211. for i := 4; i < 7; i++ {
  212. aValue := points[i][0].Float64
  213. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  214. So(aValue, ShouldEqual, 20)
  215. So(aTime, ShouldEqual, dt)
  216. dt = dt.Add(5 * time.Minute)
  217. }
  218. })
  219. Convey("When doing a metric query using timeGroup with float fill enabled", func() {
  220. query := &tsdb.TsdbQuery{
  221. Queries: []*tsdb.Query{
  222. {
  223. Model: simplejson.NewFromAny(map[string]interface{}{
  224. "rawSql": "SELECT $__timeGroup(time, '5m', 1.5), avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
  225. "format": "time_series",
  226. }),
  227. RefId: "A",
  228. },
  229. },
  230. TimeRange: &tsdb.TimeRange{
  231. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  232. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  233. },
  234. }
  235. resp, err := endpoint.Query(nil, nil, query)
  236. So(err, ShouldBeNil)
  237. queryResult := resp.Results["A"]
  238. So(queryResult.Error, ShouldBeNil)
  239. points := queryResult.Series[0].Points
  240. So(points[3][0].Float64, ShouldEqual, 1.5)
  241. })
  242. })
  243. Convey("Given a table with metrics having multiple values and measurements", func() {
  244. type metric_values struct {
  245. Time time.Time
  246. TimeInt64 int64 `xorm:"bigint 'timeInt64' not null"`
  247. TimeInt64Nullable *int64 `xorm:"bigint 'timeInt64Nullable' null"`
  248. TimeFloat64 float64 `xorm:"double 'timeFloat64' not null"`
  249. TimeFloat64Nullable *float64 `xorm:"double 'timeFloat64Nullable' null"`
  250. TimeInt32 int32 `xorm:"int(11) 'timeInt32' not null"`
  251. TimeInt32Nullable *int32 `xorm:"int(11) 'timeInt32Nullable' null"`
  252. TimeFloat32 float32 `xorm:"double 'timeFloat32' not null"`
  253. TimeFloat32Nullable *float32 `xorm:"double 'timeFloat32Nullable' null"`
  254. Measurement string
  255. ValueOne int64 `xorm:"integer 'valueOne'"`
  256. ValueTwo int64 `xorm:"integer 'valueTwo'"`
  257. }
  258. if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
  259. So(err, ShouldBeNil)
  260. sess.DropTable(metric_values{})
  261. }
  262. err := sess.CreateTable(metric_values{})
  263. So(err, ShouldBeNil)
  264. rand.Seed(time.Now().Unix())
  265. rnd := func(min, max int64) int64 {
  266. return rand.Int63n(max-min) + min
  267. }
  268. var tInitial time.Time
  269. series := []*metric_values{}
  270. for i, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
  271. if i == 0 {
  272. tInitial = t
  273. }
  274. tSeconds := t.Unix()
  275. tSecondsInt32 := int32(tSeconds)
  276. tSecondsFloat32 := float32(tSeconds)
  277. tMilliseconds := tSeconds * 1e3
  278. tMillisecondsFloat := float64(tMilliseconds)
  279. first := metric_values{
  280. Time: t,
  281. TimeInt64: tMilliseconds,
  282. TimeInt64Nullable: &(tMilliseconds),
  283. TimeFloat64: tMillisecondsFloat,
  284. TimeFloat64Nullable: &tMillisecondsFloat,
  285. TimeInt32: tSecondsInt32,
  286. TimeInt32Nullable: &tSecondsInt32,
  287. TimeFloat32: tSecondsFloat32,
  288. TimeFloat32Nullable: &tSecondsFloat32,
  289. Measurement: "Metric A",
  290. ValueOne: rnd(0, 100),
  291. ValueTwo: rnd(0, 100),
  292. }
  293. second := first
  294. second.Measurement = "Metric B"
  295. second.ValueOne = rnd(0, 100)
  296. second.ValueTwo = rnd(0, 100)
  297. series = append(series, &first)
  298. series = append(series, &second)
  299. }
  300. _, err = sess.InsertMulti(series)
  301. So(err, ShouldBeNil)
  302. Convey("When doing a metric query using epoch (int64) as time column should return metric with time in milliseconds", func() {
  303. query := &tsdb.TsdbQuery{
  304. Queries: []*tsdb.Query{
  305. {
  306. Model: simplejson.NewFromAny(map[string]interface{}{
  307. "rawSql": `SELECT "timeInt64" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 1`,
  308. "format": "time_series",
  309. }),
  310. RefId: "A",
  311. },
  312. },
  313. }
  314. resp, err := endpoint.Query(nil, nil, query)
  315. So(err, ShouldBeNil)
  316. queryResult := resp.Results["A"]
  317. So(queryResult.Error, ShouldBeNil)
  318. So(len(queryResult.Series), ShouldEqual, 1)
  319. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  320. })
  321. Convey("When doing a metric query using epoch (int64 nullable) as time column should return metric with time in milliseconds", func() {
  322. query := &tsdb.TsdbQuery{
  323. Queries: []*tsdb.Query{
  324. {
  325. Model: simplejson.NewFromAny(map[string]interface{}{
  326. "rawSql": `SELECT "timeInt64Nullable" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 1`,
  327. "format": "time_series",
  328. }),
  329. RefId: "A",
  330. },
  331. },
  332. }
  333. resp, err := endpoint.Query(nil, nil, query)
  334. So(err, ShouldBeNil)
  335. queryResult := resp.Results["A"]
  336. So(queryResult.Error, ShouldBeNil)
  337. So(len(queryResult.Series), ShouldEqual, 1)
  338. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  339. })
  340. Convey("When doing a metric query using epoch (float64) as time column should return metric with time in milliseconds", func() {
  341. query := &tsdb.TsdbQuery{
  342. Queries: []*tsdb.Query{
  343. {
  344. Model: simplejson.NewFromAny(map[string]interface{}{
  345. "rawSql": `SELECT "timeFloat64" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 1`,
  346. "format": "time_series",
  347. }),
  348. RefId: "A",
  349. },
  350. },
  351. }
  352. resp, err := endpoint.Query(nil, nil, query)
  353. So(err, ShouldBeNil)
  354. queryResult := resp.Results["A"]
  355. So(queryResult.Error, ShouldBeNil)
  356. So(len(queryResult.Series), ShouldEqual, 1)
  357. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  358. })
  359. Convey("When doing a metric query using epoch (float64 nullable) as time column should return metric with time in milliseconds", func() {
  360. query := &tsdb.TsdbQuery{
  361. Queries: []*tsdb.Query{
  362. {
  363. Model: simplejson.NewFromAny(map[string]interface{}{
  364. "rawSql": `SELECT "timeFloat64Nullable" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 1`,
  365. "format": "time_series",
  366. }),
  367. RefId: "A",
  368. },
  369. },
  370. }
  371. resp, err := endpoint.Query(nil, nil, query)
  372. So(err, ShouldBeNil)
  373. queryResult := resp.Results["A"]
  374. So(queryResult.Error, ShouldBeNil)
  375. So(len(queryResult.Series), ShouldEqual, 1)
  376. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  377. })
  378. Convey("When doing a metric query using epoch (int32) as time column should return metric with time in milliseconds", func() {
  379. query := &tsdb.TsdbQuery{
  380. Queries: []*tsdb.Query{
  381. {
  382. Model: simplejson.NewFromAny(map[string]interface{}{
  383. "rawSql": `SELECT "timeInt32" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 1`,
  384. "format": "time_series",
  385. }),
  386. RefId: "A",
  387. },
  388. },
  389. }
  390. resp, err := endpoint.Query(nil, nil, query)
  391. So(err, ShouldBeNil)
  392. queryResult := resp.Results["A"]
  393. So(queryResult.Error, ShouldBeNil)
  394. So(len(queryResult.Series), ShouldEqual, 1)
  395. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  396. })
  397. Convey("When doing a metric query using epoch (int32 nullable) as time column should return metric with time in milliseconds", func() {
  398. query := &tsdb.TsdbQuery{
  399. Queries: []*tsdb.Query{
  400. {
  401. Model: simplejson.NewFromAny(map[string]interface{}{
  402. "rawSql": `SELECT "timeInt32Nullable" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 1`,
  403. "format": "time_series",
  404. }),
  405. RefId: "A",
  406. },
  407. },
  408. }
  409. resp, err := endpoint.Query(nil, nil, query)
  410. So(err, ShouldBeNil)
  411. queryResult := resp.Results["A"]
  412. So(queryResult.Error, ShouldBeNil)
  413. So(len(queryResult.Series), ShouldEqual, 1)
  414. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  415. })
  416. Convey("When doing a metric query using epoch (float32) as time column should return metric with time in milliseconds", func() {
  417. query := &tsdb.TsdbQuery{
  418. Queries: []*tsdb.Query{
  419. {
  420. Model: simplejson.NewFromAny(map[string]interface{}{
  421. "rawSql": `SELECT "timeFloat32" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 1`,
  422. "format": "time_series",
  423. }),
  424. RefId: "A",
  425. },
  426. },
  427. }
  428. resp, err := endpoint.Query(nil, nil, query)
  429. So(err, ShouldBeNil)
  430. queryResult := resp.Results["A"]
  431. So(queryResult.Error, ShouldBeNil)
  432. So(len(queryResult.Series), ShouldEqual, 1)
  433. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
  434. })
  435. Convey("When doing a metric query using epoch (float32 nullable) as time column should return metric with time in milliseconds", func() {
  436. query := &tsdb.TsdbQuery{
  437. Queries: []*tsdb.Query{
  438. {
  439. Model: simplejson.NewFromAny(map[string]interface{}{
  440. "rawSql": `SELECT "timeFloat32Nullable" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 1`,
  441. "format": "time_series",
  442. }),
  443. RefId: "A",
  444. },
  445. },
  446. }
  447. resp, err := endpoint.Query(nil, nil, query)
  448. So(err, ShouldBeNil)
  449. queryResult := resp.Results["A"]
  450. So(queryResult.Error, ShouldBeNil)
  451. So(len(queryResult.Series), ShouldEqual, 1)
  452. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
  453. })
  454. Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
  455. query := &tsdb.TsdbQuery{
  456. Queries: []*tsdb.Query{
  457. {
  458. Model: simplejson.NewFromAny(map[string]interface{}{
  459. "rawSql": `SELECT $__timeEpoch(time), measurement || ' - value one' as metric, "valueOne" FROM metric_values ORDER BY 1`,
  460. "format": "time_series",
  461. }),
  462. RefId: "A",
  463. },
  464. },
  465. }
  466. resp, err := endpoint.Query(nil, nil, query)
  467. So(err, ShouldBeNil)
  468. queryResult := resp.Results["A"]
  469. So(queryResult.Error, ShouldBeNil)
  470. So(len(queryResult.Series), ShouldEqual, 2)
  471. So(queryResult.Series[0].Name, ShouldEqual, "Metric A - value one")
  472. So(queryResult.Series[1].Name, ShouldEqual, "Metric B - value one")
  473. })
  474. Convey("When doing a metric query grouping by time should return correct series", func() {
  475. query := &tsdb.TsdbQuery{
  476. Queries: []*tsdb.Query{
  477. {
  478. Model: simplejson.NewFromAny(map[string]interface{}{
  479. "rawSql": `SELECT $__timeEpoch(time), "valueOne", "valueTwo" FROM metric_values ORDER BY 1`,
  480. "format": "time_series",
  481. }),
  482. RefId: "A",
  483. },
  484. },
  485. }
  486. resp, err := endpoint.Query(nil, nil, query)
  487. So(err, ShouldBeNil)
  488. queryResult := resp.Results["A"]
  489. So(queryResult.Error, ShouldBeNil)
  490. So(len(queryResult.Series), ShouldEqual, 2)
  491. So(queryResult.Series[0].Name, ShouldEqual, "valueOne")
  492. So(queryResult.Series[1].Name, ShouldEqual, "valueTwo")
  493. })
  494. })
  495. Convey("Given a table with event data", func() {
  496. type event struct {
  497. TimeSec int64
  498. Description string
  499. Tags string
  500. }
  501. if exist, err := sess.IsTableExist(event{}); err != nil || exist {
  502. So(err, ShouldBeNil)
  503. sess.DropTable(event{})
  504. }
  505. err := sess.CreateTable(event{})
  506. So(err, ShouldBeNil)
  507. events := []*event{}
  508. for _, t := range genTimeRangeByInterval(fromStart.Add(-20*time.Minute), 60*time.Minute, 25*time.Minute) {
  509. events = append(events, &event{
  510. TimeSec: t.Unix(),
  511. Description: "Someone deployed something",
  512. Tags: "deploy",
  513. })
  514. events = append(events, &event{
  515. TimeSec: t.Add(5 * time.Minute).Unix(),
  516. Description: "New support ticket registered",
  517. Tags: "ticket",
  518. })
  519. }
  520. for _, e := range events {
  521. _, err = sess.Insert(e)
  522. So(err, ShouldBeNil)
  523. }
  524. Convey("When doing an annotation query of deploy events should return expected result", func() {
  525. query := &tsdb.TsdbQuery{
  526. Queries: []*tsdb.Query{
  527. {
  528. Model: simplejson.NewFromAny(map[string]interface{}{
  529. "rawSql": `SELECT "time_sec" as time, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC`,
  530. "format": "table",
  531. }),
  532. RefId: "Deploys",
  533. },
  534. },
  535. TimeRange: &tsdb.TimeRange{
  536. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  537. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  538. },
  539. }
  540. resp, err := endpoint.Query(nil, nil, query)
  541. queryResult := resp.Results["Deploys"]
  542. So(err, ShouldBeNil)
  543. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  544. })
  545. Convey("When doing an annotation query of ticket events should return expected result", func() {
  546. query := &tsdb.TsdbQuery{
  547. Queries: []*tsdb.Query{
  548. {
  549. Model: simplejson.NewFromAny(map[string]interface{}{
  550. "rawSql": `SELECT "time_sec" as time, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC`,
  551. "format": "table",
  552. }),
  553. RefId: "Tickets",
  554. },
  555. },
  556. TimeRange: &tsdb.TimeRange{
  557. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  558. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  559. },
  560. }
  561. resp, err := endpoint.Query(nil, nil, query)
  562. queryResult := resp.Results["Tickets"]
  563. So(err, ShouldBeNil)
  564. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  565. })
  566. Convey("When doing an annotation query with a time column in datetime format", func() {
  567. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  568. dtFormat := "2006-01-02 15:04:05.999999999"
  569. query := &tsdb.TsdbQuery{
  570. Queries: []*tsdb.Query{
  571. {
  572. Model: simplejson.NewFromAny(map[string]interface{}{
  573. "rawSql": fmt.Sprintf(`SELECT
  574. CAST('%s' AS TIMESTAMP) as time,
  575. 'message' as text,
  576. 'tag1,tag2' as tags
  577. `, dt.Format(dtFormat)),
  578. "format": "table",
  579. }),
  580. RefId: "A",
  581. },
  582. },
  583. }
  584. resp, err := endpoint.Query(nil, nil, query)
  585. So(err, ShouldBeNil)
  586. queryResult := resp.Results["A"]
  587. So(queryResult.Error, ShouldBeNil)
  588. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  589. columns := queryResult.Tables[0].Rows[0]
  590. //Should be in milliseconds
  591. So(columns[0].(float64), ShouldEqual, float64(dt.UnixNano()/1e6))
  592. })
  593. Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
  594. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  595. query := &tsdb.TsdbQuery{
  596. Queries: []*tsdb.Query{
  597. {
  598. Model: simplejson.NewFromAny(map[string]interface{}{
  599. "rawSql": fmt.Sprintf(`SELECT
  600. %d as time,
  601. 'message' as text,
  602. 'tag1,tag2' as tags
  603. `, dt.Unix()),
  604. "format": "table",
  605. }),
  606. RefId: "A",
  607. },
  608. },
  609. }
  610. resp, err := endpoint.Query(nil, nil, query)
  611. So(err, ShouldBeNil)
  612. queryResult := resp.Results["A"]
  613. So(queryResult.Error, ShouldBeNil)
  614. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  615. columns := queryResult.Tables[0].Rows[0]
  616. //Should be in milliseconds
  617. So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
  618. })
  619. Convey("When doing an annotation query with a time column in epoch second format (int) should return ms", func() {
  620. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  621. query := &tsdb.TsdbQuery{
  622. Queries: []*tsdb.Query{
  623. {
  624. Model: simplejson.NewFromAny(map[string]interface{}{
  625. "rawSql": fmt.Sprintf(`SELECT
  626. cast(%d as bigint) as time,
  627. 'message' as text,
  628. 'tag1,tag2' as tags
  629. `, dt.Unix()),
  630. "format": "table",
  631. }),
  632. RefId: "A",
  633. },
  634. },
  635. }
  636. resp, err := endpoint.Query(nil, nil, query)
  637. So(err, ShouldBeNil)
  638. queryResult := resp.Results["A"]
  639. So(queryResult.Error, ShouldBeNil)
  640. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  641. columns := queryResult.Tables[0].Rows[0]
  642. //Should be in milliseconds
  643. So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
  644. })
  645. Convey("When doing an annotation query with a time column in epoch millisecond format should return ms", func() {
  646. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  647. query := &tsdb.TsdbQuery{
  648. Queries: []*tsdb.Query{
  649. {
  650. Model: simplejson.NewFromAny(map[string]interface{}{
  651. "rawSql": fmt.Sprintf(`SELECT
  652. %d as time,
  653. 'message' as text,
  654. 'tag1,tag2' as tags
  655. `, dt.Unix()*1000),
  656. "format": "table",
  657. }),
  658. RefId: "A",
  659. },
  660. },
  661. }
  662. resp, err := endpoint.Query(nil, nil, query)
  663. So(err, ShouldBeNil)
  664. queryResult := resp.Results["A"]
  665. So(queryResult.Error, ShouldBeNil)
  666. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  667. columns := queryResult.Tables[0].Rows[0]
  668. //Should be in milliseconds
  669. So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
  670. })
  671. Convey("When doing an annotation query with a time column holding a bigint null value should return nil", func() {
  672. query := &tsdb.TsdbQuery{
  673. Queries: []*tsdb.Query{
  674. {
  675. Model: simplejson.NewFromAny(map[string]interface{}{
  676. "rawSql": `SELECT
  677. cast(null as bigint) as time,
  678. 'message' as text,
  679. 'tag1,tag2' as tags
  680. `,
  681. "format": "table",
  682. }),
  683. RefId: "A",
  684. },
  685. },
  686. }
  687. resp, err := endpoint.Query(nil, nil, query)
  688. So(err, ShouldBeNil)
  689. queryResult := resp.Results["A"]
  690. So(queryResult.Error, ShouldBeNil)
  691. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  692. columns := queryResult.Tables[0].Rows[0]
  693. //Should be in milliseconds
  694. So(columns[0], ShouldBeNil)
  695. })
  696. Convey("When doing an annotation query with a time column holding a timestamp null value should return nil", func() {
  697. query := &tsdb.TsdbQuery{
  698. Queries: []*tsdb.Query{
  699. {
  700. Model: simplejson.NewFromAny(map[string]interface{}{
  701. "rawSql": `SELECT
  702. cast(null as timestamp) as time,
  703. 'message' as text,
  704. 'tag1,tag2' as tags
  705. `,
  706. "format": "table",
  707. }),
  708. RefId: "A",
  709. },
  710. },
  711. }
  712. resp, err := endpoint.Query(nil, nil, query)
  713. So(err, ShouldBeNil)
  714. queryResult := resp.Results["A"]
  715. So(queryResult.Error, ShouldBeNil)
  716. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  717. columns := queryResult.Tables[0].Rows[0]
  718. //Should be in milliseconds
  719. So(columns[0], ShouldBeNil)
  720. })
  721. })
  722. })
  723. }
  724. func InitPostgresTestDB(t *testing.T) *xorm.Engine {
  725. x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, strings.Replace(sqlutil.TestDB_Postgres.ConnStr, "dbname=grafanatest", "dbname=grafanadstest", 1))
  726. if err != nil {
  727. t.Fatalf("Failed to init postgres db %v", err)
  728. }
  729. x.DatabaseTZ = time.UTC
  730. x.TZLocation = time.UTC
  731. // x.ShowSQL()
  732. return x
  733. }
  734. func genTimeRangeByInterval(from time.Time, duration time.Duration, interval time.Duration) []time.Time {
  735. durationSec := int64(duration.Seconds())
  736. intervalSec := int64(interval.Seconds())
  737. timeRange := []time.Time{}
  738. for i := int64(0); i < durationSec; i += intervalSec {
  739. timeRange = append(timeRange, from)
  740. from = from.Add(time.Duration(int64(time.Second) * intervalSec))
  741. }
  742. return timeRange
  743. }