Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
context.py3256 linesDownload Raw Back to orm
1# orm/context.py
2# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7# mypy: ignore-errors
8
9from __future__ import annotations
10
11import itertools
12from typing import Any
13from typing import cast
14from typing import Dict
15from typing import Iterable
16from typing import List
17from typing import Optional
18from typing import Set
19from typing import Tuple
20from typing import Type
21from typing import TYPE_CHECKING
22from typing import TypeVar
23from typing import Union
24
25from . import attributes
26from . import interfaces
27from . import loading
28from .base import _is_aliased_class
29from .interfaces import ORMColumnDescription
30from .interfaces import ORMColumnsClauseRole
31from .path_registry import PathRegistry
32from .util import _entity_corresponds_to
33from .util import _ORMJoin
34from .util import _TraceAdaptRole
35from .util import AliasedClass
36from .util import Bundle
37from .util import ORMAdapter
38from .util import ORMStatementAdapter
39from .. import exc as sa_exc
40from .. import future
41from .. import inspect
42from .. import sql
43from .. import util
44from ..sql import coercions
45from ..sql import expression
46from ..sql import roles
47from ..sql import util as sql_util
48from ..sql import visitors
49from ..sql._typing import _TP
50from ..sql._typing import is_dml
51from ..sql._typing import is_insert_update
52from ..sql._typing import is_select_base
53from ..sql.base import _select_iterables
54from ..sql.base import CacheableOptions
55from ..sql.base import CompileState
56from ..sql.base import Executable
57from ..sql.base import Generative
58from ..sql.base import Options
59from ..sql.dml import UpdateBase
60from ..sql.elements import GroupedElement
61from ..sql.elements import TextClause
62from ..sql.selectable import CompoundSelectState
63from ..sql.selectable import LABEL_STYLE_DISAMBIGUATE_ONLY
64from ..sql.selectable import LABEL_STYLE_NONE
65from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
66from ..sql.selectable import Select
67from ..sql.selectable import SelectLabelStyle
68from ..sql.selectable import SelectState
69from ..sql.selectable import TypedReturnsRows
70from ..sql.visitors import InternalTraversal
71
72if TYPE_CHECKING:
73    from ._typing import _InternalEntityType
74    from ._typing import OrmExecuteOptionsParameter
75    from .loading import PostLoad
76    from .mapper import Mapper
77    from .query import Query
78    from .session import _BindArguments
79    from .session import Session
80    from ..engine import Result
81    from ..engine.interfaces import _CoreSingleExecuteParams
82    from ..sql._typing import _ColumnsClauseArgument
83    from ..sql.compiler import SQLCompiler
84    from ..sql.dml import _DMLTableElement
85    from ..sql.elements import ColumnElement
86    from ..sql.selectable import _JoinTargetElement
87    from ..sql.selectable import _LabelConventionCallable
88    from ..sql.selectable import _SetupJoinsElement
89    from ..sql.selectable import ExecutableReturnsRows
90    from ..sql.selectable import SelectBase
91    from ..sql.type_api import TypeEngine
92
93_T = TypeVar("_T", bound=Any)
94_path_registry = PathRegistry.root
95
96_EMPTY_DICT = util.immutabledict()
97
98
99LABEL_STYLE_LEGACY_ORM = SelectLabelStyle.LABEL_STYLE_LEGACY_ORM
100
101
102class QueryContext:
103    __slots__ = (
104        "top_level_context",
105        "compile_state",
106        "query",
107        "params",
108        "load_options",
109        "bind_arguments",
110        "execution_options",
111        "session",
112        "autoflush",
113        "populate_existing",
114        "invoke_all_eagers",
115        "version_check",
116        "refresh_state",
117        "create_eager_joins",
118        "propagated_loader_options",
119        "attributes",
120        "runid",
121        "partials",
122        "post_load_paths",
123        "identity_token",
124        "yield_per",
125        "loaders_require_buffering",
126        "loaders_require_uniquing",
127    )
128
129    runid: int
130    post_load_paths: Dict[PathRegistry, PostLoad]
131    compile_state: ORMCompileState
132
133    class default_load_options(Options):
134        _only_return_tuples = False
135        _populate_existing = False
136        _version_check = False
137        _invoke_all_eagers = True
138        _autoflush = True
139        _identity_token = None
140        _yield_per = None
141        _refresh_state = None
142        _lazy_loaded_from = None
143        _legacy_uniquing = False
144        _sa_top_level_orm_context = None
145        _is_user_refresh = False
146
147    def __init__(
148        self,
149        compile_state: CompileState,
150        statement: Union[Select[Any], FromStatement[Any]],
151        params: _CoreSingleExecuteParams,
152        session: Session,
153        load_options: Union[
154            Type[QueryContext.default_load_options],
155            QueryContext.default_load_options,
156        ],
157        execution_options: Optional[OrmExecuteOptionsParameter] = None,
158        bind_arguments: Optional[_BindArguments] = None,
159    ):
160        self.load_options = load_options
161        self.execution_options = execution_options or _EMPTY_DICT
162        self.bind_arguments = bind_arguments or _EMPTY_DICT
163        self.compile_state = compile_state
164        self.query = statement
165        self.session = session
166        self.loaders_require_buffering = False
167        self.loaders_require_uniquing = False
168        self.params = params
169        self.top_level_context = load_options._sa_top_level_orm_context
170
171        cached_options = compile_state.select_statement._with_options
172        uncached_options = statement._with_options
173
174        # see issue #7447 , #8399 for some background
175        # propagated loader options will be present on loaded InstanceState
176        # objects under state.load_options and are typically used by
177        # LazyLoader to apply options to the SELECT statement it emits.
178        # For compile state options (i.e. loader strategy options), these
179        # need to line up with the ".load_path" attribute which in
180        # loader.py is pulled from context.compile_state.current_path.
181        # so, this means these options have to be the ones from the
182        # *cached* statement that's travelling with compile_state, not the
183        # *current* statement which won't match up for an ad-hoc
184        # AliasedClass
185        self.propagated_loader_options = tuple(
186            opt._adapt_cached_option_to_uncached_option(self, uncached_opt)
187            for opt, uncached_opt in zip(cached_options, uncached_options)
188            if opt.propagate_to_loaders
189        )
190
191        self.attributes = dict(compile_state.attributes)
192
193        self.autoflush = load_options._autoflush
194        self.populate_existing = load_options._populate_existing
195        self.invoke_all_eagers = load_options._invoke_all_eagers
196        self.version_check = load_options._version_check
197        self.refresh_state = load_options._refresh_state
198        self.yield_per = load_options._yield_per
199        self.identity_token = load_options._identity_token
200
201    def _get_top_level_context(self) -> QueryContext:
202        return self.top_level_context or self
203
204
205_orm_load_exec_options = util.immutabledict(
206    {"_result_disable_adapt_to_context": True}
207)
208
209
210class AbstractORMCompileState(CompileState):
211    is_dml_returning = False
212
213    def _init_global_attributes(
214        self, statement, compiler, *, toplevel, process_criteria_for_toplevel
215    ):
216        self.attributes = {}
217
218        if compiler is None:
219            # this is the legacy / testing only ORM _compile_state() use case.
220            # there is no need to apply criteria options for this.
221            self.global_attributes = ga = {}
222            assert toplevel
223            return
224        else:
225            self.global_attributes = ga = compiler._global_attributes
226
227        if toplevel:
228            ga["toplevel_orm"] = True
229
230            if process_criteria_for_toplevel:
231                for opt in statement._with_options:
232                    if opt._is_criteria_option:
233                        opt.process_compile_state(self)
234
235            return
236        elif ga.get("toplevel_orm", False):
237            return
238
239        stack_0 = compiler.stack[0]
240
241        try:
242            toplevel_stmt = stack_0["selectable"]
243        except KeyError:
244            pass
245        else:
246            for opt in toplevel_stmt._with_options:
247                if opt._is_compile_state and opt._is_criteria_option:
248                    opt.process_compile_state(self)
249
250        ga["toplevel_orm"] = True
251
252    @classmethod
253    def create_for_statement(
254        cls,
255        statement: Union[Select, FromStatement],
256        compiler: Optional[SQLCompiler],
257        **kw: Any,
258    ) -> AbstractORMCompileState:
259        """Create a context for a statement given a :class:`.Compiler`.
260
261        This method is always invoked in the context of SQLCompiler.process().
262
263        For a Select object, this would be invoked from
264        SQLCompiler.visit_select(). For the special FromStatement object used
265        by Query to indicate "Query.from_statement()", this is called by
266        FromStatement._compiler_dispatch() that would be called by
267        SQLCompiler.process().
268        """
269        return super().create_for_statement(statement, compiler, **kw)
270
271    @classmethod
272    def orm_pre_session_exec(
273        cls,
274        session,
275        statement,
276        params,
277        execution_options,
278        bind_arguments,
279        is_pre_event,
280    ):
281        raise NotImplementedError()
282
283    @classmethod
284    def orm_execute_statement(
285        cls,
286        session,
287        statement,
288        params,
289        execution_options,
290        bind_arguments,
291        conn,
292    ) -> Result:
293        result = conn.execute(
294            statement, params or {}, execution_options=execution_options
295        )
296        return cls.orm_setup_cursor_result(
297            session,
298            statement,
299            params,
300            execution_options,
301            bind_arguments,
302            result,
303        )
304
305    @classmethod
306    def orm_setup_cursor_result(
307        cls,
308        session,
309        statement,
310        params,
311        execution_options,
312        bind_arguments,
313        result,
314    ):
315        raise NotImplementedError()
316
317
318class AutoflushOnlyORMCompileState(AbstractORMCompileState):
319    """ORM compile state that is a passthrough, except for autoflush."""
320
321    @classmethod
322    def orm_pre_session_exec(
323        cls,
324        session,
325        statement,
326        params,
327        execution_options,
328        bind_arguments,
329        is_pre_event,
330    ):
331        # consume result-level load_options.  These may have been set up
332        # in an ORMExecuteState hook
333        (
334            load_options,
335            execution_options,
336        ) = QueryContext.default_load_options.from_execution_options(
337            "_sa_orm_load_options",
338            {
339                "autoflush",
340            },
341            execution_options,
342            statement._execution_options,
343        )
344
345        if not is_pre_event and load_options._autoflush:
346            session._autoflush()
347
348        return statement, execution_options
349
350    @classmethod
351    def orm_setup_cursor_result(
352        cls,
353        session,
354        statement,
355        params,
356        execution_options,
357        bind_arguments,
358        result,
359    ):
360        return result
361
362
363class ORMCompileState(AbstractORMCompileState):
364    class default_compile_options(CacheableOptions):
365        _cache_key_traversal = [
366            ("_use_legacy_query_style", InternalTraversal.dp_boolean),
367            ("_for_statement", InternalTraversal.dp_boolean),
368            ("_bake_ok", InternalTraversal.dp_boolean),
369            ("_current_path", InternalTraversal.dp_has_cache_key),
370            ("_enable_single_crit", InternalTraversal.dp_boolean),
371            ("_enable_eagerloads", InternalTraversal.dp_boolean),
372            ("_only_load_props", InternalTraversal.dp_plain_obj),
373            ("_set_base_alias", InternalTraversal.dp_boolean),
374            ("_for_refresh_state", InternalTraversal.dp_boolean),
375            ("_render_for_subquery", InternalTraversal.dp_boolean),
376            ("_is_star", InternalTraversal.dp_boolean),
377        ]
378
379        # set to True by default from Query._statement_20(), to indicate
380        # the rendered query should look like a legacy ORM query.  right
381        # now this basically indicates we should use tablename_columnname
382        # style labels.    Generally indicates the statement originated
383        # from a Query object.
384        _use_legacy_query_style = False
385
386        # set *only* when we are coming from the Query.statement
387        # accessor, or a Query-level equivalent such as
388        # query.subquery().  this supersedes "toplevel".
389        _for_statement = False
390
391        _bake_ok = True
392        _current_path = _path_registry
393        _enable_single_crit = True
394        _enable_eagerloads = True
395        _only_load_props = None
396        _set_base_alias = False
397        _for_refresh_state = False
398        _render_for_subquery = False
399        _is_star = False
400
401    attributes: Dict[Any, Any]
402    global_attributes: Dict[Any, Any]
403
404    statement: Union[Select[Any], FromStatement[Any]]
405    select_statement: Union[Select[Any], FromStatement[Any]]
406    _entities: List[_QueryEntity]
407    _polymorphic_adapters: Dict[_InternalEntityType, ORMAdapter]
408    compile_options: Union[
409        Type[default_compile_options], default_compile_options
410    ]
411    _primary_entity: Optional[_QueryEntity]
412    use_legacy_query_style: bool
413    _label_convention: _LabelConventionCallable
414    primary_columns: List[ColumnElement[Any]]
415    secondary_columns: List[ColumnElement[Any]]
416    dedupe_columns: Set[ColumnElement[Any]]
417    create_eager_joins: List[
418        # TODO: this structure is set up by JoinedLoader
419        Tuple[Any, ...]
420    ]
421    current_path: PathRegistry = _path_registry
422    _has_mapper_entities = False
423
424    def __init__(self, *arg, **kw):
425        raise NotImplementedError()
426
427    if TYPE_CHECKING:
428
429        @classmethod
430        def create_for_statement(
431            cls,
432            statement: Union[Select, FromStatement],
433            compiler: Optional[SQLCompiler],
434            **kw: Any,
435        ) -> ORMCompileState: ...
436
437    def _append_dedupe_col_collection(self, obj, col_collection):
438        dedupe = self.dedupe_columns
439        if obj not in dedupe:
440            dedupe.add(obj)
441            col_collection.append(obj)
442
443    @classmethod
444    def _column_naming_convention(
445        cls, label_style: SelectLabelStyle, legacy: bool
446    ) -> _LabelConventionCallable:
447        if legacy:
448
449            def name(col, col_name=None):
450                if col_name:
451                    return col_name
452                else:
453                    return getattr(col, "key")
454
455            return name
456        else:
457            return SelectState._column_naming_convention(label_style)
458
459    @classmethod
460    def get_column_descriptions(cls, statement):
461        return _column_descriptions(statement)
462
463    @classmethod
464    def orm_pre_session_exec(
465        cls,
466        session,
467        statement,
468        params,
469        execution_options,
470        bind_arguments,
471        is_pre_event,
472    ):
473        # consume result-level load_options.  These may have been set up
474        # in an ORMExecuteState hook
475        (
476            load_options,
477            execution_options,
478        ) = QueryContext.default_load_options.from_execution_options(
479            "_sa_orm_load_options",
480            {
481                "populate_existing",
482                "autoflush",
483                "yield_per",
484                "identity_token",
485                "sa_top_level_orm_context",
486            },
487            execution_options,
488            statement._execution_options,
489        )
490
491        # default execution options for ORM results:
492        # 1. _result_disable_adapt_to_context=True
493        #    this will disable the ResultSetMetadata._adapt_to_context()
494        #    step which we don't need, as we have result processors cached
495        #    against the original SELECT statement before caching.
496
497        if "sa_top_level_orm_context" in execution_options:
498            ctx = execution_options["sa_top_level_orm_context"]
499            execution_options = ctx.query._execution_options.merge_with(
500                ctx.execution_options, execution_options
501            )
502
503        if not execution_options:
504            execution_options = _orm_load_exec_options
505        else:
506            execution_options = execution_options.union(_orm_load_exec_options)
507
508        # would have been placed here by legacy Query only
509        if load_options._yield_per:
510            execution_options = execution_options.union(
511                {"yield_per": load_options._yield_per}
512            )
513
514        if (
515            getattr(statement._compile_options, "_current_path", None)
516            and len(statement._compile_options._current_path) > 10
517            and execution_options.get("compiled_cache", True) is not None
518        ):
519            execution_options: util.immutabledict[str, Any] = (
520                execution_options.union(
521                    {
522                        "compiled_cache": None,
523                        "_cache_disable_reason": "excess depth for "
524                        "ORM loader options",
525                    }
526                )
527            )
528
529        bind_arguments["clause"] = statement
530
531        # new in 1.4 - the coercions system is leveraged to allow the
532        # "subject" mapper of a statement be propagated to the top
533        # as the statement is built.   "subject" mapper is the generally
534        # standard object used as an identifier for multi-database schemes.
535
536        # we are here based on the fact that _propagate_attrs contains
537        # "compile_state_plugin": "orm".   The "plugin_subject"
538        # needs to be present as well.
539
540        try:
541            plugin_subject = statement._propagate_attrs["plugin_subject"]
542        except KeyError:
543            assert False, "statement had 'orm' plugin but no plugin_subject"
544        else:
545            if plugin_subject:
546                bind_arguments["mapper"] = plugin_subject.mapper
547
548        if not is_pre_event and load_options._autoflush:
549            session._autoflush()
550
551        return statement, execution_options
552
553    @classmethod
554    def orm_setup_cursor_result(
555        cls,
556        session,
557        statement,
558        params,
559        execution_options,
560        bind_arguments,
561        result,
562    ):
563        execution_context = result.context
564        compile_state = execution_context.compiled.compile_state
565
566        # cover edge case where ORM entities used in legacy select
567        # were passed to session.execute:
568        # session.execute(legacy_select([User.id, User.name]))
569        # see test_query->test_legacy_tuple_old_select
570
571        load_options = execution_options.get(
572            "_sa_orm_load_options", QueryContext.default_load_options
573        )
574
575        if compile_state.compile_options._is_star:
576            return result
577
578        querycontext = QueryContext(
579            compile_state,
580            statement,
581            params,
582            session,
583            load_options,
584            execution_options,
585            bind_arguments,
586        )
587        return loading.instances(result, querycontext)
588
589    @property
590    def _lead_mapper_entities(self):
591        """return all _MapperEntity objects in the lead entities collection.
592
593        Does **not** include entities that have been replaced by
594        with_entities(), with_only_columns()
595
596        """
597        return [
598            ent for ent in self._entities if isinstance(ent, _MapperEntity)
599        ]
600
601    def _create_with_polymorphic_adapter(self, ext_info, selectable):
602        """given MapperEntity or ORMColumnEntity, setup polymorphic loading
603        if called for by the Mapper.
604
605        As of #8168 in 2.0.0rc1, polymorphic adapters, which greatly increase
606        the complexity of the query creation process, are not used at all
607        except in the quasi-legacy cases of with_polymorphic referring to an
608        alias and/or subquery. This would apply to concrete polymorphic
609        loading, and joined inheritance where a subquery is
610        passed to with_polymorphic (which is completely unnecessary in modern
611        use).
612
613        """
614        if (
615            not ext_info.is_aliased_class
616            and ext_info.mapper.persist_selectable
617            not in self._polymorphic_adapters
618        ):
619            for mp in ext_info.mapper.iterate_to_root():
620                self._mapper_loads_polymorphically_with(
621                    mp,
622                    ORMAdapter(
623                        _TraceAdaptRole.WITH_POLYMORPHIC_ADAPTER,
624                        mp,
625                        equivalents=mp._equivalent_columns,
626                        selectable=selectable,
627                    ),
628                )
629
630    def _mapper_loads_polymorphically_with(self, mapper, adapter):
631        for m2 in mapper._with_polymorphic_mappers or [mapper]:
632            self._polymorphic_adapters[m2] = adapter
633
634            for m in m2.iterate_to_root():
635                self._polymorphic_adapters[m.local_table] = adapter
636
637    @classmethod
638    def _create_entities_collection(cls, query, legacy):
639        raise NotImplementedError(
640            "this method only works for ORMSelectCompileState"
641        )
642
643
644class DMLReturningColFilter:
645    """an adapter used for the DML RETURNING case.
646
647    Has a subset of the interface used by
648    :class:`.ORMAdapter` and is used for :class:`._QueryEntity`
649    instances to set up their columns as used in RETURNING for a
650    DML statement.
651
652    """
653
654    __slots__ = ("mapper", "columns", "__weakref__")
655
656    def __init__(self, target_mapper, immediate_dml_mapper):
657        if (
658            immediate_dml_mapper is not None
659            and target_mapper.local_table
660            is not immediate_dml_mapper.local_table
661        ):
662            # joined inh, or in theory other kinds of multi-table mappings
663            self.mapper = immediate_dml_mapper
664        else:
665            # single inh, normal mappings, etc.
666            self.mapper = target_mapper
667        self.columns = self.columns = util.WeakPopulateDict(
668            self.adapt_check_present  # type: ignore
669        )
670
671    def __call__(self, col, as_filter):
672        for cc in sql_util._find_columns(col):
673            c2 = self.adapt_check_present(cc)
674            if c2 is not None:
675                return col
676        else:
677            return None
678
679    def adapt_check_present(self, col):
680        mapper = self.mapper
681        prop = mapper._columntoproperty.get(col, None)
682        if prop is None:
683            return None
684        return mapper.local_table.c.corresponding_column(col)
685
686
687@sql.base.CompileState.plugin_for("orm", "orm_from_statement")
688class ORMFromStatementCompileState(ORMCompileState):
689    _from_obj_alias = None
690    _has_mapper_entities = False
691
692    statement_container: FromStatement
693    requested_statement: Union[SelectBase, TextClause, UpdateBase]
694    dml_table: Optional[_DMLTableElement] = None
695
696    _has_orm_entities = False
697    multi_row_eager_loaders = False
698    eager_adding_joins = False
699    compound_eager_adapter = None
700
701    extra_criteria_entities = _EMPTY_DICT
702    eager_joins = _EMPTY_DICT
703
704    @classmethod
705    def create_for_statement(
706        cls,
707        statement_container: Union[Select, FromStatement],
708        compiler: Optional[SQLCompiler],
709        **kw: Any,
710    ) -> ORMFromStatementCompileState:
711        assert isinstance(statement_container, FromStatement)
712
713        if compiler is not None and compiler.stack:
714            raise sa_exc.CompileError(
715                "The ORM FromStatement construct only supports being "
716                "invoked as the topmost statement, as it is only intended to "
717                "define how result rows should be returned."
718            )
719
720        self = cls.__new__(cls)
721        self._primary_entity = None
722
723        self.use_legacy_query_style = (
724            statement_container._compile_options._use_legacy_query_style
725        )
726        self.statement_container = self.select_statement = statement_container
727        self.requested_statement = statement = statement_container.element
728
729        if statement.is_dml:
730            self.dml_table = statement.table
731            self.is_dml_returning = True
732
733        self._entities = []
734        self._polymorphic_adapters = {}
735
736        self.compile_options = statement_container._compile_options
737
738        if (
739            self.use_legacy_query_style
740            and isinstance(statement, expression.SelectBase)
741            and not statement._is_textual
742            and not statement.is_dml
743            and statement._label_style is LABEL_STYLE_NONE
744        ):
745            self.statement = statement.set_label_style(
746                LABEL_STYLE_TABLENAME_PLUS_COL
747            )
748        else:
749            self.statement = statement
750
751        self._label_convention = self._column_naming_convention(
752            (
753                statement._label_style
754                if not statement._is_textual and not statement.is_dml
755                else LABEL_STYLE_NONE
756            ),
757            self.use_legacy_query_style,
758        )
759
760        _QueryEntity.to_compile_state(
761            self,
762            statement_container._raw_columns,
763            self._entities,
764            is_current_entities=True,
765        )
766
767        self.current_path = statement_container._compile_options._current_path
768
769        self._init_global_attributes(
770            statement_container,
771            compiler,
772            process_criteria_for_toplevel=False,
773            toplevel=True,
774        )
775
776        if statement_container._with_options:
777            for opt in statement_container._with_options:
778                if opt._is_compile_state:
779                    opt.process_compile_state(self)
780
781        if statement_container._with_context_options:
782            for fn, key in statement_container._with_context_options:
783                fn(self)
784
785        self.primary_columns = []
786        self.secondary_columns = []
787        self.dedupe_columns = set()
788        self.create_eager_joins = []
789        self._fallback_from_clauses = []
790
791        self.order_by = None
792
793        if isinstance(self.statement, expression.TextClause):
794            # TextClause has no "column" objects at all.  for this case,
795            # we generate columns from our _QueryEntity objects, then
796            # flip on all the "please match no matter what" parameters.
797            self.extra_criteria_entities = {}
798
799            for entity in self._entities:
800                entity.setup_compile_state(self)
801
802            compiler._ordered_columns = compiler._textual_ordered_columns = (
803                False
804            )
805
806            # enable looser result column matching.  this is shown to be
807            # needed by test_query.py::TextTest
808            compiler._loose_column_name_matching = True
809
810            for c in self.primary_columns:
811                compiler.process(
812                    c,
813                    within_columns_clause=True,
814                    add_to_result_map=compiler._add_to_result_map,
815                )
816        else:
817            # for everyone else, Select, Insert, Update, TextualSelect, they
818            # have column objects already.  After much
819            # experimentation here, the best approach seems to be, use
820            # those columns completely, don't interfere with the compiler
821            # at all; just in ORM land, use an adapter to convert from
822            # our ORM columns to whatever columns are in the statement,
823            # before we look in the result row. Adapt on names
824            # to accept cases such as issue #9217, however also allow
825            # this to be overridden for cases such as #9273.
826            self._from_obj_alias = ORMStatementAdapter(
827                _TraceAdaptRole.ADAPT_FROM_STATEMENT,
828                self.statement,
829                adapt_on_names=statement_container._adapt_on_names,
830            )
831
832        return self
833
834    def _adapt_col_list(self, cols, current_adapter):
835        return cols
836
837    def _get_current_adapter(self):
838        return None
839
840    def setup_dml_returning_compile_state(self, dml_mapper):
841        """used by BulkORMInsert (and Update / Delete?) to set up a handler
842        for RETURNING to return ORM objects and expressions
843
844        """
845        target_mapper = self.statement._propagate_attrs.get(
846            "plugin_subject", None
847        )
848        adapter = DMLReturningColFilter(target_mapper, dml_mapper)
849
850        if self.compile_options._is_star and (len(self._entities) != 1):
851            raise sa_exc.CompileError(
852                "Can't generate ORM query that includes multiple expressions "
853                "at the same time as '*'; query for '*' alone if present"
854            )
855
856        for entity in self._entities:
857            entity.setup_dml_returning_compile_state(self, adapter)
858
859
860class FromStatement(GroupedElement, Generative, TypedReturnsRows[_TP]):
861    """Core construct that represents a load of ORM objects from various
862    :class:`.ReturnsRows` and other classes including:
863
864    :class:`.Select`, :class:`.TextClause`, :class:`.TextualSelect`,
865    :class:`.CompoundSelect`, :class`.Insert`, :class:`.Update`,
866    and in theory, :class:`.Delete`.
867
868    """
869
870    __visit_name__ = "orm_from_statement"
871
872    _compile_options = ORMFromStatementCompileState.default_compile_options
873
874    _compile_state_factory = ORMFromStatementCompileState.create_for_statement
875
876    _for_update_arg = None
877
878    element: Union[ExecutableReturnsRows, TextClause]
879
880    _adapt_on_names: bool
881
882    _traverse_internals = [
883        ("_raw_columns", InternalTraversal.dp_clauseelement_list),
884        ("element", InternalTraversal.dp_clauseelement),
885    ] + Executable._executable_traverse_internals
886
887    _cache_key_traversal = _traverse_internals + [
888        ("_compile_options", InternalTraversal.dp_has_cache_key)
889    ]
890
891    is_from_statement = True
892
893    def __init__(
894        self,
895        entities: Iterable[_ColumnsClauseArgument[Any]],
896        element: Union[ExecutableReturnsRows, TextClause],
897        _adapt_on_names: bool = True,
898    ):
899        self._raw_columns = [
900            coercions.expect(
901                roles.ColumnsClauseRole,
902                ent,
903                apply_propagate_attrs=self,
904                post_inspect=True,
905            )
906            for ent in util.to_list(entities)
907        ]
908        self.element = element
909        self.is_dml = element.is_dml
910        self.is_select = element.is_select
911        self.is_delete = element.is_delete
912        self.is_insert = element.is_insert
913        self.is_update = element.is_update
914        self._label_style = (
915            element._label_style if is_select_base(element) else None
916        )
917        self._adapt_on_names = _adapt_on_names
918
919    def _compiler_dispatch(self, compiler, **kw):
920        """provide a fixed _compiler_dispatch method.
921
922        This is roughly similar to using the sqlalchemy.ext.compiler
923        ``@compiles`` extension.
924
925        """
926
927        compile_state = self._compile_state_factory(self, compiler, **kw)
928
929        toplevel = not compiler.stack
930
931        if toplevel:
932            compiler.compile_state = compile_state
933
934        return compiler.process(compile_state.statement, **kw)
935
936    @property
937    def column_descriptions(self):
938        """Return a :term:`plugin-enabled` 'column descriptions' structure
939        referring to the columns which are SELECTed by this statement.
940
941        See the section :ref:`queryguide_inspection` for an overview
942        of this feature.
943
944        .. seealso::
945
946            :ref:`queryguide_inspection` - ORM background
947
948        """
949        meth = cast(
950            ORMSelectCompileState, SelectState.get_plugin_class(self)
951        ).get_column_descriptions
952        return meth(self)
953
954    def _ensure_disambiguated_names(self):
955        return self
956
957    def get_children(self, **kw):
958        yield from itertools.chain.from_iterable(
959            element._from_objects for element in self._raw_columns
960        )
961        yield from super().get_children(**kw)
962
963    @property
964    def _all_selected_columns(self):
965        return self.element._all_selected_columns
966
967    @property
968    def _return_defaults(self):
969        return self.element._return_defaults if is_dml(self.element) else None
970
971    @property
972    def _returning(self):
973        return self.element._returning if is_dml(self.element) else None
974
975    @property
976    def _inline(self):
977        return self.element._inline if is_insert_update(self.element) else None
978
979
980@sql.base.CompileState.plugin_for("orm", "compound_select")
981class CompoundSelectCompileState(
982    AutoflushOnlyORMCompileState, CompoundSelectState
983):
984    pass
985
986
987@sql.base.CompileState.plugin_for("orm", "select")
988class ORMSelectCompileState(ORMCompileState, SelectState):
989    _already_joined_edges = ()
990
991    _memoized_entities = _EMPTY_DICT
992
993    _from_obj_alias = None
994    _has_mapper_entities = False
995
996    _has_orm_entities = False
997    multi_row_eager_loaders = False
998    eager_adding_joins = False
999    compound_eager_adapter = None
1000
1001    correlate = None
1002    correlate_except = None
1003    _where_criteria = ()
1004    _having_criteria = ()
1005
1006    @classmethod
1007    def create_for_statement(
1008        cls,
1009        statement: Union[Select, FromStatement],
1010        compiler: Optional[SQLCompiler],
1011        **kw: Any,
1012    ) -> ORMSelectCompileState:
1013        """compiler hook, we arrive here from compiler.visit_select() only."""
1014
1015        self = cls.__new__(cls)
1016
1017        if compiler is not None:
1018            toplevel = not compiler.stack
1019        else:
1020            toplevel = True
1021
1022        select_statement = statement
1023
1024        # if we are a select() that was never a legacy Query, we won't
1025        # have ORM level compile options.
1026        statement._compile_options = cls.default_compile_options.safe_merge(
1027            statement._compile_options
1028        )
1029
1030        if select_statement._execution_options:
1031            # execution options should not impact the compilation of a
1032            # query, and at the moment subqueryloader is putting some things
1033            # in here that we explicitly don't want stuck in a cache.
1034            self.select_statement = select_statement._clone()
1035            self.select_statement._execution_options = util.immutabledict()
1036        else:
1037            self.select_statement = select_statement
1038
1039        # indicates this select() came from Query.statement
1040        self.for_statement = select_statement._compile_options._for_statement
1041
1042        # generally if we are from Query or directly from a select()
1043        self.use_legacy_query_style = (
1044            select_statement._compile_options._use_legacy_query_style
1045        )
1046
1047        self._entities = []
1048        self._primary_entity = None
1049        self._polymorphic_adapters = {}
1050
1051        self.compile_options = select_statement._compile_options
1052
1053        if not toplevel:
1054            # for subqueries, turn off eagerloads and set
1055            # "render_for_subquery".
1056            self.compile_options += {
1057                "_enable_eagerloads": False,
1058                "_render_for_subquery": True,
1059            }
1060
1061        # determine label style.   we can make different decisions here.
1062        # at the moment, trying to see if we can always use DISAMBIGUATE_ONLY
1063        # rather than LABEL_STYLE_NONE, and if we can use disambiguate style
1064        # for new style ORM selects too.
1065        if (
1066            self.use_legacy_query_style
1067            and self.select_statement._label_style is LABEL_STYLE_LEGACY_ORM
1068        ):
1069            if not self.for_statement:
1070                self.label_style = LABEL_STYLE_TABLENAME_PLUS_COL
1071            else:
1072                self.label_style = LABEL_STYLE_DISAMBIGUATE_ONLY
1073        else:
1074            self.label_style = self.select_statement._label_style
1075
1076        if select_statement._memoized_select_entities:
1077            self._memoized_entities = {
1078                memoized_entities: _QueryEntity.to_compile_state(
1079                    self,
1080                    memoized_entities._raw_columns,
1081                    [],
1082                    is_current_entities=False,
1083                )
1084                for memoized_entities in (
1085                    select_statement._memoized_select_entities
1086                )
1087            }
1088
1089        # label_convention is stateful and will yield deduping keys if it
1090        # sees the same key twice.  therefore it's important that it is not
1091        # invoked for the above "memoized" entities that aren't actually
1092        # in the columns clause
1093        self._label_convention = self._column_naming_convention(
1094            statement._label_style, self.use_legacy_query_style
1095        )
1096
1097        _QueryEntity.to_compile_state(
1098            self,
1099            select_statement._raw_columns,
1100            self._entities,
1101            is_current_entities=True,
1102        )
1103
1104        self.current_path = select_statement._compile_options._current_path
1105
1106        self.eager_order_by = ()
1107
1108        self._init_global_attributes(
1109            select_statement,
1110            compiler,
1111            toplevel=toplevel,
1112            process_criteria_for_toplevel=False,
1113        )
1114
1115        if toplevel and (
1116            select_statement._with_options
1117            or select_statement._memoized_select_entities
1118        ):
1119            for (
1120                memoized_entities
1121            ) in select_statement._memoized_select_entities:
1122                for opt in memoized_entities._with_options:
1123                    if opt._is_compile_state:
1124                        opt.process_compile_state_replaced_entities(
1125                            self,
1126                            [
1127                                ent
1128                                for ent in self._memoized_entities[
1129                                    memoized_entities
1130                                ]
1131                                if isinstance(ent, _MapperEntity)
1132                            ],
1133                        )
1134
1135            for opt in self.select_statement._with_options:
1136                if opt._is_compile_state:
1137                    opt.process_compile_state(self)
1138
1139        # uncomment to print out the context.attributes structure
1140        # after it's been set up above
1141        # self._dump_option_struct()
1142
1143        if select_statement._with_context_options:
1144            for fn, key in select_statement._with_context_options:
1145                fn(self)
1146
1147        self.primary_columns = []
1148        self.secondary_columns = []
1149        self.dedupe_columns = set()
1150        self.eager_joins = {}
1151        self.extra_criteria_entities = {}
1152        self.create_eager_joins = []
1153        self._fallback_from_clauses = []
1154
1155        # normalize the FROM clauses early by themselves, as this makes
1156        # it an easier job when we need to assemble a JOIN onto these,
1157        # for select.join() as well as joinedload().   As of 1.4 there are now
1158        # potentially more complex sets of FROM objects here as the use
1159        # of lambda statements for lazyload, load_on_pk etc. uses more
1160        # cloning of the select() construct.  See #6495
1161        self.from_clauses = self._normalize_froms(
1162            info.selectable for info in select_statement._from_obj
1163        )
1164
1165        # this is a fairly arbitrary break into a second method,
1166        # so it might be nicer to break up create_for_statement()
1167        # and _setup_for_generate into three or four logical sections
1168        self._setup_for_generate()
1169
1170        SelectState.__init__(self, self.statement, compiler, **kw)
1171        return self
1172
1173    def _dump_option_struct(self):
1174        print("\n---------------------------------------------------\n")
1175        print(f"current path: {self.current_path}")
1176        for key in self.attributes:
1177            if isinstance(key, tuple) and key[0] == "loader":
1178                print(f"\nLoader:           {PathRegistry.coerce(key[1])}")
1179                print(f"    {self.attributes[key]}")
1180                print(f"    {self.attributes[key].__dict__}")
1181            elif isinstance(key, tuple) and key[0] == "path_with_polymorphic":
1182                print(f"\nWith Polymorphic: {PathRegistry.coerce(key[1])}")
1183                print(f"    {self.attributes[key]}")
1184
1185    def _setup_for_generate(self):
1186        query = self.select_statement
1187
1188        self.statement = None
1189        self._join_entities = ()
1190
1191        if self.compile_options._set_base_alias:
1192            # legacy Query only
1193            self._set_select_from_alias()
1194
1195        for memoized_entities in query._memoized_select_entities:
1196            if memoized_entities._setup_joins:
1197                self._join(
1198                    memoized_entities._setup_joins,
1199                    self._memoized_entities[memoized_entities],
1200                )

Showing the first 1,200 of 3256 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai