postgres_test.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953
  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. Model: simplejson.NewFromAny(map[string]interface{}{
  266. "rawSql": "SELECT $__timeGroup(time, '5m', previous), avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
  267. "format": "time_series",
  268. }),
  269. RefId: "A",
  270. },
  271. },
  272. TimeRange: &tsdb.TimeRange{
  273. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  274. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  275. },
  276. }
  277. resp, err := endpoint.Query(nil, nil, query)
  278. So(err, ShouldBeNil)
  279. queryResult := resp.Results["A"]
  280. So(queryResult.Error, ShouldBeNil)
  281. points := queryResult.Series[0].Points
  282. So(points[2][0].Float64, ShouldEqual, 15.0)
  283. So(points[3][0].Float64, ShouldEqual, 15.0)
  284. So(points[6][0].Float64, ShouldEqual, 20.0)
  285. })
  286. Convey("Given a table with metrics having multiple values and measurements", func() {
  287. type metric_values struct {
  288. Time time.Time
  289. TimeInt64 int64 `xorm:"bigint 'timeInt64' not null"`
  290. TimeInt64Nullable *int64 `xorm:"bigint 'timeInt64Nullable' null"`
  291. TimeFloat64 float64 `xorm:"double 'timeFloat64' not null"`
  292. TimeFloat64Nullable *float64 `xorm:"double 'timeFloat64Nullable' null"`
  293. TimeInt32 int32 `xorm:"int(11) 'timeInt32' not null"`
  294. TimeInt32Nullable *int32 `xorm:"int(11) 'timeInt32Nullable' null"`
  295. TimeFloat32 float32 `xorm:"double 'timeFloat32' not null"`
  296. TimeFloat32Nullable *float32 `xorm:"double 'timeFloat32Nullable' null"`
  297. Measurement string
  298. ValueOne int64 `xorm:"integer 'valueOne'"`
  299. ValueTwo int64 `xorm:"integer 'valueTwo'"`
  300. }
  301. if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
  302. So(err, ShouldBeNil)
  303. sess.DropTable(metric_values{})
  304. }
  305. err := sess.CreateTable(metric_values{})
  306. So(err, ShouldBeNil)
  307. rand.Seed(time.Now().Unix())
  308. rnd := func(min, max int64) int64 {
  309. return rand.Int63n(max-min) + min
  310. }
  311. var tInitial time.Time
  312. series := []*metric_values{}
  313. for i, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
  314. if i == 0 {
  315. tInitial = t
  316. }
  317. tSeconds := t.Unix()
  318. tSecondsInt32 := int32(tSeconds)
  319. tSecondsFloat32 := float32(tSeconds)
  320. tMilliseconds := tSeconds * 1e3
  321. tMillisecondsFloat := float64(tMilliseconds)
  322. first := metric_values{
  323. Time: t,
  324. TimeInt64: tMilliseconds,
  325. TimeInt64Nullable: &(tMilliseconds),
  326. TimeFloat64: tMillisecondsFloat,
  327. TimeFloat64Nullable: &tMillisecondsFloat,
  328. TimeInt32: tSecondsInt32,
  329. TimeInt32Nullable: &tSecondsInt32,
  330. TimeFloat32: tSecondsFloat32,
  331. TimeFloat32Nullable: &tSecondsFloat32,
  332. Measurement: "Metric A",
  333. ValueOne: rnd(0, 100),
  334. ValueTwo: rnd(0, 100),
  335. }
  336. second := first
  337. second.Measurement = "Metric B"
  338. second.ValueOne = rnd(0, 100)
  339. second.ValueTwo = rnd(0, 100)
  340. series = append(series, &first)
  341. series = append(series, &second)
  342. }
  343. _, err = sess.InsertMulti(series)
  344. So(err, ShouldBeNil)
  345. Convey("When doing a metric query using epoch (int64) as time column and value column (int64) should return metric with time in milliseconds", func() {
  346. query := &tsdb.TsdbQuery{
  347. Queries: []*tsdb.Query{
  348. {
  349. DataSource: &models.DataSource{JsonData: simplejson.New()},
  350. Model: simplejson.NewFromAny(map[string]interface{}{
  351. "rawSql": `SELECT "timeInt64" as time, "timeInt64" FROM metric_values ORDER BY time LIMIT 1`,
  352. "format": "time_series",
  353. }),
  354. RefId: "A",
  355. },
  356. },
  357. }
  358. resp, err := endpoint.Query(nil, nil, query)
  359. So(err, ShouldBeNil)
  360. queryResult := resp.Results["A"]
  361. So(queryResult.Error, ShouldBeNil)
  362. So(len(queryResult.Series), ShouldEqual, 1)
  363. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  364. })
  365. 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() {
  366. query := &tsdb.TsdbQuery{
  367. Queries: []*tsdb.Query{
  368. {
  369. DataSource: &models.DataSource{JsonData: simplejson.New()},
  370. Model: simplejson.NewFromAny(map[string]interface{}{
  371. "rawSql": `SELECT "timeInt64Nullable" as time, "timeInt64Nullable" FROM metric_values ORDER BY time LIMIT 1`,
  372. "format": "time_series",
  373. }),
  374. RefId: "A",
  375. },
  376. },
  377. }
  378. resp, err := endpoint.Query(nil, nil, query)
  379. So(err, ShouldBeNil)
  380. queryResult := resp.Results["A"]
  381. So(queryResult.Error, ShouldBeNil)
  382. So(len(queryResult.Series), ShouldEqual, 1)
  383. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  384. })
  385. Convey("When doing a metric query using epoch (float64) as time column and value column (float64) should return metric with time in milliseconds", func() {
  386. query := &tsdb.TsdbQuery{
  387. Queries: []*tsdb.Query{
  388. {
  389. DataSource: &models.DataSource{JsonData: simplejson.New()},
  390. Model: simplejson.NewFromAny(map[string]interface{}{
  391. "rawSql": `SELECT "timeFloat64" as time, "timeFloat64" FROM metric_values ORDER BY time LIMIT 1`,
  392. "format": "time_series",
  393. }),
  394. RefId: "A",
  395. },
  396. },
  397. }
  398. resp, err := endpoint.Query(nil, nil, query)
  399. So(err, ShouldBeNil)
  400. queryResult := resp.Results["A"]
  401. So(queryResult.Error, ShouldBeNil)
  402. So(len(queryResult.Series), ShouldEqual, 1)
  403. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  404. })
  405. 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() {
  406. query := &tsdb.TsdbQuery{
  407. Queries: []*tsdb.Query{
  408. {
  409. DataSource: &models.DataSource{JsonData: simplejson.New()},
  410. Model: simplejson.NewFromAny(map[string]interface{}{
  411. "rawSql": `SELECT "timeFloat64Nullable" as time, "timeFloat64Nullable" FROM metric_values ORDER BY time LIMIT 1`,
  412. "format": "time_series",
  413. }),
  414. RefId: "A",
  415. },
  416. },
  417. }
  418. resp, err := endpoint.Query(nil, nil, query)
  419. So(err, ShouldBeNil)
  420. queryResult := resp.Results["A"]
  421. So(queryResult.Error, ShouldBeNil)
  422. So(len(queryResult.Series), ShouldEqual, 1)
  423. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  424. })
  425. Convey("When doing a metric query using epoch (int32) as time column and value column (int32) should return metric with time in milliseconds", func() {
  426. query := &tsdb.TsdbQuery{
  427. Queries: []*tsdb.Query{
  428. {
  429. Model: simplejson.NewFromAny(map[string]interface{}{
  430. "rawSql": `SELECT "timeInt32" as time, "timeInt32" FROM metric_values ORDER BY time LIMIT 1`,
  431. "format": "time_series",
  432. }),
  433. RefId: "A",
  434. },
  435. },
  436. }
  437. resp, err := endpoint.Query(nil, nil, query)
  438. So(err, ShouldBeNil)
  439. queryResult := resp.Results["A"]
  440. So(queryResult.Error, ShouldBeNil)
  441. So(len(queryResult.Series), ShouldEqual, 1)
  442. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  443. })
  444. 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() {
  445. query := &tsdb.TsdbQuery{
  446. Queries: []*tsdb.Query{
  447. {
  448. DataSource: &models.DataSource{JsonData: simplejson.New()},
  449. Model: simplejson.NewFromAny(map[string]interface{}{
  450. "rawSql": `SELECT "timeInt32Nullable" as time, "timeInt32Nullable" FROM metric_values ORDER BY time LIMIT 1`,
  451. "format": "time_series",
  452. }),
  453. RefId: "A",
  454. },
  455. },
  456. }
  457. resp, err := endpoint.Query(nil, nil, query)
  458. So(err, ShouldBeNil)
  459. queryResult := resp.Results["A"]
  460. So(queryResult.Error, ShouldBeNil)
  461. So(len(queryResult.Series), ShouldEqual, 1)
  462. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  463. })
  464. Convey("When doing a metric query using epoch (float32) as time column and value column (float32) should return metric with time in milliseconds", func() {
  465. query := &tsdb.TsdbQuery{
  466. Queries: []*tsdb.Query{
  467. {
  468. DataSource: &models.DataSource{JsonData: simplejson.New()},
  469. Model: simplejson.NewFromAny(map[string]interface{}{
  470. "rawSql": `SELECT "timeFloat32" as time, "timeFloat32" FROM metric_values ORDER BY time LIMIT 1`,
  471. "format": "time_series",
  472. }),
  473. RefId: "A",
  474. },
  475. },
  476. }
  477. resp, err := endpoint.Query(nil, nil, query)
  478. So(err, ShouldBeNil)
  479. queryResult := resp.Results["A"]
  480. So(queryResult.Error, ShouldBeNil)
  481. So(len(queryResult.Series), ShouldEqual, 1)
  482. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float32(tInitial.Unix()))*1e3)
  483. })
  484. 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() {
  485. query := &tsdb.TsdbQuery{
  486. Queries: []*tsdb.Query{
  487. {
  488. DataSource: &models.DataSource{JsonData: simplejson.New()},
  489. Model: simplejson.NewFromAny(map[string]interface{}{
  490. "rawSql": `SELECT "timeFloat32Nullable" as time, "timeFloat32Nullable" FROM metric_values ORDER BY time LIMIT 1`,
  491. "format": "time_series",
  492. }),
  493. RefId: "A",
  494. },
  495. },
  496. }
  497. resp, err := endpoint.Query(nil, nil, query)
  498. So(err, ShouldBeNil)
  499. queryResult := resp.Results["A"]
  500. So(queryResult.Error, ShouldBeNil)
  501. So(len(queryResult.Series), ShouldEqual, 1)
  502. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float32(tInitial.Unix()))*1e3)
  503. })
  504. Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
  505. query := &tsdb.TsdbQuery{
  506. Queries: []*tsdb.Query{
  507. {
  508. DataSource: &models.DataSource{JsonData: simplejson.New()},
  509. Model: simplejson.NewFromAny(map[string]interface{}{
  510. "rawSql": `SELECT $__timeEpoch(time), measurement || ' - value one' as metric, "valueOne" FROM metric_values ORDER BY 1`,
  511. "format": "time_series",
  512. }),
  513. RefId: "A",
  514. },
  515. },
  516. }
  517. resp, err := endpoint.Query(nil, nil, query)
  518. So(err, ShouldBeNil)
  519. queryResult := resp.Results["A"]
  520. So(queryResult.Error, ShouldBeNil)
  521. So(len(queryResult.Series), ShouldEqual, 2)
  522. So(queryResult.Series[0].Name, ShouldEqual, "Metric A - value one")
  523. So(queryResult.Series[1].Name, ShouldEqual, "Metric B - value one")
  524. })
  525. Convey("When doing a metric query with metric column and multiple value columns", func() {
  526. query := &tsdb.TsdbQuery{
  527. Queries: []*tsdb.Query{
  528. {
  529. Model: simplejson.NewFromAny(map[string]interface{}{
  530. "rawSql": `SELECT $__timeEpoch(time), measurement as metric, "valueOne", "valueTwo" FROM metric_values ORDER BY 1`,
  531. "format": "time_series",
  532. }),
  533. RefId: "A",
  534. },
  535. },
  536. }
  537. resp, err := endpoint.Query(nil, nil, query)
  538. So(err, ShouldBeNil)
  539. queryResult := resp.Results["A"]
  540. So(queryResult.Error, ShouldBeNil)
  541. So(len(queryResult.Series), ShouldEqual, 4)
  542. So(queryResult.Series[0].Name, ShouldEqual, "Metric A valueOne")
  543. So(queryResult.Series[1].Name, ShouldEqual, "Metric A valueTwo")
  544. So(queryResult.Series[2].Name, ShouldEqual, "Metric B valueOne")
  545. So(queryResult.Series[3].Name, ShouldEqual, "Metric B valueTwo")
  546. })
  547. Convey("When doing a metric query grouping by time should return correct series", func() {
  548. query := &tsdb.TsdbQuery{
  549. Queries: []*tsdb.Query{
  550. {
  551. DataSource: &models.DataSource{JsonData: simplejson.New()},
  552. Model: simplejson.NewFromAny(map[string]interface{}{
  553. "rawSql": `SELECT $__timeEpoch(time), "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, 2)
  565. So(queryResult.Series[0].Name, ShouldEqual, "valueOne")
  566. So(queryResult.Series[1].Name, ShouldEqual, "valueTwo")
  567. })
  568. })
  569. Convey("Given a table with event data", func() {
  570. type event struct {
  571. TimeSec int64
  572. Description string
  573. Tags string
  574. }
  575. if exist, err := sess.IsTableExist(event{}); err != nil || exist {
  576. So(err, ShouldBeNil)
  577. sess.DropTable(event{})
  578. }
  579. err := sess.CreateTable(event{})
  580. So(err, ShouldBeNil)
  581. events := []*event{}
  582. for _, t := range genTimeRangeByInterval(fromStart.Add(-20*time.Minute), 60*time.Minute, 25*time.Minute) {
  583. events = append(events, &event{
  584. TimeSec: t.Unix(),
  585. Description: "Someone deployed something",
  586. Tags: "deploy",
  587. })
  588. events = append(events, &event{
  589. TimeSec: t.Add(5 * time.Minute).Unix(),
  590. Description: "New support ticket registered",
  591. Tags: "ticket",
  592. })
  593. }
  594. for _, e := range events {
  595. _, err = sess.Insert(e)
  596. So(err, ShouldBeNil)
  597. }
  598. Convey("When doing an annotation query of deploy events should return expected result", func() {
  599. query := &tsdb.TsdbQuery{
  600. Queries: []*tsdb.Query{
  601. {
  602. DataSource: &models.DataSource{JsonData: simplejson.New()},
  603. Model: simplejson.NewFromAny(map[string]interface{}{
  604. "rawSql": `SELECT "time_sec" as time, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC`,
  605. "format": "table",
  606. }),
  607. RefId: "Deploys",
  608. },
  609. },
  610. TimeRange: &tsdb.TimeRange{
  611. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  612. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  613. },
  614. }
  615. resp, err := endpoint.Query(nil, nil, query)
  616. queryResult := resp.Results["Deploys"]
  617. So(err, ShouldBeNil)
  618. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  619. })
  620. Convey("When doing an annotation query of ticket events should return expected result", func() {
  621. query := &tsdb.TsdbQuery{
  622. Queries: []*tsdb.Query{
  623. {
  624. DataSource: &models.DataSource{JsonData: simplejson.New()},
  625. Model: simplejson.NewFromAny(map[string]interface{}{
  626. "rawSql": `SELECT "time_sec" as time, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC`,
  627. "format": "table",
  628. }),
  629. RefId: "Tickets",
  630. },
  631. },
  632. TimeRange: &tsdb.TimeRange{
  633. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  634. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  635. },
  636. }
  637. resp, err := endpoint.Query(nil, nil, query)
  638. queryResult := resp.Results["Tickets"]
  639. So(err, ShouldBeNil)
  640. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  641. })
  642. Convey("When doing an annotation query with a time column in datetime format", func() {
  643. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  644. dtFormat := "2006-01-02 15:04:05.999999999"
  645. query := &tsdb.TsdbQuery{
  646. Queries: []*tsdb.Query{
  647. {
  648. DataSource: &models.DataSource{JsonData: simplejson.New()},
  649. Model: simplejson.NewFromAny(map[string]interface{}{
  650. "rawSql": fmt.Sprintf(`SELECT
  651. CAST('%s' AS TIMESTAMP) as time,
  652. 'message' as text,
  653. 'tag1,tag2' as tags
  654. `, dt.Format(dtFormat)),
  655. "format": "table",
  656. }),
  657. RefId: "A",
  658. },
  659. },
  660. }
  661. resp, err := endpoint.Query(nil, nil, query)
  662. So(err, ShouldBeNil)
  663. queryResult := resp.Results["A"]
  664. So(queryResult.Error, ShouldBeNil)
  665. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  666. columns := queryResult.Tables[0].Rows[0]
  667. //Should be in milliseconds
  668. So(columns[0].(float64), ShouldEqual, float64(dt.UnixNano()/1e6))
  669. })
  670. Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
  671. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  672. query := &tsdb.TsdbQuery{
  673. Queries: []*tsdb.Query{
  674. {
  675. DataSource: &models.DataSource{JsonData: simplejson.New()},
  676. Model: simplejson.NewFromAny(map[string]interface{}{
  677. "rawSql": fmt.Sprintf(`SELECT
  678. %d as time,
  679. 'message' as text,
  680. 'tag1,tag2' as tags
  681. `, dt.Unix()),
  682. "format": "table",
  683. }),
  684. RefId: "A",
  685. },
  686. },
  687. }
  688. resp, err := endpoint.Query(nil, nil, query)
  689. So(err, ShouldBeNil)
  690. queryResult := resp.Results["A"]
  691. So(queryResult.Error, ShouldBeNil)
  692. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  693. columns := queryResult.Tables[0].Rows[0]
  694. //Should be in milliseconds
  695. So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
  696. })
  697. Convey("When doing an annotation query with a time column in epoch second format (int) should return ms", func() {
  698. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  699. query := &tsdb.TsdbQuery{
  700. Queries: []*tsdb.Query{
  701. {
  702. DataSource: &models.DataSource{JsonData: simplejson.New()},
  703. Model: simplejson.NewFromAny(map[string]interface{}{
  704. "rawSql": fmt.Sprintf(`SELECT
  705. cast(%d as bigint) as time,
  706. 'message' as text,
  707. 'tag1,tag2' as tags
  708. `, dt.Unix()),
  709. "format": "table",
  710. }),
  711. RefId: "A",
  712. },
  713. },
  714. }
  715. resp, err := endpoint.Query(nil, nil, query)
  716. So(err, ShouldBeNil)
  717. queryResult := resp.Results["A"]
  718. So(queryResult.Error, ShouldBeNil)
  719. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  720. columns := queryResult.Tables[0].Rows[0]
  721. //Should be in milliseconds
  722. So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
  723. })
  724. Convey("When doing an annotation query with a time column in epoch millisecond format should return ms", func() {
  725. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  726. query := &tsdb.TsdbQuery{
  727. Queries: []*tsdb.Query{
  728. {
  729. DataSource: &models.DataSource{JsonData: simplejson.New()},
  730. Model: simplejson.NewFromAny(map[string]interface{}{
  731. "rawSql": fmt.Sprintf(`SELECT
  732. %d as time,
  733. 'message' as text,
  734. 'tag1,tag2' as tags
  735. `, dt.Unix()*1000),
  736. "format": "table",
  737. }),
  738. RefId: "A",
  739. },
  740. },
  741. }
  742. resp, err := endpoint.Query(nil, nil, query)
  743. So(err, ShouldBeNil)
  744. queryResult := resp.Results["A"]
  745. So(queryResult.Error, ShouldBeNil)
  746. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  747. columns := queryResult.Tables[0].Rows[0]
  748. //Should be in milliseconds
  749. So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
  750. })
  751. Convey("When doing an annotation query with a time column holding a bigint null value should return nil", func() {
  752. query := &tsdb.TsdbQuery{
  753. Queries: []*tsdb.Query{
  754. {
  755. DataSource: &models.DataSource{JsonData: simplejson.New()},
  756. Model: simplejson.NewFromAny(map[string]interface{}{
  757. "rawSql": `SELECT
  758. cast(null as bigint) as time,
  759. 'message' as text,
  760. 'tag1,tag2' as tags
  761. `,
  762. "format": "table",
  763. }),
  764. RefId: "A",
  765. },
  766. },
  767. }
  768. resp, err := endpoint.Query(nil, nil, query)
  769. So(err, ShouldBeNil)
  770. queryResult := resp.Results["A"]
  771. So(queryResult.Error, ShouldBeNil)
  772. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  773. columns := queryResult.Tables[0].Rows[0]
  774. //Should be in milliseconds
  775. So(columns[0], ShouldBeNil)
  776. })
  777. Convey("When doing an annotation query with a time column holding a timestamp null value should return nil", func() {
  778. query := &tsdb.TsdbQuery{
  779. Queries: []*tsdb.Query{
  780. {
  781. DataSource: &models.DataSource{JsonData: simplejson.New()},
  782. Model: simplejson.NewFromAny(map[string]interface{}{
  783. "rawSql": `SELECT
  784. cast(null as timestamp) as time,
  785. 'message' as text,
  786. 'tag1,tag2' as tags
  787. `,
  788. "format": "table",
  789. }),
  790. RefId: "A",
  791. },
  792. },
  793. }
  794. resp, err := endpoint.Query(nil, nil, query)
  795. So(err, ShouldBeNil)
  796. queryResult := resp.Results["A"]
  797. So(queryResult.Error, ShouldBeNil)
  798. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  799. columns := queryResult.Tables[0].Rows[0]
  800. //Should be in milliseconds
  801. So(columns[0], ShouldBeNil)
  802. })
  803. })
  804. })
  805. }
  806. func InitPostgresTestDB(t *testing.T) *xorm.Engine {
  807. x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, strings.Replace(sqlutil.TestDB_Postgres.ConnStr, "dbname=grafanatest", "dbname=grafanadstest", 1))
  808. if err != nil {
  809. t.Fatalf("Failed to init postgres db %v", err)
  810. }
  811. x.DatabaseTZ = time.UTC
  812. x.TZLocation = time.UTC
  813. // x.ShowSQL()
  814. return x
  815. }
  816. func genTimeRangeByInterval(from time.Time, duration time.Duration, interval time.Duration) []time.Time {
  817. durationSec := int64(duration.Seconds())
  818. intervalSec := int64(interval.Seconds())
  819. timeRange := []time.Time{}
  820. for i := int64(0); i < durationSec; i += intervalSec {
  821. timeRange = append(timeRange, from)
  822. from = from.Add(time.Duration(int64(time.Second) * intervalSec))
  823. }
  824. return timeRange
  825. }