mirror of
https://github.com/django/django.git
synced 2025-07-06 18:59:13 +00:00
Fixed some more join and lookup tests.
git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@6121 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
184a6435cd
commit
ff3f6df54c
@ -334,8 +334,8 @@ class Model(object):
|
|||||||
qn(self._meta.db_table), qn(self._meta.pk.column), op)
|
qn(self._meta.db_table), qn(self._meta.pk.column), op)
|
||||||
param = smart_str(getattr(self, field.attname))
|
param = smart_str(getattr(self, field.attname))
|
||||||
q = self.__class__._default_manager.filter(**kwargs).order_by((not is_next and '-' or '') + field.name, (not is_next and '-' or '') + self._meta.pk.name)
|
q = self.__class__._default_manager.filter(**kwargs).order_by((not is_next and '-' or '') + field.name, (not is_next and '-' or '') + self._meta.pk.name)
|
||||||
q._where.append(where)
|
q.extra(where=where, params=[param, param,
|
||||||
q._params.extend([param, param, getattr(self, self._meta.pk.attname)])
|
getattr(self, self._meta.pk.attname)])
|
||||||
try:
|
try:
|
||||||
return q[0]
|
return q[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
|
@ -10,7 +10,7 @@ all about the internals of models in order to get the information it needs.
|
|||||||
import copy
|
import copy
|
||||||
|
|
||||||
from django.utils import tree
|
from django.utils import tree
|
||||||
from django.db.models.sql.where import WhereNode, AND
|
from django.db.models.sql.where import WhereNode, AND, OR
|
||||||
from django.db.models.sql.datastructures import Count
|
from django.db.models.sql.datastructures import Count
|
||||||
from django.db.models.fields import FieldDoesNotExist
|
from django.db.models.fields import FieldDoesNotExist
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes import generic
|
||||||
@ -233,14 +233,27 @@ class Query(object):
|
|||||||
# Work out how to relabel the rhs aliases, if necessary.
|
# Work out how to relabel the rhs aliases, if necessary.
|
||||||
change_map = {}
|
change_map = {}
|
||||||
used = {}
|
used = {}
|
||||||
|
first_new_join = True
|
||||||
for alias in rhs.tables:
|
for alias in rhs.tables:
|
||||||
|
if not rhs.alias_map[alias][ALIAS_REFCOUNT]:
|
||||||
|
# An unused alias.
|
||||||
|
continue
|
||||||
promote = (rhs.alias_map[alias][ALIAS_JOIN][JOIN_TYPE] ==
|
promote = (rhs.alias_map[alias][ALIAS_JOIN][JOIN_TYPE] ==
|
||||||
self.LOUTER)
|
self.LOUTER)
|
||||||
new_alias = self.join(rhs.rev_join_map[alias], exclusions=used,
|
new_alias = self.join(rhs.rev_join_map[alias], exclusions=used,
|
||||||
promote=promote)
|
promote=promote, outer_if_first=True)
|
||||||
|
if self.alias_map[alias][ALIAS_REFCOUNT] == 1:
|
||||||
|
first_new_join = False
|
||||||
used[new_alias] = None
|
used[new_alias] = None
|
||||||
change_map[alias] = new_alias
|
change_map[alias] = new_alias
|
||||||
|
|
||||||
|
# So that we don't exclude valid results, the first join that is
|
||||||
|
# exclusive to the lhs (self) must be converted to an outer join.
|
||||||
|
for alias in self.tables[1:]:
|
||||||
|
if self.alias_map[alias][ALIAS_REFCOUNT] == 1:
|
||||||
|
self.alias_map[alias][ALIAS_JOIN][JOIN_TYPE] = self.LOUTER
|
||||||
|
break
|
||||||
|
|
||||||
# Now relabel a copy of the rhs where-clause and add it to the current
|
# Now relabel a copy of the rhs where-clause and add it to the current
|
||||||
# one.
|
# one.
|
||||||
if rhs.where:
|
if rhs.where:
|
||||||
@ -380,8 +393,12 @@ class Query(object):
|
|||||||
""" Decreases the reference count for this alias. """
|
""" Decreases the reference count for this alias. """
|
||||||
self.alias_map[alias][ALIAS_REFCOUNT] -= 1
|
self.alias_map[alias][ALIAS_REFCOUNT] -= 1
|
||||||
|
|
||||||
|
def promote_alias(self, alias):
|
||||||
|
""" Promotes the join type of an alias to an outer join. """
|
||||||
|
self.alias_map[alias][ALIAS_JOIN][JOIN_TYPE] = self.LOUTER
|
||||||
|
|
||||||
def join(self, (lhs, table, lhs_col, col), always_create=False,
|
def join(self, (lhs, table, lhs_col, col), always_create=False,
|
||||||
exclusions=(), promote=False):
|
exclusions=(), promote=False, outer_if_first=False):
|
||||||
"""
|
"""
|
||||||
Returns an alias for a join between 'table' and 'lhs' on the given
|
Returns an alias for a join between 'table' and 'lhs' on the given
|
||||||
columns, either reusing an existing alias for that join or creating a
|
columns, either reusing an existing alias for that join or creating a
|
||||||
@ -398,6 +415,10 @@ class Query(object):
|
|||||||
If 'promote' is True, the join type for the alias will be LOUTER (if
|
If 'promote' is True, the join type for the alias will be LOUTER (if
|
||||||
the alias previously existed, the join type will be promoted from INNER
|
the alias previously existed, the join type will be promoted from INNER
|
||||||
to LOUTER, if necessary).
|
to LOUTER, if necessary).
|
||||||
|
|
||||||
|
If 'outer_if_first' is True and a new join is created, it will have the
|
||||||
|
LOUTER join type. This is used when joining certain types of querysets
|
||||||
|
and Q-objects together.
|
||||||
"""
|
"""
|
||||||
if lhs not in self.alias_map:
|
if lhs not in self.alias_map:
|
||||||
lhs_table = lhs
|
lhs_table = lhs
|
||||||
@ -422,7 +443,7 @@ class Query(object):
|
|||||||
assert not is_table, \
|
assert not is_table, \
|
||||||
"Must pass in lhs alias when creating a new join."
|
"Must pass in lhs alias when creating a new join."
|
||||||
alias, _ = self.table_alias(table, True)
|
alias, _ = self.table_alias(table, True)
|
||||||
join_type = promote and self.LOUTER or self.INNER
|
join_type = (promote or outer_if_first) and self.LOUTER or self.INNER
|
||||||
join = [table, alias, join_type, lhs, lhs_col, col]
|
join = [table, alias, join_type, lhs, lhs_col, col]
|
||||||
if not lhs:
|
if not lhs:
|
||||||
# Not all tables need to be joined to anything. No join type
|
# Not all tables need to be joined to anything. No join type
|
||||||
@ -487,7 +508,8 @@ class Query(object):
|
|||||||
opts = self.model._meta
|
opts = self.model._meta
|
||||||
alias = self.join((None, opts.db_table, None, None))
|
alias = self.join((None, opts.db_table, None, None))
|
||||||
dupe_multis = (connection == AND)
|
dupe_multis = (connection == AND)
|
||||||
last = None
|
seen_aliases = []
|
||||||
|
done_split = not self.where
|
||||||
|
|
||||||
# FIXME: Using enumerate() here is expensive. We only need 'i' to
|
# FIXME: Using enumerate() here is expensive. We only need 'i' to
|
||||||
# check we aren't joining against a non-joinable field. Find a
|
# check we aren't joining against a non-joinable field. Find a
|
||||||
@ -498,23 +520,35 @@ class Query(object):
|
|||||||
if name == 'pk':
|
if name == 'pk':
|
||||||
name = target_field.name
|
name = target_field.name
|
||||||
if joins is not None:
|
if joins is not None:
|
||||||
|
seen_aliases.extend(joins)
|
||||||
last = joins
|
last = joins
|
||||||
alias = joins[-1]
|
alias = joins[-1]
|
||||||
|
if connection == OR and not done_split:
|
||||||
|
if self.alias_map[joins[0]][ALIAS_REFCOUNT] == 1:
|
||||||
|
done_split = True
|
||||||
|
self.promote_alias(joins[0])
|
||||||
|
for t in self.tables[1:]:
|
||||||
|
if t in seen_aliases:
|
||||||
|
continue
|
||||||
|
self.promote_alias(t)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
seen_aliases.extend(joins)
|
||||||
else:
|
else:
|
||||||
# Normal field lookup must be the last field in the filter.
|
# Normal field lookup must be the last field in the filter.
|
||||||
if i != len(parts) - 1:
|
if i != len(parts) - 1:
|
||||||
raise TypeError("Joins on field %r not permitted."
|
raise TypeError("Join on field %r not permitted."
|
||||||
% name)
|
% name)
|
||||||
|
|
||||||
col = target_col or target_field.column
|
col = target_col or target_field.column
|
||||||
|
|
||||||
if target_field is opts.pk and last:
|
if target_field is opts.pk and seen_aliases:
|
||||||
# An optimization: if the final join is against a primary key,
|
# An optimization: if the final join is against a primary key,
|
||||||
# we can go back one step in the join chain and compare against
|
# we can go back one step in the join chain and compare against
|
||||||
# the lhs of the join instead. The result (potentially) involves
|
# the lhs of the join instead. The result (potentially) involves
|
||||||
# one less table join.
|
# one less table join.
|
||||||
self.unref_alias(alias)
|
self.unref_alias(alias)
|
||||||
join = self.alias_map[last[-1]][ALIAS_JOIN]
|
join = self.alias_map[seen_aliases[-1]][ALIAS_JOIN]
|
||||||
alias = join[LHS_ALIAS]
|
alias = join[LHS_ALIAS]
|
||||||
col = join[LHS_JOIN_COL]
|
col = join[LHS_JOIN_COL]
|
||||||
|
|
||||||
@ -523,13 +557,13 @@ class Query(object):
|
|||||||
# join when connecting to the previous model. We make that
|
# join when connecting to the previous model. We make that
|
||||||
# adjustment here. We don't do this unless needed because it's less
|
# adjustment here. We don't do this unless needed because it's less
|
||||||
# efficient at the database level.
|
# efficient at the database level.
|
||||||
self.alias_map[joins[0]][ALIAS_JOIN][JOIN_TYPE] = self.LOUTER
|
self.promote_alias(joins[0])
|
||||||
|
|
||||||
self.where.add([alias, col, orig_field, lookup_type, value],
|
self.where.add([alias, col, orig_field, lookup_type, value],
|
||||||
connection)
|
connection)
|
||||||
if negate:
|
if negate:
|
||||||
if last:
|
if seen_aliases:
|
||||||
self.alias_map[last[0]][ALIAS_JOIN][JOIN_TYPE] = self.LOUTER
|
self.promote_alias(last[0])
|
||||||
self.where.negate()
|
self.where.negate()
|
||||||
|
|
||||||
def add_q(self, q_object):
|
def add_q(self, q_object):
|
||||||
|
@ -64,11 +64,19 @@ class WhereNode(tree.Node):
|
|||||||
else:
|
else:
|
||||||
format = '(%s)'
|
format = '(%s)'
|
||||||
else:
|
else:
|
||||||
sql = self.make_atom(child)
|
try:
|
||||||
params = child[2].get_db_prep_lookup(child[3], child[4])
|
sql = self.make_atom(child)
|
||||||
format = '%s'
|
params = child[2].get_db_prep_lookup(child[3], child[4])
|
||||||
result.append(format % sql)
|
format = '%s'
|
||||||
result_params.extend(params)
|
except EmptyResultSet:
|
||||||
|
if node.negated:
|
||||||
|
# If this is a "not" atom, being empty means it has no
|
||||||
|
# effect on the result, so we can ignore it.
|
||||||
|
continue
|
||||||
|
raise
|
||||||
|
if sql:
|
||||||
|
result.append(format % sql)
|
||||||
|
result_params.extend(params)
|
||||||
conn = ' %s ' % node.connection
|
conn = ' %s ' % node.connection
|
||||||
return conn.join(result), result_params
|
return conn.join(result), result_params
|
||||||
|
|
||||||
|
@ -21,6 +21,10 @@ class Node(object):
|
|||||||
self.subtree_parents = []
|
self.subtree_parents = []
|
||||||
self.negated = False
|
self.negated = False
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '(%s: %s)' % (self.connection, ', '.join([str(c) for c in
|
||||||
|
self.children]))
|
||||||
|
|
||||||
def __deepcopy__(self, memodict):
|
def __deepcopy__(self, memodict):
|
||||||
"""
|
"""
|
||||||
Utility method used by copy.deepcopy().
|
Utility method used by copy.deepcopy().
|
||||||
@ -59,7 +63,8 @@ class Node(object):
|
|||||||
if len(self.children) < 2:
|
if len(self.children) < 2:
|
||||||
self.connection = conn_type
|
self.connection = conn_type
|
||||||
if self.connection == conn_type:
|
if self.connection == conn_type:
|
||||||
if isinstance(node, Node) and node.connection == conn_type:
|
if isinstance(node, Node) and (node.connection == conn_type
|
||||||
|
or len(node) == 1):
|
||||||
self.children.extend(node.children)
|
self.children.extend(node.children)
|
||||||
else:
|
else:
|
||||||
self.children.append(node)
|
self.children.append(node)
|
||||||
|
@ -258,7 +258,7 @@ TypeError: Cannot resolve keyword 'pub_date_year' into field. Choices are: id, h
|
|||||||
>>> Article.objects.filter(headline__starts='Article')
|
>>> Article.objects.filter(headline__starts='Article')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
TypeError: Cannot resolve keyword 'headline__starts' into field. Choices are: id, headline, pub_date
|
TypeError: Join on field 'headline' not permitted.
|
||||||
|
|
||||||
# Create some articles with a bit more interesting headlines for testing field lookups:
|
# Create some articles with a bit more interesting headlines for testing field lookups:
|
||||||
>>> now = datetime.now()
|
>>> now = datetime.now()
|
||||||
|
@ -111,11 +111,9 @@ Bug #4464
|
|||||||
>>> Item.objects.filter(tags__in=[t1, t2]).filter(tags=t3)
|
>>> Item.objects.filter(tags__in=[t1, t2]).filter(tags=t3)
|
||||||
[<Item: two>]
|
[<Item: two>]
|
||||||
|
|
||||||
Bug #2080
|
Bug #2080, #3592
|
||||||
# FIXME: Still problematic: the join needs to be "left outer" on the reverse
|
>>> Author.objects.filter(Q(name='a3') | Q(item__name='one'))
|
||||||
# fk, but the individual joins only need to be inner.
|
[<Author: a1>, <Author: a3>]
|
||||||
# >>> Author.objects.filter(Q(name='a3') | Q(item__name='one'))
|
|
||||||
# [<Author: a3>]
|
|
||||||
|
|
||||||
Bug #2939
|
Bug #2939
|
||||||
# FIXME: ValueQuerySets don't work yet.
|
# FIXME: ValueQuerySets don't work yet.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user