summaryrefslogtreecommitdiff
path: root/django/db/models/sql
diff options
context:
space:
mode:
Diffstat (limited to 'django/db/models/sql')
-rw-r--r--django/db/models/sql/aggregates.py2
-rw-r--r--django/db/models/sql/compiler.py6
-rw-r--r--django/db/models/sql/query.py105
-rw-r--r--django/db/models/sql/where.py26
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)