postgres_test.go 32 KB

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