We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
GA Data Source throws an error when no rows returned
### Issue Summary

Google Analytics Data Source throws `Error running query: 'rows'` when the query result is empty.
I have a pretty simple query with dimensions and filters, like:

```json
{
  "ids": "ga:177xxxxxx",
  "start_date": "2018-10-08",
  "end_date": "2018-10-12",
  "metrics": "ga:uniqueEvents",
  "dimensions": "ga:dimension1,ga:dimension3",
  "filters": "ga:dimension2==userrole;ga:eventCategory==eventcategory;ga:eventAction==enentaction;ga:dimension1!=demo"
}
```

Sometimes it returns empty result as there is no data. This results in error in redash.

### Steps to Reproduce

1. Create the Google Analytics Data Source
2. Make some query returning zero rows
3. Execute it in query editor

`Error running query: 'rows'` will be thrown. While this might be considered not a bug, I'd expect just an empty result with no errors.

### Technical details:

* Redash Version: 5.0.1
* Browser/OS: Chrome/macOS
* How did you install Redash: docker-compose

GA Data Source throws an error when no rows returned
### Issue Summary

Google Analytics Data Source throws `Error running query: 'rows'` when the query result is empty.
I have a pretty simple query with dimensions and filters, like:

```json
{
  "ids": "ga:177xxxxxx",
  "start_date": "2018-10-08",
  "end_date": "2018-10-12",
  "metrics": "ga:uniqueEvents",
  "dimensions": "ga:dimension1,ga:dimension3",
  "filters": "ga:dimension2==userrole;ga:eventCategory==eventcategory;ga:eventAction==enentaction;ga:dimension1!=demo"
}
```

Sometimes it returns empty result as there is no data. This results in error in redash.

### Steps to Reproduce

1. Create the Google Analytics Data Source
2. Make some query returning zero rows
3. Execute it in query editor

`Error running query: 'rows'` will be thrown. While this might be considered not a bug, I'd expect just an empty result with no errors.

### Technical details:

* Redash Version: 5.0.1
* Browser/OS: Chrome/macOS
* How did you install Redash: docker-compose


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `redash/query_runner/google_analytics.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 
3 import logging
4 from base64 import b64decode
5 from datetime import datetime
6 from urlparse import parse_qs, urlparse
7 
8 from redash.query_runner import *
9 from redash.utils import json_dumps, json_loads
10 
11 logger = logging.getLogger(__name__)
12 
13 try:
14     from oauth2client.service_account import ServiceAccountCredentials
15     from apiclient.discovery import build
16     from apiclient.errors import HttpError
17     import httplib2
18     enabled = True
19 except ImportError as e:
20     enabled = False
21 
22 
23 types_conv = dict(
24     STRING=TYPE_STRING,
25     INTEGER=TYPE_INTEGER,
26     FLOAT=TYPE_FLOAT,
27     DATE=TYPE_DATE,
28     DATETIME=TYPE_DATETIME
29 )
30 
31 
32 def parse_ga_response(response):
33     columns = []
34     for h in response['columnHeaders']:
35         if h['name'] in ('ga:date', 'mcf:conversionDate'):
36             h['dataType'] = 'DATE'
37         elif h['name'] == 'ga:dateHour':
38             h['dataType'] = 'DATETIME'
39         columns.append({
40             'name': h['name'],
41             'friendly_name': h['name'].split(':', 1)[1],
42             'type': types_conv.get(h['dataType'], 'string')
43         })
44 
45     rows = []
46     for r in response['rows']:
47         d = {}
48         for c, value in enumerate(r):
49             column_name = response['columnHeaders'][c]['name']
50             column_type = filter(lambda col: col['name'] == column_name, columns)[0]['type']
51 
52             # mcf results come a bit different than ga results:
53             if isinstance(value, dict):
54                 if 'primitiveValue' in value:
55                     value = value['primitiveValue']
56                 elif 'conversionPathValue' in value:
57                     steps = []
58                     for step in value['conversionPathValue']:
59                         steps.append('{}:{}'.format(step['interactionType'], step['nodeValue']))
60                     value = ', '.join(steps)
61                 else:
62                     raise Exception("Results format not supported")
63 
64             if column_type == TYPE_DATE:
65                 value = datetime.strptime(value, '%Y%m%d')
66             elif column_type == TYPE_DATETIME:
67                 if len(value) == 10:
68                     value = datetime.strptime(value, '%Y%m%d%H')
69                 elif len(value) == 12:
70                     value = datetime.strptime(value, '%Y%m%d%H%M')
71                 else:
72                     raise Exception("Unknown date/time format in results: '{}'".format(value))
73 
74             d[column_name] = value
75         rows.append(d)
76 
77     return {'columns': columns, 'rows': rows}
78 
79 
80 class GoogleAnalytics(BaseSQLQueryRunner):
81     @classmethod
82     def annotate_query(cls):
83         return False
84 
85     @classmethod
86     def type(cls):
87         return "google_analytics"
88 
89     @classmethod
90     def name(cls):
91         return "Google Analytics"
92 
93     @classmethod
94     def enabled(cls):
95         return enabled
96 
97     @classmethod
98     def configuration_schema(cls):
99         return {
100             'type': 'object',
101             'properties': {
102                 'jsonKeyFile': {
103                     "type": "string",
104                     'title': 'JSON Key File'
105                 }
106             },
107             'required': ['jsonKeyFile'],
108             'secret': ['jsonKeyFile']
109         }
110 
111     def __init__(self, configuration):
112         super(GoogleAnalytics, self).__init__(configuration)
113         self.syntax = 'json'
114 
115     def _get_analytics_service(self):
116         scope = ['https://www.googleapis.com/auth/analytics.readonly']
117         key = json_loads(b64decode(self.configuration['jsonKeyFile']))
118         creds = ServiceAccountCredentials.from_json_keyfile_dict(key, scope)
119         return build('analytics', 'v3', http=creds.authorize(httplib2.Http()))
120 
121     def _get_tables(self, schema):
122         accounts = self._get_analytics_service().management().accounts().list().execute().get('items')
123         if accounts is None:
124             raise Exception("Failed getting accounts.")
125         else:
126             for account in accounts:
127                 schema[account['name']] = {'name': account['name'], 'columns': []}
128                 properties = self._get_analytics_service().management().webproperties().list(
129                     accountId=account['id']).execute().get('items', [])
130                 for property_ in properties:
131                     if 'defaultProfileId' in property_ and 'name' in property_:
132                         schema[account['name']]['columns'].append(
133                             u'{0} (ga:{1})'.format(property_['name'], property_['defaultProfileId'])
134                         )
135 
136         return schema.values()
137 
138     def test_connection(self):
139         try:
140             service = self._get_analytics_service()
141             service.management().accounts().list().execute()
142         except HttpError as e:
143             # Make sure we return a more readable error to the end user
144             raise Exception(e._get_reason())
145 
146     def run_query(self, query, user):
147         logger.debug("Analytics is about to execute query: %s", query)
148         try:
149             params = json_loads(query)
150         except:
151             params = parse_qs(urlparse(query).query, keep_blank_values=True)
152             for key in params.keys():
153                 params[key] = ','.join(params[key])
154                 if '-' in key:
155                     params[key.replace('-', '_')] = params.pop(key)
156 
157         if 'mcf:' in params['metrics'] and 'ga:' in params['metrics']:
158             raise Exception("Can't mix mcf: and ga: metrics.")
159 
160         if 'mcf:' in params.get('dimensions', '') and 'ga:' in params.get('dimensions', ''):
161             raise Exception("Can't mix mcf: and ga: dimensions.")
162 
163         if 'mcf:' in params['metrics']:
164             api = self._get_analytics_service().data().mcf()
165         else:
166             api = self._get_analytics_service().data().ga()
167 
168         if len(params) > 0:
169             try:
170                 response = api.get(**params).execute()
171                 data = parse_ga_response(response)
172                 error = None
173                 json_data = json_dumps(data)
174             except HttpError as e:
175                 # Make sure we return a more readable error to the end user
176                 error = e._get_reason()
177                 json_data = None
178         else:
179             error = 'Wrong query format.'
180             json_data = None
181         return json_data, error
182 
183 
184 register(GoogleAnalytics)
185
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/redash/query_runner/google_analytics.py b/redash/query_runner/google_analytics.py
--- a/redash/query_runner/google_analytics.py
+++ b/redash/query_runner/google_analytics.py
@@ -43,7 +43,7 @@
         })
 
     rows = []
-    for r in response['rows']:
+    for r in response.get('rows', []):
         d = {}
         for c, value in enumerate(r):
             column_name = response['columnHeaders'][c]['name']

{"golden_diff": "diff --git a/redash/query_runner/google_analytics.py b/redash/query_runner/google_analytics.py\n--- a/redash/query_runner/google_analytics.py\n+++ b/redash/query_runner/google_analytics.py\n@@ -43,7 +43,7 @@\n         })\n \n     rows = []\n-    for r in response['rows']:\n+    for r in response.get('rows', []):\n         d = {}\n         for c, value in enumerate(r):\n             column_name = response['columnHeaders'][c]['name']\n", "issue": "GA Data Source throws an error when no rows returned\n### Issue Summary\r\n\r\nGoogle Analytics Data Source throws `Error running query: 'rows'` when the query result is empty.\r\nI have a pretty simple query with dimensions and filters, like:\r\n\r\n```json\r\n{\r\n  \"ids\": \"ga:177xxxxxx\",\r\n  \"start_date\": \"2018-10-08\",\r\n  \"end_date\": \"2018-10-12\",\r\n  \"metrics\": \"ga:uniqueEvents\",\r\n  \"dimensions\": \"ga:dimension1,ga:dimension3\",\r\n  \"filters\": \"ga:dimension2==userrole;ga:eventCategory==eventcategory;ga:eventAction==enentaction;ga:dimension1!=demo\"\r\n}\r\n```\r\n\r\nSometimes it returns empty result as there is no data. This results in error in redash.\r\n\r\n### Steps to Reproduce\r\n\r\n1. Create the Google Analytics Data Source\r\n2. Make some query returning zero rows\r\n3. Execute it in query editor\r\n\r\n`Error running query: 'rows'` will be thrown. While this might be considered not a bug, I'd expect just an empty result with no errors.\r\n\r\n### Technical details:\r\n\r\n* Redash Version: 5.0.1\r\n* Browser/OS: Chrome/macOS\r\n* How did you install Redash: docker-compose\r\n\nGA Data Source throws an error when no rows returned\n### Issue Summary\r\n\r\nGoogle Analytics Data Source throws `Error running query: 'rows'` when the query result is empty.\r\nI have a pretty simple query with dimensions and filters, like:\r\n\r\n```json\r\n{\r\n  \"ids\": \"ga:177xxxxxx\",\r\n  \"start_date\": \"2018-10-08\",\r\n  \"end_date\": \"2018-10-12\",\r\n  \"metrics\": \"ga:uniqueEvents\",\r\n  \"dimensions\": \"ga:dimension1,ga:dimension3\",\r\n  \"filters\": \"ga:dimension2==userrole;ga:eventCategory==eventcategory;ga:eventAction==enentaction;ga:dimension1!=demo\"\r\n}\r\n```\r\n\r\nSometimes it returns empty result as there is no data. This results in error in redash.\r\n\r\n### Steps to Reproduce\r\n\r\n1. Create the Google Analytics Data Source\r\n2. Make some query returning zero rows\r\n3. Execute it in query editor\r\n\r\n`Error running query: 'rows'` will be thrown. While this might be considered not a bug, I'd expect just an empty result with no errors.\r\n\r\n### Technical details:\r\n\r\n* Redash Version: 5.0.1\r\n* Browser/OS: Chrome/macOS\r\n* How did you install Redash: docker-compose\r\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\nimport logging\nfrom base64 import b64decode\nfrom datetime import datetime\nfrom urlparse import parse_qs, urlparse\n\nfrom redash.query_runner import *\nfrom redash.utils import json_dumps, json_loads\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    from oauth2client.service_account import ServiceAccountCredentials\n    from apiclient.discovery import build\n    from apiclient.errors import HttpError\n    import httplib2\n    enabled = True\nexcept ImportError as e:\n    enabled = False\n\n\ntypes_conv = dict(\n    STRING=TYPE_STRING,\n    INTEGER=TYPE_INTEGER,\n    FLOAT=TYPE_FLOAT,\n    DATE=TYPE_DATE,\n    DATETIME=TYPE_DATETIME\n)\n\n\ndef parse_ga_response(response):\n    columns = []\n    for h in response['columnHeaders']:\n        if h['name'] in ('ga:date', 'mcf:conversionDate'):\n            h['dataType'] = 'DATE'\n        elif h['name'] == 'ga:dateHour':\n            h['dataType'] = 'DATETIME'\n        columns.append({\n            'name': h['name'],\n            'friendly_name': h['name'].split(':', 1)[1],\n            'type': types_conv.get(h['dataType'], 'string')\n        })\n\n    rows = []\n    for r in response['rows']:\n        d = {}\n        for c, value in enumerate(r):\n            column_name = response['columnHeaders'][c]['name']\n            column_type = filter(lambda col: col['name'] == column_name, columns)[0]['type']\n\n            # mcf results come a bit different than ga results:\n            if isinstance(value, dict):\n                if 'primitiveValue' in value:\n                    value = value['primitiveValue']\n                elif 'conversionPathValue' in value:\n                    steps = []\n                    for step in value['conversionPathValue']:\n                        steps.append('{}:{}'.format(step['interactionType'], step['nodeValue']))\n                    value = ', '.join(steps)\n                else:\n                    raise Exception(\"Results format not supported\")\n\n            if column_type == TYPE_DATE:\n                value = datetime.strptime(value, '%Y%m%d')\n            elif column_type == TYPE_DATETIME:\n                if len(value) == 10:\n                    value = datetime.strptime(value, '%Y%m%d%H')\n                elif len(value) == 12:\n                    value = datetime.strptime(value, '%Y%m%d%H%M')\n                else:\n                    raise Exception(\"Unknown date/time format in results: '{}'\".format(value))\n\n            d[column_name] = value\n        rows.append(d)\n\n    return {'columns': columns, 'rows': rows}\n\n\nclass GoogleAnalytics(BaseSQLQueryRunner):\n    @classmethod\n    def annotate_query(cls):\n        return False\n\n    @classmethod\n    def type(cls):\n        return \"google_analytics\"\n\n    @classmethod\n    def name(cls):\n        return \"Google Analytics\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            'type': 'object',\n            'properties': {\n                'jsonKeyFile': {\n                    \"type\": \"string\",\n                    'title': 'JSON Key File'\n                }\n            },\n            'required': ['jsonKeyFile'],\n            'secret': ['jsonKeyFile']\n        }\n\n    def __init__(self, configuration):\n        super(GoogleAnalytics, self).__init__(configuration)\n        self.syntax = 'json'\n\n    def _get_analytics_service(self):\n        scope = ['https://www.googleapis.com/auth/analytics.readonly']\n        key = json_loads(b64decode(self.configuration['jsonKeyFile']))\n        creds = ServiceAccountCredentials.from_json_keyfile_dict(key, scope)\n        return build('analytics', 'v3', http=creds.authorize(httplib2.Http()))\n\n    def _get_tables(self, schema):\n        accounts = self._get_analytics_service().management().accounts().list().execute().get('items')\n        if accounts is None:\n            raise Exception(\"Failed getting accounts.\")\n        else:\n            for account in accounts:\n                schema[account['name']] = {'name': account['name'], 'columns': []}\n                properties = self._get_analytics_service().management().webproperties().list(\n                    accountId=account['id']).execute().get('items', [])\n                for property_ in properties:\n                    if 'defaultProfileId' in property_ and 'name' in property_:\n                        schema[account['name']]['columns'].append(\n                            u'{0} (ga:{1})'.format(property_['name'], property_['defaultProfileId'])\n                        )\n\n        return schema.values()\n\n    def test_connection(self):\n        try:\n            service = self._get_analytics_service()\n            service.management().accounts().list().execute()\n        except HttpError as e:\n            # Make sure we return a more readable error to the end user\n            raise Exception(e._get_reason())\n\n    def run_query(self, query, user):\n        logger.debug(\"Analytics is about to execute query: %s\", query)\n        try:\n            params = json_loads(query)\n        except:\n            params = parse_qs(urlparse(query).query, keep_blank_values=True)\n            for key in params.keys():\n                params[key] = ','.join(params[key])\n                if '-' in key:\n                    params[key.replace('-', '_')] = params.pop(key)\n\n        if 'mcf:' in params['metrics'] and 'ga:' in params['metrics']:\n            raise Exception(\"Can't mix mcf: and ga: metrics.\")\n\n        if 'mcf:' in params.get('dimensions', '') and 'ga:' in params.get('dimensions', ''):\n            raise Exception(\"Can't mix mcf: and ga: dimensions.\")\n\n        if 'mcf:' in params['metrics']:\n            api = self._get_analytics_service().data().mcf()\n        else:\n            api = self._get_analytics_service().data().ga()\n\n        if len(params) > 0:\n            try:\n                response = api.get(**params).execute()\n                data = parse_ga_response(response)\n                error = None\n                json_data = json_dumps(data)\n            except HttpError as e:\n                # Make sure we return a more readable error to the end user\n                error = e._get_reason()\n                json_data = None\n        else:\n            error = 'Wrong query format.'\n            json_data = None\n        return json_data, error\n\n\nregister(GoogleAnalytics)\n", "path": "redash/query_runner/google_analytics.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\nimport logging\nfrom base64 import b64decode\nfrom datetime import datetime\nfrom urlparse import parse_qs, urlparse\n\nfrom redash.query_runner import *\nfrom redash.utils import json_dumps, json_loads\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    from oauth2client.service_account import ServiceAccountCredentials\n    from apiclient.discovery import build\n    from apiclient.errors import HttpError\n    import httplib2\n    enabled = True\nexcept ImportError as e:\n    enabled = False\n\n\ntypes_conv = dict(\n    STRING=TYPE_STRING,\n    INTEGER=TYPE_INTEGER,\n    FLOAT=TYPE_FLOAT,\n    DATE=TYPE_DATE,\n    DATETIME=TYPE_DATETIME\n)\n\n\ndef parse_ga_response(response):\n    columns = []\n    for h in response['columnHeaders']:\n        if h['name'] in ('ga:date', 'mcf:conversionDate'):\n            h['dataType'] = 'DATE'\n        elif h['name'] == 'ga:dateHour':\n            h['dataType'] = 'DATETIME'\n        columns.append({\n            'name': h['name'],\n            'friendly_name': h['name'].split(':', 1)[1],\n            'type': types_conv.get(h['dataType'], 'string')\n        })\n\n    rows = []\n    for r in response.get('rows', []):\n        d = {}\n        for c, value in enumerate(r):\n            column_name = response['columnHeaders'][c]['name']\n            column_type = filter(lambda col: col['name'] == column_name, columns)[0]['type']\n\n            # mcf results come a bit different than ga results:\n            if isinstance(value, dict):\n                if 'primitiveValue' in value:\n                    value = value['primitiveValue']\n                elif 'conversionPathValue' in value:\n                    steps = []\n                    for step in value['conversionPathValue']:\n                        steps.append('{}:{}'.format(step['interactionType'], step['nodeValue']))\n                    value = ', '.join(steps)\n                else:\n                    raise Exception(\"Results format not supported\")\n\n            if column_type == TYPE_DATE:\n                value = datetime.strptime(value, '%Y%m%d')\n            elif column_type == TYPE_DATETIME:\n                if len(value) == 10:\n                    value = datetime.strptime(value, '%Y%m%d%H')\n                elif len(value) == 12:\n                    value = datetime.strptime(value, '%Y%m%d%H%M')\n                else:\n                    raise Exception(\"Unknown date/time format in results: '{}'\".format(value))\n\n            d[column_name] = value\n        rows.append(d)\n\n    return {'columns': columns, 'rows': rows}\n\n\nclass GoogleAnalytics(BaseSQLQueryRunner):\n    @classmethod\n    def annotate_query(cls):\n        return False\n\n    @classmethod\n    def type(cls):\n        return \"google_analytics\"\n\n    @classmethod\n    def name(cls):\n        return \"Google Analytics\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            'type': 'object',\n            'properties': {\n                'jsonKeyFile': {\n                    \"type\": \"string\",\n                    'title': 'JSON Key File'\n                }\n            },\n            'required': ['jsonKeyFile'],\n            'secret': ['jsonKeyFile']\n        }\n\n    def __init__(self, configuration):\n        super(GoogleAnalytics, self).__init__(configuration)\n        self.syntax = 'json'\n\n    def _get_analytics_service(self):\n        scope = ['https://www.googleapis.com/auth/analytics.readonly']\n        key = json_loads(b64decode(self.configuration['jsonKeyFile']))\n        creds = ServiceAccountCredentials.from_json_keyfile_dict(key, scope)\n        return build('analytics', 'v3', http=creds.authorize(httplib2.Http()))\n\n    def _get_tables(self, schema):\n        accounts = self._get_analytics_service().management().accounts().list().execute().get('items')\n        if accounts is None:\n            raise Exception(\"Failed getting accounts.\")\n        else:\n            for account in accounts:\n                schema[account['name']] = {'name': account['name'], 'columns': []}\n                properties = self._get_analytics_service().management().webproperties().list(\n                    accountId=account['id']).execute().get('items', [])\n                for property_ in properties:\n                    if 'defaultProfileId' in property_ and 'name' in property_:\n                        schema[account['name']]['columns'].append(\n                            u'{0} (ga:{1})'.format(property_['name'], property_['defaultProfileId'])\n                        )\n\n        return schema.values()\n\n    def test_connection(self):\n        try:\n            service = self._get_analytics_service()\n            service.management().accounts().list().execute()\n        except HttpError as e:\n            # Make sure we return a more readable error to the end user\n            raise Exception(e._get_reason())\n\n    def run_query(self, query, user):\n        logger.debug(\"Analytics is about to execute query: %s\", query)\n        try:\n            params = json_loads(query)\n        except:\n            params = parse_qs(urlparse(query).query, keep_blank_values=True)\n            for key in params.keys():\n                params[key] = ','.join(params[key])\n                if '-' in key:\n                    params[key.replace('-', '_')] = params.pop(key)\n\n        if 'mcf:' in params['metrics'] and 'ga:' in params['metrics']:\n            raise Exception(\"Can't mix mcf: and ga: metrics.\")\n\n        if 'mcf:' in params.get('dimensions', '') and 'ga:' in params.get('dimensions', ''):\n            raise Exception(\"Can't mix mcf: and ga: dimensions.\")\n\n        if 'mcf:' in params['metrics']:\n            api = self._get_analytics_service().data().mcf()\n        else:\n            api = self._get_analytics_service().data().ga()\n\n        if len(params) > 0:\n            try:\n                response = api.get(**params).execute()\n                data = parse_ga_response(response)\n                error = None\n                json_data = json_dumps(data)\n            except HttpError as e:\n                # Make sure we return a more readable error to the end user\n                error = e._get_reason()\n                json_data = None\n        else:\n            error = 'Wrong query format.'\n            json_data = None\n        return json_data, error\n\n\nregister(GoogleAnalytics)\n", "path": "redash/query_runner/google_analytics.py"}]}