summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-12-05 16:11:59 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2022-12-07 17:35:26 -0500
commit66c6b8558a6b64820b790199816acc66deffdacc (patch)
tree0e1c929fa8615d5042fad2a402c941b40d10b619 /lib/sqlalchemy/sql
parent59f5beff1928e752b33d65a541cd68295ae0a5f1 (diff)
downloadsqlalchemy-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.py51
-rw-r--r--lib/sqlalchemy/sql/visitors.py13
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]: