diff --git a/django/contrib/mongodb/base.py b/django/contrib/mongodb/base.py index 70f46c91d4..c98562912c 100644 --- a/django/contrib/mongodb/base.py +++ b/django/contrib/mongodb/base.py @@ -10,6 +10,7 @@ from django.utils.importlib import import_module class DatabaseFeatures(object): interprets_empty_strings_as_nulls = False typed_columns = False + sql_nulls = False class DatabaseOperations(object): diff --git a/django/contrib/mongodb/compiler.py b/django/contrib/mongodb/compiler.py index 9636711f6b..de50819cc6 100644 --- a/django/contrib/mongodb/compiler.py +++ b/django/contrib/mongodb/compiler.py @@ -1,3 +1,6 @@ +from django.db.models.sql.datastructures import FullResultSet + + # TODO: ... class SQLCompiler(object): def __init__(self, query, connection, using): @@ -5,7 +8,7 @@ class SQLCompiler(object): self.connection = connection self.using = using - def get_filters(self, where, correct=False): + def get_filters(self, where): assert where.connector == "AND" filters = {} for child in where.children: @@ -15,13 +18,15 @@ class SQLCompiler(object): if k in filters: v = {"$and": [filters[k], v]} if where.negated: - v = {"$not": v} - filters[k] = v + filters.update(self.negate(k, v)) + else: + filters[k] = v else: - field, val = self.make_atom(*child, **{"negated": where.negated}) - filters[field] = val - if correct: - self.correct_filters(filters) + try: + field, val = self.make_atom(*child, **{"negated": where.negated}) + filters[field] = val + except FullResultSet: + pass return filters def make_atom(self, lhs, lookup_type, value_annotation, params_or_value, negated): @@ -48,17 +53,10 @@ class SQLCompiler(object): val = {"$not": val} return column, val elif lookup_type == "lt": + if negated: + return {"$gte": 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): if isinstance(v, dict): if v.keys() == ["$not"]: @@ -76,7 +74,7 @@ class SQLCompiler(object): assert self.query.high_mark is None 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) def results_iter(self): diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 959990f628..6836337429 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1077,7 +1077,11 @@ class Query(object): # it's short-circuited in the Where class. # We also need to handle the case where a subquery is provided 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() self.where.add(entry, AND) diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py index 4e5a647259..e953bca703 100644 --- a/django/db/models/sql/where.py +++ b/django/db/models/sql/where.py @@ -267,8 +267,9 @@ class Constraint(object): An object that can be passed to WhereNode.add() and knows how to 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.elimintable_if = eliminatable_if def __getstate__(self): """Save the state of the Constraint for pickling. @@ -321,6 +322,9 @@ class Constraint(object): except ObjectDoesNotExist: raise EmptyShortCircuit + if self.elimintable_if and self.elimintable_if(connection): + raise FullResultSet + return (self.alias, self.col, db_type), params def relabel_aliases(self, change_map): diff --git a/tests/regressiontests/mongodb/tests.py b/tests/regressiontests/mongodb/tests.py index f6729f6d8d..831752e052 100644 --- a/tests/regressiontests/mongodb/tests.py +++ b/tests/regressiontests/mongodb/tests.py @@ -62,20 +62,8 @@ class MongoTestCase(TestCase): q = Group.objects.create(name="Queen", year_formed=1971) 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( - qs, [ + Group.objects.exclude(year_formed=1972), [ "Queen", ], lambda g: g.name, @@ -105,4 +93,10 @@ class MongoTestCase(TestCase): [], lambda g: g.name ) - + + self.assertQuerysetEqual( + Group.objects.exclude(year_formed__lt=1972), [ + "The E Street Band" + ], + lambda g: g.name, + )