diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-12-05 16:11:59 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-12-07 17:35:26 -0500 |
commit | 66c6b8558a6b64820b790199816acc66deffdacc (patch) | |
tree | 0e1c929fa8615d5042fad2a402c941b40d10b619 /lib/sqlalchemy/sql | |
parent | 59f5beff1928e752b33d65a541cd68295ae0a5f1 (diff) | |
download | sqlalchemy-66c6b8558a6b64820b790199816acc66deffdacc.tar.gz |
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
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r-- | lib/sqlalchemy/sql/util.py | 51 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/visitors.py | 13 |
2 files changed, 40 insertions, 24 deletions
diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index d162428ec..bef4372ec 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -12,6 +12,7 @@ from __future__ import annotations from collections import deque +import copy from itertools import chain import typing from typing import AbstractSet @@ -1064,6 +1065,16 @@ class ClauseAdapter(visitors.ReplacingExternalTraversal): """ + __slots__ = ( + "__traverse_options__", + "selectable", + "include_fn", + "exclude_fn", + "equivalents", + "adapt_on_names", + "adapt_from_selectables", + ) + def __init__( self, selectable: Selectable, @@ -1136,6 +1147,11 @@ class ClauseAdapter(visitors.ReplacingExternalTraversal): # TODO: cython candidate + if self.include_fn and not self.include_fn(col): # type: ignore + return None + elif self.exclude_fn and self.exclude_fn(col): # type: ignore + return None + if isinstance(col, FromClause) and not isinstance( col, functions.FunctionElement ): @@ -1173,6 +1189,7 @@ class ClauseAdapter(visitors.ReplacingExternalTraversal): # however the logic to check this moved here as of #7154 so that # it is made specific to SQL rewriting and not all column # correspondence + return None if "adapt_column" in col._annotations: @@ -1191,14 +1208,9 @@ class ClauseAdapter(visitors.ReplacingExternalTraversal): if TYPE_CHECKING: assert isinstance(col, KeyedColumnElement) - if self.include_fn and not self.include_fn(col): - return None - elif self.exclude_fn and self.exclude_fn(col): - return None - else: - return self._corresponding_column( # type: ignore - col, require_embedded=True - ) + return self._corresponding_column( # type: ignore + col, require_embedded=True + ) class _ColumnLookup(Protocol): @@ -1253,6 +1265,14 @@ class ColumnAdapter(ClauseAdapter): """ + __slots__ = ( + "columns", + "adapt_required", + "allow_label_resolve", + "_wrap", + "__weakref__", + ) + columns: _ColumnLookup def __init__( @@ -1267,8 +1287,7 @@ class ColumnAdapter(ClauseAdapter): anonymize_labels: bool = False, adapt_from_selectables: Optional[AbstractSet[FromClause]] = None, ): - ClauseAdapter.__init__( - self, + super().__init__( selectable, equivalents, include_fn=include_fn, @@ -1301,8 +1320,7 @@ class ColumnAdapter(ClauseAdapter): return self.columns[key] def wrap(self, adapter): - ac = self.__class__.__new__(self.__class__) - ac.__dict__.update(self.__dict__) + ac = copy.copy(self) ac._wrap = adapter ac.columns = util.WeakPopulateDict(ac._locate_col) # type: ignore if ac.include_fn or ac.exclude_fn: @@ -1391,15 +1409,6 @@ class ColumnAdapter(ClauseAdapter): return c - def __getstate__(self): - d = self.__dict__.copy() - del d["columns"] - return d - - def __setstate__(self, state): - self.__dict__.update(state) - self.columns = util.WeakPopulateDict(self._locate_col) # type: ignore - def _offset_or_limit_clause( element: Union[int, _ColumnExpressionArgument[int]], diff --git a/lib/sqlalchemy/sql/visitors.py b/lib/sqlalchemy/sql/visitors.py index 737107844..b820cf9d1 100644 --- a/lib/sqlalchemy/sql/visitors.py +++ b/lib/sqlalchemy/sql/visitors.py @@ -656,7 +656,7 @@ class _TraverseTransformCallableType(Protocol[_ET]): _ExtT = TypeVar("_ExtT", bound="ExternalTraversal") -class ExternalTraversal: +class ExternalTraversal(util.MemoizedSlots): """Base class for visitor objects which can traverse externally using the :func:`.visitors.traverse` function. @@ -665,6 +665,8 @@ class ExternalTraversal: """ + __slots__ = ("_visitor_dict", "_next") + __traverse_options__: Dict[str, Any] = {} _next: Optional[ExternalTraversal] @@ -698,8 +700,9 @@ class ExternalTraversal: return traverse(obj, self.__traverse_options__, self._visitor_dict) - @util.memoized_property - def _visitor_dict(self) -> Dict[str, _TraverseCallableType[Any]]: + def _memoized_attr__visitor_dict( + self, + ) -> Dict[str, _TraverseCallableType[Any]]: visitors = {} for name in dir(self): @@ -737,6 +740,8 @@ class CloningExternalTraversal(ExternalTraversal): """ + __slots__ = () + def copy_and_process( self, list_: List[ExternallyTraversible] ) -> List[ExternallyTraversible]: @@ -773,6 +778,8 @@ class ReplacingExternalTraversal(CloningExternalTraversal): """ + __slots__ = () + def replace( self, elem: ExternallyTraversible ) -> Optional[ExternallyTraversible]: |