mssql_test.go 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074
  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 string = "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. So(len(points), ShouldEqual, 6)
  176. dt := fromStart
  177. for i := 0; i < 3; i++ {
  178. aValue := points[i][0].Float64
  179. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  180. So(aValue, ShouldEqual, 15)
  181. So(aTime, ShouldEqual, dt)
  182. dt = dt.Add(5 * time.Minute)
  183. }
  184. // adjust for 5 minute gap
  185. dt = dt.Add(5 * time.Minute)
  186. for i := 3; i < 6; i++ {
  187. aValue := points[i][0].Float64
  188. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  189. So(aValue, ShouldEqual, 20)
  190. So(aTime, ShouldEqual, dt)
  191. dt = dt.Add(5 * time.Minute)
  192. }
  193. })
  194. Convey("When doing a metric query using timeGroup with NULL fill enabled", func() {
  195. query := &tsdb.TsdbQuery{
  196. Queries: []*tsdb.Query{
  197. {
  198. Model: simplejson.NewFromAny(map[string]interface{}{
  199. "rawSql": "SELECT $__timeGroup(time, '5m', NULL) AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1",
  200. "format": "time_series",
  201. }),
  202. RefId: "A",
  203. },
  204. },
  205. TimeRange: &tsdb.TimeRange{
  206. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  207. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  208. },
  209. }
  210. resp, err := endpoint.Query(nil, nil, query)
  211. So(err, ShouldBeNil)
  212. queryResult := resp.Results["A"]
  213. So(queryResult.Error, ShouldBeNil)
  214. points := queryResult.Series[0].Points
  215. So(len(points), ShouldEqual, 7)
  216. dt := fromStart
  217. for i := 0; i < 3; i++ {
  218. aValue := points[i][0].Float64
  219. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  220. So(aValue, ShouldEqual, 15)
  221. So(aTime, ShouldEqual, dt)
  222. dt = dt.Add(5 * time.Minute)
  223. }
  224. So(points[3][0].Valid, ShouldBeFalse)
  225. // adjust for 5 minute gap
  226. dt = dt.Add(5 * time.Minute)
  227. for i := 4; i < 7; i++ {
  228. aValue := points[i][0].Float64
  229. aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
  230. So(aValue, ShouldEqual, 20)
  231. So(aTime, ShouldEqual, dt)
  232. dt = dt.Add(5 * time.Minute)
  233. }
  234. })
  235. Convey("When doing a metric query using timeGroup with float fill enabled", func() {
  236. query := &tsdb.TsdbQuery{
  237. Queries: []*tsdb.Query{
  238. {
  239. Model: simplejson.NewFromAny(map[string]interface{}{
  240. "rawSql": "SELECT $__timeGroup(time, '5m', 1.5) AS time, avg(value) as value FROM metric GROUP BY $__timeGroup(time, '5m') ORDER BY 1",
  241. "format": "time_series",
  242. }),
  243. RefId: "A",
  244. },
  245. },
  246. TimeRange: &tsdb.TimeRange{
  247. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  248. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  249. },
  250. }
  251. resp, err := endpoint.Query(nil, nil, query)
  252. So(err, ShouldBeNil)
  253. queryResult := resp.Results["A"]
  254. So(queryResult.Error, ShouldBeNil)
  255. points := queryResult.Series[0].Points
  256. So(points[3][0].Float64, ShouldEqual, 1.5)
  257. })
  258. })
  259. Convey("Given a table with metrics having multiple values and measurements", func() {
  260. type metric_values struct {
  261. Time time.Time
  262. TimeInt64 int64 `xorm:"bigint 'timeInt64' not null"`
  263. TimeInt64Nullable *int64 `xorm:"bigint 'timeInt64Nullable' null"`
  264. TimeFloat64 float64 `xorm:"float 'timeFloat64' not null"`
  265. TimeFloat64Nullable *float64 `xorm:"float 'timeFloat64Nullable' null"`
  266. TimeInt32 int32 `xorm:"int(11) 'timeInt32' not null"`
  267. TimeInt32Nullable *int32 `xorm:"int(11) 'timeInt32Nullable' null"`
  268. TimeFloat32 float32 `xorm:"float(11) 'timeFloat32' not null"`
  269. TimeFloat32Nullable *float32 `xorm:"float(11) 'timeFloat32Nullable' null"`
  270. Measurement string
  271. ValueOne int64 `xorm:"integer 'valueOne'"`
  272. ValueTwo int64 `xorm:"integer 'valueTwo'"`
  273. }
  274. if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
  275. So(err, ShouldBeNil)
  276. sess.DropTable(metric_values{})
  277. }
  278. err := sess.CreateTable(metric_values{})
  279. So(err, ShouldBeNil)
  280. rand.Seed(time.Now().Unix())
  281. rnd := func(min, max int64) int64 {
  282. return rand.Int63n(max-min) + min
  283. }
  284. var tInitial time.Time
  285. series := []*metric_values{}
  286. for i, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
  287. if i == 0 {
  288. tInitial = t
  289. }
  290. tSeconds := t.Unix()
  291. tSecondsInt32 := int32(tSeconds)
  292. tSecondsFloat32 := float32(tSeconds)
  293. tMilliseconds := tSeconds * 1e3
  294. tMillisecondsFloat := float64(tMilliseconds)
  295. first := metric_values{
  296. Time: t,
  297. TimeInt64: tMilliseconds,
  298. TimeInt64Nullable: &(tMilliseconds),
  299. TimeFloat64: tMillisecondsFloat,
  300. TimeFloat64Nullable: &tMillisecondsFloat,
  301. TimeInt32: tSecondsInt32,
  302. TimeInt32Nullable: &tSecondsInt32,
  303. TimeFloat32: tSecondsFloat32,
  304. TimeFloat32Nullable: &tSecondsFloat32,
  305. Measurement: "Metric A",
  306. ValueOne: rnd(0, 100),
  307. ValueTwo: rnd(0, 100),
  308. }
  309. second := first
  310. second.Measurement = "Metric B"
  311. second.ValueOne = rnd(0, 100)
  312. second.ValueTwo = rnd(0, 100)
  313. series = append(series, &first)
  314. series = append(series, &second)
  315. }
  316. _, err = sess.InsertMulti(series)
  317. So(err, ShouldBeNil)
  318. Convey("When doing a metric query using epoch (int64) as time column should return metric with time in milliseconds", func() {
  319. query := &tsdb.TsdbQuery{
  320. Queries: []*tsdb.Query{
  321. {
  322. Model: simplejson.NewFromAny(map[string]interface{}{
  323. "rawSql": `SELECT TOP 1 timeInt64 as time, valueOne FROM metric_values ORDER BY time`,
  324. "format": "time_series",
  325. }),
  326. RefId: "A",
  327. },
  328. },
  329. }
  330. resp, err := endpoint.Query(nil, nil, query)
  331. So(err, ShouldBeNil)
  332. queryResult := resp.Results["A"]
  333. So(queryResult.Error, ShouldBeNil)
  334. So(len(queryResult.Series), ShouldEqual, 1)
  335. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  336. })
  337. Convey("When doing a metric query using epoch (int64 nullable) as time column should return metric with time in milliseconds", func() {
  338. query := &tsdb.TsdbQuery{
  339. Queries: []*tsdb.Query{
  340. {
  341. Model: simplejson.NewFromAny(map[string]interface{}{
  342. "rawSql": `SELECT TOP 1 timeInt64Nullable as time, valueOne FROM metric_values ORDER BY time`,
  343. "format": "time_series",
  344. }),
  345. RefId: "A",
  346. },
  347. },
  348. }
  349. resp, err := endpoint.Query(nil, nil, query)
  350. So(err, ShouldBeNil)
  351. queryResult := resp.Results["A"]
  352. So(queryResult.Error, ShouldBeNil)
  353. So(len(queryResult.Series), ShouldEqual, 1)
  354. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  355. })
  356. Convey("When doing a metric query using epoch (float64) as time column should return metric with time in milliseconds", func() {
  357. query := &tsdb.TsdbQuery{
  358. Queries: []*tsdb.Query{
  359. {
  360. Model: simplejson.NewFromAny(map[string]interface{}{
  361. "rawSql": `SELECT TOP 1 timeFloat64 as time, valueOne FROM metric_values ORDER BY time`,
  362. "format": "time_series",
  363. }),
  364. RefId: "A",
  365. },
  366. },
  367. }
  368. resp, err := endpoint.Query(nil, nil, query)
  369. So(err, ShouldBeNil)
  370. queryResult := resp.Results["A"]
  371. So(queryResult.Error, ShouldBeNil)
  372. So(len(queryResult.Series), ShouldEqual, 1)
  373. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  374. })
  375. Convey("When doing a metric query using epoch (float64 nullable) as time column should return metric with time in milliseconds", func() {
  376. query := &tsdb.TsdbQuery{
  377. Queries: []*tsdb.Query{
  378. {
  379. Model: simplejson.NewFromAny(map[string]interface{}{
  380. "rawSql": `SELECT TOP 1 timeFloat64Nullable as time, valueOne FROM metric_values ORDER BY time`,
  381. "format": "time_series",
  382. }),
  383. RefId: "A",
  384. },
  385. },
  386. }
  387. resp, err := endpoint.Query(nil, nil, query)
  388. So(err, ShouldBeNil)
  389. queryResult := resp.Results["A"]
  390. So(queryResult.Error, ShouldBeNil)
  391. So(len(queryResult.Series), ShouldEqual, 1)
  392. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  393. })
  394. Convey("When doing a metric query using epoch (int32) as time column should return metric with time in milliseconds", func() {
  395. query := &tsdb.TsdbQuery{
  396. Queries: []*tsdb.Query{
  397. {
  398. Model: simplejson.NewFromAny(map[string]interface{}{
  399. "rawSql": `SELECT TOP 1 timeInt32 as time, valueOne FROM metric_values ORDER BY time`,
  400. "format": "time_series",
  401. }),
  402. RefId: "A",
  403. },
  404. },
  405. }
  406. resp, err := endpoint.Query(nil, nil, query)
  407. So(err, ShouldBeNil)
  408. queryResult := resp.Results["A"]
  409. So(queryResult.Error, ShouldBeNil)
  410. So(len(queryResult.Series), ShouldEqual, 1)
  411. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  412. })
  413. Convey("When doing a metric query using epoch (int32 nullable) as time column should return metric with time in milliseconds", func() {
  414. query := &tsdb.TsdbQuery{
  415. Queries: []*tsdb.Query{
  416. {
  417. Model: simplejson.NewFromAny(map[string]interface{}{
  418. "rawSql": `SELECT TOP 1 timeInt32Nullable as time, valueOne FROM metric_values ORDER BY time`,
  419. "format": "time_series",
  420. }),
  421. RefId: "A",
  422. },
  423. },
  424. }
  425. resp, err := endpoint.Query(nil, nil, query)
  426. So(err, ShouldBeNil)
  427. queryResult := resp.Results["A"]
  428. So(queryResult.Error, ShouldBeNil)
  429. So(len(queryResult.Series), ShouldEqual, 1)
  430. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
  431. })
  432. Convey("When doing a metric query using epoch (float32) as time column should return metric with time in milliseconds", func() {
  433. query := &tsdb.TsdbQuery{
  434. Queries: []*tsdb.Query{
  435. {
  436. Model: simplejson.NewFromAny(map[string]interface{}{
  437. "rawSql": `SELECT TOP 1 timeFloat32 as time, valueOne FROM metric_values ORDER BY time`,
  438. "format": "time_series",
  439. }),
  440. RefId: "A",
  441. },
  442. },
  443. }
  444. resp, err := endpoint.Query(nil, nil, query)
  445. So(err, ShouldBeNil)
  446. queryResult := resp.Results["A"]
  447. So(queryResult.Error, ShouldBeNil)
  448. So(len(queryResult.Series), ShouldEqual, 1)
  449. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
  450. })
  451. Convey("When doing a metric query using epoch (float32 nullable) as time column should return metric with time in milliseconds", func() {
  452. query := &tsdb.TsdbQuery{
  453. Queries: []*tsdb.Query{
  454. {
  455. Model: simplejson.NewFromAny(map[string]interface{}{
  456. "rawSql": `SELECT TOP 1 timeFloat32Nullable as time, valueOne FROM metric_values ORDER BY time`,
  457. "format": "time_series",
  458. }),
  459. RefId: "A",
  460. },
  461. },
  462. }
  463. resp, err := endpoint.Query(nil, nil, query)
  464. So(err, ShouldBeNil)
  465. queryResult := resp.Results["A"]
  466. So(queryResult.Error, ShouldBeNil)
  467. So(len(queryResult.Series), ShouldEqual, 1)
  468. So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
  469. })
  470. Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
  471. query := &tsdb.TsdbQuery{
  472. Queries: []*tsdb.Query{
  473. {
  474. Model: simplejson.NewFromAny(map[string]interface{}{
  475. "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values ORDER BY 1",
  476. "format": "time_series",
  477. }),
  478. RefId: "A",
  479. },
  480. },
  481. }
  482. resp, err := endpoint.Query(nil, nil, query)
  483. So(err, ShouldBeNil)
  484. queryResult := resp.Results["A"]
  485. So(queryResult.Error, ShouldBeNil)
  486. So(len(queryResult.Series), ShouldEqual, 2)
  487. So(queryResult.Series[0].Name, ShouldEqual, "Metric A - value one")
  488. So(queryResult.Series[1].Name, ShouldEqual, "Metric B - value one")
  489. })
  490. Convey("When doing a metric query grouping by time should return correct series", func() {
  491. query := &tsdb.TsdbQuery{
  492. Queries: []*tsdb.Query{
  493. {
  494. Model: simplejson.NewFromAny(map[string]interface{}{
  495. "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values ORDER BY 1",
  496. "format": "time_series",
  497. }),
  498. RefId: "A",
  499. },
  500. },
  501. }
  502. resp, err := endpoint.Query(nil, nil, query)
  503. So(err, ShouldBeNil)
  504. queryResult := resp.Results["A"]
  505. So(queryResult.Error, ShouldBeNil)
  506. So(len(queryResult.Series), ShouldEqual, 2)
  507. So(queryResult.Series[0].Name, ShouldEqual, "valueOne")
  508. So(queryResult.Series[1].Name, ShouldEqual, "valueTwo")
  509. })
  510. Convey("Given a stored procedure that takes @from and @to in epoch time", func() {
  511. sql := `
  512. IF object_id('sp_test_epoch') IS NOT NULL
  513. DROP PROCEDURE sp_test_epoch
  514. `
  515. _, err := sess.Exec(sql)
  516. So(err, ShouldBeNil)
  517. sql = `
  518. CREATE PROCEDURE sp_test_epoch(
  519. @from int,
  520. @to int,
  521. @interval nvarchar(50) = '5m',
  522. @metric nvarchar(200) = 'ALL'
  523. ) AS
  524. BEGIN
  525. DECLARE @dInterval int
  526. SELECT @dInterval = 300
  527. IF @interval = '10m'
  528. SELECT @dInterval = 600
  529. SELECT
  530. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
  531. measurement + ' - value one' as metric,
  532. avg(valueOne) as value
  533. FROM
  534. metric_values
  535. WHERE
  536. time BETWEEN DATEADD(s, @from, '1970-01-01') AND DATEADD(s, @to, '1970-01-01') AND
  537. (@metric = 'ALL' OR measurement = @metric)
  538. GROUP BY
  539. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
  540. measurement
  541. UNION ALL
  542. SELECT
  543. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
  544. measurement + ' - value two' as metric,
  545. avg(valueTwo) as value
  546. FROM
  547. metric_values
  548. WHERE
  549. time BETWEEN DATEADD(s, @from, '1970-01-01') AND DATEADD(s, @to, '1970-01-01') AND
  550. (@metric = 'ALL' OR measurement = @metric)
  551. GROUP BY
  552. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
  553. measurement
  554. ORDER BY 1
  555. END
  556. `
  557. _, err = sess.Exec(sql)
  558. So(err, ShouldBeNil)
  559. Convey("When doing a metric query using stored procedure should return correct result", func() {
  560. query := &tsdb.TsdbQuery{
  561. Queries: []*tsdb.Query{
  562. {
  563. Model: simplejson.NewFromAny(map[string]interface{}{
  564. "rawSql": `DECLARE
  565. @from int = $__unixEpochFrom(),
  566. @to int = $__unixEpochTo()
  567. EXEC dbo.sp_test_epoch @from, @to`,
  568. "format": "time_series",
  569. }),
  570. RefId: "A",
  571. },
  572. },
  573. TimeRange: &tsdb.TimeRange{
  574. From: "1521117000000",
  575. To: "1521122100000",
  576. },
  577. }
  578. resp, err := endpoint.Query(nil, nil, query)
  579. queryResult := resp.Results["A"]
  580. So(err, ShouldBeNil)
  581. So(queryResult.Error, ShouldBeNil)
  582. So(len(queryResult.Series), ShouldEqual, 4)
  583. So(queryResult.Series[0].Name, ShouldEqual, "Metric A - value one")
  584. So(queryResult.Series[1].Name, ShouldEqual, "Metric B - value one")
  585. So(queryResult.Series[2].Name, ShouldEqual, "Metric A - value two")
  586. So(queryResult.Series[3].Name, ShouldEqual, "Metric B - value two")
  587. })
  588. })
  589. Convey("Given a stored procedure that takes @from and @to in datetime", func() {
  590. sql := `
  591. IF object_id('sp_test_datetime') IS NOT NULL
  592. DROP PROCEDURE sp_test_datetime
  593. `
  594. _, err := sess.Exec(sql)
  595. So(err, ShouldBeNil)
  596. sql = `
  597. CREATE PROCEDURE sp_test_datetime(
  598. @from datetime,
  599. @to datetime,
  600. @interval nvarchar(50) = '5m',
  601. @metric nvarchar(200) = 'ALL'
  602. ) AS
  603. BEGIN
  604. DECLARE @dInterval int
  605. SELECT @dInterval = 300
  606. IF @interval = '10m'
  607. SELECT @dInterval = 600
  608. SELECT
  609. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
  610. measurement + ' - value one' as metric,
  611. avg(valueOne) as value
  612. FROM
  613. metric_values
  614. WHERE
  615. time BETWEEN @from AND @to AND
  616. (@metric = 'ALL' OR measurement = @metric)
  617. GROUP BY
  618. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
  619. measurement
  620. UNION ALL
  621. SELECT
  622. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
  623. measurement + ' - value two' as metric,
  624. avg(valueTwo) as value
  625. FROM
  626. metric_values
  627. WHERE
  628. time BETWEEN @from AND @to AND
  629. (@metric = 'ALL' OR measurement = @metric)
  630. GROUP BY
  631. CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
  632. measurement
  633. ORDER BY 1
  634. END
  635. `
  636. _, err = sess.Exec(sql)
  637. So(err, ShouldBeNil)
  638. Convey("When doing a metric query using stored procedure should return correct result", func() {
  639. query := &tsdb.TsdbQuery{
  640. Queries: []*tsdb.Query{
  641. {
  642. Model: simplejson.NewFromAny(map[string]interface{}{
  643. "rawSql": `DECLARE
  644. @from int = $__unixEpochFrom(),
  645. @to int = $__unixEpochTo()
  646. EXEC dbo.sp_test_epoch @from, @to`,
  647. "format": "time_series",
  648. }),
  649. RefId: "A",
  650. },
  651. },
  652. TimeRange: &tsdb.TimeRange{
  653. From: "1521117000000",
  654. To: "1521122100000",
  655. },
  656. }
  657. resp, err := endpoint.Query(nil, nil, query)
  658. queryResult := resp.Results["A"]
  659. So(err, ShouldBeNil)
  660. So(queryResult.Error, ShouldBeNil)
  661. So(len(queryResult.Series), ShouldEqual, 4)
  662. So(queryResult.Series[0].Name, ShouldEqual, "Metric A - value one")
  663. So(queryResult.Series[1].Name, ShouldEqual, "Metric B - value one")
  664. So(queryResult.Series[2].Name, ShouldEqual, "Metric A - value two")
  665. So(queryResult.Series[3].Name, ShouldEqual, "Metric B - value two")
  666. })
  667. })
  668. })
  669. Convey("Given a table with event data", func() {
  670. sql := `
  671. IF OBJECT_ID('dbo.[event]', 'U') IS NOT NULL
  672. DROP TABLE dbo.[event]
  673. CREATE TABLE [event] (
  674. time_sec int,
  675. description nvarchar(100),
  676. tags nvarchar(100),
  677. )
  678. `
  679. _, err := sess.Exec(sql)
  680. So(err, ShouldBeNil)
  681. type event struct {
  682. TimeSec int64
  683. Description string
  684. Tags string
  685. }
  686. events := []*event{}
  687. for _, t := range genTimeRangeByInterval(fromStart.Add(-20*time.Minute), 60*time.Minute, 25*time.Minute) {
  688. events = append(events, &event{
  689. TimeSec: t.Unix(),
  690. Description: "Someone deployed something",
  691. Tags: "deploy",
  692. })
  693. events = append(events, &event{
  694. TimeSec: t.Add(5 * time.Minute).Unix(),
  695. Description: "New support ticket registered",
  696. Tags: "ticket",
  697. })
  698. }
  699. for _, e := range events {
  700. sql = fmt.Sprintf(`
  701. INSERT [event] (time_sec, description, tags)
  702. VALUES(%d, '%s', '%s')
  703. `, e.TimeSec, e.Description, e.Tags)
  704. _, err = sess.Exec(sql)
  705. So(err, ShouldBeNil)
  706. }
  707. Convey("When doing an annotation query of deploy events should return expected result", func() {
  708. query := &tsdb.TsdbQuery{
  709. Queries: []*tsdb.Query{
  710. {
  711. Model: simplejson.NewFromAny(map[string]interface{}{
  712. "rawSql": "SELECT time_sec as time, description as [text], tags FROM [event] WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC",
  713. "format": "table",
  714. }),
  715. RefId: "Deploys",
  716. },
  717. },
  718. TimeRange: &tsdb.TimeRange{
  719. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  720. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  721. },
  722. }
  723. resp, err := endpoint.Query(nil, nil, query)
  724. queryResult := resp.Results["Deploys"]
  725. So(err, ShouldBeNil)
  726. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  727. })
  728. Convey("When doing an annotation query of ticket events should return expected result", func() {
  729. query := &tsdb.TsdbQuery{
  730. Queries: []*tsdb.Query{
  731. {
  732. Model: simplejson.NewFromAny(map[string]interface{}{
  733. "rawSql": "SELECT time_sec as time, description as [text], tags FROM [event] WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC",
  734. "format": "table",
  735. }),
  736. RefId: "Tickets",
  737. },
  738. },
  739. TimeRange: &tsdb.TimeRange{
  740. From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
  741. To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
  742. },
  743. }
  744. resp, err := endpoint.Query(nil, nil, query)
  745. queryResult := resp.Results["Tickets"]
  746. So(err, ShouldBeNil)
  747. So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
  748. })
  749. Convey("When doing an annotation query with a time column in datetime format", func() {
  750. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  751. dtFormat := "2006-01-02 15:04:05.999999999"
  752. query := &tsdb.TsdbQuery{
  753. Queries: []*tsdb.Query{
  754. {
  755. Model: simplejson.NewFromAny(map[string]interface{}{
  756. "rawSql": fmt.Sprintf(`SELECT
  757. CAST('%s' AS DATETIME) as time,
  758. 'message' as text,
  759. 'tag1,tag2' as tags
  760. `, dt.Format(dtFormat)),
  761. "format": "table",
  762. }),
  763. RefId: "A",
  764. },
  765. },
  766. }
  767. resp, err := endpoint.Query(nil, nil, query)
  768. So(err, ShouldBeNil)
  769. queryResult := resp.Results["A"]
  770. So(queryResult.Error, ShouldBeNil)
  771. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  772. columns := queryResult.Tables[0].Rows[0]
  773. //Should be in milliseconds
  774. So(columns[0].(float64), ShouldEqual, float64(dt.UnixNano()/1e6))
  775. })
  776. Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
  777. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  778. query := &tsdb.TsdbQuery{
  779. Queries: []*tsdb.Query{
  780. {
  781. Model: simplejson.NewFromAny(map[string]interface{}{
  782. "rawSql": fmt.Sprintf(`SELECT
  783. %d as time,
  784. 'message' as text,
  785. 'tag1,tag2' as tags
  786. `, dt.Unix()),
  787. "format": "table",
  788. }),
  789. RefId: "A",
  790. },
  791. },
  792. }
  793. resp, err := endpoint.Query(nil, nil, query)
  794. So(err, ShouldBeNil)
  795. queryResult := resp.Results["A"]
  796. So(queryResult.Error, ShouldBeNil)
  797. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  798. columns := queryResult.Tables[0].Rows[0]
  799. //Should be in milliseconds
  800. So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
  801. })
  802. Convey("When doing an annotation query with a time column in epoch second format (int) should return ms", func() {
  803. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  804. query := &tsdb.TsdbQuery{
  805. Queries: []*tsdb.Query{
  806. {
  807. Model: simplejson.NewFromAny(map[string]interface{}{
  808. "rawSql": fmt.Sprintf(`SELECT
  809. cast(%d as int) as time,
  810. 'message' as text,
  811. 'tag1,tag2' as tags
  812. `, dt.Unix()),
  813. "format": "table",
  814. }),
  815. RefId: "A",
  816. },
  817. },
  818. }
  819. resp, err := endpoint.Query(nil, nil, query)
  820. So(err, ShouldBeNil)
  821. queryResult := resp.Results["A"]
  822. So(queryResult.Error, ShouldBeNil)
  823. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  824. columns := queryResult.Tables[0].Rows[0]
  825. //Should be in milliseconds
  826. So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
  827. })
  828. Convey("When doing an annotation query with a time column in epoch millisecond format should return ms", func() {
  829. dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
  830. query := &tsdb.TsdbQuery{
  831. Queries: []*tsdb.Query{
  832. {
  833. Model: simplejson.NewFromAny(map[string]interface{}{
  834. "rawSql": fmt.Sprintf(`SELECT
  835. %d as time,
  836. 'message' as text,
  837. 'tag1,tag2' as tags
  838. `, dt.Unix()*1000),
  839. "format": "table",
  840. }),
  841. RefId: "A",
  842. },
  843. },
  844. }
  845. resp, err := endpoint.Query(nil, nil, query)
  846. So(err, ShouldBeNil)
  847. queryResult := resp.Results["A"]
  848. So(queryResult.Error, ShouldBeNil)
  849. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  850. columns := queryResult.Tables[0].Rows[0]
  851. //Should be in milliseconds
  852. So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000))
  853. })
  854. Convey("When doing an annotation query with a time column holding a bigint null value should return nil", func() {
  855. query := &tsdb.TsdbQuery{
  856. Queries: []*tsdb.Query{
  857. {
  858. Model: simplejson.NewFromAny(map[string]interface{}{
  859. "rawSql": `SELECT
  860. cast(null as bigint) as time,
  861. 'message' as text,
  862. 'tag1,tag2' as tags
  863. `,
  864. "format": "table",
  865. }),
  866. RefId: "A",
  867. },
  868. },
  869. }
  870. resp, err := endpoint.Query(nil, nil, query)
  871. So(err, ShouldBeNil)
  872. queryResult := resp.Results["A"]
  873. So(queryResult.Error, ShouldBeNil)
  874. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  875. columns := queryResult.Tables[0].Rows[0]
  876. //Should be in milliseconds
  877. So(columns[0], ShouldBeNil)
  878. })
  879. Convey("When doing an annotation query with a time column holding a datetime null value should return nil", func() {
  880. query := &tsdb.TsdbQuery{
  881. Queries: []*tsdb.Query{
  882. {
  883. Model: simplejson.NewFromAny(map[string]interface{}{
  884. "rawSql": `SELECT
  885. cast(null as datetime) as time,
  886. 'message' as text,
  887. 'tag1,tag2' as tags
  888. `,
  889. "format": "table",
  890. }),
  891. RefId: "A",
  892. },
  893. },
  894. }
  895. resp, err := endpoint.Query(nil, nil, query)
  896. So(err, ShouldBeNil)
  897. queryResult := resp.Results["A"]
  898. So(queryResult.Error, ShouldBeNil)
  899. So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
  900. columns := queryResult.Tables[0].Rows[0]
  901. //Should be in milliseconds
  902. So(columns[0], ShouldBeNil)
  903. })
  904. })
  905. })
  906. }
  907. func InitMSSQLTestDB(t *testing.T) *xorm.Engine {
  908. x, err := xorm.NewEngine(sqlutil.TestDB_Mssql.DriverName, strings.Replace(sqlutil.TestDB_Mssql.ConnStr, "localhost", serverIP, 1))
  909. if err != nil {
  910. t.Fatalf("Failed to init mssql db %v", err)
  911. }
  912. x.DatabaseTZ = time.UTC
  913. x.TZLocation = time.UTC
  914. // x.ShowSQL()
  915. return x
  916. }
  917. func genTimeRangeByInterval(from time.Time, duration time.Duration, interval time.Duration) []time.Time {
  918. durationSec := int64(duration.Seconds())
  919. intervalSec := int64(interval.Seconds())
  920. timeRange := []time.Time{}
  921. for i := int64(0); i < durationSec; i += intervalSec {
  922. timeRange = append(timeRange, from)
  923. from = from.Add(time.Duration(int64(time.Second) * intervalSec))
  924. }
  925. return timeRange
  926. }