From 66c6b8558a6b64820b790199816acc66deffdacc Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 5 Dec 2022 16:11:59 -0500 Subject: disable polymorphic adaption in most cases Improved a fix first made in version 1.4 for :ticket:`8456` which scaled back the usage of internal "polymorphic adapters", that are used to render ORM queries when the :paramref:`_orm.Mapper.with_polymorphic` parameter is used. These adapters, which are very complex and error prone, are now used only in those cases where an explicit user-supplied subquery is used for :paramref:`_orm.Mapper.with_polymorphic`, which includes only the use case of concrete inheritance mappings that use the :func:`_orm.polymorphic_union` helper, as well as the legacy use case of using an aliased subquery for joined inheritance mappings, which is not needed in modern use. For the most common case of joined inheritance mappings that use the built-in polymorphic loading scheme, which includes those which make use of the :paramref:`_orm.Mapper.polymorphic_load` parameter set to ``inline``, polymorphic adapters are now no longer used. This has both a positive performance impact on the construction of queries as well as a substantial simplification of the internal query rendering process. The specific issue targeted was to allow a :func:`_orm.column_property` to refer to joined-inheritance classes within a scalar subquery, which now works as intuitively as is feasible. ORM context, mapper, strategies now use ORMAdapter in all cases instead of straight ColumnAdapter; added some more parameters to ORMAdapter to make this possible. ORMAdapter now includes a "trace" enumeration that identifies the use path for the adapter and can aid in debugging. implement __slots__ for the ExternalTraversal hierarchy up to ORMAdapter. Within this change, we have to change the ClauseAdapter.wrap() method, which is only used in one polymorphic codepath, to use copy.copy() instead of `__dict__` access (apparently `__reduce_ex__` is implemented for objects with `__slots__`), and we also remove pickling ability, which should not be needed for adapters (this might have been needed for 1.3 and earlier in order for Query to be picklable, but none of that state is present within Query / select() / etc. anymore). Fixes: #8168 Change-Id: I3f6593eb02ab5e5964807c53a9fa4894c826d017 --- lib/sqlalchemy/orm/context.py | 59 ++++++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 15 deletions(-) (limited to 'lib/sqlalchemy/orm/context.py') diff --git a/lib/sqlalchemy/orm/context.py b/lib/sqlalchemy/orm/context.py index b5b326bca..b3c8e78b3 100644 --- a/lib/sqlalchemy/orm/context.py +++ b/lib/sqlalchemy/orm/context.py @@ -31,9 +31,11 @@ from .interfaces import ORMColumnsClauseRole from .path_registry import PathRegistry from .util import _entity_corresponds_to from .util import _ORMJoin +from .util import _TraceAdaptRole from .util import AliasedClass from .util import Bundle from .util import ORMAdapter +from .util import ORMStatementAdapter from .. import exc as sa_exc from .. import future from .. import inspect @@ -509,7 +511,15 @@ class ORMCompileState(AbstractORMCompileState): def _create_with_polymorphic_adapter(self, ext_info, selectable): """given MapperEntity or ORMColumnEntity, setup polymorphic loading - if appropriate + if called for by the Mapper. + + As of #8168 in 2.0.0b5, polymorphic adapters, which greatly increase + the complexity of the query creation process, are not used at all + except in the quasi-legacy cases of with_polymorphic referring to an + alias and/or subquery. This would apply to concrete polymorphic + loading, and joined inheritance where a subquery is + passed to with_polymorphic (which is completely unnecessary in modern + use). """ if ( @@ -521,7 +531,10 @@ class ORMCompileState(AbstractORMCompileState): self._mapper_loads_polymorphically_with( mp, ORMAdapter( - mp, mp._equivalent_columns, selectable=selectable + _TraceAdaptRole.WITH_POLYMORPHIC_ADAPTER, + mp, + equivalents=mp._equivalent_columns, + selectable=selectable, ), ) @@ -727,8 +740,11 @@ class ORMFromStatementCompileState(ORMCompileState): ) == "orm" ) - self._from_obj_alias = sql.util.ColumnAdapter( - self.statement, adapt_on_names=not statement_is_orm + + self._from_obj_alias = ORMStatementAdapter( + _TraceAdaptRole.ADAPT_FROM_STATEMENT, + self.statement, + adapt_on_names=not statement_is_orm, ) return self @@ -1068,6 +1084,7 @@ class ORMSelectCompileState(ORMCompileState, SelectState): self._join_entities = () if self.compile_options._set_base_alias: + # legacy Query only self._set_select_from_alias() for memoized_entities in query._memoized_select_entities: @@ -1285,6 +1302,7 @@ class ORMSelectCompileState(ORMCompileState, SelectState): return stmt def _set_select_from_alias(self): + """used only for legacy Query cases""" query = self.select_statement # query @@ -1297,6 +1315,8 @@ class ORMSelectCompileState(ORMCompileState, SelectState): self._from_obj_alias = adapter def _get_select_from_alias_from_obj(self, from_obj): + """used only for legacy Query cases""" + info = from_obj if "parententity" in info._annotations: @@ -1313,7 +1333,12 @@ class ORMSelectCompileState(ORMCompileState, SelectState): elif isinstance(info.selectable, sql.selectable.AliasedReturnsRows): equivs = self._all_equivs() - return sql_util.ColumnAdapter(info, equivs) + assert info is info.selectable + return ORMStatementAdapter( + _TraceAdaptRole.LEGACY_SELECT_FROM_ALIAS, + info.selectable, + equivalents=equivs, + ) else: return None @@ -1417,7 +1442,9 @@ class ORMSelectCompileState(ORMCompileState, SelectState): equivs = self._all_equivs() - self.compound_eager_adapter = sql_util.ColumnAdapter(inner, equivs) + self.compound_eager_adapter = ORMStatementAdapter( + _TraceAdaptRole.COMPOUND_EAGER_STATEMENT, inner, equivalents=equivs + ) statement = future.select( *([inner] + self.secondary_columns) # use_labels=self.labels @@ -2128,10 +2155,14 @@ class ORMSelectCompileState(ORMCompileState, SelectState): ) if need_adapter: + # if need_adapter is True, we are in a deprecated case and + # a warning has been emitted. assert right_mapper adapter = ORMAdapter( - inspect(right), equivalents=right_mapper._equivalent_columns + _TraceAdaptRole.DEPRECATED_JOIN_ADAPT_RIGHT_SIDE, + inspect(right), + equivalents=right_mapper._equivalent_columns, ) # if an alias() on the right side was generated, @@ -2142,11 +2173,7 @@ class ORMSelectCompileState(ORMCompileState, SelectState): elif ( not r_info.is_clause_element and not right_is_aliased - and right_mapper.with_polymorphic - and isinstance( - right_mapper._with_polymorphic_selectable, - expression.AliasedReturnsRows, - ) + and right_mapper._has_aliased_polymorphic_fromclause ): # for the case where the target mapper has a with_polymorphic # set up, ensure an adapter is set up for criteria that works @@ -2159,9 +2186,11 @@ class ORMSelectCompileState(ORMCompileState, SelectState): # and similar self._mapper_loads_polymorphically_with( right_mapper, - sql_util.ColumnAdapter( - right_mapper.selectable, - right_mapper._equivalent_columns, + ORMAdapter( + _TraceAdaptRole.WITH_POLYMORPHIC_ADAPTER_RIGHT_JOIN, + right_mapper, + selectable=right_mapper.selectable, + equivalents=right_mapper._equivalent_columns, ), ) # if the onclause is a ClauseElement, adapt it with any -- cgit v1.2.1