summaryrefslogtreecommitdiff
path: root/django/db/models/sql
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2016-10-28 11:20:23 -0400
committerGitHub <noreply@github.com>2016-10-28 11:20:23 -0400
commit1bc249c2a67c24fcd28436c85388eff1d826e305 (patch)
tree429351ccffaed9711a00b8c4d6eae760c72ac453 /django/db/models/sql
parent80e742d991b276023a3aa51a29ed757879051282 (diff)
Fixed #20939 -- Simplified query generation by converting QuerySet to Query.
Thanks Anssi Kääriäinen for the initial patch and Anssi, Simon Charette, and Josh Smeaton for review.
Diffstat (limited to 'django/db/models/sql')
-rw-r--r--django/db/models/sql/compiler.py28
-rw-r--r--django/db/models/sql/query.py48
-rw-r--r--django/db/models/sql/subqueries.py6
-rw-r--r--django/db/models/sql/where.py16
4 files changed, 41 insertions, 57 deletions
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index 4e317d0371..c7455ecceb 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -13,6 +13,8 @@ from django.db.transaction import TransactionManagementError
from django.db.utils import DatabaseError
from django.utils.six.moves import zip
+FORCE = object()
+
class SQLCompiler(object):
def __init__(self, query, connection, using):
@@ -28,7 +30,6 @@ class SQLCompiler(object):
self.annotation_col_map = None
self.klass_info = None
self.ordering_parts = re.compile(r'(.*)\s(ASC|DESC)(.*)')
- self.subquery = False
def setup_query(self):
if all(self.query.alias_refcount[a] == 0 for a in self.query.tables):
@@ -355,11 +356,11 @@ class SQLCompiler(object):
sql, params = vendor_impl(self, self.connection)
else:
sql, params = node.as_sql(self, self.connection)
- if select_format and not self.subquery:
+ if select_format is FORCE or (select_format and not self.query.subquery):
return node.output_field.select_format(self, sql, params)
return sql, params
- def as_sql(self, with_limits=True, with_col_aliases=False, subquery=False):
+ def as_sql(self, with_limits=True, with_col_aliases=False):
"""
Creates the SQL for this query. Returns the SQL string and list of
parameters.
@@ -367,7 +368,6 @@ class SQLCompiler(object):
If 'with_limits' is False, any limit/offset information is not included
in the query.
"""
- self.subquery = subquery
refcounts_before = self.query.alias_refcount.copy()
try:
extra_select, order_by, group_by = self.pre_sql_setup()
@@ -466,24 +466,6 @@ class SQLCompiler(object):
# Finally do cleanup - get rid of the joins we created above.
self.query.reset_refcounts(refcounts_before)
- def as_nested_sql(self):
- """
- Perform the same functionality as the as_sql() method, returning an
- SQL string and parameters. However, the alias prefixes are bumped
- beforehand (in a copy -- the current query isn't changed), and any
- ordering is removed if the query is unsliced.
-
- Used when nesting this query inside another.
- """
- obj = self.query.clone()
- # It's safe to drop ordering if the queryset isn't using slicing,
- # distinct(*fields) or select_for_update().
- if (obj.low_mark == 0 and obj.high_mark is None and
- not self.query.distinct_fields and
- not self.query.select_for_update):
- obj.clear_ordering(True)
- return obj.get_compiler(connection=self.connection).as_sql(subquery=True)
-
def get_default_columns(self, start_alias=None, opts=None, from_parent=None):
"""
Computes the default columns for selecting every field in the base
@@ -1218,7 +1200,7 @@ class SQLAggregateCompiler(SQLCompiler):
"""
sql, params = [], []
for annotation in self.query.annotation_select.values():
- ann_sql, ann_params = self.compile(annotation, select_format=True)
+ ann_sql, ann_params = self.compile(annotation, select_format=FORCE)
sql.append(ann_sql)
params.extend(ann_params)
self.col_count = len(self.query.annotation_select)
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index f82eca3774..97e90aa082 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -143,6 +143,7 @@ class Query(object):
self.standard_ordering = True
self.used_aliases = set()
self.filter_is_sticky = False
+ self.subquery = False
# SQL-related attributes
# Select and related select clauses are expressions to use in the
@@ -319,6 +320,7 @@ class Query(object):
else:
obj.used_aliases = set()
obj.filter_is_sticky = False
+ obj.subquery = self.subquery
if 'alias_prefix' in self.__dict__:
obj.alias_prefix = self.alias_prefix
if 'subq_aliases' in self.__dict__:
@@ -964,6 +966,9 @@ class Query(object):
self.append_annotation_mask([alias])
self.annotations[alias] = annotation
+ def _prepare_as_filter_value(self):
+ return self.clone()
+
def prepare_lookup_value(self, value, lookups, can_reuse, allow_joins=True):
# Default lookup if none given is exact.
used_joins = []
@@ -974,8 +979,7 @@ class Query(object):
if value is None:
if lookups[-1] not in ('exact', 'iexact'):
raise ValueError("Cannot use None as a query value")
- lookups[-1] = 'isnull'
- value = True
+ return True, ['isnull'], used_joins
elif hasattr(value, 'resolve_expression'):
pre_joins = self.alias_refcount.copy()
value = value.resolve_expression(self, reuse=can_reuse, allow_joins=allow_joins)
@@ -997,11 +1001,8 @@ class Query(object):
# Subqueries need to use a different set of aliases than the
# outer query. Call bump_prefix to change aliases of the inner
# query (the value).
- if hasattr(value, 'query') and hasattr(value.query, 'bump_prefix'):
- value = value._clone()
- value.query.bump_prefix(self)
- if hasattr(value, 'bump_prefix'):
- value = value.clone()
+ if hasattr(value, '_prepare_as_filter_value'):
+ value = value._prepare_as_filter_value()
value.bump_prefix(self)
# For Oracle '' is equivalent to null. The check needs to be done
# at this stage because join promotion can't be done at compiler
@@ -1049,14 +1050,20 @@ class Query(object):
Checks the type of object passed to query relations.
"""
if field.is_relation:
- # QuerySets implement is_compatible_query_object_type() to
- # determine compatibility with the given field.
- if hasattr(value, 'is_compatible_query_object_type'):
- if not value.is_compatible_query_object_type(opts, field):
- raise ValueError(
- 'Cannot use QuerySet for "%s": Use a QuerySet for "%s".' %
- (value.model._meta.object_name, opts.object_name)
- )
+ # Check that the field and the queryset use the same model in a
+ # query like .filter(author=Author.objects.all()). For example, the
+ # opts would be Author's (from the author field) and value.model
+ # would be Author.objects.all() queryset's .model (Author also).
+ # The field is the related field on the lhs side.
+ # If _forced_pk isn't set, this isn't a queryset query or values()
+ # or values_list() was specified by the developer in which case
+ # that choice is trusted.
+ if (getattr(value, '_forced_pk', False) and
+ not check_rel_lookup_compatibility(value.model, opts, field)):
+ raise ValueError(
+ 'Cannot use QuerySet for "%s": Use a QuerySet for "%s".' %
+ (value.model._meta.object_name, opts.object_name)
+ )
elif hasattr(value, '_meta'):
self.check_query_object_type(value, opts, field)
elif hasattr(value, '__iter__'):
@@ -2005,6 +2012,17 @@ class Query(object):
else:
return field.null
+ def as_subquery_filter(self, db):
+ self._db = db
+ self.subquery = True
+ # It's safe to drop ordering if the queryset isn't using slicing,
+ # distinct(*fields) or select_for_update().
+ if (self.low_mark == 0 and self.high_mark is None and
+ not self.distinct_fields and
+ not self.select_for_update):
+ self.clear_ordering(True)
+ return self
+
def get_order_dir(field, default='ASC'):
"""
diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py
index 9cf8ffbdcc..089f04e04e 100644
--- a/django/db/models/sql/subqueries.py
+++ b/django/db/models/sql/subqueries.py
@@ -205,7 +205,5 @@ class AggregateQuery(Query):
compiler = 'SQLAggregateCompiler'
def add_subquery(self, query, using):
- self.subquery, self.sub_params = query.get_compiler(using).as_sql(
- with_col_aliases=True,
- subquery=True,
- )
+ query.subquery = True
+ self.subquery, self.sub_params = query.get_compiler(using).as_sql(with_col_aliases=True)
diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py
index 04e9a7c1bd..df44803b8a 100644
--- a/django/db/models/sql/where.py
+++ b/django/db/models/sql/where.py
@@ -197,20 +197,6 @@ class SubqueryConstraint(object):
def as_sql(self, compiler, connection):
query = self.query_object
-
- # QuerySet was sent
- if hasattr(query, 'values'):
- if query._db and connection.alias != query._db:
- raise ValueError("Can't do subqueries with queries on different DBs.")
- # Do not override already existing values.
- if query._fields is None:
- query = query.values(*self.targets)
- else:
- query = query._clone()
- query = query.query
- if query.can_filter():
- # If there is no slicing in use, then we can safely drop all ordering
- query.clear_ordering(True)
-
+ query.set_values(self.targets)
query_compiler = query.get_compiler(connection=connection)
return query_compiler.as_subquery_condition(self.alias, self.columns, compiler)