diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2023-02-26 09:31:36 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2023-02-27 01:35:26 -0500 |
commit | 40b00498e62d3bf10f75874852bab6d6e0e3a09a (patch) | |
tree | 77c86bd6098758a5391da69088db6008a9a8fae6 /lib/sqlalchemy/orm/mapper.py | |
parent | 8b108297d075ae68178cd18a9cb4d06feee7e075 (diff) | |
download | sqlalchemy-40b00498e62d3bf10f75874852bab6d6e0e3a09a.tar.gz |
include columns from superclasses that indicate "selectin"
Added support for the :paramref:`_orm.Mapper.polymorphic_load` parameter to
be applied to each mapper in an inheritance hierarchy more than one level
deep, allowing columns to load for all classes in the hierarchy that
indicate ``"selectin"`` using a single statement, rather than ignoring
elements on those intermediary classes that nonetheless indicate they also
would participate in ``"selectin"`` loading and were not part of the
base-most SELECT statement.
Fixes: #9373
Change-Id: If8dcba0f0191f6c2818ecd15870bccfdf5ce1112
Diffstat (limited to 'lib/sqlalchemy/orm/mapper.py')
-rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 76 |
1 files changed, 73 insertions, 3 deletions
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index c0ff2ed10..2ae6dadcd 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -3698,6 +3698,65 @@ class Mapper( if m is mapper: break + @HasMemoized.memoized_attribute + def _would_selectinload_combinations_cache(self): + return {} + + def _would_selectin_load_only_from_given_mapper(self, super_mapper): + """return True if this mapper would "selectin" polymorphic load based + on the given super mapper, and not from a setting from a subclass. + + given:: + + class A: + ... + + class B(A): + __mapper_args__ = {"polymorphic_load": "selectin"} + + class C(B): + ... + + class D(B): + __mapper_args__ = {"polymorphic_load": "selectin"} + + ``inspect(C)._would_selectin_load_only_from_given_mapper(inspect(B))`` + returns True, because C does selectin loading because of B's setting. + + OTOH, ``inspect(D) + ._would_selectin_load_only_from_given_mapper(inspect(B))`` + returns False, because D does selectin loading because of its own + setting; when we are doing a selectin poly load from B, we want to + filter out D because it would already have its own selectin poly load + set up separately. + + Added as part of #9373. + + """ + cache = self._would_selectinload_combinations_cache + + try: + return cache[super_mapper] + except KeyError: + pass + + # assert that given object is a supermapper, meaning we already + # strong reference it directly or indirectly. this allows us + # to not worry that we are creating new strongrefs to unrelated + # mappers or other objects. + assert self.isa(super_mapper) + + mapper = super_mapper + for m in self._iterate_to_target_viawpoly(mapper): + if m.polymorphic_load == "selectin": + retval = m is super_mapper + break + else: + retval = False + + cache[super_mapper] = retval + return retval + def _should_selectin_load(self, enabled_via_opt, polymorphic_from): if not enabled_via_opt: # common case, takes place for all polymorphic loads @@ -3721,7 +3780,7 @@ class Mapper( return None @util.preload_module("sqlalchemy.orm.strategy_options") - def _subclass_load_via_in(self, entity): + def _subclass_load_via_in(self, entity, polymorphic_from): """Assemble a that can load the columns local to this subclass as a SELECT with IN. @@ -3739,6 +3798,16 @@ class Mapper( disable_opt = strategy_options.Load(entity) enable_opt = strategy_options.Load(entity) + classes_to_include = {self} + m: Optional[Mapper[Any]] = self.inherits + while ( + m is not None + and m is not polymorphic_from + and m.polymorphic_load == "selectin" + ): + classes_to_include.add(m) + m = m.inherits + for prop in self.attrs: # skip prop keys that are not instrumented on the mapped class. @@ -3747,7 +3816,7 @@ class Mapper( if prop.key not in self.class_manager: continue - if prop.parent is self or prop in keep_props: + if prop.parent in classes_to_include or prop in keep_props: # "enable" options, to turn on the properties that we want to # load by default (subject to options from the query) if not isinstance(prop, StrategizedProperty): @@ -3811,7 +3880,8 @@ class Mapper( @HasMemoized.memoized_attribute def _subclass_load_via_in_mapper(self): - return self._subclass_load_via_in(self) + # the default is loading this mapper against the basemost mapper + return self._subclass_load_via_in(self, self.base_mapper) def cascade_iterator( self, |