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