Calling :func:`_orm.aliased` against a :func:`_sql.select` or
:func:`_sql.union` / :class:`_sql.CompoundSelect` construct, which
previously failed with an obscure ``AttributeError`` regarding a missing
``.mapper`` attribute, now raises when using SQLAlchemy 2.1, and emits a
deprecation warning under SQLAlchemy 2.0 as it coerces the construct into a
subquery instead. This matches the behavior of other similar implicit
SELECT-to-FROM coercions. Pull request courtesy Rens Groothuijsen.
Fixes: #6274
Closes: #12433
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/12433
Pull-request-sha:
416dde8509ac05b209400603d9e00bc82aa47c79
Change-Id: I63bdec71074b81fb85bf29e6ca0dd81cbe3f8cb3
(cherry picked from commit
16177b8c73e492e5adaad8f51095f9981831da41)
--- /dev/null
+.. change::
+ :tags: bug, orm
+ :tickets: 6274
+
+ Calling :func:`_orm.aliased` against a :func:`_sql.select` or
+ :func:`_sql.union` / :class:`_sql.CompoundSelect` construct, which
+ previously failed with an obscure ``AttributeError`` regarding a missing
+ ``.mapper`` attribute, now raises when using SQLAlchemy 2.1, and emits a
+ deprecation warning under SQLAlchemy 2.0 as it coerces the construct into a
+ subquery instead. This matches the behavior of other similar implicit
+ SELECT-to-FROM coercions. Pull request courtesy Rens Groothuijsen.
from ..sql.elements import ColumnElement
from ..sql.elements import KeyedColumnElement
from ..sql.selectable import FromClause
+from ..sql.selectable import GenerativeSelect
from ..util.langhelpers import MemoizedSlots
from ..util.typing import de_stringify_annotation as _de_stringify_annotation
from ..util.typing import eval_name_only as _eval_name_only
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)
+ elif isinstance(element, FromClause):
if adapt_on_names:
raise sa_exc.ArgumentError(
"adapt_on_names only applies to ORM elements"
from sqlalchemy import String
from sqlalchemy import testing
from sqlalchemy import text
+from sqlalchemy import union
from sqlalchemy.engine import default
from sqlalchemy.engine import result_tuple
from sqlalchemy.orm import aliased
"ON users.id = anon_1.user_id",
)
+ def test_aliased_select_deprecated(self):
+ """test #6274"""
+
+ User = self.classes.User
+
+ with self._expect_implicit_subquery():
+ q1 = aliased(select(User.id, User.name))
+
+ self.assert_compile(
+ select(q1),
+ "SELECT anon_1.id, anon_1.name FROM "
+ "(SELECT users.id AS id, users.name AS name FROM users) AS anon_1",
+ )
+
+ def test_aliased_compound_select_deprecated(self):
+ """test #6274"""
+
+ User = self.classes.User
+
+ s1 = select(User.id)
+ s2 = select(User.name)
+ with self._expect_implicit_subquery():
+ q1 = aliased(union(s1, s2))
+
+ self.assert_compile(
+ select(q1),
+ "SELECT anon_1.id FROM (SELECT users.id AS id FROM users "
+ "UNION SELECT users.name AS name FROM users) AS anon_1",
+ )
+
def test_invalid_column(self):
User = self.classes.User
inspect(Point),
)
+ def test_aliased_select_subquery(self):
+ """test for #6274"""
+
+ class Point:
+ pass
+
+ self._fixture(Point)
+
+ subq = select(Point.x).filter(Point.id == 1).subquery()
+ q1 = aliased(subq, name="point_alias")
+ self.assert_compile(
+ select(q1),
+ "SELECT point_alias.x FROM (SELECT point.x AS x "
+ "FROM point WHERE point.id = :id_1) AS point_alias",
+ )
+
class IdentityKeyTest(_fixtures.FixtureTest):
run_inserts = None