postgres_test.go 26 KB

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