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