mssql_test.go 35 KB

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