postgres_test.go 29 KB

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