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