From: Mike Bayer Date: Sat, 16 Nov 2024 20:41:04 +0000 (-0500) Subject: remove _implicit_subquery and all derived functions X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=0cd1104323c51ae7f8e8a48a6e80da7e75290e3b;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git remove _implicit_subquery and all derived functions The ``.c`` and ``.columns`` attributes on the :class:`.Select` and :class:`.TextualSelect` constructs, which are not instances of :class:`.FromClause`, have been removed completely, in addition to the ``.select()`` method as well as other codepaths which would implicitly generate a subquery from a :class:`.Select` without the need to explicitly call the :meth:`.Select.subquery` method. In the case of ``.c`` and ``.columns``, these attributes were never useful in practice and have caused a great deal of confusion, hence were deprecated back in version 1.4, and have emitted warnings since that version. Accessing the columns that are specific to a :class:`.Select` construct is done via the :attr:`.Select.selected_columns` attribute, which was added in version 1.4 to suit the use case that users often expected ``.c`` to accomplish. In the larger sense, implicit production of subqueries works against SQLAlchemy's modern practice of making SQL structure as explicit as possible. Note that this is **not related** to the usual :attr:`.FromClause.c` and :attr:`.FromClause.columns` attributes, common to objects such as :class:`.Table` and :class:`.Subquery`, which are unaffected by this change. Fixes: #10236 Change-Id: If241b8674ccacce7e860bfed25b5d266bfe1aca7 --- diff --git a/doc/build/changelog/unreleased_21/10236.rst b/doc/build/changelog/unreleased_21/10236.rst new file mode 100644 index 0000000000..96e3b51a73 --- /dev/null +++ b/doc/build/changelog/unreleased_21/10236.rst @@ -0,0 +1,30 @@ +.. change:: + :tags: change, sql + :tickets: 10236 + + The ``.c`` and ``.columns`` attributes on the :class:`.Select` and + :class:`.TextualSelect` constructs, which are not instances of + :class:`.FromClause`, have been removed completely, in addition to the + ``.select()`` method as well as other codepaths which would implicitly + generate a subquery from a :class:`.Select` without the need to explicitly + call the :meth:`.Select.subquery` method. + + In the case of ``.c`` and ``.columns``, these attributes were never useful + in practice and have caused a great deal of confusion, hence were + deprecated back in version 1.4, and have emitted warnings since that + version. Accessing the columns that are specific to a :class:`.Select` + construct is done via the :attr:`.Select.selected_columns` attribute, which + was added in version 1.4 to suit the use case that users often expected + ``.c`` to accomplish. In the larger sense, implicit production of + subqueries works against SQLAlchemy's modern practice of making SQL + structure as explicit as possible. + + Note that this is **not related** to the usual :attr:`.FromClause.c` and + :attr:`.FromClause.columns` attributes, common to objects such as + :class:`.Table` and :class:`.Subquery`, which are unaffected by this + change. + + .. seealso:: + + :ref:`change_4617` - original notes from SQLAlchemy 1.4 + diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 1955abb974..a9d7207d5d 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -134,7 +134,7 @@ class ORMEntityColumnsClauseRole(ORMColumnsClauseRole[_T]): _role_name = "ORM mapped or aliased entity" -class ORMFromClauseRole(roles.StrictFromClauseRole): +class ORMFromClauseRole(roles.FromClauseRole): __slots__ = () _role_name = "ORM mapped entity, aliased entity, or FROM expression" diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index b15c6e0513..53d2fa40ea 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -767,7 +767,7 @@ class Mapper( if local_table is not None: self.local_table = coercions.expect( - roles.StrictFromClauseRole, + roles.FromClauseRole, local_table, disable_inspection=True, argname="local_table", @@ -1416,9 +1416,8 @@ class Mapper( self.with_polymorphic = ( self.with_polymorphic[0], coercions.expect( - roles.StrictFromClauseRole, + roles.FromClauseRole, self.with_polymorphic[1], - allow_select=True, ), ) @@ -2918,7 +2917,8 @@ class Mapper( ) -> Tuple[Sequence[Mapper[Any]], FromClause]: if selectable not in (None, False): selectable = coercions.expect( - roles.StrictFromClauseRole, selectable, allow_select=True + roles.FromClauseRole, + selectable, ) if self.with_polymorphic: diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 11936bbce8..fc1cf2b121 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -368,9 +368,8 @@ class Query( ) -> None: fa = [ coercions.expect( - roles.StrictFromClauseRole, + roles.FromClauseRole, elem, - allow_select=True, apply_propagate_attrs=self, ) for elem in obj diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index 0360eb20e8..eb74514d47 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -366,9 +366,7 @@ def polymorphic_union( for key in table_map: table = table_map[key] - table = coercions.expect( - roles.StrictFromClauseRole, table, allow_select=True - ) + table = coercions.expect(roles.FromClauseRole, table) table_map[key] = table m = {} diff --git a/lib/sqlalchemy/sql/coercions.py b/lib/sqlalchemy/sql/coercions.py index 136fc48646..63f9f85529 100644 --- a/lib/sqlalchemy/sql/coercions.py +++ b/lib/sqlalchemy/sql/coercions.py @@ -1273,25 +1273,12 @@ class FromClauseImpl(_SelectIsNotFrom, _NoTextCoercion, RoleImpl): argname: Optional[str] = None, *, explicit_subquery: bool = False, - allow_select: bool = True, **kw: Any, ) -> Any: - if resolved._is_select_base: - if explicit_subquery: - return resolved.subquery() - elif allow_select: - util.warn_deprecated( - "Implicit coercion of SELECT and textual SELECT " - "constructs into FROM clauses is deprecated; please call " - ".subquery() on any Core select or ORM Query object in " - "order to produce a subquery object.", - version="1.4", - ) - return resolved._implicit_subquery - elif resolved._is_text_clause: - return resolved - else: - self._raise_for_expected(element, argname, resolved) + if resolved._is_select_base and explicit_subquery: + return resolved.subquery() + + self._raise_for_expected(element, argname, resolved) def _post_coercion(self, element, *, deannotate=False, **kw): if deannotate: @@ -1300,32 +1287,7 @@ class FromClauseImpl(_SelectIsNotFrom, _NoTextCoercion, RoleImpl): return element -class StrictFromClauseImpl(FromClauseImpl): - __slots__ = () - - def _implicit_coercions( - self, - element: Any, - resolved: Any, - argname: Optional[str] = None, - *, - allow_select: bool = False, - **kw: Any, - ) -> Any: - if resolved._is_select_base and allow_select: - util.warn_deprecated( - "Implicit coercion of SELECT and textual SELECT constructs " - "into FROM clauses is deprecated; please call .subquery() " - "on any Core select or ORM Query object in order to produce a " - "subquery object.", - version="1.4", - ) - return resolved._implicit_subquery - else: - self._raise_for_expected(element, argname, resolved) - - -class AnonymizedFromClauseImpl(StrictFromClauseImpl): +class AnonymizedFromClauseImpl(FromClauseImpl): __slots__ = () def _post_coercion(self, element, *, flat=False, name=None, **kw): diff --git a/lib/sqlalchemy/sql/roles.py b/lib/sqlalchemy/sql/roles.py index ae70ac3a5b..f37398cf61 100644 --- a/lib/sqlalchemy/sql/roles.py +++ b/lib/sqlalchemy/sql/roles.py @@ -215,12 +215,7 @@ class FromClauseRole(ColumnsClauseRole, JoinTargetRole): named_with_column: bool -class StrictFromClauseRole(FromClauseRole): - __slots__ = () - # does not allow text() or select() objects - - -class AnonymizedFromClauseRole(StrictFromClauseRole): +class AnonymizedFromClauseRole(FromClauseRole): __slots__ = () if TYPE_CHECKING: diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index 3fd88739e5..46ed0be334 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -842,7 +842,7 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable): ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]: """A :class:`_expression.ColumnCollection` that represents the "exported" - columns of this :class:`_expression.Selectable`. + columns of this :class:`_expression.FromClause`. The "exported" columns for a :class:`_expression.FromClause` object are synonymous @@ -1751,9 +1751,9 @@ class Alias(roles.DMLTableRole, FromClauseAlias): name: Optional[str] = None, flat: bool = False, ) -> NamedFromClause: - return coercions.expect( - roles.FromClauseRole, selectable, allow_select=True - ).alias(name=name, flat=flat) + return coercions.expect(roles.FromClauseRole, selectable).alias( + name=name, flat=flat + ) class TableValuedAlias(LateralFromClause, Alias): @@ -3485,29 +3485,6 @@ class SelectBase( """ return self.selected_columns.as_readonly() - @property - @util.deprecated( - "1.4", - "The :attr:`_expression.SelectBase.c` and " - ":attr:`_expression.SelectBase.columns` attributes " - "are deprecated and will be removed in a future release; these " - "attributes implicitly create a subquery that should be explicit. " - "Please call :meth:`_expression.SelectBase.subquery` " - "first in order to create " - "a subquery, which then contains this attribute. To access the " - "columns that this SELECT object SELECTs " - "from, use the :attr:`_expression.SelectBase.selected_columns` " - "attribute.", - ) - def c(self) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]: - return self._implicit_subquery.columns - - @property - def columns( - self, - ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]: - return self.c - def get_label_style(self) -> SelectLabelStyle: """ Retrieve the current label style. @@ -3526,22 +3503,6 @@ class SelectBase( raise NotImplementedError() - @util.deprecated( - "1.4", - "The :meth:`_expression.SelectBase.select` method is deprecated " - "and will be removed in a future release; this method implicitly " - "creates a subquery that should be explicit. " - "Please call :meth:`_expression.SelectBase.subquery` " - "first in order to create " - "a subquery, which then can be selected.", - ) - def select(self, *arg: Any, **kw: Any) -> Select[Unpack[TupleAny]]: - return self._implicit_subquery.select(*arg, **kw) - - @HasMemoized.memoized_attribute - def _implicit_subquery(self) -> Subquery: - return self.subquery() - def _scalar_type(self) -> TypeEngine[Any]: raise NotImplementedError() diff --git a/lib/sqlalchemy/testing/suite/__init__.py b/lib/sqlalchemy/testing/suite/__init__.py index a146cb3163..ee4b33b0a2 100644 --- a/lib/sqlalchemy/testing/suite/__init__.py +++ b/lib/sqlalchemy/testing/suite/__init__.py @@ -6,7 +6,6 @@ # the MIT License: https://www.opensource.org/licenses/mit-license.php from .test_cte import * # noqa from .test_ddl import * # noqa -from .test_deprecations import * # noqa from .test_dialect import * # noqa from .test_insert import * # noqa from .test_reflection import * # noqa diff --git a/lib/sqlalchemy/testing/suite/test_deprecations.py b/lib/sqlalchemy/testing/suite/test_deprecations.py deleted file mode 100644 index dc6a71a901..0000000000 --- a/lib/sqlalchemy/testing/suite/test_deprecations.py +++ /dev/null @@ -1,153 +0,0 @@ -# testing/suite/test_deprecations.py -# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors -# -# -# This module is part of SQLAlchemy and is released under -# the MIT License: https://www.opensource.org/licenses/mit-license.php -# mypy: ignore-errors - -from .. import fixtures -from ..assertions import eq_ -from ..schema import Column -from ..schema import Table -from ... import Integer -from ... import select -from ... import testing -from ... import union - - -class DeprecatedCompoundSelectTest(fixtures.TablesTest): - __backend__ = True - - @classmethod - def define_tables(cls, metadata): - Table( - "some_table", - metadata, - Column("id", Integer, primary_key=True), - Column("x", Integer), - Column("y", Integer), - ) - - @classmethod - def insert_data(cls, connection): - connection.execute( - cls.tables.some_table.insert(), - [ - {"id": 1, "x": 1, "y": 2}, - {"id": 2, "x": 2, "y": 3}, - {"id": 3, "x": 3, "y": 4}, - {"id": 4, "x": 4, "y": 5}, - ], - ) - - def _assert_result(self, conn, select, result, params=None): - eq_(conn.execute(select, params).fetchall(), result) - - def test_plain_union(self, connection): - table = self.tables.some_table - s1 = select(table).where(table.c.id == 2) - s2 = select(table).where(table.c.id == 3) - - u1 = union(s1, s2) - with testing.expect_deprecated( - "The SelectBase.c and SelectBase.columns " - "attributes are deprecated" - ): - self._assert_result( - connection, u1.order_by(u1.c.id), [(2, 2, 3), (3, 3, 4)] - ) - - # note we've had to remove one use case entirely, which is this - # one. the Select gets its FROMS from the WHERE clause and the - # columns clause, but not the ORDER BY, which means the old ".c" system - # allowed you to "order_by(s.c.foo)" to get an unnamed column in the - # ORDER BY without adding the SELECT into the FROM and breaking the - # query. Users will have to adjust for this use case if they were doing - # it before. - def _dont_test_select_from_plain_union(self, connection): - table = self.tables.some_table - s1 = select(table).where(table.c.id == 2) - s2 = select(table).where(table.c.id == 3) - - u1 = union(s1, s2).alias().select() - with testing.expect_deprecated( - "The SelectBase.c and SelectBase.columns " - "attributes are deprecated" - ): - self._assert_result( - connection, u1.order_by(u1.c.id), [(2, 2, 3), (3, 3, 4)] - ) - - @testing.requires.order_by_col_from_union - @testing.requires.parens_in_union_contained_select_w_limit_offset - def test_limit_offset_selectable_in_unions(self, connection): - table = self.tables.some_table - s1 = select(table).where(table.c.id == 2).limit(1).order_by(table.c.id) - s2 = select(table).where(table.c.id == 3).limit(1).order_by(table.c.id) - - u1 = union(s1, s2).limit(2) - with testing.expect_deprecated( - "The SelectBase.c and SelectBase.columns " - "attributes are deprecated" - ): - self._assert_result( - connection, u1.order_by(u1.c.id), [(2, 2, 3), (3, 3, 4)] - ) - - @testing.requires.parens_in_union_contained_select_wo_limit_offset - def test_order_by_selectable_in_unions(self, connection): - table = self.tables.some_table - s1 = select(table).where(table.c.id == 2).order_by(table.c.id) - s2 = select(table).where(table.c.id == 3).order_by(table.c.id) - - u1 = union(s1, s2).limit(2) - with testing.expect_deprecated( - "The SelectBase.c and SelectBase.columns " - "attributes are deprecated" - ): - self._assert_result( - connection, u1.order_by(u1.c.id), [(2, 2, 3), (3, 3, 4)] - ) - - def test_distinct_selectable_in_unions(self, connection): - table = self.tables.some_table - s1 = select(table).where(table.c.id == 2).distinct() - s2 = select(table).where(table.c.id == 3).distinct() - - u1 = union(s1, s2).limit(2) - with testing.expect_deprecated( - "The SelectBase.c and SelectBase.columns " - "attributes are deprecated" - ): - self._assert_result( - connection, u1.order_by(u1.c.id), [(2, 2, 3), (3, 3, 4)] - ) - - def test_limit_offset_aliased_selectable_in_unions(self, connection): - table = self.tables.some_table - s1 = ( - select(table) - .where(table.c.id == 2) - .limit(1) - .order_by(table.c.id) - .alias() - .select() - ) - s2 = ( - select(table) - .where(table.c.id == 3) - .limit(1) - .order_by(table.c.id) - .alias() - .select() - ) - - u1 = union(s1, s2).limit(2) - with testing.expect_deprecated( - "The SelectBase.c and SelectBase.columns " - "attributes are deprecated" - ): - self._assert_result( - connection, u1.order_by(u1.c.id), [(2, 2, 3), (3, 3, 4)] - ) diff --git a/test/aaa_profiling/test_memusage.py b/test/aaa_profiling/test_memusage.py index 94629b1416..230832a714 100644 --- a/test/aaa_profiling/test_memusage.py +++ b/test/aaa_profiling/test_memusage.py @@ -1084,62 +1084,6 @@ class MemUsageWBackendTest(fixtures.MappedTest, EnsureZeroed): # in pysqlite itself. background at: # https://thread.gmane.org/gmane.comp.python.db.pysqlite.user/2290 - @testing.crashes("mysql+cymysql", "blocking") - def test_join_cache_deprecated_coercion(self): - metadata = MetaData() - table1 = Table( - "table1", - metadata, - Column( - "id", Integer, primary_key=True, test_needs_autoincrement=True - ), - Column("data", String(30)), - ) - table2 = Table( - "table2", - metadata, - Column( - "id", Integer, primary_key=True, test_needs_autoincrement=True - ), - Column("data", String(30)), - Column("t1id", Integer, ForeignKey("table1.id")), - ) - - class Foo: - pass - - class Bar: - pass - - self.mapper_registry.map_imperatively( - Foo, - table1, - properties={ - "bars": relationship( - self.mapper_registry.map_imperatively(Bar, table2) - ) - }, - ) - metadata.create_all(self.engine) - session = sessionmaker(self.engine) - - @profile_memory() - def go(): - s = table2.select() - sess = session() - with testing.expect_deprecated( - "Implicit coercion of SELECT and textual SELECT constructs", - "An alias is being generated automatically", - assert_=False, - ): - sess.query(Foo).join(s, Foo.bars).all() - sess.rollback() - - try: - go() - finally: - metadata.drop_all(self.engine) - @testing.crashes("mysql+cymysql", "blocking") def test_join_cache(self): metadata = MetaData() diff --git a/test/orm/test_deprecations.py b/test/orm/test_deprecations.py index 9721c96dca..81d0d926f5 100644 --- a/test/orm/test_deprecations.py +++ b/test/orm/test_deprecations.py @@ -46,7 +46,6 @@ from sqlalchemy.orm import undefer from sqlalchemy.orm import with_parent from sqlalchemy.orm import with_polymorphic from sqlalchemy.orm.collections import collection -from sqlalchemy.orm.util import polymorphic_union from sqlalchemy.testing import assert_raises_message from sqlalchemy.testing import assertions from sqlalchemy.testing import AssertsCompiledSQL @@ -56,7 +55,6 @@ from sqlalchemy.testing import expect_deprecated from sqlalchemy.testing import expect_raises_message from sqlalchemy.testing import fixtures from sqlalchemy.testing import is_ -from sqlalchemy.testing import is_true from sqlalchemy.testing import mock from sqlalchemy.testing.entities import ComparableEntity from sqlalchemy.testing.fixtures import CacheKeyFixture @@ -494,34 +492,6 @@ class DeprecatedQueryTest(_fixtures.FixtureTest, AssertsCompiledSQL): def setup_mappers(cls): cls._setup_stock_mapping() - @classmethod - def _expect_implicit_subquery(cls): - return assertions.expect_deprecated( - "Implicit coercion of SELECT and textual SELECT constructs into " - r"FROM clauses is deprecated; please call \.subquery\(\) on any " - "Core select or ORM Query object in order to produce a " - "subquery object." - ) - - def test_deprecated_select_coercion_join_target(self): - User = self.classes.User - addresses = self.tables.addresses - - s = addresses.select() - sess = fixture_session() - with testing.expect_deprecated( - "Implicit coercion of SELECT and textual SELECT constructs", - "An alias is being generated automatically against joined entity", - ): - self.assert_compile( - sess.query(User).join(s, User.addresses), - "SELECT users.id AS users_id, users.name AS users_name " - "FROM users JOIN (SELECT addresses.id AS id, " - "addresses.user_id AS user_id, addresses.email_address " - "AS email_address FROM addresses) AS anon_1 " - "ON users.id = anon_1.user_id", - ) - def test_invalid_column(self): User = self.classes.User @@ -570,20 +540,6 @@ class DeprecatedQueryTest(_fixtures.FixtureTest, AssertsCompiledSQL): ): s.query(User).as_scalar() - def test_select_from_q_statement_no_aliasing(self): - User = self.classes.User - sess = fixture_session() - - q = sess.query(User) - with self._expect_implicit_subquery(): - q = sess.query(User).select_from(User, q.statement) - self.assert_compile( - q.filter(User.name == "ed"), - "SELECT users.id AS users_id, users.name AS users_name " - "FROM users, (SELECT users.id AS id, users.name AS name FROM " - "users) AS anon_1 WHERE users.name = :name_1", - ) - def test_apply_labels(self): User = self.classes.User @@ -653,19 +609,6 @@ class LazyLoadOptSpecificityTest(fixtures.DeclarativeMappedTest): self.assert_sql_count(testing.db, go, expected) -class DeprecatedInhTest(_poly_fixtures._Polymorphic): - def test_with_polymorphic(self): - Person = _poly_fixtures.Person - Engineer = _poly_fixtures.Engineer - - with DeprecatedQueryTest._expect_implicit_subquery(): - p_poly = with_polymorphic(Person, [Engineer], select(Person)) - - is_true( - sa.inspect(p_poly).selectable.compare(select(Person).subquery()) - ) - - class DeprecatedMapperTest( fixtures.RemovesEvents, _fixtures.FixtureTest, AssertsCompiledSQL ): @@ -763,26 +706,6 @@ class DeprecatedMapperTest( ): is_(manager.deferred_scalar_loader, myloader) - def test_polymorphic_union_w_select(self): - users, addresses = self.tables.users, self.tables.addresses - - with DeprecatedQueryTest._expect_implicit_subquery(): - dep = polymorphic_union( - {"u": users.select(), "a": addresses.select()}, - "type", - "bcjoin", - ) - - subq_version = polymorphic_union( - { - "u": users.select().subquery(), - "a": addresses.select().subquery(), - }, - "type", - "bcjoin", - ) - is_true(dep.compare(subq_version)) - def test_comparable_column(self): users, User = self.tables.users, self.classes.User diff --git a/test/sql/test_compiler.py b/test/sql/test_compiler.py index 3e8fca59a8..9e5d11bbfd 100644 --- a/test/sql/test_compiler.py +++ b/test/sql/test_compiler.py @@ -264,11 +264,6 @@ class SelectTest(fixtures.TestBase, AssertsCompiledSQL): assert not hasattr(table1.select().subquery().c.myid, "columns") assert not hasattr(table1.alias().c.myid, "columns") assert not hasattr(table1.alias().c.myid, "c") - with testing.expect_deprecated( - "The SelectBase.c and SelectBase.columns attributes are " - "deprecated" - ): - assert hasattr(table1.select(), "c") assert_raises_message( exc.InvalidRequestError, diff --git a/test/sql/test_deprecations.py b/test/sql/test_deprecations.py index 96b636bd05..4cd5c6402a 100644 --- a/test/sql/test_deprecations.py +++ b/test/sql/test_deprecations.py @@ -1,33 +1,21 @@ -from sqlalchemy import alias from sqlalchemy import and_ -from sqlalchemy import bindparam from sqlalchemy import CHAR from sqlalchemy import column from sqlalchemy import exc from sqlalchemy import ForeignKey from sqlalchemy import func from sqlalchemy import Integer -from sqlalchemy import join from sqlalchemy import literal_column from sqlalchemy import MetaData -from sqlalchemy import null from sqlalchemy import or_ from sqlalchemy import schema from sqlalchemy import select from sqlalchemy import Sequence -from sqlalchemy import sql from sqlalchemy import String from sqlalchemy import table from sqlalchemy import testing -from sqlalchemy import text from sqlalchemy.engine import default -from sqlalchemy.sql import coercions -from sqlalchemy.sql import LABEL_STYLE_TABLENAME_PLUS_COL from sqlalchemy.sql import operators -from sqlalchemy.sql import quoted_name -from sqlalchemy.sql import roles -from sqlalchemy.sql import visitors -from sqlalchemy.sql.selectable import SelectStatementGrouping from sqlalchemy.testing import assertions from sqlalchemy.testing import AssertsCompiledSQL from sqlalchemy.testing import config @@ -127,31 +115,6 @@ class SubqueryCoercionsTest(fixtures.TestBase, AssertsCompiledSQL): "myothertable", column("otherid", Integer), column("othername", String) ) - def test_select_of_select(self): - stmt = select(self.table1.c.myid) - - with testing.expect_deprecated( - r"The SelectBase.select\(\) method is deprecated and will be " - "removed" - ): - self.assert_compile( - stmt.select(), - "SELECT anon_1.myid FROM (SELECT mytable.myid AS myid " - "FROM mytable) AS anon_1", - ) - - def test_standalone_alias(self): - with testing.expect_deprecated( - "Implicit coercion of SELECT and textual SELECT constructs" - ): - stmt = alias(select(self.table1.c.myid), "foo") - - self.assert_compile(stmt, "SELECT mytable.myid FROM mytable") - - is_true( - stmt.compare(select(self.table1.c.myid).subquery().alias("foo")) - ) - def test_as_scalar(self): with testing.expect_deprecated( r"The SelectBase.as_scalar\(\) method is deprecated and " @@ -170,64 +133,6 @@ class SubqueryCoercionsTest(fixtures.TestBase, AssertsCompiledSQL): is_true(stmt.compare(select(self.table1.c.myid).scalar_subquery())) - def test_fromclause_subquery(self): - stmt = select(self.table1.c.myid) - with testing.expect_deprecated( - "Implicit coercion of SELECT and textual SELECT constructs " - "into FROM clauses is deprecated" - ): - coerced = coercions.expect( - roles.StrictFromClauseRole, stmt, allow_select=True - ) - - is_true(coerced.compare(stmt.subquery())) - - def test_plain_fromclause_select_to_subquery(self): - with testing.expect_deprecated( - "Implicit coercion of SELECT and textual SELECT " - "constructs into FROM clauses is deprecated;" - ): - element = coercions.expect( - roles.FromClauseRole, - SelectStatementGrouping(select(self.table1)), - ) - is_true( - element.compare( - SelectStatementGrouping(select(self.table1)).subquery() - ) - ) - - def test_functions_select_method_two(self): - expr = func.rows("foo") - with testing.expect_deprecated( - "Implicit coercion of SELECT and textual SELECT constructs " - "into FROM clauses is deprecated" - ): - stmt = select("*").select_from(expr.select()) - self.assert_compile( - stmt, "SELECT * FROM (SELECT rows(:rows_2) AS rows_1) AS anon_1" - ) - - def test_functions_with_cols(self): - users = table( - "users", column("id"), column("name"), column("fullname") - ) - calculate = select(column("q"), column("z"), column("r")).select_from( - func.calculate(bindparam("x", None), bindparam("y", None)) - ) - - with testing.expect_deprecated( - "The SelectBase.c and SelectBase.columns attributes are " - "deprecated and will be removed" - ): - self.assert_compile( - select(users).where(users.c.id > calculate.c.z), - "SELECT users.id, users.name, users.fullname " - "FROM users, (SELECT q, z, r " - "FROM calculate(:x, :y)) AS anon_1 " - "WHERE users.id > anon_1.z", - ) - class LateralSubqueryCoercionsTest(fixtures.TablesTest, AssertsCompiledSQL): __dialect__ = default.DefaultDialect(supports_native_boolean=True) @@ -338,214 +243,6 @@ class SelectableTest(fixtures.TestBase, AssertsCompiledSQL): "ON basefrom.a = joinfrom.a", ) - def test_against_cloned_non_table(self): - # test that corresponding column digs across - # clone boundaries with anonymous labeled elements - col = func.count().label("foo") - sel = select(col) - - sel2 = visitors.ReplacingCloningVisitor().traverse(sel) - with testing.expect_deprecated("The SelectBase.c"): - assert ( - sel2._implicit_subquery.corresponding_column(col) is sel2.c.foo - ) - - sel3 = visitors.ReplacingCloningVisitor().traverse(sel2) - with testing.expect_deprecated("The SelectBase.c"): - assert ( - sel3._implicit_subquery.corresponding_column(col) is sel3.c.foo - ) - - def test_alias_union(self): - # same as testunion, except its an alias of the union - - u = ( - select( - self.table1.c.col1, - self.table1.c.col2, - self.table1.c.col3, - self.table1.c.colx, - null().label("coly"), - ) - .union( - select( - self.table2.c.col1, - self.table2.c.col2, - self.table2.c.col3, - null().label("colx"), - self.table2.c.coly, - ) - ) - .alias("analias") - ) - s1 = self.table1.select().set_label_style( - LABEL_STYLE_TABLENAME_PLUS_COL - ) - s2 = self.table2.select().set_label_style( - LABEL_STYLE_TABLENAME_PLUS_COL - ) - with self._c_deprecated(): - assert u.corresponding_column(s1.c.table1_col2) is u.c.col2 - assert u.corresponding_column(s2.c.table2_col2) is u.c.col2 - assert u.corresponding_column(s2.c.table2_coly) is u.c.coly - assert s2.c.corresponding_column(u.c.coly) is s2.c.table2_coly - - def test_join_against_self_implicit_subquery(self): - jj = select(self.table1.c.col1.label("bar_col1")) - with testing.expect_deprecated( - "The SelectBase.c and SelectBase.columns attributes are " - "deprecated and will be removed", - "Implicit coercion of SELECT", - ): - jjj = join(self.table1, jj, self.table1.c.col1 == jj.c.bar_col1) - - jjj_bar_col1 = jjj.c["%s_bar_col1" % jj._implicit_subquery.name] - assert jjj_bar_col1 is not None - - # test column directly against itself - - assert jjj.corresponding_column(jjj.c.table1_col1) is jjj.c.table1_col1 - with testing.expect_deprecated( - "The SelectBase.c and SelectBase.columns attributes are " - "deprecated and will be removed" - ): - assert jjj.corresponding_column(jj.c.bar_col1) is jjj_bar_col1 - - def test_select_labels(self): - a = self.table1.select().set_label_style( - LABEL_STYLE_TABLENAME_PLUS_COL - ) - j = join(a._implicit_subquery, self.table2) - - criterion = a._implicit_subquery.c.table1_col1 == self.table2.c.col2 - self.assert_(criterion.compare(j.onclause)) - - -class QuoteTest(fixtures.TestBase, AssertsCompiledSQL): - __dialect__ = "default" - - def test_literal_column_label_embedded_select_samename_explicit_quote( - self, - ): - col = sql.literal_column("NEEDS QUOTES").label( - quoted_name("NEEDS QUOTES", True) - ) - - with testing.expect_deprecated( - r"The SelectBase.select\(\) method is deprecated" - ): - self.assert_compile( - select(col).select(), - 'SELECT anon_1."NEEDS QUOTES" FROM ' - '(SELECT NEEDS QUOTES AS "NEEDS QUOTES") AS anon_1', - ) - - def test_literal_column_label_embedded_select_diffname_explicit_quote( - self, - ): - col = sql.literal_column("NEEDS QUOTES").label( - quoted_name("NEEDS QUOTES_", True) - ) - - with testing.expect_deprecated( - r"The SelectBase.select\(\) method is deprecated" - ): - self.assert_compile( - select(col).select(), - 'SELECT anon_1."NEEDS QUOTES_" FROM ' - '(SELECT NEEDS QUOTES AS "NEEDS QUOTES_") AS anon_1', - ) - - def test_literal_column_label_embedded_select_diffname(self): - col = sql.literal_column("NEEDS QUOTES").label("NEEDS QUOTES_") - - with testing.expect_deprecated( - r"The SelectBase.select\(\) method is deprecated" - ): - self.assert_compile( - select(col).select(), - 'SELECT anon_1."NEEDS QUOTES_" FROM (SELECT NEEDS QUOTES AS ' - '"NEEDS QUOTES_") AS anon_1', - ) - - def test_literal_column_label_embedded_select_samename(self): - col = sql.literal_column("NEEDS QUOTES").label("NEEDS QUOTES") - - with testing.expect_deprecated( - r"The SelectBase.select\(\) method is deprecated" - ): - self.assert_compile( - select(col).select(), - 'SELECT anon_1."NEEDS QUOTES" FROM (SELECT NEEDS QUOTES AS ' - '"NEEDS QUOTES") AS anon_1', - ) - - -class TextualSelectTest(fixtures.TestBase, AssertsCompiledSQL): - __dialect__ = "default" - - table1 = table( - "mytable", - column("myid", Integer), - column("name", String), - column("description", String), - ) - - table2 = table( - "myothertable", column("otherid", Integer), column("othername", String) - ) - - def test_basic_subquery_resultmap(self): - table1 = self.table1 - t = text("select id, name from user").columns(id=Integer, name=String) - - with testing.expect_deprecated( - "The SelectBase.c and SelectBase.columns", "Implicit coercion" - ): - stmt = select(table1.c.myid).select_from( - table1.join(t, table1.c.myid == t.c.id) - ) - compiled = stmt.compile() - eq_( - compiled._create_result_map(), - { - "myid": ( - "myid", - (table1.c.myid, "myid", "myid", "mytable_myid"), - table1.c.myid.type, - 0, - ) - }, - ) - - def test_column_collection_ordered(self): - t = text("select a, b, c from foo").columns( - column("a"), column("b"), column("c") - ) - with testing.expect_deprecated( - "The SelectBase.c and SelectBase.columns" - ): - eq_(t.c.keys(), ["a", "b", "c"]) - - def test_column_collection_pos_plus_bykey(self): - # overlapping positional names + type names - t = text("select a, b, c from foo").columns( - column("a"), column("b"), b=Integer, c=String - ) - - with testing.expect_deprecated( - "The SelectBase.c and SelectBase.columns" - ): - eq_(t.c.keys(), ["a", "b", "c"]) - with testing.expect_deprecated( - "The SelectBase.c and SelectBase.columns" - ): - eq_(t.c.b.type._type_affinity, Integer) - with testing.expect_deprecated( - "The SelectBase.c and SelectBase.columns" - ): - eq_(t.c.c.type._type_affinity, String) - class KeyTargetingTest(fixtures.TablesTest): run_inserts = "once" diff --git a/test/sql/test_operators.py b/test/sql/test_operators.py index 8afe091925..1804d02ca9 100644 --- a/test/sql/test_operators.py +++ b/test/sql/test_operators.py @@ -739,16 +739,6 @@ class _CustomComparatorTests: c1 = Column("foo", self._add_override_factory()) self._assert_add_override(c1) - def test_column_proxy(self): - t = Table("t", MetaData(), Column("foo", self._add_override_factory())) - with testing.expect_deprecated( - "The SelectBase.c and SelectBase.columns attributes " - "are deprecated" - ): - proxied = t.select().c.foo - self._assert_add_override(proxied) - self._assert_and_override(proxied) - def test_subquery_proxy(self): t = Table("t", MetaData(), Column("foo", self._add_override_factory())) proxied = t.select().subquery().c.foo diff --git a/test/sql/test_roles.py b/test/sql/test_roles.py index 09e34691e8..1c97dd181d 100644 --- a/test/sql/test_roles.py +++ b/test/sql/test_roles.py @@ -32,6 +32,7 @@ from sqlalchemy.sql.selectable import SelectStatementGrouping from sqlalchemy.testing import assert_raises from sqlalchemy.testing import assert_raises_message from sqlalchemy.testing import AssertsCompiledSQL +from sqlalchemy.testing import expect_raises_message from sqlalchemy.testing import fixtures from sqlalchemy.testing import is_ from sqlalchemy.testing import is_instance_of @@ -262,16 +263,11 @@ class RoleTest(fixtures.TestBase): ) def test_select_is_coerced_into_fromclause_w_deprecation(self): - with testing.expect_deprecated( - "Implicit coercion of SELECT and textual SELECT " - "constructs into FROM clauses is deprecated;" + with testing.expect_raises_message( + exc.ArgumentError, + r"FROM expression, such as a Table or alias\(\) object expected", ): - element = expect( - roles.FromClauseRole, SelectStatementGrouping(select(t)) - ) - is_true( - element.compare(SelectStatementGrouping(select(t)).subquery()) - ) + expect(roles.FromClauseRole, SelectStatementGrouping(select(t))) def test_offset_or_limit_role_only_ints_or_clauseelement(self): assert_raises(ValueError, select(t).limit, "some limit") @@ -310,22 +306,20 @@ class RoleTest(fixtures.TestBase): d1 = DDL("hi") is_(expect(roles.StatementRole, d1), d1) - def test_strict_from_clause_role(self): + def test_from_clause_role(self): stmt = select(t).subquery() is_true( - expect(roles.StrictFromClauseRole, stmt).compare( - select(t).subquery() - ) + expect(roles.FromClauseRole, stmt).compare(select(t).subquery()) ) - def test_strict_from_clause_role_disallow_select(self): + def test_from_clause_role_disallow_select(self): stmt = select(t) assert_raises_message( exc.ArgumentError, r"FROM expression, such as a Table or alias\(\) " "object expected, got .*Select", expect, - roles.StrictFromClauseRole, + roles.FromClauseRole, stmt, ) @@ -402,6 +396,24 @@ class SubqueryCoercionsTest(fixtures.TestBase, AssertsCompiledSQL): coerced = coercions.expect(role, stmt.alias()) is_true(coerced.compare(stmt.scalar_subquery())) + def test_fromclause_subquery(self): + stmt = select(self.table1.c.myid) + with expect_raises_message( + exc.ArgumentError, + r"FROM expression, such as a Table or alias\(\) object expected", + ): + coercions.expect(roles.FromClauseRole, stmt) + + def test_plain_fromclause_select_to_subquery(self): + with expect_raises_message( + exc.ArgumentError, + r"FROM expression, such as a Table or alias\(\) object expected", + ): + coercions.expect( + roles.FromClauseRole, + SelectStatementGrouping(select(self.table1)), + ) + def test_labeled_role(self): stmt = select(self.table1.c.myid) diff --git a/test/sql/test_selectable.py b/test/sql/test_selectable.py index 4a252930a3..c2f07444b8 100644 --- a/test/sql/test_selectable.py +++ b/test/sql/test_selectable.py @@ -1575,24 +1575,6 @@ class SelectableTest( "SELECT table1.col1 AS a FROM table1) AS b) AS c) AS anon_1", ) - def test_self_referential_select_raises(self): - t = table("t", column("x")) - - # this issue is much less likely as subquery() applies a labeling - # style to the select, eliminating the self-referential call unless - # the select already had labeling applied - - s = select(t).set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL) - - with testing.expect_deprecated("The SelectBase.c"): - s.where.non_generative(s, s.c.t_x > 5) - - assert_raises_message( - exc.InvalidRequestError, - r"select\(\) construct refers to itself as a FROM", - s.compile, - ) - def test_unusual_column_elements_text(self): """test that .c excludes text()."""