1# orm/context.py
2# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7# mypy: ignore-errors
8
9from __future__ import annotations
10
11import collections
12import itertools
13from typing import Any
14from typing import cast
15from typing import Dict
16from typing import Iterable
17from typing import List
18from typing import Optional
19from typing import Set
20from typing import Tuple
21from typing import Type
22from typing import TYPE_CHECKING
23from typing import TypeVar
24from typing import Union
25
26from . import attributes
27from . import interfaces
28from . import loading
29from .base import _is_aliased_class
30from .interfaces import ORMColumnDescription
31from .interfaces import ORMColumnsClauseRole
32from .path_registry import PathRegistry
33from .util import _entity_corresponds_to
34from .util import _ORMJoin
35from .util import _TraceAdaptRole
36from .util import AliasedClass
37from .util import Bundle
38from .util import ORMAdapter
39from .util import ORMStatementAdapter
40from .. import exc as sa_exc
41from .. import future
42from .. import inspect
43from .. import sql
44from .. import util
45from ..sql import coercions
46from ..sql import expression
47from ..sql import roles
48from ..sql import util as sql_util
49from ..sql import visitors
50from ..sql._typing import is_dml
51from ..sql._typing import is_insert_update
52from ..sql._typing import is_select_base
53from ..sql.base import _select_iterables
54from ..sql.base import CacheableOptions
55from ..sql.base import CompileState
56from ..sql.base import Executable
57from ..sql.base import ExecutableStatement
58from ..sql.base import Generative
59from ..sql.base import Options
60from ..sql.dml import UpdateBase
61from ..sql.elements import GroupedElement
62from ..sql.elements import TextClause
63from ..sql.selectable import CompoundSelectState
64from ..sql.selectable import LABEL_STYLE_DISAMBIGUATE_ONLY
65from ..sql.selectable import LABEL_STYLE_NONE
66from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
67from ..sql.selectable import Select
68from ..sql.selectable import SelectLabelStyle
69from ..sql.selectable import SelectState
70from ..sql.selectable import TypedReturnsRows
71from ..sql.visitors import InternalTraversal
72from ..util.typing import TupleAny
73from ..util.typing import TypeVarTuple
74from ..util.typing import Unpack
75
76
77if TYPE_CHECKING:
78 from ._typing import _InternalEntityType
79 from ._typing import OrmExecuteOptionsParameter
80 from .loading import _PostLoad
81 from .mapper import Mapper
82 from .query import Query
83 from .session import _BindArguments
84 from .session import Session
85 from ..engine import Result
86 from ..engine.interfaces import _CoreSingleExecuteParams
87 from ..sql._typing import _ColumnsClauseArgument
88 from ..sql.compiler import SQLCompiler
89 from ..sql.dml import _DMLTableElement
90 from ..sql.elements import ColumnElement
91 from ..sql.selectable import _JoinTargetElement
92 from ..sql.selectable import _LabelConventionCallable
93 from ..sql.selectable import _SetupJoinsElement
94 from ..sql.selectable import ExecutableReturnsRows
95 from ..sql.selectable import SelectBase
96 from ..sql.type_api import TypeEngine
97
98_T = TypeVar("_T", bound=Any)
99_Ts = TypeVarTuple("_Ts")
100_path_registry = PathRegistry.root
101
102_EMPTY_DICT = util.immutabledict()
103
104
105LABEL_STYLE_LEGACY_ORM = SelectLabelStyle.LABEL_STYLE_LEGACY_ORM
106
107
108class QueryContext:
109 __slots__ = (
110 "top_level_context",
111 "compile_state",
112 "query",
113 "user_passed_query",
114 "params",
115 "load_options",
116 "bind_arguments",
117 "execution_options",
118 "session",
119 "autoflush",
120 "populate_existing",
121 "invoke_all_eagers",
122 "version_check",
123 "refresh_state",
124 "create_eager_joins",
125 "propagated_loader_options",
126 "attributes",
127 "runid",
128 "partials",
129 "post_load_paths",
130 "identity_token",
131 "yield_per",
132 "loaders_require_buffering",
133 "loaders_require_uniquing",
134 )
135
136 runid: int
137 post_load_paths: Dict[PathRegistry, _PostLoad]
138 compile_state: _ORMCompileState
139
140 class default_load_options(Options):
141 _only_return_tuples = False
142 _populate_existing = False
143 _version_check = False
144 _invoke_all_eagers = True
145 _autoflush = True
146 _identity_token = None
147 _yield_per = None
148 _refresh_state = None
149 _lazy_loaded_from = None
150 _legacy_uniquing = False
151 _sa_top_level_orm_context = None
152 _is_user_refresh = False
153
154 def __init__(
155 self,
156 compile_state: CompileState,
157 statement: Union[
158 Select[Unpack[TupleAny]],
159 FromStatement[Unpack[TupleAny]],
160 UpdateBase,
161 ],
162 user_passed_query: Union[
163 Select[Unpack[TupleAny]],
164 FromStatement[Unpack[TupleAny]],
165 UpdateBase,
166 ],
167 params: _CoreSingleExecuteParams,
168 session: Session,
169 load_options: Union[
170 Type[QueryContext.default_load_options],
171 QueryContext.default_load_options,
172 ],
173 execution_options: Optional[OrmExecuteOptionsParameter] = None,
174 bind_arguments: Optional[_BindArguments] = None,
175 ):
176 self.load_options = load_options
177 self.execution_options = execution_options or _EMPTY_DICT
178 self.bind_arguments = bind_arguments or _EMPTY_DICT
179 self.compile_state = compile_state
180 self.query = statement
181
182 # the query that the end user passed to Session.execute() or similar.
183 # this is usually the same as .query, except in the bulk_persistence
184 # routines where a separate FromStatement is manufactured in the
185 # compile stage; this allows differentiation in that case.
186 self.user_passed_query = user_passed_query
187
188 self.session = session
189 self.loaders_require_buffering = False
190 self.loaders_require_uniquing = False
191 self.params = params
192 self.top_level_context = load_options._sa_top_level_orm_context
193
194 cached_options = compile_state.select_statement._with_options
195 uncached_options = user_passed_query._with_options
196
197 # see issue #7447 , #8399 for some background
198 # propagated loader options will be present on loaded InstanceState
199 # objects under state.load_options and are typically used by
200 # LazyLoader to apply options to the SELECT statement it emits.
201 # For compile state options (i.e. loader strategy options), these
202 # need to line up with the ".load_path" attribute which in
203 # loader.py is pulled from context.compile_state.current_path.
204 # so, this means these options have to be the ones from the
205 # *cached* statement that's travelling with compile_state, not the
206 # *current* statement which won't match up for an ad-hoc
207 # AliasedClass
208 self.propagated_loader_options = tuple(
209 opt._adapt_cached_option_to_uncached_option(self, uncached_opt)
210 for opt, uncached_opt in zip(cached_options, uncached_options)
211 if opt.propagate_to_loaders
212 )
213
214 self.attributes = dict(compile_state.attributes)
215
216 self.autoflush = load_options._autoflush
217 self.populate_existing = load_options._populate_existing
218 self.invoke_all_eagers = load_options._invoke_all_eagers
219 self.version_check = load_options._version_check
220 self.refresh_state = load_options._refresh_state
221 self.yield_per = load_options._yield_per
222 self.identity_token = load_options._identity_token
223
224 def _get_top_level_context(self) -> QueryContext:
225 return self.top_level_context or self
226
227
228_orm_load_exec_options = util.immutabledict(
229 {"_result_disable_adapt_to_context": True}
230)
231
232
233class _AbstractORMCompileState(CompileState):
234 is_dml_returning = False
235
236 def _init_global_attributes(
237 self, statement, compiler, *, toplevel, process_criteria_for_toplevel
238 ):
239 self.attributes = {}
240
241 if compiler is None:
242 # this is the legacy / testing only ORM _compile_state() use case.
243 # there is no need to apply criteria options for this.
244 self.global_attributes = {}
245 assert toplevel
246 return
247 else:
248 self.global_attributes = ga = compiler._global_attributes
249
250 if toplevel:
251 ga["toplevel_orm"] = True
252
253 if process_criteria_for_toplevel:
254 for opt in statement._with_options:
255 if opt._is_criteria_option:
256 opt.process_compile_state(self)
257
258 return
259 elif ga.get("toplevel_orm", False):
260 return
261
262 stack_0 = compiler.stack[0]
263
264 try:
265 toplevel_stmt = stack_0["selectable"]
266 except KeyError:
267 pass
268 else:
269 for opt in toplevel_stmt._with_options:
270 if opt._is_compile_state and opt._is_criteria_option:
271 opt.process_compile_state(self)
272
273 ga["toplevel_orm"] = True
274
275 @classmethod
276 def create_for_statement(
277 cls,
278 statement: Executable,
279 compiler: SQLCompiler,
280 **kw: Any,
281 ) -> CompileState:
282 """Create a context for a statement given a :class:`.Compiler`.
283
284 This method is always invoked in the context of SQLCompiler.process().
285
286 For a Select object, this would be invoked from
287 SQLCompiler.visit_select(). For the special FromStatement object used
288 by Query to indicate "Query.from_statement()", this is called by
289 FromStatement._compiler_dispatch() that would be called by
290 SQLCompiler.process().
291 """
292 return super().create_for_statement(statement, compiler, **kw)
293
294 @classmethod
295 def orm_pre_session_exec(
296 cls,
297 session,
298 statement,
299 params,
300 execution_options,
301 bind_arguments,
302 is_pre_event,
303 ):
304 raise NotImplementedError()
305
306 @classmethod
307 def orm_execute_statement(
308 cls,
309 session,
310 statement,
311 params,
312 execution_options,
313 bind_arguments,
314 conn,
315 ) -> Result:
316 result = conn.execute(
317 statement, params or {}, execution_options=execution_options
318 )
319 return cls.orm_setup_cursor_result(
320 session,
321 statement,
322 params,
323 execution_options,
324 bind_arguments,
325 result,
326 )
327
328 @classmethod
329 def orm_setup_cursor_result(
330 cls,
331 session,
332 statement,
333 params,
334 execution_options,
335 bind_arguments,
336 result,
337 ):
338 raise NotImplementedError()
339
340
341class _AutoflushOnlyORMCompileState(_AbstractORMCompileState):
342 """ORM compile state that is a passthrough, except for autoflush."""
343
344 @classmethod
345 def orm_pre_session_exec(
346 cls,
347 session,
348 statement,
349 params,
350 execution_options,
351 bind_arguments,
352 is_pre_event,
353 ):
354 # consume result-level load_options. These may have been set up
355 # in an ORMExecuteState hook
356 (
357 load_options,
358 execution_options,
359 ) = QueryContext.default_load_options.from_execution_options(
360 "_sa_orm_load_options",
361 {
362 "autoflush",
363 },
364 execution_options,
365 statement._execution_options,
366 )
367
368 if not is_pre_event and load_options._autoflush:
369 session._autoflush()
370
371 return statement, execution_options, params
372
373 @classmethod
374 def orm_setup_cursor_result(
375 cls,
376 session,
377 statement,
378 params,
379 execution_options,
380 bind_arguments,
381 result,
382 ):
383 return result
384
385
386class _ORMCompileState(_AbstractORMCompileState):
387 class default_compile_options(CacheableOptions):
388 _cache_key_traversal = [
389 ("_use_legacy_query_style", InternalTraversal.dp_boolean),
390 ("_for_statement", InternalTraversal.dp_boolean),
391 ("_bake_ok", InternalTraversal.dp_boolean),
392 ("_current_path", InternalTraversal.dp_has_cache_key),
393 ("_enable_single_crit", InternalTraversal.dp_boolean),
394 ("_enable_eagerloads", InternalTraversal.dp_boolean),
395 ("_only_load_props", InternalTraversal.dp_plain_obj),
396 ("_set_base_alias", InternalTraversal.dp_boolean),
397 ("_for_refresh_state", InternalTraversal.dp_boolean),
398 ("_render_for_subquery", InternalTraversal.dp_boolean),
399 ("_is_star", InternalTraversal.dp_boolean),
400 ]
401
402 # set to True by default from Query._statement_20(), to indicate
403 # the rendered query should look like a legacy ORM query. right
404 # now this basically indicates we should use tablename_columnname
405 # style labels. Generally indicates the statement originated
406 # from a Query object.
407 _use_legacy_query_style = False
408
409 # set *only* when we are coming from the Query.statement
410 # accessor, or a Query-level equivalent such as
411 # query.subquery(). this supersedes "toplevel".
412 _for_statement = False
413
414 _bake_ok = True
415 _current_path = _path_registry
416 _enable_single_crit = True
417 _enable_eagerloads = True
418 _only_load_props = None
419 _set_base_alias = False
420 _for_refresh_state = False
421 _render_for_subquery = False
422 _is_star = False
423
424 attributes: Dict[Any, Any]
425 global_attributes: Dict[Any, Any]
426
427 statement: Union[
428 Select[Unpack[TupleAny]], FromStatement[Unpack[TupleAny]], UpdateBase
429 ]
430 select_statement: Union[
431 Select[Unpack[TupleAny]], FromStatement[Unpack[TupleAny]]
432 ]
433 _entities: List[_QueryEntity]
434 _polymorphic_adapters: Dict[_InternalEntityType, ORMAdapter]
435 compile_options: Union[
436 Type[default_compile_options], default_compile_options
437 ]
438 _primary_entity: Optional[_QueryEntity]
439 use_legacy_query_style: bool
440 _label_convention: _LabelConventionCallable
441 primary_columns: List[ColumnElement[Any]]
442 secondary_columns: List[ColumnElement[Any]]
443 dedupe_columns: Set[ColumnElement[Any]]
444 create_eager_joins: List[
445 # TODO: this structure is set up by JoinedLoader
446 TupleAny
447 ]
448 current_path: PathRegistry = _path_registry
449 _has_mapper_entities = False
450
451 def __init__(self, *arg, **kw):
452 raise NotImplementedError()
453
454 @classmethod
455 def create_for_statement(
456 cls,
457 statement: Executable,
458 compiler: SQLCompiler,
459 **kw: Any,
460 ) -> _ORMCompileState:
461 return cls._create_orm_context(
462 cast("Union[Select, FromStatement]", statement),
463 toplevel=not compiler.stack,
464 compiler=compiler,
465 **kw,
466 )
467
468 @classmethod
469 def _create_orm_context(
470 cls,
471 statement: Union[Select, FromStatement],
472 *,
473 toplevel: bool,
474 compiler: Optional[SQLCompiler],
475 **kw: Any,
476 ) -> _ORMCompileState:
477 raise NotImplementedError()
478
479 def _append_dedupe_col_collection(self, obj, col_collection):
480 dedupe = self.dedupe_columns
481 if obj not in dedupe:
482 dedupe.add(obj)
483 col_collection.append(obj)
484
485 @classmethod
486 def _column_naming_convention(
487 cls, label_style: SelectLabelStyle, legacy: bool
488 ) -> _LabelConventionCallable:
489 if legacy:
490
491 def name(col, col_name=None):
492 if col_name:
493 return col_name
494 else:
495 return getattr(col, "key")
496
497 return name
498 else:
499 return SelectState._column_naming_convention(label_style)
500
501 @classmethod
502 def get_column_descriptions(cls, statement):
503 return _column_descriptions(statement)
504
505 @classmethod
506 def orm_pre_session_exec(
507 cls,
508 session,
509 statement,
510 params,
511 execution_options,
512 bind_arguments,
513 is_pre_event,
514 ):
515 # consume result-level load_options. These may have been set up
516 # in an ORMExecuteState hook
517 (
518 load_options,
519 execution_options,
520 ) = QueryContext.default_load_options.from_execution_options(
521 "_sa_orm_load_options",
522 {
523 "populate_existing",
524 "autoflush",
525 "yield_per",
526 "identity_token",
527 "sa_top_level_orm_context",
528 },
529 execution_options,
530 statement._execution_options,
531 )
532
533 # default execution options for ORM results:
534 # 1. _result_disable_adapt_to_context=True
535 # this will disable the ResultSetMetadata._adapt_to_context()
536 # step which we don't need, as we have result processors cached
537 # against the original SELECT statement before caching.
538
539 if "sa_top_level_orm_context" in execution_options:
540 ctx = execution_options["sa_top_level_orm_context"]
541 execution_options = ctx.query._execution_options.merge_with(
542 ctx.execution_options, execution_options
543 )
544
545 if not execution_options:
546 execution_options = _orm_load_exec_options
547 else:
548 execution_options = execution_options.union(_orm_load_exec_options)
549
550 # would have been placed here by legacy Query only
551 if load_options._yield_per:
552 execution_options = execution_options.union(
553 {"yield_per": load_options._yield_per}
554 )
555
556 if (
557 getattr(statement._compile_options, "_current_path", None)
558 and len(statement._compile_options._current_path) > 10
559 and execution_options.get("compiled_cache", True) is not None
560 ):
561 execution_options: util.immutabledict[str, Any] = (
562 execution_options.union(
563 {
564 "compiled_cache": None,
565 "_cache_disable_reason": "excess depth for "
566 "ORM loader options",
567 }
568 )
569 )
570
571 bind_arguments["clause"] = statement
572
573 # new in 1.4 - the coercions system is leveraged to allow the
574 # "subject" mapper of a statement be propagated to the top
575 # as the statement is built. "subject" mapper is the generally
576 # standard object used as an identifier for multi-database schemes.
577
578 # we are here based on the fact that _propagate_attrs contains
579 # "compile_state_plugin": "orm". The "plugin_subject"
580 # needs to be present as well.
581
582 try:
583 plugin_subject = statement._propagate_attrs["plugin_subject"]
584 except KeyError:
585 assert False, "statement had 'orm' plugin but no plugin_subject"
586 else:
587 if plugin_subject:
588 bind_arguments["mapper"] = plugin_subject.mapper
589
590 if not is_pre_event and load_options._autoflush:
591 session._autoflush()
592
593 return statement, execution_options, params
594
595 @classmethod
596 def orm_setup_cursor_result(
597 cls,
598 session,
599 statement,
600 params,
601 execution_options,
602 bind_arguments,
603 result,
604 ):
605 execution_context = result.context
606 compile_state = execution_context.compiled.compile_state
607
608 # cover edge case where ORM entities used in legacy select
609 # were passed to session.execute:
610 # session.execute(legacy_select([User.id, User.name]))
611 # see test_query->test_legacy_tuple_old_select
612
613 load_options = execution_options.get(
614 "_sa_orm_load_options", QueryContext.default_load_options
615 )
616
617 if compile_state.compile_options._is_star:
618 return result
619
620 querycontext = QueryContext(
621 compile_state,
622 statement,
623 statement,
624 params,
625 session,
626 load_options,
627 execution_options,
628 bind_arguments,
629 )
630 return loading.instances(result, querycontext)
631
632 @property
633 def _lead_mapper_entities(self):
634 """return all _MapperEntity objects in the lead entities collection.
635
636 Does **not** include entities that have been replaced by
637 with_entities(), with_only_columns()
638
639 """
640 return [
641 ent for ent in self._entities if isinstance(ent, _MapperEntity)
642 ]
643
644 def _create_with_polymorphic_adapter(self, ext_info, selectable):
645 """given MapperEntity or ORMColumnEntity, setup polymorphic loading
646 if called for by the Mapper.
647
648 As of #8168 in 2.0.0rc1, polymorphic adapters, which greatly increase
649 the complexity of the query creation process, are not used at all
650 except in the quasi-legacy cases of with_polymorphic referring to an
651 alias and/or subquery. This would apply to concrete polymorphic
652 loading, and joined inheritance where a subquery is
653 passed to with_polymorphic (which is completely unnecessary in modern
654 use).
655
656 TODO: What is a "quasi-legacy" case? Do we need this method with
657 2.0 style select() queries or not? Why is with_polymorphic referring
658 to an alias or subquery "legacy" ?
659
660 """
661 if (
662 not ext_info.is_aliased_class
663 and ext_info.mapper.persist_selectable
664 not in self._polymorphic_adapters
665 ):
666 for mp in ext_info.mapper.iterate_to_root():
667 self._mapper_loads_polymorphically_with(
668 mp,
669 ORMAdapter(
670 _TraceAdaptRole.WITH_POLYMORPHIC_ADAPTER,
671 mp,
672 equivalents=mp._equivalent_columns,
673 selectable=selectable,
674 ),
675 )
676
677 def _mapper_loads_polymorphically_with(self, mapper, adapter):
678 for m2 in mapper._with_polymorphic_mappers or [mapper]:
679 self._polymorphic_adapters[m2] = adapter
680
681 for m in m2.iterate_to_root():
682 self._polymorphic_adapters[m.local_table] = adapter
683
684 @classmethod
685 def _create_entities_collection(cls, query, legacy):
686 raise NotImplementedError(
687 "this method only works for ORMSelectCompileState"
688 )
689
690
691class _DMLReturningColFilter:
692 """a base for an adapter used for the DML RETURNING cases
693
694 Has a subset of the interface used by
695 :class:`.ORMAdapter` and is used for :class:`._QueryEntity`
696 instances to set up their columns as used in RETURNING for a
697 DML statement.
698
699 """
700
701 __slots__ = ("mapper", "columns", "__weakref__")
702
703 def __init__(self, target_mapper, immediate_dml_mapper):
704 if (
705 immediate_dml_mapper is not None
706 and target_mapper.local_table
707 is not immediate_dml_mapper.local_table
708 ):
709 # joined inh, or in theory other kinds of multi-table mappings
710 self.mapper = immediate_dml_mapper
711 else:
712 # single inh, normal mappings, etc.
713 self.mapper = target_mapper
714 self.columns = self.columns = util.WeakPopulateDict(
715 self.adapt_check_present # type: ignore
716 )
717
718 def __call__(self, col, as_filter):
719 for cc in sql_util._find_columns(col):
720 c2 = self.adapt_check_present(cc)
721 if c2 is not None:
722 return col
723 else:
724 return None
725
726 def adapt_check_present(self, col):
727 raise NotImplementedError()
728
729
730class _DMLBulkInsertReturningColFilter(_DMLReturningColFilter):
731 """an adapter used for the DML RETURNING case specifically
732 for ORM bulk insert (or any hypothetical DML that is splitting out a class
733 hierarchy among multiple DML statements....ORM bulk insert is the only
734 example right now)
735
736 its main job is to limit the columns in a RETURNING to only a specific
737 mapped table in a hierarchy.
738
739 """
740
741 def adapt_check_present(self, col):
742 mapper = self.mapper
743 prop = mapper._columntoproperty.get(col, None)
744 if prop is None:
745 return None
746 return mapper.local_table.c.corresponding_column(col)
747
748
749class _DMLUpdateDeleteReturningColFilter(_DMLReturningColFilter):
750 """an adapter used for the DML RETURNING case specifically
751 for ORM enabled UPDATE/DELETE
752
753 its main job is to limit the columns in a RETURNING to include
754 only direct persisted columns from the immediate selectable, not
755 expressions like column_property(), or to also allow columns from other
756 mappers for the UPDATE..FROM use case.
757
758 """
759
760 def adapt_check_present(self, col):
761 mapper = self.mapper
762 prop = mapper._columntoproperty.get(col, None)
763 if prop is not None:
764 # if the col is from the immediate mapper, only return a persisted
765 # column, not any kind of column_property expression
766 return mapper.persist_selectable.c.corresponding_column(col)
767
768 # if the col is from some other mapper, just return it, assume the
769 # user knows what they are doing
770 return col
771
772
773@sql.base.CompileState.plugin_for("orm", "orm_from_statement")
774class _ORMFromStatementCompileState(_ORMCompileState):
775 _from_obj_alias = None
776 _has_mapper_entities = False
777
778 statement_container: FromStatement
779 requested_statement: Union[SelectBase, TextClause, UpdateBase]
780 dml_table: Optional[_DMLTableElement] = None
781
782 _has_orm_entities = False
783 multi_row_eager_loaders = False
784 eager_adding_joins = False
785 compound_eager_adapter = None
786
787 extra_criteria_entities = _EMPTY_DICT
788 eager_joins = _EMPTY_DICT
789
790 @classmethod
791 def _create_orm_context(
792 cls,
793 statement: Union[Select, FromStatement],
794 *,
795 toplevel: bool,
796 compiler: Optional[SQLCompiler],
797 **kw: Any,
798 ) -> _ORMFromStatementCompileState:
799 statement_container = statement
800
801 assert isinstance(statement_container, FromStatement)
802
803 if compiler is not None and compiler.stack:
804 raise sa_exc.CompileError(
805 "The ORM FromStatement construct only supports being "
806 "invoked as the topmost statement, as it is only intended to "
807 "define how result rows should be returned."
808 )
809
810 self = cls.__new__(cls)
811 self._primary_entity = None
812
813 self.use_legacy_query_style = (
814 statement_container._compile_options._use_legacy_query_style
815 )
816 self.statement_container = self.select_statement = statement_container
817 self.requested_statement = statement = statement_container.element
818
819 if statement.is_dml:
820 self.dml_table = statement.table
821 self.is_dml_returning = True
822
823 self._entities = []
824 self._polymorphic_adapters = {}
825
826 self.compile_options = statement_container._compile_options
827
828 if (
829 self.use_legacy_query_style
830 and isinstance(statement, expression.SelectBase)
831 and not statement._is_textual
832 and not statement.is_dml
833 and statement._label_style is LABEL_STYLE_NONE
834 ):
835 self.statement = statement.set_label_style(
836 LABEL_STYLE_TABLENAME_PLUS_COL
837 )
838 else:
839 self.statement = statement
840
841 self._label_convention = self._column_naming_convention(
842 (
843 statement._label_style
844 if not statement._is_textual and not statement.is_dml
845 else LABEL_STYLE_NONE
846 ),
847 self.use_legacy_query_style,
848 )
849
850 _QueryEntity.to_compile_state(
851 self,
852 statement_container._raw_columns,
853 self._entities,
854 is_current_entities=True,
855 )
856
857 self.current_path = statement_container._compile_options._current_path
858
859 self._init_global_attributes(
860 statement_container,
861 compiler,
862 process_criteria_for_toplevel=False,
863 toplevel=True,
864 )
865
866 if statement_container._with_options:
867 for opt in statement_container._with_options:
868 if opt._is_compile_state:
869 opt.process_compile_state(self)
870
871 if statement_container._compile_state_funcs:
872 for fn, key in statement_container._compile_state_funcs:
873 fn(self)
874
875 self.primary_columns = []
876 self.secondary_columns = []
877 self.dedupe_columns = set()
878 self.create_eager_joins = []
879 self._fallback_from_clauses = []
880
881 self.order_by = None
882
883 if isinstance(self.statement, expression.TextClause):
884 # TextClause has no "column" objects at all. for this case,
885 # we generate columns from our _QueryEntity objects, then
886 # flip on all the "please match no matter what" parameters.
887 self.extra_criteria_entities = {}
888
889 for entity in self._entities:
890 entity.setup_compile_state(self)
891
892 compiler._ordered_columns = compiler._textual_ordered_columns = (
893 False
894 )
895
896 # enable looser result column matching. this is shown to be
897 # needed by test_query.py::TextTest
898 compiler._loose_column_name_matching = True
899
900 for c in self.primary_columns:
901 compiler.process(
902 c,
903 within_columns_clause=True,
904 add_to_result_map=compiler._add_to_result_map,
905 )
906 else:
907 # for everyone else, Select, Insert, Update, TextualSelect, they
908 # have column objects already. After much
909 # experimentation here, the best approach seems to be, use
910 # those columns completely, don't interfere with the compiler
911 # at all; just in ORM land, use an adapter to convert from
912 # our ORM columns to whatever columns are in the statement,
913 # before we look in the result row. Adapt on names
914 # to accept cases such as issue #9217, however also allow
915 # this to be overridden for cases such as #9273.
916 self._from_obj_alias = ORMStatementAdapter(
917 _TraceAdaptRole.ADAPT_FROM_STATEMENT,
918 self.statement,
919 adapt_on_names=statement_container._adapt_on_names,
920 )
921
922 return self
923
924 def _adapt_col_list(self, cols, current_adapter):
925 return cols
926
927 def _get_current_adapter(self):
928 return None
929
930 def setup_dml_returning_compile_state(self, dml_mapper):
931 """used by BulkORMInsert, Update, Delete to set up a handler
932 for RETURNING to return ORM objects and expressions
933
934 """
935 target_mapper = self.statement._propagate_attrs.get(
936 "plugin_subject", None
937 )
938
939 if self.statement.is_insert:
940 adapter = _DMLBulkInsertReturningColFilter(
941 target_mapper, dml_mapper
942 )
943 elif self.statement.is_update or self.statement.is_delete:
944 adapter = _DMLUpdateDeleteReturningColFilter(
945 target_mapper, dml_mapper
946 )
947 else:
948 adapter = None
949
950 if self.compile_options._is_star and (len(self._entities) != 1):
951 raise sa_exc.CompileError(
952 "Can't generate ORM query that includes multiple expressions "
953 "at the same time as '*'; query for '*' alone if present"
954 )
955
956 for entity in self._entities:
957 entity.setup_dml_returning_compile_state(self, adapter)
958
959
960class FromStatement(GroupedElement, Generative, TypedReturnsRows[Unpack[_Ts]]):
961 """Core construct that represents a load of ORM objects from various
962 :class:`.ReturnsRows` and other classes including:
963
964 :class:`.Select`, :class:`.TextClause`, :class:`.TextualSelect`,
965 :class:`.CompoundSelect`, :class`.Insert`, :class:`.Update`,
966 and in theory, :class:`.Delete`.
967
968 """
969
970 __visit_name__ = "orm_from_statement"
971
972 _compile_options = _ORMFromStatementCompileState.default_compile_options
973
974 _compile_state_factory = _ORMFromStatementCompileState.create_for_statement
975
976 _for_update_arg = None
977
978 element: Union[ExecutableReturnsRows, TextClause]
979
980 _adapt_on_names: bool
981
982 _traverse_internals = [
983 ("_raw_columns", InternalTraversal.dp_clauseelement_list),
984 ("element", InternalTraversal.dp_clauseelement),
985 ] + ExecutableStatement._executable_traverse_internals
986
987 _cache_key_traversal = _traverse_internals + [
988 ("_compile_options", InternalTraversal.dp_has_cache_key)
989 ]
990
991 is_from_statement = True
992
993 def __init__(
994 self,
995 entities: Iterable[_ColumnsClauseArgument[Any]],
996 element: Union[ExecutableReturnsRows, TextClause],
997 _adapt_on_names: bool = True,
998 ):
999 self._raw_columns = [
1000 coercions.expect(
1001 roles.ColumnsClauseRole,
1002 ent,
1003 apply_propagate_attrs=self,
1004 post_inspect=True,
1005 )
1006 for ent in util.to_list(entities)
1007 ]
1008 self.element = element
1009 self.is_dml = element.is_dml
1010 self.is_select = element.is_select
1011 self.is_delete = element.is_delete
1012 self.is_insert = element.is_insert
1013 self.is_update = element.is_update
1014 self._label_style = (
1015 element._label_style if is_select_base(element) else None
1016 )
1017 self._adapt_on_names = _adapt_on_names
1018
1019 def _compiler_dispatch(self, compiler, **kw):
1020 """provide a fixed _compiler_dispatch method.
1021
1022 This is roughly similar to using the sqlalchemy.ext.compiler
1023 ``@compiles`` extension.
1024
1025 """
1026
1027 compile_state = self._compile_state_factory(self, compiler, **kw)
1028
1029 toplevel = not compiler.stack
1030
1031 if toplevel:
1032 compiler.compile_state = compile_state
1033
1034 return compiler.process(compile_state.statement, **kw)
1035
1036 @property
1037 def column_descriptions(self):
1038 """Return a :term:`plugin-enabled` 'column descriptions' structure
1039 referring to the columns which are SELECTed by this statement.
1040
1041 See the section :ref:`queryguide_inspection` for an overview
1042 of this feature.
1043
1044 .. seealso::
1045
1046 :ref:`queryguide_inspection` - ORM background
1047
1048 """
1049 meth = cast(
1050 _ORMSelectCompileState, SelectState.get_plugin_class(self)
1051 ).get_column_descriptions
1052 return meth(self)
1053
1054 def _ensure_disambiguated_names(self):
1055 return self
1056
1057 def get_children(self, **kw):
1058 yield from itertools.chain.from_iterable(
1059 element._from_objects for element in self._raw_columns
1060 )
1061 yield from super().get_children(**kw)
1062
1063 @property
1064 def _all_selected_columns(self):
1065 return self.element._all_selected_columns
1066
1067 @property
1068 def _return_defaults(self):
1069 return self.element._return_defaults if is_dml(self.element) else None
1070
1071 @property
1072 def _returning(self):
1073 return self.element._returning if is_dml(self.element) else None
1074
1075 @property
1076 def _inline(self):
1077 return self.element._inline if is_insert_update(self.element) else None
1078
1079
1080@sql.base.CompileState.plugin_for("orm", "compound_select")
1081class _CompoundSelectCompileState(
1082 _AutoflushOnlyORMCompileState, CompoundSelectState
1083):
1084 pass
1085
1086
1087@sql.base.CompileState.plugin_for("orm", "select")
1088class _ORMSelectCompileState(_ORMCompileState, SelectState):
1089 _already_joined_edges = ()
1090
1091 _memoized_entities = _EMPTY_DICT
1092
1093 _from_obj_alias = None
1094 _has_mapper_entities = False
1095
1096 _has_orm_entities = False
1097 multi_row_eager_loaders = False
1098 eager_adding_joins = False
1099 compound_eager_adapter = None
1100
1101 correlate = None
1102 correlate_except = None
1103 _where_criteria = ()
1104 _having_criteria = ()
1105
1106 @classmethod
1107 def _create_orm_context(
1108 cls,
1109 statement: Union[Select, FromStatement],
1110 *,
1111 toplevel: bool,
1112 compiler: Optional[SQLCompiler],
1113 **kw: Any,
1114 ) -> _ORMSelectCompileState:
1115
1116 self = cls.__new__(cls)
1117
1118 select_statement = statement
1119
1120 # if we are a select() that was never a legacy Query, we won't
1121 # have ORM level compile options.
1122 statement._compile_options = cls.default_compile_options.safe_merge(
1123 statement._compile_options
1124 )
1125
1126 if select_statement._execution_options:
1127 # execution options should not impact the compilation of a
1128 # query, and at the moment subqueryloader is putting some things
1129 # in here that we explicitly don't want stuck in a cache.
1130 self.select_statement = select_statement._clone()
1131 self.select_statement._execution_options = util.immutabledict()
1132 else:
1133 self.select_statement = select_statement
1134
1135 # indicates this select() came from Query.statement
1136 self.for_statement = select_statement._compile_options._for_statement
1137
1138 # generally if we are from Query or directly from a select()
1139 self.use_legacy_query_style = (
1140 select_statement._compile_options._use_legacy_query_style
1141 )
1142
1143 self._entities = []
1144 self._primary_entity = None
1145 self._polymorphic_adapters = {}
1146
1147 self.compile_options = select_statement._compile_options
1148
1149 if not toplevel:
1150 # for subqueries, turn off eagerloads and set
1151 # "render_for_subquery".
1152 self.compile_options += {
1153 "_enable_eagerloads": False,
1154 "_render_for_subquery": True,
1155 }
1156
1157 # determine label style. we can make different decisions here.
1158 # at the moment, trying to see if we can always use DISAMBIGUATE_ONLY
1159 # rather than LABEL_STYLE_NONE, and if we can use disambiguate style
1160 # for new style ORM selects too.
1161 if (
1162 self.use_legacy_query_style
1163 and self.select_statement._label_style is LABEL_STYLE_LEGACY_ORM
1164 ):
1165 if not self.for_statement:
1166 self.label_style = LABEL_STYLE_TABLENAME_PLUS_COL
1167 else:
1168 self.label_style = LABEL_STYLE_DISAMBIGUATE_ONLY
1169 else:
1170 self.label_style = self.select_statement._label_style
1171
1172 if select_statement._memoized_select_entities:
1173 self._memoized_entities = {
1174 memoized_entities: _QueryEntity.to_compile_state(
1175 self,
1176 memoized_entities._raw_columns,
1177 [],
1178 is_current_entities=False,
1179 )
1180 for memoized_entities in (
1181 select_statement._memoized_select_entities
1182 )
1183 }
1184
1185 # label_convention is stateful and will yield deduping keys if it
1186 # sees the same key twice. therefore it's important that it is not
1187 # invoked for the above "memoized" entities that aren't actually
1188 # in the columns clause
1189 self._label_convention = self._column_naming_convention(
1190 statement._label_style, self.use_legacy_query_style
1191 )
1192
1193 _QueryEntity.to_compile_state(
1194 self,
1195 select_statement._raw_columns,
1196 self._entities,
1197 is_current_entities=True,
1198 )
1199
1200 self.current_path = select_statement._compile_options._current_path
1201
1202 self.eager_order_by = ()
1203
1204 self._init_global_attributes(
1205 select_statement,
1206 compiler,
1207 toplevel=toplevel,
1208 process_criteria_for_toplevel=False,
1209 )
1210
1211 if toplevel and (
1212 select_statement._with_options
1213 or select_statement._memoized_select_entities
1214 ):
1215 for (
1216 memoized_entities
1217 ) in select_statement._memoized_select_entities:
1218 for opt in memoized_entities._with_options:
1219 if opt._is_compile_state:
1220 opt.process_compile_state_replaced_entities(
1221 self,
1222 [
1223 ent
1224 for ent in self._memoized_entities[
1225 memoized_entities
1226 ]
1227 if isinstance(ent, _MapperEntity)
1228 ],
1229 )
1230
1231 for opt in self.select_statement._with_options:
1232 if opt._is_compile_state:
1233 opt.process_compile_state(self)
1234
1235 # uncomment to print out the context.attributes structure
1236 # after it's been set up above
1237 # self._dump_option_struct()
1238
1239 if select_statement._compile_state_funcs:
1240 for fn, key in select_statement._compile_state_funcs:
1241 fn(self)
1242
1243 self.primary_columns = []
1244 self.secondary_columns = []
1245 self.dedupe_columns = set()
1246 self.eager_joins = {}
1247 self.extra_criteria_entities = {}
1248 self.create_eager_joins = []
1249 self._fallback_from_clauses = []
1250
1251 # normalize the FROM clauses early by themselves, as this makes
1252 # it an easier job when we need to assemble a JOIN onto these,
1253 # for select.join() as well as joinedload(). As of 1.4 there are now
1254 # potentially more complex sets of FROM objects here as the use
1255 # of lambda statements for lazyload, load_on_pk etc. uses more
1256 # cloning of the select() construct. See #6495
1257 self.from_clauses = self._normalize_froms(
1258 info.selectable for info in select_statement._from_obj
1259 )
1260
1261 # this is a fairly arbitrary break into a second method,
1262 # so it might be nicer to break up create_for_statement()
1263 # and _setup_for_generate into three or four logical sections
1264 self._setup_for_generate()
1265
1266 SelectState.__init__(self, self.statement, compiler, **kw)
1267 return self
1268
1269 def _dump_option_struct(self):
1270 print("\n---------------------------------------------------\n")
1271 print(f"current path: {self.current_path}")
1272 for key in self.attributes:
1273 if isinstance(key, tuple) and key[0] == "loader":
1274 print(f"\nLoader: {PathRegistry.coerce(key[1])}")
1275 print(f" {self.attributes[key]}")
1276 print(f" {self.attributes[key].__dict__}")
1277 elif isinstance(key, tuple) and key[0] == "path_with_polymorphic":
1278 print(f"\nWith Polymorphic: {PathRegistry.coerce(key[1])}")
1279 print(f" {self.attributes[key]}")
1280
1281 def _setup_for_generate(self):
1282 query = self.select_statement
1283
1284 self.statement = None
1285 self._join_entities = ()
1286
1287 if self.compile_options._set_base_alias:
1288 # legacy Query only
1289 self._set_select_from_alias()
1290
1291 for memoized_entities in query._memoized_select_entities:
1292 if memoized_entities._setup_joins:
1293 self._join(
1294 memoized_entities._setup_joins,
1295 self._memoized_entities[memoized_entities],
1296 )
1297
1298 if query._setup_joins:
1299 self._join(query._setup_joins, self._entities)
1300
1301 current_adapter = self._get_current_adapter()
1302
1303 if query._where_criteria:
1304 self._where_criteria = query._where_criteria
1305
1306 if current_adapter:
1307 self._where_criteria = tuple(
1308 current_adapter(crit, True)
1309 for crit in self._where_criteria
1310 )
1311
1312 # TODO: some complexity with order_by here was due to mapper.order_by.
1313 # now that this is removed we can hopefully make order_by /
1314 # group_by act identically to how they are in Core select.
1315 self.order_by = (
1316 self._adapt_col_list(query._order_by_clauses, current_adapter)
1317 if current_adapter and query._order_by_clauses not in (None, False)
1318 else query._order_by_clauses
1319 )
1320
1321 if query._having_criteria:
1322 self._having_criteria = tuple(
1323 current_adapter(crit, True) if current_adapter else crit
1324 for crit in query._having_criteria
1325 )
1326
1327 self.group_by = (
1328 self._adapt_col_list(
1329 util.flatten_iterator(query._group_by_clauses), current_adapter
1330 )
1331 if current_adapter and query._group_by_clauses not in (None, False)
1332 else query._group_by_clauses or None
1333 )
1334
1335 if self.eager_order_by:
1336 adapter = self.from_clauses[0]._target_adapter
1337 self.eager_order_by = adapter.copy_and_process(self.eager_order_by)
1338
1339 if query._distinct_on:
1340 self.distinct_on = self._adapt_col_list(
1341 query._distinct_on, current_adapter
1342 )
1343 else:
1344 self.distinct_on = ()
1345
1346 self.distinct = query._distinct
1347
1348 self.syntax_extensions = {
1349 key: current_adapter(value, True) if current_adapter else value
1350 for key, value in query._get_syntax_extensions_as_dict().items()
1351 }
1352
1353 if query._correlate:
1354 # ORM mapped entities that are mapped to joins can be passed
1355 # to .correlate, so here they are broken into their component
1356 # tables.
1357 self.correlate = tuple(
1358 util.flatten_iterator(
1359 sql_util.surface_selectables(s) if s is not None else None
1360 for s in query._correlate
1361 )
1362 )
1363 elif query._correlate_except is not None:
1364 self.correlate_except = tuple(
1365 util.flatten_iterator(
1366 sql_util.surface_selectables(s) if s is not None else None
1367 for s in query._correlate_except
1368 )
1369 )
1370 elif not query._auto_correlate:
1371 self.correlate = (None,)
1372
1373 # PART II
1374
1375 self._for_update_arg = query._for_update_arg
1376
1377 if self.compile_options._is_star and (len(self._entities) != 1):
1378 raise sa_exc.CompileError(
1379 "Can't generate ORM query that includes multiple expressions "
1380 "at the same time as '*'; query for '*' alone if present"
1381 )
1382 for entity in self._entities:
1383 entity.setup_compile_state(self)
1384
1385 for rec in self.create_eager_joins:
1386 strategy = rec[0]
1387 strategy(self, *rec[1:])
1388
1389 # else "load from discrete FROMs" mode,
1390 # i.e. when each _MappedEntity has its own FROM
1391
1392 if self.compile_options._enable_single_crit:
1393 self._adjust_for_extra_criteria()
1394
1395 if not self.primary_columns:
1396 if self.compile_options._only_load_props:
1397 assert False, "no columns were included in _only_load_props"
1398
1399 raise sa_exc.InvalidRequestError(
1400 "Query contains no columns with which to SELECT from."
1401 )
1402
1403 if not self.from_clauses:
1404 self.from_clauses = list(self._fallback_from_clauses)
1405
1406 if self.order_by is False:
1407 self.order_by = None
1408
1409 if self._should_nest_selectable:
1410 self.statement = self._compound_eager_statement()
1411 else:
1412 self.statement = self._simple_statement()
1413
1414 if self.for_statement:
1415 ezero = self._mapper_zero()
1416 if ezero is not None:
1417 # TODO: this goes away once we get rid of the deep entity
1418 # thing
1419 self.statement = self.statement._annotate(
1420 {"deepentity": ezero}
1421 )
1422
1423 @classmethod
1424 def _create_entities_collection(cls, query, legacy):
1425 """Creates a partial ORMSelectCompileState that includes
1426 the full collection of _MapperEntity and other _QueryEntity objects.
1427
1428 Supports a few remaining use cases that are pre-compilation
1429 but still need to gather some of the column / adaption information.
1430
1431 """
1432 self = cls.__new__(cls)
1433
1434 self._entities = []
1435 self._primary_entity = None
1436 self._polymorphic_adapters = {}
1437
1438 self._label_convention = self._column_naming_convention(
1439 query._label_style, legacy
1440 )
1441
1442 # entities will also set up polymorphic adapters for mappers
1443 # that have with_polymorphic configured
1444 _QueryEntity.to_compile_state(
1445 self, query._raw_columns, self._entities, is_current_entities=True
1446 )
1447 return self
1448
1449 @classmethod
1450 def determine_last_joined_entity(cls, statement):
1451 setup_joins = statement._setup_joins
1452
1453 return _determine_last_joined_entity(setup_joins, None)
1454
1455 @classmethod
1456 def all_selected_columns(cls, statement):
1457 for element in statement._raw_columns:
1458 if (
1459 element.is_selectable
1460 and "entity_namespace" in element._annotations
1461 ):
1462 ens = element._annotations["entity_namespace"]
1463 if not ens.is_mapper and not ens.is_aliased_class:
1464 yield from _select_iterables([element])
1465 else:
1466 yield from _select_iterables(ens._all_column_expressions)
1467 else:
1468 yield from _select_iterables([element])
1469
1470 @classmethod
1471 def get_columns_clause_froms(cls, statement):
1472 return cls._normalize_froms(
1473 itertools.chain.from_iterable(
1474 (
1475 element._from_objects
1476 if "parententity" not in element._annotations
1477 else [
1478 element._annotations[
1479 "parententity"
1480 ].__clause_element__()
1481 ]
1482 )
1483 for element in statement._raw_columns
1484 )
1485 )
1486
1487 @classmethod
1488 def from_statement(cls, statement, from_statement):
1489 from_statement = coercions.expect(
1490 roles.ReturnsRowsRole,
1491 from_statement,
1492 apply_propagate_attrs=statement,
1493 )
1494
1495 stmt = FromStatement(statement._raw_columns, from_statement)
1496
1497 stmt.__dict__.update(
1498 _with_options=statement._with_options,
1499 _compile_state_funcs=statement._compile_state_funcs,
1500 _execution_options=statement._execution_options,
1501 _propagate_attrs=statement._propagate_attrs,
1502 )
1503 return stmt
1504
1505 def _set_select_from_alias(self):
1506 """used only for legacy Query cases"""
1507
1508 query = self.select_statement # query
1509
1510 assert self.compile_options._set_base_alias
1511 assert len(query._from_obj) == 1
1512
1513 adapter = self._get_select_from_alias_from_obj(query._from_obj[0])
1514 if adapter:
1515 self.compile_options += {"_enable_single_crit": False}
1516 self._from_obj_alias = adapter
1517
1518 def _get_select_from_alias_from_obj(self, from_obj):
1519 """used only for legacy Query cases"""
1520
1521 info = from_obj
1522
1523 if "parententity" in info._annotations:
1524 info = info._annotations["parententity"]
1525
1526 if hasattr(info, "mapper"):
1527 if not info.is_aliased_class:
1528 raise sa_exc.ArgumentError(
1529 "A selectable (FromClause) instance is "
1530 "expected when the base alias is being set."
1531 )
1532 else:
1533 return info._adapter
1534
1535 elif isinstance(info.selectable, sql.selectable.AliasedReturnsRows):
1536 equivs = self._all_equivs()
1537 assert info is info.selectable
1538 return ORMStatementAdapter(
1539 _TraceAdaptRole.LEGACY_SELECT_FROM_ALIAS,
1540 info.selectable,
1541 equivalents=equivs,
1542 )
1543 else:
1544 return None
1545
1546 def _mapper_zero(self):
1547 """return the Mapper associated with the first QueryEntity."""
1548 return self._entities[0].mapper
1549
1550 def _entity_zero(self):
1551 """Return the 'entity' (mapper or AliasedClass) associated
1552 with the first QueryEntity, or alternatively the 'select from'
1553 entity if specified."""
1554
1555 for ent in self.from_clauses:
1556 if "parententity" in ent._annotations:
1557 return ent._annotations["parententity"]
1558 for qent in self._entities:
1559 if qent.entity_zero:
1560 return qent.entity_zero
1561
1562 return None
1563
1564 def _only_full_mapper_zero(self, methname):
1565 if self._entities != [self._primary_entity]:
1566 raise sa_exc.InvalidRequestError(
1567 "%s() can only be used against "
1568 "a single mapped class." % methname
1569 )
1570 return self._primary_entity.entity_zero
1571
1572 def _only_entity_zero(self, rationale=None):
1573 if len(self._entities) > 1:
1574 raise sa_exc.InvalidRequestError(
1575 rationale
1576 or "This operation requires a Query "
1577 "against a single mapper."
1578 )
1579 return self._entity_zero()
1580
1581 def _all_equivs(self):
1582 equivs = {}
1583
1584 for memoized_entities in self._memoized_entities.values():
1585 for ent in [
1586 ent
1587 for ent in memoized_entities
1588 if isinstance(ent, _MapperEntity)
1589 ]:
1590 equivs.update(ent.mapper._equivalent_columns)
1591
1592 for ent in [
1593 ent for ent in self._entities if isinstance(ent, _MapperEntity)
1594 ]:
1595 equivs.update(ent.mapper._equivalent_columns)
1596 return equivs
1597
1598 def _compound_eager_statement(self):
1599 # for eager joins present and LIMIT/OFFSET/DISTINCT,
1600 # wrap the query inside a select,
1601 # then append eager joins onto that
1602
1603 if self.order_by:
1604 # the default coercion for ORDER BY is now the OrderByRole,
1605 # which adds an additional post coercion to ByOfRole in that
1606 # elements are converted into label references. For the
1607 # eager load / subquery wrapping case, we need to un-coerce
1608 # the original expressions outside of the label references
1609 # in order to have them render.
1610 unwrapped_order_by = [
1611 (
1612 elem.element
1613 if isinstance(elem, sql.elements._label_reference)
1614 else elem
1615 )
1616 for elem in self.order_by
1617 ]
1618
1619 order_by_col_expr = sql_util.expand_column_list_from_order_by(
1620 self.primary_columns, unwrapped_order_by
1621 )
1622 else:
1623 order_by_col_expr = []
1624 unwrapped_order_by = None
1625
1626 # put FOR UPDATE on the inner query, where MySQL will honor it,
1627 # as well as if it has an OF so PostgreSQL can use it.
1628 inner = self._select_statement(
1629 self.primary_columns
1630 + [c for c in order_by_col_expr if c not in self.dedupe_columns],
1631 self.from_clauses,
1632 self._where_criteria,
1633 self._having_criteria,
1634 self.label_style,
1635 self.order_by,
1636 for_update=self._for_update_arg,
1637 hints=self.select_statement._hints,
1638 statement_hints=self.select_statement._statement_hints,
1639 correlate=self.correlate,
1640 correlate_except=self.correlate_except,
1641 **self._select_args,
1642 )
1643
1644 inner = inner.alias()
1645
1646 equivs = self._all_equivs()
1647
1648 self.compound_eager_adapter = ORMStatementAdapter(
1649 _TraceAdaptRole.COMPOUND_EAGER_STATEMENT, inner, equivalents=equivs
1650 )
1651
1652 statement = future.select(
1653 *([inner] + self.secondary_columns) # use_labels=self.labels
1654 )
1655 statement._label_style = self.label_style
1656
1657 # Oracle Database however does not allow FOR UPDATE on the subquery,
1658 # and the Oracle Database dialects ignore it, plus for PostgreSQL,
1659 # MySQL we expect that all elements of the row are locked, so also put
1660 # it on the outside (except in the case of PG when OF is used)
1661 if (
1662 self._for_update_arg is not None
1663 and self._for_update_arg.of is None
1664 ):
1665 statement._for_update_arg = self._for_update_arg
1666
1667 from_clause = inner
1668 for eager_join in self.eager_joins.values():
1669 # EagerLoader places a 'stop_on' attribute on the join,
1670 # giving us a marker as to where the "splice point" of
1671 # the join should be
1672 from_clause = sql_util.splice_joins(
1673 from_clause, eager_join, eager_join.stop_on
1674 )
1675
1676 statement.select_from.non_generative(statement, from_clause)
1677
1678 if unwrapped_order_by:
1679 statement.order_by.non_generative(
1680 statement,
1681 *self.compound_eager_adapter.copy_and_process(
1682 unwrapped_order_by
1683 ),
1684 )
1685
1686 statement.order_by.non_generative(statement, *self.eager_order_by)
1687 return statement
1688
1689 def _simple_statement(self):
1690 statement = self._select_statement(
1691 self.primary_columns + self.secondary_columns,
1692 tuple(self.from_clauses) + tuple(self.eager_joins.values()),
1693 self._where_criteria,
1694 self._having_criteria,
1695 self.label_style,
1696 self.order_by,
1697 for_update=self._for_update_arg,
1698 hints=self.select_statement._hints,
1699 statement_hints=self.select_statement._statement_hints,
1700 correlate=self.correlate,
1701 correlate_except=self.correlate_except,
1702 **self._select_args,
1703 )
1704
1705 if self.eager_order_by:
1706 statement.order_by.non_generative(statement, *self.eager_order_by)
1707 return statement
1708
1709 def _select_statement(
1710 self,
1711 raw_columns,
1712 from_obj,
1713 where_criteria,
1714 having_criteria,
1715 label_style,
1716 order_by,
1717 for_update,
1718 hints,
1719 statement_hints,
1720 correlate,
1721 correlate_except,
1722 limit_clause,
1723 offset_clause,
1724 fetch_clause,
1725 fetch_clause_options,
1726 distinct,
1727 distinct_on,
1728 prefixes,
1729 suffixes,
1730 group_by,
1731 independent_ctes,
1732 independent_ctes_opts,
1733 syntax_extensions,
1734 ):
1735 statement = Select._create_raw_select(
1736 _raw_columns=raw_columns,
1737 _from_obj=from_obj,
1738 _label_style=label_style,
1739 )
1740
1741 if where_criteria:
1742 statement._where_criteria = where_criteria
1743 if having_criteria:
1744 statement._having_criteria = having_criteria
1745
1746 if order_by:
1747 statement._order_by_clauses += tuple(order_by)
1748
1749 if distinct_on:
1750 statement._distinct = True
1751 statement._distinct_on = distinct_on
1752 elif distinct:
1753 statement._distinct = True
1754
1755 if group_by:
1756 statement._group_by_clauses += tuple(group_by)
1757
1758 statement._limit_clause = limit_clause
1759 statement._offset_clause = offset_clause
1760 statement._fetch_clause = fetch_clause
1761 statement._fetch_clause_options = fetch_clause_options
1762 statement._independent_ctes = independent_ctes
1763 statement._independent_ctes_opts = independent_ctes_opts
1764 if syntax_extensions:
1765 statement._set_syntax_extensions(**syntax_extensions)
1766
1767 if prefixes:
1768 statement._prefixes = prefixes
1769
1770 if suffixes:
1771 statement._suffixes = suffixes
1772
1773 statement._for_update_arg = for_update
1774
1775 if hints:
1776 statement._hints = hints
1777 if statement_hints:
1778 statement._statement_hints = statement_hints
1779
1780 if correlate:
1781 statement.correlate.non_generative(statement, *correlate)
1782
1783 if correlate_except is not None:
1784 statement.correlate_except.non_generative(
1785 statement, *correlate_except
1786 )
1787
1788 return statement
1789
1790 def _adapt_polymorphic_element(self, element):
1791 if "parententity" in element._annotations:
1792 search = element._annotations["parententity"]
1793 alias = self._polymorphic_adapters.get(search, None)
1794 if alias:
1795 return alias.adapt_clause(element)
1796
1797 if isinstance(element, expression.FromClause):
1798 search = element
1799 elif hasattr(element, "table"):
1800 search = element.table
1801 else:
1802 return None
1803
1804 alias = self._polymorphic_adapters.get(search, None)
1805 if alias:
1806 return alias.adapt_clause(element)
1807
1808 def _adapt_col_list(self, cols, current_adapter):
1809 if current_adapter:
1810 return [current_adapter(o, True) for o in cols]
1811 else:
1812 return cols
1813
1814 def _get_current_adapter(self):
1815 adapters = []
1816
1817 if self._from_obj_alias:
1818 # used for legacy going forward for query set_ops, e.g.
1819 # union(), union_all(), etc.
1820 # 1.4 and previously, also used for from_self(),
1821 # select_entity_from()
1822 #
1823 # for the "from obj" alias, apply extra rule to the
1824 # 'ORM only' check, if this query were generated from a
1825 # subquery of itself, i.e. _from_selectable(), apply adaption
1826 # to all SQL constructs.
1827 adapters.append(
1828 self._from_obj_alias.replace,
1829 )
1830
1831 # this was *hopefully* the only adapter we were going to need
1832 # going forward...however, we unfortunately need _from_obj_alias
1833 # for query.union(), which we can't drop
1834 if self._polymorphic_adapters:
1835 adapters.append(self._adapt_polymorphic_element)
1836
1837 if not adapters:
1838 return None
1839
1840 def _adapt_clause(clause, as_filter):
1841 # do we adapt all expression elements or only those
1842 # tagged as 'ORM' constructs ?
1843
1844 def replace(elem):
1845 for adapter in adapters:
1846 e = adapter(elem)
1847 if e is not None:
1848 return e
1849
1850 return visitors.replacement_traverse(clause, {}, replace)
1851
1852 return _adapt_clause
1853
1854 def _join(self, args, entities_collection):
1855 for right, onclause, from_, flags in args:
1856 isouter = flags["isouter"]
1857 full = flags["full"]
1858
1859 right = inspect(right)
1860 if onclause is not None:
1861 onclause = inspect(onclause)
1862
1863 if isinstance(right, interfaces.PropComparator):
1864 if onclause is not None:
1865 raise sa_exc.InvalidRequestError(
1866 "No 'on clause' argument may be passed when joining "
1867 "to a relationship path as a target"
1868 )
1869
1870 onclause = right
1871 right = None
1872 elif "parententity" in right._annotations:
1873 right = right._annotations["parententity"]
1874
1875 if onclause is None:
1876 if not right.is_selectable and not hasattr(right, "mapper"):
1877 raise sa_exc.ArgumentError(
1878 "Expected mapped entity or "
1879 "selectable/table as join target"
1880 )
1881
1882 if isinstance(onclause, interfaces.PropComparator):
1883 # descriptor/property given (or determined); this tells us
1884 # explicitly what the expected "left" side of the join is.
1885
1886 of_type = getattr(onclause, "_of_type", None)
1887
1888 if right is None:
1889 if of_type:
1890 right = of_type
1891 else:
1892 right = onclause.property
1893
1894 try:
1895 right = right.entity
1896 except AttributeError as err:
1897 raise sa_exc.ArgumentError(
1898 "Join target %s does not refer to a "
1899 "mapped entity" % right
1900 ) from err
1901
1902 left = onclause._parententity
1903
1904 prop = onclause.property
1905 if not isinstance(onclause, attributes.QueryableAttribute):
1906 onclause = prop
1907
1908 # check for this path already present. don't render in that
1909 # case.
1910 if (left, right, prop.key) in self._already_joined_edges:
1911 continue
1912
1913 if from_ is not None:
1914 if (
1915 from_ is not left
1916 and from_._annotations.get("parententity", None)
1917 is not left
1918 ):
1919 raise sa_exc.InvalidRequestError(
1920 "explicit from clause %s does not match left side "
1921 "of relationship attribute %s"
1922 % (
1923 from_._annotations.get("parententity", from_),
1924 onclause,
1925 )
1926 )
1927 elif from_ is not None:
1928 prop = None
1929 left = from_
1930 else:
1931 # no descriptor/property given; we will need to figure out
1932 # what the effective "left" side is
1933 prop = left = None
1934
1935 # figure out the final "left" and "right" sides and create an
1936 # ORMJoin to add to our _from_obj tuple
1937 self._join_left_to_right(
1938 entities_collection,
1939 left,
1940 right,
1941 onclause,
1942 prop,
1943 isouter,
1944 full,
1945 )
1946
1947 def _join_left_to_right(
1948 self,
1949 entities_collection,
1950 left,
1951 right,
1952 onclause,
1953 prop,
1954 outerjoin,
1955 full,
1956 ):
1957 """given raw "left", "right", "onclause" parameters consumed from
1958 a particular key within _join(), add a real ORMJoin object to
1959 our _from_obj list (or augment an existing one)
1960
1961 """
1962
1963 explicit_left = left
1964 if left is None:
1965 # left not given (e.g. no relationship object/name specified)
1966 # figure out the best "left" side based on our existing froms /
1967 # entities
1968 assert prop is None
1969 (
1970 left,
1971 replace_from_obj_index,
1972 use_entity_index,
1973 ) = self._join_determine_implicit_left_side(
1974 entities_collection, left, right, onclause
1975 )
1976 else:
1977 # left is given via a relationship/name, or as explicit left side.
1978 # Determine where in our
1979 # "froms" list it should be spliced/appended as well as what
1980 # existing entity it corresponds to.
1981 (
1982 replace_from_obj_index,
1983 use_entity_index,
1984 ) = self._join_place_explicit_left_side(entities_collection, left)
1985
1986 if left is right:
1987 raise sa_exc.InvalidRequestError(
1988 "Can't construct a join from %s to %s, they "
1989 "are the same entity" % (left, right)
1990 )
1991
1992 # the right side as given often needs to be adapted. additionally
1993 # a lot of things can be wrong with it. handle all that and
1994 # get back the new effective "right" side
1995 r_info, right, onclause = self._join_check_and_adapt_right_side(
1996 left, right, onclause, prop
1997 )
1998
1999 if not r_info.is_selectable:
2000 extra_criteria = self._get_extra_criteria(r_info)
2001 else:
2002 extra_criteria = ()
2003
2004 if replace_from_obj_index is not None:
2005 # splice into an existing element in the
2006 # self._from_obj list
2007 left_clause = self.from_clauses[replace_from_obj_index]
2008
2009 if explicit_left is not None and onclause is None:
2010 onclause = _ORMJoin._join_condition(explicit_left, right)
2011
2012 self.from_clauses = (
2013 self.from_clauses[:replace_from_obj_index]
2014 + [
2015 _ORMJoin(
2016 left_clause,
2017 right,
2018 onclause,
2019 isouter=outerjoin,
2020 full=full,
2021 _extra_criteria=extra_criteria,
2022 )
2023 ]
2024 + self.from_clauses[replace_from_obj_index + 1 :]
2025 )
2026 else:
2027 # add a new element to the self._from_obj list
2028 if use_entity_index is not None:
2029 # make use of _MapperEntity selectable, which is usually
2030 # entity_zero.selectable, but if with_polymorphic() were used
2031 # might be distinct
2032 assert isinstance(
2033 entities_collection[use_entity_index], _MapperEntity
2034 )
2035 left_clause = entities_collection[use_entity_index].selectable
2036 else:
2037 left_clause = left
2038
2039 self.from_clauses = self.from_clauses + [
2040 _ORMJoin(
2041 left_clause,
2042 r_info,
2043 onclause,
2044 isouter=outerjoin,
2045 full=full,
2046 _extra_criteria=extra_criteria,
2047 )
2048 ]
2049
2050 def _join_determine_implicit_left_side(
2051 self, entities_collection, left, right, onclause
2052 ):
2053 """When join conditions don't express the left side explicitly,
2054 determine if an existing FROM or entity in this query
2055 can serve as the left hand side.
2056
2057 """
2058
2059 # when we are here, it means join() was called without an ORM-
2060 # specific way of telling us what the "left" side is, e.g.:
2061 #
2062 # join(RightEntity)
2063 #
2064 # or
2065 #
2066 # join(RightEntity, RightEntity.foo == LeftEntity.bar)
2067 #
2068
2069 r_info = inspect(right)
2070
2071 replace_from_obj_index = use_entity_index = None
2072
2073 if self.from_clauses:
2074 # we have a list of FROMs already. So by definition this
2075 # join has to connect to one of those FROMs.
2076
2077 indexes = sql_util.find_left_clause_to_join_from(
2078 self.from_clauses, r_info.selectable, onclause
2079 )
2080
2081 if len(indexes) == 1:
2082 replace_from_obj_index = indexes[0]
2083 left = self.from_clauses[replace_from_obj_index]
2084 elif len(indexes) > 1:
2085 raise sa_exc.InvalidRequestError(
2086 "Can't determine which FROM clause to join "
2087 "from, there are multiple FROMS which can "
2088 "join to this entity. Please use the .select_from() "
2089 "method to establish an explicit left side, as well as "
2090 "providing an explicit ON clause if not present already "
2091 "to help resolve the ambiguity."
2092 )
2093 else:
2094 raise sa_exc.InvalidRequestError(
2095 "Don't know how to join to %r. "
2096 "Please use the .select_from() "
2097 "method to establish an explicit left side, as well as "
2098 "providing an explicit ON clause if not present already "
2099 "to help resolve the ambiguity." % (right,)
2100 )
2101
2102 elif entities_collection:
2103 # we have no explicit FROMs, so the implicit left has to
2104 # come from our list of entities.
2105
2106 potential = {}
2107 for entity_index, ent in enumerate(entities_collection):
2108 entity = ent.entity_zero_or_selectable
2109 if entity is None:
2110 continue
2111 ent_info = inspect(entity)
2112 if ent_info is r_info: # left and right are the same, skip
2113 continue
2114
2115 # by using a dictionary with the selectables as keys this
2116 # de-duplicates those selectables as occurs when the query is
2117 # against a series of columns from the same selectable
2118 if isinstance(ent, _MapperEntity):
2119 potential[ent.selectable] = (entity_index, entity)
2120 else:
2121 potential[ent_info.selectable] = (None, entity)
2122
2123 all_clauses = list(potential.keys())
2124 indexes = sql_util.find_left_clause_to_join_from(
2125 all_clauses, r_info.selectable, onclause
2126 )
2127
2128 if len(indexes) == 1:
2129 use_entity_index, left = potential[all_clauses[indexes[0]]]
2130 elif len(indexes) > 1:
2131 raise sa_exc.InvalidRequestError(
2132 "Can't determine which FROM clause to join "
2133 "from, there are multiple FROMS which can "
2134 "join to this entity. Please use the .select_from() "
2135 "method to establish an explicit left side, as well as "
2136 "providing an explicit ON clause if not present already "
2137 "to help resolve the ambiguity."
2138 )
2139 else:
2140 raise sa_exc.InvalidRequestError(
2141 "Don't know how to join to %r. "
2142 "Please use the .select_from() "
2143 "method to establish an explicit left side, as well as "
2144 "providing an explicit ON clause if not present already "
2145 "to help resolve the ambiguity." % (right,)
2146 )
2147 else:
2148 raise sa_exc.InvalidRequestError(
2149 "No entities to join from; please use "
2150 "select_from() to establish the left "
2151 "entity/selectable of this join"
2152 )
2153
2154 return left, replace_from_obj_index, use_entity_index
2155
2156 def _join_place_explicit_left_side(self, entities_collection, left):
2157 """When join conditions express a left side explicitly, determine
2158 where in our existing list of FROM clauses we should join towards,
2159 or if we need to make a new join, and if so is it from one of our
2160 existing entities.
2161
2162 """
2163
2164 # when we are here, it means join() was called with an indicator
2165 # as to an exact left side, which means a path to a
2166 # Relationship was given, e.g.:
2167 #
2168 # join(RightEntity, LeftEntity.right)
2169 #
2170 # or
2171 #
2172 # join(LeftEntity.right)
2173 #
2174 # as well as string forms:
2175 #
2176 # join(RightEntity, "right")
2177 #
2178 # etc.
2179 #
2180
2181 replace_from_obj_index = use_entity_index = None
2182
2183 l_info = inspect(left)
2184 if self.from_clauses:
2185 indexes = sql_util.find_left_clause_that_matches_given(
2186 self.from_clauses, l_info.selectable
2187 )
2188
2189 if len(indexes) > 1:
2190 raise sa_exc.InvalidRequestError(
2191 "Can't identify which entity in which to assign the "
2192 "left side of this join. Please use a more specific "
2193 "ON clause."
2194 )
2195
2196 # have an index, means the left side is already present in
2197 # an existing FROM in the self._from_obj tuple
2198 if indexes:
2199 replace_from_obj_index = indexes[0]
2200
2201 # no index, means we need to add a new element to the
2202 # self._from_obj tuple
2203
2204 # no from element present, so we will have to add to the
2205 # self._from_obj tuple. Determine if this left side matches up
2206 # with existing mapper entities, in which case we want to apply the
2207 # aliasing / adaptation rules present on that entity if any
2208 if (
2209 replace_from_obj_index is None
2210 and entities_collection
2211 and hasattr(l_info, "mapper")
2212 ):
2213 for idx, ent in enumerate(entities_collection):
2214 # TODO: should we be checking for multiple mapper entities
2215 # matching?
2216 if isinstance(ent, _MapperEntity) and ent.corresponds_to(left):
2217 use_entity_index = idx
2218 break
2219
2220 return replace_from_obj_index, use_entity_index
2221
2222 def _join_check_and_adapt_right_side(self, left, right, onclause, prop):
2223 """transform the "right" side of the join as well as the onclause
2224 according to polymorphic mapping translations, aliasing on the query
2225 or on the join, special cases where the right and left side have
2226 overlapping tables.
2227
2228 """
2229
2230 l_info = inspect(left)
2231 r_info = inspect(right)
2232
2233 overlap = False
2234
2235 right_mapper = getattr(r_info, "mapper", None)
2236 # if the target is a joined inheritance mapping,
2237 # be more liberal about auto-aliasing.
2238 if right_mapper and (
2239 right_mapper.with_polymorphic
2240 or isinstance(right_mapper.persist_selectable, expression.Join)
2241 ):
2242 for from_obj in self.from_clauses or [l_info.selectable]:
2243 if sql_util.selectables_overlap(
2244 l_info.selectable, from_obj
2245 ) and sql_util.selectables_overlap(
2246 from_obj, r_info.selectable
2247 ):
2248 overlap = True
2249 break
2250
2251 if overlap and l_info.selectable is r_info.selectable:
2252 raise sa_exc.InvalidRequestError(
2253 "Can't join table/selectable '%s' to itself"
2254 % l_info.selectable
2255 )
2256
2257 right_mapper, right_selectable, right_is_aliased = (
2258 getattr(r_info, "mapper", None),
2259 r_info.selectable,
2260 getattr(r_info, "is_aliased_class", False),
2261 )
2262
2263 if (
2264 right_mapper
2265 and prop
2266 and not right_mapper.common_parent(prop.mapper)
2267 ):
2268 raise sa_exc.InvalidRequestError(
2269 "Join target %s does not correspond to "
2270 "the right side of join condition %s" % (right, onclause)
2271 )
2272
2273 # _join_entities is used as a hint for single-table inheritance
2274 # purposes at the moment
2275 if hasattr(r_info, "mapper"):
2276 self._join_entities += (r_info,)
2277
2278 need_adapter = False
2279
2280 # test for joining to an unmapped selectable as the target
2281 if r_info.is_clause_element:
2282 if prop:
2283 right_mapper = prop.mapper
2284
2285 if right_selectable._is_lateral:
2286 # orm_only is disabled to suit the case where we have to
2287 # adapt an explicit correlate(Entity) - the select() loses
2288 # the ORM-ness in this case right now, ideally it would not
2289 current_adapter = self._get_current_adapter()
2290 if current_adapter is not None:
2291 # TODO: we had orm_only=False here before, removing
2292 # it didn't break things. if we identify the rationale,
2293 # may need to apply "_orm_only" annotation here.
2294 right = current_adapter(right, True)
2295
2296 elif prop:
2297 # joining to selectable with a mapper property given
2298 # as the ON clause
2299
2300 if not right_selectable.is_derived_from(
2301 right_mapper.persist_selectable
2302 ):
2303 raise sa_exc.InvalidRequestError(
2304 "Selectable '%s' is not derived from '%s'"
2305 % (
2306 right_selectable.description,
2307 right_mapper.persist_selectable.description,
2308 )
2309 )
2310
2311 # if the destination selectable is a plain select(),
2312 # turn it into an alias().
2313 if isinstance(right_selectable, expression.SelectBase):
2314 right_selectable = coercions.expect(
2315 roles.FromClauseRole, right_selectable
2316 )
2317 need_adapter = True
2318
2319 # make the right hand side target into an ORM entity
2320 right = AliasedClass(right_mapper, right_selectable)
2321
2322 util.warn_deprecated(
2323 "An alias is being generated automatically against "
2324 "joined entity %s for raw clauseelement, which is "
2325 "deprecated and will be removed in a later release. "
2326 "Use the aliased() "
2327 "construct explicitly, see the linked example."
2328 % right_mapper,
2329 "1.4",
2330 code="xaj1",
2331 )
2332
2333 # test for overlap:
2334 # orm/inheritance/relationships.py
2335 # SelfReferentialM2MTest
2336 aliased_entity = right_mapper and not right_is_aliased and overlap
2337
2338 if not need_adapter and aliased_entity:
2339 # there are a few places in the ORM that automatic aliasing
2340 # is still desirable, and can't be automatic with a Core
2341 # only approach. For illustrations of "overlaps" see
2342 # test/orm/inheritance/test_relationships.py. There are also
2343 # general overlap cases with many-to-many tables where automatic
2344 # aliasing is desirable.
2345 right = AliasedClass(right, flat=True)
2346 need_adapter = True
2347
2348 util.warn(
2349 "An alias is being generated automatically against "
2350 "joined entity %s due to overlapping tables. This is a "
2351 "legacy pattern which may be "
2352 "deprecated in a later release. Use the "
2353 "aliased(<entity>, flat=True) "
2354 "construct explicitly, see the linked example." % right_mapper,
2355 code="xaj2",
2356 )
2357
2358 if need_adapter:
2359 # if need_adapter is True, we are in a deprecated case and
2360 # a warning has been emitted.
2361 assert right_mapper
2362
2363 adapter = ORMAdapter(
2364 _TraceAdaptRole.DEPRECATED_JOIN_ADAPT_RIGHT_SIDE,
2365 inspect(right),
2366 equivalents=right_mapper._equivalent_columns,
2367 )
2368
2369 # if an alias() on the right side was generated,
2370 # which is intended to wrap a the right side in a subquery,
2371 # ensure that columns retrieved from this target in the result
2372 # set are also adapted.
2373 self._mapper_loads_polymorphically_with(right_mapper, adapter)
2374 elif (
2375 not r_info.is_clause_element
2376 and not right_is_aliased
2377 and right_mapper._has_aliased_polymorphic_fromclause
2378 ):
2379 # for the case where the target mapper has a with_polymorphic
2380 # set up, ensure an adapter is set up for criteria that works
2381 # against this mapper. Previously, this logic used to
2382 # use the "create_aliases or aliased_entity" case to generate
2383 # an aliased() object, but this creates an alias that isn't
2384 # strictly necessary.
2385 # see test/orm/test_core_compilation.py
2386 # ::RelNaturalAliasedJoinsTest::test_straight
2387 # and similar
2388 self._mapper_loads_polymorphically_with(
2389 right_mapper,
2390 ORMAdapter(
2391 _TraceAdaptRole.WITH_POLYMORPHIC_ADAPTER_RIGHT_JOIN,
2392 right_mapper,
2393 selectable=right_mapper.selectable,
2394 equivalents=right_mapper._equivalent_columns,
2395 ),
2396 )
2397 # if the onclause is a ClauseElement, adapt it with any
2398 # adapters that are in place right now
2399 if isinstance(onclause, expression.ClauseElement):
2400 current_adapter = self._get_current_adapter()
2401 if current_adapter:
2402 onclause = current_adapter(onclause, True)
2403
2404 # if joining on a MapperProperty path,
2405 # track the path to prevent redundant joins
2406 if prop:
2407 self._already_joined_edges += ((left, right, prop.key),)
2408
2409 return inspect(right), right, onclause
2410
2411 @property
2412 def _select_args(self):
2413 return {
2414 "limit_clause": self.select_statement._limit_clause,
2415 "offset_clause": self.select_statement._offset_clause,
2416 "distinct": self.distinct,
2417 "distinct_on": self.distinct_on,
2418 "prefixes": self.select_statement._prefixes,
2419 "suffixes": self.select_statement._suffixes,
2420 "group_by": self.group_by or None,
2421 "fetch_clause": self.select_statement._fetch_clause,
2422 "fetch_clause_options": (
2423 self.select_statement._fetch_clause_options
2424 ),
2425 "independent_ctes": self.select_statement._independent_ctes,
2426 "independent_ctes_opts": (
2427 self.select_statement._independent_ctes_opts
2428 ),
2429 "syntax_extensions": self.syntax_extensions,
2430 }
2431
2432 @property
2433 def _should_nest_selectable(self):
2434 kwargs = self._select_args
2435
2436 if not self.eager_adding_joins:
2437 return False
2438
2439 return (
2440 (
2441 kwargs.get("limit_clause") is not None
2442 and self.multi_row_eager_loaders
2443 )
2444 or (
2445 kwargs.get("offset_clause") is not None
2446 and self.multi_row_eager_loaders
2447 )
2448 or kwargs.get("distinct", False)
2449 or kwargs.get("distinct_on", ())
2450 or kwargs.get("group_by", False)
2451 )
2452
2453 def _get_extra_criteria(self, ext_info):
2454 if (
2455 "additional_entity_criteria",
2456 ext_info.mapper,
2457 ) in self.global_attributes:
2458 return tuple(
2459 ae._resolve_where_criteria(ext_info)
2460 for ae in self.global_attributes[
2461 ("additional_entity_criteria", ext_info.mapper)
2462 ]
2463 if (ae.include_aliases or ae.entity is ext_info)
2464 and ae._should_include(self)
2465 )
2466 else:
2467 return ()
2468
2469 def _adjust_for_extra_criteria(self):
2470 """Apply extra criteria filtering.
2471
2472 For all distinct single-table-inheritance mappers represented in
2473 the columns clause of this query, as well as the "select from entity",
2474 add criterion to the WHERE
2475 clause of the given QueryContext such that only the appropriate
2476 subtypes are selected from the total results.
2477
2478 Additionally, add WHERE criteria originating from LoaderCriteriaOptions
2479 associated with the global context.
2480
2481 """
2482
2483 for fromclause in self.from_clauses:
2484 ext_info = fromclause._annotations.get("parententity", None)
2485
2486 if (
2487 ext_info
2488 and (
2489 ext_info.mapper._single_table_criterion is not None
2490 or ("additional_entity_criteria", ext_info.mapper)
2491 in self.global_attributes
2492 )
2493 and ext_info not in self.extra_criteria_entities
2494 ):
2495 self.extra_criteria_entities[ext_info] = (
2496 ext_info,
2497 ext_info._adapter if ext_info.is_aliased_class else None,
2498 )
2499
2500 _where_criteria_to_add = ()
2501
2502 merged_single_crit = collections.defaultdict(
2503 lambda: (util.OrderedSet(), set())
2504 )
2505
2506 for ext_info, adapter in util.OrderedSet(
2507 self.extra_criteria_entities.values()
2508 ):
2509 if ext_info in self._join_entities:
2510 continue
2511
2512 # assemble single table inheritance criteria.
2513 if (
2514 ext_info.is_aliased_class
2515 and ext_info._base_alias()._is_with_polymorphic
2516 ):
2517 # for a with_polymorphic(), we always include the full
2518 # hierarchy from what's given as the base class for the wpoly.
2519 # this is new in 2.1 for #12395 so that it matches the behavior
2520 # of joined inheritance.
2521 hierarchy_root = ext_info._base_alias()
2522 else:
2523 hierarchy_root = ext_info
2524
2525 single_crit_component = (
2526 hierarchy_root.mapper._single_table_criteria_component
2527 )
2528
2529 if single_crit_component is not None:
2530 polymorphic_on, criteria = single_crit_component
2531
2532 polymorphic_on = polymorphic_on._annotate(
2533 {
2534 "parententity": hierarchy_root,
2535 "parentmapper": hierarchy_root.mapper,
2536 }
2537 )
2538
2539 list_of_single_crits, adapters = merged_single_crit[
2540 (hierarchy_root, polymorphic_on)
2541 ]
2542 list_of_single_crits.update(criteria)
2543 if adapter:
2544 adapters.add(adapter)
2545
2546 # assemble "additional entity criteria", which come from
2547 # with_loader_criteria() options
2548 if not self.compile_options._for_refresh_state:
2549 additional_entity_criteria = self._get_extra_criteria(ext_info)
2550 _where_criteria_to_add += tuple(
2551 adapter.traverse(crit) if adapter else crit
2552 for crit in additional_entity_criteria
2553 )
2554
2555 # merge together single table inheritance criteria keyed to
2556 # top-level mapper / aliasedinsp (which may be a with_polymorphic())
2557 for (ext_info, polymorphic_on), (
2558 merged_crit,
2559 adapters,
2560 ) in merged_single_crit.items():
2561 new_crit = polymorphic_on.in_(merged_crit)
2562 for adapter in adapters:
2563 new_crit = adapter.traverse(new_crit)
2564 _where_criteria_to_add += (new_crit,)
2565
2566 current_adapter = self._get_current_adapter()
2567 if current_adapter:
2568 # finally run all the criteria through the "main" adapter, if we
2569 # have one, and concatenate to final WHERE criteria
2570 for crit in _where_criteria_to_add:
2571 crit = current_adapter(crit, False)
2572 self._where_criteria += (crit,)
2573 else:
2574 # else just concatenate our criteria to the final WHERE criteria
2575 self._where_criteria += _where_criteria_to_add
2576
2577
2578def _column_descriptions(
2579 query_or_select_stmt: Union[Query, Select, FromStatement],
2580 compile_state: Optional[_ORMSelectCompileState] = None,
2581 legacy: bool = False,
2582) -> List[ORMColumnDescription]:
2583 if compile_state is None:
2584 compile_state = _ORMSelectCompileState._create_entities_collection(
2585 query_or_select_stmt, legacy=legacy
2586 )
2587 ctx = compile_state
2588 d = [
2589 {
2590 "name": ent._label_name,
2591 "type": ent.type,
2592 "aliased": getattr(insp_ent, "is_aliased_class", False),
2593 "expr": ent.expr,
2594 "entity": (
2595 getattr(insp_ent, "entity", None)
2596 if ent.entity_zero is not None
2597 and not insp_ent.is_clause_element
2598 else None
2599 ),
2600 }
2601 for ent, insp_ent in [
2602 (_ent, _ent.entity_zero) for _ent in ctx._entities
2603 ]
2604 ]
2605 return d
2606
2607
2608def _legacy_filter_by_entity_zero(
2609 query_or_augmented_select: Union[Query[Any], Select[Unpack[TupleAny]]],
2610) -> Optional[_InternalEntityType[Any]]:
2611 self = query_or_augmented_select
2612 if self._setup_joins:
2613 _last_joined_entity = self._last_joined_entity
2614 if _last_joined_entity is not None:
2615 return _last_joined_entity
2616
2617 if self._from_obj and "parententity" in self._from_obj[0]._annotations:
2618 return self._from_obj[0]._annotations["parententity"]
2619
2620 return _entity_from_pre_ent_zero(self)
2621
2622
2623def _entity_from_pre_ent_zero(
2624 query_or_augmented_select: Union[Query[Any], Select[Unpack[TupleAny]]],
2625) -> Optional[_InternalEntityType[Any]]:
2626 self = query_or_augmented_select
2627 if not self._raw_columns:
2628 return None
2629
2630 ent = self._raw_columns[0]
2631
2632 if "parententity" in ent._annotations:
2633 return ent._annotations["parententity"]
2634 elif isinstance(ent, ORMColumnsClauseRole):
2635 return ent.entity
2636 elif "bundle" in ent._annotations:
2637 return ent._annotations["bundle"]
2638 else:
2639 return ent
2640
2641
2642def _determine_last_joined_entity(
2643 setup_joins: Tuple[_SetupJoinsElement, ...],
2644 entity_zero: Optional[_InternalEntityType[Any]] = None,
2645) -> Optional[Union[_InternalEntityType[Any], _JoinTargetElement]]:
2646 if not setup_joins:
2647 return None
2648
2649 (target, onclause, from_, flags) = setup_joins[-1]
2650
2651 if isinstance(
2652 target,
2653 attributes.QueryableAttribute,
2654 ):
2655 return target.entity
2656 else:
2657 return target
2658
2659
2660class _QueryEntity:
2661 """represent an entity column returned within a Query result."""
2662
2663 __slots__ = ()
2664
2665 supports_single_entity: bool
2666
2667 _non_hashable_value = False
2668 _null_column_type = False
2669 use_id_for_hash = False
2670
2671 _label_name: Optional[str]
2672 type: Union[Type[Any], TypeEngine[Any]]
2673 expr: Union[_InternalEntityType, ColumnElement[Any]]
2674 entity_zero: Optional[_InternalEntityType]
2675
2676 def setup_compile_state(self, compile_state: _ORMCompileState) -> None:
2677 raise NotImplementedError()
2678
2679 def setup_dml_returning_compile_state(
2680 self,
2681 compile_state: _ORMCompileState,
2682 adapter: Optional[_DMLReturningColFilter],
2683 ) -> None:
2684 raise NotImplementedError()
2685
2686 def row_processor(self, context, result):
2687 raise NotImplementedError()
2688
2689 @classmethod
2690 def to_compile_state(
2691 cls, compile_state, entities, entities_collection, is_current_entities
2692 ):
2693 for idx, entity in enumerate(entities):
2694 if entity._is_lambda_element:
2695 if entity._is_sequence:
2696 cls.to_compile_state(
2697 compile_state,
2698 entity._resolved,
2699 entities_collection,
2700 is_current_entities,
2701 )
2702 continue
2703 else:
2704 entity = entity._resolved
2705
2706 if entity.is_clause_element:
2707 if entity.is_selectable:
2708 if "parententity" in entity._annotations:
2709 _MapperEntity(
2710 compile_state,
2711 entity,
2712 entities_collection,
2713 is_current_entities,
2714 )
2715 else:
2716 _ColumnEntity._for_columns(
2717 compile_state,
2718 entity._select_iterable,
2719 entities_collection,
2720 idx,
2721 is_current_entities,
2722 )
2723 else:
2724 if entity._annotations.get("bundle", False):
2725 _BundleEntity(
2726 compile_state,
2727 entity,
2728 entities_collection,
2729 is_current_entities,
2730 )
2731 elif entity._is_clause_list:
2732 # this is legacy only - test_composites.py
2733 # test_query_cols_legacy
2734 _ColumnEntity._for_columns(
2735 compile_state,
2736 entity._select_iterable,
2737 entities_collection,
2738 idx,
2739 is_current_entities,
2740 )
2741 else:
2742 _ColumnEntity._for_columns(
2743 compile_state,
2744 [entity],
2745 entities_collection,
2746 idx,
2747 is_current_entities,
2748 )
2749 elif entity.is_bundle:
2750 _BundleEntity(compile_state, entity, entities_collection)
2751
2752 return entities_collection
2753
2754
2755class _MapperEntity(_QueryEntity):
2756 """mapper/class/AliasedClass entity"""
2757
2758 __slots__ = (
2759 "expr",
2760 "mapper",
2761 "entity_zero",
2762 "is_aliased_class",
2763 "path",
2764 "_extra_entities",
2765 "_label_name",
2766 "_with_polymorphic_mappers",
2767 "selectable",
2768 "_polymorphic_discriminator",
2769 )
2770
2771 expr: _InternalEntityType
2772 mapper: Mapper[Any]
2773 entity_zero: _InternalEntityType
2774 is_aliased_class: bool
2775 path: PathRegistry
2776 _label_name: str
2777
2778 def __init__(
2779 self, compile_state, entity, entities_collection, is_current_entities
2780 ):
2781 entities_collection.append(self)
2782 if is_current_entities:
2783 if compile_state._primary_entity is None:
2784 compile_state._primary_entity = self
2785 compile_state._has_mapper_entities = True
2786 compile_state._has_orm_entities = True
2787
2788 entity = entity._annotations["parententity"]
2789 entity._post_inspect
2790 ext_info = self.entity_zero = entity
2791 entity = ext_info.entity
2792
2793 self.expr = entity
2794 self.mapper = mapper = ext_info.mapper
2795
2796 self._extra_entities = (self.expr,)
2797
2798 if ext_info.is_aliased_class:
2799 self._label_name = ext_info.name
2800 else:
2801 self._label_name = mapper.class_.__name__
2802
2803 self.is_aliased_class = ext_info.is_aliased_class
2804 self.path = ext_info._path_registry
2805
2806 self.selectable = ext_info.selectable
2807 self._with_polymorphic_mappers = ext_info.with_polymorphic_mappers
2808 self._polymorphic_discriminator = ext_info.polymorphic_on
2809
2810 if mapper._should_select_with_poly_adapter:
2811 compile_state._create_with_polymorphic_adapter(
2812 ext_info, self.selectable
2813 )
2814
2815 supports_single_entity = True
2816
2817 _non_hashable_value = True
2818 use_id_for_hash = True
2819
2820 @property
2821 def type(self):
2822 return self.mapper.class_
2823
2824 @property
2825 def entity_zero_or_selectable(self):
2826 return self.entity_zero
2827
2828 def corresponds_to(self, entity):
2829 return _entity_corresponds_to(self.entity_zero, entity)
2830
2831 def _get_entity_clauses(self, compile_state):
2832 adapter = None
2833
2834 if not self.is_aliased_class:
2835 if compile_state._polymorphic_adapters:
2836 adapter = compile_state._polymorphic_adapters.get(
2837 self.mapper, None
2838 )
2839 else:
2840 adapter = self.entity_zero._adapter
2841
2842 if adapter:
2843 if compile_state._from_obj_alias:
2844 ret = adapter.wrap(compile_state._from_obj_alias)
2845 else:
2846 ret = adapter
2847 else:
2848 ret = compile_state._from_obj_alias
2849
2850 return ret
2851
2852 def row_processor(self, context, result):
2853 compile_state = context.compile_state
2854 adapter = self._get_entity_clauses(compile_state)
2855
2856 if compile_state.compound_eager_adapter and adapter:
2857 adapter = adapter.wrap(compile_state.compound_eager_adapter)
2858 elif not adapter:
2859 adapter = compile_state.compound_eager_adapter
2860
2861 if compile_state._primary_entity is self:
2862 only_load_props = compile_state.compile_options._only_load_props
2863 refresh_state = context.refresh_state
2864 else:
2865 only_load_props = refresh_state = None
2866
2867 _instance = loading._instance_processor(
2868 self,
2869 self.mapper,
2870 context,
2871 result,
2872 self.path,
2873 adapter,
2874 only_load_props=only_load_props,
2875 refresh_state=refresh_state,
2876 polymorphic_discriminator=self._polymorphic_discriminator,
2877 )
2878
2879 return _instance, self._label_name, self._extra_entities
2880
2881 def setup_dml_returning_compile_state(
2882 self,
2883 compile_state: _ORMCompileState,
2884 adapter: Optional[_DMLReturningColFilter],
2885 ) -> None:
2886 loading._setup_entity_query(
2887 compile_state,
2888 self.mapper,
2889 self,
2890 self.path,
2891 adapter,
2892 compile_state.primary_columns,
2893 with_polymorphic=self._with_polymorphic_mappers,
2894 only_load_props=compile_state.compile_options._only_load_props,
2895 polymorphic_discriminator=self._polymorphic_discriminator,
2896 )
2897
2898 def setup_compile_state(self, compile_state):
2899 adapter = self._get_entity_clauses(compile_state)
2900
2901 single_table_crit = self.mapper._single_table_criterion
2902 if (
2903 single_table_crit is not None
2904 or ("additional_entity_criteria", self.mapper)
2905 in compile_state.global_attributes
2906 ):
2907 ext_info = self.entity_zero
2908 compile_state.extra_criteria_entities[ext_info] = (
2909 ext_info,
2910 ext_info._adapter if ext_info.is_aliased_class else None,
2911 )
2912
2913 loading._setup_entity_query(
2914 compile_state,
2915 self.mapper,
2916 self,
2917 self.path,
2918 adapter,
2919 compile_state.primary_columns,
2920 with_polymorphic=self._with_polymorphic_mappers,
2921 only_load_props=compile_state.compile_options._only_load_props,
2922 polymorphic_discriminator=self._polymorphic_discriminator,
2923 )
2924 compile_state._fallback_from_clauses.append(self.selectable)
2925
2926
2927class _BundleEntity(_QueryEntity):
2928 _extra_entities = ()
2929
2930 __slots__ = (
2931 "bundle",
2932 "expr",
2933 "type",
2934 "_label_name",
2935 "_entities",
2936 "supports_single_entity",
2937 )
2938
2939 _entities: List[_QueryEntity]
2940 bundle: Bundle
2941 type: Type[Any]
2942 _label_name: str
2943 supports_single_entity: bool
2944 expr: Bundle
2945
2946 def __init__(
2947 self,
2948 compile_state,
2949 expr,
2950 entities_collection,
2951 is_current_entities,
2952 setup_entities=True,
2953 parent_bundle=None,
2954 ):
2955 compile_state._has_orm_entities = True
2956
2957 expr = expr._annotations["bundle"]
2958 if parent_bundle:
2959 parent_bundle._entities.append(self)
2960 else:
2961 entities_collection.append(self)
2962
2963 if isinstance(
2964 expr, (attributes.QueryableAttribute, interfaces.PropComparator)
2965 ):
2966 bundle = expr.__clause_element__()
2967 else:
2968 bundle = expr
2969
2970 self.bundle = self.expr = bundle
2971 self.type = type(bundle)
2972 self._label_name = bundle.name
2973 self._entities = []
2974
2975 if setup_entities:
2976 for expr in bundle.exprs:
2977 if "bundle" in expr._annotations:
2978 _BundleEntity(
2979 compile_state,
2980 expr,
2981 entities_collection,
2982 is_current_entities,
2983 parent_bundle=self,
2984 )
2985 elif isinstance(expr, Bundle):
2986 _BundleEntity(
2987 compile_state,
2988 expr,
2989 entities_collection,
2990 is_current_entities,
2991 parent_bundle=self,
2992 )
2993 else:
2994 _ORMColumnEntity._for_columns(
2995 compile_state,
2996 [expr],
2997 entities_collection,
2998 None,
2999 is_current_entities,
3000 parent_bundle=self,
3001 )
3002
3003 self.supports_single_entity = self.bundle.single_entity
3004
3005 @property
3006 def mapper(self):
3007 ezero = self.entity_zero
3008 if ezero is not None:
3009 return ezero.mapper
3010 else:
3011 return None
3012
3013 @property
3014 def entity_zero(self):
3015 for ent in self._entities:
3016 ezero = ent.entity_zero
3017 if ezero is not None:
3018 return ezero
3019 else:
3020 return None
3021
3022 def corresponds_to(self, entity):
3023 # TODO: we might be able to implement this but for now
3024 # we are working around it
3025 return False
3026
3027 @property
3028 def entity_zero_or_selectable(self):
3029 for ent in self._entities:
3030 ezero = ent.entity_zero_or_selectable
3031 if ezero is not None:
3032 return ezero
3033 else:
3034 return None
3035
3036 def setup_compile_state(self, compile_state):
3037 for ent in self._entities:
3038 ent.setup_compile_state(compile_state)
3039
3040 def setup_dml_returning_compile_state(
3041 self,
3042 compile_state: _ORMCompileState,
3043 adapter: Optional[_DMLReturningColFilter],
3044 ) -> None:
3045 return self.setup_compile_state(compile_state)
3046
3047 def row_processor(self, context, result):
3048 procs, labels, extra = zip(
3049 *[ent.row_processor(context, result) for ent in self._entities]
3050 )
3051
3052 proc = self.bundle.create_row_processor(context.query, procs, labels)
3053
3054 return proc, self._label_name, self._extra_entities
3055
3056
3057class _ColumnEntity(_QueryEntity):
3058 __slots__ = (
3059 "_fetch_column",
3060 "_row_processor",
3061 "raw_column_index",
3062 "translate_raw_column",
3063 )
3064
3065 @classmethod
3066 def _for_columns(
3067 cls,
3068 compile_state,
3069 columns,
3070 entities_collection,
3071 raw_column_index,
3072 is_current_entities,
3073 parent_bundle=None,
3074 ):
3075 for column in columns:
3076 annotations = column._annotations
3077 if "parententity" in annotations:
3078 _entity = annotations["parententity"]
3079 else:
3080 _entity = sql_util.extract_first_column_annotation(
3081 column, "parententity"
3082 )
3083
3084 if _entity:
3085 if "identity_token" in column._annotations:
3086 _IdentityTokenEntity(
3087 compile_state,
3088 column,
3089 entities_collection,
3090 _entity,
3091 raw_column_index,
3092 is_current_entities,
3093 parent_bundle=parent_bundle,
3094 )
3095 else:
3096 _ORMColumnEntity(
3097 compile_state,
3098 column,
3099 entities_collection,
3100 _entity,
3101 raw_column_index,
3102 is_current_entities,
3103 parent_bundle=parent_bundle,
3104 )
3105 else:
3106 _RawColumnEntity(
3107 compile_state,
3108 column,
3109 entities_collection,
3110 raw_column_index,
3111 is_current_entities,
3112 parent_bundle=parent_bundle,
3113 )
3114
3115 @property
3116 def type(self):
3117 return self.column.type
3118
3119 @property
3120 def _non_hashable_value(self):
3121 return not self.column.type.hashable
3122
3123 @property
3124 def _null_column_type(self):
3125 return self.column.type._isnull
3126
3127 def row_processor(self, context, result):
3128 compile_state = context.compile_state
3129
3130 # the resulting callable is entirely cacheable so just return
3131 # it if we already made one
3132 if self._row_processor is not None:
3133 getter, label_name, extra_entities = self._row_processor
3134 if self.translate_raw_column:
3135 extra_entities += (
3136 context.query._raw_columns[self.raw_column_index],
3137 )
3138
3139 return getter, label_name, extra_entities
3140
3141 # retrieve the column that would have been set up in
3142 # setup_compile_state, to avoid doing redundant work
3143 if self._fetch_column is not None:
3144 column = self._fetch_column
3145 else:
3146 # fetch_column will be None when we are doing a from_statement
3147 # and setup_compile_state may not have been called.
3148 column = self.column
3149
3150 # previously, the RawColumnEntity didn't look for from_obj_alias
3151 # however I can't think of a case where we would be here and
3152 # we'd want to ignore it if this is the from_statement use case.
3153 # it's not really a use case to have raw columns + from_statement
3154 if compile_state._from_obj_alias:
3155 column = compile_state._from_obj_alias.columns[column]
3156
3157 if column._annotations:
3158 # annotated columns perform more slowly in compiler and
3159 # result due to the __eq__() method, so use deannotated
3160 column = column._deannotate()
3161
3162 if compile_state.compound_eager_adapter:
3163 column = compile_state.compound_eager_adapter.columns[column]
3164
3165 getter = result._getter(column)
3166 ret = getter, self._label_name, self._extra_entities
3167 self._row_processor = ret
3168
3169 if self.translate_raw_column:
3170 extra_entities = self._extra_entities + (
3171 context.query._raw_columns[self.raw_column_index],
3172 )
3173 return getter, self._label_name, extra_entities
3174 else:
3175 return ret
3176
3177
3178class _RawColumnEntity(_ColumnEntity):
3179 entity_zero = None
3180 mapper = None
3181 supports_single_entity = False
3182
3183 __slots__ = (
3184 "expr",
3185 "column",
3186 "_label_name",
3187 "entity_zero_or_selectable",
3188 "_extra_entities",
3189 )
3190
3191 def __init__(
3192 self,
3193 compile_state,
3194 column,
3195 entities_collection,
3196 raw_column_index,
3197 is_current_entities,
3198 parent_bundle=None,
3199 ):
3200 self.expr = column
3201 self.raw_column_index = raw_column_index
3202 self.translate_raw_column = raw_column_index is not None
3203
3204 if column._is_star:
3205 compile_state.compile_options += {"_is_star": True}
3206
3207 if not is_current_entities or column._is_text_clause:
3208 self._label_name = None
3209 else:
3210 if parent_bundle:
3211 self._label_name = column._proxy_key
3212 else:
3213 self._label_name = compile_state._label_convention(column)
3214
3215 if parent_bundle:
3216 parent_bundle._entities.append(self)
3217 else:
3218 entities_collection.append(self)
3219
3220 self.column = column
3221 self.entity_zero_or_selectable = (
3222 self.column._from_objects[0] if self.column._from_objects else None
3223 )
3224 self._extra_entities = (self.expr, self.column)
3225 self._fetch_column = self._row_processor = None
3226
3227 def corresponds_to(self, entity):
3228 return False
3229
3230 def setup_dml_returning_compile_state(
3231 self,
3232 compile_state: _ORMCompileState,
3233 adapter: Optional[_DMLReturningColFilter],
3234 ) -> None:
3235 return self.setup_compile_state(compile_state)
3236
3237 def setup_compile_state(self, compile_state):
3238 current_adapter = compile_state._get_current_adapter()
3239 if current_adapter:
3240 column = current_adapter(self.column, False)
3241 if column is None:
3242 return
3243 else:
3244 column = self.column
3245
3246 if column._annotations:
3247 # annotated columns perform more slowly in compiler and
3248 # result due to the __eq__() method, so use deannotated
3249 column = column._deannotate()
3250
3251 compile_state.dedupe_columns.add(column)
3252 compile_state.primary_columns.append(column)
3253 self._fetch_column = column
3254
3255
3256class _ORMColumnEntity(_ColumnEntity):
3257 """Column/expression based entity."""
3258
3259 supports_single_entity = False
3260
3261 __slots__ = (
3262 "expr",
3263 "mapper",
3264 "column",
3265 "_label_name",
3266 "entity_zero_or_selectable",
3267 "entity_zero",
3268 "_extra_entities",
3269 )
3270
3271 def __init__(
3272 self,
3273 compile_state,
3274 column,
3275 entities_collection,
3276 parententity,
3277 raw_column_index,
3278 is_current_entities,
3279 parent_bundle=None,
3280 ):
3281 annotations = column._annotations
3282
3283 _entity = parententity
3284
3285 # an AliasedClass won't have proxy_key in the annotations for
3286 # a column if it was acquired using the class' adapter directly,
3287 # such as using AliasedInsp._adapt_element(). this occurs
3288 # within internal loaders.
3289
3290 orm_key = annotations.get("proxy_key", None)
3291 proxy_owner = annotations.get("proxy_owner", _entity)
3292 if orm_key:
3293 self.expr = getattr(proxy_owner.entity, orm_key)
3294 self.translate_raw_column = False
3295 else:
3296 # if orm_key is not present, that means this is an ad-hoc
3297 # SQL ColumnElement, like a CASE() or other expression.
3298 # include this column position from the invoked statement
3299 # in the ORM-level ResultSetMetaData on each execute, so that
3300 # it can be targeted by identity after caching
3301 self.expr = column
3302 self.translate_raw_column = raw_column_index is not None
3303
3304 self.raw_column_index = raw_column_index
3305
3306 if is_current_entities:
3307 if parent_bundle:
3308 self._label_name = orm_key if orm_key else column._proxy_key
3309 else:
3310 self._label_name = compile_state._label_convention(
3311 column, col_name=orm_key
3312 )
3313 else:
3314 self._label_name = None
3315
3316 _entity._post_inspect
3317 self.entity_zero = self.entity_zero_or_selectable = ezero = _entity
3318 self.mapper = mapper = _entity.mapper
3319
3320 if parent_bundle:
3321 parent_bundle._entities.append(self)
3322 else:
3323 entities_collection.append(self)
3324
3325 compile_state._has_orm_entities = True
3326
3327 self.column = column
3328
3329 self._fetch_column = self._row_processor = None
3330
3331 self._extra_entities = (self.expr, self.column)
3332
3333 if mapper._should_select_with_poly_adapter:
3334 compile_state._create_with_polymorphic_adapter(
3335 ezero, ezero.selectable
3336 )
3337
3338 def corresponds_to(self, entity):
3339 if _is_aliased_class(entity):
3340 # TODO: polymorphic subclasses ?
3341 return entity is self.entity_zero
3342 else:
3343 return not _is_aliased_class(
3344 self.entity_zero
3345 ) and entity.common_parent(self.entity_zero)
3346
3347 def setup_dml_returning_compile_state(
3348 self,
3349 compile_state: _ORMCompileState,
3350 adapter: Optional[_DMLReturningColFilter],
3351 ) -> None:
3352
3353 self._fetch_column = column = self.column
3354 if adapter:
3355 column = adapter(column, False)
3356
3357 if column is not None:
3358 compile_state.dedupe_columns.add(column)
3359 compile_state.primary_columns.append(column)
3360
3361 def setup_compile_state(self, compile_state):
3362 current_adapter = compile_state._get_current_adapter()
3363 if current_adapter:
3364 column = current_adapter(self.column, False)
3365 if column is None:
3366 assert compile_state.is_dml_returning
3367 self._fetch_column = self.column
3368 return
3369 else:
3370 column = self.column
3371
3372 ezero = self.entity_zero
3373
3374 single_table_crit = self.mapper._single_table_criterion
3375 if (
3376 single_table_crit is not None
3377 or ("additional_entity_criteria", self.mapper)
3378 in compile_state.global_attributes
3379 ):
3380 compile_state.extra_criteria_entities[ezero] = (
3381 ezero,
3382 ezero._adapter if ezero.is_aliased_class else None,
3383 )
3384
3385 if column._annotations and not column._expression_label:
3386 # annotated columns perform more slowly in compiler and
3387 # result due to the __eq__() method, so use deannotated
3388 column = column._deannotate()
3389
3390 # use entity_zero as the from if we have it. this is necessary
3391 # for polymorphic scenarios where our FROM is based on ORM entity,
3392 # not the FROM of the column. but also, don't use it if our column
3393 # doesn't actually have any FROMs that line up, such as when its
3394 # a scalar subquery.
3395 if set(self.column._from_objects).intersection(
3396 ezero.selectable._from_objects
3397 ):
3398 compile_state._fallback_from_clauses.append(ezero.selectable)
3399
3400 compile_state.dedupe_columns.add(column)
3401 compile_state.primary_columns.append(column)
3402 self._fetch_column = column
3403
3404
3405class _IdentityTokenEntity(_ORMColumnEntity):
3406 translate_raw_column = False
3407
3408 def setup_compile_state(self, compile_state):
3409 pass
3410
3411 def row_processor(self, context, result):
3412 def getter(row):
3413 return context.load_options._identity_token
3414
3415 return getter, self._label_name, self._extra_entities