postgres_test.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  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. // without fill this should result in 4 buckets
  161. So(len(points), ShouldEqual, 4)
  162. dt := fromStart
  163. for i := 0; i < 2; i++ {
  164. aValue := points[i][0].Float64
  165. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  166. So(aValue, ShouldEqual, 15)
  167. So(aTime, ShouldEqual, dt)
  168. So(aTime.Unix()%300, ShouldEqual, 0)
  169. dt = dt.Add(5 * time.Minute)
  170. }
  171. // adjust for 10 minute gap between first and second set of points
  172. dt = dt.Add(10 * time.Minute)
  173. for i := 2; i < 4; i++ {
  174. aValue := points[i][0].Float64
  175. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  176. So(aValue, ShouldEqual, 20)
  177. So(aTime, ShouldEqual, dt)
  178. dt = dt.Add(5 * time.Minute)
  179. }
  180. })
  181. Convey("When doing a metric query using timeGroup with NULL fill enabled", func() {
  182. query := &tsdb.TsdbQuery{
  183. Queries: []*tsdb.Query{
  184. {
  185. Model: simplejson.NewFromAny(map[string]interface{}{
  186. "rawSql": "SELECT $__timeGroup(time, '5m', NULL), avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
  187. "format": "time_series",
  188. }),
  189. RefId: "A",
  190. },
  191. },
  192. TimeRange: &tsdb.TimeRange{
  193. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  194. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  195. },
  196. }
  197. resp, err := endpoint.Query(nil, nil, query)
  198. So(err, ShouldBeNil)
  199. queryResult := resp.Results["A"]
  200. So(queryResult.Error, ShouldBeNil)
  201. points := queryResult.Series[0].Points
  202. So(len(points), ShouldEqual, 7)
  203. dt := fromStart
  204. for i := 0; i < 2; i++ {
  205. aValue := points[i][0].Float64
  206. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  207. So(aValue, ShouldEqual, 15)
  208. So(aTime, ShouldEqual, dt)
  209. dt = dt.Add(5 * time.Minute)
  210. }
  211. // check for NULL values inserted by fill
  212. So(points[2][0].Valid, ShouldBeFalse)
  213. So(points[3][0].Valid, ShouldBeFalse)
  214. // adjust for 10 minute gap between first and second set of points
  215. dt = dt.Add(10 * time.Minute)
  216. for i := 4; i < 6; i++ {
  217. aValue := points[i][0].Float64
  218. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  219. So(aValue, ShouldEqual, 20)
  220. So(aTime, ShouldEqual, dt)
  221. dt = dt.Add(5 * time.Minute)
  222. }
  223. // check for NULL values inserted by fill
  224. So(points[6][0].Valid, ShouldBeFalse)
  225. })
  226. Convey("When doing a metric query using timeGroup with float fill enabled", func() {
  227. query := &tsdb.TsdbQuery{
  228. Queries: []*tsdb.Query{
  229. {
  230. Model: simplejson.NewFromAny(map[string]interface{}{
  231. "rawSql": "SELECT $__timeGroup(time, '5m', 1.5), avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
  232. "format": "time_series",
  233. }),
  234. RefId: "A",
  235. },
  236. },
  237. TimeRange: &tsdb.TimeRange{
  238. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  239. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  240. },
  241. }
  242. resp, err := endpoint.Query(nil, nil, query)
  243. So(err, ShouldBeNil)
  244. queryResult := resp.Results["A"]
  245. So(queryResult.Error, ShouldBeNil)
  246. points := queryResult.Series[0].Points
  247. So(points[3][0].Float64, ShouldEqual, 1.5)
  248. })
  249. })
  250. Convey("Given a table with metrics having multiple values and measurements", func() {
  251. type metric_values struct {
  252. Time time.Time
  253. TimeInt64 int64 `xorm:"bigint 'timeInt64' not null"`
  254. TimeInt64Nullable *int64 `xorm:"bigint 'timeInt64Nullable' null"`
  255. TimeFloat64 float64 `xorm:"double 'timeFloat64' not null"`
  256. TimeFloat64Nullable *float64 `xorm:"double 'timeFloat64Nullable' null"`
  257. TimeInt32 int32 `xorm:"int(11) 'timeInt32' not null"`
  258. TimeInt32Nullable *int32 `xorm:"int(11) 'timeInt32Nullable' null"`
  259. TimeFloat32 float32 `xorm:"double 'timeFloat32' not null"`
  260. TimeFloat32Nullable *float32 `xorm:"double 'timeFloat32Nullable' null"`
  261. Measurement string
  262. ValueOne int64 `xorm:"integer 'valueOne'"`
  263. ValueTwo int64 `xorm:"integer 'valueTwo'"`
  264. }
  265. if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
  266. So(err, ShouldBeNil)
  267. sess.DropTable(metric_values{})
  268. }
  269. err := sess.CreateTable(metric_values{})
  270. So(err, ShouldBeNil)
  271. rand.Seed(time.Now().Unix())
  272. rnd := func(min, max int64) int64 {
  273. return rand.Int63n(max-min) + min
  274. }
  275. var tInitial time.Time
  276. series := []*metric_values{}
  277. for i, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
  278. if i == 0 {
  279. tInitial = t
  280. }
  281. tSeconds := t.Unix()
  282. tSecondsInt32 := int32(tSeconds)
  283. tSecondsFloat32 := float32(tSeconds)
  284. tMilliseconds := tSeconds * 1e3
  285. tMillisecondsFloat := float64(tMilliseconds)
  286. first := metric_values{
  287. Time: t,
  288. TimeInt64: tMilliseconds,
  289. TimeInt64Nullable: &(tMilliseconds),
  290. TimeFloat64: tMillisecondsFloat,
  291. TimeFloat64Nullable: &tMillisecondsFloat,
  292. TimeInt32: tSecondsInt32,
  293. TimeInt32Nullable: &tSecondsInt32,
  294. TimeFloat32: tSecondsFloat32,
  295. TimeFloat32Nullable: &tSecondsFloat32,
  296. Measurement: "Metric A",
  297. ValueOne: rnd(0, 100),
  298. ValueTwo: rnd(0, 100),
  299. }
  300. second := first
  301. second.Measurement = "Metric B"
  302. second.ValueOne = rnd(0, 100)
  303. second.ValueTwo = rnd(0, 100)
  304. series = append(series, &first)
  305. series = append(series, &second)
  306. }
  307. _, err = sess.InsertMulti(series)
  308. So(err, ShouldBeNil)
  309. Convey("When doing a metric query using epoch (int64) as time column and value column (int64) should return metric with time in milliseconds", func() {
  310. query := &tsdb.TsdbQuery{
  311. Queries: []*tsdb.Query{
  312. {
  313. Model: simplejson.NewFromAny(map[string]interface{}{
  314. "rawSql": `SELECT "timeInt64" as time, "timeInt64" FROM metric_values ORDER BY time LIMIT 1`,
  315. "format": "time_series",
  316. }),
  317. RefId: "A",
  318. },
  319. },
  320. }
  321. resp, err := endpoint.Query(nil, nil, query)
  322. So(err, ShouldBeNil)
  323. queryResult := resp.Results["A"]
  324. So(queryResult.Error, ShouldBeNil)
  325. So(len(queryResult.Series), ShouldEqual, 1)
  326. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  327. })
  328. Convey("When doing a metric query using epoch (int64 nullable) as time column and value column (int64 nullable) should return metric with time in milliseconds", func() {
  329. query := &tsdb.TsdbQuery{
  330. Queries: []*tsdb.Query{
  331. {
  332. Model: simplejson.NewFromAny(map[string]interface{}{
  333. "rawSql": `SELECT "timeInt64Nullable" as time, "timeInt64Nullable" FROM metric_values ORDER BY time LIMIT 1`,
  334. "format": "time_series",
  335. }),
  336. RefId: "A",
  337. },
  338. },
  339. }
  340. resp, err := endpoint.Query(nil, nil, query)
  341. So(err, ShouldBeNil)
  342. queryResult := resp.Results["A"]
  343. So(queryResult.Error, ShouldBeNil)
  344. So(len(queryResult.Series), ShouldEqual, 1)
  345. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  346. })
  347. Convey("When doing a metric query using epoch (float64) as time column and value column (float64) should return metric with time in milliseconds", func() {
  348. query := &tsdb.TsdbQuery{
  349. Queries: []*tsdb.Query{
  350. {
  351. Model: simplejson.NewFromAny(map[string]interface{}{
  352. "rawSql": `SELECT "timeFloat64" as time, "timeFloat64" FROM metric_values ORDER BY time LIMIT 1`,
  353. "format": "time_series",
  354. }),
  355. RefId: "A",
  356. },
  357. },
  358. }
  359. resp, err := endpoint.Query(nil, nil, query)
  360. So(err, ShouldBeNil)
  361. queryResult := resp.Results["A"]
  362. So(queryResult.Error, ShouldBeNil)
  363. So(len(queryResult.Series), ShouldEqual, 1)
  364. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  365. })
  366. Convey("When doing a metric query using epoch (float64 nullable) as time column and value column (float64 nullable) should return metric with time in milliseconds", func() {
  367. query := &tsdb.TsdbQuery{
  368. Queries: []*tsdb.Query{
  369. {
  370. Model: simplejson.NewFromAny(map[string]interface{}{
  371. "rawSql": `SELECT "timeFloat64Nullable" as time, "timeFloat64Nullable" FROM metric_values ORDER BY time LIMIT 1`,
  372. "format": "time_series",
  373. }),
  374. RefId: "A",
  375. },
  376. },
  377. }
  378. resp, err := endpoint.Query(nil, nil, query)
  379. So(err, ShouldBeNil)
  380. queryResult := resp.Results["A"]
  381. So(queryResult.Error, ShouldBeNil)
  382. So(len(queryResult.Series), ShouldEqual, 1)
  383. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  384. })
  385. Convey("When doing a metric query using epoch (int32) as time column and value column (int32) should return metric with time in milliseconds", func() {
  386. query := &tsdb.TsdbQuery{
  387. Queries: []*tsdb.Query{
  388. {
  389. Model: simplejson.NewFromAny(map[string]interface{}{
  390. "rawSql": `SELECT "timeInt32" as time, "timeInt32" FROM metric_values ORDER BY time LIMIT 1`,
  391. "format": "time_series",
  392. }),
  393. RefId: "A",
  394. },
  395. },
  396. }
  397. resp, err := endpoint.Query(nil, nil, query)
  398. So(err, ShouldBeNil)
  399. queryResult := resp.Results["A"]
  400. So(queryResult.Error, ShouldBeNil)
  401. So(len(queryResult.Series), ShouldEqual, 1)
  402. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  403. })
  404. Convey("When doing a metric query using epoch (int32 nullable) as time column and value column (int32 nullable) should return metric with time in milliseconds", func() {
  405. query := &tsdb.TsdbQuery{
  406. Queries: []*tsdb.Query{
  407. {
  408. Model: simplejson.NewFromAny(map[string]interface{}{
  409. "rawSql": `SELECT "timeInt32Nullable" as time, "timeInt32Nullable" FROM metric_values ORDER BY time LIMIT 1`,
  410. "format": "time_series",
  411. }),
  412. RefId: "A",
  413. },
  414. },
  415. }
  416. resp, err := endpoint.Query(nil, nil, query)
  417. So(err, ShouldBeNil)
  418. queryResult := resp.Results["A"]
  419. So(queryResult.Error, ShouldBeNil)
  420. So(len(queryResult.Series), ShouldEqual, 1)
  421. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  422. })
  423. Convey("When doing a metric query using epoch (float32) as time column and value column (float32) should return metric with time in milliseconds", func() {
  424. query := &tsdb.TsdbQuery{
  425. Queries: []*tsdb.Query{
  426. {
  427. Model: simplejson.NewFromAny(map[string]interface{}{
  428. "rawSql": `SELECT "timeFloat32" as time, "timeFloat32" FROM metric_values ORDER BY time LIMIT 1`,
  429. "format": "time_series",
  430. }),
  431. RefId: "A",
  432. },
  433. },
  434. }
  435. resp, err := endpoint.Query(nil, nil, query)
  436. So(err, ShouldBeNil)
  437. queryResult := resp.Results["A"]
  438. So(queryResult.Error, ShouldBeNil)
  439. So(len(queryResult.Series), ShouldEqual, 1)
  440. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
  441. })
  442. Convey("When doing a metric query using epoch (float32 nullable) as time column and value column (float32 nullable) should return metric with time in milliseconds", func() {
  443. query := &tsdb.TsdbQuery{
  444. Queries: []*tsdb.Query{
  445. {
  446. Model: simplejson.NewFromAny(map[string]interface{}{
  447. "rawSql": `SELECT "timeFloat32Nullable" as time, "timeFloat32Nullable" FROM metric_values ORDER BY time LIMIT 1`,
  448. "format": "time_series",
  449. }),
  450. RefId: "A",
  451. },
  452. },
  453. }
  454. resp, err := endpoint.Query(nil, nil, query)
  455. So(err, ShouldBeNil)
  456. queryResult := resp.Results["A"]
  457. So(queryResult.Error, ShouldBeNil)
  458. So(len(queryResult.Series), ShouldEqual, 1)
  459. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
  460. })
  461. Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
  462. query := &tsdb.TsdbQuery{
  463. Queries: []*tsdb.Query{
  464. {
  465. Model: simplejson.NewFromAny(map[string]interface{}{
  466. "rawSql": `SELECT $__timeEpoch(time), measurement || ' - value one' as metric, "valueOne" FROM metric_values ORDER BY 1`,
  467. "format": "time_series",
  468. }),
  469. RefId: "A",
  470. },
  471. },
  472. }
  473. resp, err := endpoint.Query(nil, nil, query)
  474. So(err, ShouldBeNil)
  475. queryResult := resp.Results["A"]
  476. So(queryResult.Error, ShouldBeNil)
  477. So(len(queryResult.Series), ShouldEqual, 2)
  478. So(queryResult.Series[0].Name, ShouldEqual, "Metric A - value one")
  479. So(queryResult.Series[1].Name, ShouldEqual, "Metric B - value one")
  480. })
  481. Convey("When doing a metric query grouping by time should return correct series", func() {
  482. query := &tsdb.TsdbQuery{
  483. Queries: []*tsdb.Query{
  484. {
  485. Model: simplejson.NewFromAny(map[string]interface{}{
  486. "rawSql": `SELECT $__timeEpoch(time), "valueOne", "valueTwo" FROM metric_values ORDER BY 1`,
  487. "format": "time_series",
  488. }),
  489. RefId: "A",
  490. },
  491. },
  492. }
  493. resp, err := endpoint.Query(nil, nil, query)
  494. So(err, ShouldBeNil)
  495. queryResult := resp.Results["A"]
  496. So(queryResult.Error, ShouldBeNil)
  497. So(len(queryResult.Series), ShouldEqual, 2)
  498. So(queryResult.Series[0].Name, ShouldEqual, "valueOne")
  499. So(queryResult.Series[1].Name, ShouldEqual, "valueTwo")
  500. })
  501. })
  502. Convey("Given a table with event data", func() {
  503. type event struct {
  504. TimeSec int64
  505. Description string
  506. Tags string
  507. }
  508. if exist, err := sess.IsTableExist(event{}); err != nil || exist {
  509. So(err, ShouldBeNil)
  510. sess.DropTable(event{})
  511. }
  512. err := sess.CreateTable(event{})
  513. So(err, ShouldBeNil)
  514. events := []*event{}
  515. for _, t := range genTimeRangeByInterval(fromStart.Add(-20*time.Minute), 60*time.Minute, 25*time.Minute) {
  516. events = append(events, &event{
  517. TimeSec: t.Unix(),
  518. Description: "Someone deployed something",
  519. Tags: "deploy",
  520. })
  521. events = append(events, &event{
  522. TimeSec: t.Add(5 * time.Minute).Unix(),
  523. Description: "New support ticket registered",
  524. Tags: "ticket",
  525. })
  526. }
  527. for _, e := range events {
  528. _, err = sess.Insert(e)
  529. So(err, ShouldBeNil)
  530. }
  531. Convey("When doing an annotation query of deploy events should return expected result", func() {
  532. query := &tsdb.TsdbQuery{
  533. Queries: []*tsdb.Query{
  534. {
  535. Model: simplejson.NewFromAny(map[string]interface{}{
  536. "rawSql": `SELECT "time_sec" as time, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC`,
  537. "format": "table",
  538. }),
  539. RefId: "Deploys",
  540. },
  541. },
  542. TimeRange: &tsdb.TimeRange{
  543. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  544. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  545. },
  546. }
  547. resp, err := endpoint.Query(nil, nil, query)
  548. queryResult := resp.Results["Deploys"]
  549. So(err, ShouldBeNil)
  550. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  551. })
  552. Convey("When doing an annotation query of ticket events should return expected result", func() {
  553. query := &tsdb.TsdbQuery{
  554. Queries: []*tsdb.Query{
  555. {
  556. Model: simplejson.NewFromAny(map[string]interface{}{
  557. "rawSql": `SELECT "time_sec" as time, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC`,
  558. "format": "table",
  559. }),
  560. RefId: "Tickets",
  561. },
  562. },
  563. TimeRange: &tsdb.TimeRange{
  564. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  565. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  566. },
  567. }
  568. resp, err := endpoint.Query(nil, nil, query)
  569. queryResult := resp.Results["Tickets"]
  570. So(err, ShouldBeNil)
  571. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  572. })
  573. Convey("When doing an annotation query with a time column in datetime format", func() {
  574. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  575. dtFormat := "2006-01-02 15:04:05.999999999"
  576. query := &tsdb.TsdbQuery{
  577. Queries: []*tsdb.Query{
  578. {
  579. Model: simplejson.NewFromAny(map[string]interface{}{
  580. "rawSql": fmt.Sprintf(`SELECT
  581. CAST('%s' AS TIMESTAMP) as time,
  582. 'message' as text,
  583. 'tag1,tag2' as tags
  584. `, dt.Format(dtFormat)),
  585. "format": "table",
  586. }),
  587. RefId: "A",
  588. },
  589. },
  590. }
  591. resp, err := endpoint.Query(nil, nil, query)
  592. So(err, ShouldBeNil)
  593. queryResult := resp.Results["A"]
  594. So(queryResult.Error, ShouldBeNil)
  595. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  596. columns := queryResult.Tables[0].Rows[0]
  597. //Should be in milliseconds
  598. So(columns[0].(float64), ShouldEqual, float64(dt.UnixNano()/1e6))
  599. })
  600. Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
  601. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  602. query := &tsdb.TsdbQuery{
  603. Queries: []*tsdb.Query{
  604. {
  605. Model: simplejson.NewFromAny(map[string]interface{}{
  606. "rawSql": fmt.Sprintf(`SELECT
  607. %d as time,
  608. 'message' as text,
  609. 'tag1,tag2' as tags
  610. `, dt.Unix()),
  611. "format": "table",
  612. }),
  613. RefId: "A",
  614. },
  615. },
  616. }
  617. resp, err := endpoint.Query(nil, nil, query)
  618. So(err, ShouldBeNil)
  619. queryResult := resp.Results["A"]
  620. So(queryResult.Error, ShouldBeNil)
  621. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  622. columns := queryResult.Tables[0].Rows[0]
  623. //Should be in milliseconds
  624. So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
  625. })
  626. Convey("When doing an annotation query with a time column in epoch second format (int) should return ms", func() {
  627. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  628. query := &tsdb.TsdbQuery{
  629. Queries: []*tsdb.Query{
  630. {
  631. Model: simplejson.NewFromAny(map[string]interface{}{
  632. "rawSql": fmt.Sprintf(`SELECT
  633. cast(%d as bigint) as time,
  634. 'message' as text,
  635. 'tag1,tag2' as tags
  636. `, dt.Unix()),
  637. "format": "table",
  638. }),
  639. RefId: "A",
  640. },
  641. },
  642. }
  643. resp, err := endpoint.Query(nil, nil, query)
  644. So(err, ShouldBeNil)
  645. queryResult := resp.Results["A"]
  646. So(queryResult.Error, ShouldBeNil)
  647. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  648. columns := queryResult.Tables[0].Rows[0]
  649. //Should be in milliseconds
  650. So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
  651. })
  652. Convey("When doing an annotation query with a time column in epoch millisecond format should return ms", func() {
  653. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  654. query := &tsdb.TsdbQuery{
  655. Queries: []*tsdb.Query{
  656. {
  657. Model: simplejson.NewFromAny(map[string]interface{}{
  658. "rawSql": fmt.Sprintf(`SELECT
  659. %d as time,
  660. 'message' as text,
  661. 'tag1,tag2' as tags
  662. `, dt.Unix()*1000),
  663. "format": "table",
  664. }),
  665. RefId: "A",
  666. },
  667. },
  668. }
  669. resp, err := endpoint.Query(nil, nil, query)
  670. So(err, ShouldBeNil)
  671. queryResult := resp.Results["A"]
  672. So(queryResult.Error, ShouldBeNil)
  673. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  674. columns := queryResult.Tables[0].Rows[0]
  675. //Should be in milliseconds
  676. So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
  677. })
  678. Convey("When doing an annotation query with a time column holding a bigint null value should return nil", func() {
  679. query := &tsdb.TsdbQuery{
  680. Queries: []*tsdb.Query{
  681. {
  682. Model: simplejson.NewFromAny(map[string]interface{}{
  683. "rawSql": `SELECT
  684. cast(null as bigint) as time,
  685. 'message' as text,
  686. 'tag1,tag2' as tags
  687. `,
  688. "format": "table",
  689. }),
  690. RefId: "A",
  691. },
  692. },
  693. }
  694. resp, err := endpoint.Query(nil, nil, query)
  695. So(err, ShouldBeNil)
  696. queryResult := resp.Results["A"]
  697. So(queryResult.Error, ShouldBeNil)
  698. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  699. columns := queryResult.Tables[0].Rows[0]
  700. //Should be in milliseconds
  701. So(columns[0], ShouldBeNil)
  702. })
  703. Convey("When doing an annotation query with a time column holding a timestamp null value should return nil", func() {
  704. query := &tsdb.TsdbQuery{
  705. Queries: []*tsdb.Query{
  706. {
  707. Model: simplejson.NewFromAny(map[string]interface{}{
  708. "rawSql": `SELECT
  709. cast(null as timestamp) as time,
  710. 'message' as text,
  711. 'tag1,tag2' as tags
  712. `,
  713. "format": "table",
  714. }),
  715. RefId: "A",
  716. },
  717. },
  718. }
  719. resp, err := endpoint.Query(nil, nil, query)
  720. So(err, ShouldBeNil)
  721. queryResult := resp.Results["A"]
  722. So(queryResult.Error, ShouldBeNil)
  723. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  724. columns := queryResult.Tables[0].Rows[0]
  725. //Should be in milliseconds
  726. So(columns[0], ShouldBeNil)
  727. })
  728. })
  729. })
  730. }
  731. func InitPostgresTestDB(t *testing.T) *xorm.Engine {
  732. x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, strings.Replace(sqlutil.TestDB_Postgres.ConnStr, "dbname=grafanatest", "dbname=grafanadstest", 1))
  733. if err != nil {
  734. t.Fatalf("Failed to init postgres db %v", err)
  735. }
  736. x.DatabaseTZ = time.UTC
  737. x.TZLocation = time.UTC
  738. // x.ShowSQL()
  739. return x
  740. }
  741. func genTimeRangeByInterval(from time.Time, duration time.Duration, interval time.Duration) []time.Time {
  742. durationSec := int64(duration.Seconds())
  743. intervalSec := int64(interval.Seconds())
  744. timeRange := []time.Time{}
  745. for i := int64(0); i < durationSec; i += intervalSec {
  746. timeRange = append(timeRange, from)
  747. from = from.Add(time.Duration(int64(time.Second) * intervalSec))
  748. }
  749. return timeRange
  750. }