﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
37432	QuerySet.select_for_update() after union() and friends is silently ignored	Alex Henman		"`select_for_update()` doesn't raise an error when called after `union()`, `intersection()` or `difference()`. Instead, the locking is silently dropped:
- the generated SQL contains no FOR UPDATE clause, so no rows are locked;
- the ""select_for_update cannot be used outside of a transaction."" `TransactionManagementError` is never raised, because the compiler only performs that check for non-combined queries.

For example, on PostgreSQL:

{{{#!python
from django.db import transaction

qs = Number.objects.filter(num__lt=5)
qs.select_for_update()  # outside a transaction, as expected:
TransactionManagementError: select_for_update cannot be used outside of a transaction.

qs.union(qs).select_for_update()  # no error, query runs
<QuerySet [<Number: 0>, <Number: 4>, <Number: 3>, <Number: 2>, <Number: 1>]>

with transaction.atomic():
    print(qs.union(qs).select_for_update().query)
# NOTE: there's no FOR UPDATE
(SELECT ""queries_number"".""id"" AS ""col1"", ""queries_number"".""num"" AS ""col2"", ""queries_number"".""other_num"" AS ""col3"", ""queries_number"".""another_num"" AS ""col4"" FROM ""queries_number"" WHERE ""queries_number"".""num"" < 5) UNION (SELECT ""queries_number"".""id"" AS ""col1"", ""queries_number"".""num"" AS ""col2"", ""queries_number"".""other_num"" AS ""col3"", ""queries_number"".""another_num"" AS ""col4"" FROM ""queries_number"" WHERE ""queries_number"".""num"" < 5)
}}}

The [https://docs.djangoproject.com/en/dev/ref/models/querysets/#union union() docs] say that only slicing, `count()`, `exists()`, `order_by()`, and `values()`/`values_list()` are allowed on the resulting `QuerySet`. Other unsupported methods already raise `NotSupportedError` (""Calling QuerySet.filter() after union() is not supported."") following #27995, #31148, and #31580, but `select_for_update` was never added to that list. PostgreSQL rejects FOR UPDATE on UNION/INTERSECT/EXCEPT and their inputs, and Oracle and SQLite don’t support it either. So raising `NotSupportedError` like the other methods seems the right fix. I think that’s a one-line `self._not_support_combined_queries(""select_for_update"")` in `QuerySet.select_for_update()`.

Here's an example failing test based on the others in `test_qs_combinators`:
{{{#!python
def test_select_for_update_unsupported_on_combined_qs(self):
    qs = Number.objects.all()
    msg = ""Calling QuerySet.select_for_update() after %s() is not supported.""
    combinators = [""union""]
    if connection.features.supports_select_difference:
        combinators.append(""difference"")
    if connection.features.supports_select_intersection:
        combinators.append(""intersection"")
    for combinator in combinators:
        with self.subTest(combinator=combinator):
            with self.assertRaisesMessage(NotSupportedError, msg % combinator):
                getattr(qs, combinator)(qs).select_for_update()
}}}

I had a hunt for any other methods that might be missing this check and the only one flagged up was the internal `complex_filter`. I'm not really familiar with this method but it would be simple enough to add the same check there if it's considered necessary.

Happy to create the patch as outlined above if that seems like a sensible fix."	Bug	new	Database layer (models, ORM)	dev	Normal				Unreviewed	0	0	0	0	0	0
