From f9a33e3c3f4cfbff60522cd54455e78c96a9d2a4 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Sun, 2 Dec 2018 22:17:32 +0000 Subject: [PATCH] Fixed #29932 -- Fixed combining compound queries with sub-compound queries on SQLite and Oracle. --- django/db/backends/base/features.py | 1 + django/db/backends/sqlite3/features.py | 1 + django/db/models/sql/compiler.py | 12 +++++++++++- tests/queries/test_qs_combinators.py | 6 ++++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py index 56f7d4fb6a..9787901b07 100644 --- a/django/db/backends/base/features.py +++ b/django/db/backends/base/features.py @@ -226,6 +226,7 @@ class BaseDatabaseFeatures: supports_select_intersection = True supports_select_difference = True supports_slicing_ordering_in_compound = False + supports_parentheses_in_compound = True # Does the database support SQL 2003 FILTER (WHERE ...) in aggregate # expressions? diff --git a/django/db/backends/sqlite3/features.py b/django/db/backends/sqlite3/features.py index d9ee9c85b8..aa24024f91 100644 --- a/django/db/backends/sqlite3/features.py +++ b/django/db/backends/sqlite3/features.py @@ -37,6 +37,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): supports_partial_indexes = Database.version_info >= (3, 8, 0) # Is "ALTER TABLE ... RENAME COLUMN" supported? can_alter_table_rename_column = Database.sqlite_version_info >= (3, 25, 0) + supports_parentheses_in_compound = False @cached_property def supports_stddev(self): diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index aa9ffc7b0e..30fb1418ab 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -429,7 +429,17 @@ class SQLCompiler: *self.query.values_select, *self.query.annotation_select, )) - parts += (compiler.as_sql(),) + part_sql, part_args = compiler.as_sql() + if compiler.query.combinator: + # Wrap in a subquery if wrapping in parentheses isn't + # supported. + if not features.supports_parentheses_in_compound: + part_sql = 'SELECT * FROM ({})'.format(part_sql) + # Add parentheses when combining with compound query if not + # already added for all compound queries. + elif not features.supports_slicing_ordering_in_compound: + part_sql = '({})'.format(part_sql) + parts += ((part_sql, part_args),) except EmptyResultSet: # Omit the empty queryset with UNION and with DIFFERENCE if the # first queryset is nonempty. diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index f1785dd783..8a928ba91f 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -214,3 +214,9 @@ class QuerySetSetOperationTests(TestCase): list(qs1.union(qs2).order_by('num')) # switched order, now 'exists' again: list(qs2.union(qs1).order_by('num')) + + @skipUnlessDBFeature('supports_select_difference', 'supports_select_intersection') + def test_qs_with_subcompound_qs(self): + qs1 = Number.objects.all() + qs2 = Number.objects.intersection(Number.objects.filter(num__gt=1)) + self.assertEqual(qs1.difference(qs2).count(), 2)