Skip to content

Coerce aliased Select and CompoundSelect to subquery #12433

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

rgroothuijsen
Copy link

Fixes issue #6274

Description

When aliased() is used on a select statement or compound select statement, it currently throws an error. The proposed solution in #6274 was to instead return a subquery and emit a warning to the user that aliases are not supported for these types of statements.

Checklist

This pull request is:

  • A documentation / typographical / small typing error fix
    • Good to go, no issue or tests are needed
  • A short code fix
    • please include the issue number, and create an issue if none exists, which
      must include a complete example of the issue. one line code fixes without an
      issue and demonstration will not be accepted.
    • Please include: Fixes: #<issue number> in the commit message
    • please include tests. one line code fixes without tests will not be accepted.
  • A new feature implementation
    • please include the issue number, and create an issue if none exists, which must
      include a complete example of how the feature would look.
    • Please include: Fixes: #<issue number> in the commit message
    • please include tests.

Have a nice day!

Copy link
Member

@zzzeek zzzeek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great job. I think we can simplify the fix and also add some more test support, see inline comments

@@ -1015,6 +1016,19 @@ def _alias_factory(
flat: bool = False,
adapt_on_names: bool = False,
) -> Union[AliasedClass[_O], FromClause]:
if isinstance(element, GenerativeSelect):
util.warn(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good start. We already have this warning in coercions itself, and adding "subquery()" is in fact a coercion, so coercions can handle this right now like this:

diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index 48282b2d5..c7bcb35c7 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -41,6 +41,7 @@ from ._typing import insp_is_mapper
 from ._typing import prop_is_relationship
 from .base import _class_to_mapper as _class_to_mapper
 from .base import _MappedAnnotationBase
+from ..sql.selectable import GenerativeSelect
 from .base import _never_set as _never_set  # noqa: F401
 from .base import _none_only_set as _none_only_set  # noqa: F401
 from .base import _none_set as _none_set  # noqa: F401
@@ -1015,7 +1016,11 @@ class AliasedInsp(
         flat: bool = False,
         adapt_on_names: bool = False,
     ) -> Union[AliasedClass[_O], FromClause]:
-        if isinstance(element, FromClause):
+        if isinstance(element, GenerativeSelect):
+            return coercions.expect(
+                roles.FromClauseRole, element, flat=flat, allow_select=True
+            )
+        elif isinstance(element, FromClause):
             if adapt_on_names:
                 raise sa_exc.ArgumentError(
                     "adapt_on_names only applies to ORM elements"

I dont see any need to distinguish between "named" and "not named", if the user wants a specific subquery name they should call .subquery(name) as is the correct API

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't get the exact code above to produce the expected result. It appears a GenerativeSelect can't be automatically coerced to FromClause without first turning it into a subquery.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the coercion itself does the subquery() call. I tested the above patch with the original case at #6274, and if it is not working for an additional case here then I'd want to add that support to coercions, I can do that if you show me the failure

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

somethings weird here because this worked in my main branch and isnt working in this PR, checking

element=select(Point.x).filter(Point.id == 1),
name="point_alias",
)
eq_(q1.name, "point_alias")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think each of these tests should add a simple self.assert_compile(select(q1), " <expected SQL>") statement just as a sanity check

Copy link
Member

@CaselIT CaselIT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer just go to the "raise informative error" step without passing to the deprecation step, since we had an uninformative error already

f"{type(element).__name__} can't be aliased, "
f"coercing to subquery instead."
)
return coercions.expect(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about we instead just raise an informative unsupported error here? This is what other cases like this behave in 2.0 so I don't think it makes much sense to add support for a previously unsupported feature (from by understanding this raised an exception in all cases) and also deprecate it right away

Copy link
Member

@zzzeek zzzeek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK we changed from coercion + warning to raise in #10236. So this PR needs to have a separate version for main vs. 2.0. in main, these test should expect a raise. in 2.0, we need to use expect_deprecated. the tests themselves should be in test/orm/test_deprecated.py.

@CaselIT this is getting complicated do we want to just move to gerrit and straighten it out

@CaselIT
Copy link
Member

CaselIT commented Mar 15, 2025

@CaselIT this is getting complicated do we want to just move to gerrit and straighten it out

it's likely the best option

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants