From 142e400c5cbff91ba70d3636a4429a48131a2edd Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Sun, 14 Oct 2007 02:14:53 +0000 Subject: [PATCH] queryset-refactor: Create a new join when merging two QuerySets that use a 1-m field. git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@6492 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/sql/query.py | 26 ++++++++++++++++++------- tests/regressiontests/queries/models.py | 7 ++----- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index c9ae75a414..505f7124db 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -48,6 +48,7 @@ RHS_JOIN_COL = 5 ALIAS_TABLE = 0 ALIAS_REFCOUNT = 1 ALIAS_JOIN = 2 +ALIAS_MERGE_SEP = 3 # How many results to expect from a cursor.execute call MULTI = 'multi' @@ -252,8 +253,10 @@ class Query(object): continue promote = (rhs.alias_map[alias][ALIAS_JOIN][JOIN_TYPE] == self.LOUTER) + merge_separate = (connection == AND) new_alias = self.join(rhs.rev_join_map[alias], exclusions=used, - promote=promote, outer_if_first=True) + promote=promote, outer_if_first=True, + merge_separate=merge_separate) if self.alias_map[alias][ALIAS_REFCOUNT] == 1: first_new_join = False used[new_alias] = None @@ -417,7 +420,7 @@ class Query(object): alias = table_name else: alias = '%s%d' % (self.alias_prefix, len(self.alias_map) + 1) - self.alias_map[alias] = [table_name, 1, None] + self.alias_map[alias] = [table_name, 1, None, False] self.table_map.setdefault(table_name, []).append(alias) self.tables.append(alias) return alias, True @@ -435,7 +438,8 @@ class Query(object): self.alias_map[alias][ALIAS_JOIN][JOIN_TYPE] = self.LOUTER def join(self, (lhs, table, lhs_col, col), always_create=False, - exclusions=(), promote=False, outer_if_first=False): + exclusions=(), promote=False, outer_if_first=False, + merge_separate=False): """ 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 @@ -456,6 +460,10 @@ class Query(object): 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 the 'merge_separate' parameter is True, we create a new alias if we + would otherwise reuse an alias that also had 'merge_separate' set to + True when it was created. """ if lhs not in self.alias_map: lhs_table = lhs @@ -467,7 +475,9 @@ class Query(object): aliases = self.join_map.get(t_ident) if aliases and not always_create: for alias in aliases: - if alias not in exclusions: + if (alias not in exclusions and + not (merge_separate and + self.alias_map[alias][ALIAS_MERGE_SEP])): self.ref_alias(alias) if promote: self.alias_map[alias][ALIAS_JOIN][JOIN_TYPE] = \ @@ -487,6 +497,7 @@ class Query(object): # means the later columns are ignored. join[JOIN_TYPE] = None self.alias_map[alias][ALIAS_JOIN] = join + self.alias_map[alias][ALIAS_MERGE_SEP] = merge_separate self.join_map.setdefault(t_ident, []).append(alias) self.rev_join_map[alias] = t_ident return alias @@ -647,7 +658,7 @@ class Query(object): opts.pk.column, field.m2m_column_name()), dupe_multis) far_alias = self.join((int_alias, remote_opts.db_table, field.m2m_reverse_name(), remote_opts.pk.column), - dupe_multis) + dupe_multis, merge_separate=True) return ([int_alias, far_alias], remote_opts, field, remote_opts.pk, None) @@ -661,7 +672,7 @@ class Query(object): opts.pk.column, field.m2m_reverse_name()), dupe_multis) far_alias = self.join((int_alias, remote_opts.db_table, field.m2m_column_name(), remote_opts.pk.column), - dupe_multis) + dupe_multis, merge_separate=True) # XXX: Why is the final component able to be None here? return ([int_alias, far_alias], remote_opts, field, remote_opts.pk, None) @@ -673,7 +684,8 @@ class Query(object): field = field.field local_field = opts.get_field(field.rel.field_name) alias = self.join((root_alias, remote_opts.db_table, - local_field.column, field.column), dupe_multis) + local_field.column, field.column), dupe_multis, + merge_separate=True) return ([alias], remote_opts, field, field, remote_opts.pk.column) diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py index a2fc5d18d2..689304d57a 100644 --- a/tests/regressiontests/queries/models.py +++ b/tests/regressiontests/queries/models.py @@ -81,11 +81,8 @@ Bug #1801 [] >>> Author.objects.filter(item=i3) [] - -# FIXME: When we join these queries, we MUST NOT share the table joins. this is -# the case for all m-to-m and 1-to-m joins (but m-to-1 is fine). -# >>> Author.objects.filter(item=i2) & Author.objects.filter(item=i3) -# [] +>>> Author.objects.filter(item=i2) & Author.objects.filter(item=i3) +[] Bug #2306 Checking that no join types are "left outer" joins.