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

Skip to content

Commit a05ae2c

Browse files
committed
apply criteria options from top-level core-only statement
Made an improvement to the :func:`_orm.with_loader_criteria` loader option to allow it to be indicated in the :meth:`.Executable.options` method of a top-level statement that is not itself an ORM statement. Examples include :func:`_sql.select` that's embedded in compound statements such as :func:`_sql.union`, within an :meth:`_dml.Insert.from_select` construct, as well as within CTE expressions that are not ORM related at the top level. Improved propagation of :func:`_orm.with_loader_criteria` within ORM enabled UPDATE and DELETE statements as well. Fixes: sqlalchemy#9635 Change-Id: I088ad91929dc797c06f292f5dc547d48ffb30430
1 parent acf7fbd commit a05ae2c

File tree

4 files changed

+315
-35
lines changed

4 files changed

+315
-35
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.. change::
2+
:tags: bug, orm
3+
:tickets: 9635
4+
5+
Made an improvement to the :func:`_orm.with_loader_criteria` loader option
6+
to allow it to be indicated in the :meth:`.Executable.options` method of a
7+
top-level statement that is not itself an ORM statement. Examples include
8+
:func:`_sql.select` that's embedded in compound statements such as
9+
:func:`_sql.union`, within an :meth:`_dml.Insert.from_select` construct, as
10+
well as within CTE expressions that are not ORM related at the top level.

lib/sqlalchemy/orm/bulk_persistence.py

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,15 +1346,14 @@ def _setup_for_orm_update(self, statement, compiler, **kw):
13461346

13471347
self.mapper = mapper = ext_info.mapper
13481348

1349-
self.extra_criteria_entities = {}
1350-
13511349
self._resolved_values = self._get_resolved_values(mapper, statement)
13521350

1353-
extra_criteria_attributes = {}
1354-
1355-
for opt in statement._with_options:
1356-
if opt._is_criteria_option:
1357-
opt.get_global_criteria(extra_criteria_attributes)
1351+
self._init_global_attributes(
1352+
statement,
1353+
compiler,
1354+
toplevel=True,
1355+
process_criteria_for_toplevel=True,
1356+
)
13581357

13591358
if statement._values:
13601359
self._resolved_values = dict(self._resolved_values)
@@ -1372,7 +1371,7 @@ def _setup_for_orm_update(self, statement, compiler, **kw):
13721371
new_stmt._values = self._resolved_values
13731372

13741373
new_crit = self._adjust_for_extra_criteria(
1375-
extra_criteria_attributes, mapper
1374+
self.global_attributes, mapper
13761375
)
13771376
if new_crit:
13781377
new_stmt = new_stmt.where(*new_crit)
@@ -1741,19 +1740,18 @@ def create_for_statement(cls, statement, compiler, **kw):
17411740
ext_info = statement.table._annotations["parententity"]
17421741
self.mapper = mapper = ext_info.mapper
17431742

1744-
self.extra_criteria_entities = {}
1745-
1746-
extra_criteria_attributes = {}
1747-
1748-
for opt in statement._with_options:
1749-
if opt._is_criteria_option:
1750-
opt.get_global_criteria(extra_criteria_attributes)
1743+
self._init_global_attributes(
1744+
statement,
1745+
compiler,
1746+
toplevel=True,
1747+
process_criteria_for_toplevel=True,
1748+
)
17511749

17521750
new_stmt = statement._clone()
17531751
new_stmt.table = mapper.local_table
17541752

17551753
new_crit = cls._adjust_for_extra_criteria(
1756-
extra_criteria_attributes, mapper
1754+
self.global_attributes, mapper
17571755
)
17581756
if new_crit:
17591757
new_stmt = new_stmt.where(*new_crit)

lib/sqlalchemy/orm/context.py

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,45 @@ def _get_top_level_context(self) -> QueryContext:
209209
class AbstractORMCompileState(CompileState):
210210
is_dml_returning = False
211211

212+
def _init_global_attributes(
213+
self, statement, compiler, *, toplevel, process_criteria_for_toplevel
214+
):
215+
self.attributes = {}
216+
217+
if compiler is None:
218+
# this is the legacy / testing only ORM _compile_state() use case.
219+
# there is no need to apply criteria options for this.
220+
self.global_attributes = ga = {}
221+
assert toplevel
222+
return
223+
else:
224+
self.global_attributes = ga = compiler._global_attributes
225+
226+
if toplevel:
227+
ga["toplevel_orm"] = True
228+
229+
if process_criteria_for_toplevel:
230+
for opt in statement._with_options:
231+
if opt._is_criteria_option:
232+
opt.process_compile_state(self)
233+
234+
return
235+
elif ga.get("toplevel_orm", False):
236+
return
237+
238+
stack_0 = compiler.stack[0]
239+
240+
try:
241+
toplevel_stmt = stack_0["selectable"]
242+
except KeyError:
243+
pass
244+
else:
245+
for opt in toplevel_stmt._with_options:
246+
if opt._is_compile_state and opt._is_criteria_option:
247+
opt.process_compile_state(self)
248+
249+
ga["toplevel_orm"] = True
250+
212251
@classmethod
213252
def create_for_statement(
214253
cls,
@@ -622,17 +661,13 @@ def create_for_statement(
622661

623662
assert isinstance(statement_container, FromStatement)
624663

625-
if compiler is not None:
626-
toplevel = not compiler.stack
627-
else:
628-
toplevel = True
629-
630-
if not toplevel:
664+
if compiler is not None and compiler.stack:
631665
raise sa_exc.CompileError(
632666
"The ORM FromStatement construct only supports being "
633667
"invoked as the topmost statement, as it is only intended to "
634668
"define how result rows should be returned."
635669
)
670+
636671
self = cls.__new__(cls)
637672
self._primary_entity = None
638673

@@ -680,18 +715,18 @@ def create_for_statement(
680715

681716
self.current_path = statement_container._compile_options._current_path
682717

683-
if toplevel and statement_container._with_options:
684-
self.attributes = {}
685-
self.global_attributes = compiler._global_attributes
718+
self._init_global_attributes(
719+
statement_container,
720+
compiler,
721+
process_criteria_for_toplevel=False,
722+
toplevel=True,
723+
)
686724

725+
if statement_container._with_options:
687726
for opt in statement_container._with_options:
688727
if opt._is_compile_state:
689728
opt.process_compile_state(self)
690729

691-
else:
692-
self.attributes = {}
693-
self.global_attributes = compiler._global_attributes
694-
695730
if statement_container._with_context_options:
696731
for fn, key in statement_container._with_context_options:
697732
fn(self)
@@ -911,10 +946,8 @@ def create_for_statement(
911946

912947
if compiler is not None:
913948
toplevel = not compiler.stack
914-
self.global_attributes = compiler._global_attributes
915949
else:
916950
toplevel = True
917-
self.global_attributes = {}
918951

919952
select_statement = statement
920953

@@ -1002,11 +1035,17 @@ def create_for_statement(
10021035

10031036
self.eager_order_by = ()
10041037

1038+
self._init_global_attributes(
1039+
select_statement,
1040+
compiler,
1041+
toplevel=toplevel,
1042+
process_criteria_for_toplevel=False,
1043+
)
1044+
10051045
if toplevel and (
10061046
select_statement._with_options
10071047
or select_statement._memoized_select_entities
10081048
):
1009-
self.attributes = {}
10101049

10111050
for (
10121051
memoized_entities
@@ -1028,9 +1067,6 @@ def create_for_statement(
10281067
if opt._is_compile_state:
10291068
opt.process_compile_state(self)
10301069

1031-
else:
1032-
self.attributes = {}
1033-
10341070
# uncomment to print out the context.attributes structure
10351071
# after it's been set up above
10361072
# self._dump_option_struct()

0 commit comments

Comments
 (0)