From 2c71a8d17df6671fa0cde33d643c993a4317536d Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Mon, 4 Dec 2023 13:33:29 +0100 Subject: [PATCH] Include GROUP BY in _should_nest_selectable criteria Fixed bug where usage of joined eager loading would not properly wrap the query inside of a subquery when :meth:`.Query.group_by` were used against the query. When any kind of result-limiting approach is used, such as DISTINCT, LIMIT, OFFSET, joined eager loading embeds the row-limited query inside of a subquery so that the collection results are not impacted. For some reason, the presence of GROUP BY was never included in this criterion, even though it has a similar effect as using DISTINCT. Additionally, the bug would prevent using GROUP BY at all for a joined eager load query for most database platforms which forbid non-aggregated, non-grouped columns from being in the query, as the additional columns for the joined eager load would not be accepted by the database. Fixes: #5065 Change-Id: I9a2ed8196f83297ec38012138d1a5acdf9e88155 --- lib/sqlalchemy/orm/query.py | 3 ++- test/orm/test_eager_relations.py | 40 +++++++++++++++++++++++++++++ test/orm/test_subquery_relations.py | 39 ++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index a17f590..bd0081f 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -3076,7 +3076,8 @@ class Query(object): kwargs = self._select_args return (kwargs.get('limit') is not None or kwargs.get('offset') is not None or - kwargs.get('distinct', False)) + kwargs.get('distinct', False) or + kwargs.get("group_by", False)) def exists(self): """A convenience method that turns a query into an EXISTS subquery diff --git a/test/orm/test_eager_relations.py b/test/orm/test_eager_relations.py index 3c669d9..9aeeca5 100644 --- a/test/orm/test_eager_relations.py +++ b/test/orm/test_eager_relations.py @@ -747,6 +747,46 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL): eq_(self.static.user_address_result, result) self.assert_sql_count(testing.db, go, 1) + def test_group_by_only(self): + # like distinct(), a group_by() has a similar effect so the + # joined eager load needs to subquery for this as well + users, Address, addresses, User = ( + self.tables.users, + self.classes.Address, + self.tables.addresses, + self.classes.User, + ) + + mapper( + User, + users, + properties={ + "addresses": relationship( + mapper(Address, addresses), + lazy="joined", + order_by=addresses.c.email_address, + ) + }, + ) + + q = create_session().query(User) + eq_( + [ + User(id=7, addresses=[Address(id=1)]), + User( + id=8, + addresses=[ + Address(id=3, email_address="ed@bettyboop.com"), + Address(id=4, email_address="ed@lala.com"), + Address(id=2, email_address="ed@wood.com"), + ], + ), + User(id=9, addresses=[Address(id=5)]), + User(id=10, addresses=[]), + ], + q.order_by(User.id).group_by(User).all(), # group by all columns + ) + def test_limit_2(self): keywords, items, item_keywords, Keyword, Item = ( self.tables.keywords, diff --git a/test/orm/test_subquery_relations.py b/test/orm/test_subquery_relations.py index 606beb5..aef9c34 100644 --- a/test/orm/test_subquery_relations.py +++ b/test/orm/test_subquery_relations.py @@ -754,6 +754,45 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL): result = q.limit(2).all() eq_(result, list(reversed(self.static.user_address_result[2:4]))) + def test_group_by_only(self): + # test group_by() not impacting results, similarly to joinedload + users, Address, addresses, User = ( + self.tables.users, + self.classes.Address, + self.tables.addresses, + self.classes.User, + ) + + mapper( + User, + users, + properties={ + "addresses": relationship( + mapper(Address, addresses), + lazy="subquery", + order_by=addresses.c.email_address, + ) + }, + ) + + q = create_session().query(User) + eq_( + [ + User(id=7, addresses=[Address(id=1)]), + User( + id=8, + addresses=[ + Address(id=3, email_address="ed@bettyboop.com"), + Address(id=4, email_address="ed@lala.com"), + Address(id=2, email_address="ed@wood.com"), + ], + ), + User(id=9, addresses=[Address(id=5)]), + User(id=10, addresses=[]), + ], + q.order_by(User.id).group_by(User).all(), # group by all columns + ) + def test_one_to_many_scalar(self): Address, addresses, users, User = (self.classes.Address, self.tables.addresses, -- 2.43.0