postgres_test.go 26 KB

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