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:
parent
485bfe4861
commit
a35cbdbeaf
@ -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"):
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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"))
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user