mssql_test.go 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080
  1. package mssql
  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/simplejson"
  10. "github.com/grafana/grafana/pkg/log"
  11. "github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
  12. "github.com/grafana/grafana/pkg/tsdb"
  13. . "github.com/smartystreets/goconvey/convey"
  14. )
  15. // To run this test, remove the Skip from SkipConvey
  16. // The tests require a MSSQL db named grafanatest and a user/password grafana/Password!
  17. // Use the docker/blocks/mssql_tests/docker-compose.yaml to spin up a
  18. // preconfigured MSSQL server suitable for running these tests.
  19. // There is also a dashboard.json in same directory that you can import to Grafana
  20. // once you've created a datasource for the test server/database.
  21. // If needed, change the variable below to the IP address of the database.
  22. var serverIP = "localhost"
  23. func TestMSSQL(t *testing.T) {
  24. SkipConvey("MSSQL", t, func() {
  25. x := InitMSSQLTestDB(t)
  26. endpoint := &MssqlQueryEndpoint{
  27. sqlEngine: &tsdb.DefaultSqlEngine{
  28. MacroEngine: NewMssqlMacroEngine(),
  29. XormEngine: x,
  30. },
  31. log: log.New("tsdb.mssql"),
  32. }
  33. sess := x.NewSession()
  34. defer sess.Close()
  35. fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local)
  36. Convey("Given a table with different native data types", func() {
  37. sql := `
  38. IF OBJECT_ID('dbo.[mssql_types]', 'U') IS NOT NULL
  39. DROP TABLE dbo.[mssql_types]
  40. CREATE TABLE [mssql_types] (
  41. c_bit bit,
  42. c_tinyint tinyint,
  43. c_smallint smallint,
  44. c_int int,
  45. c_bigint bigint,
  46. c_money money,
  47. c_smallmoney smallmoney,
  48. c_numeric numeric(10,5),
  49. c_real real,
  50. c_decimal decimal(10,2),
  51. c_float float,
  52. c_char char(10),
  53. c_varchar varchar(10),
  54. c_text text,
  55. c_nchar nchar(12),
  56. c_nvarchar nvarchar(12),
  57. c_ntext ntext,
  58. c_datetime datetime,
  59. c_datetime2 datetime2,
  60. c_smalldatetime smalldatetime,
  61. c_date date,
  62. c_time time,
  63. c_datetimeoffset datetimeoffset
  64. )
  65. `
  66. _, err := sess.Exec(sql)
  67. So(err, ShouldBeNil)
  68. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  69. dtFormat := "2006-01-02 15:04:05.999999999"
  70. d := dt.Format(dtFormat)
  71. dt2 := time.Date(2018, 3, 14, 21, 20, 6, 8896406e2, time.UTC)
  72. dt2Format := "2006-01-02 15:04:05.999999999 -07:00"
  73. d2 := dt2.Format(dt2Format)
  74. sql = fmt.Sprintf(`
  75. INSERT INTO [mssql_types]
  76. SELECT
  77. 1, 5, 20020, 980300, 1420070400, '$20000.15', '£2.15', 12345.12,
  78. 1.11, 2.22, 3.33,
  79. 'char10', 'varchar10', 'text',
  80. N'☺nchar12☺', N'☺nvarchar12☺', N'☺text☺',
  81. CAST('%s' AS DATETIME), CAST('%s' AS DATETIME2), CAST('%s' AS SMALLDATETIME), CAST('%s' AS DATE), CAST('%s' AS TIME), SWITCHOFFSET(CAST('%s' AS DATETIMEOFFSET), '-07:00')
  82. `, d, d2, d, d, d, d2)
  83. _, err = sess.Exec(sql)
  84. So(err, ShouldBeNil)
  85. Convey("When doing a table query should map MSSQL column types to Go types", func() {
  86. query := &tsdb.TsdbQuery{
  87. Queries: []*tsdb.Query{
  88. {
  89. Model: simplejson.NewFromAny(map[string]interface{}{
  90. "rawSql": "SELECT * FROM mssql_types",
  91. "format": "table",
  92. }),
  93. RefId: "A",
  94. },
  95. },
  96. }
  97. resp, err := endpoint.Query(nil, nil, query)
  98. queryResult := resp.Results["A"]
  99. So(err, ShouldBeNil)
  100. column := queryResult.Tables[0].Rows[0]
  101. So(column[0].(bool), ShouldEqual, true)
  102. So(column[1].(int64), ShouldEqual, 5)
  103. So(column[2].(int64), ShouldEqual, 20020)
  104. So(column[3].(int64), ShouldEqual, 980300)
  105. So(column[4].(int64), ShouldEqual, 1420070400)
  106. So(column[5].(float64), ShouldEqual, 20000.15)
  107. So(column[6].(float64), ShouldEqual, 2.15)
  108. So(column[7].(float64), ShouldEqual, 12345.12)
  109. So(column[8].(float64), ShouldEqual, 1.1100000143051147)
  110. So(column[9].(float64), ShouldEqual, 2.22)
  111. So(column[10].(float64), ShouldEqual, 3.33)
  112. So(column[11].(string), ShouldEqual, "char10 ")
  113. So(column[12].(string), ShouldEqual, "varchar10")
  114. So(column[13].(string), ShouldEqual, "text")
  115. So(column[14].(string), ShouldEqual, "☺nchar12☺ ")
  116. So(column[15].(string), ShouldEqual, "☺nvarchar12☺")
  117. So(column[16].(string), ShouldEqual, "☺text☺")
  118. So(column[17].(time.Time), ShouldEqual, dt)
  119. So(column[18].(time.Time), ShouldEqual, dt2)
  120. So(column[19].(time.Time), ShouldEqual, dt.Truncate(time.Minute))
  121. So(column[20].(time.Time), ShouldEqual, dt.Truncate(24*time.Hour))
  122. So(column[21].(time.Time), ShouldEqual, time.Date(1, 1, 1, dt.Hour(), dt.Minute(), dt.Second(), dt.Nanosecond(), time.UTC))
  123. So(column[22].(time.Time), ShouldEqual, dt2.In(time.FixedZone("UTC", int(-7*time.Hour))))
  124. })
  125. })
  126. Convey("Given a table with metrics that lacks data for some series ", func() {
  127. sql := `
  128. IF OBJECT_ID('dbo.[metric]', 'U') IS NOT NULL
  129. DROP TABLE dbo.[metric]
  130. CREATE TABLE [metric] (
  131. time datetime,
  132. value int
  133. )
  134. `
  135. _, err := sess.Exec(sql)
  136. So(err, ShouldBeNil)
  137. type metric struct {
  138. Time time.Time
  139. Value int64
  140. }
  141. series := []*metric{}
  142. firstRange := genTimeRangeByInterval(fromStart, 10*time.Minute, 10*time.Second)
  143. secondRange := genTimeRangeByInterval(fromStart.Add(20*time.Minute), 10*time.Minute, 10*time.Second)
  144. for _, t := range firstRange {
  145. series = append(series, &metric{
  146. Time: t,
  147. Value: 15,
  148. })
  149. }
  150. for _, t := range secondRange {
  151. series = append(series, &metric{
  152. Time: t,
  153. Value: 20,
  154. })
  155. }
  156. _, err = sess.InsertMulti(series)
  157. So(err, ShouldBeNil)
  158. Convey("When doing a metric query using timeGroup", func() {
  159. query := &tsdb.TsdbQuery{
  160. Queries: []*tsdb.Query{
  161. {
  162. Model: simplejson.NewFromAny(map[string]interface{}{
  163. "rawSql": "SELECT $__timeGroup(time, '5m') AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1",
  164. "format": "time_series",
  165. }),
  166. RefId: "A",
  167. },
  168. },
  169. }
  170. resp, err := endpoint.Query(nil, nil, query)
  171. So(err, ShouldBeNil)
  172. queryResult := resp.Results["A"]
  173. So(queryResult.Error, ShouldBeNil)
  174. points := queryResult.Series[0].Points
  175. // without fill this should result in 4 buckets
  176. So(len(points), ShouldEqual, 4)
  177. dt := fromStart
  178. for i := 0; i < 2; i++ {
  179. aValue := points[i][0].Float64
  180. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  181. So(aValue, ShouldEqual, 15)
  182. So(aTime, ShouldEqual, dt)
  183. dt = dt.Add(5 * time.Minute)
  184. }
  185. // adjust for 10 minute gap between first and second set of points
  186. dt = dt.Add(10 * time.Minute)
  187. for i := 2; i < 4; i++ {
  188. aValue := points[i][0].Float64
  189. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  190. So(aValue, ShouldEqual, 20)
  191. So(aTime, ShouldEqual, dt)
  192. dt = dt.Add(5 * time.Minute)
  193. }
  194. })
  195. Convey("When doing a metric query using timeGroup with NULL fill enabled", func() {
  196. query := &tsdb.TsdbQuery{
  197. Queries: []*tsdb.Query{
  198. {
  199. Model: simplejson.NewFromAny(map[string]interface{}{
  200. "rawSql": "SELECT $__timeGroup(time, '5m', NULL) AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1",
  201. "format": "time_series",
  202. }),
  203. RefId: "A",
  204. },
  205. },
  206. TimeRange: &tsdb.TimeRange{
  207. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  208. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  209. },
  210. }
  211. resp, err := endpoint.Query(nil, nil, query)
  212. So(err, ShouldBeNil)
  213. queryResult := resp.Results["A"]
  214. So(queryResult.Error, ShouldBeNil)
  215. points := queryResult.Series[0].Points
  216. So(len(points), ShouldEqual, 7)
  217. dt := fromStart
  218. for i := 0; i < 2; i++ {
  219. aValue := points[i][0].Float64
  220. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  221. So(aValue, ShouldEqual, 15)
  222. So(aTime, ShouldEqual, dt)
  223. dt = dt.Add(5 * time.Minute)
  224. }
  225. // check for NULL values inserted by fill
  226. So(points[2][0].Valid, ShouldBeFalse)
  227. So(points[3][0].Valid, ShouldBeFalse)
  228. // adjust for 10 minute gap between first and second set of points
  229. dt = dt.Add(10 * time.Minute)
  230. for i := 4; i < 6; i++ {
  231. aValue := points[i][0].Float64
  232. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  233. So(aValue, ShouldEqual, 20)
  234. So(aTime, ShouldEqual, dt)
  235. dt = dt.Add(5 * time.Minute)
  236. }
  237. So(points[6][0].Valid, ShouldBeFalse)
  238. })
  239. Convey("When doing a metric query using timeGroup with float fill enabled", func() {
  240. query := &tsdb.TsdbQuery{
  241. Queries: []*tsdb.Query{
  242. {
  243. Model: simplejson.NewFromAny(map[string]interface{}{
  244. "rawSql": "SELECT $__timeGroup(time, '5m', 1.5) AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1",
  245. "format": "time_series",
  246. }),
  247. RefId: "A",
  248. },
  249. },
  250. TimeRange: &tsdb.TimeRange{
  251. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  252. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  253. },
  254. }
  255. resp, err := endpoint.Query(nil, nil, query)
  256. So(err, ShouldBeNil)
  257. queryResult := resp.Results["A"]
  258. So(queryResult.Error, ShouldBeNil)
  259. points := queryResult.Series[0].Points
  260. So(points[3][0].Float64, ShouldEqual, 1.5)
  261. })
  262. })
  263. Convey("Given a table with metrics having multiple values and measurements", func() {
  264. type metric_values struct {
  265. Time time.Time
  266. TimeInt64 int64 `xorm:"bigint 'timeInt64' not null"`
  267. TimeInt64Nullable *int64 `xorm:"bigint 'timeInt64Nullable' null"`
  268. TimeFloat64 float64 `xorm:"float 'timeFloat64' not null"`
  269. TimeFloat64Nullable *float64 `xorm:"float 'timeFloat64Nullable' null"`
  270. TimeInt32 int32 `xorm:"int(11) 'timeInt32' not null"`
  271. TimeInt32Nullable *int32 `xorm:"int(11) 'timeInt32Nullable' null"`
  272. TimeFloat32 float32 `xorm:"float(11) 'timeFloat32' not null"`
  273. TimeFloat32Nullable *float32 `xorm:"float(11) 'timeFloat32Nullable' null"`
  274. Measurement string
  275. ValueOne int64 `xorm:"integer 'valueOne'"`
  276. ValueTwo int64 `xorm:"integer 'valueTwo'"`
  277. }
  278. if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
  279. So(err, ShouldBeNil)
  280. sess.DropTable(metric_values{})
  281. }
  282. err := sess.CreateTable(metric_values{})
  283. So(err, ShouldBeNil)
  284. rand.Seed(time.Now().Unix())
  285. rnd := func(min, max int64) int64 {
  286. return rand.Int63n(max-min) + min
  287. }
  288. var tInitial time.Time
  289. series := []*metric_values{}
  290. for i, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
  291. if i == 0 {
  292. tInitial = t
  293. }
  294. tSeconds := t.Unix()
  295. tSecondsInt32 := int32(tSeconds)
  296. tSecondsFloat32 := float32(tSeconds)
  297. tMilliseconds := tSeconds * 1e3
  298. tMillisecondsFloat := float64(tMilliseconds)
  299. first := metric_values{
  300. Time: t,
  301. TimeInt64: tMilliseconds,
  302. TimeInt64Nullable: &(tMilliseconds),
  303. TimeFloat64: tMillisecondsFloat,
  304. TimeFloat64Nullable: &tMillisecondsFloat,
  305. TimeInt32: tSecondsInt32,
  306. TimeInt32Nullable: &tSecondsInt32,
  307. TimeFloat32: tSecondsFloat32,
  308. TimeFloat32Nullable: &tSecondsFloat32,
  309. Measurement: "Metric A",
  310. ValueOne: rnd(0, 100),
  311. ValueTwo: rnd(0, 100),
  312. }
  313. second := first
  314. second.Measurement = "Metric B"
  315. second.ValueOne = rnd(0, 100)
  316. second.ValueTwo = rnd(0, 100)
  317. series = append(series, &first)
  318. series = append(series, &second)
  319. }
  320. _, err = sess.InsertMulti(series)
  321. So(err, ShouldBeNil)
  322. Convey("When doing a metric query using epoch (int64) as time column and value column (int64) should return metric with time in milliseconds", func() {
  323. query := &tsdb.TsdbQuery{
  324. Queries: []*tsdb.Query{
  325. {
  326. Model: simplejson.NewFromAny(map[string]interface{}{
  327. "rawSql": `SELECT TOP 1 timeInt64 as time, timeInt64 FROM metric_values ORDER BY time`,
  328. "format": "time_series",
  329. }),
  330. RefId: "A",
  331. },
  332. },
  333. }
  334. resp, err := endpoint.Query(nil, nil, query)
  335. So(err, ShouldBeNil)
  336. queryResult := resp.Results["A"]
  337. So(queryResult.Error, ShouldBeNil)
  338. So(len(queryResult.Series), ShouldEqual, 1)
  339. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  340. })
  341. 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() {
  342. query := &tsdb.TsdbQuery{
  343. Queries: []*tsdb.Query{
  344. {
  345. Model: simplejson.NewFromAny(map[string]interface{}{
  346. "rawSql": `SELECT TOP 1 timeInt64Nullable as time, timeInt64Nullable FROM metric_values ORDER BY time`,
  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 (float64) as time column and value column (float64) 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 TOP 1 timeFloat64 as time, timeFloat64 FROM metric_values ORDER BY time`,
  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 nullable) as time column and value column (float64 nullable) 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 TOP 1 timeFloat64Nullable as time, timeFloat64Nullable FROM metric_values ORDER BY time`,
  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 (int32) as time column and value column (int32) 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 TOP 1 timeInt32 as time, timeInt32 FROM metric_values ORDER BY time`,
  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 nullable) as time column and value column (int32 nullable) 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 TOP 1 timeInt32Nullable as time, timeInt32Nullable FROM metric_values ORDER BY time`,
  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 (float32) as time column and value column (float32) 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 TOP 1 timeFloat32 as time, timeFloat32 FROM metric_values ORDER BY time`,
  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(float32(tInitial.Unix()))*1e3)
  454. })
  455. 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() {
  456. query := &tsdb.TsdbQuery{
  457. Queries: []*tsdb.Query{
  458. {
  459. Model: simplejson.NewFromAny(map[string]interface{}{
  460. "rawSql": `SELECT TOP 1 timeFloat32Nullable as time, timeFloat32Nullable FROM metric_values ORDER BY time`,
  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 grouping by time and select metric column should return correct series", func() {
  475. query := &tsdb.TsdbQuery{
  476. Queries: []*tsdb.Query{
  477. {
  478. Model: simplejson.NewFromAny(map[string]interface{}{
  479. "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values ORDER BY 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, 2)
  491. So(queryResult.Series[0].Name, ShouldEqual, "Metric A - value one")
  492. So(queryResult.Series[1].Name, ShouldEqual, "Metric B - value one")
  493. })
  494. Convey("When doing a metric query grouping by time should return correct series", func() {
  495. query := &tsdb.TsdbQuery{
  496. Queries: []*tsdb.Query{
  497. {
  498. Model: simplejson.NewFromAny(map[string]interface{}{
  499. "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values ORDER BY 1",
  500. "format": "time_series",
  501. }),
  502. RefId: "A",
  503. },
  504. },
  505. }
  506. resp, err := endpoint.Query(nil, nil, query)
  507. So(err, ShouldBeNil)
  508. queryResult := resp.Results["A"]
  509. So(queryResult.Error, ShouldBeNil)
  510. So(len(queryResult.Series), ShouldEqual, 2)
  511. So(queryResult.Series[0].Name, ShouldEqual, "valueOne")
  512. So(queryResult.Series[1].Name, ShouldEqual, "valueTwo")
  513. })
  514. Convey("Given a stored procedure that takes @from and @to in epoch time", func() {
  515. sql := `
  516. IF object_id('sp_test_epoch') IS NOT NULL
  517. DROP PROCEDURE sp_test_epoch
  518. `
  519. _, err := sess.Exec(sql)
  520. So(err, ShouldBeNil)
  521. sql = `
  522. CREATE PROCEDURE sp_test_epoch(
  523. @from int,
  524. @to int,
  525. @interval nvarchar(50) = '5m',
  526. @metric nvarchar(200) = 'ALL'
  527. ) AS
  528. BEGIN
  529. DECLARE @dInterval int
  530. SELECT @dInterval = 300
  531. IF @interval = '10m'
  532. SELECT @dInterval = 600
  533. SELECT
  534. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
  535. measurement + ' - value one' as metric,
  536. avg(valueOne) as value
  537. FROM
  538. metric_values
  539. WHERE
  540. time BETWEEN DATEADD(s, @from, '1970-01-01') AND DATEADD(s, @to, '1970-01-01') AND
  541. (@metric = 'ALL' OR measurement = @metric)
  542. GROUP BY
  543. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
  544. measurement
  545. UNION ALL
  546. SELECT
  547. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
  548. measurement + ' - value two' as metric,
  549. avg(valueTwo) as value
  550. FROM
  551. metric_values
  552. WHERE
  553. time BETWEEN DATEADD(s, @from, '1970-01-01') AND DATEADD(s, @to, '1970-01-01') AND
  554. (@metric = 'ALL' OR measurement = @metric)
  555. GROUP BY
  556. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
  557. measurement
  558. ORDER BY 1
  559. END
  560. `
  561. _, err = sess.Exec(sql)
  562. So(err, ShouldBeNil)
  563. Convey("When doing a metric query using stored procedure should return correct result", func() {
  564. query := &tsdb.TsdbQuery{
  565. Queries: []*tsdb.Query{
  566. {
  567. Model: simplejson.NewFromAny(map[string]interface{}{
  568. "rawSql": `DECLARE
  569. @from int = $__unixEpochFrom(),
  570. @to int = $__unixEpochTo()
  571. EXEC dbo.sp_test_epoch @from, @to`,
  572. "format": "time_series",
  573. }),
  574. RefId: "A",
  575. },
  576. },
  577. TimeRange: &tsdb.TimeRange{
  578. From: "1521117000000",
  579. To: "1521122100000",
  580. },
  581. }
  582. resp, err := endpoint.Query(nil, nil, query)
  583. queryResult := resp.Results["A"]
  584. So(err, ShouldBeNil)
  585. So(queryResult.Error, ShouldBeNil)
  586. So(len(queryResult.Series), ShouldEqual, 4)
  587. So(queryResult.Series[0].Name, ShouldEqual, "Metric A - value one")
  588. So(queryResult.Series[1].Name, ShouldEqual, "Metric B - value one")
  589. So(queryResult.Series[2].Name, ShouldEqual, "Metric A - value two")
  590. So(queryResult.Series[3].Name, ShouldEqual, "Metric B - value two")
  591. })
  592. })
  593. Convey("Given a stored procedure that takes @from and @to in datetime", func() {
  594. sql := `
  595. IF object_id('sp_test_datetime') IS NOT NULL
  596. DROP PROCEDURE sp_test_datetime
  597. `
  598. _, err := sess.Exec(sql)
  599. So(err, ShouldBeNil)
  600. sql = `
  601. CREATE PROCEDURE sp_test_datetime(
  602. @from datetime,
  603. @to datetime,
  604. @interval nvarchar(50) = '5m',
  605. @metric nvarchar(200) = 'ALL'
  606. ) AS
  607. BEGIN
  608. DECLARE @dInterval int
  609. SELECT @dInterval = 300
  610. IF @interval = '10m'
  611. SELECT @dInterval = 600
  612. SELECT
  613. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
  614. measurement + ' - value one' as metric,
  615. avg(valueOne) as value
  616. FROM
  617. metric_values
  618. WHERE
  619. time BETWEEN @from AND @to AND
  620. (@metric = 'ALL' OR measurement = @metric)
  621. GROUP BY
  622. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
  623. measurement
  624. UNION ALL
  625. SELECT
  626. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
  627. measurement + ' - value two' as metric,
  628. avg(valueTwo) as value
  629. FROM
  630. metric_values
  631. WHERE
  632. time BETWEEN @from AND @to AND
  633. (@metric = 'ALL' OR measurement = @metric)
  634. GROUP BY
  635. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
  636. measurement
  637. ORDER BY 1
  638. END
  639. `
  640. _, err = sess.Exec(sql)
  641. So(err, ShouldBeNil)
  642. Convey("When doing a metric query using stored procedure should return correct result", func() {
  643. query := &tsdb.TsdbQuery{
  644. Queries: []*tsdb.Query{
  645. {
  646. Model: simplejson.NewFromAny(map[string]interface{}{
  647. "rawSql": `DECLARE
  648. @from int = $__unixEpochFrom(),
  649. @to int = $__unixEpochTo()
  650. EXEC dbo.sp_test_epoch @from, @to`,
  651. "format": "time_series",
  652. }),
  653. RefId: "A",
  654. },
  655. },
  656. TimeRange: &tsdb.TimeRange{
  657. From: "1521117000000",
  658. To: "1521122100000",
  659. },
  660. }
  661. resp, err := endpoint.Query(nil, nil, query)
  662. queryResult := resp.Results["A"]
  663. So(err, ShouldBeNil)
  664. So(queryResult.Error, ShouldBeNil)
  665. So(len(queryResult.Series), ShouldEqual, 4)
  666. So(queryResult.Series[0].Name, ShouldEqual, "Metric A - value one")
  667. So(queryResult.Series[1].Name, ShouldEqual, "Metric B - value one")
  668. So(queryResult.Series[2].Name, ShouldEqual, "Metric A - value two")
  669. So(queryResult.Series[3].Name, ShouldEqual, "Metric B - value two")
  670. })
  671. })
  672. })
  673. Convey("Given a table with event data", func() {
  674. sql := `
  675. IF OBJECT_ID('dbo.[event]', 'U') IS NOT NULL
  676. DROP TABLE dbo.[event]
  677. CREATE TABLE [event] (
  678. time_sec int,
  679. description nvarchar(100),
  680. tags nvarchar(100),
  681. )
  682. `
  683. _, err := sess.Exec(sql)
  684. So(err, ShouldBeNil)
  685. type event struct {
  686. TimeSec int64
  687. Description string
  688. Tags string
  689. }
  690. events := []*event{}
  691. for _, t := range genTimeRangeByInterval(fromStart.Add(-20*time.Minute), 60*time.Minute, 25*time.Minute) {
  692. events = append(events, &event{
  693. TimeSec: t.Unix(),
  694. Description: "Someone deployed something",
  695. Tags: "deploy",
  696. })
  697. events = append(events, &event{
  698. TimeSec: t.Add(5 * time.Minute).Unix(),
  699. Description: "New support ticket registered",
  700. Tags: "ticket",
  701. })
  702. }
  703. for _, e := range events {
  704. sql = fmt.Sprintf(`
  705. INSERT [event] (time_sec, description, tags)
  706. VALUES(%d, '%s', '%s')
  707. `, e.TimeSec, e.Description, e.Tags)
  708. _, err = sess.Exec(sql)
  709. So(err, ShouldBeNil)
  710. }
  711. Convey("When doing an annotation query of deploy events should return expected result", func() {
  712. query := &tsdb.TsdbQuery{
  713. Queries: []*tsdb.Query{
  714. {
  715. Model: simplejson.NewFromAny(map[string]interface{}{
  716. "rawSql": "SELECT time_sec as time, description as [text], tags FROM [event] WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC",
  717. "format": "table",
  718. }),
  719. RefId: "Deploys",
  720. },
  721. },
  722. TimeRange: &tsdb.TimeRange{
  723. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  724. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  725. },
  726. }
  727. resp, err := endpoint.Query(nil, nil, query)
  728. queryResult := resp.Results["Deploys"]
  729. So(err, ShouldBeNil)
  730. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  731. })
  732. Convey("When doing an annotation query of ticket events should return expected result", func() {
  733. query := &tsdb.TsdbQuery{
  734. Queries: []*tsdb.Query{
  735. {
  736. Model: simplejson.NewFromAny(map[string]interface{}{
  737. "rawSql": "SELECT time_sec as time, description as [text], tags FROM [event] WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC",
  738. "format": "table",
  739. }),
  740. RefId: "Tickets",
  741. },
  742. },
  743. TimeRange: &tsdb.TimeRange{
  744. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  745. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  746. },
  747. }
  748. resp, err := endpoint.Query(nil, nil, query)
  749. queryResult := resp.Results["Tickets"]
  750. So(err, ShouldBeNil)
  751. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  752. })
  753. Convey("When doing an annotation query with a time column in datetime format", func() {
  754. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  755. dtFormat := "2006-01-02 15:04:05.999999999"
  756. query := &tsdb.TsdbQuery{
  757. Queries: []*tsdb.Query{
  758. {
  759. Model: simplejson.NewFromAny(map[string]interface{}{
  760. "rawSql": fmt.Sprintf(`SELECT
  761. CAST('%s' AS DATETIME) as time,
  762. 'message' as text,
  763. 'tag1,tag2' as tags
  764. `, dt.Format(dtFormat)),
  765. "format": "table",
  766. }),
  767. RefId: "A",
  768. },
  769. },
  770. }
  771. resp, err := endpoint.Query(nil, nil, query)
  772. So(err, ShouldBeNil)
  773. queryResult := resp.Results["A"]
  774. So(queryResult.Error, ShouldBeNil)
  775. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  776. columns := queryResult.Tables[0].Rows[0]
  777. //Should be in milliseconds
  778. So(columns[0].(float64), ShouldEqual, float64(dt.UnixNano()/1e6))
  779. })
  780. Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
  781. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  782. query := &tsdb.TsdbQuery{
  783. Queries: []*tsdb.Query{
  784. {
  785. Model: simplejson.NewFromAny(map[string]interface{}{
  786. "rawSql": fmt.Sprintf(`SELECT
  787. %d as time,
  788. 'message' as text,
  789. 'tag1,tag2' as tags
  790. `, dt.Unix()),
  791. "format": "table",
  792. }),
  793. RefId: "A",
  794. },
  795. },
  796. }
  797. resp, err := endpoint.Query(nil, nil, query)
  798. So(err, ShouldBeNil)
  799. queryResult := resp.Results["A"]
  800. So(queryResult.Error, ShouldBeNil)
  801. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  802. columns := queryResult.Tables[0].Rows[0]
  803. //Should be in milliseconds
  804. So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
  805. })
  806. Convey("When doing an annotation query with a time column in epoch second format (int) should return ms", func() {
  807. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  808. query := &tsdb.TsdbQuery{
  809. Queries: []*tsdb.Query{
  810. {
  811. Model: simplejson.NewFromAny(map[string]interface{}{
  812. "rawSql": fmt.Sprintf(`SELECT
  813. cast(%d as int) as time,
  814. 'message' as text,
  815. 'tag1,tag2' as tags
  816. `, dt.Unix()),
  817. "format": "table",
  818. }),
  819. RefId: "A",
  820. },
  821. },
  822. }
  823. resp, err := endpoint.Query(nil, nil, query)
  824. So(err, ShouldBeNil)
  825. queryResult := resp.Results["A"]
  826. So(queryResult.Error, ShouldBeNil)
  827. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  828. columns := queryResult.Tables[0].Rows[0]
  829. //Should be in milliseconds
  830. So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
  831. })
  832. Convey("When doing an annotation query with a time column in epoch millisecond format should return ms", func() {
  833. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  834. query := &tsdb.TsdbQuery{
  835. Queries: []*tsdb.Query{
  836. {
  837. Model: simplejson.NewFromAny(map[string]interface{}{
  838. "rawSql": fmt.Sprintf(`SELECT
  839. %d as time,
  840. 'message' as text,
  841. 'tag1,tag2' as tags
  842. `, dt.Unix()*1000),
  843. "format": "table",
  844. }),
  845. RefId: "A",
  846. },
  847. },
  848. }
  849. resp, err := endpoint.Query(nil, nil, query)
  850. So(err, ShouldBeNil)
  851. queryResult := resp.Results["A"]
  852. So(queryResult.Error, ShouldBeNil)
  853. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  854. columns := queryResult.Tables[0].Rows[0]
  855. //Should be in milliseconds
  856. So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000))
  857. })
  858. Convey("When doing an annotation query with a time column holding a bigint null value should return nil", func() {
  859. query := &tsdb.TsdbQuery{
  860. Queries: []*tsdb.Query{
  861. {
  862. Model: simplejson.NewFromAny(map[string]interface{}{
  863. "rawSql": `SELECT
  864. cast(null as bigint) as time,
  865. 'message' as text,
  866. 'tag1,tag2' as tags
  867. `,
  868. "format": "table",
  869. }),
  870. RefId: "A",
  871. },
  872. },
  873. }
  874. resp, err := endpoint.Query(nil, nil, query)
  875. So(err, ShouldBeNil)
  876. queryResult := resp.Results["A"]
  877. So(queryResult.Error, ShouldBeNil)
  878. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  879. columns := queryResult.Tables[0].Rows[0]
  880. //Should be in milliseconds
  881. So(columns[0], ShouldBeNil)
  882. })
  883. Convey("When doing an annotation query with a time column holding a datetime null value should return nil", func() {
  884. query := &tsdb.TsdbQuery{
  885. Queries: []*tsdb.Query{
  886. {
  887. Model: simplejson.NewFromAny(map[string]interface{}{
  888. "rawSql": `SELECT
  889. cast(null as datetime) as time,
  890. 'message' as text,
  891. 'tag1,tag2' as tags
  892. `,
  893. "format": "table",
  894. }),
  895. RefId: "A",
  896. },
  897. },
  898. }
  899. resp, err := endpoint.Query(nil, nil, query)
  900. So(err, ShouldBeNil)
  901. queryResult := resp.Results["A"]
  902. So(queryResult.Error, ShouldBeNil)
  903. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  904. columns := queryResult.Tables[0].Rows[0]
  905. //Should be in milliseconds
  906. So(columns[0], ShouldBeNil)
  907. })
  908. })
  909. })
  910. }
  911. func InitMSSQLTestDB(t *testing.T) *xorm.Engine {
  912. x, err := xorm.NewEngine(sqlutil.TestDB_Mssql.DriverName, strings.Replace(sqlutil.TestDB_Mssql.ConnStr, "localhost", serverIP, 1))
  913. if err != nil {
  914. t.Fatalf("Failed to init mssql db %v", err)
  915. }
  916. x.DatabaseTZ = time.UTC
  917. x.TZLocation = time.UTC
  918. // x.ShowSQL()
  919. return x
  920. }
  921. func genTimeRangeByInterval(from time.Time, duration time.Duration, interval time.Duration) []time.Time {
  922. durationSec := int64(duration.Seconds())
  923. intervalSec := int64(interval.Seconds())
  924. timeRange := []time.Time{}
  925. for i := int64(0); i < durationSec; i += intervalSec {
  926. timeRange = append(timeRange, from)
  927. from = from.Add(time.Duration(int64(time.Second) * intervalSec))
  928. }
  929. return timeRange
  930. }