1
0
mirror of https://github.com/django/django.git synced 2025-07-04 17:59:13 +00:00

[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
This commit is contained in:
Alex Gaynor 2010-07-19 20:36:07 +00:00
parent 485bfe4861
commit a35cbdbeaf
4 changed files with 71 additions and 18 deletions

View File

@ -2,6 +2,7 @@ import re
from pymongo import ASCENDING, DESCENDING from pymongo import ASCENDING, DESCENDING
from django.db import UnsupportedDatabaseOperation
from django.db.models import F from django.db.models import F
from django.db.models.sql.datastructures import FullResultSet, EmptyResultSet from django.db.models.sql.datastructures import FullResultSet, EmptyResultSet
@ -43,10 +44,13 @@ class SQLCompiler(object):
pass pass
return filters return filters
def make_atom(self, lhs, lookup_type, value_annotation, params_or_value, negated): def make_atom(self, lhs, lookup_type, value_annotation, params_or_value,
negated):
assert lookup_type in self.LOOKUP_TYPES, lookup_type assert lookup_type in self.LOOKUP_TYPES, lookup_type
if hasattr(lhs, "process"): if hasattr(lhs, "process"):
lhs, params = lhs.process(lookup_type, params_or_value, self.connection) lhs, params = lhs.process(
lookup_type, params_or_value, self.connection
)
else: else:
params = Field().get_db_prep_lookup(lookup_type, params_or_value, params = Field().get_db_prep_lookup(lookup_type, params_or_value,
connection=self.connection, prepared=True) connection=self.connection, prepared=True)
@ -56,7 +60,8 @@ class SQLCompiler(object):
if column == self.query.model._meta.pk.column: if column == self.query.model._meta.pk.column:
column = "_id" column = "_id"
return column, self.LOOKUP_TYPES[lookup_type](params, value_annotation, negated) val = self.LOOKUP_TYPES[lookup_type](params, value_annotation, negated)
return column, val
def negate(self, k, v): def negate(self, k, v):
# Regex lookups are of the form {"field": re.compile("pattern") and # Regex lookups are of the form {"field": re.compile("pattern") and
@ -79,14 +84,18 @@ class SQLCompiler(object):
return None return None
def build_query(self, aggregates=False): def build_query(self, aggregates=False):
assert len([a for a in self.query.alias_map if self.query.alias_refcount[a]]) <= 1 if len([a for a in self.query.alias_map if self.query.alias_refcount[a]]) > 1:
raise UnsupportedDatabaseOperation("MongoDB does not support "
"operations across relations.")
if self.query.extra:
raise UnsupportedDatabaseOperation("MongoDB does not support extra().")
assert not self.query.distinct assert not self.query.distinct
assert not self.query.extra
assert not self.query.having assert not self.query.having
filters = self.get_filters(self.query.where) filters = self.get_filters(self.query.where)
fields = self.get_fields(aggregates=aggregates) fields = self.get_fields(aggregates=aggregates)
cursor = self.connection.db[self.query.model._meta.db_table].find(filters, fields=fields) collection = self.connection.db[self.query.model._meta.db_table]
cursor = collection.find(filters, fields=fields)
if self.query.order_by: if self.query.order_by:
cursor = cursor.sort([ cursor = cursor.sort([
(ordering.lstrip("-"), DESCENDING if ordering.startswith("-") else ASCENDING) (ordering.lstrip("-"), DESCENDING if ordering.startswith("-") else ASCENDING)
@ -125,14 +134,19 @@ class SQLCompiler(object):
return True return True
def get_aggregates(self): def get_aggregates(self):
if len(self.query.aggregates) != 1:
raise UnsupportedDatabaseOperation("MongoDB doesn't support "
"multiple aggregates in a single query.")
assert len(self.query.aggregates) == 1 assert len(self.query.aggregates) == 1
agg = self.query.aggregates.values()[0] agg = self.query.aggregates.values()[0]
assert ( if not isinstance(agg, self.query.aggregates_module.Count):
isinstance(agg, self.query.aggregates_module.Count) and ( raise UnsupportedDatabaseOperation("MongoDB does not support "
agg.col == "*" or "aggregates other than Count.")
isinstance(agg.col, tuple) and agg.col == (self.query.model._meta.db_table, self.query.model._meta.pk.column) opts = self.query.model._meta
) if not (agg.col == "*" or agg.col == (opts.db_table, opts.pk.column)):
) raise UnsupportedDatabaseOperation("MongoDB does not support "
"aggregation over fields besides the primary key.")
return [self.build_query(aggregates=True).count()] return [self.build_query(aggregates=True).count()]
@ -152,8 +166,7 @@ class SQLUpdateCompiler(SQLCompiler):
def update(self, result_type): def update(self, result_type):
# TODO: more asserts # TODO: more asserts
filters = self.get_filters(self.query.where) filters = self.get_filters(self.query.where)
# TODO: Don't use set for everything, use INC and such where
# appropriate.
vals = {} vals = {}
for field, o, value in self.query.values: for field, o, value in self.query.values:
if hasattr(value, "evaluate"): if hasattr(value, "evaluate"):

View File

@ -6,7 +6,7 @@ from django.db.utils import ConnectionHandler, ConnectionRouter, load_backend, D
from django.utils.functional import curry from django.utils.functional import curry
__all__ = ('backend', 'connection', 'connections', 'router', 'DatabaseError', __all__ = ('backend', 'connection', 'connections', 'router', 'DatabaseError',
'IntegrityError', 'DEFAULT_DB_ALIAS') 'IntegrityError', 'UnsupportedDatabaseOperation', 'DEFAULT_DB_ALIAS')
# For backwards compatibility - Port any old database settings over to # For backwards compatibility - Port any old database settings over to
@ -75,6 +75,12 @@ router = ConnectionRouter(settings.DATABASE_ROUTERS)
connection = connections[DEFAULT_DB_ALIAS] connection = connections[DEFAULT_DB_ALIAS]
backend = load_backend(connection.settings_dict['ENGINE']) backend = load_backend(connection.settings_dict['ENGINE'])
class UnsupportedDatabaseOperation(Exception):
"""
Raised when an operation attempted on a QuerySet is unsupported on the
database for it's execution.
"""
# Register an event that closes the database connection # Register an event that closes the database connection
# when a Django request is finished. # when a Django request is finished.
def close_connection(**kwargs): def close_connection(**kwargs):

View File

@ -7,7 +7,8 @@ class Artist(models.Model):
good = models.BooleanField() good = models.BooleanField()
age = models.IntegerField(null=True) age = models.IntegerField(null=True)
current_group = models.ForeignKey("Group", null=True) current_group = models.ForeignKey("Group", null=True,
related_name="current_artists")
def __unicode__(self): def __unicode__(self):
return self.name return self.name

View File

@ -1,5 +1,5 @@
from django.db import connection from django.db import connection, UnsupportedDatabaseOperation
from django.db.models import Count, F from django.db.models import Count, Sum, F
from django.test import TestCase from django.test import TestCase
from models import Artist, Group from models import Artist, Group
@ -359,3 +359,36 @@ class MongoTestCase(TestCase):
# Ensure that closing a connection that was never established doesn't # Ensure that closing a connection that was never established doesn't
# blow up. # blow up.
connection.close() connection.close()
def assert_unsupported(self, obj):
if callable(obj):
# Queryset wrapped in a function (for aggregates and such)
self.assertRaises(UnsupportedDatabaseOperation, obj)
else:
# Just a queryset that blows up on evaluation
self.assertRaises(UnsupportedDatabaseOperation, list, obj)
def test_unsupported_ops(self):
self.assert_unsupported(
Artist.objects.filter(current_group__name="The Beatles")
)
self.assert_unsupported(
Artist.objects.extra(select={"a": "1.0"})
)
self.assert_unsupported(
Group.objects.annotate(artists=Count("current_artists"))
)
self.assert_unsupported(
lambda: Artist.objects.aggregate(Sum("age"))
)
self.assert_unsupported(
lambda: Artist.objects.aggregate(Count("age"))
)
self.assert_unsupported(
lambda: Artist.objects.aggregate(Count("id"), Count("pk"))
)