Team Ai
Datasetpublic

codekingpro/portable-devtools

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

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

codekingpro/portable-devtools · Team Ai