Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 88b77c8

Browse files
zzzeekGerrit Code Review
authored andcommitted
Merge "group together with_polymorphic for single inh criteria" into main
2 parents 33be272 + 7fb3ef3 commit 88b77c8

File tree

4 files changed

+424
-30
lines changed

4 files changed

+424
-30
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
.. change::
2+
:tags: bug, orm
3+
:tickets: 12395
4+
5+
The behavior of :func:`_orm.with_polymorphic` when used with a single
6+
inheritance mapping has been changed such that its behavior should match as
7+
closely as possible to that of an equivalent joined inheritance mapping.
8+
Specifically this means that the base class specified in the
9+
:func:`_orm.with_polymorphic` construct will be the basemost class that is
10+
loaded, as well as all descendant classes of that basemost class.
11+
The change includes that the descendant classes named will no longer be
12+
exclusively indicated in "WHERE polymorphic_col IN" criteria; instead, the
13+
whole hierarchy starting with the given basemost class will be loaded. If
14+
the query indicates that rows should only be instances of a specific
15+
subclass within the polymorphic hierarchy, an error is raised if an
16+
incompatible superclass is loaded in the result since it cannot be made to
17+
match the requested class; this behavior is the same as what joined
18+
inheritance has done for many years. The change also allows a single result
19+
set to include column-level results from multiple sibling classes at once
20+
which was not previously possible with single table inheritance.

lib/sqlalchemy/orm/context.py

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from __future__ import annotations
1010

11+
import collections
1112
import itertools
1213
from typing import Any
1314
from typing import cast
@@ -2494,31 +2495,83 @@ def _adjust_for_extra_criteria(self):
24942495
ext_info._adapter if ext_info.is_aliased_class else None,
24952496
)
24962497

2497-
search = set(self.extra_criteria_entities.values())
2498+
_where_criteria_to_add = ()
24982499

2499-
for ext_info, adapter in search:
2500+
merged_single_crit = collections.defaultdict(
2501+
lambda: (util.OrderedSet(), set())
2502+
)
2503+
2504+
for ext_info, adapter in util.OrderedSet(
2505+
self.extra_criteria_entities.values()
2506+
):
25002507
if ext_info in self._join_entities:
25012508
continue
25022509

2503-
single_crit = ext_info.mapper._single_table_criterion
2504-
2505-
if self.compile_options._for_refresh_state:
2506-
additional_entity_criteria = []
2510+
# assemble single table inheritance criteria.
2511+
if (
2512+
ext_info.is_aliased_class
2513+
and ext_info._base_alias()._is_with_polymorphic
2514+
):
2515+
# for a with_polymorphic(), we always include the full
2516+
# hierarchy from what's given as the base class for the wpoly.
2517+
# this is new in 2.1 for #12395 so that it matches the behavior
2518+
# of joined inheritance.
2519+
hierarchy_root = ext_info._base_alias()
25072520
else:
2508-
additional_entity_criteria = self._get_extra_criteria(ext_info)
2521+
hierarchy_root = ext_info
25092522

2510-
if single_crit is not None:
2511-
additional_entity_criteria += (single_crit,)
2523+
single_crit_component = (
2524+
hierarchy_root.mapper._single_table_criteria_component
2525+
)
25122526

2513-
current_adapter = self._get_current_adapter()
2514-
for crit in additional_entity_criteria:
2527+
if single_crit_component is not None:
2528+
polymorphic_on, criteria = single_crit_component
2529+
2530+
polymorphic_on = polymorphic_on._annotate(
2531+
{
2532+
"parententity": hierarchy_root,
2533+
"parentmapper": hierarchy_root.mapper,
2534+
}
2535+
)
2536+
2537+
list_of_single_crits, adapters = merged_single_crit[
2538+
(hierarchy_root, polymorphic_on)
2539+
]
2540+
list_of_single_crits.update(criteria)
25152541
if adapter:
2516-
crit = adapter.traverse(crit)
2542+
adapters.add(adapter)
25172543

2518-
if current_adapter:
2519-
crit = sql_util._deep_annotate(crit, {"_orm_adapt": True})
2520-
crit = current_adapter(crit, False)
2544+
# assemble "additional entity criteria", which come from
2545+
# with_loader_criteria() options
2546+
if not self.compile_options._for_refresh_state:
2547+
additional_entity_criteria = self._get_extra_criteria(ext_info)
2548+
_where_criteria_to_add += tuple(
2549+
adapter.traverse(crit) if adapter else crit
2550+
for crit in additional_entity_criteria
2551+
)
2552+
2553+
# merge together single table inheritance criteria keyed to
2554+
# top-level mapper / aliasedinsp (which may be a with_polymorphic())
2555+
for (ext_info, polymorphic_on), (
2556+
merged_crit,
2557+
adapters,
2558+
) in merged_single_crit.items():
2559+
new_crit = polymorphic_on.in_(merged_crit)
2560+
for adapter in adapters:
2561+
new_crit = adapter.traverse(new_crit)
2562+
_where_criteria_to_add += (new_crit,)
2563+
2564+
current_adapter = self._get_current_adapter()
2565+
if current_adapter:
2566+
# finally run all the criteria through the "main" adapter, if we
2567+
# have one, and concatenate to final WHERE criteria
2568+
for crit in _where_criteria_to_add:
2569+
crit = sql_util._deep_annotate(crit, {"_orm_adapt": True})
2570+
crit = current_adapter(crit, False)
25212571
self._where_criteria += (crit,)
2572+
else:
2573+
# else just concatenate our criteria to the final WHERE criteria
2574+
self._where_criteria += _where_criteria_to_add
25222575

25232576

25242577
def _column_descriptions(
@@ -2552,7 +2605,7 @@ def _column_descriptions(
25522605

25532606

25542607
def _legacy_filter_by_entity_zero(
2555-
query_or_augmented_select: Union[Query[Any], Select[Unpack[TupleAny]]]
2608+
query_or_augmented_select: Union[Query[Any], Select[Unpack[TupleAny]]],
25562609
) -> Optional[_InternalEntityType[Any]]:
25572610
self = query_or_augmented_select
25582611
if self._setup_joins:
@@ -2567,7 +2620,7 @@ def _legacy_filter_by_entity_zero(
25672620

25682621

25692622
def _entity_from_pre_ent_zero(
2570-
query_or_augmented_select: Union[Query[Any], Select[Unpack[TupleAny]]]
2623+
query_or_augmented_select: Union[Query[Any], Select[Unpack[TupleAny]]],
25712624
) -> Optional[_InternalEntityType[Any]]:
25722625
self = query_or_augmented_select
25732626
if not self._raw_columns:

lib/sqlalchemy/orm/mapper.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2626,17 +2626,29 @@ def _version_id_has_server_side_value(self) -> bool:
26262626
)
26272627

26282628
@HasMemoized.memoized_attribute
2629-
def _single_table_criterion(self):
2629+
def _single_table_criteria_component(self):
26302630
if self.single and self.inherits and self.polymorphic_on is not None:
2631-
return self.polymorphic_on._annotate(
2632-
{"parententity": self, "parentmapper": self}
2633-
).in_(
2634-
[
2635-
m.polymorphic_identity
2636-
for m in self.self_and_descendants
2637-
if not m.polymorphic_abstract
2638-
]
2631+
2632+
hierarchy = tuple(
2633+
m.polymorphic_identity
2634+
for m in self.self_and_descendants
2635+
if not m.polymorphic_abstract
26392636
)
2637+
2638+
return (
2639+
self.polymorphic_on._annotate(
2640+
{"parententity": self, "parentmapper": self}
2641+
),
2642+
hierarchy,
2643+
)
2644+
else:
2645+
return None
2646+
2647+
@HasMemoized.memoized_attribute
2648+
def _single_table_criterion(self):
2649+
component = self._single_table_criteria_component
2650+
if component is not None:
2651+
return component[0].in_(component[1])
26402652
else:
26412653
return None
26422654

0 commit comments

Comments
 (0)