mssql_test.go 33 KB

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