From e247b364472bd48cd067baa6231c14b500298a4e Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Sat, 22 Dec 2007 11:16:04 +0000 Subject: [PATCH] queryset-refactor: Work around the fact that "where id is NULL" can return different results in different circumstances in MySQL(!!). git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@6967 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/sql/datastructures.py | 3 ++ django/db/models/sql/query.py | 11 ++-- django/db/models/sql/where.py | 55 +++++++++++++------- tests/regressiontests/null_queries/models.py | 4 +- 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/django/db/models/sql/datastructures.py b/django/db/models/sql/datastructures.py index 59343aa515..ab03c72ea2 100644 --- a/django/db/models/sql/datastructures.py +++ b/django/db/models/sql/datastructures.py @@ -6,6 +6,9 @@ the SQL domain. class EmptyResultSet(Exception): pass +class FullResultSet(Exception): + pass + class Aggregate(object): """ Base class for all aggregate-related classes (min, max, avg, count, sum). diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 2f7a08e4dd..f2d6b577f2 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -15,7 +15,7 @@ from django.utils.tree import Node from django.utils.datastructures import SortedDict from django.dispatch import dispatcher from django.db.models import signals -from django.db.models.sql.where import WhereNode, AND, OR +from django.db.models.sql.where import WhereNode, EverythingNode, AND, OR from django.db.models.sql.datastructures import Count, Date from django.db.models.fields import FieldDoesNotExist, Field, related from django.contrib.contenttypes import generic @@ -311,19 +311,18 @@ class Query(object): w.relabel_aliases(change_map) if not self.where: # Since 'self' matches everything, add an explicit "include - # everything" (pk is not NULL) where-constraint so that - # connections between the where clauses won't exclude valid - # results. + # everything" where-constraint so that connections between the + # where clauses won't exclude valid results. alias = self.join((None, self.model._meta.db_table, None, None)) pk = self.model._meta.pk - self.where.add([alias, pk.column, pk, 'isnull', False], AND) + self.where.add(EverythingNode(), AND) elif self.where: # rhs has an empty where clause. Make it match everything (see # above for reasoning). w = WhereNode() alias = self.join((None, self.model._meta.db_table, None, None)) pk = self.model._meta.pk - w.add([alias, pk.column, pk, 'isnull', False], AND) + w.add(EverythingNode(), AND) else: w = WhereNode() self.where.add(w, connector) diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py index d361916953..198c6e2e40 100644 --- a/django/db/models/sql/where.py +++ b/django/db/models/sql/where.py @@ -5,7 +5,7 @@ import datetime from django.utils import tree from django.db import connection -from datastructures import EmptyResultSet +from datastructures import EmptyResultSet, FullResultSet # Connection types AND = 'AND' @@ -43,26 +43,36 @@ class WhereNode(tree.Node): result_params = [] empty = True for child in node.children: - if hasattr(child, 'as_sql'): - sql, params = child.as_sql(qn=qn) - format = '(%s)' - elif isinstance(child, tree.Node): - sql, params = self.as_sql(child, qn) - if child.negated: - format = 'NOT (%s)' - else: + try: + if hasattr(child, 'as_sql'): + sql, params = child.as_sql(qn=qn) format = '(%s)' - else: - try: + elif isinstance(child, tree.Node): + sql, params = self.as_sql(child, qn) + if child.negated: + format = 'NOT (%s)' + else: + format = '(%s)' + else: sql, params = self.make_atom(child, qn) format = '%s' - except EmptyResultSet: - if self.connector == AND and not node.negated: - # We can bail out early in this particular case (only). - raise - elif node.negated: - empty = False - continue + except EmptyResultSet: + if self.connector == AND and not node.negated: + # We can bail out early in this particular case (only). + raise + elif node.negated: + empty = False + continue + except FullResultSet: + if self.connector == OR: + if node.negated: + empty = True + break + # We match everything. No need for any constraints. + return '', [] + if node.negated: + empty = True + continue empty = False if sql: result.append(format % sql) @@ -156,3 +166,12 @@ class WhereNode(tree.Node): val = child[0] child[0] = change_map.get(val, val) +class EverythingNode(object): + """ + A node that matches everything. + """ + def as_sql(self, qn=None): + raise FullResultSet + + def relabel_aliases(self, change_map, node=None): + return diff --git a/tests/regressiontests/null_queries/models.py b/tests/regressiontests/null_queries/models.py index 739b75d96d..d6993b8be5 100644 --- a/tests/regressiontests/null_queries/models.py +++ b/tests/regressiontests/null_queries/models.py @@ -26,11 +26,11 @@ __test__ = {'API_TESTS':""" # Exact query with value None returns nothing ("is NULL" in sql, but every 'id' # field has a value). ->>> Choice.objects.filter(id__exact=None) +>>> Choice.objects.filter(choice__exact=None) [] Excluding the previous result returns everything. ->>> Choice.objects.exclude(id=None).order_by('id') +>>> Choice.objects.exclude(choice=None).order_by('id') [, ] # Valid query, but fails because foo isn't a keyword