postgres_test.go 30 KB

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