postgres_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  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. for _, s := range series {
  133. _, err = sess.Insert(s)
  134. So(err, ShouldBeNil)
  135. }
  136. Convey("When doing a metric query using timeGroup", func() {
  137. query := &tsdb.TsdbQuery{
  138. Queries: []*tsdb.Query{
  139. {
  140. Model: simplejson.NewFromAny(map[string]interface{}{
  141. "rawSql": "SELECT $__timeGroup(time, '5m'), avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
  142. "format": "time_series",
  143. }),
  144. RefId: "A",
  145. },
  146. },
  147. }
  148. resp, err := endpoint.Query(nil, nil, query)
  149. So(err, ShouldBeNil)
  150. queryResult := resp.Results["A"]
  151. So(queryResult.Error, ShouldBeNil)
  152. points := queryResult.Series[0].Points
  153. So(len(points), ShouldEqual, 6)
  154. dt := fromStart
  155. for i := 0; i < 3; i++ {
  156. aValue := points[i][0].Float64
  157. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  158. So(aValue, ShouldEqual, 15)
  159. So(aTime, ShouldEqual, dt)
  160. dt = dt.Add(5 * time.Minute)
  161. }
  162. // adjust for 5 minute gap
  163. dt = dt.Add(5 * time.Minute)
  164. for i := 3; i < 6; i++ {
  165. aValue := points[i][0].Float64
  166. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  167. So(aValue, ShouldEqual, 20)
  168. So(aTime, ShouldEqual, dt)
  169. dt = dt.Add(5 * time.Minute)
  170. }
  171. })
  172. Convey("When doing a metric query using timeGroup with NULL fill enabled", func() {
  173. query := &tsdb.TsdbQuery{
  174. Queries: []*tsdb.Query{
  175. {
  176. Model: simplejson.NewFromAny(map[string]interface{}{
  177. "rawSql": "SELECT $__timeGroup(time, '5m', NULL), avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
  178. "format": "time_series",
  179. }),
  180. RefId: "A",
  181. },
  182. },
  183. TimeRange: &tsdb.TimeRange{
  184. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  185. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  186. },
  187. }
  188. resp, err := endpoint.Query(nil, nil, query)
  189. So(err, ShouldBeNil)
  190. queryResult := resp.Results["A"]
  191. So(queryResult.Error, ShouldBeNil)
  192. points := queryResult.Series[0].Points
  193. So(len(points), ShouldEqual, 7)
  194. dt := fromStart
  195. for i := 0; i < 3; i++ {
  196. aValue := points[i][0].Float64
  197. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  198. So(aValue, ShouldEqual, 15)
  199. So(aTime, ShouldEqual, dt)
  200. dt = dt.Add(5 * time.Minute)
  201. }
  202. So(points[3][0].Valid, ShouldBeFalse)
  203. // adjust for 5 minute gap
  204. dt = dt.Add(5 * time.Minute)
  205. for i := 4; i < 7; i++ {
  206. aValue := points[i][0].Float64
  207. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  208. So(aValue, ShouldEqual, 20)
  209. So(aTime, ShouldEqual, dt)
  210. dt = dt.Add(5 * time.Minute)
  211. }
  212. })
  213. Convey("When doing a metric query using timeGroup with float fill enabled", func() {
  214. query := &tsdb.TsdbQuery{
  215. Queries: []*tsdb.Query{
  216. {
  217. Model: simplejson.NewFromAny(map[string]interface{}{
  218. "rawSql": "SELECT $__timeGroup(time, '5m', 1.5), avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
  219. "format": "time_series",
  220. }),
  221. RefId: "A",
  222. },
  223. },
  224. TimeRange: &tsdb.TimeRange{
  225. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  226. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  227. },
  228. }
  229. resp, err := endpoint.Query(nil, nil, query)
  230. So(err, ShouldBeNil)
  231. queryResult := resp.Results["A"]
  232. So(queryResult.Error, ShouldBeNil)
  233. points := queryResult.Series[0].Points
  234. So(points[3][0].Float64, ShouldEqual, 1.5)
  235. })
  236. })
  237. Convey("Given a table with metrics having multiple values and measurements", func() {
  238. type metric_values struct {
  239. Time time.Time
  240. Measurement string
  241. ValueOne int64 `xorm:"integer 'valueOne'"`
  242. ValueTwo int64 `xorm:"integer 'valueTwo'"`
  243. }
  244. if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
  245. So(err, ShouldBeNil)
  246. sess.DropTable(metric_values{})
  247. }
  248. err := sess.CreateTable(metric_values{})
  249. So(err, ShouldBeNil)
  250. rand.Seed(time.Now().Unix())
  251. rnd := func(min, max int64) int64 {
  252. return rand.Int63n(max-min) + min
  253. }
  254. series := []*metric_values{}
  255. for _, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
  256. series = append(series, &metric_values{
  257. Time: t,
  258. Measurement: "Metric A",
  259. ValueOne: rnd(0, 100),
  260. ValueTwo: rnd(0, 100),
  261. })
  262. series = append(series, &metric_values{
  263. Time: t,
  264. Measurement: "Metric B",
  265. ValueOne: rnd(0, 100),
  266. ValueTwo: rnd(0, 100),
  267. })
  268. }
  269. for _, s := range series {
  270. _, err := sess.Insert(s)
  271. So(err, ShouldBeNil)
  272. }
  273. Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
  274. query := &tsdb.TsdbQuery{
  275. Queries: []*tsdb.Query{
  276. {
  277. Model: simplejson.NewFromAny(map[string]interface{}{
  278. "rawSql": `SELECT $__timeEpoch(time), measurement || ' - value one' as metric, "valueOne" FROM metric_values ORDER BY 1`,
  279. "format": "time_series",
  280. }),
  281. RefId: "A",
  282. },
  283. },
  284. }
  285. resp, err := endpoint.Query(nil, nil, query)
  286. So(err, ShouldBeNil)
  287. queryResult := resp.Results["A"]
  288. So(queryResult.Error, ShouldBeNil)
  289. So(len(queryResult.Series), ShouldEqual, 2)
  290. So(queryResult.Series[0].Name, ShouldEqual, "Metric A - value one")
  291. So(queryResult.Series[1].Name, ShouldEqual, "Metric B - value one")
  292. })
  293. Convey("When doing a metric query grouping by time should return correct series", func() {
  294. query := &tsdb.TsdbQuery{
  295. Queries: []*tsdb.Query{
  296. {
  297. Model: simplejson.NewFromAny(map[string]interface{}{
  298. "rawSql": `SELECT $__timeEpoch(time), "valueOne", "valueTwo" FROM metric_values ORDER BY 1`,
  299. "format": "time_series",
  300. }),
  301. RefId: "A",
  302. },
  303. },
  304. }
  305. resp, err := endpoint.Query(nil, nil, query)
  306. So(err, ShouldBeNil)
  307. queryResult := resp.Results["A"]
  308. So(queryResult.Error, ShouldBeNil)
  309. So(len(queryResult.Series), ShouldEqual, 2)
  310. So(queryResult.Series[0].Name, ShouldEqual, "valueOne")
  311. So(queryResult.Series[1].Name, ShouldEqual, "valueTwo")
  312. })
  313. })
  314. Convey("Given a table with event data", func() {
  315. type event struct {
  316. TimeSec int64
  317. Description string
  318. Tags string
  319. }
  320. if exist, err := sess.IsTableExist(event{}); err != nil || exist {
  321. So(err, ShouldBeNil)
  322. sess.DropTable(event{})
  323. }
  324. err := sess.CreateTable(event{})
  325. So(err, ShouldBeNil)
  326. events := []*event{}
  327. for _, t := range genTimeRangeByInterval(fromStart.Add(-20*time.Minute), 60*time.Minute, 25*time.Minute) {
  328. events = append(events, &event{
  329. TimeSec: t.Unix(),
  330. Description: "Someone deployed something",
  331. Tags: "deploy",
  332. })
  333. events = append(events, &event{
  334. TimeSec: t.Add(5 * time.Minute).Unix(),
  335. Description: "New support ticket registered",
  336. Tags: "ticket",
  337. })
  338. }
  339. for _, e := range events {
  340. _, err = sess.Insert(e)
  341. So(err, ShouldBeNil)
  342. }
  343. Convey("When doing an annotation query of deploy events should return expected result", func() {
  344. query := &tsdb.TsdbQuery{
  345. Queries: []*tsdb.Query{
  346. {
  347. Model: simplejson.NewFromAny(map[string]interface{}{
  348. "rawSql": `SELECT "time_sec" as time, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC`,
  349. "format": "table",
  350. }),
  351. RefId: "Deploys",
  352. },
  353. },
  354. TimeRange: &tsdb.TimeRange{
  355. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  356. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  357. },
  358. }
  359. resp, err := endpoint.Query(nil, nil, query)
  360. queryResult := resp.Results["Deploys"]
  361. So(err, ShouldBeNil)
  362. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  363. })
  364. Convey("When doing an annotation query of ticket events should return expected result", func() {
  365. query := &tsdb.TsdbQuery{
  366. Queries: []*tsdb.Query{
  367. {
  368. Model: simplejson.NewFromAny(map[string]interface{}{
  369. "rawSql": `SELECT "time_sec" as time, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC`,
  370. "format": "table",
  371. }),
  372. RefId: "Tickets",
  373. },
  374. },
  375. TimeRange: &tsdb.TimeRange{
  376. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  377. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  378. },
  379. }
  380. resp, err := endpoint.Query(nil, nil, query)
  381. queryResult := resp.Results["Tickets"]
  382. So(err, ShouldBeNil)
  383. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  384. })
  385. Convey("When doing an annotation query with a time column in datetime format", func() {
  386. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  387. dtFormat := "2006-01-02 15:04:05.999999999"
  388. query := &tsdb.TsdbQuery{
  389. Queries: []*tsdb.Query{
  390. {
  391. Model: simplejson.NewFromAny(map[string]interface{}{
  392. "rawSql": fmt.Sprintf(`SELECT
  393. CAST('%s' AS TIMESTAMP) as time,
  394. 'message' as text,
  395. 'tag1,tag2' as tags
  396. `, dt.Format(dtFormat)),
  397. "format": "table",
  398. }),
  399. RefId: "A",
  400. },
  401. },
  402. }
  403. resp, err := endpoint.Query(nil, nil, query)
  404. So(err, ShouldBeNil)
  405. queryResult := resp.Results["A"]
  406. So(queryResult.Error, ShouldBeNil)
  407. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  408. columns := queryResult.Tables[0].Rows[0]
  409. //Should be in milliseconds
  410. So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000))
  411. })
  412. Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
  413. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  414. query := &tsdb.TsdbQuery{
  415. Queries: []*tsdb.Query{
  416. {
  417. Model: simplejson.NewFromAny(map[string]interface{}{
  418. "rawSql": fmt.Sprintf(`SELECT
  419. %d as time,
  420. 'message' as text,
  421. 'tag1,tag2' as tags
  422. `, dt.Unix()),
  423. "format": "table",
  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.Tables[0].Rows), ShouldEqual, 1)
  434. columns := queryResult.Tables[0].Rows[0]
  435. //Should be in milliseconds
  436. So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
  437. })
  438. Convey("When doing an annotation query with a time column in epoch second format (int) should return ms", func() {
  439. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  440. query := &tsdb.TsdbQuery{
  441. Queries: []*tsdb.Query{
  442. {
  443. Model: simplejson.NewFromAny(map[string]interface{}{
  444. "rawSql": fmt.Sprintf(`SELECT
  445. cast(%d as bigint) as time,
  446. 'message' as text,
  447. 'tag1,tag2' as tags
  448. `, dt.Unix()),
  449. "format": "table",
  450. }),
  451. RefId: "A",
  452. },
  453. },
  454. }
  455. resp, err := endpoint.Query(nil, nil, query)
  456. So(err, ShouldBeNil)
  457. queryResult := resp.Results["A"]
  458. So(queryResult.Error, ShouldBeNil)
  459. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  460. columns := queryResult.Tables[0].Rows[0]
  461. //Should be in milliseconds
  462. So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
  463. })
  464. Convey("When doing an annotation query with a time column in epoch millisecond format should return ms", func() {
  465. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  466. query := &tsdb.TsdbQuery{
  467. Queries: []*tsdb.Query{
  468. {
  469. Model: simplejson.NewFromAny(map[string]interface{}{
  470. "rawSql": fmt.Sprintf(`SELECT
  471. %d as time,
  472. 'message' as text,
  473. 'tag1,tag2' as tags
  474. `, dt.Unix()*1000),
  475. "format": "table",
  476. }),
  477. RefId: "A",
  478. },
  479. },
  480. }
  481. resp, err := endpoint.Query(nil, nil, query)
  482. So(err, ShouldBeNil)
  483. queryResult := resp.Results["A"]
  484. So(queryResult.Error, ShouldBeNil)
  485. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  486. columns := queryResult.Tables[0].Rows[0]
  487. //Should be in milliseconds
  488. So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
  489. })
  490. Convey("When doing an annotation query with a time column holding a bigint null value should return nil", func() {
  491. query := &tsdb.TsdbQuery{
  492. Queries: []*tsdb.Query{
  493. {
  494. Model: simplejson.NewFromAny(map[string]interface{}{
  495. "rawSql": `SELECT
  496. cast(null as bigint) as time,
  497. 'message' as text,
  498. 'tag1,tag2' as tags
  499. `,
  500. "format": "table",
  501. }),
  502. RefId: "A",
  503. },
  504. },
  505. }
  506. resp, err := endpoint.Query(nil, nil, query)
  507. So(err, ShouldBeNil)
  508. queryResult := resp.Results["A"]
  509. So(queryResult.Error, ShouldBeNil)
  510. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  511. columns := queryResult.Tables[0].Rows[0]
  512. //Should be in milliseconds
  513. So(columns[0], ShouldBeNil)
  514. })
  515. Convey("When doing an annotation query with a time column holding a timestamp null value should return nil", func() {
  516. query := &tsdb.TsdbQuery{
  517. Queries: []*tsdb.Query{
  518. {
  519. Model: simplejson.NewFromAny(map[string]interface{}{
  520. "rawSql": `SELECT
  521. cast(null as timestamp) as time,
  522. 'message' as text,
  523. 'tag1,tag2' as tags
  524. `,
  525. "format": "table",
  526. }),
  527. RefId: "A",
  528. },
  529. },
  530. }
  531. resp, err := endpoint.Query(nil, nil, query)
  532. So(err, ShouldBeNil)
  533. queryResult := resp.Results["A"]
  534. So(queryResult.Error, ShouldBeNil)
  535. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  536. columns := queryResult.Tables[0].Rows[0]
  537. //Should be in milliseconds
  538. So(columns[0], ShouldBeNil)
  539. })
  540. })
  541. })
  542. }
  543. func InitPostgresTestDB(t *testing.T) *xorm.Engine {
  544. x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
  545. x.DatabaseTZ = time.UTC
  546. x.TZLocation = time.UTC
  547. // x.ShowSQL()
  548. if err != nil {
  549. t.Fatalf("Failed to init postgres db %v", err)
  550. }
  551. return x
  552. }
  553. func genTimeRangeByInterval(from time.Time, duration time.Duration, interval time.Duration) []time.Time {
  554. durationSec := int64(duration.Seconds())
  555. intervalSec := int64(interval.Seconds())
  556. timeRange := []time.Time{}
  557. for i := int64(0); i < durationSec; i += intervalSec {
  558. timeRange = append(timeRange, from)
  559. from = from.Add(time.Duration(int64(time.Second) * intervalSec))
  560. }
  561. return timeRange
  562. }