mssql_test.go 37 KB

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