Skip to content

Commit a35cbdb

Browse files
committed
[soc2010/query-refactor] On unsupported operations raise a useful exception.
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2010/query-refactor@13437 bcc190cf-cafb-0310-a4f2-bffc1f526a37
1 parent 485bfe4 commit a35cbdb

File tree

4 files changed

+71
-18
lines changed

4 files changed

+71
-18
lines changed

django/contrib/mongodb/compiler.py

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from pymongo import ASCENDING, DESCENDING
44

5+
from django.db import UnsupportedDatabaseOperation
56
from django.db.models import F
67
from django.db.models.sql.datastructures import FullResultSet, EmptyResultSet
78

@@ -43,10 +44,13 @@ def get_filters(self, where):
4344
pass
4445
return filters
4546

46-
def make_atom(self, lhs, lookup_type, value_annotation, params_or_value, negated):
47+
def make_atom(self, lhs, lookup_type, value_annotation, params_or_value,
48+
negated):
4749
assert lookup_type in self.LOOKUP_TYPES, lookup_type
4850
if hasattr(lhs, "process"):
49-
lhs, params = lhs.process(lookup_type, params_or_value, self.connection)
51+
lhs, params = lhs.process(
52+
lookup_type, params_or_value, self.connection
53+
)
5054
else:
5155
params = Field().get_db_prep_lookup(lookup_type, params_or_value,
5256
connection=self.connection, prepared=True)
@@ -56,7 +60,8 @@ def make_atom(self, lhs, lookup_type, value_annotation, params_or_value, negated
5660
if column == self.query.model._meta.pk.column:
5761
column = "_id"
5862

59-
return column, self.LOOKUP_TYPES[lookup_type](params, value_annotation, negated)
63+
val = self.LOOKUP_TYPES[lookup_type](params, value_annotation, negated)
64+
return column, val
6065

6166
def negate(self, k, v):
6267
# Regex lookups are of the form {"field": re.compile("pattern") and
@@ -79,14 +84,18 @@ def get_fields(self, aggregates):
7984
return None
8085

8186
def build_query(self, aggregates=False):
82-
assert len([a for a in self.query.alias_map if self.query.alias_refcount[a]]) <= 1
87+
if len([a for a in self.query.alias_map if self.query.alias_refcount[a]]) > 1:
88+
raise UnsupportedDatabaseOperation("MongoDB does not support "
89+
"operations across relations.")
90+
if self.query.extra:
91+
raise UnsupportedDatabaseOperation("MongoDB does not support extra().")
8392
assert not self.query.distinct
84-
assert not self.query.extra
8593
assert not self.query.having
8694

8795
filters = self.get_filters(self.query.where)
8896
fields = self.get_fields(aggregates=aggregates)
89-
cursor = self.connection.db[self.query.model._meta.db_table].find(filters, fields=fields)
97+
collection = self.connection.db[self.query.model._meta.db_table]
98+
cursor = collection.find(filters, fields=fields)
9099
if self.query.order_by:
91100
cursor = cursor.sort([
92101
(ordering.lstrip("-"), DESCENDING if ordering.startswith("-") else ASCENDING)
@@ -125,14 +134,19 @@ def has_results(self):
125134
return True
126135

127136
def get_aggregates(self):
137+
if len(self.query.aggregates) != 1:
138+
raise UnsupportedDatabaseOperation("MongoDB doesn't support "
139+
"multiple aggregates in a single query.")
128140
assert len(self.query.aggregates) == 1
129141
agg = self.query.aggregates.values()[0]
130-
assert (
131-
isinstance(agg, self.query.aggregates_module.Count) and (
132-
agg.col == "*" or
133-
isinstance(agg.col, tuple) and agg.col == (self.query.model._meta.db_table, self.query.model._meta.pk.column)
134-
)
135-
)
142+
if not isinstance(agg, self.query.aggregates_module.Count):
143+
raise UnsupportedDatabaseOperation("MongoDB does not support "
144+
"aggregates other than Count.")
145+
opts = self.query.model._meta
146+
if not (agg.col == "*" or agg.col == (opts.db_table, opts.pk.column)):
147+
raise UnsupportedDatabaseOperation("MongoDB does not support "
148+
"aggregation over fields besides the primary key.")
149+
136150
return [self.build_query(aggregates=True).count()]
137151

138152

@@ -152,8 +166,7 @@ class SQLUpdateCompiler(SQLCompiler):
152166
def update(self, result_type):
153167
# TODO: more asserts
154168
filters = self.get_filters(self.query.where)
155-
# TODO: Don't use set for everything, use INC and such where
156-
# appropriate.
169+
157170
vals = {}
158171
for field, o, value in self.query.values:
159172
if hasattr(value, "evaluate"):

django/db/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from django.utils.functional import curry
77

88
__all__ = ('backend', 'connection', 'connections', 'router', 'DatabaseError',
9-
'IntegrityError', 'DEFAULT_DB_ALIAS')
9+
'IntegrityError', 'UnsupportedDatabaseOperation', 'DEFAULT_DB_ALIAS')
1010

1111

1212
# For backwards compatibility - Port any old database settings over to
@@ -75,6 +75,12 @@
7575
connection = connections[DEFAULT_DB_ALIAS]
7676
backend = load_backend(connection.settings_dict['ENGINE'])
7777

78+
class UnsupportedDatabaseOperation(Exception):
79+
"""
80+
Raised when an operation attempted on a QuerySet is unsupported on the
81+
database for it's execution.
82+
"""
83+
7884
# Register an event that closes the database connection
7985
# when a Django request is finished.
8086
def close_connection(**kwargs):

tests/regressiontests/mongodb/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ class Artist(models.Model):
77
good = models.BooleanField()
88
age = models.IntegerField(null=True)
99

10-
current_group = models.ForeignKey("Group", null=True)
10+
current_group = models.ForeignKey("Group", null=True,
11+
related_name="current_artists")
1112

1213
def __unicode__(self):
1314
return self.name

tests/regressiontests/mongodb/tests.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from django.db import connection
2-
from django.db.models import Count, F
1+
from django.db import connection, UnsupportedDatabaseOperation
2+
from django.db.models import Count, Sum, F
33
from django.test import TestCase
44

55
from models import Artist, Group
@@ -359,3 +359,36 @@ def test_close(self):
359359
# Ensure that closing a connection that was never established doesn't
360360
# blow up.
361361
connection.close()
362+
363+
def assert_unsupported(self, obj):
364+
if callable(obj):
365+
# Queryset wrapped in a function (for aggregates and such)
366+
self.assertRaises(UnsupportedDatabaseOperation, obj)
367+
else:
368+
# Just a queryset that blows up on evaluation
369+
self.assertRaises(UnsupportedDatabaseOperation, list, obj)
370+
371+
def test_unsupported_ops(self):
372+
self.assert_unsupported(
373+
Artist.objects.filter(current_group__name="The Beatles")
374+
)
375+
376+
self.assert_unsupported(
377+
Artist.objects.extra(select={"a": "1.0"})
378+
)
379+
380+
self.assert_unsupported(
381+
Group.objects.annotate(artists=Count("current_artists"))
382+
)
383+
384+
self.assert_unsupported(
385+
lambda: Artist.objects.aggregate(Sum("age"))
386+
)
387+
388+
self.assert_unsupported(
389+
lambda: Artist.objects.aggregate(Count("age"))
390+
)
391+
392+
self.assert_unsupported(
393+
lambda: Artist.objects.aggregate(Count("id"), Count("pk"))
394+
)

0 commit comments

Comments
 (0)