diff options
| author | Florian Apolloner <apollo13@users.noreply.github.com> | 2017-01-14 14:32:07 +0100 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2017-01-14 08:32:07 -0500 |
| commit | 84c1826ded17b2d74f66717fb745fc36e37949fd (patch) | |
| tree | 24b9e86375c400e670fd737c2619c013e665704c /django/db/models/sql | |
| parent | 611ef422b173b450b1fc6f7f94eb262961b24e54 (diff) | |
Fixed #27718 -- Added QuerySet.union(), intersection(), difference().
Thanks Mariusz Felisiak for review and Oracle assistance.
Thanks Tim Graham for review and writing docs.
Diffstat (limited to 'django/db/models/sql')
| -rw-r--r-- | django/db/models/sql/compiler.py | 148 | ||||
| -rw-r--r-- | django/db/models/sql/query.py | 8 |
2 files changed, 105 insertions, 51 deletions
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index b197ab90cc..37442c06c4 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -309,6 +309,21 @@ class SQLCompiler(object): seen = set() for expr, is_ref in order_by: + if self.query.combinator: + src = expr.get_source_expressions()[0] + # Relabel order by columns to raw numbers if this is a combined + # query; necessary since the columns can't be referenced by the + # fully qualified name and the simple column names may collide. + for idx, (sel_expr, _, col_alias) in enumerate(self.select): + if is_ref and col_alias == src.refs: + src = src.source + elif col_alias: + continue + if src == sel_expr: + expr.set_source_expressions([RawSQL('%d' % (idx + 1), ())]) + break + else: + raise DatabaseError('ORDER BY term does not match any column in the result set.') resolved = expr.resolve_expression( self.query, allow_joins=True, reuse=None) sql, params = self.compile(resolved) @@ -360,6 +375,30 @@ class SQLCompiler(object): return node.output_field.select_format(self, sql, params) return sql, params + def get_combinator_sql(self, combinator, all): + features = self.connection.features + compilers = [ + query.get_compiler(self.using, self.connection) + for query in self.query.combined_queries + ] + if not features.supports_slicing_ordering_in_compound: + for query, compiler in zip(self.query.combined_queries, compilers): + if query.low_mark or query.high_mark: + raise DatabaseError('LIMIT/OFFSET not allowed in subqueries of compound statements.') + if compiler.get_order_by(): + raise DatabaseError('ORDER BY not allowed in subqueries of compound statements.') + parts = (compiler.as_sql() for compiler in compilers) + combinator_sql = self.connection.ops.set_operators[combinator] + if all and combinator == 'union': + combinator_sql += ' ALL' + braces = '({})' if features.supports_slicing_ordering_in_compound else '{}' + sql_parts, args_parts = zip(*((braces.format(sql), args) for sql, args in parts)) + result = [' {} '.format(combinator_sql).join(sql_parts)] + params = [] + for part in args_parts: + params.extend(part) + return result, params + def as_sql(self, with_limits=True, with_col_aliases=False): """ Creates the SQL for this query. Returns the SQL string and list of @@ -377,69 +416,76 @@ class SQLCompiler(object): # docstring of get_from_clause() for details. from_, f_params = self.get_from_clause() + for_update_part = None where, w_params = self.compile(self.where) if self.where is not None else ("", []) having, h_params = self.compile(self.having) if self.having is not None else ("", []) - params = [] - result = ['SELECT'] - if self.query.distinct: - result.append(self.connection.ops.distinct_sql(distinct_fields)) + combinator = self.query.combinator + features = self.connection.features + if combinator: + if not getattr(features, 'supports_select_{}'.format(combinator)): + raise DatabaseError('{} not supported on this database backend.'.format(combinator)) + result, params = self.get_combinator_sql(combinator, self.query.combinator_all) + else: + result = ['SELECT'] + params = [] - out_cols = [] - col_idx = 1 - for _, (s_sql, s_params), alias in self.select + extra_select: - if alias: - s_sql = '%s AS %s' % (s_sql, self.connection.ops.quote_name(alias)) - elif with_col_aliases: - s_sql = '%s AS %s' % (s_sql, 'Col%d' % col_idx) - col_idx += 1 - params.extend(s_params) - out_cols.append(s_sql) + if self.query.distinct: + result.append(self.connection.ops.distinct_sql(distinct_fields)) - result.append(', '.join(out_cols)) + out_cols = [] + col_idx = 1 + for _, (s_sql, s_params), alias in self.select + extra_select: + if alias: + s_sql = '%s AS %s' % (s_sql, self.connection.ops.quote_name(alias)) + elif with_col_aliases: + s_sql = '%s AS %s' % (s_sql, 'Col%d' % col_idx) + col_idx += 1 + params.extend(s_params) + out_cols.append(s_sql) - result.append('FROM') - result.extend(from_) - params.extend(f_params) + result.append(', '.join(out_cols)) - for_update_part = None - if self.query.select_for_update and self.connection.features.has_select_for_update: - if self.connection.get_autocommit(): - raise TransactionManagementError("select_for_update cannot be used outside of a transaction.") + result.append('FROM') + result.extend(from_) + params.extend(f_params) - nowait = self.query.select_for_update_nowait - skip_locked = self.query.select_for_update_skip_locked - # If it's a NOWAIT/SKIP LOCKED query but the backend doesn't - # support it, raise a DatabaseError to prevent a possible - # deadlock. - if nowait and not self.connection.features.has_select_for_update_nowait: - raise DatabaseError('NOWAIT is not supported on this database backend.') - elif skip_locked and not self.connection.features.has_select_for_update_skip_locked: - raise DatabaseError('SKIP LOCKED is not supported on this database backend.') - for_update_part = self.connection.ops.for_update_sql(nowait=nowait, skip_locked=skip_locked) + if self.query.select_for_update and self.connection.features.has_select_for_update: + if self.connection.get_autocommit(): + raise TransactionManagementError('select_for_update cannot be used outside of a transaction.') - if for_update_part and self.connection.features.for_update_after_from: - result.append(for_update_part) + nowait = self.query.select_for_update_nowait + skip_locked = self.query.select_for_update_skip_locked + # If it's a NOWAIT/SKIP LOCKED query but the backend + # doesn't support it, raise a DatabaseError to prevent a + # possible deadlock. + if nowait and not self.connection.features.has_select_for_update_nowait: + raise DatabaseError('NOWAIT is not supported on this database backend.') + elif skip_locked and not self.connection.features.has_select_for_update_skip_locked: + raise DatabaseError('SKIP LOCKED is not supported on this database backend.') + for_update_part = self.connection.ops.for_update_sql(nowait=nowait, skip_locked=skip_locked) + + if for_update_part and self.connection.features.for_update_after_from: + result.append(for_update_part) - if where: - result.append('WHERE %s' % where) - params.extend(w_params) + if where: + result.append('WHERE %s' % where) + params.extend(w_params) - grouping = [] - for g_sql, g_params in group_by: - grouping.append(g_sql) - params.extend(g_params) - if grouping: - if distinct_fields: - raise NotImplementedError( - "annotate() + distinct(fields) is not implemented.") - if not order_by: - order_by = self.connection.ops.force_no_ordering() - result.append('GROUP BY %s' % ', '.join(grouping)) + grouping = [] + for g_sql, g_params in group_by: + grouping.append(g_sql) + params.extend(g_params) + if grouping: + if distinct_fields: + raise NotImplementedError('annotate() + distinct(fields) is not implemented.') + if not order_by: + order_by = self.connection.ops.force_no_ordering() + result.append('GROUP BY %s' % ', '.join(grouping)) - if having: - result.append('HAVING %s' % having) - params.extend(h_params) + if having: + result.append('HAVING %s' % having) + params.extend(h_params) if order_by: ordering = [] diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 5eea5ad939..16ed92a4d4 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -186,6 +186,11 @@ class Query(object): self.annotation_select_mask = None self._annotation_select_cache = None + # Set combination attributes + self.combinator = None + self.combinator_all = False + self.combined_queries = () + # These are for extensions. The contents are more or less appended # verbatim to the appropriate clause. # The _extra attribute is an OrderedDict, lazily created similarly to @@ -303,6 +308,9 @@ class Query(object): # used. obj._annotation_select_cache = None obj.max_depth = self.max_depth + obj.combinator = self.combinator + obj.combinator_all = self.combinator_all + obj.combined_queries = self.combined_queries obj._extra = self._extra.copy() if self._extra is not None else None if self.extra_select_mask is None: obj.extra_select_mask = None |
