diff --git a/circle.yml b/.circleci/config.yml
similarity index 100%
rename from circle.yml
rename to .circleci/config.yml
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 69862c3f214..ee3d004f22d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,20 +1,28 @@
# 5.1.0 (unreleased)
+* **MSSQL**: New Microsoft SQL Server data source [#10093](https://github.com/grafana/grafana/pull/10093), [#11298](https://github.com/grafana/grafana/pull/11298), thx [@linuxchips](https://github.com/linuxchips)
* **Prometheus**: The heatmap panel now support Prometheus histograms [#10009](https://github.com/grafana/grafana/issues/10009)
* **Postgres/MySQL**: Ability to insert 0s or nulls for missing intervals [#9487](https://github.com/grafana/grafana/issues/9487), thanks [@svenklemm](https://github.com/svenklemm)
+* **Graph**: Align left and right Y-axes to one level [#1271](https://github.com/grafana/grafana/issues/1271) & [#2740](https://github.com/grafana/grafana/issues/2740) thx [@ilgizar](https://github.com/ilgizar)
* **Graph**: Thresholds for Right Y axis [#7107](https://github.com/grafana/grafana/issues/7107), thx [@ilgizar](https://github.com/ilgizar)
* **Graph**: Support multiple series stacking in histogram mode [#8151](https://github.com/grafana/grafana/issues/8151), thx [@mtanda](https://github.com/mtanda)
* **Alerting**: Pausing/un alerts now updates new_state_date [#10942](https://github.com/grafana/grafana/pull/10942)
* **Alerting**: Support Pagerduty notification channel using Pagerduty V2 API [#10531](https://github.com/grafana/grafana/issues/10531), thx [@jbaublitz](https://github.com/jbaublitz)
* **Templating**: Add comma templating format [#10632](https://github.com/grafana/grafana/issues/10632), thx [@mtanda](https://github.com/mtanda)
* **Prometheus**: Support POST for query and query_range [#9859](https://github.com/grafana/grafana/pull/9859), thx [@mtanda](https://github.com/mtanda)
+* **Alerting**: Add support for retries on alert queries [#5855](https://github.com/grafana/grafana/issues/5855), thx [@Thib17](https://github.com/Thib17)
+* **Server**: Adjust permissions of unix socket [#11343](https://github.com/grafana/grafana/pull/11343), thx [@corny](https://github.com/corny)
### Minor
* **OpsGenie**: Add triggered alerts as description [#11046](https://github.com/grafana/grafana/pull/11046), thx [@llamashoes](https://github.com/llamashoes)
* **Cloudwatch**: Support high resolution metrics [#10925](https://github.com/grafana/grafana/pull/10925), thx [@mtanda](https://github.com/mtanda)
* **Cloudwatch**: Add dimension filtering to CloudWatch `dimension_values()` [#10029](https://github.com/grafana/grafana/issues/10029), thx [@willyhutw](https://github.com/willyhutw)
-* **Units**: Second to HH:mm:ss formatter [#11107](https://github.com/grafana/grafana/issues/11107), thx [@gladdiologist](https://github.com/gladdiologist)
+* **Units**: Second to HH:mm:ss formatter [#11107](https://github.com/grafana/grafana/issues/11107), thx [@gladdiologist](https://github.com/gladdiologist)
* **Singlestat**: Add color to prefix and postfix in singlestat panel [#11143](https://github.com/grafana/grafana/pull/11143), thx [@ApsOps](https://github.com/ApsOps)
+* **Dashboards**: Version cleanup fails on old databases with many entries [#11278](https://github.com/grafana/grafana/issues/11278)
+
+# 5.0.4 (unreleased)
+* **Dashboard** Fixed bug where collapsed panels could not be directly linked to/renderer [#11114](https://github.com/grafana/grafana/issues/11114) & [#11086](https://github.com/grafana/grafana/issues/11086)
# 5.0.3 (2018-03-16)
* **Mysql**: Mysql panic occurring occasionally upon Grafana dashboard access (a bigger patch than the one in 5.0.2) [#11155](https://github.com/grafana/grafana/issues/11155)
diff --git a/Gopkg.lock b/Gopkg.lock
index 78316b77664..efc30115e74 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -153,7 +153,6 @@
packages = [
".",
"memcache",
- "mysql",
"postgres",
"redis"
]
@@ -179,12 +178,14 @@
[[projects]]
name = "github.com/go-xorm/core"
packages = ["."]
- revision = "e8409d73255791843585964791443dbad877058c"
+ revision = "da1adaf7a28ca792961721a34e6e04945200c890"
+ version = "v0.5.7"
[[projects]]
name = "github.com/go-xorm/xorm"
packages = ["."]
- revision = "6687a2b4e824f4d87f2d65060ec5cb0d896dff1e"
+ revision = "1933dd69e294c0a26c0266637067f24dbb25770c"
+ version = "v0.6.4"
[[projects]]
branch = "master"
diff --git a/Gopkg.toml b/Gopkg.toml
index 1f8cbba6e11..58048fee2ad 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -85,13 +85,11 @@ ignored = [
[[constraint]]
name = "github.com/go-xorm/core"
- revision = "e8409d73255791843585964791443dbad877058c"
- #version = "0.5.7" //keeping this since we would rather depend on version then commit
+ version = "0.5.7"
[[constraint]]
name = "github.com/go-xorm/xorm"
- revision = "6687a2b4e824f4d87f2d65060ec5cb0d896dff1e"
- #version = "0.6.4" //keeping this since we would rather depend on version then commit
+ version = "0.6.4"
[[constraint]]
name = "github.com/gorilla/websocket"
diff --git a/conf/defaults.ini b/conf/defaults.ini
index 4a2240f1924..11d173d955d 100644
--- a/conf/defaults.ini
+++ b/conf/defaults.ini
@@ -82,6 +82,9 @@ max_idle_conn = 2
# Max conn setting default is 0 (mean not set)
max_open_conn =
+# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours)
+conn_max_lifetime = 14400
+
# Set to true to log the sql calls and execution times.
log_queries =
@@ -125,6 +128,9 @@ cookie_secure = false
session_life_time = 86400
gc_interval_time = 86400
+# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours)
+conn_max_lifetime = 14400
+
#################################### Data proxy ###########################
[dataproxy]
diff --git a/conf/sample.ini b/conf/sample.ini
index 34b28ccf3fe..1af5bbdb62b 100644
--- a/conf/sample.ini
+++ b/conf/sample.ini
@@ -90,6 +90,9 @@
# Max conn setting default is 0 (mean not set)
;max_open_conn =
+# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours)
+;conn_max_lifetime = 14400
+
# Set to true to log the sql calls and execution times.
log_queries =
diff --git a/docker/blocks/mssql_tests/dashboard.json b/docker/blocks/mssql_tests/dashboard.json
index 323a61bb49a..20e3907b48b 100644
--- a/docker/blocks/mssql_tests/dashboard.json
+++ b/docker/blocks/mssql_tests/dashboard.json
@@ -53,7 +53,7 @@
"iconColor": "#6ed0e0",
"limit": 100,
"name": "Deploys",
- "rawQuery": "SELECT\n time_sec as time,\n description as [text],\n tags\n FROM [event]\n WHERE $__unixEpochFilter(time_sec) AND tags='deploy'\n ORDER BY 1 ASC\n ",
+ "rawQuery": "SELECT\n $__time(time_sec),\n description as [text],\n tags\n FROM [event]\n WHERE $__unixEpochFilter(time_sec) AND tags='deploy'\n ORDER BY 1 ASC\n ",
"showIn": 0,
"tags": [],
"type": "tags"
@@ -65,7 +65,7 @@
"iconColor": "rgba(255, 96, 96, 1)",
"limit": 100,
"name": "Tickets",
- "rawQuery": "SELECT\n time_sec as time,\n description as [text],\n tags\n FROM [event]\n WHERE $__unixEpochFilter(time_sec) AND tags='ticket'\n ORDER BY 1 ASC\n ",
+ "rawQuery": "SELECT\n $__time(time_sec),\n description as [text],\n tags\n FROM [event]\n WHERE $__unixEpochFilter(time_sec) AND tags='ticket'\n ORDER BY 1 ASC\n ",
"showIn": 0,
"tags": [],
"type": "tags"
@@ -76,8 +76,20 @@
"hide": false,
"iconColor": "#7eb26d",
"limit": 100,
- "name": "Metric Values",
- "rawQuery": "SELECT \n time, \n measurement as text, \n '' as tags\nFROM\n metric_values \nORDER BY 1",
+ "name": "Metric Values timeEpoch macro",
+ "rawQuery": "SELECT \n $__timeEpoch(time), \n measurement as text, \n '' as tags\nFROM\n metric_values \nWHERE\n $__timeFilter(time)\nORDER BY 1",
+ "showIn": 0,
+ "tags": [],
+ "type": "tags"
+ },
+ {
+ "datasource": "${DS_MSSQL_TEST}",
+ "enable": false,
+ "hide": false,
+ "iconColor": "#1f78c1",
+ "limit": 100,
+ "name": "Metric Values native time",
+ "rawQuery": "SELECT \n time, \n measurement as text, \n '' as tags\nFROM\n metric_values \nWHERE\n $__timeFilter(time)\nORDER BY 1",
"showIn": 0,
"tags": [],
"type": "tags"
@@ -88,7 +100,7 @@
"gnetId": null,
"graphTooltip": 0,
"id": null,
- "iteration": 1521481503341,
+ "iteration": 1521715844826,
"links": [],
"panels": [
{
@@ -138,6 +150,222 @@
"transform": "table",
"type": "table"
},
+ {
+ "columns": [],
+ "datasource": "${DS_MSSQL_TEST}",
+ "fontSize": "100%",
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 0,
+ "y": 4
+ },
+ "id": 32,
+ "links": [],
+ "pageSize": null,
+ "scroll": true,
+ "showHeader": true,
+ "sort": {
+ "col": 0,
+ "desc": true
+ },
+ "styles": [
+ {
+ "alias": "Time",
+ "dateFormat": "YYYY-MM-DD HH:mm:ss",
+ "pattern": "time",
+ "type": "date"
+ },
+ {
+ "alias": "",
+ "colorMode": null,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "decimals": 2,
+ "pattern": "/.*/",
+ "thresholds": [],
+ "type": "number",
+ "unit": "short"
+ }
+ ],
+ "targets": [
+ {
+ "alias": "",
+ "format": "table",
+ "rawSql": "SELECT cast(null as bigint) as time",
+ "refId": "A",
+ "target": ""
+ }
+ ],
+ "title": "cast(null as bigint) as time",
+ "transform": "table",
+ "type": "table"
+ },
+ {
+ "columns": [],
+ "datasource": "${DS_MSSQL_TEST}",
+ "fontSize": "100%",
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 6,
+ "y": 4
+ },
+ "id": 33,
+ "links": [],
+ "pageSize": null,
+ "scroll": true,
+ "showHeader": true,
+ "sort": {
+ "col": 0,
+ "desc": true
+ },
+ "styles": [
+ {
+ "alias": "Time",
+ "dateFormat": "YYYY-MM-DD HH:mm:ss",
+ "pattern": "time",
+ "type": "date"
+ },
+ {
+ "alias": "",
+ "colorMode": null,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "decimals": 2,
+ "pattern": "/.*/",
+ "thresholds": [],
+ "type": "number",
+ "unit": "short"
+ }
+ ],
+ "targets": [
+ {
+ "alias": "",
+ "format": "table",
+ "rawSql": "SELECT cast(null as datetime) as time",
+ "refId": "A",
+ "target": ""
+ }
+ ],
+ "title": "cast(null as datetime) as time",
+ "transform": "table",
+ "type": "table"
+ },
+ {
+ "columns": [],
+ "datasource": "${DS_MSSQL_TEST}",
+ "fontSize": "100%",
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 12,
+ "y": 4
+ },
+ "id": 34,
+ "links": [],
+ "pageSize": null,
+ "scroll": true,
+ "showHeader": true,
+ "sort": {
+ "col": 0,
+ "desc": true
+ },
+ "styles": [
+ {
+ "alias": "Time",
+ "dateFormat": "YYYY-MM-DD HH:mm:ss",
+ "pattern": "time",
+ "type": "date"
+ },
+ {
+ "alias": "",
+ "colorMode": null,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "decimals": 2,
+ "pattern": "/.*/",
+ "thresholds": [],
+ "type": "number",
+ "unit": "short"
+ }
+ ],
+ "targets": [
+ {
+ "alias": "",
+ "format": "table",
+ "rawSql": "SELECT GETDATE() as time",
+ "refId": "A",
+ "target": ""
+ }
+ ],
+ "title": "GETDATE() as time",
+ "transform": "table",
+ "type": "table"
+ },
+ {
+ "columns": [],
+ "datasource": "${DS_MSSQL_TEST}",
+ "fontSize": "100%",
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 18,
+ "y": 4
+ },
+ "id": 35,
+ "links": [],
+ "pageSize": null,
+ "scroll": true,
+ "showHeader": true,
+ "sort": {
+ "col": 0,
+ "desc": true
+ },
+ "styles": [
+ {
+ "alias": "Time",
+ "dateFormat": "YYYY-MM-DD HH:mm:ss",
+ "pattern": "time",
+ "type": "date"
+ },
+ {
+ "alias": "",
+ "colorMode": null,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "decimals": 2,
+ "pattern": "/.*/",
+ "thresholds": [],
+ "type": "number",
+ "unit": "short"
+ }
+ ],
+ "targets": [
+ {
+ "alias": "",
+ "format": "table",
+ "rawSql": "SELECT GETUTCDATE() as time",
+ "refId": "A",
+ "target": ""
+ }
+ ],
+ "title": "GETUTCDATE() as time",
+ "transform": "table",
+ "type": "table"
+ },
{
"aliasColors": {},
"bars": false,
@@ -149,7 +377,7 @@
"h": 9,
"w": 8,
"x": 0,
- "y": 4
+ "y": 7
},
"id": 7,
"legend": {
@@ -228,7 +456,7 @@
"h": 9,
"w": 8,
"x": 8,
- "y": 4
+ "y": 7
},
"id": 9,
"legend": {
@@ -307,7 +535,7 @@
"h": 9,
"w": 8,
"x": 16,
- "y": 4
+ "y": 7
},
"id": 10,
"legend": {
@@ -386,7 +614,7 @@
"h": 9,
"w": 8,
"x": 0,
- "y": 13
+ "y": 16
},
"id": 16,
"legend": {
@@ -465,7 +693,7 @@
"h": 9,
"w": 8,
"x": 8,
- "y": 13
+ "y": 16
},
"id": 12,
"legend": {
@@ -544,7 +772,7 @@
"h": 9,
"w": 8,
"x": 16,
- "y": 13
+ "y": 16
},
"id": 13,
"legend": {
@@ -623,7 +851,7 @@
"h": 8,
"w": 12,
"x": 0,
- "y": 22
+ "y": 25
},
"id": 27,
"legend": {
@@ -655,13 +883,13 @@
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT \n $__timeGroup(time, '$summarize') as time, \n measurement + ' - value one' as metric, \n avg(valueOne) as valueOne\nFROM\n metric_values \nWHERE\n $__timeFilter(time)\nGROUP BY \n $__timeGroup(time, '$summarize'), \n measurement \nORDER BY 1",
+ "rawSql": "SELECT \n $__timeGroup(time, '$summarize') as time, \n measurement + ' - value one' as metric, \n avg(valueOne) as valueOne\nFROM\n metric_values \nWHERE\n $__timeFilter(time) AND\n ($metric = 'ALL' OR measurement = $metric)\nGROUP BY \n $__timeGroup(time, '$summarize'), \n measurement \nORDER BY 1",
"refId": "A"
},
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT \n $__timeGroup(time, '$summarize') as time, \n measurement + ' - value two' as metric, \n avg(valueTwo) as valueTwo \nFROM\n metric_values \nGROUP BY \n $__timeGroup(time, '$summarize'), \n measurement \nORDER BY 1",
+ "rawSql": "SELECT \n $__timeGroup(time, '$summarize') as time, \n measurement + ' - value two' as metric, \n avg(valueTwo) as valueTwo \nFROM\n metric_values\nWHERE\n $__timeFilter(time) AND\n ($metric = 'ALL' OR measurement = $metric)\nGROUP BY \n $__timeGroup(time, '$summarize'), \n measurement \nORDER BY 1",
"refId": "B"
}
],
@@ -712,7 +940,7 @@
"h": 8,
"w": 12,
"x": 12,
- "y": 22
+ "y": 25
},
"id": 5,
"legend": {
@@ -734,7 +962,19 @@
"pointradius": 3,
"points": false,
"renderer": "flot",
- "seriesOverrides": [],
+ "seriesOverrides": [
+ {
+ "alias": "MovingAverageValueOne",
+ "dashes": true,
+ "lines": false
+ },
+ {
+ "alias": "MovingAverageValueTwo",
+ "dashes": true,
+ "lines": false,
+ "yaxis": 1
+ }
+ ],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
@@ -742,8 +982,14 @@
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT \n $__timeGroup(time, '$summarize') as time, \n avg(valueOne) as valueOne, \n avg(valueTwo) as valueTwo \nFROM\n metric_values \nGROUP BY \n $__timeGroup(time, '$summarize')\nORDER BY 1",
+ "rawSql": "SELECT \n $__timeGroup(time, '$summarize') as time, \n avg(valueOne) as valueOne, \n avg(valueTwo) as valueTwo \nFROM\n metric_values \nWHERE \n $__timeFilter(time) AND \n ($metric = 'ALL' OR measurement = $metric)\nGROUP BY \n $__timeGroup(time, '$summarize')\nORDER BY 1",
"refId": "A"
+ },
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT \n time,\n avg(valueOne) OVER (ORDER BY time ROWS BETWEEN 6 PRECEDING AND 6 FOLLOWING) as MovingAverageValueOne,\n avg(valueTwo) OVER (ORDER BY time ROWS BETWEEN 6 PRECEDING AND 6 FOLLOWING) as MovingAverageValueTwo\nFROM\n metric_values \nWHERE \n $__timeFilter(time) AND \n ($metric = 'ALL' OR measurement = $metric)\nORDER BY 1",
+ "refId": "B"
}
],
"thresholds": [],
@@ -793,7 +1039,7 @@
"h": 8,
"w": 12,
"x": 0,
- "y": 30
+ "y": 33
},
"id": 4,
"legend": {
@@ -825,13 +1071,13 @@
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values WHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "A"
},
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value two' as metric, valueTwo FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value two' as metric, valueTwo FROM metric_values WHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "B"
}
],
@@ -882,7 +1128,7 @@
"h": 8,
"w": 12,
"x": 12,
- "y": 30
+ "y": 33
},
"id": 28,
"legend": {
@@ -963,7 +1209,7 @@
"h": 8,
"w": 12,
"x": 0,
- "y": 38
+ "y": 41
},
"id": 19,
"legend": {
@@ -995,13 +1241,13 @@
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values WHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "A"
},
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value two' as metric, valueTwo FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value two' as metric, valueTwo FROM metric_values WHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "B"
}
],
@@ -1052,7 +1298,7 @@
"h": 8,
"w": 12,
"x": 12,
- "y": 38
+ "y": 41
},
"id": 18,
"legend": {
@@ -1082,7 +1328,7 @@
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values\nWHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "A"
}
],
@@ -1133,7 +1379,7 @@
"h": 8,
"w": 12,
"x": 0,
- "y": 46
+ "y": 49
},
"id": 17,
"legend": {
@@ -1165,13 +1411,13 @@
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values WHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "A"
},
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value two' as metric, valueTwo FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value two' as metric, valueTwo FROM metric_values WHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "B"
}
],
@@ -1222,7 +1468,7 @@
"h": 8,
"w": 12,
"x": 12,
- "y": 46
+ "y": 49
},
"id": 20,
"legend": {
@@ -1252,7 +1498,7 @@
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values\nWHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "A"
}
],
@@ -1303,7 +1549,7 @@
"h": 8,
"w": 12,
"x": 0,
- "y": 54
+ "y": 57
},
"id": 29,
"legend": {
@@ -1335,7 +1581,7 @@
{
"alias": "",
"format": "time_series",
- "rawSql": "DECLARE \n @from int = $__unixEpochFrom(),\n @to int = $__unixEpochTo()\n \nEXEC dbo.sp_test_epoch @from, @to",
+ "rawSql": "DECLARE\n @from int = $__unixEpochFrom(), \n @to int = $__unixEpochTo(), \n @interval nvarchar(50) = '$summarize', \n @metric nvarchar(200) = $metric\n \nEXEC dbo.sp_test_epoch @from, @to, @interval, @metric",
"refId": "A"
}
],
@@ -1386,7 +1632,7 @@
"h": 8,
"w": 12,
"x": 12,
- "y": 54
+ "y": 57
},
"id": 30,
"legend": {
@@ -1418,7 +1664,7 @@
{
"alias": "",
"format": "time_series",
- "rawSql": "DECLARE \n @from datetime = $__timeFrom(),\n @to datetime = $__timeTo()\n \nEXEC dbo.sp_test_datetime @from, @to",
+ "rawSql": "DECLARE\n @from datetime = $__timeFrom(), \n @to datetime = $__timeTo(), \n @interval nvarchar(50) = '$summarize', \n @metric nvarchar(200) = $metric\n \nEXEC dbo.sp_test_datetime @from, @to, @interval, @metric",
"refId": "A"
}
],
@@ -1469,7 +1715,7 @@
"h": 8,
"w": 12,
"x": 0,
- "y": 62
+ "y": 65
},
"id": 14,
"legend": {
@@ -1499,13 +1745,13 @@
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values \nWHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "A"
},
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value two' as metric, valueTwo FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value two' as metric, valueTwo FROM metric_values \nWHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "B"
}
],
@@ -1559,7 +1805,7 @@
"h": 8,
"w": 12,
"x": 12,
- "y": 62
+ "y": 65
},
"id": 15,
"legend": {
@@ -1589,7 +1835,7 @@
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values\nWHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "A"
}
],
@@ -1642,7 +1888,7 @@
"h": 8,
"w": 12,
"x": 0,
- "y": 70
+ "y": 73
},
"id": 25,
"legend": {
@@ -1672,13 +1918,13 @@
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values \nWHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "A"
},
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value two' as metric, valueTwo FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value two' as metric, valueTwo FROM metric_values \nWHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "B"
}
],
@@ -1732,7 +1978,7 @@
"h": 8,
"w": 12,
"x": 12,
- "y": 70
+ "y": 73
},
"id": 22,
"legend": {
@@ -1762,7 +2008,7 @@
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values\nWHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "A"
}
],
@@ -1815,7 +2061,7 @@
"h": 8,
"w": 12,
"x": 0,
- "y": 78
+ "y": 81
},
"id": 21,
"legend": {
@@ -1845,13 +2091,13 @@
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values \nWHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "A"
},
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value two' as metric, valueTwo FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value two' as metric, valueTwo FROM metric_values \nWHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "B"
}
],
@@ -1905,7 +2151,7 @@
"h": 8,
"w": 12,
"x": 12,
- "y": 78
+ "y": 81
},
"id": 26,
"legend": {
@@ -1935,7 +2181,7 @@
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values \nWHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "A"
}
],
@@ -1988,7 +2234,7 @@
"h": 8,
"w": 12,
"x": 0,
- "y": 86
+ "y": 89
},
"id": 23,
"legend": {
@@ -2018,13 +2264,13 @@
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value one' as metric, valueOne FROM metric_values\nWHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "A"
},
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value two' as metric, valueTwo FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), measurement + ' - value two' as metric, valueTwo FROM metric_values \nWHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "B"
}
],
@@ -2078,7 +2324,7 @@
"h": 8,
"w": 12,
"x": 12,
- "y": 86
+ "y": 89
},
"id": 24,
"legend": {
@@ -2108,7 +2354,7 @@
{
"alias": "",
"format": "time_series",
- "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values ORDER BY 1",
+ "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values \nWHERE $__timeFilter(time) AND ($metric = 'ALL' OR measurement = $metric) ORDER BY 1",
"refId": "A"
}
],
@@ -2157,6 +2403,26 @@
"tags": [],
"templating": {
"list": [
+ {
+ "allValue": "'ALL'",
+ "current": {},
+ "datasource": "${DS_MSSQL_TEST}",
+ "hide": 0,
+ "includeAll": true,
+ "label": "Metric",
+ "multi": false,
+ "name": "metric",
+ "options": [],
+ "query": "SELECT DISTINCT measurement FROM metric_values",
+ "refresh": 1,
+ "regex": "",
+ "sort": 0,
+ "tagValuesQuery": "",
+ "tags": [],
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ },
{
"auto": false,
"auto_count": 30,
@@ -2208,7 +2474,7 @@
},
"time": {
"from": "2018-03-15T12:30:00.000Z",
- "to": "2018-03-15T13:55:00.000Z"
+ "to": "2018-03-15T13:55:01.000Z"
},
"timepicker": {
"refresh_intervals": [
@@ -2238,5 +2504,5 @@
"timezone": "",
"title": "Microsoft SQL Server Data Source Test",
"uid": "GlAqcPgmz",
- "version": 37
+ "version": 57
}
\ No newline at end of file
diff --git a/docker/blocks/mysql_tests/dashboard.json b/docker/blocks/mysql_tests/dashboard.json
new file mode 100644
index 00000000000..3ab08a7da35
--- /dev/null
+++ b/docker/blocks/mysql_tests/dashboard.json
@@ -0,0 +1,2350 @@
+{
+ "__inputs": [
+ {
+ "name": "DS_MYSQL_TEST",
+ "label": "MySQL TEST",
+ "description": "",
+ "type": "datasource",
+ "pluginId": "mysql",
+ "pluginName": "MySQL"
+ },
+ {
+ "name": "DS_MSSQL_TEST",
+ "label": "MSSQL Test",
+ "description": "",
+ "type": "datasource",
+ "pluginId": "mssql",
+ "pluginName": "Microsoft SQL Server"
+ }
+ ],
+ "__requires": [
+ {
+ "type": "grafana",
+ "id": "grafana",
+ "name": "Grafana",
+ "version": "5.0.0"
+ },
+ {
+ "type": "panel",
+ "id": "graph",
+ "name": "Graph",
+ "version": "5.0.0"
+ },
+ {
+ "type": "datasource",
+ "id": "mssql",
+ "name": "Microsoft SQL Server",
+ "version": "1.0.0"
+ },
+ {
+ "type": "datasource",
+ "id": "mysql",
+ "name": "MySQL",
+ "version": "5.0.0"
+ },
+ {
+ "type": "panel",
+ "id": "table",
+ "name": "Table",
+ "version": "5.0.0"
+ }
+ ],
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": "-- Grafana --",
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "type": "dashboard"
+ },
+ {
+ "datasource": "${DS_MYSQL_TEST}",
+ "enable": false,
+ "hide": false,
+ "iconColor": "#6ed0e0",
+ "limit": 100,
+ "name": "Deploys",
+ "rawQuery": "SELECT\n time_sec,\n description as text,\n tags\n FROM event\n WHERE $__unixEpochFilter(time_sec) AND tags='deploy'\n ORDER BY 1 ASC\n ",
+ "showIn": 0,
+ "tags": [],
+ "type": "tags"
+ },
+ {
+ "datasource": "${DS_MYSQL_TEST}",
+ "enable": false,
+ "hide": false,
+ "iconColor": "rgba(255, 96, 96, 1)",
+ "limit": 100,
+ "name": "Tickets",
+ "rawQuery": "SELECT\n time_sec as time,\n description as text,\n tags\n FROM event\n WHERE $__unixEpochFilter(time_sec) AND tags='ticket'\n ORDER BY 1 ASC\n ",
+ "showIn": 0,
+ "tags": [],
+ "type": "tags"
+ },
+ {
+ "datasource": "${DS_MYSQL_TEST}",
+ "enable": false,
+ "hide": false,
+ "iconColor": "#7eb26d",
+ "limit": 100,
+ "name": "Metric Values timeEpoch macro",
+ "rawQuery": "SELECT \n $__timeEpoch(time), \n measurement as text, \n '' as tags\nFROM\n metric_values \nWHERE\n $__timeFilter(time)\nORDER BY 1",
+ "showIn": 0,
+ "tags": [],
+ "type": "tags"
+ },
+ {
+ "datasource": "${DS_MYSQL_TEST}",
+ "enable": false,
+ "hide": false,
+ "iconColor": "#1f78c1",
+ "limit": 100,
+ "name": "Metric Values native time",
+ "rawQuery": "SELECT \n time, \n measurement as text, \n '' as tags\nFROM\n metric_values \nWHERE\n $__timeFilter(time)\nORDER BY 1",
+ "showIn": 0,
+ "tags": [],
+ "type": "tags"
+ }
+ ]
+ },
+ "editable": true,
+ "gnetId": null,
+ "graphTooltip": 0,
+ "id": null,
+ "iteration": 1521715720483,
+ "links": [],
+ "panels": [
+ {
+ "columns": [],
+ "datasource": "${DS_MYSQL_TEST}",
+ "fontSize": "100%",
+ "gridPos": {
+ "h": 4,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 2,
+ "links": [],
+ "pageSize": null,
+ "scroll": true,
+ "showHeader": true,
+ "sort": {
+ "col": 0,
+ "desc": true
+ },
+ "styles": [
+ {
+ "alias": "",
+ "colorMode": null,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "decimals": 2,
+ "pattern": "/.*/",
+ "thresholds": [],
+ "type": "string",
+ "unit": "short"
+ }
+ ],
+ "targets": [
+ {
+ "alias": "",
+ "format": "table",
+ "rawSql": "SELECT * from mysql_types",
+ "refId": "A"
+ }
+ ],
+ "title": "Data types",
+ "transform": "table",
+ "type": "table"
+ },
+ {
+ "columns": [],
+ "datasource": "${DS_MYSQL_TEST}",
+ "fontSize": "100%",
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 0,
+ "y": 4
+ },
+ "id": 32,
+ "links": [],
+ "pageSize": null,
+ "scroll": true,
+ "showHeader": true,
+ "sort": {
+ "col": 0,
+ "desc": true
+ },
+ "styles": [
+ {
+ "alias": "Time",
+ "dateFormat": "YYYY-MM-DD HH:mm:ss",
+ "pattern": "time_sec",
+ "type": "date"
+ },
+ {
+ "alias": "",
+ "colorMode": null,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "decimals": 2,
+ "pattern": "/.*/",
+ "thresholds": [],
+ "type": "number",
+ "unit": "short"
+ }
+ ],
+ "targets": [
+ {
+ "alias": "",
+ "format": "table",
+ "rawSql": "SELECT cast(null as unsigned integer) as time_sec",
+ "refId": "A",
+ "target": ""
+ }
+ ],
+ "title": "cast(null as unsigned integer) as time",
+ "transform": "table",
+ "type": "table"
+ },
+ {
+ "columns": [],
+ "datasource": "${DS_MYSQL_TEST}",
+ "fontSize": "100%",
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 6,
+ "y": 4
+ },
+ "id": 33,
+ "links": [],
+ "pageSize": null,
+ "scroll": true,
+ "showHeader": true,
+ "sort": {
+ "col": 0,
+ "desc": true
+ },
+ "styles": [
+ {
+ "alias": "Time",
+ "dateFormat": "YYYY-MM-DD HH:mm:ss",
+ "pattern": "time_sec",
+ "type": "date"
+ },
+ {
+ "alias": "",
+ "colorMode": null,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "decimals": 2,
+ "pattern": "/.*/",
+ "thresholds": [],
+ "type": "number",
+ "unit": "short"
+ }
+ ],
+ "targets": [
+ {
+ "alias": "",
+ "format": "table",
+ "rawSql": "SELECT cast(null as datetime) as time_sec",
+ "refId": "A",
+ "target": ""
+ }
+ ],
+ "title": "cast(null as datetime) as time",
+ "transform": "table",
+ "type": "table"
+ },
+ {
+ "columns": [],
+ "datasource": "${DS_MYSQL_TEST}",
+ "fontSize": "100%",
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 12,
+ "y": 4
+ },
+ "id": 34,
+ "links": [],
+ "pageSize": null,
+ "scroll": true,
+ "showHeader": true,
+ "sort": {
+ "col": 0,
+ "desc": true
+ },
+ "styles": [
+ {
+ "alias": "Time",
+ "dateFormat": "YYYY-MM-DD HH:mm:ss",
+ "pattern": "time_sec",
+ "type": "date"
+ },
+ {
+ "alias": "",
+ "colorMode": null,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "decimals": 2,
+ "pattern": "/.*/",
+ "thresholds": [],
+ "type": "number",
+ "unit": "short"
+ }
+ ],
+ "targets": [
+ {
+ "alias": "",
+ "format": "table",
+ "rawSql": "SELECT cast(NOW() as datetime) as time_sec",
+ "refId": "A",
+ "target": ""
+ }
+ ],
+ "title": "cast()NOW() as datetime) as time",
+ "transform": "table",
+ "type": "table"
+ },
+ {
+ "columns": [],
+ "datasource": "${DS_MYSQL_TEST}",
+ "fontSize": "100%",
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 18,
+ "y": 4
+ },
+ "id": 35,
+ "links": [],
+ "pageSize": null,
+ "scroll": true,
+ "showHeader": true,
+ "sort": {
+ "col": 0,
+ "desc": true
+ },
+ "styles": [
+ {
+ "alias": "Time",
+ "dateFormat": "YYYY-MM-DD HH:mm:ss",
+ "pattern": "time_sec",
+ "type": "date"
+ },
+ {
+ "alias": "",
+ "colorMode": null,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "decimals": 2,
+ "pattern": "/.*/",
+ "thresholds": [],
+ "type": "number",
+ "unit": "short"
+ }
+ ],
+ "targets": [
+ {
+ "alias": "",
+ "format": "table",
+ "rawSql": "SELECT NOW() as time",
+ "refId": "A",
+ "target": ""
+ }
+ ],
+ "title": "NOW() as time",
+ "transform": "table",
+ "type": "table"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 0,
+ "y": 7
+ },
+ "id": 7,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": true,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": true,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeGroup(time, '5m') AS time, avg(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY 1 ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "timeGroup macro 5m without fill",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 8,
+ "y": 7
+ },
+ "id": 9,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null as zero",
+ "percentage": false,
+ "pointradius": 3,
+ "points": true,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": true,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeGroup(time, '5m', NULL) AS time, avg(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY 1 ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "timeGroup macro 5m with fill(NULL) and null as zero",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 16,
+ "y": 7
+ },
+ "id": 10,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": true,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": true,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeGroup(time, '5m', 10.0) AS time, avg(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY 1 ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "timeGroup macro 5m with fill(10.0)",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 0,
+ "y": 16
+ },
+ "id": 16,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": false,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": true,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeGroup(time, '$summarize') AS time, avg(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY 1 ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Metrics - timeGroup macro $summarize without fill",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 8,
+ "y": 16
+ },
+ "id": 12,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": false,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null as zero",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": true,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeGroup(time, '$summarize', NULL) AS time, sum(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY 1 ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Metrics - timeGroup macro $summarize with fill(NULL)",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 16,
+ "y": 16
+ },
+ "id": 13,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": false,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": true,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeGroup(time, '$summarize', 100.0) AS time, sum(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY 1 ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Metrics - timeGroup macro $summarize with fill(100.0)",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 25
+ },
+ "id": 27,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "hideEmpty": false,
+ "hideZero": false,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT \n $__timeGroup(time, '$summarize') as time, \n CONCAT(measurement, ' - value one') as metric, \n avg(valueOne) as valueOne\nFROM\n metric_values \nWHERE\n $__timeFilter(time) AND\n measurement IN($metric)\nGROUP BY 1, 2\nORDER BY 1",
+ "refId": "A"
+ },
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT \n $__timeGroup(time, '$summarize') as time, \n CONCAT(measurement, ' - value two') as metric, \n avg(valueTwo) as valueTwo \nFROM\n metric_values\nWHERE\n $__timeFilter(time) AND\n measurement IN($metric)\nGROUP BY 1,2\nORDER BY 1",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series with metric column using timeGroup macro ($summarize)",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 25
+ },
+ "id": 5,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [
+ {
+ "alias": "MovingAverageValueOne",
+ "dashes": true,
+ "lines": false
+ },
+ {
+ "alias": "MovingAverageValueTwo",
+ "dashes": true,
+ "lines": false,
+ "yaxis": 1
+ }
+ ],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT \n $__timeGroup(time, '$summarize') as time, \n avg(valueOne) as valueOne, \n avg(valueTwo) as valueTwo \nFROM\n metric_values \nWHERE \n $__timeFilter(time) AND \n measurement IN($metric)\nGROUP BY 1\nORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series without metric column using timeGroup macro ($summarize)",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 33
+ },
+ "id": 4,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "hideEmpty": false,
+ "hideZero": false,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__time(time), CONCAT(measurement, ' - value one') as metric, valueOne FROM metric_values WHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "A"
+ },
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__time(time), CONCAT(measurement, ' - value two') as metric, valueTwo FROM metric_values WHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series with metric column",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 33
+ },
+ "id": 28,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__time(time), valueOne, valueTwo FROM metric_values ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series without metric column",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 41
+ },
+ "id": 19,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "hideEmpty": false,
+ "hideZero": false,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__time(time), CONCAT(measurement, ' - value one') as metric, valueOne FROM metric_values WHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "A"
+ },
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__time(time), CONCAT(measurement, ' - value two') as metric, valueTwo FROM metric_values WHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series with metric column - stacked",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 41
+ },
+ "id": 18,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values\nWHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series without metric column - stacked",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 49
+ },
+ "id": 17,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "hideEmpty": false,
+ "hideZero": false,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": true,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__time(time), CONCAT(measurement, ' - value one') as metric, valueOne FROM metric_values WHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "A"
+ },
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__time(time), CONCAT(measurement, ' - value two') as metric, valueTwo FROM metric_values WHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series with metric column - stacked percent",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 49
+ },
+ "id": 20,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": true,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values\nWHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series without metric column - stacked percent",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 1,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 57
+ },
+ "id": 14,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": false,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), CONCAT(measurement, ' - value one') as metric, valueOne FROM metric_values \nWHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "A"
+ },
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), CONCAT(measurement, ' - value two') as metric, valueTwo FROM metric_values \nWHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series with metric column - series mode",
+ "tooltip": {
+ "shared": false,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "series",
+ "name": null,
+ "show": true,
+ "values": [
+ "total"
+ ]
+ },
+ "yaxes": [
+ {
+ "decimals": null,
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MSSQL_TEST}",
+ "fill": 1,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 57
+ },
+ "id": 15,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": false,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values\nWHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series without metric column - series mode",
+ "tooltip": {
+ "shared": false,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "series",
+ "name": null,
+ "show": true,
+ "values": [
+ "total"
+ ]
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 1,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 65
+ },
+ "id": 25,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": false,
+ "total": true,
+ "values": true
+ },
+ "lines": false,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), CONCAT(measurement, ' - value one') as metric, valueOne FROM metric_values \nWHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "A"
+ },
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), CONCAT(measurement, ' - value two') as metric, valueTwo FROM metric_values \nWHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series with metric column - histogram",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": 50,
+ "mode": "histogram",
+ "name": null,
+ "show": true,
+ "values": [
+ "current"
+ ]
+ },
+ "yaxes": [
+ {
+ "decimals": null,
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 1,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 65
+ },
+ "id": 22,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": false,
+ "total": true,
+ "values": true
+ },
+ "lines": false,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values\nWHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series without metric column - histogram",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": 100,
+ "mode": "histogram",
+ "name": null,
+ "show": true,
+ "values": [
+ "total"
+ ]
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 1,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 73
+ },
+ "id": 21,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": false,
+ "total": true,
+ "values": true
+ },
+ "lines": false,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), CONCAT(measurement, ' - value one') as metric, valueOne FROM metric_values \nWHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "A"
+ },
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), CONCAT(measurement, ' - value two') as metric, valueTwo FROM metric_values \nWHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series with metric column - histogram stacked",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": 20,
+ "mode": "histogram",
+ "name": null,
+ "show": true,
+ "values": [
+ "current"
+ ]
+ },
+ "yaxes": [
+ {
+ "decimals": null,
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 1,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 73
+ },
+ "id": 26,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": false,
+ "total": true,
+ "values": true
+ },
+ "lines": false,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values\nWHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series without metric column - histogram stacked",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": 20,
+ "mode": "histogram",
+ "name": null,
+ "show": true,
+ "values": [
+ "total"
+ ]
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 1,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 81
+ },
+ "id": 23,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": false,
+ "total": true,
+ "values": true
+ },
+ "lines": false,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": true,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), CONCAT(measurement, ' - value one') as metric, valueOne FROM metric_values \nWHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "A"
+ },
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), CONCAT(measurement, ' - value two') as metric, valueTwo FROM metric_values \nWHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series with metric column - histogram stacked percent",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": 20,
+ "mode": "histogram",
+ "name": null,
+ "show": true,
+ "values": [
+ "current"
+ ]
+ },
+ "yaxes": [
+ {
+ "decimals": null,
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_MYSQL_TEST}",
+ "fill": 1,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 81
+ },
+ "id": 24,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": false,
+ "total": true,
+ "values": true
+ },
+ "lines": false,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": true,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), valueOne, valueTwo FROM metric_values\nWHERE $__timeFilter(time) AND measurement IN($metric) ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series without metric column - histogram stacked percent",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": 20,
+ "mode": "histogram",
+ "name": null,
+ "show": true,
+ "values": [
+ "total"
+ ]
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ }
+ ],
+ "refresh": false,
+ "schemaVersion": 16,
+ "style": "dark",
+ "tags": [],
+ "templating": {
+ "list": [
+ {
+ "allValue": "",
+ "current": {},
+ "datasource": "${DS_MYSQL_TEST}",
+ "hide": 0,
+ "includeAll": true,
+ "label": "Metric",
+ "multi": true,
+ "name": "metric",
+ "options": [],
+ "query": "SELECT DISTINCT measurement FROM metric_values",
+ "refresh": 1,
+ "regex": "",
+ "sort": 0,
+ "tagValuesQuery": "",
+ "tags": [],
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ },
+ {
+ "auto": false,
+ "auto_count": 30,
+ "auto_min": "10s",
+ "current": {
+ "text": "10m",
+ "value": "10m"
+ },
+ "hide": 0,
+ "label": "Interval",
+ "name": "summarize",
+ "options": [
+ {
+ "selected": false,
+ "text": "1s",
+ "value": "1s"
+ },
+ {
+ "selected": false,
+ "text": "10s",
+ "value": "10s"
+ },
+ {
+ "selected": false,
+ "text": "30s",
+ "value": "30s"
+ },
+ {
+ "selected": false,
+ "text": "1m",
+ "value": "1m"
+ },
+ {
+ "selected": false,
+ "text": "5m",
+ "value": "5m"
+ },
+ {
+ "selected": true,
+ "text": "10m",
+ "value": "10m"
+ }
+ ],
+ "query": "1s,10s,30s,1m,5m,10m",
+ "refresh": 2,
+ "type": "interval"
+ }
+ ]
+ },
+ "time": {
+ "from": "2018-03-15T11:30:00.000Z",
+ "to": "2018-03-15T12:55:01.000Z"
+ },
+ "timepicker": {
+ "refresh_intervals": [
+ "5s",
+ "10s",
+ "30s",
+ "1m",
+ "5m",
+ "15m",
+ "30m",
+ "1h",
+ "2h",
+ "1d"
+ ],
+ "time_options": [
+ "5m",
+ "15m",
+ "1h",
+ "6h",
+ "12h",
+ "24h",
+ "2d",
+ "7d",
+ "30d"
+ ]
+ },
+ "timezone": "",
+ "title": "MySQL Data Source Test",
+ "uid": "Hmf8FDkmz",
+ "version": 9
+}
\ No newline at end of file
diff --git a/docker/blocks/postgres_tests/dashboard.json b/docker/blocks/postgres_tests/dashboard.json
new file mode 100644
index 00000000000..eea95863716
--- /dev/null
+++ b/docker/blocks/postgres_tests/dashboard.json
@@ -0,0 +1,2324 @@
+{
+ "__inputs": [
+ {
+ "name": "DS_POSTGRES_TEST",
+ "label": "Postgres TEST",
+ "description": "",
+ "type": "datasource",
+ "pluginId": "postgres",
+ "pluginName": "PostgreSQL"
+ }
+ ],
+ "__requires": [
+ {
+ "type": "grafana",
+ "id": "grafana",
+ "name": "Grafana",
+ "version": "5.0.0"
+ },
+ {
+ "type": "panel",
+ "id": "graph",
+ "name": "Graph",
+ "version": "5.0.0"
+ },
+ {
+ "type": "datasource",
+ "id": "postgres",
+ "name": "PostgreSQL",
+ "version": "5.0.0"
+ },
+ {
+ "type": "panel",
+ "id": "table",
+ "name": "Table",
+ "version": "5.0.0"
+ }
+ ],
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": "-- Grafana --",
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "type": "dashboard"
+ },
+ {
+ "datasource": "${DS_POSTGRES_TEST}",
+ "enable": false,
+ "hide": false,
+ "iconColor": "#6ed0e0",
+ "limit": 100,
+ "name": "Deploys",
+ "rawQuery": "SELECT \"time_sec\" as time, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC",
+ "showIn": 0,
+ "tags": [],
+ "type": "tags"
+ },
+ {
+ "datasource": "${DS_POSTGRES_TEST}",
+ "enable": false,
+ "hide": false,
+ "iconColor": "rgba(255, 96, 96, 1)",
+ "limit": 100,
+ "name": "Tickets",
+ "rawQuery": "SELECT \"time_sec\" as time, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC",
+ "showIn": 0,
+ "tags": [],
+ "type": "tags"
+ },
+ {
+ "datasource": "${DS_POSTGRES_TEST}",
+ "enable": false,
+ "hide": false,
+ "iconColor": "#7eb26d",
+ "limit": 100,
+ "name": "Metric Values timeEpoch macro",
+ "rawQuery": "SELECT \n $__timeEpoch(time), \n measurement as text, \n '' as tags\nFROM\n metric_values \nWHERE\n $__timeFilter(time)\nORDER BY 1",
+ "showIn": 0,
+ "tags": [],
+ "type": "tags"
+ },
+ {
+ "datasource": "${DS_POSTGRES_TEST}",
+ "enable": false,
+ "hide": false,
+ "iconColor": "#1f78c1",
+ "limit": 100,
+ "name": "Metric Values native time",
+ "rawQuery": "SELECT \n time, \n measurement as text, \n '' as tags\nFROM\n metric_values \nWHERE\n $__timeFilter(time)\nORDER BY 1",
+ "showIn": 0,
+ "tags": [],
+ "type": "tags"
+ }
+ ]
+ },
+ "editable": true,
+ "gnetId": null,
+ "graphTooltip": 0,
+ "id": null,
+ "iteration": 1521725946837,
+ "links": [],
+ "panels": [
+ {
+ "columns": [],
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fontSize": "100%",
+ "gridPos": {
+ "h": 4,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 2,
+ "links": [],
+ "pageSize": null,
+ "scroll": true,
+ "showHeader": true,
+ "sort": {
+ "col": 1,
+ "desc": false
+ },
+ "styles": [
+ {
+ "alias": "",
+ "colorMode": null,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "decimals": 2,
+ "pattern": "/.*/",
+ "thresholds": [],
+ "type": "string",
+ "unit": "short"
+ }
+ ],
+ "targets": [
+ {
+ "alias": "",
+ "format": "table",
+ "rawSql": "SELECT * FROM postgres_types",
+ "refId": "A"
+ }
+ ],
+ "title": "Data types",
+ "transform": "table",
+ "type": "table"
+ },
+ {
+ "columns": [],
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fontSize": "100%",
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 0,
+ "y": 4
+ },
+ "id": 32,
+ "links": [],
+ "pageSize": null,
+ "scroll": true,
+ "showHeader": true,
+ "sort": {
+ "col": 0,
+ "desc": true
+ },
+ "styles": [
+ {
+ "alias": "Time",
+ "dateFormat": "YYYY-MM-DD HH:mm:ss",
+ "pattern": "time",
+ "type": "date"
+ },
+ {
+ "alias": "",
+ "colorMode": null,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "decimals": 2,
+ "pattern": "/.*/",
+ "thresholds": [],
+ "type": "number",
+ "unit": "short"
+ }
+ ],
+ "targets": [
+ {
+ "alias": "",
+ "format": "table",
+ "rawSql": "SELECT cast(null as bigint) as time",
+ "refId": "A",
+ "target": ""
+ }
+ ],
+ "title": "cast(null as bigint) as time",
+ "transform": "table",
+ "type": "table"
+ },
+ {
+ "columns": [],
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fontSize": "100%",
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 6,
+ "y": 4
+ },
+ "id": 33,
+ "links": [],
+ "pageSize": null,
+ "scroll": true,
+ "showHeader": true,
+ "sort": {
+ "col": 0,
+ "desc": true
+ },
+ "styles": [
+ {
+ "alias": "Time",
+ "dateFormat": "YYYY-MM-DD HH:mm:ss",
+ "pattern": "time",
+ "type": "date"
+ },
+ {
+ "alias": "",
+ "colorMode": null,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "decimals": 2,
+ "pattern": "/.*/",
+ "thresholds": [],
+ "type": "number",
+ "unit": "short"
+ }
+ ],
+ "targets": [
+ {
+ "alias": "",
+ "format": "table",
+ "rawSql": "SELECT cast(null as timestamp) as time",
+ "refId": "A",
+ "target": ""
+ }
+ ],
+ "title": "cast(null as datetime) as time",
+ "transform": "table",
+ "type": "table"
+ },
+ {
+ "columns": [],
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fontSize": "100%",
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 12,
+ "y": 4
+ },
+ "id": 34,
+ "links": [],
+ "pageSize": null,
+ "scroll": true,
+ "showHeader": true,
+ "sort": {
+ "col": 0,
+ "desc": true
+ },
+ "styles": [
+ {
+ "alias": "Time",
+ "dateFormat": "YYYY-MM-DD HH:mm:ss",
+ "pattern": "time",
+ "type": "date"
+ },
+ {
+ "alias": "",
+ "colorMode": null,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "decimals": 2,
+ "pattern": "/.*/",
+ "thresholds": [],
+ "type": "number",
+ "unit": "short"
+ }
+ ],
+ "targets": [
+ {
+ "alias": "",
+ "format": "table",
+ "rawSql": "SELECT localtimestamp as time",
+ "refId": "A",
+ "target": ""
+ }
+ ],
+ "title": "localtimestamp as time",
+ "transform": "table",
+ "type": "table"
+ },
+ {
+ "columns": [],
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fontSize": "100%",
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 18,
+ "y": 4
+ },
+ "id": 35,
+ "links": [],
+ "pageSize": null,
+ "scroll": true,
+ "showHeader": true,
+ "sort": {
+ "col": 0,
+ "desc": true
+ },
+ "styles": [
+ {
+ "alias": "Time",
+ "dateFormat": "YYYY-MM-DD HH:mm:ss",
+ "pattern": "time",
+ "type": "date"
+ },
+ {
+ "alias": "",
+ "colorMode": null,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "decimals": 2,
+ "pattern": "/.*/",
+ "thresholds": [],
+ "type": "number",
+ "unit": "short"
+ }
+ ],
+ "targets": [
+ {
+ "alias": "",
+ "format": "table",
+ "rawSql": "SELECT NOW() as time",
+ "refId": "A",
+ "target": ""
+ }
+ ],
+ "title": "NOW() as time",
+ "transform": "table",
+ "type": "table"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 0,
+ "y": 7
+ },
+ "id": 7,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": true,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": true,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeGroup(time, '5m'), avg(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY 1 ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "timeGroup macro 5m without fill",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 8,
+ "y": 7
+ },
+ "id": 9,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null as zero",
+ "percentage": false,
+ "pointradius": 3,
+ "points": true,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": true,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeGroup(time, '5m', NULL), avg(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY 1 ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "timeGroup macro 5m with fill(NULL) and null as zero",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 16,
+ "y": 7
+ },
+ "id": 10,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": true,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": true,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeGroup(time, '5m', 10.0), avg(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY 1 ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "timeGroup macro 5m with fill(10.0)",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 0,
+ "y": 16
+ },
+ "id": 16,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": false,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": true,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeGroup(time, '$summarize'), avg(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY 1 ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Metrics - timeGroup macro $summarize without fill",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 8,
+ "y": 16
+ },
+ "id": 12,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": false,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null as zero",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": true,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeGroup(time, '$summarize', NULL), sum(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY 1 ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Metrics - timeGroup macro $summarize with fill(NULL)",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 9,
+ "w": 8,
+ "x": 16,
+ "y": 16
+ },
+ "id": 13,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": false,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": true,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeGroup(time, '$summarize', 100.0), sum(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY 1 ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Metrics - timeGroup macro $summarize with fill(100.0)",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 25
+ },
+ "id": 27,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "hideEmpty": false,
+ "hideZero": false,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT \n $__timeGroup(time, '$summarize'), \n measurement || ' - value one' as metric, \n avg(\"valueOne\") as \"valueOne\"\nFROM\n metric_values \nWHERE\n $__timeFilter(time) AND\n measurement in($metric)\nGROUP BY 1, 2\nORDER BY 1",
+ "refId": "A"
+ },
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT \n $__timeGroup(time, '$summarize'), \n measurement || ' - value two' as metric, \n avg(\"valueTwo\") as \"valueTwo\"\nFROM\n metric_values \nWHERE\n $__timeFilter(time) AND\n measurement in($metric)\nGROUP BY 1, 2\nORDER BY 1",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series with metric column using timeGroup macro ($summarize)",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 25
+ },
+ "id": 5,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT \n $__timeGroup(time, '$summarize'), \n avg(\"valueOne\") as \"valueOne\", \n avg(\"valueTwo\") as \"valueTwo\" \nFROM\n metric_values \nWHERE\n $__timeFilter(time) AND\n measurement in($metric)\nGROUP BY 1\nORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series without metric column using timeGroup macro ($summarize)",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 33
+ },
+ "id": 4,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "hideEmpty": false,
+ "hideZero": false,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), measurement || ' - value one' as metric, \"valueOne\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "A"
+ },
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), measurement || ' - value two' as metric, \"valueTwo\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series with metric column",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 33
+ },
+ "id": 28,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), \"valueOne\", \"valueTwo\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series without metric column",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 41
+ },
+ "id": 19,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "hideEmpty": false,
+ "hideZero": false,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), measurement || ' - value one' as metric, \"valueOne\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "A"
+ },
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), measurement || ' - value two' as metric, \"valueTwo\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series with metric column - stacked",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 41
+ },
+ "id": 18,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), \"valueOne\", \"valueTwo\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series without metric column - stacked",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 49
+ },
+ "id": 17,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "hideEmpty": false,
+ "hideZero": false,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": true,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), measurement || ' - value one' as metric, \"valueOne\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "A"
+ },
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), measurement || ' - value two' as metric, \"valueTwo\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series with metric column - stacked percent",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 2,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 49
+ },
+ "id": 20,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": true,
+ "pointradius": 3,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), \"valueOne\", \"valueTwo\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series without metric column - stacked percent",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 1,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 57
+ },
+ "id": 14,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": false,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), measurement || ' - value one' as metric, \"valueOne\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "A"
+ },
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), measurement || ' - value two' as metric, \"valueTwo\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series with metric column - series mode",
+ "tooltip": {
+ "shared": false,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "series",
+ "name": null,
+ "show": true,
+ "values": [
+ "total"
+ ]
+ },
+ "yaxes": [
+ {
+ "decimals": null,
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 1,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 57
+ },
+ "id": 15,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": true,
+ "total": true,
+ "values": true
+ },
+ "lines": false,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), \"valueOne\", \"valueTwo\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series without metric column - series mode",
+ "tooltip": {
+ "shared": false,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "series",
+ "name": null,
+ "show": true,
+ "values": [
+ "total"
+ ]
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 1,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 65
+ },
+ "id": 25,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": false,
+ "total": true,
+ "values": true
+ },
+ "lines": false,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), measurement || ' - value one' as metric, \"valueOne\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "A"
+ },
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), measurement || ' - value two' as metric, \"valueTwo\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series with metric column - histogram",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": 50,
+ "mode": "histogram",
+ "name": null,
+ "show": true,
+ "values": [
+ "current"
+ ]
+ },
+ "yaxes": [
+ {
+ "decimals": null,
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 1,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 65
+ },
+ "id": 22,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": false,
+ "total": true,
+ "values": true
+ },
+ "lines": false,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), \"valueOne\", \"valueTwo\" FROM metric_values\nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series without metric column - histogram",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": 100,
+ "mode": "histogram",
+ "name": null,
+ "show": true,
+ "values": [
+ "total"
+ ]
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 1,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 73
+ },
+ "id": 21,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": false,
+ "total": true,
+ "values": true
+ },
+ "lines": false,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), measurement || ' - value one' as metric, \"valueOne\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "A"
+ },
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), measurement || ' - value two' as metric, \"valueTwo\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series with metric column - histogram stacked",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": 20,
+ "mode": "histogram",
+ "name": null,
+ "show": true,
+ "values": [
+ "current"
+ ]
+ },
+ "yaxes": [
+ {
+ "decimals": null,
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 1,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 73
+ },
+ "id": 26,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": false,
+ "total": true,
+ "values": true
+ },
+ "lines": false,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), \"valueOne\", \"valueTwo\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series without metric column - histogram stacked",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": 20,
+ "mode": "histogram",
+ "name": null,
+ "show": true,
+ "values": [
+ "total"
+ ]
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 1,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 81
+ },
+ "id": 23,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": false,
+ "total": true,
+ "values": true
+ },
+ "lines": false,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": true,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), measurement || ' - value one' as metric, \"valueOne\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "A"
+ },
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), measurement || ' - value two' as metric, \"valueTwo\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series with metric column - histogram stacked percent",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": 20,
+ "mode": "histogram",
+ "name": null,
+ "show": true,
+ "values": [
+ "current"
+ ]
+ },
+ "yaxes": [
+ {
+ "decimals": null,
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ },
+ {
+ "aliasColors": {},
+ "bars": true,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "${DS_POSTGRES_TEST}",
+ "fill": 1,
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 81
+ },
+ "id": 24,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": true,
+ "min": true,
+ "rightSide": true,
+ "show": false,
+ "total": true,
+ "values": true
+ },
+ "lines": false,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": true,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "",
+ "format": "time_series",
+ "rawSql": "SELECT $__timeEpoch(time), \"valueOne\", \"valueTwo\" FROM metric_values \nWHERE $__timeFilter(time) AND measurement in($metric) ORDER BY 1",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Multiple series without metric column - histogram stacked percent",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": 20,
+ "mode": "histogram",
+ "name": null,
+ "show": true,
+ "values": [
+ "total"
+ ]
+ },
+ "yaxes": [
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": "0",
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ }
+ ],
+ "refresh": false,
+ "schemaVersion": 16,
+ "style": "dark",
+ "tags": [],
+ "templating": {
+ "list": [
+ {
+ "allValue": null,
+ "current": {},
+ "datasource": "${DS_POSTGRES_TEST}",
+ "hide": 0,
+ "includeAll": true,
+ "label": "Metric",
+ "multi": true,
+ "name": "metric",
+ "options": [],
+ "query": "SELECT DISTINCT measurement FROM metric_values",
+ "refresh": 1,
+ "regex": "",
+ "sort": 1,
+ "tagValuesQuery": "",
+ "tags": [],
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ },
+ {
+ "auto": false,
+ "auto_count": 30,
+ "auto_min": "10s",
+ "current": {
+ "text": "10m",
+ "value": "10m"
+ },
+ "hide": 0,
+ "label": "Interval",
+ "name": "summarize",
+ "options": [
+ {
+ "selected": false,
+ "text": "1s",
+ "value": "1s"
+ },
+ {
+ "selected": false,
+ "text": "10s",
+ "value": "10s"
+ },
+ {
+ "selected": false,
+ "text": "30s",
+ "value": "30s"
+ },
+ {
+ "selected": false,
+ "text": "1m",
+ "value": "1m"
+ },
+ {
+ "selected": false,
+ "text": "5m",
+ "value": "5m"
+ },
+ {
+ "selected": true,
+ "text": "10m",
+ "value": "10m"
+ }
+ ],
+ "query": "1s,10s,30s,1m,5m,10m",
+ "refresh": 2,
+ "type": "interval"
+ }
+ ]
+ },
+ "time": {
+ "from": "2018-03-15T12:30:00.000Z",
+ "to": "2018-03-15T13:55:01.000Z"
+ },
+ "timepicker": {
+ "refresh_intervals": [
+ "5s",
+ "10s",
+ "30s",
+ "1m",
+ "5m",
+ "15m",
+ "30m",
+ "1h",
+ "2h",
+ "1d"
+ ],
+ "time_options": [
+ "5m",
+ "15m",
+ "1h",
+ "6h",
+ "12h",
+ "24h",
+ "2d",
+ "7d",
+ "30d"
+ ]
+ },
+ "timezone": "",
+ "title": "Postgres Data Source Test",
+ "uid": "vHQdlVziz",
+ "version": 14
+}
\ No newline at end of file
diff --git a/docs/sources/administration/provisioning.md b/docs/sources/administration/provisioning.md
index 135973df52a..b43ea68bcd8 100644
--- a/docs/sources/administration/provisioning.md
+++ b/docs/sources/administration/provisioning.md
@@ -11,11 +11,13 @@ weight = 8
# Provisioning Grafana
-## Config file
+In previous versions of Grafana, you could only use the API for provisioning data sources and dashboards. But that required the service to be running before you started creating dashboards and you also needed to set up credentials for the HTTP API. In v5.0 we decided to improve this experience by adding a new active provisioning system that uses config files. This will make GitOps more natural as data sources and dashboards can be defined via files that can be version controlled. We hope to extend this system to later add support for users, orgs and alerts as well.
+
+## Config File
Checkout the [configuration](/installation/configuration) page for more information on what you can configure in `grafana.ini`
-### Config file locations
+### Config File Locations
- Default configuration from `$WORKING_DIR/conf/defaults.ini`
- Custom configuration from `$WORKING_DIR/conf/custom.ini`
@@ -26,7 +28,7 @@ Checkout the [configuration](/installation/configuration) page for more informat
> `/etc/grafana/grafana.ini`. This path is specified in the Grafana
> init.d script using `--config` file parameter.
-### Using environment variables
+### Using Environment Variables
All options in the configuration file (listed below) can be overridden
using environment variables using the syntax:
@@ -59,7 +61,7 @@ export GF_AUTH_GOOGLE_CLIENT_SECRET=newS3cretKey
-## Configuration management tools
+## Configuration Management Tools
Currently we do not provide any scripts/manifests for configuring Grafana. Rather than spending time learning and creating scripts/manifests for each tool, we think our time is better spent making Grafana easier to provision. Therefore, we heavily relay on the expertise of the community.
@@ -76,10 +78,12 @@ Saltstack | [https://github.com/salt-formulas/salt-formula-grafana](https://gith
It's possible to manage datasources in Grafana by adding one or more yaml config files in the [`provisioning/datasources`](/installation/configuration/#provisioning) directory. Each config file can contain a list of `datasources` that will be added or updated during start up. If the datasource already exists, Grafana will update it to match the configuration file. The config file can also contain a list of datasources that should be deleted. That list is called `delete_datasources`. Grafana will delete datasources listed in `delete_datasources` before inserting/updating those in the `datasource` list.
-### Running multiple Grafana instances.
+### Running Multiple Grafana Instances
+
If you are running multiple instances of Grafana you might run into problems if they have different versions of the `datasource.yaml` configuration file. The best way to solve this problem is to add a version number to each datasource in the configuration and increase it when you update the config. Grafana will only update datasources with the same or lower version number than specified in the config. That way, old configs cannot overwrite newer configs if they restart at the same time.
-### Example datasource config file
+### Example Datasource Config File
+
```yaml
# config file version
apiVersion: 1
@@ -133,14 +137,20 @@ datasources:
editable: false
```
-#### Json data
+#### Custom Settings per Datasource
+
+| Datasource | Misc |
+| ---- | ---- |
+| Elasticsearch | Elasticsearch uses the `database` property to configure the index for a datasource |
+
+#### Json Data
Since not all datasources have the same configuration settings we only have the most common ones as fields. The rest should be stored as a json blob in the `json_data` field. Here are the most common settings that the core datasources use.
-| Name | Type | Datasource |Description |
-| ----| ---- | ---- | --- |
+| Name | Type | Datasource | Description |
+| ---- | ---- | ---- | ---- |
| tlsAuth | boolean | *All* | Enable TLS authentication using client cert configured in secure json data |
-| tlsAuthWithCACert | boolean | *All* | Enable TLS authtication using CA cert |
+| tlsAuthWithCACert | boolean | *All* | Enable TLS authentication using CA cert |
| tlsSkipVerify | boolean | *All* | Controls whether a client verifies the server's certificate chain and host name. |
| graphiteVersion | string | Graphite | Graphite version |
| timeInterval | string | Elastic, Influxdb & Prometheus | Lowest interval/step value that should be used for this data source |
@@ -155,8 +165,7 @@ Since not all datasources have the same configuration settings we only have the
| tsdbResolution | string | OpenTsdb | Resolution |
| sslmode | string | Postgre | SSLmode. 'disable', 'require', 'verify-ca' or 'verify-full' |
-
-#### Secure Json data
+#### Secure Json Data
`{"authType":"keys","defaultRegion":"us-west-2","timeField":"@timestamp"}`
@@ -194,7 +203,7 @@ providers:
When Grafana starts, it will update/insert all dashboards available in the configured path. Then later on poll that path and look for updated json files and insert those update/insert those into the database.
-### Reuseable dashboard urls
+### Reuseable Dashboard Urls
If the dashboard in the json file contains an [uid](/reference/dashboard/#json-fields), Grafana will force insert/update on that uid. This allows you to migrate dashboards betweens Grafana instances and provisioning Grafana from configuration without breaking the urls given since the new dashboard url uses the uid as identifer.
When Grafana starts, it will update/insert all dashboards available in the configured folders. If you modify the file, the dashboard will also be updated.
diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md
index 66072a98f84..6169280b798 100644
--- a/docs/sources/installation/configuration.md
+++ b/docs/sources/installation/configuration.md
@@ -234,7 +234,12 @@ The maximum number of connections in the idle connection pool.
### max_open_conn
The maximum number of open connections to the database.
+### conn_max_lifetime
+
+Sets the maximum amount of time a connection may be reused. The default is 14400 (which means 14400 seconds or 4 hours). For MySQL, this setting should be shorter than the [`wait_timeout`](https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_wait_timeout) variable.
+
### log_queries
+
Set to `true` to log the sql calls and execution times.
diff --git a/docs/sources/reference/templating.md b/docs/sources/reference/templating.md
index 3a15b4ed7d1..f9e16e26610 100644
--- a/docs/sources/reference/templating.md
+++ b/docs/sources/reference/templating.md
@@ -1,6 +1,6 @@
+++
title = "Variables"
-keywords = ["grafana", "templating", "documentation", "guide"]
+keywords = ["grafana", "templating", "documentation", "guide", "template", "variable"]
type = "docs"
[menu.docs]
name = "Variables"
@@ -80,6 +80,73 @@ Option | Description
*Regex* | Regex to filter or capture specific parts of the names return by your data source query. Optional.
*Sort* | Define sort order for options in dropdown. **Disabled** means that the order of options returned by your data source query will be used.
+#### Using regex to filter/modify values in the Variable dropdown
+
+Using the Regex Query Option, you filter the list of options returned by the Variable query or modify the options returned.
+
+Examples of filtering on the following list of options:
+
+```text
+backend_01
+backend_02
+backend_03
+backend_04
+```
+
+##### Filter so that only the options that end with `01` or `02` are returned:
+
+Regex:
+
+```regex
+/.*[01|02]/
+```
+
+Result:
+
+```text
+backend_01
+backend_02
+```
+
+##### Filter and modify the options using a regex capture group to return part of the text:
+
+Regex:
+
+```regex
+/.*(01|02)/
+```
+
+Result:
+
+```text
+01
+02
+```
+
+#### Filter and modify - Prometheus Example
+
+List of options:
+
+```text
+up{instance="demo.robustperception.io:9090",job="prometheus"} 1 1521630638000
+up{instance="demo.robustperception.io:9093",job="alertmanager"} 1 1521630638000
+up{instance="demo.robustperception.io:9100",job="node"} 1 1521630638000
+```
+
+Regex:
+
+```regex
+/.*instance="([^"]*).*/
+```
+
+Result:
+
+```text
+demo.robustperception.io:9090
+demo.robustperception.io:9093
+demo.robustperception.io:9100
+```
+
### Query expressions
The query expressions are different for each data source.
diff --git a/emails/templates/layouts/default.html b/emails/templates/layouts/default.html
index 07eb32874c7..ca54acdd206 100644
--- a/emails/templates/layouts/default.html
+++ b/emails/templates/layouts/default.html
@@ -143,7 +143,7 @@ td[class="stack-column-center"] {
Sent by Grafana v[[.BuildVersion]]
- © 2016 Grafana and raintank
+ © 2018 Grafana Labs
diff --git a/package.json b/package.json
index 3d24245b6d7..6dcfc16b82b 100644
--- a/package.json
+++ b/package.json
@@ -118,8 +118,8 @@
"prettier --write",
"git add"
],
- "*.go": [
- "gofmt -w -s pkg",
+ "*pkg/**/*.go": [
+ "gofmt -w -s",
"git add"
]
},
diff --git a/pkg/api/admin.go b/pkg/api/admin.go
index 286f23356ea..52d271ce69b 100644
--- a/pkg/api/admin.go
+++ b/pkg/api/admin.go
@@ -1,6 +1,7 @@
package api
import (
+ "regexp"
"strings"
"github.com/grafana/grafana/pkg/bus"
@@ -21,6 +22,14 @@ func AdminGetSettings(c *m.ReqContext) {
if strings.Contains(keyName, "secret") || strings.Contains(keyName, "password") || (strings.Contains(keyName, "provider_config")) {
value = "************"
}
+ if strings.Contains(keyName, "url") {
+ var rgx = regexp.MustCompile(`.*:\/\/([^:]*):([^@]*)@.*?$`)
+ var subs = rgx.FindAllSubmatch([]byte(value), -1)
+ if subs != nil && len(subs[0]) == 3 {
+ value = strings.Replace(value, string(subs[0][1]), "******", 1)
+ value = strings.Replace(value, string(subs[0][2]), "******", 1)
+ }
+ }
jsonSec[keyName] = value
}
diff --git a/pkg/api/admin_users.go b/pkg/api/admin_users.go
index 4cf7f4db4ec..dc3d390dda9 100644
--- a/pkg/api/admin_users.go
+++ b/pkg/api/admin_users.go
@@ -47,14 +47,14 @@ func AdminCreateUser(c *m.ReqContext, form dtos.AdminCreateUserForm) {
}
func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordForm) {
- userId := c.ParamsInt64(":id")
+ userID := c.ParamsInt64(":id")
if len(form.Password) < 4 {
c.JsonApiErr(400, "New password too short", nil)
return
}
- userQuery := m.GetUserByIdQuery{Id: userId}
+ userQuery := m.GetUserByIdQuery{Id: userID}
if err := bus.Dispatch(&userQuery); err != nil {
c.JsonApiErr(500, "Could not read user from database", err)
@@ -64,7 +64,7 @@ func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordF
passwordHashed := util.EncodePassword(form.Password, userQuery.Result.Salt)
cmd := m.ChangeUserPasswordCommand{
- UserId: userId,
+ UserId: userID,
NewPassword: passwordHashed,
}
@@ -77,10 +77,10 @@ func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordF
}
func AdminUpdateUserPermissions(c *m.ReqContext, form dtos.AdminUpdateUserPermissionsForm) {
- userId := c.ParamsInt64(":id")
+ userID := c.ParamsInt64(":id")
cmd := m.UpdateUserPermissionsCommand{
- UserId: userId,
+ UserId: userID,
IsGrafanaAdmin: form.IsGrafanaAdmin,
}
@@ -93,9 +93,9 @@ func AdminUpdateUserPermissions(c *m.ReqContext, form dtos.AdminUpdateUserPermis
}
func AdminDeleteUser(c *m.ReqContext) {
- userId := c.ParamsInt64(":id")
+ userID := c.ParamsInt64(":id")
- cmd := m.DeleteUserCommand{UserId: userId}
+ cmd := m.DeleteUserCommand{UserId: userID}
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "Failed to delete user", err)
diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go
index eea4ef90c05..a9a3773ceb1 100644
--- a/pkg/api/alerting.go
+++ b/pkg/api/alerting.go
@@ -26,10 +26,10 @@ func ValidateOrgAlert(c *m.ReqContext) {
}
func GetAlertStatesForDashboard(c *m.ReqContext) Response {
- dashboardId := c.QueryInt64("dashboardId")
+ dashboardID := c.QueryInt64("dashboardId")
- if dashboardId == 0 {
- return ApiError(400, "Missing query parameter dashboardId", nil)
+ if dashboardID == 0 {
+ return Error(400, "Missing query parameter dashboardId", nil)
}
query := m.GetAlertStatesForDashboardQuery{
@@ -38,10 +38,10 @@ func GetAlertStatesForDashboard(c *m.ReqContext) Response {
}
if err := bus.Dispatch(&query); err != nil {
- return ApiError(500, "Failed to fetch alert states", err)
+ return Error(500, "Failed to fetch alert states", err)
}
- return Json(200, query.Result)
+ return JSON(200, query.Result)
}
// GET /api/alerts
@@ -60,20 +60,20 @@ func GetAlerts(c *m.ReqContext) Response {
}
if err := bus.Dispatch(&query); err != nil {
- return ApiError(500, "List alerts failed", err)
+ return Error(500, "List alerts failed", err)
}
for _, alert := range query.Result {
alert.Url = m.GetDashboardUrl(alert.DashboardUid, alert.DashboardSlug)
}
- return Json(200, query.Result)
+ return JSON(200, query.Result)
}
// POST /api/alerts/test
func AlertTest(c *m.ReqContext, dto dtos.AlertTestCommand) Response {
if _, idErr := dto.Dashboard.Get("id").Int64(); idErr != nil {
- return ApiError(400, "The dashboard needs to be saved at least once before you can test an alert rule", nil)
+ return Error(400, "The dashboard needs to be saved at least once before you can test an alert rule", nil)
}
backendCmd := alerting.AlertTestCommand{
@@ -84,9 +84,9 @@ func AlertTest(c *m.ReqContext, dto dtos.AlertTestCommand) Response {
if err := bus.Dispatch(&backendCmd); err != nil {
if validationErr, ok := err.(alerting.ValidationError); ok {
- return ApiError(422, validationErr.Error(), nil)
+ return Error(422, validationErr.Error(), nil)
}
- return ApiError(500, "Failed to test rule", err)
+ return Error(500, "Failed to test rule", err)
}
res := backendCmd.Result
@@ -109,7 +109,7 @@ func AlertTest(c *m.ReqContext, dto dtos.AlertTestCommand) Response {
dtoRes.TimeMs = fmt.Sprintf("%1.3fms", res.GetDurationMs())
- return Json(200, dtoRes)
+ return JSON(200, dtoRes)
}
// GET /api/alerts/:id
@@ -118,21 +118,21 @@ func GetAlert(c *m.ReqContext) Response {
query := m.GetAlertByIdQuery{Id: id}
if err := bus.Dispatch(&query); err != nil {
- return ApiError(500, "List alerts failed", err)
+ return Error(500, "List alerts failed", err)
}
- return Json(200, &query.Result)
+ return JSON(200, &query.Result)
}
func GetAlertNotifiers(c *m.ReqContext) Response {
- return Json(200, alerting.GetNotifiers())
+ return JSON(200, alerting.GetNotifiers())
}
func GetAlertNotifications(c *m.ReqContext) Response {
query := &m.GetAllAlertNotificationsQuery{OrgId: c.OrgId}
if err := bus.Dispatch(query); err != nil {
- return ApiError(500, "Failed to get alert notifications", err)
+ return Error(500, "Failed to get alert notifications", err)
}
result := make([]*dtos.AlertNotification, 0)
@@ -148,40 +148,40 @@ func GetAlertNotifications(c *m.ReqContext) Response {
})
}
- return Json(200, result)
+ return JSON(200, result)
}
-func GetAlertNotificationById(c *m.ReqContext) Response {
+func GetAlertNotificationByID(c *m.ReqContext) Response {
query := &m.GetAlertNotificationsQuery{
OrgId: c.OrgId,
Id: c.ParamsInt64("notificationId"),
}
if err := bus.Dispatch(query); err != nil {
- return ApiError(500, "Failed to get alert notifications", err)
+ return Error(500, "Failed to get alert notifications", err)
}
- return Json(200, query.Result)
+ return JSON(200, query.Result)
}
func CreateAlertNotification(c *m.ReqContext, cmd m.CreateAlertNotificationCommand) Response {
cmd.OrgId = c.OrgId
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to create alert notification", err)
+ return Error(500, "Failed to create alert notification", err)
}
- return Json(200, cmd.Result)
+ return JSON(200, cmd.Result)
}
func UpdateAlertNotification(c *m.ReqContext, cmd m.UpdateAlertNotificationCommand) Response {
cmd.OrgId = c.OrgId
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to update alert notification", err)
+ return Error(500, "Failed to update alert notification", err)
}
- return Json(200, cmd.Result)
+ return JSON(200, cmd.Result)
}
func DeleteAlertNotification(c *m.ReqContext) Response {
@@ -191,10 +191,10 @@ func DeleteAlertNotification(c *m.ReqContext) Response {
}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to delete alert notification", err)
+ return Error(500, "Failed to delete alert notification", err)
}
- return ApiSuccess("Notification deleted")
+ return Success("Notification deleted")
}
//POST /api/alert-notifications/test
@@ -207,41 +207,41 @@ func NotificationTest(c *m.ReqContext, dto dtos.NotificationTestCommand) Respons
if err := bus.Dispatch(cmd); err != nil {
if err == m.ErrSmtpNotEnabled {
- return ApiError(412, err.Error(), err)
+ return Error(412, err.Error(), err)
}
- return ApiError(500, "Failed to send alert notifications", err)
+ return Error(500, "Failed to send alert notifications", err)
}
- return ApiSuccess("Test notification sent")
+ return Success("Test notification sent")
}
//POST /api/alerts/:alertId/pause
func PauseAlert(c *m.ReqContext, dto dtos.PauseAlertCommand) Response {
- alertId := c.ParamsInt64("alertId")
+ alertID := c.ParamsInt64("alertId")
- query := m.GetAlertByIdQuery{Id: alertId}
+ query := m.GetAlertByIdQuery{Id: alertID}
if err := bus.Dispatch(&query); err != nil {
- return ApiError(500, "Get Alert failed", err)
+ return Error(500, "Get Alert failed", err)
}
guardian := guardian.New(query.Result.DashboardId, c.OrgId, c.SignedInUser)
if canEdit, err := guardian.CanEdit(); err != nil || !canEdit {
if err != nil {
- return ApiError(500, "Error while checking permissions for Alert", err)
+ return Error(500, "Error while checking permissions for Alert", err)
}
- return ApiError(403, "Access denied to this dashboard and alert", nil)
+ return Error(403, "Access denied to this dashboard and alert", nil)
}
cmd := m.PauseAlertCommand{
OrgId: c.OrgId,
- AlertIds: []int64{alertId},
+ AlertIds: []int64{alertID},
Paused: dto.Paused,
}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "", err)
+ return Error(500, "", err)
}
var response m.AlertStateType = m.AlertStatePending
@@ -252,12 +252,12 @@ func PauseAlert(c *m.ReqContext, dto dtos.PauseAlertCommand) Response {
}
result := map[string]interface{}{
- "alertId": alertId,
+ "alertId": alertID,
"state": response,
"message": "Alert " + pausedState,
}
- return Json(200, result)
+ return JSON(200, result)
}
//POST /api/admin/pause-all-alerts
@@ -267,7 +267,7 @@ func PauseAllAlerts(c *m.ReqContext, dto dtos.PauseAllAlertsCommand) Response {
}
if err := bus.Dispatch(&updateCmd); err != nil {
- return ApiError(500, "Failed to pause alerts", err)
+ return Error(500, "Failed to pause alerts", err)
}
var response m.AlertStateType = m.AlertStatePending
@@ -283,5 +283,5 @@ func PauseAllAlerts(c *m.ReqContext, dto dtos.PauseAllAlertsCommand) Response {
"alertsAffected": updateCmd.ResultCount,
}
- return Json(200, result)
+ return JSON(200, result)
}
diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go
index fb75e0bf129..886913d6324 100644
--- a/pkg/api/annotations.go
+++ b/pkg/api/annotations.go
@@ -30,7 +30,7 @@ func GetAnnotations(c *m.ReqContext) Response {
items, err := repo.Find(query)
if err != nil {
- return ApiError(500, "Failed to get annotations", err)
+ return Error(500, "Failed to get annotations", err)
}
for _, item := range items {
@@ -40,7 +40,7 @@ func GetAnnotations(c *m.ReqContext) Response {
item.Time = item.Time * 1000
}
- return Json(200, items)
+ return JSON(200, items)
}
type CreateAnnotationError struct {
@@ -52,7 +52,7 @@ func (e *CreateAnnotationError) Error() string {
}
func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
- if canSave, err := canSaveByDashboardId(c, cmd.DashboardId); err != nil || !canSave {
+ if canSave, err := canSaveByDashboardID(c, cmd.DashboardId); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
@@ -60,7 +60,7 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
if cmd.Text == "" {
err := &CreateAnnotationError{"text field should not be empty"}
- return ApiError(500, "Failed to save annotation", err)
+ return Error(500, "Failed to save annotation", err)
}
item := annotations.Item{
@@ -79,7 +79,7 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
}
if err := repo.Save(&item); err != nil {
- return ApiError(500, "Failed to save annotation", err)
+ return Error(500, "Failed to save annotation", err)
}
startID := item.Id
@@ -93,24 +93,24 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
}
if err := repo.Update(&item); err != nil {
- return ApiError(500, "Failed set regionId on annotation", err)
+ return Error(500, "Failed set regionId on annotation", err)
}
item.Id = 0
item.Epoch = cmd.TimeEnd / 1000
if err := repo.Save(&item); err != nil {
- return ApiError(500, "Failed save annotation for region end time", err)
+ return Error(500, "Failed save annotation for region end time", err)
}
- return Json(200, util.DynMap{
+ return JSON(200, util.DynMap{
"message": "Annotation added",
"id": startID,
"endId": item.Id,
})
}
- return Json(200, util.DynMap{
+ return JSON(200, util.DynMap{
"message": "Annotation added",
"id": startID,
})
@@ -129,7 +129,7 @@ func PostGraphiteAnnotation(c *m.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd
if cmd.What == "" {
err := &CreateAnnotationError{"what field should not be empty"}
- return ApiError(500, "Failed to save Graphite annotation", err)
+ return Error(500, "Failed to save Graphite annotation", err)
}
if cmd.When == 0 {
@@ -152,12 +152,12 @@ func PostGraphiteAnnotation(c *m.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd
tagsArray = append(tagsArray, tagStr)
} else {
err := &CreateAnnotationError{"tag should be a string"}
- return ApiError(500, "Failed to save Graphite annotation", err)
+ return Error(500, "Failed to save Graphite annotation", err)
}
}
default:
err := &CreateAnnotationError{"unsupported tags format"}
- return ApiError(500, "Failed to save Graphite annotation", err)
+ return Error(500, "Failed to save Graphite annotation", err)
}
item := annotations.Item{
@@ -169,35 +169,35 @@ func PostGraphiteAnnotation(c *m.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd
}
if err := repo.Save(&item); err != nil {
- return ApiError(500, "Failed to save Graphite annotation", err)
+ return Error(500, "Failed to save Graphite annotation", err)
}
- return Json(200, util.DynMap{
+ return JSON(200, util.DynMap{
"message": "Graphite annotation added",
"id": item.Id,
})
}
func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response {
- annotationId := c.ParamsInt64(":annotationId")
+ annotationID := c.ParamsInt64(":annotationId")
repo := annotations.GetRepository()
- if resp := canSave(c, repo, annotationId); resp != nil {
+ if resp := canSave(c, repo, annotationID); resp != nil {
return resp
}
item := annotations.Item{
OrgId: c.OrgId,
UserId: c.UserId,
- Id: annotationId,
+ Id: annotationID,
Epoch: cmd.Time / 1000,
Text: cmd.Text,
Tags: cmd.Tags,
}
if err := repo.Update(&item); err != nil {
- return ApiError(500, "Failed to update annotation", err)
+ return Error(500, "Failed to update annotation", err)
}
if cmd.IsRegion {
@@ -210,11 +210,11 @@ func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response {
itemRight.Id = 0
if err := repo.Update(&itemRight); err != nil {
- return ApiError(500, "Failed to update annotation for region end time", err)
+ return Error(500, "Failed to update annotation for region end time", err)
}
}
- return ApiSuccess("Annotation updated")
+ return Success("Annotation updated")
}
func DeleteAnnotations(c *m.ReqContext, cmd dtos.DeleteAnnotationsCmd) Response {
@@ -227,57 +227,57 @@ func DeleteAnnotations(c *m.ReqContext, cmd dtos.DeleteAnnotationsCmd) Response
})
if err != nil {
- return ApiError(500, "Failed to delete annotations", err)
+ return Error(500, "Failed to delete annotations", err)
}
- return ApiSuccess("Annotations deleted")
+ return Success("Annotations deleted")
}
-func DeleteAnnotationById(c *m.ReqContext) Response {
+func DeleteAnnotationByID(c *m.ReqContext) Response {
repo := annotations.GetRepository()
- annotationId := c.ParamsInt64(":annotationId")
+ annotationID := c.ParamsInt64(":annotationId")
- if resp := canSave(c, repo, annotationId); resp != nil {
+ if resp := canSave(c, repo, annotationID); resp != nil {
return resp
}
err := repo.Delete(&annotations.DeleteParams{
- Id: annotationId,
+ Id: annotationID,
})
if err != nil {
- return ApiError(500, "Failed to delete annotation", err)
+ return Error(500, "Failed to delete annotation", err)
}
- return ApiSuccess("Annotation deleted")
+ return Success("Annotation deleted")
}
func DeleteAnnotationRegion(c *m.ReqContext) Response {
repo := annotations.GetRepository()
- regionId := c.ParamsInt64(":regionId")
+ regionID := c.ParamsInt64(":regionId")
- if resp := canSave(c, repo, regionId); resp != nil {
+ if resp := canSave(c, repo, regionID); resp != nil {
return resp
}
err := repo.Delete(&annotations.DeleteParams{
- RegionId: regionId,
+ RegionId: regionID,
})
if err != nil {
- return ApiError(500, "Failed to delete annotation region", err)
+ return Error(500, "Failed to delete annotation region", err)
}
- return ApiSuccess("Annotation region deleted")
+ return Success("Annotation region deleted")
}
-func canSaveByDashboardId(c *m.ReqContext, dashboardId int64) (bool, error) {
- if dashboardId == 0 && !c.SignedInUser.HasRole(m.ROLE_EDITOR) {
+func canSaveByDashboardID(c *m.ReqContext, dashboardID int64) (bool, error) {
+ if dashboardID == 0 && !c.SignedInUser.HasRole(m.ROLE_EDITOR) {
return false, nil
}
- if dashboardId > 0 {
- guardian := guardian.New(dashboardId, c.OrgId, c.SignedInUser)
+ if dashboardID > 0 {
+ guardian := guardian.New(dashboardID, c.OrgId, c.SignedInUser)
if canEdit, err := guardian.CanEdit(); err != nil || !canEdit {
return false, err
}
@@ -286,32 +286,32 @@ func canSaveByDashboardId(c *m.ReqContext, dashboardId int64) (bool, error) {
return true, nil
}
-func canSave(c *m.ReqContext, repo annotations.Repository, annotationId int64) Response {
- items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationId, OrgId: c.OrgId})
+func canSave(c *m.ReqContext, repo annotations.Repository, annotationID int64) Response {
+ items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationID, OrgId: c.OrgId})
if err != nil || len(items) == 0 {
- return ApiError(500, "Could not find annotation to update", err)
+ return Error(500, "Could not find annotation to update", err)
}
- dashboardId := items[0].DashboardId
+ dashboardID := items[0].DashboardId
- if canSave, err := canSaveByDashboardId(c, dashboardId); err != nil || !canSave {
+ if canSave, err := canSaveByDashboardID(c, dashboardID); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
return nil
}
-func canSaveByRegionId(c *m.ReqContext, repo annotations.Repository, regionId int64) Response {
- items, err := repo.Find(&annotations.ItemQuery{RegionId: regionId, OrgId: c.OrgId})
+func canSaveByRegionID(c *m.ReqContext, repo annotations.Repository, regionID int64) Response {
+ items, err := repo.Find(&annotations.ItemQuery{RegionId: regionID, OrgId: c.OrgId})
if err != nil || len(items) == 0 {
- return ApiError(500, "Could not find annotation to update", err)
+ return Error(500, "Could not find annotation to update", err)
}
- dashboardId := items[0].DashboardId
+ dashboardID := items[0].DashboardId
- if canSave, err := canSaveByDashboardId(c, dashboardId); err != nil || !canSave {
+ if canSave, err := canSaveByDashboardID(c, dashboardID); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
diff --git a/pkg/api/annotations_test.go b/pkg/api/annotations_test.go
index 7c298550673..9fe96245b9b 100644
--- a/pkg/api/annotations_test.go
+++ b/pkg/api/annotations_test.go
@@ -41,7 +41,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
- sc.handlerFunc = DeleteAnnotationById
+ sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 403)
})
@@ -68,7 +68,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
- sc.handlerFunc = DeleteAnnotationById
+ sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200)
})
@@ -132,7 +132,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
- sc.handlerFunc = DeleteAnnotationById
+ sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 403)
})
@@ -159,7 +159,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
- sc.handlerFunc = DeleteAnnotationById
+ sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200)
})
diff --git a/pkg/api/api.go b/pkg/api/api.go
index 5b3cde09fd5..3c7b81e472d 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -9,14 +9,14 @@ import (
)
// Register adds http routes
-func (hs *HttpServer) registerRoutes() {
+func (hs *HTTPServer) registerRoutes() {
macaronR := hs.macaron
reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})
reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})
reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)
reqOrgAdmin := middleware.RoleAuth(m.ROLE_ADMIN)
- redirectFromLegacyDashboardUrl := middleware.RedirectFromLegacyDashboardUrl()
- redirectFromLegacyDashboardSoloUrl := middleware.RedirectFromLegacyDashboardSoloUrl()
+ redirectFromLegacyDashboardURL := middleware.RedirectFromLegacyDashboardURL()
+ redirectFromLegacyDashboardSoloURL := middleware.RedirectFromLegacyDashboardSoloURL()
quota := middleware.Quota
bind := binding.Bind
@@ -67,11 +67,11 @@ func (hs *HttpServer) registerRoutes() {
r.Get("/d/:uid/:slug", reqSignedIn, Index)
r.Get("/d/:uid", reqSignedIn, Index)
- r.Get("/dashboard/db/:slug", reqSignedIn, redirectFromLegacyDashboardUrl, Index)
+ r.Get("/dashboard/db/:slug", reqSignedIn, redirectFromLegacyDashboardURL, Index)
r.Get("/dashboard/script/*", reqSignedIn, Index)
r.Get("/dashboard-solo/snapshot/*", Index)
r.Get("/d-solo/:uid/:slug", reqSignedIn, Index)
- r.Get("/dashboard-solo/db/:slug", reqSignedIn, redirectFromLegacyDashboardSoloUrl, Index)
+ r.Get("/dashboard-solo/db/:slug", reqSignedIn, redirectFromLegacyDashboardSoloURL, Index)
r.Get("/dashboard-solo/script/*", reqSignedIn, Index)
r.Get("/import/dashboard", reqSignedIn, Index)
r.Get("/dashboards/", reqSignedIn, Index)
@@ -110,7 +110,7 @@ func (hs *HttpServer) registerRoutes() {
r.Get("/api/snapshots-delete/:key", reqEditorRole, wrap(DeleteDashboardSnapshot))
// api renew session based on remember cookie
- r.Get("/api/login/ping", quota("session"), LoginApiPing)
+ r.Get("/api/login/ping", quota("session"), LoginAPIPing)
// authed api
r.Group("/api", func(apiRoute RouteRegister) {
@@ -139,7 +139,7 @@ func (hs *HttpServer) registerRoutes() {
apiRoute.Group("/users", func(usersRoute RouteRegister) {
usersRoute.Get("/", wrap(SearchUsers))
usersRoute.Get("/search", wrap(SearchUsersWithPaging))
- usersRoute.Get("/:id", wrap(GetUserById))
+ usersRoute.Get("/:id", wrap(GetUserByID))
usersRoute.Get("/:id/orgs", wrap(GetUserOrgList))
// query parameters /users/lookup?loginOrEmail=admin@example.com
usersRoute.Get("/lookup", wrap(GetUserByLoginOrEmail))
@@ -149,11 +149,11 @@ func (hs *HttpServer) registerRoutes() {
// team (admin permission required)
apiRoute.Group("/teams", func(teamsRoute RouteRegister) {
- teamsRoute.Get("/:teamId", wrap(GetTeamById))
+ teamsRoute.Get("/:teamId", wrap(GetTeamByID))
teamsRoute.Get("/search", wrap(SearchTeams))
teamsRoute.Post("/", bind(m.CreateTeamCommand{}), wrap(CreateTeam))
teamsRoute.Put("/:teamId", bind(m.UpdateTeamCommand{}), wrap(UpdateTeam))
- teamsRoute.Delete("/:teamId", wrap(DeleteTeamById))
+ teamsRoute.Delete("/:teamId", wrap(DeleteTeamByID))
teamsRoute.Get("/:teamId/members", wrap(GetTeamMembers))
teamsRoute.Post("/:teamId/members", bind(m.AddTeamMemberCommand{}), wrap(AddTeamMember))
teamsRoute.Delete("/:teamId/members/:userId", wrap(RemoveTeamMember))
@@ -192,10 +192,10 @@ func (hs *HttpServer) registerRoutes() {
// orgs (admin routes)
apiRoute.Group("/orgs/:orgId", func(orgsRoute RouteRegister) {
- orgsRoute.Get("/", wrap(GetOrgById))
+ orgsRoute.Get("/", wrap(GetOrgByID))
orgsRoute.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrg))
orgsRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddress))
- orgsRoute.Delete("/", wrap(DeleteOrgById))
+ orgsRoute.Delete("/", wrap(DeleteOrgByID))
orgsRoute.Get("/users", wrap(GetOrgUsers))
orgsRoute.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUser))
orgsRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUser))
@@ -211,9 +211,9 @@ func (hs *HttpServer) registerRoutes() {
// auth api keys
apiRoute.Group("/auth/keys", func(keysRoute RouteRegister) {
- keysRoute.Get("/", wrap(GetApiKeys))
- keysRoute.Post("/", quota("api_key"), bind(m.AddApiKeyCommand{}), wrap(AddApiKey))
- keysRoute.Delete("/:id", wrap(DeleteApiKey))
+ keysRoute.Get("/", wrap(GetAPIKeys))
+ keysRoute.Post("/", quota("api_key"), bind(m.AddApiKeyCommand{}), wrap(AddAPIKey))
+ keysRoute.Delete("/:id", wrap(DeleteAPIKey))
}, reqOrgAdmin)
// Preferences
@@ -226,16 +226,16 @@ func (hs *HttpServer) registerRoutes() {
datasourceRoute.Get("/", wrap(GetDataSources))
datasourceRoute.Post("/", quota("data_source"), bind(m.AddDataSourceCommand{}), wrap(AddDataSource))
datasourceRoute.Put("/:id", bind(m.UpdateDataSourceCommand{}), wrap(UpdateDataSource))
- datasourceRoute.Delete("/:id", wrap(DeleteDataSourceById))
+ datasourceRoute.Delete("/:id", wrap(DeleteDataSourceByID))
datasourceRoute.Delete("/name/:name", wrap(DeleteDataSourceByName))
- datasourceRoute.Get("/:id", wrap(GetDataSourceById))
+ datasourceRoute.Get("/:id", wrap(GetDataSourceByID))
datasourceRoute.Get("/name/:name", wrap(GetDataSourceByName))
}, reqOrgAdmin)
- apiRoute.Get("/datasources/id/:name", wrap(GetDataSourceIdByName), reqSignedIn)
+ apiRoute.Get("/datasources/id/:name", wrap(GetDataSourceIDByName), reqSignedIn)
apiRoute.Get("/plugins", wrap(GetPluginList))
- apiRoute.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingById))
+ apiRoute.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingByID))
apiRoute.Get("/plugins/:pluginId/markdown/:name", wrap(GetPluginMarkdown))
apiRoute.Group("/plugins", func(pluginRoute RouteRegister) {
@@ -250,11 +250,11 @@ func (hs *HttpServer) registerRoutes() {
// Folders
apiRoute.Group("/folders", func(folderRoute RouteRegister) {
folderRoute.Get("/", wrap(GetFolders))
- folderRoute.Get("/id/:id", wrap(GetFolderById))
+ folderRoute.Get("/id/:id", wrap(GetFolderByID))
folderRoute.Post("/", bind(m.CreateFolderCommand{}), wrap(CreateFolder))
folderRoute.Group("/:uid", func(folderUidRoute RouteRegister) {
- folderUidRoute.Get("/", wrap(GetFolderByUid))
+ folderUidRoute.Get("/", wrap(GetFolderByUID))
folderUidRoute.Put("/", bind(m.UpdateFolderCommand{}), wrap(UpdateFolder))
folderUidRoute.Delete("/", wrap(DeleteFolder))
@@ -268,7 +268,7 @@ func (hs *HttpServer) registerRoutes() {
// Dashboard
apiRoute.Group("/dashboards", func(dashboardRoute RouteRegister) {
dashboardRoute.Get("/uid/:uid", wrap(GetDashboard))
- dashboardRoute.Delete("/uid/:uid", wrap(DeleteDashboardByUid))
+ dashboardRoute.Delete("/uid/:uid", wrap(DeleteDashboardByUID))
dashboardRoute.Get("/db/:slug", wrap(GetDashboard))
dashboardRoute.Delete("/db/:slug", wrap(DeleteDashboard))
@@ -314,7 +314,7 @@ func (hs *HttpServer) registerRoutes() {
// metrics
apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), wrap(QueryMetrics))
apiRoute.Get("/tsdb/testdata/scenarios", wrap(GetTestDataScenarios))
- apiRoute.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, wrap(GenerateSqlTestData))
+ apiRoute.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, wrap(GenerateSQLTestData))
apiRoute.Get("/tsdb/testdata/random-walk", wrap(GetTestDataRandomWalk))
apiRoute.Group("/alerts", func(alertsRoute RouteRegister) {
@@ -332,7 +332,7 @@ func (hs *HttpServer) registerRoutes() {
alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), wrap(NotificationTest))
alertNotifications.Post("/", bind(m.CreateAlertNotificationCommand{}), wrap(CreateAlertNotification))
alertNotifications.Put("/:notificationId", bind(m.UpdateAlertNotificationCommand{}), wrap(UpdateAlertNotification))
- alertNotifications.Get("/:notificationId", wrap(GetAlertNotificationById))
+ alertNotifications.Get("/:notificationId", wrap(GetAlertNotificationByID))
alertNotifications.Delete("/:notificationId", wrap(DeleteAlertNotification))
}, reqEditorRole)
@@ -341,7 +341,7 @@ func (hs *HttpServer) registerRoutes() {
apiRoute.Group("/annotations", func(annotationsRoute RouteRegister) {
annotationsRoute.Post("/", bind(dtos.PostAnnotationsCmd{}), wrap(PostAnnotation))
- annotationsRoute.Delete("/:annotationId", wrap(DeleteAnnotationById))
+ annotationsRoute.Delete("/:annotationId", wrap(DeleteAnnotationByID))
annotationsRoute.Put("/:annotationId", bind(dtos.UpdateAnnotationsCmd{}), wrap(UpdateAnnotation))
annotationsRoute.Delete("/region/:regionId", wrap(DeleteAnnotationRegion))
annotationsRoute.Post("/graphite", reqEditorRole, bind(dtos.PostGraphiteAnnotationsCmd{}), wrap(PostGraphiteAnnotation))
diff --git a/pkg/api/apikey.go b/pkg/api/apikey.go
index 24ed69ec691..7fda738f1cd 100644
--- a/pkg/api/apikey.go
+++ b/pkg/api/apikey.go
@@ -7,11 +7,11 @@ import (
m "github.com/grafana/grafana/pkg/models"
)
-func GetApiKeys(c *m.ReqContext) Response {
+func GetAPIKeys(c *m.ReqContext) Response {
query := m.GetApiKeysQuery{OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
- return ApiError(500, "Failed to list api keys", err)
+ return Error(500, "Failed to list api keys", err)
}
result := make([]*m.ApiKeyDTO, len(query.Result))
@@ -23,25 +23,25 @@ func GetApiKeys(c *m.ReqContext) Response {
}
}
- return Json(200, result)
+ return JSON(200, result)
}
-func DeleteApiKey(c *m.ReqContext) Response {
+func DeleteAPIKey(c *m.ReqContext) Response {
id := c.ParamsInt64(":id")
cmd := &m.DeleteApiKeyCommand{Id: id, OrgId: c.OrgId}
err := bus.Dispatch(cmd)
if err != nil {
- return ApiError(500, "Failed to delete API key", err)
+ return Error(500, "Failed to delete API key", err)
}
- return ApiSuccess("API key deleted")
+ return Success("API key deleted")
}
-func AddApiKey(c *m.ReqContext, cmd m.AddApiKeyCommand) Response {
+func AddAPIKey(c *m.ReqContext, cmd m.AddApiKeyCommand) Response {
if !cmd.Role.IsValid() {
- return ApiError(400, "Invalid role specified", nil)
+ return Error(400, "Invalid role specified", nil)
}
cmd.OrgId = c.OrgId
@@ -50,12 +50,12 @@ func AddApiKey(c *m.ReqContext, cmd m.AddApiKeyCommand) Response {
cmd.Key = newKeyInfo.HashedKey
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to add API key", err)
+ return Error(500, "Failed to add API key", err)
}
result := &dtos.NewApiKeyResult{
Name: cmd.Result.Name,
Key: newKeyInfo.ClientSecret}
- return Json(200, result)
+ return JSON(200, result)
}
diff --git a/pkg/api/app_routes.go b/pkg/api/app_routes.go
index 8d74d96396b..0b7dcd32ce3 100644
--- a/pkg/api/app_routes.go
+++ b/pkg/api/app_routes.go
@@ -55,11 +55,11 @@ func InitAppPluginRoutes(r *macaron.Macaron) {
}
}
-func AppPluginRoute(route *plugins.AppPluginRoute, appId string) macaron.Handler {
+func AppPluginRoute(route *plugins.AppPluginRoute, appID string) macaron.Handler {
return func(c *m.ReqContext) {
path := c.Params("*")
- proxy := pluginproxy.NewApiPluginProxy(c, path, route, appId)
+ proxy := pluginproxy.NewApiPluginProxy(c, path, route, appID)
proxy.Transport = pluginProxyTransport
proxy.ServeHTTP(c.Resp, c.Req.Request)
}
diff --git a/pkg/api/common.go b/pkg/api/common.go
index 370f78f8b1d..97f41ff7c72 100644
--- a/pkg/api/common.go
+++ b/pkg/api/common.go
@@ -11,10 +11,10 @@ import (
var (
NotFound = func() Response {
- return ApiError(404, "Not found", nil)
+ return Error(404, "Not found", nil)
}
ServerError = func(err error) Response {
- return ApiError(500, "Server error", err)
+ return Error(500, "Server error", err)
}
)
@@ -67,22 +67,25 @@ func (r *NormalResponse) Header(key, value string) *NormalResponse {
return r
}
-// functions to create responses
+// Empty create an empty response
func Empty(status int) *NormalResponse {
return Respond(status, nil)
}
-func Json(status int, body interface{}) *NormalResponse {
+// JSON create a JSON response
+func JSON(status int, body interface{}) *NormalResponse {
return Respond(status, body).Header("Content-Type", "application/json")
}
-func ApiSuccess(message string) *NormalResponse {
+// Success create a successful response
+func Success(message string) *NormalResponse {
resp := make(map[string]interface{})
resp["message"] = message
- return Json(200, resp)
+ return JSON(200, resp)
}
-func ApiError(status int, message string, err error) *NormalResponse {
+// Error create a erroneous response
+func Error(status int, message string, err error) *NormalResponse {
data := make(map[string]interface{})
switch status {
@@ -102,7 +105,7 @@ func ApiError(status int, message string, err error) *NormalResponse {
}
}
- resp := Json(status, data)
+ resp := JSON(status, data)
if err != nil {
resp.errMessage = message
@@ -112,6 +115,7 @@ func ApiError(status int, message string, err error) *NormalResponse {
return resp
}
+// Respond create a response
func Respond(status int, body interface{}) *NormalResponse {
var b []byte
var err error
@@ -122,7 +126,7 @@ func Respond(status int, body interface{}) *NormalResponse {
b = []byte(t)
default:
if b, err = json.Marshal(body); err != nil {
- return ApiError(500, "body json marshal", err)
+ return Error(500, "body json marshal", err)
}
}
return &NormalResponse{
diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go
index e1cbd20edb3..a4a547d8bbf 100644
--- a/pkg/api/common_test.go
+++ b/pkg/api/common_test.go
@@ -99,7 +99,7 @@ func setupScenarioContext(url string) *scenarioContext {
}))
sc.m.Use(middleware.GetContextHandler())
- sc.m.Use(middleware.Sessioner(&session.Options{}))
+ sc.m.Use(middleware.Sessioner(&session.Options{}, 0))
return sc
}
diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go
index 877524ad5dd..11a028cdd29 100644
--- a/pkg/api/dashboard.go
+++ b/pkg/api/dashboard.go
@@ -22,12 +22,12 @@ import (
"github.com/grafana/grafana/pkg/util"
)
-func isDashboardStarredByUser(c *m.ReqContext, dashId int64) (bool, error) {
+func isDashboardStarredByUser(c *m.ReqContext, dashID int64) (bool, error) {
if !c.IsSignedIn {
return false, nil
}
- query := m.IsStarredByUserQuery{UserId: c.UserId, DashboardId: dashId}
+ query := m.IsStarredByUserQuery{UserId: c.UserId, DashboardId: dashID}
if err := bus.Dispatch(&query); err != nil {
return false, err
}
@@ -37,10 +37,10 @@ func isDashboardStarredByUser(c *m.ReqContext, dashId int64) (bool, error) {
func dashboardGuardianResponse(err error) Response {
if err != nil {
- return ApiError(500, "Error while checking dashboard permissions", err)
+ return Error(500, "Error while checking dashboard permissions", err)
}
- return ApiError(403, "Access denied to this dashboard", nil)
+ return Error(403, "Access denied to this dashboard", nil)
}
func GetDashboard(c *m.ReqContext) Response {
@@ -60,7 +60,7 @@ func GetDashboard(c *m.ReqContext) Response {
isStarred, err := isDashboardStarredByUser(c, dash.Id)
if err != nil {
- return ApiError(500, "Error while checking if dashboard was starred by user", err)
+ return Error(500, "Error while checking if dashboard was starred by user", err)
}
// Finding creator and last updater of the dashboard
@@ -96,7 +96,7 @@ func GetDashboard(c *m.ReqContext) Response {
if dash.FolderId > 0 {
query := m.GetDashboardQuery{Id: dash.FolderId, OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
- return ApiError(500, "Dashboard folder could not be read", err)
+ return Error(500, "Dashboard folder could not be read", err)
}
meta.FolderTitle = query.Result.Title
meta.FolderUrl = query.Result.GetUrl()
@@ -111,31 +111,29 @@ func GetDashboard(c *m.ReqContext) Response {
}
c.TimeRequest(metrics.M_Api_Dashboard_Get)
- return Json(200, dto)
+ return JSON(200, dto)
}
-func getUserLogin(userId int64) string {
- query := m.GetUserByIdQuery{Id: userId}
+func getUserLogin(userID int64) string {
+ query := m.GetUserByIdQuery{Id: userID}
err := bus.Dispatch(&query)
if err != nil {
return "Anonymous"
- } else {
- user := query.Result
- return user.Login
}
+ return query.Result.Login
}
-func getDashboardHelper(orgId int64, slug string, id int64, uid string) (*m.Dashboard, Response) {
+func getDashboardHelper(orgID int64, slug string, id int64, uid string) (*m.Dashboard, Response) {
var query m.GetDashboardQuery
if len(uid) > 0 {
- query = m.GetDashboardQuery{Uid: uid, Id: id, OrgId: orgId}
+ query = m.GetDashboardQuery{Uid: uid, Id: id, OrgId: orgID}
} else {
- query = m.GetDashboardQuery{Slug: slug, Id: id, OrgId: orgId}
+ query = m.GetDashboardQuery{Slug: slug, Id: id, OrgId: orgID}
}
if err := bus.Dispatch(&query); err != nil {
- return nil, ApiError(404, "Dashboard not found", err)
+ return nil, Error(404, "Dashboard not found", err)
}
return query.Result, nil
@@ -145,11 +143,11 @@ func DeleteDashboard(c *m.ReqContext) Response {
query := m.GetDashboardsBySlugQuery{OrgId: c.OrgId, Slug: c.Params(":slug")}
if err := bus.Dispatch(&query); err != nil {
- return ApiError(500, "Failed to retrieve dashboards by slug", err)
+ return Error(500, "Failed to retrieve dashboards by slug", err)
}
if len(query.Result) > 1 {
- return Json(412, util.DynMap{"status": "multiple-slugs-exists", "message": m.ErrDashboardsWithSameSlugExists.Error()})
+ return JSON(412, util.DynMap{"status": "multiple-slugs-exists", "message": m.ErrDashboardsWithSameSlugExists.Error()})
}
dash, rsp := getDashboardHelper(c.OrgId, c.Params(":slug"), 0, "")
@@ -164,16 +162,16 @@ func DeleteDashboard(c *m.ReqContext) Response {
cmd := m.DeleteDashboardCommand{OrgId: c.OrgId, Id: dash.Id}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to delete dashboard", err)
+ return Error(500, "Failed to delete dashboard", err)
}
- return Json(200, util.DynMap{
+ return JSON(200, util.DynMap{
"title": dash.Title,
"message": fmt.Sprintf("Dashboard %s deleted", dash.Title),
})
}
-func DeleteDashboardByUid(c *m.ReqContext) Response {
+func DeleteDashboardByUID(c *m.ReqContext) Response {
dash, rsp := getDashboardHelper(c.OrgId, "", 0, c.Params(":uid"))
if rsp != nil {
return rsp
@@ -186,10 +184,10 @@ func DeleteDashboardByUid(c *m.ReqContext) Response {
cmd := m.DeleteDashboardCommand{OrgId: c.OrgId, Id: dash.Id}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to delete dashboard", err)
+ return Error(500, "Failed to delete dashboard", err)
}
- return Json(200, util.DynMap{
+ return JSON(200, util.DynMap{
"title": dash.Title,
"message": fmt.Sprintf("Dashboard %s deleted", dash.Title),
})
@@ -204,10 +202,10 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
if dash.Id == 0 && dash.Uid == "" {
limitReached, err := quota.QuotaReached(c, "dashboard")
if err != nil {
- return ApiError(500, "failed to get quota", err)
+ return Error(500, "failed to get quota", err)
}
if limitReached {
- return ApiError(403, "Quota reached", nil)
+ return Error(403, "Quota reached", nil)
}
}
@@ -231,23 +229,23 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
err == m.ErrFolderNotFound ||
err == m.ErrDashboardFolderCannotHaveParent ||
err == m.ErrDashboardFolderNameExists {
- return ApiError(400, err.Error(), nil)
+ return Error(400, err.Error(), nil)
}
if err == m.ErrDashboardUpdateAccessDenied {
- return ApiError(403, err.Error(), err)
+ return Error(403, err.Error(), err)
}
if err == m.ErrDashboardContainsInvalidAlertData {
- return ApiError(500, "Invalid alert data. Cannot save dashboard", err)
+ return Error(500, "Invalid alert data. Cannot save dashboard", err)
}
if err != nil {
if err == m.ErrDashboardWithSameNameInFolderExists {
- return Json(412, util.DynMap{"status": "name-exists", "message": err.Error()})
+ return JSON(412, util.DynMap{"status": "name-exists", "message": err.Error()})
}
if err == m.ErrDashboardVersionMismatch {
- return Json(412, util.DynMap{"status": "version-mismatch", "message": err.Error()})
+ return JSON(412, util.DynMap{"status": "version-mismatch", "message": err.Error()})
}
if pluginErr, ok := err.(m.UpdatePluginDashboardError); ok {
message := "The dashboard belongs to plugin " + pluginErr.PluginId + "."
@@ -255,20 +253,20 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
if pluginDef, exist := plugins.Plugins[pluginErr.PluginId]; exist {
message = "The dashboard belongs to plugin " + pluginDef.Name + "."
}
- return Json(412, util.DynMap{"status": "plugin-dashboard", "message": message})
+ return JSON(412, util.DynMap{"status": "plugin-dashboard", "message": message})
}
if err == m.ErrDashboardNotFound {
- return Json(404, util.DynMap{"status": "not-found", "message": err.Error()})
+ return JSON(404, util.DynMap{"status": "not-found", "message": err.Error()})
}
- return ApiError(500, "Failed to save dashboard", err)
+ return Error(500, "Failed to save dashboard", err)
}
if err == m.ErrDashboardFailedToUpdateAlertData {
- return ApiError(500, "Invalid alert data. Cannot save dashboard", err)
+ return Error(500, "Invalid alert data. Cannot save dashboard", err)
}
c.TimeRequest(metrics.M_Api_Dashboard_Save)
- return Json(200, util.DynMap{
+ return JSON(200, util.DynMap{
"status": "success",
"slug": dashboard.Slug,
"version": dashboard.Version,
@@ -281,7 +279,7 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
func GetHomeDashboard(c *m.ReqContext) Response {
prefsQuery := m.GetPreferencesWithDefaultsQuery{OrgId: c.OrgId, UserId: c.UserId}
if err := bus.Dispatch(&prefsQuery); err != nil {
- return ApiError(500, "Failed to get preferences", err)
+ return Error(500, "Failed to get preferences", err)
}
if prefsQuery.Result.HomeDashboardId != 0 {
@@ -290,16 +288,15 @@ func GetHomeDashboard(c *m.ReqContext) Response {
if err == nil {
url := m.GetDashboardUrl(slugQuery.Result.Uid, slugQuery.Result.Slug)
dashRedirect := dtos.DashboardRedirect{RedirectUri: url}
- return Json(200, &dashRedirect)
- } else {
- log.Warn("Failed to get slug from database, %s", err.Error())
+ return JSON(200, &dashRedirect)
}
+ log.Warn("Failed to get slug from database, %s", err.Error())
}
filePath := path.Join(setting.StaticRootPath, "dashboards/home.json")
file, err := os.Open(filePath)
if err != nil {
- return ApiError(500, "Failed to load home dashboard", err)
+ return Error(500, "Failed to load home dashboard", err)
}
dash := dtos.DashboardFullWithMeta{}
@@ -309,14 +306,14 @@ func GetHomeDashboard(c *m.ReqContext) Response {
jsonParser := json.NewDecoder(file)
if err := jsonParser.Decode(&dash.Dashboard); err != nil {
- return ApiError(500, "Failed to load home dashboard", err)
+ return Error(500, "Failed to load home dashboard", err)
}
if c.HasUserRole(m.ROLE_ADMIN) && !c.HasHelpFlag(m.HelpFlagGettingStartedPanelDismissed) {
addGettingStartedPanelToHomeDashboard(dash.Dashboard)
}
- return Json(200, &dash)
+ return JSON(200, &dash)
}
func addGettingStartedPanelToHomeDashboard(dash *simplejson.Json) {
@@ -339,22 +336,22 @@ func addGettingStartedPanelToHomeDashboard(dash *simplejson.Json) {
// GetDashboardVersions returns all dashboard versions as JSON
func GetDashboardVersions(c *m.ReqContext) Response {
- dashId := c.ParamsInt64(":dashboardId")
+ dashID := c.ParamsInt64(":dashboardId")
- guardian := guardian.New(dashId, c.OrgId, c.SignedInUser)
+ guardian := guardian.New(dashID, c.OrgId, c.SignedInUser)
if canSave, err := guardian.CanSave(); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
query := m.GetDashboardVersionsQuery{
OrgId: c.OrgId,
- DashboardId: dashId,
+ DashboardId: dashID,
Limit: c.QueryInt("limit"),
Start: c.QueryInt("start"),
}
if err := bus.Dispatch(&query); err != nil {
- return ApiError(404, fmt.Sprintf("No versions found for dashboardId %d", dashId), err)
+ return Error(404, fmt.Sprintf("No versions found for dashboardId %d", dashID), err)
}
for _, version := range query.Result {
@@ -373,26 +370,26 @@ func GetDashboardVersions(c *m.ReqContext) Response {
}
}
- return Json(200, query.Result)
+ return JSON(200, query.Result)
}
// GetDashboardVersion returns the dashboard version with the given ID.
func GetDashboardVersion(c *m.ReqContext) Response {
- dashId := c.ParamsInt64(":dashboardId")
+ dashID := c.ParamsInt64(":dashboardId")
- guardian := guardian.New(dashId, c.OrgId, c.SignedInUser)
+ guardian := guardian.New(dashID, c.OrgId, c.SignedInUser)
if canSave, err := guardian.CanSave(); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
query := m.GetDashboardVersionQuery{
OrgId: c.OrgId,
- DashboardId: dashId,
+ DashboardId: dashID,
Version: c.ParamsInt(":id"),
}
if err := bus.Dispatch(&query); err != nil {
- return ApiError(500, fmt.Sprintf("Dashboard version %d not found for dashboardId %d", query.Version, dashId), err)
+ return Error(500, fmt.Sprintf("Dashboard version %d not found for dashboardId %d", query.Version, dashID), err)
}
creator := "Anonymous"
@@ -405,7 +402,7 @@ func GetDashboardVersion(c *m.ReqContext) Response {
CreatedBy: creator,
}
- return Json(200, dashVersionMeta)
+ return JSON(200, dashVersionMeta)
}
// POST /api/dashboards/calculate-diff performs diffs on two dashboards
@@ -441,9 +438,9 @@ func CalculateDashboardDiff(c *m.ReqContext, apiOptions dtos.CalculateDiffOption
result, err := dashdiffs.CalculateDiff(&options)
if err != nil {
if err == m.ErrDashboardVersionNotFound {
- return ApiError(404, "Dashboard version not found", err)
+ return Error(404, "Dashboard version not found", err)
}
- return ApiError(500, "Unable to compute diff", err)
+ return Error(500, "Unable to compute diff", err)
}
if options.DiffType == dashdiffs.DiffDelta {
@@ -467,7 +464,7 @@ func RestoreDashboardVersion(c *m.ReqContext, apiCmd dtos.RestoreDashboardVersio
versionQuery := m.GetDashboardVersionQuery{DashboardId: dash.Id, Version: apiCmd.Version, OrgId: c.OrgId}
if err := bus.Dispatch(&versionQuery); err != nil {
- return ApiError(404, "Dashboard version not found", nil)
+ return Error(404, "Dashboard version not found", nil)
}
version := versionQuery.Result
diff --git a/pkg/api/dashboard_permission.go b/pkg/api/dashboard_permission.go
index a62c27ab320..653815aea5c 100644
--- a/pkg/api/dashboard_permission.go
+++ b/pkg/api/dashboard_permission.go
@@ -10,14 +10,14 @@ import (
)
func GetDashboardPermissionList(c *m.ReqContext) Response {
- dashId := c.ParamsInt64(":dashboardId")
+ dashID := c.ParamsInt64(":dashboardId")
- _, rsp := getDashboardHelper(c.OrgId, "", dashId, "")
+ _, rsp := getDashboardHelper(c.OrgId, "", dashID, "")
if rsp != nil {
return rsp
}
- g := guardian.New(dashId, c.OrgId, c.SignedInUser)
+ g := guardian.New(dashID, c.OrgId, c.SignedInUser)
if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin {
return dashboardGuardianResponse(err)
@@ -25,7 +25,7 @@ func GetDashboardPermissionList(c *m.ReqContext) Response {
acl, err := g.GetAcl()
if err != nil {
- return ApiError(500, "Failed to get dashboard permissions", err)
+ return Error(500, "Failed to get dashboard permissions", err)
}
for _, perm := range acl {
@@ -34,29 +34,29 @@ func GetDashboardPermissionList(c *m.ReqContext) Response {
}
}
- return Json(200, acl)
+ return JSON(200, acl)
}
func UpdateDashboardPermissions(c *m.ReqContext, apiCmd dtos.UpdateDashboardAclCommand) Response {
- dashId := c.ParamsInt64(":dashboardId")
+ dashID := c.ParamsInt64(":dashboardId")
- _, rsp := getDashboardHelper(c.OrgId, "", dashId, "")
+ _, rsp := getDashboardHelper(c.OrgId, "", dashID, "")
if rsp != nil {
return rsp
}
- g := guardian.New(dashId, c.OrgId, c.SignedInUser)
+ g := guardian.New(dashID, c.OrgId, c.SignedInUser)
if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin {
return dashboardGuardianResponse(err)
}
cmd := m.UpdateDashboardAclCommand{}
- cmd.DashboardId = dashId
+ cmd.DashboardId = dashID
for _, item := range apiCmd.Items {
cmd.Items = append(cmd.Items, &m.DashboardAcl{
OrgId: c.OrgId,
- DashboardId: dashId,
+ DashboardId: dashID,
UserId: item.UserId,
TeamId: item.TeamId,
Role: item.Role,
@@ -70,21 +70,21 @@ func UpdateDashboardPermissions(c *m.ReqContext, apiCmd dtos.UpdateDashboardAclC
if err != nil {
if err == guardian.ErrGuardianPermissionExists ||
err == guardian.ErrGuardianOverride {
- return ApiError(400, err.Error(), err)
+ return Error(400, err.Error(), err)
}
- return ApiError(500, "Error while checking dashboard permissions", err)
+ return Error(500, "Error while checking dashboard permissions", err)
}
- return ApiError(403, "Cannot remove own admin permission for a folder", nil)
+ return Error(403, "Cannot remove own admin permission for a folder", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrDashboardAclInfoMissing || err == m.ErrDashboardPermissionDashboardEmpty {
- return ApiError(409, err.Error(), err)
+ return Error(409, err.Error(), err)
}
- return ApiError(500, "Failed to create permission", err)
+ return Error(500, "Failed to create permission", err)
}
- return ApiSuccess("Dashboard permissions updated")
+ return Success("Dashboard permissions updated")
}
diff --git a/pkg/api/dashboard_snapshot.go b/pkg/api/dashboard_snapshot.go
index 4656940d2bb..f474d357df5 100644
--- a/pkg/api/dashboard_snapshot.go
+++ b/pkg/api/dashboard_snapshot.go
@@ -99,32 +99,32 @@ func DeleteDashboardSnapshot(c *m.ReqContext) Response {
err := bus.Dispatch(query)
if err != nil {
- return ApiError(500, "Failed to get dashboard snapshot", err)
+ return Error(500, "Failed to get dashboard snapshot", err)
}
if query.Result == nil {
- return ApiError(404, "Failed to get dashboard snapshot", nil)
+ return Error(404, "Failed to get dashboard snapshot", nil)
}
dashboard := query.Result.Dashboard
- dashboardId := dashboard.Get("id").MustInt64()
+ dashboardID := dashboard.Get("id").MustInt64()
- guardian := guardian.New(dashboardId, c.OrgId, c.SignedInUser)
+ guardian := guardian.New(dashboardID, c.OrgId, c.SignedInUser)
canEdit, err := guardian.CanEdit()
if err != nil {
- return ApiError(500, "Error while checking permissions for snapshot", err)
+ return Error(500, "Error while checking permissions for snapshot", err)
}
if !canEdit && query.Result.UserId != c.SignedInUser.UserId {
- return ApiError(403, "Access denied to this snapshot", nil)
+ return Error(403, "Access denied to this snapshot", nil)
}
cmd := &m.DeleteDashboardSnapshotCommand{DeleteKey: key}
if err := bus.Dispatch(cmd); err != nil {
- return ApiError(500, "Failed to delete dashboard snapshot", err)
+ return Error(500, "Failed to delete dashboard snapshot", err)
}
- return Json(200, util.DynMap{"message": "Snapshot deleted. It might take an hour before it's cleared from a CDN cache."})
+ return JSON(200, util.DynMap{"message": "Snapshot deleted. It might take an hour before it's cleared from a CDN cache."})
}
// GET /api/dashboard/snapshots
@@ -145,7 +145,7 @@ func SearchDashboardSnapshots(c *m.ReqContext) Response {
err := bus.Dispatch(&searchQuery)
if err != nil {
- return ApiError(500, "Search failed", err)
+ return Error(500, "Search failed", err)
}
dtos := make([]*m.DashboardSnapshotDTO, len(searchQuery.Result))
@@ -165,5 +165,5 @@ func SearchDashboardSnapshots(c *m.ReqContext) Response {
}
}
- return Json(200, dtos)
+ return JSON(200, dtos)
}
diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go
index 6c5b4e4c102..0d87023ce40 100644
--- a/pkg/api/dashboard_test.go
+++ b/pkg/api/dashboard_test.go
@@ -105,7 +105,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
- CallDeleteDashboardByUid(sc)
+ CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() {
@@ -165,7 +165,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
- CallDeleteDashboardByUid(sc)
+ CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 200)
Convey("Should lookup dashboard by uid", func() {
@@ -271,7 +271,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
- CallDeleteDashboardByUid(sc)
+ CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() {
@@ -329,7 +329,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
- CallDeleteDashboardByUid(sc)
+ CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() {
@@ -398,7 +398,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
- CallDeleteDashboardByUid(sc)
+ CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 200)
Convey("Should lookup dashboard by uid", func() {
@@ -468,7 +468,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
- CallDeleteDashboardByUid(sc)
+ CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() {
@@ -527,7 +527,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
- CallDeleteDashboardByUid(sc)
+ CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 200)
Convey("Should lookup dashboard by uid", func() {
@@ -594,7 +594,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
- CallDeleteDashboardByUid(sc)
+ CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() {
@@ -638,7 +638,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
Convey("Should result in 412 Precondition failed", func() {
So(sc.resp.Code, ShouldEqual, 412)
- result := sc.ToJson()
+ result := sc.ToJSON()
So(result.Get("status").MustString(), ShouldEqual, "multiple-slugs-exists")
So(result.Get("message").MustString(), ShouldEqual, m.ErrDashboardsWithSameSlugExists.Error())
})
@@ -686,7 +686,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
Convey("It should return correct response data", func() {
- result := sc.ToJson()
+ result := sc.ToJSON()
So(result.Get("status").MustString(), ShouldEqual, "success")
So(result.Get("id").MustInt64(), ShouldEqual, 2)
So(result.Get("uid").MustString(), ShouldEqual, "uid")
@@ -837,12 +837,12 @@ func CallDeleteDashboard(sc *scenarioContext) {
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
}
-func CallDeleteDashboardByUid(sc *scenarioContext) {
+func CallDeleteDashboardByUID(sc *scenarioContext) {
bus.AddHandler("test", func(cmd *m.DeleteDashboardCommand) error {
return nil
})
- sc.handlerFunc = DeleteDashboardByUid
+ sc.handlerFunc = DeleteDashboardByUID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
}
@@ -903,7 +903,7 @@ func postDiffScenario(desc string, url string, routePattern string, cmd dtos.Cal
})
}
-func (sc *scenarioContext) ToJson() *simplejson.Json {
+func (sc *scenarioContext) ToJSON() *simplejson.Json {
var result *simplejson.Json
err := json.NewDecoder(sc.resp.Body).Decode(&result)
So(err, ShouldBeNil)
diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go
index c6fe8b6cd8c..33839ca985d 100644
--- a/pkg/api/dataproxy.go
+++ b/pkg/api/dataproxy.go
@@ -13,19 +13,19 @@ import (
const HeaderNameNoBackendCache = "X-Grafana-NoCache"
-func (hs *HttpServer) getDatasourceById(id int64, orgId int64, nocache bool) (*m.DataSource, error) {
+func (hs *HTTPServer) getDatasourceByID(id int64, orgID int64, nocache bool) (*m.DataSource, error) {
cacheKey := fmt.Sprintf("ds-%d", id)
if !nocache {
if cached, found := hs.cache.Get(cacheKey); found {
ds := cached.(*m.DataSource)
- if ds.OrgId == orgId {
+ if ds.OrgId == orgID {
return ds, nil
}
}
}
- query := m.GetDataSourceByIdQuery{Id: id, OrgId: orgId}
+ query := m.GetDataSourceByIdQuery{Id: id, OrgId: orgID}
if err := bus.Dispatch(&query); err != nil {
return nil, err
}
@@ -34,12 +34,12 @@ func (hs *HttpServer) getDatasourceById(id int64, orgId int64, nocache bool) (*m
return query.Result, nil
}
-func (hs *HttpServer) ProxyDataSourceRequest(c *m.ReqContext) {
+func (hs *HTTPServer) ProxyDataSourceRequest(c *m.ReqContext) {
c.TimeRequest(metrics.M_DataSource_ProxyReq_Timer)
nocache := c.Req.Header.Get(HeaderNameNoBackendCache) == "true"
- ds, err := hs.getDatasourceById(c.ParamsInt64(":id"), c.OrgId, nocache)
+ ds, err := hs.getDatasourceByID(c.ParamsInt64(":id"), c.OrgId, nocache)
if err != nil {
c.JsonApiErr(500, "Unable to load datasource meta data", err)
diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go
index ed8fc5d2a66..99677a93ee6 100644
--- a/pkg/api/datasources.go
+++ b/pkg/api/datasources.go
@@ -14,7 +14,7 @@ func GetDataSources(c *m.ReqContext) Response {
query := m.GetDataSourcesQuery{OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
- return ApiError(500, "Failed to query datasources", err)
+ return Error(500, "Failed to query datasources", err)
}
result := make(dtos.DataSourceList, 0)
@@ -46,10 +46,10 @@ func GetDataSources(c *m.ReqContext) Response {
sort.Sort(result)
- return Json(200, &result)
+ return JSON(200, &result)
}
-func GetDataSourceById(c *m.ReqContext) Response {
+func GetDataSourceByID(c *m.ReqContext) Response {
query := m.GetDataSourceByIdQuery{
Id: c.ParamsInt64(":id"),
OrgId: c.OrgId,
@@ -57,66 +57,66 @@ func GetDataSourceById(c *m.ReqContext) Response {
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrDataSourceNotFound {
- return ApiError(404, "Data source not found", nil)
+ return Error(404, "Data source not found", nil)
}
- return ApiError(500, "Failed to query datasources", err)
+ return Error(500, "Failed to query datasources", err)
}
ds := query.Result
dtos := convertModelToDtos(ds)
- return Json(200, &dtos)
+ return JSON(200, &dtos)
}
-func DeleteDataSourceById(c *m.ReqContext) Response {
+func DeleteDataSourceByID(c *m.ReqContext) Response {
id := c.ParamsInt64(":id")
if id <= 0 {
- return ApiError(400, "Missing valid datasource id", nil)
+ return Error(400, "Missing valid datasource id", nil)
}
- ds, err := getRawDataSourceById(id, c.OrgId)
+ ds, err := getRawDataSourceByID(id, c.OrgId)
if err != nil {
- return ApiError(400, "Failed to delete datasource", nil)
+ return Error(400, "Failed to delete datasource", nil)
}
if ds.ReadOnly {
- return ApiError(403, "Cannot delete read-only data source", nil)
+ return Error(403, "Cannot delete read-only data source", nil)
}
cmd := &m.DeleteDataSourceByIdCommand{Id: id, OrgId: c.OrgId}
err = bus.Dispatch(cmd)
if err != nil {
- return ApiError(500, "Failed to delete datasource", err)
+ return Error(500, "Failed to delete datasource", err)
}
- return ApiSuccess("Data source deleted")
+ return Success("Data source deleted")
}
func DeleteDataSourceByName(c *m.ReqContext) Response {
name := c.Params(":name")
if name == "" {
- return ApiError(400, "Missing valid datasource name", nil)
+ return Error(400, "Missing valid datasource name", nil)
}
getCmd := &m.GetDataSourceByNameQuery{Name: name, OrgId: c.OrgId}
if err := bus.Dispatch(getCmd); err != nil {
- return ApiError(500, "Failed to delete datasource", err)
+ return Error(500, "Failed to delete datasource", err)
}
if getCmd.Result.ReadOnly {
- return ApiError(403, "Cannot delete read-only data source", nil)
+ return Error(403, "Cannot delete read-only data source", nil)
}
cmd := &m.DeleteDataSourceByNameCommand{Name: name, OrgId: c.OrgId}
err := bus.Dispatch(cmd)
if err != nil {
- return ApiError(500, "Failed to delete datasource", err)
+ return Error(500, "Failed to delete datasource", err)
}
- return ApiSuccess("Data source deleted")
+ return Success("Data source deleted")
}
func AddDataSource(c *m.ReqContext, cmd m.AddDataSourceCommand) Response {
@@ -124,14 +124,14 @@ func AddDataSource(c *m.ReqContext, cmd m.AddDataSourceCommand) Response {
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrDataSourceNameExists {
- return ApiError(409, err.Error(), err)
+ return Error(409, err.Error(), err)
}
- return ApiError(500, "Failed to add datasource", err)
+ return Error(500, "Failed to add datasource", err)
}
ds := convertModelToDtos(cmd.Result)
- return Json(200, util.DynMap{
+ return JSON(200, util.DynMap{
"message": "Datasource added",
"id": cmd.Result.Id,
"name": cmd.Result.Name,
@@ -143,21 +143,20 @@ func UpdateDataSource(c *m.ReqContext, cmd m.UpdateDataSourceCommand) Response {
cmd.OrgId = c.OrgId
cmd.Id = c.ParamsInt64(":id")
- err := fillWithSecureJsonData(&cmd)
+ err := fillWithSecureJSONData(&cmd)
if err != nil {
- return ApiError(500, "Failed to update datasource", err)
+ return Error(500, "Failed to update datasource", err)
}
err = bus.Dispatch(&cmd)
if err != nil {
if err == m.ErrDataSourceUpdatingOldVersion {
- return ApiError(500, "Failed to update datasource. Reload new version and try again", err)
- } else {
- return ApiError(500, "Failed to update datasource", err)
+ return Error(500, "Failed to update datasource. Reload new version and try again", err)
}
+ return Error(500, "Failed to update datasource", err)
}
ds := convertModelToDtos(cmd.Result)
- return Json(200, util.DynMap{
+ return JSON(200, util.DynMap{
"message": "Datasource updated",
"id": cmd.Id,
"name": cmd.Name,
@@ -165,12 +164,12 @@ func UpdateDataSource(c *m.ReqContext, cmd m.UpdateDataSourceCommand) Response {
})
}
-func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error {
+func fillWithSecureJSONData(cmd *m.UpdateDataSourceCommand) error {
if len(cmd.SecureJsonData) == 0 {
return nil
}
- ds, err := getRawDataSourceById(cmd.Id, cmd.OrgId)
+ ds, err := getRawDataSourceByID(cmd.Id, cmd.OrgId)
if err != nil {
return err
}
@@ -179,8 +178,8 @@ func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error {
return m.ErrDatasourceIsReadOnly
}
- secureJsonData := ds.SecureJsonData.Decrypt()
- for k, v := range secureJsonData {
+ secureJSONData := ds.SecureJsonData.Decrypt()
+ for k, v := range secureJSONData {
if _, ok := cmd.SecureJsonData[k]; !ok {
cmd.SecureJsonData[k] = v
@@ -190,10 +189,10 @@ func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error {
return nil
}
-func getRawDataSourceById(id int64, orgId int64) (*m.DataSource, error) {
+func getRawDataSourceByID(id int64, orgID int64) (*m.DataSource, error) {
query := m.GetDataSourceByIdQuery{
Id: id,
- OrgId: orgId,
+ OrgId: orgID,
}
if err := bus.Dispatch(&query); err != nil {
@@ -209,25 +208,25 @@ func GetDataSourceByName(c *m.ReqContext) Response {
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrDataSourceNotFound {
- return ApiError(404, "Data source not found", nil)
+ return Error(404, "Data source not found", nil)
}
- return ApiError(500, "Failed to query datasources", err)
+ return Error(500, "Failed to query datasources", err)
}
dtos := convertModelToDtos(query.Result)
dtos.ReadOnly = true
- return Json(200, &dtos)
+ return JSON(200, &dtos)
}
// Get /api/datasources/id/:name
-func GetDataSourceIdByName(c *m.ReqContext) Response {
+func GetDataSourceIDByName(c *m.ReqContext) Response {
query := m.GetDataSourceByNameQuery{Name: c.Params(":name"), OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrDataSourceNotFound {
- return ApiError(404, "Data source not found", nil)
+ return Error(404, "Data source not found", nil)
}
- return ApiError(500, "Failed to query datasources", err)
+ return Error(500, "Failed to query datasources", err)
}
ds := query.Result
@@ -235,7 +234,7 @@ func GetDataSourceIdByName(c *m.ReqContext) Response {
Id: ds.Id,
}
- return Json(200, &dtos)
+ return JSON(200, &dtos)
}
func convertModelToDtos(ds *m.DataSource) dtos.DataSource {
diff --git a/pkg/api/dtos/prefs.go b/pkg/api/dtos/prefs.go
index 97e20bb4011..ef1ea4040e5 100644
--- a/pkg/api/dtos/prefs.go
+++ b/pkg/api/dtos/prefs.go
@@ -2,12 +2,12 @@ package dtos
type Prefs struct {
Theme string `json:"theme"`
- HomeDashboardId int64 `json:"homeDashboardId"`
+ HomeDashboardID int64 `json:"homeDashboardId"`
Timezone string `json:"timezone"`
}
type UpdatePrefsCmd struct {
Theme string `json:"theme"`
- HomeDashboardId int64 `json:"homeDashboardId"`
+ HomeDashboardID int64 `json:"homeDashboardId"`
Timezone string `json:"timezone"`
}
diff --git a/pkg/api/folder.go b/pkg/api/folder.go
index 143892fa6e8..f0cdff24d20 100644
--- a/pkg/api/folder.go
+++ b/pkg/api/folder.go
@@ -28,30 +28,30 @@ func GetFolders(c *m.ReqContext) Response {
})
}
- return Json(200, result)
+ return JSON(200, result)
}
-func GetFolderByUid(c *m.ReqContext) Response {
+func GetFolderByUID(c *m.ReqContext) Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
- folder, err := s.GetFolderByUid(c.Params(":uid"))
+ folder, err := s.GetFolderByUID(c.Params(":uid"))
if err != nil {
return toFolderError(err)
}
g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
- return Json(200, toFolderDto(g, folder))
+ return JSON(200, toFolderDto(g, folder))
}
-func GetFolderById(c *m.ReqContext) Response {
+func GetFolderByID(c *m.ReqContext) Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
- folder, err := s.GetFolderById(c.ParamsInt64(":id"))
+ folder, err := s.GetFolderByID(c.ParamsInt64(":id"))
if err != nil {
return toFolderError(err)
}
g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
- return Json(200, toFolderDto(g, folder))
+ return JSON(200, toFolderDto(g, folder))
}
func CreateFolder(c *m.ReqContext, cmd m.CreateFolderCommand) Response {
@@ -62,7 +62,7 @@ func CreateFolder(c *m.ReqContext, cmd m.CreateFolderCommand) Response {
}
g := guardian.New(cmd.Result.Id, c.OrgId, c.SignedInUser)
- return Json(200, toFolderDto(g, cmd.Result))
+ return JSON(200, toFolderDto(g, cmd.Result))
}
func UpdateFolder(c *m.ReqContext, cmd m.UpdateFolderCommand) Response {
@@ -73,7 +73,7 @@ func UpdateFolder(c *m.ReqContext, cmd m.UpdateFolderCommand) Response {
}
g := guardian.New(cmd.Result.Id, c.OrgId, c.SignedInUser)
- return Json(200, toFolderDto(g, cmd.Result))
+ return JSON(200, toFolderDto(g, cmd.Result))
}
func DeleteFolder(c *m.ReqContext) Response {
@@ -83,7 +83,7 @@ func DeleteFolder(c *m.ReqContext) Response {
return toFolderError(err)
}
- return Json(200, util.DynMap{
+ return JSON(200, util.DynMap{
"title": f.Title,
"message": fmt.Sprintf("Folder %s deleted", f.Title),
})
@@ -127,20 +127,20 @@ func toFolderError(err error) Response {
err == m.ErrDashboardTypeMismatch ||
err == m.ErrDashboardInvalidUid ||
err == m.ErrDashboardUidToLong {
- return ApiError(400, err.Error(), nil)
+ return Error(400, err.Error(), nil)
}
if err == m.ErrFolderAccessDenied {
- return ApiError(403, "Access denied", err)
+ return Error(403, "Access denied", err)
}
if err == m.ErrFolderNotFound {
- return Json(404, util.DynMap{"status": "not-found", "message": m.ErrFolderNotFound.Error()})
+ return JSON(404, util.DynMap{"status": "not-found", "message": m.ErrFolderNotFound.Error()})
}
if err == m.ErrFolderVersionMismatch {
- return Json(412, util.DynMap{"status": "version-mismatch", "message": m.ErrFolderVersionMismatch.Error()})
+ return JSON(412, util.DynMap{"status": "version-mismatch", "message": m.ErrFolderVersionMismatch.Error()})
}
- return ApiError(500, "Folder API error", err)
+ return Error(500, "Folder API error", err)
}
diff --git a/pkg/api/folder_permission.go b/pkg/api/folder_permission.go
index 1b04eb20e53..0d0904c99ea 100644
--- a/pkg/api/folder_permission.go
+++ b/pkg/api/folder_permission.go
@@ -12,7 +12,7 @@ import (
func GetFolderPermissionList(c *m.ReqContext) Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
- folder, err := s.GetFolderByUid(c.Params(":uid"))
+ folder, err := s.GetFolderByUID(c.Params(":uid"))
if err != nil {
return toFolderError(err)
@@ -26,7 +26,7 @@ func GetFolderPermissionList(c *m.ReqContext) Response {
acl, err := g.GetAcl()
if err != nil {
- return ApiError(500, "Failed to get folder permissions", err)
+ return Error(500, "Failed to get folder permissions", err)
}
for _, perm := range acl {
@@ -38,12 +38,12 @@ func GetFolderPermissionList(c *m.ReqContext) Response {
}
}
- return Json(200, acl)
+ return JSON(200, acl)
}
func UpdateFolderPermissions(c *m.ReqContext, apiCmd dtos.UpdateDashboardAclCommand) Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
- folder, err := s.GetFolderByUid(c.Params(":uid"))
+ folder, err := s.GetFolderByUID(c.Params(":uid"))
if err != nil {
return toFolderError(err)
@@ -79,13 +79,13 @@ func UpdateFolderPermissions(c *m.ReqContext, apiCmd dtos.UpdateDashboardAclComm
if err != nil {
if err == guardian.ErrGuardianPermissionExists ||
err == guardian.ErrGuardianOverride {
- return ApiError(400, err.Error(), err)
+ return Error(400, err.Error(), err)
}
- return ApiError(500, "Error while checking folder permissions", err)
+ return Error(500, "Error while checking folder permissions", err)
}
- return ApiError(403, "Cannot remove own admin permission for a folder", nil)
+ return Error(403, "Cannot remove own admin permission for a folder", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
@@ -97,11 +97,11 @@ func UpdateFolderPermissions(c *m.ReqContext, apiCmd dtos.UpdateDashboardAclComm
}
if err == m.ErrFolderAclInfoMissing || err == m.ErrFolderPermissionFolderEmpty {
- return ApiError(409, err.Error(), err)
+ return Error(409, err.Error(), err)
}
- return ApiError(500, "Failed to create permission", err)
+ return Error(500, "Failed to create permission", err)
}
- return ApiSuccess("Folder permissions updated")
+ return Success("Folder permissions updated")
}
diff --git a/pkg/api/folder_permission_test.go b/pkg/api/folder_permission_test.go
index 00d025fdce2..f7458af6dce 100644
--- a/pkg/api/folder_permission_test.go
+++ b/pkg/api/folder_permission_test.go
@@ -17,7 +17,7 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
Convey("Folder permissions test", t, func() {
Convey("Given folder not exists", func() {
mock := &fakeFolderService{
- GetFolderByUidError: m.ErrFolderNotFound,
+ GetFolderByUIDError: m.ErrFolderNotFound,
}
origNewFolderService := dashboards.NewFolderService
@@ -49,7 +49,7 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false})
mock := &fakeFolderService{
- GetFolderByUidResult: &m.Folder{
+ GetFolderByUIDResult: &m.Folder{
Id: 1,
Uid: "uid",
Title: "Folder",
@@ -96,7 +96,7 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
})
mock := &fakeFolderService{
- GetFolderByUidResult: &m.Folder{
+ GetFolderByUIDResult: &m.Folder{
Id: 1,
Uid: "uid",
Title: "Folder",
@@ -142,7 +142,7 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
})
mock := &fakeFolderService{
- GetFolderByUidResult: &m.Folder{
+ GetFolderByUIDResult: &m.Folder{
Id: 1,
Uid: "uid",
Title: "Folder",
@@ -178,7 +178,7 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
)
mock := &fakeFolderService{
- GetFolderByUidResult: &m.Folder{
+ GetFolderByUIDResult: &m.Folder{
Id: 1,
Uid: "uid",
Title: "Folder",
diff --git a/pkg/api/folder_test.go b/pkg/api/folder_test.go
index 7cefdcf8544..0d9b9495686 100644
--- a/pkg/api/folder_test.go
+++ b/pkg/api/folder_test.go
@@ -133,8 +133,8 @@ func TestFoldersApiEndpoint(t *testing.T) {
})
}
-func callGetFolderByUid(sc *scenarioContext) {
- sc.handlerFunc = GetFolderByUid
+func callGetFolderByUID(sc *scenarioContext) {
+ sc.handlerFunc = GetFolderByUID
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
}
@@ -204,10 +204,10 @@ func updateFolderScenario(desc string, url string, routePattern string, mock *fa
type fakeFolderService struct {
GetFoldersResult []*m.Folder
GetFoldersError error
- GetFolderByUidResult *m.Folder
- GetFolderByUidError error
- GetFolderByIdResult *m.Folder
- GetFolderByIdError error
+ GetFolderByUIDResult *m.Folder
+ GetFolderByUIDError error
+ GetFolderByIDResult *m.Folder
+ GetFolderByIDError error
CreateFolderResult *m.Folder
CreateFolderError error
UpdateFolderResult *m.Folder
@@ -221,12 +221,12 @@ func (s *fakeFolderService) GetFolders(limit int) ([]*m.Folder, error) {
return s.GetFoldersResult, s.GetFoldersError
}
-func (s *fakeFolderService) GetFolderById(id int64) (*m.Folder, error) {
- return s.GetFolderByIdResult, s.GetFolderByIdError
+func (s *fakeFolderService) GetFolderByID(id int64) (*m.Folder, error) {
+ return s.GetFolderByIDResult, s.GetFolderByIDError
}
-func (s *fakeFolderService) GetFolderByUid(uid string) (*m.Folder, error) {
- return s.GetFolderByUidResult, s.GetFolderByUidError
+func (s *fakeFolderService) GetFolderByUID(uid string) (*m.Folder, error) {
+ return s.GetFolderByUIDResult, s.GetFolderByUIDError
}
func (s *fakeFolderService) CreateFolder(cmd *m.CreateFolderCommand) error {
@@ -234,7 +234,7 @@ func (s *fakeFolderService) CreateFolder(cmd *m.CreateFolderCommand) error {
return s.CreateFolderError
}
-func (s *fakeFolderService) UpdateFolder(existingUid string, cmd *m.UpdateFolderCommand) error {
+func (s *fakeFolderService) UpdateFolder(existingUID string, cmd *m.UpdateFolderCommand) error {
cmd.Result = s.UpdateFolderResult
return s.UpdateFolderError
}
diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go
index b911780913d..387a543ca89 100644
--- a/pkg/api/http_server.go
+++ b/pkg/api/http_server.go
@@ -29,7 +29,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
-type HttpServer struct {
+type HTTPServer struct {
log log.Logger
macaron *macaron.Macaron
context context.Context
@@ -39,14 +39,14 @@ type HttpServer struct {
httpSrv *http.Server
}
-func NewHttpServer() *HttpServer {
- return &HttpServer{
+func NewHTTPServer() *HTTPServer {
+ return &HTTPServer{
log: log.New("http.server"),
cache: gocache.New(5*time.Minute, 10*time.Minute),
}
}
-func (hs *HttpServer) Start(ctx context.Context) error {
+func (hs *HTTPServer) Start(ctx context.Context) error {
var err error
hs.context = ctx
@@ -74,12 +74,15 @@ func (hs *HttpServer) Start(ctx context.Context) error {
return nil
}
case setting.SOCKET:
- ln, err := net.Listen("unix", setting.SocketPath)
+ ln, err := net.ListenUnix("unix", &net.UnixAddr{Name: setting.SocketPath, Net: "unix"})
if err != nil {
hs.log.Debug("server was shutdown gracefully")
return nil
}
+ // Make socket writable by group
+ os.Chmod(setting.SocketPath, 0660)
+
err = hs.httpSrv.Serve(ln)
if err != nil {
hs.log.Debug("server was shutdown gracefully")
@@ -93,13 +96,13 @@ func (hs *HttpServer) Start(ctx context.Context) error {
return err
}
-func (hs *HttpServer) Shutdown(ctx context.Context) error {
+func (hs *HTTPServer) Shutdown(ctx context.Context) error {
err := hs.httpSrv.Shutdown(ctx)
hs.log.Info("Stopped HTTP server")
return err
}
-func (hs *HttpServer) listenAndServeTLS(certfile, keyfile string) error {
+func (hs *HTTPServer) listenAndServeTLS(certfile, keyfile string) error {
if certfile == "" {
return fmt.Errorf("cert_file cannot be empty when using HTTPS")
}
@@ -141,7 +144,7 @@ func (hs *HttpServer) listenAndServeTLS(certfile, keyfile string) error {
return hs.httpSrv.ListenAndServeTLS(setting.CertFile, setting.KeyFile)
}
-func (hs *HttpServer) newMacaron() *macaron.Macaron {
+func (hs *HTTPServer) newMacaron() *macaron.Macaron {
macaron.Env = setting.Env
m := macaron.New()
@@ -175,7 +178,7 @@ func (hs *HttpServer) newMacaron() *macaron.Macaron {
m.Use(hs.healthHandler)
m.Use(hs.metricsEndpoint)
m.Use(middleware.GetContextHandler())
- m.Use(middleware.Sessioner(&setting.SessionOptions))
+ m.Use(middleware.Sessioner(&setting.SessionOptions, setting.SessionConnMaxLifetime))
m.Use(middleware.OrgRedirect())
// needs to be after context handler
@@ -188,7 +191,7 @@ func (hs *HttpServer) newMacaron() *macaron.Macaron {
return m
}
-func (hs *HttpServer) metricsEndpoint(ctx *macaron.Context) {
+func (hs *HTTPServer) metricsEndpoint(ctx *macaron.Context) {
if ctx.Req.Method != "GET" || ctx.Req.URL.Path != "/metrics" {
return
}
@@ -197,7 +200,7 @@ func (hs *HttpServer) metricsEndpoint(ctx *macaron.Context) {
ServeHTTP(ctx.Resp, ctx.Req.Request)
}
-func (hs *HttpServer) healthHandler(ctx *macaron.Context) {
+func (hs *HTTPServer) healthHandler(ctx *macaron.Context) {
notHeadOrGet := ctx.Req.Method != http.MethodGet && ctx.Req.Method != http.MethodHead
if notHeadOrGet || ctx.Req.URL.Path != "/api/health" {
return
@@ -221,7 +224,7 @@ func (hs *HttpServer) healthHandler(ctx *macaron.Context) {
ctx.Resp.Write(dataBytes)
}
-func (hs *HttpServer) mapStatic(m *macaron.Macaron, rootDir string, dir string, prefix string) {
+func (hs *HTTPServer) mapStatic(m *macaron.Macaron, rootDir string, dir string, prefix string) {
headers := func(c *macaron.Context) {
c.Resp.Header().Set("Cache-Control", "public, max-age=3600")
}
diff --git a/pkg/api/index.go b/pkg/api/index.go
index e50c59e082a..a1d21d1c686 100644
--- a/pkg/api/index.go
+++ b/pkg/api/index.go
@@ -32,13 +32,13 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
locale = parts[0]
}
- appUrl := setting.AppUrl
- appSubUrl := setting.AppSubUrl
+ appURL := setting.AppUrl
+ appSubURL := setting.AppSubUrl
// special case when doing localhost call from phantomjs
if c.IsRenderCall {
- appUrl = fmt.Sprintf("%s://localhost:%s", setting.Protocol, setting.HttpPort)
- appSubUrl = ""
+ appURL = fmt.Sprintf("%s://localhost:%s", setting.Protocol, setting.HttpPort)
+ appSubURL = ""
settings["appSubUrl"] = ""
}
@@ -62,8 +62,8 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
},
Settings: settings,
Theme: prefs.Theme,
- AppUrl: appUrl,
- AppSubUrl: appSubUrl,
+ AppUrl: appURL,
+ AppSubUrl: appSubURL,
GoogleAnalyticsId: setting.GoogleAnalyticsId,
GoogleTagManagerId: setting.GoogleTagManagerId,
BuildVersion: setting.BuildVersion,
@@ -80,8 +80,8 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
data.User.Name = data.User.Login
}
- themeUrlParam := c.Query("theme")
- if themeUrlParam == "light" {
+ themeURLParam := c.Query("theme")
+ if themeURLParam == "light" {
data.User.LightTheme = true
data.Theme = "light"
}
@@ -299,12 +299,12 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
}
func Index(c *m.ReqContext) {
- if data, err := setIndexViewData(c); err != nil {
+ data, err := setIndexViewData(c)
+ if err != nil {
c.Handle(500, "Failed to get settings", err)
return
- } else {
- c.HTML(200, "index", data)
}
+ c.HTML(200, "index", data)
}
func NotFoundHandler(c *m.ReqContext) {
@@ -313,10 +313,11 @@ func NotFoundHandler(c *m.ReqContext) {
return
}
- if data, err := setIndexViewData(c); err != nil {
+ data, err := setIndexViewData(c)
+ if err != nil {
c.Handle(500, "Failed to get settings", err)
return
- } else {
- c.HTML(404, "index", data)
}
+
+ c.HTML(404, "index", data)
}
diff --git a/pkg/api/login.go b/pkg/api/login.go
index 2ca2ce5a3e2..671e5fb7ecd 100644
--- a/pkg/api/login.go
+++ b/pkg/api/login.go
@@ -14,7 +14,7 @@ import (
)
const (
- VIEW_INDEX = "index"
+ ViewIndex = "index"
)
func LoginView(c *m.ReqContext) {
@@ -40,7 +40,7 @@ func LoginView(c *m.ReqContext) {
}
if !tryLoginUsingRememberCookie(c) {
- c.HTML(200, VIEW_INDEX, viewData)
+ c.HTML(200, ViewIndex, viewData)
return
}
@@ -87,7 +87,7 @@ func tryLoginUsingRememberCookie(c *m.ReqContext) bool {
return true
}
-func LoginApiPing(c *m.ReqContext) {
+func LoginAPIPing(c *m.ReqContext) {
if !tryLoginUsingRememberCookie(c) {
c.JsonApiErr(401, "Unauthorized", nil)
return
@@ -98,7 +98,7 @@ func LoginApiPing(c *m.ReqContext) {
func LoginPost(c *m.ReqContext, cmd dtos.LoginCommand) Response {
if setting.DisableLoginForm {
- return ApiError(401, "Login is disabled", nil)
+ return Error(401, "Login is disabled", nil)
}
authQuery := login.LoginUserQuery{
@@ -109,10 +109,10 @@ func LoginPost(c *m.ReqContext, cmd dtos.LoginCommand) Response {
if err := bus.Dispatch(&authQuery); err != nil {
if err == login.ErrInvalidCredentials || err == login.ErrTooManyLoginAttempts {
- return ApiError(401, "Invalid username or password", err)
+ return Error(401, "Invalid username or password", err)
}
- return ApiError(500, "Error while trying to authenticate user", err)
+ return Error(500, "Error while trying to authenticate user", err)
}
user := authQuery.User
@@ -130,7 +130,7 @@ func LoginPost(c *m.ReqContext, cmd dtos.LoginCommand) Response {
metrics.M_Api_Login_Post.Inc()
- return Json(200, result)
+ return JSON(200, result)
}
func loginUserWithUser(user *m.User, c *m.ReqContext) {
diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go
index 5d395d655a9..5c06d652b70 100644
--- a/pkg/api/metrics.go
+++ b/pkg/api/metrics.go
@@ -17,17 +17,17 @@ func QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) Response {
timeRange := tsdb.NewTimeRange(reqDto.From, reqDto.To)
if len(reqDto.Queries) == 0 {
- return ApiError(400, "No queries found in query", nil)
+ return Error(400, "No queries found in query", nil)
}
- dsId, err := reqDto.Queries[0].Get("datasourceId").Int64()
+ dsID, err := reqDto.Queries[0].Get("datasourceId").Int64()
if err != nil {
- return ApiError(400, "Query missing datasourceId", nil)
+ return Error(400, "Query missing datasourceId", nil)
}
- dsQuery := m.GetDataSourceByIdQuery{Id: dsId, OrgId: c.OrgId}
+ dsQuery := m.GetDataSourceByIdQuery{Id: dsID, OrgId: c.OrgId}
if err := bus.Dispatch(&dsQuery); err != nil {
- return ApiError(500, "failed to fetch data source", err)
+ return Error(500, "failed to fetch data source", err)
}
request := &tsdb.TsdbQuery{TimeRange: timeRange}
@@ -44,7 +44,7 @@ func QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) Response {
resp, err := tsdb.HandleRequest(context.Background(), dsQuery.Result, request)
if err != nil {
- return ApiError(500, "Metric request error", err)
+ return Error(500, "Metric request error", err)
}
statusCode := 200
@@ -56,7 +56,7 @@ func QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) Response {
}
}
- return Json(statusCode, &resp)
+ return JSON(statusCode, &resp)
}
// GET /api/tsdb/testdata/scenarios
@@ -72,22 +72,22 @@ func GetTestDataScenarios(c *m.ReqContext) Response {
})
}
- return Json(200, &result)
+ return JSON(200, &result)
}
// Genereates a index out of range error
func GenerateError(c *m.ReqContext) Response {
var array []string
- return Json(200, array[20])
+ return JSON(200, array[20])
}
// GET /api/tsdb/testdata/gensql
-func GenerateSqlTestData(c *m.ReqContext) Response {
+func GenerateSQLTestData(c *m.ReqContext) Response {
if err := bus.Dispatch(&m.InsertSqlTestDataCommand{}); err != nil {
- return ApiError(500, "Failed to insert test data", err)
+ return Error(500, "Failed to insert test data", err)
}
- return Json(200, &util.DynMap{"message": "OK"})
+ return JSON(200, &util.DynMap{"message": "OK"})
}
// GET /api/tsdb/testdata/random-walk
@@ -111,8 +111,8 @@ func GetTestDataRandomWalk(c *m.ReqContext) Response {
resp, err := tsdb.HandleRequest(context.Background(), dsInfo, request)
if err != nil {
- return ApiError(500, "Metric request error", err)
+ return Error(500, "Metric request error", err)
}
- return Json(200, &resp)
+ return JSON(200, &resp)
}
diff --git a/pkg/api/org.go b/pkg/api/org.go
index 5f20559dbbe..8d0d48bad13 100644
--- a/pkg/api/org.go
+++ b/pkg/api/org.go
@@ -15,7 +15,7 @@ func GetOrgCurrent(c *m.ReqContext) Response {
}
// GET /api/orgs/:orgId
-func GetOrgById(c *m.ReqContext) Response {
+func GetOrgByID(c *m.ReqContext) Response {
return getOrgHelper(c.ParamsInt64(":orgId"))
}
@@ -24,10 +24,10 @@ func GetOrgByName(c *m.ReqContext) Response {
query := m.GetOrgByNameQuery{Name: c.Params(":name")}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrOrgNotFound {
- return ApiError(404, "Organization not found", err)
+ return Error(404, "Organization not found", err)
}
- return ApiError(500, "Failed to get organization", err)
+ return Error(500, "Failed to get organization", err)
}
org := query.Result
result := m.OrgDetailsDTO{
@@ -43,18 +43,18 @@ func GetOrgByName(c *m.ReqContext) Response {
},
}
- return Json(200, &result)
+ return JSON(200, &result)
}
-func getOrgHelper(orgId int64) Response {
- query := m.GetOrgByIdQuery{Id: orgId}
+func getOrgHelper(orgID int64) Response {
+ query := m.GetOrgByIdQuery{Id: orgID}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrOrgNotFound {
- return ApiError(404, "Organization not found", err)
+ return Error(404, "Organization not found", err)
}
- return ApiError(500, "Failed to get organization", err)
+ return Error(500, "Failed to get organization", err)
}
org := query.Result
@@ -71,26 +71,26 @@ func getOrgHelper(orgId int64) Response {
},
}
- return Json(200, &result)
+ return JSON(200, &result)
}
// POST /api/orgs
func CreateOrg(c *m.ReqContext, cmd m.CreateOrgCommand) Response {
if !c.IsSignedIn || (!setting.AllowUserOrgCreate && !c.IsGrafanaAdmin) {
- return ApiError(403, "Access denied", nil)
+ return Error(403, "Access denied", nil)
}
cmd.UserId = c.UserId
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrOrgNameTaken {
- return ApiError(409, "Organization name taken", err)
+ return Error(409, "Organization name taken", err)
}
- return ApiError(500, "Failed to create organization", err)
+ return Error(500, "Failed to create organization", err)
}
metrics.M_Api_Org_Create.Inc()
- return Json(200, &util.DynMap{
+ return JSON(200, &util.DynMap{
"orgId": cmd.Result.Id,
"message": "Organization created",
})
@@ -106,16 +106,16 @@ func UpdateOrg(c *m.ReqContext, form dtos.UpdateOrgForm) Response {
return updateOrgHelper(form, c.ParamsInt64(":orgId"))
}
-func updateOrgHelper(form dtos.UpdateOrgForm, orgId int64) Response {
- cmd := m.UpdateOrgCommand{Name: form.Name, OrgId: orgId}
+func updateOrgHelper(form dtos.UpdateOrgForm, orgID int64) Response {
+ cmd := m.UpdateOrgCommand{Name: form.Name, OrgId: orgID}
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrOrgNameTaken {
- return ApiError(400, "Organization name taken", err)
+ return Error(400, "Organization name taken", err)
}
- return ApiError(500, "Failed to update organization", err)
+ return Error(500, "Failed to update organization", err)
}
- return ApiSuccess("Organization updated")
+ return Success("Organization updated")
}
// PUT /api/org/address
@@ -128,9 +128,9 @@ func UpdateOrgAddress(c *m.ReqContext, form dtos.UpdateOrgAddressForm) Response
return updateOrgAddressHelper(form, c.ParamsInt64(":orgId"))
}
-func updateOrgAddressHelper(form dtos.UpdateOrgAddressForm, orgId int64) Response {
+func updateOrgAddressHelper(form dtos.UpdateOrgAddressForm, orgID int64) Response {
cmd := m.UpdateOrgAddressCommand{
- OrgId: orgId,
+ OrgId: orgID,
Address: m.Address{
Address1: form.Address1,
Address2: form.Address2,
@@ -142,21 +142,21 @@ func updateOrgAddressHelper(form dtos.UpdateOrgAddressForm, orgId int64) Respons
}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to update org address", err)
+ return Error(500, "Failed to update org address", err)
}
- return ApiSuccess("Address updated")
+ return Success("Address updated")
}
// GET /api/orgs/:orgId
-func DeleteOrgById(c *m.ReqContext) Response {
+func DeleteOrgByID(c *m.ReqContext) Response {
if err := bus.Dispatch(&m.DeleteOrgCommand{Id: c.ParamsInt64(":orgId")}); err != nil {
if err == m.ErrOrgNotFound {
- return ApiError(404, "Failed to delete organization. ID not found", nil)
+ return Error(404, "Failed to delete organization. ID not found", nil)
}
- return ApiError(500, "Failed to update organization", err)
+ return Error(500, "Failed to update organization", err)
}
- return ApiSuccess("Organization deleted")
+ return Success("Organization deleted")
}
func SearchOrgs(c *m.ReqContext) Response {
@@ -168,8 +168,8 @@ func SearchOrgs(c *m.ReqContext) Response {
}
if err := bus.Dispatch(&query); err != nil {
- return ApiError(500, "Failed to search orgs", err)
+ return Error(500, "Failed to search orgs", err)
}
- return Json(200, query.Result)
+ return JSON(200, query.Result)
}
diff --git a/pkg/api/org_invite.go b/pkg/api/org_invite.go
index 6a727dd95cc..d6ab1c9d372 100644
--- a/pkg/api/org_invite.go
+++ b/pkg/api/org_invite.go
@@ -16,30 +16,30 @@ func GetPendingOrgInvites(c *m.ReqContext) Response {
query := m.GetTempUsersQuery{OrgId: c.OrgId, Status: m.TmpUserInvitePending}
if err := bus.Dispatch(&query); err != nil {
- return ApiError(500, "Failed to get invites from db", err)
+ return Error(500, "Failed to get invites from db", err)
}
for _, invite := range query.Result {
invite.Url = setting.ToAbsUrl("invite/" + invite.Code)
}
- return Json(200, query.Result)
+ return JSON(200, query.Result)
}
func AddOrgInvite(c *m.ReqContext, inviteDto dtos.AddInviteForm) Response {
if !inviteDto.Role.IsValid() {
- return ApiError(400, "Invalid role specified", nil)
+ return Error(400, "Invalid role specified", nil)
}
// first try get existing user
userQuery := m.GetUserByLoginQuery{LoginOrEmail: inviteDto.LoginOrEmail}
if err := bus.Dispatch(&userQuery); err != nil {
if err != m.ErrUserNotFound {
- return ApiError(500, "Failed to query db for existing user check", err)
+ return Error(500, "Failed to query db for existing user check", err)
}
if setting.DisableLoginForm {
- return ApiError(401, "User could not be found", nil)
+ return Error(401, "User could not be found", nil)
}
} else {
return inviteExistingUserToOrg(c, userQuery.Result, &inviteDto)
@@ -56,7 +56,7 @@ func AddOrgInvite(c *m.ReqContext, inviteDto dtos.AddInviteForm) Response {
cmd.RemoteAddr = c.Req.RemoteAddr
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to save invite to database", err)
+ return Error(500, "Failed to save invite to database", err)
}
// send invite email
@@ -74,18 +74,18 @@ func AddOrgInvite(c *m.ReqContext, inviteDto dtos.AddInviteForm) Response {
}
if err := bus.Dispatch(&emailCmd); err != nil {
- return ApiError(500, "Failed to send email invite", err)
+ return Error(500, "Failed to send email invite", err)
}
emailSentCmd := m.UpdateTempUserWithEmailSentCommand{Code: cmd.Result.Code}
if err := bus.Dispatch(&emailSentCmd); err != nil {
- return ApiError(500, "Failed to update invite with email sent info", err)
+ return Error(500, "Failed to update invite with email sent info", err)
}
- return ApiSuccess(fmt.Sprintf("Sent invite to %s", inviteDto.LoginOrEmail))
+ return Success(fmt.Sprintf("Sent invite to %s", inviteDto.LoginOrEmail))
}
- return ApiSuccess(fmt.Sprintf("Created invite for %s", inviteDto.LoginOrEmail))
+ return Success(fmt.Sprintf("Created invite for %s", inviteDto.LoginOrEmail))
}
func inviteExistingUserToOrg(c *m.ReqContext, user *m.User, inviteDto *dtos.AddInviteForm) Response {
@@ -93,29 +93,28 @@ func inviteExistingUserToOrg(c *m.ReqContext, user *m.User, inviteDto *dtos.AddI
createOrgUserCmd := m.AddOrgUserCommand{OrgId: c.OrgId, UserId: user.Id, Role: inviteDto.Role}
if err := bus.Dispatch(&createOrgUserCmd); err != nil {
if err == m.ErrOrgUserAlreadyAdded {
- return ApiError(412, fmt.Sprintf("User %s is already added to organization", inviteDto.LoginOrEmail), err)
+ return Error(412, fmt.Sprintf("User %s is already added to organization", inviteDto.LoginOrEmail), err)
}
- return ApiError(500, "Error while trying to create org user", err)
- } else {
-
- if inviteDto.SendEmail && util.IsEmail(user.Email) {
- emailCmd := m.SendEmailCommand{
- To: []string{user.Email},
- Template: "invited_to_org.html",
- Data: map[string]interface{}{
- "Name": user.NameOrFallback(),
- "OrgName": c.OrgName,
- "InvitedBy": util.StringsFallback3(c.Name, c.Email, c.Login),
- },
- }
-
- if err := bus.Dispatch(&emailCmd); err != nil {
- return ApiError(500, "Failed to send email invited_to_org", err)
- }
- }
-
- return ApiSuccess(fmt.Sprintf("Existing Grafana user %s added to org %s", user.NameOrFallback(), c.OrgName))
+ return Error(500, "Error while trying to create org user", err)
}
+
+ if inviteDto.SendEmail && util.IsEmail(user.Email) {
+ emailCmd := m.SendEmailCommand{
+ To: []string{user.Email},
+ Template: "invited_to_org.html",
+ Data: map[string]interface{}{
+ "Name": user.NameOrFallback(),
+ "OrgName": c.OrgName,
+ "InvitedBy": util.StringsFallback3(c.Name, c.Email, c.Login),
+ },
+ }
+
+ if err := bus.Dispatch(&emailCmd); err != nil {
+ return Error(500, "Failed to send email invited_to_org", err)
+ }
+ }
+
+ return Success(fmt.Sprintf("Existing Grafana user %s added to org %s", user.NameOrFallback(), c.OrgName))
}
func RevokeInvite(c *m.ReqContext) Response {
@@ -123,7 +122,7 @@ func RevokeInvite(c *m.ReqContext) Response {
return rsp
}
- return ApiSuccess("Invite revoked")
+ return Success("Invite revoked")
}
func GetInviteInfoByCode(c *m.ReqContext) Response {
@@ -131,14 +130,14 @@ func GetInviteInfoByCode(c *m.ReqContext) Response {
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrTempUserNotFound {
- return ApiError(404, "Invite not found", nil)
+ return Error(404, "Invite not found", nil)
}
- return ApiError(500, "Failed to get invite", err)
+ return Error(500, "Failed to get invite", err)
}
invite := query.Result
- return Json(200, dtos.InviteInfo{
+ return JSON(200, dtos.InviteInfo{
Email: invite.Email,
Name: invite.Name,
Username: invite.Email,
@@ -151,14 +150,14 @@ func CompleteInvite(c *m.ReqContext, completeInvite dtos.CompleteInviteForm) Res
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrTempUserNotFound {
- return ApiError(404, "Invite not found", nil)
+ return Error(404, "Invite not found", nil)
}
- return ApiError(500, "Failed to get invite", err)
+ return Error(500, "Failed to get invite", err)
}
invite := query.Result
if invite.Status != m.TmpUserInvitePending {
- return ApiError(412, fmt.Sprintf("Invite cannot be used in status %s", invite.Status), nil)
+ return Error(412, fmt.Sprintf("Invite cannot be used in status %s", invite.Status), nil)
}
cmd := m.CreateUserCommand{
@@ -170,7 +169,7 @@ func CompleteInvite(c *m.ReqContext, completeInvite dtos.CompleteInviteForm) Res
}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "failed to create user", err)
+ return Error(500, "failed to create user", err)
}
user := &cmd.Result
@@ -189,14 +188,14 @@ func CompleteInvite(c *m.ReqContext, completeInvite dtos.CompleteInviteForm) Res
metrics.M_Api_User_SignUpCompleted.Inc()
metrics.M_Api_User_SignUpInvite.Inc()
- return ApiSuccess("User created and logged in")
+ return Success("User created and logged in")
}
func updateTempUserStatus(code string, status m.TempUserStatus) (bool, Response) {
// update temp user status
updateTmpUserCmd := m.UpdateTempUserStatusCommand{Code: code, Status: status}
if err := bus.Dispatch(&updateTmpUserCmd); err != nil {
- return false, ApiError(500, "Failed to update invite status", err)
+ return false, Error(500, "Failed to update invite status", err)
}
return true, nil
@@ -207,7 +206,7 @@ func applyUserInvite(user *m.User, invite *m.TempUserDTO, setActive bool) (bool,
addOrgUserCmd := m.AddOrgUserCommand{OrgId: invite.OrgId, UserId: user.Id, Role: invite.Role}
if err := bus.Dispatch(&addOrgUserCmd); err != nil {
if err != m.ErrOrgUserAlreadyAdded {
- return false, ApiError(500, "Error while trying to create org user", err)
+ return false, Error(500, "Error while trying to create org user", err)
}
}
@@ -219,7 +218,7 @@ func applyUserInvite(user *m.User, invite *m.TempUserDTO, setActive bool) (bool,
if setActive {
// set org to active
if err := bus.Dispatch(&m.SetUsingOrgCommand{OrgId: invite.OrgId, UserId: user.Id}); err != nil {
- return false, ApiError(500, "Failed to set org as active", err)
+ return false, Error(500, "Failed to set org as active", err)
}
}
diff --git a/pkg/api/org_users.go b/pkg/api/org_users.go
index 6d7c2bb94bd..4e2ed36431e 100644
--- a/pkg/api/org_users.go
+++ b/pkg/api/org_users.go
@@ -20,13 +20,13 @@ func AddOrgUser(c *m.ReqContext, cmd m.AddOrgUserCommand) Response {
func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
if !cmd.Role.IsValid() {
- return ApiError(400, "Invalid role specified", nil)
+ return Error(400, "Invalid role specified", nil)
}
userQuery := m.GetUserByLoginQuery{LoginOrEmail: cmd.LoginOrEmail}
err := bus.Dispatch(&userQuery)
if err != nil {
- return ApiError(404, "User not found", nil)
+ return Error(404, "User not found", nil)
}
userToAdd := userQuery.Result
@@ -35,12 +35,12 @@ func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrOrgUserAlreadyAdded {
- return ApiError(409, "User is already member of this organization", nil)
+ return Error(409, "User is already member of this organization", nil)
}
- return ApiError(500, "Could not add user to organization", err)
+ return Error(500, "Could not add user to organization", err)
}
- return ApiSuccess("User added to organization")
+ return Success("User added to organization")
}
// GET /api/org/users
@@ -53,22 +53,22 @@ func GetOrgUsers(c *m.ReqContext) Response {
return getOrgUsersHelper(c.ParamsInt64(":orgId"), "", 0)
}
-func getOrgUsersHelper(orgId int64, query string, limit int) Response {
+func getOrgUsersHelper(orgID int64, query string, limit int) Response {
q := m.GetOrgUsersQuery{
- OrgId: orgId,
+ OrgId: orgID,
Query: query,
Limit: limit,
}
if err := bus.Dispatch(&q); err != nil {
- return ApiError(500, "Failed to get account user", err)
+ return Error(500, "Failed to get account user", err)
}
for _, user := range q.Result {
user.AvatarUrl = dtos.GetGravatarUrl(user.Email)
}
- return Json(200, q.Result)
+ return JSON(200, q.Result)
}
// PATCH /api/org/users/:userId
@@ -87,41 +87,41 @@ func UpdateOrgUser(c *m.ReqContext, cmd m.UpdateOrgUserCommand) Response {
func updateOrgUserHelper(cmd m.UpdateOrgUserCommand) Response {
if !cmd.Role.IsValid() {
- return ApiError(400, "Invalid role specified", nil)
+ return Error(400, "Invalid role specified", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrLastOrgAdmin {
- return ApiError(400, "Cannot change role so that there is no organization admin left", nil)
+ return Error(400, "Cannot change role so that there is no organization admin left", nil)
}
- return ApiError(500, "Failed update org user", err)
+ return Error(500, "Failed update org user", err)
}
- return ApiSuccess("Organization user updated")
+ return Success("Organization user updated")
}
// DELETE /api/org/users/:userId
func RemoveOrgUserForCurrentOrg(c *m.ReqContext) Response {
- userId := c.ParamsInt64(":userId")
- return removeOrgUserHelper(c.OrgId, userId)
+ userID := c.ParamsInt64(":userId")
+ return removeOrgUserHelper(c.OrgId, userID)
}
// DELETE /api/orgs/:orgId/users/:userId
func RemoveOrgUser(c *m.ReqContext) Response {
- userId := c.ParamsInt64(":userId")
- orgId := c.ParamsInt64(":orgId")
- return removeOrgUserHelper(orgId, userId)
+ userID := c.ParamsInt64(":userId")
+ orgID := c.ParamsInt64(":orgId")
+ return removeOrgUserHelper(orgID, userID)
}
-func removeOrgUserHelper(orgId int64, userId int64) Response {
- cmd := m.RemoveOrgUserCommand{OrgId: orgId, UserId: userId}
+func removeOrgUserHelper(orgID int64, userID int64) Response {
+ cmd := m.RemoveOrgUserCommand{OrgId: orgID, UserId: userID}
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrLastOrgAdmin {
- return ApiError(400, "Cannot remove last organization admin", nil)
+ return Error(400, "Cannot remove last organization admin", nil)
}
- return ApiError(500, "Failed to remove user from organization", err)
+ return Error(500, "Failed to remove user from organization", err)
}
- return ApiSuccess("User removed from organization")
+ return Success("User removed from organization")
}
diff --git a/pkg/api/password.go b/pkg/api/password.go
index 31ea5d91b34..7dd901c898e 100644
--- a/pkg/api/password.go
+++ b/pkg/api/password.go
@@ -12,15 +12,15 @@ func SendResetPasswordEmail(c *m.ReqContext, form dtos.SendResetPasswordEmailFor
if err := bus.Dispatch(&userQuery); err != nil {
c.Logger.Info("Requested password reset for user that was not found", "user", userQuery.LoginOrEmail)
- return ApiError(200, "Email sent", err)
+ return Error(200, "Email sent", err)
}
emailCmd := m.SendResetPasswordEmailCommand{User: userQuery.Result}
if err := bus.Dispatch(&emailCmd); err != nil {
- return ApiError(500, "Failed to send email", err)
+ return Error(500, "Failed to send email", err)
}
- return ApiSuccess("Email sent")
+ return Success("Email sent")
}
func ResetPassword(c *m.ReqContext, form dtos.ResetUserPasswordForm) Response {
@@ -28,13 +28,13 @@ func ResetPassword(c *m.ReqContext, form dtos.ResetUserPasswordForm) Response {
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrInvalidEmailCode {
- return ApiError(400, "Invalid or expired reset password code", nil)
+ return Error(400, "Invalid or expired reset password code", nil)
}
- return ApiError(500, "Unknown error validating email code", err)
+ return Error(500, "Unknown error validating email code", err)
}
if form.NewPassword != form.ConfirmPassword {
- return ApiError(400, "Passwords do not match", nil)
+ return Error(400, "Passwords do not match", nil)
}
cmd := m.ChangeUserPasswordCommand{}
@@ -42,8 +42,8 @@ func ResetPassword(c *m.ReqContext, form dtos.ResetUserPasswordForm) Response {
cmd.NewPassword = util.EncodePassword(form.NewPassword, query.Result.Salt)
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to change user password", err)
+ return Error(500, "Failed to change user password", err)
}
- return ApiSuccess("User password changed")
+ return Success("User password changed")
}
diff --git a/pkg/api/playlist.go b/pkg/api/playlist.go
index 45de40ce337..d2413dfbb4c 100644
--- a/pkg/api/playlist.go
+++ b/pkg/api/playlist.go
@@ -55,10 +55,10 @@ func SearchPlaylists(c *m.ReqContext) Response {
err := bus.Dispatch(&searchQuery)
if err != nil {
- return ApiError(500, "Search failed", err)
+ return Error(500, "Search failed", err)
}
- return Json(200, searchQuery.Result)
+ return JSON(200, searchQuery.Result)
}
func GetPlaylist(c *m.ReqContext) Response {
@@ -66,7 +66,7 @@ func GetPlaylist(c *m.ReqContext) Response {
cmd := m.GetPlaylistByIdQuery{Id: id}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Playlist not found", err)
+ return Error(500, "Playlist not found", err)
}
playlistDTOs, _ := LoadPlaylistItemDTOs(id)
@@ -79,7 +79,7 @@ func GetPlaylist(c *m.ReqContext) Response {
Items: playlistDTOs,
}
- return Json(200, dto)
+ return JSON(200, dto)
}
func LoadPlaylistItemDTOs(id int64) ([]m.PlaylistItemDTO, error) {
@@ -120,21 +120,21 @@ func GetPlaylistItems(c *m.ReqContext) Response {
playlistDTOs, err := LoadPlaylistItemDTOs(id)
if err != nil {
- return ApiError(500, "Could not load playlist items", err)
+ return Error(500, "Could not load playlist items", err)
}
- return Json(200, playlistDTOs)
+ return JSON(200, playlistDTOs)
}
func GetPlaylistDashboards(c *m.ReqContext) Response {
- playlistId := c.ParamsInt64(":id")
+ playlistID := c.ParamsInt64(":id")
- playlists, err := LoadPlaylistDashboards(c.OrgId, c.SignedInUser, playlistId)
+ playlists, err := LoadPlaylistDashboards(c.OrgId, c.SignedInUser, playlistID)
if err != nil {
- return ApiError(500, "Could not load dashboards", err)
+ return Error(500, "Could not load dashboards", err)
}
- return Json(200, playlists)
+ return JSON(200, playlists)
}
func DeletePlaylist(c *m.ReqContext) Response {
@@ -142,34 +142,34 @@ func DeletePlaylist(c *m.ReqContext) Response {
cmd := m.DeletePlaylistCommand{Id: id, OrgId: c.OrgId}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to delete playlist", err)
+ return Error(500, "Failed to delete playlist", err)
}
- return Json(200, "")
+ return JSON(200, "")
}
func CreatePlaylist(c *m.ReqContext, cmd m.CreatePlaylistCommand) Response {
cmd.OrgId = c.OrgId
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to create playlist", err)
+ return Error(500, "Failed to create playlist", err)
}
- return Json(200, cmd.Result)
+ return JSON(200, cmd.Result)
}
func UpdatePlaylist(c *m.ReqContext, cmd m.UpdatePlaylistCommand) Response {
cmd.OrgId = c.OrgId
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to save playlist", err)
+ return Error(500, "Failed to save playlist", err)
}
playlistDTOs, err := LoadPlaylistItemDTOs(cmd.Id)
if err != nil {
- return ApiError(500, "Failed to save playlist", err)
+ return Error(500, "Failed to save playlist", err)
}
cmd.Result.Items = playlistDTOs
- return Json(200, cmd.Result)
+ return JSON(200, cmd.Result)
}
diff --git a/pkg/api/playlist_play.go b/pkg/api/playlist_play.go
index 1d059e06be5..e82c7b438b4 100644
--- a/pkg/api/playlist_play.go
+++ b/pkg/api/playlist_play.go
@@ -11,11 +11,11 @@ import (
"github.com/grafana/grafana/pkg/services/search"
)
-func populateDashboardsById(dashboardByIds []int64, dashboardIdOrder map[int64]int) (dtos.PlaylistDashboardsSlice, error) {
+func populateDashboardsByID(dashboardByIDs []int64, dashboardIDOrder map[int64]int) (dtos.PlaylistDashboardsSlice, error) {
result := make(dtos.PlaylistDashboardsSlice, 0)
- if len(dashboardByIds) > 0 {
- dashboardQuery := m.GetDashboardsQuery{DashboardIds: dashboardByIds}
+ if len(dashboardByIDs) > 0 {
+ dashboardQuery := m.GetDashboardsQuery{DashboardIds: dashboardByIDs}
if err := bus.Dispatch(&dashboardQuery); err != nil {
return result, err
}
@@ -26,7 +26,7 @@ func populateDashboardsById(dashboardByIds []int64, dashboardIdOrder map[int64]i
Slug: item.Slug,
Title: item.Title,
Uri: "db/" + item.Slug,
- Order: dashboardIdOrder[item.Id],
+ Order: dashboardIDOrder[item.Id],
})
}
}
@@ -34,29 +34,27 @@ func populateDashboardsById(dashboardByIds []int64, dashboardIdOrder map[int64]i
return result, nil
}
-func populateDashboardsByTag(orgId int64, signedInUser *m.SignedInUser, dashboardByTag []string, dashboardTagOrder map[string]int) dtos.PlaylistDashboardsSlice {
+func populateDashboardsByTag(orgID int64, signedInUser *m.SignedInUser, dashboardByTag []string, dashboardTagOrder map[string]int) dtos.PlaylistDashboardsSlice {
result := make(dtos.PlaylistDashboardsSlice, 0)
- if len(dashboardByTag) > 0 {
- for _, tag := range dashboardByTag {
- searchQuery := search.Query{
- Title: "",
- Tags: []string{tag},
- SignedInUser: signedInUser,
- Limit: 100,
- IsStarred: false,
- OrgId: orgId,
- }
+ for _, tag := range dashboardByTag {
+ searchQuery := search.Query{
+ Title: "",
+ Tags: []string{tag},
+ SignedInUser: signedInUser,
+ Limit: 100,
+ IsStarred: false,
+ OrgId: orgID,
+ }
- if err := bus.Dispatch(&searchQuery); err == nil {
- for _, item := range searchQuery.Result {
- result = append(result, dtos.PlaylistDashboard{
- Id: item.Id,
- Title: item.Title,
- Uri: item.Uri,
- Order: dashboardTagOrder[tag],
- })
- }
+ if err := bus.Dispatch(&searchQuery); err == nil {
+ for _, item := range searchQuery.Result {
+ result = append(result, dtos.PlaylistDashboard{
+ Id: item.Id,
+ Title: item.Title,
+ Uri: item.Uri,
+ Order: dashboardTagOrder[tag],
+ })
}
}
}
@@ -64,19 +62,19 @@ func populateDashboardsByTag(orgId int64, signedInUser *m.SignedInUser, dashboar
return result
}
-func LoadPlaylistDashboards(orgId int64, signedInUser *m.SignedInUser, playlistId int64) (dtos.PlaylistDashboardsSlice, error) {
- playlistItems, _ := LoadPlaylistItems(playlistId)
+func LoadPlaylistDashboards(orgID int64, signedInUser *m.SignedInUser, playlistID int64) (dtos.PlaylistDashboardsSlice, error) {
+ playlistItems, _ := LoadPlaylistItems(playlistID)
- dashboardByIds := make([]int64, 0)
+ dashboardByIDs := make([]int64, 0)
dashboardByTag := make([]string, 0)
- dashboardIdOrder := make(map[int64]int)
+ dashboardIDOrder := make(map[int64]int)
dashboardTagOrder := make(map[string]int)
for _, i := range playlistItems {
if i.Type == "dashboard_by_id" {
- dashboardId, _ := strconv.ParseInt(i.Value, 10, 64)
- dashboardByIds = append(dashboardByIds, dashboardId)
- dashboardIdOrder[dashboardId] = i.Order
+ dashboardID, _ := strconv.ParseInt(i.Value, 10, 64)
+ dashboardByIDs = append(dashboardByIDs, dashboardID)
+ dashboardIDOrder[dashboardID] = i.Order
}
if i.Type == "dashboard_by_tag" {
@@ -87,9 +85,9 @@ func LoadPlaylistDashboards(orgId int64, signedInUser *m.SignedInUser, playlistI
result := make(dtos.PlaylistDashboardsSlice, 0)
- var k, _ = populateDashboardsById(dashboardByIds, dashboardIdOrder)
+ var k, _ = populateDashboardsByID(dashboardByIDs, dashboardIDOrder)
result = append(result, k...)
- result = append(result, populateDashboardsByTag(orgId, signedInUser, dashboardByTag, dashboardTagOrder)...)
+ result = append(result, populateDashboardsByTag(orgID, signedInUser, dashboardByTag, dashboardTagOrder)...)
sort.Sort(result)
return result, nil
diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go
index b861a344c75..7d2d804c007 100644
--- a/pkg/api/pluginproxy/ds_proxy.go
+++ b/pkg/api/pluginproxy/ds_proxy.go
@@ -25,8 +25,8 @@ import (
)
var (
- logger log.Logger = log.New("data-proxy-log")
- client *http.Client = &http.Client{
+ logger = log.New("data-proxy-log")
+ client = &http.Client{
Timeout: time.Second * 30,
Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
}
@@ -49,14 +49,14 @@ type DataSourceProxy struct {
}
func NewDataSourceProxy(ds *m.DataSource, plugin *plugins.DataSourcePlugin, ctx *m.ReqContext, proxyPath string) *DataSourceProxy {
- targetUrl, _ := url.Parse(ds.Url)
+ targetURL, _ := url.Parse(ds.Url)
return &DataSourceProxy{
ds: ds,
plugin: plugin,
ctx: ctx,
proxyPath: proxyPath,
- targetUrl: targetUrl,
+ targetUrl: targetURL,
}
}
@@ -279,16 +279,16 @@ func (proxy *DataSourceProxy) applyRoute(req *http.Request) {
SecureJsonData: proxy.ds.SecureJsonData.Decrypt(),
}
- routeUrl, err := url.Parse(proxy.route.Url)
+ routeURL, err := url.Parse(proxy.route.Url)
if err != nil {
logger.Error("Error parsing plugin route url")
return
}
- req.URL.Scheme = routeUrl.Scheme
- req.URL.Host = routeUrl.Host
- req.Host = routeUrl.Host
- req.URL.Path = util.JoinUrlFragments(routeUrl.Path, proxy.proxyPath)
+ req.URL.Scheme = routeURL.Scheme
+ req.URL.Host = routeURL.Host
+ req.Host = routeURL.Host
+ req.URL.Path = util.JoinUrlFragments(routeURL.Path, proxy.proxyPath)
if err := addHeaders(&req.Header, proxy.route, data); err != nil {
logger.Error("Failed to render plugin headers", "error", err)
@@ -320,11 +320,11 @@ func (proxy *DataSourceProxy) getAccessToken(data templateData) (string, error)
params := make(url.Values)
for key, value := range proxy.route.TokenAuth.Params {
- if interpolatedParam, err := interpolateString(value, data); err != nil {
+ interpolatedParam, err := interpolateString(value, data)
+ if err != nil {
return "", err
- } else {
- params.Add(key, interpolatedParam)
}
+ params.Add(key, interpolatedParam)
}
getTokenReq, _ := http.NewRequest("POST", urlInterpolated, bytes.NewBufferString(params.Encode()))
@@ -354,13 +354,13 @@ func (proxy *DataSourceProxy) getAccessToken(data templateData) (string, error)
func interpolateString(text string, data templateData) (string, error) {
t, err := template.New("content").Parse(text)
if err != nil {
- return "", errors.New(fmt.Sprintf("Could not parse template %s.", text))
+ return "", fmt.Errorf("could not parse template %s", text)
}
var contentBuf bytes.Buffer
err = t.Execute(&contentBuf, data)
if err != nil {
- return "", errors.New(fmt.Sprintf("Failed to execute template %s.", text))
+ return "", fmt.Errorf("failed to execute template %s", text)
}
return contentBuf.String(), nil
diff --git a/pkg/api/pluginproxy/ds_proxy_test.go b/pkg/api/pluginproxy/ds_proxy_test.go
index 3cf67d9178a..3fc1392a851 100644
--- a/pkg/api/pluginproxy/ds_proxy_test.go
+++ b/pkg/api/pluginproxy/ds_proxy_test.go
@@ -107,8 +107,8 @@ func TestDSRouteRule(t *testing.T) {
proxy := NewDataSourceProxy(ds, plugin, ctx, "/render")
- requestUrl, _ := url.Parse("http://grafana.com/sub")
- req := http.Request{URL: requestUrl}
+ requestURL, _ := url.Parse("http://grafana.com/sub")
+ req := http.Request{URL: requestURL}
proxy.getDirector()(&req)
@@ -132,8 +132,8 @@ func TestDSRouteRule(t *testing.T) {
ctx := &m.ReqContext{}
proxy := NewDataSourceProxy(ds, plugin, ctx, "")
- requestUrl, _ := url.Parse("http://grafana.com/sub")
- req := http.Request{URL: requestUrl}
+ requestURL, _ := url.Parse("http://grafana.com/sub")
+ req := http.Request{URL: requestURL}
proxy.getDirector()(&req)
@@ -162,8 +162,8 @@ func TestDSRouteRule(t *testing.T) {
ctx := &m.ReqContext{}
proxy := NewDataSourceProxy(ds, plugin, ctx, "")
- requestUrl, _ := url.Parse("http://grafana.com/sub")
- req := http.Request{URL: requestUrl, Header: make(http.Header)}
+ requestURL, _ := url.Parse("http://grafana.com/sub")
+ req := http.Request{URL: requestURL, Header: make(http.Header)}
cookies := "grafana_user=admin; grafana_remember=99; grafana_sess=11; JSESSION_ID=test"
req.Header.Set("Cookie", cookies)
@@ -188,8 +188,8 @@ func TestDSRouteRule(t *testing.T) {
ctx := &m.ReqContext{}
proxy := NewDataSourceProxy(ds, plugin, ctx, "")
- requestUrl, _ := url.Parse("http://grafana.com/sub")
- req := http.Request{URL: requestUrl, Header: make(http.Header)}
+ requestURL, _ := url.Parse("http://grafana.com/sub")
+ req := http.Request{URL: requestURL, Header: make(http.Header)}
cookies := "grafana_user=admin; grafana_remember=99; grafana_sess=11; JSESSION_ID=test"
req.Header.Set("Cookie", cookies)
diff --git a/pkg/api/pluginproxy/pluginproxy.go b/pkg/api/pluginproxy/pluginproxy.go
index eb78250838a..ffbe470cb20 100644
--- a/pkg/api/pluginproxy/pluginproxy.go
+++ b/pkg/api/pluginproxy/pluginproxy.go
@@ -19,10 +19,10 @@ type templateData struct {
SecureJsonData map[string]string
}
-func getHeaders(route *plugins.AppPluginRoute, orgId int64, appId string) (http.Header, error) {
+func getHeaders(route *plugins.AppPluginRoute, orgId int64, appID string) (http.Header, error) {
result := http.Header{}
- query := m.GetPluginSettingByIdQuery{OrgId: orgId, PluginId: appId}
+ query := m.GetPluginSettingByIdQuery{OrgId: orgId, PluginId: appID}
if err := bus.Dispatch(&query); err != nil {
return nil, err
@@ -37,16 +37,16 @@ func getHeaders(route *plugins.AppPluginRoute, orgId int64, appId string) (http.
return result, err
}
-func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPluginRoute, appId string) *httputil.ReverseProxy {
- targetUrl, _ := url.Parse(route.Url)
+func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPluginRoute, appID string) *httputil.ReverseProxy {
+ targetURL, _ := url.Parse(route.Url)
director := func(req *http.Request) {
- req.URL.Scheme = targetUrl.Scheme
- req.URL.Host = targetUrl.Host
- req.Host = targetUrl.Host
+ req.URL.Scheme = targetURL.Scheme
+ req.URL.Host = targetURL.Host
+ req.Host = targetURL.Host
- req.URL.Path = util.JoinUrlFragments(targetUrl.Path, proxyPath)
+ req.URL.Path = util.JoinUrlFragments(targetURL.Path, proxyPath)
// clear cookie headers
req.Header.Del("Cookie")
@@ -80,7 +80,7 @@ func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPl
req.Header.Add("X-Grafana-Context", string(ctxJson))
if len(route.Headers) > 0 {
- headers, err := getHeaders(route, ctx.OrgId, appId)
+ headers, err := getHeaders(route, ctx.OrgId, appID)
if err != nil {
ctx.JsonApiErr(500, "Could not generate plugin route header", err)
return
diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go
index bc38f4a7775..f757f2b9adc 100644
--- a/pkg/api/plugins.go
+++ b/pkg/api/plugins.go
@@ -19,7 +19,7 @@ func GetPluginList(c *m.ReqContext) Response {
pluginSettingsMap, err := plugins.GetPluginSettings(c.OrgId)
if err != nil {
- return ApiError(500, "Failed to get list of plugins", err)
+ return Error(500, "Failed to get list of plugins", err)
}
result := make(dtos.PluginList, 0)
@@ -75,92 +75,94 @@ func GetPluginList(c *m.ReqContext) Response {
}
sort.Sort(result)
- return Json(200, result)
+ return JSON(200, result)
}
-func GetPluginSettingById(c *m.ReqContext) Response {
- pluginId := c.Params(":pluginId")
+func GetPluginSettingByID(c *m.ReqContext) Response {
+ pluginID := c.Params(":pluginId")
- if def, exists := plugins.Plugins[pluginId]; !exists {
- return ApiError(404, "Plugin not found, no installed plugin with that id", nil)
- } else {
-
- dto := &dtos.PluginSetting{
- Type: def.Type,
- Id: def.Id,
- Name: def.Name,
- Info: &def.Info,
- Dependencies: &def.Dependencies,
- Includes: def.Includes,
- BaseUrl: def.BaseUrl,
- Module: def.Module,
- DefaultNavUrl: def.DefaultNavUrl,
- LatestVersion: def.GrafanaNetVersion,
- HasUpdate: def.GrafanaNetHasUpdate,
- State: def.State,
- }
-
- query := m.GetPluginSettingByIdQuery{PluginId: pluginId, OrgId: c.OrgId}
- if err := bus.Dispatch(&query); err != nil {
- if err != m.ErrPluginSettingNotFound {
- return ApiError(500, "Failed to get login settings", nil)
- }
- } else {
- dto.Enabled = query.Result.Enabled
- dto.Pinned = query.Result.Pinned
- dto.JsonData = query.Result.JsonData
- }
-
- return Json(200, dto)
+ def, exists := plugins.Plugins[pluginID]
+ if !exists {
+ return Error(404, "Plugin not found, no installed plugin with that id", nil)
}
+
+ dto := &dtos.PluginSetting{
+ Type: def.Type,
+ Id: def.Id,
+ Name: def.Name,
+ Info: &def.Info,
+ Dependencies: &def.Dependencies,
+ Includes: def.Includes,
+ BaseUrl: def.BaseUrl,
+ Module: def.Module,
+ DefaultNavUrl: def.DefaultNavUrl,
+ LatestVersion: def.GrafanaNetVersion,
+ HasUpdate: def.GrafanaNetHasUpdate,
+ State: def.State,
+ }
+
+ query := m.GetPluginSettingByIdQuery{PluginId: pluginID, OrgId: c.OrgId}
+ if err := bus.Dispatch(&query); err != nil {
+ if err != m.ErrPluginSettingNotFound {
+ return Error(500, "Failed to get login settings", nil)
+ }
+ } else {
+ dto.Enabled = query.Result.Enabled
+ dto.Pinned = query.Result.Pinned
+ dto.JsonData = query.Result.JsonData
+ }
+
+ return JSON(200, dto)
}
func UpdatePluginSetting(c *m.ReqContext, cmd m.UpdatePluginSettingCmd) Response {
- pluginId := c.Params(":pluginId")
+ pluginID := c.Params(":pluginId")
cmd.OrgId = c.OrgId
- cmd.PluginId = pluginId
+ cmd.PluginId = pluginID
if _, ok := plugins.Apps[cmd.PluginId]; !ok {
- return ApiError(404, "Plugin not installed.", nil)
+ return Error(404, "Plugin not installed.", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to update plugin setting", err)
+ return Error(500, "Failed to update plugin setting", err)
}
- return ApiSuccess("Plugin settings updated")
+ return Success("Plugin settings updated")
}
func GetPluginDashboards(c *m.ReqContext) Response {
- pluginId := c.Params(":pluginId")
+ pluginID := c.Params(":pluginId")
- if list, err := plugins.GetPluginDashboards(c.OrgId, pluginId); err != nil {
+ list, err := plugins.GetPluginDashboards(c.OrgId, pluginID)
+ if err != nil {
if notfound, ok := err.(plugins.PluginNotFoundError); ok {
- return ApiError(404, notfound.Error(), nil)
+ return Error(404, notfound.Error(), nil)
}
- return ApiError(500, "Failed to get plugin dashboards", err)
- } else {
- return Json(200, list)
+ return Error(500, "Failed to get plugin dashboards", err)
}
+
+ return JSON(200, list)
}
func GetPluginMarkdown(c *m.ReqContext) Response {
- pluginId := c.Params(":pluginId")
+ pluginID := c.Params(":pluginId")
name := c.Params(":name")
- if content, err := plugins.GetPluginMarkdown(pluginId, name); err != nil {
+ content, err := plugins.GetPluginMarkdown(pluginID, name)
+ if err != nil {
if notfound, ok := err.(plugins.PluginNotFoundError); ok {
- return ApiError(404, notfound.Error(), nil)
+ return Error(404, notfound.Error(), nil)
}
- return ApiError(500, "Could not get markdown file", err)
- } else {
- resp := Respond(200, content)
- resp.Header("Content-Type", "text/plain; charset=utf-8")
- return resp
+ return Error(500, "Could not get markdown file", err)
}
+
+ resp := Respond(200, content)
+ resp.Header("Content-Type", "text/plain; charset=utf-8")
+ return resp
}
func ImportDashboard(c *m.ReqContext, apiCmd dtos.ImportDashboardCommand) Response {
@@ -176,8 +178,8 @@ func ImportDashboard(c *m.ReqContext, apiCmd dtos.ImportDashboardCommand) Respon
}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to import dashboard", err)
+ return Error(500, "Failed to import dashboard", err)
}
- return Json(200, cmd.Result)
+ return JSON(200, cmd.Result)
}
diff --git a/pkg/api/preferences.go b/pkg/api/preferences.go
index eb0ffa14b39..26695130975 100644
--- a/pkg/api/preferences.go
+++ b/pkg/api/preferences.go
@@ -13,10 +13,10 @@ func SetHomeDashboard(c *m.ReqContext, cmd m.SavePreferencesCommand) Response {
cmd.OrgId = c.OrgId
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to set home dashboard", err)
+ return Error(500, "Failed to set home dashboard", err)
}
- return ApiSuccess("Home dashboard set")
+ return Success("Home dashboard set")
}
// GET /api/user/preferences
@@ -24,20 +24,20 @@ func GetUserPreferences(c *m.ReqContext) Response {
return getPreferencesFor(c.OrgId, c.UserId)
}
-func getPreferencesFor(orgId int64, userId int64) Response {
- prefsQuery := m.GetPreferencesQuery{UserId: userId, OrgId: orgId}
+func getPreferencesFor(orgID int64, userID int64) Response {
+ prefsQuery := m.GetPreferencesQuery{UserId: userID, OrgId: orgID}
if err := bus.Dispatch(&prefsQuery); err != nil {
- return ApiError(500, "Failed to get preferences", err)
+ return Error(500, "Failed to get preferences", err)
}
dto := dtos.Prefs{
Theme: prefsQuery.Result.Theme,
- HomeDashboardId: prefsQuery.Result.HomeDashboardId,
+ HomeDashboardID: prefsQuery.Result.HomeDashboardId,
Timezone: prefsQuery.Result.Timezone,
}
- return Json(200, &dto)
+ return JSON(200, &dto)
}
// PUT /api/user/preferences
@@ -45,20 +45,20 @@ func UpdateUserPreferences(c *m.ReqContext, dtoCmd dtos.UpdatePrefsCmd) Response
return updatePreferencesFor(c.OrgId, c.UserId, &dtoCmd)
}
-func updatePreferencesFor(orgId int64, userId int64, dtoCmd *dtos.UpdatePrefsCmd) Response {
+func updatePreferencesFor(orgID int64, userID int64, dtoCmd *dtos.UpdatePrefsCmd) Response {
saveCmd := m.SavePreferencesCommand{
- UserId: userId,
- OrgId: orgId,
+ UserId: userID,
+ OrgId: orgID,
Theme: dtoCmd.Theme,
Timezone: dtoCmd.Timezone,
- HomeDashboardId: dtoCmd.HomeDashboardId,
+ HomeDashboardId: dtoCmd.HomeDashboardID,
}
if err := bus.Dispatch(&saveCmd); err != nil {
- return ApiError(500, "Failed to save preferences", err)
+ return Error(500, "Failed to save preferences", err)
}
- return ApiSuccess("Preferences updated")
+ return Success("Preferences updated")
}
// GET /api/org/preferences
diff --git a/pkg/api/quota.go b/pkg/api/quota.go
index f92acaf470f..d469f843930 100644
--- a/pkg/api/quota.go
+++ b/pkg/api/quota.go
@@ -8,60 +8,60 @@ import (
func GetOrgQuotas(c *m.ReqContext) Response {
if !setting.Quota.Enabled {
- return ApiError(404, "Quotas not enabled", nil)
+ return Error(404, "Quotas not enabled", nil)
}
query := m.GetOrgQuotasQuery{OrgId: c.ParamsInt64(":orgId")}
if err := bus.Dispatch(&query); err != nil {
- return ApiError(500, "Failed to get org quotas", err)
+ return Error(500, "Failed to get org quotas", err)
}
- return Json(200, query.Result)
+ return JSON(200, query.Result)
}
func UpdateOrgQuota(c *m.ReqContext, cmd m.UpdateOrgQuotaCmd) Response {
if !setting.Quota.Enabled {
- return ApiError(404, "Quotas not enabled", nil)
+ return Error(404, "Quotas not enabled", nil)
}
cmd.OrgId = c.ParamsInt64(":orgId")
cmd.Target = c.Params(":target")
if _, ok := setting.Quota.Org.ToMap()[cmd.Target]; !ok {
- return ApiError(404, "Invalid quota target", nil)
+ return Error(404, "Invalid quota target", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to update org quotas", err)
+ return Error(500, "Failed to update org quotas", err)
}
- return ApiSuccess("Organization quota updated")
+ return Success("Organization quota updated")
}
func GetUserQuotas(c *m.ReqContext) Response {
if !setting.Quota.Enabled {
- return ApiError(404, "Quotas not enabled", nil)
+ return Error(404, "Quotas not enabled", nil)
}
query := m.GetUserQuotasQuery{UserId: c.ParamsInt64(":id")}
if err := bus.Dispatch(&query); err != nil {
- return ApiError(500, "Failed to get org quotas", err)
+ return Error(500, "Failed to get org quotas", err)
}
- return Json(200, query.Result)
+ return JSON(200, query.Result)
}
func UpdateUserQuota(c *m.ReqContext, cmd m.UpdateUserQuotaCmd) Response {
if !setting.Quota.Enabled {
- return ApiError(404, "Quotas not enabled", nil)
+ return Error(404, "Quotas not enabled", nil)
}
cmd.UserId = c.ParamsInt64(":id")
cmd.Target = c.Params(":target")
if _, ok := setting.Quota.User.ToMap()[cmd.Target]; !ok {
- return ApiError(404, "Invalid quota target", nil)
+ return Error(404, "Invalid quota target", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to update org quotas", err)
+ return Error(500, "Failed to update org quotas", err)
}
- return ApiSuccess("Organization quota updated")
+ return Success("Organization quota updated")
}
diff --git a/pkg/api/search.go b/pkg/api/search.go
index c8a0a5592bb..8c2b708d5a2 100644
--- a/pkg/api/search.go
+++ b/pkg/api/search.go
@@ -25,19 +25,19 @@ func Search(c *m.ReqContext) {
permission = m.PERMISSION_EDIT
}
- dbids := make([]int64, 0)
+ dbIDs := make([]int64, 0)
for _, id := range c.QueryStrings("dashboardIds") {
- dashboardId, err := strconv.ParseInt(id, 10, 64)
+ dashboardID, err := strconv.ParseInt(id, 10, 64)
if err == nil {
- dbids = append(dbids, dashboardId)
+ dbIDs = append(dbIDs, dashboardID)
}
}
- folderIds := make([]int64, 0)
+ folderIDs := make([]int64, 0)
for _, id := range c.QueryStrings("folderIds") {
- folderId, err := strconv.ParseInt(id, 10, 64)
+ folderID, err := strconv.ParseInt(id, 10, 64)
if err == nil {
- folderIds = append(folderIds, folderId)
+ folderIDs = append(folderIDs, folderID)
}
}
@@ -48,9 +48,9 @@ func Search(c *m.ReqContext) {
Limit: limit,
IsStarred: starred == "true",
OrgId: c.OrgId,
- DashboardIds: dbids,
+ DashboardIds: dbIDs,
Type: dashboardType,
- FolderIds: folderIds,
+ FolderIds: folderIDs,
Permission: permission,
}
diff --git a/pkg/api/signup.go b/pkg/api/signup.go
index 838d2f9c0af..200a3ebc9d1 100644
--- a/pkg/api/signup.go
+++ b/pkg/api/signup.go
@@ -12,7 +12,7 @@ import (
// GET /api/user/signup/options
func GetSignUpOptions(c *m.ReqContext) Response {
- return Json(200, util.DynMap{
+ return JSON(200, util.DynMap{
"verifyEmailEnabled": setting.VerifyEmailEnabled,
"autoAssignOrg": setting.AutoAssignOrg,
})
@@ -21,12 +21,12 @@ func GetSignUpOptions(c *m.ReqContext) Response {
// POST /api/user/signup
func SignUp(c *m.ReqContext, form dtos.SignUpForm) Response {
if !setting.AllowUserSignUp {
- return ApiError(401, "User signup is disabled", nil)
+ return Error(401, "User signup is disabled", nil)
}
existing := m.GetUserByLoginQuery{LoginOrEmail: form.Email}
if err := bus.Dispatch(&existing); err == nil {
- return ApiError(422, "User with same email address already exists", nil)
+ return Error(422, "User with same email address already exists", nil)
}
cmd := m.CreateTempUserCommand{}
@@ -38,7 +38,7 @@ func SignUp(c *m.ReqContext, form dtos.SignUpForm) Response {
cmd.RemoteAddr = c.Req.RemoteAddr
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to create signup", err)
+ return Error(500, "Failed to create signup", err)
}
bus.Publish(&events.SignUpStarted{
@@ -48,12 +48,12 @@ func SignUp(c *m.ReqContext, form dtos.SignUpForm) Response {
metrics.M_Api_User_SignUpStarted.Inc()
- return Json(200, util.DynMap{"status": "SignUpCreated"})
+ return JSON(200, util.DynMap{"status": "SignUpCreated"})
}
func SignUpStep2(c *m.ReqContext, form dtos.SignUpStep2Form) Response {
if !setting.AllowUserSignUp {
- return ApiError(401, "User signup is disabled", nil)
+ return Error(401, "User signup is disabled", nil)
}
createUserCmd := m.CreateUserCommand{
@@ -75,12 +75,12 @@ func SignUpStep2(c *m.ReqContext, form dtos.SignUpStep2Form) Response {
// check if user exists
existing := m.GetUserByLoginQuery{LoginOrEmail: form.Email}
if err := bus.Dispatch(&existing); err == nil {
- return ApiError(401, "User with same email address already exists", nil)
+ return Error(401, "User with same email address already exists", nil)
}
// dispatch create command
if err := bus.Dispatch(&createUserCmd); err != nil {
- return ApiError(500, "Failed to create user", err)
+ return Error(500, "Failed to create user", err)
}
// publish signup event
@@ -98,7 +98,7 @@ func SignUpStep2(c *m.ReqContext, form dtos.SignUpStep2Form) Response {
// check for pending invites
invitesQuery := m.GetTempUsersQuery{Email: form.Email, Status: m.TmpUserInvitePending}
if err := bus.Dispatch(&invitesQuery); err != nil {
- return ApiError(500, "Failed to query database for invites", err)
+ return Error(500, "Failed to query database for invites", err)
}
apiResponse := util.DynMap{"message": "User sign up completed successfully", "code": "redirect-to-landing-page"}
@@ -112,7 +112,7 @@ func SignUpStep2(c *m.ReqContext, form dtos.SignUpStep2Form) Response {
loginUserWithUser(user, c)
metrics.M_Api_User_SignUpCompleted.Inc()
- return Json(200, apiResponse)
+ return JSON(200, apiResponse)
}
func verifyUserSignUpEmail(email string, code string) (bool, Response) {
@@ -120,14 +120,14 @@ func verifyUserSignUpEmail(email string, code string) (bool, Response) {
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrTempUserNotFound {
- return false, ApiError(404, "Invalid email verification code", nil)
+ return false, Error(404, "Invalid email verification code", nil)
}
- return false, ApiError(500, "Failed to read temp user", err)
+ return false, Error(500, "Failed to read temp user", err)
}
tempUser := query.Result
if tempUser.Email != email {
- return false, ApiError(404, "Email verification code does not match email", nil)
+ return false, Error(404, "Email verification code does not match email", nil)
}
return true, nil
diff --git a/pkg/api/stars.go b/pkg/api/stars.go
index 5361f64eea6..2c55b95dfbe 100644
--- a/pkg/api/stars.go
+++ b/pkg/api/stars.go
@@ -7,20 +7,20 @@ import (
func StarDashboard(c *m.ReqContext) Response {
if !c.IsSignedIn {
- return ApiError(412, "You need to sign in to star dashboards", nil)
+ return Error(412, "You need to sign in to star dashboards", nil)
}
cmd := m.StarDashboardCommand{UserId: c.UserId, DashboardId: c.ParamsInt64(":id")}
if cmd.DashboardId <= 0 {
- return ApiError(400, "Missing dashboard id", nil)
+ return Error(400, "Missing dashboard id", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to star dashboard", err)
+ return Error(500, "Failed to star dashboard", err)
}
- return ApiSuccess("Dashboard starred!")
+ return Success("Dashboard starred!")
}
func UnstarDashboard(c *m.ReqContext) Response {
@@ -28,12 +28,12 @@ func UnstarDashboard(c *m.ReqContext) Response {
cmd := m.UnstarDashboardCommand{UserId: c.UserId, DashboardId: c.ParamsInt64(":id")}
if cmd.DashboardId <= 0 {
- return ApiError(400, "Missing dashboard id", nil)
+ return Error(400, "Missing dashboard id", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to unstar dashboard", err)
+ return Error(500, "Failed to unstar dashboard", err)
}
- return ApiSuccess("Dashboard unstarred")
+ return Success("Dashboard unstarred")
}
diff --git a/pkg/api/team.go b/pkg/api/team.go
index 316adfc4e7c..9919305881b 100644
--- a/pkg/api/team.go
+++ b/pkg/api/team.go
@@ -12,12 +12,12 @@ func CreateTeam(c *m.ReqContext, cmd m.CreateTeamCommand) Response {
cmd.OrgId = c.OrgId
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrTeamNameTaken {
- return ApiError(409, "Team name taken", err)
+ return Error(409, "Team name taken", err)
}
- return ApiError(500, "Failed to create Team", err)
+ return Error(500, "Failed to create Team", err)
}
- return Json(200, &util.DynMap{
+ return JSON(200, &util.DynMap{
"teamId": cmd.Result.Id,
"message": "Team created",
})
@@ -29,23 +29,23 @@ func UpdateTeam(c *m.ReqContext, cmd m.UpdateTeamCommand) Response {
cmd.Id = c.ParamsInt64(":teamId")
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrTeamNameTaken {
- return ApiError(400, "Team name taken", err)
+ return Error(400, "Team name taken", err)
}
- return ApiError(500, "Failed to update Team", err)
+ return Error(500, "Failed to update Team", err)
}
- return ApiSuccess("Team updated")
+ return Success("Team updated")
}
// DELETE /api/teams/:teamId
-func DeleteTeamById(c *m.ReqContext) Response {
+func DeleteTeamByID(c *m.ReqContext) Response {
if err := bus.Dispatch(&m.DeleteTeamCommand{OrgId: c.OrgId, Id: c.ParamsInt64(":teamId")}); err != nil {
if err == m.ErrTeamNotFound {
- return ApiError(404, "Failed to delete Team. ID not found", nil)
+ return Error(404, "Failed to delete Team. ID not found", nil)
}
- return ApiError(500, "Failed to update Team", err)
+ return Error(500, "Failed to update Team", err)
}
- return ApiSuccess("Team deleted")
+ return Success("Team deleted")
}
// GET /api/teams/search
@@ -68,7 +68,7 @@ func SearchTeams(c *m.ReqContext) Response {
}
if err := bus.Dispatch(&query); err != nil {
- return ApiError(500, "Failed to search Teams", err)
+ return Error(500, "Failed to search Teams", err)
}
for _, team := range query.Result.Teams {
@@ -78,20 +78,20 @@ func SearchTeams(c *m.ReqContext) Response {
query.Result.Page = page
query.Result.PerPage = perPage
- return Json(200, query.Result)
+ return JSON(200, query.Result)
}
// GET /api/teams/:teamId
-func GetTeamById(c *m.ReqContext) Response {
+func GetTeamByID(c *m.ReqContext) Response {
query := m.GetTeamByIdQuery{OrgId: c.OrgId, Id: c.ParamsInt64(":teamId")}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrTeamNotFound {
- return ApiError(404, "Team not found", err)
+ return Error(404, "Team not found", err)
}
- return ApiError(500, "Failed to get Team", err)
+ return Error(500, "Failed to get Team", err)
}
- return Json(200, &query.Result)
+ return JSON(200, &query.Result)
}
diff --git a/pkg/api/team_members.go b/pkg/api/team_members.go
index 4fb05b016e3..60a170a8c31 100644
--- a/pkg/api/team_members.go
+++ b/pkg/api/team_members.go
@@ -12,14 +12,14 @@ func GetTeamMembers(c *m.ReqContext) Response {
query := m.GetTeamMembersQuery{OrgId: c.OrgId, TeamId: c.ParamsInt64(":teamId")}
if err := bus.Dispatch(&query); err != nil {
- return ApiError(500, "Failed to get Team Members", err)
+ return Error(500, "Failed to get Team Members", err)
}
for _, member := range query.Result {
member.AvatarUrl = dtos.GetGravatarUrl(member.Email)
}
- return Json(200, query.Result)
+ return JSON(200, query.Result)
}
// POST /api/teams/:teamId/members
@@ -29,17 +29,17 @@ func AddTeamMember(c *m.ReqContext, cmd m.AddTeamMemberCommand) Response {
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrTeamNotFound {
- return ApiError(404, "Team not found", nil)
+ return Error(404, "Team not found", nil)
}
if err == m.ErrTeamMemberAlreadyAdded {
- return ApiError(400, "User is already added to this team", nil)
+ return Error(400, "User is already added to this team", nil)
}
- return ApiError(500, "Failed to add Member to Team", err)
+ return Error(500, "Failed to add Member to Team", err)
}
- return Json(200, &util.DynMap{
+ return JSON(200, &util.DynMap{
"message": "Member added to Team",
})
}
@@ -48,14 +48,14 @@ func AddTeamMember(c *m.ReqContext, cmd m.AddTeamMemberCommand) Response {
func RemoveTeamMember(c *m.ReqContext) Response {
if err := bus.Dispatch(&m.RemoveTeamMemberCommand{OrgId: c.OrgId, TeamId: c.ParamsInt64(":teamId"), UserId: c.ParamsInt64(":userId")}); err != nil {
if err == m.ErrTeamNotFound {
- return ApiError(404, "Team not found", nil)
+ return Error(404, "Team not found", nil)
}
if err == m.ErrTeamMemberNotFound {
- return ApiError(404, "Team member not found", nil)
+ return Error(404, "Team member not found", nil)
}
- return ApiError(500, "Failed to remove Member from Team", err)
+ return Error(500, "Failed to remove Member from Team", err)
}
- return ApiSuccess("Team Member removed")
+ return Success("Team Member removed")
}
diff --git a/pkg/api/user.go b/pkg/api/user.go
index b8483316b9d..725c623575f 100644
--- a/pkg/api/user.go
+++ b/pkg/api/user.go
@@ -14,21 +14,21 @@ func GetSignedInUser(c *m.ReqContext) Response {
}
// GET /api/users/:id
-func GetUserById(c *m.ReqContext) Response {
+func GetUserByID(c *m.ReqContext) Response {
return getUserUserProfile(c.ParamsInt64(":id"))
}
-func getUserUserProfile(userId int64) Response {
- query := m.GetUserProfileQuery{UserId: userId}
+func getUserUserProfile(userID int64) Response {
+ query := m.GetUserProfileQuery{UserId: userID}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrUserNotFound {
- return ApiError(404, m.ErrUserNotFound.Error(), nil)
+ return Error(404, m.ErrUserNotFound.Error(), nil)
}
- return ApiError(500, "Failed to get user", err)
+ return Error(500, "Failed to get user", err)
}
- return Json(200, query.Result)
+ return JSON(200, query.Result)
}
// GET /api/users/lookup
@@ -36,9 +36,9 @@ func GetUserByLoginOrEmail(c *m.ReqContext) Response {
query := m.GetUserByLoginQuery{LoginOrEmail: c.Query("loginOrEmail")}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrUserNotFound {
- return ApiError(404, m.ErrUserNotFound.Error(), nil)
+ return Error(404, m.ErrUserNotFound.Error(), nil)
}
- return ApiError(500, "Failed to get user", err)
+ return Error(500, "Failed to get user", err)
}
user := query.Result
result := m.UserProfileDTO{
@@ -50,17 +50,17 @@ func GetUserByLoginOrEmail(c *m.ReqContext) Response {
IsGrafanaAdmin: user.IsAdmin,
OrgId: user.OrgId,
}
- return Json(200, &result)
+ return JSON(200, &result)
}
// POST /api/user
func UpdateSignedInUser(c *m.ReqContext, cmd m.UpdateUserCommand) Response {
if setting.AuthProxyEnabled {
if setting.AuthProxyHeaderProperty == "email" && cmd.Email != c.Email {
- return ApiError(400, "Not allowed to change email when auth proxy is using email property", nil)
+ return Error(400, "Not allowed to change email when auth proxy is using email property", nil)
}
if setting.AuthProxyHeaderProperty == "username" && cmd.Login != c.Login {
- return ApiError(400, "Not allowed to change username when auth proxy is using username property", nil)
+ return Error(400, "Not allowed to change username when auth proxy is using username property", nil)
}
}
cmd.UserId = c.UserId
@@ -75,35 +75,35 @@ func UpdateUser(c *m.ReqContext, cmd m.UpdateUserCommand) Response {
//POST /api/users/:id/using/:orgId
func UpdateUserActiveOrg(c *m.ReqContext) Response {
- userId := c.ParamsInt64(":id")
- orgId := c.ParamsInt64(":orgId")
+ userID := c.ParamsInt64(":id")
+ orgID := c.ParamsInt64(":orgId")
- if !validateUsingOrg(userId, orgId) {
- return ApiError(401, "Not a valid organization", nil)
+ if !validateUsingOrg(userID, orgID) {
+ return Error(401, "Not a valid organization", nil)
}
- cmd := m.SetUsingOrgCommand{UserId: userId, OrgId: orgId}
+ cmd := m.SetUsingOrgCommand{UserId: userID, OrgId: orgID}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to change active organization", err)
+ return Error(500, "Failed to change active organization", err)
}
- return ApiSuccess("Active organization changed")
+ return Success("Active organization changed")
}
func handleUpdateUser(cmd m.UpdateUserCommand) Response {
if len(cmd.Login) == 0 {
cmd.Login = cmd.Email
if len(cmd.Login) == 0 {
- return ApiError(400, "Validation error, need to specify either username or email", nil)
+ return Error(400, "Validation error, need to specify either username or email", nil)
}
}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to update user", err)
+ return Error(500, "Failed to update user", err)
}
- return ApiSuccess("User updated")
+ return Success("User updated")
}
// GET /api/user/orgs
@@ -116,18 +116,18 @@ func GetUserOrgList(c *m.ReqContext) Response {
return getUserOrgList(c.ParamsInt64(":id"))
}
-func getUserOrgList(userId int64) Response {
- query := m.GetUserOrgListQuery{UserId: userId}
+func getUserOrgList(userID int64) Response {
+ query := m.GetUserOrgListQuery{UserId: userID}
if err := bus.Dispatch(&query); err != nil {
- return ApiError(500, "Failed to get user organizations", err)
+ return Error(500, "Failed to get user organizations", err)
}
- return Json(200, query.Result)
+ return JSON(200, query.Result)
}
-func validateUsingOrg(userId int64, orgId int64) bool {
- query := m.GetUserOrgListQuery{UserId: userId}
+func validateUsingOrg(userID int64, orgID int64) bool {
+ query := m.GetUserOrgListQuery{UserId: userID}
if err := bus.Dispatch(&query); err != nil {
return false
@@ -136,7 +136,7 @@ func validateUsingOrg(userId int64, orgId int64) bool {
// validate that the org id in the list
valid := false
for _, other := range query.Result {
- if other.OrgId == orgId {
+ if other.OrgId == orgID {
valid = true
}
}
@@ -146,30 +146,30 @@ func validateUsingOrg(userId int64, orgId int64) bool {
// POST /api/user/using/:id
func UserSetUsingOrg(c *m.ReqContext) Response {
- orgId := c.ParamsInt64(":id")
+ orgID := c.ParamsInt64(":id")
- if !validateUsingOrg(c.UserId, orgId) {
- return ApiError(401, "Not a valid organization", nil)
+ if !validateUsingOrg(c.UserId, orgID) {
+ return Error(401, "Not a valid organization", nil)
}
- cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgId}
+ cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgID}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to change active organization", err)
+ return Error(500, "Failed to change active organization", err)
}
- return ApiSuccess("Active organization changed")
+ return Success("Active organization changed")
}
// GET /profile/switch-org/:id
func ChangeActiveOrgAndRedirectToHome(c *m.ReqContext) {
- orgId := c.ParamsInt64(":id")
+ orgID := c.ParamsInt64(":id")
- if !validateUsingOrg(c.UserId, orgId) {
+ if !validateUsingOrg(c.UserId, orgID) {
NotFoundHandler(c)
}
- cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgId}
+ cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgID}
if err := bus.Dispatch(&cmd); err != nil {
NotFoundHandler(c)
@@ -180,53 +180,53 @@ func ChangeActiveOrgAndRedirectToHome(c *m.ReqContext) {
func ChangeUserPassword(c *m.ReqContext, cmd m.ChangeUserPasswordCommand) Response {
if setting.LdapEnabled || setting.AuthProxyEnabled {
- return ApiError(400, "Not allowed to change password when LDAP or Auth Proxy is enabled", nil)
+ return Error(400, "Not allowed to change password when LDAP or Auth Proxy is enabled", nil)
}
userQuery := m.GetUserByIdQuery{Id: c.UserId}
if err := bus.Dispatch(&userQuery); err != nil {
- return ApiError(500, "Could not read user from database", err)
+ return Error(500, "Could not read user from database", err)
}
passwordHashed := util.EncodePassword(cmd.OldPassword, userQuery.Result.Salt)
if passwordHashed != userQuery.Result.Password {
- return ApiError(401, "Invalid old password", nil)
+ return Error(401, "Invalid old password", nil)
}
password := m.Password(cmd.NewPassword)
if password.IsWeak() {
- return ApiError(400, "New password is too short", nil)
+ return Error(400, "New password is too short", nil)
}
cmd.UserId = c.UserId
cmd.NewPassword = util.EncodePassword(cmd.NewPassword, userQuery.Result.Salt)
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to change user password", err)
+ return Error(500, "Failed to change user password", err)
}
- return ApiSuccess("User password changed")
+ return Success("User password changed")
}
// GET /api/users
func SearchUsers(c *m.ReqContext) Response {
query, err := searchUser(c)
if err != nil {
- return ApiError(500, "Failed to fetch users", err)
+ return Error(500, "Failed to fetch users", err)
}
- return Json(200, query.Result.Users)
+ return JSON(200, query.Result.Users)
}
// GET /api/users/search
func SearchUsersWithPaging(c *m.ReqContext) Response {
query, err := searchUser(c)
if err != nil {
- return ApiError(500, "Failed to fetch users", err)
+ return Error(500, "Failed to fetch users", err)
}
- return Json(200, query.Result)
+ return JSON(200, query.Result)
}
func searchUser(c *m.ReqContext) (*m.SearchUsersQuery, error) {
@@ -269,10 +269,10 @@ func SetHelpFlag(c *m.ReqContext) Response {
}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to update help flag", err)
+ return Error(500, "Failed to update help flag", err)
}
- return Json(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1})
+ return JSON(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1})
}
func ClearHelpFlags(c *m.ReqContext) Response {
@@ -282,8 +282,8 @@ func ClearHelpFlags(c *m.ReqContext) Response {
}
if err := bus.Dispatch(&cmd); err != nil {
- return ApiError(500, "Failed to update help flag", err)
+ return Error(500, "Failed to update help flag", err)
}
- return Json(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1})
+ return JSON(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1})
}
diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go
index 8ed3196e4ad..5bbf43087ec 100644
--- a/pkg/cmd/grafana-server/server.go
+++ b/pkg/cmd/grafana-server/server.go
@@ -49,7 +49,7 @@ type GrafanaServerImpl struct {
childRoutines *errgroup.Group
log log.Logger
- httpServer *api.HttpServer
+ httpServer *api.HTTPServer
}
func (g *GrafanaServerImpl) Start() error {
@@ -120,7 +120,7 @@ func (g *GrafanaServerImpl) initLogging() {
}
func (g *GrafanaServerImpl) startHttpServer() error {
- g.httpServer = api.NewHttpServer()
+ g.httpServer = api.NewHTTPServer()
err := g.httpServer.Start(g.context)
diff --git a/pkg/middleware/auth.go b/pkg/middleware/auth.go
index d6c377bc9ac..37e79c01071 100644
--- a/pkg/middleware/auth.go
+++ b/pkg/middleware/auth.go
@@ -17,10 +17,10 @@ type AuthOptions struct {
}
func getRequestUserId(c *m.ReqContext) int64 {
- userId := c.Session.Get(session.SESS_KEY_USERID)
+ userID := c.Session.Get(session.SESS_KEY_USERID)
- if userId != nil {
- return userId.(int64)
+ if userID != nil {
+ return userID.(int64)
}
return 0
diff --git a/pkg/middleware/auth_proxy.go b/pkg/middleware/auth_proxy.go
index 4d2a7a98908..e1801404453 100644
--- a/pkg/middleware/auth_proxy.go
+++ b/pkg/middleware/auth_proxy.go
@@ -14,7 +14,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
-func initContextWithAuthProxy(ctx *m.ReqContext, orgId int64) bool {
+func initContextWithAuthProxy(ctx *m.ReqContext, orgID int64) bool {
if !setting.AuthProxyEnabled {
return false
}
@@ -31,31 +31,31 @@ func initContextWithAuthProxy(ctx *m.ReqContext, orgId int64) bool {
}
query := getSignedInUserQueryForProxyAuth(proxyHeaderValue)
- query.OrgId = orgId
+ query.OrgId = orgID
if err := bus.Dispatch(query); err != nil {
if err != m.ErrUserNotFound {
ctx.Handle(500, "Failed to find user specified in auth proxy header", err)
return true
}
- if setting.AuthProxyAutoSignUp {
- cmd := getCreateUserCommandForProxyAuth(proxyHeaderValue)
- if setting.LdapEnabled {
- cmd.SkipOrgSetup = true
- }
-
- if err := bus.Dispatch(cmd); err != nil {
- ctx.Handle(500, "Failed to create user specified in auth proxy header", err)
- return true
- }
- query = &m.GetSignedInUserQuery{UserId: cmd.Result.Id, OrgId: orgId}
- if err := bus.Dispatch(query); err != nil {
- ctx.Handle(500, "Failed find user after creation", err)
- return true
- }
- } else {
+ if !setting.AuthProxyAutoSignUp {
return false
}
+
+ cmd := getCreateUserCommandForProxyAuth(proxyHeaderValue)
+ if setting.LdapEnabled {
+ cmd.SkipOrgSetup = true
+ }
+
+ if err := bus.Dispatch(cmd); err != nil {
+ ctx.Handle(500, "Failed to create user specified in auth proxy header", err)
+ return true
+ }
+ query = &m.GetSignedInUserQuery{UserId: cmd.Result.Id, OrgId: orgID}
+ if err := bus.Dispatch(query); err != nil {
+ ctx.Handle(500, "Failed find user after creation", err)
+ return true
+ }
}
// initialize session
@@ -96,50 +96,53 @@ func initContextWithAuthProxy(ctx *m.ReqContext, orgId int64) bool {
}
var syncGrafanaUserWithLdapUser = func(ctx *m.ReqContext, query *m.GetSignedInUserQuery) error {
- if setting.LdapEnabled {
- expireEpoch := time.Now().Add(time.Duration(-setting.AuthProxyLdapSyncTtl) * time.Minute).Unix()
+ if !setting.LdapEnabled {
+ return nil
+ }
- var lastLdapSync int64
- if lastLdapSyncInSession := ctx.Session.Get(session.SESS_KEY_LASTLDAPSYNC); lastLdapSyncInSession != nil {
- lastLdapSync = lastLdapSyncInSession.(int64)
- }
+ expireEpoch := time.Now().Add(time.Duration(-setting.AuthProxyLdapSyncTtl) * time.Minute).Unix()
- if lastLdapSync < expireEpoch {
- ldapCfg := login.LdapCfg
+ var lastLdapSync int64
+ if lastLdapSyncInSession := ctx.Session.Get(session.SESS_KEY_LASTLDAPSYNC); lastLdapSyncInSession != nil {
+ lastLdapSync = lastLdapSyncInSession.(int64)
+ }
- for _, server := range ldapCfg.Servers {
- author := login.NewLdapAuthenticator(server)
- if err := author.SyncSignedInUser(query.Result); err != nil {
- return err
- }
+ if lastLdapSync < expireEpoch {
+ ldapCfg := login.LdapCfg
+
+ for _, server := range ldapCfg.Servers {
+ author := login.NewLdapAuthenticator(server)
+ if err := author.SyncSignedInUser(query.Result); err != nil {
+ return err
}
-
- ctx.Session.Set(session.SESS_KEY_LASTLDAPSYNC, time.Now().Unix())
}
+
+ ctx.Session.Set(session.SESS_KEY_LASTLDAPSYNC, time.Now().Unix())
}
return nil
}
func checkAuthenticationProxy(ctx *m.ReqContext, proxyHeaderValue string) error {
- if len(strings.TrimSpace(setting.AuthProxyWhitelist)) > 0 {
- proxies := strings.Split(setting.AuthProxyWhitelist, ",")
- remoteAddrSplit := strings.Split(ctx.Req.RemoteAddr, ":")
- sourceIP := remoteAddrSplit[0]
+ if len(strings.TrimSpace(setting.AuthProxyWhitelist)) == 0 {
+ return nil
+ }
+ proxies := strings.Split(setting.AuthProxyWhitelist, ",")
+ remoteAddrSplit := strings.Split(ctx.Req.RemoteAddr, ":")
+ sourceIP := remoteAddrSplit[0]
- found := false
- for _, proxyIP := range proxies {
- if sourceIP == strings.TrimSpace(proxyIP) {
- found = true
- break
- }
+ found := false
+ for _, proxyIP := range proxies {
+ if sourceIP == strings.TrimSpace(proxyIP) {
+ found = true
+ break
}
+ }
- if !found {
- msg := fmt.Sprintf("Request for user (%s) is not from the authentication proxy", proxyHeaderValue)
- err := errors.New(msg)
- return err
- }
+ if !found {
+ msg := fmt.Sprintf("Request for user (%s) is not from the authentication proxy", proxyHeaderValue)
+ err := errors.New(msg)
+ return err
}
return nil
diff --git a/pkg/middleware/dashboard_redirect.go b/pkg/middleware/dashboard_redirect.go
index 7c2af548a8f..2edf04d543e 100644
--- a/pkg/middleware/dashboard_redirect.go
+++ b/pkg/middleware/dashboard_redirect.go
@@ -10,8 +10,8 @@ import (
"gopkg.in/macaron.v1"
)
-func getDashboardUrlBySlug(orgId int64, slug string) (string, error) {
- query := m.GetDashboardQuery{Slug: slug, OrgId: orgId}
+func getDashboardURLBySlug(orgID int64, slug string) (string, error) {
+ query := m.GetDashboardQuery{Slug: slug, OrgId: orgID}
if err := bus.Dispatch(&query); err != nil {
return "", m.ErrDashboardNotFound
@@ -20,12 +20,12 @@ func getDashboardUrlBySlug(orgId int64, slug string) (string, error) {
return m.GetDashboardUrl(query.Result.Uid, query.Result.Slug), nil
}
-func RedirectFromLegacyDashboardUrl() macaron.Handler {
+func RedirectFromLegacyDashboardURL() macaron.Handler {
return func(c *m.ReqContext) {
slug := c.Params("slug")
if slug != "" {
- if url, err := getDashboardUrlBySlug(c.OrgId, slug); err == nil {
+ if url, err := getDashboardURLBySlug(c.OrgId, slug); err == nil {
url = fmt.Sprintf("%s?%s", url, c.Req.URL.RawQuery)
c.Redirect(url, 301)
return
@@ -34,13 +34,13 @@ func RedirectFromLegacyDashboardUrl() macaron.Handler {
}
}
-func RedirectFromLegacyDashboardSoloUrl() macaron.Handler {
+func RedirectFromLegacyDashboardSoloURL() macaron.Handler {
return func(c *m.ReqContext) {
slug := c.Params("slug")
renderRequest := c.QueryBool("render")
if slug != "" {
- if url, err := getDashboardUrlBySlug(c.OrgId, slug); err == nil {
+ if url, err := getDashboardURLBySlug(c.OrgId, slug); err == nil {
if renderRequest && strings.Contains(url, setting.AppSubUrl) {
url = strings.Replace(url, setting.AppSubUrl, "", 1)
}
diff --git a/pkg/middleware/dashboard_redirect_test.go b/pkg/middleware/dashboard_redirect_test.go
index 0af06347ed0..1e4d8293cae 100644
--- a/pkg/middleware/dashboard_redirect_test.go
+++ b/pkg/middleware/dashboard_redirect_test.go
@@ -13,8 +13,8 @@ import (
func TestMiddlewareDashboardRedirect(t *testing.T) {
Convey("Given the dashboard redirect middleware", t, func() {
bus.ClearBusHandlers()
- redirectFromLegacyDashboardUrl := RedirectFromLegacyDashboardUrl()
- redirectFromLegacyDashboardSoloUrl := RedirectFromLegacyDashboardSoloUrl()
+ redirectFromLegacyDashboardUrl := RedirectFromLegacyDashboardURL()
+ redirectFromLegacyDashboardSoloUrl := RedirectFromLegacyDashboardSoloURL()
fakeDash := m.NewDashboard("Child dash")
fakeDash.Id = 1
@@ -34,9 +34,9 @@ func TestMiddlewareDashboardRedirect(t *testing.T) {
Convey("Should redirect to new dashboard url with a 301 Moved Permanently", func() {
So(sc.resp.Code, ShouldEqual, 301)
- redirectUrl, _ := sc.resp.Result().Location()
- So(redirectUrl.Path, ShouldEqual, m.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug))
- So(len(redirectUrl.Query()), ShouldEqual, 2)
+ redirectURL, _ := sc.resp.Result().Location()
+ So(redirectURL.Path, ShouldEqual, m.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug))
+ So(len(redirectURL.Query()), ShouldEqual, 2)
})
})
@@ -47,11 +47,11 @@ func TestMiddlewareDashboardRedirect(t *testing.T) {
Convey("Should redirect to new dashboard url with a 301 Moved Permanently", func() {
So(sc.resp.Code, ShouldEqual, 301)
- redirectUrl, _ := sc.resp.Result().Location()
- expectedUrl := m.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug)
- expectedUrl = strings.Replace(expectedUrl, "/d/", "/d-solo/", 1)
- So(redirectUrl.Path, ShouldEqual, expectedUrl)
- So(len(redirectUrl.Query()), ShouldEqual, 2)
+ redirectURL, _ := sc.resp.Result().Location()
+ expectedURL := m.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug)
+ expectedURL = strings.Replace(expectedURL, "/d/", "/d-solo/", 1)
+ So(redirectURL.Path, ShouldEqual, expectedURL)
+ So(len(redirectURL.Query()), ShouldEqual, 2)
})
})
})
diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go
index 83efc65d4d4..c8e9e535cfa 100644
--- a/pkg/middleware/middleware_test.go
+++ b/pkg/middleware/middleware_test.go
@@ -338,7 +338,7 @@ func middlewareScenario(desc string, fn scenarioFunc) {
sc.m.Use(GetContextHandler())
// mock out gc goroutine
session.StartSessionGC = func() {}
- sc.m.Use(Sessioner(&ms.Options{}))
+ sc.m.Use(Sessioner(&ms.Options{}, 0))
sc.m.Use(OrgRedirect())
sc.m.Use(AddDefaultResponseHeaders())
diff --git a/pkg/middleware/recovery_test.go b/pkg/middleware/recovery_test.go
index 32545b7caca..4bbedbc3b21 100644
--- a/pkg/middleware/recovery_test.go
+++ b/pkg/middleware/recovery_test.go
@@ -14,10 +14,10 @@ import (
func TestRecoveryMiddleware(t *testing.T) {
Convey("Given an api route that panics", t, func() {
- apiUrl := "/api/whatever"
- recoveryScenario("recovery middleware should return json", apiUrl, func(sc *scenarioContext) {
+ apiURL := "/api/whatever"
+ recoveryScenario("recovery middleware should return json", apiURL, func(sc *scenarioContext) {
sc.handlerFunc = PanicHandler
- sc.fakeReq("GET", apiUrl).exec()
+ sc.fakeReq("GET", apiURL).exec()
sc.req.Header.Add("content-type", "application/json")
So(sc.resp.Code, ShouldEqual, 500)
@@ -27,10 +27,10 @@ func TestRecoveryMiddleware(t *testing.T) {
})
Convey("Given a non-api route that panics", t, func() {
- apiUrl := "/whatever"
- recoveryScenario("recovery middleware should return html", apiUrl, func(sc *scenarioContext) {
+ apiURL := "/whatever"
+ recoveryScenario("recovery middleware should return html", apiURL, func(sc *scenarioContext) {
sc.handlerFunc = PanicHandler
- sc.fakeReq("GET", apiUrl).exec()
+ sc.fakeReq("GET", apiURL).exec()
So(sc.resp.Code, ShouldEqual, 500)
So(sc.resp.Header().Get("content-type"), ShouldEqual, "text/html; charset=UTF-8")
@@ -63,7 +63,7 @@ func recoveryScenario(desc string, url string, fn scenarioFunc) {
sc.m.Use(GetContextHandler())
// mock out gc goroutine
session.StartSessionGC = func() {}
- sc.m.Use(Sessioner(&ms.Options{}))
+ sc.m.Use(Sessioner(&ms.Options{}, 0))
sc.m.Use(OrgRedirect())
sc.m.Use(AddDefaultResponseHeaders())
diff --git a/pkg/middleware/render_auth.go b/pkg/middleware/render_auth.go
index 225645e659e..6c338becbda 100644
--- a/pkg/middleware/render_auth.go
+++ b/pkg/middleware/render_auth.go
@@ -19,16 +19,16 @@ func initContextWithRenderAuth(ctx *m.ReqContext) bool {
renderKeysLock.Lock()
defer renderKeysLock.Unlock()
- if renderUser, exists := renderKeys[key]; !exists {
+ renderUser, exists := renderKeys[key]
+ if !exists {
ctx.JsonApiErr(401, "Invalid Render Key", nil)
return true
- } else {
-
- ctx.IsSignedIn = true
- ctx.SignedInUser = renderUser
- ctx.IsRenderCall = true
- return true
}
+
+ ctx.IsSignedIn = true
+ ctx.SignedInUser = renderUser
+ ctx.IsRenderCall = true
+ return true
}
type renderContextFunc func(key string) (string, error)
diff --git a/pkg/middleware/session.go b/pkg/middleware/session.go
index 5654a42cb7d..19cfa368b49 100644
--- a/pkg/middleware/session.go
+++ b/pkg/middleware/session.go
@@ -8,8 +8,8 @@ import (
"github.com/grafana/grafana/pkg/services/session"
)
-func Sessioner(options *ms.Options) macaron.Handler {
- session.Init(options)
+func Sessioner(options *ms.Options, sessionConnMaxLifetime int64) macaron.Handler {
+ session.Init(options, sessionConnMaxLifetime)
return func(ctx *m.ReqContext) {
ctx.Next()
diff --git a/pkg/services/alerting/engine.go b/pkg/services/alerting/engine.go
index 4448a5cb978..a6f97333d1b 100644
--- a/pkg/services/alerting/engine.go
+++ b/pkg/services/alerting/engine.go
@@ -86,17 +86,63 @@ func (e *Engine) runJobDispatcher(grafanaCtx context.Context) error {
case <-grafanaCtx.Done():
return dispatcherGroup.Wait()
case job := <-e.execQueue:
- dispatcherGroup.Go(func() error { return e.processJob(alertCtx, job) })
+ dispatcherGroup.Go(func() error { return e.processJobWithRetry(alertCtx, job) })
}
}
}
var (
unfinishedWorkTimeout time.Duration = time.Second * 5
- alertTimeout time.Duration = time.Second * 30
+ // TODO: Make alertTimeout and alertMaxAttempts configurable in the config file.
+ alertTimeout time.Duration = time.Second * 30
+ alertMaxAttempts int = 3
)
-func (e *Engine) processJob(grafanaCtx context.Context, job *Job) error {
+func (e *Engine) processJobWithRetry(grafanaCtx context.Context, job *Job) error {
+ defer func() {
+ if err := recover(); err != nil {
+ e.log.Error("Alert Panic", "error", err, "stack", log.Stack(1))
+ }
+ }()
+
+ cancelChan := make(chan context.CancelFunc, alertMaxAttempts)
+ attemptChan := make(chan int, 1)
+
+ // Initialize with first attemptID=1
+ attemptChan <- 1
+ job.Running = true
+
+ for {
+ select {
+ case <-grafanaCtx.Done():
+ // In case grafana server context is cancel, let a chance to job processing
+ // to finish gracefully - by waiting a timeout duration - before forcing its end.
+ unfinishedWorkTimer := time.NewTimer(unfinishedWorkTimeout)
+ select {
+ case <-unfinishedWorkTimer.C:
+ return e.endJob(grafanaCtx.Err(), cancelChan, job)
+ case <-attemptChan:
+ return e.endJob(nil, cancelChan, job)
+ }
+ case attemptID, more := <-attemptChan:
+ if !more {
+ return e.endJob(nil, cancelChan, job)
+ }
+ go e.processJob(attemptID, attemptChan, cancelChan, job)
+ }
+ }
+}
+
+func (e *Engine) endJob(err error, cancelChan chan context.CancelFunc, job *Job) error {
+ job.Running = false
+ close(cancelChan)
+ for cancelFn := range cancelChan {
+ cancelFn()
+ }
+ return err
+}
+
+func (e *Engine) processJob(attemptID int, attemptChan chan int, cancelChan chan context.CancelFunc, job *Job) {
defer func() {
if err := recover(); err != nil {
e.log.Error("Alert Panic", "error", err, "stack", log.Stack(1))
@@ -104,14 +150,13 @@ func (e *Engine) processJob(grafanaCtx context.Context, job *Job) error {
}()
alertCtx, cancelFn := context.WithTimeout(context.Background(), alertTimeout)
+ cancelChan <- cancelFn
span := opentracing.StartSpan("alert execution")
alertCtx = opentracing.ContextWithSpan(alertCtx, span)
- job.Running = true
evalContext := NewEvalContext(alertCtx, job.Rule)
evalContext.Ctx = alertCtx
- done := make(chan struct{})
go func() {
defer func() {
if err := recover(); err != nil {
@@ -122,43 +167,36 @@ func (e *Engine) processJob(grafanaCtx context.Context, job *Job) error {
tlog.String("message", "failed to execute alert rule. panic was recovered."),
)
span.Finish()
- close(done)
+ close(attemptChan)
}
}()
e.evalHandler.Eval(evalContext)
- e.resultHandler.Handle(evalContext)
span.SetTag("alertId", evalContext.Rule.Id)
span.SetTag("dashboardId", evalContext.Rule.DashboardId)
span.SetTag("firing", evalContext.Firing)
span.SetTag("nodatapoints", evalContext.NoDataFound)
+ span.SetTag("attemptID", attemptID)
+
if evalContext.Error != nil {
ext.Error.Set(span, true)
span.LogFields(
tlog.Error(evalContext.Error),
- tlog.String("message", "alerting execution failed"),
+ tlog.String("message", "alerting execution attempt failed"),
)
+ if attemptID < alertMaxAttempts {
+ span.Finish()
+ e.log.Debug("Job Execution attempt triggered retry", "timeMs", evalContext.GetDurationMs(), "alertId", evalContext.Rule.Id, "name", evalContext.Rule.Name, "firing", evalContext.Firing, "attemptID", attemptID)
+ attemptChan <- (attemptID + 1)
+ return
+ }
}
+ evalContext.Rule.State = evalContext.GetNewState()
+ e.resultHandler.Handle(evalContext)
span.Finish()
- close(done)
+ e.log.Debug("Job Execution completed", "timeMs", evalContext.GetDurationMs(), "alertId", evalContext.Rule.Id, "name", evalContext.Rule.Name, "firing", evalContext.Firing, "attemptID", attemptID)
+ close(attemptChan)
}()
-
- var err error = nil
- select {
- case <-grafanaCtx.Done():
- select {
- case <-time.After(unfinishedWorkTimeout):
- cancelFn()
- err = grafanaCtx.Err()
- case <-done:
- }
- case <-done:
- }
-
- e.log.Debug("Job Execution completed", "timeMs", evalContext.GetDurationMs(), "alertId", evalContext.Rule.Id, "name", evalContext.Rule.Name, "firing", evalContext.Firing)
- job.Running = false
- cancelFn()
- return err
}
diff --git a/pkg/services/alerting/engine_test.go b/pkg/services/alerting/engine_test.go
new file mode 100644
index 00000000000..64f954c6dd5
--- /dev/null
+++ b/pkg/services/alerting/engine_test.go
@@ -0,0 +1,118 @@
+package alerting
+
+import (
+ "context"
+ "errors"
+ "math"
+ "testing"
+
+ . "github.com/smartystreets/goconvey/convey"
+)
+
+type FakeEvalHandler struct {
+ SuccessCallID int // 0 means never sucess
+ CallNb int
+}
+
+func NewFakeEvalHandler(successCallID int) *FakeEvalHandler {
+ return &FakeEvalHandler{
+ SuccessCallID: successCallID,
+ CallNb: 0,
+ }
+}
+
+func (handler *FakeEvalHandler) Eval(evalContext *EvalContext) {
+ handler.CallNb++
+ if handler.CallNb != handler.SuccessCallID {
+ evalContext.Error = errors.New("Fake evaluation failure")
+ }
+}
+
+type FakeResultHandler struct{}
+
+func (handler *FakeResultHandler) Handle(evalContext *EvalContext) error {
+ return nil
+}
+
+func TestEngineProcessJob(t *testing.T) {
+ Convey("Alerting engine job processing", t, func() {
+ engine := NewEngine()
+ engine.resultHandler = &FakeResultHandler{}
+ job := &Job{Running: true, Rule: &Rule{}}
+
+ Convey("Should trigger retry if needed", func() {
+
+ Convey("error + not last attempt -> retry", func() {
+ engine.evalHandler = NewFakeEvalHandler(0)
+
+ for i := 1; i < alertMaxAttempts; i++ {
+ attemptChan := make(chan int, 1)
+ cancelChan := make(chan context.CancelFunc, alertMaxAttempts)
+
+ engine.processJob(i, attemptChan, cancelChan, job)
+ nextAttemptID, more := <-attemptChan
+
+ So(nextAttemptID, ShouldEqual, i+1)
+ So(more, ShouldEqual, true)
+ So(<-cancelChan, ShouldNotBeNil)
+ }
+ })
+
+ Convey("error + last attempt -> no retry", func() {
+ engine.evalHandler = NewFakeEvalHandler(0)
+ attemptChan := make(chan int, 1)
+ cancelChan := make(chan context.CancelFunc, alertMaxAttempts)
+
+ engine.processJob(alertMaxAttempts, attemptChan, cancelChan, job)
+ nextAttemptID, more := <-attemptChan
+
+ So(nextAttemptID, ShouldEqual, 0)
+ So(more, ShouldEqual, false)
+ So(<-cancelChan, ShouldNotBeNil)
+ })
+
+ Convey("no error -> no retry", func() {
+ engine.evalHandler = NewFakeEvalHandler(1)
+ attemptChan := make(chan int, 1)
+ cancelChan := make(chan context.CancelFunc, alertMaxAttempts)
+
+ engine.processJob(1, attemptChan, cancelChan, job)
+ nextAttemptID, more := <-attemptChan
+
+ So(nextAttemptID, ShouldEqual, 0)
+ So(more, ShouldEqual, false)
+ So(<-cancelChan, ShouldNotBeNil)
+ })
+ })
+
+ Convey("Should trigger as many retries as needed", func() {
+
+ Convey("never sucess -> max retries number", func() {
+ expectedAttempts := alertMaxAttempts
+ evalHandler := NewFakeEvalHandler(0)
+ engine.evalHandler = evalHandler
+
+ engine.processJobWithRetry(context.TODO(), job)
+ So(evalHandler.CallNb, ShouldEqual, expectedAttempts)
+ })
+
+ Convey("always sucess -> never retry", func() {
+ expectedAttempts := 1
+ evalHandler := NewFakeEvalHandler(1)
+ engine.evalHandler = evalHandler
+
+ engine.processJobWithRetry(context.TODO(), job)
+ So(evalHandler.CallNb, ShouldEqual, expectedAttempts)
+ })
+
+ Convey("some errors before sucess -> some retries", func() {
+ expectedAttempts := int(math.Ceil(float64(alertMaxAttempts) / 2))
+ evalHandler := NewFakeEvalHandler(expectedAttempts)
+ engine.evalHandler = evalHandler
+
+ engine.processJobWithRetry(context.TODO(), job)
+ So(evalHandler.CallNb, ShouldEqual, expectedAttempts)
+ })
+ })
+ })
+}
diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go
index d598203d675..91d0e179a14 100644
--- a/pkg/services/alerting/eval_context.go
+++ b/pkg/services/alerting/eval_context.go
@@ -112,3 +112,34 @@ func (c *EvalContext) GetRuleUrl() (string, error) {
return fmt.Sprintf(urlFormat, m.GetFullDashboardUrl(ref.Uid, ref.Slug), c.Rule.PanelId, c.Rule.OrgId), nil
}
}
+
+func (c *EvalContext) GetNewState() m.AlertStateType {
+ if c.Error != nil {
+ c.log.Error("Alert Rule Result Error",
+ "ruleId", c.Rule.Id,
+ "name", c.Rule.Name,
+ "error", c.Error,
+ "changing state to", c.Rule.ExecutionErrorState.ToAlertState())
+
+ if c.Rule.ExecutionErrorState == m.ExecutionErrorKeepState {
+ return c.PrevAlertState
+ }
+ return c.Rule.ExecutionErrorState.ToAlertState()
+
+ } else if c.Firing {
+ return m.AlertStateAlerting
+
+ } else if c.NoDataFound {
+ c.log.Info("Alert Rule returned no data",
+ "ruleId", c.Rule.Id,
+ "name", c.Rule.Name,
+ "changing state to", c.Rule.NoDataState.ToAlertState())
+
+ if c.Rule.NoDataState == m.NoDataKeepState {
+ return c.PrevAlertState
+ }
+ return c.Rule.NoDataState.ToAlertState()
+ }
+
+ return m.AlertStateOK
+}
diff --git a/pkg/services/alerting/eval_context_test.go b/pkg/services/alerting/eval_context_test.go
index 019ca1ed01f..709eeee4e5e 100644
--- a/pkg/services/alerting/eval_context_test.go
+++ b/pkg/services/alerting/eval_context_test.go
@@ -2,6 +2,7 @@ package alerting
import (
"context"
+ "fmt"
"testing"
"github.com/grafana/grafana/pkg/models"
@@ -12,7 +13,7 @@ func TestAlertingEvalContext(t *testing.T) {
Convey("Eval context", t, func() {
ctx := NewEvalContext(context.TODO(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}})
- Convey("Should update alert state", func() {
+ Convey("Should update alert state when needed", func() {
Convey("ok -> alerting", func() {
ctx.PrevAlertState = models.AlertStateOK
@@ -28,5 +29,71 @@ func TestAlertingEvalContext(t *testing.T) {
So(ctx.ShouldUpdateAlertState(), ShouldBeFalse)
})
})
+
+ Convey("Should compute and replace properly new rule state", func() {
+ dummieError := fmt.Errorf("dummie error")
+
+ Convey("ok -> alerting", func() {
+ ctx.PrevAlertState = models.AlertStateOK
+ ctx.Firing = true
+
+ ctx.Rule.State = ctx.GetNewState()
+ So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
+ })
+
+ Convey("ok -> error(alerting)", func() {
+ ctx.PrevAlertState = models.AlertStateOK
+ ctx.Error = dummieError
+ ctx.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting
+
+ ctx.Rule.State = ctx.GetNewState()
+ So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
+ })
+
+ Convey("ok -> error(keep_last)", func() {
+ ctx.PrevAlertState = models.AlertStateOK
+ ctx.Error = dummieError
+ ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
+
+ ctx.Rule.State = ctx.GetNewState()
+ So(ctx.Rule.State, ShouldEqual, models.AlertStateOK)
+ })
+
+ Convey("pending -> error(keep_last)", func() {
+ ctx.PrevAlertState = models.AlertStatePending
+ ctx.Error = dummieError
+ ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
+
+ ctx.Rule.State = ctx.GetNewState()
+ So(ctx.Rule.State, ShouldEqual, models.AlertStatePending)
+ })
+
+ Convey("ok -> no_data(alerting)", func() {
+ ctx.PrevAlertState = models.AlertStateOK
+ ctx.Rule.NoDataState = models.NoDataSetAlerting
+ ctx.NoDataFound = true
+
+ ctx.Rule.State = ctx.GetNewState()
+ So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
+ })
+
+ Convey("ok -> no_data(keep_last)", func() {
+ ctx.PrevAlertState = models.AlertStateOK
+ ctx.Rule.NoDataState = models.NoDataKeepState
+ ctx.NoDataFound = true
+
+ ctx.Rule.State = ctx.GetNewState()
+ So(ctx.Rule.State, ShouldEqual, models.AlertStateOK)
+ })
+
+ Convey("pending -> no_data(keep_last)", func() {
+ ctx.PrevAlertState = models.AlertStatePending
+ ctx.Rule.NoDataState = models.NoDataKeepState
+ ctx.NoDataFound = true
+
+ ctx.Rule.State = ctx.GetNewState()
+ So(ctx.Rule.State, ShouldEqual, models.AlertStatePending)
+ })
+ })
})
}
diff --git a/pkg/services/alerting/eval_handler.go b/pkg/services/alerting/eval_handler.go
index 457e02000fa..aa24efa77cd 100644
--- a/pkg/services/alerting/eval_handler.go
+++ b/pkg/services/alerting/eval_handler.go
@@ -7,7 +7,6 @@ import (
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
- "github.com/grafana/grafana/pkg/models"
)
type DefaultEvalHandler struct {
@@ -66,40 +65,7 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) {
context.Firing = firing
context.NoDataFound = noDataFound
context.EndTime = time.Now()
- context.Rule.State = e.getNewState(context)
elapsedTime := context.EndTime.Sub(context.StartTime).Nanoseconds() / int64(time.Millisecond)
metrics.M_Alerting_Execution_Time.Observe(float64(elapsedTime))
}
-
-// This should be move into evalContext once its been refactored. (Carl Bergquist)
-func (handler *DefaultEvalHandler) getNewState(evalContext *EvalContext) models.AlertStateType {
- if evalContext.Error != nil {
- handler.log.Error("Alert Rule Result Error",
- "ruleId", evalContext.Rule.Id,
- "name", evalContext.Rule.Name,
- "error", evalContext.Error,
- "changing state to", evalContext.Rule.ExecutionErrorState.ToAlertState())
-
- if evalContext.Rule.ExecutionErrorState == models.ExecutionErrorKeepState {
- return evalContext.PrevAlertState
- } else {
- return evalContext.Rule.ExecutionErrorState.ToAlertState()
- }
- } else if evalContext.Firing {
- return models.AlertStateAlerting
- } else if evalContext.NoDataFound {
- handler.log.Info("Alert Rule returned no data",
- "ruleId", evalContext.Rule.Id,
- "name", evalContext.Rule.Name,
- "changing state to", evalContext.Rule.NoDataState.ToAlertState())
-
- if evalContext.Rule.NoDataState == models.NoDataKeepState {
- return evalContext.PrevAlertState
- } else {
- return evalContext.Rule.NoDataState.ToAlertState()
- }
- }
-
- return models.AlertStateOK
-}
diff --git a/pkg/services/alerting/eval_handler_test.go b/pkg/services/alerting/eval_handler_test.go
index c942e24818f..a7c1f1e67fa 100644
--- a/pkg/services/alerting/eval_handler_test.go
+++ b/pkg/services/alerting/eval_handler_test.go
@@ -2,10 +2,8 @@ package alerting
import (
"context"
- "fmt"
"testing"
- "github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
)
@@ -203,73 +201,5 @@ func TestAlertingEvaluationHandler(t *testing.T) {
handler.Eval(context)
So(context.NoDataFound, ShouldBeTrue)
})
-
- Convey("EvalHandler can replace alert state based for errors and no_data", func() {
- ctx := NewEvalContext(context.TODO(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}})
- dummieError := fmt.Errorf("dummie error")
- Convey("Should update alert state", func() {
-
- Convey("ok -> alerting", func() {
- ctx.PrevAlertState = models.AlertStateOK
- ctx.Firing = true
-
- So(handler.getNewState(ctx), ShouldEqual, models.AlertStateAlerting)
- })
-
- Convey("ok -> error(alerting)", func() {
- ctx.PrevAlertState = models.AlertStateOK
- ctx.Error = dummieError
- ctx.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting
-
- ctx.Rule.State = handler.getNewState(ctx)
- So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
- })
-
- Convey("ok -> error(keep_last)", func() {
- ctx.PrevAlertState = models.AlertStateOK
- ctx.Error = dummieError
- ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
-
- ctx.Rule.State = handler.getNewState(ctx)
- So(ctx.Rule.State, ShouldEqual, models.AlertStateOK)
- })
-
- Convey("pending -> error(keep_last)", func() {
- ctx.PrevAlertState = models.AlertStatePending
- ctx.Error = dummieError
- ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
-
- ctx.Rule.State = handler.getNewState(ctx)
- So(ctx.Rule.State, ShouldEqual, models.AlertStatePending)
- })
-
- Convey("ok -> no_data(alerting)", func() {
- ctx.PrevAlertState = models.AlertStateOK
- ctx.Rule.NoDataState = models.NoDataSetAlerting
- ctx.NoDataFound = true
-
- ctx.Rule.State = handler.getNewState(ctx)
- So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
- })
-
- Convey("ok -> no_data(keep_last)", func() {
- ctx.PrevAlertState = models.AlertStateOK
- ctx.Rule.NoDataState = models.NoDataKeepState
- ctx.NoDataFound = true
-
- ctx.Rule.State = handler.getNewState(ctx)
- So(ctx.Rule.State, ShouldEqual, models.AlertStateOK)
- })
-
- Convey("pending -> no_data(keep_last)", func() {
- ctx.PrevAlertState = models.AlertStatePending
- ctx.Rule.NoDataState = models.NoDataKeepState
- ctx.NoDataFound = true
-
- ctx.Rule.State = handler.getNewState(ctx)
- So(ctx.Rule.State, ShouldEqual, models.AlertStatePending)
- })
- })
- })
})
}
diff --git a/pkg/services/alerting/test_rule.go b/pkg/services/alerting/test_rule.go
index e3aa95e0ede..88418bff14e 100644
--- a/pkg/services/alerting/test_rule.go
+++ b/pkg/services/alerting/test_rule.go
@@ -53,6 +53,7 @@ func testAlertRule(rule *Rule) *EvalContext {
context.IsTestRun = true
handler.Eval(context)
+ context.Rule.State = context.GetNewState()
return context
}
diff --git a/pkg/services/dashboards/folder_service.go b/pkg/services/dashboards/folder_service.go
index 66afa6306fb..ae92952056e 100644
--- a/pkg/services/dashboards/folder_service.go
+++ b/pkg/services/dashboards/folder_service.go
@@ -10,8 +10,8 @@ import (
// FolderService service for operating on folders
type FolderService interface {
GetFolders(limit int) ([]*models.Folder, error)
- GetFolderById(id int64) (*models.Folder, error)
- GetFolderByUid(uid string) (*models.Folder, error)
+ GetFolderByID(id int64) (*models.Folder, error)
+ GetFolderByUID(uid string) (*models.Folder, error)
CreateFolder(cmd *models.CreateFolderCommand) error
UpdateFolder(uid string, cmd *models.UpdateFolderCommand) error
DeleteFolder(uid string) (*models.Folder, error)
@@ -57,7 +57,7 @@ func (dr *dashboardServiceImpl) GetFolders(limit int) ([]*models.Folder, error)
return folders, nil
}
-func (dr *dashboardServiceImpl) GetFolderById(id int64) (*models.Folder, error) {
+func (dr *dashboardServiceImpl) GetFolderByID(id int64) (*models.Folder, error) {
query := models.GetDashboardQuery{OrgId: dr.orgId, Id: id}
dashFolder, err := getFolder(query)
@@ -76,7 +76,7 @@ func (dr *dashboardServiceImpl) GetFolderById(id int64) (*models.Folder, error)
return dashToFolder(dashFolder), nil
}
-func (dr *dashboardServiceImpl) GetFolderByUid(uid string) (*models.Folder, error) {
+func (dr *dashboardServiceImpl) GetFolderByUID(uid string) (*models.Folder, error) {
query := models.GetDashboardQuery{OrgId: dr.orgId, Uid: uid}
dashFolder, err := getFolder(query)
diff --git a/pkg/services/dashboards/folder_service_test.go b/pkg/services/dashboards/folder_service_test.go
index 6357e84805a..6c0413d1878 100644
--- a/pkg/services/dashboards/folder_service_test.go
+++ b/pkg/services/dashboards/folder_service_test.go
@@ -36,13 +36,13 @@ func TestFolderService(t *testing.T) {
})
Convey("When get folder by id should return access denied error", func() {
- _, err := service.GetFolderById(1)
+ _, err := service.GetFolderByID(1)
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrFolderAccessDenied)
})
Convey("When get folder by uid should return access denied error", func() {
- _, err := service.GetFolderByUid("uid")
+ _, err := service.GetFolderByUID("uid")
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrFolderAccessDenied)
})
@@ -147,14 +147,14 @@ func TestFolderService(t *testing.T) {
})
Convey("When get folder by id should return folder", func() {
- f, _ := service.GetFolderById(1)
+ f, _ := service.GetFolderByID(1)
So(f.Id, ShouldEqual, dashFolder.Id)
So(f.Uid, ShouldEqual, dashFolder.Uid)
So(f.Title, ShouldEqual, dashFolder.Title)
})
Convey("When get folder by uid should return folder", func() {
- f, _ := service.GetFolderByUid("uid")
+ f, _ := service.GetFolderByUID("uid")
So(f.Id, ShouldEqual, dashFolder.Id)
So(f.Uid, ShouldEqual, dashFolder.Uid)
So(f.Title, ShouldEqual, dashFolder.Title)
diff --git a/vendor/github.com/go-macaron/session/mysql/mysql.go b/pkg/services/session/mysql.go
similarity index 87%
rename from vendor/github.com/go-macaron/session/mysql/mysql.go
rename to pkg/services/session/mysql.go
index a16db779c63..f8c5d828cfa 100644
--- a/vendor/github.com/go-macaron/session/mysql/mysql.go
+++ b/pkg/services/session/mysql.go
@@ -108,6 +108,7 @@ func (p *MysqlProvider) Init(expire int64, connStr string) (err error) {
p.expire = expire
p.c, err = sql.Open("mysql", connStr)
+ p.c.SetConnMaxLifetime(time.Second * time.Duration(sessionConnMaxLifetime))
if err != nil {
return err
}
@@ -141,12 +142,29 @@ func (p *MysqlProvider) Read(sid string) (session.RawStore, error) {
// Exist returns true if session with given ID exists.
func (p *MysqlProvider) Exist(sid string) bool {
+ exists, err := p.queryExists(sid)
+
+ if err != nil {
+ exists, err = p.queryExists(sid)
+ }
+
+ if err != nil {
+ log.Printf("session/mysql: error checking if session exists: %v", err)
+ return false
+ }
+
+ return exists
+}
+
+func (p *MysqlProvider) queryExists(sid string) (bool, error) {
var data []byte
err := p.c.QueryRow("SELECT data FROM session WHERE `key`=?", sid).Scan(&data)
+
if err != nil && err != sql.ErrNoRows {
- panic("session/mysql: error checking existence: " + err.Error())
+ return false, err
}
- return err != sql.ErrNoRows
+
+ return err != sql.ErrNoRows, nil
}
// Destory deletes a session by session ID.
@@ -185,7 +203,12 @@ func (p *MysqlProvider) Count() (total int) {
// GC calls GC to clean expired sessions.
func (p *MysqlProvider) GC() {
- if _, err := p.c.Exec("DELETE FROM session WHERE expiry + ? <= UNIX_TIMESTAMP(NOW())", p.expire); err != nil {
+ var err error
+ if _, err = p.c.Exec("DELETE FROM session WHERE expiry + ? <= UNIX_TIMESTAMP(NOW())", p.expire); err != nil {
+ _, err = p.c.Exec("DELETE FROM session WHERE expiry + ? <= UNIX_TIMESTAMP(NOW())", p.expire)
+ }
+
+ if err != nil {
log.Printf("session/mysql: error garbage collecting: %v", err)
}
}
diff --git a/pkg/services/session/session.go b/pkg/services/session/session.go
index bfdc58bc5cc..5873a6a5b72 100644
--- a/pkg/services/session/session.go
+++ b/pkg/services/session/session.go
@@ -6,7 +6,6 @@ import (
ms "github.com/go-macaron/session"
_ "github.com/go-macaron/session/memcache"
- _ "github.com/go-macaron/session/mysql"
_ "github.com/go-macaron/session/postgres"
_ "github.com/go-macaron/session/redis"
"github.com/grafana/grafana/pkg/log"
@@ -25,6 +24,7 @@ var sessionOptions *ms.Options
var StartSessionGC func()
var GetSessionCount func() int
var sessionLogger = log.New("session")
+var sessionConnMaxLifetime int64
func init() {
StartSessionGC = func() {
@@ -37,9 +37,10 @@ func init() {
}
}
-func Init(options *ms.Options) {
+func Init(options *ms.Options, connMaxLifetime int64) {
var err error
sessionOptions = prepareOptions(options)
+ sessionConnMaxLifetime = connMaxLifetime
sessionManager, err = ms.NewManager(options.Provider, *options)
if err != nil {
panic(err)
diff --git a/pkg/services/sqlstore/alert.go b/pkg/services/sqlstore/alert.go
index f449bec5849..e99367e4d6f 100644
--- a/pkg/services/sqlstore/alert.go
+++ b/pkg/services/sqlstore/alert.go
@@ -255,7 +255,7 @@ func SetAlertState(cmd *m.SetAlertStateCommand) error {
}
alert.State = cmd.State
- alert.StateChanges += 1
+ alert.StateChanges++
alert.NewStateDate = timeNow()
alert.EvalData = cmd.EvalData
diff --git a/pkg/services/sqlstore/dashboard_folder_test.go b/pkg/services/sqlstore/dashboard_folder_test.go
index ea8f1216706..4c92c097931 100644
--- a/pkg/services/sqlstore/dashboard_folder_test.go
+++ b/pkg/services/sqlstore/dashboard_folder_test.go
@@ -46,6 +46,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id},
}
err := SearchDashboards(query)
+
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].Id, ShouldEqual, dashInRoot.Id)
diff --git a/pkg/services/sqlstore/dashboard_version.go b/pkg/services/sqlstore/dashboard_version.go
index 547f62628f3..1f2850b2021 100644
--- a/pkg/services/sqlstore/dashboard_version.go
+++ b/pkg/services/sqlstore/dashboard_version.go
@@ -67,30 +67,39 @@ func GetDashboardVersions(query *m.GetDashboardVersionsQuery) error {
return nil
}
+const MAX_VERSIONS_TO_DELETE = 100
+
func DeleteExpiredVersions(cmd *m.DeleteExpiredVersionsCommand) error {
return inTransaction(func(sess *DBSession) error {
- versions := []DashboardVersionExp{}
versionsToKeep := setting.DashboardVersionsToKeep
-
if versionsToKeep < 1 {
versionsToKeep = 1
}
- err := sess.Table("dashboard_version").
- Select("dashboard_version.id, dashboard_version.version, dashboard_version.dashboard_id").
- Where(`dashboard_id IN (
- SELECT dashboard_id FROM dashboard_version
- GROUP BY dashboard_id HAVING COUNT(dashboard_version.id) > ?
- )`, versionsToKeep).
- Desc("dashboard_version.dashboard_id", "dashboard_version.version").
- Find(&versions)
+ // Idea of this query is finding version IDs to delete based on formula:
+ // min_version_to_keep = min_version + (versions_count - versions_to_keep)
+ // where version stats is processed for each dashboard. This guarantees that we keep at least versions_to_keep
+ // versions, but in some cases (when versions are sparse) this number may be more.
+ versionIdsToDeleteQuery := `SELECT id
+ FROM dashboard_version, (
+ SELECT dashboard_id, count(version) as count, min(version) as min
+ FROM dashboard_version
+ GROUP BY dashboard_id
+ ) AS vtd
+ WHERE dashboard_version.dashboard_id=vtd.dashboard_id
+ AND version < vtd.min + vtd.count - ?`
+ var versionIdsToDelete []interface{}
+ err := sess.SQL(versionIdsToDeleteQuery, versionsToKeep).Find(&versionIdsToDelete)
if err != nil {
return err
}
- // Keep last versionsToKeep versions and delete other
- versionIdsToDelete := getVersionIDsToDelete(versions, versionsToKeep)
+ // Don't delete more than MAX_VERSIONS_TO_DELETE version per time
+ if len(versionIdsToDelete) > MAX_VERSIONS_TO_DELETE {
+ versionIdsToDelete = versionIdsToDelete[:MAX_VERSIONS_TO_DELETE]
+ }
+
if len(versionIdsToDelete) > 0 {
deleteExpiredSql := `DELETE FROM dashboard_version WHERE id IN (?` + strings.Repeat(",?", len(versionIdsToDelete)-1) + `)`
expiredResponse, err := sess.Exec(deleteExpiredSql, versionIdsToDelete...)
@@ -103,34 +112,3 @@ func DeleteExpiredVersions(cmd *m.DeleteExpiredVersionsCommand) error {
return nil
})
}
-
-// Short version of DashboardVersion for getting expired versions
-type DashboardVersionExp struct {
- Id int64 `json:"id"`
- DashboardId int64 `json:"dashboardId"`
- Version int `json:"version"`
-}
-
-func getVersionIDsToDelete(versions []DashboardVersionExp, versionsToKeep int) []interface{} {
- versionIds := make([]interface{}, 0)
-
- if len(versions) == 0 {
- return versionIds
- }
-
- currentDashboard := versions[0].DashboardId
- count := 0
- for _, v := range versions {
- if v.DashboardId == currentDashboard {
- count++
- } else {
- count = 1
- currentDashboard = v.DashboardId
- }
- if count > versionsToKeep {
- versionIds = append(versionIds, v.Id)
- }
- }
-
- return versionIds
-}
diff --git a/pkg/services/sqlstore/dashboard_version_test.go b/pkg/services/sqlstore/dashboard_version_test.go
index 1b74e7847c4..a6403755d05 100644
--- a/pkg/services/sqlstore/dashboard_version_test.go
+++ b/pkg/services/sqlstore/dashboard_version_test.go
@@ -136,10 +136,30 @@ func TestDeleteExpiredVersions(t *testing.T) {
err := DeleteExpiredVersions(&m.DeleteExpiredVersionsCommand{})
So(err, ShouldBeNil)
- query := m.GetDashboardVersionsQuery{DashboardId: savedDash.Id, OrgId: 1}
+ query := m.GetDashboardVersionsQuery{DashboardId: savedDash.Id, OrgId: 1, Limit: versionsToWrite}
GetDashboardVersions(&query)
So(len(query.Result), ShouldEqual, versionsToWrite)
})
+
+ Convey("Don't delete more than MAX_VERSIONS_TO_DELETE per iteration", func() {
+ versionsToWriteBigNumber := MAX_VERSIONS_TO_DELETE + versionsToWrite
+ for i := 0; i < versionsToWriteBigNumber-versionsToWrite; i++ {
+ updateTestDashboard(savedDash, map[string]interface{}{
+ "tags": "different-tag",
+ })
+ }
+
+ err := DeleteExpiredVersions(&m.DeleteExpiredVersionsCommand{})
+ So(err, ShouldBeNil)
+
+ query := m.GetDashboardVersionsQuery{DashboardId: savedDash.Id, OrgId: 1, Limit: versionsToWriteBigNumber}
+ GetDashboardVersions(&query)
+
+ // Ensure we have at least versionsToKeep versions
+ So(len(query.Result), ShouldBeGreaterThanOrEqualTo, versionsToKeep)
+ // Ensure we haven't deleted more than MAX_VERSIONS_TO_DELETE rows
+ So(versionsToWriteBigNumber-len(query.Result), ShouldBeLessThanOrEqualTo, MAX_VERSIONS_TO_DELETE)
+ })
})
}
diff --git a/pkg/services/sqlstore/migrator/migrator.go b/pkg/services/sqlstore/migrator/migrator.go
index a8bd36ac8a3..0fde3f27c01 100644
--- a/pkg/services/sqlstore/migrator/migrator.go
+++ b/pkg/services/sqlstore/migrator/migrator.go
@@ -125,7 +125,7 @@ func (mg *Migrator) exec(m Migration, sess *xorm.Session) error {
condition := m.GetCondition()
if condition != nil {
sql, args := condition.Sql(mg.dialect)
- results, err := sess.Query(sql, args...)
+ results, err := sess.SQL(sql).Query(args...)
if err != nil || len(results) == 0 {
mg.Logger.Info("Skipping migration condition not fulfilled", "id", m.Id())
return sess.Rollback()
diff --git a/pkg/services/sqlstore/org_test.go b/pkg/services/sqlstore/org_test.go
index c57d15a48d5..63b20aa6e86 100644
--- a/pkg/services/sqlstore/org_test.go
+++ b/pkg/services/sqlstore/org_test.go
@@ -2,6 +2,7 @@ package sqlstore
import (
"testing"
+ "time"
. "github.com/smartystreets/goconvey/convey"
@@ -241,6 +242,8 @@ func TestAccountDataAccess(t *testing.T) {
func testHelperUpdateDashboardAcl(dashboardId int64, items ...m.DashboardAcl) error {
cmd := m.UpdateDashboardAclCommand{DashboardId: dashboardId}
for _, item := range items {
+ item.Created = time.Now()
+ item.Updated = time.Now()
cmd.Items = append(cmd.Items, &item)
}
return UpdateDashboardAcl(&cmd)
diff --git a/pkg/services/sqlstore/quota.go b/pkg/services/sqlstore/quota.go
index 0a857efce40..3db3fc2657e 100644
--- a/pkg/services/sqlstore/quota.go
+++ b/pkg/services/sqlstore/quota.go
@@ -2,6 +2,7 @@ package sqlstore
import (
"fmt"
+ "time"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
@@ -98,8 +99,9 @@ func UpdateOrgQuota(cmd *m.UpdateOrgQuotaCmd) error {
return inTransaction(func(sess *DBSession) error {
//Check if quota is already defined in the DB
quota := m.Quota{
- Target: cmd.Target,
- OrgId: cmd.OrgId,
+ Target: cmd.Target,
+ OrgId: cmd.OrgId,
+ Updated: time.Now(),
}
has, err := sess.Get("a)
if err != nil {
@@ -107,6 +109,7 @@ func UpdateOrgQuota(cmd *m.UpdateOrgQuotaCmd) error {
}
quota.Limit = cmd.Limit
if has == false {
+ quota.Created = time.Now()
//No quota in the DB for this target, so create a new one.
if _, err := sess.Insert("a); err != nil {
return err
@@ -198,8 +201,9 @@ func UpdateUserQuota(cmd *m.UpdateUserQuotaCmd) error {
return inTransaction(func(sess *DBSession) error {
//Check if quota is already defined in the DB
quota := m.Quota{
- Target: cmd.Target,
- UserId: cmd.UserId,
+ Target: cmd.Target,
+ UserId: cmd.UserId,
+ Updated: time.Now(),
}
has, err := sess.Get("a)
if err != nil {
@@ -207,6 +211,7 @@ func UpdateUserQuota(cmd *m.UpdateUserQuotaCmd) error {
}
quota.Limit = cmd.Limit
if has == false {
+ quota.Created = time.Now()
//No quota in the DB for this target, so create a new one.
if _, err := sess.Insert("a); err != nil {
return err
diff --git a/pkg/services/sqlstore/quota_test.go b/pkg/services/sqlstore/quota_test.go
index 5ef618e166d..ed6565b1c3f 100644
--- a/pkg/services/sqlstore/quota_test.go
+++ b/pkg/services/sqlstore/quota_test.go
@@ -104,12 +104,12 @@ func TestQuotaCommandsAndQueries(t *testing.T) {
})
})
Convey("Given saved user quota for org", func() {
- userQoutaCmd := m.UpdateUserQuotaCmd{
+ userQuotaCmd := m.UpdateUserQuotaCmd{
UserId: userId,
Target: "org_user",
Limit: 10,
}
- err := UpdateUserQuota(&userQoutaCmd)
+ err := UpdateUserQuota(&userQuotaCmd)
So(err, ShouldBeNil)
Convey("Should be able to get saved quota by user id and target", func() {
diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go
index c00a55667d1..6aace350193 100644
--- a/pkg/services/sqlstore/sqlstore.go
+++ b/pkg/services/sqlstore/sqlstore.go
@@ -8,6 +8,7 @@ import (
"path/filepath"
"strings"
"testing"
+ "time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
@@ -35,6 +36,7 @@ type DatabaseConfig struct {
ServerCertName string
MaxOpenConn int
MaxIdleConn int
+ ConnMaxLifetime int
}
var (
@@ -159,18 +161,20 @@ func getEngine() (*xorm.Engine, error) {
engine, err := xorm.NewEngine(DbCfg.Type, cnnstr)
if err != nil {
return nil, err
- } else {
- engine.SetMaxOpenConns(DbCfg.MaxOpenConn)
- engine.SetMaxIdleConns(DbCfg.MaxIdleConn)
- debugSql := setting.Cfg.Section("database").Key("log_queries").MustBool(false)
- if !debugSql {
- engine.SetLogger(&xorm.DiscardLogger{})
- } else {
- engine.SetLogger(NewXormLogger(log.LvlInfo, log.New("sqlstore.xorm")))
- engine.ShowSQL(true)
- engine.ShowExecTime(true)
- }
}
+
+ engine.SetMaxOpenConns(DbCfg.MaxOpenConn)
+ engine.SetMaxIdleConns(DbCfg.MaxIdleConn)
+ engine.SetConnMaxLifetime(time.Second * time.Duration(DbCfg.ConnMaxLifetime))
+ debugSql := setting.Cfg.Section("database").Key("log_queries").MustBool(false)
+ if !debugSql {
+ engine.SetLogger(&xorm.DiscardLogger{})
+ } else {
+ engine.SetLogger(NewXormLogger(log.LvlInfo, log.New("sqlstore.xorm")))
+ engine.ShowSQL(true)
+ engine.ShowExecTime(true)
+ }
+
return engine, nil
}
@@ -204,6 +208,7 @@ func LoadConfig() {
}
DbCfg.MaxOpenConn = sec.Key("max_open_conn").MustInt(0)
DbCfg.MaxIdleConn = sec.Key("max_idle_conn").MustInt(0)
+ DbCfg.ConnMaxLifetime = sec.Key("conn_max_lifetime").MustInt(14400)
if DbCfg.Type == "sqlite3" {
UseSQLite3 = true
@@ -227,8 +232,8 @@ var (
func InitTestDB(t *testing.T) *xorm.Engine {
selectedDb := dbSqlite
- //selectedDb := dbMySql
- //selectedDb := dbPostgres
+ // selectedDb := dbMySql
+ // selectedDb := dbPostgres
var x *xorm.Engine
var err error
@@ -247,6 +252,9 @@ func InitTestDB(t *testing.T) *xorm.Engine {
x, err = xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr)
}
+ x.DatabaseTZ = time.UTC
+ x.TZLocation = time.UTC
+
// x.ShowSQL()
if err != nil {
diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go
index 73ea07f031f..f42ff5fb2ed 100644
--- a/pkg/services/sqlstore/user.go
+++ b/pkg/services/sqlstore/user.go
@@ -315,6 +315,7 @@ func GetUserProfile(query *m.GetUserProfileQuery) error {
}
query.Result = m.UserProfileDTO{
+ Id: user.Id,
Name: user.Name,
Email: user.Email,
Login: user.Login,
diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go
index 6099388f668..5b79e866964 100644
--- a/pkg/setting/setting.go
+++ b/pkg/setting/setting.go
@@ -131,7 +131,8 @@ var (
PluginAppsSkipVerifyTLS bool
// Session settings.
- SessionOptions session.Options
+ SessionOptions session.Options
+ SessionConnMaxLifetime int64
// Global setting objects.
Cfg *ini.File
@@ -634,6 +635,8 @@ func readSessionConfig() {
if SessionOptions.CookiePath == "" {
SessionOptions.CookiePath = "/"
}
+
+ SessionConnMaxLifetime = Cfg.Section("session").Key("conn_max_lifetime").MustInt64(14400)
}
func initLogging() {
diff --git a/pkg/tsdb/mssql/macros.go b/pkg/tsdb/mssql/macros.go
index 92c6ede148e..9d41cd03255 100644
--- a/pkg/tsdb/mssql/macros.go
+++ b/pkg/tsdb/mssql/macros.go
@@ -73,25 +73,20 @@ func (m *MsSqlMacroEngine) evaluateMacro(name string, args []string) (string, er
return "", fmt.Errorf("missing time column argument for macro %v", name)
}
return fmt.Sprintf("%s AS time", args[0]), nil
- case "__utcTime":
- if len(args) == 0 {
- return "", fmt.Errorf("missing time column argument for macro %v", name)
- }
- return fmt.Sprintf("DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), %s) AS time", args[0]), nil
case "__timeEpoch":
if len(args) == 0 {
return "", fmt.Errorf("missing time column argument for macro %v", name)
}
- return fmt.Sprintf("DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), %s) ) AS time", args[0]), nil
+ return fmt.Sprintf("DATEDIFF(second, '1970-01-01', %s) AS time", args[0]), nil
case "__timeFilter":
if len(args) == 0 {
return "", fmt.Errorf("missing time column argument for macro %v", name)
}
- return fmt.Sprintf("%s >= DATEADD(s, %d+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01') AND %s <= DATEADD(s, %d+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil
+ return fmt.Sprintf("%s >= DATEADD(s, %d, '1970-01-01') AND %s <= DATEADD(s, %d, '1970-01-01')", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil
case "__timeFrom":
- return fmt.Sprintf("DATEADD(second, %d+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil
+ return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", uint64(m.TimeRange.GetFromAsMsEpoch()/1000)), nil
case "__timeTo":
- return fmt.Sprintf("DATEADD(second, %d+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil
+ return fmt.Sprintf("DATEADD(second, %d, '1970-01-01')", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil
case "__timeGroup":
if len(args) < 2 {
return "", fmt.Errorf("macro %v needs time column and interval", name)
@@ -113,7 +108,7 @@ func (m *MsSqlMacroEngine) evaluateMacro(name string, args []string) (string, er
m.Query.Model.Set("fillValue", floatVal)
}
}
- return fmt.Sprintf("cast(cast(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), %s))/%.0f as int)*%.0f as int)", args[0], interval.Seconds(), interval.Seconds()), nil
+ return fmt.Sprintf("CAST(ROUND(DATEDIFF(second, '1970-01-01', %s)/%.1f, 0) as bigint)*%.0f", args[0], interval.Seconds(), interval.Seconds()), nil
case "__unixEpochFilter":
if len(args) == 0 {
return "", fmt.Errorf("missing time column argument for macro %v", name)
diff --git a/pkg/tsdb/mssql/macros_test.go b/pkg/tsdb/mssql/macros_test.go
index db1f5670924..12a9b0d82be 100644
--- a/pkg/tsdb/mssql/macros_test.go
+++ b/pkg/tsdb/mssql/macros_test.go
@@ -25,46 +25,39 @@ func TestMacroEngine(t *testing.T) {
So(sql, ShouldEqual, "select time_column AS time")
})
- Convey("interpolate __utcTime function", func() {
- sql, err := engine.Interpolate(query, nil, "select $__utcTime(time_column)")
- So(err, ShouldBeNil)
-
- So(sql, ShouldEqual, "select DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time_column) AS time")
- })
-
Convey("interpolate __timeEpoch function", func() {
sql, err := engine.Interpolate(query, nil, "select $__timeEpoch(time_column)")
So(err, ShouldBeNil)
- So(sql, ShouldEqual, "select DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time_column) ) AS time")
+ So(sql, ShouldEqual, "select DATEDIFF(second, '1970-01-01', time_column) AS time")
})
Convey("interpolate __timeEpoch function wrapped in aggregation", func() {
sql, err := engine.Interpolate(query, nil, "select min($__timeEpoch(time_column))")
So(err, ShouldBeNil)
- So(sql, ShouldEqual, "select min(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time_column) ) AS time)")
+ So(sql, ShouldEqual, "select min(DATEDIFF(second, '1970-01-01', time_column) AS time)")
})
Convey("interpolate __timeFilter function", func() {
sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
So(err, ShouldBeNil)
- So(sql, ShouldEqual, "WHERE time_column >= DATEADD(s, 18446744066914186738+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01') AND time_column <= DATEADD(s, 18446744066914187038+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')")
+ So(sql, ShouldEqual, "WHERE time_column >= DATEADD(s, 18446744066914186738, '1970-01-01') AND time_column <= DATEADD(s, 18446744066914187038, '1970-01-01')")
})
Convey("interpolate __timeGroup function", func() {
sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m')")
So(err, ShouldBeNil)
- So(sql, ShouldEqual, "GROUP BY cast(cast(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time_column))/300 as int)*300 as int)")
+ So(sql, ShouldEqual, "GROUP BY CAST(ROUND(DATEDIFF(second, '1970-01-01', time_column)/300.0, 0) as bigint)*300")
})
Convey("interpolate __timeGroup function with spaces around arguments", func() {
sql, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column , '5m')")
So(err, ShouldBeNil)
- So(sql, ShouldEqual, "GROUP BY cast(cast(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time_column))/300 as int)*300 as int)")
+ So(sql, ShouldEqual, "GROUP BY CAST(ROUND(DATEDIFF(second, '1970-01-01', time_column)/300.0, 0) as bigint)*300")
})
Convey("interpolate __timeGroup function with fill (value = NULL)", func() {
@@ -97,21 +90,21 @@ func TestMacroEngine(t *testing.T) {
sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
So(err, ShouldBeNil)
- So(sql, ShouldEqual, "select DATEADD(second, 18446744066914186738+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')")
+ So(sql, ShouldEqual, "select DATEADD(second, 18446744066914186738, '1970-01-01')")
})
Convey("interpolate __timeTo function", func() {
sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
So(err, ShouldBeNil)
- So(sql, ShouldEqual, "select DATEADD(second, 18446744066914187038+DATEDIFF(second,GETUTCDATE(),GETDATE()), '1970-01-01')")
+ So(sql, ShouldEqual, "select DATEADD(second, 18446744066914187038, '1970-01-01')")
})
Convey("interpolate __unixEpochFilter function", func() {
- sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(18446744066914186738)")
+ sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time_column)")
So(err, ShouldBeNil)
- So(sql, ShouldEqual, "select 18446744066914186738 >= 18446744066914186738 AND 18446744066914186738 <= 18446744066914187038")
+ So(sql, ShouldEqual, "select time_column >= 18446744066914186738 AND time_column <= 18446744066914187038")
})
Convey("interpolate __unixEpochFrom function", func() {
diff --git a/pkg/tsdb/mssql/mssql.go b/pkg/tsdb/mssql/mssql.go
index af68ca0424e..2638fd8bb40 100644
--- a/pkg/tsdb/mssql/mssql.go
+++ b/pkg/tsdb/mssql/mssql.go
@@ -119,15 +119,10 @@ func (e MssqlQueryEndpoint) transformToTable(query *tsdb.Query, rows *core.Rows,
return err
}
- // convert column named time to unix timestamp to make
- // native datetime mssql types work in annotation queries
- if timeIndex != -1 {
- switch value := values[timeIndex].(type) {
- case time.Time:
- values[timeIndex] = float64(value.Unix())
- }
- }
-
+ // converts column named time to unix timestamp in milliseconds
+ // to make native mssql datetime types and epoch dates work in
+ // annotation and table queries.
+ tsdb.ConvertSqlTimeColumnToEpochMs(values, timeIndex)
table.Rows = append(table.Rows, values)
}
diff --git a/pkg/tsdb/mssql/mssql_test.go b/pkg/tsdb/mssql/mssql_test.go
index 88b35b1aa2c..4bd1e3a8ad7 100644
--- a/pkg/tsdb/mssql/mssql_test.go
+++ b/pkg/tsdb/mssql/mssql_test.go
@@ -19,6 +19,8 @@ import (
// and set up a MSSQL db named grafanatest and a user/password grafana/Password!
// Use the docker/blocks/mssql_tests/docker-compose.yaml to spin up a
// preconfigured MSSQL server suitable for running these tests.
+// Thers's also a dashboard.json in same directory that you can import to Grafana
+// once you've created a datasource for the test server/database.
// If needed, change the variable below to the IP address of the database.
var serverIP string = "localhost"
@@ -37,44 +39,44 @@ func TestMSSQL(t *testing.T) {
sess := x.NewSession()
defer sess.Close()
- fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC)
+ fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local)
Convey("Given a table with different native data types", func() {
sql := `
- IF OBJECT_ID('dbo.[mssql_types]', 'U') IS NOT NULL
- DROP TABLE dbo.[mssql_types]
+ IF OBJECT_ID('dbo.[mssql_types]', 'U') IS NOT NULL
+ DROP TABLE dbo.[mssql_types]
- CREATE TABLE [mssql_types] (
- c_bit bit,
+ CREATE TABLE [mssql_types] (
+ c_bit bit,
- c_tinyint tinyint,
- c_smallint smallint,
- c_int int,
- c_bigint bigint,
+ c_tinyint tinyint,
+ c_smallint smallint,
+ c_int int,
+ c_bigint bigint,
- c_money money,
- c_smallmoney smallmoney,
- c_numeric numeric(10,5),
- c_real real,
- c_decimal decimal(10,2),
- c_float float,
+ c_money money,
+ c_smallmoney smallmoney,
+ c_numeric numeric(10,5),
+ c_real real,
+ c_decimal decimal(10,2),
+ c_float float,
- c_char char(10),
- c_varchar varchar(10),
- c_text text,
+ c_char char(10),
+ c_varchar varchar(10),
+ c_text text,
- c_nchar nchar(12),
- c_nvarchar nvarchar(12),
- c_ntext ntext,
+ c_nchar nchar(12),
+ c_nvarchar nvarchar(12),
+ c_ntext ntext,
- c_datetime datetime,
- c_datetime2 datetime2,
- c_smalldatetime smalldatetime,
- c_date date,
- c_time time,
- c_datetimeoffset datetimeoffset
- )
- `
+ c_datetime datetime,
+ c_datetime2 datetime2,
+ c_smalldatetime smalldatetime,
+ c_date date,
+ c_time time,
+ c_datetimeoffset datetimeoffset
+ )
+ `
_, err := sess.Exec(sql)
So(err, ShouldBeNil)
@@ -87,14 +89,14 @@ func TestMSSQL(t *testing.T) {
d2 := dt2.Format(dt2Format)
sql = fmt.Sprintf(`
- INSERT INTO [mssql_types]
- SELECT
- 1, 5, 20020, 980300, 1420070400, '$20000.15', '£2.15', 12345.12,
- 1.11, 2.22, 3.33,
- 'char10', 'varchar10', 'text',
- N'☺nchar12☺', N'☺nvarchar12☺', N'☺text☺',
- 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')
- `, d, d2, d, d, d, d2)
+ INSERT INTO [mssql_types]
+ SELECT
+ 1, 5, 20020, 980300, 1420070400, '$20000.15', '£2.15', 12345.12,
+ 1.11, 2.22, 3.33,
+ 'char10', 'varchar10', 'text',
+ N'☺nchar12☺', N'☺nvarchar12☺', N'☺text☺',
+ 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')
+ `, d, d2, d, d, d, d2)
_, err = sess.Exec(sql)
So(err, ShouldBeNil)
@@ -151,14 +153,14 @@ func TestMSSQL(t *testing.T) {
Convey("Given a table with metrics that lacks data for some series ", func() {
sql := `
- IF OBJECT_ID('dbo.[metric]', 'U') IS NOT NULL
- DROP TABLE dbo.[metric]
+ IF OBJECT_ID('dbo.[metric]', 'U') IS NOT NULL
+ DROP TABLE dbo.[metric]
- CREATE TABLE [metric] (
- time datetime,
- value int
- )
- `
+ CREATE TABLE [metric] (
+ time datetime,
+ value int
+ )
+ `
_, err := sess.Exec(sql)
So(err, ShouldBeNil)
@@ -186,14 +188,8 @@ func TestMSSQL(t *testing.T) {
})
}
- dtFormat := "2006-01-02 15:04:05.999999999"
for _, s := range series {
- sql = fmt.Sprintf(`
- INSERT INTO metric (time, value)
- VALUES(CAST('%s' AS DATETIME), %d)
- `, s.Time.Format(dtFormat), s.Value)
-
- _, err = sess.Exec(sql)
+ _, err = sess.Insert(s)
So(err, ShouldBeNil)
}
@@ -211,22 +207,32 @@ func TestMSSQL(t *testing.T) {
}
resp, err := endpoint.Query(nil, nil, query)
- queryResult := resp.Results["A"]
So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
points := queryResult.Series[0].Points
+ So(len(points), ShouldEqual, 6)
- So(len(points), ShouldEqual, 4)
- actualValueFirst := points[0][0].Float64
- actualTimeFirst := time.Unix(int64(points[0][1].Float64)/1000, 0)
- So(actualValueFirst, ShouldEqual, 15)
- So(actualTimeFirst, ShouldEqual, fromStart)
+ dt := fromStart
- actualValueLast := points[3][0].Float64
- actualTimeLast := time.Unix(int64(points[3][1].Float64)/1000, 0)
- So(actualValueLast, ShouldEqual, 20)
- So(actualTimeLast, ShouldEqual, fromStart.Add(25*time.Minute))
+ for i := 0; i < 3; i++ {
+ aValue := points[i][0].Float64
+ aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
+ So(aValue, ShouldEqual, 15)
+ So(aTime, ShouldEqual, dt)
+ dt = dt.Add(5 * time.Minute)
+ }
+
+ // adjust for 5 minute gap
+ dt = dt.Add(5 * time.Minute)
+ for i := 3; i < 6; i++ {
+ aValue := points[i][0].Float64
+ aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
+ So(aValue, ShouldEqual, 20)
+ So(aTime, ShouldEqual, dt)
+ dt = dt.Add(5 * time.Minute)
+ }
})
Convey("When doing a metric query using timeGroup with NULL fill enabled", func() {
@@ -247,33 +253,34 @@ func TestMSSQL(t *testing.T) {
}
resp, err := endpoint.Query(nil, nil, query)
- queryResult := resp.Results["A"]
So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
points := queryResult.Series[0].Points
-
So(len(points), ShouldEqual, 7)
- actualValueFirst := points[0][0].Float64
- actualTimeFirst := time.Unix(int64(points[0][1].Float64)/1000, 0)
- So(actualValueFirst, ShouldEqual, 15)
- So(actualTimeFirst, ShouldEqual, fromStart)
- actualNullPoint := points[3][0]
- actualNullTime := time.Unix(int64(points[3][1].Float64)/1000, 0)
- So(actualNullPoint.Valid, ShouldBeFalse)
- So(actualNullTime, ShouldEqual, fromStart.Add(15*time.Minute))
+ dt := fromStart
- actualValueLast := points[5][0].Float64
- actualTimeLast := time.Unix(int64(points[5][1].Float64)/1000, 0)
- So(actualValueLast, ShouldEqual, 20)
- So(actualTimeLast, ShouldEqual, fromStart.Add(25*time.Minute))
+ for i := 0; i < 3; i++ {
+ aValue := points[i][0].Float64
+ aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
+ So(aValue, ShouldEqual, 15)
+ So(aTime, ShouldEqual, dt)
+ dt = dt.Add(5 * time.Minute)
+ }
- actualLastNullPoint := points[6][0]
- actualLastNullTime := time.Unix(int64(points[6][1].Float64)/1000, 0)
- So(actualLastNullPoint.Valid, ShouldBeFalse)
- So(actualLastNullTime, ShouldEqual, fromStart.Add(30*time.Minute))
+ So(points[3][0].Valid, ShouldBeFalse)
+ // adjust for 5 minute gap
+ dt = dt.Add(5 * time.Minute)
+ for i := 4; i < 7; i++ {
+ aValue := points[i][0].Float64
+ aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
+ So(aValue, ShouldEqual, 20)
+ So(aTime, ShouldEqual, dt)
+ dt = dt.Add(5 * time.Minute)
+ }
})
Convey("When doing a metric query using timeGroup with float fill enabled", func() {
@@ -294,53 +301,44 @@ func TestMSSQL(t *testing.T) {
}
resp, err := endpoint.Query(nil, nil, query)
- queryResult := resp.Results["A"]
So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
points := queryResult.Series[0].Points
-
- So(points[6][0].Float64, ShouldEqual, 1.5)
+ So(points[3][0].Float64, ShouldEqual, 1.5)
})
})
Convey("Given a table with metrics having multiple values and measurements", func() {
- sql := `
- IF OBJECT_ID('dbo.[metric_values]', 'U') IS NOT NULL
- DROP TABLE dbo.[metric_values]
-
- CREATE TABLE [metric_values] (
- time datetime,
- measurement nvarchar(100),
- valueOne int,
- valueTwo int,
- )
- `
-
- _, err := sess.Exec(sql)
- So(err, ShouldBeNil)
-
- type metricValues struct {
+ type metric_values struct {
Time time.Time
Measurement string
- ValueOne int64
- ValueTwo int64
+ ValueOne int64 `xorm:"integer 'valueOne'"`
+ ValueTwo int64 `xorm:"integer 'valueTwo'"`
}
+ if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
+ So(err, ShouldBeNil)
+ sess.DropTable(metric_values{})
+ }
+ err := sess.CreateTable(metric_values{})
+ So(err, ShouldBeNil)
+
rand.Seed(time.Now().Unix())
rnd := func(min, max int64) int64 {
return rand.Int63n(max-min) + min
}
- series := []*metricValues{}
+ series := []*metric_values{}
for _, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
- series = append(series, &metricValues{
+ series = append(series, &metric_values{
Time: t,
Measurement: "Metric A",
ValueOne: rnd(0, 100),
ValueTwo: rnd(0, 100),
})
- series = append(series, &metricValues{
+ series = append(series, &metric_values{
Time: t,
Measurement: "Metric B",
ValueOne: rnd(0, 100),
@@ -348,14 +346,8 @@ func TestMSSQL(t *testing.T) {
})
}
- dtFormat := "2006-01-02 15:04:05"
for _, s := range series {
- sql = fmt.Sprintf(`
- INSERT metric_values (time, measurement, valueOne, valueTwo)
- VALUES(CAST('%s' AS DATETIME), '%s', %d, %d)
- `, s.Time.Format(dtFormat), s.Measurement, s.ValueOne, s.ValueTwo)
-
- _, err = sess.Exec(sql)
+ _, err = sess.Insert(s)
So(err, ShouldBeNil)
}
@@ -373,8 +365,8 @@ func TestMSSQL(t *testing.T) {
}
resp, err := endpoint.Query(nil, nil, query)
- queryResult := resp.Results["A"]
So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
So(len(queryResult.Series), ShouldEqual, 2)
@@ -396,8 +388,8 @@ func TestMSSQL(t *testing.T) {
}
resp, err := endpoint.Query(nil, nil, query)
- queryResult := resp.Results["A"]
So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
So(len(queryResult.Series), ShouldEqual, 2)
@@ -407,45 +399,55 @@ func TestMSSQL(t *testing.T) {
Convey("Given a stored procedure that takes @from and @to in epoch time", func() {
sql := `
- IF object_id('sp_test_epoch') IS NOT NULL
- DROP PROCEDURE sp_test_epoch
- `
+ IF object_id('sp_test_epoch') IS NOT NULL
+ DROP PROCEDURE sp_test_epoch
+ `
_, err := sess.Exec(sql)
So(err, ShouldBeNil)
sql = `
- CREATE PROCEDURE sp_test_epoch(
- @from int,
- @to int
- ) AS
- BEGIN
- SELECT
- cast(cast(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time))/600 as int)*600 as int) as time,
- measurement + ' - value one' as metric,
- avg(valueOne) as value
- FROM
- metric_values
- WHERE
- time >= DATEADD(s, @from, '1970-01-01') AND time <= DATEADD(s, @to, '1970-01-01')
- GROUP BY
- cast(cast(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time))/600 as int)*600 as int),
- measurement
- UNION ALL
- SELECT
- cast(cast(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time))/600 as int)*600 as int) as time,
- measurement + ' - value two' as metric,
- avg(valueTwo) as value
- FROM
- metric_values
- WHERE
- time >= DATEADD(s, @from, '1970-01-01') AND time <= DATEADD(s, @to, '1970-01-01')
- GROUP BY
- cast(cast(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second,GETDATE(),GETUTCDATE()), time))/600 as int)*600 as int),
- measurement
- ORDER BY 1
- END
- `
+ CREATE PROCEDURE sp_test_epoch(
+ @from int,
+ @to int,
+ @interval nvarchar(50) = '5m',
+ @metric nvarchar(200) = 'ALL'
+ ) AS
+ BEGIN
+ DECLARE @dInterval int
+ SELECT @dInterval = 300
+
+ IF @interval = '10m'
+ SELECT @dInterval = 600
+
+ SELECT
+ CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
+ measurement + ' - value one' as metric,
+ avg(valueOne) as value
+ FROM
+ metric_values
+ WHERE
+ time BETWEEN DATEADD(s, @from, '1970-01-01') AND DATEADD(s, @to, '1970-01-01') AND
+ (@metric = 'ALL' OR measurement = @metric)
+ GROUP BY
+ CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
+ measurement
+ UNION ALL
+ SELECT
+ CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
+ measurement + ' - value two' as metric,
+ avg(valueTwo) as value
+ FROM
+ metric_values
+ WHERE
+ time BETWEEN DATEADD(s, @from, '1970-01-01') AND DATEADD(s, @to, '1970-01-01') AND
+ (@metric = 'ALL' OR measurement = @metric)
+ GROUP BY
+ CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
+ measurement
+ ORDER BY 1
+ END
+ `
_, err = sess.Exec(sql)
So(err, ShouldBeNil)
@@ -456,10 +458,10 @@ func TestMSSQL(t *testing.T) {
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `DECLARE
- @from int = $__unixEpochFrom(),
- @to int = $__unixEpochTo()
+ @from int = $__unixEpochFrom(),
+ @to int = $__unixEpochTo()
- EXEC dbo.sp_test_epoch @from, @to`,
+ EXEC dbo.sp_test_epoch @from, @to`,
"format": "time_series",
}),
RefId: "A",
@@ -474,6 +476,7 @@ func TestMSSQL(t *testing.T) {
resp, err := endpoint.Query(nil, nil, query)
queryResult := resp.Results["A"]
So(err, ShouldBeNil)
+ fmt.Println("query", "sql", queryResult.Meta)
So(queryResult.Error, ShouldBeNil)
So(len(queryResult.Series), ShouldEqual, 4)
@@ -486,45 +489,55 @@ func TestMSSQL(t *testing.T) {
Convey("Given a stored procedure that takes @from and @to in datetime", func() {
sql := `
- IF object_id('sp_test_datetime') IS NOT NULL
- DROP PROCEDURE sp_test_datetime
- `
+ IF object_id('sp_test_datetime') IS NOT NULL
+ DROP PROCEDURE sp_test_datetime
+ `
_, err := sess.Exec(sql)
So(err, ShouldBeNil)
sql = `
- CREATE PROCEDURE sp_test_datetime(
- @from datetime,
- @to datetime
- ) AS
- BEGIN
- SELECT
- cast(cast(DATEDIFF(second, {d '1970-01-01'}, time)/600 as int)*600 as int) as time,
- measurement + ' - value one' as metric,
- avg(valueOne) as value
- FROM
- metric_values
- WHERE
- time >= @from AND time <= @to
- GROUP BY
- cast(cast(DATEDIFF(second, {d '1970-01-01'}, time)/600 as int)*600 as int),
- measurement
- UNION ALL
- SELECT
- cast(cast(DATEDIFF(second, {d '1970-01-01'}, time)/600 as int)*600 as int) as time,
- measurement + ' - value two' as metric,
- avg(valueTwo) as value
- FROM
- metric_values
- WHERE
- time >= @from AND time <= @to
- GROUP BY
- cast(cast(DATEDIFF(second, {d '1970-01-01'}, time)/600 as int)*600 as int),
- measurement
- ORDER BY 1
- END
- `
+ CREATE PROCEDURE sp_test_datetime(
+ @from datetime,
+ @to datetime,
+ @interval nvarchar(50) = '5m',
+ @metric nvarchar(200) = 'ALL'
+ ) AS
+ BEGIN
+ DECLARE @dInterval int
+ SELECT @dInterval = 300
+
+ IF @interval = '10m'
+ SELECT @dInterval = 600
+
+ SELECT
+ CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
+ measurement + ' - value one' as metric,
+ avg(valueOne) as value
+ FROM
+ metric_values
+ WHERE
+ time BETWEEN @from AND @to AND
+ (@metric = 'ALL' OR measurement = @metric)
+ GROUP BY
+ CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
+ measurement
+ UNION ALL
+ SELECT
+ CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval as time,
+ measurement + ' - value two' as metric,
+ avg(valueTwo) as value
+ FROM
+ metric_values
+ WHERE
+ time BETWEEN @from AND @to AND
+ (@metric = 'ALL' OR measurement = @metric)
+ GROUP BY
+ CAST(ROUND(DATEDIFF(second, '1970-01-01', time)/CAST(@dInterval as float), 0) as bigint)*@dInterval,
+ measurement
+ ORDER BY 1
+ END
+ `
_, err = sess.Exec(sql)
So(err, ShouldBeNil)
@@ -535,10 +548,10 @@ func TestMSSQL(t *testing.T) {
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": `DECLARE
- @from int = $__unixEpochFrom(),
- @to int = $__unixEpochTo()
+ @from int = $__unixEpochFrom(),
+ @to int = $__unixEpochTo()
- EXEC dbo.sp_test_epoch @from, @to`,
+ EXEC dbo.sp_test_epoch @from, @to`,
"format": "time_series",
}),
RefId: "A",
@@ -570,7 +583,7 @@ func TestMSSQL(t *testing.T) {
DROP TABLE dbo.[event]
CREATE TABLE [event] (
- time_sec bigint,
+ time_sec int,
description nvarchar(100),
tags nvarchar(100),
)
@@ -654,12 +667,191 @@ func TestMSSQL(t *testing.T) {
So(err, ShouldBeNil)
So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
})
+
+ Convey("When doing an annotation query with a time column in datetime format", func() {
+ dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
+ dtFormat := "2006-01-02 15:04:05.999999999"
+
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": fmt.Sprintf(`SELECT
+ CAST('%s' AS DATETIME) as time,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `, dt.Format(dtFormat)),
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000))
+ })
+
+ Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
+ dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
+
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": fmt.Sprintf(`SELECT
+ %d as time,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `, dt.Unix()),
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
+ })
+
+ Convey("When doing an annotation query with a time column in epoch second format (int) should return ms", func() {
+ dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
+
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": fmt.Sprintf(`SELECT
+ cast(%d as int) as time,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `, dt.Unix()),
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
+ })
+
+ Convey("When doing an annotation query with a time column in epoch millisecond format should return ms", func() {
+ dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
+
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": fmt.Sprintf(`SELECT
+ %d as time,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `, dt.Unix()*1000),
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000))
+ })
+
+ Convey("When doing an annotation query with a time column holding a bigint null value should return nil", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": `SELECT
+ cast(null as bigint) as time,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `,
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0], ShouldBeNil)
+ })
+
+ Convey("When doing an annotation query with a time column holding a datetime null value should return nil", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": `SELECT
+ cast(null as datetime) as time,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `,
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0], ShouldBeNil)
+ })
})
})
}
func InitMSSQLTestDB(t *testing.T) *xorm.Engine {
x, err := xorm.NewEngine(sqlutil.TestDB_Mssql.DriverName, strings.Replace(sqlutil.TestDB_Mssql.ConnStr, "localhost", serverIP, 1))
+ x.DatabaseTZ = time.UTC
+ x.TZLocation = time.UTC
// x.ShowSQL()
@@ -667,8 +859,6 @@ func InitMSSQLTestDB(t *testing.T) *xorm.Engine {
t.Fatalf("Failed to init mssql db %v", err)
}
- sqlutil.CleanDB(x)
-
return x
}
diff --git a/pkg/tsdb/mysql/macros.go b/pkg/tsdb/mysql/macros.go
index b0170070dcf..a292f209429 100644
--- a/pkg/tsdb/mysql/macros.go
+++ b/pkg/tsdb/mysql/macros.go
@@ -68,7 +68,7 @@ func replaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]str
func (m *MySqlMacroEngine) evaluateMacro(name string, args []string) (string, error) {
switch name {
- case "__time":
+ case "__timeEpoch", "__time":
if len(args) == 0 {
return "", fmt.Errorf("missing time column argument for macro %v", name)
}
diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go
index f3060e235e5..483974c55a4 100644
--- a/pkg/tsdb/mysql/mysql.go
+++ b/pkg/tsdb/mysql/mysql.go
@@ -81,7 +81,7 @@ func (e MysqlQueryEndpoint) transformToTable(query *tsdb.Query, rows *core.Rows,
// check if there is a column named time
for i, col := range columnNames {
switch col {
- case "time_sec":
+ case "time", "time_sec":
timeIndex = i
}
}
@@ -96,13 +96,10 @@ func (e MysqlQueryEndpoint) transformToTable(query *tsdb.Query, rows *core.Rows,
return err
}
- // for annotations, convert to epoch
- if timeIndex != -1 {
- switch value := values[timeIndex].(type) {
- case time.Time:
- values[timeIndex] = float64(value.UnixNano() / 1e9)
- }
- }
+ // converts column named time to unix timestamp in milliseconds to make
+ // native mysql datetime types and epoch dates work in
+ // annotation and table queries.
+ tsdb.ConvertSqlTimeColumnToEpochMs(values, timeIndex)
table.Rows = append(table.Rows, values)
}
@@ -185,9 +182,37 @@ func (e MysqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core.
return err
}
- rowData := NewStringStringScan(columnNames)
+ columnTypes, err := rows.ColumnTypes()
+ if err != nil {
+ return err
+ }
+
rowLimit := 1000000
rowCount := 0
+ timeIndex := -1
+ metricIndex := -1
+
+ // check columns of resultset: a column named time is mandatory
+ // the first text column is treated as metric name unless a column named metric is present
+ for i, col := range columnNames {
+ switch col {
+ case "time", "time_sec":
+ timeIndex = i
+ case "metric":
+ metricIndex = i
+ default:
+ if metricIndex == -1 {
+ switch columnTypes[i].DatabaseTypeName() {
+ case "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT":
+ metricIndex = i
+ }
+ }
+ }
+ }
+
+ if timeIndex == -1 {
+ return fmt.Errorf("Found no column named time or time_sec")
+ }
fillMissing := query.Model.Get("fill").MustBool(false)
var fillInterval float64
@@ -198,53 +223,90 @@ func (e MysqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core.
fillValue.Float64 = query.Model.Get("fillValue").MustFloat64()
fillValue.Valid = true
}
-
}
- for ; rows.Next(); rowCount++ {
+ for rows.Next() {
+ var timestamp float64
+ var value null.Float
+ var metric string
+
if rowCount > rowLimit {
- return fmt.Errorf("MySQL query row limit exceeded, limit %d", rowLimit)
+ return fmt.Errorf("PostgreSQL query row limit exceeded, limit %d", rowLimit)
}
- err := rowData.Update(rows.Rows)
+ values, err := e.getTypedRowData(rows)
if err != nil {
- e.log.Error("MySQL response parsing", "error", err)
- return fmt.Errorf("MySQL response parsing error %v", err)
+ return err
}
- if rowData.metric == "" {
- rowData.metric = "Unknown"
+ switch columnValue := values[timeIndex].(type) {
+ case int64:
+ timestamp = float64(columnValue * 1000)
+ case float64:
+ timestamp = columnValue * 1000
+ case time.Time:
+ timestamp = float64(columnValue.UnixNano() / 1e6)
+ default:
+ return fmt.Errorf("Invalid type for column time, must be of type timestamp or unix timestamp, got: %T %v", columnValue, columnValue)
}
- if !rowData.time.Valid {
- return fmt.Errorf("Found row with no time value")
- }
-
- series, exist := pointsBySeries[rowData.metric]
- if exist == false {
- series = &tsdb.TimeSeries{Name: rowData.metric}
- pointsBySeries[rowData.metric] = series
- seriesByQueryOrder.PushBack(rowData.metric)
- }
-
- if fillMissing {
- var intervalStart float64
- if exist == false {
- intervalStart = float64(tsdbQuery.TimeRange.MustGetFrom().UnixNano() / 1e6)
+ if metricIndex >= 0 {
+ if columnValue, ok := values[metricIndex].(string); ok == true {
+ metric = columnValue
} else {
- intervalStart = series.Points[len(series.Points)-1][1].Float64 + fillInterval
- }
-
- // align interval start
- intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval
-
- for i := intervalStart; i < rowData.time.Float64; i += fillInterval {
- series.Points = append(series.Points, tsdb.TimePoint{fillValue, null.FloatFrom(i)})
- rowCount++
+ return fmt.Errorf("Column metric must be of type char,varchar or text, got: %T %v", values[metricIndex], values[metricIndex])
}
}
- series.Points = append(series.Points, tsdb.TimePoint{rowData.value, rowData.time})
+ for i, col := range columnNames {
+ if i == timeIndex || i == metricIndex {
+ continue
+ }
+
+ switch columnValue := values[i].(type) {
+ case int64:
+ value = null.FloatFrom(float64(columnValue))
+ case float64:
+ value = null.FloatFrom(columnValue)
+ case nil:
+ value.Valid = false
+ default:
+ return fmt.Errorf("Value column must have numeric datatype, column: %s type: %T value: %v", col, columnValue, columnValue)
+ }
+ if metricIndex == -1 {
+ metric = col
+ }
+
+ series, exist := pointsBySeries[metric]
+ if exist == false {
+ series = &tsdb.TimeSeries{Name: metric}
+ pointsBySeries[metric] = series
+ seriesByQueryOrder.PushBack(metric)
+ }
+
+ if fillMissing {
+ var intervalStart float64
+ if exist == false {
+ intervalStart = float64(tsdbQuery.TimeRange.MustGetFrom().UnixNano() / 1e6)
+ } else {
+ intervalStart = series.Points[len(series.Points)-1][1].Float64 + fillInterval
+ }
+
+ // align interval start
+ intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval
+
+ for i := intervalStart; i < timestamp; i += fillInterval {
+ series.Points = append(series.Points, tsdb.TimePoint{fillValue, null.FloatFrom(i)})
+ rowCount++
+ }
+ }
+
+ series.Points = append(series.Points, tsdb.TimePoint{value, null.FloatFrom(timestamp)})
+
+ e.log.Debug("Rows", "metric", metric, "time", timestamp, "value", value)
+ rowCount++
+
+ }
}
for elem := seriesByQueryOrder.Front(); elem != nil; elem = elem.Next() {
@@ -269,62 +331,3 @@ func (e MysqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core.
result.Meta.Set("rowCount", rowCount)
return nil
}
-
-type stringStringScan struct {
- rowPtrs []interface{}
- rowValues []string
- columnNames []string
- columnCount int
-
- time null.Float
- value null.Float
- metric string
-}
-
-func NewStringStringScan(columnNames []string) *stringStringScan {
- s := &stringStringScan{
- columnCount: len(columnNames),
- columnNames: columnNames,
- rowPtrs: make([]interface{}, len(columnNames)),
- rowValues: make([]string, len(columnNames)),
- }
-
- for i := 0; i < s.columnCount; i++ {
- s.rowPtrs[i] = new(sql.RawBytes)
- }
-
- return s
-}
-
-func (s *stringStringScan) Update(rows *sql.Rows) error {
- if err := rows.Scan(s.rowPtrs...); err != nil {
- return err
- }
-
- s.time = null.FloatFromPtr(nil)
- s.value = null.FloatFromPtr(nil)
-
- for i := 0; i < s.columnCount; i++ {
- if rb, ok := s.rowPtrs[i].(*sql.RawBytes); ok {
- s.rowValues[i] = string(*rb)
-
- switch s.columnNames[i] {
- case "time_sec":
- if sec, err := strconv.ParseInt(s.rowValues[i], 10, 64); err == nil {
- s.time = null.FloatFrom(float64(sec * 1000))
- }
- case "value":
- if value, err := strconv.ParseFloat(s.rowValues[i], 64); err == nil {
- s.value = null.FloatFrom(value)
- }
- case "metric":
- s.metric = s.rowValues[i]
- }
-
- *rb = nil // reset pointer to discard current value to avoid a bug
- } else {
- return fmt.Errorf("Cannot convert index %d column %s to type *sql.RawBytes", i, s.columnNames[i])
- }
- }
- return nil
-}
diff --git a/pkg/tsdb/mysql/mysql_test.go b/pkg/tsdb/mysql/mysql_test.go
index fe2c82223d2..750704c9965 100644
--- a/pkg/tsdb/mysql/mysql_test.go
+++ b/pkg/tsdb/mysql/mysql_test.go
@@ -1,6 +1,8 @@
package mysql
import (
+ "fmt"
+ "math/rand"
"testing"
"time"
@@ -14,6 +16,10 @@ import (
// To run this test, remove the Skip from SkipConvey
// and set up a MySQL db named grafana_tests and a user/password grafana/password
+// Use the docker/blocks/mysql_tests/docker-compose.yaml to spin up a
+// preconfigured MySQL server suitable for running these tests.
+// Thers's also a dashboard.json in same directory that you can import to Grafana
+// once you've created a datasource for the test server/database.
func TestMySQL(t *testing.T) {
SkipConvey("MySQL", t, func() {
x := InitMySQLTestDB(t)
@@ -29,110 +35,621 @@ func TestMySQL(t *testing.T) {
sess := x.NewSession()
defer sess.Close()
- sql := "CREATE TABLE `mysql_types` ("
- sql += "`atinyint` tinyint(1) NOT NULL,"
- sql += "`avarchar` varchar(3) NOT NULL,"
- sql += "`achar` char(3),"
- sql += "`amediumint` mediumint NOT NULL,"
- sql += "`asmallint` smallint NOT NULL,"
- sql += "`abigint` bigint NOT NULL,"
- sql += "`aint` int(11) NOT NULL,"
- sql += "`adouble` double(10,2),"
- sql += "`anewdecimal` decimal(10,2),"
- sql += "`afloat` float(10,2) NOT NULL,"
- sql += "`atimestamp` timestamp NOT NULL,"
- sql += "`adatetime` datetime NOT NULL,"
- sql += "`atime` time NOT NULL,"
- // sql += "`ayear` year," // Crashes xorm when running cleandb
- sql += "`abit` bit(1),"
- sql += "`atinytext` tinytext,"
- sql += "`atinyblob` tinyblob,"
- sql += "`atext` text,"
- sql += "`ablob` blob,"
- sql += "`amediumtext` mediumtext,"
- sql += "`amediumblob` mediumblob,"
- sql += "`alongtext` longtext,"
- sql += "`alongblob` longblob,"
- sql += "`aenum` enum('val1', 'val2'),"
- sql += "`aset` set('a', 'b', 'c', 'd'),"
- sql += "`adate` date,"
- sql += "`time_sec` datetime(6),"
- sql += "`aintnull` int(11),"
- sql += "`afloatnull` float(10,2),"
- sql += "`avarcharnull` varchar(3),"
- sql += "`adecimalnull` decimal(10,2)"
- sql += ") ENGINE=InnoDB DEFAULT CHARSET=latin1;"
- _, err := sess.Exec(sql)
- So(err, ShouldBeNil)
+ fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.Local)
- sql = "INSERT INTO `mysql_types` "
- sql += "(`atinyint`, `avarchar`, `achar`, `amediumint`, `asmallint`, `abigint`, `aint`, `adouble`, "
- sql += "`anewdecimal`, `afloat`, `adatetime`, `atimestamp`, `atime`, `abit`, `atinytext`, "
- sql += "`atinyblob`, `atext`, `ablob`, `amediumtext`, `amediumblob`, `alongtext`, `alongblob`, "
- sql += "`aenum`, `aset`, `adate`, `time_sec`) "
- sql += "VALUES(1, 'abc', 'def', 1, 10, 100, 1420070400, 1.11, "
- sql += "2.22, 3.33, now(), current_timestamp(), '11:11:11', 1, 'tinytext', "
- sql += "'tinyblob', 'text', 'blob', 'mediumtext', 'mediumblob', 'longtext', 'longblob', "
- sql += "'val2', 'a,b', curdate(), '2018-01-01 00:01:01.123456');"
- _, err = sess.Exec(sql)
- So(err, ShouldBeNil)
-
- Convey("Query with Table format should map MySQL column types to Go types", func() {
- query := &tsdb.TsdbQuery{
- Queries: []*tsdb.Query{
- {
- Model: simplejson.NewFromAny(map[string]interface{}{
- "rawSql": "SELECT * FROM mysql_types",
- "format": "table",
- }),
- RefId: "A",
- },
- },
+ Convey("Given a table with different native data types", func() {
+ if exists, err := sess.IsTableExist("mysql_types"); err != nil || exists {
+ So(err, ShouldBeNil)
+ sess.DropTable("mysql_types")
}
- resp, err := endpoint.Query(nil, nil, query)
- queryResult := resp.Results["A"]
+ sql := "CREATE TABLE `mysql_types` ("
+ sql += "`atinyint` tinyint(1) NOT NULL,"
+ sql += "`avarchar` varchar(3) NOT NULL,"
+ sql += "`achar` char(3),"
+ sql += "`amediumint` mediumint NOT NULL,"
+ sql += "`asmallint` smallint NOT NULL,"
+ sql += "`abigint` bigint NOT NULL,"
+ sql += "`aint` int(11) NOT NULL,"
+ sql += "`adouble` double(10,2),"
+ sql += "`anewdecimal` decimal(10,2),"
+ sql += "`afloat` float(10,2) NOT NULL,"
+ sql += "`atimestamp` timestamp NOT NULL,"
+ sql += "`adatetime` datetime NOT NULL,"
+ sql += "`atime` time NOT NULL,"
+ sql += "`ayear` year," // Crashes xorm when running cleandb
+ sql += "`abit` bit(1),"
+ sql += "`atinytext` tinytext,"
+ sql += "`atinyblob` tinyblob,"
+ sql += "`atext` text,"
+ sql += "`ablob` blob,"
+ sql += "`amediumtext` mediumtext,"
+ sql += "`amediumblob` mediumblob,"
+ sql += "`alongtext` longtext,"
+ sql += "`alongblob` longblob,"
+ sql += "`aenum` enum('val1', 'val2'),"
+ sql += "`aset` set('a', 'b', 'c', 'd'),"
+ sql += "`adate` date,"
+ sql += "`time_sec` datetime(6),"
+ sql += "`aintnull` int(11),"
+ sql += "`afloatnull` float(10,2),"
+ sql += "`avarcharnull` varchar(3),"
+ sql += "`adecimalnull` decimal(10,2)"
+ sql += ") ENGINE=InnoDB DEFAULT CHARSET=latin1;"
+ _, err := sess.Exec(sql)
So(err, ShouldBeNil)
- column := queryResult.Tables[0].Rows[0]
+ sql = "INSERT INTO `mysql_types` "
+ sql += "(`atinyint`, `avarchar`, `achar`, `amediumint`, `asmallint`, `abigint`, `aint`, `adouble`, "
+ sql += "`anewdecimal`, `afloat`, `adatetime`, `atimestamp`, `atime`, `ayear`, `abit`, `atinytext`, "
+ sql += "`atinyblob`, `atext`, `ablob`, `amediumtext`, `amediumblob`, `alongtext`, `alongblob`, "
+ sql += "`aenum`, `aset`, `adate`, `time_sec`) "
+ sql += "VALUES(1, 'abc', 'def', 1, 10, 100, 1420070400, 1.11, "
+ sql += "2.22, 3.33, now(), current_timestamp(), '11:11:11', '2018', 1, 'tinytext', "
+ sql += "'tinyblob', 'text', 'blob', 'mediumtext', 'mediumblob', 'longtext', 'longblob', "
+ sql += "'val2', 'a,b', curdate(), '2018-01-01 00:01:01.123456');"
+ _, err = sess.Exec(sql)
+ So(err, ShouldBeNil)
- So(*column[0].(*int8), ShouldEqual, 1)
- So(column[1].(string), ShouldEqual, "abc")
- So(column[2].(string), ShouldEqual, "def")
- So(*column[3].(*int32), ShouldEqual, 1)
- So(*column[4].(*int16), ShouldEqual, 10)
- So(*column[5].(*int64), ShouldEqual, 100)
- So(*column[6].(*int32), ShouldEqual, 1420070400)
- So(column[7].(float64), ShouldEqual, 1.11)
- So(column[8].(float64), ShouldEqual, 2.22)
- So(*column[9].(*float32), ShouldEqual, 3.33)
- _, offset := time.Now().Zone()
- So(column[10].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now().Add(time.Duration(offset)*time.Second))
- So(column[11].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now().Add(time.Duration(offset)*time.Second))
- So(column[12].(string), ShouldEqual, "11:11:11")
- So(*column[13].(*[]byte), ShouldHaveSameTypeAs, []byte{1})
- So(column[14].(string), ShouldEqual, "tinytext")
- So(column[15].(string), ShouldEqual, "tinyblob")
- So(column[16].(string), ShouldEqual, "text")
- So(column[17].(string), ShouldEqual, "blob")
- So(column[18].(string), ShouldEqual, "mediumtext")
- So(column[19].(string), ShouldEqual, "mediumblob")
- So(column[20].(string), ShouldEqual, "longtext")
- So(column[21].(string), ShouldEqual, "longblob")
- So(column[22].(string), ShouldEqual, "val2")
- So(column[23].(string), ShouldEqual, "a,b")
- So(column[24].(time.Time).Format("2006-01-02T00:00:00Z"), ShouldEqual, time.Now().Format("2006-01-02T00:00:00Z"))
- So(column[25].(float64), ShouldEqual, 1514764861)
- So(column[26], ShouldEqual, nil)
- So(column[27], ShouldEqual, nil)
- So(column[28], ShouldEqual, "")
- So(column[29], ShouldEqual, nil)
+ Convey("Query with Table format should map MySQL column types to Go types", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": "SELECT * FROM mysql_types",
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+
+ column := queryResult.Tables[0].Rows[0]
+
+ So(*column[0].(*int8), ShouldEqual, 1)
+ So(column[1].(string), ShouldEqual, "abc")
+ So(column[2].(string), ShouldEqual, "def")
+ So(*column[3].(*int32), ShouldEqual, 1)
+ So(*column[4].(*int16), ShouldEqual, 10)
+ So(*column[5].(*int64), ShouldEqual, 100)
+ So(*column[6].(*int32), ShouldEqual, 1420070400)
+ So(column[7].(float64), ShouldEqual, 1.11)
+ So(column[8].(float64), ShouldEqual, 2.22)
+ So(*column[9].(*float32), ShouldEqual, 3.33)
+ _, offset := time.Now().Zone()
+ So(column[10].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now().Add(time.Duration(offset)*time.Second))
+ So(column[11].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now().Add(time.Duration(offset)*time.Second))
+ So(column[12].(string), ShouldEqual, "11:11:11")
+ So(column[13].(int64), ShouldEqual, 2018)
+ So(*column[14].(*[]byte), ShouldHaveSameTypeAs, []byte{1})
+ So(column[15].(string), ShouldEqual, "tinytext")
+ So(column[16].(string), ShouldEqual, "tinyblob")
+ So(column[17].(string), ShouldEqual, "text")
+ So(column[18].(string), ShouldEqual, "blob")
+ So(column[19].(string), ShouldEqual, "mediumtext")
+ So(column[20].(string), ShouldEqual, "mediumblob")
+ So(column[21].(string), ShouldEqual, "longtext")
+ So(column[22].(string), ShouldEqual, "longblob")
+ So(column[23].(string), ShouldEqual, "val2")
+ So(column[24].(string), ShouldEqual, "a,b")
+ So(column[25].(time.Time).Format("2006-01-02T00:00:00Z"), ShouldEqual, time.Now().Format("2006-01-02T00:00:00Z"))
+ So(column[26].(float64), ShouldEqual, float64(1514764861000))
+ So(column[27], ShouldEqual, nil)
+ So(column[28], ShouldEqual, nil)
+ So(column[29], ShouldEqual, "")
+ So(column[30], ShouldEqual, nil)
+ })
+ })
+
+ Convey("Given a table with metrics that lacks data for some series ", func() {
+ type metric struct {
+ Time time.Time
+ Value int64
+ }
+
+ if exist, err := sess.IsTableExist(metric{}); err != nil || exist {
+ So(err, ShouldBeNil)
+ sess.DropTable(metric{})
+ }
+ err := sess.CreateTable(metric{})
+ So(err, ShouldBeNil)
+
+ series := []*metric{}
+ firstRange := genTimeRangeByInterval(fromStart, 10*time.Minute, 10*time.Second)
+ secondRange := genTimeRangeByInterval(fromStart.Add(20*time.Minute), 10*time.Minute, 10*time.Second)
+
+ for _, t := range firstRange {
+ series = append(series, &metric{
+ Time: t,
+ Value: 15,
+ })
+ }
+
+ for _, t := range secondRange {
+ series = append(series, &metric{
+ Time: t,
+ Value: 20,
+ })
+ }
+
+ for _, s := range series {
+ _, err = sess.Insert(s)
+ So(err, ShouldBeNil)
+ }
+
+ Convey("When doing a metric query using timeGroup", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": "SELECT $__timeGroup(time, '5m') as time_sec, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
+ "format": "time_series",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+
+ points := queryResult.Series[0].Points
+ So(len(points), ShouldEqual, 6)
+
+ dt := fromStart
+
+ for i := 0; i < 3; i++ {
+ aValue := points[i][0].Float64
+ aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
+ So(aValue, ShouldEqual, 15)
+ So(aTime, ShouldEqual, dt)
+ dt = dt.Add(5 * time.Minute)
+ }
+
+ // adjust for 5 minute gap
+ dt = dt.Add(5 * time.Minute)
+ for i := 3; i < 6; i++ {
+ aValue := points[i][0].Float64
+ aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
+ So(aValue, ShouldEqual, 20)
+ So(aTime, ShouldEqual, dt)
+ dt = dt.Add(5 * time.Minute)
+ }
+ })
+
+ Convey("When doing a metric query using timeGroup with NULL fill enabled", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": "SELECT $__timeGroup(time, '5m', NULL) as time_sec, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
+ "format": "time_series",
+ }),
+ RefId: "A",
+ },
+ },
+ TimeRange: &tsdb.TimeRange{
+ From: fmt.Sprintf("%v", fromStart.Unix()*1000),
+ To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+
+ points := queryResult.Series[0].Points
+ So(len(points), ShouldEqual, 7)
+
+ dt := fromStart
+
+ for i := 0; i < 3; i++ {
+ aValue := points[i][0].Float64
+ aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
+ So(aValue, ShouldEqual, 15)
+ So(aTime, ShouldEqual, dt)
+ dt = dt.Add(5 * time.Minute)
+ }
+
+ So(points[3][0].Valid, ShouldBeFalse)
+
+ // adjust for 5 minute gap
+ dt = dt.Add(5 * time.Minute)
+ for i := 4; i < 7; i++ {
+ aValue := points[i][0].Float64
+ aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
+ So(aValue, ShouldEqual, 20)
+ So(aTime, ShouldEqual, dt)
+ dt = dt.Add(5 * time.Minute)
+ }
+ })
+
+ Convey("When doing a metric query using timeGroup with float fill enabled", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": "SELECT $__timeGroup(time, '5m', 1.5) as time_sec, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
+ "format": "time_series",
+ }),
+ RefId: "A",
+ },
+ },
+ TimeRange: &tsdb.TimeRange{
+ From: fmt.Sprintf("%v", fromStart.Unix()*1000),
+ To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+
+ points := queryResult.Series[0].Points
+ So(points[3][0].Float64, ShouldEqual, 1.5)
+ })
+ })
+
+ Convey("Given a table with metrics having multiple values and measurements", func() {
+ type metric_values struct {
+ Time time.Time
+ Measurement string
+ ValueOne int64 `xorm:"integer 'valueOne'"`
+ ValueTwo int64 `xorm:"integer 'valueTwo'"`
+ }
+
+ if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
+ So(err, ShouldBeNil)
+ sess.DropTable(metric_values{})
+ }
+ err := sess.CreateTable(metric_values{})
+ So(err, ShouldBeNil)
+
+ rand.Seed(time.Now().Unix())
+ rnd := func(min, max int64) int64 {
+ return rand.Int63n(max-min) + min
+ }
+
+ series := []*metric_values{}
+ for _, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
+ series = append(series, &metric_values{
+ Time: t,
+ Measurement: "Metric A",
+ ValueOne: rnd(0, 100),
+ ValueTwo: rnd(0, 100),
+ })
+ series = append(series, &metric_values{
+ Time: t,
+ Measurement: "Metric B",
+ ValueOne: rnd(0, 100),
+ ValueTwo: rnd(0, 100),
+ })
+ }
+
+ for _, s := range series {
+ _, err := sess.Insert(s)
+ So(err, ShouldBeNil)
+ }
+
+ Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": `SELECT $__time(time), CONCAT(measurement, ' - value one') as metric, valueOne FROM metric_values ORDER BY 1`,
+ "format": "time_series",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+
+ So(len(queryResult.Series), ShouldEqual, 2)
+ So(queryResult.Series[0].Name, ShouldEqual, "Metric B - value one")
+ So(queryResult.Series[1].Name, ShouldEqual, "Metric A - value one")
+ })
+
+ Convey("When doing a metric query grouping by time should return correct series", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": `SELECT $__time(time), valueOne, valueTwo FROM metric_values ORDER BY 1`,
+ "format": "time_series",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+
+ So(len(queryResult.Series), ShouldEqual, 2)
+ So(queryResult.Series[0].Name, ShouldEqual, "valueOne")
+ So(queryResult.Series[1].Name, ShouldEqual, "valueTwo")
+ })
+ })
+
+ Convey("Given a table with event data", func() {
+ type event struct {
+ TimeSec int64
+ Description string
+ Tags string
+ }
+
+ if exist, err := sess.IsTableExist(event{}); err != nil || exist {
+ So(err, ShouldBeNil)
+ sess.DropTable(event{})
+ }
+ err := sess.CreateTable(event{})
+ So(err, ShouldBeNil)
+
+ events := []*event{}
+ for _, t := range genTimeRangeByInterval(fromStart.Add(-20*time.Minute), 60*time.Minute, 25*time.Minute) {
+ events = append(events, &event{
+ TimeSec: t.Unix(),
+ Description: "Someone deployed something",
+ Tags: "deploy",
+ })
+ events = append(events, &event{
+ TimeSec: t.Add(5 * time.Minute).Unix(),
+ Description: "New support ticket registered",
+ Tags: "ticket",
+ })
+ }
+
+ for _, e := range events {
+ _, err = sess.Insert(e)
+ So(err, ShouldBeNil)
+ }
+
+ Convey("When doing an annotation query of deploy events should return expected result", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": `SELECT time_sec, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC`,
+ "format": "table",
+ }),
+ RefId: "Deploys",
+ },
+ },
+ TimeRange: &tsdb.TimeRange{
+ From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
+ To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ queryResult := resp.Results["Deploys"]
+ So(err, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
+ })
+
+ Convey("When doing an annotation query of ticket events should return expected result", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": `SELECT time_sec, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC`,
+ "format": "table",
+ }),
+ RefId: "Tickets",
+ },
+ },
+ TimeRange: &tsdb.TimeRange{
+ From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
+ To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ queryResult := resp.Results["Tickets"]
+ So(err, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
+ })
+
+ Convey("When doing an annotation query with a time column in datetime format", func() {
+ dt := time.Date(2018, 3, 14, 21, 20, 6, 0, time.UTC)
+ dtFormat := "2006-01-02 15:04:05.999999999"
+
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": fmt.Sprintf(`SELECT
+ CAST('%s' as datetime) as time_sec,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `, dt.Format(dtFormat)),
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000))
+ })
+
+ Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
+ dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
+
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": fmt.Sprintf(`SELECT
+ %d as time_sec,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `, dt.Unix()),
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
+ })
+
+ Convey("When doing an annotation query with a time column in epoch second format (signed integer) should return ms", func() {
+ dt := time.Date(2018, 3, 14, 21, 20, 6, 0, time.Local)
+
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": fmt.Sprintf(`SELECT
+ CAST('%d' as signed integer) as time_sec,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `, dt.Unix()),
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
+ })
+
+ Convey("When doing an annotation query with a time column in epoch millisecond format should return ms", func() {
+ dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
+
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": fmt.Sprintf(`SELECT
+ %d as time_sec,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `, dt.Unix()*1000),
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
+ })
+
+ Convey("When doing an annotation query with a time column holding a unsigned integer null value should return nil", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": `SELECT
+ cast(null as unsigned integer) as time_sec,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `,
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0], ShouldBeNil)
+ })
+
+ Convey("When doing an annotation query with a time column holding a DATETIME null value should return nil", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": `SELECT
+ cast(null as DATETIME) as time_sec,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `,
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0], ShouldBeNil)
+ })
})
})
}
func InitMySQLTestDB(t *testing.T) *xorm.Engine {
x, err := xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr+"&parseTime=true")
+ x.DatabaseTZ = time.Local
+ x.TZLocation = time.Local
// x.ShowSQL()
@@ -140,7 +657,18 @@ func InitMySQLTestDB(t *testing.T) *xorm.Engine {
t.Fatalf("Failed to init mysql db %v", err)
}
- sqlutil.CleanDB(x)
-
return x
}
+
+func genTimeRangeByInterval(from time.Time, duration time.Duration, interval time.Duration) []time.Time {
+ durationSec := int64(duration.Seconds())
+ intervalSec := int64(interval.Seconds())
+ timeRange := []time.Time{}
+
+ for i := int64(0); i < durationSec; i += intervalSec {
+ timeRange = append(timeRange, from)
+ from = from.Add(time.Duration(int64(time.Second) * intervalSec))
+ }
+
+ return timeRange
+}
diff --git a/pkg/tsdb/postgres/postgres.go b/pkg/tsdb/postgres/postgres.go
index 6a084ad1237..5f6b56ebcf1 100644
--- a/pkg/tsdb/postgres/postgres.go
+++ b/pkg/tsdb/postgres/postgres.go
@@ -63,7 +63,6 @@ func (e *PostgresQueryEndpoint) Query(ctx context.Context, dsInfo *models.DataSo
}
func (e PostgresQueryEndpoint) transformToTable(query *tsdb.Query, rows *core.Rows, result *tsdb.QueryResult, tsdbQuery *tsdb.TsdbQuery) error {
-
columnNames, err := rows.Columns()
if err != nil {
return err
@@ -100,14 +99,10 @@ func (e PostgresQueryEndpoint) transformToTable(query *tsdb.Query, rows *core.Ro
return err
}
- // convert column named time to unix timestamp to make
- // native datetime postgres types work in annotation queries
- if timeIndex != -1 {
- switch value := values[timeIndex].(type) {
- case time.Time:
- values[timeIndex] = float64(value.UnixNano() / 1e9)
- }
- }
+ // converts column named time to unix timestamp in milliseconds to make
+ // native postgres datetime types and epoch dates work in
+ // annotation and table queries.
+ tsdb.ConvertSqlTimeColumnToEpochMs(values, timeIndex)
table.Rows = append(table.Rows, values)
}
@@ -118,7 +113,6 @@ func (e PostgresQueryEndpoint) transformToTable(query *tsdb.Query, rows *core.Ro
}
func (e PostgresQueryEndpoint) getTypedRowData(rows *core.Rows) (tsdb.RowValues, error) {
-
types, err := rows.ColumnTypes()
if err != nil {
return nil, err
@@ -209,7 +203,6 @@ func (e PostgresQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *co
fillValue.Float64 = query.Model.Get("fillValue").MustFloat64()
fillValue.Valid = true
}
-
}
for rows.Next() {
diff --git a/pkg/tsdb/postgres/postgres_test.go b/pkg/tsdb/postgres/postgres_test.go
index 75e8cb77f2e..3f2203ac7a4 100644
--- a/pkg/tsdb/postgres/postgres_test.go
+++ b/pkg/tsdb/postgres/postgres_test.go
@@ -1,6 +1,8 @@
package postgres
import (
+ "fmt"
+ "math/rand"
"testing"
"time"
@@ -14,7 +16,11 @@ import (
)
// To run this test, remove the Skip from SkipConvey
-// and set up a PostgreSQL db named grafanatest and a user/password grafanatest/grafanatest
+// and set up a PostgreSQL db named grafanatest and a user/password grafanatest/grafanatest!
+// Use the docker/blocks/postgres_tests/docker-compose.yaml to spin up a
+// preconfigured Postgres server suitable for running these tests.
+// Thers's also a dashboard.json in same directory that you can import to Grafana
+// once you've created a datasource for the test server/database.
func TestPostgres(t *testing.T) {
SkipConvey("PostgreSQL", t, func() {
x := InitPostgresTestDB(t)
@@ -30,88 +36,599 @@ func TestPostgres(t *testing.T) {
sess := x.NewSession()
defer sess.Close()
- sql := `
- CREATE TABLE postgres_types(
- c00_smallint smallint,
- c01_integer integer,
- c02_bigint bigint,
+ fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local)
- c03_real real,
- c04_double double precision,
- c05_decimal decimal(10,2),
- c06_numeric numeric(10,2),
+ Convey("Given a table with different native data types", func() {
+ sql := `
+ DROP TABLE IF EXISTS postgres_types;
+ CREATE TABLE postgres_types(
+ c00_smallint smallint,
+ c01_integer integer,
+ c02_bigint bigint,
- c07_char char(10),
- c08_varchar varchar(10),
- c09_text text,
+ c03_real real,
+ c04_double double precision,
+ c05_decimal decimal(10,2),
+ c06_numeric numeric(10,2),
- c10_timestamp timestamp without time zone,
- c11_timestamptz timestamp with time zone,
- c12_date date,
- c13_time time without time zone,
- c14_timetz time with time zone,
- c15_interval interval
- );
- `
- _, err := sess.Exec(sql)
- So(err, ShouldBeNil)
+ c07_char char(10),
+ c08_varchar varchar(10),
+ c09_text text,
- sql = `
- INSERT INTO postgres_types VALUES(
- 1,2,3,
- 4.5,6.7,1.1,1.2,
- 'char10','varchar10','text',
+ c10_timestamp timestamp without time zone,
+ c11_timestamptz timestamp with time zone,
+ c12_date date,
+ c13_time time without time zone,
+ c14_timetz time with time zone,
- now(),now(),now(),now(),now(),'15m'::interval
- );
- `
- _, err = sess.Exec(sql)
- So(err, ShouldBeNil)
-
- Convey("Query with Table format should map PostgreSQL column types to Go types", func() {
- query := &tsdb.TsdbQuery{
- Queries: []*tsdb.Query{
- {
- Model: simplejson.NewFromAny(map[string]interface{}{
- "rawSql": "SELECT * FROM postgres_types",
- "format": "table",
- }),
- RefId: "A",
- },
- },
- }
-
- resp, err := endpoint.Query(nil, nil, query)
- queryResult := resp.Results["A"]
+ c15_interval interval
+ );
+ `
+ _, err := sess.Exec(sql)
So(err, ShouldBeNil)
- column := queryResult.Tables[0].Rows[0]
- So(column[0].(int64), ShouldEqual, 1)
- So(column[1].(int64), ShouldEqual, 2)
- So(column[2].(int64), ShouldEqual, 3)
- So(column[3].(float64), ShouldEqual, 4.5)
- So(column[4].(float64), ShouldEqual, 6.7)
- // libpq doesnt properly convert decimal, numeric and char to go types but returns []uint8 instead
- // So(column[5].(float64), ShouldEqual, 1.1)
- // So(column[6].(float64), ShouldEqual, 1.2)
- // So(column[7].(string), ShouldEqual, "char")
- So(column[8].(string), ShouldEqual, "varchar10")
- So(column[9].(string), ShouldEqual, "text")
+ sql = `
+ INSERT INTO postgres_types VALUES(
+ 1,2,3,
+ 4.5,6.7,1.1,1.2,
+ 'char10','varchar10','text',
- So(column[10].(time.Time), ShouldHaveSameTypeAs, time.Now())
- So(column[11].(time.Time), ShouldHaveSameTypeAs, time.Now())
- So(column[12].(time.Time), ShouldHaveSameTypeAs, time.Now())
- So(column[13].(time.Time), ShouldHaveSameTypeAs, time.Now())
- So(column[14].(time.Time), ShouldHaveSameTypeAs, time.Now())
+ now(),now(),now(),now(),now(),'15m'::interval
+ );
+ `
+ _, err = sess.Exec(sql)
+ So(err, ShouldBeNil)
- // libpq doesnt properly convert interval to go types but returns []uint8 instead
- // So(column[15].(time.Time), ShouldHaveSameTypeAs, time.Now())
+ Convey("When doing a table query should map Postgres column types to Go types", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": "SELECT * FROM postgres_types",
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+
+ column := queryResult.Tables[0].Rows[0]
+ So(column[0].(int64), ShouldEqual, 1)
+ So(column[1].(int64), ShouldEqual, 2)
+ So(column[2].(int64), ShouldEqual, 3)
+
+ So(column[3].(float64), ShouldEqual, 4.5)
+ So(column[4].(float64), ShouldEqual, 6.7)
+ So(column[5].(float64), ShouldEqual, 1.1)
+ So(column[6].(float64), ShouldEqual, 1.2)
+
+ So(column[7].(string), ShouldEqual, "char10 ")
+ So(column[8].(string), ShouldEqual, "varchar10")
+ So(column[9].(string), ShouldEqual, "text")
+
+ So(column[10].(time.Time), ShouldHaveSameTypeAs, time.Now())
+ So(column[11].(time.Time), ShouldHaveSameTypeAs, time.Now())
+ So(column[12].(time.Time), ShouldHaveSameTypeAs, time.Now())
+ So(column[13].(time.Time), ShouldHaveSameTypeAs, time.Now())
+ So(column[14].(time.Time), ShouldHaveSameTypeAs, time.Now())
+
+ So(column[15].(string), ShouldEqual, "00:15:00")
+ })
+ })
+
+ Convey("Given a table with metrics that lacks data for some series ", func() {
+ sql := `
+ DROP TABLE IF EXISTS metric;
+ CREATE TABLE metric (
+ time timestamp,
+ value integer
+ )
+ `
+
+ _, err := sess.Exec(sql)
+ So(err, ShouldBeNil)
+
+ type metric struct {
+ Time time.Time
+ Value int64
+ }
+
+ series := []*metric{}
+ firstRange := genTimeRangeByInterval(fromStart, 10*time.Minute, 10*time.Second)
+ secondRange := genTimeRangeByInterval(fromStart.Add(20*time.Minute), 10*time.Minute, 10*time.Second)
+
+ for _, t := range firstRange {
+ series = append(series, &metric{
+ Time: t,
+ Value: 15,
+ })
+ }
+
+ for _, t := range secondRange {
+ series = append(series, &metric{
+ Time: t,
+ Value: 20,
+ })
+ }
+
+ for _, s := range series {
+ _, err = sess.Insert(s)
+ So(err, ShouldBeNil)
+ }
+
+ Convey("When doing a metric query using timeGroup", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": "SELECT $__timeGroup(time, '5m'), avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
+ "format": "time_series",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+
+ points := queryResult.Series[0].Points
+ So(len(points), ShouldEqual, 6)
+
+ dt := fromStart
+
+ for i := 0; i < 3; i++ {
+ aValue := points[i][0].Float64
+ aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
+ So(aValue, ShouldEqual, 15)
+ So(aTime, ShouldEqual, dt)
+ dt = dt.Add(5 * time.Minute)
+ }
+
+ // adjust for 5 minute gap
+ dt = dt.Add(5 * time.Minute)
+ for i := 3; i < 6; i++ {
+ aValue := points[i][0].Float64
+ aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
+ So(aValue, ShouldEqual, 20)
+ So(aTime, ShouldEqual, dt)
+ dt = dt.Add(5 * time.Minute)
+ }
+ })
+
+ Convey("When doing a metric query using timeGroup with NULL fill enabled", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": "SELECT $__timeGroup(time, '5m', NULL), avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
+ "format": "time_series",
+ }),
+ RefId: "A",
+ },
+ },
+ TimeRange: &tsdb.TimeRange{
+ From: fmt.Sprintf("%v", fromStart.Unix()*1000),
+ To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+
+ points := queryResult.Series[0].Points
+ So(len(points), ShouldEqual, 7)
+
+ dt := fromStart
+
+ for i := 0; i < 3; i++ {
+ aValue := points[i][0].Float64
+ aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
+ So(aValue, ShouldEqual, 15)
+ So(aTime, ShouldEqual, dt)
+ dt = dt.Add(5 * time.Minute)
+ }
+
+ So(points[3][0].Valid, ShouldBeFalse)
+
+ // adjust for 5 minute gap
+ dt = dt.Add(5 * time.Minute)
+ for i := 4; i < 7; i++ {
+ aValue := points[i][0].Float64
+ aTime := time.Unix(int64(points[i][1].Float64)/1000, 0)
+ So(aValue, ShouldEqual, 20)
+ So(aTime, ShouldEqual, dt)
+ dt = dt.Add(5 * time.Minute)
+ }
+ })
+
+ Convey("When doing a metric query using timeGroup with float fill enabled", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": "SELECT $__timeGroup(time, '5m', 1.5), avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
+ "format": "time_series",
+ }),
+ RefId: "A",
+ },
+ },
+ TimeRange: &tsdb.TimeRange{
+ From: fmt.Sprintf("%v", fromStart.Unix()*1000),
+ To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+
+ points := queryResult.Series[0].Points
+ So(points[3][0].Float64, ShouldEqual, 1.5)
+ })
+ })
+
+ Convey("Given a table with metrics having multiple values and measurements", func() {
+ type metric_values struct {
+ Time time.Time
+ Measurement string
+ ValueOne int64 `xorm:"integer 'valueOne'"`
+ ValueTwo int64 `xorm:"integer 'valueTwo'"`
+ }
+
+ if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
+ So(err, ShouldBeNil)
+ sess.DropTable(metric_values{})
+ }
+ err := sess.CreateTable(metric_values{})
+ So(err, ShouldBeNil)
+
+ rand.Seed(time.Now().Unix())
+ rnd := func(min, max int64) int64 {
+ return rand.Int63n(max-min) + min
+ }
+
+ series := []*metric_values{}
+ for _, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
+ series = append(series, &metric_values{
+ Time: t,
+ Measurement: "Metric A",
+ ValueOne: rnd(0, 100),
+ ValueTwo: rnd(0, 100),
+ })
+ series = append(series, &metric_values{
+ Time: t,
+ Measurement: "Metric B",
+ ValueOne: rnd(0, 100),
+ ValueTwo: rnd(0, 100),
+ })
+ }
+
+ for _, s := range series {
+ _, err := sess.Insert(s)
+ So(err, ShouldBeNil)
+ }
+
+ Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": `SELECT $__timeEpoch(time), measurement || ' - value one' as metric, "valueOne" FROM metric_values ORDER BY 1`,
+ "format": "time_series",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+
+ So(len(queryResult.Series), ShouldEqual, 2)
+ So(queryResult.Series[0].Name, ShouldEqual, "Metric A - value one")
+ So(queryResult.Series[1].Name, ShouldEqual, "Metric B - value one")
+ })
+
+ Convey("When doing a metric query grouping by time should return correct series", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": `SELECT $__timeEpoch(time), "valueOne", "valueTwo" FROM metric_values ORDER BY 1`,
+ "format": "time_series",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+
+ So(len(queryResult.Series), ShouldEqual, 2)
+ So(queryResult.Series[0].Name, ShouldEqual, "valueOne")
+ So(queryResult.Series[1].Name, ShouldEqual, "valueTwo")
+ })
+ })
+
+ Convey("Given a table with event data", func() {
+ type event struct {
+ TimeSec int64
+ Description string
+ Tags string
+ }
+
+ if exist, err := sess.IsTableExist(event{}); err != nil || exist {
+ So(err, ShouldBeNil)
+ sess.DropTable(event{})
+ }
+ err := sess.CreateTable(event{})
+ So(err, ShouldBeNil)
+
+ events := []*event{}
+ for _, t := range genTimeRangeByInterval(fromStart.Add(-20*time.Minute), 60*time.Minute, 25*time.Minute) {
+ events = append(events, &event{
+ TimeSec: t.Unix(),
+ Description: "Someone deployed something",
+ Tags: "deploy",
+ })
+ events = append(events, &event{
+ TimeSec: t.Add(5 * time.Minute).Unix(),
+ Description: "New support ticket registered",
+ Tags: "ticket",
+ })
+ }
+
+ for _, e := range events {
+ _, err = sess.Insert(e)
+ So(err, ShouldBeNil)
+ }
+
+ Convey("When doing an annotation query of deploy events should return expected result", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": `SELECT "time_sec" as time, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='deploy' ORDER BY 1 ASC`,
+ "format": "table",
+ }),
+ RefId: "Deploys",
+ },
+ },
+ TimeRange: &tsdb.TimeRange{
+ From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
+ To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ queryResult := resp.Results["Deploys"]
+ So(err, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
+ })
+
+ Convey("When doing an annotation query of ticket events should return expected result", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": `SELECT "time_sec" as time, description as text, tags FROM event WHERE $__unixEpochFilter(time_sec) AND tags='ticket' ORDER BY 1 ASC`,
+ "format": "table",
+ }),
+ RefId: "Tickets",
+ },
+ },
+ TimeRange: &tsdb.TimeRange{
+ From: fmt.Sprintf("%v", fromStart.Add(-20*time.Minute).Unix()*1000),
+ To: fmt.Sprintf("%v", fromStart.Add(40*time.Minute).Unix()*1000),
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ queryResult := resp.Results["Tickets"]
+ So(err, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 3)
+ })
+
+ Convey("When doing an annotation query with a time column in datetime format", func() {
+ dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
+ dtFormat := "2006-01-02 15:04:05.999999999"
+
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": fmt.Sprintf(`SELECT
+ CAST('%s' AS TIMESTAMP) as time,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `, dt.Format(dtFormat)),
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000))
+ })
+
+ Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
+ dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
+
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": fmt.Sprintf(`SELECT
+ %d as time,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `, dt.Unix()),
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
+ })
+
+ Convey("When doing an annotation query with a time column in epoch second format (int) should return ms", func() {
+ dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
+
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": fmt.Sprintf(`SELECT
+ cast(%d as bigint) as time,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `, dt.Unix()),
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0].(int64), ShouldEqual, int64(dt.Unix()*1000))
+ })
+
+ Convey("When doing an annotation query with a time column in epoch millisecond format should return ms", func() {
+ dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
+
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": fmt.Sprintf(`SELECT
+ %d as time,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `, dt.Unix()*1000),
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0].(int64), ShouldEqual, dt.Unix()*1000)
+ })
+
+ Convey("When doing an annotation query with a time column holding a bigint null value should return nil", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": `SELECT
+ cast(null as bigint) as time,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `,
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0], ShouldBeNil)
+ })
+
+ Convey("When doing an annotation query with a time column holding a timestamp null value should return nil", func() {
+ query := &tsdb.TsdbQuery{
+ Queries: []*tsdb.Query{
+ {
+ Model: simplejson.NewFromAny(map[string]interface{}{
+ "rawSql": `SELECT
+ cast(null as timestamp) as time,
+ 'message' as text,
+ 'tag1,tag2' as tags
+ `,
+ "format": "table",
+ }),
+ RefId: "A",
+ },
+ },
+ }
+
+ resp, err := endpoint.Query(nil, nil, query)
+ So(err, ShouldBeNil)
+ queryResult := resp.Results["A"]
+ So(queryResult.Error, ShouldBeNil)
+ So(len(queryResult.Tables[0].Rows), ShouldEqual, 1)
+ columns := queryResult.Tables[0].Rows[0]
+
+ //Should be in milliseconds
+ So(columns[0], ShouldBeNil)
+ })
})
})
}
func InitPostgresTestDB(t *testing.T) *xorm.Engine {
x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
+ x.DatabaseTZ = time.UTC
+ x.TZLocation = time.UTC
// x.ShowSQL()
@@ -119,7 +636,18 @@ func InitPostgresTestDB(t *testing.T) *xorm.Engine {
t.Fatalf("Failed to init postgres db %v", err)
}
- sqlutil.CleanDB(x)
-
return x
}
+
+func genTimeRangeByInterval(from time.Time, duration time.Duration, interval time.Duration) []time.Time {
+ durationSec := int64(duration.Seconds())
+ intervalSec := int64(interval.Seconds())
+ timeRange := []time.Time{}
+
+ for i := int64(0); i < durationSec; i += intervalSec {
+ timeRange = append(timeRange, from)
+ from = from.Add(time.Duration(int64(time.Second) * intervalSec))
+ }
+
+ return timeRange
+}
diff --git a/pkg/tsdb/sql_engine.go b/pkg/tsdb/sql_engine.go
index 7ea0682235f..16370a4ea7f 100644
--- a/pkg/tsdb/sql_engine.go
+++ b/pkg/tsdb/sql_engine.go
@@ -3,6 +3,7 @@ package tsdb
import (
"context"
"sync"
+ "time"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
@@ -133,3 +134,30 @@ func (e *DefaultSqlEngine) Query(
return result, nil
}
+
+// ConvertTimeColumnToEpochMs converts column named time to unix timestamp in milliseconds
+// to make native datetime types and epoch dates work in annotation and table queries.
+func ConvertSqlTimeColumnToEpochMs(values RowValues, timeIndex int) {
+ if timeIndex >= 0 {
+ switch value := values[timeIndex].(type) {
+ case time.Time:
+ values[timeIndex] = EpochPrecisionToMs(float64(value.Unix()))
+ case *time.Time:
+ if value != nil {
+ values[timeIndex] = EpochPrecisionToMs(float64((*value).Unix()))
+ }
+ case int64:
+ values[timeIndex] = int64(EpochPrecisionToMs(float64(value)))
+ case *int64:
+ if value != nil {
+ values[timeIndex] = int64(EpochPrecisionToMs(float64(*value)))
+ }
+ case float64:
+ values[timeIndex] = EpochPrecisionToMs(value)
+ case *float64:
+ if value != nil {
+ values[timeIndex] = EpochPrecisionToMs(*value)
+ }
+ }
+ }
+}
diff --git a/pkg/tsdb/sql_engine_test.go b/pkg/tsdb/sql_engine_test.go
new file mode 100644
index 00000000000..48aac2c4d45
--- /dev/null
+++ b/pkg/tsdb/sql_engine_test.go
@@ -0,0 +1,46 @@
+package tsdb
+
+import (
+ "testing"
+ "time"
+
+ . "github.com/smartystreets/goconvey/convey"
+)
+
+func TestSqlEngine(t *testing.T) {
+ Convey("SqlEngine", t, func() {
+ Convey("Given row values with time columns when converting them", func() {
+ dt := time.Date(2018, 3, 14, 21, 20, 6, 527e6, time.UTC)
+ fixtures := make([]interface{}, 8)
+ fixtures[0] = dt
+ fixtures[1] = dt.Unix() * 1000
+ fixtures[2] = dt.Unix()
+ fixtures[3] = float64(dt.Unix() * 1000)
+ fixtures[4] = float64(dt.Unix())
+
+ var nilDt *time.Time
+ var nilInt64 *int64
+ var nilFloat64 *float64
+ fixtures[5] = nilDt
+ fixtures[6] = nilInt64
+ fixtures[7] = nilFloat64
+
+ for i := range fixtures {
+ ConvertSqlTimeColumnToEpochMs(fixtures, i)
+ }
+
+ Convey("Should convert sql time columns to epoch time in ms ", func() {
+ expected := float64(dt.Unix() * 1000)
+ So(fixtures[0].(float64), ShouldEqual, expected)
+ So(fixtures[1].(int64), ShouldEqual, expected)
+ So(fixtures[2].(int64), ShouldEqual, expected)
+ So(fixtures[3].(float64), ShouldEqual, expected)
+ So(fixtures[4].(float64), ShouldEqual, expected)
+
+ So(fixtures[5], ShouldBeNil)
+ So(fixtures[6], ShouldBeNil)
+ So(fixtures[7], ShouldBeNil)
+ })
+ })
+ })
+}
diff --git a/pkg/tsdb/time_range.go b/pkg/tsdb/time_range.go
index fd797bf731a..fd0cb3f8e82 100644
--- a/pkg/tsdb/time_range.go
+++ b/pkg/tsdb/time_range.go
@@ -88,3 +88,13 @@ func (tr *TimeRange) ParseTo() (time.Time, error) {
return time.Time{}, fmt.Errorf("cannot parse to value %s", tr.To)
}
+
+// EpochPrecisionToMs converts epoch precision to millisecond, if needed.
+// Only seconds to milliseconds supported right now
+func EpochPrecisionToMs(value float64) float64 {
+ if int64(value)/1e10 == 0 {
+ return float64(value * 1e3)
+ }
+
+ return float64(value)
+}
diff --git a/public/app/containers/ManageDashboards/FolderSettings.tsx b/public/app/containers/ManageDashboards/FolderSettings.tsx
index 586a8f05b4c..e56659ad789 100644
--- a/public/app/containers/ManageDashboards/FolderSettings.tsx
+++ b/public/app/containers/ManageDashboards/FolderSettings.tsx
@@ -143,7 +143,7 @@ export class FolderSettings extends React.Component {
className="btn btn-success"
disabled={!folder.folder.canSave || !folder.folder.hasChanged}
>
- Save
+ Save
Delete
diff --git a/public/app/core/components/code_editor/code_editor.ts b/public/app/core/components/code_editor/code_editor.ts
index 8cbd888bb1b..886ae2a6407 100644
--- a/public/app/core/components/code_editor/code_editor.ts
+++ b/public/app/core/components/code_editor/code_editor.ts
@@ -21,6 +21,8 @@
* data-tab-size - Tab size, default is 2.
* data-behaviours-enabled - Specifies whether to use behaviors or not. "Behaviors" in this case is the auto-pairing of
* special characters, like quotation marks, parenthesis, or brackets.
+ * data-snippets-enabled - Specifies whether to use snippets or not. "Snippets" are small pieces of code that can be
+ * inserted via the completion box.
*
* Keybindings:
* Ctrl-Enter (Command-Enter): run onChange() function
@@ -49,6 +51,7 @@ const DEFAULT_MODE = 'text';
const DEFAULT_MAX_LINES = 10;
const DEFAULT_TAB_SIZE = 2;
const DEFAULT_BEHAVIOURS = true;
+const DEFAULT_SNIPPETS = true;
let editorTemplate = `
`;
@@ -59,6 +62,7 @@ function link(scope, elem, attrs) {
let showGutter = attrs.showGutter !== undefined;
let tabSize = attrs.tabSize || DEFAULT_TAB_SIZE;
let behavioursEnabled = attrs.behavioursEnabled ? attrs.behavioursEnabled === 'true' : DEFAULT_BEHAVIOURS;
+ let snippetsEnabled = attrs.snippetsEnabled ? attrs.snippetsEnabled === 'true' : DEFAULT_SNIPPETS;
// Initialize editor
let aceElem = elem.get(0);
@@ -143,7 +147,7 @@ function link(scope, elem, attrs) {
codeEditor.setOptions({
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
- enableSnippets: true,
+ enableSnippets: snippetsEnabled,
});
if (scope.getCompleter()) {
diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts
index 1658d74be0a..0d468b6980f 100644
--- a/public/app/core/services/keybindingSrv.ts
+++ b/public/app/core/services/keybindingSrv.ts
@@ -178,6 +178,14 @@ export class KeybindingSrv {
}
});
+ // duplicate panel
+ this.bind('p d', () => {
+ if (dashboard.meta.focusPanelId && dashboard.meta.canEdit) {
+ let panelIndex = dashboard.getPanelInfoById(dashboard.meta.focusPanelId).index;
+ dashboard.duplicatePanel(dashboard.panels[panelIndex]);
+ }
+ });
+
// share panel
this.bind('p s', () => {
if (dashboard.meta.focusPanelId) {
diff --git a/public/app/features/dashboard/partials/folder_settings.html b/public/app/features/dashboard/partials/folder_settings.html
index 6c53c330d5d..8e819be5fe8 100644
--- a/public/app/features/dashboard/partials/folder_settings.html
+++ b/public/app/features/dashboard/partials/folder_settings.html
@@ -11,8 +11,7 @@
diff --git a/public/app/plugins/datasource/mssql/partials/query.editor.html b/public/app/plugins/datasource/mssql/partials/query.editor.html
index e8f44c8c9f8..c7dc030be6e 100644
--- a/public/app/plugins/datasource/mssql/partials/query.editor.html
+++ b/public/app/plugins/datasource/mssql/partials/query.editor.html
@@ -48,15 +48,22 @@ Table:
Macros:
- $__time(column) -> column AS time
-- $__utcTime(column) -> DATEADD(second, DATEDIFF(second, GETDATE(), GETUTCDATE()), column) AS time
-- $__timeEpoch(column) -> DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second, GETDATE(), GETUTCDATE()), column) ) AS time
-- $__timeFilter(column) -> column > DATEADD(s, 1492750877+DATEDIFF(second, GETUTCDATE(), GETDATE()), '1970-01-01') AND column < DATEADD(s, 1492750877+DATEDIFF(second, GETUTCDATE(), GETDATE()), '1970-01-01')
-- $__unixEpochFilter(column) -> column > 1492750877 AND column < 1492750877
-- $__timeGroup(column, '5m'[, fillvalue]) -> cast(cast(DATEDIFF(second, {d '1970-01-01'}, DATEADD(second, DATEDIFF(second, GETDATE(), GETUTCDATE()), column))/300 as int)*300 as int). Providing a fillValue of NULL or floating value will automatically fill empty series in timerange with that value.
+- $__timeEpoch(column) -> DATEDIFF(second, '1970-01-01', column) AS time
+- $__timeFilter(column) -> column >= DATEADD(s, 18446744066914186738, '1970-01-01') AND column &t;= DATEADD(s, 18446744066914187038, '1970-01-01')
+- $__unixEpochFilter(column) -> column >= 1492750877 AND column <= 1492750877
+- $__timeGroup(column, '5m'[, fillvalue]) -> CAST(ROUND(DATEDIFF(second, '1970-01-01', column)/300.0, 0) as bigint)*300. Providing a fillValue of NULL or floating value will automatically fill empty series in timerange with that value.
+
+Example of group by and order by with $__timeGroup:
+SELECT
+ $__timeGroup(date_time_col, '1h') AS time,
+ sum(value) as value
+FROM yourtable
+GROUP BY $__timeGroup(date_time_col, '1h')
+ORDER BY 1
Or build your own conditionals using these macros which just return the values:
-- $__timeFrom() -> DATEADD(second, 1492750877+DATEDIFF(second, GETUTCDATE(), GETDATE()), '1970-01-01')
-- $__timeTo() -> DATEADD(second, 1492750877+DATEDIFF(second, GETUTCDATE(), GETDATE()), '1970-01-01')
+- $__timeFrom() -> DATEADD(second, 1492750877, '1970-01-01')
+- $__timeTo() -> DATEADD(second, 1492750877, '1970-01-01')
- $__unixEpochFrom() -> 1492750877
- $__unixEpochTo() -> 1492750877
diff --git a/public/app/plugins/datasource/mssql/response_parser.ts b/public/app/plugins/datasource/mssql/response_parser.ts
index b7d96d820cb..b6f538707b0 100644
--- a/public/app/plugins/datasource/mssql/response_parser.ts
+++ b/public/app/plugins/datasource/mssql/response_parser.ts
@@ -128,7 +128,7 @@ export default class ResponseParser {
const row = table.rows[i];
list.push({
annotation: options.annotation,
- time: Math.floor(row[timeColumnIndex]) * 1000,
+ time: Math.floor(row[timeColumnIndex]),
text: row[textColumnIndex],
tags: row[tagsColumnIndex] ? row[tagsColumnIndex].trim().split(/\s*,\s*/) : [],
});
diff --git a/public/app/plugins/datasource/mssql/specs/datasource.jest.ts b/public/app/plugins/datasource/mssql/specs/datasource.jest.ts
new file mode 100644
index 00000000000..dd2d4a60cec
--- /dev/null
+++ b/public/app/plugins/datasource/mssql/specs/datasource.jest.ts
@@ -0,0 +1,228 @@
+import moment from 'moment';
+import { MssqlDatasource } from '../datasource';
+import { TemplateSrvStub } from 'test/specs/helpers';
+import { CustomVariable } from 'app/features/templating/custom_variable';
+import q from 'q';
+
+describe('MSSQLDatasource', function() {
+ const ctx: any = {
+ backendSrv: {},
+ templateSrv: new TemplateSrvStub(),
+ };
+
+ beforeEach(function() {
+ ctx.$q = q;
+ ctx.instanceSettings = { name: 'mssql' };
+
+ ctx.ds = new MssqlDatasource(ctx.instanceSettings, ctx.backendSrv, ctx.$q, ctx.templateSrv);
+ });
+
+ describe('When performing annotationQuery', function() {
+ let results;
+
+ const annotationName = 'MyAnno';
+
+ const options = {
+ annotation: {
+ name: annotationName,
+ rawQuery: 'select time, text, tags from table;',
+ },
+ range: {
+ from: moment(1432288354),
+ to: moment(1432288401),
+ },
+ };
+
+ const response = {
+ results: {
+ MyAnno: {
+ refId: annotationName,
+ tables: [
+ {
+ columns: [{ text: 'time' }, { text: 'text' }, { text: 'tags' }],
+ rows: [
+ [1521545610656, 'some text', 'TagA,TagB'],
+ [1521546251185, 'some text2', ' TagB , TagC'],
+ [1521546501378, 'some text3'],
+ ],
+ },
+ ],
+ },
+ },
+ };
+
+ beforeEach(() => {
+ ctx.backendSrv.datasourceRequest = options => {
+ return ctx.$q.when({ data: response, status: 200 });
+ };
+
+ return ctx.ds.annotationQuery(options).then(data => {
+ results = data;
+ });
+ });
+
+ it('should return annotation list', function() {
+ expect(results.length).toBe(3);
+
+ expect(results[0].text).toBe('some text');
+ expect(results[0].tags[0]).toBe('TagA');
+ expect(results[0].tags[1]).toBe('TagB');
+
+ expect(results[1].tags[0]).toBe('TagB');
+ expect(results[1].tags[1]).toBe('TagC');
+
+ expect(results[2].tags.length).toBe(0);
+ });
+ });
+
+ describe('When performing metricFindQuery', function() {
+ let results;
+ const query = 'select * from atable';
+ const response = {
+ results: {
+ tempvar: {
+ meta: {
+ rowCount: 3,
+ },
+ refId: 'tempvar',
+ tables: [
+ {
+ columns: [{ text: 'title' }, { text: 'text' }],
+ rows: [['aTitle', 'some text'], ['aTitle2', 'some text2'], ['aTitle3', 'some text3']],
+ },
+ ],
+ },
+ },
+ };
+
+ beforeEach(function() {
+ ctx.backendSrv.datasourceRequest = function(options) {
+ return ctx.$q.when({ data: response, status: 200 });
+ };
+
+ return ctx.ds.metricFindQuery(query).then(function(data) {
+ results = data;
+ });
+ });
+
+ it('should return list of all column values', function() {
+ expect(results.length).toBe(6);
+ expect(results[0].text).toBe('aTitle');
+ expect(results[5].text).toBe('some text3');
+ });
+ });
+
+ describe('When performing metricFindQuery with key, value columns', function() {
+ let results;
+ const query = 'select * from atable';
+ const response = {
+ results: {
+ tempvar: {
+ meta: {
+ rowCount: 3,
+ },
+ refId: 'tempvar',
+ tables: [
+ {
+ columns: [{ text: '__value' }, { text: '__text' }],
+ rows: [['value1', 'aTitle'], ['value2', 'aTitle2'], ['value3', 'aTitle3']],
+ },
+ ],
+ },
+ },
+ };
+
+ beforeEach(function() {
+ ctx.backendSrv.datasourceRequest = function(options) {
+ return ctx.$q.when({ data: response, status: 200 });
+ };
+
+ return ctx.ds.metricFindQuery(query).then(function(data) {
+ results = data;
+ });
+ });
+
+ it('should return list of as text, value', function() {
+ expect(results.length).toBe(3);
+ expect(results[0].text).toBe('aTitle');
+ expect(results[0].value).toBe('value1');
+ expect(results[2].text).toBe('aTitle3');
+ expect(results[2].value).toBe('value3');
+ });
+ });
+
+ describe('When performing metricFindQuery with key, value columns and with duplicate keys', function() {
+ let results;
+ const query = 'select * from atable';
+ const response = {
+ results: {
+ tempvar: {
+ meta: {
+ rowCount: 3,
+ },
+ refId: 'tempvar',
+ tables: [
+ {
+ columns: [{ text: '__text' }, { text: '__value' }],
+ rows: [['aTitle', 'same'], ['aTitle', 'same'], ['aTitle', 'diff']],
+ },
+ ],
+ },
+ },
+ };
+
+ beforeEach(function() {
+ ctx.backendSrv.datasourceRequest = function(options) {
+ return ctx.$q.when({ data: response, status: 200 });
+ };
+
+ return ctx.ds.metricFindQuery(query).then(function(data) {
+ results = data;
+ });
+ });
+
+ it('should return list of unique keys', function() {
+ expect(results.length).toBe(1);
+ expect(results[0].text).toBe('aTitle');
+ expect(results[0].value).toBe('same');
+ });
+ });
+
+ describe('When interpolating variables', () => {
+ beforeEach(function() {
+ ctx.variable = new CustomVariable({}, {});
+ });
+
+ describe('and value is a string', () => {
+ it('should return an unquoted value', () => {
+ expect(ctx.ds.interpolateVariable('abc', ctx.variable)).toEqual('abc');
+ });
+ });
+
+ describe('and value is a number', () => {
+ it('should return an unquoted value', () => {
+ expect(ctx.ds.interpolateVariable(1000, ctx.variable)).toEqual(1000);
+ });
+ });
+
+ describe('and value is an array of strings', () => {
+ it('should return comma separated quoted values', () => {
+ expect(ctx.ds.interpolateVariable(['a', 'b', 'c'], ctx.variable)).toEqual("'a','b','c'");
+ });
+ });
+
+ describe('and variable allows multi-value and value is a string', () => {
+ it('should return a quoted value', () => {
+ ctx.variable.multi = true;
+ expect(ctx.ds.interpolateVariable('abc', ctx.variable)).toEqual("'abc'");
+ });
+ });
+
+ describe('and variable allows all and value is a string', () => {
+ it('should return a quoted value', () => {
+ ctx.variable.includeAll = true;
+ expect(ctx.ds.interpolateVariable('abc', ctx.variable)).toEqual("'abc'");
+ });
+ });
+ });
+});
diff --git a/public/app/plugins/datasource/mssql/specs/datasource_specs.ts b/public/app/plugins/datasource/mssql/specs/datasource_specs.ts
deleted file mode 100644
index a144f21a258..00000000000
--- a/public/app/plugins/datasource/mssql/specs/datasource_specs.ts
+++ /dev/null
@@ -1,236 +0,0 @@
-import {describe, beforeEach, it, expect, angularMocks} from 'test/lib/common';
-import moment from 'moment';
-import helpers from 'test/specs/helpers';
-import {MssqlDatasource} from '../datasource';
-import {CustomVariable} from 'app/features/templating/custom_variable';
-
-describe('MSSQLDatasource', function() {
- var ctx = new helpers.ServiceTestContext();
- var instanceSettings = {name: 'mssql'};
-
- beforeEach(angularMocks.module('grafana.core'));
- beforeEach(angularMocks.module('grafana.services'));
- beforeEach(ctx.providePhase(['backendSrv']));
-
- beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
- ctx.$q = $q;
- ctx.$httpBackend = $httpBackend;
- ctx.$rootScope = $rootScope;
- ctx.ds = $injector.instantiate(MssqlDatasource, {instanceSettings: instanceSettings});
- $httpBackend.when('GET', /\.html$/).respond('');
- }));
-
- describe('When performing annotationQuery', function() {
- let results;
-
- const annotationName = 'MyAnno';
-
- const options = {
- annotation: {
- name: annotationName,
- rawQuery: 'select time, text, tags from table;'
- },
- range: {
- from: moment(1432288354),
- to: moment(1432288401)
- }
- };
-
- const response = {
- results: {
- MyAnno: {
- refId: annotationName,
- tables: [
- {
- columns: [{text: 'time'}, {text: 'text'}, {text: 'tags'}],
- rows: [
- [1432288355, 'some text', 'TagA,TagB'],
- [1432288390, 'some text2', ' TagB , TagC'],
- [1432288400, 'some text3']
- ]
- }
- ]
- }
- }
- };
-
- beforeEach(function() {
- ctx.backendSrv.datasourceRequest = function(options) {
- return ctx.$q.when({data: response, status: 200});
- };
- ctx.ds.annotationQuery(options).then(function(data) { results = data; });
- ctx.$rootScope.$apply();
- });
-
- it('should return annotation list', function() {
- expect(results.length).to.be(3);
-
- expect(results[0].text).to.be('some text');
- expect(results[0].tags[0]).to.be('TagA');
- expect(results[0].tags[1]).to.be('TagB');
-
- expect(results[1].tags[0]).to.be('TagB');
- expect(results[1].tags[1]).to.be('TagC');
-
- expect(results[2].tags.length).to.be(0);
- });
- });
-
- describe('When performing metricFindQuery', function() {
- let results;
- const query = 'select * from atable';
- const response = {
- results: {
- tempvar: {
- meta: {
- rowCount: 3
- },
- refId: 'tempvar',
- tables: [
- {
- columns: [{text: 'title'}, {text: 'text'}],
- rows: [
- ['aTitle', 'some text'],
- ['aTitle2', 'some text2'],
- ['aTitle3', 'some text3']
- ]
- }
- ]
- }
- }
- };
-
- beforeEach(function() {
- ctx.backendSrv.datasourceRequest = function(options) {
- return ctx.$q.when({data: response, status: 200});
- };
- ctx.ds.metricFindQuery(query).then(function(data) { results = data; });
- ctx.$rootScope.$apply();
- });
-
- it('should return list of all column values', function() {
- expect(results.length).to.be(6);
- expect(results[0].text).to.be('aTitle');
- expect(results[5].text).to.be('some text3');
- });
- });
-
- describe('When performing metricFindQuery with key, value columns', function() {
- let results;
- const query = 'select * from atable';
- const response = {
- results: {
- tempvar: {
- meta: {
- rowCount: 3
- },
- refId: 'tempvar',
- tables: [
- {
- columns: [{text: '__value'}, {text: '__text'}],
- rows: [
- ['value1', 'aTitle'],
- ['value2', 'aTitle2'],
- ['value3', 'aTitle3']
- ]
- }
- ]
- }
- }
- };
-
- beforeEach(function() {
- ctx.backendSrv.datasourceRequest = function(options) {
- return ctx.$q.when({data: response, status: 200});
- };
- ctx.ds.metricFindQuery(query).then(function(data) { results = data; });
- ctx.$rootScope.$apply();
- });
-
- it('should return list of as text, value', function() {
- expect(results.length).to.be(3);
- expect(results[0].text).to.be('aTitle');
- expect(results[0].value).to.be('value1');
- expect(results[2].text).to.be('aTitle3');
- expect(results[2].value).to.be('value3');
- });
- });
-
- describe('When performing metricFindQuery with key, value columns and with duplicate keys', function() {
- let results;
- const query = 'select * from atable';
- const response = {
- results: {
- tempvar: {
- meta: {
- rowCount: 3
- },
- refId: 'tempvar',
- tables: [
- {
- columns: [{text: '__text'}, {text: '__value'}],
- rows: [
- ['aTitle', 'same'],
- ['aTitle', 'same'],
- ['aTitle', 'diff']
- ]
- }
- ]
- }
- }
- };
-
- beforeEach(function() {
- ctx.backendSrv.datasourceRequest = function(options) {
- return ctx.$q.when({data: response, status: 200});
- };
- ctx.ds.metricFindQuery(query).then(function(data) { results = data; });
- ctx.$rootScope.$apply();
- });
-
- it('should return list of unique keys', function() {
- expect(results.length).to.be(1);
- expect(results[0].text).to.be('aTitle');
- expect(results[0].value).to.be('same');
- });
- });
-
- describe('When interpolating variables', () => {
- beforeEach(function() {
- ctx.variable = new CustomVariable({},{});
- });
-
- describe('and value is a string', () => {
- it('should return an unquoted value', () => {
- expect(ctx.ds.interpolateVariable('abc', ctx.variable)).to.eql('abc');
- });
- });
-
- describe('and value is a number', () => {
- it('should return an unquoted value', () => {
- expect(ctx.ds.interpolateVariable(1000, ctx.variable)).to.eql(1000);
- });
- });
-
- describe('and value is an array of strings', () => {
- it('should return comma separated quoted values', () => {
- expect(ctx.ds.interpolateVariable(['a', 'b', 'c'], ctx.variable)).to.eql('\'a\',\'b\',\'c\'');
- });
- });
-
- describe('and variable allows multi-value and value is a string', () => {
- it('should return a quoted value', () => {
- ctx.variable.multi = true;
- expect(ctx.ds.interpolateVariable('abc', ctx.variable)).to.eql('\'abc\'');
- });
- });
-
- describe('and variable allows all and value is a string', () => {
- it('should return a quoted value', () => {
- ctx.variable.includeAll = true;
- expect(ctx.ds.interpolateVariable('abc', ctx.variable)).to.eql('\'abc\'');
- });
- });
-
- });
-});
diff --git a/public/app/plugins/datasource/mysql/partials/annotations.editor.html b/public/app/plugins/datasource/mysql/partials/annotations.editor.html
index b34eff5b011..d142e091fed 100644
--- a/public/app/plugins/datasource/mysql/partials/annotations.editor.html
+++ b/public/app/plugins/datasource/mysql/partials/annotations.editor.html
@@ -18,15 +18,16 @@
Annotation Query Format
-An annotation is an event that is overlayed on top of graphs. The query can have up to four columns per row, the time_sec column is mandatory. Annotation rendering is expensive so it is important to limit the number of rows returned.
+An annotation is an event that is overlayed on top of graphs. The query can have up to three columns per row, the time or time_sec column is mandatory. Annotation rendering is expensive so it is important to limit the number of rows returned.
-- column with alias: time_sec for the annotation event. Format is UTC in seconds, use UNIX_TIMESTAMP(column)
+- column with alias: time or time_sec for the annotation event time. Use epoch time or any native date data type.
- column with alias: text for the annotation text
- column with alias: tags for annotation tags. This is a comma separated string of tags e.g. 'tag1,tag2'
Macros:
-- $__time(column) -> UNIX_TIMESTAMP(column) as time_sec
+- $__time(column) -> UNIX_TIMESTAMP(column) as time (or as time_sec)
+- $__timeEpoch(column) -> UNIX_TIMESTAMP(column) as time (or as time_sec)
- $__timeFilter(column) -> UNIX_TIMESTAMP(time_date_time) > 1492750877 AND UNIX_TIMESTAMP(time_date_time) < 1492750877
- $__unixEpochFilter(column) -> time_unix_epoch > 1492750877 AND time_unix_epoch < 1492750877
diff --git a/public/app/plugins/datasource/mysql/partials/query.editor.html b/public/app/plugins/datasource/mysql/partials/query.editor.html
index 22d64c9190f..9acf32405c1 100644
--- a/public/app/plugins/datasource/mysql/partials/query.editor.html
+++ b/public/app/plugins/datasource/mysql/partials/query.editor.html
@@ -38,15 +38,16 @@
Time series:
-- return column named time_sec (UTC in seconds), use UNIX_TIMESTAMP(column)
-- return column named value for the time point value
-- return column named metric to represent the series name
+- return column named time or time_sec (in UTC), as a unix time stamp or any sql native date data type. You can use the macros below.
+- return column(s) with numeric datatype as values
+- (Optional: return column named metric to represent the series name. If no column named metric is found the column name of the value column is used as series name)
Table:
- return any set of columns
Macros:
- $__time(column) -> UNIX_TIMESTAMP(column) as time_sec
+- $__timeEpoch(column) -> UNIX_TIMESTAMP(column) as time_sec
- $__timeFilter(column) -> UNIX_TIMESTAMP(time_date_time) ≥ 1492750877 AND UNIX_TIMESTAMP(time_date_time) ≤ 1492750877
- $__unixEpochFilter(column) -> time_unix_epoch > 1492750877 AND time_unix_epoch < 1492750877
- $__timeGroup(column,'5m') -> cast(cast(UNIX_TIMESTAMP(column)/(300) as signed)*300 as signed)
diff --git a/public/app/plugins/datasource/mysql/response_parser.ts b/public/app/plugins/datasource/mysql/response_parser.ts
index 50683578035..e5d8ab79f2a 100644
--- a/public/app/plugins/datasource/mysql/response_parser.ts
+++ b/public/app/plugins/datasource/mysql/response_parser.ts
@@ -113,7 +113,7 @@ export default class ResponseParser {
let tagsColumnIndex = -1;
for (let i = 0; i < table.columns.length; i++) {
- if (table.columns[i].text === 'time_sec') {
+ if (table.columns[i].text === 'time_sec' || table.columns[i].text === 'time') {
timeColumnIndex = i;
} else if (table.columns[i].text === 'title') {
return this.$q.reject({
@@ -137,7 +137,7 @@ export default class ResponseParser {
const row = table.rows[i];
list.push({
annotation: options.annotation,
- time: Math.floor(row[timeColumnIndex]) * 1000,
+ time: Math.floor(row[timeColumnIndex]),
text: row[textColumnIndex] ? row[textColumnIndex].toString() : '',
tags: row[tagsColumnIndex] ? row[tagsColumnIndex].trim().split(/\s*,\s*/) : [],
});
diff --git a/public/app/plugins/datasource/postgres/partials/annotations.editor.html b/public/app/plugins/datasource/postgres/partials/annotations.editor.html
index b56f7523087..09232d6f8ed 100644
--- a/public/app/plugins/datasource/postgres/partials/annotations.editor.html
+++ b/public/app/plugins/datasource/postgres/partials/annotations.editor.html
@@ -18,15 +18,16 @@
Annotation Query Format
-An annotation is an event that is overlayed on top of graphs. The query can have up to four columns per row, the time column is mandatory. Annotation rendering is expensive so it is important to limit the number of rows returned.
+An annotation is an event that is overlayed on top of graphs. The query can have up to three columns per row, the time column is mandatory. Annotation rendering is expensive so it is important to limit the number of rows returned.
-- column with alias: time for the annotation event. Format is UTC in seconds, use extract(epoch from column) as "time"
+- column with alias: time for the annotation event time. Use epoch time or any native date data type.
- column with alias: text for the annotation text
- column with alias: tags for annotation tags. This is a comma separated string of tags e.g. 'tag1,tag2'
Macros:
- $__time(column) -> column as "time"
+- $__timeEpoch -> extract(epoch from column) as "time"
- $__timeFilter(column) -> column ≥ to_timestamp(1492750877) AND column ≤ to_timestamp(1492750877)
- $__unixEpochFilter(column) -> column > 1492750877 AND column < 1492750877
diff --git a/public/app/plugins/datasource/postgres/response_parser.ts b/public/app/plugins/datasource/postgres/response_parser.ts
index 620aba5fa7e..ebc9598468b 100644
--- a/public/app/plugins/datasource/postgres/response_parser.ts
+++ b/public/app/plugins/datasource/postgres/response_parser.ts
@@ -134,7 +134,7 @@ export default class ResponseParser {
const row = table.rows[i];
list.push({
annotation: options.annotation,
- time: Math.floor(row[timeColumnIndex]) * 1000,
+ time: Math.floor(row[timeColumnIndex]),
title: row[titleColumnIndex],
text: row[textColumnIndex],
tags: row[tagsColumnIndex] ? row[tagsColumnIndex].trim().split(/\s*,\s*/) : [],
diff --git a/public/app/plugins/datasource/prometheus/specs/completer_specs.ts b/public/app/plugins/datasource/prometheus/specs/completer_specs.ts
index b1c1691c0b8..84694834089 100644
--- a/public/app/plugins/datasource/prometheus/specs/completer_specs.ts
+++ b/public/app/plugins/datasource/prometheus/specs/completer_specs.ts
@@ -47,12 +47,9 @@ describe('Prometheus editor completer', function() {
variables: [
{
name: 'var_name',
- options: [
- { text: 'foo', value: 'foo', selected: false },
- { text: 'bar', value: 'bar', selected: true }
- ]
- }
- ]
+ options: [{ text: 'foo', value: 'foo', selected: false }, { text: 'bar', value: 'bar', selected: true }],
+ },
+ ],
};
let completer = new PromCompleter(datasourceStub, templateSrv);
diff --git a/public/app/plugins/panel/graph/align_yaxes.ts b/public/app/plugins/panel/graph/align_yaxes.ts
new file mode 100644
index 00000000000..e0d8794c250
--- /dev/null
+++ b/public/app/plugins/panel/graph/align_yaxes.ts
@@ -0,0 +1,154 @@
+import _ from 'lodash';
+
+/**
+ * To align two Y axes by Y level
+ * @param yAxes data [{min: min_y1, min: max_y1}, {min: min_y2, max: max_y2}]
+ * @param level Y level
+ */
+export function alignYLevel(yAxes, level) {
+ if (isNaN(level) || !checkCorrectAxis(yAxes)) {
+ return;
+ }
+
+ var [yLeft, yRight] = yAxes;
+ moveLevelToZero(yLeft, yRight, level);
+
+ expandStuckValues(yLeft, yRight);
+
+ // one of graphs on zero
+ var zero = yLeft.min === 0 || yRight.min === 0 || yLeft.max === 0 || yRight.max === 0;
+
+ var oneSide = checkOneSide(yLeft, yRight);
+
+ if (zero && oneSide) {
+ yLeft.min = yLeft.max > 0 ? 0 : yLeft.min;
+ yLeft.max = yLeft.max > 0 ? yLeft.max : 0;
+ yRight.min = yRight.max > 0 ? 0 : yRight.min;
+ yRight.max = yRight.max > 0 ? yRight.max : 0;
+ } else {
+ if (checkOppositeSides(yLeft, yRight)) {
+ if (yLeft.min >= 0) {
+ yLeft.min = -yLeft.max;
+ yRight.max = -yRight.min;
+ } else {
+ yLeft.max = -yLeft.min;
+ yRight.min = -yRight.max;
+ }
+ } else {
+ var rate = getRate(yLeft, yRight);
+
+ if (oneSide) {
+ // all graphs above the Y level
+ if (yLeft.min > 0) {
+ yLeft.min = yLeft.max / rate;
+ yRight.min = yRight.max / rate;
+ } else {
+ yLeft.max = yLeft.min / rate;
+ yRight.max = yRight.min / rate;
+ }
+ } else {
+ if (checkTwoCross(yLeft, yRight)) {
+ yLeft.min = yRight.min ? yRight.min * rate : yLeft.min;
+ yRight.min = yLeft.min ? yLeft.min / rate : yRight.min;
+ yLeft.max = yRight.max ? yRight.max * rate : yLeft.max;
+ yRight.max = yLeft.max ? yLeft.max / rate : yRight.max;
+ } else {
+ yLeft.min = yLeft.min > 0 ? yRight.min * rate : yLeft.min;
+ yRight.min = yRight.min > 0 ? yLeft.min / rate : yRight.min;
+ yLeft.max = yLeft.max < 0 ? yRight.max * rate : yLeft.max;
+ yRight.max = yRight.max < 0 ? yLeft.max / rate : yRight.max;
+ }
+ }
+ }
+ }
+
+ restoreLevelFromZero(yLeft, yRight, level);
+}
+
+function expandStuckValues(yLeft, yRight) {
+ // wide Y min and max using increased wideFactor
+ var wideFactor = 0.25;
+ if (yLeft.max === yLeft.min) {
+ yLeft.min -= wideFactor;
+ yLeft.max += wideFactor;
+ }
+ if (yRight.max === yRight.min) {
+ yRight.min -= wideFactor;
+ yRight.max += wideFactor;
+ }
+}
+
+function moveLevelToZero(yLeft, yRight, level) {
+ if (level !== 0) {
+ yLeft.min -= level;
+ yLeft.max -= level;
+ yRight.min -= level;
+ yRight.max -= level;
+ }
+}
+
+function restoreLevelFromZero(yLeft, yRight, level) {
+ if (level !== 0) {
+ yLeft.min += level;
+ yLeft.max += level;
+ yRight.min += level;
+ yRight.max += level;
+ }
+}
+
+function checkCorrectAxis(axis) {
+ return axis.length === 2 && checkCorrectAxes(axis[0]) && checkCorrectAxes(axis[1]);
+}
+
+function checkCorrectAxes(axes) {
+ return 'min' in axes && 'max' in axes;
+}
+
+function checkOneSide(yLeft, yRight) {
+ // on the one hand with respect to zero
+ return (yLeft.min >= 0 && yRight.min >= 0) || (yLeft.max <= 0 && yRight.max <= 0);
+}
+
+function checkTwoCross(yLeft, yRight) {
+ // both across zero
+ return yLeft.min <= 0 && yLeft.max >= 0 && yRight.min <= 0 && yRight.max >= 0;
+}
+
+function checkOppositeSides(yLeft, yRight) {
+ // on the opposite sides with respect to zero
+ return (yLeft.min >= 0 && yRight.max <= 0) || (yLeft.max <= 0 && yRight.min >= 0);
+}
+
+function getRate(yLeft, yRight) {
+ var rateLeft, rateRight, rate;
+ if (checkTwoCross(yLeft, yRight)) {
+ rateLeft = yRight.min ? yLeft.min / yRight.min : 0;
+ rateRight = yRight.max ? yLeft.max / yRight.max : 0;
+ } else {
+ if (checkOneSide(yLeft, yRight)) {
+ var absLeftMin = Math.abs(yLeft.min);
+ var absLeftMax = Math.abs(yLeft.max);
+ var absRightMin = Math.abs(yRight.min);
+ var absRightMax = Math.abs(yRight.max);
+ var upLeft = _.max([absLeftMin, absLeftMax]);
+ var downLeft = _.min([absLeftMin, absLeftMax]);
+ var upRight = _.max([absRightMin, absRightMax]);
+ var downRight = _.min([absRightMin, absRightMax]);
+
+ rateLeft = downLeft ? upLeft / downLeft : upLeft;
+ rateRight = downRight ? upRight / downRight : upRight;
+ } else {
+ if (yLeft.min > 0 || yRight.min > 0) {
+ rateLeft = yLeft.max / yRight.max;
+ rateRight = 0;
+ } else {
+ rateLeft = 0;
+ rateRight = yLeft.min / yRight.min;
+ }
+ }
+ }
+
+ rate = rateLeft > rateRight ? rateLeft : rateRight;
+
+ return rate;
+}
diff --git a/public/app/plugins/panel/graph/axes_editor.html b/public/app/plugins/panel/graph/axes_editor.html
index 6160ef01fec..6ec64015746 100644
--- a/public/app/plugins/panel/graph/axes_editor.html
+++ b/public/app/plugins/panel/graph/axes_editor.html
@@ -11,6 +11,7 @@
Unit
+
Scale
@@ -28,8 +29,10 @@
Y-Max
-
-
+
+
+
+
+
+
Y-Axes
+
+
+
+ Level
+
+
+
+
+
diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts
index 369f37022f4..8a2aea8c4c2 100755
--- a/public/app/plugins/panel/graph/graph.ts
+++ b/public/app/plugins/panel/graph/graph.ts
@@ -18,6 +18,7 @@ import GraphTooltip from './graph_tooltip';
import { ThresholdManager } from './threshold_manager';
import { EventManager } from 'app/features/annotations/all';
import { convertToHistogramData } from './histogram';
+import { alignYLevel } from './align_yaxes';
import config from 'app/core/config';
/** @ngInject **/
@@ -155,6 +156,16 @@ function graphDirective(timeSrv, popoverSrv, contextSrv) {
}
}
+ function processRangeHook(plot) {
+ var yAxes = plot.getYAxes();
+ const align = panel.yaxis.align || false;
+
+ if (yAxes.length > 1 && align === true) {
+ const level = panel.yaxis.alignLevel || 0;
+ alignYLevel(yAxes, parseFloat(level));
+ }
+ }
+
// Series could have different timeSteps,
// let's find the smallest one so that bars are correctly rendered.
// In addition, only take series which are rendered as bars for this.
@@ -294,6 +305,7 @@ function graphDirective(timeSrv, popoverSrv, contextSrv) {
hooks: {
draw: [drawHook],
processOffset: [processOffsetHook],
+ processRange: [processRangeHook],
},
legend: { show: false },
series: {
diff --git a/public/app/plugins/panel/graph/legend.ts b/public/app/plugins/panel/graph/legend.ts
index 0c8852bf55a..d1186ae0b1e 100644
--- a/public/app/plugins/panel/graph/legend.ts
+++ b/public/app/plugins/panel/graph/legend.ts
@@ -15,6 +15,7 @@ module.directive('graphLegend', function(popoverSrv, $timeout) {
var seriesList;
var i;
var legendScrollbar;
+ const legendRightDefaultWidth = 10;
scope.$on('$destroy', function() {
if (legendScrollbar) {
@@ -111,6 +112,7 @@ module.directive('graphLegend', function(popoverSrv, $timeout) {
}
function render() {
+ let legendWidth = elem.width();
if (!ctrl.panel.legend.show) {
elem.empty();
firstRender = true;
@@ -163,7 +165,7 @@ module.directive('graphLegend', function(popoverSrv, $timeout) {
}
// render first time for getting proper legend height
- if (!panel.legend.rightSide) {
+ if (!panel.legend.rightSide || (panel.legend.rightSide && legendWidth !== legendRightDefaultWidth)) {
renderLegendElement(tableHeaderElem);
elem.empty();
}
@@ -227,6 +229,8 @@ module.directive('graphLegend', function(popoverSrv, $timeout) {
}
function renderLegendElement(tableHeaderElem) {
+ let legendWidth = elem.width();
+
var seriesElements = renderSeriesLegendElements();
if (panel.legend.alignAsTable) {
@@ -238,7 +242,7 @@ module.directive('graphLegend', function(popoverSrv, $timeout) {
elem.append(seriesElements);
}
- if (!panel.legend.rightSide) {
+ if (!panel.legend.rightSide || (panel.legend.rightSide && legendWidth !== legendRightDefaultWidth)) {
addScrollbar();
} else {
destroyScrollbar();
@@ -263,6 +267,7 @@ module.directive('graphLegend', function(popoverSrv, $timeout) {
function destroyScrollbar() {
if (legendScrollbar) {
legendScrollbar.destroy();
+ legendScrollbar = undefined;
}
}
},
diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts
index 59e72124c74..6cebbe65ab8 100644
--- a/public/app/plugins/panel/graph/module.ts
+++ b/public/app/plugins/panel/graph/module.ts
@@ -55,6 +55,10 @@ class GraphCtrl extends MetricsPanelCtrl {
values: [],
buckets: null,
},
+ yaxis: {
+ align: false,
+ alignLevel: null,
+ },
// show/hide lines
lines: true,
// fill factor
diff --git a/public/app/plugins/panel/graph/specs/align_yaxes.jest.ts b/public/app/plugins/panel/graph/specs/align_yaxes.jest.ts
new file mode 100644
index 00000000000..da3aff91275
--- /dev/null
+++ b/public/app/plugins/panel/graph/specs/align_yaxes.jest.ts
@@ -0,0 +1,210 @@
+import { alignYLevel } from '../align_yaxes';
+
+describe('Graph Y axes aligner', function() {
+ let yaxes, expected;
+ let alignY = 0;
+
+ describe('on the one hand with respect to zero', () => {
+ it('Should shrink Y axis', () => {
+ yaxes = [{ min: 5, max: 10 }, { min: 2, max: 3 }];
+ expected = [{ min: 5, max: 10 }, { min: 1.5, max: 3 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+
+ it('Should shrink Y axis', () => {
+ yaxes = [{ min: 2, max: 3 }, { min: 5, max: 10 }];
+ expected = [{ min: 1.5, max: 3 }, { min: 5, max: 10 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+
+ it('Should shrink Y axis', () => {
+ yaxes = [{ min: -10, max: -5 }, { min: -3, max: -2 }];
+ expected = [{ min: -10, max: -5 }, { min: -3, max: -1.5 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+
+ it('Should shrink Y axis', () => {
+ yaxes = [{ min: -3, max: -2 }, { min: -10, max: -5 }];
+ expected = [{ min: -3, max: -1.5 }, { min: -10, max: -5 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+ });
+
+ describe('on the opposite sides with respect to zero', () => {
+ it('Should shrink Y axes', () => {
+ yaxes = [{ min: -3, max: -1 }, { min: 5, max: 10 }];
+ expected = [{ min: -3, max: 3 }, { min: -10, max: 10 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+
+ it('Should shrink Y axes', () => {
+ yaxes = [{ min: 1, max: 3 }, { min: -10, max: -5 }];
+ expected = [{ min: -3, max: 3 }, { min: -10, max: 10 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+ });
+
+ describe('both across zero', () => {
+ it('Should shrink Y axes', () => {
+ yaxes = [{ min: -10, max: 5 }, { min: -2, max: 3 }];
+ expected = [{ min: -10, max: 15 }, { min: -2, max: 3 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+
+ it('Should shrink Y axes', () => {
+ yaxes = [{ min: -5, max: 10 }, { min: -3, max: 2 }];
+ expected = [{ min: -15, max: 10 }, { min: -3, max: 2 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+ });
+
+ describe('one of graphs on zero', () => {
+ it('Should shrink Y axes', () => {
+ yaxes = [{ min: 0, max: 3 }, { min: 5, max: 10 }];
+ expected = [{ min: 0, max: 3 }, { min: 0, max: 10 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+
+ it('Should shrink Y axes', () => {
+ yaxes = [{ min: 5, max: 10 }, { min: 0, max: 3 }];
+ expected = [{ min: 0, max: 10 }, { min: 0, max: 3 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+
+ it('Should shrink Y axes', () => {
+ yaxes = [{ min: -3, max: 0 }, { min: -10, max: -5 }];
+ expected = [{ min: -3, max: 0 }, { min: -10, max: 0 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+
+ it('Should shrink Y axes', () => {
+ yaxes = [{ min: -10, max: -5 }, { min: -3, max: 0 }];
+ expected = [{ min: -10, max: 0 }, { min: -3, max: 0 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+ });
+
+ describe('both graphs on zero', () => {
+ it('Should shrink Y axes', () => {
+ yaxes = [{ min: 0, max: 3 }, { min: -10, max: 0 }];
+ expected = [{ min: -3, max: 3 }, { min: -10, max: 10 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+
+ it('Should shrink Y axes', () => {
+ yaxes = [{ min: -3, max: 0 }, { min: 0, max: 10 }];
+ expected = [{ min: -3, max: 3 }, { min: -10, max: 10 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+ });
+
+ describe('mixed placement of graphs relative to zero', () => {
+ it('Should shrink Y axes', () => {
+ yaxes = [{ min: -10, max: 5 }, { min: 1, max: 3 }];
+ expected = [{ min: -10, max: 5 }, { min: -6, max: 3 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+
+ it('Should shrink Y axes', () => {
+ yaxes = [{ min: 1, max: 3 }, { min: -10, max: 5 }];
+ expected = [{ min: -6, max: 3 }, { min: -10, max: 5 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+
+ it('Should shrink Y axes', () => {
+ yaxes = [{ min: -10, max: 5 }, { min: -3, max: -1 }];
+ expected = [{ min: -10, max: 5 }, { min: -3, max: 1.5 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+
+ it('Should shrink Y axes', () => {
+ yaxes = [{ min: -3, max: -1 }, { min: -10, max: 5 }];
+ expected = [{ min: -3, max: 1.5 }, { min: -10, max: 5 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+ });
+
+ describe('on level not zero', () => {
+ it('Should shrink Y axis', () => {
+ alignY = 1;
+ yaxes = [{ min: 5, max: 10 }, { min: 2, max: 4 }];
+ expected = [{ min: 4, max: 10 }, { min: 2, max: 4 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+
+ it('Should shrink Y axes', () => {
+ alignY = 2;
+ yaxes = [{ min: -3, max: 1 }, { min: 5, max: 10 }];
+ expected = [{ min: -3, max: 7 }, { min: -6, max: 10 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+
+ it('Should shrink Y axes', () => {
+ alignY = -1;
+ yaxes = [{ min: -5, max: 5 }, { min: -2, max: 3 }];
+ expected = [{ min: -5, max: 15 }, { min: -2, max: 3 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+
+ it('Should shrink Y axes', () => {
+ alignY = -2;
+ yaxes = [{ min: -2, max: 3 }, { min: 5, max: 10 }];
+ expected = [{ min: -2, max: 3 }, { min: -2, max: 10 }];
+
+ alignYLevel(yaxes, alignY);
+ expect(yaxes).toMatchObject(expected);
+ });
+ });
+
+ describe('on level not number value', () => {
+ it('Should ignore without errors', () => {
+ yaxes = [{ min: 5, max: 10 }, { min: 2, max: 4 }];
+ expected = [{ min: 5, max: 10 }, { min: 2, max: 4 }];
+
+ alignYLevel(yaxes, 'q');
+ expect(yaxes).toMatchObject(expected);
+ });
+ });
+});
diff --git a/public/app/stores/ViewStore/ViewStore.ts b/public/app/stores/ViewStore/ViewStore.ts
index d00bf3c429f..ba966a194d8 100644
--- a/public/app/stores/ViewStore/ViewStore.ts
+++ b/public/app/stores/ViewStore/ViewStore.ts
@@ -26,7 +26,9 @@ export const ViewStore = types
function updateQuery(query: any) {
self.query.clear();
for (let key of Object.keys(query)) {
- self.query.set(key, query[key]);
+ if (query[key]) {
+ self.query.set(key, query[key]);
+ }
}
}
@@ -34,7 +36,9 @@ export const ViewStore = types
function updateRouteParams(routeParams: any) {
self.routeParams.clear();
for (let key of Object.keys(routeParams)) {
- self.routeParams.set(key, routeParams[key]);
+ if (routeParams[key]) {
+ self.routeParams.set(key, routeParams[key]);
+ }
}
}
diff --git a/public/img/kibana.png b/public/img/kibana.png
deleted file mode 100644
index 85220e33226..00000000000
Binary files a/public/img/kibana.png and /dev/null differ
diff --git a/public/img/small.png b/public/img/small.png
deleted file mode 100644
index e355b6efc7e..00000000000
Binary files a/public/img/small.png and /dev/null differ
diff --git a/public/sass/components/_panel_graph.scss b/public/sass/components/_panel_graph.scss
index 716778096d6..e15cd576367 100644
--- a/public/sass/components/_panel_graph.scss
+++ b/public/sass/components/_panel_graph.scss
@@ -4,24 +4,26 @@
height: 100%;
&--legend-right {
- flex-direction: row;
+ @include media-breakpoint-up(sm) {
+ flex-direction: row;
- .graph-legend {
- flex: 0 1 10px;
- max-height: 100%;
- }
+ .graph-legend {
+ flex: 0 1 10px;
+ max-height: 100%;
+ }
- .graph-legend-series {
- display: block;
- padding-left: 0px;
- }
+ .graph-legend-series {
+ display: block;
+ padding-left: 0px;
+ }
- .graph-legend-table {
- width: auto;
- }
+ .graph-legend-table {
+ width: auto;
+ }
- .graph-legend-table .graph-legend-series {
- display: table-row;
+ .graph-legend-table .graph-legend-series {
+ display: table-row;
+ }
}
}
}
diff --git a/public/vendor/flot/jquery.flot.js b/public/vendor/flot/jquery.flot.js
index ec35fb87bd8..8ee09e25c41 100644
--- a/public/vendor/flot/jquery.flot.js
+++ b/public/vendor/flot/jquery.flot.js
@@ -632,6 +632,7 @@ Licensed under the MIT license.
processRawData: [],
processDatapoints: [],
processOffset: [],
+ processRange: [],
drawBackground: [],
drawSeries: [],
draw: [],
@@ -1613,20 +1614,32 @@ Licensed under the MIT license.
setRange(axis);
});
+ executeHooks(hooks.processRange, []);
+
if (showGrid) {
var allocatedAxes = $.grep(axes, function (axis) {
return axis.show || axis.reserveSpace;
});
- $.each(allocatedAxes, function (_, axis) {
- // make the ticks
- setupTickGeneration(axis);
- setTicks(axis);
- snapRangeToTicks(axis, axis.ticks);
- // find labelWidth/Height for axis
- measureTickLabels(axis);
- });
+ var snaped = false;
+ for (var i = 0; i < 2; i++) {
+ $.each(allocatedAxes, function (_, axis) {
+ // make the ticks
+ setupTickGeneration(axis);
+ setTicks(axis);
+ snaped = snapRangeToTicks(axis, axis.ticks) || snaped;
+ // find labelWidth/Height for axis
+ measureTickLabels(axis);
+ });
+
+ if (snaped && hooks.processRange.length > 0) {
+ executeHooks(hooks.processRange, []);
+ snaped = false;
+ } else {
+ break;
+ }
+ }
// with all dimensions calculated, we can compute the
// axis bounding boxes, start from the outside
@@ -1643,6 +1656,7 @@ Licensed under the MIT license.
});
}
+
plotWidth = surface.width - plotOffset.left - plotOffset.right;
plotHeight = surface.height - plotOffset.bottom - plotOffset.top;
@@ -1876,13 +1890,19 @@ Licensed under the MIT license.
}
function snapRangeToTicks(axis, ticks) {
+ var changed = false;
if (axis.options.autoscaleMargin && ticks.length > 0) {
// snap to ticks
- if (axis.options.min == null)
+ if (axis.options.min == null) {
axis.min = Math.min(axis.min, ticks[0].v);
- if (axis.options.max == null && ticks.length > 1)
+ changed = true;
+ }
+ if (axis.options.max == null && ticks.length > 1) {
axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
+ changed = true;
+ }
}
+ return changed;
}
function draw() {
diff --git a/vendor/github.com/bradfitz/gomemcache/memcache/memcache.go b/vendor/github.com/bradfitz/gomemcache/memcache/memcache.go
index 8508063bc35..b98a7653467 100644
--- a/vendor/github.com/bradfitz/gomemcache/memcache/memcache.go
+++ b/vendor/github.com/bradfitz/gomemcache/memcache/memcache.go
@@ -457,7 +457,7 @@ func (c *Client) GetMulti(keys []string) (map[string]*Item, error) {
}
var err error
- for range keyMap {
+ for _ = range keyMap {
if ge := <-ch; ge != nil {
err = ge
}
diff --git a/vendor/github.com/denisenkom/go-mssqldb/mssql.go b/vendor/github.com/denisenkom/go-mssqldb/mssql.go
index bc773f77ccd..8a3f28f1151 100644
--- a/vendor/github.com/denisenkom/go-mssqldb/mssql.go
+++ b/vendor/github.com/denisenkom/go-mssqldb/mssql.go
@@ -582,7 +582,7 @@ type Rows struct {
func (rc *Rows) Close() error {
rc.cancel()
- for range rc.tokchan {
+ for _ = range rc.tokchan {
}
rc.tokchan = nil
return nil
diff --git a/vendor/github.com/denisenkom/go-mssqldb/tds.go b/vendor/github.com/denisenkom/go-mssqldb/tds.go
index 6519b341121..a18c406c1f8 100644
--- a/vendor/github.com/denisenkom/go-mssqldb/tds.go
+++ b/vendor/github.com/denisenkom/go-mssqldb/tds.go
@@ -166,7 +166,7 @@ func writePrelogin(w *tdsBuffer, fields map[uint8][]byte) error {
w.BeginPacket(packPrelogin, false)
offset := uint16(5*len(fields) + 1)
keys := make(KeySlice, 0, len(fields))
- for k := range fields {
+ for k, _ := range fields {
keys = append(keys, k)
}
sort.Sort(keys)
@@ -1147,7 +1147,7 @@ func dialConnection(ctx context.Context, p connectParams) (conn net.Conn, err er
}
// Wait for either the *first* successful connection, or all the errors
wait_loop:
- for i := range ips {
+ for i, _ := range ips {
select {
case conn = <-connChan:
// Got a connection to use, close any others
diff --git a/vendor/github.com/go-xorm/core/column.go b/vendor/github.com/go-xorm/core/column.go
index c59d01021fa..d9362e98578 100644
--- a/vendor/github.com/go-xorm/core/column.go
+++ b/vendor/github.com/go-xorm/core/column.go
@@ -13,12 +13,13 @@ const (
ONLYFROMDB
)
-// database column
+// Column defines database column
type Column struct {
Name string
TableName string
FieldName string
SQLType SQLType
+ IsJSON bool
Length int
Length2 int
Nullable bool
@@ -37,6 +38,7 @@ type Column struct {
SetOptions map[string]int
DisableTimeZone bool
TimeZone *time.Location // column specified time zone
+ Comment string
}
func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column {
@@ -60,6 +62,7 @@ func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable
IsVersion: false,
DefaultIsEmpty: false,
EnumOptions: make(map[string]int),
+ Comment: "",
}
}
diff --git a/vendor/github.com/go-xorm/core/dialect.go b/vendor/github.com/go-xorm/core/dialect.go
index 74478301e45..6f2e81d017b 100644
--- a/vendor/github.com/go-xorm/core/dialect.go
+++ b/vendor/github.com/go-xorm/core/dialect.go
@@ -244,6 +244,9 @@ func (b *Base) CreateTableSql(table *Table, tableName, storeEngine, charset stri
sql += col.StringNoPk(b.dialect)
}
sql = strings.TrimSpace(sql)
+ if b.DriverName() == MYSQL && len(col.Comment) > 0 {
+ sql += " COMMENT '" + col.Comment + "'"
+ }
sql += ", "
}
diff --git a/vendor/github.com/go-xorm/core/rows.go b/vendor/github.com/go-xorm/core/rows.go
index c4dec23e0de..4a4acaa4c26 100644
--- a/vendor/github.com/go-xorm/core/rows.go
+++ b/vendor/github.com/go-xorm/core/rows.go
@@ -247,6 +247,18 @@ type Row struct {
err error // deferred error for easy chaining
}
+// ErrorRow return an error row
+func ErrorRow(err error) *Row {
+ return &Row{
+ err: err,
+ }
+}
+
+// NewRow from rows
+func NewRow(rows *Rows, err error) *Row {
+ return &Row{rows, err}
+}
+
func (row *Row) Columns() ([]string, error) {
if row.err != nil {
return nil, row.err
diff --git a/vendor/github.com/go-xorm/core/table.go b/vendor/github.com/go-xorm/core/table.go
index e6f6a7518b5..88199bedd61 100644
--- a/vendor/github.com/go-xorm/core/table.go
+++ b/vendor/github.com/go-xorm/core/table.go
@@ -22,6 +22,7 @@ type Table struct {
Cacher Cacher
StoreEngine string
Charset string
+ Comment string
}
func (table *Table) Columns() []*Column {
diff --git a/vendor/github.com/go-xorm/core/type.go b/vendor/github.com/go-xorm/core/type.go
index 86048225176..8010a2220fc 100644
--- a/vendor/github.com/go-xorm/core/type.go
+++ b/vendor/github.com/go-xorm/core/type.go
@@ -100,7 +100,8 @@ var (
LongBlob = "LONGBLOB"
Bytea = "BYTEA"
- Bool = "BOOL"
+ Bool = "BOOL"
+ Boolean = "BOOLEAN"
Serial = "SERIAL"
BigSerial = "BIGSERIAL"
@@ -163,7 +164,7 @@ var (
uintTypes = sort.StringSlice{"*uint", "*uint16", "*uint32", "*uint8"}
)
-// !nashtsai! treat following var as interal const values, these are used for reflect.TypeOf comparision
+// !nashtsai! treat following var as interal const values, these are used for reflect.TypeOf comparison
var (
c_EMPTY_STRING string
c_BOOL_DEFAULT bool
diff --git a/vendor/github.com/go-xorm/xorm/lru_cacher.go b/vendor/github.com/go-xorm/xorm/cache_lru.go
similarity index 90%
rename from vendor/github.com/go-xorm/xorm/lru_cacher.go
rename to vendor/github.com/go-xorm/xorm/cache_lru.go
index 4a74504351f..c9672cebe4d 100644
--- a/vendor/github.com/go-xorm/xorm/lru_cacher.go
+++ b/vendor/github.com/go-xorm/xorm/cache_lru.go
@@ -15,13 +15,12 @@ import (
// LRUCacher implments cache object facilities
type LRUCacher struct {
- idList *list.List
- sqlList *list.List
- idIndex map[string]map[string]*list.Element
- sqlIndex map[string]map[string]*list.Element
- store core.CacheStore
- mutex sync.Mutex
- // maxSize int
+ idList *list.List
+ sqlList *list.List
+ idIndex map[string]map[string]*list.Element
+ sqlIndex map[string]map[string]*list.Element
+ store core.CacheStore
+ mutex sync.Mutex
MaxElementSize int
Expired time.Duration
GcInterval time.Duration
@@ -54,8 +53,6 @@ func (m *LRUCacher) RunGC() {
// GC check ids lit and sql list to remove all element expired
func (m *LRUCacher) GC() {
- //fmt.Println("begin gc ...")
- //defer fmt.Println("end gc ...")
m.mutex.Lock()
defer m.mutex.Unlock()
var removedNum int
@@ -64,12 +61,10 @@ func (m *LRUCacher) GC() {
time.Now().Sub(e.Value.(*idNode).lastVisit) > m.Expired {
removedNum++
next := e.Next()
- //fmt.Println("removing ...", e.Value)
node := e.Value.(*idNode)
m.delBean(node.tbName, node.id)
e = next
} else {
- //fmt.Printf("removing %d cache nodes ..., left %d\n", removedNum, m.idList.Len())
break
}
}
@@ -80,12 +75,10 @@ func (m *LRUCacher) GC() {
time.Now().Sub(e.Value.(*sqlNode).lastVisit) > m.Expired {
removedNum++
next := e.Next()
- //fmt.Println("removing ...", e.Value)
node := e.Value.(*sqlNode)
m.delIds(node.tbName, node.sql)
e = next
} else {
- //fmt.Printf("removing %d cache nodes ..., left %d\n", removedNum, m.sqlList.Len())
break
}
}
@@ -116,7 +109,6 @@ func (m *LRUCacher) GetIds(tableName, sql string) interface{} {
}
m.delIds(tableName, sql)
-
return nil
}
@@ -134,7 +126,6 @@ func (m *LRUCacher) GetBean(tableName string, id string) interface{} {
// if expired, remove the node and return nil
if time.Now().Sub(lastTime) > m.Expired {
m.delBean(tableName, id)
- //m.clearIds(tableName)
return nil
}
m.idList.MoveToBack(el)
@@ -148,7 +139,6 @@ func (m *LRUCacher) GetBean(tableName string, id string) interface{} {
// store bean is not exist, then remove memory's index
m.delBean(tableName, id)
- //m.clearIds(tableName)
return nil
}
@@ -166,8 +156,8 @@ func (m *LRUCacher) clearIds(tableName string) {
// ClearIds clears all sql-ids mapping on table tableName from cache
func (m *LRUCacher) ClearIds(tableName string) {
m.mutex.Lock()
- defer m.mutex.Unlock()
m.clearIds(tableName)
+ m.mutex.Unlock()
}
func (m *LRUCacher) clearBeans(tableName string) {
@@ -184,14 +174,13 @@ func (m *LRUCacher) clearBeans(tableName string) {
// ClearBeans clears all beans in some table
func (m *LRUCacher) ClearBeans(tableName string) {
m.mutex.Lock()
- defer m.mutex.Unlock()
m.clearBeans(tableName)
+ m.mutex.Unlock()
}
// PutIds pus ids into table
func (m *LRUCacher) PutIds(tableName, sql string, ids interface{}) {
m.mutex.Lock()
- defer m.mutex.Unlock()
if _, ok := m.sqlIndex[tableName]; !ok {
m.sqlIndex[tableName] = make(map[string]*list.Element)
}
@@ -207,12 +196,12 @@ func (m *LRUCacher) PutIds(tableName, sql string, ids interface{}) {
node := e.Value.(*sqlNode)
m.delIds(node.tbName, node.sql)
}
+ m.mutex.Unlock()
}
// PutBean puts beans into table
func (m *LRUCacher) PutBean(tableName string, id string, obj interface{}) {
m.mutex.Lock()
- defer m.mutex.Unlock()
var el *list.Element
var ok bool
@@ -229,6 +218,7 @@ func (m *LRUCacher) PutBean(tableName string, id string, obj interface{}) {
node := e.Value.(*idNode)
m.delBean(node.tbName, node.id)
}
+ m.mutex.Unlock()
}
func (m *LRUCacher) delIds(tableName, sql string) {
@@ -244,8 +234,8 @@ func (m *LRUCacher) delIds(tableName, sql string) {
// DelIds deletes ids
func (m *LRUCacher) DelIds(tableName, sql string) {
m.mutex.Lock()
- defer m.mutex.Unlock()
m.delIds(tableName, sql)
+ m.mutex.Unlock()
}
func (m *LRUCacher) delBean(tableName string, id string) {
@@ -261,8 +251,8 @@ func (m *LRUCacher) delBean(tableName string, id string) {
// DelBean deletes beans in some table
func (m *LRUCacher) DelBean(tableName string, id string) {
m.mutex.Lock()
- defer m.mutex.Unlock()
m.delBean(tableName, id)
+ m.mutex.Unlock()
}
type idNode struct {
diff --git a/vendor/github.com/go-xorm/xorm/memory_store.go b/vendor/github.com/go-xorm/xorm/cache_memory_store.go
similarity index 100%
rename from vendor/github.com/go-xorm/xorm/memory_store.go
rename to vendor/github.com/go-xorm/xorm/cache_memory_store.go
diff --git a/vendor/github.com/go-xorm/xorm/context.go b/vendor/github.com/go-xorm/xorm/context.go
new file mode 100644
index 00000000000..074ba35a80a
--- /dev/null
+++ b/vendor/github.com/go-xorm/xorm/context.go
@@ -0,0 +1,26 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.8
+
+package xorm
+
+import "context"
+
+// PingContext tests if database is alive
+func (engine *Engine) PingContext(ctx context.Context) error {
+ session := engine.NewSession()
+ defer session.Close()
+ return session.PingContext(ctx)
+}
+
+// PingContext test if database is ok
+func (session *Session) PingContext(ctx context.Context) error {
+ if session.isAutoClose {
+ defer session.Close()
+ }
+
+ session.engine.logger.Infof("PING DATABASE %v", session.engine.DriverName())
+ return session.DB().PingContext(ctx)
+}
diff --git a/vendor/github.com/go-xorm/xorm/convert.go b/vendor/github.com/go-xorm/xorm/convert.go
index 87f0d3f1ec5..2316ca0b4dc 100644
--- a/vendor/github.com/go-xorm/xorm/convert.go
+++ b/vendor/github.com/go-xorm/xorm/convert.go
@@ -209,10 +209,10 @@ func convertAssign(dest, src interface{}) error {
if src == nil {
dv.Set(reflect.Zero(dv.Type()))
return nil
- } else {
- dv.Set(reflect.New(dv.Type().Elem()))
- return convertAssign(dv.Interface(), src)
}
+
+ dv.Set(reflect.New(dv.Type().Elem()))
+ return convertAssign(dv.Interface(), src)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
s := asString(src)
i64, err := strconv.ParseInt(s, 10, dv.Type().Bits())
@@ -247,3 +247,102 @@ func convertAssign(dest, src interface{}) error {
return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, dest)
}
+
+func asKind(vv reflect.Value, tp reflect.Type) (interface{}, error) {
+ switch tp.Kind() {
+ case reflect.Int64:
+ return vv.Int(), nil
+ case reflect.Int:
+ return int(vv.Int()), nil
+ case reflect.Int32:
+ return int32(vv.Int()), nil
+ case reflect.Int16:
+ return int16(vv.Int()), nil
+ case reflect.Int8:
+ return int8(vv.Int()), nil
+ case reflect.Uint64:
+ return vv.Uint(), nil
+ case reflect.Uint:
+ return uint(vv.Uint()), nil
+ case reflect.Uint32:
+ return uint32(vv.Uint()), nil
+ case reflect.Uint16:
+ return uint16(vv.Uint()), nil
+ case reflect.Uint8:
+ return uint8(vv.Uint()), nil
+ case reflect.String:
+ return vv.String(), nil
+ case reflect.Slice:
+ if tp.Elem().Kind() == reflect.Uint8 {
+ v, err := strconv.ParseInt(string(vv.Interface().([]byte)), 10, 64)
+ if err != nil {
+ return nil, err
+ }
+ return v, nil
+ }
+
+ }
+ return nil, fmt.Errorf("unsupported primary key type: %v, %v", tp, vv)
+}
+
+func convertFloat(v interface{}) (float64, error) {
+ switch v.(type) {
+ case float32:
+ return float64(v.(float32)), nil
+ case float64:
+ return v.(float64), nil
+ case string:
+ i, err := strconv.ParseFloat(v.(string), 64)
+ if err != nil {
+ return 0, err
+ }
+ return i, nil
+ case []byte:
+ i, err := strconv.ParseFloat(string(v.([]byte)), 64)
+ if err != nil {
+ return 0, err
+ }
+ return i, nil
+ }
+ return 0, fmt.Errorf("unsupported type: %v", v)
+}
+
+func convertInt(v interface{}) (int64, error) {
+ switch v.(type) {
+ case int:
+ return int64(v.(int)), nil
+ case int8:
+ return int64(v.(int8)), nil
+ case int16:
+ return int64(v.(int16)), nil
+ case int32:
+ return int64(v.(int32)), nil
+ case int64:
+ return v.(int64), nil
+ case []byte:
+ i, err := strconv.ParseInt(string(v.([]byte)), 10, 64)
+ if err != nil {
+ return 0, err
+ }
+ return i, nil
+ case string:
+ i, err := strconv.ParseInt(v.(string), 10, 64)
+ if err != nil {
+ return 0, err
+ }
+ return i, nil
+ }
+ return 0, fmt.Errorf("unsupported type: %v", v)
+}
+
+func asBool(bs []byte) (bool, error) {
+ if len(bs) == 0 {
+ return false, nil
+ }
+ if bs[0] == 0x00 {
+ return false, nil
+ } else if bs[0] == 0x01 {
+ return true, nil
+ }
+ return strconv.ParseBool(string(bs))
+}
diff --git a/vendor/github.com/go-xorm/xorm/dialect_mssql.go b/vendor/github.com/go-xorm/xorm/dialect_mssql.go
index 70fcaf6ea08..6d2291dc1da 100644
--- a/vendor/github.com/go-xorm/xorm/dialect_mssql.go
+++ b/vendor/github.com/go-xorm/xorm/dialect_mssql.go
@@ -215,10 +215,10 @@ func (db *mssql) SqlType(c *core.Column) string {
var res string
switch t := c.SQLType.Name; t {
case core.Bool:
- res = core.TinyInt
- if c.Default == "true" {
+ res = core.Bit
+ if strings.EqualFold(c.Default, "true") {
c.Default = "1"
- } else if c.Default == "false" {
+ } else {
c.Default = "0"
}
case core.Serial:
@@ -250,6 +250,9 @@ func (db *mssql) SqlType(c *core.Column) string {
case core.Uuid:
res = core.Varchar
c.Length = 40
+ case core.TinyInt:
+ res = core.TinyInt
+ c.Length = 0
default:
res = t
}
@@ -335,9 +338,15 @@ func (db *mssql) TableCheckSql(tableName string) (string, []interface{}) {
func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column, error) {
args := []interface{}{}
s := `select a.name as name, b.name as ctype,a.max_length,a.precision,a.scale,a.is_nullable as nullable,
- replace(replace(isnull(c.text,''),'(',''),')','') as vdefault
- from sys.columns a left join sys.types b on a.user_type_id=b.user_type_id
- left join sys.syscomments c on a.default_object_id=c.id
+ replace(replace(isnull(c.text,''),'(',''),')','') as vdefault,
+ ISNULL(i.is_primary_key, 0)
+ from sys.columns a
+ left join sys.types b on a.user_type_id=b.user_type_id
+ left join sys.syscomments c on a.default_object_id=c.id
+ LEFT OUTER JOIN
+ sys.index_columns ic ON ic.object_id = a.object_id AND ic.column_id = a.column_id
+ LEFT OUTER JOIN
+ sys.indexes i ON ic.object_id = i.object_id AND ic.index_id = i.index_id
where a.object_id=object_id('` + tableName + `')`
db.LogSQL(s, args)
@@ -352,8 +361,8 @@ func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column
for rows.Next() {
var name, ctype, vdefault string
var maxLen, precision, scale int
- var nullable bool
- err = rows.Scan(&name, &ctype, &maxLen, &precision, &scale, &nullable, &vdefault)
+ var nullable, isPK bool
+ err = rows.Scan(&name, &ctype, &maxLen, &precision, &scale, &nullable, &vdefault, &isPK)
if err != nil {
return nil, nil, err
}
@@ -363,6 +372,7 @@ func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column
col.Name = strings.Trim(name, "` ")
col.Nullable = nullable
col.Default = vdefault
+ col.IsPrimaryKey = isPK
ct := strings.ToUpper(ctype)
if ct == "DECIMAL" {
col.Length = precision
@@ -468,9 +478,10 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =?
}
colName = strings.Trim(colName, "` ")
-
+ var isRegular bool
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
indexName = indexName[5+len(tableName):]
+ isRegular = true
}
var index *core.Index
@@ -479,6 +490,7 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =?
index = new(core.Index)
index.Type = indexType
index.Name = indexName
+ index.IsRegular = isRegular
indexes[indexName] = index
}
index.AddColumn(colName)
@@ -534,7 +546,6 @@ type odbcDriver struct {
func (p *odbcDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
kv := strings.Split(dataSourceName, ";")
var dbName string
-
for _, c := range kv {
vv := strings.Split(strings.TrimSpace(c), "=")
if len(vv) == 2 {
diff --git a/vendor/github.com/go-xorm/xorm/dialect_mysql.go b/vendor/github.com/go-xorm/xorm/dialect_mysql.go
index 55cfdd7640b..99100b23251 100644
--- a/vendor/github.com/go-xorm/xorm/dialect_mysql.go
+++ b/vendor/github.com/go-xorm/xorm/dialect_mysql.go
@@ -299,7 +299,7 @@ func (db *mysql) TableCheckSql(tableName string) (string, []interface{}) {
func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column, error) {
args := []interface{}{db.DbName, tableName}
s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," +
- " `COLUMN_KEY`, `EXTRA` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?"
+ " `COLUMN_KEY`, `EXTRA`,`COLUMN_COMMENT` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?"
db.LogSQL(s, args)
rows, err := db.DB().Query(s, args...)
@@ -314,13 +314,14 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column
col := new(core.Column)
col.Indexes = make(map[string]int)
- var columnName, isNullable, colType, colKey, extra string
+ var columnName, isNullable, colType, colKey, extra, comment string
var colDefault *string
- err = rows.Scan(&columnName, &isNullable, &colDefault, &colType, &colKey, &extra)
+ err = rows.Scan(&columnName, &isNullable, &colDefault, &colType, &colKey, &extra, &comment)
if err != nil {
return nil, nil, err
}
col.Name = strings.Trim(columnName, "` ")
+ col.Comment = comment
if "YES" == isNullable {
col.Nullable = true
}
@@ -407,7 +408,7 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column
func (db *mysql) GetTables() ([]*core.Table, error) {
args := []interface{}{db.DbName}
- s := "SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT` from " +
+ s := "SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT`, `TABLE_COMMENT` from " +
"`INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? AND (`ENGINE`='MyISAM' OR `ENGINE` = 'InnoDB' OR `ENGINE` = 'TokuDB')"
db.LogSQL(s, args)
@@ -420,14 +421,15 @@ func (db *mysql) GetTables() ([]*core.Table, error) {
tables := make([]*core.Table, 0)
for rows.Next() {
table := core.NewEmptyTable()
- var name, engine, tableRows string
+ var name, engine, tableRows, comment string
var autoIncr *string
- err = rows.Scan(&name, &engine, &tableRows, &autoIncr)
+ err = rows.Scan(&name, &engine, &tableRows, &autoIncr, &comment)
if err != nil {
return nil, err
}
table.Name = name
+ table.Comment = comment
table.StoreEngine = engine
tables = append(tables, table)
}
diff --git a/vendor/github.com/go-xorm/xorm/dialect_oracle.go b/vendor/github.com/go-xorm/xorm/dialect_oracle.go
index 8c43aa4cecb..ac0081b38f7 100644
--- a/vendor/github.com/go-xorm/xorm/dialect_oracle.go
+++ b/vendor/github.com/go-xorm/xorm/dialect_oracle.go
@@ -824,6 +824,12 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) {
indexName = strings.Trim(indexName, `" `)
+ var isRegular bool
+ if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
+ indexName = indexName[5+len(tableName):]
+ isRegular = true
+ }
+
if uniqueness == "UNIQUE" {
indexType = core.UniqueType
} else {
@@ -836,6 +842,7 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) {
index = new(core.Index)
index.Type = indexType
index.Name = indexName
+ index.IsRegular = isRegular
indexes[indexName] = index
}
index.AddColumn(colName)
diff --git a/vendor/github.com/go-xorm/xorm/dialect_postgres.go b/vendor/github.com/go-xorm/xorm/dialect_postgres.go
index 05fc1235ef4..83e9a1015c4 100644
--- a/vendor/github.com/go-xorm/xorm/dialect_postgres.go
+++ b/vendor/github.com/go-xorm/xorm/dialect_postgres.go
@@ -8,7 +8,6 @@ import (
"errors"
"fmt"
"net/url"
- "sort"
"strconv"
"strings"
@@ -781,6 +780,9 @@ func (db *postgres) SqlType(c *core.Column) string {
case core.TinyInt:
res = core.SmallInt
return res
+ case core.Bit:
+ res = core.Boolean
+ return res
case core.MediumInt, core.Int, core.Integer:
if c.IsAutoIncrement {
return core.Serial
@@ -1078,9 +1080,10 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error)
}
cs := strings.Split(indexdef, "(")
colNames = strings.Split(cs[1][0:len(cs[1])-1], ",")
-
+ var isRegular bool
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
newIdxName := indexName[5+len(tableName):]
+ isRegular = true
if newIdxName != "" {
indexName = newIdxName
}
@@ -1090,6 +1093,7 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error)
for _, colName := range colNames {
index.Cols = append(index.Cols, strings.Trim(colName, `" `))
}
+ index.IsRegular = isRegular
indexes[index.Name] = index
}
return indexes, nil
@@ -1112,10 +1116,6 @@ func (vs values) Get(k string) (v string) {
return vs[k]
}
-func errorf(s string, args ...interface{}) {
- panic(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...)))
-}
-
func parseURL(connstr string) (string, error) {
u, err := url.Parse(connstr)
if err != nil {
@@ -1126,46 +1126,18 @@ func parseURL(connstr string) (string, error) {
return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme)
}
- var kvs []string
escaper := strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`)
- accrue := func(k, v string) {
- if v != "" {
- kvs = append(kvs, k+"="+escaper.Replace(v))
- }
- }
-
- if u.User != nil {
- v := u.User.Username()
- accrue("user", v)
-
- v, _ = u.User.Password()
- accrue("password", v)
- }
-
- i := strings.Index(u.Host, ":")
- if i < 0 {
- accrue("host", u.Host)
- } else {
- accrue("host", u.Host[:i])
- accrue("port", u.Host[i+1:])
- }
if u.Path != "" {
- accrue("dbname", u.Path[1:])
+ return escaper.Replace(u.Path[1:]), nil
}
- q := u.Query()
- for k := range q {
- accrue(k, q.Get(k))
- }
-
- sort.Strings(kvs) // Makes testing easier (not a performance concern)
- return strings.Join(kvs, " "), nil
+ return "", nil
}
-func parseOpts(name string, o values) {
+func parseOpts(name string, o values) error {
if len(name) == 0 {
- return
+ return fmt.Errorf("invalid options: %s", name)
}
name = strings.TrimSpace(name)
@@ -1174,31 +1146,36 @@ func parseOpts(name string, o values) {
for _, p := range ps {
kv := strings.Split(p, "=")
if len(kv) < 2 {
- errorf("invalid option: %q", p)
+ return fmt.Errorf("invalid option: %q", p)
}
o.Set(kv[0], kv[1])
}
+
+ return nil
}
func (p *pqDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) {
db := &core.Uri{DbType: core.POSTGRES}
- o := make(values)
var err error
+
if strings.HasPrefix(dataSourceName, "postgresql://") || strings.HasPrefix(dataSourceName, "postgres://") {
- dataSourceName, err = parseURL(dataSourceName)
+ db.DbName, err = parseURL(dataSourceName)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ o := make(values)
+ err = parseOpts(dataSourceName, o)
if err != nil {
return nil, err
}
- }
- parseOpts(dataSourceName, o)
- db.DbName = o.Get("dbname")
+ db.DbName = o.Get("dbname")
+ }
+
if db.DbName == "" {
return nil, errors.New("dbname is empty")
}
- /*db.Schema = o.Get("schema")
- if len(db.Schema) == 0 {
- db.Schema = "public"
- }*/
+
return db, nil
}
diff --git a/vendor/github.com/go-xorm/xorm/dialect_sqlite3.go b/vendor/github.com/go-xorm/xorm/dialect_sqlite3.go
index c190c4d900f..a55b1615e71 100644
--- a/vendor/github.com/go-xorm/xorm/dialect_sqlite3.go
+++ b/vendor/github.com/go-xorm/xorm/dialect_sqlite3.go
@@ -14,10 +14,6 @@ import (
"github.com/go-xorm/core"
)
-// func init() {
-// RegisterDialect("sqlite3", &sqlite3{})
-// }
-
var (
sqlite3ReservedWords = map[string]bool{
"ABORT": true,
@@ -310,11 +306,25 @@ func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*core.Colu
for _, colStr := range colCreates {
reg = regexp.MustCompile(`,\s`)
colStr = reg.ReplaceAllString(colStr, ",")
+ if strings.HasPrefix(strings.TrimSpace(colStr), "PRIMARY KEY") {
+ parts := strings.Split(strings.TrimSpace(colStr), "(")
+ if len(parts) == 2 {
+ pkCols := strings.Split(strings.TrimRight(strings.TrimSpace(parts[1]), ")"), ",")
+ for _, pk := range pkCols {
+ if col, ok := cols[strings.Trim(strings.TrimSpace(pk), "`")]; ok {
+ col.IsPrimaryKey = true
+ }
+ }
+ }
+ continue
+ }
+
fields := strings.Fields(strings.TrimSpace(colStr))
col := new(core.Column)
col.Indexes = make(map[string]int)
col.Nullable = true
col.DefaultIsEmpty = true
+
for idx, field := range fields {
if idx == 0 {
col.Name = strings.Trim(strings.Trim(field, "`[] "), `"`)
@@ -405,8 +415,10 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error)
}
indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []")
+ var isRegular bool
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
index.Name = indexName[5+len(tableName):]
+ isRegular = true
} else {
index.Name = indexName
}
@@ -425,6 +437,7 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error)
for _, col := range colIndexes {
index.Cols = append(index.Cols, strings.Trim(col, "` []"))
}
+ index.IsRegular = isRegular
indexes[index.Name] = index
}
diff --git a/vendor/github.com/go-xorm/xorm/doc.go b/vendor/github.com/go-xorm/xorm/doc.go
index 5b36fcd80ba..a687e694768 100644
--- a/vendor/github.com/go-xorm/xorm/doc.go
+++ b/vendor/github.com/go-xorm/xorm/doc.go
@@ -8,7 +8,7 @@ Package xorm is a simple and powerful ORM for Go.
Installation
-Make sure you have installed Go 1.1+ and then:
+Make sure you have installed Go 1.6+ and then:
go get github.com/go-xorm/xorm
@@ -51,11 +51,15 @@ There are 8 major ORM methods and many helpful methods to use to operate databas
// INSERT INTO struct1 () values ()
// INSERT INTO struct2 () values (),(),()
-2. Query one record from database
+2. Query one record or one variable from database
has, err := engine.Get(&user)
// SELECT * FROM user LIMIT 1
+ var id int64
+ has, err := engine.Table("user").Where("name = ?", name).Get(&id)
+ // SELECT id FROM user WHERE name = ? LIMIT 1
+
3. Query multiple records from database
var sliceOfStructs []Struct
@@ -86,7 +90,7 @@ another is Rows
5. Update one or more records
- affected, err := engine.Id(...).Update(&user)
+ affected, err := engine.ID(...).Update(&user)
// UPDATE user SET ...
6. Delete one or more records, Delete MUST has condition
@@ -99,6 +103,9 @@ another is Rows
counts, err := engine.Count(&user)
// SELECT count(*) AS total FROM user
+ counts, err := engine.SQL("select count(*) FROM user").Count()
+ // select count(*) FROM user
+
8. Sum records
sumFloat64, err := engine.Sum(&user, "id")
diff --git a/vendor/github.com/go-xorm/xorm/engine.go b/vendor/github.com/go-xorm/xorm/engine.go
index 134e6b147d9..444611afb16 100644
--- a/vendor/github.com/go-xorm/xorm/engine.go
+++ b/vendor/github.com/go-xorm/xorm/engine.go
@@ -19,6 +19,7 @@ import (
"sync"
"time"
+ "github.com/go-xorm/builder"
"github.com/go-xorm/core"
)
@@ -40,12 +41,29 @@ type Engine struct {
showExecTime bool
logger core.ILogger
- TZLocation *time.Location
+ TZLocation *time.Location // The timezone of the application
DatabaseTZ *time.Location // The timezone of the database
disableGlobalCache bool
tagHandlers map[string]tagHandler
+
+ engineGroup *EngineGroup
+}
+
+// BufferSize sets buffer size for iterate
+func (engine *Engine) BufferSize(size int) *Session {
+ session := engine.NewSession()
+ session.isAutoClose = true
+ return session.BufferSize(size)
+}
+
+// CondDeleted returns the conditions whether a record is soft deleted.
+func (engine *Engine) CondDeleted(colName string) builder.Cond {
+ if engine.dialect.DBType() == core.MSSQL {
+ return builder.IsNull{colName}
+ }
+ return builder.IsNull{colName}.Or(builder.Eq{colName: zeroTime1})
}
// ShowSQL show SQL statement or not on logger if log level is great than INFO
@@ -78,6 +96,11 @@ func (engine *Engine) SetLogger(logger core.ILogger) {
engine.dialect.SetLogger(logger)
}
+// SetLogLevel sets the logger level
+func (engine *Engine) SetLogLevel(level core.LogLevel) {
+ engine.logger.SetLevel(level)
+}
+
// SetDisableGlobalCache disable global cache or not
func (engine *Engine) SetDisableGlobalCache(disable bool) {
if engine.disableGlobalCache != disable {
@@ -143,7 +166,6 @@ func (engine *Engine) Quote(value string) string {
// QuoteTo quotes string and writes into the buffer
func (engine *Engine) QuoteTo(buf *bytes.Buffer, value string) {
-
if buf == nil {
return
}
@@ -169,7 +191,7 @@ func (engine *Engine) quote(sql string) string {
return engine.dialect.QuoteStr() + sql + engine.dialect.QuoteStr()
}
-// SqlType will be depracated, please use SQLType instead
+// SqlType will be deprecated, please use SQLType instead
//
// Deprecated: use SQLType instead
func (engine *Engine) SqlType(c *core.Column) string {
@@ -201,26 +223,36 @@ func (engine *Engine) SetDefaultCacher(cacher core.Cacher) {
engine.Cacher = cacher
}
+// GetDefaultCacher returns the default cacher
+func (engine *Engine) GetDefaultCacher() core.Cacher {
+ return engine.Cacher
+}
+
// NoCache If you has set default cacher, and you want temporilly stop use cache,
// you can use NoCache()
func (engine *Engine) NoCache() *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.NoCache()
}
// NoCascade If you do not want to auto cascade load object
func (engine *Engine) NoCascade() *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.NoCascade()
}
// MapCacher Set a table use a special cacher
-func (engine *Engine) MapCacher(bean interface{}, cacher core.Cacher) {
+func (engine *Engine) MapCacher(bean interface{}, cacher core.Cacher) error {
v := rValue(bean)
- tb := engine.autoMapType(v)
+ tb, err := engine.autoMapType(v)
+ if err != nil {
+ return err
+ }
+
tb.Cacher = cacher
+ return nil
}
// NewDB provides an interface to operate database directly
@@ -240,7 +272,7 @@ func (engine *Engine) Dialect() core.Dialect {
// NewSession New a session
func (engine *Engine) NewSession() *Session {
- session := &Session{Engine: engine}
+ session := &Session{engine: engine}
session.Init()
return session
}
@@ -254,7 +286,6 @@ func (engine *Engine) Close() error {
func (engine *Engine) Ping() error {
session := engine.NewSession()
defer session.Close()
- engine.logger.Infof("PING DATABASE %v", engine.DriverName())
return session.Ping()
}
@@ -262,43 +293,13 @@ func (engine *Engine) Ping() error {
func (engine *Engine) logSQL(sqlStr string, sqlArgs ...interface{}) {
if engine.showSQL && !engine.showExecTime {
if len(sqlArgs) > 0 {
- engine.logger.Infof("[SQL] %v %v", sqlStr, sqlArgs)
+ engine.logger.Infof("[SQL] %v %#v", sqlStr, sqlArgs)
} else {
engine.logger.Infof("[SQL] %v", sqlStr)
}
}
}
-func (engine *Engine) logSQLQueryTime(sqlStr string, args []interface{}, executionBlock func() (*core.Stmt, *core.Rows, error)) (*core.Stmt, *core.Rows, error) {
- if engine.showSQL && engine.showExecTime {
- b4ExecTime := time.Now()
- stmt, res, err := executionBlock()
- execDuration := time.Since(b4ExecTime)
- if len(args) > 0 {
- engine.logger.Infof("[SQL] %s %v - took: %v", sqlStr, args, execDuration)
- } else {
- engine.logger.Infof("[SQL] %s - took: %v", sqlStr, execDuration)
- }
- return stmt, res, err
- }
- return executionBlock()
-}
-
-func (engine *Engine) logSQLExecutionTime(sqlStr string, args []interface{}, executionBlock func() (sql.Result, error)) (sql.Result, error) {
- if engine.showSQL && engine.showExecTime {
- b4ExecTime := time.Now()
- res, err := executionBlock()
- execDuration := time.Since(b4ExecTime)
- if len(args) > 0 {
- engine.logger.Infof("[sql] %s [args] %v - took: %v", sqlStr, args, execDuration)
- } else {
- engine.logger.Infof("[sql] %s - took: %v", sqlStr, execDuration)
- }
- return res, err
- }
- return executionBlock()
-}
-
// Sql provides raw sql input parameter. When you have a complex SQL statement
// and cannot use Where, Id, In and etc. Methods to describe, you can use SQL.
//
@@ -315,7 +316,7 @@ func (engine *Engine) Sql(querystring string, args ...interface{}) *Session {
// This code will execute "select * from user" and set the records to users
func (engine *Engine) SQL(query interface{}, args ...interface{}) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.SQL(query, args...)
}
@@ -324,14 +325,14 @@ func (engine *Engine) SQL(query interface{}, args ...interface{}) *Session {
// invoked. Call NoAutoTime if you dont' want to fill automatically.
func (engine *Engine) NoAutoTime() *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.NoAutoTime()
}
// NoAutoCondition disable auto generate Where condition from bean or not
func (engine *Engine) NoAutoCondition(no ...bool) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.NoAutoCondition(no...)
}
@@ -565,56 +566,56 @@ func (engine *Engine) tbName(v reflect.Value) string {
// Cascade use cascade or not
func (engine *Engine) Cascade(trueOrFalse ...bool) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Cascade(trueOrFalse...)
}
// Where method provide a condition query
func (engine *Engine) Where(query interface{}, args ...interface{}) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Where(query, args...)
}
-// Id will be depracated, please use ID instead
+// Id will be deprecated, please use ID instead
func (engine *Engine) Id(id interface{}) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Id(id)
}
// ID method provoide a condition as (id) = ?
func (engine *Engine) ID(id interface{}) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.ID(id)
}
// Before apply before Processor, affected bean is passed to closure arg
func (engine *Engine) Before(closures func(interface{})) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Before(closures)
}
// After apply after insert Processor, affected bean is passed to closure arg
func (engine *Engine) After(closures func(interface{})) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.After(closures)
}
// Charset set charset when create table, only support mysql now
func (engine *Engine) Charset(charset string) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Charset(charset)
}
// StoreEngine set store engine when create table, only support mysql now
func (engine *Engine) StoreEngine(storeEngine string) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.StoreEngine(storeEngine)
}
@@ -623,35 +624,35 @@ func (engine *Engine) StoreEngine(storeEngine string) *Session {
// but distinct will not provide id
func (engine *Engine) Distinct(columns ...string) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Distinct(columns...)
}
// Select customerize your select columns or contents
func (engine *Engine) Select(str string) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Select(str)
}
// Cols only use the parameters as select or update columns
func (engine *Engine) Cols(columns ...string) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Cols(columns...)
}
// AllCols indicates that all columns should be use
func (engine *Engine) AllCols() *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.AllCols()
}
// MustCols specify some columns must use even if they are empty
func (engine *Engine) MustCols(columns ...string) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.MustCols(columns...)
}
@@ -662,77 +663,84 @@ func (engine *Engine) MustCols(columns ...string) *Session {
// it will use parameters's columns
func (engine *Engine) UseBool(columns ...string) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.UseBool(columns...)
}
// Omit only not use the parameters as select or update columns
func (engine *Engine) Omit(columns ...string) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Omit(columns...)
}
// Nullable set null when column is zero-value and nullable for update
func (engine *Engine) Nullable(columns ...string) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Nullable(columns...)
}
// In will generate "column IN (?, ?)"
func (engine *Engine) In(column string, args ...interface{}) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.In(column, args...)
}
+// NotIn will generate "column NOT IN (?, ?)"
+func (engine *Engine) NotIn(column string, args ...interface{}) *Session {
+ session := engine.NewSession()
+ session.isAutoClose = true
+ return session.NotIn(column, args...)
+}
+
// Incr provides a update string like "column = column + ?"
func (engine *Engine) Incr(column string, arg ...interface{}) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Incr(column, arg...)
}
// Decr provides a update string like "column = column - ?"
func (engine *Engine) Decr(column string, arg ...interface{}) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Decr(column, arg...)
}
// SetExpr provides a update string like "column = {expression}"
func (engine *Engine) SetExpr(column string, expression string) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.SetExpr(column, expression)
}
// Table temporarily change the Get, Find, Update's table
func (engine *Engine) Table(tableNameOrBean interface{}) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Table(tableNameOrBean)
}
// Alias set the table alias
func (engine *Engine) Alias(alias string) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Alias(alias)
}
// Limit will generate "LIMIT start, limit"
func (engine *Engine) Limit(limit int, start ...int) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Limit(limit, start...)
}
// Desc will generate "ORDER BY column1 DESC, column2 DESC"
func (engine *Engine) Desc(colNames ...string) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Desc(colNames...)
}
@@ -744,39 +752,53 @@ func (engine *Engine) Desc(colNames ...string) *Session {
//
func (engine *Engine) Asc(colNames ...string) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Asc(colNames...)
}
// OrderBy will generate "ORDER BY order"
func (engine *Engine) OrderBy(order string) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.OrderBy(order)
}
+// Prepare enables prepare statement
+func (engine *Engine) Prepare() *Session {
+ session := engine.NewSession()
+ session.isAutoClose = true
+ return session.Prepare()
+}
+
// Join the join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN
func (engine *Engine) Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Join(joinOperator, tablename, condition, args...)
}
// GroupBy generate group by statement
func (engine *Engine) GroupBy(keys string) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.GroupBy(keys)
}
// Having generate having statement
func (engine *Engine) Having(conditions string) *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Having(conditions)
}
-func (engine *Engine) autoMapType(v reflect.Value) *core.Table {
+// UnMapType removes the datbase mapper of a type
+func (engine *Engine) UnMapType(t reflect.Type) {
+ engine.mutex.Lock()
+ defer engine.mutex.Unlock()
+ delete(engine.Tables, t)
+}
+
+func (engine *Engine) autoMapType(v reflect.Value) (*core.Table, error) {
t := v.Type()
engine.mutex.Lock()
defer engine.mutex.Unlock()
@@ -785,24 +807,23 @@ func (engine *Engine) autoMapType(v reflect.Value) *core.Table {
var err error
table, err = engine.mapType(v)
if err != nil {
- engine.logger.Error(err)
- } else {
- engine.Tables[t] = table
- if engine.Cacher != nil {
- if v.CanAddr() {
- engine.GobRegister(v.Addr().Interface())
- } else {
- engine.GobRegister(v.Interface())
- }
+ return nil, err
+ }
+
+ engine.Tables[t] = table
+ if engine.Cacher != nil {
+ if v.CanAddr() {
+ engine.GobRegister(v.Addr().Interface())
+ } else {
+ engine.GobRegister(v.Interface())
}
}
}
- return table
+ return table, nil
}
// GobRegister register one struct to gob for cache use
func (engine *Engine) GobRegister(v interface{}) *Engine {
- //fmt.Printf("Type: %[1]T => Data: %[1]#v\n", v)
gob.Register(v)
return engine
}
@@ -813,10 +834,19 @@ type Table struct {
Name string
}
+// IsValid if table is valid
+func (t *Table) IsValid() bool {
+ return t.Table != nil && len(t.Name) > 0
+}
+
// TableInfo get table info according to bean's content
func (engine *Engine) TableInfo(bean interface{}) *Table {
v := rValue(bean)
- return &Table{engine.autoMapType(v), engine.tbName(v)}
+ tb, err := engine.autoMapType(v)
+ if err != nil {
+ engine.logger.Error(err)
+ }
+ return &Table{tb, engine.tbName(v)}
}
func addIndex(indexName string, table *core.Table, col *core.Column, indexType int) {
@@ -911,6 +941,7 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) {
k := strings.ToUpper(key)
ctx.tagName = k
+ ctx.params = []string{}
pStart := strings.Index(k, "(")
if pStart == 0 {
@@ -918,18 +949,18 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) {
}
if pStart > -1 {
if !strings.HasSuffix(k, ")") {
- return nil, errors.New("cannot match ) charactor")
+ return nil, fmt.Errorf("field %s tag %s cannot match ) charactor", col.FieldName, key)
}
ctx.tagName = k[:pStart]
- ctx.params = strings.Split(k[pStart+1:len(k)-1], ",")
+ ctx.params = strings.Split(key[pStart+1:len(k)-1], ",")
}
if j > 0 {
ctx.preTag = strings.ToUpper(tags[j-1])
}
if j < len(tags)-1 {
- ctx.nextTag = strings.ToUpper(tags[j+1])
+ ctx.nextTag = tags[j+1]
} else {
ctx.nextTag = ""
}
@@ -993,6 +1024,10 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) {
col = core.NewColumn(engine.ColumnMapper.Obj2Table(t.Field(i).Name),
t.Field(i).Name, sqlType, sqlType.DefaultLength,
sqlType.DefaultLength2, true)
+
+ if fieldType.Kind() == reflect.Int64 && (strings.ToUpper(col.FieldName) == "ID" || strings.HasSuffix(strings.ToUpper(col.FieldName), ".ID")) {
+ idFieldColName = col.Name
+ }
}
if col.IsAutoIncrement {
col.Nullable = false
@@ -1000,9 +1035,6 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) {
table.AddColumn(col)
- if fieldType.Kind() == reflect.Int64 && (strings.ToUpper(col.FieldName) == "ID" || strings.HasSuffix(strings.ToUpper(col.FieldName), ".ID")) {
- idFieldColName = col.Name
- }
} // end for
if idFieldColName != "" && len(table.PrimaryKeys) == 0 {
@@ -1066,21 +1098,54 @@ func (engine *Engine) IdOfV(rv reflect.Value) core.PK {
// IDOfV get id from one value of struct
func (engine *Engine) IDOfV(rv reflect.Value) core.PK {
+ pk, err := engine.idOfV(rv)
+ if err != nil {
+ engine.logger.Error(err)
+ return nil
+ }
+ return pk
+}
+
+func (engine *Engine) idOfV(rv reflect.Value) (core.PK, error) {
v := reflect.Indirect(rv)
- table := engine.autoMapType(v)
+ table, err := engine.autoMapType(v)
+ if err != nil {
+ return nil, err
+ }
+
pk := make([]interface{}, len(table.PrimaryKeys))
for i, col := range table.PKColumns() {
+ var err error
pkField := v.FieldByName(col.FieldName)
switch pkField.Kind() {
case reflect.String:
- pk[i] = pkField.String()
+ pk[i], err = engine.idTypeAssertion(col, pkField.String())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- pk[i] = pkField.Int()
+ pk[i], err = engine.idTypeAssertion(col, strconv.FormatInt(pkField.Int(), 10))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- pk[i] = pkField.Uint()
+ // id of uint will be converted to int64
+ pk[i], err = engine.idTypeAssertion(col, strconv.FormatUint(pkField.Uint(), 10))
+ }
+
+ if err != nil {
+ return nil, err
}
}
- return core.PK(pk)
+ return core.PK(pk), nil
+}
+
+func (engine *Engine) idTypeAssertion(col *core.Column, sid string) (interface{}, error) {
+ if col.SQLType.IsNumeric() {
+ n, err := strconv.ParseInt(sid, 10, 64)
+ if err != nil {
+ return nil, err
+ }
+ return n, nil
+ } else if col.SQLType.IsText() {
+ return sid, nil
+ } else {
+ return nil, errors.New("not supported")
+ }
}
// CreateIndexes create indexes
@@ -1101,13 +1166,6 @@ func (engine *Engine) getCacher2(table *core.Table) core.Cacher {
return table.Cacher
}
-func (engine *Engine) getCacher(v reflect.Value) core.Cacher {
- if table := engine.autoMapType(v); table != nil {
- return table.Cacher
- }
- return engine.Cacher
-}
-
// ClearCacheBean if enabled cache, clear the cache bean
func (engine *Engine) ClearCacheBean(bean interface{}, id string) error {
v := rValue(bean)
@@ -1116,7 +1174,10 @@ func (engine *Engine) ClearCacheBean(bean interface{}, id string) error {
return errors.New("error params")
}
tableName := engine.tbName(v)
- table := engine.autoMapType(v)
+ table, err := engine.autoMapType(v)
+ if err != nil {
+ return err
+ }
cacher := table.Cacher
if cacher == nil {
cacher = engine.Cacher
@@ -1137,7 +1198,11 @@ func (engine *Engine) ClearCache(beans ...interface{}) error {
return errors.New("error params")
}
tableName := engine.tbName(v)
- table := engine.autoMapType(v)
+ table, err := engine.autoMapType(v)
+ if err != nil {
+ return err
+ }
+
cacher := table.Cacher
if cacher == nil {
cacher = engine.Cacher
@@ -1154,19 +1219,23 @@ func (engine *Engine) ClearCache(beans ...interface{}) error {
// table, column, index, unique. but will not delete or change anything.
// If you change some field, you should change the database manually.
func (engine *Engine) Sync(beans ...interface{}) error {
+ session := engine.NewSession()
+ defer session.Close()
+
for _, bean := range beans {
v := rValue(bean)
tableName := engine.tbName(v)
- table := engine.autoMapType(v)
+ table, err := engine.autoMapType(v)
+ if err != nil {
+ return err
+ }
- s := engine.NewSession()
- defer s.Close()
- isExist, err := s.Table(bean).isTableExist(tableName)
+ isExist, err := session.Table(bean).isTableExist(tableName)
if err != nil {
return err
}
if !isExist {
- err = engine.CreateTables(bean)
+ err = session.createTable(bean)
if err != nil {
return err
}
@@ -1177,11 +1246,11 @@ func (engine *Engine) Sync(beans ...interface{}) error {
}*/
var isEmpty bool
if isEmpty {
- err = engine.DropTables(bean)
+ err = session.dropTable(bean)
if err != nil {
return err
}
- err = engine.CreateTables(bean)
+ err = session.createTable(bean)
if err != nil {
return err
}
@@ -1192,9 +1261,9 @@ func (engine *Engine) Sync(beans ...interface{}) error {
return err
}
if !isExist {
- session := engine.NewSession()
- session.Statement.setRefValue(v)
- defer session.Close()
+ if err := session.statement.setRefValue(v); err != nil {
+ return err
+ }
err = session.addColumn(col.Name)
if err != nil {
return err
@@ -1203,19 +1272,19 @@ func (engine *Engine) Sync(beans ...interface{}) error {
}
for name, index := range table.Indexes {
- session := engine.NewSession()
- session.Statement.setRefValue(v)
- defer session.Close()
+ if err := session.statement.setRefValue(v); err != nil {
+ return err
+ }
if index.Type == core.UniqueType {
- //isExist, err := session.isIndexExist(table.Name, name, true)
isExist, err := session.isIndexExist2(tableName, index.Cols, true)
if err != nil {
return err
}
if !isExist {
- session := engine.NewSession()
- session.Statement.setRefValue(v)
- defer session.Close()
+ if err := session.statement.setRefValue(v); err != nil {
+ return err
+ }
+
err = session.addUnique(tableName, name)
if err != nil {
return err
@@ -1227,9 +1296,10 @@ func (engine *Engine) Sync(beans ...interface{}) error {
return err
}
if !isExist {
- session := engine.NewSession()
- session.Statement.setRefValue(v)
- defer session.Close()
+ if err := session.statement.setRefValue(v); err != nil {
+ return err
+ }
+
err = session.addIndex(tableName, name)
if err != nil {
return err
@@ -1251,35 +1321,6 @@ func (engine *Engine) Sync2(beans ...interface{}) error {
return s.Sync2(beans...)
}
-func (engine *Engine) unMap(beans ...interface{}) (e error) {
- engine.mutex.Lock()
- defer engine.mutex.Unlock()
- for _, bean := range beans {
- t := rType(bean)
- if _, ok := engine.Tables[t]; ok {
- delete(engine.Tables, t)
- }
- }
- return
-}
-
-// Drop all mapped table
-func (engine *Engine) dropAll() error {
- session := engine.NewSession()
- defer session.Close()
-
- err := session.Begin()
- if err != nil {
- return err
- }
- err = session.dropAll()
- if err != nil {
- session.Rollback()
- return err
- }
- return session.Commit()
-}
-
// CreateTables create tabls according bean
func (engine *Engine) CreateTables(beans ...interface{}) error {
session := engine.NewSession()
@@ -1291,7 +1332,7 @@ func (engine *Engine) CreateTables(beans ...interface{}) error {
}
for _, bean := range beans {
- err = session.CreateTable(bean)
+ err = session.createTable(bean)
if err != nil {
session.Rollback()
return err
@@ -1311,7 +1352,7 @@ func (engine *Engine) DropTables(beans ...interface{}) error {
}
for _, bean := range beans {
- err = session.DropTable(bean)
+ err = session.dropTable(bean)
if err != nil {
session.Rollback()
return err
@@ -1320,10 +1361,11 @@ func (engine *Engine) DropTables(beans ...interface{}) error {
return session.Commit()
}
-func (engine *Engine) createAll() error {
+// DropIndexes drop indexes of a table
+func (engine *Engine) DropIndexes(bean interface{}) error {
session := engine.NewSession()
defer session.Close()
- return session.createAll()
+ return session.DropIndexes(bean)
}
// Exec raw sql
@@ -1334,10 +1376,24 @@ func (engine *Engine) Exec(sql string, args ...interface{}) (sql.Result, error)
}
// Query a raw sql and return records as []map[string][]byte
-func (engine *Engine) Query(sql string, paramStr ...interface{}) (resultsSlice []map[string][]byte, err error) {
+func (engine *Engine) Query(sqlorArgs ...interface{}) (resultsSlice []map[string][]byte, err error) {
session := engine.NewSession()
defer session.Close()
- return session.Query(sql, paramStr...)
+ return session.Query(sqlorArgs...)
+}
+
+// QueryString runs a raw sql and return records as []map[string]string
+func (engine *Engine) QueryString(sqlorArgs ...interface{}) ([]map[string]string, error) {
+ session := engine.NewSession()
+ defer session.Close()
+ return session.QueryString(sqlorArgs...)
+}
+
+// QueryInterface runs a raw sql and return records as []map[string]interface{}
+func (engine *Engine) QueryInterface(sqlorArgs ...interface{}) ([]map[string]interface{}, error) {
+ session := engine.NewSession()
+ defer session.Close()
+ return session.QueryInterface(sqlorArgs...)
}
// Insert one or more records
@@ -1381,6 +1437,13 @@ func (engine *Engine) Get(bean interface{}) (bool, error) {
return session.Get(bean)
}
+// Exist returns true if the record exist otherwise return false
+func (engine *Engine) Exist(bean ...interface{}) (bool, error) {
+ session := engine.NewSession()
+ defer session.Close()
+ return session.Exist(bean...)
+}
+
// Find retrieve records from table, condiBeans's non-empty fields
// are conditions. beans could be []Struct, []*Struct, map[int64]Struct
// map[int64]*Struct
@@ -1406,10 +1469,10 @@ func (engine *Engine) Rows(bean interface{}) (*Rows, error) {
}
// Count counts the records. bean's non-empty fields are conditions.
-func (engine *Engine) Count(bean interface{}) (int64, error) {
+func (engine *Engine) Count(bean ...interface{}) (int64, error) {
session := engine.NewSession()
defer session.Close()
- return session.Count(bean)
+ return session.Count(bean...)
}
// Sum sum the records by some column. bean's non-empty fields are conditions.
@@ -1419,6 +1482,13 @@ func (engine *Engine) Sum(bean interface{}, colName string) (float64, error) {
return session.Sum(bean, colName)
}
+// SumInt sum the records by some column. bean's non-empty fields are conditions.
+func (engine *Engine) SumInt(bean interface{}, colName string) (int64, error) {
+ session := engine.NewSession()
+ defer session.Close()
+ return session.SumInt(bean, colName)
+}
+
// Sums sum the records by some columns. bean's non-empty fields are conditions.
func (engine *Engine) Sums(bean interface{}, colNames ...string) ([]float64, error) {
session := engine.NewSession()
@@ -1474,7 +1544,6 @@ func (engine *Engine) Import(r io.Reader) ([]sql.Result, error) {
results = append(results, result)
if err != nil {
return nil, err
- //lastError = err
}
}
}
@@ -1482,49 +1551,32 @@ func (engine *Engine) Import(r io.Reader) ([]sql.Result, error) {
return results, lastError
}
-// TZTime change one time to xorm time location
-func (engine *Engine) TZTime(t time.Time) time.Time {
- if !t.IsZero() { // if time is not initialized it's not suitable for Time.In()
- return t.In(engine.TZLocation)
+// nowTime return current time
+func (engine *Engine) nowTime(col *core.Column) (interface{}, time.Time) {
+ t := time.Now()
+ var tz = engine.DatabaseTZ
+ if !col.DisableTimeZone && col.TimeZone != nil {
+ tz = col.TimeZone
}
- return t
-}
-
-// NowTime return current time
-func (engine *Engine) NowTime(sqlTypeName string) interface{} {
- t := time.Now()
- return engine.FormatTime(sqlTypeName, t)
-}
-
-// NowTime2 return current time
-func (engine *Engine) NowTime2(sqlTypeName string) (interface{}, time.Time) {
- t := time.Now()
- return engine.FormatTime(sqlTypeName, t), t
-}
-
-// FormatTime format time
-func (engine *Engine) FormatTime(sqlTypeName string, t time.Time) (v interface{}) {
- return engine.formatTime(engine.TZLocation, sqlTypeName, t)
+ return engine.formatTime(col.SQLType.Name, t.In(tz)), t.In(engine.TZLocation)
}
func (engine *Engine) formatColTime(col *core.Column, t time.Time) (v interface{}) {
- if col.DisableTimeZone {
- return engine.formatTime(nil, col.SQLType.Name, t)
- } else if col.TimeZone != nil {
- return engine.formatTime(col.TimeZone, col.SQLType.Name, t)
+ if t.IsZero() {
+ if col.Nullable {
+ return nil
+ }
+ return ""
}
- return engine.formatTime(engine.TZLocation, col.SQLType.Name, t)
+
+ if col.TimeZone != nil {
+ return engine.formatTime(col.SQLType.Name, t.In(col.TimeZone))
+ }
+ return engine.formatTime(col.SQLType.Name, t.In(engine.DatabaseTZ))
}
-func (engine *Engine) formatTime(tz *time.Location, sqlTypeName string, t time.Time) (v interface{}) {
- if engine.dialect.DBType() == core.ORACLE {
- return t
- }
- if tz != nil {
- t = t.In(tz)
- } else {
- t = engine.TZTime(t)
- }
+// formatTime format time as column type
+func (engine *Engine) formatTime(sqlTypeName string, t time.Time) (v interface{}) {
switch sqlTypeName {
case core.Time:
s := t.Format("2006-01-02 15:04:05") //time.RFC3339
@@ -1532,18 +1584,10 @@ func (engine *Engine) formatTime(tz *time.Location, sqlTypeName string, t time.T
case core.Date:
v = t.Format("2006-01-02")
case core.DateTime, core.TimeStamp:
- if engine.dialect.DBType() == "ql" {
- v = t
- } else if engine.dialect.DBType() == "sqlite3" {
- v = t.UTC().Format("2006-01-02 15:04:05")
- } else {
- v = t.Format("2006-01-02 15:04:05")
- }
+ v = t.Format("2006-01-02 15:04:05")
case core.TimeStampz:
if engine.dialect.DBType() == core.MSSQL {
v = t.Format("2006-01-02T15:04:05.9999999Z07:00")
- } else if engine.DriverName() == "mssql" {
- v = t
} else {
v = t.Format(time.RFC3339Nano)
}
@@ -1555,9 +1599,39 @@ func (engine *Engine) formatTime(tz *time.Location, sqlTypeName string, t time.T
return
}
+// GetColumnMapper returns the column name mapper
+func (engine *Engine) GetColumnMapper() core.IMapper {
+ return engine.ColumnMapper
+}
+
+// GetTableMapper returns the table name mapper
+func (engine *Engine) GetTableMapper() core.IMapper {
+ return engine.TableMapper
+}
+
+// GetTZLocation returns time zone of the application
+func (engine *Engine) GetTZLocation() *time.Location {
+ return engine.TZLocation
+}
+
+// SetTZLocation sets time zone of the application
+func (engine *Engine) SetTZLocation(tz *time.Location) {
+ engine.TZLocation = tz
+}
+
+// GetTZDatabase returns time zone of the database
+func (engine *Engine) GetTZDatabase() *time.Location {
+ return engine.DatabaseTZ
+}
+
+// SetTZDatabase sets time zone of the database
+func (engine *Engine) SetTZDatabase(tz *time.Location) {
+ engine.DatabaseTZ = tz
+}
+
// Unscoped always disable struct tag "deleted"
func (engine *Engine) Unscoped() *Session {
session := engine.NewSession()
- session.IsAutoClose = true
+ session.isAutoClose = true
return session.Unscoped()
}
diff --git a/vendor/github.com/go-xorm/xorm/engine_cond.go b/vendor/github.com/go-xorm/xorm/engine_cond.go
new file mode 100644
index 00000000000..6c8e3879cee
--- /dev/null
+++ b/vendor/github.com/go-xorm/xorm/engine_cond.go
@@ -0,0 +1,230 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+ "database/sql/driver"
+ "encoding/json"
+ "fmt"
+ "reflect"
+ "time"
+
+ "github.com/go-xorm/builder"
+ "github.com/go-xorm/core"
+)
+
+func (engine *Engine) buildConds(table *core.Table, bean interface{},
+ includeVersion bool, includeUpdated bool, includeNil bool,
+ includeAutoIncr bool, allUseBool bool, useAllCols bool, unscoped bool,
+ mustColumnMap map[string]bool, tableName, aliasName string, addedTableName bool) (builder.Cond, error) {
+ var conds []builder.Cond
+ for _, col := range table.Columns() {
+ if !includeVersion && col.IsVersion {
+ continue
+ }
+ if !includeUpdated && col.IsUpdated {
+ continue
+ }
+ if !includeAutoIncr && col.IsAutoIncrement {
+ continue
+ }
+
+ if engine.dialect.DBType() == core.MSSQL && (col.SQLType.Name == core.Text || col.SQLType.IsBlob() || col.SQLType.Name == core.TimeStampz) {
+ continue
+ }
+ if col.SQLType.IsJson() {
+ continue
+ }
+
+ var colName string
+ if addedTableName {
+ var nm = tableName
+ if len(aliasName) > 0 {
+ nm = aliasName
+ }
+ colName = engine.Quote(nm) + "." + engine.Quote(col.Name)
+ } else {
+ colName = engine.Quote(col.Name)
+ }
+
+ fieldValuePtr, err := col.ValueOf(bean)
+ if err != nil {
+ engine.logger.Error(err)
+ continue
+ }
+
+ if col.IsDeleted && !unscoped { // tag "deleted" is enabled
+ conds = append(conds, engine.CondDeleted(colName))
+ }
+
+ fieldValue := *fieldValuePtr
+ if fieldValue.Interface() == nil {
+ continue
+ }
+
+ fieldType := reflect.TypeOf(fieldValue.Interface())
+ requiredField := useAllCols
+
+ if b, ok := getFlagForColumn(mustColumnMap, col); ok {
+ if b {
+ requiredField = true
+ } else {
+ continue
+ }
+ }
+
+ if fieldType.Kind() == reflect.Ptr {
+ if fieldValue.IsNil() {
+ if includeNil {
+ conds = append(conds, builder.Eq{colName: nil})
+ }
+ continue
+ } else if !fieldValue.IsValid() {
+ continue
+ } else {
+ // dereference ptr type to instance type
+ fieldValue = fieldValue.Elem()
+ fieldType = reflect.TypeOf(fieldValue.Interface())
+ requiredField = true
+ }
+ }
+
+ var val interface{}
+ switch fieldType.Kind() {
+ case reflect.Bool:
+ if allUseBool || requiredField {
+ val = fieldValue.Interface()
+ } else {
+ // if a bool in a struct, it will not be as a condition because it default is false,
+ // please use Where() instead
+ continue
+ }
+ case reflect.String:
+ if !requiredField && fieldValue.String() == "" {
+ continue
+ }
+ // for MyString, should convert to string or panic
+ if fieldType.String() != reflect.String.String() {
+ val = fieldValue.String()
+ } else {
+ val = fieldValue.Interface()
+ }
+ case reflect.Int8, reflect.Int16, reflect.Int, reflect.Int32, reflect.Int64:
+ if !requiredField && fieldValue.Int() == 0 {
+ continue
+ }
+ val = fieldValue.Interface()
+ case reflect.Float32, reflect.Float64:
+ if !requiredField && fieldValue.Float() == 0.0 {
+ continue
+ }
+ val = fieldValue.Interface()
+ case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64:
+ if !requiredField && fieldValue.Uint() == 0 {
+ continue
+ }
+ t := int64(fieldValue.Uint())
+ val = reflect.ValueOf(&t).Interface()
+ case reflect.Struct:
+ if fieldType.ConvertibleTo(core.TimeType) {
+ t := fieldValue.Convert(core.TimeType).Interface().(time.Time)
+ if !requiredField && (t.IsZero() || !fieldValue.IsValid()) {
+ continue
+ }
+ val = engine.formatColTime(col, t)
+ } else if _, ok := reflect.New(fieldType).Interface().(core.Conversion); ok {
+ continue
+ } else if valNul, ok := fieldValue.Interface().(driver.Valuer); ok {
+ val, _ = valNul.Value()
+ if val == nil {
+ continue
+ }
+ } else {
+ if col.SQLType.IsJson() {
+ if col.SQLType.IsText() {
+ bytes, err := json.Marshal(fieldValue.Interface())
+ if err != nil {
+ engine.logger.Error(err)
+ continue
+ }
+ val = string(bytes)
+ } else if col.SQLType.IsBlob() {
+ var bytes []byte
+ var err error
+ bytes, err = json.Marshal(fieldValue.Interface())
+ if err != nil {
+ engine.logger.Error(err)
+ continue
+ }
+ val = bytes
+ }
+ } else {
+ engine.autoMapType(fieldValue)
+ if table, ok := engine.Tables[fieldValue.Type()]; ok {
+ if len(table.PrimaryKeys) == 1 {
+ pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumns()[0].FieldName)
+ // fix non-int pk issues
+ //if pkField.Int() != 0 {
+ if pkField.IsValid() && !isZero(pkField.Interface()) {
+ val = pkField.Interface()
+ } else {
+ continue
+ }
+ } else {
+ //TODO: how to handler?
+ return nil, fmt.Errorf("not supported %v as %v", fieldValue.Interface(), table.PrimaryKeys)
+ }
+ } else {
+ val = fieldValue.Interface()
+ }
+ }
+ }
+ case reflect.Array:
+ continue
+ case reflect.Slice, reflect.Map:
+ if fieldValue == reflect.Zero(fieldType) {
+ continue
+ }
+ if fieldValue.IsNil() || !fieldValue.IsValid() || fieldValue.Len() == 0 {
+ continue
+ }
+
+ if col.SQLType.IsText() {
+ bytes, err := json.Marshal(fieldValue.Interface())
+ if err != nil {
+ engine.logger.Error(err)
+ continue
+ }
+ val = string(bytes)
+ } else if col.SQLType.IsBlob() {
+ var bytes []byte
+ var err error
+ if (fieldType.Kind() == reflect.Array || fieldType.Kind() == reflect.Slice) &&
+ fieldType.Elem().Kind() == reflect.Uint8 {
+ if fieldValue.Len() > 0 {
+ val = fieldValue.Bytes()
+ } else {
+ continue
+ }
+ } else {
+ bytes, err = json.Marshal(fieldValue.Interface())
+ if err != nil {
+ engine.logger.Error(err)
+ continue
+ }
+ val = bytes
+ }
+ } else {
+ continue
+ }
+ default:
+ val = fieldValue.Interface()
+ }
+
+ conds = append(conds, builder.Eq{colName: val})
+ }
+
+ return builder.And(conds...), nil
+}
diff --git a/vendor/github.com/go-xorm/xorm/engine_group.go b/vendor/github.com/go-xorm/xorm/engine_group.go
new file mode 100644
index 00000000000..1de425f372c
--- /dev/null
+++ b/vendor/github.com/go-xorm/xorm/engine_group.go
@@ -0,0 +1,194 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+ "github.com/go-xorm/core"
+)
+
+// EngineGroup defines an engine group
+type EngineGroup struct {
+ *Engine
+ slaves []*Engine
+ policy GroupPolicy
+}
+
+// NewEngineGroup creates a new engine group
+func NewEngineGroup(args1 interface{}, args2 interface{}, policies ...GroupPolicy) (*EngineGroup, error) {
+ var eg EngineGroup
+ if len(policies) > 0 {
+ eg.policy = policies[0]
+ } else {
+ eg.policy = RoundRobinPolicy()
+ }
+
+ driverName, ok1 := args1.(string)
+ conns, ok2 := args2.([]string)
+ if ok1 && ok2 {
+ engines := make([]*Engine, len(conns))
+ for i, conn := range conns {
+ engine, err := NewEngine(driverName, conn)
+ if err != nil {
+ return nil, err
+ }
+ engine.engineGroup = &eg
+ engines[i] = engine
+ }
+
+ eg.Engine = engines[0]
+ eg.slaves = engines[1:]
+ return &eg, nil
+ }
+
+ master, ok3 := args1.(*Engine)
+ slaves, ok4 := args2.([]*Engine)
+ if ok3 && ok4 {
+ master.engineGroup = &eg
+ for i := 0; i < len(slaves); i++ {
+ slaves[i].engineGroup = &eg
+ }
+ eg.Engine = master
+ eg.slaves = slaves
+ return &eg, nil
+ }
+ return nil, ErrParamsType
+}
+
+// Close the engine
+func (eg *EngineGroup) Close() error {
+ err := eg.Engine.Close()
+ if err != nil {
+ return err
+ }
+
+ for i := 0; i < len(eg.slaves); i++ {
+ err := eg.slaves[i].Close()
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// Master returns the master engine
+func (eg *EngineGroup) Master() *Engine {
+ return eg.Engine
+}
+
+// Ping tests if database is alive
+func (eg *EngineGroup) Ping() error {
+ if err := eg.Engine.Ping(); err != nil {
+ return err
+ }
+
+ for _, slave := range eg.slaves {
+ if err := slave.Ping(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// SetColumnMapper set the column name mapping rule
+func (eg *EngineGroup) SetColumnMapper(mapper core.IMapper) {
+ eg.Engine.ColumnMapper = mapper
+ for i := 0; i < len(eg.slaves); i++ {
+ eg.slaves[i].ColumnMapper = mapper
+ }
+}
+
+// SetDefaultCacher set the default cacher
+func (eg *EngineGroup) SetDefaultCacher(cacher core.Cacher) {
+ eg.Engine.SetDefaultCacher(cacher)
+ for i := 0; i < len(eg.slaves); i++ {
+ eg.slaves[i].SetDefaultCacher(cacher)
+ }
+}
+
+// SetLogger set the new logger
+func (eg *EngineGroup) SetLogger(logger core.ILogger) {
+ eg.Engine.SetLogger(logger)
+ for i := 0; i < len(eg.slaves); i++ {
+ eg.slaves[i].SetLogger(logger)
+ }
+}
+
+// SetLogLevel sets the logger level
+func (eg *EngineGroup) SetLogLevel(level core.LogLevel) {
+ eg.Engine.SetLogLevel(level)
+ for i := 0; i < len(eg.slaves); i++ {
+ eg.slaves[i].SetLogLevel(level)
+ }
+}
+
+// SetMapper set the name mapping rules
+func (eg *EngineGroup) SetMapper(mapper core.IMapper) {
+ eg.Engine.SetMapper(mapper)
+ for i := 0; i < len(eg.slaves); i++ {
+ eg.slaves[i].SetMapper(mapper)
+ }
+}
+
+// SetMaxIdleConns set the max idle connections on pool, default is 2
+func (eg *EngineGroup) SetMaxIdleConns(conns int) {
+ eg.Engine.db.SetMaxIdleConns(conns)
+ for i := 0; i < len(eg.slaves); i++ {
+ eg.slaves[i].db.SetMaxIdleConns(conns)
+ }
+}
+
+// SetMaxOpenConns is only available for go 1.2+
+func (eg *EngineGroup) SetMaxOpenConns(conns int) {
+ eg.Engine.db.SetMaxOpenConns(conns)
+ for i := 0; i < len(eg.slaves); i++ {
+ eg.slaves[i].db.SetMaxOpenConns(conns)
+ }
+}
+
+// SetPolicy set the group policy
+func (eg *EngineGroup) SetPolicy(policy GroupPolicy) *EngineGroup {
+ eg.policy = policy
+ return eg
+}
+
+// SetTableMapper set the table name mapping rule
+func (eg *EngineGroup) SetTableMapper(mapper core.IMapper) {
+ eg.Engine.TableMapper = mapper
+ for i := 0; i < len(eg.slaves); i++ {
+ eg.slaves[i].TableMapper = mapper
+ }
+}
+
+// ShowExecTime show SQL statement and execute time or not on logger if log level is great than INFO
+func (eg *EngineGroup) ShowExecTime(show ...bool) {
+ eg.Engine.ShowExecTime(show...)
+ for i := 0; i < len(eg.slaves); i++ {
+ eg.slaves[i].ShowExecTime(show...)
+ }
+}
+
+// ShowSQL show SQL statement or not on logger if log level is great than INFO
+func (eg *EngineGroup) ShowSQL(show ...bool) {
+ eg.Engine.ShowSQL(show...)
+ for i := 0; i < len(eg.slaves); i++ {
+ eg.slaves[i].ShowSQL(show...)
+ }
+}
+
+// Slave returns one of the physical databases which is a slave according the policy
+func (eg *EngineGroup) Slave() *Engine {
+ switch len(eg.slaves) {
+ case 0:
+ return eg.Engine
+ case 1:
+ return eg.slaves[0]
+ }
+ return eg.policy.Slave(eg)
+}
+
+// Slaves returns all the slaves
+func (eg *EngineGroup) Slaves() []*Engine {
+ return eg.slaves
+}
diff --git a/vendor/github.com/go-xorm/xorm/engine_group_policy.go b/vendor/github.com/go-xorm/xorm/engine_group_policy.go
new file mode 100644
index 00000000000..5b56e8995fd
--- /dev/null
+++ b/vendor/github.com/go-xorm/xorm/engine_group_policy.go
@@ -0,0 +1,116 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+ "math/rand"
+ "sync"
+ "time"
+)
+
+// GroupPolicy is be used by chosing the current slave from slaves
+type GroupPolicy interface {
+ Slave(*EngineGroup) *Engine
+}
+
+// GroupPolicyHandler should be used when a function is a GroupPolicy
+type GroupPolicyHandler func(*EngineGroup) *Engine
+
+// Slave implements the chosen of slaves
+func (h GroupPolicyHandler) Slave(eg *EngineGroup) *Engine {
+ return h(eg)
+}
+
+// RandomPolicy implmentes randomly chose the slave of slaves
+func RandomPolicy() GroupPolicyHandler {
+ var r = rand.New(rand.NewSource(time.Now().UnixNano()))
+ return func(g *EngineGroup) *Engine {
+ return g.Slaves()[r.Intn(len(g.Slaves()))]
+ }
+}
+
+// WeightRandomPolicy implmentes randomly chose the slave of slaves
+func WeightRandomPolicy(weights []int) GroupPolicyHandler {
+ var rands = make([]int, 0, len(weights))
+ for i := 0; i < len(weights); i++ {
+ for n := 0; n < weights[i]; n++ {
+ rands = append(rands, i)
+ }
+ }
+ var r = rand.New(rand.NewSource(time.Now().UnixNano()))
+
+ return func(g *EngineGroup) *Engine {
+ var slaves = g.Slaves()
+ idx := rands[r.Intn(len(rands))]
+ if idx >= len(slaves) {
+ idx = len(slaves) - 1
+ }
+ return slaves[idx]
+ }
+}
+
+func RoundRobinPolicy() GroupPolicyHandler {
+ var pos = -1
+ var lock sync.Mutex
+ return func(g *EngineGroup) *Engine {
+ var slaves = g.Slaves()
+
+ lock.Lock()
+ defer lock.Unlock()
+ pos++
+ if pos >= len(slaves) {
+ pos = 0
+ }
+
+ return slaves[pos]
+ }
+}
+
+func WeightRoundRobinPolicy(weights []int) GroupPolicyHandler {
+ var rands = make([]int, 0, len(weights))
+ for i := 0; i < len(weights); i++ {
+ for n := 0; n < weights[i]; n++ {
+ rands = append(rands, i)
+ }
+ }
+ var pos = -1
+ var lock sync.Mutex
+
+ return func(g *EngineGroup) *Engine {
+ var slaves = g.Slaves()
+ lock.Lock()
+ defer lock.Unlock()
+ pos++
+ if pos >= len(rands) {
+ pos = 0
+ }
+
+ idx := rands[pos]
+ if idx >= len(slaves) {
+ idx = len(slaves) - 1
+ }
+ return slaves[idx]
+ }
+}
+
+// LeastConnPolicy implements GroupPolicy, every time will get the least connections slave
+func LeastConnPolicy() GroupPolicyHandler {
+ return func(g *EngineGroup) *Engine {
+ var slaves = g.Slaves()
+ connections := 0
+ idx := 0
+ for i := 0; i < len(slaves); i++ {
+ openConnections := slaves[i].DB().Stats().OpenConnections
+ if i == 0 {
+ connections = openConnections
+ idx = i
+ } else if openConnections <= connections {
+ connections = openConnections
+ idx = i
+ }
+ }
+ return slaves[idx]
+ }
+}
diff --git a/vendor/github.com/go-xorm/xorm/engine_maxlife.go b/vendor/github.com/go-xorm/xorm/engine_maxlife.go
new file mode 100644
index 00000000000..22666c5f44c
--- /dev/null
+++ b/vendor/github.com/go-xorm/xorm/engine_maxlife.go
@@ -0,0 +1,22 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.6
+
+package xorm
+
+import "time"
+
+// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
+func (engine *Engine) SetConnMaxLifetime(d time.Duration) {
+ engine.db.SetConnMaxLifetime(d)
+}
+
+// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
+func (eg *EngineGroup) SetConnMaxLifetime(d time.Duration) {
+ eg.Engine.SetConnMaxLifetime(d)
+ for i := 0; i < len(eg.slaves); i++ {
+ eg.slaves[i].SetConnMaxLifetime(d)
+ }
+}
diff --git a/vendor/github.com/go-xorm/xorm/error.go b/vendor/github.com/go-xorm/xorm/error.go
index 2a334f47c23..cfeefc31e8e 100644
--- a/vendor/github.com/go-xorm/xorm/error.go
+++ b/vendor/github.com/go-xorm/xorm/error.go
@@ -23,4 +23,6 @@ var (
ErrNeedDeletedCond = errors.New("Delete need at least one condition")
// ErrNotImplemented not implemented
ErrNotImplemented = errors.New("Not implemented")
+ // ErrConditionType condition type unsupported
+ ErrConditionType = errors.New("Unsupported conditon type")
)
diff --git a/vendor/github.com/go-xorm/xorm/helpers.go b/vendor/github.com/go-xorm/xorm/helpers.go
index 398ec679fe0..f39ed472560 100644
--- a/vendor/github.com/go-xorm/xorm/helpers.go
+++ b/vendor/github.com/go-xorm/xorm/helpers.go
@@ -196,25 +196,43 @@ func isArrayValueZero(v reflect.Value) bool {
func int64ToIntValue(id int64, tp reflect.Type) reflect.Value {
var v interface{}
- switch tp.Kind() {
- case reflect.Int16:
- v = int16(id)
- case reflect.Int32:
- v = int32(id)
- case reflect.Int:
- v = int(id)
- case reflect.Int64:
- v = id
- case reflect.Uint16:
- v = uint16(id)
- case reflect.Uint32:
- v = uint32(id)
- case reflect.Uint64:
- v = uint64(id)
- case reflect.Uint:
- v = uint(id)
+ kind := tp.Kind()
+
+ if kind == reflect.Ptr {
+ kind = tp.Elem().Kind()
}
- return reflect.ValueOf(v).Convert(tp)
+
+ switch kind {
+ case reflect.Int16:
+ temp := int16(id)
+ v = &temp
+ case reflect.Int32:
+ temp := int32(id)
+ v = &temp
+ case reflect.Int:
+ temp := int(id)
+ v = &temp
+ case reflect.Int64:
+ temp := id
+ v = &temp
+ case reflect.Uint16:
+ temp := uint16(id)
+ v = &temp
+ case reflect.Uint32:
+ temp := uint32(id)
+ v = &temp
+ case reflect.Uint64:
+ temp := uint64(id)
+ v = &temp
+ case reflect.Uint:
+ temp := uint(id)
+ v = &temp
+ }
+
+ if tp.Kind() == reflect.Ptr {
+ return reflect.ValueOf(v).Convert(tp)
+ }
+ return reflect.ValueOf(v).Elem().Convert(tp)
}
func int64ToInt(id int64, tp reflect.Type) interface{} {
@@ -302,180 +320,6 @@ func sliceEq(left, right []string) bool {
return true
}
-func reflect2value(rawValue *reflect.Value) (str string, err error) {
- aa := reflect.TypeOf((*rawValue).Interface())
- vv := reflect.ValueOf((*rawValue).Interface())
- switch aa.Kind() {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- str = strconv.FormatInt(vv.Int(), 10)
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- str = strconv.FormatUint(vv.Uint(), 10)
- case reflect.Float32, reflect.Float64:
- str = strconv.FormatFloat(vv.Float(), 'f', -1, 64)
- case reflect.String:
- str = vv.String()
- case reflect.Array, reflect.Slice:
- switch aa.Elem().Kind() {
- case reflect.Uint8:
- data := rawValue.Interface().([]byte)
- str = string(data)
- default:
- err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
- }
- // time type
- case reflect.Struct:
- if aa.ConvertibleTo(core.TimeType) {
- str = vv.Convert(core.TimeType).Interface().(time.Time).Format(time.RFC3339Nano)
- } else {
- err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
- }
- case reflect.Bool:
- str = strconv.FormatBool(vv.Bool())
- case reflect.Complex128, reflect.Complex64:
- str = fmt.Sprintf("%v", vv.Complex())
- /* TODO: unsupported types below
- case reflect.Map:
- case reflect.Ptr:
- case reflect.Uintptr:
- case reflect.UnsafePointer:
- case reflect.Chan, reflect.Func, reflect.Interface:
- */
- default:
- err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
- }
- return
-}
-
-func value2Bytes(rawValue *reflect.Value) (data []byte, err error) {
- var str string
- str, err = reflect2value(rawValue)
- if err != nil {
- return
- }
- data = []byte(str)
- return
-}
-
-func value2String(rawValue *reflect.Value) (data string, err error) {
- data, err = reflect2value(rawValue)
- if err != nil {
- return
- }
- return
-}
-
-func rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error) {
- fields, err := rows.Columns()
- if err != nil {
- return nil, err
- }
- for rows.Next() {
- result, err := row2mapStr(rows, fields)
- if err != nil {
- return nil, err
- }
- resultsSlice = append(resultsSlice, result)
- }
-
- return resultsSlice, nil
-}
-
-func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) {
- fields, err := rows.Columns()
- if err != nil {
- return nil, err
- }
- for rows.Next() {
- result, err := row2map(rows, fields)
- if err != nil {
- return nil, err
- }
- resultsSlice = append(resultsSlice, result)
- }
-
- return resultsSlice, nil
-}
-
-func row2map(rows *core.Rows, fields []string) (resultsMap map[string][]byte, err error) {
- result := make(map[string][]byte)
- scanResultContainers := make([]interface{}, len(fields))
- for i := 0; i < len(fields); i++ {
- var scanResultContainer interface{}
- scanResultContainers[i] = &scanResultContainer
- }
- if err := rows.Scan(scanResultContainers...); err != nil {
- return nil, err
- }
-
- for ii, key := range fields {
- rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii]))
- //if row is null then ignore
- if rawValue.Interface() == nil {
- //fmt.Println("ignore ...", key, rawValue)
- continue
- }
-
- if data, err := value2Bytes(&rawValue); err == nil {
- result[key] = data
- } else {
- return nil, err // !nashtsai! REVIEW, should return err or just error log?
- }
- }
- return result, nil
-}
-
-func row2mapStr(rows *core.Rows, fields []string) (resultsMap map[string]string, err error) {
- result := make(map[string]string)
- scanResultContainers := make([]interface{}, len(fields))
- for i := 0; i < len(fields); i++ {
- var scanResultContainer interface{}
- scanResultContainers[i] = &scanResultContainer
- }
- if err := rows.Scan(scanResultContainers...); err != nil {
- return nil, err
- }
-
- for ii, key := range fields {
- rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii]))
- //if row is null then ignore
- if rawValue.Interface() == nil {
- //fmt.Println("ignore ...", key, rawValue)
- continue
- }
-
- if data, err := value2String(&rawValue); err == nil {
- result[key] = data
- } else {
- return nil, err // !nashtsai! REVIEW, should return err or just error log?
- }
- }
- return result, nil
-}
-
-func txQuery2(tx *core.Tx, sqlStr string, params ...interface{}) (resultsSlice []map[string]string, err error) {
- rows, err := tx.Query(sqlStr, params...)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
-
- return rows2Strings(rows)
-}
-
-func query2(db *core.DB, sqlStr string, params ...interface{}) (resultsSlice []map[string]string, err error) {
- s, err := db.Prepare(sqlStr)
- if err != nil {
- return nil, err
- }
- defer s.Close()
- rows, err := s.Query(params...)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
- return rows2Strings(rows)
-}
-
func setColumnInt(bean interface{}, col *core.Column, t int64) {
v, err := col.ValueOf(bean)
if err != nil {
@@ -514,7 +358,7 @@ func genCols(table *core.Table, session *Session, bean interface{}, useCol bool,
for _, col := range table.Columns() {
if useCol && !col.IsVersion && !col.IsCreated && !col.IsUpdated {
- if _, ok := getFlagForColumn(session.Statement.columnMap, col); !ok {
+ if _, ok := getFlagForColumn(session.statement.columnMap, col); !ok {
continue
}
}
@@ -542,6 +386,10 @@ func genCols(table *core.Table, session *Session, bean interface{}, useCol bool,
if len(fieldValue.String()) == 0 {
continue
}
+ case reflect.Ptr:
+ if fieldValue.Pointer() == 0 {
+ continue
+ }
}
}
@@ -549,28 +397,32 @@ func genCols(table *core.Table, session *Session, bean interface{}, useCol bool,
continue
}
- if session.Statement.ColumnStr != "" {
- if _, ok := getFlagForColumn(session.Statement.columnMap, col); !ok {
+ if session.statement.ColumnStr != "" {
+ if _, ok := getFlagForColumn(session.statement.columnMap, col); !ok {
+ continue
+ } else if _, ok := session.statement.incrColumns[col.Name]; ok {
+ continue
+ } else if _, ok := session.statement.decrColumns[col.Name]; ok {
continue
}
}
- if session.Statement.OmitStr != "" {
- if _, ok := getFlagForColumn(session.Statement.columnMap, col); ok {
+ if session.statement.OmitStr != "" {
+ if _, ok := getFlagForColumn(session.statement.columnMap, col); ok {
continue
}
}
// !evalphobia! set fieldValue as nil when column is nullable and zero-value
- if _, ok := getFlagForColumn(session.Statement.nullableMap, col); ok {
+ if _, ok := getFlagForColumn(session.statement.nullableMap, col); ok {
if col.Nullable && isZero(fieldValue.Interface()) {
var nilValue *int
fieldValue = reflect.ValueOf(nilValue)
}
}
- if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ {
+ if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ {
// if time is non-empty, then set to auto time
- val, t := session.Engine.NowTime2(col.SQLType.Name)
+ val, t := session.engine.nowTime(col)
args = append(args, val)
var colName = col.Name
@@ -578,7 +430,7 @@ func genCols(table *core.Table, session *Session, bean interface{}, useCol bool,
col := table.GetColumn(colName)
setColumnTime(bean, col, t)
})
- } else if col.IsVersion && session.Statement.checkVersion {
+ } else if col.IsVersion && session.statement.checkVersion {
args = append(args, 1)
} else {
arg, err := session.value2Interface(col, fieldValue)
@@ -589,7 +441,7 @@ func genCols(table *core.Table, session *Session, bean interface{}, useCol bool,
}
if includeQuote {
- colNames = append(colNames, session.Engine.Quote(col.Name)+" = ?")
+ colNames = append(colNames, session.engine.Quote(col.Name)+" = ?")
} else {
colNames = append(colNames, col.Name)
}
@@ -602,7 +454,6 @@ func indexName(tableName, idxName string) string {
}
func getFlagForColumn(m map[string]bool, col *core.Column) (val bool, has bool) {
-
if len(m) == 0 {
return false, false
}
diff --git a/vendor/github.com/go-xorm/xorm/helpler_time.go b/vendor/github.com/go-xorm/xorm/helpler_time.go
new file mode 100644
index 00000000000..f4013e27e1a
--- /dev/null
+++ b/vendor/github.com/go-xorm/xorm/helpler_time.go
@@ -0,0 +1,21 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import "time"
+
+const (
+ zeroTime0 = "0000-00-00 00:00:00"
+ zeroTime1 = "0001-01-01 00:00:00"
+)
+
+func formatTime(t time.Time) string {
+ return t.Format("2006-01-02 15:04:05")
+}
+
+func isTimeZero(t time.Time) bool {
+ return t.IsZero() || formatTime(t) == zeroTime0 ||
+ formatTime(t) == zeroTime1
+}
diff --git a/vendor/github.com/go-xorm/xorm/interface.go b/vendor/github.com/go-xorm/xorm/interface.go
new file mode 100644
index 00000000000..9a3b6da0b2b
--- /dev/null
+++ b/vendor/github.com/go-xorm/xorm/interface.go
@@ -0,0 +1,103 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+ "database/sql"
+ "reflect"
+ "time"
+
+ "github.com/go-xorm/core"
+)
+
+// Interface defines the interface which Engine, EngineGroup and Session will implementate.
+type Interface interface {
+ AllCols() *Session
+ Alias(alias string) *Session
+ Asc(colNames ...string) *Session
+ BufferSize(size int) *Session
+ Cols(columns ...string) *Session
+ Count(...interface{}) (int64, error)
+ CreateIndexes(bean interface{}) error
+ CreateUniques(bean interface{}) error
+ Decr(column string, arg ...interface{}) *Session
+ Desc(...string) *Session
+ Delete(interface{}) (int64, error)
+ Distinct(columns ...string) *Session
+ DropIndexes(bean interface{}) error
+ Exec(string, ...interface{}) (sql.Result, error)
+ Exist(bean ...interface{}) (bool, error)
+ Find(interface{}, ...interface{}) error
+ Get(interface{}) (bool, error)
+ GroupBy(keys string) *Session
+ ID(interface{}) *Session
+ In(string, ...interface{}) *Session
+ Incr(column string, arg ...interface{}) *Session
+ Insert(...interface{}) (int64, error)
+ InsertOne(interface{}) (int64, error)
+ IsTableEmpty(bean interface{}) (bool, error)
+ IsTableExist(beanOrTableName interface{}) (bool, error)
+ Iterate(interface{}, IterFunc) error
+ Limit(int, ...int) *Session
+ NoAutoCondition(...bool) *Session
+ NotIn(string, ...interface{}) *Session
+ Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session
+ Omit(columns ...string) *Session
+ OrderBy(order string) *Session
+ Ping() error
+ Query(sqlOrAgrs ...interface{}) (resultsSlice []map[string][]byte, err error)
+ QueryInterface(sqlorArgs ...interface{}) ([]map[string]interface{}, error)
+ QueryString(sqlorArgs ...interface{}) ([]map[string]string, error)
+ Rows(bean interface{}) (*Rows, error)
+ SetExpr(string, string) *Session
+ SQL(interface{}, ...interface{}) *Session
+ Sum(bean interface{}, colName string) (float64, error)
+ SumInt(bean interface{}, colName string) (int64, error)
+ Sums(bean interface{}, colNames ...string) ([]float64, error)
+ SumsInt(bean interface{}, colNames ...string) ([]int64, error)
+ Table(tableNameOrBean interface{}) *Session
+ Unscoped() *Session
+ Update(bean interface{}, condiBeans ...interface{}) (int64, error)
+ UseBool(...string) *Session
+ Where(interface{}, ...interface{}) *Session
+}
+
+// EngineInterface defines the interface which Engine, EngineGroup will implementate.
+type EngineInterface interface {
+ Interface
+
+ Before(func(interface{})) *Session
+ Charset(charset string) *Session
+ CreateTables(...interface{}) error
+ DBMetas() ([]*core.Table, error)
+ Dialect() core.Dialect
+ DropTables(...interface{}) error
+ DumpAllToFile(fp string, tp ...core.DbType) error
+ GetColumnMapper() core.IMapper
+ GetDefaultCacher() core.Cacher
+ GetTableMapper() core.IMapper
+ GetTZDatabase() *time.Location
+ GetTZLocation() *time.Location
+ NewSession() *Session
+ NoAutoTime() *Session
+ Quote(string) string
+ SetDefaultCacher(core.Cacher)
+ SetLogLevel(core.LogLevel)
+ SetMapper(core.IMapper)
+ SetTZDatabase(tz *time.Location)
+ SetTZLocation(tz *time.Location)
+ ShowSQL(show ...bool)
+ Sync(...interface{}) error
+ Sync2(...interface{}) error
+ StoreEngine(storeEngine string) *Session
+ TableInfo(bean interface{}) *Table
+ UnMapType(reflect.Type)
+}
+
+var (
+ _ Interface = &Session{}
+ _ EngineInterface = &Engine{}
+ _ EngineInterface = &EngineGroup{}
+)
diff --git a/vendor/github.com/go-xorm/xorm/processors.go b/vendor/github.com/go-xorm/xorm/processors.go
index 77dd30e5785..dcd9c6ac0b4 100644
--- a/vendor/github.com/go-xorm/xorm/processors.go
+++ b/vendor/github.com/go-xorm/xorm/processors.go
@@ -29,13 +29,6 @@ type AfterSetProcessor interface {
AfterSet(string, Cell)
}
-// !nashtsai! TODO enable BeforeValidateProcessor when xorm start to support validations
-//// Executed before an object is validated
-//type BeforeValidateProcessor interface {
-// BeforeValidate()
-//}
-// --
-
// AfterInsertProcessor executed after an object is persisted to the database
type AfterInsertProcessor interface {
AfterInsert()
@@ -50,3 +43,36 @@ type AfterUpdateProcessor interface {
type AfterDeleteProcessor interface {
AfterDelete()
}
+
+// AfterLoadProcessor executed after an ojbect has been loaded from database
+type AfterLoadProcessor interface {
+ AfterLoad()
+}
+
+// AfterLoadSessionProcessor executed after an ojbect has been loaded from database with session parameter
+type AfterLoadSessionProcessor interface {
+ AfterLoad(*Session)
+}
+
+type executedProcessorFunc func(*Session, interface{}) error
+
+type executedProcessor struct {
+ fun executedProcessorFunc
+ session *Session
+ bean interface{}
+}
+
+func (executor *executedProcessor) execute() error {
+ return executor.fun(executor.session, executor.bean)
+}
+
+func (session *Session) executeProcessors() error {
+ processors := session.afterProcessors
+ session.afterProcessors = make([]executedProcessor, 0)
+ for _, processor := range processors {
+ if err := processor.execute(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/go-xorm/xorm/rows.go b/vendor/github.com/go-xorm/xorm/rows.go
index a91d08b7791..31e29ae26f6 100644
--- a/vendor/github.com/go-xorm/xorm/rows.go
+++ b/vendor/github.com/go-xorm/xorm/rows.go
@@ -17,7 +17,6 @@ type Rows struct {
NoTypeCheck bool
session *Session
- stmt *core.Stmt
rows *core.Rows
fields []string
beanType reflect.Type
@@ -29,50 +28,33 @@ func newRows(session *Session, bean interface{}) (*Rows, error) {
rows.session = session
rows.beanType = reflect.Indirect(reflect.ValueOf(bean)).Type()
- defer rows.session.resetStatement()
-
var sqlStr string
var args []interface{}
+ var err error
- rows.session.Statement.setRefValue(rValue(bean))
- if len(session.Statement.TableName()) <= 0 {
+ if err = rows.session.statement.setRefValue(rValue(bean)); err != nil {
+ return nil, err
+ }
+
+ if len(session.statement.TableName()) <= 0 {
return nil, ErrTableNotFound
}
- if rows.session.Statement.RawSQL == "" {
- sqlStr, args = rows.session.Statement.genGetSQL(bean)
- } else {
- sqlStr = rows.session.Statement.RawSQL
- args = rows.session.Statement.RawParams
- }
-
- for _, filter := range rows.session.Engine.dialect.Filters() {
- sqlStr = filter.Do(sqlStr, session.Engine.dialect, rows.session.Statement.RefTable)
- }
-
- rows.session.saveLastSQL(sqlStr, args...)
- var err error
- if rows.session.prepareStmt {
- rows.stmt, err = rows.session.DB().Prepare(sqlStr)
+ if rows.session.statement.RawSQL == "" {
+ sqlStr, args, err = rows.session.statement.genGetSQL(bean)
if err != nil {
- rows.lastError = err
- rows.Close()
- return nil, err
- }
-
- rows.rows, err = rows.stmt.Query(args...)
- if err != nil {
- rows.lastError = err
- rows.Close()
return nil, err
}
} else {
- rows.rows, err = rows.session.DB().Query(sqlStr, args...)
- if err != nil {
- rows.lastError = err
- rows.Close()
- return nil, err
- }
+ sqlStr = rows.session.statement.RawSQL
+ args = rows.session.statement.RawParams
+ }
+
+ rows.rows, err = rows.session.queryRows(sqlStr, args...)
+ if err != nil {
+ rows.lastError = err
+ rows.Close()
+ return nil, err
}
rows.fields, err = rows.rows.Columns()
@@ -113,15 +95,26 @@ func (rows *Rows) Scan(bean interface{}) error {
}
dataStruct := rValue(bean)
- rows.session.Statement.setRefValue(dataStruct)
- _, err := rows.session.row2Bean(rows.rows, rows.fields, len(rows.fields), bean, &dataStruct, rows.session.Statement.RefTable)
+ if err := rows.session.statement.setRefValue(dataStruct); err != nil {
+ return err
+ }
- return err
+ scanResults, err := rows.session.row2Slice(rows.rows, rows.fields, bean)
+ if err != nil {
+ return err
+ }
+
+ _, err = rows.session.slice2Bean(scanResults, rows.fields, bean, &dataStruct, rows.session.statement.RefTable)
+ if err != nil {
+ return err
+ }
+
+ return rows.session.executeProcessors()
}
// Close session if session.IsAutoClose is true, and claimed any opened resources
func (rows *Rows) Close() error {
- if rows.session.IsAutoClose {
+ if rows.session.isAutoClose {
defer rows.session.Close()
}
@@ -129,17 +122,10 @@ func (rows *Rows) Close() error {
if rows.rows != nil {
rows.lastError = rows.rows.Close()
if rows.lastError != nil {
- defer rows.stmt.Close()
return rows.lastError
}
}
- if rows.stmt != nil {
- rows.lastError = rows.stmt.Close()
- }
} else {
- if rows.stmt != nil {
- defer rows.stmt.Close()
- }
if rows.rows != nil {
defer rows.rows.Close()
}
diff --git a/vendor/github.com/go-xorm/xorm/session.go b/vendor/github.com/go-xorm/xorm/session.go
index 0e4cfcf1574..5c6cb5f9def 100644
--- a/vendor/github.com/go-xorm/xorm/session.go
+++ b/vendor/github.com/go-xorm/xorm/session.go
@@ -11,7 +11,6 @@ import (
"fmt"
"hash/crc32"
"reflect"
- "strconv"
"strings"
"time"
@@ -22,17 +21,16 @@ import (
// kind of database operations.
type Session struct {
db *core.DB
- Engine *Engine
- Tx *core.Tx
- Statement Statement
- IsAutoCommit bool
- IsCommitedOrRollbacked bool
- TransType string
- IsAutoClose bool
+ engine *Engine
+ tx *core.Tx
+ statement Statement
+ isAutoCommit bool
+ isCommitedOrRollbacked bool
+ isAutoClose bool
// Automatically reset the statement after operations that execute a SQL
// query such as Count(), Find(), Get(), ...
- AutoResetStatement bool
+ autoResetStatement bool
// !nashtsai! storing these beans due to yet committed tx
afterInsertBeans map[interface{}]*[]func(interface{})
@@ -43,14 +41,17 @@ type Session struct {
beforeClosures []func(interface{})
afterClosures []func(interface{})
+ afterProcessors []executedProcessor
+
prepareStmt bool
stmtCache map[uint32]*core.Stmt //key: hash.Hash32 of (queryStr, len(queryStr))
- cascadeDeep int
// !evalphobia! stored the last executed query on this session
//beforeSQLExec func(string, ...interface{})
lastSQL string
lastSQLArgs []interface{}
+
+ err error
}
// Clone copy all the session's content and return a new session
@@ -61,12 +62,12 @@ func (session *Session) Clone() *Session {
// Init reset the session as the init status.
func (session *Session) Init() {
- session.Statement.Init()
- session.Statement.Engine = session.Engine
- session.IsAutoCommit = true
- session.IsCommitedOrRollbacked = false
- session.IsAutoClose = false
- session.AutoResetStatement = true
+ session.statement.Init()
+ session.statement.Engine = session.engine
+ session.isAutoCommit = true
+ session.isCommitedOrRollbacked = false
+ session.isAutoClose = false
+ session.autoResetStatement = true
session.prepareStmt = false
// !nashtsai! is lazy init better?
@@ -75,6 +76,9 @@ func (session *Session) Init() {
session.afterDeleteBeans = make(map[interface{}]*[]func(interface{}), 0)
session.beforeClosures = make([]func(interface{}), 0)
session.afterClosures = make([]func(interface{}), 0)
+ session.stmtCache = make(map[uint32]*core.Stmt)
+
+ session.afterProcessors = make([]executedProcessor, 0)
session.lastSQL = ""
session.lastSQLArgs = []interface{}{}
@@ -89,19 +93,23 @@ func (session *Session) Close() {
if session.db != nil {
// When Close be called, if session is a transaction and do not call
// Commit or Rollback, then call Rollback.
- if session.Tx != nil && !session.IsCommitedOrRollbacked {
+ if session.tx != nil && !session.isCommitedOrRollbacked {
session.Rollback()
}
- session.Tx = nil
+ session.tx = nil
session.stmtCache = nil
- session.Init()
session.db = nil
}
}
+// IsClosed returns if session is closed
+func (session *Session) IsClosed() bool {
+ return session.db == nil
+}
+
func (session *Session) resetStatement() {
- if session.AutoResetStatement {
- session.Statement.Init()
+ if session.autoResetStatement {
+ session.statement.Init()
}
}
@@ -129,75 +137,75 @@ func (session *Session) After(closures func(interface{})) *Session {
// Table can input a string or pointer to struct for special a table to operate.
func (session *Session) Table(tableNameOrBean interface{}) *Session {
- session.Statement.Table(tableNameOrBean)
+ session.statement.Table(tableNameOrBean)
return session
}
// Alias set the table alias
func (session *Session) Alias(alias string) *Session {
- session.Statement.Alias(alias)
+ session.statement.Alias(alias)
return session
}
// NoCascade indicate that no cascade load child object
func (session *Session) NoCascade() *Session {
- session.Statement.UseCascade = false
+ session.statement.UseCascade = false
return session
}
// ForUpdate Set Read/Write locking for UPDATE
func (session *Session) ForUpdate() *Session {
- session.Statement.IsForUpdate = true
+ session.statement.IsForUpdate = true
return session
}
// NoAutoCondition disable generate SQL condition from beans
func (session *Session) NoAutoCondition(no ...bool) *Session {
- session.Statement.NoAutoCondition(no...)
+ session.statement.NoAutoCondition(no...)
return session
}
// Limit provide limit and offset query condition
func (session *Session) Limit(limit int, start ...int) *Session {
- session.Statement.Limit(limit, start...)
+ session.statement.Limit(limit, start...)
return session
}
// OrderBy provide order by query condition, the input parameter is the content
// after order by on a sql statement.
func (session *Session) OrderBy(order string) *Session {
- session.Statement.OrderBy(order)
+ session.statement.OrderBy(order)
return session
}
// Desc provide desc order by query condition, the input parameters are columns.
func (session *Session) Desc(colNames ...string) *Session {
- session.Statement.Desc(colNames...)
+ session.statement.Desc(colNames...)
return session
}
// Asc provide asc order by query condition, the input parameters are columns.
func (session *Session) Asc(colNames ...string) *Session {
- session.Statement.Asc(colNames...)
+ session.statement.Asc(colNames...)
return session
}
// StoreEngine is only avialble mysql dialect currently
func (session *Session) StoreEngine(storeEngine string) *Session {
- session.Statement.StoreEngine = storeEngine
+ session.statement.StoreEngine = storeEngine
return session
}
// Charset is only avialble mysql dialect currently
func (session *Session) Charset(charset string) *Session {
- session.Statement.Charset = charset
+ session.statement.Charset = charset
return session
}
// Cascade indicates if loading sub Struct
func (session *Session) Cascade(trueOrFalse ...bool) *Session {
if len(trueOrFalse) >= 1 {
- session.Statement.UseCascade = trueOrFalse[0]
+ session.statement.UseCascade = trueOrFalse[0]
}
return session
}
@@ -205,32 +213,32 @@ func (session *Session) Cascade(trueOrFalse ...bool) *Session {
// NoCache ask this session do not retrieve data from cache system and
// get data from database directly.
func (session *Session) NoCache() *Session {
- session.Statement.UseCache = false
+ session.statement.UseCache = false
return session
}
// Join join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN
func (session *Session) Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session {
- session.Statement.Join(joinOperator, tablename, condition, args...)
+ session.statement.Join(joinOperator, tablename, condition, args...)
return session
}
// GroupBy Generate Group By statement
func (session *Session) GroupBy(keys string) *Session {
- session.Statement.GroupBy(keys)
+ session.statement.GroupBy(keys)
return session
}
// Having Generate Having statement
func (session *Session) Having(conditions string) *Session {
- session.Statement.Having(conditions)
+ session.statement.Having(conditions)
return session
}
// DB db return the wrapper of sql.DB
func (session *Session) DB() *core.DB {
if session.db == nil {
- session.db = session.Engine.db
+ session.db = session.engine.db
session.stmtCache = make(map[uint32]*core.Stmt, 0)
}
return session.db
@@ -243,25 +251,25 @@ func cleanupProcessorsClosures(slices *[]func(interface{})) {
}
func (session *Session) canCache() bool {
- if session.Statement.RefTable == nil ||
- session.Statement.JoinStr != "" ||
- session.Statement.RawSQL != "" ||
- !session.Statement.UseCache ||
- session.Statement.IsForUpdate ||
- session.Tx != nil ||
- len(session.Statement.selectStr) > 0 {
+ if session.statement.RefTable == nil ||
+ session.statement.JoinStr != "" ||
+ session.statement.RawSQL != "" ||
+ !session.statement.UseCache ||
+ session.statement.IsForUpdate ||
+ session.tx != nil ||
+ len(session.statement.selectStr) > 0 {
return false
}
return true
}
-func (session *Session) doPrepare(sqlStr string) (stmt *core.Stmt, err error) {
+func (session *Session) doPrepare(db *core.DB, sqlStr string) (stmt *core.Stmt, err error) {
crc := crc32.ChecksumIEEE([]byte(sqlStr))
// TODO try hash(sqlStr+len(sqlStr))
var has bool
stmt, has = session.stmtCache[crc]
if !has {
- stmt, err = session.DB().Prepare(sqlStr)
+ stmt, err = db.Prepare(sqlStr)
if err != nil {
return nil, err
}
@@ -273,18 +281,18 @@ func (session *Session) doPrepare(sqlStr string) (stmt *core.Stmt, err error) {
func (session *Session) getField(dataStruct *reflect.Value, key string, table *core.Table, idx int) *reflect.Value {
var col *core.Column
if col = table.GetColumnIdx(key, idx); col == nil {
- //session.Engine.logger.Warnf("table %v has no column %v. %v", table.Name, key, table.ColumnsSeq())
+ //session.engine.logger.Warnf("table %v has no column %v. %v", table.Name, key, table.ColumnsSeq())
return nil
}
fieldValue, err := col.ValueOfV(dataStruct)
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
return nil
}
if !fieldValue.IsValid() || !fieldValue.CanSet() {
- session.Engine.logger.Warnf("table %v's column %v is not valid or cannot set", table.Name, key)
+ session.engine.logger.Warnf("table %v's column %v is not valid or cannot set", table.Name, key)
return nil
}
return fieldValue
@@ -293,28 +301,40 @@ func (session *Session) getField(dataStruct *reflect.Value, key string, table *c
// Cell cell is a result of one column field
type Cell *interface{}
-func (session *Session) rows2Beans(rows *core.Rows, fields []string, fieldsCount int,
+func (session *Session) rows2Beans(rows *core.Rows, fields []string,
table *core.Table, newElemFunc func([]string) reflect.Value,
sliceValueSetFunc func(*reflect.Value, core.PK) error) error {
for rows.Next() {
var newValue = newElemFunc(fields)
bean := newValue.Interface()
- dataStruct := rValue(bean)
- pk, err := session.row2Bean(rows, fields, fieldsCount, bean, &dataStruct, table)
- if err != nil {
- return err
- }
+ dataStruct := newValue.Elem()
- err = sliceValueSetFunc(&newValue, pk)
+ // handle beforeClosures
+ scanResults, err := session.row2Slice(rows, fields, bean)
if err != nil {
return err
}
+ pk, err := session.slice2Bean(scanResults, fields, bean, &dataStruct, table)
+ if err != nil {
+ return err
+ }
+ session.afterProcessors = append(session.afterProcessors, executedProcessor{
+ fun: func(*Session, interface{}) error {
+ return sliceValueSetFunc(&newValue, pk)
+ },
+ session: session,
+ bean: bean,
+ })
}
return nil
}
-func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount int, bean interface{}, dataStruct *reflect.Value, table *core.Table) (core.PK, error) {
- scanResults := make([]interface{}, fieldsCount)
+func (session *Session) row2Slice(rows *core.Rows, fields []string, bean interface{}) ([]interface{}, error) {
+ for _, closure := range session.beforeClosures {
+ closure(bean)
+ }
+
+ scanResults := make([]interface{}, len(fields))
for i := 0; i < len(fields); i++ {
var cell interface{}
scanResults[i] = &cell
@@ -328,7 +348,10 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
b.BeforeSet(key, Cell(scanResults[ii].(*interface{})))
}
}
+ return scanResults, nil
+}
+func (session *Session) slice2Bean(scanResults []interface{}, fields []string, bean interface{}, dataStruct *reflect.Value, table *core.Table) (core.PK, error) {
defer func() {
if b, hasAfterSet := bean.(AfterSetProcessor); hasAfterSet {
for ii, key := range fields {
@@ -337,6 +360,40 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
}
}()
+ // handle afterClosures
+ for _, closure := range session.afterClosures {
+ session.afterProcessors = append(session.afterProcessors, executedProcessor{
+ fun: func(sess *Session, bean interface{}) error {
+ closure(bean)
+ return nil
+ },
+ session: session,
+ bean: bean,
+ })
+ }
+
+ if a, has := bean.(AfterLoadProcessor); has {
+ session.afterProcessors = append(session.afterProcessors, executedProcessor{
+ fun: func(sess *Session, bean interface{}) error {
+ a.AfterLoad()
+ return nil
+ },
+ session: session,
+ bean: bean,
+ })
+ }
+
+ if a, has := bean.(AfterLoadSessionProcessor); has {
+ session.afterProcessors = append(session.afterProcessors, executedProcessor{
+ fun: func(sess *Session, bean interface{}) error {
+ a.AfterLoad(sess)
+ return nil
+ },
+ session: session,
+ bean: bean,
+ })
+ }
+
var tempMap = make(map[string]int)
var pk core.PK
for ii, key := range fields {
@@ -361,9 +418,11 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
if fieldValue.CanAddr() {
if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok {
if data, err := value2Bytes(&rawValue); err == nil {
- structConvert.FromDB(data)
+ if err := structConvert.FromDB(data); err != nil {
+ return nil, err
+ }
} else {
- session.Engine.logger.Error(err)
+ return nil, err
}
continue
}
@@ -376,7 +435,7 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
}
fieldValue.Interface().(core.Conversion).FromDB(data)
} else {
- session.Engine.logger.Error(err)
+ return nil, err
}
continue
}
@@ -403,17 +462,19 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
hasAssigned = true
if len(bs) > 0 {
+ if fieldType.Kind() == reflect.String {
+ fieldValue.SetString(string(bs))
+ continue
+ }
if fieldValue.CanAddr() {
err := json.Unmarshal(bs, fieldValue.Addr().Interface())
if err != nil {
- session.Engine.logger.Error(key, err)
return nil, err
}
} else {
x := reflect.New(fieldType)
err := json.Unmarshal(bs, x.Interface())
if err != nil {
- session.Engine.logger.Error(key, err)
return nil, err
}
fieldValue.Set(x.Elem())
@@ -438,14 +499,12 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
if fieldValue.CanAddr() {
err := json.Unmarshal(bs, fieldValue.Addr().Interface())
if err != nil {
- session.Engine.logger.Error(err)
return nil, err
}
} else {
x := reflect.New(fieldType)
err := json.Unmarshal(bs, x.Interface())
if err != nil {
- session.Engine.logger.Error(err)
return nil, err
}
fieldValue.Set(x.Elem())
@@ -462,14 +521,19 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
x := reflect.New(fieldType)
err := json.Unmarshal(vv.Bytes(), x.Interface())
if err != nil {
- session.Engine.logger.Error(err)
return nil, err
}
fieldValue.Set(x.Elem())
} else {
- for i := 0; i < fieldValue.Len(); i++ {
- if i < vv.Len() {
- fieldValue.Index(i).Set(vv.Index(i))
+ if fieldValue.Len() > 0 {
+ for i := 0; i < fieldValue.Len(); i++ {
+ if i < vv.Len() {
+ fieldValue.Index(i).Set(vv.Index(i))
+ }
+ }
+ } else {
+ for i := 0; i < vv.Len(); i++ {
+ fieldValue.Set(reflect.Append(*fieldValue, vv.Index(i)))
}
}
}
@@ -509,57 +573,38 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
}
case reflect.Struct:
if fieldType.ConvertibleTo(core.TimeType) {
+ dbTZ := session.engine.DatabaseTZ
+ if col.TimeZone != nil {
+ dbTZ = col.TimeZone
+ }
+
if rawValueType == core.TimeType {
hasAssigned = true
t := vv.Convert(core.TimeType).Interface().(time.Time)
z, _ := t.Zone()
- dbTZ := session.Engine.DatabaseTZ
- if dbTZ == nil {
- if session.Engine.dialect.DBType() == core.SQLITE {
- dbTZ = time.UTC
- } else {
- dbTZ = time.Local
- }
- }
-
// set new location if database don't save timezone or give an incorrect timezone
if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbTZ.String() { // !nashtsai! HACK tmp work around for lib/pq doesn't properly time with location
- session.Engine.logger.Debugf("empty zone key[%v] : %v | zone: %v | location: %+v\n", key, t, z, *t.Location())
+ session.engine.logger.Debugf("empty zone key[%v] : %v | zone: %v | location: %+v\n", key, t, z, *t.Location())
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(),
t.Minute(), t.Second(), t.Nanosecond(), dbTZ)
}
- // !nashtsai! convert to engine location
- if col.TimeZone == nil {
- t = t.In(session.Engine.TZLocation)
- } else {
- t = t.In(col.TimeZone)
- }
+ t = t.In(session.engine.TZLocation)
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
-
- // t = fieldValue.Interface().(time.Time)
- // z, _ = t.Zone()
- // session.Engine.LogDebug("fieldValue key[%v]: %v | zone: %v | location: %+v\n", key, t, z, *t.Location())
} else if rawValueType == core.IntType || rawValueType == core.Int64Type ||
rawValueType == core.Int32Type {
hasAssigned = true
- var tz *time.Location
- if col.TimeZone == nil {
- tz = session.Engine.TZLocation
- } else {
- tz = col.TimeZone
- }
- t := time.Unix(vv.Int(), 0).In(tz)
- //vv = reflect.ValueOf(t)
+
+ t := time.Unix(vv.Int(), 0).In(session.engine.TZLocation)
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
} else {
if d, ok := vv.Interface().([]uint8); ok {
hasAssigned = true
t, err := session.byte2Time(col, d)
if err != nil {
- session.Engine.logger.Error("byte2Time error:", err.Error())
+ session.engine.logger.Error("byte2Time error:", err.Error())
hasAssigned = false
} else {
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
@@ -568,20 +613,20 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
hasAssigned = true
t, err := session.str2Time(col, d)
if err != nil {
- session.Engine.logger.Error("byte2Time error:", err.Error())
+ session.engine.logger.Error("byte2Time error:", err.Error())
hasAssigned = false
} else {
fieldValue.Set(reflect.ValueOf(t).Convert(fieldType))
}
} else {
- panic(fmt.Sprintf("rawValueType is %v, value is %v", rawValueType, vv.Interface()))
+ return nil, fmt.Errorf("rawValueType is %v, value is %v", rawValueType, vv.Interface())
}
}
} else if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok {
// !! 增加支持sql.Scanner接口的结构,如sql.NullString
hasAssigned = true
if err := nulVal.Scan(vv.Interface()); err != nil {
- session.Engine.logger.Error("sql.Sanner error:", err.Error())
+ session.engine.logger.Error("sql.Sanner error:", err.Error())
hasAssigned = false
}
} else if col.SQLType.IsJson() {
@@ -591,7 +636,6 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), x.Interface())
if err != nil {
- session.Engine.logger.Error(err)
return nil, err
}
fieldValue.Set(x.Elem())
@@ -602,76 +646,45 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
if len(vv.Bytes()) > 0 {
err := json.Unmarshal(vv.Bytes(), x.Interface())
if err != nil {
- session.Engine.logger.Error(err)
return nil, err
}
fieldValue.Set(x.Elem())
}
}
- } else if session.Statement.UseCascade {
- table := session.Engine.autoMapType(*fieldValue)
- if table != nil {
- hasAssigned = true
- if len(table.PrimaryKeys) != 1 {
- panic("unsupported non or composited primary key cascade")
- }
- var pk = make(core.PK, len(table.PrimaryKeys))
+ } else if session.statement.UseCascade {
+ table, err := session.engine.autoMapType(*fieldValue)
+ if err != nil {
+ return nil, err
+ }
- switch rawValueType.Kind() {
- case reflect.Int64:
- pk[0] = vv.Int()
- case reflect.Int:
- pk[0] = int(vv.Int())
- case reflect.Int32:
- pk[0] = int32(vv.Int())
- case reflect.Int16:
- pk[0] = int16(vv.Int())
- case reflect.Int8:
- pk[0] = int8(vv.Int())
- case reflect.Uint64:
- pk[0] = vv.Uint()
- case reflect.Uint:
- pk[0] = uint(vv.Uint())
- case reflect.Uint32:
- pk[0] = uint32(vv.Uint())
- case reflect.Uint16:
- pk[0] = uint16(vv.Uint())
- case reflect.Uint8:
- pk[0] = uint8(vv.Uint())
- case reflect.String:
- pk[0] = vv.String()
- case reflect.Slice:
- pk[0], _ = strconv.ParseInt(string(rawValue.Interface().([]byte)), 10, 64)
- default:
- panic(fmt.Sprintf("unsupported primary key type: %v, %v", rawValueType, fieldValue))
- }
+ hasAssigned = true
+ if len(table.PrimaryKeys) != 1 {
+ return nil, errors.New("unsupported non or composited primary key cascade")
+ }
+ var pk = make(core.PK, len(table.PrimaryKeys))
+ pk[0], err = asKind(vv, rawValueType)
+ if err != nil {
+ return nil, err
+ }
- if !isPKZero(pk) {
- // !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch
- // however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne
- // property to be fetched lazily
- structInter := reflect.New(fieldValue.Type())
- newsession := session.Engine.NewSession()
- defer newsession.Close()
- has, err := newsession.Id(pk).NoCascade().Get(structInter.Interface())
- if err != nil {
- return nil, err
- }
- if has {
- //v := structInter.Elem().Interface()
- //fieldValue.Set(reflect.ValueOf(v))
- fieldValue.Set(structInter.Elem())
- } else {
- return nil, errors.New("cascade obj is not exist")
- }
+ if !isPKZero(pk) {
+ // !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch
+ // however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne
+ // property to be fetched lazily
+ structInter := reflect.New(fieldValue.Type())
+ has, err := session.ID(pk).NoCascade().get(structInter.Interface())
+ if err != nil {
+ return nil, err
+ }
+ if has {
+ fieldValue.Set(structInter.Elem())
+ } else {
+ return nil, errors.New("cascade obj is not exist")
}
- } else {
- session.Engine.logger.Error("unsupported struct type in Scan: ", fieldValue.Type().String())
}
}
case reflect.Ptr:
// !nashtsai! TODO merge duplicated codes above
- //typeStr := fieldType.String()
switch fieldType {
// following types case matching ptr's native type, therefore assign ptr directly
case core.PtrStringType:
@@ -769,10 +782,9 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), &x)
if err != nil {
- session.Engine.logger.Error(err)
- } else {
- fieldValue.Set(reflect.ValueOf(&x))
+ return nil, err
}
+ fieldValue.Set(reflect.ValueOf(&x))
}
hasAssigned = true
case core.Complex128Type:
@@ -780,24 +792,23 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
if len([]byte(vv.String())) > 0 {
err := json.Unmarshal([]byte(vv.String()), &x)
if err != nil {
- session.Engine.logger.Error(err)
- } else {
- fieldValue.Set(reflect.ValueOf(&x))
+ return nil, err
}
+ fieldValue.Set(reflect.ValueOf(&x))
}
hasAssigned = true
} // switch fieldType
- // default:
- // session.Engine.LogError("unsupported type in Scan: ", reflect.TypeOf(v).String())
} // switch fieldType.Kind()
// !nashtsai! for value can't be assigned directly fallback to convert to []byte then back to value
if !hasAssigned {
data, err := value2Bytes(&rawValue)
- if err == nil {
- session.bytes2Value(col, fieldValue, data)
- } else {
- session.Engine.logger.Error(err.Error())
+ if err != nil {
+ return nil, err
+ }
+
+ if err = session.bytes2Value(col, fieldValue, data); err != nil {
+ return nil, err
}
}
}
@@ -805,19 +816,11 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i
return pk, nil
}
-func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) {
- for _, filter := range session.Engine.dialect.Filters() {
- *sqlStr = filter.Do(*sqlStr, session.Engine.dialect, session.Statement.RefTable)
- }
-
- session.saveLastSQL(*sqlStr, paramStr...)
-}
-
// saveLastSQL stores executed query information
func (session *Session) saveLastSQL(sql string, args ...interface{}) {
session.lastSQL = sql
session.lastSQLArgs = args
- session.Engine.logSQL(sql, args...)
+ session.engine.logSQL(sql, args...)
}
// LastSQL returns last query information
@@ -827,8 +830,8 @@ func (session *Session) LastSQL() (string, []interface{}) {
// tbName get some table's table name
func (session *Session) tbNameNoSchema(table *core.Table) string {
- if len(session.Statement.AltTableName) > 0 {
- return session.Statement.AltTableName
+ if len(session.statement.AltTableName) > 0 {
+ return session.statement.AltTableName
}
return table.Name
@@ -836,6 +839,6 @@ func (session *Session) tbNameNoSchema(table *core.Table) string {
// Unscoped always disable struct tag "deleted"
func (session *Session) Unscoped() *Session {
- session.Statement.Unscoped()
+ session.statement.Unscoped()
return session
}
diff --git a/vendor/github.com/go-xorm/xorm/session_cols.go b/vendor/github.com/go-xorm/xorm/session_cols.go
index 91185defc8c..9972cb0ae4b 100644
--- a/vendor/github.com/go-xorm/xorm/session_cols.go
+++ b/vendor/github.com/go-xorm/xorm/session_cols.go
@@ -6,43 +6,43 @@ package xorm
// Incr provides a query string like "count = count + 1"
func (session *Session) Incr(column string, arg ...interface{}) *Session {
- session.Statement.Incr(column, arg...)
+ session.statement.Incr(column, arg...)
return session
}
// Decr provides a query string like "count = count - 1"
func (session *Session) Decr(column string, arg ...interface{}) *Session {
- session.Statement.Decr(column, arg...)
+ session.statement.Decr(column, arg...)
return session
}
// SetExpr provides a query string like "column = {expression}"
func (session *Session) SetExpr(column string, expression string) *Session {
- session.Statement.SetExpr(column, expression)
+ session.statement.SetExpr(column, expression)
return session
}
// Select provides some columns to special
func (session *Session) Select(str string) *Session {
- session.Statement.Select(str)
+ session.statement.Select(str)
return session
}
// Cols provides some columns to special
func (session *Session) Cols(columns ...string) *Session {
- session.Statement.Cols(columns...)
+ session.statement.Cols(columns...)
return session
}
// AllCols ask all columns
func (session *Session) AllCols() *Session {
- session.Statement.AllCols()
+ session.statement.AllCols()
return session
}
// MustCols specify some columns must use even if they are empty
func (session *Session) MustCols(columns ...string) *Session {
- session.Statement.MustCols(columns...)
+ session.statement.MustCols(columns...)
return session
}
@@ -52,7 +52,7 @@ func (session *Session) MustCols(columns ...string) *Session {
// If no parameters, it will use all the bool field of struct, or
// it will use parameters's columns
func (session *Session) UseBool(columns ...string) *Session {
- session.Statement.UseBool(columns...)
+ session.statement.UseBool(columns...)
return session
}
@@ -60,25 +60,25 @@ func (session *Session) UseBool(columns ...string) *Session {
// distinct will not be cached because cache system need id,
// but distinct will not provide id
func (session *Session) Distinct(columns ...string) *Session {
- session.Statement.Distinct(columns...)
+ session.statement.Distinct(columns...)
return session
}
// Omit Only not use the parameters as select or update columns
func (session *Session) Omit(columns ...string) *Session {
- session.Statement.Omit(columns...)
+ session.statement.Omit(columns...)
return session
}
// Nullable Set null when column is zero-value and nullable for update
func (session *Session) Nullable(columns ...string) *Session {
- session.Statement.Nullable(columns...)
+ session.statement.Nullable(columns...)
return session
}
// NoAutoTime means do not automatically give created field and updated field
// the current time on the current session temporarily
func (session *Session) NoAutoTime() *Session {
- session.Statement.UseAutoTime = false
+ session.statement.UseAutoTime = false
return session
}
diff --git a/vendor/github.com/go-xorm/xorm/session_cond.go b/vendor/github.com/go-xorm/xorm/session_cond.go
index 948a90bc1fc..e1d528f2dbd 100644
--- a/vendor/github.com/go-xorm/xorm/session_cond.go
+++ b/vendor/github.com/go-xorm/xorm/session_cond.go
@@ -17,25 +17,25 @@ func (session *Session) Sql(query string, args ...interface{}) *Session {
// SQL provides raw sql input parameter. When you have a complex SQL statement
// and cannot use Where, Id, In and etc. Methods to describe, you can use SQL.
func (session *Session) SQL(query interface{}, args ...interface{}) *Session {
- session.Statement.SQL(query, args...)
+ session.statement.SQL(query, args...)
return session
}
// Where provides custom query condition.
func (session *Session) Where(query interface{}, args ...interface{}) *Session {
- session.Statement.Where(query, args...)
+ session.statement.Where(query, args...)
return session
}
// And provides custom query condition.
func (session *Session) And(query interface{}, args ...interface{}) *Session {
- session.Statement.And(query, args...)
+ session.statement.And(query, args...)
return session
}
// Or provides custom query condition.
func (session *Session) Or(query interface{}, args ...interface{}) *Session {
- session.Statement.Or(query, args...)
+ session.statement.Or(query, args...)
return session
}
@@ -48,23 +48,23 @@ func (session *Session) Id(id interface{}) *Session {
// ID provides converting id as a query condition
func (session *Session) ID(id interface{}) *Session {
- session.Statement.ID(id)
+ session.statement.ID(id)
return session
}
// In provides a query string like "id in (1, 2, 3)"
func (session *Session) In(column string, args ...interface{}) *Session {
- session.Statement.In(column, args...)
+ session.statement.In(column, args...)
return session
}
// NotIn provides a query string like "id in (1, 2, 3)"
func (session *Session) NotIn(column string, args ...interface{}) *Session {
- session.Statement.NotIn(column, args...)
+ session.statement.NotIn(column, args...)
return session
}
-// Conds returns session query conditions
+// Conds returns session query conditions except auto bean conditions
func (session *Session) Conds() builder.Cond {
- return session.Statement.cond
+ return session.statement.cond
}
diff --git a/vendor/github.com/go-xorm/xorm/session_convert.go b/vendor/github.com/go-xorm/xorm/session_convert.go
index 36ab465f5ee..1f9d8aa1bd0 100644
--- a/vendor/github.com/go-xorm/xorm/session_convert.go
+++ b/vendor/github.com/go-xorm/xorm/session_convert.go
@@ -23,41 +23,38 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti
var x time.Time
var err error
- if sdata == "0000-00-00 00:00:00" ||
- sdata == "0001-01-01 00:00:00" {
+ var parseLoc = session.engine.DatabaseTZ
+ if col.TimeZone != nil {
+ parseLoc = col.TimeZone
+ }
+
+ if sdata == zeroTime0 || sdata == zeroTime1 {
} else if !strings.ContainsAny(sdata, "- :") { // !nashtsai! has only found that mymysql driver is using this for time type column
// time stamp
sd, err := strconv.ParseInt(sdata, 10, 64)
if err == nil {
x = time.Unix(sd, 0)
- // !nashtsai! HACK mymysql driver is causing Local location being change to CHAT and cause wrong time conversion
- if col.TimeZone == nil {
- x = x.In(session.Engine.TZLocation)
- } else {
- x = x.In(col.TimeZone)
- }
- session.Engine.logger.Debugf("time(0) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
+ //session.engine.logger.Debugf("time(0) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
} else {
- session.Engine.logger.Debugf("time(0) err key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
+ //session.engine.logger.Debugf("time(0) err key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
}
} else if len(sdata) > 19 && strings.Contains(sdata, "-") {
- x, err = time.ParseInLocation(time.RFC3339Nano, sdata, session.Engine.TZLocation)
- session.Engine.logger.Debugf("time(1) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
+ x, err = time.ParseInLocation(time.RFC3339Nano, sdata, parseLoc)
+ session.engine.logger.Debugf("time(1) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
if err != nil {
- x, err = time.ParseInLocation("2006-01-02 15:04:05.999999999", sdata, session.Engine.TZLocation)
- session.Engine.logger.Debugf("time(2) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
+ x, err = time.ParseInLocation("2006-01-02 15:04:05.999999999", sdata, parseLoc)
+ //session.engine.logger.Debugf("time(2) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
}
if err != nil {
- x, err = time.ParseInLocation("2006-01-02 15:04:05.9999999 Z07:00", sdata, session.Engine.TZLocation)
- session.Engine.logger.Debugf("time(3) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
+ x, err = time.ParseInLocation("2006-01-02 15:04:05.9999999 Z07:00", sdata, parseLoc)
+ //session.engine.logger.Debugf("time(3) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
}
-
} else if len(sdata) == 19 && strings.Contains(sdata, "-") {
- x, err = time.ParseInLocation("2006-01-02 15:04:05", sdata, session.Engine.TZLocation)
- session.Engine.logger.Debugf("time(4) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
+ x, err = time.ParseInLocation("2006-01-02 15:04:05", sdata, parseLoc)
+ //session.engine.logger.Debugf("time(4) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
} else if len(sdata) == 10 && sdata[4] == '-' && sdata[7] == '-' {
- x, err = time.ParseInLocation("2006-01-02", sdata, session.Engine.TZLocation)
- session.Engine.logger.Debugf("time(5) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
+ x, err = time.ParseInLocation("2006-01-02", sdata, parseLoc)
+ //session.engine.logger.Debugf("time(5) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
} else if col.SQLType.Name == core.Time {
if strings.Contains(sdata, " ") {
ssd := strings.Split(sdata, " ")
@@ -65,13 +62,13 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti
}
sdata = strings.TrimSpace(sdata)
- if session.Engine.dialect.DBType() == core.MYSQL && len(sdata) > 8 {
+ if session.engine.dialect.DBType() == core.MYSQL && len(sdata) > 8 {
sdata = sdata[len(sdata)-8:]
}
st := fmt.Sprintf("2006-01-02 %v", sdata)
- x, err = time.ParseInLocation("2006-01-02 15:04:05", st, session.Engine.TZLocation)
- session.Engine.logger.Debugf("time(6) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
+ x, err = time.ParseInLocation("2006-01-02 15:04:05", st, parseLoc)
+ //session.engine.logger.Debugf("time(6) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
} else {
outErr = fmt.Errorf("unsupported time format %v", sdata)
return
@@ -80,7 +77,7 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti
outErr = fmt.Errorf("unsupported time format %v: %v", sdata, err)
return
}
- outTime = x
+ outTime = x.In(session.engine.TZLocation)
return
}
@@ -108,7 +105,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
if len(data) > 0 {
err := json.Unmarshal(data, x.Interface())
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
return err
}
fieldValue.Set(x.Elem())
@@ -122,7 +119,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
if len(data) > 0 {
err := json.Unmarshal(data, x.Interface())
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
return err
}
fieldValue.Set(x.Elem())
@@ -135,7 +132,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
if len(data) > 0 {
err := json.Unmarshal(data, x.Interface())
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
return err
}
fieldValue.Set(x.Elem())
@@ -147,8 +144,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
case reflect.String:
fieldValue.SetString(string(data))
case reflect.Bool:
- d := string(data)
- v, err := strconv.ParseBool(d)
+ v, err := asBool(data)
if err != nil {
return fmt.Errorf("arg %v as bool: %s", key, err.Error())
}
@@ -159,7 +155,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
var err error
// for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit &&
- session.Engine.dialect.DBType() == core.MYSQL { // !nashtsai! TODO dialect needs to provide conversion interface API
+ session.engine.dialect.DBType() == core.MYSQL { // !nashtsai! TODO dialect needs to provide conversion interface API
if len(data) == 1 {
x = int64(data[0])
} else {
@@ -207,41 +203,39 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
}
v = x
fieldValue.Set(reflect.ValueOf(v).Convert(fieldType))
- } else if session.Statement.UseCascade {
- table := session.Engine.autoMapType(*fieldValue)
- if table != nil {
- // TODO: current only support 1 primary key
- if len(table.PrimaryKeys) > 1 {
- panic("unsupported composited primary key cascade")
- }
- var pk = make(core.PK, len(table.PrimaryKeys))
- rawValueType := table.ColumnType(table.PKColumns()[0].FieldName)
- var err error
- pk[0], err = str2PK(string(data), rawValueType)
+ } else if session.statement.UseCascade {
+ table, err := session.engine.autoMapType(*fieldValue)
+ if err != nil {
+ return err
+ }
+
+ // TODO: current only support 1 primary key
+ if len(table.PrimaryKeys) > 1 {
+ return errors.New("unsupported composited primary key cascade")
+ }
+
+ var pk = make(core.PK, len(table.PrimaryKeys))
+ rawValueType := table.ColumnType(table.PKColumns()[0].FieldName)
+ pk[0], err = str2PK(string(data), rawValueType)
+ if err != nil {
+ return err
+ }
+
+ if !isPKZero(pk) {
+ // !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch
+ // however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne
+ // property to be fetched lazily
+ structInter := reflect.New(fieldValue.Type())
+ has, err := session.ID(pk).NoCascade().get(structInter.Interface())
if err != nil {
return err
}
-
- if !isPKZero(pk) {
- // !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch
- // however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne
- // property to be fetched lazily
- structInter := reflect.New(fieldValue.Type())
- newsession := session.Engine.NewSession()
- defer newsession.Close()
- has, err := newsession.Id(pk).NoCascade().Get(structInter.Interface())
- if err != nil {
- return err
- }
- if has {
- v = structInter.Elem().Interface()
- fieldValue.Set(reflect.ValueOf(v))
- } else {
- return errors.New("cascade obj is not exist")
- }
+ if has {
+ v = structInter.Elem().Interface()
+ fieldValue.Set(reflect.ValueOf(v))
+ } else {
+ return errors.New("cascade obj is not exist")
}
- } else {
- return fmt.Errorf("unsupported struct type in Scan: %s", fieldValue.Type().String())
}
}
}
@@ -267,7 +261,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
if len(data) > 0 {
err := json.Unmarshal(data, &x)
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
return err
}
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
@@ -278,7 +272,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
if len(data) > 0 {
err := json.Unmarshal(data, &x)
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
return err
}
fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
@@ -350,7 +344,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
var err error
// for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit &&
- strings.Contains(session.Engine.DriverName(), "mysql") {
+ strings.Contains(session.engine.DriverName(), "mysql") {
if len(data) == 1 {
x = int64(data[0])
} else {
@@ -375,7 +369,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
var err error
// for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit &&
- strings.Contains(session.Engine.DriverName(), "mysql") {
+ strings.Contains(session.engine.DriverName(), "mysql") {
if len(data) == 1 {
x = int(data[0])
} else {
@@ -403,7 +397,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
var err error
// for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit &&
- session.Engine.dialect.DBType() == core.MYSQL {
+ session.engine.dialect.DBType() == core.MYSQL {
if len(data) == 1 {
x = int32(data[0])
} else {
@@ -431,7 +425,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
var err error
// for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit &&
- strings.Contains(session.Engine.DriverName(), "mysql") {
+ strings.Contains(session.engine.DriverName(), "mysql") {
if len(data) == 1 {
x = int8(data[0])
} else {
@@ -459,7 +453,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
var err error
// for mysql, when use bit, it returned \x01
if col.SQLType.Name == core.Bit &&
- strings.Contains(session.Engine.DriverName(), "mysql") {
+ strings.Contains(session.engine.DriverName(), "mysql") {
if len(data) == 1 {
x = int16(data[0])
} else {
@@ -491,37 +485,37 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
v = x
fieldValue.Set(reflect.ValueOf(&x))
default:
- if session.Statement.UseCascade {
+ if session.statement.UseCascade {
structInter := reflect.New(fieldType.Elem())
- table := session.Engine.autoMapType(structInter.Elem())
- if table != nil {
- if len(table.PrimaryKeys) > 1 {
- panic("unsupported composited primary key cascade")
- }
- var pk = make(core.PK, len(table.PrimaryKeys))
- var err error
- rawValueType := table.ColumnType(table.PKColumns()[0].FieldName)
- pk[0], err = str2PK(string(data), rawValueType)
+ table, err := session.engine.autoMapType(structInter.Elem())
+ if err != nil {
+ return err
+ }
+
+ if len(table.PrimaryKeys) > 1 {
+ return errors.New("unsupported composited primary key cascade")
+ }
+
+ var pk = make(core.PK, len(table.PrimaryKeys))
+ rawValueType := table.ColumnType(table.PKColumns()[0].FieldName)
+ pk[0], err = str2PK(string(data), rawValueType)
+ if err != nil {
+ return err
+ }
+
+ if !isPKZero(pk) {
+ // !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch
+ // however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne
+ // property to be fetched lazily
+ has, err := session.ID(pk).NoCascade().get(structInter.Interface())
if err != nil {
return err
}
-
- if !isPKZero(pk) {
- // !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch
- // however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne
- // property to be fetched lazily
- newsession := session.Engine.NewSession()
- defer newsession.Close()
- has, err := newsession.Id(pk).NoCascade().Get(structInter.Interface())
- if err != nil {
- return err
- }
- if has {
- v = structInter.Interface()
- fieldValue.Set(reflect.ValueOf(v))
- } else {
- return errors.New("cascade obj is not exist")
- }
+ if has {
+ v = structInter.Interface()
+ fieldValue.Set(reflect.ValueOf(v))
+ } else {
+ return errors.New("cascade obj is not exist")
}
}
} else {
@@ -570,7 +564,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val
if fieldValue.IsNil() {
return nil, nil
} else if !fieldValue.IsValid() {
- session.Engine.logger.Warn("the field[", col.FieldName, "] is invalid")
+ session.engine.logger.Warn("the field[", col.FieldName, "] is invalid")
return nil, nil
} else {
// !nashtsai! deference pointer type to instance type
@@ -588,12 +582,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val
case reflect.Struct:
if fieldType.ConvertibleTo(core.TimeType) {
t := fieldValue.Convert(core.TimeType).Interface().(time.Time)
- if session.Engine.dialect.DBType() == core.MSSQL {
- if t.IsZero() {
- return nil, nil
- }
- }
- tf := session.Engine.FormatTime(col.SQLType.Name, t)
+ tf := session.engine.formatColTime(col, t)
return tf, nil
}
@@ -603,7 +592,10 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val
return v.Value()
}
- fieldTable := session.Engine.autoMapType(fieldValue)
+ fieldTable, err := session.engine.autoMapType(fieldValue)
+ if err != nil {
+ return nil, err
+ }
if len(fieldTable.PrimaryKeys) == 1 {
pkField := reflect.Indirect(fieldValue).FieldByName(fieldTable.PKColumns()[0].FieldName)
return pkField.Interface(), nil
@@ -614,14 +606,14 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val
if col.SQLType.IsText() {
bytes, err := json.Marshal(fieldValue.Interface())
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
return 0, err
}
return string(bytes), nil
} else if col.SQLType.IsBlob() {
bytes, err := json.Marshal(fieldValue.Interface())
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
return 0, err
}
return bytes, nil
@@ -630,7 +622,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val
case reflect.Complex64, reflect.Complex128:
bytes, err := json.Marshal(fieldValue.Interface())
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
return 0, err
}
return string(bytes), nil
@@ -642,7 +634,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val
if col.SQLType.IsText() {
bytes, err := json.Marshal(fieldValue.Interface())
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
return 0, err
}
return string(bytes), nil
@@ -655,7 +647,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val
} else {
bytes, err = json.Marshal(fieldValue.Interface())
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
return 0, err
}
}
diff --git a/vendor/github.com/go-xorm/xorm/session_delete.go b/vendor/github.com/go-xorm/xorm/session_delete.go
index 1c458fe1ea4..688b122ca6d 100644
--- a/vendor/github.com/go-xorm/xorm/session_delete.go
+++ b/vendor/github.com/go-xorm/xorm/session_delete.go
@@ -12,26 +12,26 @@ import (
"github.com/go-xorm/core"
)
-func (session *Session) cacheDelete(sqlStr string, args ...interface{}) error {
- if session.Statement.RefTable == nil ||
- session.Tx != nil {
+func (session *Session) cacheDelete(table *core.Table, tableName, sqlStr string, args ...interface{}) error {
+ if table == nil ||
+ session.tx != nil {
return ErrCacheFailed
}
- for _, filter := range session.Engine.dialect.Filters() {
- sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable)
+ for _, filter := range session.engine.dialect.Filters() {
+ sqlStr = filter.Do(sqlStr, session.engine.dialect, table)
}
- newsql := session.Statement.convertIDSQL(sqlStr)
+ newsql := session.statement.convertIDSQL(sqlStr)
if newsql == "" {
return ErrCacheFailed
}
- cacher := session.Engine.getCacher2(session.Statement.RefTable)
- tableName := session.Statement.TableName()
+ cacher := session.engine.getCacher2(table)
+ pkColumns := table.PKColumns()
ids, err := core.GetCacheSql(cacher, tableName, newsql, args)
if err != nil {
- resultsSlice, err := session.query(newsql, args...)
+ resultsSlice, err := session.queryBytes(newsql, args...)
if err != nil {
return err
}
@@ -40,7 +40,7 @@ func (session *Session) cacheDelete(sqlStr string, args ...interface{}) error {
for _, data := range resultsSlice {
var id int64
var pk core.PK = make([]interface{}, 0)
- for _, col := range session.Statement.RefTable.PKColumns() {
+ for _, col := range pkColumns {
if v, ok := data[col.Name]; !ok {
return errors.New("no id")
} else if col.SQLType.IsText() {
@@ -58,33 +58,30 @@ func (session *Session) cacheDelete(sqlStr string, args ...interface{}) error {
ids = append(ids, pk)
}
}
- } /*else {
- session.Engine.LogDebug("delete cache sql %v", newsql)
- cacher.DelIds(tableName, genSqlKey(newsql, args))
- }*/
+ }
for _, id := range ids {
- session.Engine.logger.Debug("[cacheDelete] delete cache obj", tableName, id)
+ session.engine.logger.Debug("[cacheDelete] delete cache obj:", tableName, id)
sid, err := id.ToString()
if err != nil {
return err
}
cacher.DelBean(tableName, sid)
}
- session.Engine.logger.Debug("[cacheDelete] clear cache sql", tableName)
+ session.engine.logger.Debug("[cacheDelete] clear cache table:", tableName)
cacher.ClearIds(tableName)
return nil
}
// Delete records, bean's non-empty fields are conditions
func (session *Session) Delete(bean interface{}) (int64, error) {
- defer session.resetStatement()
- if session.IsAutoClose {
+ if session.isAutoClose {
defer session.Close()
}
- session.Statement.setRefValue(rValue(bean))
- var table = session.Statement.RefTable
+ if err := session.statement.setRefValue(rValue(bean)); err != nil {
+ return 0, err
+ }
// handle before delete processors
for _, closure := range session.beforeClosures {
@@ -96,13 +93,17 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
processor.BeforeDelete()
}
- // --
- condSQL, condArgs, _ := session.Statement.genConds(bean)
- if len(condSQL) == 0 && session.Statement.LimitN == 0 {
+ condSQL, condArgs, err := session.statement.genConds(bean)
+ if err != nil {
+ return 0, err
+ }
+ if len(condSQL) == 0 && session.statement.LimitN == 0 {
return 0, ErrNeedDeletedCond
}
- var tableName = session.Engine.Quote(session.Statement.TableName())
+ var tableNameNoQuote = session.statement.TableName()
+ var tableName = session.engine.Quote(tableNameNoQuote)
+ var table = session.statement.RefTable
var deleteSQL string
if len(condSQL) > 0 {
deleteSQL = fmt.Sprintf("DELETE FROM %v WHERE %v", tableName, condSQL)
@@ -111,15 +112,15 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
}
var orderSQL string
- if len(session.Statement.OrderStr) > 0 {
- orderSQL += fmt.Sprintf(" ORDER BY %s", session.Statement.OrderStr)
+ if len(session.statement.OrderStr) > 0 {
+ orderSQL += fmt.Sprintf(" ORDER BY %s", session.statement.OrderStr)
}
- if session.Statement.LimitN > 0 {
- orderSQL += fmt.Sprintf(" LIMIT %d", session.Statement.LimitN)
+ if session.statement.LimitN > 0 {
+ orderSQL += fmt.Sprintf(" LIMIT %d", session.statement.LimitN)
}
if len(orderSQL) > 0 {
- switch session.Engine.dialect.DBType() {
+ switch session.engine.dialect.DBType() {
case core.POSTGRES:
inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL)
if len(condSQL) > 0 {
@@ -144,7 +145,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
var realSQL string
argsForCache := make([]interface{}, 0, len(condArgs)*2)
- if session.Statement.unscoped || table.DeletedColumn() == nil { // tag "deleted" is disabled
+ if session.statement.unscoped || table.DeletedColumn() == nil { // tag "deleted" is disabled
realSQL = deleteSQL
copy(argsForCache, condArgs)
argsForCache = append(condArgs, argsForCache...)
@@ -155,12 +156,12 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
deletedColumn := table.DeletedColumn()
realSQL = fmt.Sprintf("UPDATE %v SET %v = ? WHERE %v",
- session.Engine.Quote(session.Statement.TableName()),
- session.Engine.Quote(deletedColumn.Name),
+ session.engine.Quote(session.statement.TableName()),
+ session.engine.Quote(deletedColumn.Name),
condSQL)
if len(orderSQL) > 0 {
- switch session.Engine.dialect.DBType() {
+ switch session.engine.dialect.DBType() {
case core.POSTGRES:
inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL)
if len(condSQL) > 0 {
@@ -183,12 +184,12 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
}
}
- // !oinume! Insert NowTime to the head of session.Statement.Params
+ // !oinume! Insert nowTime to the head of session.statement.Params
condArgs = append(condArgs, "")
paramsLen := len(condArgs)
copy(condArgs[1:paramsLen], condArgs[0:paramsLen-1])
- val, t := session.Engine.NowTime2(deletedColumn.SQLType.Name)
+ val, t := session.engine.nowTime(deletedColumn)
condArgs[0] = val
var colName = deletedColumn.Name
@@ -198,17 +199,18 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
})
}
- if cacher := session.Engine.getCacher2(session.Statement.RefTable); cacher != nil && session.Statement.UseCache {
- session.cacheDelete(deleteSQL, argsForCache...)
+ if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache {
+ session.cacheDelete(table, tableNameNoQuote, deleteSQL, argsForCache...)
}
+ session.statement.RefTable = table
res, err := session.exec(realSQL, condArgs...)
if err != nil {
return 0, err
}
// handle after delete processors
- if session.IsAutoCommit {
+ if session.isAutoCommit {
for _, closure := range session.afterClosures {
closure(bean)
}
diff --git a/vendor/github.com/go-xorm/xorm/session_exist.go b/vendor/github.com/go-xorm/xorm/session_exist.go
new file mode 100644
index 00000000000..049c1ddff14
--- /dev/null
+++ b/vendor/github.com/go-xorm/xorm/session_exist.go
@@ -0,0 +1,77 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+
+ "github.com/go-xorm/builder"
+)
+
+// Exist returns true if the record exist otherwise return false
+func (session *Session) Exist(bean ...interface{}) (bool, error) {
+ if session.isAutoClose {
+ defer session.Close()
+ }
+
+ var sqlStr string
+ var args []interface{}
+ var err error
+
+ if session.statement.RawSQL == "" {
+ if len(bean) == 0 {
+ tableName := session.statement.TableName()
+ if len(tableName) <= 0 {
+ return false, ErrTableNotFound
+ }
+
+ if session.statement.cond.IsValid() {
+ condSQL, condArgs, err := builder.ToSQL(session.statement.cond)
+ if err != nil {
+ return false, err
+ }
+
+ sqlStr = fmt.Sprintf("SELECT * FROM %s WHERE %s LIMIT 1", tableName, condSQL)
+ args = condArgs
+ } else {
+ sqlStr = fmt.Sprintf("SELECT * FROM %s LIMIT 1", tableName)
+ args = []interface{}{}
+ }
+ } else {
+ beanValue := reflect.ValueOf(bean[0])
+ if beanValue.Kind() != reflect.Ptr {
+ return false, errors.New("needs a pointer")
+ }
+
+ if beanValue.Elem().Kind() == reflect.Struct {
+ if err := session.statement.setRefValue(beanValue.Elem()); err != nil {
+ return false, err
+ }
+ }
+
+ if len(session.statement.TableName()) <= 0 {
+ return false, ErrTableNotFound
+ }
+ session.statement.Limit(1)
+ sqlStr, args, err = session.statement.genGetSQL(bean[0])
+ if err != nil {
+ return false, err
+ }
+ }
+ } else {
+ sqlStr = session.statement.RawSQL
+ args = session.statement.RawParams
+ }
+
+ rows, err := session.queryRows(sqlStr, args...)
+ if err != nil {
+ return false, err
+ }
+ defer rows.Close()
+
+ return rows.Next(), nil
+}
diff --git a/vendor/github.com/go-xorm/xorm/session_find.go b/vendor/github.com/go-xorm/xorm/session_find.go
index 748e319f793..f95dcfef2cb 100644
--- a/vendor/github.com/go-xorm/xorm/session_find.go
+++ b/vendor/github.com/go-xorm/xorm/session_find.go
@@ -8,7 +8,6 @@ import (
"errors"
"fmt"
"reflect"
- "strconv"
"strings"
"github.com/go-xorm/builder"
@@ -24,11 +23,13 @@ const (
// are conditions. beans could be []Struct, []*Struct, map[int64]Struct
// map[int64]*Struct
func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) error {
- defer session.resetStatement()
- if session.IsAutoClose {
+ if session.isAutoClose {
defer session.Close()
}
+ return session.find(rowsSlicePtr, condiBean...)
+}
+func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) error {
sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr))
if sliceValue.Kind() != reflect.Slice && sliceValue.Kind() != reflect.Map {
return errors.New("needs a pointer to a slice or a map")
@@ -37,77 +38,79 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{})
sliceElementType := sliceValue.Type().Elem()
var tp = tpStruct
- if session.Statement.RefTable == nil {
+ if session.statement.RefTable == nil {
if sliceElementType.Kind() == reflect.Ptr {
if sliceElementType.Elem().Kind() == reflect.Struct {
pv := reflect.New(sliceElementType.Elem())
- session.Statement.setRefValue(pv.Elem())
+ if err := session.statement.setRefValue(pv.Elem()); err != nil {
+ return err
+ }
} else {
tp = tpNonStruct
}
} else if sliceElementType.Kind() == reflect.Struct {
pv := reflect.New(sliceElementType)
- session.Statement.setRefValue(pv.Elem())
+ if err := session.statement.setRefValue(pv.Elem()); err != nil {
+ return err
+ }
} else {
tp = tpNonStruct
}
}
- var table = session.Statement.RefTable
+ var table = session.statement.RefTable
- var addedTableName = (len(session.Statement.JoinStr) > 0)
+ var addedTableName = (len(session.statement.JoinStr) > 0)
var autoCond builder.Cond
if tp == tpStruct {
- if !session.Statement.noAutoCondition && len(condiBean) > 0 {
+ if !session.statement.noAutoCondition && len(condiBean) > 0 {
var err error
- autoCond, err = session.Statement.buildConds(table, condiBean[0], true, true, false, true, addedTableName)
+ autoCond, err = session.statement.buildConds(table, condiBean[0], true, true, false, true, addedTableName)
if err != nil {
- panic(err)
+ return err
}
} else {
// !oinume! Add " IS NULL" to WHERE whatever condiBean is given.
// See https://github.com/go-xorm/xorm/issues/179
- if col := table.DeletedColumn(); col != nil && !session.Statement.unscoped { // tag "deleted" is enabled
- var colName = session.Engine.Quote(col.Name)
+ if col := table.DeletedColumn(); col != nil && !session.statement.unscoped { // tag "deleted" is enabled
+ var colName = session.engine.Quote(col.Name)
if addedTableName {
- var nm = session.Statement.TableName()
- if len(session.Statement.TableAlias) > 0 {
- nm = session.Statement.TableAlias
+ var nm = session.statement.TableName()
+ if len(session.statement.TableAlias) > 0 {
+ nm = session.statement.TableAlias
}
- colName = session.Engine.Quote(nm) + "." + colName
- }
- if session.Engine.dialect.DBType() == core.MSSQL {
- autoCond = builder.IsNull{colName}
- } else {
- autoCond = builder.IsNull{colName}.Or(builder.Eq{colName: "0001-01-01 00:00:00"})
+ colName = session.engine.Quote(nm) + "." + colName
}
+
+ autoCond = session.engine.CondDeleted(colName)
}
}
}
var sqlStr string
var args []interface{}
- if session.Statement.RawSQL == "" {
- if len(session.Statement.TableName()) <= 0 {
+ var err error
+ if session.statement.RawSQL == "" {
+ if len(session.statement.TableName()) <= 0 {
return ErrTableNotFound
}
- var columnStr = session.Statement.ColumnStr
- if len(session.Statement.selectStr) > 0 {
- columnStr = session.Statement.selectStr
+ var columnStr = session.statement.ColumnStr
+ if len(session.statement.selectStr) > 0 {
+ columnStr = session.statement.selectStr
} else {
- if session.Statement.JoinStr == "" {
+ if session.statement.JoinStr == "" {
if columnStr == "" {
- if session.Statement.GroupByStr != "" {
- columnStr = session.Statement.Engine.Quote(strings.Replace(session.Statement.GroupByStr, ",", session.Engine.Quote(","), -1))
+ if session.statement.GroupByStr != "" {
+ columnStr = session.statement.Engine.Quote(strings.Replace(session.statement.GroupByStr, ",", session.engine.Quote(","), -1))
} else {
- columnStr = session.Statement.genColumnStr()
+ columnStr = session.statement.genColumnStr()
}
}
} else {
if columnStr == "" {
- if session.Statement.GroupByStr != "" {
- columnStr = session.Statement.Engine.Quote(strings.Replace(session.Statement.GroupByStr, ",", session.Engine.Quote(","), -1))
+ if session.statement.GroupByStr != "" {
+ columnStr = session.statement.Engine.Quote(strings.Replace(session.statement.GroupByStr, ",", session.engine.Quote(","), -1))
} else {
columnStr = "*"
}
@@ -118,31 +121,37 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{})
}
}
- condSQL, condArgs, _ := builder.ToSQL(session.Statement.cond.And(autoCond))
+ session.statement.cond = session.statement.cond.And(autoCond)
+ condSQL, condArgs, err := builder.ToSQL(session.statement.cond)
+ if err != nil {
+ return err
+ }
- args = append(session.Statement.joinArgs, condArgs...)
- sqlStr = session.Statement.genSelectSQL(columnStr, condSQL)
+ args = append(session.statement.joinArgs, condArgs...)
+ sqlStr, err = session.statement.genSelectSQL(columnStr, condSQL)
+ if err != nil {
+ return err
+ }
// for mssql and use limit
qs := strings.Count(sqlStr, "?")
if len(args)*2 == qs {
args = append(args, args...)
}
} else {
- sqlStr = session.Statement.RawSQL
- args = session.Statement.RawParams
+ sqlStr = session.statement.RawSQL
+ args = session.statement.RawParams
}
- var err error
if session.canCache() {
- if cacher := session.Engine.getCacher2(table); cacher != nil &&
- !session.Statement.IsDistinct &&
- !session.Statement.unscoped {
+ if cacher := session.engine.getCacher2(table); cacher != nil &&
+ !session.statement.IsDistinct &&
+ !session.statement.unscoped {
err = session.cacheFind(sliceElementType, sqlStr, rowsSlicePtr, args...)
if err != ErrCacheFailed {
return err
}
err = nil // !nashtsai! reset err to nil for ErrCacheFailed
- session.Engine.logger.Warn("Cache Find Failed")
+ session.engine.logger.Warn("Cache Find Failed")
}
}
@@ -150,21 +159,13 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{})
}
func (session *Session) noCacheFind(table *core.Table, containerValue reflect.Value, sqlStr string, args ...interface{}) error {
- var rawRows *core.Rows
- var err error
-
- session.queryPreprocess(&sqlStr, args...)
- if session.IsAutoCommit {
- _, rawRows, err = session.innerQuery(sqlStr, args...)
- } else {
- rawRows, err = session.Tx.Query(sqlStr, args...)
- }
+ rows, err := session.queryRows(sqlStr, args...)
if err != nil {
return err
}
- defer rawRows.Close()
+ defer rows.Close()
- fields, err := rawRows.Columns()
+ fields, err := rows.Columns()
if err != nil {
return err
}
@@ -234,20 +235,29 @@ func (session *Session) noCacheFind(table *core.Table, containerValue reflect.Va
if elemType.Kind() == reflect.Struct {
var newValue = newElemFunc(fields)
dataStruct := rValue(newValue.Interface())
- return session.rows2Beans(rawRows, fields, len(fields), session.Engine.autoMapType(dataStruct), newElemFunc, containerValueSetFunc)
+ tb, err := session.engine.autoMapType(dataStruct)
+ if err != nil {
+ return err
+ }
+ err = session.rows2Beans(rows, fields, tb, newElemFunc, containerValueSetFunc)
+ rows.Close()
+ if err != nil {
+ return err
+ }
+ return session.executeProcessors()
}
- for rawRows.Next() {
+ for rows.Next() {
var newValue = newElemFunc(fields)
bean := newValue.Interface()
switch elemType.Kind() {
case reflect.Slice:
- err = rawRows.ScanSlice(bean)
+ err = rows.ScanSlice(bean)
case reflect.Map:
- err = rawRows.ScanMap(bean)
+ err = rows.ScanMap(bean)
default:
- err = rawRows.Scan(bean)
+ err = rows.Scan(bean)
}
if err != nil {
@@ -278,22 +288,21 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
return ErrCacheFailed
}
- for _, filter := range session.Engine.dialect.Filters() {
- sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable)
+ for _, filter := range session.engine.dialect.Filters() {
+ sqlStr = filter.Do(sqlStr, session.engine.dialect, session.statement.RefTable)
}
- newsql := session.Statement.convertIDSQL(sqlStr)
+ newsql := session.statement.convertIDSQL(sqlStr)
if newsql == "" {
return ErrCacheFailed
}
- tableName := session.Statement.TableName()
-
- table := session.Statement.RefTable
- cacher := session.Engine.getCacher2(table)
+ tableName := session.statement.TableName()
+ table := session.statement.RefTable
+ cacher := session.engine.getCacher2(table)
ids, err := core.GetCacheSql(cacher, tableName, newsql, args)
if err != nil {
- rows, err := session.DB().Query(newsql, args...)
+ rows, err := session.queryRows(newsql, args...)
if err != nil {
return err
}
@@ -304,7 +313,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
for rows.Next() {
i++
if i > 500 {
- session.Engine.logger.Debug("[cacheFind] ids length > 500, no cache")
+ session.engine.logger.Debug("[cacheFind] ids length > 500, no cache")
return ErrCacheFailed
}
var res = make([]string, len(table.PrimaryKeys))
@@ -312,32 +321,24 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
if err != nil {
return err
}
-
var pk core.PK = make([]interface{}, len(table.PrimaryKeys))
for i, col := range table.PKColumns() {
- if col.SQLType.IsNumeric() {
- n, err := strconv.ParseInt(res[i], 10, 64)
- if err != nil {
- return err
- }
- pk[i] = n
- } else if col.SQLType.IsText() {
- pk[i] = res[i]
- } else {
- return errors.New("not supported")
+ pk[i], err = session.engine.idTypeAssertion(col, res[i])
+ if err != nil {
+ return err
}
}
ids = append(ids, pk)
}
- session.Engine.logger.Debug("[cacheFind] cache sql:", ids, tableName, newsql, args)
+ session.engine.logger.Debug("[cacheFind] cache sql:", ids, tableName, sqlStr, newsql, args)
err = core.PutCacheSql(cacher, ids, tableName, newsql, args)
if err != nil {
return err
}
} else {
- session.Engine.logger.Debug("[cacheFind] cache hit sql:", newsql, args)
+ session.engine.logger.Debug("[cacheFind] cache hit sql:", tableName, sqlStr, newsql, args)
}
sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr))
@@ -352,20 +353,20 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
return err
}
bean := cacher.GetBean(tableName, sid)
- if bean == nil {
+ if bean == nil || reflect.ValueOf(bean).Elem().Type() != t {
ides = append(ides, id)
ididxes[sid] = idx
} else {
- session.Engine.logger.Debug("[cacheFind] cache hit bean:", tableName, id, bean)
+ session.engine.logger.Debug("[cacheFind] cache hit bean:", tableName, id, bean)
- pk := session.Engine.IdOf(bean)
+ pk := session.engine.IdOf(bean)
xid, err := pk.ToString()
if err != nil {
return err
}
if sid != xid {
- session.Engine.logger.Error("[cacheFind] error cache", xid, sid, bean)
+ session.engine.logger.Error("[cacheFind] error cache", xid, sid, bean)
return ErrCacheFailed
}
temps[idx] = bean
@@ -373,9 +374,6 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
}
if len(ides) > 0 {
- newSession := session.Engine.NewSession()
- defer newSession.Close()
-
slices := reflect.New(reflect.SliceOf(t))
beans := slices.Interface()
@@ -385,18 +383,18 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
ff = append(ff, ie[0])
}
- newSession.In("`"+table.PrimaryKeys[0]+"`", ff...)
+ session.In("`"+table.PrimaryKeys[0]+"`", ff...)
} else {
for _, ie := range ides {
cond := builder.NewCond()
for i, name := range table.PrimaryKeys {
cond = cond.And(builder.Eq{"`" + name + "`": ie[i]})
}
- newSession.Or(cond)
+ session.Or(cond)
}
}
- err = newSession.NoCache().Find(beans)
+ err = session.NoCache().Table(tableName).find(beans)
if err != nil {
return err
}
@@ -407,7 +405,10 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
if rv.Kind() != reflect.Ptr {
rv = rv.Addr()
}
- id := session.Engine.IdOfV(rv)
+ id, err := session.engine.idOfV(rv)
+ if err != nil {
+ return err
+ }
sid, err := id.ToString()
if err != nil {
return err
@@ -415,7 +416,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
bean := rv.Interface()
temps[ididxes[sid]] = bean
- session.Engine.logger.Debug("[cacheFind] cache bean:", tableName, id, bean, temps)
+ session.engine.logger.Debug("[cacheFind] cache bean:", tableName, id, bean, temps)
cacher.PutBean(tableName, sid, bean)
}
}
@@ -423,7 +424,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
for j := 0; j < len(temps); j++ {
bean := temps[j]
if bean == nil {
- session.Engine.logger.Warn("[cacheFind] cache no hit:", tableName, ids[j], temps)
+ session.engine.logger.Warn("[cacheFind] cache no hit:", tableName, ids[j], temps)
// return errors.New("cache error") // !nashtsai! no need to return error, but continue instead
continue
}
diff --git a/vendor/github.com/go-xorm/xorm/session_get.go b/vendor/github.com/go-xorm/xorm/session_get.go
index 0c78ed94835..8faf53c02c7 100644
--- a/vendor/github.com/go-xorm/xorm/session_get.go
+++ b/vendor/github.com/go-xorm/xorm/session_get.go
@@ -15,42 +15,49 @@ import (
// Get retrieve one record from database, bean's non-empty fields
// will be as conditions
func (session *Session) Get(bean interface{}) (bool, error) {
- defer session.resetStatement()
- if session.IsAutoClose {
+ if session.isAutoClose {
defer session.Close()
}
+ return session.get(bean)
+}
+func (session *Session) get(bean interface{}) (bool, error) {
beanValue := reflect.ValueOf(bean)
if beanValue.Kind() != reflect.Ptr {
- return false, errors.New("needs a pointer to a struct")
- }
-
- // FIXME: remove this after support non-struct Get
- if beanValue.Elem().Kind() != reflect.Struct {
- return false, errors.New("needs a pointer to a struct")
+ return false, errors.New("needs a pointer to a value")
+ } else if beanValue.Elem().Kind() == reflect.Ptr {
+ return false, errors.New("a pointer to a pointer is not allowed")
}
if beanValue.Elem().Kind() == reflect.Struct {
- session.Statement.setRefValue(beanValue.Elem())
+ if err := session.statement.setRefValue(beanValue.Elem()); err != nil {
+ return false, err
+ }
}
var sqlStr string
var args []interface{}
+ var err error
- if session.Statement.RawSQL == "" {
- if len(session.Statement.TableName()) <= 0 {
+ if session.statement.RawSQL == "" {
+ if len(session.statement.TableName()) <= 0 {
return false, ErrTableNotFound
}
- session.Statement.Limit(1)
- sqlStr, args = session.Statement.genGetSQL(bean)
+ session.statement.Limit(1)
+ sqlStr, args, err = session.statement.genGetSQL(bean)
+ if err != nil {
+ return false, err
+ }
} else {
- sqlStr = session.Statement.RawSQL
- args = session.Statement.RawParams
+ sqlStr = session.statement.RawSQL
+ args = session.statement.RawParams
}
- if session.canCache() {
- if cacher := session.Engine.getCacher2(session.Statement.RefTable); cacher != nil &&
- !session.Statement.unscoped {
+ table := session.statement.RefTable
+
+ if session.canCache() && beanValue.Elem().Kind() == reflect.Struct {
+ if cacher := session.engine.getCacher2(table); cacher != nil &&
+ !session.statement.unscoped {
has, err := session.cacheGet(bean, sqlStr, args...)
if err != ErrCacheFailed {
return has, err
@@ -58,47 +65,51 @@ func (session *Session) Get(bean interface{}) (bool, error) {
}
}
- return session.nocacheGet(beanValue.Elem().Kind(), bean, sqlStr, args...)
+ return session.nocacheGet(beanValue.Elem().Kind(), table, bean, sqlStr, args...)
}
-func (session *Session) nocacheGet(beanKind reflect.Kind, bean interface{}, sqlStr string, args ...interface{}) (bool, error) {
- var rawRows *core.Rows
- var err error
- session.queryPreprocess(&sqlStr, args...)
- if session.IsAutoCommit {
- _, rawRows, err = session.innerQuery(sqlStr, args...)
- } else {
- rawRows, err = session.Tx.Query(sqlStr, args...)
- }
+func (session *Session) nocacheGet(beanKind reflect.Kind, table *core.Table, bean interface{}, sqlStr string, args ...interface{}) (bool, error) {
+ rows, err := session.queryRows(sqlStr, args...)
if err != nil {
return false, err
}
+ defer rows.Close()
- defer rawRows.Close()
+ if !rows.Next() {
+ return false, nil
+ }
- if rawRows.Next() {
- fields, err := rawRows.Columns()
+ switch beanKind {
+ case reflect.Struct:
+ fields, err := rows.Columns()
if err != nil {
- // WARN: Alougth rawRows return true, but get fields failed
+ // WARN: Alougth rows return true, but get fields failed
return true, err
}
- switch beanKind {
- case reflect.Struct:
- dataStruct := rValue(bean)
- session.Statement.setRefValue(dataStruct)
- _, err = session.row2Bean(rawRows, fields, len(fields), bean, &dataStruct, session.Statement.RefTable)
- case reflect.Slice:
- err = rawRows.ScanSlice(bean)
- case reflect.Map:
- err = rawRows.ScanMap(bean)
- default:
- err = rawRows.Scan(bean)
+ scanResults, err := session.row2Slice(rows, fields, bean)
+ if err != nil {
+ return false, err
+ }
+ // close it before covert data
+ rows.Close()
+
+ dataStruct := rValue(bean)
+ _, err = session.slice2Bean(scanResults, fields, bean, &dataStruct, table)
+ if err != nil {
+ return true, err
}
- return true, err
+ return true, session.executeProcessors()
+ case reflect.Slice:
+ err = rows.ScanSlice(bean)
+ case reflect.Map:
+ err = rows.ScanMap(bean)
+ default:
+ err = rows.Scan(bean)
}
- return false, nil
+
+ return true, err
}
func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interface{}) (has bool, err error) {
@@ -107,22 +118,22 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf
return false, ErrCacheFailed
}
- for _, filter := range session.Engine.dialect.Filters() {
- sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable)
+ for _, filter := range session.engine.dialect.Filters() {
+ sqlStr = filter.Do(sqlStr, session.engine.dialect, session.statement.RefTable)
}
- newsql := session.Statement.convertIDSQL(sqlStr)
+ newsql := session.statement.convertIDSQL(sqlStr)
if newsql == "" {
return false, ErrCacheFailed
}
- cacher := session.Engine.getCacher2(session.Statement.RefTable)
- tableName := session.Statement.TableName()
- session.Engine.logger.Debug("[cacheGet] find sql:", newsql, args)
+ cacher := session.engine.getCacher2(session.statement.RefTable)
+ tableName := session.statement.TableName()
+ session.engine.logger.Debug("[cacheGet] find sql:", newsql, args)
+ table := session.statement.RefTable
ids, err := core.GetCacheSql(cacher, tableName, newsql, args)
- table := session.Statement.RefTable
if err != nil {
var res = make([]string, len(table.PrimaryKeys))
- rows, err := session.DB().Query(newsql, args...)
+ rows, err := session.NoCache().queryRows(newsql, args...)
if err != nil {
return false, err
}
@@ -153,19 +164,19 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf
}
ids = []core.PK{pk}
- session.Engine.logger.Debug("[cacheGet] cache ids:", newsql, ids)
+ session.engine.logger.Debug("[cacheGet] cache ids:", newsql, ids)
err = core.PutCacheSql(cacher, ids, tableName, newsql, args)
if err != nil {
return false, err
}
} else {
- session.Engine.logger.Debug("[cacheGet] cache hit sql:", newsql)
+ session.engine.logger.Debug("[cacheGet] cache hit sql:", newsql, ids)
}
if len(ids) > 0 {
structValue := reflect.Indirect(reflect.ValueOf(bean))
id := ids[0]
- session.Engine.logger.Debug("[cacheGet] get bean:", tableName, id)
+ session.engine.logger.Debug("[cacheGet] get bean:", tableName, id)
sid, err := id.ToString()
if err != nil {
return false, err
@@ -173,15 +184,15 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf
cacheBean := cacher.GetBean(tableName, sid)
if cacheBean == nil {
cacheBean = bean
- has, err = session.nocacheGet(reflect.Struct, cacheBean, sqlStr, args...)
+ has, err = session.nocacheGet(reflect.Struct, table, cacheBean, sqlStr, args...)
if err != nil || !has {
return has, err
}
- session.Engine.logger.Debug("[cacheGet] cache bean:", tableName, id, cacheBean)
+ session.engine.logger.Debug("[cacheGet] cache bean:", tableName, id, cacheBean)
cacher.PutBean(tableName, sid, cacheBean)
} else {
- session.Engine.logger.Debug("[cacheGet] cache hit bean:", tableName, id, cacheBean)
+ session.engine.logger.Debug("[cacheGet] cache hit bean:", tableName, id, cacheBean)
has = true
}
structValue.Set(reflect.Indirect(reflect.ValueOf(cacheBean)))
diff --git a/vendor/github.com/go-xorm/xorm/session_insert.go b/vendor/github.com/go-xorm/xorm/session_insert.go
index 5b607b1fecb..129ee23098a 100644
--- a/vendor/github.com/go-xorm/xorm/session_insert.go
+++ b/vendor/github.com/go-xorm/xorm/session_insert.go
@@ -19,17 +19,16 @@ func (session *Session) Insert(beans ...interface{}) (int64, error) {
var affected int64
var err error
- if session.IsAutoClose {
+ if session.isAutoClose {
defer session.Close()
}
- defer session.resetStatement()
for _, bean := range beans {
sliceValue := reflect.Indirect(reflect.ValueOf(bean))
if sliceValue.Kind() == reflect.Slice {
size := sliceValue.Len()
if size > 0 {
- if session.Engine.SupportInsertMany() {
+ if session.engine.SupportInsertMany() {
cnt, err := session.innerInsertMulti(bean)
if err != nil {
return affected, err
@@ -67,13 +66,15 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
return 0, errors.New("could not insert a empty slice")
}
- session.Statement.setRefValue(sliceValue.Index(0))
+ if err := session.statement.setRefValue(reflect.ValueOf(sliceValue.Index(0).Interface())); err != nil {
+ return 0, err
+ }
- if len(session.Statement.TableName()) <= 0 {
+ if len(session.statement.TableName()) <= 0 {
return 0, ErrTableNotFound
}
- table := session.Statement.RefTable
+ table := session.statement.RefTable
size := sliceValue.Len()
var colNames []string
@@ -114,18 +115,18 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
if col.IsDeleted {
continue
}
- if session.Statement.ColumnStr != "" {
- if _, ok := getFlagForColumn(session.Statement.columnMap, col); !ok {
+ if session.statement.ColumnStr != "" {
+ if _, ok := getFlagForColumn(session.statement.columnMap, col); !ok {
continue
}
}
- if session.Statement.OmitStr != "" {
- if _, ok := getFlagForColumn(session.Statement.columnMap, col); ok {
+ if session.statement.OmitStr != "" {
+ if _, ok := getFlagForColumn(session.statement.columnMap, col); ok {
continue
}
}
- if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime {
- val, t := session.Engine.NowTime2(col.SQLType.Name)
+ if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime {
+ val, t := session.engine.nowTime(col)
args = append(args, val)
var colName = col.Name
@@ -133,7 +134,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
col := table.GetColumn(colName)
setColumnTime(bean, col, t)
})
- } else if col.IsVersion && session.Statement.checkVersion {
+ } else if col.IsVersion && session.statement.checkVersion {
args = append(args, 1)
var colName = col.Name
session.afterClosures = append(session.afterClosures, func(bean interface{}) {
@@ -169,18 +170,18 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
if col.IsDeleted {
continue
}
- if session.Statement.ColumnStr != "" {
- if _, ok := getFlagForColumn(session.Statement.columnMap, col); !ok {
+ if session.statement.ColumnStr != "" {
+ if _, ok := getFlagForColumn(session.statement.columnMap, col); !ok {
continue
}
}
- if session.Statement.OmitStr != "" {
- if _, ok := getFlagForColumn(session.Statement.columnMap, col); ok {
+ if session.statement.OmitStr != "" {
+ if _, ok := getFlagForColumn(session.statement.columnMap, col); ok {
continue
}
}
- if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime {
- val, t := session.Engine.NowTime2(col.SQLType.Name)
+ if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime {
+ val, t := session.engine.nowTime(col)
args = append(args, val)
var colName = col.Name
@@ -188,7 +189,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
col := table.GetColumn(colName)
setColumnTime(bean, col, t)
})
- } else if col.IsVersion && session.Statement.checkVersion {
+ } else if col.IsVersion && session.statement.checkVersion {
args = append(args, 1)
var colName = col.Name
session.afterClosures = append(session.afterClosures, func(bean interface{}) {
@@ -212,25 +213,26 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
var sql = "INSERT INTO %s (%v%v%v) VALUES (%v)"
var statement string
- if session.Engine.dialect.DBType() == core.ORACLE {
+ var tableName = session.statement.TableName()
+ if session.engine.dialect.DBType() == core.ORACLE {
sql = "INSERT ALL INTO %s (%v%v%v) VALUES (%v) SELECT 1 FROM DUAL"
temp := fmt.Sprintf(") INTO %s (%v%v%v) VALUES (",
- session.Engine.Quote(session.Statement.TableName()),
- session.Engine.QuoteStr(),
- strings.Join(colNames, session.Engine.QuoteStr() + ", " + session.Engine.QuoteStr()),
- session.Engine.QuoteStr())
+ session.engine.Quote(tableName),
+ session.engine.QuoteStr(),
+ strings.Join(colNames, session.engine.QuoteStr()+", "+session.engine.QuoteStr()),
+ session.engine.QuoteStr())
statement = fmt.Sprintf(sql,
- session.Engine.Quote(session.Statement.TableName()),
- session.Engine.QuoteStr(),
- strings.Join(colNames, session.Engine.QuoteStr() + ", " + session.Engine.QuoteStr()),
- session.Engine.QuoteStr(),
+ session.engine.Quote(tableName),
+ session.engine.QuoteStr(),
+ strings.Join(colNames, session.engine.QuoteStr()+", "+session.engine.QuoteStr()),
+ session.engine.QuoteStr(),
strings.Join(colMultiPlaces, temp))
} else {
statement = fmt.Sprintf(sql,
- session.Engine.Quote(session.Statement.TableName()),
- session.Engine.QuoteStr(),
- strings.Join(colNames, session.Engine.QuoteStr() + ", " + session.Engine.QuoteStr()),
- session.Engine.QuoteStr(),
+ session.engine.Quote(tableName),
+ session.engine.QuoteStr(),
+ strings.Join(colNames, session.engine.QuoteStr()+", "+session.engine.QuoteStr()),
+ session.engine.QuoteStr(),
strings.Join(colMultiPlaces, "),("))
}
res, err := session.exec(statement, args...)
@@ -238,8 +240,8 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
return 0, err
}
- if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache {
- session.cacheInsert(session.Statement.TableName())
+ if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache {
+ session.cacheInsert(table, tableName)
}
lenAfterClosures := len(session.afterClosures)
@@ -247,7 +249,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
elemValue := reflect.Indirect(sliceValue.Index(i)).Addr().Interface()
// handle AfterInsertProcessor
- if session.IsAutoCommit {
+ if session.isAutoCommit {
// !nashtsai! does user expect it's same slice to passed closure when using Before()/After() when insert multi??
for _, closure := range session.afterClosures {
closure(elemValue)
@@ -278,8 +280,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
// InsertMulti insert multiple records
func (session *Session) InsertMulti(rowsSlicePtr interface{}) (int64, error) {
- defer session.resetStatement()
- if session.IsAutoClose {
+ if session.isAutoClose {
defer session.Close()
}
@@ -297,12 +298,14 @@ func (session *Session) InsertMulti(rowsSlicePtr interface{}) (int64, error) {
}
func (session *Session) innerInsert(bean interface{}) (int64, error) {
- session.Statement.setRefValue(rValue(bean))
- if len(session.Statement.TableName()) <= 0 {
+ if err := session.statement.setRefValue(rValue(bean)); err != nil {
+ return 0, err
+ }
+ if len(session.statement.TableName()) <= 0 {
return 0, ErrTableNotFound
}
- table := session.Statement.RefTable
+ table := session.statement.RefTable
// handle BeforeInsertProcessor
for _, closure := range session.beforeClosures {
@@ -314,19 +317,19 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
processor.BeforeInsert()
}
// --
- colNames, args, err := genCols(session.Statement.RefTable, session, bean, false, false)
+ colNames, args, err := genCols(session.statement.RefTable, session, bean, false, false)
if err != nil {
return 0, err
}
// insert expr columns, override if exists
- exprColumns := session.Statement.getExpr()
+ exprColumns := session.statement.getExpr()
exprColVals := make([]string, 0, len(exprColumns))
for _, v := range exprColumns {
// remove the expr columns
for i, colName := range colNames {
if colName == v.colName {
- colNames = append(colNames[:i], colNames[i + 1:]...)
- args = append(args[:i], args[i + 1:]...)
+ colNames = append(colNames[:i], colNames[i+1:]...)
+ args = append(args[:i], args[i+1:]...)
}
}
@@ -335,22 +338,34 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
exprColVals = append(exprColVals, v.expr)
}
- colPlaces := strings.Repeat("?, ", len(colNames) - len(exprColumns))
+ colPlaces := strings.Repeat("?, ", len(colNames)-len(exprColumns))
if len(exprColVals) > 0 {
colPlaces = colPlaces + strings.Join(exprColVals, ", ")
} else {
- colPlaces = colPlaces[0 : len(colPlaces) - 2]
+ if len(colPlaces) > 0 {
+ colPlaces = colPlaces[0 : len(colPlaces)-2]
+ }
}
- sqlStr := fmt.Sprintf("INSERT INTO %s (%v%v%v) VALUES (%v)",
- session.Engine.Quote(session.Statement.TableName()),
- session.Engine.QuoteStr(),
- strings.Join(colNames, session.Engine.Quote(", ")),
- session.Engine.QuoteStr(),
- colPlaces)
+ var sqlStr string
+ var tableName = session.statement.TableName()
+ if len(colPlaces) > 0 {
+ sqlStr = fmt.Sprintf("INSERT INTO %s (%v%v%v) VALUES (%v)",
+ session.engine.Quote(tableName),
+ session.engine.QuoteStr(),
+ strings.Join(colNames, session.engine.Quote(", ")),
+ session.engine.QuoteStr(),
+ colPlaces)
+ } else {
+ if session.engine.dialect.DBType() == core.MYSQL {
+ sqlStr = fmt.Sprintf("INSERT INTO %s VALUES ()", session.engine.Quote(tableName))
+ } else {
+ sqlStr = fmt.Sprintf("INSERT INTO %s DEFAULT VALUES", session.engine.Quote(tableName))
+ }
+ }
handleAfterInsertProcessorFunc := func(bean interface{}) {
- if session.IsAutoCommit {
+ if session.isAutoCommit {
for _, closure := range session.afterClosures {
closure(bean)
}
@@ -379,23 +394,22 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
// for postgres, many of them didn't implement lastInsertId, so we should
// implemented it ourself.
- if session.Engine.dialect.DBType() == core.ORACLE && len(table.AutoIncrement) > 0 {
- //assert table.AutoIncrement != ""
- res, err := session.query("select seq_atable.currval from dual", args...)
+ if session.engine.dialect.DBType() == core.ORACLE && len(table.AutoIncrement) > 0 {
+ res, err := session.queryBytes("select seq_atable.currval from dual", args...)
if err != nil {
return 0, err
}
- handleAfterInsertProcessorFunc(bean)
+ defer handleAfterInsertProcessorFunc(bean)
- if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache {
- session.cacheInsert(session.Statement.TableName())
+ if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache {
+ session.cacheInsert(table, tableName)
}
- if table.Version != "" && session.Statement.checkVersion {
+ if table.Version != "" && session.statement.checkVersion {
verValue, err := table.VersionColumn().ValueOf(bean)
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
} else if verValue.IsValid() && verValue.CanSet() {
verValue.SetInt(1)
}
@@ -413,7 +427,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
aiValue, err := table.AutoIncrColumn().ValueOf(bean)
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
}
if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() {
@@ -423,24 +437,24 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
aiValue.Set(int64ToIntValue(id, aiValue.Type()))
return 1, nil
- } else if session.Engine.dialect.DBType() == core.POSTGRES && len(table.AutoIncrement) > 0 {
+ } else if session.engine.dialect.DBType() == core.POSTGRES && len(table.AutoIncrement) > 0 {
//assert table.AutoIncrement != ""
- sqlStr = sqlStr + " RETURNING " + session.Engine.Quote(table.AutoIncrement)
- res, err := session.query(sqlStr, args...)
+ sqlStr = sqlStr + " RETURNING " + session.engine.Quote(table.AutoIncrement)
+ res, err := session.queryBytes(sqlStr, args...)
if err != nil {
return 0, err
}
- handleAfterInsertProcessorFunc(bean)
+ defer handleAfterInsertProcessorFunc(bean)
- if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache {
- session.cacheInsert(session.Statement.TableName())
+ if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache {
+ session.cacheInsert(table, tableName)
}
- if table.Version != "" && session.Statement.checkVersion {
+ if table.Version != "" && session.statement.checkVersion {
verValue, err := table.VersionColumn().ValueOf(bean)
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
} else if verValue.IsValid() && verValue.CanSet() {
verValue.SetInt(1)
}
@@ -458,7 +472,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
aiValue, err := table.AutoIncrColumn().ValueOf(bean)
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
}
if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() {
@@ -476,14 +490,14 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
defer handleAfterInsertProcessorFunc(bean)
- if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache {
- session.cacheInsert(session.Statement.TableName())
+ if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache {
+ session.cacheInsert(table, tableName)
}
- if table.Version != "" && session.Statement.checkVersion {
+ if table.Version != "" && session.statement.checkVersion {
verValue, err := table.VersionColumn().ValueOf(bean)
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
} else if verValue.IsValid() && verValue.CanSet() {
verValue.SetInt(1)
}
@@ -501,7 +515,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
aiValue, err := table.AutoIncrColumn().ValueOf(bean)
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
}
if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() {
@@ -518,24 +532,21 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) {
// The in parameter bean must a struct or a point to struct. The return
// parameter is inserted and error
func (session *Session) InsertOne(bean interface{}) (int64, error) {
- defer session.resetStatement()
- if session.IsAutoClose {
+ if session.isAutoClose {
defer session.Close()
}
return session.innerInsert(bean)
}
-func (session *Session) cacheInsert(tables ...string) error {
- if session.Statement.RefTable == nil {
+func (session *Session) cacheInsert(table *core.Table, tables ...string) error {
+ if table == nil {
return ErrCacheFailed
}
- table := session.Statement.RefTable
- cacher := session.Engine.getCacher2(table)
-
+ cacher := session.engine.getCacher2(table)
for _, t := range tables {
- session.Engine.logger.Debug("[cache] clear sql:", t)
+ session.engine.logger.Debug("[cache] clear sql:", t)
cacher.ClearIds(t)
}
diff --git a/vendor/github.com/go-xorm/xorm/session_iterate.go b/vendor/github.com/go-xorm/xorm/session_iterate.go
index 7c148095902..071fce49921 100644
--- a/vendor/github.com/go-xorm/xorm/session_iterate.go
+++ b/vendor/github.com/go-xorm/xorm/session_iterate.go
@@ -19,6 +19,14 @@ func (session *Session) Rows(bean interface{}) (*Rows, error) {
// are conditions. beans could be []Struct, []*Struct, map[int64]Struct
// map[int64]*Struct
func (session *Session) Iterate(bean interface{}, fun IterFunc) error {
+ if session.isAutoClose {
+ defer session.Close()
+ }
+
+ if session.statement.bufferSize > 0 {
+ return session.bufferIterate(bean, fun)
+ }
+
rows, err := session.Rows(bean)
if err != nil {
return err
@@ -40,3 +48,49 @@ func (session *Session) Iterate(bean interface{}, fun IterFunc) error {
}
return err
}
+
+// BufferSize sets the buffersize for iterate
+func (session *Session) BufferSize(size int) *Session {
+ session.statement.bufferSize = size
+ return session
+}
+
+func (session *Session) bufferIterate(bean interface{}, fun IterFunc) error {
+ if session.isAutoClose {
+ defer session.Close()
+ }
+
+ var bufferSize = session.statement.bufferSize
+ var limit = session.statement.LimitN
+ if limit > 0 && bufferSize > limit {
+ bufferSize = limit
+ }
+ var start = session.statement.Start
+ v := rValue(bean)
+ sliceType := reflect.SliceOf(v.Type())
+ var idx = 0
+ for {
+ slice := reflect.New(sliceType)
+ if err := session.Limit(bufferSize, start).find(slice.Interface(), bean); err != nil {
+ return err
+ }
+
+ for i := 0; i < slice.Elem().Len(); i++ {
+ if err := fun(idx, slice.Elem().Index(i).Addr().Interface()); err != nil {
+ return err
+ }
+ idx++
+ }
+
+ start = start + slice.Elem().Len()
+ if limit > 0 && idx+bufferSize > limit {
+ bufferSize = limit - idx
+ }
+
+ if bufferSize <= 0 || slice.Elem().Len() < bufferSize || idx == limit {
+ break
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/go-xorm/xorm/session_query.go b/vendor/github.com/go-xorm/xorm/session_query.go
new file mode 100644
index 00000000000..5b4e0dc45d0
--- /dev/null
+++ b/vendor/github.com/go-xorm/xorm/session_query.go
@@ -0,0 +1,252 @@
+// Copyright 2017 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+ "fmt"
+ "reflect"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/go-xorm/builder"
+ "github.com/go-xorm/core"
+)
+
+func (session *Session) genQuerySQL(sqlorArgs ...interface{}) (string, []interface{}, error) {
+ if len(sqlorArgs) > 0 {
+ return sqlorArgs[0].(string), sqlorArgs[1:], nil
+ }
+
+ if session.statement.RawSQL != "" {
+ return session.statement.RawSQL, session.statement.RawParams, nil
+ }
+
+ if len(session.statement.TableName()) <= 0 {
+ return "", nil, ErrTableNotFound
+ }
+
+ var columnStr = session.statement.ColumnStr
+ if len(session.statement.selectStr) > 0 {
+ columnStr = session.statement.selectStr
+ } else {
+ if session.statement.JoinStr == "" {
+ if columnStr == "" {
+ if session.statement.GroupByStr != "" {
+ columnStr = session.statement.Engine.Quote(strings.Replace(session.statement.GroupByStr, ",", session.engine.Quote(","), -1))
+ } else {
+ columnStr = session.statement.genColumnStr()
+ }
+ }
+ } else {
+ if columnStr == "" {
+ if session.statement.GroupByStr != "" {
+ columnStr = session.statement.Engine.Quote(strings.Replace(session.statement.GroupByStr, ",", session.engine.Quote(","), -1))
+ } else {
+ columnStr = "*"
+ }
+ }
+ }
+ if columnStr == "" {
+ columnStr = "*"
+ }
+ }
+
+ condSQL, condArgs, err := builder.ToSQL(session.statement.cond)
+ if err != nil {
+ return "", nil, err
+ }
+
+ args := append(session.statement.joinArgs, condArgs...)
+ sqlStr, err := session.statement.genSelectSQL(columnStr, condSQL)
+ if err != nil {
+ return "", nil, err
+ }
+ // for mssql and use limit
+ qs := strings.Count(sqlStr, "?")
+ if len(args)*2 == qs {
+ args = append(args, args...)
+ }
+
+ return sqlStr, args, nil
+}
+
+// Query runs a raw sql and return records as []map[string][]byte
+func (session *Session) Query(sqlorArgs ...interface{}) ([]map[string][]byte, error) {
+ if session.isAutoClose {
+ defer session.Close()
+ }
+
+ sqlStr, args, err := session.genQuerySQL(sqlorArgs...)
+ if err != nil {
+ return nil, err
+ }
+
+ return session.queryBytes(sqlStr, args...)
+}
+
+func value2String(rawValue *reflect.Value) (str string, err error) {
+ aa := reflect.TypeOf((*rawValue).Interface())
+ vv := reflect.ValueOf((*rawValue).Interface())
+ switch aa.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ str = strconv.FormatInt(vv.Int(), 10)
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ str = strconv.FormatUint(vv.Uint(), 10)
+ case reflect.Float32, reflect.Float64:
+ str = strconv.FormatFloat(vv.Float(), 'f', -1, 64)
+ case reflect.String:
+ str = vv.String()
+ case reflect.Array, reflect.Slice:
+ switch aa.Elem().Kind() {
+ case reflect.Uint8:
+ data := rawValue.Interface().([]byte)
+ str = string(data)
+ if str == "\x00" {
+ str = "0"
+ }
+ default:
+ err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
+ }
+ // time type
+ case reflect.Struct:
+ if aa.ConvertibleTo(core.TimeType) {
+ str = vv.Convert(core.TimeType).Interface().(time.Time).Format(time.RFC3339Nano)
+ } else {
+ err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
+ }
+ case reflect.Bool:
+ str = strconv.FormatBool(vv.Bool())
+ case reflect.Complex128, reflect.Complex64:
+ str = fmt.Sprintf("%v", vv.Complex())
+ /* TODO: unsupported types below
+ case reflect.Map:
+ case reflect.Ptr:
+ case reflect.Uintptr:
+ case reflect.UnsafePointer:
+ case reflect.Chan, reflect.Func, reflect.Interface:
+ */
+ default:
+ err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name())
+ }
+ return
+}
+
+func row2mapStr(rows *core.Rows, fields []string) (resultsMap map[string]string, err error) {
+ result := make(map[string]string)
+ scanResultContainers := make([]interface{}, len(fields))
+ for i := 0; i < len(fields); i++ {
+ var scanResultContainer interface{}
+ scanResultContainers[i] = &scanResultContainer
+ }
+ if err := rows.Scan(scanResultContainers...); err != nil {
+ return nil, err
+ }
+
+ for ii, key := range fields {
+ rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii]))
+ // if row is null then as empty string
+ if rawValue.Interface() == nil {
+ result[key] = ""
+ continue
+ }
+
+ if data, err := value2String(&rawValue); err == nil {
+ result[key] = data
+ } else {
+ return nil, err
+ }
+ }
+ return result, nil
+}
+
+func rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error) {
+ fields, err := rows.Columns()
+ if err != nil {
+ return nil, err
+ }
+ for rows.Next() {
+ result, err := row2mapStr(rows, fields)
+ if err != nil {
+ return nil, err
+ }
+ resultsSlice = append(resultsSlice, result)
+ }
+
+ return resultsSlice, nil
+}
+
+// QueryString runs a raw sql and return records as []map[string]string
+func (session *Session) QueryString(sqlorArgs ...interface{}) ([]map[string]string, error) {
+ if session.isAutoClose {
+ defer session.Close()
+ }
+
+ sqlStr, args, err := session.genQuerySQL(sqlorArgs...)
+ if err != nil {
+ return nil, err
+ }
+
+ rows, err := session.queryRows(sqlStr, args...)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ return rows2Strings(rows)
+}
+
+func row2mapInterface(rows *core.Rows, fields []string) (resultsMap map[string]interface{}, err error) {
+ resultsMap = make(map[string]interface{}, len(fields))
+ scanResultContainers := make([]interface{}, len(fields))
+ for i := 0; i < len(fields); i++ {
+ var scanResultContainer interface{}
+ scanResultContainers[i] = &scanResultContainer
+ }
+ if err := rows.Scan(scanResultContainers...); err != nil {
+ return nil, err
+ }
+
+ for ii, key := range fields {
+ resultsMap[key] = reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])).Interface()
+ }
+ return
+}
+
+func rows2Interfaces(rows *core.Rows) (resultsSlice []map[string]interface{}, err error) {
+ fields, err := rows.Columns()
+ if err != nil {
+ return nil, err
+ }
+ for rows.Next() {
+ result, err := row2mapInterface(rows, fields)
+ if err != nil {
+ return nil, err
+ }
+ resultsSlice = append(resultsSlice, result)
+ }
+
+ return resultsSlice, nil
+}
+
+// QueryInterface runs a raw sql and return records as []map[string]interface{}
+func (session *Session) QueryInterface(sqlorArgs ...interface{}) ([]map[string]interface{}, error) {
+ if session.isAutoClose {
+ defer session.Close()
+ }
+
+ sqlStr, args, err := session.genQuerySQL(sqlorArgs...)
+ if err != nil {
+ return nil, err
+ }
+
+ rows, err := session.queryRows(sqlStr, args...)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ return rows2Interfaces(rows)
+}
diff --git a/vendor/github.com/go-xorm/xorm/session_raw.go b/vendor/github.com/go-xorm/xorm/session_raw.go
index 9351d5cf94b..69bf9b3c6bf 100644
--- a/vendor/github.com/go-xorm/xorm/session_raw.go
+++ b/vendor/github.com/go-xorm/xorm/session_raw.go
@@ -6,21 +6,140 @@ package xorm
import (
"database/sql"
+ "reflect"
+ "time"
"github.com/go-xorm/core"
)
-func (session *Session) query(sqlStr string, paramStr ...interface{}) (resultsSlice []map[string][]byte, err error) {
- session.queryPreprocess(&sqlStr, paramStr...)
-
- if session.IsAutoCommit {
- return session.innerQuery2(sqlStr, paramStr...)
+func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) {
+ for _, filter := range session.engine.dialect.Filters() {
+ *sqlStr = filter.Do(*sqlStr, session.engine.dialect, session.statement.RefTable)
}
- return session.txQuery(session.Tx, sqlStr, paramStr...)
+
+ session.lastSQL = *sqlStr
+ session.lastSQLArgs = paramStr
}
-func (session *Session) txQuery(tx *core.Tx, sqlStr string, params ...interface{}) (resultsSlice []map[string][]byte, err error) {
- rows, err := tx.Query(sqlStr, params...)
+func (session *Session) queryRows(sqlStr string, args ...interface{}) (*core.Rows, error) {
+ defer session.resetStatement()
+
+ session.queryPreprocess(&sqlStr, args...)
+
+ if session.engine.showSQL {
+ if session.engine.showExecTime {
+ b4ExecTime := time.Now()
+ defer func() {
+ execDuration := time.Since(b4ExecTime)
+ if len(args) > 0 {
+ session.engine.logger.Infof("[SQL] %s %#v - took: %v", sqlStr, args, execDuration)
+ } else {
+ session.engine.logger.Infof("[SQL] %s - took: %v", sqlStr, execDuration)
+ }
+ }()
+ } else {
+ if len(args) > 0 {
+ session.engine.logger.Infof("[SQL] %v %#v", sqlStr, args)
+ } else {
+ session.engine.logger.Infof("[SQL] %v", sqlStr)
+ }
+ }
+ }
+
+ if session.isAutoCommit {
+ var db *core.DB
+ if session.engine.engineGroup != nil {
+ db = session.engine.engineGroup.Slave().DB()
+ } else {
+ db = session.DB()
+ }
+
+ if session.prepareStmt {
+ // don't clear stmt since session will cache them
+ stmt, err := session.doPrepare(db, sqlStr)
+ if err != nil {
+ return nil, err
+ }
+
+ rows, err := stmt.Query(args...)
+ if err != nil {
+ return nil, err
+ }
+ return rows, nil
+ }
+
+ rows, err := db.Query(sqlStr, args...)
+ if err != nil {
+ return nil, err
+ }
+ return rows, nil
+ }
+
+ rows, err := session.tx.Query(sqlStr, args...)
+ if err != nil {
+ return nil, err
+ }
+ return rows, nil
+}
+
+func (session *Session) queryRow(sqlStr string, args ...interface{}) *core.Row {
+ return core.NewRow(session.queryRows(sqlStr, args...))
+}
+
+func value2Bytes(rawValue *reflect.Value) ([]byte, error) {
+ str, err := value2String(rawValue)
+ if err != nil {
+ return nil, err
+ }
+ return []byte(str), nil
+}
+
+func row2map(rows *core.Rows, fields []string) (resultsMap map[string][]byte, err error) {
+ result := make(map[string][]byte)
+ scanResultContainers := make([]interface{}, len(fields))
+ for i := 0; i < len(fields); i++ {
+ var scanResultContainer interface{}
+ scanResultContainers[i] = &scanResultContainer
+ }
+ if err := rows.Scan(scanResultContainers...); err != nil {
+ return nil, err
+ }
+
+ for ii, key := range fields {
+ rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii]))
+ //if row is null then ignore
+ if rawValue.Interface() == nil {
+ result[key] = []byte{}
+ continue
+ }
+
+ if data, err := value2Bytes(&rawValue); err == nil {
+ result[key] = data
+ } else {
+ return nil, err // !nashtsai! REVIEW, should return err or just error log?
+ }
+ }
+ return result, nil
+}
+
+func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) {
+ fields, err := rows.Columns()
+ if err != nil {
+ return nil, err
+ }
+ for rows.Next() {
+ result, err := row2map(rows, fields)
+ if err != nil {
+ return nil, err
+ }
+ resultsSlice = append(resultsSlice, result)
+ }
+
+ return resultsSlice, nil
+}
+
+func (session *Session) queryBytes(sqlStr string, args ...interface{}) ([]map[string][]byte, error) {
+ rows, err := session.queryRows(sqlStr, args...)
if err != nil {
return nil, err
}
@@ -29,73 +148,37 @@ func (session *Session) txQuery(tx *core.Tx, sqlStr string, params ...interface{
return rows2maps(rows)
}
-func (session *Session) innerQuery(sqlStr string, params ...interface{}) (*core.Stmt, *core.Rows, error) {
- var callback func() (*core.Stmt, *core.Rows, error)
- if session.prepareStmt {
- callback = func() (*core.Stmt, *core.Rows, error) {
- stmt, err := session.doPrepare(sqlStr)
- if err != nil {
- return nil, nil, err
- }
- rows, err := stmt.Query(params...)
- if err != nil {
- return nil, nil, err
- }
- return stmt, rows, nil
- }
- } else {
- callback = func() (*core.Stmt, *core.Rows, error) {
- rows, err := session.DB().Query(sqlStr, params...)
- if err != nil {
- return nil, nil, err
- }
- return nil, rows, err
- }
- }
- stmt, rows, err := session.Engine.logSQLQueryTime(sqlStr, params, callback)
- if err != nil {
- return nil, nil, err
- }
- return stmt, rows, nil
-}
-
-func (session *Session) innerQuery2(sqlStr string, params ...interface{}) ([]map[string][]byte, error) {
- _, rows, err := session.innerQuery(sqlStr, params...)
- if rows != nil {
- defer rows.Close()
- }
- if err != nil {
- return nil, err
- }
- return rows2maps(rows)
-}
-
-// Query a raw sql and return records as []map[string][]byte
-func (session *Session) Query(sqlStr string, paramStr ...interface{}) (resultsSlice []map[string][]byte, err error) {
+func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, error) {
defer session.resetStatement()
- if session.IsAutoClose {
- defer session.Close()
+
+ session.queryPreprocess(&sqlStr, args...)
+
+ if session.engine.showSQL {
+ if session.engine.showExecTime {
+ b4ExecTime := time.Now()
+ defer func() {
+ execDuration := time.Since(b4ExecTime)
+ if len(args) > 0 {
+ session.engine.logger.Infof("[SQL] %s %#v - took: %v", sqlStr, args, execDuration)
+ } else {
+ session.engine.logger.Infof("[SQL] %s - took: %v", sqlStr, execDuration)
+ }
+ }()
+ } else {
+ if len(args) > 0 {
+ session.engine.logger.Infof("[SQL] %v %#v", sqlStr, args)
+ } else {
+ session.engine.logger.Infof("[SQL] %v", sqlStr)
+ }
+ }
}
- return session.query(sqlStr, paramStr...)
-}
-
-// =============================
-// for string
-// =============================
-func (session *Session) query2(sqlStr string, paramStr ...interface{}) (resultsSlice []map[string]string, err error) {
- session.queryPreprocess(&sqlStr, paramStr...)
-
- if session.IsAutoCommit {
- return query2(session.DB(), sqlStr, paramStr...)
+ if !session.isAutoCommit {
+ return session.tx.Exec(sqlStr, args...)
}
- return txQuery2(session.Tx, sqlStr, paramStr...)
-}
-// Execute sql
-func (session *Session) innerExec(sqlStr string, args ...interface{}) (sql.Result, error) {
if session.prepareStmt {
- stmt, err := session.doPrepare(sqlStr)
+ stmt, err := session.doPrepare(session.DB(), sqlStr)
if err != nil {
return nil, err
}
@@ -110,33 +193,9 @@ func (session *Session) innerExec(sqlStr string, args ...interface{}) (sql.Resul
return session.DB().Exec(sqlStr, args...)
}
-func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, error) {
- for _, filter := range session.Engine.dialect.Filters() {
- // TODO: for table name, it's no need to RefTable
- sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable)
- }
-
- session.saveLastSQL(sqlStr, args...)
-
- return session.Engine.logSQLExecutionTime(sqlStr, args, func() (sql.Result, error) {
- if session.IsAutoCommit {
- // FIXME: oci8 can not auto commit (github.com/mattn/go-oci8)
- if session.Engine.dialect.DBType() == core.ORACLE {
- session.Begin()
- r, err := session.Tx.Exec(sqlStr, args...)
- session.Commit()
- return r, err
- }
- return session.innerExec(sqlStr, args...)
- }
- return session.Tx.Exec(sqlStr, args...)
- })
-}
-
// Exec raw sql
func (session *Session) Exec(sqlStr string, args ...interface{}) (sql.Result, error) {
- defer session.resetStatement()
- if session.IsAutoClose {
+ if session.isAutoClose {
defer session.Close()
}
diff --git a/vendor/github.com/go-xorm/xorm/session_schema.go b/vendor/github.com/go-xorm/xorm/session_schema.go
index 21fa2996149..a2708b736c0 100644
--- a/vendor/github.com/go-xorm/xorm/session_schema.go
+++ b/vendor/github.com/go-xorm/xorm/session_schema.go
@@ -16,38 +16,50 @@ import (
// Ping test if database is ok
func (session *Session) Ping() error {
- defer session.resetStatement()
- if session.IsAutoClose {
+ if session.isAutoClose {
defer session.Close()
}
+ session.engine.logger.Infof("PING DATABASE %v", session.engine.DriverName())
return session.DB().Ping()
}
// CreateTable create a table according a bean
func (session *Session) CreateTable(bean interface{}) error {
- v := rValue(bean)
- session.Statement.setRefValue(v)
-
- defer session.resetStatement()
- if session.IsAutoClose {
+ if session.isAutoClose {
defer session.Close()
}
- return session.createOneTable()
+ return session.createTable(bean)
+}
+
+func (session *Session) createTable(bean interface{}) error {
+ v := rValue(bean)
+ if err := session.statement.setRefValue(v); err != nil {
+ return err
+ }
+
+ sqlStr := session.statement.genCreateTableSQL()
+ _, err := session.exec(sqlStr)
+ return err
}
// CreateIndexes create indexes
func (session *Session) CreateIndexes(bean interface{}) error {
- v := rValue(bean)
- session.Statement.setRefValue(v)
-
- defer session.resetStatement()
- if session.IsAutoClose {
+ if session.isAutoClose {
defer session.Close()
}
- sqls := session.Statement.genIndexSQL()
+ return session.createIndexes(bean)
+}
+
+func (session *Session) createIndexes(bean interface{}) error {
+ v := rValue(bean)
+ if err := session.statement.setRefValue(v); err != nil {
+ return err
+ }
+
+ sqls := session.statement.genIndexSQL()
for _, sqlStr := range sqls {
_, err := session.exec(sqlStr)
if err != nil {
@@ -59,15 +71,19 @@ func (session *Session) CreateIndexes(bean interface{}) error {
// CreateUniques create uniques
func (session *Session) CreateUniques(bean interface{}) error {
- v := rValue(bean)
- session.Statement.setRefValue(v)
-
- defer session.resetStatement()
- if session.IsAutoClose {
+ if session.isAutoClose {
defer session.Close()
}
+ return session.createUniques(bean)
+}
- sqls := session.Statement.genUniqueSQL()
+func (session *Session) createUniques(bean interface{}) error {
+ v := rValue(bean)
+ if err := session.statement.setRefValue(v); err != nil {
+ return err
+ }
+
+ sqls := session.statement.genUniqueSQL()
for _, sqlStr := range sqls {
_, err := session.exec(sqlStr)
if err != nil {
@@ -77,41 +93,22 @@ func (session *Session) CreateUniques(bean interface{}) error {
return nil
}
-func (session *Session) createOneTable() error {
- sqlStr := session.Statement.genCreateTableSQL()
- _, err := session.exec(sqlStr)
- return err
-}
-
-// to be deleted
-func (session *Session) createAll() error {
- if session.IsAutoClose {
- defer session.Close()
- }
-
- for _, table := range session.Engine.Tables {
- session.Statement.RefTable = table
- session.Statement.tableName = table.Name
- err := session.createOneTable()
- session.resetStatement()
- if err != nil {
- return err
- }
- }
- return nil
-}
-
// DropIndexes drop indexes
func (session *Session) DropIndexes(bean interface{}) error {
- v := rValue(bean)
- session.Statement.setRefValue(v)
-
- defer session.resetStatement()
- if session.IsAutoClose {
+ if session.isAutoClose {
defer session.Close()
}
- sqls := session.Statement.genDelIndexSQL()
+ return session.dropIndexes(bean)
+}
+
+func (session *Session) dropIndexes(bean interface{}) error {
+ v := rValue(bean)
+ if err := session.statement.setRefValue(v); err != nil {
+ return err
+ }
+
+ sqls := session.statement.genDelIndexSQL()
for _, sqlStr := range sqls {
_, err := session.exec(sqlStr)
if err != nil {
@@ -123,15 +120,23 @@ func (session *Session) DropIndexes(bean interface{}) error {
// DropTable drop table will drop table if exist, if drop failed, it will return error
func (session *Session) DropTable(beanOrTableName interface{}) error {
- tableName, err := session.Engine.tableName(beanOrTableName)
+ if session.isAutoClose {
+ defer session.Close()
+ }
+
+ return session.dropTable(beanOrTableName)
+}
+
+func (session *Session) dropTable(beanOrTableName interface{}) error {
+ tableName, err := session.engine.tableName(beanOrTableName)
if err != nil {
return err
}
var needDrop = true
- if !session.Engine.dialect.SupportDropIfExists() {
- sqlStr, args := session.Engine.dialect.TableCheckSql(tableName)
- results, err := session.query(sqlStr, args...)
+ if !session.engine.dialect.SupportDropIfExists() {
+ sqlStr, args := session.engine.dialect.TableCheckSql(tableName)
+ results, err := session.queryBytes(sqlStr, args...)
if err != nil {
return err
}
@@ -139,7 +144,7 @@ func (session *Session) DropTable(beanOrTableName interface{}) error {
}
if needDrop {
- sqlStr := session.Engine.Dialect().DropTableSql(tableName)
+ sqlStr := session.engine.Dialect().DropTableSql(tableName)
_, err = session.exec(sqlStr)
return err
}
@@ -148,7 +153,11 @@ func (session *Session) DropTable(beanOrTableName interface{}) error {
// IsTableExist if a table is exist
func (session *Session) IsTableExist(beanOrTableName interface{}) (bool, error) {
- tableName, err := session.Engine.tableName(beanOrTableName)
+ if session.isAutoClose {
+ defer session.Close()
+ }
+
+ tableName, err := session.engine.tableName(beanOrTableName)
if err != nil {
return false, err
}
@@ -157,12 +166,8 @@ func (session *Session) IsTableExist(beanOrTableName interface{}) (bool, error)
}
func (session *Session) isTableExist(tableName string) (bool, error) {
- defer session.resetStatement()
- if session.IsAutoClose {
- defer session.Close()
- }
- sqlStr, args := session.Engine.dialect.TableCheckSql(tableName)
- results, err := session.query(sqlStr, args...)
+ sqlStr, args := session.engine.dialect.TableCheckSql(tableName)
+ results, err := session.queryBytes(sqlStr, args...)
return len(results) > 0, err
}
@@ -172,6 +177,9 @@ func (session *Session) IsTableEmpty(bean interface{}) (bool, error) {
t := v.Type()
if t.Kind() == reflect.String {
+ if session.isAutoClose {
+ defer session.Close()
+ }
return session.isTableEmpty(bean.(string))
} else if t.Kind() == reflect.Struct {
rows, err := session.Count(bean)
@@ -181,15 +189,9 @@ func (session *Session) IsTableEmpty(bean interface{}) (bool, error) {
}
func (session *Session) isTableEmpty(tableName string) (bool, error) {
- defer session.resetStatement()
- if session.IsAutoClose {
- defer session.Close()
- }
-
var total int64
- sqlStr := fmt.Sprintf("select count(*) from %s", session.Engine.Quote(tableName))
- err := session.DB().QueryRow(sqlStr).Scan(&total)
- session.saveLastSQL(sqlStr)
+ sqlStr := fmt.Sprintf("select count(*) from %s", session.engine.Quote(tableName))
+ err := session.queryRow(sqlStr).Scan(&total)
if err != nil {
if err == sql.ErrNoRows {
err = nil
@@ -200,30 +202,9 @@ func (session *Session) isTableEmpty(tableName string) (bool, error) {
return total == 0, nil
}
-func (session *Session) isIndexExist(tableName, idxName string, unique bool) (bool, error) {
- defer session.resetStatement()
- if session.IsAutoClose {
- defer session.Close()
- }
- var idx string
- if unique {
- idx = uniqueName(tableName, idxName)
- } else {
- idx = indexName(tableName, idxName)
- }
- sqlStr, args := session.Engine.dialect.IndexCheckSql(tableName, idx)
- results, err := session.query(sqlStr, args...)
- return len(results) > 0, err
-}
-
// find if index is exist according cols
func (session *Session) isIndexExist2(tableName string, cols []string, unique bool) (bool, error) {
- defer session.resetStatement()
- if session.IsAutoClose {
- defer session.Close()
- }
-
- indexes, err := session.Engine.dialect.GetIndexes(tableName)
+ indexes, err := session.engine.dialect.GetIndexes(tableName)
if err != nil {
return false, err
}
@@ -240,62 +221,34 @@ func (session *Session) isIndexExist2(tableName string, cols []string, unique bo
}
func (session *Session) addColumn(colName string) error {
- defer session.resetStatement()
- if session.IsAutoClose {
- defer session.Close()
- }
-
- col := session.Statement.RefTable.GetColumn(colName)
- sql, args := session.Statement.genAddColumnStr(col)
+ col := session.statement.RefTable.GetColumn(colName)
+ sql, args := session.statement.genAddColumnStr(col)
_, err := session.exec(sql, args...)
return err
}
func (session *Session) addIndex(tableName, idxName string) error {
- defer session.resetStatement()
- if session.IsAutoClose {
- defer session.Close()
- }
- index := session.Statement.RefTable.Indexes[idxName]
- sqlStr := session.Engine.dialect.CreateIndexSql(tableName, index)
-
+ index := session.statement.RefTable.Indexes[idxName]
+ sqlStr := session.engine.dialect.CreateIndexSql(tableName, index)
_, err := session.exec(sqlStr)
return err
}
func (session *Session) addUnique(tableName, uqeName string) error {
- defer session.resetStatement()
- if session.IsAutoClose {
- defer session.Close()
- }
- index := session.Statement.RefTable.Indexes[uqeName]
- sqlStr := session.Engine.dialect.CreateIndexSql(tableName, index)
+ index := session.statement.RefTable.Indexes[uqeName]
+ sqlStr := session.engine.dialect.CreateIndexSql(tableName, index)
_, err := session.exec(sqlStr)
return err
}
-// To be deleted
-func (session *Session) dropAll() error {
- defer session.resetStatement()
- if session.IsAutoClose {
- defer session.Close()
- }
-
- for _, table := range session.Engine.Tables {
- session.Statement.Init()
- session.Statement.RefTable = table
- sqlStr := session.Engine.Dialect().DropTableSql(session.Statement.TableName())
- _, err := session.exec(sqlStr)
- if err != nil {
- return err
- }
- }
- return nil
-}
-
// Sync2 synchronize structs to database tables
func (session *Session) Sync2(beans ...interface{}) error {
- engine := session.Engine
+ engine := session.engine
+
+ if session.isAutoClose {
+ session.isAutoClose = false
+ defer session.Close()
+ }
tables, err := engine.DBMetas()
if err != nil {
@@ -322,17 +275,17 @@ func (session *Session) Sync2(beans ...interface{}) error {
}
if oriTable == nil {
- err = session.StoreEngine(session.Statement.StoreEngine).CreateTable(bean)
+ err = session.StoreEngine(session.statement.StoreEngine).createTable(bean)
if err != nil {
return err
}
- err = session.CreateUniques(bean)
+ err = session.createUniques(bean)
if err != nil {
return err
}
- err = session.CreateIndexes(bean)
+ err = session.createIndexes(bean)
if err != nil {
return err
}
@@ -357,7 +310,7 @@ func (session *Session) Sync2(beans ...interface{}) error {
engine.dialect.DBType() == core.POSTGRES {
engine.logger.Infof("Table %s column %s change type from %s to %s\n",
tbName, col.Name, curType, expectedType)
- _, err = engine.Exec(engine.dialect.ModifyColumnSql(table.Name, col))
+ _, err = session.exec(engine.dialect.ModifyColumnSql(table.Name, col))
} else {
engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s\n",
tbName, col.Name, curType, expectedType)
@@ -367,7 +320,7 @@ func (session *Session) Sync2(beans ...interface{}) error {
if oriCol.Length < col.Length {
engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n",
tbName, col.Name, oriCol.Length, col.Length)
- _, err = engine.Exec(engine.dialect.ModifyColumnSql(table.Name, col))
+ _, err = session.exec(engine.dialect.ModifyColumnSql(table.Name, col))
}
}
} else {
@@ -381,7 +334,7 @@ func (session *Session) Sync2(beans ...interface{}) error {
if oriCol.Length < col.Length {
engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n",
tbName, col.Name, oriCol.Length, col.Length)
- _, err = engine.Exec(engine.dialect.ModifyColumnSql(table.Name, col))
+ _, err = session.exec(engine.dialect.ModifyColumnSql(table.Name, col))
}
}
}
@@ -394,10 +347,8 @@ func (session *Session) Sync2(beans ...interface{}) error {
tbName, col.Name, oriCol.Nullable, col.Nullable)
}
} else {
- session := engine.NewSession()
- session.Statement.RefTable = table
- session.Statement.tableName = tbName
- defer session.Close()
+ session.statement.RefTable = table
+ session.statement.tableName = tbName
err = session.addColumn(col.Name)
}
if err != nil {
@@ -421,7 +372,7 @@ func (session *Session) Sync2(beans ...interface{}) error {
if oriIndex != nil {
if oriIndex.Type != index.Type {
sql := engine.dialect.DropIndexSql(tbName, oriIndex)
- _, err = engine.Exec(sql)
+ _, err = session.exec(sql)
if err != nil {
return err
}
@@ -437,7 +388,7 @@ func (session *Session) Sync2(beans ...interface{}) error {
for name2, index2 := range oriTable.Indexes {
if _, ok := foundIndexNames[name2]; !ok {
sql := engine.dialect.DropIndexSql(tbName, index2)
- _, err = engine.Exec(sql)
+ _, err = session.exec(sql)
if err != nil {
return err
}
@@ -446,16 +397,12 @@ func (session *Session) Sync2(beans ...interface{}) error {
for name, index := range addedNames {
if index.Type == core.UniqueType {
- session := engine.NewSession()
- session.Statement.RefTable = table
- session.Statement.tableName = tbName
- defer session.Close()
+ session.statement.RefTable = table
+ session.statement.tableName = tbName
err = session.addUnique(tbName, name)
} else if index.Type == core.IndexType {
- session := engine.NewSession()
- session.Statement.RefTable = table
- session.Statement.tableName = tbName
- defer session.Close()
+ session.statement.RefTable = table
+ session.statement.tableName = tbName
err = session.addIndex(tbName, name)
}
if err != nil {
diff --git a/vendor/github.com/go-xorm/xorm/session_stats.go b/vendor/github.com/go-xorm/xorm/session_stats.go
new file mode 100644
index 00000000000..c2cac830697
--- /dev/null
+++ b/vendor/github.com/go-xorm/xorm/session_stats.go
@@ -0,0 +1,98 @@
+// Copyright 2016 The Xorm Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xorm
+
+import (
+ "database/sql"
+ "errors"
+ "reflect"
+)
+
+// Count counts the records. bean's non-empty fields
+// are conditions.
+func (session *Session) Count(bean ...interface{}) (int64, error) {
+ if session.isAutoClose {
+ defer session.Close()
+ }
+
+ var sqlStr string
+ var args []interface{}
+ var err error
+ if session.statement.RawSQL == "" {
+ sqlStr, args, err = session.statement.genCountSQL(bean...)
+ if err != nil {
+ return 0, err
+ }
+ } else {
+ sqlStr = session.statement.RawSQL
+ args = session.statement.RawParams
+ }
+
+ var total int64
+ err = session.queryRow(sqlStr, args...).Scan(&total)
+ if err == sql.ErrNoRows || err == nil {
+ return total, nil
+ }
+
+ return 0, err
+}
+
+// sum call sum some column. bean's non-empty fields are conditions.
+func (session *Session) sum(res interface{}, bean interface{}, columnNames ...string) error {
+ if session.isAutoClose {
+ defer session.Close()
+ }
+
+ v := reflect.ValueOf(res)
+ if v.Kind() != reflect.Ptr {
+ return errors.New("need a pointer to a variable")
+ }
+
+ var isSlice = v.Elem().Kind() == reflect.Slice
+ var sqlStr string
+ var args []interface{}
+ var err error
+ if len(session.statement.RawSQL) == 0 {
+ sqlStr, args, err = session.statement.genSumSQL(bean, columnNames...)
+ if err != nil {
+ return err
+ }
+ } else {
+ sqlStr = session.statement.RawSQL
+ args = session.statement.RawParams
+ }
+
+ if isSlice {
+ err = session.queryRow(sqlStr, args...).ScanSlice(res)
+ } else {
+ err = session.queryRow(sqlStr, args...).Scan(res)
+ }
+ if err == sql.ErrNoRows || err == nil {
+ return nil
+ }
+ return err
+}
+
+// Sum call sum some column. bean's non-empty fields are conditions.
+func (session *Session) Sum(bean interface{}, columnName string) (res float64, err error) {
+ return res, session.sum(&res, bean, columnName)
+}
+
+// SumInt call sum some column. bean's non-empty fields are conditions.
+func (session *Session) SumInt(bean interface{}, columnName string) (res int64, err error) {
+ return res, session.sum(&res, bean, columnName)
+}
+
+// Sums call sum some columns. bean's non-empty fields are conditions.
+func (session *Session) Sums(bean interface{}, columnNames ...string) ([]float64, error) {
+ var res = make([]float64, len(columnNames), len(columnNames))
+ return res, session.sum(&res, bean, columnNames...)
+}
+
+// SumsInt sum specify columns and return as []int64 instead of []float64
+func (session *Session) SumsInt(bean interface{}, columnNames ...string) ([]int64, error) {
+ var res = make([]int64, len(columnNames), len(columnNames))
+ return res, session.sum(&res, bean, columnNames...)
+}
diff --git a/vendor/github.com/go-xorm/xorm/session_sum.go b/vendor/github.com/go-xorm/xorm/session_sum.go
deleted file mode 100644
index e1409c7ff43..00000000000
--- a/vendor/github.com/go-xorm/xorm/session_sum.go
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright 2016 The Xorm Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package xorm
-
-import "database/sql"
-
-// Count counts the records. bean's non-empty fields
-// are conditions.
-func (session *Session) Count(bean interface{}) (int64, error) {
- defer session.resetStatement()
- if session.IsAutoClose {
- defer session.Close()
- }
-
- var sqlStr string
- var args []interface{}
- if session.Statement.RawSQL == "" {
- sqlStr, args = session.Statement.genCountSQL(bean)
- } else {
- sqlStr = session.Statement.RawSQL
- args = session.Statement.RawParams
- }
-
- session.queryPreprocess(&sqlStr, args...)
-
- var err error
- var total int64
- if session.IsAutoCommit {
- err = session.DB().QueryRow(sqlStr, args...).Scan(&total)
- } else {
- err = session.Tx.QueryRow(sqlStr, args...).Scan(&total)
- }
-
- if err == sql.ErrNoRows || err == nil {
- return total, nil
- }
-
- return 0, err
-}
-
-// Sum call sum some column. bean's non-empty fields are conditions.
-func (session *Session) Sum(bean interface{}, columnName string) (float64, error) {
- defer session.resetStatement()
- if session.IsAutoClose {
- defer session.Close()
- }
-
- var sqlStr string
- var args []interface{}
- if len(session.Statement.RawSQL) == 0 {
- sqlStr, args = session.Statement.genSumSQL(bean, columnName)
- } else {
- sqlStr = session.Statement.RawSQL
- args = session.Statement.RawParams
- }
-
- session.queryPreprocess(&sqlStr, args...)
-
- var err error
- var res float64
- if session.IsAutoCommit {
- err = session.DB().QueryRow(sqlStr, args...).Scan(&res)
- } else {
- err = session.Tx.QueryRow(sqlStr, args...).Scan(&res)
- }
-
- if err == sql.ErrNoRows || err == nil {
- return res, nil
- }
- return 0, err
-}
-
-// Sums call sum some columns. bean's non-empty fields are conditions.
-func (session *Session) Sums(bean interface{}, columnNames ...string) ([]float64, error) {
- defer session.resetStatement()
- if session.IsAutoClose {
- defer session.Close()
- }
-
- var sqlStr string
- var args []interface{}
- if len(session.Statement.RawSQL) == 0 {
- sqlStr, args = session.Statement.genSumSQL(bean, columnNames...)
- } else {
- sqlStr = session.Statement.RawSQL
- args = session.Statement.RawParams
- }
-
- session.queryPreprocess(&sqlStr, args...)
-
- var err error
- var res = make([]float64, len(columnNames), len(columnNames))
- if session.IsAutoCommit {
- err = session.DB().QueryRow(sqlStr, args...).ScanSlice(&res)
- } else {
- err = session.Tx.QueryRow(sqlStr, args...).ScanSlice(&res)
- }
-
- if err == sql.ErrNoRows || err == nil {
- return res, nil
- }
- return nil, err
-}
-
-// SumsInt sum specify columns and return as []int64 instead of []float64
-func (session *Session) SumsInt(bean interface{}, columnNames ...string) ([]int64, error) {
- defer session.resetStatement()
- if session.IsAutoClose {
- defer session.Close()
- }
-
- var sqlStr string
- var args []interface{}
- if len(session.Statement.RawSQL) == 0 {
- sqlStr, args = session.Statement.genSumSQL(bean, columnNames...)
- } else {
- sqlStr = session.Statement.RawSQL
- args = session.Statement.RawParams
- }
-
- session.queryPreprocess(&sqlStr, args...)
-
- var err error
- var res = make([]int64, len(columnNames), len(columnNames))
- if session.IsAutoCommit {
- err = session.DB().QueryRow(sqlStr, args...).ScanSlice(&res)
- } else {
- err = session.Tx.QueryRow(sqlStr, args...).ScanSlice(&res)
- }
-
- if err == sql.ErrNoRows || err == nil {
- return res, nil
- }
- return nil, err
-}
diff --git a/vendor/github.com/go-xorm/xorm/session_tx.go b/vendor/github.com/go-xorm/xorm/session_tx.go
index 302bc104d54..84d2f7f9dcf 100644
--- a/vendor/github.com/go-xorm/xorm/session_tx.go
+++ b/vendor/github.com/go-xorm/xorm/session_tx.go
@@ -6,14 +6,14 @@ package xorm
// Begin a transaction
func (session *Session) Begin() error {
- if session.IsAutoCommit {
+ if session.isAutoCommit {
tx, err := session.DB().Begin()
if err != nil {
return err
}
- session.IsAutoCommit = false
- session.IsCommitedOrRollbacked = false
- session.Tx = tx
+ session.isAutoCommit = false
+ session.isCommitedOrRollbacked = false
+ session.tx = tx
session.saveLastSQL("BEGIN TRANSACTION")
}
return nil
@@ -21,25 +21,23 @@ func (session *Session) Begin() error {
// Rollback When using transaction, you can rollback if any error
func (session *Session) Rollback() error {
- if !session.IsAutoCommit && !session.IsCommitedOrRollbacked {
- session.saveLastSQL(session.Engine.dialect.RollBackStr())
- session.IsCommitedOrRollbacked = true
- return session.Tx.Rollback()
+ if !session.isAutoCommit && !session.isCommitedOrRollbacked {
+ session.saveLastSQL(session.engine.dialect.RollBackStr())
+ session.isCommitedOrRollbacked = true
+ return session.tx.Rollback()
}
return nil
}
// Commit When using transaction, Commit will commit all operations.
func (session *Session) Commit() error {
- if !session.IsAutoCommit && !session.IsCommitedOrRollbacked {
+ if !session.isAutoCommit && !session.isCommitedOrRollbacked {
session.saveLastSQL("COMMIT")
- session.IsCommitedOrRollbacked = true
+ session.isCommitedOrRollbacked = true
var err error
- if err = session.Tx.Commit(); err == nil {
+ if err = session.tx.Commit(); err == nil {
// handle processors after tx committed
-
closureCallFunc := func(closuresPtr *[]func(interface{}), bean interface{}) {
-
if closuresPtr != nil {
for _, closure := range *closuresPtr {
closure(bean)
diff --git a/vendor/github.com/go-xorm/xorm/session_update.go b/vendor/github.com/go-xorm/xorm/session_update.go
index 0f2d1b5cefb..f558745667f 100644
--- a/vendor/github.com/go-xorm/xorm/session_update.go
+++ b/vendor/github.com/go-xorm/xorm/session_update.go
@@ -15,20 +15,20 @@ import (
"github.com/go-xorm/core"
)
-func (session *Session) cacheUpdate(sqlStr string, args ...interface{}) error {
- if session.Statement.RefTable == nil ||
- session.Tx != nil {
+func (session *Session) cacheUpdate(table *core.Table, tableName, sqlStr string, args ...interface{}) error {
+ if table == nil ||
+ session.tx != nil {
return ErrCacheFailed
}
- oldhead, newsql := session.Statement.convertUpdateSQL(sqlStr)
+ oldhead, newsql := session.statement.convertUpdateSQL(sqlStr)
if newsql == "" {
return ErrCacheFailed
}
- for _, filter := range session.Engine.dialect.Filters() {
- newsql = filter.Do(newsql, session.Engine.dialect, session.Statement.RefTable)
+ for _, filter := range session.engine.dialect.Filters() {
+ newsql = filter.Do(newsql, session.engine.dialect, table)
}
- session.Engine.logger.Debug("[cacheUpdate] new sql", oldhead, newsql)
+ session.engine.logger.Debug("[cacheUpdate] new sql", oldhead, newsql)
var nStart int
if len(args) > 0 {
@@ -39,13 +39,12 @@ func (session *Session) cacheUpdate(sqlStr string, args ...interface{}) error {
nStart = strings.Count(oldhead, "$")
}
}
- table := session.Statement.RefTable
- cacher := session.Engine.getCacher2(table)
- tableName := session.Statement.TableName()
- session.Engine.logger.Debug("[cacheUpdate] get cache sql", newsql, args[nStart:])
+
+ cacher := session.engine.getCacher2(table)
+ session.engine.logger.Debug("[cacheUpdate] get cache sql", newsql, args[nStart:])
ids, err := core.GetCacheSql(cacher, tableName, newsql, args[nStart:])
if err != nil {
- rows, err := session.DB().Query(newsql, args[nStart:]...)
+ rows, err := session.NoCache().queryRows(newsql, args[nStart:]...)
if err != nil {
return err
}
@@ -75,9 +74,9 @@ func (session *Session) cacheUpdate(sqlStr string, args ...interface{}) error {
ids = append(ids, pk)
}
- session.Engine.logger.Debug("[cacheUpdate] find updated id", ids)
+ session.engine.logger.Debug("[cacheUpdate] find updated id", ids)
} /*else {
- session.Engine.LogDebug("[xorm:cacheUpdate] del cached sql:", tableName, newsql, args)
+ session.engine.LogDebug("[xorm:cacheUpdate] del cached sql:", tableName, newsql, args)
cacher.DelIds(tableName, genSqlKey(newsql, args))
}*/
@@ -103,36 +102,36 @@ func (session *Session) cacheUpdate(sqlStr string, args ...interface{}) error {
colName := sps2[len(sps2)-1]
if strings.Contains(colName, "`") {
colName = strings.TrimSpace(strings.Replace(colName, "`", "", -1))
- } else if strings.Contains(colName, session.Engine.QuoteStr()) {
- colName = strings.TrimSpace(strings.Replace(colName, session.Engine.QuoteStr(), "", -1))
+ } else if strings.Contains(colName, session.engine.QuoteStr()) {
+ colName = strings.TrimSpace(strings.Replace(colName, session.engine.QuoteStr(), "", -1))
} else {
- session.Engine.logger.Debug("[cacheUpdate] cannot find column", tableName, colName)
+ session.engine.logger.Debug("[cacheUpdate] cannot find column", tableName, colName)
return ErrCacheFailed
}
if col := table.GetColumn(colName); col != nil {
fieldValue, err := col.ValueOf(bean)
if err != nil {
- session.Engine.logger.Error(err)
+ session.engine.logger.Error(err)
} else {
- session.Engine.logger.Debug("[cacheUpdate] set bean field", bean, colName, fieldValue.Interface())
- if col.IsVersion && session.Statement.checkVersion {
+ session.engine.logger.Debug("[cacheUpdate] set bean field", bean, colName, fieldValue.Interface())
+ if col.IsVersion && session.statement.checkVersion {
fieldValue.SetInt(fieldValue.Int() + 1)
} else {
fieldValue.Set(reflect.ValueOf(args[idx]))
}
}
} else {
- session.Engine.logger.Errorf("[cacheUpdate] ERROR: column %v is not table %v's",
+ session.engine.logger.Errorf("[cacheUpdate] ERROR: column %v is not table %v's",
colName, table.Name)
}
}
- session.Engine.logger.Debug("[cacheUpdate] update cache", tableName, id, bean)
+ session.engine.logger.Debug("[cacheUpdate] update cache", tableName, id, bean)
cacher.PutBean(tableName, sid, bean)
}
}
- session.Engine.logger.Debug("[cacheUpdate] clear cached table sql:", tableName)
+ session.engine.logger.Debug("[cacheUpdate] clear cached table sql:", tableName)
cacher.ClearIds(tableName)
return nil
}
@@ -144,8 +143,7 @@ func (session *Session) cacheUpdate(sqlStr string, args ...interface{}) error {
// You should call UseBool if you have bool to use.
// 2.float32 & float64 may be not inexact as conditions
func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int64, error) {
- defer session.resetStatement()
- if session.IsAutoClose {
+ if session.isAutoClose {
defer session.Close()
}
@@ -169,19 +167,21 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
var isMap = t.Kind() == reflect.Map
var isStruct = t.Kind() == reflect.Struct
if isStruct {
- session.Statement.setRefValue(v)
+ if err := session.statement.setRefValue(v); err != nil {
+ return 0, err
+ }
- if len(session.Statement.TableName()) <= 0 {
+ if len(session.statement.TableName()) <= 0 {
return 0, ErrTableNotFound
}
- if session.Statement.ColumnStr == "" {
- colNames, args = buildUpdates(session.Engine, session.Statement.RefTable, bean, false, false,
- false, false, session.Statement.allUseBool, session.Statement.useAllCols,
- session.Statement.mustColumnMap, session.Statement.nullableMap,
- session.Statement.columnMap, true, session.Statement.unscoped)
+ if session.statement.ColumnStr == "" {
+ colNames, args = buildUpdates(session.engine, session.statement.RefTable, bean, false, false,
+ false, false, session.statement.allUseBool, session.statement.useAllCols,
+ session.statement.mustColumnMap, session.statement.nullableMap,
+ session.statement.columnMap, true, session.statement.unscoped)
} else {
- colNames, args, err = genCols(session.Statement.RefTable, session, bean, true, true)
+ colNames, args, err = genCols(session.statement.RefTable, session, bean, true, true)
if err != nil {
return 0, err
}
@@ -192,68 +192,84 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
bValue := reflect.Indirect(reflect.ValueOf(bean))
for _, v := range bValue.MapKeys() {
- colNames = append(colNames, session.Engine.Quote(v.String())+" = ?")
+ colNames = append(colNames, session.engine.Quote(v.String())+" = ?")
args = append(args, bValue.MapIndex(v).Interface())
}
} else {
return 0, ErrParamsType
}
- table := session.Statement.RefTable
+ table := session.statement.RefTable
- if session.Statement.UseAutoTime && table != nil && table.Updated != "" {
- colNames = append(colNames, session.Engine.Quote(table.Updated)+" = ?")
- col := table.UpdatedColumn()
- val, t := session.Engine.NowTime2(col.SQLType.Name)
- args = append(args, val)
+ if session.statement.UseAutoTime && table != nil && table.Updated != "" {
+ if _, ok := session.statement.columnMap[strings.ToLower(table.Updated)]; !ok {
+ colNames = append(colNames, session.engine.Quote(table.Updated)+" = ?")
+ col := table.UpdatedColumn()
+ val, t := session.engine.nowTime(col)
+ args = append(args, val)
- var colName = col.Name
- if isStruct {
- session.afterClosures = append(session.afterClosures, func(bean interface{}) {
- col := table.GetColumn(colName)
- setColumnTime(bean, col, t)
- })
+ var colName = col.Name
+ if isStruct {
+ session.afterClosures = append(session.afterClosures, func(bean interface{}) {
+ col := table.GetColumn(colName)
+ setColumnTime(bean, col, t)
+ })
+ }
}
}
//for update action to like "column = column + ?"
- incColumns := session.Statement.getInc()
+ incColumns := session.statement.getInc()
for _, v := range incColumns {
- colNames = append(colNames, session.Engine.Quote(v.colName)+" = "+session.Engine.Quote(v.colName)+" + ?")
+ colNames = append(colNames, session.engine.Quote(v.colName)+" = "+session.engine.Quote(v.colName)+" + ?")
args = append(args, v.arg)
}
//for update action to like "column = column - ?"
- decColumns := session.Statement.getDec()
+ decColumns := session.statement.getDec()
for _, v := range decColumns {
- colNames = append(colNames, session.Engine.Quote(v.colName)+" = "+session.Engine.Quote(v.colName)+" - ?")
+ colNames = append(colNames, session.engine.Quote(v.colName)+" = "+session.engine.Quote(v.colName)+" - ?")
args = append(args, v.arg)
}
//for update action to like "column = expression"
- exprColumns := session.Statement.getExpr()
+ exprColumns := session.statement.getExpr()
for _, v := range exprColumns {
- colNames = append(colNames, session.Engine.Quote(v.colName)+" = "+v.expr)
+ colNames = append(colNames, session.engine.Quote(v.colName)+" = "+v.expr)
}
- session.Statement.processIDParam()
+ if err = session.statement.processIDParam(); err != nil {
+ return 0, err
+ }
var autoCond builder.Cond
- if !session.Statement.noAutoCondition && len(condiBean) > 0 {
- var err error
- autoCond, err = session.Statement.buildConds(session.Statement.RefTable, condiBean[0], true, true, false, true, false)
- if err != nil {
- return 0, err
+ if !session.statement.noAutoCondition && len(condiBean) > 0 {
+ if c, ok := condiBean[0].(map[string]interface{}); ok {
+ autoCond = builder.Eq(c)
+ } else {
+ ct := reflect.TypeOf(condiBean[0])
+ k := ct.Kind()
+ if k == reflect.Ptr {
+ k = ct.Elem().Kind()
+ }
+ if k == reflect.Struct {
+ var err error
+ autoCond, err = session.statement.buildConds(session.statement.RefTable, condiBean[0], true, true, false, true, false)
+ if err != nil {
+ return 0, err
+ }
+ } else {
+ return 0, ErrConditionType
+ }
}
}
- st := session.Statement
- defer session.resetStatement()
+ st := &session.statement
var sqlStr string
var condArgs []interface{}
var condSQL string
- cond := session.Statement.cond.And(autoCond)
+ cond := session.statement.cond.And(autoCond)
- var doIncVer = (table != nil && table.Version != "" && session.Statement.checkVersion)
+ var doIncVer = (table != nil && table.Version != "" && session.statement.checkVersion)
var verValue *reflect.Value
if doIncVer {
verValue, err = table.VersionColumn().ValueOf(bean)
@@ -261,11 +277,15 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
return 0, err
}
- cond = cond.And(builder.Eq{session.Engine.Quote(table.Version): verValue.Interface()})
- colNames = append(colNames, session.Engine.Quote(table.Version)+" = "+session.Engine.Quote(table.Version)+" + 1")
+ cond = cond.And(builder.Eq{session.engine.Quote(table.Version): verValue.Interface()})
+ colNames = append(colNames, session.engine.Quote(table.Version)+" = "+session.engine.Quote(table.Version)+" + 1")
+ }
+
+ condSQL, condArgs, err = builder.ToSQL(cond)
+ if err != nil {
+ return 0, err
}
- condSQL, condArgs, _ = builder.ToSQL(cond)
if len(condSQL) > 0 {
condSQL = "WHERE " + condSQL
}
@@ -274,6 +294,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
condSQL = condSQL + fmt.Sprintf(" ORDER BY %v", st.OrderStr)
}
+ var tableName = session.statement.TableName()
// TODO: Oracle support needed
var top string
if st.LimitN > 0 {
@@ -282,27 +303,53 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
} else if st.Engine.dialect.DBType() == core.SQLITE {
tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", st.LimitN)
cond = cond.And(builder.Expr(fmt.Sprintf("rowid IN (SELECT rowid FROM %v %v)",
- session.Engine.Quote(session.Statement.TableName()), tempCondSQL), condArgs...))
- condSQL, condArgs, _ = builder.ToSQL(cond)
+ session.engine.Quote(tableName), tempCondSQL), condArgs...))
+ condSQL, condArgs, err = builder.ToSQL(cond)
+ if err != nil {
+ return 0, err
+ }
if len(condSQL) > 0 {
condSQL = "WHERE " + condSQL
}
} else if st.Engine.dialect.DBType() == core.POSTGRES {
tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", st.LimitN)
cond = cond.And(builder.Expr(fmt.Sprintf("CTID IN (SELECT CTID FROM %v %v)",
- session.Engine.Quote(session.Statement.TableName()), tempCondSQL), condArgs...))
- condSQL, condArgs, _ = builder.ToSQL(cond)
+ session.engine.Quote(tableName), tempCondSQL), condArgs...))
+ condSQL, condArgs, err = builder.ToSQL(cond)
+ if err != nil {
+ return 0, err
+ }
+
if len(condSQL) > 0 {
condSQL = "WHERE " + condSQL
}
} else if st.Engine.dialect.DBType() == core.MSSQL {
- top = fmt.Sprintf("top (%d) ", st.LimitN)
+ if st.OrderStr != "" && st.Engine.dialect.DBType() == core.MSSQL &&
+ table != nil && len(table.PrimaryKeys) == 1 {
+ cond = builder.Expr(fmt.Sprintf("%s IN (SELECT TOP (%d) %s FROM %v%v)",
+ table.PrimaryKeys[0], st.LimitN, table.PrimaryKeys[0],
+ session.engine.Quote(tableName), condSQL), condArgs...)
+
+ condSQL, condArgs, err = builder.ToSQL(cond)
+ if err != nil {
+ return 0, err
+ }
+ if len(condSQL) > 0 {
+ condSQL = "WHERE " + condSQL
+ }
+ } else {
+ top = fmt.Sprintf("TOP (%d) ", st.LimitN)
+ }
}
}
+ if len(colNames) <= 0 {
+ return 0, errors.New("No content found to be updated")
+ }
+
sqlStr = fmt.Sprintf("UPDATE %v%v SET %v %v",
top,
- session.Engine.Quote(session.Statement.TableName()),
+ session.engine.Quote(tableName),
strings.Join(colNames, ", "),
condSQL)
@@ -316,19 +363,20 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
}
if table != nil {
- if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache {
- cacher.ClearIds(session.Statement.TableName())
- cacher.ClearBeans(session.Statement.TableName())
+ if cacher := session.engine.getCacher2(table); cacher != nil && session.statement.UseCache {
+ //session.cacheUpdate(table, tableName, sqlStr, args...)
+ cacher.ClearIds(tableName)
+ cacher.ClearBeans(tableName)
}
}
// handle after update processors
- if session.IsAutoCommit {
+ if session.isAutoCommit {
for _, closure := range session.afterClosures {
closure(bean)
}
if processor, ok := interface{}(bean).(AfterUpdateProcessor); ok {
- session.Engine.logger.Debug("[event]", session.Statement.TableName(), " has after update processor")
+ session.engine.logger.Debug("[event]", tableName, " has after update processor")
processor.AfterUpdate()
}
} else {
diff --git a/vendor/github.com/go-xorm/xorm/statement.go b/vendor/github.com/go-xorm/xorm/statement.go
index 82101ff20e9..6400425b20e 100644
--- a/vendor/github.com/go-xorm/xorm/statement.go
+++ b/vendor/github.com/go-xorm/xorm/statement.go
@@ -73,6 +73,7 @@ type Statement struct {
decrColumns map[string]decrParam
exprColumns map[string]exprParam
cond builder.Cond
+ bufferSize int
}
// Init reset all the statement's fields
@@ -111,6 +112,7 @@ func (statement *Statement) Init() {
statement.decrColumns = make(map[string]decrParam)
statement.exprColumns = make(map[string]exprParam)
statement.cond = builder.NewCond()
+ statement.bufferSize = 0
}
// NoAutoCondition if you do not want convert bean's field as query condition, then use this function
@@ -158,6 +160,9 @@ func (statement *Statement) And(query interface{}, args ...interface{}) *Stateme
case string:
cond := builder.Expr(query.(string), args...)
statement.cond = statement.cond.And(cond)
+ case map[string]interface{}:
+ cond := builder.Eq(query.(map[string]interface{}))
+ statement.cond = statement.cond.And(cond)
case builder.Cond:
cond := query.(builder.Cond)
statement.cond = statement.cond.And(cond)
@@ -179,6 +184,9 @@ func (statement *Statement) Or(query interface{}, args ...interface{}) *Statemen
case string:
cond := builder.Expr(query.(string), args...)
statement.cond = statement.cond.Or(cond)
+ case map[string]interface{}:
+ cond := builder.Eq(query.(map[string]interface{}))
+ statement.cond = statement.cond.Or(cond)
case builder.Cond:
cond := query.(builder.Cond)
statement.cond = statement.cond.Or(cond)
@@ -207,9 +215,14 @@ func (statement *Statement) NotIn(column string, args ...interface{}) *Statement
return statement
}
-func (statement *Statement) setRefValue(v reflect.Value) {
- statement.RefTable = statement.Engine.autoMapType(reflect.Indirect(v))
+func (statement *Statement) setRefValue(v reflect.Value) error {
+ var err error
+ statement.RefTable, err = statement.Engine.autoMapType(reflect.Indirect(v))
+ if err != nil {
+ return err
+ }
statement.tableName = statement.Engine.tbName(v)
+ return nil
}
// Table tempororily set table name, the parameter could be a string or a pointer of struct
@@ -219,7 +232,12 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement {
if t.Kind() == reflect.String {
statement.AltTableName = tableNameOrBean.(string)
} else if t.Kind() == reflect.Struct {
- statement.RefTable = statement.Engine.autoMapType(v)
+ var err error
+ statement.RefTable, err = statement.Engine.autoMapType(v)
+ if err != nil {
+ statement.Engine.logger.Error(err)
+ return statement
+ }
statement.AltTableName = statement.Engine.tbName(v)
}
return statement
@@ -262,6 +280,9 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{},
fieldValue := *fieldValuePtr
fieldType := reflect.TypeOf(fieldValue.Interface())
+ if fieldType == nil {
+ continue
+ }
requiredField := useAllCols
includeNil := useAllCols
@@ -366,7 +387,7 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{},
if !requiredField && (t.IsZero() || !fieldValue.IsValid()) {
continue
}
- val = engine.FormatTime(col.SQLType.Name, t)
+ val = engine.formatColTime(col, t)
} else if nulType, ok := fieldValue.Interface().(driver.Valuer); ok {
val, _ = nulType.Value()
} else {
@@ -480,224 +501,6 @@ func (statement *Statement) colName(col *core.Column, tableName string) string {
return statement.Engine.Quote(col.Name)
}
-func buildConds(engine *Engine, table *core.Table, bean interface{},
- includeVersion bool, includeUpdated bool, includeNil bool,
- includeAutoIncr bool, allUseBool bool, useAllCols bool, unscoped bool,
- mustColumnMap map[string]bool, tableName, aliasName string, addedTableName bool) (builder.Cond, error) {
- var conds []builder.Cond
- for _, col := range table.Columns() {
- if !includeVersion && col.IsVersion {
- continue
- }
- if !includeUpdated && col.IsUpdated {
- continue
- }
- if !includeAutoIncr && col.IsAutoIncrement {
- continue
- }
-
- if engine.dialect.DBType() == core.MSSQL && (col.SQLType.Name == core.Text || col.SQLType.IsBlob() || col.SQLType.Name == core.TimeStampz) {
- continue
- }
- if col.SQLType.IsJson() {
- continue
- }
-
- var colName string
- if addedTableName {
- var nm = tableName
- if len(aliasName) > 0 {
- nm = aliasName
- }
- colName = engine.Quote(nm) + "." + engine.Quote(col.Name)
- } else {
- colName = engine.Quote(col.Name)
- }
-
- fieldValuePtr, err := col.ValueOf(bean)
- if err != nil {
- engine.logger.Error(err)
- continue
- }
-
- if col.IsDeleted && !unscoped { // tag "deleted" is enabled
- if engine.dialect.DBType() == core.MSSQL {
- conds = append(conds, builder.IsNull{colName})
- } else {
- conds = append(conds, builder.IsNull{colName}.Or(builder.Eq{colName: "0001-01-01 00:00:00"}))
- }
- }
-
- fieldValue := *fieldValuePtr
- if fieldValue.Interface() == nil {
- continue
- }
-
- fieldType := reflect.TypeOf(fieldValue.Interface())
- requiredField := useAllCols
-
- if b, ok := getFlagForColumn(mustColumnMap, col); ok {
- if b {
- requiredField = true
- } else {
- continue
- }
- }
-
- if fieldType.Kind() == reflect.Ptr {
- if fieldValue.IsNil() {
- if includeNil {
- conds = append(conds, builder.Eq{colName: nil})
- }
- continue
- } else if !fieldValue.IsValid() {
- continue
- } else {
- // dereference ptr type to instance type
- fieldValue = fieldValue.Elem()
- fieldType = reflect.TypeOf(fieldValue.Interface())
- requiredField = true
- }
- }
-
- var val interface{}
- switch fieldType.Kind() {
- case reflect.Bool:
- if allUseBool || requiredField {
- val = fieldValue.Interface()
- } else {
- // if a bool in a struct, it will not be as a condition because it default is false,
- // please use Where() instead
- continue
- }
- case reflect.String:
- if !requiredField && fieldValue.String() == "" {
- continue
- }
- // for MyString, should convert to string or panic
- if fieldType.String() != reflect.String.String() {
- val = fieldValue.String()
- } else {
- val = fieldValue.Interface()
- }
- case reflect.Int8, reflect.Int16, reflect.Int, reflect.Int32, reflect.Int64:
- if !requiredField && fieldValue.Int() == 0 {
- continue
- }
- val = fieldValue.Interface()
- case reflect.Float32, reflect.Float64:
- if !requiredField && fieldValue.Float() == 0.0 {
- continue
- }
- val = fieldValue.Interface()
- case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64:
- if !requiredField && fieldValue.Uint() == 0 {
- continue
- }
- t := int64(fieldValue.Uint())
- val = reflect.ValueOf(&t).Interface()
- case reflect.Struct:
- if fieldType.ConvertibleTo(core.TimeType) {
- t := fieldValue.Convert(core.TimeType).Interface().(time.Time)
- if !requiredField && (t.IsZero() || !fieldValue.IsValid()) {
- continue
- }
- val = engine.FormatTime(col.SQLType.Name, t)
- } else if _, ok := reflect.New(fieldType).Interface().(core.Conversion); ok {
- continue
- } else if valNul, ok := fieldValue.Interface().(driver.Valuer); ok {
- val, _ = valNul.Value()
- if val == nil {
- continue
- }
- } else {
- if col.SQLType.IsJson() {
- if col.SQLType.IsText() {
- bytes, err := json.Marshal(fieldValue.Interface())
- if err != nil {
- engine.logger.Error(err)
- continue
- }
- val = string(bytes)
- } else if col.SQLType.IsBlob() {
- var bytes []byte
- var err error
- bytes, err = json.Marshal(fieldValue.Interface())
- if err != nil {
- engine.logger.Error(err)
- continue
- }
- val = bytes
- }
- } else {
- engine.autoMapType(fieldValue)
- if table, ok := engine.Tables[fieldValue.Type()]; ok {
- if len(table.PrimaryKeys) == 1 {
- pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumns()[0].FieldName)
- // fix non-int pk issues
- //if pkField.Int() != 0 {
- if pkField.IsValid() && !isZero(pkField.Interface()) {
- val = pkField.Interface()
- } else {
- continue
- }
- } else {
- //TODO: how to handler?
- panic(fmt.Sprintln("not supported", fieldValue.Interface(), "as", table.PrimaryKeys))
- }
- } else {
- val = fieldValue.Interface()
- }
- }
- }
- case reflect.Array:
- continue
- case reflect.Slice, reflect.Map:
- if fieldValue == reflect.Zero(fieldType) {
- continue
- }
- if fieldValue.IsNil() || !fieldValue.IsValid() || fieldValue.Len() == 0 {
- continue
- }
-
- if col.SQLType.IsText() {
- bytes, err := json.Marshal(fieldValue.Interface())
- if err != nil {
- engine.logger.Error(err)
- continue
- }
- val = string(bytes)
- } else if col.SQLType.IsBlob() {
- var bytes []byte
- var err error
- if (fieldType.Kind() == reflect.Array || fieldType.Kind() == reflect.Slice) &&
- fieldType.Elem().Kind() == reflect.Uint8 {
- if fieldValue.Len() > 0 {
- val = fieldValue.Bytes()
- } else {
- continue
- }
- } else {
- bytes, err = json.Marshal(fieldValue.Interface())
- if err != nil {
- engine.logger.Error(err)
- continue
- }
- val = bytes
- }
- } else {
- continue
- }
- default:
- val = fieldValue.Interface()
- }
-
- conds = append(conds, builder.Eq{colName: val})
- }
-
- return builder.And(conds...), nil
-}
-
// TableName return current tableName
func (statement *Statement) TableName() string {
if statement.AltTableName != "" {
@@ -800,6 +603,22 @@ func (statement *Statement) col2NewColsWithQuote(columns ...string) []string {
return newColumns
}
+func (statement *Statement) colmap2NewColsWithQuote() []string {
+ newColumns := make([]string, 0, len(statement.columnMap))
+ for col := range statement.columnMap {
+ fields := strings.Split(strings.TrimSpace(col), ".")
+ if len(fields) == 1 {
+ newColumns = append(newColumns, statement.Engine.quote(fields[0]))
+ } else if len(fields) == 2 {
+ newColumns = append(newColumns, statement.Engine.quote(fields[0])+"."+
+ statement.Engine.quote(fields[1]))
+ } else {
+ panic(errors.New("unwanted colnames"))
+ }
+ }
+ return newColumns
+}
+
// Distinct generates "DISTINCT col1, col2 " statement
func (statement *Statement) Distinct(columns ...string) *Statement {
statement.IsDistinct = true
@@ -826,7 +645,7 @@ func (statement *Statement) Cols(columns ...string) *Statement {
statement.columnMap[strings.ToLower(nc)] = true
}
- newColumns := statement.col2NewColsWithQuote(columns...)
+ newColumns := statement.colmap2NewColsWithQuote()
statement.ColumnStr = strings.Join(newColumns, ", ")
statement.ColumnStr = strings.Replace(statement.ColumnStr, statement.Engine.quote("*"), "*", -1)
return statement
@@ -1088,33 +907,50 @@ func (statement *Statement) genDelIndexSQL() []string {
func (statement *Statement) genAddColumnStr(col *core.Column) (string, []interface{}) {
quote := statement.Engine.Quote
- sql := fmt.Sprintf("ALTER TABLE %v ADD %v;", quote(statement.TableName()),
+ sql := fmt.Sprintf("ALTER TABLE %v ADD %v", quote(statement.TableName()),
col.String(statement.Engine.dialect))
+ if statement.Engine.dialect.DBType() == core.MYSQL && len(col.Comment) > 0 {
+ sql += " COMMENT '" + col.Comment + "'"
+ }
+ sql += ";"
return sql, []interface{}{}
}
func (statement *Statement) buildConds(table *core.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, addedTableName bool) (builder.Cond, error) {
- return buildConds(statement.Engine, table, bean, includeVersion, includeUpdated, includeNil, includeAutoIncr, statement.allUseBool, statement.useAllCols,
+ return statement.Engine.buildConds(table, bean, includeVersion, includeUpdated, includeNil, includeAutoIncr, statement.allUseBool, statement.useAllCols,
statement.unscoped, statement.mustColumnMap, statement.TableName(), statement.TableAlias, addedTableName)
}
-func (statement *Statement) genConds(bean interface{}) (string, []interface{}, error) {
+func (statement *Statement) mergeConds(bean interface{}) error {
if !statement.noAutoCondition {
var addedTableName = (len(statement.JoinStr) > 0)
autoCond, err := statement.buildConds(statement.RefTable, bean, true, true, false, true, addedTableName)
if err != nil {
- return "", nil, err
+ return err
}
statement.cond = statement.cond.And(autoCond)
}
- statement.processIDParam()
+ if err := statement.processIDParam(); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (statement *Statement) genConds(bean interface{}) (string, []interface{}, error) {
+ if err := statement.mergeConds(bean); err != nil {
+ return "", nil, err
+ }
return builder.ToSQL(statement.cond)
}
-func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{}) {
- statement.setRefValue(rValue(bean))
+func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{}, error) {
+ v := rValue(bean)
+ isStruct := v.Kind() == reflect.Struct
+ if isStruct {
+ statement.setRefValue(v)
+ }
var columnStr = statement.ColumnStr
if len(statement.selectStr) > 0 {
@@ -1133,22 +969,46 @@ func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{})
if len(columnStr) == 0 {
if len(statement.GroupByStr) > 0 {
columnStr = statement.Engine.Quote(strings.Replace(statement.GroupByStr, ",", statement.Engine.Quote(","), -1))
- } else {
- columnStr = "*"
}
}
}
}
- condSQL, condArgs, _ := statement.genConds(bean)
+ if len(columnStr) == 0 {
+ columnStr = "*"
+ }
- return statement.genSelectSQL(columnStr, condSQL), append(statement.joinArgs, condArgs...)
+ if isStruct {
+ if err := statement.mergeConds(bean); err != nil {
+ return "", nil, err
+ }
+ }
+ condSQL, condArgs, err := builder.ToSQL(statement.cond)
+ if err != nil {
+ return "", nil, err
+ }
+
+ sqlStr, err := statement.genSelectSQL(columnStr, condSQL)
+ if err != nil {
+ return "", nil, err
+ }
+
+ return sqlStr, append(statement.joinArgs, condArgs...), nil
}
-func (statement *Statement) genCountSQL(bean interface{}) (string, []interface{}) {
- statement.setRefValue(rValue(bean))
-
- condSQL, condArgs, _ := statement.genConds(bean)
+func (statement *Statement) genCountSQL(beans ...interface{}) (string, []interface{}, error) {
+ var condSQL string
+ var condArgs []interface{}
+ var err error
+ if len(beans) > 0 {
+ statement.setRefValue(rValue(beans[0]))
+ condSQL, condArgs, err = statement.genConds(beans[0])
+ } else {
+ condSQL, condArgs, err = builder.ToSQL(statement.cond)
+ }
+ if err != nil {
+ return "", nil, err
+ }
var selectSQL = statement.selectStr
if len(selectSQL) <= 0 {
@@ -1158,23 +1018,40 @@ func (statement *Statement) genCountSQL(bean interface{}) (string, []interface{}
selectSQL = "count(*)"
}
}
- return statement.genSelectSQL(selectSQL, condSQL), append(statement.joinArgs, condArgs...)
+ sqlStr, err := statement.genSelectSQL(selectSQL, condSQL)
+ if err != nil {
+ return "", nil, err
+ }
+
+ return sqlStr, append(statement.joinArgs, condArgs...), nil
}
-func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (string, []interface{}) {
+func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (string, []interface{}, error) {
statement.setRefValue(rValue(bean))
var sumStrs = make([]string, 0, len(columns))
for _, colName := range columns {
- sumStrs = append(sumStrs, fmt.Sprintf("COALESCE(sum(%s),0)", statement.Engine.Quote(colName)))
+ if !strings.Contains(colName, " ") && !strings.Contains(colName, "(") {
+ colName = statement.Engine.Quote(colName)
+ }
+ sumStrs = append(sumStrs, fmt.Sprintf("COALESCE(sum(%s),0)", colName))
+ }
+ sumSelect := strings.Join(sumStrs, ", ")
+
+ condSQL, condArgs, err := statement.genConds(bean)
+ if err != nil {
+ return "", nil, err
}
- condSQL, condArgs, _ := statement.genConds(bean)
+ sqlStr, err := statement.genSelectSQL(sumSelect, condSQL)
+ if err != nil {
+ return "", nil, err
+ }
- return statement.genSelectSQL(strings.Join(sumStrs, ", "), condSQL), append(statement.joinArgs, condArgs...)
+ return sqlStr, append(statement.joinArgs, condArgs...), nil
}
-func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string) {
+func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string, err error) {
var distinct string
if statement.IsDistinct && !strings.HasPrefix(columnStr, "count") {
distinct = "DISTINCT "
@@ -1185,15 +1062,23 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string) {
var top string
var mssqlCondi string
- statement.processIDParam()
+ if err := statement.processIDParam(); err != nil {
+ return "", err
+ }
var buf bytes.Buffer
if len(condSQL) > 0 {
fmt.Fprintf(&buf, " WHERE %v", condSQL)
}
var whereStr = buf.String()
+ var fromStr = " FROM "
+
+ if dialect.DBType() == core.MSSQL && strings.Contains(statement.TableName(), "..") {
+ fromStr += statement.TableName()
+ } else {
+ fromStr += quote(statement.TableName())
+ }
- var fromStr = " FROM " + quote(statement.TableName())
if statement.TableAlias != "" {
if dialect.DBType() == core.ORACLE {
fromStr += " " + quote(statement.TableAlias)
@@ -1246,7 +1131,7 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string) {
}
// !nashtsai! REVIEW Sprintf is considered slowest mean of string concatnation, better to work with builder pattern
- a = fmt.Sprintf("SELECT %v%v%v%v%v", top, distinct, columnStr, fromStr, whereStr)
+ a = fmt.Sprintf("SELECT %v%v%v%v%v", distinct, top, columnStr, fromStr, whereStr)
if len(mssqlCondi) > 0 {
if len(whereStr) > 0 {
a += " AND " + mssqlCondi
@@ -1282,19 +1167,23 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string) {
return
}
-func (statement *Statement) processIDParam() {
+func (statement *Statement) processIDParam() error {
if statement.idParam == nil {
- return
+ return nil
+ }
+
+ if len(statement.RefTable.PrimaryKeys) != len(*statement.idParam) {
+ return fmt.Errorf("ID condition is error, expect %d primarykeys, there are %d",
+ len(statement.RefTable.PrimaryKeys),
+ len(*statement.idParam),
+ )
}
for i, col := range statement.RefTable.PKColumns() {
var colName = statement.colName(col, statement.TableName())
- if i < len(*(statement.idParam)) {
- statement.cond = statement.cond.And(builder.Eq{colName: (*(statement.idParam))[i]})
- } else {
- statement.cond = statement.cond.And(builder.Eq{colName: ""})
- }
+ statement.cond = statement.cond.And(builder.Eq{colName: (*(statement.idParam))[i]})
}
+ return nil
}
func (statement *Statement) joinColumns(cols []*core.Column, includeTableName bool) string {
@@ -1328,7 +1217,8 @@ func (statement *Statement) convertIDSQL(sqlStr string) string {
top = fmt.Sprintf("TOP %d ", statement.LimitN)
}
- return fmt.Sprintf("SELECT %s%s FROM %v", top, colstrs, sqls[1])
+ newsql := fmt.Sprintf("SELECT %s%s FROM %v", top, colstrs, sqls[1])
+ return newsql
}
return ""
}
diff --git a/vendor/github.com/go-xorm/xorm/tag.go b/vendor/github.com/go-xorm/xorm/tag.go
index 4b0e3f54a57..e1c821fb540 100644
--- a/vendor/github.com/go-xorm/xorm/tag.go
+++ b/vendor/github.com/go-xorm/xorm/tag.go
@@ -54,6 +54,7 @@ var (
"UNIQUE": UniqueTagHandler,
"CACHE": CacheTagHandler,
"NOCACHE": NoCacheTagHandler,
+ "COMMENT": CommentTagHandler,
}
)
@@ -192,6 +193,14 @@ func UniqueTagHandler(ctx *tagContext) error {
return nil
}
+// CommentTagHandler add comment to column
+func CommentTagHandler(ctx *tagContext) error {
+ if len(ctx.params) > 0 {
+ ctx.col.Comment = strings.Trim(ctx.params[0], "' ")
+ }
+ return nil
+}
+
// SQLTypeTagHandler describes SQL Type tag handler
func SQLTypeTagHandler(ctx *tagContext) error {
ctx.col.SQLType = core.SQLType{Name: ctx.tagName}
diff --git a/vendor/github.com/go-xorm/xorm/xorm.go b/vendor/github.com/go-xorm/xorm/xorm.go
index 2cfbe9ecd31..4fdadf2fade 100644
--- a/vendor/github.com/go-xorm/xorm/xorm.go
+++ b/vendor/github.com/go-xorm/xorm/xorm.go
@@ -17,7 +17,7 @@ import (
const (
// Version show the xorm's version
- Version string = "0.6.2.0326"
+ Version string = "0.6.4.0910"
)
func regDrvsNDialects() bool {
@@ -50,10 +50,13 @@ func close(engine *Engine) {
engine.Close()
}
+func init() {
+ regDrvsNDialects()
+}
+
// NewEngine new a db manager according to the parameter. Currently support four
// drivers
func NewEngine(driverName string, dataSourceName string) (*Engine, error) {
- regDrvsNDialects()
driver := core.QueryDriver(driverName)
if driver == nil {
return nil, fmt.Errorf("Unsupported driver name: %v", driverName)
@@ -89,6 +92,12 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) {
tagHandlers: defaultTagHandlers,
}
+ if uri.DbType == core.SQLITE {
+ engine.DatabaseTZ = time.UTC
+ } else {
+ engine.DatabaseTZ = time.Local
+ }
+
logger := NewSimpleLogger(os.Stdout)
logger.SetLevel(core.LOG_INFO)
engine.SetLogger(logger)
diff --git a/vendor/github.com/hashicorp/go-plugin/client.go b/vendor/github.com/hashicorp/go-plugin/client.go
index de03690703f..b912826b200 100644
--- a/vendor/github.com/hashicorp/go-plugin/client.go
+++ b/vendor/github.com/hashicorp/go-plugin/client.go
@@ -567,7 +567,7 @@ func (c *Client) Start() (addr net.Addr, err error) {
// so they don't block since it is an io.Pipe
defer func() {
go func() {
- for range linesCh {
+ for _ = range linesCh {
}
}()
}()
diff --git a/vendor/github.com/hashicorp/go-plugin/rpc_client.go b/vendor/github.com/hashicorp/go-plugin/rpc_client.go
index 4d99d42c7e1..f30a4b1d387 100644
--- a/vendor/github.com/hashicorp/go-plugin/rpc_client.go
+++ b/vendor/github.com/hashicorp/go-plugin/rpc_client.go
@@ -75,7 +75,7 @@ func NewRPCClient(conn io.ReadWriteCloser, plugins map[string]Plugin) (*RPCClien
// Connect stdout, stderr streams
stdstream := make([]net.Conn, 2)
- for i := range stdstream {
+ for i, _ := range stdstream {
stdstream[i], err = mux.Open()
if err != nil {
mux.Close()
diff --git a/vendor/github.com/hashicorp/go-plugin/rpc_server.go b/vendor/github.com/hashicorp/go-plugin/rpc_server.go
index 168ef7dd944..5bb18dd5db1 100644
--- a/vendor/github.com/hashicorp/go-plugin/rpc_server.go
+++ b/vendor/github.com/hashicorp/go-plugin/rpc_server.go
@@ -78,7 +78,7 @@ func (s *RPCServer) ServeConn(conn io.ReadWriteCloser) {
// Connect the stdstreams (in, out, err)
stdstream := make([]net.Conn, 2)
- for i := range stdstream {
+ for i, _ := range stdstream {
stdstream[i], err = mux.Accept()
if err != nil {
mux.Close()
diff --git a/vendor/github.com/sergi/go-diff/diffmatchpatch/diff.go b/vendor/github.com/sergi/go-diff/diffmatchpatch/diff.go
index 1857f93f226..82ad7bc8f1c 100644
--- a/vendor/github.com/sergi/go-diff/diffmatchpatch/diff.go
+++ b/vendor/github.com/sergi/go-diff/diffmatchpatch/diff.go
@@ -85,7 +85,7 @@ func (dmp *DiffMatchPatch) diffMainRunes(text1, text2 []rune, checklines bool, d
// Restore the prefix and suffix.
if len(commonprefix) != 0 {
- diffs = append([]Diff{{DiffEqual, string(commonprefix)}}, diffs...)
+ diffs = append([]Diff{Diff{DiffEqual, string(commonprefix)}}, diffs...)
}
if len(commonsuffix) != 0 {
diffs = append(diffs, Diff{DiffEqual, string(commonsuffix)})
@@ -122,16 +122,16 @@ func (dmp *DiffMatchPatch) diffCompute(text1, text2 []rune, checklines bool, dea
}
// Shorter text is inside the longer text (speedup).
return []Diff{
- {op, string(longtext[:i])},
- {DiffEqual, string(shorttext)},
- {op, string(longtext[i+len(shorttext):])},
+ Diff{op, string(longtext[:i])},
+ Diff{DiffEqual, string(shorttext)},
+ Diff{op, string(longtext[i+len(shorttext):])},
}
} else if len(shorttext) == 1 {
// Single character string.
// After the previous speedup, the character can't be an equality.
return []Diff{
- {DiffDelete, string(text1)},
- {DiffInsert, string(text2)},
+ Diff{DiffDelete, string(text1)},
+ Diff{DiffInsert, string(text2)},
}
// Check to see if the problem can be split in two.
} else if hm := dmp.diffHalfMatch(text1, text2); hm != nil {
@@ -145,7 +145,7 @@ func (dmp *DiffMatchPatch) diffCompute(text1, text2 []rune, checklines bool, dea
diffsA := dmp.diffMainRunes(text1A, text2A, checklines, deadline)
diffsB := dmp.diffMainRunes(text1B, text2B, checklines, deadline)
// Merge the results.
- return append(diffsA, append([]Diff{{DiffEqual, string(midCommon)}}, diffsB...)...)
+ return append(diffsA, append([]Diff{Diff{DiffEqual, string(midCommon)}}, diffsB...)...)
} else if checklines && len(text1) > 100 && len(text2) > 100 {
return dmp.diffLineMode(text1, text2, deadline)
}
@@ -330,8 +330,8 @@ func (dmp *DiffMatchPatch) diffBisect(runes1, runes2 []rune, deadline time.Time)
}
// Diff took too long and hit the deadline or number of diffs equals number of characters, no commonality at all.
return []Diff{
- {DiffDelete, string(runes1)},
- {DiffInsert, string(runes2)},
+ Diff{DiffDelete, string(runes1)},
+ Diff{DiffInsert, string(runes2)},
}
}
@@ -673,7 +673,7 @@ func (dmp *DiffMatchPatch) DiffCleanupSemantic(diffs []Diff) []Diff {
insPoint := equalities.data
diffs = append(
diffs[:insPoint],
- append([]Diff{{DiffDelete, lastequality}}, diffs[insPoint:]...)...)
+ append([]Diff{Diff{DiffDelete, lastequality}}, diffs[insPoint:]...)...)
// Change second copy to insert.
diffs[insPoint+1].Type = DiffInsert
@@ -726,7 +726,7 @@ func (dmp *DiffMatchPatch) DiffCleanupSemantic(diffs []Diff) []Diff {
// Overlap found. Insert an equality and trim the surrounding edits.
diffs = append(
diffs[:pointer],
- append([]Diff{{DiffEqual, insertion[:overlapLength1]}}, diffs[pointer:]...)...)
+ append([]Diff{Diff{DiffEqual, insertion[:overlapLength1]}}, diffs[pointer:]...)...)
diffs[pointer-1].Text =
deletion[0 : len(deletion)-overlapLength1]
@@ -955,7 +955,7 @@ func (dmp *DiffMatchPatch) DiffCleanupEfficiency(diffs []Diff) []Diff {
// Duplicate record.
diffs = append(diffs[:insPoint],
- append([]Diff{{DiffDelete, lastequality}}, diffs[insPoint:]...)...)
+ append([]Diff{Diff{DiffDelete, lastequality}}, diffs[insPoint:]...)...)
// Change second copy to insert.
diffs[insPoint+1].Type = DiffInsert
@@ -1028,7 +1028,7 @@ func (dmp *DiffMatchPatch) DiffCleanupMerge(diffs []Diff) []Diff {
if x > 0 && diffs[x-1].Type == DiffEqual {
diffs[x-1].Text += string(textInsert[:commonlength])
} else {
- diffs = append([]Diff{{DiffEqual, string(textInsert[:commonlength])}}, diffs...)
+ diffs = append([]Diff{Diff{DiffEqual, string(textInsert[:commonlength])}}, diffs...)
pointer++
}
textInsert = textInsert[commonlength:]
diff --git a/vendor/github.com/sergi/go-diff/diffmatchpatch/patch.go b/vendor/github.com/sergi/go-diff/diffmatchpatch/patch.go
index 1708a96fbed..223c43c4268 100644
--- a/vendor/github.com/sergi/go-diff/diffmatchpatch/patch.go
+++ b/vendor/github.com/sergi/go-diff/diffmatchpatch/patch.go
@@ -93,7 +93,7 @@ func (dmp *DiffMatchPatch) PatchAddContext(patch Patch, text string) Patch {
// Add the prefix.
prefix := text[max(0, patch.Start2-padding):patch.Start2]
if len(prefix) != 0 {
- patch.diffs = append([]Diff{{DiffEqual, prefix}}, patch.diffs...)
+ patch.diffs = append([]Diff{Diff{DiffEqual, prefix}}, patch.diffs...)
}
// Add the suffix.
suffix := text[patch.Start2+patch.Length1 : min(len(text), patch.Start2+patch.Length1+padding)]
@@ -336,7 +336,7 @@ func (dmp *DiffMatchPatch) PatchAddPadding(patches []Patch) string {
// Add some padding on start of first diff.
if len(patches[0].diffs) == 0 || patches[0].diffs[0].Type != DiffEqual {
// Add nullPadding equality.
- patches[0].diffs = append([]Diff{{DiffEqual, nullPadding}}, patches[0].diffs...)
+ patches[0].diffs = append([]Diff{Diff{DiffEqual, nullPadding}}, patches[0].diffs...)
patches[0].Start1 -= paddingLength // Should be 0.
patches[0].Start2 -= paddingLength // Should be 0.
patches[0].Length1 += paddingLength
diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go
index a3fe975f049..e6b321f4bb6 100644
--- a/vendor/golang.org/x/net/http2/transport.go
+++ b/vendor/golang.org/x/net/http2/transport.go
@@ -321,9 +321,7 @@ func (noCachedConnError) Error() string { return "http2: no cached c
// or its equivalent renamed type in net/http2's h2_bundle.go. Both types
// may coexist in the same running program.
func isNoCachedConnError(err error) bool {
- _, ok := err.(interface {
- IsHTTP2NoCachedConnError()
- })
+ _, ok := err.(interface{ IsHTTP2NoCachedConnError() })
return ok
}
diff --git a/vendor/golang.org/x/text/language/gen.go b/vendor/golang.org/x/text/language/gen.go
index fea288d4621..302f1940aaf 100644
--- a/vendor/golang.org/x/text/language/gen.go
+++ b/vendor/golang.org/x/text/language/gen.go
@@ -1050,7 +1050,7 @@ func (b *builder) writeRegion() {
m49Index := [9]int16{}
fromM49 := []uint16{}
m49 := []int{}
- for k := range fromM49map {
+ for k, _ := range fromM49map {
m49 = append(m49, int(k))
}
sort.Ints(m49)
diff --git a/vendor/golang.org/x/text/language/lookup.go b/vendor/golang.org/x/text/language/lookup.go
index 96d16dac9d3..1d80ac37082 100644
--- a/vendor/golang.org/x/text/language/lookup.go
+++ b/vendor/golang.org/x/text/language/lookup.go
@@ -344,39 +344,39 @@ var (
// grandfatheredMap holds a mapping from legacy and grandfathered tags to
// their base language or index to more elaborate tag.
grandfatheredMap = map[[maxLen]byte]int16{
- {'a', 'r', 't', '-', 'l', 'o', 'j', 'b', 'a', 'n'}: _jbo, // art-lojban
- {'i', '-', 'a', 'm', 'i'}: _ami, // i-ami
- {'i', '-', 'b', 'n', 'n'}: _bnn, // i-bnn
- {'i', '-', 'h', 'a', 'k'}: _hak, // i-hak
- {'i', '-', 'k', 'l', 'i', 'n', 'g', 'o', 'n'}: _tlh, // i-klingon
- {'i', '-', 'l', 'u', 'x'}: _lb, // i-lux
- {'i', '-', 'n', 'a', 'v', 'a', 'j', 'o'}: _nv, // i-navajo
- {'i', '-', 'p', 'w', 'n'}: _pwn, // i-pwn
- {'i', '-', 't', 'a', 'o'}: _tao, // i-tao
- {'i', '-', 't', 'a', 'y'}: _tay, // i-tay
- {'i', '-', 't', 's', 'u'}: _tsu, // i-tsu
- {'n', 'o', '-', 'b', 'o', 'k'}: _nb, // no-bok
- {'n', 'o', '-', 'n', 'y', 'n'}: _nn, // no-nyn
- {'s', 'g', 'n', '-', 'b', 'e', '-', 'f', 'r'}: _sfb, // sgn-BE-FR
- {'s', 'g', 'n', '-', 'b', 'e', '-', 'n', 'l'}: _vgt, // sgn-BE-NL
- {'s', 'g', 'n', '-', 'c', 'h', '-', 'd', 'e'}: _sgg, // sgn-CH-DE
- {'z', 'h', '-', 'g', 'u', 'o', 'y', 'u'}: _cmn, // zh-guoyu
- {'z', 'h', '-', 'h', 'a', 'k', 'k', 'a'}: _hak, // zh-hakka
- {'z', 'h', '-', 'm', 'i', 'n', '-', 'n', 'a', 'n'}: _nan, // zh-min-nan
- {'z', 'h', '-', 'x', 'i', 'a', 'n', 'g'}: _hsn, // zh-xiang
+ [maxLen]byte{'a', 'r', 't', '-', 'l', 'o', 'j', 'b', 'a', 'n'}: _jbo, // art-lojban
+ [maxLen]byte{'i', '-', 'a', 'm', 'i'}: _ami, // i-ami
+ [maxLen]byte{'i', '-', 'b', 'n', 'n'}: _bnn, // i-bnn
+ [maxLen]byte{'i', '-', 'h', 'a', 'k'}: _hak, // i-hak
+ [maxLen]byte{'i', '-', 'k', 'l', 'i', 'n', 'g', 'o', 'n'}: _tlh, // i-klingon
+ [maxLen]byte{'i', '-', 'l', 'u', 'x'}: _lb, // i-lux
+ [maxLen]byte{'i', '-', 'n', 'a', 'v', 'a', 'j', 'o'}: _nv, // i-navajo
+ [maxLen]byte{'i', '-', 'p', 'w', 'n'}: _pwn, // i-pwn
+ [maxLen]byte{'i', '-', 't', 'a', 'o'}: _tao, // i-tao
+ [maxLen]byte{'i', '-', 't', 'a', 'y'}: _tay, // i-tay
+ [maxLen]byte{'i', '-', 't', 's', 'u'}: _tsu, // i-tsu
+ [maxLen]byte{'n', 'o', '-', 'b', 'o', 'k'}: _nb, // no-bok
+ [maxLen]byte{'n', 'o', '-', 'n', 'y', 'n'}: _nn, // no-nyn
+ [maxLen]byte{'s', 'g', 'n', '-', 'b', 'e', '-', 'f', 'r'}: _sfb, // sgn-BE-FR
+ [maxLen]byte{'s', 'g', 'n', '-', 'b', 'e', '-', 'n', 'l'}: _vgt, // sgn-BE-NL
+ [maxLen]byte{'s', 'g', 'n', '-', 'c', 'h', '-', 'd', 'e'}: _sgg, // sgn-CH-DE
+ [maxLen]byte{'z', 'h', '-', 'g', 'u', 'o', 'y', 'u'}: _cmn, // zh-guoyu
+ [maxLen]byte{'z', 'h', '-', 'h', 'a', 'k', 'k', 'a'}: _hak, // zh-hakka
+ [maxLen]byte{'z', 'h', '-', 'm', 'i', 'n', '-', 'n', 'a', 'n'}: _nan, // zh-min-nan
+ [maxLen]byte{'z', 'h', '-', 'x', 'i', 'a', 'n', 'g'}: _hsn, // zh-xiang
// Grandfathered tags with no modern replacement will be converted as
// follows:
- {'c', 'e', 'l', '-', 'g', 'a', 'u', 'l', 'i', 's', 'h'}: -1, // cel-gaulish
- {'e', 'n', '-', 'g', 'b', '-', 'o', 'e', 'd'}: -2, // en-GB-oed
- {'i', '-', 'd', 'e', 'f', 'a', 'u', 'l', 't'}: -3, // i-default
- {'i', '-', 'e', 'n', 'o', 'c', 'h', 'i', 'a', 'n'}: -4, // i-enochian
- {'i', '-', 'm', 'i', 'n', 'g', 'o'}: -5, // i-mingo
- {'z', 'h', '-', 'm', 'i', 'n'}: -6, // zh-min
+ [maxLen]byte{'c', 'e', 'l', '-', 'g', 'a', 'u', 'l', 'i', 's', 'h'}: -1, // cel-gaulish
+ [maxLen]byte{'e', 'n', '-', 'g', 'b', '-', 'o', 'e', 'd'}: -2, // en-GB-oed
+ [maxLen]byte{'i', '-', 'd', 'e', 'f', 'a', 'u', 'l', 't'}: -3, // i-default
+ [maxLen]byte{'i', '-', 'e', 'n', 'o', 'c', 'h', 'i', 'a', 'n'}: -4, // i-enochian
+ [maxLen]byte{'i', '-', 'm', 'i', 'n', 'g', 'o'}: -5, // i-mingo
+ [maxLen]byte{'z', 'h', '-', 'm', 'i', 'n'}: -6, // zh-min
// CLDR-specific tag.
- {'r', 'o', 'o', 't'}: 0, // root
- {'e', 'n', '-', 'u', 's', '-', 'p', 'o', 's', 'i', 'x'}: -7, // en_US_POSIX"
+ [maxLen]byte{'r', 'o', 'o', 't'}: 0, // root
+ [maxLen]byte{'e', 'n', '-', 'u', 's', '-', 'p', 'o', 's', 'i', 'x'}: -7, // en_US_POSIX"
}
altTagIndex = [...]uint8{0, 17, 31, 45, 61, 74, 86, 102}
diff --git a/vendor/golang.org/x/text/language/tables.go b/vendor/golang.org/x/text/language/tables.go
index a28524e1d72..b738d457b5d 100644
--- a/vendor/golang.org/x/text/language/tables.go
+++ b/vendor/golang.org/x/text/language/tables.go
@@ -3348,9 +3348,9 @@ var regionToGroups = [358]uint8{
// Size: 18 bytes, 3 elements
var paradigmLocales = [3][3]uint16{
- 0: {0x139, 0x0, 0x7b},
- 1: {0x13e, 0x0, 0x1f},
- 2: {0x3c0, 0x41, 0xee},
+ 0: [3]uint16{0x139, 0x0, 0x7b},
+ 1: [3]uint16{0x13e, 0x0, 0x1f},
+ 2: [3]uint16{0x3c0, 0x41, 0xee},
}
type mutualIntelligibility struct {
diff --git a/vendor/golang.org/x/text/unicode/cldr/cldr.go b/vendor/golang.org/x/text/unicode/cldr/cldr.go
index 19b8cefd706..2197f8ac268 100644
--- a/vendor/golang.org/x/text/unicode/cldr/cldr.go
+++ b/vendor/golang.org/x/text/unicode/cldr/cldr.go
@@ -110,7 +110,7 @@ func (cldr *CLDR) Supplemental() *SupplementalData {
func (cldr *CLDR) Locales() []string {
loc := []string{"root"}
hasRoot := false
- for l := range cldr.locale {
+ for l, _ := range cldr.locale {
if l == "root" {
hasRoot = true
continue
diff --git a/vendor/golang.org/x/text/unicode/cldr/resolve.go b/vendor/golang.org/x/text/unicode/cldr/resolve.go
index c6919216b80..691b5903fe4 100644
--- a/vendor/golang.org/x/text/unicode/cldr/resolve.go
+++ b/vendor/golang.org/x/text/unicode/cldr/resolve.go
@@ -289,7 +289,7 @@ var distinguishing = map[string][]string{
"mzone": nil,
"from": nil,
"to": nil,
- "type": {
+ "type": []string{
"abbreviationFallback",
"default",
"mapping",
@@ -527,7 +527,7 @@ func (cldr *CLDR) inheritSlice(enc, v, parent reflect.Value) (res reflect.Value,
}
}
keys := make([]string, 0, len(index))
- for k := range index {
+ for k, _ := range index {
keys = append(keys, k)
}
sort.Strings(keys)
diff --git a/vendor/golang.org/x/text/unicode/cldr/slice.go b/vendor/golang.org/x/text/unicode/cldr/slice.go
index ea5f31a3903..388c983ff13 100644
--- a/vendor/golang.org/x/text/unicode/cldr/slice.go
+++ b/vendor/golang.org/x/text/unicode/cldr/slice.go
@@ -83,7 +83,7 @@ func (s Slice) Group(fn func(e Elem) string) []Slice {
m[key] = append(m[key], vi)
}
keys := []string{}
- for k := range m {
+ for k, _ := range m {
keys = append(keys, k)
}
sort.Strings(keys)
diff --git a/vendor/golang.org/x/text/unicode/norm/maketables.go b/vendor/golang.org/x/text/unicode/norm/maketables.go
index f66778d450a..338c395ee6f 100644
--- a/vendor/golang.org/x/text/unicode/norm/maketables.go
+++ b/vendor/golang.org/x/text/unicode/norm/maketables.go
@@ -241,7 +241,7 @@ func compactCCC() {
m[c.ccc] = 0
}
cccs := []int{}
- for v := range m {
+ for v, _ := range m {
cccs = append(cccs, int(v))
}
sort.Ints(cccs)
diff --git a/vendor/gopkg.in/macaron.v1/context.go b/vendor/gopkg.in/macaron.v1/context.go
index 0f86e0d41ec..94a8c45d7da 100644
--- a/vendor/gopkg.in/macaron.v1/context.go
+++ b/vendor/gopkg.in/macaron.v1/context.go
@@ -270,7 +270,7 @@ func (ctx *Context) SetParams(name, val string) {
// ReplaceAllParams replace all current params with given params
func (ctx *Context) ReplaceAllParams(params Params) {
- ctx.params = params
+ ctx.params = params;
}
// ParamsEscape returns escapred params result.