diff options
| author | Tim Graham <timograham@gmail.com> | 2016-10-28 11:20:23 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2016-10-28 11:20:23 -0400 |
| commit | 1bc249c2a67c24fcd28436c85388eff1d826e305 (patch) | |
| tree | 429351ccffaed9711a00b8c4d6eae760c72ac453 /django/db/models/sql | |
| parent | 80e742d991b276023a3aa51a29ed757879051282 (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.py | 28 | ||||
| -rw-r--r-- | django/db/models/sql/query.py | 48 | ||||
| -rw-r--r-- | django/db/models/sql/subqueries.py | 6 | ||||
| -rw-r--r-- | django/db/models/sql/where.py | 16 |
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) |
