mssql_test.go 37 KB

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