Opened 2 days ago
Last modified 5 hours ago
#36065 new Bug
order_by("pk") not equivalent to order_by(F("pk")) for CompositePrimaryKey
Reported by: | Jacob Walls | Owned by: | |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Release blocker | Keywords: | |
Cc: | Csirmaz Bendegúz | Triage Stage: | Accepted |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
For CompositePrimaryKey, order_by("pk")
and order_by(F("pk"))
produce different results.
For "pk"
, direction is distributed to all cols (col1 ASC, col2 ASC)
, but for F("pk")
, it is not: (col1, col2 ASC)
.
Failing test:
-
tests/composite_pk/test_filter.py
diff --git a/tests/composite_pk/test_filter.py b/tests/composite_pk/test_filter.py index 7e361c5925..81bbfc65be 100644
a b 1 from django.db.models import F 1 2 from django.test import TestCase 2 3 3 4 from .models import Comment, Tenant, User … … class CompositePKFilterTests(TestCase): 78 79 ), 79 80 ) 80 81 82 def test_order_by_comments_by_pk_asc_f(self): 83 self.assertSequenceEqual( 84 Comment.objects.order_by("pk"), 85 Comment.objects.order_by(F("pk")), 86 ) 87 81 88 def test_filter_comments_by_pk_gt(self): 82 89 c11, c12, c13, c24, c15 = ( 83 90 self.comment_1,
====================================================================== FAIL: test_order_by_comments_by_pk_asc_f (composite_pk.test_filter.CompositePKFilterTests.test_order_by_comments_by_pk_asc_f) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/user/django/tests/composite_pk/test_filter.py", line 83, in test_order_by_comments_by_pk_asc_f self.assertSequenceEqual( AssertionError: Sequences differ: <Quer[123 chars] Comment object ((1, 5))>, <Comment: Comment object ((2, 4))>]> != <Quer[123 chars] Comment object ((1, 5))>, <Comment: Comment object ((2, 4))>]> <QuerySet [<Comment: Comment object ((1, 1))>, <Comment: Comment object ((1, 2))>, <Comment: Comment object ((1, 3))>, <Comment: Comment object ((1, 5))>, <Comment: Comment object ((2, 4))>]> ---------------------------------------------------------------------- (0.000) SELECT "composite_pk_comment"."tenant_id", "composite_pk_comment"."comment_id", "composite_pk_comment"."user_id", "composite_pk_comment"."text" FROM "composite_pk_comment" ORDER BY "composite_pk_comment"."tenant_id" ASC, "composite_pk_comment"."comment_id" ASC; args=(); ALIAS=DEFAULT (0.000) SELECT "composite_pk_comment"."tenant_id", "composite_pk_comment"."comment_id", "composite_pk_comment"."user_id", "composite_pk_comment"."text" FROM "composite_pk_comment" ORDER BY "composite_pk_comment"."tenant_id", "composite_pk_comment"."comment_id" ASC; args=(); ALIAS=DEFAULT ---------------------------------------------------------------------- Ran 103 tests in 0.107s FAILED (failures=1)
Change History (2)
comment:1 by , 13 hours ago
Cc: | added |
---|---|
Triage Stage: | Unreviewed → Accepted |
comment:2 by , 5 hours ago
The changes made to SQLCompiler.find_ordering_name
to accommodate for order_by(*: str)
references to a composite primary key failed to account for the fact that resolvable and direct OrderBy
instance can be passed to QuerySet.order_by
.
The solution likely lies in adjusting find_ordering_name
to create a ColPairs
when there are multiple targets (instead of calling composite.unnest
) and adjusting OrderBy.as_sql
to properly deal with ColPairs
. I guess we should test for F("pk").asc(nulls_first=True)
and F("pk").asc(nulls_last=True)
(even if primary keys cannot contain nulls) while we're at it to ensure the proper NULLS LAST
emulation is working correction.
-
django/db/models/expressions.py
diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 667e9f93c6..2e1828ff9b 100644
a b def get_source_expressions(self): 1850 1850 return [self.expression] 1851 1851 1852 1852 def as_sql(self, compiler, connection, template=None, **extra_context): 1853 if isinstance(self.expression, ColPairs): 1854 sql_parts = [] 1855 params = [] 1856 for col in self.expression.get_cols(): 1857 copy = self.copy() 1858 copy.set_source_expressions([col]) 1859 sql, col_params = compiler.compile(copy) 1860 sql_parts.append(sql) 1861 params.extend(col_params) 1862 return ", ".join(sql_parts), params 1853 1863 template = template or self.template 1854 1864 if connection.features.supports_order_by_nulls_modifier: 1855 1865 if self.nulls_last: -
django/db/models/sql/compiler.py
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 5bb491d823..251cc08e51 100644
a b def find_ordering_name( 1117 1117 ) 1118 1118 return results 1119 1119 targets, alias, _ = self.query.trim_joins(targets, joins, path) 1120 target_fields = composite.unnest(targets)1121 1120 return [ 1122 1121 (OrderBy(transform_function(t, alias), descending=descending), False) 1123 for t in target _fields1122 for t in targets 1124 1123 ] 1125 1124 1126 1125 def _setup_joins(self, pieces, opts, alias): -
tests/composite_pk/test_filter.py
diff --git a/tests/composite_pk/test_filter.py b/tests/composite_pk/test_filter.py index 7e361c5925..be0e80a518 100644
a b 1 from django.db.models import F 1 2 from django.test import TestCase 2 3 3 4 from .models import Comment, Tenant, User … … def test_order_comments_by_pk_desc(self): 78 79 ), 79 80 ) 80 81 82 def test_order_comments_by_pk_asc_f(self): 83 self.assertQuerySetEqual( 84 Comment.objects.order_by("pk"), 85 Comment.objects.order_by(F("pk")), 86 ) 87 81 88 def test_filter_comments_by_pk_gt(self): 82 89 c11, c12, c13, c24, c15 = ( 83 90 self.comment_1
Note that we'll need to mark OrderBy.allows_composite_expressions = True
per the work on #36042 depending on the order in which the changes are merged.
Thank you!