1
0
mirror of https://github.com/django/django.git synced 2025-07-06 10:49:17 +00:00

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
This commit is contained in:
Malcolm Tredinnick 2007-12-22 11:16:04 +00:00
parent f47cfe12ae
commit e247b36447
4 changed files with 47 additions and 26 deletions

View File

@ -6,6 +6,9 @@ the SQL domain.
class EmptyResultSet(Exception): class EmptyResultSet(Exception):
pass pass
class FullResultSet(Exception):
pass
class Aggregate(object): class Aggregate(object):
""" """
Base class for all aggregate-related classes (min, max, avg, count, sum). Base class for all aggregate-related classes (min, max, avg, count, sum).

View File

@ -15,7 +15,7 @@ from django.utils.tree import Node
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
from django.dispatch import dispatcher from django.dispatch import dispatcher
from django.db.models import signals 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.sql.datastructures import Count, Date
from django.db.models.fields import FieldDoesNotExist, Field, related from django.db.models.fields import FieldDoesNotExist, Field, related
from django.contrib.contenttypes import generic from django.contrib.contenttypes import generic
@ -311,19 +311,18 @@ class Query(object):
w.relabel_aliases(change_map) w.relabel_aliases(change_map)
if not self.where: if not self.where:
# Since 'self' matches everything, add an explicit "include # Since 'self' matches everything, add an explicit "include
# everything" (pk is not NULL) where-constraint so that # everything" where-constraint so that connections between the
# connections between the where clauses won't exclude valid # where clauses won't exclude valid results.
# results.
alias = self.join((None, self.model._meta.db_table, None, None)) alias = self.join((None, self.model._meta.db_table, None, None))
pk = self.model._meta.pk pk = self.model._meta.pk
self.where.add([alias, pk.column, pk, 'isnull', False], AND) self.where.add(EverythingNode(), AND)
elif self.where: elif self.where:
# rhs has an empty where clause. Make it match everything (see # rhs has an empty where clause. Make it match everything (see
# above for reasoning). # above for reasoning).
w = WhereNode() w = WhereNode()
alias = self.join((None, self.model._meta.db_table, None, None)) alias = self.join((None, self.model._meta.db_table, None, None))
pk = self.model._meta.pk pk = self.model._meta.pk
w.add([alias, pk.column, pk, 'isnull', False], AND) w.add(EverythingNode(), AND)
else: else:
w = WhereNode() w = WhereNode()
self.where.add(w, connector) self.where.add(w, connector)

View File

@ -5,7 +5,7 @@ import datetime
from django.utils import tree from django.utils import tree
from django.db import connection from django.db import connection
from datastructures import EmptyResultSet from datastructures import EmptyResultSet, FullResultSet
# Connection types # Connection types
AND = 'AND' AND = 'AND'
@ -43,26 +43,36 @@ class WhereNode(tree.Node):
result_params = [] result_params = []
empty = True empty = True
for child in node.children: for child in node.children:
if hasattr(child, 'as_sql'): try:
sql, params = child.as_sql(qn=qn) if hasattr(child, 'as_sql'):
format = '(%s)' sql, params = child.as_sql(qn=qn)
elif isinstance(child, tree.Node):
sql, params = self.as_sql(child, qn)
if child.negated:
format = 'NOT (%s)'
else:
format = '(%s)' format = '(%s)'
else: elif isinstance(child, tree.Node):
try: sql, params = self.as_sql(child, qn)
if child.negated:
format = 'NOT (%s)'
else:
format = '(%s)'
else:
sql, params = self.make_atom(child, qn) sql, params = self.make_atom(child, qn)
format = '%s' format = '%s'
except EmptyResultSet: except EmptyResultSet:
if self.connector == AND and not node.negated: if self.connector == AND and not node.negated:
# We can bail out early in this particular case (only). # We can bail out early in this particular case (only).
raise raise
elif node.negated: elif node.negated:
empty = False empty = False
continue 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 empty = False
if sql: if sql:
result.append(format % sql) result.append(format % sql)
@ -156,3 +166,12 @@ class WhereNode(tree.Node):
val = child[0] val = child[0]
child[0] = change_map.get(val, val) 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

View File

@ -26,11 +26,11 @@ __test__ = {'API_TESTS':"""
# Exact query with value None returns nothing ("is NULL" in sql, but every 'id' # Exact query with value None returns nothing ("is NULL" in sql, but every 'id'
# field has a value). # field has a value).
>>> Choice.objects.filter(id__exact=None) >>> Choice.objects.filter(choice__exact=None)
[] []
Excluding the previous result returns everything. Excluding the previous result returns everything.
>>> Choice.objects.exclude(id=None).order_by('id') >>> Choice.objects.exclude(choice=None).order_by('id')
[<Choice: Choice: Because. in poll Q: Why? >, <Choice: Choice: Why Not? in poll Q: Why? >] [<Choice: Choice: Because. in poll Q: Why? >, <Choice: Choice: Why Not? in poll Q: Why? >]
# Valid query, but fails because foo isn't a keyword # Valid query, but fails because foo isn't a keyword