Skip to content

Commit 9688849

Browse files
committed
add query debug support
fixes #24
1 parent d1f48c5 commit 9688849

File tree

4 files changed

+100
-15
lines changed

4 files changed

+100
-15
lines changed

django_mongodb/base.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from .introspection import DatabaseIntrospection
1212
from .operations import DatabaseOperations
1313
from .schema import DatabaseSchemaEditor
14+
from .utils import CollectionDebugWrapper
1415

1516

1617
class Cursor:
@@ -74,7 +75,10 @@ def __init__(self, *args, **kwargs):
7475
del self.connection
7576

7677
def get_collection(self, name, **kwargs):
77-
return Collection(self.database, name, **kwargs)
78+
collection = Collection(self.database, name, **kwargs)
79+
if self.queries_logged:
80+
collection = CollectionDebugWrapper(collection, self)
81+
return collection
7882

7983
def __getattr__(self, attr):
8084
"""

django_mongodb/compiler.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
1-
from django.conf import settings
21
from django.core.exceptions import EmptyResultSet
3-
from django.db import (
4-
DatabaseError,
5-
IntegrityError,
6-
NotSupportedError,
7-
connections,
8-
)
2+
from django.db import DatabaseError, IntegrityError, NotSupportedError
93
from django.db.models import NOT_PROVIDED, Count, Expression, Value
104
from django.db.models.aggregates import Aggregate
115
from django.db.models.constants import LOOKUP_SEP
@@ -144,11 +138,6 @@ def build_query(self, columns=None):
144138
query = self.query_class(self, columns)
145139
query.add_filters(self.query.where)
146140
query.order_by(self._get_ordering())
147-
148-
# This at least satisfies the most basic unit tests.
149-
force_debug_cursor = connections[self.using].force_debug_cursor
150-
if force_debug_cursor or (force_debug_cursor is None and settings.DEBUG):
151-
self.connection.queries_log.append({"sql": repr(query)})
152141
return query
153142

154143
def get_columns(self):

django_mongodb/features.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ class DatabaseFeatures(BaseDatabaseFeatures):
1111
uses_savepoints = False
1212

1313
django_test_expected_failures = {
14-
"basic.tests.ModelInstanceCreationTests.test_save_parent_primary_with_default",
14+
# Database defaults not supported: bson.errors.InvalidDocument:
15+
# cannot encode object: <django.db.models.expressions.DatabaseDefault
1516
"basic.tests.ModelInstanceCreationTests.test_save_primary_with_db_default",
16-
"basic.tests.ModelInstanceCreationTests.test_save_primary_with_default",
1717
# Date lookups aren't implemented: https://github.com/mongodb-labs/django-mongodb/issues/9
1818
# (e.g. 'ExtractMonth' object has no attribute 'alias')
1919
"basic.tests.ModelLookupTest.test_does_not_exist",

django_mongodb/utils.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
import copy
2+
import time
3+
14
import django
5+
from django.conf import settings
26
from django.core.exceptions import ImproperlyConfigured
7+
from django.db.backends.utils import logger
38
from django.utils.version import get_version_tuple
9+
from pymongo.cursor import Cursor
410

511

612
def check_django_compatability():
@@ -18,3 +24,89 @@ def check_django_compatability():
1824
f"You must use the latest version of django-mongodb {A}.{B}.x "
1925
f"with Django {A}.{B}.y (found django-mongodb {__version__})."
2026
)
27+
28+
29+
class CollectionDebugWrapper:
30+
def __init__(self, collection, db):
31+
self.collection = collection
32+
self.db = db
33+
34+
def __getattr__(self, attr):
35+
return getattr(self.collection, attr)
36+
37+
def profile_call(self, func, args=(), kwargs=None):
38+
start = time.monotonic()
39+
retval = func(*args, **kwargs or {})
40+
duration = time.monotonic() - start
41+
return duration, retval
42+
43+
def log(self, op, duration, args, kwargs=None):
44+
msg = "(%.3f) %s"
45+
args = " ".join(str(arg) for arg in args)
46+
operation = f"{self.collection.name}.{op}({args})"
47+
kwargs = {k: v for k, v in kwargs.items() if v}
48+
if kwargs:
49+
operation += f"; kwargs={kwargs}"
50+
if len(settings.DATABASES) > 1:
51+
msg += f"; alias={self.db.alias}"
52+
self.db.queries_log.append(
53+
{
54+
"sql": operation,
55+
"time": "%.3f" % duration,
56+
}
57+
)
58+
logger.debug(
59+
msg,
60+
duration,
61+
operation,
62+
extra={
63+
"duration": duration,
64+
"sql": operation,
65+
"kwargs": kwargs,
66+
"alias": self.db.alias,
67+
},
68+
)
69+
70+
def find(self, *args, **kwargs):
71+
return DebugCursor(self, self.collection, *args, **kwargs)
72+
73+
def logging_wrapper(method):
74+
def wrapper(self, *args, **kwargs):
75+
func = getattr(self.collection, method)
76+
# Collection.insert_one() mutates args[0] (the document) by adding
77+
# the _id. deepcopy() to avoid logging that version.
78+
original_args = copy.deepcopy(args)
79+
duration, retval = self.profile_call(func, args, kwargs)
80+
self.log(method, duration, original_args, kwargs)
81+
return retval
82+
83+
return wrapper
84+
85+
# These are the operations that this backend uses.
86+
count_documents = logging_wrapper("count_documents")
87+
insert_one = logging_wrapper("insert_one")
88+
delete_many = logging_wrapper("delete_many")
89+
update_many = logging_wrapper("update_many")
90+
91+
del logging_wrapper
92+
93+
94+
class DebugCursor(Cursor):
95+
def __init__(self, collection_wrapper, *args, **kwargs):
96+
self.collection_wrapper = collection_wrapper
97+
super().__init__(*args, **kwargs)
98+
99+
def _refresh(self):
100+
super_method = super()._refresh
101+
if self._Cursor__id is not None:
102+
return super_method()
103+
# self.__id is None: first time the .find() iterator is
104+
# entered. find() profiling happens here.
105+
duration, retval = self.collection_wrapper.profile_call(super_method)
106+
kwargs = {
107+
"limit": self._Cursor__limit,
108+
"skip": self._Cursor__skip,
109+
"sort": self._Cursor__ordering,
110+
}
111+
self.collection_wrapper.log("find", duration, [self._Cursor__spec], kwargs)
112+
return retval

0 commit comments

Comments
 (0)