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