postgres_test.go 30 KB

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