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