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