Fixed #33796 -- Fixed ordered combined queryset crash when used in subquery on PostgreSQL and MySQL.

Thanks Shai Berger for the report.

Regression in 30a0144134.
This commit is contained in:
Mariusz Felisiak 2022-06-24 07:29:58 +02:00
parent 6f63e0ce8e
commit 44ffd8d06f
4 changed files with 51 additions and 0 deletions

View File

@ -175,6 +175,25 @@ class DatabaseFeatures(BaseDatabaseFeatures):
}, },
} }
) )
if (
self.connection.mysql_is_mariadb and self.connection.mysql_version < (10, 4)
) or (
not self.connection.mysql_is_mariadb
and self.connection.mysql_version < (8,)
):
skips.update(
{
"Parenthesized combined queries are not supported on MySQL < 8 and "
"MariaDB < 10.4": {
"queries.test_qs_combinators.QuerySetSetOperationTests."
"test_union_in_subquery",
"queries.test_qs_combinators.QuerySetSetOperationTests."
"test_union_in_subquery_related_outerref",
"queries.test_qs_combinators.QuerySetSetOperationTests."
"test_union_in_with_ordering",
}
}
)
if not self.supports_explain_analyze: if not self.supports_explain_analyze:
skips.update( skips.update(
{ {

View File

@ -116,6 +116,11 @@ class DatabaseFeatures(BaseDatabaseFeatures):
"migrations.test_operations.OperationTests." "migrations.test_operations.OperationTests."
"test_alter_field_pk_fk_db_collation", "test_alter_field_pk_fk_db_collation",
}, },
"Oracle raises an error when a subquery contains unnecessary ORDER BY "
"clause (#32786).": {
"queries.test_qs_combinators.QuerySetSetOperationTests."
"test_union_in_with_ordering",
},
} }
django_test_expected_failures = { django_test_expected_failures = {
# A bug in Django/cx_Oracle with respect to string handling (#23843). # A bug in Django/cx_Oracle with respect to string handling (#23843).

View File

@ -548,6 +548,11 @@ class SQLCompiler:
or not features.supports_slicing_ordering_in_compound or not features.supports_slicing_ordering_in_compound
): ):
part_sql = "({})".format(part_sql) part_sql = "({})".format(part_sql)
elif (
self.query.subquery
and features.supports_slicing_ordering_in_compound
):
part_sql = "({})".format(part_sql)
parts += ((part_sql, part_args),) parts += ((part_sql, part_args),)
except EmptyResultSet: except EmptyResultSet:
# Omit the empty queryset with UNION and with DIFFERENCE if the # Omit the empty queryset with UNION and with DIFFERENCE if the

View File

@ -321,6 +321,28 @@ class QuerySetSetOperationTests(TestCase):
# Combined queries don't mutate. # Combined queries don't mutate.
self.assertCountEqual(qs, ["a1", "a2"]) self.assertCountEqual(qs, ["a1", "a2"])
@skipUnlessDBFeature("supports_slicing_ordering_in_compound")
def test_union_in_with_ordering(self):
qs1 = Number.objects.filter(num__gt=7).order_by("num")
qs2 = Number.objects.filter(num__lt=2).order_by("num")
self.assertNumbersEqual(
Number.objects.exclude(id__in=qs1.union(qs2).values("id")),
[2, 3, 4, 5, 6, 7],
ordered=False,
)
@skipUnlessDBFeature(
"supports_slicing_ordering_in_compound", "allow_sliced_subqueries_with_in"
)
def test_union_in_with_ordering_and_slice(self):
qs1 = Number.objects.filter(num__gt=7).order_by("num")[:1]
qs2 = Number.objects.filter(num__lt=2).order_by("-num")[:1]
self.assertNumbersEqual(
Number.objects.exclude(id__in=qs1.union(qs2).values("id")),
[0, 2, 3, 4, 5, 6, 7, 9],
ordered=False,
)
def test_count_union(self): def test_count_union(self):
qs1 = Number.objects.filter(num__lte=1).values("num") qs1 = Number.objects.filter(num__lte=1).values("num")
qs2 = Number.objects.filter(num__gte=2, num__lte=3).values("num") qs2 = Number.objects.filter(num__gte=2, num__lte=3).values("num")