Skip to content

Commit abc335f

Browse files
Jibolatimgraham
andcommitted
Make QuerySet.explain() return parsable JSON
Co-authored-by: Tim Graham <[email protected]>
1 parent e91193b commit abc335f

File tree

4 files changed

+60
-8
lines changed

4 files changed

+60
-8
lines changed

django_mongodb_backend/compiler.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import itertools
2-
import pprint
32
from collections import defaultdict
43

5-
from bson import SON
4+
from bson import SON, json_util
65
from django.core.exceptions import EmptyResultSet, FieldError, FullResultSet
76
from django.db import IntegrityError, NotSupportedError
87
from django.db.models import Count
@@ -652,12 +651,7 @@ def explain_query(self):
652651
{"aggregate": self.collection_name, "pipeline": pipeline, "cursor": {}},
653652
**kwargs,
654653
)
655-
# Generate the output: a list of lines that Django joins with newlines.
656-
result = []
657-
for key, value in explain.items():
658-
formatted_value = pprint.pformat(value, indent=4)
659-
result.append(f"{key}: {formatted_value}")
660-
return result
654+
return [json_util.dumps(explain, indent=4, ensure_ascii=False)]
661655

662656

663657
class SQLInsertCompiler(SQLCompiler):

docs/source/ref/models/querysets.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ In addition, :meth:`QuerySet.delete() <django.db.models.query.QuerySet.delete>`
1515
and :meth:`update() <django.db.models.query.QuerySet.update>` do not support
1616
queries that span multiple collections.
1717

18+
.. _queryset-explain:
19+
1820
``QuerySet.explain()``
1921
======================
2022

@@ -29,6 +31,24 @@ Example::
2931
Valid values for ``verbosity`` are ``"queryPlanner"`` (default),
3032
``"executionStats"``, and ``"allPlansExecution"``.
3133

34+
The result of ``explain()`` is a string::
35+
36+
>>> print(Model.objects.explain())
37+
{
38+
"explainVersion": "1",
39+
"queryPlanner": {
40+
...
41+
},
42+
...
43+
}
44+
45+
that can be parsed as JSON::
46+
47+
>>> from bson import json_util
48+
>>> result = Model.objects.filter(name="MongoDB").explain()
49+
>>> json_util.loads(result)['command']["pipeline"]
50+
[{'$match': {'$expr': {'$eq': ['$name', 'MongoDB']}}}]
51+
3252
MongoDB-specific ``QuerySet`` methods
3353
=====================================
3454

docs/source/releases/5.2.x.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ New features
1717
- Added :class:`~.fields.PolymorphicEmbeddedModelField` for storing a model
1818
instance that may be of more than one model class.
1919

20+
Bug fixes
21+
---------
22+
23+
- :meth:`QuerySet.explain() <django.db.models.query.QuerySet.explain>` now
24+
:ref:`returns a string that can be parsed as JSON <queryset-explain>`.
25+
2026
5.2.0 beta 1
2127
============
2228

tests/queries_/test_explain.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import json
2+
3+
from bson import ObjectId, json_util
4+
from django.test import TestCase
5+
6+
from .models import Author
7+
8+
9+
class ExplainTests(TestCase):
10+
def test_object_id(self):
11+
"""
12+
The json is dumped with bson.json_util() so that BSON types like ObjectID are
13+
specially encoded.
14+
"""
15+
id = ObjectId()
16+
result = Author.objects.filter(id=id).explain()
17+
parsed = json_util.loads(result)
18+
self.assertEqual(
19+
parsed["command"]["pipeline"], [{"$match": {"$expr": {"$eq": ["$_id", id]}}}]
20+
)
21+
22+
def test_non_ascii(self):
23+
"""The json is dumped with ensure_ascii=False."""
24+
name = "\U0001d120"
25+
result = Author.objects.filter(name=name).explain()
26+
# The non-decoded string must be checked since json.loads() unescapes
27+
# non-ASCII characters.
28+
self.assertIn(name, result)
29+
parsed = json.loads(result)
30+
self.assertEqual(
31+
parsed["command"]["pipeline"], [{"$match": {"$expr": {"$eq": ["$name", name]}}}]
32+
)

0 commit comments

Comments
 (0)