Opened 6 years ago

Closed 6 years ago

#29530 closed Bug (fixed)

Chaining .annotate() on models generates wrong SQL aliases.

Reported by: Volodymyr Owned by: Mariusz Felisiak
Component: Database layer (models, ORM) Version: 2.0
Severity: Normal Keywords: annotate, INNER JOIN, model,
Cc: Mariusz Felisiak 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

I simplified my models code as I could, keeping necessary fields.
So with these models given:

class Station(models.Model):
    pass

class Route(models.Model):
    """ just ties route nodes together """
    pass

class RouteNode(models.Model):
    """
    Class that handles many-to-many relationship between stations and routes.
    """
    route = models.ForeignKey(Route, on_delete=models.CASCADE)
    station = models.ForeignKey(Station, on_delete=models.CASCADE)
    duration = models.DurationField()

class Trip(models.Model):
    route = models.ForeignKey(Route, on_delete=models.DO_NOTHING)

I try to query them like:

Trip.objects.filter(
            route__routenode__station=1
        ).annotate(
            board_station_duration=F('route__routenode__duration'),
        ).filter(
            route__routenode__station=2,
        ).annotate(
            dest_station_duration=F('route__routenode__duration'),
        )

The code above being run on environment with
Django 2.0, Python 3.6.2, DB engine : psycopg2
generates the following SQL, so "board_station_duration" and "dest_station_duration" referencing same field of same routenode record.

SELECT
  -- not-relevant to this ticket fields --
  "routenode"."duration"  AS "board_station_duration",
  "routenode"."duration"  AS "dest_station_duration"
FROM "trip"
  INNER JOIN "route" ON ("trip"."route_id" = "route"."id")
  INNER JOIN "routenode" ON ("route"."id" = "routenode"."route_id")
  INNER JOIN "routenode" T5 ON ("route"."id" = T5."route_id")
WHERE ("routenode"."station_id" = 1 AND T5."station_id" = 2);

And the same python code being run on env
Django 1.10, Python 2.7.14, DB engine : psycopg2
generates the following SQL (as expected), so "board_station_duration" and "dest_station_duration" referencing fields of different routenode record.

SELECT
  -- not-relevant to this ticket fields --
  "routenode"."duration"  AS "board_station_duration",
  T5."duration"  AS "dest_station_duration"
FROM "trip"
  INNER JOIN "route" ON ("trip"."route_id" = "route"."id")
  INNER JOIN "routenode" ON ("route"."id" = "routenode"."route_id")
  INNER JOIN "routenode" T5 ON ("route"."id" = T5."route_id")
WHERE ("routenode"."station_id" = 1 AND T5."station_id" = 2);

Attachments (1)

29530.diff (954 bytes ) - added by Mariusz Felisiak 6 years ago.
Regression test.

Download all attachments as: .zip

Change History (11)

comment:1 by Mariusz Felisiak, 6 years ago

Cc: Mariusz Felisiak added

by Mariusz Felisiak, 6 years ago

Attachment: 29530.diff added

Regression test.

comment:2 by Mariusz Felisiak, 6 years ago

Triage Stage: UnreviewedAccepted

I reproduced this issue and prepared regression test in our test suite. I will bisect it to check if that should be marked as a release blocker.

comment:3 by Mariusz Felisiak, 6 years ago

Can you tell me in which exact version that works for you?

comment:4 by Volodymyr, 6 years ago

Django 1.10, Python 2.7.14, DB engine : psycopg2

in reply to:  4 comment:5 by Mariusz Felisiak, 6 years ago

Thanks. It passes on Django 1.10 but only on Python 2.7, it fails on Python 3.6 🤯

Replying to Volodymyr:

Django 1.10, Python 2.7.14, DB engine : psycopg2

comment:6 by Mariusz Felisiak, 6 years ago

Owner: changed from nobody to Mariusz Felisiak
Status: newassigned

comment:7 by Mariusz Felisiak, 6 years ago

Bisected to the 8d66bffbae8e5a230da51c7638d24fdbd327a96b on Python 2.7

commit 8d66bffbae8e5a230da51c7638d24fdbd327a96b
Author: Bo Marchman <bo.marchman@gmail.com>
Date:   Thu Mar 2 09:36:25 2017 -0500

    [1.11.x] Fixed #26522 -- Fixed a nondeterministic AssertionError in QuerySet combining.
    
    Thanks Andrew Brown for the test case.

    Backport of 9bbb6e2d2536c4ac20dc13a94c1f80494e51f8d9 from master

It is related with changing self.alias_map to OrderedDict(). That's why it doesn't work on Python 3.6.

comment:8 by Mariusz Felisiak, 6 years ago

Has patch: set

comment:9 by Tim Graham, 6 years ago

Triage Stage: AcceptedReady for checkin

comment:10 by GitHub <noreply@…>, 6 years ago

Resolution: fixed
Status: assignedclosed

In 0e64e04:

Fixed #29530 -- Fixed aliases ordering when chaining annotate() and filter().

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