mirror of
https://github.com/django/django.git
synced 2025-07-04 09:49:12 +00:00
[soc2010/query-refactor] Cleaned up implementation of negation in MongoDB, and no longer rely on a feature from MongoDB unstable version.
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2010/query-refactor@13368 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
dd5e718296
commit
706b18966f
@ -10,6 +10,7 @@ from django.utils.importlib import import_module
|
|||||||
class DatabaseFeatures(object):
|
class DatabaseFeatures(object):
|
||||||
interprets_empty_strings_as_nulls = False
|
interprets_empty_strings_as_nulls = False
|
||||||
typed_columns = False
|
typed_columns = False
|
||||||
|
sql_nulls = False
|
||||||
|
|
||||||
|
|
||||||
class DatabaseOperations(object):
|
class DatabaseOperations(object):
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
from django.db.models.sql.datastructures import FullResultSet
|
||||||
|
|
||||||
|
|
||||||
# TODO: ...
|
# TODO: ...
|
||||||
class SQLCompiler(object):
|
class SQLCompiler(object):
|
||||||
def __init__(self, query, connection, using):
|
def __init__(self, query, connection, using):
|
||||||
@ -5,7 +8,7 @@ class SQLCompiler(object):
|
|||||||
self.connection = connection
|
self.connection = connection
|
||||||
self.using = using
|
self.using = using
|
||||||
|
|
||||||
def get_filters(self, where, correct=False):
|
def get_filters(self, where):
|
||||||
assert where.connector == "AND"
|
assert where.connector == "AND"
|
||||||
filters = {}
|
filters = {}
|
||||||
for child in where.children:
|
for child in where.children:
|
||||||
@ -15,13 +18,15 @@ class SQLCompiler(object):
|
|||||||
if k in filters:
|
if k in filters:
|
||||||
v = {"$and": [filters[k], v]}
|
v = {"$and": [filters[k], v]}
|
||||||
if where.negated:
|
if where.negated:
|
||||||
v = {"$not": v}
|
filters.update(self.negate(k, v))
|
||||||
filters[k] = v
|
else:
|
||||||
|
filters[k] = v
|
||||||
else:
|
else:
|
||||||
field, val = self.make_atom(*child, **{"negated": where.negated})
|
try:
|
||||||
filters[field] = val
|
field, val = self.make_atom(*child, **{"negated": where.negated})
|
||||||
if correct:
|
filters[field] = val
|
||||||
self.correct_filters(filters)
|
except FullResultSet:
|
||||||
|
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):
|
||||||
@ -48,17 +53,10 @@ class SQLCompiler(object):
|
|||||||
val = {"$not": val}
|
val = {"$not": val}
|
||||||
return column, val
|
return column, val
|
||||||
elif lookup_type == "lt":
|
elif lookup_type == "lt":
|
||||||
|
if negated:
|
||||||
|
return {"$gte": params[0]}
|
||||||
return column, {"$lt": params[0]}
|
return column, {"$lt": params[0]}
|
||||||
|
|
||||||
def correct_filters(self, filters):
|
|
||||||
for k, v in filters.items():
|
|
||||||
if isinstance(v, dict) and v.keys() == ["$not"]:
|
|
||||||
if isinstance(v["$not"], dict) and v["$not"].keys() == ["$and"]:
|
|
||||||
del filters[k]
|
|
||||||
or_vals = [self.negate(k, v) for v in v["$not"]["$and"]]
|
|
||||||
assert "$or" not in filters
|
|
||||||
filters["$or"] = or_vals
|
|
||||||
|
|
||||||
def negate(self, k, v):
|
def negate(self, k, v):
|
||||||
if isinstance(v, dict):
|
if isinstance(v, dict):
|
||||||
if v.keys() == ["$not"]:
|
if v.keys() == ["$not"]:
|
||||||
@ -76,7 +74,7 @@ class SQLCompiler(object):
|
|||||||
assert self.query.high_mark is None
|
assert self.query.high_mark is None
|
||||||
assert not self.query.order_by
|
assert not self.query.order_by
|
||||||
|
|
||||||
filters = self.get_filters(self.query.where, correct=True)
|
filters = self.get_filters(self.query.where)
|
||||||
return self.connection.db[self.query.model._meta.db_table].find(filters)
|
return self.connection.db[self.query.model._meta.db_table].find(filters)
|
||||||
|
|
||||||
def results_iter(self):
|
def results_iter(self):
|
||||||
|
@ -1077,7 +1077,11 @@ class Query(object):
|
|||||||
# it's short-circuited in the Where class.
|
# it's short-circuited in the Where class.
|
||||||
# We also need to handle the case where a subquery is provided
|
# We also need to handle the case where a subquery is provided
|
||||||
entry = self.where_class()
|
entry = self.where_class()
|
||||||
entry.add((Constraint(alias, col, None), 'isnull', True), AND)
|
entry.add((
|
||||||
|
Constraint(alias, col, None, eliminatable_if=lambda connection: not getattr(connection.features, "sql_nulls", True)),
|
||||||
|
'isnull',
|
||||||
|
True
|
||||||
|
), AND)
|
||||||
entry.negate()
|
entry.negate()
|
||||||
self.where.add(entry, AND)
|
self.where.add(entry, AND)
|
||||||
|
|
||||||
|
@ -267,8 +267,9 @@ class Constraint(object):
|
|||||||
An object that can be passed to WhereNode.add() and knows how to
|
An object that can be passed to WhereNode.add() and knows how to
|
||||||
pre-process itself prior to including in the WhereNode.
|
pre-process itself prior to including in the WhereNode.
|
||||||
"""
|
"""
|
||||||
def __init__(self, alias, col, field):
|
def __init__(self, alias, col, field, eliminatable_if=None):
|
||||||
self.alias, self.col, self.field = alias, col, field
|
self.alias, self.col, self.field = alias, col, field
|
||||||
|
self.elimintable_if = eliminatable_if
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
"""Save the state of the Constraint for pickling.
|
"""Save the state of the Constraint for pickling.
|
||||||
@ -321,6 +322,9 @@ class Constraint(object):
|
|||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
raise EmptyShortCircuit
|
raise EmptyShortCircuit
|
||||||
|
|
||||||
|
if self.elimintable_if and self.elimintable_if(connection):
|
||||||
|
raise FullResultSet
|
||||||
|
|
||||||
return (self.alias, self.col, db_type), params
|
return (self.alias, self.col, db_type), params
|
||||||
|
|
||||||
def relabel_aliases(self, change_map):
|
def relabel_aliases(self, change_map):
|
||||||
|
@ -62,20 +62,8 @@ class MongoTestCase(TestCase):
|
|||||||
q = Group.objects.create(name="Queen", year_formed=1971)
|
q = Group.objects.create(name="Queen", year_formed=1971)
|
||||||
e = Group.objects.create(name="The E Street Band", year_formed=1972)
|
e = Group.objects.create(name="The E Street Band", year_formed=1972)
|
||||||
|
|
||||||
qs = Group.objects.exclude(year_formed=1972)
|
|
||||||
v = qs.query.get_compiler(qs.db).get_filters(qs.query.where, correct=True)
|
|
||||||
self.assertEqual(v, {
|
|
||||||
"$or": [
|
|
||||||
{"year_formed": {"$ne": 1972}},
|
|
||||||
{"year_formed": None},
|
|
||||||
]
|
|
||||||
})
|
|
||||||
# A bug in MongoDB prevents this query from actually working, but test
|
|
||||||
# that we're at least generating the right query.
|
|
||||||
return
|
|
||||||
|
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
qs, [
|
Group.objects.exclude(year_formed=1972), [
|
||||||
"Queen",
|
"Queen",
|
||||||
],
|
],
|
||||||
lambda g: g.name,
|
lambda g: g.name,
|
||||||
@ -105,4 +93,10 @@ class MongoTestCase(TestCase):
|
|||||||
[],
|
[],
|
||||||
lambda g: g.name
|
lambda g: g.name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
Group.objects.exclude(year_formed__lt=1972), [
|
||||||
|
"The E Street Band"
|
||||||
|
],
|
||||||
|
lambda g: g.name,
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user