Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/context.py: 19%
1209 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
1# orm/context.py
2# Copyright (C) 2005-2022 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
7import itertools
9from . import attributes
10from . import interfaces
11from . import loading
12from .base import _is_aliased_class
13from .interfaces import ORMColumnsClauseRole
14from .path_registry import PathRegistry
15from .util import _entity_corresponds_to
16from .util import _ORMJoin
17from .util import aliased
18from .util import Bundle
19from .util import ORMAdapter
20from .. import exc as sa_exc
21from .. import future
22from .. import inspect
23from .. import sql
24from .. import util
25from ..sql import ClauseElement
26from ..sql import coercions
27from ..sql import expression
28from ..sql import roles
29from ..sql import util as sql_util
30from ..sql import visitors
31from ..sql.base import _entity_namespace_key
32from ..sql.base import _select_iterables
33from ..sql.base import CacheableOptions
34from ..sql.base import CompileState
35from ..sql.base import Options
36from ..sql.selectable import LABEL_STYLE_DISAMBIGUATE_ONLY
37from ..sql.selectable import LABEL_STYLE_NONE
38from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
39from ..sql.selectable import SelectState
40from ..sql.visitors import ExtendedInternalTraversal
41from ..sql.visitors import InternalTraversal
43_path_registry = PathRegistry.root
45_EMPTY_DICT = util.immutabledict()
48LABEL_STYLE_LEGACY_ORM = util.symbol("LABEL_STYLE_LEGACY_ORM")
51class QueryContext(object):
52 __slots__ = (
53 "compile_state",
54 "query",
55 "params",
56 "load_options",
57 "bind_arguments",
58 "execution_options",
59 "session",
60 "autoflush",
61 "populate_existing",
62 "invoke_all_eagers",
63 "version_check",
64 "refresh_state",
65 "create_eager_joins",
66 "propagated_loader_options",
67 "attributes",
68 "runid",
69 "partials",
70 "post_load_paths",
71 "identity_token",
72 "yield_per",
73 "loaders_require_buffering",
74 "loaders_require_uniquing",
75 )
77 class default_load_options(Options):
78 _only_return_tuples = False
79 _populate_existing = False
80 _version_check = False
81 _invoke_all_eagers = True
82 _autoflush = True
83 _refresh_identity_token = None
84 _yield_per = None
85 _refresh_state = None
86 _lazy_loaded_from = None
87 _legacy_uniquing = False
89 def __init__(
90 self,
91 compile_state,
92 statement,
93 params,
94 session,
95 load_options,
96 execution_options=None,
97 bind_arguments=None,
98 ):
99 self.load_options = load_options
100 self.execution_options = execution_options or _EMPTY_DICT
101 self.bind_arguments = bind_arguments or _EMPTY_DICT
102 self.compile_state = compile_state
103 self.query = statement
104 self.session = session
105 self.loaders_require_buffering = False
106 self.loaders_require_uniquing = False
107 self.params = params
109 cached_options = compile_state.select_statement._with_options
110 uncached_options = statement._with_options
112 # see issue #7447 , #8399 for some background
113 # propagated loader options will be present on loaded InstanceState
114 # objects under state.load_options and are typically used by
115 # LazyLoader to apply options to the SELECT statement it emits.
116 # For compile state options (i.e. loader strategy options), these
117 # need to line up with the ".load_path" attribute which in
118 # loader.py is pulled from context.compile_state.current_path.
119 # so, this means these options have to be the ones from the
120 # *cached* statement that's travelling with compile_state, not the
121 # *current* statement which won't match up for an ad-hoc
122 # AliasedClass
123 self.propagated_loader_options = tuple(
124 opt._adapt_cached_option_to_uncached_option(self, uncached_opt)
125 for opt, uncached_opt in zip(cached_options, uncached_options)
126 if opt.propagate_to_loaders
127 )
129 self.attributes = dict(compile_state.attributes)
131 self.autoflush = load_options._autoflush
132 self.populate_existing = load_options._populate_existing
133 self.invoke_all_eagers = load_options._invoke_all_eagers
134 self.version_check = load_options._version_check
135 self.refresh_state = load_options._refresh_state
136 self.yield_per = load_options._yield_per
137 self.identity_token = load_options._refresh_identity_token
139 if self.yield_per and compile_state._no_yield_pers:
140 raise sa_exc.InvalidRequestError(
141 "The yield_per Query option is currently not "
142 "compatible with %s eager loading. Please "
143 "specify lazyload('*') or query.enable_eagerloads(False) in "
144 "order to "
145 "proceed with query.yield_per()."
146 % ", ".join(compile_state._no_yield_pers)
147 )
150_orm_load_exec_options = util.immutabledict(
151 {"_result_disable_adapt_to_context": True, "future_result": True}
152)
155class ORMCompileState(CompileState):
156 # note this is a dictionary, but the
157 # default_compile_options._with_polymorphic_adapt_map is a tuple
158 _with_polymorphic_adapt_map = _EMPTY_DICT
160 class default_compile_options(CacheableOptions):
161 _cache_key_traversal = [
162 ("_use_legacy_query_style", InternalTraversal.dp_boolean),
163 ("_for_statement", InternalTraversal.dp_boolean),
164 ("_bake_ok", InternalTraversal.dp_boolean),
165 (
166 "_with_polymorphic_adapt_map",
167 ExtendedInternalTraversal.dp_has_cache_key_tuples,
168 ),
169 ("_current_path", InternalTraversal.dp_has_cache_key),
170 ("_enable_single_crit", InternalTraversal.dp_boolean),
171 ("_enable_eagerloads", InternalTraversal.dp_boolean),
172 ("_orm_only_from_obj_alias", InternalTraversal.dp_boolean),
173 ("_only_load_props", InternalTraversal.dp_plain_obj),
174 ("_set_base_alias", InternalTraversal.dp_boolean),
175 ("_for_refresh_state", InternalTraversal.dp_boolean),
176 ("_render_for_subquery", InternalTraversal.dp_boolean),
177 ("_is_star", InternalTraversal.dp_boolean),
178 ]
180 # set to True by default from Query._statement_20(), to indicate
181 # the rendered query should look like a legacy ORM query. right
182 # now this basically indicates we should use tablename_columnname
183 # style labels. Generally indicates the statement originated
184 # from a Query object.
185 _use_legacy_query_style = False
187 # set *only* when we are coming from the Query.statement
188 # accessor, or a Query-level equivalent such as
189 # query.subquery(). this supersedes "toplevel".
190 _for_statement = False
192 _bake_ok = True
193 _with_polymorphic_adapt_map = ()
194 _current_path = _path_registry
195 _enable_single_crit = True
196 _enable_eagerloads = True
197 _orm_only_from_obj_alias = True
198 _only_load_props = None
199 _set_base_alias = False
200 _for_refresh_state = False
201 _render_for_subquery = False
202 _is_star = False
204 current_path = _path_registry
206 def __init__(self, *arg, **kw):
207 raise NotImplementedError()
209 def _append_dedupe_col_collection(self, obj, col_collection):
210 dedupe = self.dedupe_columns
211 if obj not in dedupe:
212 dedupe.add(obj)
213 col_collection.append(obj)
215 @classmethod
216 def _column_naming_convention(cls, label_style, legacy):
218 if legacy:
220 def name(col, col_name=None):
221 if col_name:
222 return col_name
223 else:
224 return getattr(col, "key")
226 return name
227 else:
228 return SelectState._column_naming_convention(label_style)
230 @classmethod
231 def create_for_statement(cls, statement_container, compiler, **kw):
232 """Create a context for a statement given a :class:`.Compiler`.
234 This method is always invoked in the context of SQLCompiler.process().
236 For a Select object, this would be invoked from
237 SQLCompiler.visit_select(). For the special FromStatement object used
238 by Query to indicate "Query.from_statement()", this is called by
239 FromStatement._compiler_dispatch() that would be called by
240 SQLCompiler.process().
242 """
243 raise NotImplementedError()
245 @classmethod
246 def get_column_descriptions(cls, statement):
247 return _column_descriptions(statement)
249 @classmethod
250 def orm_pre_session_exec(
251 cls,
252 session,
253 statement,
254 params,
255 execution_options,
256 bind_arguments,
257 is_reentrant_invoke,
258 ):
259 if is_reentrant_invoke:
260 return statement, execution_options
262 (
263 load_options,
264 execution_options,
265 ) = QueryContext.default_load_options.from_execution_options(
266 "_sa_orm_load_options",
267 {"populate_existing", "autoflush", "yield_per"},
268 execution_options,
269 statement._execution_options,
270 )
272 # default execution options for ORM results:
273 # 1. _result_disable_adapt_to_context=True
274 # this will disable the ResultSetMetadata._adapt_to_context()
275 # step which we don't need, as we have result processors cached
276 # against the original SELECT statement before caching.
277 # 2. future_result=True. The ORM should **never** resolve columns
278 # in a result set based on names, only on Column objects that
279 # are correctly adapted to the context. W the legacy result
280 # it will still attempt name-based resolution and also emit a
281 # warning.
282 if not execution_options:
283 execution_options = _orm_load_exec_options
284 else:
285 execution_options = execution_options.union(_orm_load_exec_options)
287 if load_options._yield_per:
288 execution_options = execution_options.union(
289 {"yield_per": load_options._yield_per}
290 )
292 bind_arguments["clause"] = statement
294 # new in 1.4 - the coercions system is leveraged to allow the
295 # "subject" mapper of a statement be propagated to the top
296 # as the statement is built. "subject" mapper is the generally
297 # standard object used as an identifier for multi-database schemes.
299 # we are here based on the fact that _propagate_attrs contains
300 # "compile_state_plugin": "orm". The "plugin_subject"
301 # needs to be present as well.
303 try:
304 plugin_subject = statement._propagate_attrs["plugin_subject"]
305 except KeyError:
306 assert False, "statement had 'orm' plugin but no plugin_subject"
307 else:
308 if plugin_subject:
309 bind_arguments["mapper"] = plugin_subject.mapper
311 if load_options._autoflush:
312 session._autoflush()
314 return statement, execution_options
316 @classmethod
317 def orm_setup_cursor_result(
318 cls,
319 session,
320 statement,
321 params,
322 execution_options,
323 bind_arguments,
324 result,
325 ):
326 execution_context = result.context
327 compile_state = execution_context.compiled.compile_state
329 # cover edge case where ORM entities used in legacy select
330 # were passed to session.execute:
331 # session.execute(legacy_select([User.id, User.name]))
332 # see test_query->test_legacy_tuple_old_select
334 load_options = execution_options.get(
335 "_sa_orm_load_options", QueryContext.default_load_options
336 )
337 if compile_state.compile_options._is_star:
338 return result
340 querycontext = QueryContext(
341 compile_state,
342 statement,
343 params,
344 session,
345 load_options,
346 execution_options,
347 bind_arguments,
348 )
349 return loading.instances(result, querycontext)
351 @property
352 def _lead_mapper_entities(self):
353 """return all _MapperEntity objects in the lead entities collection.
355 Does **not** include entities that have been replaced by
356 with_entities(), with_only_columns()
358 """
359 return [
360 ent for ent in self._entities if isinstance(ent, _MapperEntity)
361 ]
363 def _create_with_polymorphic_adapter(self, ext_info, selectable):
364 """given MapperEntity or ORMColumnEntity, setup polymorphic loading
365 if appropriate
367 """
368 if (
369 not ext_info.is_aliased_class
370 and ext_info.mapper.persist_selectable
371 not in self._polymorphic_adapters
372 ):
373 for mp in ext_info.mapper.iterate_to_root():
374 self._mapper_loads_polymorphically_with(
375 mp,
376 sql_util.ColumnAdapter(selectable, mp._equivalent_columns),
377 )
379 def _mapper_loads_polymorphically_with(self, mapper, adapter):
380 for m2 in mapper._with_polymorphic_mappers or [mapper]:
381 self._polymorphic_adapters[m2] = adapter
382 for m in m2.iterate_to_root(): # TODO: redundant ?
383 self._polymorphic_adapters[m.local_table] = adapter
385 @classmethod
386 def _create_entities_collection(cls, query, legacy):
387 raise NotImplementedError(
388 "this method only works for ORMSelectCompileState"
389 )
392@sql.base.CompileState.plugin_for("orm", "orm_from_statement")
393class ORMFromStatementCompileState(ORMCompileState):
394 _aliased_generations = util.immutabledict()
395 _from_obj_alias = None
396 _has_mapper_entities = False
398 _has_orm_entities = False
399 multi_row_eager_loaders = False
400 eager_adding_joins = False
401 compound_eager_adapter = None
403 extra_criteria_entities = _EMPTY_DICT
404 eager_joins = _EMPTY_DICT
406 @classmethod
407 def create_for_statement(cls, statement_container, compiler, **kw):
409 if compiler is not None:
410 toplevel = not compiler.stack
411 else:
412 toplevel = True
414 self = cls.__new__(cls)
415 self._primary_entity = None
417 self.use_legacy_query_style = (
418 statement_container._compile_options._use_legacy_query_style
419 )
420 self.statement_container = self.select_statement = statement_container
421 self.requested_statement = statement = statement_container.element
423 if statement.is_dml:
424 self.dml_table = statement.table
426 self._entities = []
427 self._polymorphic_adapters = {}
428 self._no_yield_pers = set()
430 self.compile_options = statement_container._compile_options
432 if (
433 self.use_legacy_query_style
434 and isinstance(statement, expression.SelectBase)
435 and not statement._is_textual
436 and not statement.is_dml
437 and statement._label_style is LABEL_STYLE_NONE
438 ):
439 self.statement = statement.set_label_style(
440 LABEL_STYLE_TABLENAME_PLUS_COL
441 )
442 else:
443 self.statement = statement
445 self._label_convention = self._column_naming_convention(
446 statement._label_style
447 if not statement._is_textual and not statement.is_dml
448 else LABEL_STYLE_NONE,
449 self.use_legacy_query_style,
450 )
452 _QueryEntity.to_compile_state(
453 self,
454 statement_container._raw_columns,
455 self._entities,
456 is_current_entities=True,
457 )
459 self.current_path = statement_container._compile_options._current_path
461 if toplevel and statement_container._with_options:
462 self.attributes = {"_unbound_load_dedupes": set()}
463 self.global_attributes = compiler._global_attributes
465 for opt in statement_container._with_options:
466 if opt._is_compile_state:
467 opt.process_compile_state(self)
469 else:
470 self.attributes = {}
471 self.global_attributes = compiler._global_attributes
473 if statement_container._with_context_options:
474 for fn, key in statement_container._with_context_options:
475 fn(self)
477 self.primary_columns = []
478 self.secondary_columns = []
479 self.dedupe_columns = set()
480 self.create_eager_joins = []
481 self._fallback_from_clauses = []
483 self.order_by = None
485 if isinstance(
486 self.statement, (expression.TextClause, expression.UpdateBase)
487 ):
489 self.extra_criteria_entities = {}
491 # setup for all entities. Currently, this is not useful
492 # for eager loaders, as the eager loaders that work are able
493 # to do their work entirely in row_processor.
494 for entity in self._entities:
495 entity.setup_compile_state(self)
497 # we did the setup just to get primary columns.
498 self.statement = _AdHocColumnsStatement(
499 self.statement, self.primary_columns
500 )
501 else:
502 # allow TextualSelect with implicit columns as well
503 # as select() with ad-hoc columns, see test_query::TextTest
504 self._from_obj_alias = sql.util.ColumnAdapter(
505 self.statement, adapt_on_names=True
506 )
507 # set up for eager loaders, however if we fix subqueryload
508 # it should not need to do this here. the model of eager loaders
509 # that can work entirely in row_processor might be interesting
510 # here though subqueryloader has a lot of upfront work to do
511 # see test/orm/test_query.py -> test_related_eagerload_against_text
512 # for where this part makes a difference. would rather have
513 # subqueryload figure out what it needs more intelligently.
514 # for entity in self._entities:
515 # entity.setup_compile_state(self)
517 return self
519 def _adapt_col_list(self, cols, current_adapter):
520 return cols
522 def _get_current_adapter(self):
523 return None
526class _AdHocColumnsStatement(ClauseElement):
527 """internal object created to somewhat act like a SELECT when we
528 are selecting columns from a DML RETURNING.
531 """
533 __visit_name__ = None
535 def __init__(self, text, columns):
536 self.element = text
537 self.column_args = [
538 coercions.expect(roles.ColumnsClauseRole, c) for c in columns
539 ]
541 def _generate_cache_key(self):
542 raise NotImplementedError()
544 def _gen_cache_key(self, anon_map, bindparams):
545 raise NotImplementedError()
547 def _compiler_dispatch(
548 self, compiler, compound_index=None, asfrom=False, **kw
549 ):
550 """provide a fixed _compiler_dispatch method."""
552 toplevel = not compiler.stack
553 entry = (
554 compiler._default_stack_entry if toplevel else compiler.stack[-1]
555 )
557 populate_result_map = (
558 toplevel
559 # these two might not be needed
560 or (
561 compound_index == 0
562 and entry.get("need_result_map_for_compound", False)
563 )
564 or entry.get("need_result_map_for_nested", False)
565 )
567 if populate_result_map:
568 compiler._ordered_columns = (
569 compiler._textual_ordered_columns
570 ) = False
572 # enable looser result column matching. this is shown to be
573 # needed by test_query.py::TextTest
574 compiler._loose_column_name_matching = True
576 for c in self.column_args:
577 compiler.process(
578 c,
579 within_columns_clause=True,
580 add_to_result_map=compiler._add_to_result_map,
581 )
582 return compiler.process(self.element, **kw)
585@sql.base.CompileState.plugin_for("orm", "select")
586class ORMSelectCompileState(ORMCompileState, SelectState):
587 _joinpath = _joinpoint = _EMPTY_DICT
589 _memoized_entities = _EMPTY_DICT
591 _from_obj_alias = None
592 _has_mapper_entities = False
594 _has_orm_entities = False
595 multi_row_eager_loaders = False
596 eager_adding_joins = False
597 compound_eager_adapter = None
599 correlate = None
600 correlate_except = None
601 _where_criteria = ()
602 _having_criteria = ()
604 @classmethod
605 def create_for_statement(cls, statement, compiler, **kw):
606 """compiler hook, we arrive here from compiler.visit_select() only."""
608 self = cls.__new__(cls)
610 if compiler is not None:
611 toplevel = not compiler.stack
612 self.global_attributes = compiler._global_attributes
613 else:
614 toplevel = True
615 self.global_attributes = {}
617 select_statement = statement
619 # if we are a select() that was never a legacy Query, we won't
620 # have ORM level compile options.
621 statement._compile_options = cls.default_compile_options.safe_merge(
622 statement._compile_options
623 )
625 if select_statement._execution_options:
626 # execution options should not impact the compilation of a
627 # query, and at the moment subqueryloader is putting some things
628 # in here that we explicitly don't want stuck in a cache.
629 self.select_statement = select_statement._clone()
630 self.select_statement._execution_options = util.immutabledict()
631 else:
632 self.select_statement = select_statement
634 # indicates this select() came from Query.statement
635 self.for_statement = select_statement._compile_options._for_statement
637 # generally if we are from Query or directly from a select()
638 self.use_legacy_query_style = (
639 select_statement._compile_options._use_legacy_query_style
640 )
642 self._entities = []
643 self._primary_entity = None
644 self._aliased_generations = {}
645 self._polymorphic_adapters = {}
646 self._no_yield_pers = set()
648 # legacy: only for query.with_polymorphic()
649 if select_statement._compile_options._with_polymorphic_adapt_map:
650 self._with_polymorphic_adapt_map = dict(
651 select_statement._compile_options._with_polymorphic_adapt_map
652 )
653 self._setup_with_polymorphics()
655 self.compile_options = select_statement._compile_options
657 if not toplevel:
658 # for subqueries, turn off eagerloads and set
659 # "render_for_subquery".
660 self.compile_options += {
661 "_enable_eagerloads": False,
662 "_render_for_subquery": True,
663 }
665 # determine label style. we can make different decisions here.
666 # at the moment, trying to see if we can always use DISAMBIGUATE_ONLY
667 # rather than LABEL_STYLE_NONE, and if we can use disambiguate style
668 # for new style ORM selects too.
669 if (
670 self.use_legacy_query_style
671 and self.select_statement._label_style is LABEL_STYLE_LEGACY_ORM
672 ):
673 if not self.for_statement:
674 self.label_style = LABEL_STYLE_TABLENAME_PLUS_COL
675 else:
676 self.label_style = LABEL_STYLE_DISAMBIGUATE_ONLY
677 else:
678 self.label_style = self.select_statement._label_style
680 if select_statement._memoized_select_entities:
681 self._memoized_entities = {
682 memoized_entities: _QueryEntity.to_compile_state(
683 self,
684 memoized_entities._raw_columns,
685 [],
686 is_current_entities=False,
687 )
688 for memoized_entities in (
689 select_statement._memoized_select_entities
690 )
691 }
693 # label_convention is stateful and will yield deduping keys if it
694 # sees the same key twice. therefore it's important that it is not
695 # invoked for the above "memoized" entities that aren't actually
696 # in the columns clause
697 self._label_convention = self._column_naming_convention(
698 statement._label_style, self.use_legacy_query_style
699 )
701 _QueryEntity.to_compile_state(
702 self,
703 select_statement._raw_columns,
704 self._entities,
705 is_current_entities=True,
706 )
708 self.current_path = select_statement._compile_options._current_path
710 self.eager_order_by = ()
712 if toplevel and (
713 select_statement._with_options
714 or select_statement._memoized_select_entities
715 ):
716 self.attributes = {"_unbound_load_dedupes": set()}
718 for (
719 memoized_entities
720 ) in select_statement._memoized_select_entities:
721 for opt in memoized_entities._with_options:
722 if opt._is_compile_state:
723 opt.process_compile_state_replaced_entities(
724 self,
725 [
726 ent
727 for ent in self._memoized_entities[
728 memoized_entities
729 ]
730 if isinstance(ent, _MapperEntity)
731 ],
732 )
734 for opt in self.select_statement._with_options:
735 if opt._is_compile_state:
736 opt.process_compile_state(self)
737 else:
738 self.attributes = {}
740 if select_statement._with_context_options:
741 for fn, key in select_statement._with_context_options:
742 fn(self)
744 self.primary_columns = []
745 self.secondary_columns = []
746 self.dedupe_columns = set()
747 self.eager_joins = {}
748 self.extra_criteria_entities = {}
749 self.create_eager_joins = []
750 self._fallback_from_clauses = []
752 # normalize the FROM clauses early by themselves, as this makes
753 # it an easier job when we need to assemble a JOIN onto these,
754 # for select.join() as well as joinedload(). As of 1.4 there are now
755 # potentially more complex sets of FROM objects here as the use
756 # of lambda statements for lazyload, load_on_pk etc. uses more
757 # cloning of the select() construct. See #6495
758 self.from_clauses = self._normalize_froms(
759 info.selectable for info in select_statement._from_obj
760 )
762 # this is a fairly arbitrary break into a second method,
763 # so it might be nicer to break up create_for_statement()
764 # and _setup_for_generate into three or four logical sections
765 self._setup_for_generate()
767 SelectState.__init__(self, self.statement, compiler, **kw)
769 return self
771 def _setup_for_generate(self):
772 query = self.select_statement
774 self.statement = None
775 self._join_entities = ()
777 if self.compile_options._set_base_alias:
778 self._set_select_from_alias()
780 for memoized_entities in query._memoized_select_entities:
781 if memoized_entities._setup_joins:
782 self._join(
783 memoized_entities._setup_joins,
784 self._memoized_entities[memoized_entities],
785 )
786 if memoized_entities._legacy_setup_joins:
787 self._legacy_join(
788 memoized_entities._legacy_setup_joins,
789 self._memoized_entities[memoized_entities],
790 )
792 if query._setup_joins:
793 self._join(query._setup_joins, self._entities)
795 if query._legacy_setup_joins:
796 self._legacy_join(query._legacy_setup_joins, self._entities)
798 current_adapter = self._get_current_adapter()
800 if query._where_criteria:
801 self._where_criteria = query._where_criteria
803 if current_adapter:
804 self._where_criteria = tuple(
805 current_adapter(crit, True)
806 for crit in self._where_criteria
807 )
809 # TODO: some complexity with order_by here was due to mapper.order_by.
810 # now that this is removed we can hopefully make order_by /
811 # group_by act identically to how they are in Core select.
812 self.order_by = (
813 self._adapt_col_list(query._order_by_clauses, current_adapter)
814 if current_adapter and query._order_by_clauses not in (None, False)
815 else query._order_by_clauses
816 )
818 if query._having_criteria:
819 self._having_criteria = tuple(
820 current_adapter(crit, True) if current_adapter else crit
821 for crit in query._having_criteria
822 )
824 self.group_by = (
825 self._adapt_col_list(
826 util.flatten_iterator(query._group_by_clauses), current_adapter
827 )
828 if current_adapter and query._group_by_clauses not in (None, False)
829 else query._group_by_clauses or None
830 )
832 if self.eager_order_by:
833 adapter = self.from_clauses[0]._target_adapter
834 self.eager_order_by = adapter.copy_and_process(self.eager_order_by)
836 if query._distinct_on:
837 self.distinct_on = self._adapt_col_list(
838 query._distinct_on, current_adapter
839 )
840 else:
841 self.distinct_on = ()
843 self.distinct = query._distinct
845 if query._correlate:
846 # ORM mapped entities that are mapped to joins can be passed
847 # to .correlate, so here they are broken into their component
848 # tables.
849 self.correlate = tuple(
850 util.flatten_iterator(
851 sql_util.surface_selectables(s) if s is not None else None
852 for s in query._correlate
853 )
854 )
855 elif query._correlate_except is not None:
856 self.correlate_except = tuple(
857 util.flatten_iterator(
858 sql_util.surface_selectables(s) if s is not None else None
859 for s in query._correlate_except
860 )
861 )
862 elif not query._auto_correlate:
863 self.correlate = (None,)
865 # PART II
867 self._for_update_arg = query._for_update_arg
869 if self.compile_options._is_star and (len(self._entities) != 1):
870 raise sa_exc.CompileError(
871 "Can't generate ORM query that includes multiple expressions "
872 "at the same time as '*'; query for '*' alone if present"
873 )
874 for entity in self._entities:
875 entity.setup_compile_state(self)
877 for rec in self.create_eager_joins:
878 strategy = rec[0]
879 strategy(self, *rec[1:])
881 # else "load from discrete FROMs" mode,
882 # i.e. when each _MappedEntity has its own FROM
884 if self.compile_options._enable_single_crit:
885 self._adjust_for_extra_criteria()
887 if not self.primary_columns:
888 if self.compile_options._only_load_props:
889 raise sa_exc.InvalidRequestError(
890 "No column-based properties specified for "
891 "refresh operation. Use session.expire() "
892 "to reload collections and related items."
893 )
894 else:
895 raise sa_exc.InvalidRequestError(
896 "Query contains no columns with which to SELECT from."
897 )
899 if not self.from_clauses:
900 self.from_clauses = list(self._fallback_from_clauses)
902 if self.order_by is False:
903 self.order_by = None
905 if (
906 self.multi_row_eager_loaders
907 and self.eager_adding_joins
908 and self._should_nest_selectable
909 ):
910 self.statement = self._compound_eager_statement()
911 else:
912 self.statement = self._simple_statement()
914 if self.for_statement:
915 ezero = self._mapper_zero()
916 if ezero is not None:
917 # TODO: this goes away once we get rid of the deep entity
918 # thing
919 self.statement = self.statement._annotate(
920 {"deepentity": ezero}
921 )
923 @classmethod
924 def _create_entities_collection(cls, query, legacy):
925 """Creates a partial ORMSelectCompileState that includes
926 the full collection of _MapperEntity and other _QueryEntity objects.
928 Supports a few remaining use cases that are pre-compilation
929 but still need to gather some of the column / adaption information.
931 """
932 self = cls.__new__(cls)
934 self._entities = []
935 self._primary_entity = None
936 self._aliased_generations = {}
937 self._polymorphic_adapters = {}
939 compile_options = cls.default_compile_options.safe_merge(
940 query._compile_options
941 )
942 # legacy: only for query.with_polymorphic()
943 if compile_options._with_polymorphic_adapt_map:
944 self._with_polymorphic_adapt_map = dict(
945 compile_options._with_polymorphic_adapt_map
946 )
947 self._setup_with_polymorphics()
949 self._label_convention = self._column_naming_convention(
950 query._label_style, legacy
951 )
953 # entities will also set up polymorphic adapters for mappers
954 # that have with_polymorphic configured
955 _QueryEntity.to_compile_state(
956 self, query._raw_columns, self._entities, is_current_entities=True
957 )
958 return self
960 @classmethod
961 def determine_last_joined_entity(cls, statement):
962 setup_joins = statement._setup_joins
964 if not setup_joins:
965 return None
967 (target, onclause, from_, flags) = setup_joins[-1]
969 if isinstance(target, interfaces.PropComparator):
970 return target.entity
971 else:
972 return target
974 @classmethod
975 def all_selected_columns(cls, statement):
976 for element in statement._raw_columns:
977 if (
978 element.is_selectable
979 and "entity_namespace" in element._annotations
980 ):
981 ens = element._annotations["entity_namespace"]
982 if not ens.is_mapper and not ens.is_aliased_class:
983 for elem in _select_iterables([element]):
984 yield elem
985 else:
986 for elem in _select_iterables(ens._all_column_expressions):
987 yield elem
988 else:
989 for elem in _select_iterables([element]):
990 yield elem
992 @classmethod
993 def get_columns_clause_froms(cls, statement):
994 return cls._normalize_froms(
995 itertools.chain.from_iterable(
996 element._from_objects
997 if "parententity" not in element._annotations
998 else [
999 element._annotations["parententity"].__clause_element__()
1000 ]
1001 for element in statement._raw_columns
1002 )
1003 )
1005 @classmethod
1006 @util.preload_module("sqlalchemy.orm.query")
1007 def from_statement(cls, statement, from_statement):
1008 query = util.preloaded.orm_query
1010 from_statement = coercions.expect(
1011 roles.ReturnsRowsRole,
1012 from_statement,
1013 apply_propagate_attrs=statement,
1014 )
1016 stmt = query.FromStatement(statement._raw_columns, from_statement)
1018 stmt.__dict__.update(
1019 _with_options=statement._with_options,
1020 _with_context_options=statement._with_context_options,
1021 _execution_options=statement._execution_options,
1022 _propagate_attrs=statement._propagate_attrs,
1023 )
1024 return stmt
1026 def _setup_with_polymorphics(self):
1027 # legacy: only for query.with_polymorphic()
1028 for ext_info, wp in self._with_polymorphic_adapt_map.items():
1029 self._mapper_loads_polymorphically_with(ext_info, wp._adapter)
1031 def _set_select_from_alias(self):
1033 query = self.select_statement # query
1035 assert self.compile_options._set_base_alias
1036 assert len(query._from_obj) == 1
1038 adapter = self._get_select_from_alias_from_obj(query._from_obj[0])
1039 if adapter:
1040 self.compile_options += {"_enable_single_crit": False}
1041 self._from_obj_alias = adapter
1043 def _get_select_from_alias_from_obj(self, from_obj):
1044 info = from_obj
1046 if "parententity" in info._annotations:
1047 info = info._annotations["parententity"]
1049 if hasattr(info, "mapper"):
1050 if not info.is_aliased_class:
1051 raise sa_exc.ArgumentError(
1052 "A selectable (FromClause) instance is "
1053 "expected when the base alias is being set."
1054 )
1055 else:
1056 return info._adapter
1058 elif isinstance(info.selectable, sql.selectable.AliasedReturnsRows):
1059 equivs = self._all_equivs()
1060 return sql_util.ColumnAdapter(info, equivs)
1061 else:
1062 return None
1064 def _mapper_zero(self):
1065 """return the Mapper associated with the first QueryEntity."""
1066 return self._entities[0].mapper
1068 def _entity_zero(self):
1069 """Return the 'entity' (mapper or AliasedClass) associated
1070 with the first QueryEntity, or alternatively the 'select from'
1071 entity if specified."""
1073 for ent in self.from_clauses:
1074 if "parententity" in ent._annotations:
1075 return ent._annotations["parententity"]
1076 for qent in self._entities:
1077 if qent.entity_zero:
1078 return qent.entity_zero
1080 return None
1082 def _only_full_mapper_zero(self, methname):
1083 if self._entities != [self._primary_entity]:
1084 raise sa_exc.InvalidRequestError(
1085 "%s() can only be used against "
1086 "a single mapped class." % methname
1087 )
1088 return self._primary_entity.entity_zero
1090 def _only_entity_zero(self, rationale=None):
1091 if len(self._entities) > 1:
1092 raise sa_exc.InvalidRequestError(
1093 rationale
1094 or "This operation requires a Query "
1095 "against a single mapper."
1096 )
1097 return self._entity_zero()
1099 def _all_equivs(self):
1100 equivs = {}
1102 for memoized_entities in self._memoized_entities.values():
1103 for ent in [
1104 ent
1105 for ent in memoized_entities
1106 if isinstance(ent, _MapperEntity)
1107 ]:
1108 equivs.update(ent.mapper._equivalent_columns)
1110 for ent in [
1111 ent for ent in self._entities if isinstance(ent, _MapperEntity)
1112 ]:
1113 equivs.update(ent.mapper._equivalent_columns)
1114 return equivs
1116 def _compound_eager_statement(self):
1117 # for eager joins present and LIMIT/OFFSET/DISTINCT,
1118 # wrap the query inside a select,
1119 # then append eager joins onto that
1121 if self.order_by:
1122 # the default coercion for ORDER BY is now the OrderByRole,
1123 # which adds an additional post coercion to ByOfRole in that
1124 # elements are converted into label references. For the
1125 # eager load / subquery wrapping case, we need to un-coerce
1126 # the original expressions outside of the label references
1127 # in order to have them render.
1128 unwrapped_order_by = [
1129 elem.element
1130 if isinstance(elem, sql.elements._label_reference)
1131 else elem
1132 for elem in self.order_by
1133 ]
1135 order_by_col_expr = sql_util.expand_column_list_from_order_by(
1136 self.primary_columns, unwrapped_order_by
1137 )
1138 else:
1139 order_by_col_expr = []
1140 unwrapped_order_by = None
1142 # put FOR UPDATE on the inner query, where MySQL will honor it,
1143 # as well as if it has an OF so PostgreSQL can use it.
1144 inner = self._select_statement(
1145 self.primary_columns
1146 + [c for c in order_by_col_expr if c not in self.dedupe_columns],
1147 self.from_clauses,
1148 self._where_criteria,
1149 self._having_criteria,
1150 self.label_style,
1151 self.order_by,
1152 for_update=self._for_update_arg,
1153 hints=self.select_statement._hints,
1154 statement_hints=self.select_statement._statement_hints,
1155 correlate=self.correlate,
1156 correlate_except=self.correlate_except,
1157 **self._select_args
1158 )
1160 inner = inner.alias()
1162 equivs = self._all_equivs()
1164 self.compound_eager_adapter = sql_util.ColumnAdapter(inner, equivs)
1166 statement = future.select(
1167 *([inner] + self.secondary_columns) # use_labels=self.labels
1168 )
1169 statement._label_style = self.label_style
1171 # Oracle however does not allow FOR UPDATE on the subquery,
1172 # and the Oracle dialect ignores it, plus for PostgreSQL, MySQL
1173 # we expect that all elements of the row are locked, so also put it
1174 # on the outside (except in the case of PG when OF is used)
1175 if (
1176 self._for_update_arg is not None
1177 and self._for_update_arg.of is None
1178 ):
1179 statement._for_update_arg = self._for_update_arg
1181 from_clause = inner
1182 for eager_join in self.eager_joins.values():
1183 # EagerLoader places a 'stop_on' attribute on the join,
1184 # giving us a marker as to where the "splice point" of
1185 # the join should be
1186 from_clause = sql_util.splice_joins(
1187 from_clause, eager_join, eager_join.stop_on
1188 )
1190 statement.select_from.non_generative(statement, from_clause)
1192 if unwrapped_order_by:
1193 statement.order_by.non_generative(
1194 statement,
1195 *self.compound_eager_adapter.copy_and_process(
1196 unwrapped_order_by
1197 )
1198 )
1200 statement.order_by.non_generative(statement, *self.eager_order_by)
1201 return statement
1203 def _simple_statement(self):
1205 if (
1206 self.compile_options._use_legacy_query_style
1207 and (self.distinct and not self.distinct_on)
1208 and self.order_by
1209 ):
1210 to_add = sql_util.expand_column_list_from_order_by(
1211 self.primary_columns, self.order_by
1212 )
1213 if to_add:
1214 util.warn_deprecated_20(
1215 "ORDER BY columns added implicitly due to "
1216 "DISTINCT is deprecated and will be removed in "
1217 "SQLAlchemy 2.0. SELECT statements with DISTINCT "
1218 "should be written to explicitly include the appropriate "
1219 "columns in the columns clause"
1220 )
1221 self.primary_columns += to_add
1223 statement = self._select_statement(
1224 self.primary_columns + self.secondary_columns,
1225 tuple(self.from_clauses) + tuple(self.eager_joins.values()),
1226 self._where_criteria,
1227 self._having_criteria,
1228 self.label_style,
1229 self.order_by,
1230 for_update=self._for_update_arg,
1231 hints=self.select_statement._hints,
1232 statement_hints=self.select_statement._statement_hints,
1233 correlate=self.correlate,
1234 correlate_except=self.correlate_except,
1235 **self._select_args
1236 )
1238 if self.eager_order_by:
1239 statement.order_by.non_generative(statement, *self.eager_order_by)
1240 return statement
1242 def _select_statement(
1243 self,
1244 raw_columns,
1245 from_obj,
1246 where_criteria,
1247 having_criteria,
1248 label_style,
1249 order_by,
1250 for_update,
1251 hints,
1252 statement_hints,
1253 correlate,
1254 correlate_except,
1255 limit_clause,
1256 offset_clause,
1257 fetch_clause,
1258 fetch_clause_options,
1259 distinct,
1260 distinct_on,
1261 prefixes,
1262 suffixes,
1263 group_by,
1264 ):
1266 Select = future.Select
1267 statement = Select._create_raw_select(
1268 _raw_columns=raw_columns,
1269 _from_obj=from_obj,
1270 _label_style=label_style,
1271 )
1273 if where_criteria:
1274 statement._where_criteria = where_criteria
1275 if having_criteria:
1276 statement._having_criteria = having_criteria
1278 if order_by:
1279 statement._order_by_clauses += tuple(order_by)
1281 if distinct_on:
1282 statement.distinct.non_generative(statement, *distinct_on)
1283 elif distinct:
1284 statement.distinct.non_generative(statement)
1286 if group_by:
1287 statement._group_by_clauses += tuple(group_by)
1289 statement._limit_clause = limit_clause
1290 statement._offset_clause = offset_clause
1291 statement._fetch_clause = fetch_clause
1292 statement._fetch_clause_options = fetch_clause_options
1294 if prefixes:
1295 statement._prefixes = prefixes
1297 if suffixes:
1298 statement._suffixes = suffixes
1300 statement._for_update_arg = for_update
1302 if hints:
1303 statement._hints = hints
1304 if statement_hints:
1305 statement._statement_hints = statement_hints
1307 if correlate:
1308 statement.correlate.non_generative(statement, *correlate)
1310 if correlate_except is not None:
1311 statement.correlate_except.non_generative(
1312 statement, *correlate_except
1313 )
1315 return statement
1317 def _adapt_polymorphic_element(self, element):
1318 if "parententity" in element._annotations:
1319 search = element._annotations["parententity"]
1320 alias = self._polymorphic_adapters.get(search, None)
1321 if alias:
1322 return alias.adapt_clause(element)
1324 if isinstance(element, expression.FromClause):
1325 search = element
1326 elif hasattr(element, "table"):
1327 search = element.table
1328 else:
1329 return None
1331 alias = self._polymorphic_adapters.get(search, None)
1332 if alias:
1333 return alias.adapt_clause(element)
1335 def _adapt_aliased_generation(self, element):
1336 # this is crazy logic that I look forward to blowing away
1337 # when aliased=True is gone :)
1338 if "aliased_generation" in element._annotations:
1339 for adapter in self._aliased_generations.get(
1340 element._annotations["aliased_generation"], ()
1341 ):
1342 replaced_elem = adapter.replace(element)
1343 if replaced_elem is not None:
1344 return replaced_elem
1346 return None
1348 def _adapt_col_list(self, cols, current_adapter):
1349 if current_adapter:
1350 return [current_adapter(o, True) for o in cols]
1351 else:
1352 return cols
1354 def _get_current_adapter(self):
1356 adapters = []
1358 if self._from_obj_alias:
1359 # used for legacy going forward for query set_ops, e.g.
1360 # union(), union_all(), etc.
1361 # 1.4 and previously, also used for from_self(),
1362 # select_entity_from()
1363 #
1364 # for the "from obj" alias, apply extra rule to the
1365 # 'ORM only' check, if this query were generated from a
1366 # subquery of itself, i.e. _from_selectable(), apply adaption
1367 # to all SQL constructs.
1368 adapters.append(
1369 (
1370 False
1371 if self.compile_options._orm_only_from_obj_alias
1372 else True,
1373 self._from_obj_alias.replace,
1374 )
1375 )
1377 # vvvvvvvvvvvvvvv legacy vvvvvvvvvvvvvvvvvv
1378 # this can totally go away when we remove join(..., aliased=True)
1379 if self._aliased_generations:
1380 adapters.append((False, self._adapt_aliased_generation))
1381 # ^^^^^^^^^^^^^ legacy ^^^^^^^^^^^^^^^^^^^^^
1383 # this was *hopefully* the only adapter we were going to need
1384 # going forward...however, we unfortunately need _from_obj_alias
1385 # for query.union(), which we can't drop
1386 if self._polymorphic_adapters:
1387 adapters.append((False, self._adapt_polymorphic_element))
1389 if not adapters:
1390 return None
1392 def _adapt_clause(clause, as_filter):
1393 # do we adapt all expression elements or only those
1394 # tagged as 'ORM' constructs ?
1396 def replace(elem):
1397 is_orm_adapt = (
1398 "_orm_adapt" in elem._annotations
1399 or "parententity" in elem._annotations
1400 )
1401 for always_adapt, adapter in adapters:
1402 if is_orm_adapt or always_adapt:
1403 e = adapter(elem)
1404 if e is not None:
1405 return e
1407 return visitors.replacement_traverse(clause, {}, replace)
1409 return _adapt_clause
1411 def _join(self, args, entities_collection):
1412 for (right, onclause, from_, flags) in args:
1413 isouter = flags["isouter"]
1414 full = flags["full"]
1415 # maybe?
1416 self._reset_joinpoint()
1418 right = inspect(right)
1419 if onclause is not None:
1420 onclause = inspect(onclause)
1422 if onclause is None and isinstance(
1423 right, interfaces.PropComparator
1424 ):
1425 # determine onclause/right_entity. still need to think
1426 # about how to best organize this since we are getting:
1427 #
1428 #
1429 # q.join(Entity, Parent.property)
1430 # q.join(Parent.property)
1431 # q.join(Parent.property.of_type(Entity))
1432 # q.join(some_table)
1433 # q.join(some_table, some_parent.c.id==some_table.c.parent_id)
1434 #
1435 # is this still too many choices? how do we handle this
1436 # when sometimes "right" is implied and sometimes not?
1437 #
1438 onclause = right
1439 right = None
1440 elif "parententity" in right._annotations:
1441 right = right._annotations["parententity"]
1443 if onclause is None:
1444 if not right.is_selectable and not hasattr(right, "mapper"):
1445 raise sa_exc.ArgumentError(
1446 "Expected mapped entity or "
1447 "selectable/table as join target"
1448 )
1450 of_type = None
1452 if isinstance(onclause, interfaces.PropComparator):
1453 # descriptor/property given (or determined); this tells us
1454 # explicitly what the expected "left" side of the join is.
1456 of_type = getattr(onclause, "_of_type", None)
1458 if right is None:
1459 if of_type:
1460 right = of_type
1461 else:
1462 right = onclause.property
1464 try:
1465 right = right.entity
1466 except AttributeError as err:
1467 util.raise_(
1468 sa_exc.ArgumentError(
1469 "Join target %s does not refer to a "
1470 "mapped entity" % right
1471 ),
1472 replace_context=err,
1473 )
1475 left = onclause._parententity
1477 alias = self._polymorphic_adapters.get(left, None)
1479 # could be None or could be ColumnAdapter also
1480 if isinstance(alias, ORMAdapter) and alias.mapper.isa(left):
1481 left = alias.aliased_class
1482 onclause = getattr(left, onclause.key)
1484 prop = onclause.property
1485 if not isinstance(onclause, attributes.QueryableAttribute):
1486 onclause = prop
1488 # TODO: this is where "check for path already present"
1489 # would occur. see if this still applies?
1491 if from_ is not None:
1492 if (
1493 from_ is not left
1494 and from_._annotations.get("parententity", None)
1495 is not left
1496 ):
1497 raise sa_exc.InvalidRequestError(
1498 "explicit from clause %s does not match left side "
1499 "of relationship attribute %s"
1500 % (
1501 from_._annotations.get("parententity", from_),
1502 onclause,
1503 )
1504 )
1505 elif from_ is not None:
1506 prop = None
1507 left = from_
1508 else:
1509 # no descriptor/property given; we will need to figure out
1510 # what the effective "left" side is
1511 prop = left = None
1513 # figure out the final "left" and "right" sides and create an
1514 # ORMJoin to add to our _from_obj tuple
1515 self._join_left_to_right(
1516 entities_collection,
1517 left,
1518 right,
1519 onclause,
1520 prop,
1521 False,
1522 False,
1523 isouter,
1524 full,
1525 )
1527 def _legacy_join(self, args, entities_collection):
1528 """consumes arguments from join() or outerjoin(), places them into a
1529 consistent format with which to form the actual JOIN constructs.
1531 """
1532 for (right, onclause, left, flags) in args:
1534 outerjoin = flags["isouter"]
1535 create_aliases = flags["aliased"]
1536 from_joinpoint = flags["from_joinpoint"]
1537 full = flags["full"]
1538 aliased_generation = flags["aliased_generation"]
1540 # do a quick inspect to accommodate for a lambda
1541 if right is not None and not isinstance(right, util.string_types):
1542 right = inspect(right)
1543 if onclause is not None and not isinstance(
1544 onclause, util.string_types
1545 ):
1546 onclause = inspect(onclause)
1548 # legacy vvvvvvvvvvvvvvvvvvvvvvvvvv
1549 if not from_joinpoint:
1550 self._reset_joinpoint()
1551 else:
1552 prev_aliased_generation = self._joinpoint.get(
1553 "aliased_generation", None
1554 )
1555 if not aliased_generation:
1556 aliased_generation = prev_aliased_generation
1557 elif prev_aliased_generation:
1558 self._aliased_generations[
1559 aliased_generation
1560 ] = self._aliased_generations.get(
1561 prev_aliased_generation, ()
1562 )
1563 # legacy ^^^^^^^^^^^^^^^^^^^^^^^^^^^
1565 if (
1566 isinstance(
1567 right, (interfaces.PropComparator, util.string_types)
1568 )
1569 and onclause is None
1570 ):
1571 onclause = right
1572 right = None
1573 elif "parententity" in right._annotations:
1574 right = right._annotations["parententity"]
1576 if onclause is None:
1577 if not right.is_selectable and not hasattr(right, "mapper"):
1578 raise sa_exc.ArgumentError(
1579 "Expected mapped entity or "
1580 "selectable/table as join target"
1581 )
1583 if isinstance(onclause, interfaces.PropComparator):
1584 of_type = getattr(onclause, "_of_type", None)
1585 else:
1586 of_type = None
1588 if isinstance(onclause, util.string_types):
1589 # string given, e.g. query(Foo).join("bar").
1590 # we look to the left entity or what we last joined
1591 # towards
1592 onclause = _entity_namespace_key(
1593 inspect(self._joinpoint_zero()), onclause
1594 )
1596 # legacy vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
1597 # check for q.join(Class.propname, from_joinpoint=True)
1598 # and Class corresponds at the mapper level to the current
1599 # joinpoint. this match intentionally looks for a non-aliased
1600 # class-bound descriptor as the onclause and if it matches the
1601 # current joinpoint at the mapper level, it's used. This
1602 # is a very old use case that is intended to make it easier
1603 # to work with the aliased=True flag, which is also something
1604 # that probably shouldn't exist on join() due to its high
1605 # complexity/usefulness ratio
1606 elif from_joinpoint and isinstance(
1607 onclause, interfaces.PropComparator
1608 ):
1609 jp0 = self._joinpoint_zero()
1610 info = inspect(jp0)
1612 if getattr(info, "mapper", None) is onclause._parententity:
1613 onclause = _entity_namespace_key(info, onclause.key)
1614 # legacy ^^^^^^^^^^^^^^^^^^^^^^^^^^^
1616 if isinstance(onclause, interfaces.PropComparator):
1617 # descriptor/property given (or determined); this tells us
1618 # explicitly what the expected "left" side of the join is.
1619 if right is None:
1620 if of_type:
1621 right = of_type
1622 else:
1623 right = onclause.property
1625 try:
1626 right = right.entity
1627 except AttributeError as err:
1628 util.raise_(
1629 sa_exc.ArgumentError(
1630 "Join target %s does not refer to a "
1631 "mapped entity" % right
1632 ),
1633 replace_context=err,
1634 )
1636 left = onclause._parententity
1638 alias = self._polymorphic_adapters.get(left, None)
1640 # could be None or could be ColumnAdapter also
1641 if isinstance(alias, ORMAdapter) and alias.mapper.isa(left):
1642 left = alias.aliased_class
1643 onclause = getattr(left, onclause.key)
1645 prop = onclause.property
1646 if not isinstance(onclause, attributes.QueryableAttribute):
1647 onclause = prop
1649 if not create_aliases:
1650 # check for this path already present.
1651 # don't render in that case.
1652 edge = (left, right, prop.key)
1653 if edge in self._joinpoint:
1654 # The child's prev reference might be stale --
1655 # it could point to a parent older than the
1656 # current joinpoint. If this is the case,
1657 # then we need to update it and then fix the
1658 # tree's spine with _update_joinpoint. Copy
1659 # and then mutate the child, which might be
1660 # shared by a different query object.
1661 jp = self._joinpoint[edge].copy()
1662 jp["prev"] = (edge, self._joinpoint)
1663 self._update_joinpoint(jp)
1665 continue
1667 else:
1668 # no descriptor/property given; we will need to figure out
1669 # what the effective "left" side is
1670 prop = left = None
1672 # figure out the final "left" and "right" sides and create an
1673 # ORMJoin to add to our _from_obj tuple
1674 self._join_left_to_right(
1675 entities_collection,
1676 left,
1677 right,
1678 onclause,
1679 prop,
1680 create_aliases,
1681 aliased_generation,
1682 outerjoin,
1683 full,
1684 )
1686 def _joinpoint_zero(self):
1687 return self._joinpoint.get("_joinpoint_entity", self._entity_zero())
1689 def _join_left_to_right(
1690 self,
1691 entities_collection,
1692 left,
1693 right,
1694 onclause,
1695 prop,
1696 create_aliases,
1697 aliased_generation,
1698 outerjoin,
1699 full,
1700 ):
1701 """given raw "left", "right", "onclause" parameters consumed from
1702 a particular key within _join(), add a real ORMJoin object to
1703 our _from_obj list (or augment an existing one)
1705 """
1707 if left is None:
1708 # left not given (e.g. no relationship object/name specified)
1709 # figure out the best "left" side based on our existing froms /
1710 # entities
1711 assert prop is None
1712 (
1713 left,
1714 replace_from_obj_index,
1715 use_entity_index,
1716 ) = self._join_determine_implicit_left_side(
1717 entities_collection, left, right, onclause
1718 )
1719 else:
1720 # left is given via a relationship/name, or as explicit left side.
1721 # Determine where in our
1722 # "froms" list it should be spliced/appended as well as what
1723 # existing entity it corresponds to.
1724 (
1725 replace_from_obj_index,
1726 use_entity_index,
1727 ) = self._join_place_explicit_left_side(entities_collection, left)
1729 if left is right and not create_aliases:
1730 raise sa_exc.InvalidRequestError(
1731 "Can't construct a join from %s to %s, they "
1732 "are the same entity" % (left, right)
1733 )
1735 # the right side as given often needs to be adapted. additionally
1736 # a lot of things can be wrong with it. handle all that and
1737 # get back the new effective "right" side
1738 r_info, right, onclause = self._join_check_and_adapt_right_side(
1739 left, right, onclause, prop, create_aliases, aliased_generation
1740 )
1742 if not r_info.is_selectable:
1743 extra_criteria = self._get_extra_criteria(r_info)
1744 else:
1745 extra_criteria = ()
1747 if replace_from_obj_index is not None:
1748 # splice into an existing element in the
1749 # self._from_obj list
1750 left_clause = self.from_clauses[replace_from_obj_index]
1752 self.from_clauses = (
1753 self.from_clauses[:replace_from_obj_index]
1754 + [
1755 _ORMJoin(
1756 left_clause,
1757 right,
1758 onclause,
1759 isouter=outerjoin,
1760 full=full,
1761 _extra_criteria=extra_criteria,
1762 )
1763 ]
1764 + self.from_clauses[replace_from_obj_index + 1 :]
1765 )
1766 else:
1767 # add a new element to the self._from_obj list
1768 if use_entity_index is not None:
1769 # make use of _MapperEntity selectable, which is usually
1770 # entity_zero.selectable, but if with_polymorphic() were used
1771 # might be distinct
1772 assert isinstance(
1773 entities_collection[use_entity_index], _MapperEntity
1774 )
1775 left_clause = entities_collection[use_entity_index].selectable
1776 else:
1777 left_clause = left
1779 self.from_clauses = self.from_clauses + [
1780 _ORMJoin(
1781 left_clause,
1782 r_info,
1783 onclause,
1784 isouter=outerjoin,
1785 full=full,
1786 _extra_criteria=extra_criteria,
1787 )
1788 ]
1790 def _join_determine_implicit_left_side(
1791 self, entities_collection, left, right, onclause
1792 ):
1793 """When join conditions don't express the left side explicitly,
1794 determine if an existing FROM or entity in this query
1795 can serve as the left hand side.
1797 """
1799 # when we are here, it means join() was called without an ORM-
1800 # specific way of telling us what the "left" side is, e.g.:
1801 #
1802 # join(RightEntity)
1803 #
1804 # or
1805 #
1806 # join(RightEntity, RightEntity.foo == LeftEntity.bar)
1807 #
1809 r_info = inspect(right)
1811 replace_from_obj_index = use_entity_index = None
1813 if self.from_clauses:
1814 # we have a list of FROMs already. So by definition this
1815 # join has to connect to one of those FROMs.
1817 indexes = sql_util.find_left_clause_to_join_from(
1818 self.from_clauses, r_info.selectable, onclause
1819 )
1821 if len(indexes) == 1:
1822 replace_from_obj_index = indexes[0]
1823 left = self.from_clauses[replace_from_obj_index]
1824 elif len(indexes) > 1:
1825 raise sa_exc.InvalidRequestError(
1826 "Can't determine which FROM clause to join "
1827 "from, there are multiple FROMS which can "
1828 "join to this entity. Please use the .select_from() "
1829 "method to establish an explicit left side, as well as "
1830 "providing an explicit ON clause if not present already "
1831 "to help resolve the ambiguity."
1832 )
1833 else:
1834 raise sa_exc.InvalidRequestError(
1835 "Don't know how to join to %r. "
1836 "Please use the .select_from() "
1837 "method to establish an explicit left side, as well as "
1838 "providing an explicit ON clause if not present already "
1839 "to help resolve the ambiguity." % (right,)
1840 )
1842 elif entities_collection:
1843 # we have no explicit FROMs, so the implicit left has to
1844 # come from our list of entities.
1846 potential = {}
1847 for entity_index, ent in enumerate(entities_collection):
1848 entity = ent.entity_zero_or_selectable
1849 if entity is None:
1850 continue
1851 ent_info = inspect(entity)
1852 if ent_info is r_info: # left and right are the same, skip
1853 continue
1855 # by using a dictionary with the selectables as keys this
1856 # de-duplicates those selectables as occurs when the query is
1857 # against a series of columns from the same selectable
1858 if isinstance(ent, _MapperEntity):
1859 potential[ent.selectable] = (entity_index, entity)
1860 else:
1861 potential[ent_info.selectable] = (None, entity)
1863 all_clauses = list(potential.keys())
1864 indexes = sql_util.find_left_clause_to_join_from(
1865 all_clauses, r_info.selectable, onclause
1866 )
1868 if len(indexes) == 1:
1869 use_entity_index, left = potential[all_clauses[indexes[0]]]
1870 elif len(indexes) > 1:
1871 raise sa_exc.InvalidRequestError(
1872 "Can't determine which FROM clause to join "
1873 "from, there are multiple FROMS which can "
1874 "join to this entity. Please use the .select_from() "
1875 "method to establish an explicit left side, as well as "
1876 "providing an explicit ON clause if not present already "
1877 "to help resolve the ambiguity."
1878 )
1879 else:
1880 raise sa_exc.InvalidRequestError(
1881 "Don't know how to join to %r. "
1882 "Please use the .select_from() "
1883 "method to establish an explicit left side, as well as "
1884 "providing an explicit ON clause if not present already "
1885 "to help resolve the ambiguity." % (right,)
1886 )
1887 else:
1888 raise sa_exc.InvalidRequestError(
1889 "No entities to join from; please use "
1890 "select_from() to establish the left "
1891 "entity/selectable of this join"
1892 )
1894 return left, replace_from_obj_index, use_entity_index
1896 def _join_place_explicit_left_side(self, entities_collection, left):
1897 """When join conditions express a left side explicitly, determine
1898 where in our existing list of FROM clauses we should join towards,
1899 or if we need to make a new join, and if so is it from one of our
1900 existing entities.
1902 """
1904 # when we are here, it means join() was called with an indicator
1905 # as to an exact left side, which means a path to a
1906 # RelationshipProperty was given, e.g.:
1907 #
1908 # join(RightEntity, LeftEntity.right)
1909 #
1910 # or
1911 #
1912 # join(LeftEntity.right)
1913 #
1914 # as well as string forms:
1915 #
1916 # join(RightEntity, "right")
1917 #
1918 # etc.
1919 #
1921 replace_from_obj_index = use_entity_index = None
1923 l_info = inspect(left)
1924 if self.from_clauses:
1925 indexes = sql_util.find_left_clause_that_matches_given(
1926 self.from_clauses, l_info.selectable
1927 )
1929 if len(indexes) > 1:
1930 raise sa_exc.InvalidRequestError(
1931 "Can't identify which entity in which to assign the "
1932 "left side of this join. Please use a more specific "
1933 "ON clause."
1934 )
1936 # have an index, means the left side is already present in
1937 # an existing FROM in the self._from_obj tuple
1938 if indexes:
1939 replace_from_obj_index = indexes[0]
1941 # no index, means we need to add a new element to the
1942 # self._from_obj tuple
1944 # no from element present, so we will have to add to the
1945 # self._from_obj tuple. Determine if this left side matches up
1946 # with existing mapper entities, in which case we want to apply the
1947 # aliasing / adaptation rules present on that entity if any
1948 if (
1949 replace_from_obj_index is None
1950 and entities_collection
1951 and hasattr(l_info, "mapper")
1952 ):
1953 for idx, ent in enumerate(entities_collection):
1954 # TODO: should we be checking for multiple mapper entities
1955 # matching?
1956 if isinstance(ent, _MapperEntity) and ent.corresponds_to(left):
1957 use_entity_index = idx
1958 break
1960 return replace_from_obj_index, use_entity_index
1962 def _join_check_and_adapt_right_side(
1963 self, left, right, onclause, prop, create_aliases, aliased_generation
1964 ):
1965 """transform the "right" side of the join as well as the onclause
1966 according to polymorphic mapping translations, aliasing on the query
1967 or on the join, special cases where the right and left side have
1968 overlapping tables.
1970 """
1972 l_info = inspect(left)
1973 r_info = inspect(right)
1975 overlap = False
1976 if not create_aliases:
1977 right_mapper = getattr(r_info, "mapper", None)
1978 # if the target is a joined inheritance mapping,
1979 # be more liberal about auto-aliasing.
1980 if right_mapper and (
1981 right_mapper.with_polymorphic
1982 or isinstance(right_mapper.persist_selectable, expression.Join)
1983 ):
1984 for from_obj in self.from_clauses or [l_info.selectable]:
1985 if sql_util.selectables_overlap(
1986 l_info.selectable, from_obj
1987 ) and sql_util.selectables_overlap(
1988 from_obj, r_info.selectable
1989 ):
1990 overlap = True
1991 break
1993 if (
1994 overlap or not create_aliases
1995 ) and l_info.selectable is r_info.selectable:
1996 raise sa_exc.InvalidRequestError(
1997 "Can't join table/selectable '%s' to itself"
1998 % l_info.selectable
1999 )
2001 right_mapper, right_selectable, right_is_aliased = (
2002 getattr(r_info, "mapper", None),
2003 r_info.selectable,
2004 getattr(r_info, "is_aliased_class", False),
2005 )
2007 if (
2008 right_mapper
2009 and prop
2010 and not right_mapper.common_parent(prop.mapper)
2011 ):
2012 raise sa_exc.InvalidRequestError(
2013 "Join target %s does not correspond to "
2014 "the right side of join condition %s" % (right, onclause)
2015 )
2017 # _join_entities is used as a hint for single-table inheritance
2018 # purposes at the moment
2019 if hasattr(r_info, "mapper"):
2020 self._join_entities += (r_info,)
2022 need_adapter = False
2024 # test for joining to an unmapped selectable as the target
2025 if r_info.is_clause_element:
2027 if prop:
2028 right_mapper = prop.mapper
2030 if right_selectable._is_lateral:
2031 # orm_only is disabled to suit the case where we have to
2032 # adapt an explicit correlate(Entity) - the select() loses
2033 # the ORM-ness in this case right now, ideally it would not
2034 current_adapter = self._get_current_adapter()
2035 if current_adapter is not None:
2036 # TODO: we had orm_only=False here before, removing
2037 # it didn't break things. if we identify the rationale,
2038 # may need to apply "_orm_only" annotation here.
2039 right = current_adapter(right, True)
2041 elif prop:
2042 # joining to selectable with a mapper property given
2043 # as the ON clause
2045 if not right_selectable.is_derived_from(
2046 right_mapper.persist_selectable
2047 ):
2048 raise sa_exc.InvalidRequestError(
2049 "Selectable '%s' is not derived from '%s'"
2050 % (
2051 right_selectable.description,
2052 right_mapper.persist_selectable.description,
2053 )
2054 )
2056 # if the destination selectable is a plain select(),
2057 # turn it into an alias().
2058 if isinstance(right_selectable, expression.SelectBase):
2059 right_selectable = coercions.expect(
2060 roles.FromClauseRole, right_selectable
2061 )
2062 need_adapter = True
2064 # make the right hand side target into an ORM entity
2065 right = aliased(right_mapper, right_selectable)
2067 util.warn_deprecated(
2068 "An alias is being generated automatically against "
2069 "joined entity %s for raw clauseelement, which is "
2070 "deprecated and will be removed in a later release. "
2071 "Use the aliased() "
2072 "construct explicitly, see the linked example."
2073 % right_mapper,
2074 "1.4",
2075 code="xaj1",
2076 )
2078 elif create_aliases:
2079 # it *could* work, but it doesn't right now and I'd rather
2080 # get rid of aliased=True completely
2081 raise sa_exc.InvalidRequestError(
2082 "The aliased=True parameter on query.join() only works "
2083 "with an ORM entity, not a plain selectable, as the "
2084 "target."
2085 )
2087 # test for overlap:
2088 # orm/inheritance/relationships.py
2089 # SelfReferentialM2MTest
2090 aliased_entity = right_mapper and not right_is_aliased and overlap
2092 if not need_adapter and (create_aliases or aliased_entity):
2093 # there are a few places in the ORM that automatic aliasing
2094 # is still desirable, and can't be automatic with a Core
2095 # only approach. For illustrations of "overlaps" see
2096 # test/orm/inheritance/test_relationships.py. There are also
2097 # general overlap cases with many-to-many tables where automatic
2098 # aliasing is desirable.
2099 right = aliased(right, flat=True)
2100 need_adapter = True
2102 if not create_aliases:
2103 util.warn(
2104 "An alias is being generated automatically against "
2105 "joined entity %s due to overlapping tables. This is a "
2106 "legacy pattern which may be "
2107 "deprecated in a later release. Use the "
2108 "aliased(<entity>, flat=True) "
2109 "construct explicitly, see the linked example."
2110 % right_mapper,
2111 code="xaj2",
2112 )
2114 if need_adapter:
2115 assert right_mapper
2117 adapter = ORMAdapter(
2118 right, equivalents=right_mapper._equivalent_columns
2119 )
2121 # if an alias() on the right side was generated,
2122 # which is intended to wrap a the right side in a subquery,
2123 # ensure that columns retrieved from this target in the result
2124 # set are also adapted.
2125 if not create_aliases:
2126 self._mapper_loads_polymorphically_with(right_mapper, adapter)
2127 elif aliased_generation:
2128 adapter._debug = True
2129 self._aliased_generations[aliased_generation] = (
2130 adapter,
2131 ) + self._aliased_generations.get(aliased_generation, ())
2132 elif (
2133 not r_info.is_clause_element
2134 and not right_is_aliased
2135 and right_mapper.with_polymorphic
2136 and isinstance(
2137 right_mapper._with_polymorphic_selectable,
2138 expression.AliasedReturnsRows,
2139 )
2140 ):
2141 # for the case where the target mapper has a with_polymorphic
2142 # set up, ensure an adapter is set up for criteria that works
2143 # against this mapper. Previously, this logic used to
2144 # use the "create_aliases or aliased_entity" case to generate
2145 # an aliased() object, but this creates an alias that isn't
2146 # strictly necessary.
2147 # see test/orm/test_core_compilation.py
2148 # ::RelNaturalAliasedJoinsTest::test_straight
2149 # and similar
2150 self._mapper_loads_polymorphically_with(
2151 right_mapper,
2152 sql_util.ColumnAdapter(
2153 right_mapper.selectable,
2154 right_mapper._equivalent_columns,
2155 ),
2156 )
2157 # if the onclause is a ClauseElement, adapt it with any
2158 # adapters that are in place right now
2159 if isinstance(onclause, expression.ClauseElement):
2160 current_adapter = self._get_current_adapter()
2161 if current_adapter:
2162 onclause = current_adapter(onclause, True)
2164 # if joining on a MapperProperty path,
2165 # track the path to prevent redundant joins
2166 if not create_aliases and prop:
2167 self._update_joinpoint(
2168 {
2169 "_joinpoint_entity": right,
2170 "prev": ((left, right, prop.key), self._joinpoint),
2171 "aliased_generation": aliased_generation,
2172 }
2173 )
2174 else:
2175 self._joinpoint = {
2176 "_joinpoint_entity": right,
2177 "aliased_generation": aliased_generation,
2178 }
2180 return inspect(right), right, onclause
2182 def _update_joinpoint(self, jp):
2183 self._joinpoint = jp
2184 # copy backwards to the root of the _joinpath
2185 # dict, so that no existing dict in the path is mutated
2186 while "prev" in jp:
2187 f, prev = jp["prev"]
2188 prev = dict(prev)
2189 prev[f] = jp.copy()
2190 jp["prev"] = (f, prev)
2191 jp = prev
2192 self._joinpath = jp
2194 def _reset_joinpoint(self):
2195 self._joinpoint = self._joinpath
2197 @property
2198 def _select_args(self):
2199 return {
2200 "limit_clause": self.select_statement._limit_clause,
2201 "offset_clause": self.select_statement._offset_clause,
2202 "distinct": self.distinct,
2203 "distinct_on": self.distinct_on,
2204 "prefixes": self.select_statement._prefixes,
2205 "suffixes": self.select_statement._suffixes,
2206 "group_by": self.group_by or None,
2207 "fetch_clause": self.select_statement._fetch_clause,
2208 "fetch_clause_options": (
2209 self.select_statement._fetch_clause_options
2210 ),
2211 }
2213 @property
2214 def _should_nest_selectable(self):
2215 kwargs = self._select_args
2216 return (
2217 kwargs.get("limit_clause") is not None
2218 or kwargs.get("offset_clause") is not None
2219 or kwargs.get("distinct", False)
2220 or kwargs.get("distinct_on", ())
2221 or kwargs.get("group_by", False)
2222 )
2224 def _get_extra_criteria(self, ext_info):
2225 if (
2226 "additional_entity_criteria",
2227 ext_info.mapper,
2228 ) in self.global_attributes:
2229 return tuple(
2230 ae._resolve_where_criteria(ext_info)
2231 for ae in self.global_attributes[
2232 ("additional_entity_criteria", ext_info.mapper)
2233 ]
2234 if (ae.include_aliases or ae.entity is ext_info)
2235 and ae._should_include(self)
2236 )
2237 else:
2238 return ()
2240 def _adjust_for_extra_criteria(self):
2241 """Apply extra criteria filtering.
2243 For all distinct single-table-inheritance mappers represented in
2244 the columns clause of this query, as well as the "select from entity",
2245 add criterion to the WHERE
2246 clause of the given QueryContext such that only the appropriate
2247 subtypes are selected from the total results.
2249 Additionally, add WHERE criteria originating from LoaderCriteriaOptions
2250 associated with the global context.
2252 """
2254 for fromclause in self.from_clauses:
2255 ext_info = fromclause._annotations.get("parententity", None)
2257 if (
2258 ext_info
2259 and (
2260 ext_info.mapper._single_table_criterion is not None
2261 or ("additional_entity_criteria", ext_info.mapper)
2262 in self.global_attributes
2263 )
2264 and ext_info not in self.extra_criteria_entities
2265 ):
2267 self.extra_criteria_entities[ext_info] = (
2268 ext_info,
2269 ext_info._adapter if ext_info.is_aliased_class else None,
2270 )
2272 search = set(self.extra_criteria_entities.values())
2274 for (ext_info, adapter) in search:
2275 if ext_info in self._join_entities:
2276 continue
2278 single_crit = ext_info.mapper._single_table_criterion
2280 if self.compile_options._for_refresh_state:
2281 additional_entity_criteria = []
2282 else:
2283 additional_entity_criteria = self._get_extra_criteria(ext_info)
2285 if single_crit is not None:
2286 additional_entity_criteria += (single_crit,)
2288 current_adapter = self._get_current_adapter()
2289 for crit in additional_entity_criteria:
2290 if adapter:
2291 crit = adapter.traverse(crit)
2293 if current_adapter:
2294 crit = sql_util._deep_annotate(crit, {"_orm_adapt": True})
2295 crit = current_adapter(crit, False)
2296 self._where_criteria += (crit,)
2299def _column_descriptions(
2300 query_or_select_stmt, compile_state=None, legacy=False
2301):
2302 if compile_state is None:
2303 compile_state = ORMSelectCompileState._create_entities_collection(
2304 query_or_select_stmt, legacy=legacy
2305 )
2306 ctx = compile_state
2307 return [
2308 {
2309 "name": ent._label_name,
2310 "type": ent.type,
2311 "aliased": getattr(insp_ent, "is_aliased_class", False),
2312 "expr": ent.expr,
2313 "entity": getattr(insp_ent, "entity", None)
2314 if ent.entity_zero is not None and not insp_ent.is_clause_element
2315 else None,
2316 }
2317 for ent, insp_ent in [
2318 (
2319 _ent,
2320 (
2321 inspect(_ent.entity_zero)
2322 if _ent.entity_zero is not None
2323 else None
2324 ),
2325 )
2326 for _ent in ctx._entities
2327 ]
2328 ]
2331def _legacy_filter_by_entity_zero(query_or_augmented_select):
2332 self = query_or_augmented_select
2333 if self._legacy_setup_joins:
2334 _last_joined_entity = self._last_joined_entity
2335 if _last_joined_entity is not None:
2336 return _last_joined_entity
2338 if self._from_obj and "parententity" in self._from_obj[0]._annotations:
2339 return self._from_obj[0]._annotations["parententity"]
2341 return _entity_from_pre_ent_zero(self)
2344def _entity_from_pre_ent_zero(query_or_augmented_select):
2345 self = query_or_augmented_select
2346 if not self._raw_columns:
2347 return None
2349 ent = self._raw_columns[0]
2351 if "parententity" in ent._annotations:
2352 return ent._annotations["parententity"]
2353 elif isinstance(ent, ORMColumnsClauseRole):
2354 return ent.entity
2355 elif "bundle" in ent._annotations:
2356 return ent._annotations["bundle"]
2357 else:
2358 return ent
2361def _legacy_determine_last_joined_entity(setup_joins, entity_zero):
2362 """given the legacy_setup_joins collection at a point in time,
2363 figure out what the "filter by entity" would be in terms
2364 of those joins.
2366 in 2.0 this logic should hopefully be much simpler as there will
2367 be far fewer ways to specify joins with the ORM
2369 """
2371 if not setup_joins:
2372 return entity_zero
2374 # CAN BE REMOVED IN 2.0:
2375 # 1. from_joinpoint
2376 # 2. aliased_generation
2377 # 3. aliased
2378 # 4. any treating of prop as str
2379 # 5. tuple madness
2380 # 6. won't need recursive call anymore without #4
2381 # 7. therefore can pass in just the last setup_joins record,
2382 # don't need entity_zero
2384 (right, onclause, left_, flags) = setup_joins[-1]
2386 from_joinpoint = flags["from_joinpoint"]
2388 if onclause is None and isinstance(
2389 right, (str, interfaces.PropComparator)
2390 ):
2391 onclause = right
2392 right = None
2394 if right is not None and "parententity" in right._annotations:
2395 right = right._annotations["parententity"].entity
2397 if right is not None:
2398 last_entity = right
2399 insp = inspect(last_entity)
2400 if insp.is_clause_element or insp.is_aliased_class or insp.is_mapper:
2401 return insp
2403 last_entity = onclause
2404 if isinstance(last_entity, interfaces.PropComparator):
2405 return last_entity.entity
2407 # legacy vvvvvvvvvvvvvvvvvvvvvvvvvvv
2408 if isinstance(onclause, str):
2409 if from_joinpoint:
2410 prev = _legacy_determine_last_joined_entity(
2411 setup_joins[0:-1], entity_zero
2412 )
2413 else:
2414 prev = entity_zero
2416 if prev is None:
2417 return None
2419 prev = inspect(prev)
2420 attr = getattr(prev.entity, onclause, None)
2421 if attr is not None:
2422 return attr.property.entity
2423 # legacy ^^^^^^^^^^^^^^^^^^^^^^^^^^^
2425 return None
2428class _QueryEntity(object):
2429 """represent an entity column returned within a Query result."""
2431 __slots__ = ()
2433 _non_hashable_value = False
2434 _null_column_type = False
2435 use_id_for_hash = False
2437 @classmethod
2438 def to_compile_state(
2439 cls, compile_state, entities, entities_collection, is_current_entities
2440 ):
2442 for idx, entity in enumerate(entities):
2443 if entity._is_lambda_element:
2444 if entity._is_sequence:
2445 cls.to_compile_state(
2446 compile_state,
2447 entity._resolved,
2448 entities_collection,
2449 is_current_entities,
2450 )
2451 continue
2452 else:
2453 entity = entity._resolved
2455 if entity.is_clause_element:
2456 if entity.is_selectable:
2457 if "parententity" in entity._annotations:
2458 _MapperEntity(
2459 compile_state,
2460 entity,
2461 entities_collection,
2462 is_current_entities,
2463 )
2464 else:
2465 _ColumnEntity._for_columns(
2466 compile_state,
2467 entity._select_iterable,
2468 entities_collection,
2469 idx,
2470 is_current_entities,
2471 )
2472 else:
2473 if entity._annotations.get("bundle", False):
2474 _BundleEntity(
2475 compile_state,
2476 entity,
2477 entities_collection,
2478 is_current_entities,
2479 )
2480 elif entity._is_clause_list:
2481 # this is legacy only - test_composites.py
2482 # test_query_cols_legacy
2483 _ColumnEntity._for_columns(
2484 compile_state,
2485 entity._select_iterable,
2486 entities_collection,
2487 idx,
2488 is_current_entities,
2489 )
2490 else:
2491 _ColumnEntity._for_columns(
2492 compile_state,
2493 [entity],
2494 entities_collection,
2495 idx,
2496 is_current_entities,
2497 )
2498 elif entity.is_bundle:
2499 _BundleEntity(compile_state, entity, entities_collection)
2501 return entities_collection
2504class _MapperEntity(_QueryEntity):
2505 """mapper/class/AliasedClass entity"""
2507 __slots__ = (
2508 "expr",
2509 "mapper",
2510 "entity_zero",
2511 "is_aliased_class",
2512 "path",
2513 "_extra_entities",
2514 "_label_name",
2515 "_with_polymorphic_mappers",
2516 "selectable",
2517 "_polymorphic_discriminator",
2518 )
2520 def __init__(
2521 self, compile_state, entity, entities_collection, is_current_entities
2522 ):
2523 entities_collection.append(self)
2524 if is_current_entities:
2525 if compile_state._primary_entity is None:
2526 compile_state._primary_entity = self
2527 compile_state._has_mapper_entities = True
2528 compile_state._has_orm_entities = True
2530 entity = entity._annotations["parententity"]
2531 entity._post_inspect
2532 ext_info = self.entity_zero = entity
2533 entity = ext_info.entity
2535 self.expr = entity
2536 self.mapper = mapper = ext_info.mapper
2538 self._extra_entities = (self.expr,)
2540 if ext_info.is_aliased_class:
2541 self._label_name = ext_info.name
2542 else:
2543 self._label_name = mapper.class_.__name__
2545 self.is_aliased_class = ext_info.is_aliased_class
2546 self.path = ext_info._path_registry
2548 if ext_info in compile_state._with_polymorphic_adapt_map:
2549 # this codepath occurs only if query.with_polymorphic() were
2550 # used
2552 wp = inspect(compile_state._with_polymorphic_adapt_map[ext_info])
2554 if self.is_aliased_class:
2555 # TODO: invalidrequest ?
2556 raise NotImplementedError(
2557 "Can't use with_polymorphic() against an Aliased object"
2558 )
2560 mappers, from_obj = mapper._with_polymorphic_args(
2561 wp.with_polymorphic_mappers, wp.selectable
2562 )
2564 self._with_polymorphic_mappers = mappers
2565 self.selectable = from_obj
2566 self._polymorphic_discriminator = wp.polymorphic_on
2568 else:
2569 self.selectable = ext_info.selectable
2570 self._with_polymorphic_mappers = ext_info.with_polymorphic_mappers
2571 self._polymorphic_discriminator = ext_info.polymorphic_on
2573 if mapper._should_select_with_poly_adapter:
2574 compile_state._create_with_polymorphic_adapter(
2575 ext_info, self.selectable
2576 )
2578 supports_single_entity = True
2580 _non_hashable_value = True
2581 use_id_for_hash = True
2583 @property
2584 def type(self):
2585 return self.mapper.class_
2587 @property
2588 def entity_zero_or_selectable(self):
2589 return self.entity_zero
2591 def corresponds_to(self, entity):
2592 return _entity_corresponds_to(self.entity_zero, entity)
2594 def _get_entity_clauses(self, compile_state):
2596 adapter = None
2598 if not self.is_aliased_class:
2599 if compile_state._polymorphic_adapters:
2600 adapter = compile_state._polymorphic_adapters.get(
2601 self.mapper, None
2602 )
2603 else:
2604 adapter = self.entity_zero._adapter
2606 if adapter:
2607 if compile_state._from_obj_alias:
2608 ret = adapter.wrap(compile_state._from_obj_alias)
2609 else:
2610 ret = adapter
2611 else:
2612 ret = compile_state._from_obj_alias
2614 return ret
2616 def row_processor(self, context, result):
2617 compile_state = context.compile_state
2618 adapter = self._get_entity_clauses(compile_state)
2620 if compile_state.compound_eager_adapter and adapter:
2621 adapter = adapter.wrap(compile_state.compound_eager_adapter)
2622 elif not adapter:
2623 adapter = compile_state.compound_eager_adapter
2625 if compile_state._primary_entity is self:
2626 only_load_props = compile_state.compile_options._only_load_props
2627 refresh_state = context.refresh_state
2628 else:
2629 only_load_props = refresh_state = None
2631 _instance = loading._instance_processor(
2632 self,
2633 self.mapper,
2634 context,
2635 result,
2636 self.path,
2637 adapter,
2638 only_load_props=only_load_props,
2639 refresh_state=refresh_state,
2640 polymorphic_discriminator=self._polymorphic_discriminator,
2641 )
2643 return _instance, self._label_name, self._extra_entities
2645 def setup_compile_state(self, compile_state):
2647 adapter = self._get_entity_clauses(compile_state)
2649 single_table_crit = self.mapper._single_table_criterion
2650 if (
2651 single_table_crit is not None
2652 or ("additional_entity_criteria", self.mapper)
2653 in compile_state.global_attributes
2654 ):
2655 ext_info = self.entity_zero
2656 compile_state.extra_criteria_entities[ext_info] = (
2657 ext_info,
2658 ext_info._adapter if ext_info.is_aliased_class else None,
2659 )
2661 loading._setup_entity_query(
2662 compile_state,
2663 self.mapper,
2664 self,
2665 self.path,
2666 adapter,
2667 compile_state.primary_columns,
2668 with_polymorphic=self._with_polymorphic_mappers,
2669 only_load_props=compile_state.compile_options._only_load_props,
2670 polymorphic_discriminator=self._polymorphic_discriminator,
2671 )
2673 compile_state._fallback_from_clauses.append(self.selectable)
2676class _BundleEntity(_QueryEntity):
2678 _extra_entities = ()
2680 __slots__ = (
2681 "bundle",
2682 "expr",
2683 "type",
2684 "_label_name",
2685 "_entities",
2686 "supports_single_entity",
2687 )
2689 def __init__(
2690 self,
2691 compile_state,
2692 expr,
2693 entities_collection,
2694 is_current_entities,
2695 setup_entities=True,
2696 parent_bundle=None,
2697 ):
2698 compile_state._has_orm_entities = True
2700 expr = expr._annotations["bundle"]
2701 if parent_bundle:
2702 parent_bundle._entities.append(self)
2703 else:
2704 entities_collection.append(self)
2706 if isinstance(
2707 expr, (attributes.QueryableAttribute, interfaces.PropComparator)
2708 ):
2709 bundle = expr.__clause_element__()
2710 else:
2711 bundle = expr
2713 self.bundle = self.expr = bundle
2714 self.type = type(bundle)
2715 self._label_name = bundle.name
2716 self._entities = []
2718 if setup_entities:
2719 for expr in bundle.exprs:
2720 if "bundle" in expr._annotations:
2721 _BundleEntity(
2722 compile_state,
2723 expr,
2724 entities_collection,
2725 is_current_entities,
2726 parent_bundle=self,
2727 )
2728 elif isinstance(expr, Bundle):
2729 _BundleEntity(
2730 compile_state,
2731 expr,
2732 entities_collection,
2733 is_current_entities,
2734 parent_bundle=self,
2735 )
2736 else:
2737 _ORMColumnEntity._for_columns(
2738 compile_state,
2739 [expr],
2740 entities_collection,
2741 None,
2742 is_current_entities,
2743 parent_bundle=self,
2744 )
2746 self.supports_single_entity = self.bundle.single_entity
2747 if (
2748 self.supports_single_entity
2749 and not compile_state.compile_options._use_legacy_query_style
2750 ):
2751 util.warn_deprecated_20(
2752 "The Bundle.single_entity flag has no effect when "
2753 "using 2.0 style execution."
2754 )
2756 @property
2757 def mapper(self):
2758 ezero = self.entity_zero
2759 if ezero is not None:
2760 return ezero.mapper
2761 else:
2762 return None
2764 @property
2765 def entity_zero(self):
2766 for ent in self._entities:
2767 ezero = ent.entity_zero
2768 if ezero is not None:
2769 return ezero
2770 else:
2771 return None
2773 def corresponds_to(self, entity):
2774 # TODO: we might be able to implement this but for now
2775 # we are working around it
2776 return False
2778 @property
2779 def entity_zero_or_selectable(self):
2780 for ent in self._entities:
2781 ezero = ent.entity_zero_or_selectable
2782 if ezero is not None:
2783 return ezero
2784 else:
2785 return None
2787 def setup_compile_state(self, compile_state):
2788 for ent in self._entities:
2789 ent.setup_compile_state(compile_state)
2791 def row_processor(self, context, result):
2792 procs, labels, extra = zip(
2793 *[ent.row_processor(context, result) for ent in self._entities]
2794 )
2796 proc = self.bundle.create_row_processor(context.query, procs, labels)
2798 return proc, self._label_name, self._extra_entities
2801class _ColumnEntity(_QueryEntity):
2802 __slots__ = (
2803 "_fetch_column",
2804 "_row_processor",
2805 "raw_column_index",
2806 "translate_raw_column",
2807 )
2809 @classmethod
2810 def _for_columns(
2811 cls,
2812 compile_state,
2813 columns,
2814 entities_collection,
2815 raw_column_index,
2816 is_current_entities,
2817 parent_bundle=None,
2818 ):
2819 for column in columns:
2820 annotations = column._annotations
2821 if "parententity" in annotations:
2822 _entity = annotations["parententity"]
2823 else:
2824 _entity = sql_util.extract_first_column_annotation(
2825 column, "parententity"
2826 )
2828 if _entity:
2829 if "identity_token" in column._annotations:
2830 _IdentityTokenEntity(
2831 compile_state,
2832 column,
2833 entities_collection,
2834 _entity,
2835 raw_column_index,
2836 is_current_entities,
2837 parent_bundle=parent_bundle,
2838 )
2839 else:
2840 _ORMColumnEntity(
2841 compile_state,
2842 column,
2843 entities_collection,
2844 _entity,
2845 raw_column_index,
2846 is_current_entities,
2847 parent_bundle=parent_bundle,
2848 )
2849 else:
2850 _RawColumnEntity(
2851 compile_state,
2852 column,
2853 entities_collection,
2854 raw_column_index,
2855 is_current_entities,
2856 parent_bundle=parent_bundle,
2857 )
2859 @property
2860 def type(self):
2861 return self.column.type
2863 @property
2864 def _non_hashable_value(self):
2865 return not self.column.type.hashable
2867 @property
2868 def _null_column_type(self):
2869 return self.column.type._isnull
2871 def row_processor(self, context, result):
2872 compile_state = context.compile_state
2874 # the resulting callable is entirely cacheable so just return
2875 # it if we already made one
2876 if self._row_processor is not None:
2877 getter, label_name, extra_entities = self._row_processor
2878 if self.translate_raw_column:
2879 extra_entities += (
2880 result.context.invoked_statement._raw_columns[
2881 self.raw_column_index
2882 ],
2883 )
2885 return getter, label_name, extra_entities
2887 # retrieve the column that would have been set up in
2888 # setup_compile_state, to avoid doing redundant work
2889 if self._fetch_column is not None:
2890 column = self._fetch_column
2891 else:
2892 # fetch_column will be None when we are doing a from_statement
2893 # and setup_compile_state may not have been called.
2894 column = self.column
2896 # previously, the RawColumnEntity didn't look for from_obj_alias
2897 # however I can't think of a case where we would be here and
2898 # we'd want to ignore it if this is the from_statement use case.
2899 # it's not really a use case to have raw columns + from_statement
2900 if compile_state._from_obj_alias:
2901 column = compile_state._from_obj_alias.columns[column]
2903 if column._annotations:
2904 # annotated columns perform more slowly in compiler and
2905 # result due to the __eq__() method, so use deannotated
2906 column = column._deannotate()
2908 if compile_state.compound_eager_adapter:
2909 column = compile_state.compound_eager_adapter.columns[column]
2911 getter = result._getter(column)
2913 ret = getter, self._label_name, self._extra_entities
2914 self._row_processor = ret
2916 if self.translate_raw_column:
2917 extra_entities = self._extra_entities + (
2918 result.context.invoked_statement._raw_columns[
2919 self.raw_column_index
2920 ],
2921 )
2922 return getter, self._label_name, extra_entities
2923 else:
2924 return ret
2927class _RawColumnEntity(_ColumnEntity):
2928 entity_zero = None
2929 mapper = None
2930 supports_single_entity = False
2932 __slots__ = (
2933 "expr",
2934 "column",
2935 "_label_name",
2936 "entity_zero_or_selectable",
2937 "_extra_entities",
2938 )
2940 def __init__(
2941 self,
2942 compile_state,
2943 column,
2944 entities_collection,
2945 raw_column_index,
2946 is_current_entities,
2947 parent_bundle=None,
2948 ):
2949 self.expr = column
2950 self.raw_column_index = raw_column_index
2951 self.translate_raw_column = raw_column_index is not None
2953 if column._is_star:
2954 compile_state.compile_options += {"_is_star": True}
2956 if not is_current_entities or column._is_text_clause:
2957 self._label_name = None
2958 else:
2959 self._label_name = compile_state._label_convention(column)
2961 if parent_bundle:
2962 parent_bundle._entities.append(self)
2963 else:
2964 entities_collection.append(self)
2966 self.column = column
2967 self.entity_zero_or_selectable = (
2968 self.column._from_objects[0] if self.column._from_objects else None
2969 )
2970 self._extra_entities = (self.expr, self.column)
2971 self._fetch_column = self._row_processor = None
2973 def corresponds_to(self, entity):
2974 return False
2976 def setup_compile_state(self, compile_state):
2977 current_adapter = compile_state._get_current_adapter()
2978 if current_adapter:
2979 column = current_adapter(self.column, False)
2980 else:
2981 column = self.column
2983 if column._annotations:
2984 # annotated columns perform more slowly in compiler and
2985 # result due to the __eq__() method, so use deannotated
2986 column = column._deannotate()
2988 compile_state.dedupe_columns.add(column)
2989 compile_state.primary_columns.append(column)
2990 self._fetch_column = column
2993class _ORMColumnEntity(_ColumnEntity):
2994 """Column/expression based entity."""
2996 supports_single_entity = False
2998 __slots__ = (
2999 "expr",
3000 "mapper",
3001 "column",
3002 "_label_name",
3003 "entity_zero_or_selectable",
3004 "entity_zero",
3005 "_extra_entities",
3006 )
3008 def __init__(
3009 self,
3010 compile_state,
3011 column,
3012 entities_collection,
3013 parententity,
3014 raw_column_index,
3015 is_current_entities,
3016 parent_bundle=None,
3017 ):
3018 annotations = column._annotations
3020 _entity = parententity
3022 # an AliasedClass won't have proxy_key in the annotations for
3023 # a column if it was acquired using the class' adapter directly,
3024 # such as using AliasedInsp._adapt_element(). this occurs
3025 # within internal loaders.
3027 orm_key = annotations.get("proxy_key", None)
3028 proxy_owner = annotations.get("proxy_owner", _entity)
3029 if orm_key:
3030 self.expr = getattr(proxy_owner.entity, orm_key)
3031 self.translate_raw_column = False
3032 else:
3033 # if orm_key is not present, that means this is an ad-hoc
3034 # SQL ColumnElement, like a CASE() or other expression.
3035 # include this column position from the invoked statement
3036 # in the ORM-level ResultSetMetaData on each execute, so that
3037 # it can be targeted by identity after caching
3038 self.expr = column
3039 self.translate_raw_column = raw_column_index is not None
3041 self.raw_column_index = raw_column_index
3043 if is_current_entities:
3044 self._label_name = compile_state._label_convention(
3045 column, col_name=orm_key
3046 )
3047 else:
3048 self._label_name = None
3050 _entity._post_inspect
3051 self.entity_zero = self.entity_zero_or_selectable = ezero = _entity
3052 self.mapper = mapper = _entity.mapper
3054 if parent_bundle:
3055 parent_bundle._entities.append(self)
3056 else:
3057 entities_collection.append(self)
3059 compile_state._has_orm_entities = True
3061 self.column = column
3063 self._fetch_column = self._row_processor = None
3065 self._extra_entities = (self.expr, self.column)
3067 if mapper._should_select_with_poly_adapter:
3068 compile_state._create_with_polymorphic_adapter(
3069 ezero, ezero.selectable
3070 )
3072 def corresponds_to(self, entity):
3073 if _is_aliased_class(entity):
3074 # TODO: polymorphic subclasses ?
3075 return entity is self.entity_zero
3076 else:
3077 return not _is_aliased_class(
3078 self.entity_zero
3079 ) and entity.common_parent(self.entity_zero)
3081 def setup_compile_state(self, compile_state):
3082 current_adapter = compile_state._get_current_adapter()
3083 if current_adapter:
3084 column = current_adapter(self.column, False)
3085 else:
3086 column = self.column
3088 ezero = self.entity_zero
3090 single_table_crit = self.mapper._single_table_criterion
3091 if (
3092 single_table_crit is not None
3093 or ("additional_entity_criteria", self.mapper)
3094 in compile_state.global_attributes
3095 ):
3097 compile_state.extra_criteria_entities[ezero] = (
3098 ezero,
3099 ezero._adapter if ezero.is_aliased_class else None,
3100 )
3102 if column._annotations and not column._expression_label:
3103 # annotated columns perform more slowly in compiler and
3104 # result due to the __eq__() method, so use deannotated
3105 column = column._deannotate()
3107 # use entity_zero as the from if we have it. this is necessary
3108 # for polymorphic scenarios where our FROM is based on ORM entity,
3109 # not the FROM of the column. but also, don't use it if our column
3110 # doesn't actually have any FROMs that line up, such as when its
3111 # a scalar subquery.
3112 if set(self.column._from_objects).intersection(
3113 ezero.selectable._from_objects
3114 ):
3115 compile_state._fallback_from_clauses.append(ezero.selectable)
3117 compile_state.dedupe_columns.add(column)
3118 compile_state.primary_columns.append(column)
3119 self._fetch_column = column
3122class _IdentityTokenEntity(_ORMColumnEntity):
3123 translate_raw_column = False
3125 def setup_compile_state(self, compile_state):
3126 pass
3128 def row_processor(self, context, result):
3129 def getter(row):
3130 return context.load_options._refresh_identity_token
3132 return getter, self._label_name, self._extra_entities