diff options
Diffstat (limited to 'django/db/models/sql')
| -rw-r--r-- | django/db/models/sql/aggregates.py | 2 | ||||
| -rw-r--r-- | django/db/models/sql/compiler.py | 6 | ||||
| -rw-r--r-- | django/db/models/sql/query.py | 105 | ||||
| -rw-r--r-- | django/db/models/sql/where.py | 26 |
4 files changed, 78 insertions, 61 deletions
diff --git a/django/db/models/sql/aggregates.py b/django/db/models/sql/aggregates.py index 23b79923d1..2bd2b2f76f 100644 --- a/django/db/models/sql/aggregates.py +++ b/django/db/models/sql/aggregates.py @@ -99,7 +99,7 @@ class Count(Aggregate): sql_template = '%(function)s(%(distinct)s%(field)s)' def __init__(self, col, distinct=False, **extra): - super(Count, self).__init__(col, distinct=distinct and 'DISTINCT ' or '', **extra) + super(Count, self).__init__(col, distinct='DISTINCT ' if distinct else '', **extra) class Max(Aggregate): sql_function = 'MAX' diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index bbe310c8c3..0bfd1b38d3 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -729,7 +729,8 @@ class SQLCompiler(object): row = self.resolve_columns(row, fields) if has_aggregate_select: - aggregate_start = len(self.query.extra_select) + len(self.query.select) + loaded_fields = self.query.get_loaded_field_names().get(self.query.model, set()) or self.query.select + aggregate_start = len(self.query.extra_select) + len(loaded_fields) aggregate_end = aggregate_start + len(self.query.aggregate_select) row = tuple(row[:aggregate_start]) + tuple([ self.query.resolve_aggregate(value, aggregate, self.connection) @@ -786,8 +787,7 @@ class SQLCompiler(object): return list(result) return result - def as_subquery_condition(self, alias, columns): - qn = self.quote_name_unless_alias + def as_subquery_condition(self, alias, columns, qn): qn2 = self.connection.ops.quote_name if len(columns) == 1: sql, params = self.as_sql() diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 0a4152587d..154b6bd204 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1422,7 +1422,9 @@ class Query(object): query.clear_ordering(True) # Try to have as simple as possible subquery -> trim leading joins from # the subquery. - trimmed_joins = query.trim_start(names_with_path) + trimmed_prefix, contains_louter = query.trim_start(names_with_path) + query.remove_inherited_models() + # Add extra check to make sure the selected field will not be null # since we are adding a IN <subquery> clause. This prevents the # database from tripping over IN (...,NULL,...) selects and returning @@ -1431,38 +1433,20 @@ class Query(object): alias, col = query.select[0].col query.where.add((Constraint(alias, col, query.select[0].field), 'isnull', False), AND) - # Still make sure that the trimmed parts in the inner query and - # trimmed prefix are in sync. So, use the trimmed_joins to make sure - # as many path elements are in the prefix as there were trimmed joins. - # In addition, convert the path elements back to names so that - # add_filter() can handle them. - trimmed_prefix = [] - paths_in_prefix = trimmed_joins - for name, path in names_with_path: - if paths_in_prefix - len(path) < 0: - break - trimmed_prefix.append(name) - paths_in_prefix -= len(path) - join_field = path[paths_in_prefix].join_field - # TODO: This should be made properly multicolumn - # join aware. It is likely better to not use build_filter - # at all, instead construct joins up to the correct point, - # then construct the needed equality constraint manually, - # or maybe using SubqueryConstraint would work, too. - # The foreign_related_fields attribute is right here, we - # don't ever split joins for direct case. - trimmed_prefix.append( - join_field.field.foreign_related_fields[0].name) - trimmed_prefix = LOOKUP_SEP.join(trimmed_prefix) condition = self.build_filter( ('%s__in' % trimmed_prefix, query), current_negated=True, branch_negated=True, can_reuse=can_reuse) - # Intentionally leave the other alias as blank, if the condition - # refers it, things will break here. - extra_restriction = join_field.get_extra_restriction( - self.where_class, None, [t for t in query.tables if query.alias_refcount[t]][0]) - if extra_restriction: - query.where.add(extra_restriction, 'AND') + if contains_louter: + or_null_condition = self.build_filter( + ('%s__isnull' % trimmed_prefix, True), + current_negated=True, branch_negated=True, can_reuse=can_reuse) + condition.add(or_null_condition, OR) + # Note that the end result will be: + # (outercol NOT IN innerq AND outercol IS NOT NULL) OR outercol IS NULL. + # This might look crazy but due to how IN works, this seems to be + # correct. If the IS NOT NULL check is removed then outercol NOT + # IN will return UNKNOWN. If the IS NULL check is removed, then if + # outercol IS NULL we will not match the row. return condition def set_empty(self): @@ -1821,35 +1805,58 @@ class Query(object): def trim_start(self, names_with_path): """ Trims joins from the start of the join path. The candidates for trim - are the PathInfos in names_with_path structure. Outer joins are not - eligible for removal. Also sets the select column so the start - matches the join. + are the PathInfos in names_with_path structure that are m2m joins. + + Also sets the select column so the start matches the join. + + This method is meant to be used for generating the subquery joins & + cols in split_exclude(). - This method is mostly useful for generating the subquery joins & col - in "WHERE somecol IN (subquery)". This construct is needed by - split_exclude(). + Returns a lookup usable for doing outerq.filter(lookup=self). Returns + also if the joins in the prefix contain a LEFT OUTER join. _""" all_paths = [] for _, paths in names_with_path: all_paths.extend(paths) - direct_join = True + contains_louter = False for pos, path in enumerate(all_paths): + if path.m2m: + break if self.alias_map[self.tables[pos + 1]].join_type == self.LOUTER: - direct_join = False - pos -= 1 + contains_louter = True + self.unref_alias(self.tables[pos]) + # The path.join_field is a Rel, lets get the other side's field + join_field = path.join_field.field + # Build the filter prefix. + trimmed_prefix = [] + paths_in_prefix = pos + for name, path in names_with_path: + if paths_in_prefix - len(path) < 0: break + trimmed_prefix.append(name) + paths_in_prefix -= len(path) + trimmed_prefix.append( + join_field.foreign_related_fields[0].name) + trimmed_prefix = LOOKUP_SEP.join(trimmed_prefix) + # Lets still see if we can trim the first join from the inner query + # (that is, self). We can't do this for LEFT JOINs because we would + # miss those rows that have nothing on the outer side. + if self.alias_map[self.tables[pos + 1]].join_type != self.LOUTER: + select_fields = [r[0] for r in join_field.related_fields] + select_alias = self.tables[pos + 1] self.unref_alias(self.tables[pos]) - if path.direct: - direct_join = not direct_join - join_side = 0 if direct_join else 1 - select_alias = self.tables[pos + 1] - join_field = path.join_field - if hasattr(join_field, 'field'): - join_field = join_field.field - select_fields = [r[join_side] for r in join_field.related_fields] + extra_restriction = join_field.get_extra_restriction( + self.where_class, None, self.tables[pos + 1]) + if extra_restriction: + self.where.add(extra_restriction, AND) + else: + # TODO: It might be possible to trim more joins from the start of the + # inner query if it happens to have a longer join chain containing the + # values in select_fields. Lets punt this one for now. + select_fields = [r[1] for r in join_field.related_fields] + select_alias = self.tables[pos] self.select = [SelectInfo((select_alias, f.column), f) for f in select_fields] - self.remove_inherited_models() - return pos + return trimmed_prefix, contains_louter def is_nullable(self, field): """ diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py index 029226383d..2a342d417a 100644 --- a/django/db/models/sql/where.py +++ b/django/db/models/sql/where.py @@ -174,6 +174,8 @@ class WhereNode(tree.Node): it. """ lvalue, lookup_type, value_annotation, params_or_value = child + field_internal_type = lvalue.field.get_internal_type() if lvalue.field else None + if isinstance(lvalue, Constraint): try: lvalue, params = lvalue.process(lookup_type, params_or_value, connection) @@ -187,7 +189,7 @@ class WhereNode(tree.Node): if isinstance(lvalue, tuple): # A direct database column lookup. - field_sql, field_params = self.sql_for_columns(lvalue, qn, connection), [] + field_sql, field_params = self.sql_for_columns(lvalue, qn, connection, field_internal_type), [] else: # A smart object with an as_sql() method. field_sql, field_params = lvalue.as_sql(qn, connection) @@ -257,7 +259,7 @@ class WhereNode(tree.Node): raise TypeError('Invalid lookup_type: %r' % lookup_type) - def sql_for_columns(self, data, qn, connection): + def sql_for_columns(self, data, qn, connection, internal_type=None): """ Returns the SQL fragment used for the left-hand side of a column constraint (for example, the "T1.foo" portion in the clause @@ -268,7 +270,7 @@ class WhereNode(tree.Node): lhs = '%s.%s' % (qn(table_alias), qn(name)) else: lhs = qn(name) - return connection.ops.field_cast_sql(db_type) % lhs + return connection.ops.field_cast_sql(db_type, internal_type) % lhs def relabel_aliases(self, change_map): """ @@ -397,13 +399,21 @@ class SubqueryConstraint(object): if hasattr(query, 'values'): if query._db and connection.alias != query._db: raise ValueError("Can't do subqueries with queries on different DBs.") - query = query.values(*self.targets).query + # Do not override already existing values. + if not hasattr(query, 'field_names'): + query = query.values(*self.targets) + else: + query = query._clone() + query = query.query query.clear_ordering(True) query_compiler = query.get_compiler(connection=connection) - return query_compiler.as_subquery_condition(self.alias, self.columns) + return query_compiler.as_subquery_condition(self.alias, self.columns, qn) + + def relabel_aliases(self, change_map): + self.alias = change_map.get(self.alias, self.alias) - def relabeled_clone(self, relabels): + def clone(self): return self.__class__( - relabels.get(self.alias, self.alias), - self.columns, self.query_object) + self.alias, self.columns, self.targets, + self.query_object) |
