Opened 2 years ago

Closed 2 years ago

#34123 closed Bug (fixed)

Ambiguous aliases in ordering on combined queries with select_related().

Reported by: Shai Berger Owned by: David Sanders
Component: Database layer (models, ORM) Version: dev
Severity: Release blocker Keywords: select_related
Cc: Simon Charette, David Sanders Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Ordering on combined queries with select_related() seems to have been slightly broken in [c58a8acd413ccc].

Consider these tests:

    # Model Author has  `ordering = ["name"]` in its Meta

    def test_union_with_select_related_and_order(self):
        base_qs = Author.objects.select_related('extra').order_by()
        qs1 = base_qs.filter(name="a1")
        qs2 = base_qs.filter(name="a2")
        self.assertFalse(
            bool(
                qs1.union(qs2).order_by("pk")
            )
        )

    # first() also calls order_by("pk")

    def test_union_with_select_related_and_first(self):
        base_qs = Author.objects.select_related('extra')
        qs1 = base_qs.filter(name="a1")
        qs2 = base_qs.filter(name="a2")
        self.assertFalse(
            bool(
                qs1.union(qs2).first()
            )
        )

On Postrgres, both of these pass before the named commit, and error after it with

django.db.utils.ProgrammingError: ORDER BY "id" is ambiguous
LINE 1: ...id") WHERE "queries_author"."name" = 'a2') ORDER BY "id" ASC

On Sqlite, the second test (where ordering from the model is in force) breaks even before this, with

django.db.utils.DatabaseError: ORDER BY not allowed in subqueries of compound statements.

but on current main, the first breaks too -- this time, with

django.db.utils.OperationalError: 1st ORDER BY term does not match any column in the result set

Note that on union queries without select_related(), ordering on pk works -- many existing tests use it, as well as another test I added (in the attached diff) just to be sure.

Attachments (1)

union-select-related-tests.diff (1.4 KB ) - added by Shai Berger 2 years ago.
Tests to show and investigate the regression

Download all attachments as: .zip

Change History (12)

by Shai Berger, 2 years ago

Tests to show and investigate the regression

comment:1 by Mariusz Felisiak, 2 years ago

Summary: Regression: Ordering on combined queries with select_relatedAmbiguous aliases in ordering on combined queries with select_related().
Triage Stage: UnreviewedAccepted

Thanks for the report!

Regression in c58a8acd413ccc992dd30afd98ed900897e1f719.

comment:2 by David Sanders, 2 years ago

Cc: David Sanders added

Interesting read following the thread back.

I'm guessing reverting to using column numbers is out of the question, is the solution here to simply alias the columns c1, c2, c3 … etc. You only need to alias the first query in the union and the others follow suite.

comment:3 by Simon Charette, 2 years ago

Making sure the inner queries are generated with aliases for each columns, which is something we already have the machinery for, should be enough to address the issue.

We just have to make sure the queries are compiled by passing with_col_aliases=True to their compiler's as_sql, something like

  • django/db/models/sql/compiler.py

    diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
    index 3097500be4..8b727f42ce 100644
    a b def get_combinator_sql(self, combinator, all):  
    563563                            *self.query.annotation_select,
    564564                        )
    565565                    )
    566                 part_sql, part_args = compiler.as_sql()
     566                part_sql, part_args = compiler.as_sql(with_col_aliases=True)
    567567                if compiler.query.combinator:
    568568                    # Wrap in a subquery if wrapping in parentheses isn't
    569569                    # supported

comment:4 by David Sanders, 2 years ago

I was going to take a look when I get bored with $job but it sounds like you already have it figured out xD

If you're too busy I can look though…

comment:5 by Simon Charette, 2 years ago

David, I'd appreciate if you could have a look.

I'm pretty convinced the solution lies in using with_col_aliases=True and as long as the ordering clause is adjusted to refer to the column alias of the left-most query (the one combining the others) then it should work.

From some initial testing it might require a bit of adjustments in SQLCompiler.get_order_by and as_sql when self.pre_sql_setup is called so with_col_aliases=with_col_aliases or bool(self.query.combinator).

Last edited 2 years ago by Simon Charette (previous) (diff)

comment:6 by David Sanders, 2 years ago

Owner: changed from nobody to David Sanders
Status: newassigned

👍

comment:7 by David Sanders, 2 years ago

Just FYI re:

django.db.utils.DatabaseError: ORDER BY not allowed in subqueries of compound statements.

SQLite doesn't support limits & ordering in compound statments: https://www.sqlite.org/lang_select.html#compound_select_statements

The error we're seeing here is a Django feature flag checking the presence of an order by. The Author model in the test has a default ordering which is causing this.

comment:8 by Simon Charette, 2 years ago

I figured the origin of the 1st ORDER BY term does not match any column in the result set error; it's another regression introduced by c58a8acd413ccc992dd30afd98ed900897e1f719 but solely on SQLite 3.28 and 3.29 which should be fixed by this PR.

The select_related issue remains though. Any help required David?

Last edited 2 years ago by Simon Charette (previous) (diff)

comment:9 by David Sanders, 2 years ago

Simon, correct the 1st ORDER BY term does not match any column in the result set on SQLite also happens because of the ambiguous reference in ORDER BY id from first() when using select_related().

I'm currently trying to determine a better way to determine an order by match rather than relying on aliases like is currently being done: https://github.com/django/django/blob/main/django/db/models/sql/compiler.py#L464-L467

(because if selects are always aliases then this is no longer something that can be relied on)

Version 0, edited 2 years ago by David Sanders (next)

comment:10 by Mariusz Felisiak, 2 years ago

Has patch: set
Triage Stage: AcceptedReady for checkin

comment:11 by Mariusz Felisiak <felisiak.mariusz@…>, 2 years ago

Resolution: fixed
Status: assignedclosed

In 70499b2:

Fixed #34123 -- Fixed combinator order by alias when using select_related().

Regression in c58a8acd413ccc992dd30afd98ed900897e1f719.

Thanks to Shai Berger for the report and tests.

Co-Authored-By: David Sanders <shang.xiao.sanders@…>

Note: See TracTickets for help on using tickets.
Back to Top