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