We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Inner objects of MongoDB results are not handled correctly
### Issue Summary

When querying a mongo object that has inner objects and returning only a given field of an inner object, results are not displayed in the UI. 
### Steps to Reproduce
1. Have a MongoDB data_source that contains an object looking like that

``` json
{
    "foo": {
        "bar":0,
        "foo":1
    }
}
```
1. Query this data source like that: 

``` json
{
    "collection": "foo",
    "query": {
    },
    "fields": {
        "foo.bar":1
     }
}
```
1. Notice that the results table is empty. 
2. Look at the response using the network tab of your browser's console.
3. Notice that the data is correctly returned but incorrectly handled in the UI. 

I expect the data to be handled correctly. Since it is not in the table, it is impossible to make any visualization. Inner objects are common in MongoDB. I believer it can be fixed either on the UI or the server since when it is an inner field, it isn't in the same format as the other fields. 
### Technical details:
- Redash Version: 0.10
- Browser/OS: Firefox/Linux
- How did you install Redash: Docker


--- 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/mongodb.py`
Content:
```
1 import datetime
2 import json
3 import logging
4 import re
5 
6 from dateutil.parser import parse
7 
8 from redash.query_runner import *
9 from redash.utils import JSONEncoder, parse_human_time
10 
11 logger = logging.getLogger(__name__)
12 
13 try:
14     import pymongo
15     from bson.objectid import ObjectId
16     from bson.timestamp import Timestamp
17     from bson.son import SON
18     from bson.json_util import object_hook as bson_object_hook
19     enabled = True
20 
21 except ImportError:
22     enabled = False
23 
24 
25 TYPES_MAP = {
26     str: TYPE_STRING,
27     unicode: TYPE_STRING,
28     int: TYPE_INTEGER,
29     long: TYPE_INTEGER,
30     float: TYPE_FLOAT,
31     bool: TYPE_BOOLEAN,
32     datetime.datetime: TYPE_DATETIME,
33 }
34 
35 
36 class MongoDBJSONEncoder(JSONEncoder):
37     def default(self, o):
38         if isinstance(o, ObjectId):
39             return str(o)
40         elif isinstance(o, Timestamp):
41             return super(MongoDBJSONEncoder, self).default(o.as_datetime())
42 
43         return super(MongoDBJSONEncoder, self).default(o)
44 
45 
46 date_regex = re.compile("ISODate\(\"(.*)\"\)", re.IGNORECASE)
47 
48 
49 def parse_oids(oids):
50     if not isinstance(oids, list):
51         raise Exception("$oids takes an array as input.")
52 
53     return [bson_object_hook({'$oid': oid}) for oid in oids]
54 
55 
56 def datetime_parser(dct):
57     for k, v in dct.iteritems():
58         if isinstance(v, basestring):
59             m = date_regex.findall(v)
60             if len(m) > 0:
61                 dct[k] = parse(m[0], yearfirst=True)
62 
63     if '$humanTime' in dct:
64         return parse_human_time(dct['$humanTime'])
65 
66     if '$oids' in dct:
67         return parse_oids(dct['$oids'])
68 
69     return bson_object_hook(dct)
70 
71 
72 def parse_query_json(query):
73     query_data = json.loads(query, object_hook=datetime_parser)
74     return query_data
75 
76 
77 class MongoDB(BaseQueryRunner):
78     @classmethod
79     def configuration_schema(cls):
80         return {
81             'type': 'object',
82             'properties': {
83                 'connectionString': {
84                     'type': 'string',
85                     'title': 'Connection String'
86                 },
87                 'dbName': {
88                     'type': 'string',
89                     'title': "Database Name"
90                 },
91                 'replicaSetName': {
92                     'type': 'string',
93                     'title': 'Replica Set Name'
94                 },
95             },
96             'required': ['connectionString', 'dbName']
97         }
98 
99     @classmethod
100     def enabled(cls):
101         return enabled
102 
103     @classmethod
104     def annotate_query(cls):
105         return False
106 
107     def __init__(self, configuration):
108         super(MongoDB, self).__init__(configuration)
109 
110         self.syntax = 'json'
111 
112         self.db_name = self.configuration["dbName"]
113 
114         self.is_replica_set = True if "replicaSetName" in self.configuration and self.configuration["replicaSetName"] else False
115 
116     def _get_column_by_name(self, columns, column_name):
117         for c in columns:
118             if "name" in c and c["name"] == column_name:
119                 return c
120 
121         return None
122 
123     def _get_db(self):
124         if self.is_replica_set:
125             db_connection = pymongo.MongoReplicaSetClient(self.configuration["connectionString"], replicaSet=self.configuration["replicaSetName"])
126         else:
127             db_connection = pymongo.MongoClient(self.configuration["connectionString"])
128 
129         return db_connection[self.db_name]
130 
131     def test_connection(self):
132         db = self._get_db()
133         if not db.command("connectionStatus")["ok"]:
134             raise Exception("MongoDB connection error")
135 
136     def _merge_property_names(self, columns, document):
137         for property in document:
138               if property not in columns:
139                   columns.append(property)
140 
141     def _get_collection_fields(self, db, collection_name):
142         # Since MongoDB is a document based database and each document doesn't have
143         # to have the same fields as another documet in the collection its a bit hard to
144         # show these attributes as fields in the schema.
145         #
146         # For now, the logic is to take the first and last documents (last is determined
147         # by the Natural Order (http://www.mongodb.org/display/DOCS/Sorting+and+Natural+Order)
148         # as we don't know the correct order. In most single server installations it would be
149         # find. In replicaset when reading from non master it might not return the really last
150         # document written.
151         first_document = None
152         last_document = None
153 
154         for d in db[collection_name].find().sort([("$natural", 1)]).limit(1):
155             first_document = d
156 
157         for d in db[collection_name].find().sort([("$natural", -1)]).limit(1):
158             last_document = d
159 
160         columns = []
161         if first_document: self._merge_property_names(columns, first_document)
162         if last_document: self._merge_property_names(columns, last_document)
163 
164         return columns
165 
166     def get_schema(self, get_stats=False):
167         schema = {}
168         db = self._get_db()
169         for collection_name in db.collection_names():
170             columns = self._get_collection_fields(db, collection_name)
171             schema[collection_name] = { "name" : collection_name, "columns" : sorted(columns) }
172 
173         return schema.values()
174 
175     def run_query(self, query, user):
176         db = self._get_db()
177 
178         logger.debug("mongodb connection string: %s", self.configuration['connectionString'])
179         logger.debug("mongodb got query: %s", query)
180 
181         try:
182             query_data = parse_query_json(query)
183         except ValueError:
184             return None, "Invalid query format. The query is not a valid JSON."
185 
186         if "collection" not in query_data:
187             return None, "'collection' must have a value to run a query"
188         else:
189             collection = query_data["collection"]
190 
191         q = query_data.get("query", None)
192         f = None
193 
194         aggregate = query_data.get("aggregate", None)
195         if aggregate:
196             for step in aggregate:
197                 if "$sort" in step:
198                     sort_list = []
199                     for sort_item in step["$sort"]:
200                         sort_list.append((sort_item["name"], sort_item["direction"]))
201 
202                     step["$sort"] = SON(sort_list)
203 
204         if not aggregate:
205             s = None
206             if "sort" in query_data and query_data["sort"]:
207                 s = []
208                 for field in query_data["sort"]:
209                     s.append((field["name"], field["direction"]))
210 
211         if "fields" in query_data:
212             f = query_data["fields"]
213 
214         s = None
215         if "sort" in query_data and query_data["sort"]:
216             s = []
217             for field_data in query_data["sort"]:
218                 s.append((field_data["name"], field_data["direction"]))
219 
220         columns = []
221         rows = []
222 
223         cursor = None
224         if q or (not q and not aggregate):
225             if s:
226                 cursor = db[collection].find(q, f).sort(s)
227             else:
228                 cursor = db[collection].find(q, f)
229 
230             if "skip" in query_data:
231                 cursor = cursor.skip(query_data["skip"])
232 
233             if "limit" in query_data:
234                 cursor = cursor.limit(query_data["limit"])
235 
236             if "count" in query_data:
237                 cursor = cursor.count()
238 
239         elif aggregate:
240             allow_disk_use = query_data.get('allowDiskUse', False)
241             r = db[collection].aggregate(aggregate, allowDiskUse=allow_disk_use)
242 
243             # Backwards compatibility with older pymongo versions.
244             #
245             # Older pymongo version would return a dictionary from an aggregate command.
246             # The dict would contain a "result" key which would hold the cursor.
247             # Newer ones return pymongo.command_cursor.CommandCursor.
248             if isinstance(r, dict):
249                 cursor = r["result"]
250             else:
251                 cursor = r
252 
253         if "count" in query_data:
254             columns.append({
255                 "name" : "count",
256                 "friendly_name" : "count",
257                 "type" : TYPE_INTEGER
258             })
259 
260             rows.append({ "count" : cursor })
261         else:
262             for r in cursor:
263                 for k in r:
264                     if self._get_column_by_name(columns, k) is None:
265                         columns.append({
266                             "name": k,
267                             "friendly_name": k,
268                             "type": TYPES_MAP.get(type(r[k]), TYPE_STRING)
269                         })
270 
271                 rows.append(r)
272 
273         if f:
274             ordered_columns = []
275             for k in sorted(f, key=f.get):
276                 ordered_columns.append(self._get_column_by_name(columns, k))
277 
278             columns = ordered_columns
279 
280         data = {
281             "columns": columns,
282             "rows": rows
283         }
284         error = None
285         json_data = json.dumps(data, cls=MongoDBJSONEncoder)
286 
287         return json_data, error
288 
289 register(MongoDB)
290
```
--- 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/mongodb.py b/redash/query_runner/mongodb.py
--- a/redash/query_runner/mongodb.py
+++ b/redash/query_runner/mongodb.py
@@ -74,6 +74,49 @@
     return query_data
 
 
+def _get_column_by_name(columns, column_name):
+    for c in columns:
+        if "name" in c and c["name"] == column_name:
+            return c
+
+    return None
+
+
+def parse_results(results):
+    rows = []
+    columns = []
+
+    for row in results:
+        parsed_row = {}
+
+        for key in row:
+            if isinstance(row[key], dict):
+                for inner_key in row[key]:
+                    column_name = '{}.{}'.format(key, inner_key)
+                    if _get_column_by_name(columns, column_name) is None:
+                        columns.append({
+                            "name": column_name,
+                            "friendly_name": column_name,
+                            "type": TYPES_MAP.get(type(row[key][inner_key]), TYPE_STRING)
+                        })
+                    
+                    parsed_row[column_name] = row[key][inner_key]
+
+            else:
+                if _get_column_by_name(columns, key) is None:
+                    columns.append({
+                        "name": key,
+                        "friendly_name": key,
+                        "type": TYPES_MAP.get(type(row[key]), TYPE_STRING)
+                    })
+                
+                parsed_row[key] = row[key]
+
+        rows.append(parsed_row)
+
+    return rows, columns
+
+
 class MongoDB(BaseQueryRunner):
     @classmethod
     def configuration_schema(cls):
@@ -113,13 +156,6 @@
 
         self.is_replica_set = True if "replicaSetName" in self.configuration and self.configuration["replicaSetName"] else False
 
-    def _get_column_by_name(self, columns, column_name):
-        for c in columns:
-            if "name" in c and c["name"] == column_name:
-                return c
-
-        return None
-
     def _get_db(self):
         if self.is_replica_set:
             db_connection = pymongo.MongoReplicaSetClient(self.configuration["connectionString"], replicaSet=self.configuration["replicaSetName"])
@@ -259,21 +295,14 @@
 
             rows.append({ "count" : cursor })
         else:
-            for r in cursor:
-                for k in r:
-                    if self._get_column_by_name(columns, k) is None:
-                        columns.append({
-                            "name": k,
-                            "friendly_name": k,
-                            "type": TYPES_MAP.get(type(r[k]), TYPE_STRING)
-                        })
-
-                rows.append(r)
+            rows, columns = parse_results(cursor)
 
         if f:
             ordered_columns = []
             for k in sorted(f, key=f.get):
-                ordered_columns.append(self._get_column_by_name(columns, k))
+                column = _get_column_by_name(columns, k)
+                if column:
+                    ordered_columns.append(column)
 
             columns = ordered_columns

{"golden_diff": "diff --git a/redash/query_runner/mongodb.py b/redash/query_runner/mongodb.py\n--- a/redash/query_runner/mongodb.py\n+++ b/redash/query_runner/mongodb.py\n@@ -74,6 +74,49 @@\n     return query_data\n \n \n+def _get_column_by_name(columns, column_name):\n+    for c in columns:\n+        if \"name\" in c and c[\"name\"] == column_name:\n+            return c\n+\n+    return None\n+\n+\n+def parse_results(results):\n+    rows = []\n+    columns = []\n+\n+    for row in results:\n+        parsed_row = {}\n+\n+        for key in row:\n+            if isinstance(row[key], dict):\n+                for inner_key in row[key]:\n+                    column_name = '{}.{}'.format(key, inner_key)\n+                    if _get_column_by_name(columns, column_name) is None:\n+                        columns.append({\n+                            \"name\": column_name,\n+                            \"friendly_name\": column_name,\n+                            \"type\": TYPES_MAP.get(type(row[key][inner_key]), TYPE_STRING)\n+                        })\n+                    \n+                    parsed_row[column_name] = row[key][inner_key]\n+\n+            else:\n+                if _get_column_by_name(columns, key) is None:\n+                    columns.append({\n+                        \"name\": key,\n+                        \"friendly_name\": key,\n+                        \"type\": TYPES_MAP.get(type(row[key]), TYPE_STRING)\n+                    })\n+                \n+                parsed_row[key] = row[key]\n+\n+        rows.append(parsed_row)\n+\n+    return rows, columns\n+\n+\n class MongoDB(BaseQueryRunner):\n     @classmethod\n     def configuration_schema(cls):\n@@ -113,13 +156,6 @@\n \n         self.is_replica_set = True if \"replicaSetName\" in self.configuration and self.configuration[\"replicaSetName\"] else False\n \n-    def _get_column_by_name(self, columns, column_name):\n-        for c in columns:\n-            if \"name\" in c and c[\"name\"] == column_name:\n-                return c\n-\n-        return None\n-\n     def _get_db(self):\n         if self.is_replica_set:\n             db_connection = pymongo.MongoReplicaSetClient(self.configuration[\"connectionString\"], replicaSet=self.configuration[\"replicaSetName\"])\n@@ -259,21 +295,14 @@\n \n             rows.append({ \"count\" : cursor })\n         else:\n-            for r in cursor:\n-                for k in r:\n-                    if self._get_column_by_name(columns, k) is None:\n-                        columns.append({\n-                            \"name\": k,\n-                            \"friendly_name\": k,\n-                            \"type\": TYPES_MAP.get(type(r[k]), TYPE_STRING)\n-                        })\n-\n-                rows.append(r)\n+            rows, columns = parse_results(cursor)\n \n         if f:\n             ordered_columns = []\n             for k in sorted(f, key=f.get):\n-                ordered_columns.append(self._get_column_by_name(columns, k))\n+                column = _get_column_by_name(columns, k)\n+                if column:\n+                    ordered_columns.append(column)\n \n             columns = ordered_columns\n", "issue": "Inner objects of MongoDB results are not handled correctly\n### Issue Summary\n\nWhen querying a mongo object that has inner objects and returning only a given field of an inner object, results are not displayed in the UI. \n### Steps to Reproduce\n1. Have a MongoDB data_source that contains an object looking like that\n\n``` json\n{\n    \"foo\": {\n        \"bar\":0,\n        \"foo\":1\n    }\n}\n```\n1. Query this data source like that: \n\n``` json\n{\n    \"collection\": \"foo\",\n    \"query\": {\n    },\n    \"fields\": {\n        \"foo.bar\":1\n     }\n}\n```\n1. Notice that the results table is empty. \n2. Look at the response using the network tab of your browser's console.\n3. Notice that the data is correctly returned but incorrectly handled in the UI. \n\nI expect the data to be handled correctly. Since it is not in the table, it is impossible to make any visualization. Inner objects are common in MongoDB. I believer it can be fixed either on the UI or the server since when it is an inner field, it isn't in the same format as the other fields. \n### Technical details:\n- Redash Version: 0.10\n- Browser/OS: Firefox/Linux\n- How did you install Redash: Docker\n\n", "before_files": [{"content": "import datetime\nimport json\nimport logging\nimport re\n\nfrom dateutil.parser import parse\n\nfrom redash.query_runner import *\nfrom redash.utils import JSONEncoder, parse_human_time\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import pymongo\n    from bson.objectid import ObjectId\n    from bson.timestamp import Timestamp\n    from bson.son import SON\n    from bson.json_util import object_hook as bson_object_hook\n    enabled = True\n\nexcept ImportError:\n    enabled = False\n\n\nTYPES_MAP = {\n    str: TYPE_STRING,\n    unicode: TYPE_STRING,\n    int: TYPE_INTEGER,\n    long: TYPE_INTEGER,\n    float: TYPE_FLOAT,\n    bool: TYPE_BOOLEAN,\n    datetime.datetime: TYPE_DATETIME,\n}\n\n\nclass MongoDBJSONEncoder(JSONEncoder):\n    def default(self, o):\n        if isinstance(o, ObjectId):\n            return str(o)\n        elif isinstance(o, Timestamp):\n            return super(MongoDBJSONEncoder, self).default(o.as_datetime())\n\n        return super(MongoDBJSONEncoder, self).default(o)\n\n\ndate_regex = re.compile(\"ISODate\\(\\\"(.*)\\\"\\)\", re.IGNORECASE)\n\n\ndef parse_oids(oids):\n    if not isinstance(oids, list):\n        raise Exception(\"$oids takes an array as input.\")\n\n    return [bson_object_hook({'$oid': oid}) for oid in oids]\n\n\ndef datetime_parser(dct):\n    for k, v in dct.iteritems():\n        if isinstance(v, basestring):\n            m = date_regex.findall(v)\n            if len(m) > 0:\n                dct[k] = parse(m[0], yearfirst=True)\n\n    if '$humanTime' in dct:\n        return parse_human_time(dct['$humanTime'])\n\n    if '$oids' in dct:\n        return parse_oids(dct['$oids'])\n\n    return bson_object_hook(dct)\n\n\ndef parse_query_json(query):\n    query_data = json.loads(query, object_hook=datetime_parser)\n    return query_data\n\n\nclass MongoDB(BaseQueryRunner):\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            'type': 'object',\n            'properties': {\n                'connectionString': {\n                    'type': 'string',\n                    'title': 'Connection String'\n                },\n                'dbName': {\n                    'type': 'string',\n                    'title': \"Database Name\"\n                },\n                'replicaSetName': {\n                    'type': 'string',\n                    'title': 'Replica Set Name'\n                },\n            },\n            'required': ['connectionString', 'dbName']\n        }\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def annotate_query(cls):\n        return False\n\n    def __init__(self, configuration):\n        super(MongoDB, self).__init__(configuration)\n\n        self.syntax = 'json'\n\n        self.db_name = self.configuration[\"dbName\"]\n\n        self.is_replica_set = True if \"replicaSetName\" in self.configuration and self.configuration[\"replicaSetName\"] else False\n\n    def _get_column_by_name(self, columns, column_name):\n        for c in columns:\n            if \"name\" in c and c[\"name\"] == column_name:\n                return c\n\n        return None\n\n    def _get_db(self):\n        if self.is_replica_set:\n            db_connection = pymongo.MongoReplicaSetClient(self.configuration[\"connectionString\"], replicaSet=self.configuration[\"replicaSetName\"])\n        else:\n            db_connection = pymongo.MongoClient(self.configuration[\"connectionString\"])\n\n        return db_connection[self.db_name]\n\n    def test_connection(self):\n        db = self._get_db()\n        if not db.command(\"connectionStatus\")[\"ok\"]:\n            raise Exception(\"MongoDB connection error\")\n\n    def _merge_property_names(self, columns, document):\n        for property in document:\n              if property not in columns:\n                  columns.append(property)\n\n    def _get_collection_fields(self, db, collection_name):\n        # Since MongoDB is a document based database and each document doesn't have\n        # to have the same fields as another documet in the collection its a bit hard to\n        # show these attributes as fields in the schema.\n        #\n        # For now, the logic is to take the first and last documents (last is determined\n        # by the Natural Order (http://www.mongodb.org/display/DOCS/Sorting+and+Natural+Order)\n        # as we don't know the correct order. In most single server installations it would be\n        # find. In replicaset when reading from non master it might not return the really last\n        # document written.\n        first_document = None\n        last_document = None\n\n        for d in db[collection_name].find().sort([(\"$natural\", 1)]).limit(1):\n            first_document = d\n\n        for d in db[collection_name].find().sort([(\"$natural\", -1)]).limit(1):\n            last_document = d\n\n        columns = []\n        if first_document: self._merge_property_names(columns, first_document)\n        if last_document: self._merge_property_names(columns, last_document)\n\n        return columns\n\n    def get_schema(self, get_stats=False):\n        schema = {}\n        db = self._get_db()\n        for collection_name in db.collection_names():\n            columns = self._get_collection_fields(db, collection_name)\n            schema[collection_name] = { \"name\" : collection_name, \"columns\" : sorted(columns) }\n\n        return schema.values()\n\n    def run_query(self, query, user):\n        db = self._get_db()\n\n        logger.debug(\"mongodb connection string: %s\", self.configuration['connectionString'])\n        logger.debug(\"mongodb got query: %s\", query)\n\n        try:\n            query_data = parse_query_json(query)\n        except ValueError:\n            return None, \"Invalid query format. The query is not a valid JSON.\"\n\n        if \"collection\" not in query_data:\n            return None, \"'collection' must have a value to run a query\"\n        else:\n            collection = query_data[\"collection\"]\n\n        q = query_data.get(\"query\", None)\n        f = None\n\n        aggregate = query_data.get(\"aggregate\", None)\n        if aggregate:\n            for step in aggregate:\n                if \"$sort\" in step:\n                    sort_list = []\n                    for sort_item in step[\"$sort\"]:\n                        sort_list.append((sort_item[\"name\"], sort_item[\"direction\"]))\n\n                    step[\"$sort\"] = SON(sort_list)\n\n        if not aggregate:\n            s = None\n            if \"sort\" in query_data and query_data[\"sort\"]:\n                s = []\n                for field in query_data[\"sort\"]:\n                    s.append((field[\"name\"], field[\"direction\"]))\n\n        if \"fields\" in query_data:\n            f = query_data[\"fields\"]\n\n        s = None\n        if \"sort\" in query_data and query_data[\"sort\"]:\n            s = []\n            for field_data in query_data[\"sort\"]:\n                s.append((field_data[\"name\"], field_data[\"direction\"]))\n\n        columns = []\n        rows = []\n\n        cursor = None\n        if q or (not q and not aggregate):\n            if s:\n                cursor = db[collection].find(q, f).sort(s)\n            else:\n                cursor = db[collection].find(q, f)\n\n            if \"skip\" in query_data:\n                cursor = cursor.skip(query_data[\"skip\"])\n\n            if \"limit\" in query_data:\n                cursor = cursor.limit(query_data[\"limit\"])\n\n            if \"count\" in query_data:\n                cursor = cursor.count()\n\n        elif aggregate:\n            allow_disk_use = query_data.get('allowDiskUse', False)\n            r = db[collection].aggregate(aggregate, allowDiskUse=allow_disk_use)\n\n            # Backwards compatibility with older pymongo versions.\n            #\n            # Older pymongo version would return a dictionary from an aggregate command.\n            # The dict would contain a \"result\" key which would hold the cursor.\n            # Newer ones return pymongo.command_cursor.CommandCursor.\n            if isinstance(r, dict):\n                cursor = r[\"result\"]\n            else:\n                cursor = r\n\n        if \"count\" in query_data:\n            columns.append({\n                \"name\" : \"count\",\n                \"friendly_name\" : \"count\",\n                \"type\" : TYPE_INTEGER\n            })\n\n            rows.append({ \"count\" : cursor })\n        else:\n            for r in cursor:\n                for k in r:\n                    if self._get_column_by_name(columns, k) is None:\n                        columns.append({\n                            \"name\": k,\n                            \"friendly_name\": k,\n                            \"type\": TYPES_MAP.get(type(r[k]), TYPE_STRING)\n                        })\n\n                rows.append(r)\n\n        if f:\n            ordered_columns = []\n            for k in sorted(f, key=f.get):\n                ordered_columns.append(self._get_column_by_name(columns, k))\n\n            columns = ordered_columns\n\n        data = {\n            \"columns\": columns,\n            \"rows\": rows\n        }\n        error = None\n        json_data = json.dumps(data, cls=MongoDBJSONEncoder)\n\n        return json_data, error\n\nregister(MongoDB)\n", "path": "redash/query_runner/mongodb.py"}], "after_files": [{"content": "import datetime\nimport json\nimport logging\nimport re\n\nfrom dateutil.parser import parse\n\nfrom redash.query_runner import *\nfrom redash.utils import JSONEncoder, parse_human_time\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import pymongo\n    from bson.objectid import ObjectId\n    from bson.timestamp import Timestamp\n    from bson.son import SON\n    from bson.json_util import object_hook as bson_object_hook\n    enabled = True\n\nexcept ImportError:\n    enabled = False\n\n\nTYPES_MAP = {\n    str: TYPE_STRING,\n    unicode: TYPE_STRING,\n    int: TYPE_INTEGER,\n    long: TYPE_INTEGER,\n    float: TYPE_FLOAT,\n    bool: TYPE_BOOLEAN,\n    datetime.datetime: TYPE_DATETIME,\n}\n\n\nclass MongoDBJSONEncoder(JSONEncoder):\n    def default(self, o):\n        if isinstance(o, ObjectId):\n            return str(o)\n        elif isinstance(o, Timestamp):\n            return super(MongoDBJSONEncoder, self).default(o.as_datetime())\n\n        return super(MongoDBJSONEncoder, self).default(o)\n\n\ndate_regex = re.compile(\"ISODate\\(\\\"(.*)\\\"\\)\", re.IGNORECASE)\n\n\ndef parse_oids(oids):\n    if not isinstance(oids, list):\n        raise Exception(\"$oids takes an array as input.\")\n\n    return [bson_object_hook({'$oid': oid}) for oid in oids]\n\n\ndef datetime_parser(dct):\n    for k, v in dct.iteritems():\n        if isinstance(v, basestring):\n            m = date_regex.findall(v)\n            if len(m) > 0:\n                dct[k] = parse(m[0], yearfirst=True)\n\n    if '$humanTime' in dct:\n        return parse_human_time(dct['$humanTime'])\n\n    if '$oids' in dct:\n        return parse_oids(dct['$oids'])\n\n    return bson_object_hook(dct)\n\n\ndef parse_query_json(query):\n    query_data = json.loads(query, object_hook=datetime_parser)\n    return query_data\n\n\ndef _get_column_by_name(columns, column_name):\n    for c in columns:\n        if \"name\" in c and c[\"name\"] == column_name:\n            return c\n\n    return None\n\n\ndef parse_results(results):\n    rows = []\n    columns = []\n\n    for row in results:\n        parsed_row = {}\n\n        for key in row:\n            if isinstance(row[key], dict):\n                for inner_key in row[key]:\n                    column_name = '{}.{}'.format(key, inner_key)\n                    if _get_column_by_name(columns, column_name) is None:\n                        columns.append({\n                            \"name\": column_name,\n                            \"friendly_name\": column_name,\n                            \"type\": TYPES_MAP.get(type(row[key][inner_key]), TYPE_STRING)\n                        })\n                    \n                    parsed_row[column_name] = row[key][inner_key]\n\n            else:\n                if _get_column_by_name(columns, key) is None:\n                    columns.append({\n                        \"name\": key,\n                        \"friendly_name\": key,\n                        \"type\": TYPES_MAP.get(type(row[key]), TYPE_STRING)\n                    })\n                \n                parsed_row[key] = row[key]\n\n        rows.append(parsed_row)\n\n    return rows, columns\n\n\nclass MongoDB(BaseQueryRunner):\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            'type': 'object',\n            'properties': {\n                'connectionString': {\n                    'type': 'string',\n                    'title': 'Connection String'\n                },\n                'dbName': {\n                    'type': 'string',\n                    'title': \"Database Name\"\n                },\n                'replicaSetName': {\n                    'type': 'string',\n                    'title': 'Replica Set Name'\n                },\n            },\n            'required': ['connectionString', 'dbName']\n        }\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def annotate_query(cls):\n        return False\n\n    def __init__(self, configuration):\n        super(MongoDB, self).__init__(configuration)\n\n        self.syntax = 'json'\n\n        self.db_name = self.configuration[\"dbName\"]\n\n        self.is_replica_set = True if \"replicaSetName\" in self.configuration and self.configuration[\"replicaSetName\"] else False\n\n    def _get_db(self):\n        if self.is_replica_set:\n            db_connection = pymongo.MongoReplicaSetClient(self.configuration[\"connectionString\"], replicaSet=self.configuration[\"replicaSetName\"])\n        else:\n            db_connection = pymongo.MongoClient(self.configuration[\"connectionString\"])\n\n        return db_connection[self.db_name]\n\n    def test_connection(self):\n        db = self._get_db()\n        if not db.command(\"connectionStatus\")[\"ok\"]:\n            raise Exception(\"MongoDB connection error\")\n\n    def _merge_property_names(self, columns, document):\n        for property in document:\n              if property not in columns:\n                  columns.append(property)\n\n    def _get_collection_fields(self, db, collection_name):\n        # Since MongoDB is a document based database and each document doesn't have\n        # to have the same fields as another documet in the collection its a bit hard to\n        # show these attributes as fields in the schema.\n        #\n        # For now, the logic is to take the first and last documents (last is determined\n        # by the Natural Order (http://www.mongodb.org/display/DOCS/Sorting+and+Natural+Order)\n        # as we don't know the correct order. In most single server installations it would be\n        # find. In replicaset when reading from non master it might not return the really last\n        # document written.\n        first_document = None\n        last_document = None\n\n        for d in db[collection_name].find().sort([(\"$natural\", 1)]).limit(1):\n            first_document = d\n\n        for d in db[collection_name].find().sort([(\"$natural\", -1)]).limit(1):\n            last_document = d\n\n        columns = []\n        if first_document: self._merge_property_names(columns, first_document)\n        if last_document: self._merge_property_names(columns, last_document)\n\n        return columns\n\n    def get_schema(self, get_stats=False):\n        schema = {}\n        db = self._get_db()\n        for collection_name in db.collection_names():\n            columns = self._get_collection_fields(db, collection_name)\n            schema[collection_name] = { \"name\" : collection_name, \"columns\" : sorted(columns) }\n\n        return schema.values()\n\n    def run_query(self, query, user):\n        db = self._get_db()\n\n        logger.debug(\"mongodb connection string: %s\", self.configuration['connectionString'])\n        logger.debug(\"mongodb got query: %s\", query)\n\n        try:\n            query_data = parse_query_json(query)\n        except ValueError:\n            return None, \"Invalid query format. The query is not a valid JSON.\"\n\n        if \"collection\" not in query_data:\n            return None, \"'collection' must have a value to run a query\"\n        else:\n            collection = query_data[\"collection\"]\n\n        q = query_data.get(\"query\", None)\n        f = None\n\n        aggregate = query_data.get(\"aggregate\", None)\n        if aggregate:\n            for step in aggregate:\n                if \"$sort\" in step:\n                    sort_list = []\n                    for sort_item in step[\"$sort\"]:\n                        sort_list.append((sort_item[\"name\"], sort_item[\"direction\"]))\n\n                    step[\"$sort\"] = SON(sort_list)\n\n        if not aggregate:\n            s = None\n            if \"sort\" in query_data and query_data[\"sort\"]:\n                s = []\n                for field in query_data[\"sort\"]:\n                    s.append((field[\"name\"], field[\"direction\"]))\n\n        if \"fields\" in query_data:\n            f = query_data[\"fields\"]\n\n        s = None\n        if \"sort\" in query_data and query_data[\"sort\"]:\n            s = []\n            for field_data in query_data[\"sort\"]:\n                s.append((field_data[\"name\"], field_data[\"direction\"]))\n\n        columns = []\n        rows = []\n\n        cursor = None\n        if q or (not q and not aggregate):\n            if s:\n                cursor = db[collection].find(q, f).sort(s)\n            else:\n                cursor = db[collection].find(q, f)\n\n            if \"skip\" in query_data:\n                cursor = cursor.skip(query_data[\"skip\"])\n\n            if \"limit\" in query_data:\n                cursor = cursor.limit(query_data[\"limit\"])\n\n            if \"count\" in query_data:\n                cursor = cursor.count()\n\n        elif aggregate:\n            allow_disk_use = query_data.get('allowDiskUse', False)\n            r = db[collection].aggregate(aggregate, allowDiskUse=allow_disk_use)\n\n            # Backwards compatibility with older pymongo versions.\n            #\n            # Older pymongo version would return a dictionary from an aggregate command.\n            # The dict would contain a \"result\" key which would hold the cursor.\n            # Newer ones return pymongo.command_cursor.CommandCursor.\n            if isinstance(r, dict):\n                cursor = r[\"result\"]\n            else:\n                cursor = r\n\n        if \"count\" in query_data:\n            columns.append({\n                \"name\" : \"count\",\n                \"friendly_name\" : \"count\",\n                \"type\" : TYPE_INTEGER\n            })\n\n            rows.append({ \"count\" : cursor })\n        else:\n            rows, columns = parse_results(cursor)\n\n        if f:\n            ordered_columns = []\n            for k in sorted(f, key=f.get):\n                column = _get_column_by_name(columns, k)\n                if column:\n                    ordered_columns.append(column)\n\n            columns = ordered_columns\n\n        data = {\n            \"columns\": columns,\n            \"rows\": rows\n        }\n        error = None\n        json_data = json.dumps(data, cls=MongoDBJSONEncoder)\n\n        return json_data, error\n\nregister(MongoDB)\n", "path": "redash/query_runner/mongodb.py"}]}