As you can see, select_related and annotate both work as expected, but not together. Queries:

In [7]:print(str(qsboth.query))
SELECT "bugrep_charlie"."id","bugrep_charlie"."alfa_id","bugrep_charlie"."bravo_id", COUNT(T4."id") AS "bravo__charlie__count","bugrep_alfa"."id" FROM "bugrep_charlie" INNER JOIN "bugrep_alfa" ON ("bugrep_charlie"."alfa_id"="bugrep_alfa"."id") LEFT OUTER JOIN "bugrep_bravo" ON ("bugrep_charlie"."bravo_id"="bugrep_bravo"."id") LEFT OUTER JOIN "bugrep_charlie" T4 ON ("bugrep_bravo"."id"= T4."bravo_id") GROUP BY "bugrep_charlie"."id","bugrep_charlie"."alfa_id","bugrep_charlie"."bravo_id","bugrep_alfa"."id"
In [8]:print(str(qsselrel.query))
SELECT "bugrep_charlie"."id","bugrep_charlie"."alfa_id","bugrep_charlie"."bravo_id","bugrep_alfa"."id" FROM "bugrep_charlie" LEFT OUTER JOIN "bugrep_alfa" ON ("bugrep_charlie"."alfa_id"="bugrep_alfa"."id")

Using only select_related yields an outer join on the alfa table, while adding annotate in the mix gives an inner join. Indeed, if we make an alfa object, it works fine:

It seems the problem is that setup_joins() arguments have changed. It is called with positional arguments in many places, and as the arguments for setup_joins() change, the flags passed to setup_joins() as positional arguments end up targeting different parameters.

I recommend to go through all uses of setup_joins() in the ORM, and change the calls to use kwarg=someval for all optional arguments. Otherwise further changes will again cause similar regressions.

The patch doesn't need to be backpatched, just checking that correct arguments are passed to setup_joins() is enough. Quickly thinking I am not sure if backpatching could cause further regressions. Seems safe to backpatch, but I can't guarantee it is actually safe to do so.

While working on adding tests for this ticket in master I noticed a couple of possibilities for improved join promotion logic. The PR at ​https://github.com/django/django/pull/1677 can now do join demotion (that is, joins that are promoted to outer joins earlier in the query can be demoted back to inner joins later on), and some cases of annotate() and .values() will create correctly inner joins instead of outer join.

There are a couple of added tests for .annotate() and .values() cases, and a couple of expectedFailures are solved by this patch.

I wonder if the added comment for promote_filter_joins() makes any sense to anybody else than me. If not, please notify me.

I have added comments explaining the changes done to the PR. To me it seems a good way to explain why things are changed the way they are changed...