浏览代码

Merge pull request #11550 from grafana/sql_ts_precision

sql tsdb: fix precision and data type support for time columns
Daniel Lee 7 年之前
父节点
当前提交
ee303c03e9

+ 122 - 26
docker/blocks/mssql_tests/dashboard.json

@@ -100,7 +100,7 @@
   "gnetId": null,
   "gnetId": null,
   "graphTooltip": 0,
   "graphTooltip": 0,
   "id": null,
   "id": null,
-  "iteration": 1521715844826,
+  "iteration": 1523320861623,
   "links": [],
   "links": [],
   "panels": [
   "panels": [
     {
     {
@@ -443,7 +443,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -522,7 +526,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -601,7 +609,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -680,7 +692,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -759,7 +775,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -838,7 +858,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -927,7 +951,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1026,7 +1054,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1115,7 +1147,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1196,7 +1232,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1285,7 +1325,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1366,7 +1410,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1455,7 +1503,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1536,7 +1588,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1619,7 +1675,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1702,7 +1762,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1792,7 +1856,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1875,7 +1943,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1965,7 +2037,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -2048,7 +2124,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -2138,7 +2218,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -2221,7 +2305,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -2311,7 +2399,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -2394,7 +2486,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     }
     }
   ],
   ],
   "refresh": false,
   "refresh": false,
@@ -2504,5 +2600,5 @@
   "timezone": "",
   "timezone": "",
   "title": "Microsoft SQL Server Data Source Test",
   "title": "Microsoft SQL Server Data Source Test",
   "uid": "GlAqcPgmz",
   "uid": "GlAqcPgmz",
-  "version": 57
+  "version": 58
 }
 }

+ 25 - 14
docker/blocks/mysql/dashboard.json

@@ -2,7 +2,7 @@
   "__inputs": [
   "__inputs": [
     {
     {
       "name": "DS_MYSQL",
       "name": "DS_MYSQL",
-      "label": "Mysql",
+      "label": "MySQL",
       "description": "",
       "description": "",
       "type": "datasource",
       "type": "datasource",
       "pluginId": "mysql",
       "pluginId": "mysql",
@@ -20,19 +20,19 @@
       "type": "panel",
       "type": "panel",
       "id": "graph",
       "id": "graph",
       "name": "Graph",
       "name": "Graph",
-      "version": ""
+      "version": "5.0.0"
     },
     },
     {
     {
       "type": "datasource",
       "type": "datasource",
       "id": "mysql",
       "id": "mysql",
       "name": "MySQL",
       "name": "MySQL",
-      "version": "1.0.0"
+      "version": "5.0.0"
     },
     },
     {
     {
       "type": "panel",
       "type": "panel",
       "id": "table",
       "id": "table",
       "name": "Table",
       "name": "Table",
-      "version": ""
+      "version": "5.0.0"
     }
     }
   ],
   ],
   "annotations": {
   "annotations": {
@@ -53,7 +53,7 @@
   "gnetId": null,
   "gnetId": null,
   "graphTooltip": 0,
   "graphTooltip": 0,
   "id": null,
   "id": null,
-  "iteration": 1518602729468,
+  "iteration": 1523372133566,
   "links": [],
   "links": [],
   "panels": [
   "panels": [
     {
     {
@@ -118,7 +118,7 @@
       ],
       ],
       "thresholds": [],
       "thresholds": [],
       "timeFrom": null,
       "timeFrom": null,
-      "timeShift": "1h",
+      "timeShift": null,
       "title": "Average logins / $summarize",
       "title": "Average logins / $summarize",
       "tooltip": {
       "tooltip": {
         "shared": true,
         "shared": true,
@@ -150,7 +150,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -204,7 +208,7 @@
       ],
       ],
       "thresholds": [],
       "thresholds": [],
       "timeFrom": null,
       "timeFrom": null,
-      "timeShift": "1h",
+      "timeShift": null,
       "title": "Average payments started/ended / $summarize",
       "title": "Average payments started/ended / $summarize",
       "tooltip": {
       "tooltip": {
         "shared": true,
         "shared": true,
@@ -236,7 +240,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -284,7 +292,7 @@
       ],
       ],
       "thresholds": [],
       "thresholds": [],
       "timeFrom": null,
       "timeFrom": null,
-      "timeShift": "1h",
+      "timeShift": null,
       "title": "Max CPU / $summarize",
       "title": "Max CPU / $summarize",
       "tooltip": {
       "tooltip": {
         "shared": true,
         "shared": true,
@@ -316,7 +324,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "columns": [],
       "columns": [],
@@ -369,7 +381,7 @@
           "target": ""
           "target": ""
         }
         }
       ],
       ],
-      "timeShift": "1h",
+      "timeShift": null,
       "title": "Values",
       "title": "Values",
       "transform": "table",
       "transform": "table",
       "type": "table"
       "type": "table"
@@ -428,7 +440,6 @@
         "auto_count": 5,
         "auto_count": 5,
         "auto_min": "10s",
         "auto_min": "10s",
         "current": {
         "current": {
-          "selected": true,
           "text": "1m",
           "text": "1m",
           "value": "1m"
           "value": "1m"
         },
         },
@@ -545,5 +556,5 @@
   "timezone": "",
   "timezone": "",
   "title": "Grafana Fake Data Gen - MySQL",
   "title": "Grafana Fake Data Gen - MySQL",
   "uid": "DGsCac3kz",
   "uid": "DGsCac3kz",
-  "version": 6
+  "version": 8
 }
 }

+ 0 - 3
docker/blocks/mysql/docker-compose.yaml

@@ -7,9 +7,6 @@
       MYSQL_PASSWORD: password
       MYSQL_PASSWORD: password
     ports:
     ports:
       - "3306:3306"
       - "3306:3306"
-    volumes:
-      - /etc/localtime:/etc/localtime:ro
-      - /etc/timezone:/etc/timezone:ro
     command: [mysqld, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci, --innodb_monitor_enable=all]
     command: [mysqld, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci, --innodb_monitor_enable=all]
 
 
   fake-mysql-data:
   fake-mysql-data:

+ 3 - 0
docker/blocks/mysql_tests/Dockerfile

@@ -0,0 +1,3 @@
+FROM mysql:latest
+ADD setup.sql /docker-entrypoint-initdb.d
+CMD ["mysqld"]

+ 116 - 42
docker/blocks/mysql_tests/dashboard.json

@@ -7,14 +7,6 @@
       "type": "datasource",
       "type": "datasource",
       "pluginId": "mysql",
       "pluginId": "mysql",
       "pluginName": "MySQL"
       "pluginName": "MySQL"
-    },
-    {
-      "name": "DS_MSSQL_TEST",
-      "label": "MSSQL Test",
-      "description": "",
-      "type": "datasource",
-      "pluginId": "mssql",
-      "pluginName": "Microsoft SQL Server"
     }
     }
   ],
   ],
   "__requires": [
   "__requires": [
@@ -30,12 +22,6 @@
       "name": "Graph",
       "name": "Graph",
       "version": "5.0.0"
       "version": "5.0.0"
     },
     },
-    {
-      "type": "datasource",
-      "id": "mssql",
-      "name": "Microsoft SQL Server",
-      "version": "1.0.0"
-    },
     {
     {
       "type": "datasource",
       "type": "datasource",
       "id": "mysql",
       "id": "mysql",
@@ -114,7 +100,7 @@
   "gnetId": null,
   "gnetId": null,
   "graphTooltip": 0,
   "graphTooltip": 0,
   "id": null,
   "id": null,
-  "iteration": 1521715720483,
+  "iteration": 1523320712115,
   "links": [],
   "links": [],
   "panels": [
   "panels": [
     {
     {
@@ -349,7 +335,7 @@
         {
         {
           "alias": "Time",
           "alias": "Time",
           "dateFormat": "YYYY-MM-DD HH:mm:ss",
           "dateFormat": "YYYY-MM-DD HH:mm:ss",
-          "pattern": "time_sec",
+          "pattern": "time",
           "type": "date"
           "type": "date"
         },
         },
         {
         {
@@ -457,7 +443,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -536,7 +526,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -615,7 +609,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -694,7 +692,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -773,7 +775,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -852,7 +858,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -941,7 +951,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1034,7 +1048,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1123,7 +1141,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1204,7 +1226,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1293,7 +1319,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1374,7 +1404,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1463,7 +1497,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1544,7 +1582,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1634,14 +1676,18 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
       "bars": true,
       "bars": true,
       "dashLength": 10,
       "dashLength": 10,
       "dashes": false,
       "dashes": false,
-      "datasource": "${DS_MSSQL_TEST}",
+      "datasource": "${DS_MYSQL_TEST}",
       "fill": 1,
       "fill": 1,
       "gridPos": {
       "gridPos": {
         "h": 8,
         "h": 8,
@@ -1717,7 +1763,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1807,7 +1857,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1890,7 +1944,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1980,7 +2038,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -2063,7 +2125,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -2153,7 +2219,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -2236,7 +2306,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     }
     }
   ],
   ],
   "refresh": false,
   "refresh": false,
@@ -2315,8 +2389,8 @@
     ]
     ]
   },
   },
   "time": {
   "time": {
-    "from": "2018-03-15T11:30:00.000Z",
-    "to": "2018-03-15T12:55:01.000Z"
+    "from": "2018-03-15T12:30:00.000Z",
+    "to": "2018-03-15T13:55:01.000Z"
   },
   },
   "timepicker": {
   "timepicker": {
     "refresh_intervals": [
     "refresh_intervals": [
@@ -2346,5 +2420,5 @@
   "timezone": "",
   "timezone": "",
   "title": "MySQL Data Source Test",
   "title": "MySQL Data Source Test",
   "uid": "Hmf8FDkmz",
   "uid": "Hmf8FDkmz",
-  "version": 9
+  "version": 12
 }
 }

+ 2 - 4
docker/blocks/mysql_tests/docker-compose.yaml

@@ -1,5 +1,6 @@
   mysqltests:
   mysqltests:
-    image: mysql:latest
+    build:
+      context: blocks/mysql_tests
     environment:
     environment:
       MYSQL_ROOT_PASSWORD: rootpass
       MYSQL_ROOT_PASSWORD: rootpass
       MYSQL_DATABASE: grafana_tests
       MYSQL_DATABASE: grafana_tests
@@ -7,7 +8,4 @@
       MYSQL_PASSWORD: password
       MYSQL_PASSWORD: password
     ports:
     ports:
       - "3306:3306"
       - "3306:3306"
-    volumes:
-      - /etc/localtime:/etc/localtime:ro
-      - /etc/timezone:/etc/timezone:ro
     tmpfs: /var/lib/mysql:rw
     tmpfs: /var/lib/mysql:rw

+ 2 - 0
docker/blocks/mysql_tests/setup.sql

@@ -0,0 +1,2 @@
+CREATE DATABASE grafana_ds_tests;
+GRANT ALL PRIVILEGES ON grafana_ds_tests.* TO 'grafana';

+ 3 - 0
docker/blocks/postgres_tests/Dockerfile

@@ -0,0 +1,3 @@
+FROM postgres:latest
+ADD setup.sql /docker-entrypoint-initdb.d
+CMD ["postgres"]

+ 111 - 23
docker/blocks/postgres_tests/dashboard.json

@@ -100,7 +100,7 @@
   "gnetId": null,
   "gnetId": null,
   "graphTooltip": 0,
   "graphTooltip": 0,
   "id": null,
   "id": null,
-  "iteration": 1521725946837,
+  "iteration": 1523320929325,
   "links": [],
   "links": [],
   "panels": [
   "panels": [
     {
     {
@@ -443,7 +443,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -522,7 +526,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -601,7 +609,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -680,7 +692,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -759,7 +775,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -838,7 +858,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -927,7 +951,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1008,7 +1036,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1097,7 +1129,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1178,7 +1214,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1267,7 +1307,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1348,7 +1392,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1437,7 +1485,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1518,7 +1570,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1608,7 +1664,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1691,7 +1751,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1781,7 +1845,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1864,7 +1932,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -1954,7 +2026,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -2037,7 +2113,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -2127,7 +2207,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     },
     },
     {
     {
       "aliasColors": {},
       "aliasColors": {},
@@ -2210,7 +2294,11 @@
           "min": null,
           "min": null,
           "show": true
           "show": true
         }
         }
-      ]
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
     }
     }
   ],
   ],
   "refresh": false,
   "refresh": false,

+ 2 - 1
docker/blocks/postgres_tests/docker-compose.yaml

@@ -1,5 +1,6 @@
   postgrestest:
   postgrestest:
-    image: postgres:latest
+    build:
+      context: blocks/postgres_tests
     environment:
     environment:
       POSTGRES_USER: grafanatest
       POSTGRES_USER: grafanatest
       POSTGRES_PASSWORD: grafanatest
       POSTGRES_PASSWORD: grafanatest

+ 3 - 0
docker/blocks/postgres_tests/setup.sql

@@ -0,0 +1,3 @@
+CREATE DATABASE grafanadstest;
+REVOKE CONNECT ON DATABASE grafanadstest FROM PUBLIC;
+GRANT CONNECT ON DATABASE grafanadstest TO grafanatest;

+ 17 - 1
pkg/services/sqlstore/sqlstore.go

@@ -258,7 +258,7 @@ func InitTestDB(t *testing.T) *xorm.Engine {
 	// x.ShowSQL()
 	// x.ShowSQL()
 
 
 	if err != nil {
 	if err != nil {
-		t.Fatalf("Failed to init in memory sqllite3 db %v", err)
+		t.Fatalf("Failed to init test database: %v", err)
 	}
 	}
 
 
 	sqlutil.CleanDB(x)
 	sqlutil.CleanDB(x)
@@ -269,3 +269,19 @@ func InitTestDB(t *testing.T) *xorm.Engine {
 
 
 	return x
 	return x
 }
 }
+
+func IsTestDbMySql() bool {
+	if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
+		return db == dbMySql
+	}
+
+	return false
+}
+
+func IsTestDbPostgres() bool {
+	if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
+		return db == dbPostgres
+	}
+
+	return false
+}

+ 8 - 7
pkg/tsdb/mssql/mssql.go

@@ -8,8 +8,6 @@ import (
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 
 
-	"time"
-
 	"math"
 	"math"
 
 
 	_ "github.com/denisenkom/go-mssqldb"
 	_ "github.com/denisenkom/go-mssqldb"
@@ -231,15 +229,18 @@ func (e MssqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core.
 			return err
 			return err
 		}
 		}
 
 
+		// 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)
+
 		switch columnValue := values[timeIndex].(type) {
 		switch columnValue := values[timeIndex].(type) {
 		case int64:
 		case int64:
-			timestamp = float64(columnValue * 1000)
+			timestamp = float64(columnValue)
 		case float64:
 		case float64:
-			timestamp = columnValue * 1000
-		case time.Time:
-			timestamp = (float64(columnValue.Unix()) * 1000) + float64(columnValue.Nanosecond()/1e6) // in case someone is trying to map times beyond 2262 :D
+			timestamp = columnValue
 		default:
 		default:
-			return fmt.Errorf("Invalid type for column time, must be of type timestamp or unix timestamp")
+			return fmt.Errorf("Invalid type for column time, must be of type timestamp or unix timestamp, got: %T %v", columnValue, columnValue)
 		}
 		}
 
 
 		if metricIndex >= 0 {
 		if metricIndex >= 0 {

+ 228 - 30
pkg/tsdb/mssql/mssql_test.go

@@ -188,10 +188,8 @@ func TestMSSQL(t *testing.T) {
 				})
 				})
 			}
 			}
 
 
-			for _, s := range series {
-				_, err = sess.Insert(s)
-				So(err, ShouldBeNil)
-			}
+			_, err = sess.InsertMulti(series)
+			So(err, ShouldBeNil)
 
 
 			Convey("When doing a metric query using timeGroup", func() {
 			Convey("When doing a metric query using timeGroup", func() {
 				query := &tsdb.TsdbQuery{
 				query := &tsdb.TsdbQuery{
@@ -312,10 +310,18 @@ func TestMSSQL(t *testing.T) {
 
 
 		Convey("Given a table with metrics having multiple values and measurements", func() {
 		Convey("Given a table with metrics having multiple values and measurements", func() {
 			type metric_values struct {
 			type metric_values struct {
-				Time        time.Time
-				Measurement string
-				ValueOne    int64 `xorm:"integer 'valueOne'"`
-				ValueTwo    int64 `xorm:"integer 'valueTwo'"`
+				Time                time.Time
+				TimeInt64           int64    `xorm:"bigint 'timeInt64' not null"`
+				TimeInt64Nullable   *int64   `xorm:"bigint 'timeInt64Nullable' null"`
+				TimeFloat64         float64  `xorm:"float 'timeFloat64' not null"`
+				TimeFloat64Nullable *float64 `xorm:"float 'timeFloat64Nullable' null"`
+				TimeInt32           int32    `xorm:"int(11) 'timeInt32' not null"`
+				TimeInt32Nullable   *int32   `xorm:"int(11) 'timeInt32Nullable' null"`
+				TimeFloat32         float32  `xorm:"float(11) 'timeFloat32' not null"`
+				TimeFloat32Nullable *float32 `xorm:"float(11) 'timeFloat32Nullable' null"`
+				Measurement         string
+				ValueOne            int64 `xorm:"integer 'valueOne'"`
+				ValueTwo            int64 `xorm:"integer 'valueTwo'"`
 			}
 			}
 
 
 			if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
 			if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
@@ -330,26 +336,219 @@ func TestMSSQL(t *testing.T) {
 				return rand.Int63n(max-min) + min
 				return rand.Int63n(max-min) + min
 			}
 			}
 
 
+			var tInitial time.Time
+
 			series := []*metric_values{}
 			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 i, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
+				if i == 0 {
+					tInitial = t
+				}
+				tSeconds := t.Unix()
+				tSecondsInt32 := int32(tSeconds)
+				tSecondsFloat32 := float32(tSeconds)
+				tMilliseconds := tSeconds * 1e3
+				tMillisecondsFloat := float64(tMilliseconds)
+				first := metric_values{
+					Time:                t,
+					TimeInt64:           tMilliseconds,
+					TimeInt64Nullable:   &(tMilliseconds),
+					TimeFloat64:         tMillisecondsFloat,
+					TimeFloat64Nullable: &tMillisecondsFloat,
+					TimeInt32:           tSecondsInt32,
+					TimeInt32Nullable:   &tSecondsInt32,
+					TimeFloat32:         tSecondsFloat32,
+					TimeFloat32Nullable: &tSecondsFloat32,
+					Measurement:         "Metric A",
+					ValueOne:            rnd(0, 100),
+					ValueTwo:            rnd(0, 100),
+				}
+				second := first
+				second.Measurement = "Metric B"
+				second.ValueOne = rnd(0, 100)
+				second.ValueTwo = rnd(0, 100)
+
+				series = append(series, &first)
+				series = append(series, &second)
 			}
 			}
 
 
-			for _, s := range series {
-				_, err = sess.Insert(s)
+			_, err = sess.InsertMulti(series)
+			So(err, ShouldBeNil)
+
+			Convey("When doing a metric query using epoch (int64) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT TOP 1 timeInt64 as time, valueOne FROM metric_values ORDER BY time`,
+								"format": "time_series",
+							}),
+							RefId: "A",
+						},
+					},
+				}
+
+				resp, err := endpoint.Query(nil, nil, query)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
-			}
+				queryResult := resp.Results["A"]
+				So(queryResult.Error, ShouldBeNil)
+
+				So(len(queryResult.Series), ShouldEqual, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (int64 nullable) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT TOP 1 timeInt64Nullable as time, valueOne FROM metric_values ORDER BY time`,
+								"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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (float64) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT TOP 1 timeFloat64 as time, valueOne FROM metric_values ORDER BY time`,
+								"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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (float64 nullable) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT TOP 1 timeFloat64Nullable as time, valueOne FROM metric_values ORDER BY time`,
+								"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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (int32) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT TOP 1 timeInt32 as time, valueOne FROM metric_values ORDER BY time`,
+								"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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (int32 nullable) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT TOP 1 timeInt32Nullable as time, valueOne FROM metric_values ORDER BY time`,
+								"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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (float32) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT TOP 1 timeFloat32 as time, valueOne FROM metric_values ORDER BY time`,
+								"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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
+			})
+
+			Convey("When doing a metric query using epoch (float32 nullable) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT TOP 1 timeFloat32Nullable as time, valueOne FROM metric_values ORDER BY time`,
+								"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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
+			})
 
 
 			Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
 			Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
 				query := &tsdb.TsdbQuery{
 				query := &tsdb.TsdbQuery{
@@ -476,7 +675,6 @@ func TestMSSQL(t *testing.T) {
 					resp, err := endpoint.Query(nil, nil, query)
 					resp, err := endpoint.Query(nil, nil, query)
 					queryResult := resp.Results["A"]
 					queryResult := resp.Results["A"]
 					So(err, ShouldBeNil)
 					So(err, ShouldBeNil)
-					fmt.Println("query", "sql", queryResult.Meta)
 					So(queryResult.Error, ShouldBeNil)
 					So(queryResult.Error, ShouldBeNil)
 
 
 					So(len(queryResult.Series), ShouldEqual, 4)
 					So(len(queryResult.Series), ShouldEqual, 4)
@@ -696,7 +894,7 @@ func TestMSSQL(t *testing.T) {
 				columns := queryResult.Tables[0].Rows[0]
 				columns := queryResult.Tables[0].Rows[0]
 
 
 				//Should be in milliseconds
 				//Should be in milliseconds
-				So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000))
+				So(columns[0].(float64), ShouldEqual, float64(dt.UnixNano()/1e6))
 			})
 			})
 
 
 			Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
 			Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
@@ -850,15 +1048,15 @@ func TestMSSQL(t *testing.T) {
 
 
 func InitMSSQLTestDB(t *testing.T) *xorm.Engine {
 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, err := xorm.NewEngine(sqlutil.TestDB_Mssql.DriverName, strings.Replace(sqlutil.TestDB_Mssql.ConnStr, "localhost", serverIP, 1))
+	if err != nil {
+		t.Fatalf("Failed to init mssql db %v", err)
+	}
+
 	x.DatabaseTZ = time.UTC
 	x.DatabaseTZ = time.UTC
 	x.TZLocation = time.UTC
 	x.TZLocation = time.UTC
 
 
 	// x.ShowSQL()
 	// x.ShowSQL()
 
 
-	if err != nil {
-		t.Fatalf("Failed to init mssql db %v", err)
-	}
-
 	return x
 	return x
 }
 }
 
 

+ 8 - 6
pkg/tsdb/mysql/mysql.go

@@ -8,7 +8,6 @@ import (
 	"math"
 	"math"
 	"reflect"
 	"reflect"
 	"strconv"
 	"strconv"
-	"time"
 
 
 	"github.com/go-sql-driver/mysql"
 	"github.com/go-sql-driver/mysql"
 	"github.com/go-xorm/core"
 	"github.com/go-xorm/core"
@@ -239,15 +238,18 @@ func (e MysqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core.
 			return err
 			return err
 		}
 		}
 
 
+		// 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)
+
 		switch columnValue := values[timeIndex].(type) {
 		switch columnValue := values[timeIndex].(type) {
 		case int64:
 		case int64:
-			timestamp = float64(columnValue * 1000)
+			timestamp = float64(columnValue)
 		case float64:
 		case float64:
-			timestamp = columnValue * 1000
-		case time.Time:
-			timestamp = float64(columnValue.UnixNano() / 1e6)
+			timestamp = columnValue
 		default:
 		default:
-			return fmt.Errorf("Invalid type for column time, must be of type timestamp or unix timestamp, got: %T %v", columnValue, columnValue)
+			return fmt.Errorf("Invalid type for column time/time_sec, must be of type timestamp or unix timestamp, got: %T %v", columnValue, columnValue)
 		}
 		}
 
 
 		if metricIndex >= 0 {
 		if metricIndex >= 0 {

+ 294 - 39
pkg/tsdb/mysql/mysql_test.go

@@ -3,25 +3,35 @@ package mysql
 import (
 import (
 	"fmt"
 	"fmt"
 	"math/rand"
 	"math/rand"
+	"strings"
 	"testing"
 	"testing"
 	"time"
 	"time"
 
 
 	"github.com/go-xorm/xorm"
 	"github.com/go-xorm/xorm"
 	"github.com/grafana/grafana/pkg/components/simplejson"
 	"github.com/grafana/grafana/pkg/components/simplejson"
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/log"
+	"github.com/grafana/grafana/pkg/services/sqlstore"
 	"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
 	"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
 	"github.com/grafana/grafana/pkg/tsdb"
 	"github.com/grafana/grafana/pkg/tsdb"
 	. "github.com/smartystreets/goconvey/convey"
 	. "github.com/smartystreets/goconvey/convey"
 )
 )
 
 
-// To run this test, remove the Skip from SkipConvey
-// and set up a MySQL db named grafana_tests and a user/password grafana/password
+// To run this test, set runMySqlTests=true
+// and set up a MySQL db named grafana_ds_tests and a user/password grafana/password
 // Use the docker/blocks/mysql_tests/docker-compose.yaml to spin up a
 // Use the docker/blocks/mysql_tests/docker-compose.yaml to spin up a
 // preconfigured MySQL server suitable for running these tests.
 // preconfigured MySQL server suitable for running these tests.
 // Thers's also a dashboard.json in same directory that you can import to Grafana
 // 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.
 // once you've created a datasource for the test server/database.
 func TestMySQL(t *testing.T) {
 func TestMySQL(t *testing.T) {
-	SkipConvey("MySQL", t, func() {
+	// change to true to run the MySQL tests
+	runMySqlTests := false
+	// runMySqlTests := true
+
+	if !(sqlstore.IsTestDbMySql() || runMySqlTests) {
+		t.Skip()
+	}
+
+	Convey("MySQL", t, func() {
 		x := InitMySQLTestDB(t)
 		x := InitMySQLTestDB(t)
 
 
 		endpoint := &MysqlQueryEndpoint{
 		endpoint := &MysqlQueryEndpoint{
@@ -35,7 +45,7 @@ func TestMySQL(t *testing.T) {
 		sess := x.NewSession()
 		sess := x.NewSession()
 		defer sess.Close()
 		defer sess.Close()
 
 
-		fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.Local)
+		fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC)
 
 
 		Convey("Given a table with different native data types", func() {
 		Convey("Given a table with different native data types", func() {
 			if exists, err := sess.IsTableExist("mysql_types"); err != nil || exists {
 			if exists, err := sess.IsTableExist("mysql_types"); err != nil || exists {
@@ -121,9 +131,8 @@ func TestMySQL(t *testing.T) {
 				So(column[7].(float64), ShouldEqual, 1.11)
 				So(column[7].(float64), ShouldEqual, 1.11)
 				So(column[8].(float64), ShouldEqual, 2.22)
 				So(column[8].(float64), ShouldEqual, 2.22)
 				So(*column[9].(*float32), ShouldEqual, 3.33)
 				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[10].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now())
+				So(column[11].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now())
 				So(column[12].(string), ShouldEqual, "11:11:11")
 				So(column[12].(string), ShouldEqual, "11:11:11")
 				So(column[13].(int64), ShouldEqual, 2018)
 				So(column[13].(int64), ShouldEqual, 2018)
 				So(*column[14].(*[]byte), ShouldHaveSameTypeAs, []byte{1})
 				So(*column[14].(*[]byte), ShouldHaveSameTypeAs, []byte{1})
@@ -137,8 +146,8 @@ func TestMySQL(t *testing.T) {
 				So(column[22].(string), ShouldEqual, "longblob")
 				So(column[22].(string), ShouldEqual, "longblob")
 				So(column[23].(string), ShouldEqual, "val2")
 				So(column[23].(string), ShouldEqual, "val2")
 				So(column[24].(string), ShouldEqual, "a,b")
 				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[25].(time.Time).Format("2006-01-02T00:00:00Z"), ShouldEqual, time.Now().UTC().Format("2006-01-02T00:00:00Z"))
+				So(column[26].(float64), ShouldEqual, float64(1.514764861123456*1e12))
 				So(column[27], ShouldEqual, nil)
 				So(column[27], ShouldEqual, nil)
 				So(column[28], ShouldEqual, nil)
 				So(column[28], ShouldEqual, nil)
 				So(column[29], ShouldEqual, "")
 				So(column[29], ShouldEqual, "")
@@ -177,10 +186,8 @@ func TestMySQL(t *testing.T) {
 				})
 				})
 			}
 			}
 
 
-			for _, s := range series {
-				_, err = sess.Insert(s)
-				So(err, ShouldBeNil)
-			}
+			_, err = sess.InsertMulti(series)
+			So(err, ShouldBeNil)
 
 
 			Convey("When doing a metric query using timeGroup", func() {
 			Convey("When doing a metric query using timeGroup", func() {
 				query := &tsdb.TsdbQuery{
 				query := &tsdb.TsdbQuery{
@@ -301,10 +308,19 @@ func TestMySQL(t *testing.T) {
 
 
 		Convey("Given a table with metrics having multiple values and measurements", func() {
 		Convey("Given a table with metrics having multiple values and measurements", func() {
 			type metric_values struct {
 			type metric_values struct {
-				Time        time.Time
-				Measurement string
-				ValueOne    int64 `xorm:"integer 'valueOne'"`
-				ValueTwo    int64 `xorm:"integer 'valueTwo'"`
+				Time                time.Time  `xorm:"datetime 'time' not null"`
+				TimeNullable        *time.Time `xorm:"datetime(6) 'timeNullable' null"`
+				TimeInt64           int64      `xorm:"bigint(20) 'timeInt64' not null"`
+				TimeInt64Nullable   *int64     `xorm:"bigint(20) 'timeInt64Nullable' null"`
+				TimeFloat64         float64    `xorm:"double 'timeFloat64' not null"`
+				TimeFloat64Nullable *float64   `xorm:"double 'timeFloat64Nullable' null"`
+				TimeInt32           int32      `xorm:"int(11) 'timeInt32' not null"`
+				TimeInt32Nullable   *int32     `xorm:"int(11) 'timeInt32Nullable' null"`
+				TimeFloat32         float32    `xorm:"double 'timeFloat32' not null"`
+				TimeFloat32Nullable *float32   `xorm:"double 'timeFloat32Nullable' null"`
+				Measurement         string
+				ValueOne            int64 `xorm:"integer 'valueOne'"`
+				ValueTwo            int64 `xorm:"integer 'valueTwo'"`
 			}
 			}
 
 
 			if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
 			if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
@@ -319,26 +335,265 @@ func TestMySQL(t *testing.T) {
 				return rand.Int63n(max-min) + min
 				return rand.Int63n(max-min) + min
 			}
 			}
 
 
+			var tInitial time.Time
+
 			series := []*metric_values{}
 			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 i, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
+				if i == 0 {
+					tInitial = t
+				}
+				tSeconds := t.Unix()
+				tSecondsInt32 := int32(tSeconds)
+				tSecondsFloat32 := float32(tSeconds)
+				tMilliseconds := tSeconds * 1e3
+				tMillisecondsFloat := float64(tMilliseconds)
+				t2 := t
+				first := metric_values{
+					Time:                t,
+					TimeNullable:        &t2,
+					TimeInt64:           tMilliseconds,
+					TimeInt64Nullable:   &(tMilliseconds),
+					TimeFloat64:         tMillisecondsFloat,
+					TimeFloat64Nullable: &tMillisecondsFloat,
+					TimeInt32:           tSecondsInt32,
+					TimeInt32Nullable:   &tSecondsInt32,
+					TimeFloat32:         tSecondsFloat32,
+					TimeFloat32Nullable: &tSecondsFloat32,
+					Measurement:         "Metric A",
+					ValueOne:            rnd(0, 100),
+					ValueTwo:            rnd(0, 100),
+				}
+				second := first
+				second.Measurement = "Metric B"
+				second.ValueOne = rnd(0, 100)
+				second.ValueTwo = rnd(0, 100)
+
+				series = append(series, &first)
+				series = append(series, &second)
 			}
 			}
 
 
-			for _, s := range series {
-				_, err := sess.Insert(s)
+			_, err = sess.InsertMulti(series)
+			So(err, ShouldBeNil)
+
+			Convey("When doing a metric query using time as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT time, valueOne FROM metric_values ORDER BY time LIMIT 1`,
+								"format": "time_series",
+							}),
+							RefId: "A",
+						},
+					},
+				}
+
+				resp, err := endpoint.Query(nil, nil, query)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
-			}
+				queryResult := resp.Results["A"]
+				So(queryResult.Error, ShouldBeNil)
+
+				So(len(queryResult.Series), ShouldEqual, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using time (nullable) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT timeNullable as time, valueOne FROM metric_values ORDER BY time LIMIT 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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (int64) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT timeInt64 as time, valueOne FROM metric_values ORDER BY time LIMIT 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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (int64 nullable) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT timeInt64Nullable as time, valueOne FROM metric_values ORDER BY time LIMIT 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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (float64) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT timeFloat64 as time, valueOne FROM metric_values ORDER BY time LIMIT 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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (float64 nullable) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT timeFloat64Nullable as time, valueOne FROM metric_values ORDER BY time LIMIT 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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (int32) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT timeInt32 as time, valueOne FROM metric_values ORDER BY time LIMIT 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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (int32 nullable) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT timeInt32Nullable as time, valueOne FROM metric_values ORDER BY time LIMIT 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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (float32) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT timeFloat32 as time, valueOne FROM metric_values ORDER BY time LIMIT 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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
+			})
+
+			Convey("When doing a metric query using epoch (float32 nullable) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT timeFloat32Nullable as time, valueOne FROM metric_values ORDER BY time LIMIT 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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
+			})
 
 
 			Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
 			Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
 				query := &tsdb.TsdbQuery{
 				query := &tsdb.TsdbQuery{
@@ -647,16 +902,16 @@ func TestMySQL(t *testing.T) {
 }
 }
 
 
 func InitMySQLTestDB(t *testing.T) *xorm.Engine {
 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()
-
+	x, err := xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, strings.Replace(sqlutil.TestDB_Mysql.ConnStr, "/grafana_tests", "/grafana_ds_tests", 1))
 	if err != nil {
 	if err != nil {
 		t.Fatalf("Failed to init mysql db %v", err)
 		t.Fatalf("Failed to init mysql db %v", err)
 	}
 	}
 
 
+	x.DatabaseTZ = time.UTC
+	x.TZLocation = time.UTC
+
+	// x.ShowSQL()
+
 	return x
 	return x
 }
 }
 
 

+ 7 - 5
pkg/tsdb/postgres/postgres.go

@@ -7,7 +7,6 @@ import (
 	"math"
 	"math"
 	"net/url"
 	"net/url"
 	"strconv"
 	"strconv"
-	"time"
 
 
 	"github.com/go-xorm/core"
 	"github.com/go-xorm/core"
 	"github.com/grafana/grafana/pkg/components/null"
 	"github.com/grafana/grafana/pkg/components/null"
@@ -219,13 +218,16 @@ func (e PostgresQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *co
 			return err
 			return err
 		}
 		}
 
 
+		// 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)
+
 		switch columnValue := values[timeIndex].(type) {
 		switch columnValue := values[timeIndex].(type) {
 		case int64:
 		case int64:
-			timestamp = float64(columnValue * 1000)
+			timestamp = float64(columnValue)
 		case float64:
 		case float64:
-			timestamp = columnValue * 1000
-		case time.Time:
-			timestamp = float64(columnValue.UnixNano() / 1e6)
+			timestamp = columnValue
 		default:
 		default:
 			return fmt.Errorf("Invalid type for column time, must be of type timestamp or unix timestamp, got: %T %v", columnValue, columnValue)
 			return fmt.Errorf("Invalid type for column time, must be of type timestamp or unix timestamp, got: %T %v", columnValue, columnValue)
 		}
 		}

+ 242 - 33
pkg/tsdb/postgres/postgres_test.go

@@ -3,26 +3,36 @@ package postgres
 import (
 import (
 	"fmt"
 	"fmt"
 	"math/rand"
 	"math/rand"
+	"strings"
 	"testing"
 	"testing"
 	"time"
 	"time"
 
 
 	"github.com/go-xorm/xorm"
 	"github.com/go-xorm/xorm"
 	"github.com/grafana/grafana/pkg/components/simplejson"
 	"github.com/grafana/grafana/pkg/components/simplejson"
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/log"
+	"github.com/grafana/grafana/pkg/services/sqlstore"
 	"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
 	"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
 	"github.com/grafana/grafana/pkg/tsdb"
 	"github.com/grafana/grafana/pkg/tsdb"
 	_ "github.com/lib/pq"
 	_ "github.com/lib/pq"
 	. "github.com/smartystreets/goconvey/convey"
 	. "github.com/smartystreets/goconvey/convey"
 )
 )
 
 
-// To run this test, remove the Skip from SkipConvey
-// and set up a PostgreSQL db named grafanatest and a user/password grafanatest/grafanatest!
+// To run this test, set runMySqlTests=true
+// and set up a PostgreSQL db named grafanadstest and a user/password grafanatest/grafanatest!
 // Use the docker/blocks/postgres_tests/docker-compose.yaml to spin up a
 // Use the docker/blocks/postgres_tests/docker-compose.yaml to spin up a
 // preconfigured Postgres server suitable for running these tests.
 // preconfigured Postgres server suitable for running these tests.
 // Thers's also a dashboard.json in same directory that you can import to Grafana
 // 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.
 // once you've created a datasource for the test server/database.
 func TestPostgres(t *testing.T) {
 func TestPostgres(t *testing.T) {
-	SkipConvey("PostgreSQL", t, func() {
+	// change to true to run the MySQL tests
+	runPostgresTests := false
+	// runPostgresTests := true
+
+	if !(sqlstore.IsTestDbPostgres() || runPostgresTests) {
+		t.Skip()
+	}
+
+	Convey("PostgreSQL", t, func() {
 		x := InitPostgresTestDB(t)
 		x := InitPostgresTestDB(t)
 
 
 		endpoint := &PostgresQueryEndpoint{
 		endpoint := &PostgresQueryEndpoint{
@@ -156,10 +166,8 @@ func TestPostgres(t *testing.T) {
 				})
 				})
 			}
 			}
 
 
-			for _, s := range series {
-				_, err = sess.Insert(s)
-				So(err, ShouldBeNil)
-			}
+			_, err = sess.InsertMulti(series)
+			So(err, ShouldBeNil)
 
 
 			Convey("When doing a metric query using timeGroup", func() {
 			Convey("When doing a metric query using timeGroup", func() {
 				query := &tsdb.TsdbQuery{
 				query := &tsdb.TsdbQuery{
@@ -280,10 +288,18 @@ func TestPostgres(t *testing.T) {
 
 
 		Convey("Given a table with metrics having multiple values and measurements", func() {
 		Convey("Given a table with metrics having multiple values and measurements", func() {
 			type metric_values struct {
 			type metric_values struct {
-				Time        time.Time
-				Measurement string
-				ValueOne    int64 `xorm:"integer 'valueOne'"`
-				ValueTwo    int64 `xorm:"integer 'valueTwo'"`
+				Time                time.Time
+				TimeInt64           int64    `xorm:"bigint 'timeInt64' not null"`
+				TimeInt64Nullable   *int64   `xorm:"bigint 'timeInt64Nullable' null"`
+				TimeFloat64         float64  `xorm:"double 'timeFloat64' not null"`
+				TimeFloat64Nullable *float64 `xorm:"double 'timeFloat64Nullable' null"`
+				TimeInt32           int32    `xorm:"int(11) 'timeInt32' not null"`
+				TimeInt32Nullable   *int32   `xorm:"int(11) 'timeInt32Nullable' null"`
+				TimeFloat32         float32  `xorm:"double 'timeFloat32' not null"`
+				TimeFloat32Nullable *float32 `xorm:"double 'timeFloat32Nullable' null"`
+				Measurement         string
+				ValueOne            int64 `xorm:"integer 'valueOne'"`
+				ValueTwo            int64 `xorm:"integer 'valueTwo'"`
 			}
 			}
 
 
 			if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
 			if exist, err := sess.IsTableExist(metric_values{}); err != nil || exist {
@@ -298,26 +314,219 @@ func TestPostgres(t *testing.T) {
 				return rand.Int63n(max-min) + min
 				return rand.Int63n(max-min) + min
 			}
 			}
 
 
+			var tInitial time.Time
+
 			series := []*metric_values{}
 			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 i, t := range genTimeRangeByInterval(fromStart.Add(-30*time.Minute), 90*time.Minute, 5*time.Minute) {
+				if i == 0 {
+					tInitial = t
+				}
+				tSeconds := t.Unix()
+				tSecondsInt32 := int32(tSeconds)
+				tSecondsFloat32 := float32(tSeconds)
+				tMilliseconds := tSeconds * 1e3
+				tMillisecondsFloat := float64(tMilliseconds)
+				first := metric_values{
+					Time:                t,
+					TimeInt64:           tMilliseconds,
+					TimeInt64Nullable:   &(tMilliseconds),
+					TimeFloat64:         tMillisecondsFloat,
+					TimeFloat64Nullable: &tMillisecondsFloat,
+					TimeInt32:           tSecondsInt32,
+					TimeInt32Nullable:   &tSecondsInt32,
+					TimeFloat32:         tSecondsFloat32,
+					TimeFloat32Nullable: &tSecondsFloat32,
+					Measurement:         "Metric A",
+					ValueOne:            rnd(0, 100),
+					ValueTwo:            rnd(0, 100),
+				}
+				second := first
+				second.Measurement = "Metric B"
+				second.ValueOne = rnd(0, 100)
+				second.ValueTwo = rnd(0, 100)
+
+				series = append(series, &first)
+				series = append(series, &second)
 			}
 			}
 
 
-			for _, s := range series {
-				_, err := sess.Insert(s)
+			_, err = sess.InsertMulti(series)
+			So(err, ShouldBeNil)
+
+			Convey("When doing a metric query using epoch (int64) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT "timeInt64" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 1`,
+								"format": "time_series",
+							}),
+							RefId: "A",
+						},
+					},
+				}
+
+				resp, err := endpoint.Query(nil, nil, query)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
-			}
+				queryResult := resp.Results["A"]
+				So(queryResult.Error, ShouldBeNil)
+
+				So(len(queryResult.Series), ShouldEqual, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (int64 nullable) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT "timeInt64Nullable" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (float64) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT "timeFloat64" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (float64 nullable) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT "timeFloat64Nullable" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (int32) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT "timeInt32" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (int32 nullable) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT "timeInt32Nullable" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(tInitial.UnixNano()/1e6))
+			})
+
+			Convey("When doing a metric query using epoch (float32) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT "timeFloat32" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
+			})
+
+			Convey("When doing a metric query using epoch (float32 nullable) as time column should return metric with time in milliseconds", func() {
+				query := &tsdb.TsdbQuery{
+					Queries: []*tsdb.Query{
+						{
+							Model: simplejson.NewFromAny(map[string]interface{}{
+								"rawSql": `SELECT "timeFloat32Nullable" as time, "valueOne" FROM metric_values ORDER BY time LIMIT 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, 1)
+				So(queryResult.Series[0].Points[0][1].Float64, ShouldEqual, float64(float64(float32(tInitial.Unix())))*1e3)
+			})
 
 
 			Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
 			Convey("When doing a metric query grouping by time and select metric column should return correct series", func() {
 				query := &tsdb.TsdbQuery{
 				query := &tsdb.TsdbQuery{
@@ -473,7 +682,7 @@ func TestPostgres(t *testing.T) {
 				columns := queryResult.Tables[0].Rows[0]
 				columns := queryResult.Tables[0].Rows[0]
 
 
 				//Should be in milliseconds
 				//Should be in milliseconds
-				So(columns[0].(float64), ShouldEqual, float64(dt.Unix()*1000))
+				So(columns[0].(float64), ShouldEqual, float64(dt.UnixNano()/1e6))
 			})
 			})
 
 
 			Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
 			Convey("When doing an annotation query with a time column in epoch second format should return ms", func() {
@@ -626,16 +835,16 @@ func TestPostgres(t *testing.T) {
 }
 }
 
 
 func InitPostgresTestDB(t *testing.T) *xorm.Engine {
 func InitPostgresTestDB(t *testing.T) *xorm.Engine {
-	x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
+	x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, strings.Replace(sqlutil.TestDB_Postgres.ConnStr, "dbname=grafanatest", "dbname=grafanadstest", 1))
+	if err != nil {
+		t.Fatalf("Failed to init postgres db %v", err)
+	}
+
 	x.DatabaseTZ = time.UTC
 	x.DatabaseTZ = time.UTC
 	x.TZLocation = time.UTC
 	x.TZLocation = time.UTC
 
 
 	// x.ShowSQL()
 	// x.ShowSQL()
 
 
-	if err != nil {
-		t.Fatalf("Failed to init postgres db %v", err)
-	}
-
 	return x
 	return x
 }
 }
 
 

+ 27 - 3
pkg/tsdb/sql_engine.go

@@ -135,16 +135,16 @@ func (e *DefaultSqlEngine) Query(
 	return result, nil
 	return result, nil
 }
 }
 
 
-// ConvertTimeColumnToEpochMs converts column named time to unix timestamp in milliseconds
+// ConvertSqlTimeColumnToEpochMs converts column named time to unix timestamp in milliseconds
 // to make native datetime types and epoch dates work in annotation and table queries.
 // to make native datetime types and epoch dates work in annotation and table queries.
 func ConvertSqlTimeColumnToEpochMs(values RowValues, timeIndex int) {
 func ConvertSqlTimeColumnToEpochMs(values RowValues, timeIndex int) {
 	if timeIndex >= 0 {
 	if timeIndex >= 0 {
 		switch value := values[timeIndex].(type) {
 		switch value := values[timeIndex].(type) {
 		case time.Time:
 		case time.Time:
-			values[timeIndex] = EpochPrecisionToMs(float64(value.Unix()))
+			values[timeIndex] = EpochPrecisionToMs(float64(value.UnixNano()))
 		case *time.Time:
 		case *time.Time:
 			if value != nil {
 			if value != nil {
-				values[timeIndex] = EpochPrecisionToMs(float64((*value).Unix()))
+				values[timeIndex] = EpochPrecisionToMs(float64((*value).UnixNano()))
 			}
 			}
 		case int64:
 		case int64:
 			values[timeIndex] = int64(EpochPrecisionToMs(float64(value)))
 			values[timeIndex] = int64(EpochPrecisionToMs(float64(value)))
@@ -152,12 +152,36 @@ func ConvertSqlTimeColumnToEpochMs(values RowValues, timeIndex int) {
 			if value != nil {
 			if value != nil {
 				values[timeIndex] = int64(EpochPrecisionToMs(float64(*value)))
 				values[timeIndex] = int64(EpochPrecisionToMs(float64(*value)))
 			}
 			}
+		case uint64:
+			values[timeIndex] = int64(EpochPrecisionToMs(float64(value)))
+		case *uint64:
+			if value != nil {
+				values[timeIndex] = int64(EpochPrecisionToMs(float64(*value)))
+			}
+		case int32:
+			values[timeIndex] = int64(EpochPrecisionToMs(float64(value)))
+		case *int32:
+			if value != nil {
+				values[timeIndex] = int64(EpochPrecisionToMs(float64(*value)))
+			}
+		case uint32:
+			values[timeIndex] = int64(EpochPrecisionToMs(float64(value)))
+		case *uint32:
+			if value != nil {
+				values[timeIndex] = int64(EpochPrecisionToMs(float64(*value)))
+			}
 		case float64:
 		case float64:
 			values[timeIndex] = EpochPrecisionToMs(value)
 			values[timeIndex] = EpochPrecisionToMs(value)
 		case *float64:
 		case *float64:
 			if value != nil {
 			if value != nil {
 				values[timeIndex] = EpochPrecisionToMs(*value)
 				values[timeIndex] = EpochPrecisionToMs(*value)
 			}
 			}
+		case float32:
+			values[timeIndex] = EpochPrecisionToMs(float64(value))
+		case *float32:
+			if value != nil {
+				values[timeIndex] = EpochPrecisionToMs(float64(*value))
+			}
 		}
 		}
 	}
 	}
 }
 }

+ 163 - 22
pkg/tsdb/sql_engine_test.go

@@ -1,6 +1,7 @@
 package tsdb
 package tsdb
 
 
 import (
 import (
+	"fmt"
 	"testing"
 	"testing"
 	"time"
 	"time"
 
 
@@ -9,37 +10,177 @@ import (
 
 
 func TestSqlEngine(t *testing.T) {
 func TestSqlEngine(t *testing.T) {
 	Convey("SqlEngine", t, func() {
 	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)
+		dt := time.Date(2018, 3, 14, 21, 20, 6, int(527345*time.Microsecond), time.UTC)
+
+		Convey("Given row values with time.Time as time columns", func() {
+			var nilPointer *time.Time
+
+			fixtures := make([]interface{}, 3)
 			fixtures[0] = dt
 			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
+			fixtures[1] = &dt
+			fixtures[2] = nilPointer
 
 
 			for i := range fixtures {
 			for i := range fixtures {
 				ConvertSqlTimeColumnToEpochMs(fixtures, i)
 				ConvertSqlTimeColumnToEpochMs(fixtures, i)
 			}
 			}
 
 
-			Convey("Should convert sql time columns to epoch time in ms ", func() {
-				expected := float64(dt.Unix() * 1000)
+			Convey("When converting them should return epoch time with millisecond precision ", func() {
+				expected := float64(dt.UnixNano()) / float64(time.Millisecond)
 				So(fixtures[0].(float64), ShouldEqual, expected)
 				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[1].(float64), ShouldEqual, expected)
+				So(fixtures[2], ShouldBeNil)
+			})
+		})
 
 
-				So(fixtures[5], ShouldBeNil)
+		Convey("Given row values with int64 as time columns", func() {
+			tSeconds := dt.Unix()
+			tMilliseconds := dt.UnixNano() / 1e6
+			tNanoSeconds := dt.UnixNano()
+			var nilPointer *int64
+
+			fixtures := make([]interface{}, 7)
+			fixtures[0] = tSeconds
+			fixtures[1] = &tSeconds
+			fixtures[2] = tMilliseconds
+			fixtures[3] = &tMilliseconds
+			fixtures[4] = tNanoSeconds
+			fixtures[5] = &tNanoSeconds
+			fixtures[6] = nilPointer
+
+			for i := range fixtures {
+				ConvertSqlTimeColumnToEpochMs(fixtures, i)
+			}
+
+			Convey("When converting them should return epoch time with millisecond precision ", func() {
+				So(fixtures[0].(int64), ShouldEqual, tSeconds*1e3)
+				So(fixtures[1].(int64), ShouldEqual, tSeconds*1e3)
+				So(fixtures[2].(int64), ShouldEqual, tMilliseconds)
+				So(fixtures[3].(int64), ShouldEqual, tMilliseconds)
+				So(fixtures[4].(int64), ShouldEqual, tMilliseconds)
+				So(fixtures[5].(int64), ShouldEqual, tMilliseconds)
 				So(fixtures[6], ShouldBeNil)
 				So(fixtures[6], ShouldBeNil)
-				So(fixtures[7], ShouldBeNil)
+			})
+		})
+
+		Convey("Given row values with uin64 as time columns", func() {
+			tSeconds := uint64(dt.Unix())
+			tMilliseconds := uint64(dt.UnixNano() / 1e6)
+			tNanoSeconds := uint64(dt.UnixNano())
+			var nilPointer *uint64
+
+			fixtures := make([]interface{}, 7)
+			fixtures[0] = tSeconds
+			fixtures[1] = &tSeconds
+			fixtures[2] = tMilliseconds
+			fixtures[3] = &tMilliseconds
+			fixtures[4] = tNanoSeconds
+			fixtures[5] = &tNanoSeconds
+			fixtures[6] = nilPointer
+
+			for i := range fixtures {
+				ConvertSqlTimeColumnToEpochMs(fixtures, i)
+			}
+
+			Convey("When converting them should return epoch time with millisecond precision ", func() {
+				So(fixtures[0].(int64), ShouldEqual, tSeconds*1e3)
+				So(fixtures[1].(int64), ShouldEqual, tSeconds*1e3)
+				So(fixtures[2].(int64), ShouldEqual, tMilliseconds)
+				So(fixtures[3].(int64), ShouldEqual, tMilliseconds)
+				So(fixtures[4].(int64), ShouldEqual, tMilliseconds)
+				So(fixtures[5].(int64), ShouldEqual, tMilliseconds)
+				So(fixtures[6], ShouldBeNil)
+			})
+		})
+
+		Convey("Given row values with int32 as time columns", func() {
+			tSeconds := int32(dt.Unix())
+			var nilInt *int32
+
+			fixtures := make([]interface{}, 3)
+			fixtures[0] = tSeconds
+			fixtures[1] = &tSeconds
+			fixtures[2] = nilInt
+
+			for i := range fixtures {
+				ConvertSqlTimeColumnToEpochMs(fixtures, i)
+			}
+
+			Convey("When converting them should return epoch time with millisecond precision ", func() {
+				So(fixtures[0].(int64), ShouldEqual, dt.Unix()*1e3)
+				So(fixtures[1].(int64), ShouldEqual, dt.Unix()*1e3)
+				So(fixtures[2], ShouldBeNil)
+			})
+		})
+
+		Convey("Given row values with uint32 as time columns", func() {
+			tSeconds := uint32(dt.Unix())
+			var nilInt *uint32
+
+			fixtures := make([]interface{}, 3)
+			fixtures[0] = tSeconds
+			fixtures[1] = &tSeconds
+			fixtures[2] = nilInt
+
+			for i := range fixtures {
+				ConvertSqlTimeColumnToEpochMs(fixtures, i)
+			}
+
+			Convey("When converting them should return epoch time with millisecond precision ", func() {
+				So(fixtures[0].(int64), ShouldEqual, dt.Unix()*1e3)
+				So(fixtures[1].(int64), ShouldEqual, dt.Unix()*1e3)
+				So(fixtures[2], ShouldBeNil)
+			})
+		})
+
+		Convey("Given row values with float64 as time columns", func() {
+			tSeconds := float64(dt.UnixNano()) / float64(time.Second)
+			tMilliseconds := float64(dt.UnixNano()) / float64(time.Millisecond)
+			tNanoSeconds := float64(dt.UnixNano())
+			var nilPointer *float64
+
+			fixtures := make([]interface{}, 7)
+			fixtures[0] = tSeconds
+			fixtures[1] = &tSeconds
+			fixtures[2] = tMilliseconds
+			fixtures[3] = &tMilliseconds
+			fixtures[4] = tNanoSeconds
+			fixtures[5] = &tNanoSeconds
+			fixtures[6] = nilPointer
+
+			for i := range fixtures {
+				ConvertSqlTimeColumnToEpochMs(fixtures, i)
+			}
+
+			Convey("When converting them should return epoch time with millisecond precision ", func() {
+				So(fixtures[0].(float64), ShouldEqual, tMilliseconds)
+				So(fixtures[1].(float64), ShouldEqual, tMilliseconds)
+				So(fixtures[2].(float64), ShouldEqual, tMilliseconds)
+				So(fixtures[3].(float64), ShouldEqual, tMilliseconds)
+				fmt.Println(fixtures[4].(float64))
+				fmt.Println(tMilliseconds)
+				So(fixtures[4].(float64), ShouldEqual, tMilliseconds)
+				So(fixtures[5].(float64), ShouldEqual, tMilliseconds)
+				So(fixtures[6], ShouldBeNil)
+			})
+		})
+
+		Convey("Given row values with float32 as time columns", func() {
+			tSeconds := float32(dt.Unix())
+			var nilInt *float32
+
+			fixtures := make([]interface{}, 3)
+			fixtures[0] = tSeconds
+			fixtures[1] = &tSeconds
+			fixtures[2] = nilInt
+
+			for i := range fixtures {
+				ConvertSqlTimeColumnToEpochMs(fixtures, i)
+			}
+
+			Convey("When converting them should return epoch time with millisecond precision ", func() {
+				So(fixtures[0].(float64), ShouldEqual, float32(dt.Unix()*1e3))
+				So(fixtures[1].(float64), ShouldEqual, float32(dt.Unix()*1e3))
+				So(fixtures[2], ShouldBeNil)
 			})
 			})
 		})
 		})
 	})
 	})

+ 8 - 3
pkg/tsdb/time_range.go

@@ -108,9 +108,14 @@ func (tr *TimeRange) ParseTo() (time.Time, error) {
 // EpochPrecisionToMs converts epoch precision to millisecond, if needed.
 // EpochPrecisionToMs converts epoch precision to millisecond, if needed.
 // Only seconds to milliseconds supported right now
 // Only seconds to milliseconds supported right now
 func EpochPrecisionToMs(value float64) float64 {
 func EpochPrecisionToMs(value float64) float64 {
-	if int64(value)/1e10 == 0 {
-		return float64(value * 1e3)
+	s := strconv.FormatFloat(value, 'e', -1, 64)
+	if strings.HasSuffix(s, "e+09") {
+		return value * float64(1e3)
 	}
 	}
 
 
-	return float64(value)
+	if strings.HasSuffix(s, "e+18") {
+		return value / float64(time.Millisecond)
+	}
+
+	return value
 }
 }