Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/context.py: 27%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1322 statements  

1# orm/context.py 

2# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors 

3# <see AUTHORS file> 

4# 

5# This module is part of SQLAlchemy and is released under 

6# the MIT License: https://www.opensource.org/licenses/mit-license.php 

7# mypy: ignore-errors 

8 

9from __future__ import annotations 

10 

11import collections 

12import itertools 

13from typing import Any 

14from typing import cast 

15from typing import Dict 

16from typing import Iterable 

17from typing import List 

18from typing import Optional 

19from typing import Set 

20from typing import Tuple 

21from typing import Type 

22from typing import TYPE_CHECKING 

23from typing import TypeVar 

24from typing import Union 

25 

26from . import attributes 

27from . import interfaces 

28from . import loading 

29from .base import _is_aliased_class 

30from .interfaces import ORMColumnDescription 

31from .interfaces import ORMColumnsClauseRole 

32from .path_registry import PathRegistry 

33from .util import _entity_corresponds_to 

34from .util import _ORMJoin 

35from .util import _TraceAdaptRole 

36from .util import AliasedClass 

37from .util import Bundle 

38from .util import ORMAdapter 

39from .util import ORMStatementAdapter 

40from .. import exc as sa_exc 

41from .. import future 

42from .. import inspect 

43from .. import sql 

44from .. import util 

45from ..sql import coercions 

46from ..sql import expression 

47from ..sql import roles 

48from ..sql import util as sql_util 

49from ..sql import visitors 

50from ..sql._typing import is_dml 

51from ..sql._typing import is_insert_update 

52from ..sql._typing import is_select_base 

53from ..sql.base import _select_iterables 

54from ..sql.base import CacheableOptions 

55from ..sql.base import CompileState 

56from ..sql.base import Executable 

57from ..sql.base import Generative 

58from ..sql.base import Options 

59from ..sql.dml import UpdateBase 

60from ..sql.elements import GroupedElement 

61from ..sql.elements import TextClause 

62from ..sql.selectable import CompoundSelectState 

63from ..sql.selectable import LABEL_STYLE_DISAMBIGUATE_ONLY 

64from ..sql.selectable import LABEL_STYLE_NONE 

65from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL 

66from ..sql.selectable import Select 

67from ..sql.selectable import SelectLabelStyle 

68from ..sql.selectable import SelectState 

69from ..sql.selectable import TypedReturnsRows 

70from ..sql.visitors import InternalTraversal 

71from ..util.typing import TupleAny 

72from ..util.typing import TypeVarTuple 

73from ..util.typing import Unpack 

74 

75 

76if 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_EMPTY_DICT = util.immutabledict() 

102 

103 

104LABEL_STYLE_LEGACY_ORM = SelectLabelStyle.LABEL_STYLE_LEGACY_ORM 

105 

106 

107class QueryContext: 

108 __slots__ = ( 

109 "top_level_context", 

110 "compile_state", 

111 "query", 

112 "user_passed_query", 

113 "params", 

114 "load_options", 

115 "bind_arguments", 

116 "execution_options", 

117 "session", 

118 "autoflush", 

119 "populate_existing", 

120 "invoke_all_eagers", 

121 "version_check", 

122 "refresh_state", 

123 "create_eager_joins", 

124 "propagated_loader_options", 

125 "attributes", 

126 "runid", 

127 "partials", 

128 "post_load_paths", 

129 "identity_token", 

130 "yield_per", 

131 "loaders_require_buffering", 

132 "loaders_require_uniquing", 

133 ) 

134 

135 runid: int 

136 post_load_paths: Dict[PathRegistry, _PostLoad] 

137 compile_state: _ORMCompileState 

138 

139 class default_load_options(Options): 

140 _only_return_tuples = False 

141 _populate_existing = False 

142 _version_check = False 

143 _invoke_all_eagers = True 

144 _autoflush = True 

145 _identity_token = None 

146 _yield_per = None 

147 _refresh_state = None 

148 _lazy_loaded_from = None 

149 _legacy_uniquing = False 

150 _sa_top_level_orm_context = None 

151 _is_user_refresh = False 

152 

153 def __init__( 

154 self, 

155 compile_state: CompileState, 

156 statement: Union[ 

157 Select[Unpack[TupleAny]], 

158 FromStatement[Unpack[TupleAny]], 

159 UpdateBase, 

160 ], 

161 user_passed_query: Union[ 

162 Select[Unpack[TupleAny]], 

163 FromStatement[Unpack[TupleAny]], 

164 UpdateBase, 

165 ], 

166 params: _CoreSingleExecuteParams, 

167 session: Session, 

168 load_options: Union[ 

169 Type[QueryContext.default_load_options], 

170 QueryContext.default_load_options, 

171 ], 

172 execution_options: Optional[OrmExecuteOptionsParameter] = None, 

173 bind_arguments: Optional[_BindArguments] = None, 

174 ): 

175 self.load_options = load_options 

176 self.execution_options = execution_options or _EMPTY_DICT 

177 self.bind_arguments = bind_arguments or _EMPTY_DICT 

178 self.compile_state = compile_state 

179 self.query = statement 

180 

181 # the query that the end user passed to Session.execute() or similar. 

182 # this is usually the same as .query, except in the bulk_persistence 

183 # routines where a separate FromStatement is manufactured in the 

184 # compile stage; this allows differentiation in that case. 

185 self.user_passed_query = user_passed_query 

186 

187 self.session = session 

188 self.loaders_require_buffering = False 

189 self.loaders_require_uniquing = False 

190 self.params = params 

191 self.top_level_context = load_options._sa_top_level_orm_context 

192 

193 cached_options = compile_state.select_statement._with_options 

194 uncached_options = user_passed_query._with_options 

195 

196 # see issue #7447 , #8399 for some background 

197 # propagated loader options will be present on loaded InstanceState 

198 # objects under state.load_options and are typically used by 

199 # LazyLoader to apply options to the SELECT statement it emits. 

200 # For compile state options (i.e. loader strategy options), these 

201 # need to line up with the ".load_path" attribute which in 

202 # loader.py is pulled from context.compile_state.current_path. 

203 # so, this means these options have to be the ones from the 

204 # *cached* statement that's travelling with compile_state, not the 

205 # *current* statement which won't match up for an ad-hoc 

206 # AliasedClass 

207 self.propagated_loader_options = tuple( 

208 opt._adapt_cached_option_to_uncached_option(self, uncached_opt) 

209 for opt, uncached_opt in zip(cached_options, uncached_options) 

210 if opt.propagate_to_loaders 

211 ) 

212 

213 self.attributes = dict(compile_state.attributes) 

214 

215 self.autoflush = load_options._autoflush 

216 self.populate_existing = load_options._populate_existing 

217 self.invoke_all_eagers = load_options._invoke_all_eagers 

218 self.version_check = load_options._version_check 

219 self.refresh_state = load_options._refresh_state 

220 self.yield_per = load_options._yield_per 

221 self.identity_token = load_options._identity_token 

222 

223 def _get_top_level_context(self) -> QueryContext: 

224 return self.top_level_context or self 

225 

226 

227_orm_load_exec_options = util.immutabledict( 

228 {"_result_disable_adapt_to_context": True} 

229) 

230 

231 

232class _AbstractORMCompileState(CompileState): 

233 is_dml_returning = False 

234 

235 def _init_global_attributes( 

236 self, statement, compiler, *, toplevel, process_criteria_for_toplevel 

237 ): 

238 self.attributes = {} 

239 

240 if compiler is None: 

241 # this is the legacy / testing only ORM _compile_state() use case. 

242 # there is no need to apply criteria options for this. 

243 self.global_attributes = {} 

244 assert toplevel 

245 return 

246 else: 

247 self.global_attributes = ga = compiler._global_attributes 

248 

249 if toplevel: 

250 ga["toplevel_orm"] = True 

251 

252 if process_criteria_for_toplevel: 

253 for opt in statement._with_options: 

254 if opt._is_criteria_option: 

255 opt.process_compile_state(self) 

256 

257 return 

258 elif ga.get("toplevel_orm", False): 

259 return 

260 

261 stack_0 = compiler.stack[0] 

262 

263 try: 

264 toplevel_stmt = stack_0["selectable"] 

265 except KeyError: 

266 pass 

267 else: 

268 for opt in toplevel_stmt._with_options: 

269 if opt._is_compile_state and opt._is_criteria_option: 

270 opt.process_compile_state(self) 

271 

272 ga["toplevel_orm"] = True 

273 

274 @classmethod 

275 def create_for_statement( 

276 cls, 

277 statement: Executable, 

278 compiler: SQLCompiler, 

279 **kw: Any, 

280 ) -> CompileState: 

281 """Create a context for a statement given a :class:`.Compiler`. 

282 

283 This method is always invoked in the context of SQLCompiler.process(). 

284 

285 For a Select object, this would be invoked from 

286 SQLCompiler.visit_select(). For the special FromStatement object used 

287 by Query to indicate "Query.from_statement()", this is called by 

288 FromStatement._compiler_dispatch() that would be called by 

289 SQLCompiler.process(). 

290 """ 

291 return super().create_for_statement(statement, compiler, **kw) 

292 

293 @classmethod 

294 def orm_pre_session_exec( 

295 cls, 

296 session, 

297 statement, 

298 params, 

299 execution_options, 

300 bind_arguments, 

301 is_pre_event, 

302 ): 

303 raise NotImplementedError() 

304 

305 @classmethod 

306 def orm_execute_statement( 

307 cls, 

308 session, 

309 statement, 

310 params, 

311 execution_options, 

312 bind_arguments, 

313 conn, 

314 ) -> Result: 

315 result = conn.execute( 

316 statement, params or {}, execution_options=execution_options 

317 ) 

318 return cls.orm_setup_cursor_result( 

319 session, 

320 statement, 

321 params, 

322 execution_options, 

323 bind_arguments, 

324 result, 

325 ) 

326 

327 @classmethod 

328 def orm_setup_cursor_result( 

329 cls, 

330 session, 

331 statement, 

332 params, 

333 execution_options, 

334 bind_arguments, 

335 result, 

336 ): 

337 raise NotImplementedError() 

338 

339 

340class _AutoflushOnlyORMCompileState(_AbstractORMCompileState): 

341 """ORM compile state that is a passthrough, except for autoflush.""" 

342 

343 @classmethod 

344 def orm_pre_session_exec( 

345 cls, 

346 session, 

347 statement, 

348 params, 

349 execution_options, 

350 bind_arguments, 

351 is_pre_event, 

352 ): 

353 # consume result-level load_options. These may have been set up 

354 # in an ORMExecuteState hook 

355 ( 

356 load_options, 

357 execution_options, 

358 ) = QueryContext.default_load_options.from_execution_options( 

359 "_sa_orm_load_options", 

360 { 

361 "autoflush", 

362 }, 

363 execution_options, 

364 statement._execution_options, 

365 ) 

366 

367 if not is_pre_event and load_options._autoflush: 

368 session._autoflush() 

369 

370 return statement, execution_options 

371 

372 @classmethod 

373 def orm_setup_cursor_result( 

374 cls, 

375 session, 

376 statement, 

377 params, 

378 execution_options, 

379 bind_arguments, 

380 result, 

381 ): 

382 return result 

383 

384 

385class _ORMCompileState(_AbstractORMCompileState): 

386 class default_compile_options(CacheableOptions): 

387 _cache_key_traversal = [ 

388 ("_use_legacy_query_style", InternalTraversal.dp_boolean), 

389 ("_for_statement", InternalTraversal.dp_boolean), 

390 ("_bake_ok", InternalTraversal.dp_boolean), 

391 ("_current_path", InternalTraversal.dp_has_cache_key), 

392 ("_enable_single_crit", InternalTraversal.dp_boolean), 

393 ("_enable_eagerloads", InternalTraversal.dp_boolean), 

394 ("_only_load_props", InternalTraversal.dp_plain_obj), 

395 ("_set_base_alias", InternalTraversal.dp_boolean), 

396 ("_for_refresh_state", InternalTraversal.dp_boolean), 

397 ("_render_for_subquery", InternalTraversal.dp_boolean), 

398 ("_is_star", InternalTraversal.dp_boolean), 

399 ] 

400 

401 # set to True by default from Query._statement_20(), to indicate 

402 # the rendered query should look like a legacy ORM query. right 

403 # now this basically indicates we should use tablename_columnname 

404 # style labels. Generally indicates the statement originated 

405 # from a Query object. 

406 _use_legacy_query_style = False 

407 

408 # set *only* when we are coming from the Query.statement 

409 # accessor, or a Query-level equivalent such as 

410 # query.subquery(). this supersedes "toplevel". 

411 _for_statement = False 

412 

413 _bake_ok = True 

414 _current_path = _path_registry 

415 _enable_single_crit = True 

416 _enable_eagerloads = True 

417 _only_load_props = None 

418 _set_base_alias = False 

419 _for_refresh_state = False 

420 _render_for_subquery = False 

421 _is_star = False 

422 

423 attributes: Dict[Any, Any] 

424 global_attributes: Dict[Any, Any] 

425 

426 statement: Union[ 

427 Select[Unpack[TupleAny]], FromStatement[Unpack[TupleAny]], UpdateBase 

428 ] 

429 select_statement: Union[ 

430 Select[Unpack[TupleAny]], FromStatement[Unpack[TupleAny]] 

431 ] 

432 _entities: List[_QueryEntity] 

433 _polymorphic_adapters: Dict[_InternalEntityType, ORMAdapter] 

434 compile_options: Union[ 

435 Type[default_compile_options], default_compile_options 

436 ] 

437 _primary_entity: Optional[_QueryEntity] 

438 use_legacy_query_style: bool 

439 _label_convention: _LabelConventionCallable 

440 primary_columns: List[ColumnElement[Any]] 

441 secondary_columns: List[ColumnElement[Any]] 

442 dedupe_columns: Set[ColumnElement[Any]] 

443 create_eager_joins: List[ 

444 # TODO: this structure is set up by JoinedLoader 

445 TupleAny 

446 ] 

447 current_path: PathRegistry = _path_registry 

448 _has_mapper_entities = False 

449 

450 def __init__(self, *arg, **kw): 

451 raise NotImplementedError() 

452 

453 @classmethod 

454 def create_for_statement( 

455 cls, 

456 statement: Executable, 

457 compiler: SQLCompiler, 

458 **kw: Any, 

459 ) -> _ORMCompileState: 

460 return cls._create_orm_context( 

461 cast("Union[Select, FromStatement]", statement), 

462 toplevel=not compiler.stack, 

463 compiler=compiler, 

464 **kw, 

465 ) 

466 

467 @classmethod 

468 def _create_orm_context( 

469 cls, 

470 statement: Union[Select, FromStatement], 

471 *, 

472 toplevel: bool, 

473 compiler: Optional[SQLCompiler], 

474 **kw: Any, 

475 ) -> _ORMCompileState: 

476 raise NotImplementedError() 

477 

478 def _append_dedupe_col_collection(self, obj, col_collection): 

479 dedupe = self.dedupe_columns 

480 if obj not in dedupe: 

481 dedupe.add(obj) 

482 col_collection.append(obj) 

483 

484 @classmethod 

485 def _column_naming_convention( 

486 cls, label_style: SelectLabelStyle, legacy: bool 

487 ) -> _LabelConventionCallable: 

488 if legacy: 

489 

490 def name(col, col_name=None): 

491 if col_name: 

492 return col_name 

493 else: 

494 return getattr(col, "key") 

495 

496 return name 

497 else: 

498 return SelectState._column_naming_convention(label_style) 

499 

500 @classmethod 

501 def get_column_descriptions(cls, statement): 

502 return _column_descriptions(statement) 

503 

504 @classmethod 

505 def orm_pre_session_exec( 

506 cls, 

507 session, 

508 statement, 

509 params, 

510 execution_options, 

511 bind_arguments, 

512 is_pre_event, 

513 ): 

514 # consume result-level load_options. These may have been set up 

515 # in an ORMExecuteState hook 

516 ( 

517 load_options, 

518 execution_options, 

519 ) = QueryContext.default_load_options.from_execution_options( 

520 "_sa_orm_load_options", 

521 { 

522 "populate_existing", 

523 "autoflush", 

524 "yield_per", 

525 "identity_token", 

526 "sa_top_level_orm_context", 

527 }, 

528 execution_options, 

529 statement._execution_options, 

530 ) 

531 

532 # default execution options for ORM results: 

533 # 1. _result_disable_adapt_to_context=True 

534 # this will disable the ResultSetMetadata._adapt_to_context() 

535 # step which we don't need, as we have result processors cached 

536 # against the original SELECT statement before caching. 

537 

538 if "sa_top_level_orm_context" in execution_options: 

539 ctx = execution_options["sa_top_level_orm_context"] 

540 execution_options = ctx.query._execution_options.merge_with( 

541 ctx.execution_options, execution_options 

542 ) 

543 

544 if not execution_options: 

545 execution_options = _orm_load_exec_options 

546 else: 

547 execution_options = execution_options.union(_orm_load_exec_options) 

548 

549 # would have been placed here by legacy Query only 

550 if load_options._yield_per: 

551 execution_options = execution_options.union( 

552 {"yield_per": load_options._yield_per} 

553 ) 

554 

555 if ( 

556 getattr(statement._compile_options, "_current_path", None) 

557 and len(statement._compile_options._current_path) > 10 

558 and execution_options.get("compiled_cache", True) is not None 

559 ): 

560 execution_options: util.immutabledict[str, Any] = ( 

561 execution_options.union( 

562 { 

563 "compiled_cache": None, 

564 "_cache_disable_reason": "excess depth for " 

565 "ORM loader options", 

566 } 

567 ) 

568 ) 

569 

570 bind_arguments["clause"] = statement 

571 

572 # new in 1.4 - the coercions system is leveraged to allow the 

573 # "subject" mapper of a statement be propagated to the top 

574 # as the statement is built. "subject" mapper is the generally 

575 # standard object used as an identifier for multi-database schemes. 

576 

577 # we are here based on the fact that _propagate_attrs contains 

578 # "compile_state_plugin": "orm". The "plugin_subject" 

579 # needs to be present as well. 

580 

581 try: 

582 plugin_subject = statement._propagate_attrs["plugin_subject"] 

583 except KeyError: 

584 assert False, "statement had 'orm' plugin but no plugin_subject" 

585 else: 

586 if plugin_subject: 

587 bind_arguments["mapper"] = plugin_subject.mapper 

588 

589 if not is_pre_event and load_options._autoflush: 

590 session._autoflush() 

591 

592 return statement, execution_options 

593 

594 @classmethod 

595 def orm_setup_cursor_result( 

596 cls, 

597 session, 

598 statement, 

599 params, 

600 execution_options, 

601 bind_arguments, 

602 result, 

603 ): 

604 execution_context = result.context 

605 compile_state = execution_context.compiled.compile_state 

606 

607 # cover edge case where ORM entities used in legacy select 

608 # were passed to session.execute: 

609 # session.execute(legacy_select([User.id, User.name])) 

610 # see test_query->test_legacy_tuple_old_select 

611 

612 load_options = execution_options.get( 

613 "_sa_orm_load_options", QueryContext.default_load_options 

614 ) 

615 

616 if compile_state.compile_options._is_star: 

617 return result 

618 

619 querycontext = QueryContext( 

620 compile_state, 

621 statement, 

622 statement, 

623 params, 

624 session, 

625 load_options, 

626 execution_options, 

627 bind_arguments, 

628 ) 

629 return loading.instances(result, querycontext) 

630 

631 @property 

632 def _lead_mapper_entities(self): 

633 """return all _MapperEntity objects in the lead entities collection. 

634 

635 Does **not** include entities that have been replaced by 

636 with_entities(), with_only_columns() 

637 

638 """ 

639 return [ 

640 ent for ent in self._entities if isinstance(ent, _MapperEntity) 

641 ] 

642 

643 def _create_with_polymorphic_adapter(self, ext_info, selectable): 

644 """given MapperEntity or ORMColumnEntity, setup polymorphic loading 

645 if called for by the Mapper. 

646 

647 As of #8168 in 2.0.0rc1, polymorphic adapters, which greatly increase 

648 the complexity of the query creation process, are not used at all 

649 except in the quasi-legacy cases of with_polymorphic referring to an 

650 alias and/or subquery. This would apply to concrete polymorphic 

651 loading, and joined inheritance where a subquery is 

652 passed to with_polymorphic (which is completely unnecessary in modern 

653 use). 

654 

655 TODO: What is a "quasi-legacy" case? Do we need this method with 

656 2.0 style select() queries or not? Why is with_polymorphic referring 

657 to an alias or subquery "legacy" ? 

658 

659 """ 

660 if ( 

661 not ext_info.is_aliased_class 

662 and ext_info.mapper.persist_selectable 

663 not in self._polymorphic_adapters 

664 ): 

665 for mp in ext_info.mapper.iterate_to_root(): 

666 self._mapper_loads_polymorphically_with( 

667 mp, 

668 ORMAdapter( 

669 _TraceAdaptRole.WITH_POLYMORPHIC_ADAPTER, 

670 mp, 

671 equivalents=mp._equivalent_columns, 

672 selectable=selectable, 

673 ), 

674 ) 

675 

676 def _mapper_loads_polymorphically_with(self, mapper, adapter): 

677 for m2 in mapper._with_polymorphic_mappers or [mapper]: 

678 self._polymorphic_adapters[m2] = adapter 

679 

680 for m in m2.iterate_to_root(): 

681 self._polymorphic_adapters[m.local_table] = adapter 

682 

683 @classmethod 

684 def _create_entities_collection(cls, query, legacy): 

685 raise NotImplementedError( 

686 "this method only works for ORMSelectCompileState" 

687 ) 

688 

689 

690class _DMLReturningColFilter: 

691 """a base for an adapter used for the DML RETURNING cases 

692 

693 Has a subset of the interface used by 

694 :class:`.ORMAdapter` and is used for :class:`._QueryEntity` 

695 instances to set up their columns as used in RETURNING for a 

696 DML statement. 

697 

698 """ 

699 

700 __slots__ = ("mapper", "columns", "__weakref__") 

701 

702 def __init__(self, target_mapper, immediate_dml_mapper): 

703 if ( 

704 immediate_dml_mapper is not None 

705 and target_mapper.local_table 

706 is not immediate_dml_mapper.local_table 

707 ): 

708 # joined inh, or in theory other kinds of multi-table mappings 

709 self.mapper = immediate_dml_mapper 

710 else: 

711 # single inh, normal mappings, etc. 

712 self.mapper = target_mapper 

713 self.columns = self.columns = util.WeakPopulateDict( 

714 self.adapt_check_present # type: ignore 

715 ) 

716 

717 def __call__(self, col, as_filter): 

718 for cc in sql_util._find_columns(col): 

719 c2 = self.adapt_check_present(cc) 

720 if c2 is not None: 

721 return col 

722 else: 

723 return None 

724 

725 def adapt_check_present(self, col): 

726 raise NotImplementedError() 

727 

728 

729class _DMLBulkInsertReturningColFilter(_DMLReturningColFilter): 

730 """an adapter used for the DML RETURNING case specifically 

731 for ORM bulk insert (or any hypothetical DML that is splitting out a class 

732 hierarchy among multiple DML statements....ORM bulk insert is the only 

733 example right now) 

734 

735 its main job is to limit the columns in a RETURNING to only a specific 

736 mapped table in a hierarchy. 

737 

738 """ 

739 

740 def adapt_check_present(self, col): 

741 mapper = self.mapper 

742 prop = mapper._columntoproperty.get(col, None) 

743 if prop is None: 

744 return None 

745 return mapper.local_table.c.corresponding_column(col) 

746 

747 

748class _DMLUpdateDeleteReturningColFilter(_DMLReturningColFilter): 

749 """an adapter used for the DML RETURNING case specifically 

750 for ORM enabled UPDATE/DELETE 

751 

752 its main job is to limit the columns in a RETURNING to include 

753 only direct persisted columns from the immediate selectable, not 

754 expressions like column_property(), or to also allow columns from other 

755 mappers for the UPDATE..FROM use case. 

756 

757 """ 

758 

759 def adapt_check_present(self, col): 

760 mapper = self.mapper 

761 prop = mapper._columntoproperty.get(col, None) 

762 if prop is not None: 

763 # if the col is from the immediate mapper, only return a persisted 

764 # column, not any kind of column_property expression 

765 return mapper.persist_selectable.c.corresponding_column(col) 

766 

767 # if the col is from some other mapper, just return it, assume the 

768 # user knows what they are doing 

769 return col 

770 

771 

772@sql.base.CompileState.plugin_for("orm", "orm_from_statement") 

773class _ORMFromStatementCompileState(_ORMCompileState): 

774 _from_obj_alias = None 

775 _has_mapper_entities = False 

776 

777 statement_container: FromStatement 

778 requested_statement: Union[SelectBase, TextClause, UpdateBase] 

779 dml_table: Optional[_DMLTableElement] = None 

780 

781 _has_orm_entities = False 

782 multi_row_eager_loaders = False 

783 eager_adding_joins = False 

784 compound_eager_adapter = None 

785 

786 extra_criteria_entities = _EMPTY_DICT 

787 eager_joins = _EMPTY_DICT 

788 

789 @classmethod 

790 def _create_orm_context( 

791 cls, 

792 statement: Union[Select, FromStatement], 

793 *, 

794 toplevel: bool, 

795 compiler: Optional[SQLCompiler], 

796 **kw: Any, 

797 ) -> _ORMFromStatementCompileState: 

798 statement_container = statement 

799 

800 assert isinstance(statement_container, FromStatement) 

801 

802 if compiler is not None and compiler.stack: 

803 raise sa_exc.CompileError( 

804 "The ORM FromStatement construct only supports being " 

805 "invoked as the topmost statement, as it is only intended to " 

806 "define how result rows should be returned." 

807 ) 

808 

809 self = cls.__new__(cls) 

810 self._primary_entity = None 

811 

812 self.use_legacy_query_style = ( 

813 statement_container._compile_options._use_legacy_query_style 

814 ) 

815 self.statement_container = self.select_statement = statement_container 

816 self.requested_statement = statement = statement_container.element 

817 

818 if statement.is_dml: 

819 self.dml_table = statement.table 

820 self.is_dml_returning = True 

821 

822 self._entities = [] 

823 self._polymorphic_adapters = {} 

824 

825 self.compile_options = statement_container._compile_options 

826 

827 if ( 

828 self.use_legacy_query_style 

829 and isinstance(statement, expression.SelectBase) 

830 and not statement._is_textual 

831 and not statement.is_dml 

832 and statement._label_style is LABEL_STYLE_NONE 

833 ): 

834 self.statement = statement.set_label_style( 

835 LABEL_STYLE_TABLENAME_PLUS_COL 

836 ) 

837 else: 

838 self.statement = statement 

839 

840 self._label_convention = self._column_naming_convention( 

841 ( 

842 statement._label_style 

843 if not statement._is_textual and not statement.is_dml 

844 else LABEL_STYLE_NONE 

845 ), 

846 self.use_legacy_query_style, 

847 ) 

848 

849 _QueryEntity.to_compile_state( 

850 self, 

851 statement_container._raw_columns, 

852 self._entities, 

853 is_current_entities=True, 

854 ) 

855 

856 self.current_path = statement_container._compile_options._current_path 

857 

858 self._init_global_attributes( 

859 statement_container, 

860 compiler, 

861 process_criteria_for_toplevel=False, 

862 toplevel=True, 

863 ) 

864 

865 if statement_container._with_options: 

866 for opt in statement_container._with_options: 

867 if opt._is_compile_state: 

868 opt.process_compile_state(self) 

869 

870 if statement_container._compile_state_funcs: 

871 for fn, key in statement_container._compile_state_funcs: 

872 fn(self) 

873 

874 self.primary_columns = [] 

875 self.secondary_columns = [] 

876 self.dedupe_columns = set() 

877 self.create_eager_joins = [] 

878 self._fallback_from_clauses = [] 

879 

880 self.order_by = None 

881 

882 if isinstance(self.statement, expression.TextClause): 

883 # TextClause has no "column" objects at all. for this case, 

884 # we generate columns from our _QueryEntity objects, then 

885 # flip on all the "please match no matter what" parameters. 

886 self.extra_criteria_entities = {} 

887 

888 for entity in self._entities: 

889 entity.setup_compile_state(self) 

890 

891 compiler._ordered_columns = compiler._textual_ordered_columns = ( 

892 False 

893 ) 

894 

895 # enable looser result column matching. this is shown to be 

896 # needed by test_query.py::TextTest 

897 compiler._loose_column_name_matching = True 

898 

899 for c in self.primary_columns: 

900 compiler.process( 

901 c, 

902 within_columns_clause=True, 

903 add_to_result_map=compiler._add_to_result_map, 

904 ) 

905 else: 

906 # for everyone else, Select, Insert, Update, TextualSelect, they 

907 # have column objects already. After much 

908 # experimentation here, the best approach seems to be, use 

909 # those columns completely, don't interfere with the compiler 

910 # at all; just in ORM land, use an adapter to convert from 

911 # our ORM columns to whatever columns are in the statement, 

912 # before we look in the result row. Adapt on names 

913 # to accept cases such as issue #9217, however also allow 

914 # this to be overridden for cases such as #9273. 

915 self._from_obj_alias = ORMStatementAdapter( 

916 _TraceAdaptRole.ADAPT_FROM_STATEMENT, 

917 self.statement, 

918 adapt_on_names=statement_container._adapt_on_names, 

919 ) 

920 

921 return self 

922 

923 def _adapt_col_list(self, cols, current_adapter): 

924 return cols 

925 

926 def _get_current_adapter(self): 

927 return None 

928 

929 def setup_dml_returning_compile_state(self, dml_mapper): 

930 """used by BulkORMInsert, Update, Delete to set up a handler 

931 for RETURNING to return ORM objects and expressions 

932 

933 """ 

934 target_mapper = self.statement._propagate_attrs.get( 

935 "plugin_subject", None 

936 ) 

937 

938 if self.statement.is_insert: 

939 adapter = _DMLBulkInsertReturningColFilter( 

940 target_mapper, dml_mapper 

941 ) 

942 elif self.statement.is_update or self.statement.is_delete: 

943 adapter = _DMLUpdateDeleteReturningColFilter( 

944 target_mapper, dml_mapper 

945 ) 

946 else: 

947 adapter = None 

948 

949 if self.compile_options._is_star and (len(self._entities) != 1): 

950 raise sa_exc.CompileError( 

951 "Can't generate ORM query that includes multiple expressions " 

952 "at the same time as '*'; query for '*' alone if present" 

953 ) 

954 

955 for entity in self._entities: 

956 entity.setup_dml_returning_compile_state(self, adapter) 

957 

958 

959class FromStatement(GroupedElement, Generative, TypedReturnsRows[Unpack[_Ts]]): 

960 """Core construct that represents a load of ORM objects from various 

961 :class:`.ReturnsRows` and other classes including: 

962 

963 :class:`.Select`, :class:`.TextClause`, :class:`.TextualSelect`, 

964 :class:`.CompoundSelect`, :class`.Insert`, :class:`.Update`, 

965 and in theory, :class:`.Delete`. 

966 

967 """ 

968 

969 __visit_name__ = "orm_from_statement" 

970 

971 _compile_options = _ORMFromStatementCompileState.default_compile_options 

972 

973 _compile_state_factory = _ORMFromStatementCompileState.create_for_statement 

974 

975 _for_update_arg = None 

976 

977 element: Union[ExecutableReturnsRows, TextClause] 

978 

979 _adapt_on_names: bool 

980 

981 _traverse_internals = [ 

982 ("_raw_columns", InternalTraversal.dp_clauseelement_list), 

983 ("element", InternalTraversal.dp_clauseelement), 

984 ] + Executable._executable_traverse_internals 

985 

986 _cache_key_traversal = _traverse_internals + [ 

987 ("_compile_options", InternalTraversal.dp_has_cache_key) 

988 ] 

989 

990 is_from_statement = True 

991 

992 def __init__( 

993 self, 

994 entities: Iterable[_ColumnsClauseArgument[Any]], 

995 element: Union[ExecutableReturnsRows, TextClause], 

996 _adapt_on_names: bool = True, 

997 ): 

998 self._raw_columns = [ 

999 coercions.expect( 

1000 roles.ColumnsClauseRole, 

1001 ent, 

1002 apply_propagate_attrs=self, 

1003 post_inspect=True, 

1004 ) 

1005 for ent in util.to_list(entities) 

1006 ] 

1007 self.element = element 

1008 self.is_dml = element.is_dml 

1009 self.is_select = element.is_select 

1010 self.is_delete = element.is_delete 

1011 self.is_insert = element.is_insert 

1012 self.is_update = element.is_update 

1013 self._label_style = ( 

1014 element._label_style if is_select_base(element) else None 

1015 ) 

1016 self._adapt_on_names = _adapt_on_names 

1017 

1018 def _compiler_dispatch(self, compiler, **kw): 

1019 """provide a fixed _compiler_dispatch method. 

1020 

1021 This is roughly similar to using the sqlalchemy.ext.compiler 

1022 ``@compiles`` extension. 

1023 

1024 """ 

1025 

1026 compile_state = self._compile_state_factory(self, compiler, **kw) 

1027 

1028 toplevel = not compiler.stack 

1029 

1030 if toplevel: 

1031 compiler.compile_state = compile_state 

1032 

1033 return compiler.process(compile_state.statement, **kw) 

1034 

1035 @property 

1036 def column_descriptions(self): 

1037 """Return a :term:`plugin-enabled` 'column descriptions' structure 

1038 referring to the columns which are SELECTed by this statement. 

1039 

1040 See the section :ref:`queryguide_inspection` for an overview 

1041 of this feature. 

1042 

1043 .. seealso:: 

1044 

1045 :ref:`queryguide_inspection` - ORM background 

1046 

1047 """ 

1048 meth = cast( 

1049 _ORMSelectCompileState, SelectState.get_plugin_class(self) 

1050 ).get_column_descriptions 

1051 return meth(self) 

1052 

1053 def _ensure_disambiguated_names(self): 

1054 return self 

1055 

1056 def get_children(self, **kw): 

1057 yield from itertools.chain.from_iterable( 

1058 element._from_objects for element in self._raw_columns 

1059 ) 

1060 yield from super().get_children(**kw) 

1061 

1062 @property 

1063 def _all_selected_columns(self): 

1064 return self.element._all_selected_columns 

1065 

1066 @property 

1067 def _return_defaults(self): 

1068 return self.element._return_defaults if is_dml(self.element) else None 

1069 

1070 @property 

1071 def _returning(self): 

1072 return self.element._returning if is_dml(self.element) else None 

1073 

1074 @property 

1075 def _inline(self): 

1076 return self.element._inline if is_insert_update(self.element) else None 

1077 

1078 

1079@sql.base.CompileState.plugin_for("orm", "compound_select") 

1080class _CompoundSelectCompileState( 

1081 _AutoflushOnlyORMCompileState, CompoundSelectState 

1082): 

1083 pass 

1084 

1085 

1086@sql.base.CompileState.plugin_for("orm", "select") 

1087class _ORMSelectCompileState(_ORMCompileState, SelectState): 

1088 _already_joined_edges = () 

1089 

1090 _memoized_entities = _EMPTY_DICT 

1091 

1092 _from_obj_alias = None 

1093 _has_mapper_entities = False 

1094 

1095 _has_orm_entities = False 

1096 multi_row_eager_loaders = False 

1097 eager_adding_joins = False 

1098 compound_eager_adapter = None 

1099 

1100 correlate = None 

1101 correlate_except = None 

1102 _where_criteria = () 

1103 _having_criteria = () 

1104 

1105 @classmethod 

1106 def _create_orm_context( 

1107 cls, 

1108 statement: Union[Select, FromStatement], 

1109 *, 

1110 toplevel: bool, 

1111 compiler: Optional[SQLCompiler], 

1112 **kw: Any, 

1113 ) -> _ORMSelectCompileState: 

1114 

1115 self = cls.__new__(cls) 

1116 

1117 select_statement = statement 

1118 

1119 # if we are a select() that was never a legacy Query, we won't 

1120 # have ORM level compile options. 

1121 statement._compile_options = cls.default_compile_options.safe_merge( 

1122 statement._compile_options 

1123 ) 

1124 

1125 if select_statement._execution_options: 

1126 # execution options should not impact the compilation of a 

1127 # query, and at the moment subqueryloader is putting some things 

1128 # in here that we explicitly don't want stuck in a cache. 

1129 self.select_statement = select_statement._clone() 

1130 self.select_statement._execution_options = util.immutabledict() 

1131 else: 

1132 self.select_statement = select_statement 

1133 

1134 # indicates this select() came from Query.statement 

1135 self.for_statement = select_statement._compile_options._for_statement 

1136 

1137 # generally if we are from Query or directly from a select() 

1138 self.use_legacy_query_style = ( 

1139 select_statement._compile_options._use_legacy_query_style 

1140 ) 

1141 

1142 self._entities = [] 

1143 self._primary_entity = None 

1144 self._polymorphic_adapters = {} 

1145 

1146 self.compile_options = select_statement._compile_options 

1147 

1148 if not toplevel: 

1149 # for subqueries, turn off eagerloads and set 

1150 # "render_for_subquery". 

1151 self.compile_options += { 

1152 "_enable_eagerloads": False, 

1153 "_render_for_subquery": True, 

1154 } 

1155 

1156 # determine label style. we can make different decisions here. 

1157 # at the moment, trying to see if we can always use DISAMBIGUATE_ONLY 

1158 # rather than LABEL_STYLE_NONE, and if we can use disambiguate style 

1159 # for new style ORM selects too. 

1160 if ( 

1161 self.use_legacy_query_style 

1162 and self.select_statement._label_style is LABEL_STYLE_LEGACY_ORM 

1163 ): 

1164 if not self.for_statement: 

1165 self.label_style = LABEL_STYLE_TABLENAME_PLUS_COL 

1166 else: 

1167 self.label_style = LABEL_STYLE_DISAMBIGUATE_ONLY 

1168 else: 

1169 self.label_style = self.select_statement._label_style 

1170 

1171 if select_statement._memoized_select_entities: 

1172 self._memoized_entities = { 

1173 memoized_entities: _QueryEntity.to_compile_state( 

1174 self, 

1175 memoized_entities._raw_columns, 

1176 [], 

1177 is_current_entities=False, 

1178 ) 

1179 for memoized_entities in ( 

1180 select_statement._memoized_select_entities 

1181 ) 

1182 } 

1183 

1184 # label_convention is stateful and will yield deduping keys if it 

1185 # sees the same key twice. therefore it's important that it is not 

1186 # invoked for the above "memoized" entities that aren't actually 

1187 # in the columns clause 

1188 self._label_convention = self._column_naming_convention( 

1189 statement._label_style, self.use_legacy_query_style 

1190 ) 

1191 

1192 _QueryEntity.to_compile_state( 

1193 self, 

1194 select_statement._raw_columns, 

1195 self._entities, 

1196 is_current_entities=True, 

1197 ) 

1198 

1199 self.current_path = select_statement._compile_options._current_path 

1200 

1201 self.eager_order_by = () 

1202 

1203 self._init_global_attributes( 

1204 select_statement, 

1205 compiler, 

1206 toplevel=toplevel, 

1207 process_criteria_for_toplevel=False, 

1208 ) 

1209 

1210 if toplevel and ( 

1211 select_statement._with_options 

1212 or select_statement._memoized_select_entities 

1213 ): 

1214 for ( 

1215 memoized_entities 

1216 ) in select_statement._memoized_select_entities: 

1217 for opt in memoized_entities._with_options: 

1218 if opt._is_compile_state: 

1219 opt.process_compile_state_replaced_entities( 

1220 self, 

1221 [ 

1222 ent 

1223 for ent in self._memoized_entities[ 

1224 memoized_entities 

1225 ] 

1226 if isinstance(ent, _MapperEntity) 

1227 ], 

1228 ) 

1229 

1230 for opt in self.select_statement._with_options: 

1231 if opt._is_compile_state: 

1232 opt.process_compile_state(self) 

1233 

1234 # uncomment to print out the context.attributes structure 

1235 # after it's been set up above 

1236 # self._dump_option_struct() 

1237 

1238 if select_statement._compile_state_funcs: 

1239 for fn, key in select_statement._compile_state_funcs: 

1240 fn(self) 

1241 

1242 self.primary_columns = [] 

1243 self.secondary_columns = [] 

1244 self.dedupe_columns = set() 

1245 self.eager_joins = {} 

1246 self.extra_criteria_entities = {} 

1247 self.create_eager_joins = [] 

1248 self._fallback_from_clauses = [] 

1249 

1250 # normalize the FROM clauses early by themselves, as this makes 

1251 # it an easier job when we need to assemble a JOIN onto these, 

1252 # for select.join() as well as joinedload(). As of 1.4 there are now 

1253 # potentially more complex sets of FROM objects here as the use 

1254 # of lambda statements for lazyload, load_on_pk etc. uses more 

1255 # cloning of the select() construct. See #6495 

1256 self.from_clauses = self._normalize_froms( 

1257 info.selectable for info in select_statement._from_obj 

1258 ) 

1259 

1260 # this is a fairly arbitrary break into a second method, 

1261 # so it might be nicer to break up create_for_statement() 

1262 # and _setup_for_generate into three or four logical sections 

1263 self._setup_for_generate() 

1264 

1265 SelectState.__init__(self, self.statement, compiler, **kw) 

1266 return self 

1267 

1268 def _dump_option_struct(self): 

1269 print("\n---------------------------------------------------\n") 

1270 print(f"current path: {self.current_path}") 

1271 for key in self.attributes: 

1272 if isinstance(key, tuple) and key[0] == "loader": 

1273 print(f"\nLoader: {PathRegistry.coerce(key[1])}") 

1274 print(f" {self.attributes[key]}") 

1275 print(f" {self.attributes[key].__dict__}") 

1276 elif isinstance(key, tuple) and key[0] == "path_with_polymorphic": 

1277 print(f"\nWith Polymorphic: {PathRegistry.coerce(key[1])}") 

1278 print(f" {self.attributes[key]}") 

1279 

1280 def _setup_for_generate(self): 

1281 query = self.select_statement 

1282 

1283 self.statement = None 

1284 self._join_entities = () 

1285 

1286 if self.compile_options._set_base_alias: 

1287 # legacy Query only 

1288 self._set_select_from_alias() 

1289 

1290 for memoized_entities in query._memoized_select_entities: 

1291 if memoized_entities._setup_joins: 

1292 self._join( 

1293 memoized_entities._setup_joins, 

1294 self._memoized_entities[memoized_entities], 

1295 ) 

1296 

1297 if query._setup_joins: 

1298 self._join(query._setup_joins, self._entities) 

1299 

1300 current_adapter = self._get_current_adapter() 

1301 

1302 if query._where_criteria: 

1303 self._where_criteria = query._where_criteria 

1304 

1305 if current_adapter: 

1306 self._where_criteria = tuple( 

1307 current_adapter(crit, True) 

1308 for crit in self._where_criteria 

1309 ) 

1310 

1311 # TODO: some complexity with order_by here was due to mapper.order_by. 

1312 # now that this is removed we can hopefully make order_by / 

1313 # group_by act identically to how they are in Core select. 

1314 self.order_by = ( 

1315 self._adapt_col_list(query._order_by_clauses, current_adapter) 

1316 if current_adapter and query._order_by_clauses not in (None, False) 

1317 else query._order_by_clauses 

1318 ) 

1319 

1320 if query._having_criteria: 

1321 self._having_criteria = tuple( 

1322 current_adapter(crit, True) if current_adapter else crit 

1323 for crit in query._having_criteria 

1324 ) 

1325 

1326 self.group_by = ( 

1327 self._adapt_col_list( 

1328 util.flatten_iterator(query._group_by_clauses), current_adapter 

1329 ) 

1330 if current_adapter and query._group_by_clauses not in (None, False) 

1331 else query._group_by_clauses or None 

1332 ) 

1333 

1334 if self.eager_order_by: 

1335 adapter = self.from_clauses[0]._target_adapter 

1336 self.eager_order_by = adapter.copy_and_process(self.eager_order_by) 

1337 

1338 if query._distinct_on: 

1339 self.distinct_on = self._adapt_col_list( 

1340 query._distinct_on, current_adapter 

1341 ) 

1342 else: 

1343 self.distinct_on = () 

1344 

1345 self.distinct = query._distinct 

1346 

1347 self.syntax_extensions = { 

1348 key: current_adapter(value, True) if current_adapter else value 

1349 for key, value in query._get_syntax_extensions_as_dict().items() 

1350 } 

1351 

1352 if query._correlate: 

1353 # ORM mapped entities that are mapped to joins can be passed 

1354 # to .correlate, so here they are broken into their component 

1355 # tables. 

1356 self.correlate = tuple( 

1357 util.flatten_iterator( 

1358 sql_util.surface_selectables(s) if s is not None else None 

1359 for s in query._correlate 

1360 ) 

1361 ) 

1362 elif query._correlate_except is not None: 

1363 self.correlate_except = tuple( 

1364 util.flatten_iterator( 

1365 sql_util.surface_selectables(s) if s is not None else None 

1366 for s in query._correlate_except 

1367 ) 

1368 ) 

1369 elif not query._auto_correlate: 

1370 self.correlate = (None,) 

1371 

1372 # PART II 

1373 

1374 self._for_update_arg = query._for_update_arg 

1375 

1376 if self.compile_options._is_star and (len(self._entities) != 1): 

1377 raise sa_exc.CompileError( 

1378 "Can't generate ORM query that includes multiple expressions " 

1379 "at the same time as '*'; query for '*' alone if present" 

1380 ) 

1381 for entity in self._entities: 

1382 entity.setup_compile_state(self) 

1383 

1384 for rec in self.create_eager_joins: 

1385 strategy = rec[0] 

1386 strategy(self, *rec[1:]) 

1387 

1388 # else "load from discrete FROMs" mode, 

1389 # i.e. when each _MappedEntity has its own FROM 

1390 

1391 if self.compile_options._enable_single_crit: 

1392 self._adjust_for_extra_criteria() 

1393 

1394 if not self.primary_columns: 

1395 if self.compile_options._only_load_props: 

1396 assert False, "no columns were included in _only_load_props" 

1397 

1398 raise sa_exc.InvalidRequestError( 

1399 "Query contains no columns with which to SELECT from." 

1400 ) 

1401 

1402 if not self.from_clauses: 

1403 self.from_clauses = list(self._fallback_from_clauses) 

1404 

1405 if self.order_by is False: 

1406 self.order_by = None 

1407 

1408 if ( 

1409 self.multi_row_eager_loaders 

1410 and self.eager_adding_joins 

1411 and self._should_nest_selectable 

1412 ): 

1413 self.statement = self._compound_eager_statement() 

1414 else: 

1415 self.statement = self._simple_statement() 

1416 

1417 if self.for_statement: 

1418 ezero = self._mapper_zero() 

1419 if ezero is not None: 

1420 # TODO: this goes away once we get rid of the deep entity 

1421 # thing 

1422 self.statement = self.statement._annotate( 

1423 {"deepentity": ezero} 

1424 ) 

1425 

1426 @classmethod 

1427 def _create_entities_collection(cls, query, legacy): 

1428 """Creates a partial ORMSelectCompileState that includes 

1429 the full collection of _MapperEntity and other _QueryEntity objects. 

1430 

1431 Supports a few remaining use cases that are pre-compilation 

1432 but still need to gather some of the column / adaption information. 

1433 

1434 """ 

1435 self = cls.__new__(cls) 

1436 

1437 self._entities = [] 

1438 self._primary_entity = None 

1439 self._polymorphic_adapters = {} 

1440 

1441 self._label_convention = self._column_naming_convention( 

1442 query._label_style, legacy 

1443 ) 

1444 

1445 # entities will also set up polymorphic adapters for mappers 

1446 # that have with_polymorphic configured 

1447 _QueryEntity.to_compile_state( 

1448 self, query._raw_columns, self._entities, is_current_entities=True 

1449 ) 

1450 return self 

1451 

1452 @classmethod 

1453 def determine_last_joined_entity(cls, statement): 

1454 setup_joins = statement._setup_joins 

1455 

1456 return _determine_last_joined_entity(setup_joins, None) 

1457 

1458 @classmethod 

1459 def all_selected_columns(cls, statement): 

1460 for element in statement._raw_columns: 

1461 if ( 

1462 element.is_selectable 

1463 and "entity_namespace" in element._annotations 

1464 ): 

1465 ens = element._annotations["entity_namespace"] 

1466 if not ens.is_mapper and not ens.is_aliased_class: 

1467 yield from _select_iterables([element]) 

1468 else: 

1469 yield from _select_iterables(ens._all_column_expressions) 

1470 else: 

1471 yield from _select_iterables([element]) 

1472 

1473 @classmethod 

1474 def get_columns_clause_froms(cls, statement): 

1475 return cls._normalize_froms( 

1476 itertools.chain.from_iterable( 

1477 ( 

1478 element._from_objects 

1479 if "parententity" not in element._annotations 

1480 else [ 

1481 element._annotations[ 

1482 "parententity" 

1483 ].__clause_element__() 

1484 ] 

1485 ) 

1486 for element in statement._raw_columns 

1487 ) 

1488 ) 

1489 

1490 @classmethod 

1491 def from_statement(cls, statement, from_statement): 

1492 from_statement = coercions.expect( 

1493 roles.ReturnsRowsRole, 

1494 from_statement, 

1495 apply_propagate_attrs=statement, 

1496 ) 

1497 

1498 stmt = FromStatement(statement._raw_columns, from_statement) 

1499 

1500 stmt.__dict__.update( 

1501 _with_options=statement._with_options, 

1502 _compile_state_funcs=statement._compile_state_funcs, 

1503 _execution_options=statement._execution_options, 

1504 _propagate_attrs=statement._propagate_attrs, 

1505 ) 

1506 return stmt 

1507 

1508 def _set_select_from_alias(self): 

1509 """used only for legacy Query cases""" 

1510 

1511 query = self.select_statement # query 

1512 

1513 assert self.compile_options._set_base_alias 

1514 assert len(query._from_obj) == 1 

1515 

1516 adapter = self._get_select_from_alias_from_obj(query._from_obj[0]) 

1517 if adapter: 

1518 self.compile_options += {"_enable_single_crit": False} 

1519 self._from_obj_alias = adapter 

1520 

1521 def _get_select_from_alias_from_obj(self, from_obj): 

1522 """used only for legacy Query cases""" 

1523 

1524 info = from_obj 

1525 

1526 if "parententity" in info._annotations: 

1527 info = info._annotations["parententity"] 

1528 

1529 if hasattr(info, "mapper"): 

1530 if not info.is_aliased_class: 

1531 raise sa_exc.ArgumentError( 

1532 "A selectable (FromClause) instance is " 

1533 "expected when the base alias is being set." 

1534 ) 

1535 else: 

1536 return info._adapter 

1537 

1538 elif isinstance(info.selectable, sql.selectable.AliasedReturnsRows): 

1539 equivs = self._all_equivs() 

1540 assert info is info.selectable 

1541 return ORMStatementAdapter( 

1542 _TraceAdaptRole.LEGACY_SELECT_FROM_ALIAS, 

1543 info.selectable, 

1544 equivalents=equivs, 

1545 ) 

1546 else: 

1547 return None 

1548 

1549 def _mapper_zero(self): 

1550 """return the Mapper associated with the first QueryEntity.""" 

1551 return self._entities[0].mapper 

1552 

1553 def _entity_zero(self): 

1554 """Return the 'entity' (mapper or AliasedClass) associated 

1555 with the first QueryEntity, or alternatively the 'select from' 

1556 entity if specified.""" 

1557 

1558 for ent in self.from_clauses: 

1559 if "parententity" in ent._annotations: 

1560 return ent._annotations["parententity"] 

1561 for qent in self._entities: 

1562 if qent.entity_zero: 

1563 return qent.entity_zero 

1564 

1565 return None 

1566 

1567 def _only_full_mapper_zero(self, methname): 

1568 if self._entities != [self._primary_entity]: 

1569 raise sa_exc.InvalidRequestError( 

1570 "%s() can only be used against " 

1571 "a single mapped class." % methname 

1572 ) 

1573 return self._primary_entity.entity_zero 

1574 

1575 def _only_entity_zero(self, rationale=None): 

1576 if len(self._entities) > 1: 

1577 raise sa_exc.InvalidRequestError( 

1578 rationale 

1579 or "This operation requires a Query " 

1580 "against a single mapper." 

1581 ) 

1582 return self._entity_zero() 

1583 

1584 def _all_equivs(self): 

1585 equivs = {} 

1586 

1587 for memoized_entities in self._memoized_entities.values(): 

1588 for ent in [ 

1589 ent 

1590 for ent in memoized_entities 

1591 if isinstance(ent, _MapperEntity) 

1592 ]: 

1593 equivs.update(ent.mapper._equivalent_columns) 

1594 

1595 for ent in [ 

1596 ent for ent in self._entities if isinstance(ent, _MapperEntity) 

1597 ]: 

1598 equivs.update(ent.mapper._equivalent_columns) 

1599 return equivs 

1600 

1601 def _compound_eager_statement(self): 

1602 # for eager joins present and LIMIT/OFFSET/DISTINCT, 

1603 # wrap the query inside a select, 

1604 # then append eager joins onto that 

1605 

1606 if self.order_by: 

1607 # the default coercion for ORDER BY is now the OrderByRole, 

1608 # which adds an additional post coercion to ByOfRole in that 

1609 # elements are converted into label references. For the 

1610 # eager load / subquery wrapping case, we need to un-coerce 

1611 # the original expressions outside of the label references 

1612 # in order to have them render. 

1613 unwrapped_order_by = [ 

1614 ( 

1615 elem.element 

1616 if isinstance(elem, sql.elements._label_reference) 

1617 else elem 

1618 ) 

1619 for elem in self.order_by 

1620 ] 

1621 

1622 order_by_col_expr = sql_util.expand_column_list_from_order_by( 

1623 self.primary_columns, unwrapped_order_by 

1624 ) 

1625 else: 

1626 order_by_col_expr = [] 

1627 unwrapped_order_by = None 

1628 

1629 # put FOR UPDATE on the inner query, where MySQL will honor it, 

1630 # as well as if it has an OF so PostgreSQL can use it. 

1631 inner = self._select_statement( 

1632 self.primary_columns 

1633 + [c for c in order_by_col_expr if c not in self.dedupe_columns], 

1634 self.from_clauses, 

1635 self._where_criteria, 

1636 self._having_criteria, 

1637 self.label_style, 

1638 self.order_by, 

1639 for_update=self._for_update_arg, 

1640 hints=self.select_statement._hints, 

1641 statement_hints=self.select_statement._statement_hints, 

1642 correlate=self.correlate, 

1643 correlate_except=self.correlate_except, 

1644 **self._select_args, 

1645 ) 

1646 

1647 inner = inner.alias() 

1648 

1649 equivs = self._all_equivs() 

1650 

1651 self.compound_eager_adapter = ORMStatementAdapter( 

1652 _TraceAdaptRole.COMPOUND_EAGER_STATEMENT, inner, equivalents=equivs 

1653 ) 

1654 

1655 statement = future.select( 

1656 *([inner] + self.secondary_columns) # use_labels=self.labels 

1657 ) 

1658 statement._label_style = self.label_style 

1659 

1660 # Oracle Database however does not allow FOR UPDATE on the subquery, 

1661 # and the Oracle Database dialects ignore it, plus for PostgreSQL, 

1662 # MySQL we expect that all elements of the row are locked, so also put 

1663 # it on the outside (except in the case of PG when OF is used) 

1664 if ( 

1665 self._for_update_arg is not None 

1666 and self._for_update_arg.of is None 

1667 ): 

1668 statement._for_update_arg = self._for_update_arg 

1669 

1670 from_clause = inner 

1671 for eager_join in self.eager_joins.values(): 

1672 # EagerLoader places a 'stop_on' attribute on the join, 

1673 # giving us a marker as to where the "splice point" of 

1674 # the join should be 

1675 from_clause = sql_util.splice_joins( 

1676 from_clause, eager_join, eager_join.stop_on 

1677 ) 

1678 

1679 statement.select_from.non_generative(statement, from_clause) 

1680 

1681 if unwrapped_order_by: 

1682 statement.order_by.non_generative( 

1683 statement, 

1684 *self.compound_eager_adapter.copy_and_process( 

1685 unwrapped_order_by 

1686 ), 

1687 ) 

1688 

1689 statement.order_by.non_generative(statement, *self.eager_order_by) 

1690 return statement 

1691 

1692 def _simple_statement(self): 

1693 statement = self._select_statement( 

1694 self.primary_columns + self.secondary_columns, 

1695 tuple(self.from_clauses) + tuple(self.eager_joins.values()), 

1696 self._where_criteria, 

1697 self._having_criteria, 

1698 self.label_style, 

1699 self.order_by, 

1700 for_update=self._for_update_arg, 

1701 hints=self.select_statement._hints, 

1702 statement_hints=self.select_statement._statement_hints, 

1703 correlate=self.correlate, 

1704 correlate_except=self.correlate_except, 

1705 **self._select_args, 

1706 ) 

1707 

1708 if self.eager_order_by: 

1709 statement.order_by.non_generative(statement, *self.eager_order_by) 

1710 return statement 

1711 

1712 def _select_statement( 

1713 self, 

1714 raw_columns, 

1715 from_obj, 

1716 where_criteria, 

1717 having_criteria, 

1718 label_style, 

1719 order_by, 

1720 for_update, 

1721 hints, 

1722 statement_hints, 

1723 correlate, 

1724 correlate_except, 

1725 limit_clause, 

1726 offset_clause, 

1727 fetch_clause, 

1728 fetch_clause_options, 

1729 distinct, 

1730 distinct_on, 

1731 prefixes, 

1732 suffixes, 

1733 group_by, 

1734 independent_ctes, 

1735 independent_ctes_opts, 

1736 syntax_extensions, 

1737 ): 

1738 statement = Select._create_raw_select( 

1739 _raw_columns=raw_columns, 

1740 _from_obj=from_obj, 

1741 _label_style=label_style, 

1742 ) 

1743 

1744 if where_criteria: 

1745 statement._where_criteria = where_criteria 

1746 if having_criteria: 

1747 statement._having_criteria = having_criteria 

1748 

1749 if order_by: 

1750 statement._order_by_clauses += tuple(order_by) 

1751 

1752 if distinct_on: 

1753 statement._distinct = True 

1754 statement._distinct_on = distinct_on 

1755 elif distinct: 

1756 statement._distinct = True 

1757 

1758 if group_by: 

1759 statement._group_by_clauses += tuple(group_by) 

1760 

1761 statement._limit_clause = limit_clause 

1762 statement._offset_clause = offset_clause 

1763 statement._fetch_clause = fetch_clause 

1764 statement._fetch_clause_options = fetch_clause_options 

1765 statement._independent_ctes = independent_ctes 

1766 statement._independent_ctes_opts = independent_ctes_opts 

1767 if syntax_extensions: 

1768 statement._set_syntax_extensions(**syntax_extensions) 

1769 

1770 if prefixes: 

1771 statement._prefixes = prefixes 

1772 

1773 if suffixes: 

1774 statement._suffixes = suffixes 

1775 

1776 statement._for_update_arg = for_update 

1777 

1778 if hints: 

1779 statement._hints = hints 

1780 if statement_hints: 

1781 statement._statement_hints = statement_hints 

1782 

1783 if correlate: 

1784 statement.correlate.non_generative(statement, *correlate) 

1785 

1786 if correlate_except is not None: 

1787 statement.correlate_except.non_generative( 

1788 statement, *correlate_except 

1789 ) 

1790 

1791 return statement 

1792 

1793 def _adapt_polymorphic_element(self, element): 

1794 if "parententity" in element._annotations: 

1795 search = element._annotations["parententity"] 

1796 alias = self._polymorphic_adapters.get(search, None) 

1797 if alias: 

1798 return alias.adapt_clause(element) 

1799 

1800 if isinstance(element, expression.FromClause): 

1801 search = element 

1802 elif hasattr(element, "table"): 

1803 search = element.table 

1804 else: 

1805 return None 

1806 

1807 alias = self._polymorphic_adapters.get(search, None) 

1808 if alias: 

1809 return alias.adapt_clause(element) 

1810 

1811 def _adapt_col_list(self, cols, current_adapter): 

1812 if current_adapter: 

1813 return [current_adapter(o, True) for o in cols] 

1814 else: 

1815 return cols 

1816 

1817 def _get_current_adapter(self): 

1818 adapters = [] 

1819 

1820 if self._from_obj_alias: 

1821 # used for legacy going forward for query set_ops, e.g. 

1822 # union(), union_all(), etc. 

1823 # 1.4 and previously, also used for from_self(), 

1824 # select_entity_from() 

1825 # 

1826 # for the "from obj" alias, apply extra rule to the 

1827 # 'ORM only' check, if this query were generated from a 

1828 # subquery of itself, i.e. _from_selectable(), apply adaption 

1829 # to all SQL constructs. 

1830 adapters.append( 

1831 ( 

1832 True, 

1833 self._from_obj_alias.replace, 

1834 ) 

1835 ) 

1836 

1837 # this was *hopefully* the only adapter we were going to need 

1838 # going forward...however, we unfortunately need _from_obj_alias 

1839 # for query.union(), which we can't drop 

1840 if self._polymorphic_adapters: 

1841 adapters.append((False, self._adapt_polymorphic_element)) 

1842 

1843 if not adapters: 

1844 return None 

1845 

1846 def _adapt_clause(clause, as_filter): 

1847 # do we adapt all expression elements or only those 

1848 # tagged as 'ORM' constructs ? 

1849 

1850 def replace(elem): 

1851 is_orm_adapt = ( 

1852 "_orm_adapt" in elem._annotations 

1853 or "parententity" in elem._annotations 

1854 ) 

1855 for always_adapt, adapter in adapters: 

1856 if is_orm_adapt or always_adapt: 

1857 e = adapter(elem) 

1858 if e is not None: 

1859 return e 

1860 

1861 return visitors.replacement_traverse(clause, {}, replace) 

1862 

1863 return _adapt_clause 

1864 

1865 def _join(self, args, entities_collection): 

1866 for right, onclause, from_, flags in args: 

1867 isouter = flags["isouter"] 

1868 full = flags["full"] 

1869 

1870 right = inspect(right) 

1871 if onclause is not None: 

1872 onclause = inspect(onclause) 

1873 

1874 if isinstance(right, interfaces.PropComparator): 

1875 if onclause is not None: 

1876 raise sa_exc.InvalidRequestError( 

1877 "No 'on clause' argument may be passed when joining " 

1878 "to a relationship path as a target" 

1879 ) 

1880 

1881 onclause = right 

1882 right = None 

1883 elif "parententity" in right._annotations: 

1884 right = right._annotations["parententity"] 

1885 

1886 if onclause is None: 

1887 if not right.is_selectable and not hasattr(right, "mapper"): 

1888 raise sa_exc.ArgumentError( 

1889 "Expected mapped entity or " 

1890 "selectable/table as join target" 

1891 ) 

1892 

1893 if isinstance(onclause, interfaces.PropComparator): 

1894 # descriptor/property given (or determined); this tells us 

1895 # explicitly what the expected "left" side of the join is. 

1896 

1897 of_type = getattr(onclause, "_of_type", None) 

1898 

1899 if right is None: 

1900 if of_type: 

1901 right = of_type 

1902 else: 

1903 right = onclause.property 

1904 

1905 try: 

1906 right = right.entity 

1907 except AttributeError as err: 

1908 raise sa_exc.ArgumentError( 

1909 "Join target %s does not refer to a " 

1910 "mapped entity" % right 

1911 ) from err 

1912 

1913 left = onclause._parententity 

1914 

1915 prop = onclause.property 

1916 if not isinstance(onclause, attributes.QueryableAttribute): 

1917 onclause = prop 

1918 

1919 # check for this path already present. don't render in that 

1920 # case. 

1921 if (left, right, prop.key) in self._already_joined_edges: 

1922 continue 

1923 

1924 if from_ is not None: 

1925 if ( 

1926 from_ is not left 

1927 and from_._annotations.get("parententity", None) 

1928 is not left 

1929 ): 

1930 raise sa_exc.InvalidRequestError( 

1931 "explicit from clause %s does not match left side " 

1932 "of relationship attribute %s" 

1933 % ( 

1934 from_._annotations.get("parententity", from_), 

1935 onclause, 

1936 ) 

1937 ) 

1938 elif from_ is not None: 

1939 prop = None 

1940 left = from_ 

1941 else: 

1942 # no descriptor/property given; we will need to figure out 

1943 # what the effective "left" side is 

1944 prop = left = None 

1945 

1946 # figure out the final "left" and "right" sides and create an 

1947 # ORMJoin to add to our _from_obj tuple 

1948 self._join_left_to_right( 

1949 entities_collection, 

1950 left, 

1951 right, 

1952 onclause, 

1953 prop, 

1954 isouter, 

1955 full, 

1956 ) 

1957 

1958 def _join_left_to_right( 

1959 self, 

1960 entities_collection, 

1961 left, 

1962 right, 

1963 onclause, 

1964 prop, 

1965 outerjoin, 

1966 full, 

1967 ): 

1968 """given raw "left", "right", "onclause" parameters consumed from 

1969 a particular key within _join(), add a real ORMJoin object to 

1970 our _from_obj list (or augment an existing one) 

1971 

1972 """ 

1973 

1974 if left is None: 

1975 # left not given (e.g. no relationship object/name specified) 

1976 # figure out the best "left" side based on our existing froms / 

1977 # entities 

1978 assert prop is None 

1979 ( 

1980 left, 

1981 replace_from_obj_index, 

1982 use_entity_index, 

1983 ) = self._join_determine_implicit_left_side( 

1984 entities_collection, left, right, onclause 

1985 ) 

1986 else: 

1987 # left is given via a relationship/name, or as explicit left side. 

1988 # Determine where in our 

1989 # "froms" list it should be spliced/appended as well as what 

1990 # existing entity it corresponds to. 

1991 ( 

1992 replace_from_obj_index, 

1993 use_entity_index, 

1994 ) = self._join_place_explicit_left_side(entities_collection, left) 

1995 

1996 if left is right: 

1997 raise sa_exc.InvalidRequestError( 

1998 "Can't construct a join from %s to %s, they " 

1999 "are the same entity" % (left, right) 

2000 ) 

2001 

2002 # the right side as given often needs to be adapted. additionally 

2003 # a lot of things can be wrong with it. handle all that and 

2004 # get back the new effective "right" side 

2005 r_info, right, onclause = self._join_check_and_adapt_right_side( 

2006 left, right, onclause, prop 

2007 ) 

2008 

2009 if not r_info.is_selectable: 

2010 extra_criteria = self._get_extra_criteria(r_info) 

2011 else: 

2012 extra_criteria = () 

2013 

2014 if replace_from_obj_index is not None: 

2015 # splice into an existing element in the 

2016 # self._from_obj list 

2017 left_clause = self.from_clauses[replace_from_obj_index] 

2018 

2019 self.from_clauses = ( 

2020 self.from_clauses[:replace_from_obj_index] 

2021 + [ 

2022 _ORMJoin( 

2023 left_clause, 

2024 right, 

2025 onclause, 

2026 isouter=outerjoin, 

2027 full=full, 

2028 _extra_criteria=extra_criteria, 

2029 ) 

2030 ] 

2031 + self.from_clauses[replace_from_obj_index + 1 :] 

2032 ) 

2033 else: 

2034 # add a new element to the self._from_obj list 

2035 if use_entity_index is not None: 

2036 # make use of _MapperEntity selectable, which is usually 

2037 # entity_zero.selectable, but if with_polymorphic() were used 

2038 # might be distinct 

2039 assert isinstance( 

2040 entities_collection[use_entity_index], _MapperEntity 

2041 ) 

2042 left_clause = entities_collection[use_entity_index].selectable 

2043 else: 

2044 left_clause = left 

2045 

2046 self.from_clauses = self.from_clauses + [ 

2047 _ORMJoin( 

2048 left_clause, 

2049 r_info, 

2050 onclause, 

2051 isouter=outerjoin, 

2052 full=full, 

2053 _extra_criteria=extra_criteria, 

2054 ) 

2055 ] 

2056 

2057 def _join_determine_implicit_left_side( 

2058 self, entities_collection, left, right, onclause 

2059 ): 

2060 """When join conditions don't express the left side explicitly, 

2061 determine if an existing FROM or entity in this query 

2062 can serve as the left hand side. 

2063 

2064 """ 

2065 

2066 # when we are here, it means join() was called without an ORM- 

2067 # specific way of telling us what the "left" side is, e.g.: 

2068 # 

2069 # join(RightEntity) 

2070 # 

2071 # or 

2072 # 

2073 # join(RightEntity, RightEntity.foo == LeftEntity.bar) 

2074 # 

2075 

2076 r_info = inspect(right) 

2077 

2078 replace_from_obj_index = use_entity_index = None 

2079 

2080 if self.from_clauses: 

2081 # we have a list of FROMs already. So by definition this 

2082 # join has to connect to one of those FROMs. 

2083 

2084 indexes = sql_util.find_left_clause_to_join_from( 

2085 self.from_clauses, r_info.selectable, onclause 

2086 ) 

2087 

2088 if len(indexes) == 1: 

2089 replace_from_obj_index = indexes[0] 

2090 left = self.from_clauses[replace_from_obj_index] 

2091 elif len(indexes) > 1: 

2092 raise sa_exc.InvalidRequestError( 

2093 "Can't determine which FROM clause to join " 

2094 "from, there are multiple FROMS which can " 

2095 "join to this entity. Please use the .select_from() " 

2096 "method to establish an explicit left side, as well as " 

2097 "providing an explicit ON clause if not present already " 

2098 "to help resolve the ambiguity." 

2099 ) 

2100 else: 

2101 raise sa_exc.InvalidRequestError( 

2102 "Don't know how to join to %r. " 

2103 "Please use the .select_from() " 

2104 "method to establish an explicit left side, as well as " 

2105 "providing an explicit ON clause if not present already " 

2106 "to help resolve the ambiguity." % (right,) 

2107 ) 

2108 

2109 elif entities_collection: 

2110 # we have no explicit FROMs, so the implicit left has to 

2111 # come from our list of entities. 

2112 

2113 potential = {} 

2114 for entity_index, ent in enumerate(entities_collection): 

2115 entity = ent.entity_zero_or_selectable 

2116 if entity is None: 

2117 continue 

2118 ent_info = inspect(entity) 

2119 if ent_info is r_info: # left and right are the same, skip 

2120 continue 

2121 

2122 # by using a dictionary with the selectables as keys this 

2123 # de-duplicates those selectables as occurs when the query is 

2124 # against a series of columns from the same selectable 

2125 if isinstance(ent, _MapperEntity): 

2126 potential[ent.selectable] = (entity_index, entity) 

2127 else: 

2128 potential[ent_info.selectable] = (None, entity) 

2129 

2130 all_clauses = list(potential.keys()) 

2131 indexes = sql_util.find_left_clause_to_join_from( 

2132 all_clauses, r_info.selectable, onclause 

2133 ) 

2134 

2135 if len(indexes) == 1: 

2136 use_entity_index, left = potential[all_clauses[indexes[0]]] 

2137 elif len(indexes) > 1: 

2138 raise sa_exc.InvalidRequestError( 

2139 "Can't determine which FROM clause to join " 

2140 "from, there are multiple FROMS which can " 

2141 "join to this entity. Please use the .select_from() " 

2142 "method to establish an explicit left side, as well as " 

2143 "providing an explicit ON clause if not present already " 

2144 "to help resolve the ambiguity." 

2145 ) 

2146 else: 

2147 raise sa_exc.InvalidRequestError( 

2148 "Don't know how to join to %r. " 

2149 "Please use the .select_from() " 

2150 "method to establish an explicit left side, as well as " 

2151 "providing an explicit ON clause if not present already " 

2152 "to help resolve the ambiguity." % (right,) 

2153 ) 

2154 else: 

2155 raise sa_exc.InvalidRequestError( 

2156 "No entities to join from; please use " 

2157 "select_from() to establish the left " 

2158 "entity/selectable of this join" 

2159 ) 

2160 

2161 return left, replace_from_obj_index, use_entity_index 

2162 

2163 def _join_place_explicit_left_side(self, entities_collection, left): 

2164 """When join conditions express a left side explicitly, determine 

2165 where in our existing list of FROM clauses we should join towards, 

2166 or if we need to make a new join, and if so is it from one of our 

2167 existing entities. 

2168 

2169 """ 

2170 

2171 # when we are here, it means join() was called with an indicator 

2172 # as to an exact left side, which means a path to a 

2173 # Relationship was given, e.g.: 

2174 # 

2175 # join(RightEntity, LeftEntity.right) 

2176 # 

2177 # or 

2178 # 

2179 # join(LeftEntity.right) 

2180 # 

2181 # as well as string forms: 

2182 # 

2183 # join(RightEntity, "right") 

2184 # 

2185 # etc. 

2186 # 

2187 

2188 replace_from_obj_index = use_entity_index = None 

2189 

2190 l_info = inspect(left) 

2191 if self.from_clauses: 

2192 indexes = sql_util.find_left_clause_that_matches_given( 

2193 self.from_clauses, l_info.selectable 

2194 ) 

2195 

2196 if len(indexes) > 1: 

2197 raise sa_exc.InvalidRequestError( 

2198 "Can't identify which entity in which to assign the " 

2199 "left side of this join. Please use a more specific " 

2200 "ON clause." 

2201 ) 

2202 

2203 # have an index, means the left side is already present in 

2204 # an existing FROM in the self._from_obj tuple 

2205 if indexes: 

2206 replace_from_obj_index = indexes[0] 

2207 

2208 # no index, means we need to add a new element to the 

2209 # self._from_obj tuple 

2210 

2211 # no from element present, so we will have to add to the 

2212 # self._from_obj tuple. Determine if this left side matches up 

2213 # with existing mapper entities, in which case we want to apply the 

2214 # aliasing / adaptation rules present on that entity if any 

2215 if ( 

2216 replace_from_obj_index is None 

2217 and entities_collection 

2218 and hasattr(l_info, "mapper") 

2219 ): 

2220 for idx, ent in enumerate(entities_collection): 

2221 # TODO: should we be checking for multiple mapper entities 

2222 # matching? 

2223 if isinstance(ent, _MapperEntity) and ent.corresponds_to(left): 

2224 use_entity_index = idx 

2225 break 

2226 

2227 return replace_from_obj_index, use_entity_index 

2228 

2229 def _join_check_and_adapt_right_side(self, left, right, onclause, prop): 

2230 """transform the "right" side of the join as well as the onclause 

2231 according to polymorphic mapping translations, aliasing on the query 

2232 or on the join, special cases where the right and left side have 

2233 overlapping tables. 

2234 

2235 """ 

2236 

2237 l_info = inspect(left) 

2238 r_info = inspect(right) 

2239 

2240 overlap = False 

2241 

2242 right_mapper = getattr(r_info, "mapper", None) 

2243 # if the target is a joined inheritance mapping, 

2244 # be more liberal about auto-aliasing. 

2245 if right_mapper and ( 

2246 right_mapper.with_polymorphic 

2247 or isinstance(right_mapper.persist_selectable, expression.Join) 

2248 ): 

2249 for from_obj in self.from_clauses or [l_info.selectable]: 

2250 if sql_util.selectables_overlap( 

2251 l_info.selectable, from_obj 

2252 ) and sql_util.selectables_overlap( 

2253 from_obj, r_info.selectable 

2254 ): 

2255 overlap = True 

2256 break 

2257 

2258 if overlap and l_info.selectable is r_info.selectable: 

2259 raise sa_exc.InvalidRequestError( 

2260 "Can't join table/selectable '%s' to itself" 

2261 % l_info.selectable 

2262 ) 

2263 

2264 right_mapper, right_selectable, right_is_aliased = ( 

2265 getattr(r_info, "mapper", None), 

2266 r_info.selectable, 

2267 getattr(r_info, "is_aliased_class", False), 

2268 ) 

2269 

2270 if ( 

2271 right_mapper 

2272 and prop 

2273 and not right_mapper.common_parent(prop.mapper) 

2274 ): 

2275 raise sa_exc.InvalidRequestError( 

2276 "Join target %s does not correspond to " 

2277 "the right side of join condition %s" % (right, onclause) 

2278 ) 

2279 

2280 # _join_entities is used as a hint for single-table inheritance 

2281 # purposes at the moment 

2282 if hasattr(r_info, "mapper"): 

2283 self._join_entities += (r_info,) 

2284 

2285 need_adapter = False 

2286 

2287 # test for joining to an unmapped selectable as the target 

2288 if r_info.is_clause_element: 

2289 if prop: 

2290 right_mapper = prop.mapper 

2291 

2292 if right_selectable._is_lateral: 

2293 # orm_only is disabled to suit the case where we have to 

2294 # adapt an explicit correlate(Entity) - the select() loses 

2295 # the ORM-ness in this case right now, ideally it would not 

2296 current_adapter = self._get_current_adapter() 

2297 if current_adapter is not None: 

2298 # TODO: we had orm_only=False here before, removing 

2299 # it didn't break things. if we identify the rationale, 

2300 # may need to apply "_orm_only" annotation here. 

2301 right = current_adapter(right, True) 

2302 

2303 elif prop: 

2304 # joining to selectable with a mapper property given 

2305 # as the ON clause 

2306 

2307 if not right_selectable.is_derived_from( 

2308 right_mapper.persist_selectable 

2309 ): 

2310 raise sa_exc.InvalidRequestError( 

2311 "Selectable '%s' is not derived from '%s'" 

2312 % ( 

2313 right_selectable.description, 

2314 right_mapper.persist_selectable.description, 

2315 ) 

2316 ) 

2317 

2318 # if the destination selectable is a plain select(), 

2319 # turn it into an alias(). 

2320 if isinstance(right_selectable, expression.SelectBase): 

2321 right_selectable = coercions.expect( 

2322 roles.FromClauseRole, right_selectable 

2323 ) 

2324 need_adapter = True 

2325 

2326 # make the right hand side target into an ORM entity 

2327 right = AliasedClass(right_mapper, right_selectable) 

2328 

2329 util.warn_deprecated( 

2330 "An alias is being generated automatically against " 

2331 "joined entity %s for raw clauseelement, which is " 

2332 "deprecated and will be removed in a later release. " 

2333 "Use the aliased() " 

2334 "construct explicitly, see the linked example." 

2335 % right_mapper, 

2336 "1.4", 

2337 code="xaj1", 

2338 ) 

2339 

2340 # test for overlap: 

2341 # orm/inheritance/relationships.py 

2342 # SelfReferentialM2MTest 

2343 aliased_entity = right_mapper and not right_is_aliased and overlap 

2344 

2345 if not need_adapter and aliased_entity: 

2346 # there are a few places in the ORM that automatic aliasing 

2347 # is still desirable, and can't be automatic with a Core 

2348 # only approach. For illustrations of "overlaps" see 

2349 # test/orm/inheritance/test_relationships.py. There are also 

2350 # general overlap cases with many-to-many tables where automatic 

2351 # aliasing is desirable. 

2352 right = AliasedClass(right, flat=True) 

2353 need_adapter = True 

2354 

2355 util.warn( 

2356 "An alias is being generated automatically against " 

2357 "joined entity %s due to overlapping tables. This is a " 

2358 "legacy pattern which may be " 

2359 "deprecated in a later release. Use the " 

2360 "aliased(<entity>, flat=True) " 

2361 "construct explicitly, see the linked example." % right_mapper, 

2362 code="xaj2", 

2363 ) 

2364 

2365 if need_adapter: 

2366 # if need_adapter is True, we are in a deprecated case and 

2367 # a warning has been emitted. 

2368 assert right_mapper 

2369 

2370 adapter = ORMAdapter( 

2371 _TraceAdaptRole.DEPRECATED_JOIN_ADAPT_RIGHT_SIDE, 

2372 inspect(right), 

2373 equivalents=right_mapper._equivalent_columns, 

2374 ) 

2375 

2376 # if an alias() on the right side was generated, 

2377 # which is intended to wrap a the right side in a subquery, 

2378 # ensure that columns retrieved from this target in the result 

2379 # set are also adapted. 

2380 self._mapper_loads_polymorphically_with(right_mapper, adapter) 

2381 elif ( 

2382 not r_info.is_clause_element 

2383 and not right_is_aliased 

2384 and right_mapper._has_aliased_polymorphic_fromclause 

2385 ): 

2386 # for the case where the target mapper has a with_polymorphic 

2387 # set up, ensure an adapter is set up for criteria that works 

2388 # against this mapper. Previously, this logic used to 

2389 # use the "create_aliases or aliased_entity" case to generate 

2390 # an aliased() object, but this creates an alias that isn't 

2391 # strictly necessary. 

2392 # see test/orm/test_core_compilation.py 

2393 # ::RelNaturalAliasedJoinsTest::test_straight 

2394 # and similar 

2395 self._mapper_loads_polymorphically_with( 

2396 right_mapper, 

2397 ORMAdapter( 

2398 _TraceAdaptRole.WITH_POLYMORPHIC_ADAPTER_RIGHT_JOIN, 

2399 right_mapper, 

2400 selectable=right_mapper.selectable, 

2401 equivalents=right_mapper._equivalent_columns, 

2402 ), 

2403 ) 

2404 # if the onclause is a ClauseElement, adapt it with any 

2405 # adapters that are in place right now 

2406 if isinstance(onclause, expression.ClauseElement): 

2407 current_adapter = self._get_current_adapter() 

2408 if current_adapter: 

2409 onclause = current_adapter(onclause, True) 

2410 

2411 # if joining on a MapperProperty path, 

2412 # track the path to prevent redundant joins 

2413 if prop: 

2414 self._already_joined_edges += ((left, right, prop.key),) 

2415 

2416 return inspect(right), right, onclause 

2417 

2418 @property 

2419 def _select_args(self): 

2420 return { 

2421 "limit_clause": self.select_statement._limit_clause, 

2422 "offset_clause": self.select_statement._offset_clause, 

2423 "distinct": self.distinct, 

2424 "distinct_on": self.distinct_on, 

2425 "prefixes": self.select_statement._prefixes, 

2426 "suffixes": self.select_statement._suffixes, 

2427 "group_by": self.group_by or None, 

2428 "fetch_clause": self.select_statement._fetch_clause, 

2429 "fetch_clause_options": ( 

2430 self.select_statement._fetch_clause_options 

2431 ), 

2432 "independent_ctes": self.select_statement._independent_ctes, 

2433 "independent_ctes_opts": ( 

2434 self.select_statement._independent_ctes_opts 

2435 ), 

2436 "syntax_extensions": self.syntax_extensions, 

2437 } 

2438 

2439 @property 

2440 def _should_nest_selectable(self): 

2441 kwargs = self._select_args 

2442 return ( 

2443 kwargs.get("limit_clause") is not None 

2444 or kwargs.get("offset_clause") is not None 

2445 or kwargs.get("distinct", False) 

2446 or kwargs.get("distinct_on", ()) 

2447 or kwargs.get("group_by", False) 

2448 ) 

2449 

2450 def _get_extra_criteria(self, ext_info): 

2451 if ( 

2452 "additional_entity_criteria", 

2453 ext_info.mapper, 

2454 ) in self.global_attributes: 

2455 return tuple( 

2456 ae._resolve_where_criteria(ext_info) 

2457 for ae in self.global_attributes[ 

2458 ("additional_entity_criteria", ext_info.mapper) 

2459 ] 

2460 if (ae.include_aliases or ae.entity is ext_info) 

2461 and ae._should_include(self) 

2462 ) 

2463 else: 

2464 return () 

2465 

2466 def _adjust_for_extra_criteria(self): 

2467 """Apply extra criteria filtering. 

2468 

2469 For all distinct single-table-inheritance mappers represented in 

2470 the columns clause of this query, as well as the "select from entity", 

2471 add criterion to the WHERE 

2472 clause of the given QueryContext such that only the appropriate 

2473 subtypes are selected from the total results. 

2474 

2475 Additionally, add WHERE criteria originating from LoaderCriteriaOptions 

2476 associated with the global context. 

2477 

2478 """ 

2479 

2480 for fromclause in self.from_clauses: 

2481 ext_info = fromclause._annotations.get("parententity", None) 

2482 

2483 if ( 

2484 ext_info 

2485 and ( 

2486 ext_info.mapper._single_table_criterion is not None 

2487 or ("additional_entity_criteria", ext_info.mapper) 

2488 in self.global_attributes 

2489 ) 

2490 and ext_info not in self.extra_criteria_entities 

2491 ): 

2492 self.extra_criteria_entities[ext_info] = ( 

2493 ext_info, 

2494 ext_info._adapter if ext_info.is_aliased_class else None, 

2495 ) 

2496 

2497 _where_criteria_to_add = () 

2498 

2499 merged_single_crit = collections.defaultdict( 

2500 lambda: (util.OrderedSet(), set()) 

2501 ) 

2502 

2503 for ext_info, adapter in util.OrderedSet( 

2504 self.extra_criteria_entities.values() 

2505 ): 

2506 if ext_info in self._join_entities: 

2507 continue 

2508 

2509 # assemble single table inheritance criteria. 

2510 if ( 

2511 ext_info.is_aliased_class 

2512 and ext_info._base_alias()._is_with_polymorphic 

2513 ): 

2514 # for a with_polymorphic(), we always include the full 

2515 # hierarchy from what's given as the base class for the wpoly. 

2516 # this is new in 2.1 for #12395 so that it matches the behavior 

2517 # of joined inheritance. 

2518 hierarchy_root = ext_info._base_alias() 

2519 else: 

2520 hierarchy_root = ext_info 

2521 

2522 single_crit_component = ( 

2523 hierarchy_root.mapper._single_table_criteria_component 

2524 ) 

2525 

2526 if single_crit_component is not None: 

2527 polymorphic_on, criteria = single_crit_component 

2528 

2529 polymorphic_on = polymorphic_on._annotate( 

2530 { 

2531 "parententity": hierarchy_root, 

2532 "parentmapper": hierarchy_root.mapper, 

2533 } 

2534 ) 

2535 

2536 list_of_single_crits, adapters = merged_single_crit[ 

2537 (hierarchy_root, polymorphic_on) 

2538 ] 

2539 list_of_single_crits.update(criteria) 

2540 if adapter: 

2541 adapters.add(adapter) 

2542 

2543 # assemble "additional entity criteria", which come from 

2544 # with_loader_criteria() options 

2545 if not self.compile_options._for_refresh_state: 

2546 additional_entity_criteria = self._get_extra_criteria(ext_info) 

2547 _where_criteria_to_add += tuple( 

2548 adapter.traverse(crit) if adapter else crit 

2549 for crit in additional_entity_criteria 

2550 ) 

2551 

2552 # merge together single table inheritance criteria keyed to 

2553 # top-level mapper / aliasedinsp (which may be a with_polymorphic()) 

2554 for (ext_info, polymorphic_on), ( 

2555 merged_crit, 

2556 adapters, 

2557 ) in merged_single_crit.items(): 

2558 new_crit = polymorphic_on.in_(merged_crit) 

2559 for adapter in adapters: 

2560 new_crit = adapter.traverse(new_crit) 

2561 _where_criteria_to_add += (new_crit,) 

2562 

2563 current_adapter = self._get_current_adapter() 

2564 if current_adapter: 

2565 # finally run all the criteria through the "main" adapter, if we 

2566 # have one, and concatenate to final WHERE criteria 

2567 for crit in _where_criteria_to_add: 

2568 crit = sql_util._deep_annotate(crit, {"_orm_adapt": True}) 

2569 crit = current_adapter(crit, False) 

2570 self._where_criteria += (crit,) 

2571 else: 

2572 # else just concatenate our criteria to the final WHERE criteria 

2573 self._where_criteria += _where_criteria_to_add 

2574 

2575 

2576def _column_descriptions( 

2577 query_or_select_stmt: Union[Query, Select, FromStatement], 

2578 compile_state: Optional[_ORMSelectCompileState] = None, 

2579 legacy: bool = False, 

2580) -> List[ORMColumnDescription]: 

2581 if compile_state is None: 

2582 compile_state = _ORMSelectCompileState._create_entities_collection( 

2583 query_or_select_stmt, legacy=legacy 

2584 ) 

2585 ctx = compile_state 

2586 d = [ 

2587 { 

2588 "name": ent._label_name, 

2589 "type": ent.type, 

2590 "aliased": getattr(insp_ent, "is_aliased_class", False), 

2591 "expr": ent.expr, 

2592 "entity": ( 

2593 getattr(insp_ent, "entity", None) 

2594 if ent.entity_zero is not None 

2595 and not insp_ent.is_clause_element 

2596 else None 

2597 ), 

2598 } 

2599 for ent, insp_ent in [ 

2600 (_ent, _ent.entity_zero) for _ent in ctx._entities 

2601 ] 

2602 ] 

2603 return d 

2604 

2605 

2606def _legacy_filter_by_entity_zero( 

2607 query_or_augmented_select: Union[Query[Any], Select[Unpack[TupleAny]]], 

2608) -> Optional[_InternalEntityType[Any]]: 

2609 self = query_or_augmented_select 

2610 if self._setup_joins: 

2611 _last_joined_entity = self._last_joined_entity 

2612 if _last_joined_entity is not None: 

2613 return _last_joined_entity 

2614 

2615 if self._from_obj and "parententity" in self._from_obj[0]._annotations: 

2616 return self._from_obj[0]._annotations["parententity"] 

2617 

2618 return _entity_from_pre_ent_zero(self) 

2619 

2620 

2621def _entity_from_pre_ent_zero( 

2622 query_or_augmented_select: Union[Query[Any], Select[Unpack[TupleAny]]], 

2623) -> Optional[_InternalEntityType[Any]]: 

2624 self = query_or_augmented_select 

2625 if not self._raw_columns: 

2626 return None 

2627 

2628 ent = self._raw_columns[0] 

2629 

2630 if "parententity" in ent._annotations: 

2631 return ent._annotations["parententity"] 

2632 elif isinstance(ent, ORMColumnsClauseRole): 

2633 return ent.entity 

2634 elif "bundle" in ent._annotations: 

2635 return ent._annotations["bundle"] 

2636 else: 

2637 return ent 

2638 

2639 

2640def _determine_last_joined_entity( 

2641 setup_joins: Tuple[_SetupJoinsElement, ...], 

2642 entity_zero: Optional[_InternalEntityType[Any]] = None, 

2643) -> Optional[Union[_InternalEntityType[Any], _JoinTargetElement]]: 

2644 if not setup_joins: 

2645 return None 

2646 

2647 (target, onclause, from_, flags) = setup_joins[-1] 

2648 

2649 if isinstance( 

2650 target, 

2651 attributes.QueryableAttribute, 

2652 ): 

2653 return target.entity 

2654 else: 

2655 return target 

2656 

2657 

2658class _QueryEntity: 

2659 """represent an entity column returned within a Query result.""" 

2660 

2661 __slots__ = () 

2662 

2663 supports_single_entity: bool 

2664 

2665 _non_hashable_value = False 

2666 _null_column_type = False 

2667 use_id_for_hash = False 

2668 

2669 _label_name: Optional[str] 

2670 type: Union[Type[Any], TypeEngine[Any]] 

2671 expr: Union[_InternalEntityType, ColumnElement[Any]] 

2672 entity_zero: Optional[_InternalEntityType] 

2673 

2674 def setup_compile_state(self, compile_state: _ORMCompileState) -> None: 

2675 raise NotImplementedError() 

2676 

2677 def setup_dml_returning_compile_state( 

2678 self, 

2679 compile_state: _ORMCompileState, 

2680 adapter: Optional[_DMLReturningColFilter], 

2681 ) -> None: 

2682 raise NotImplementedError() 

2683 

2684 def row_processor(self, context, result): 

2685 raise NotImplementedError() 

2686 

2687 @classmethod 

2688 def to_compile_state( 

2689 cls, compile_state, entities, entities_collection, is_current_entities 

2690 ): 

2691 for idx, entity in enumerate(entities): 

2692 if entity._is_lambda_element: 

2693 if entity._is_sequence: 

2694 cls.to_compile_state( 

2695 compile_state, 

2696 entity._resolved, 

2697 entities_collection, 

2698 is_current_entities, 

2699 ) 

2700 continue 

2701 else: 

2702 entity = entity._resolved 

2703 

2704 if entity.is_clause_element: 

2705 if entity.is_selectable: 

2706 if "parententity" in entity._annotations: 

2707 _MapperEntity( 

2708 compile_state, 

2709 entity, 

2710 entities_collection, 

2711 is_current_entities, 

2712 ) 

2713 else: 

2714 _ColumnEntity._for_columns( 

2715 compile_state, 

2716 entity._select_iterable, 

2717 entities_collection, 

2718 idx, 

2719 is_current_entities, 

2720 ) 

2721 else: 

2722 if entity._annotations.get("bundle", False): 

2723 _BundleEntity( 

2724 compile_state, 

2725 entity, 

2726 entities_collection, 

2727 is_current_entities, 

2728 ) 

2729 elif entity._is_clause_list: 

2730 # this is legacy only - test_composites.py 

2731 # test_query_cols_legacy 

2732 _ColumnEntity._for_columns( 

2733 compile_state, 

2734 entity._select_iterable, 

2735 entities_collection, 

2736 idx, 

2737 is_current_entities, 

2738 ) 

2739 else: 

2740 _ColumnEntity._for_columns( 

2741 compile_state, 

2742 [entity], 

2743 entities_collection, 

2744 idx, 

2745 is_current_entities, 

2746 ) 

2747 elif entity.is_bundle: 

2748 _BundleEntity(compile_state, entity, entities_collection) 

2749 

2750 return entities_collection 

2751 

2752 

2753class _MapperEntity(_QueryEntity): 

2754 """mapper/class/AliasedClass entity""" 

2755 

2756 __slots__ = ( 

2757 "expr", 

2758 "mapper", 

2759 "entity_zero", 

2760 "is_aliased_class", 

2761 "path", 

2762 "_extra_entities", 

2763 "_label_name", 

2764 "_with_polymorphic_mappers", 

2765 "selectable", 

2766 "_polymorphic_discriminator", 

2767 ) 

2768 

2769 expr: _InternalEntityType 

2770 mapper: Mapper[Any] 

2771 entity_zero: _InternalEntityType 

2772 is_aliased_class: bool 

2773 path: PathRegistry 

2774 _label_name: str 

2775 

2776 def __init__( 

2777 self, compile_state, entity, entities_collection, is_current_entities 

2778 ): 

2779 entities_collection.append(self) 

2780 if is_current_entities: 

2781 if compile_state._primary_entity is None: 

2782 compile_state._primary_entity = self 

2783 compile_state._has_mapper_entities = True 

2784 compile_state._has_orm_entities = True 

2785 

2786 entity = entity._annotations["parententity"] 

2787 entity._post_inspect 

2788 ext_info = self.entity_zero = entity 

2789 entity = ext_info.entity 

2790 

2791 self.expr = entity 

2792 self.mapper = mapper = ext_info.mapper 

2793 

2794 self._extra_entities = (self.expr,) 

2795 

2796 if ext_info.is_aliased_class: 

2797 self._label_name = ext_info.name 

2798 else: 

2799 self._label_name = mapper.class_.__name__ 

2800 

2801 self.is_aliased_class = ext_info.is_aliased_class 

2802 self.path = ext_info._path_registry 

2803 

2804 self.selectable = ext_info.selectable 

2805 self._with_polymorphic_mappers = ext_info.with_polymorphic_mappers 

2806 self._polymorphic_discriminator = ext_info.polymorphic_on 

2807 

2808 if mapper._should_select_with_poly_adapter: 

2809 compile_state._create_with_polymorphic_adapter( 

2810 ext_info, self.selectable 

2811 ) 

2812 

2813 supports_single_entity = True 

2814 

2815 _non_hashable_value = True 

2816 use_id_for_hash = True 

2817 

2818 @property 

2819 def type(self): 

2820 return self.mapper.class_ 

2821 

2822 @property 

2823 def entity_zero_or_selectable(self): 

2824 return self.entity_zero 

2825 

2826 def corresponds_to(self, entity): 

2827 return _entity_corresponds_to(self.entity_zero, entity) 

2828 

2829 def _get_entity_clauses(self, compile_state): 

2830 adapter = None 

2831 

2832 if not self.is_aliased_class: 

2833 if compile_state._polymorphic_adapters: 

2834 adapter = compile_state._polymorphic_adapters.get( 

2835 self.mapper, None 

2836 ) 

2837 else: 

2838 adapter = self.entity_zero._adapter 

2839 

2840 if adapter: 

2841 if compile_state._from_obj_alias: 

2842 ret = adapter.wrap(compile_state._from_obj_alias) 

2843 else: 

2844 ret = adapter 

2845 else: 

2846 ret = compile_state._from_obj_alias 

2847 

2848 return ret 

2849 

2850 def row_processor(self, context, result): 

2851 compile_state = context.compile_state 

2852 adapter = self._get_entity_clauses(compile_state) 

2853 

2854 if compile_state.compound_eager_adapter and adapter: 

2855 adapter = adapter.wrap(compile_state.compound_eager_adapter) 

2856 elif not adapter: 

2857 adapter = compile_state.compound_eager_adapter 

2858 

2859 if compile_state._primary_entity is self: 

2860 only_load_props = compile_state.compile_options._only_load_props 

2861 refresh_state = context.refresh_state 

2862 else: 

2863 only_load_props = refresh_state = None 

2864 

2865 _instance = loading._instance_processor( 

2866 self, 

2867 self.mapper, 

2868 context, 

2869 result, 

2870 self.path, 

2871 adapter, 

2872 only_load_props=only_load_props, 

2873 refresh_state=refresh_state, 

2874 polymorphic_discriminator=self._polymorphic_discriminator, 

2875 ) 

2876 

2877 return _instance, self._label_name, self._extra_entities 

2878 

2879 def setup_dml_returning_compile_state( 

2880 self, 

2881 compile_state: _ORMCompileState, 

2882 adapter: Optional[_DMLReturningColFilter], 

2883 ) -> None: 

2884 loading._setup_entity_query( 

2885 compile_state, 

2886 self.mapper, 

2887 self, 

2888 self.path, 

2889 adapter, 

2890 compile_state.primary_columns, 

2891 with_polymorphic=self._with_polymorphic_mappers, 

2892 only_load_props=compile_state.compile_options._only_load_props, 

2893 polymorphic_discriminator=self._polymorphic_discriminator, 

2894 ) 

2895 

2896 def setup_compile_state(self, compile_state): 

2897 adapter = self._get_entity_clauses(compile_state) 

2898 

2899 single_table_crit = self.mapper._single_table_criterion 

2900 if ( 

2901 single_table_crit is not None 

2902 or ("additional_entity_criteria", self.mapper) 

2903 in compile_state.global_attributes 

2904 ): 

2905 ext_info = self.entity_zero 

2906 compile_state.extra_criteria_entities[ext_info] = ( 

2907 ext_info, 

2908 ext_info._adapter if ext_info.is_aliased_class else None, 

2909 ) 

2910 

2911 loading._setup_entity_query( 

2912 compile_state, 

2913 self.mapper, 

2914 self, 

2915 self.path, 

2916 adapter, 

2917 compile_state.primary_columns, 

2918 with_polymorphic=self._with_polymorphic_mappers, 

2919 only_load_props=compile_state.compile_options._only_load_props, 

2920 polymorphic_discriminator=self._polymorphic_discriminator, 

2921 ) 

2922 compile_state._fallback_from_clauses.append(self.selectable) 

2923 

2924 

2925class _BundleEntity(_QueryEntity): 

2926 _extra_entities = () 

2927 

2928 __slots__ = ( 

2929 "bundle", 

2930 "expr", 

2931 "type", 

2932 "_label_name", 

2933 "_entities", 

2934 "supports_single_entity", 

2935 ) 

2936 

2937 _entities: List[_QueryEntity] 

2938 bundle: Bundle 

2939 type: Type[Any] 

2940 _label_name: str 

2941 supports_single_entity: bool 

2942 expr: Bundle 

2943 

2944 def __init__( 

2945 self, 

2946 compile_state, 

2947 expr, 

2948 entities_collection, 

2949 is_current_entities, 

2950 setup_entities=True, 

2951 parent_bundle=None, 

2952 ): 

2953 compile_state._has_orm_entities = True 

2954 

2955 expr = expr._annotations["bundle"] 

2956 if parent_bundle: 

2957 parent_bundle._entities.append(self) 

2958 else: 

2959 entities_collection.append(self) 

2960 

2961 if isinstance( 

2962 expr, (attributes.QueryableAttribute, interfaces.PropComparator) 

2963 ): 

2964 bundle = expr.__clause_element__() 

2965 else: 

2966 bundle = expr 

2967 

2968 self.bundle = self.expr = bundle 

2969 self.type = type(bundle) 

2970 self._label_name = bundle.name 

2971 self._entities = [] 

2972 

2973 if setup_entities: 

2974 for expr in bundle.exprs: 

2975 if "bundle" in expr._annotations: 

2976 _BundleEntity( 

2977 compile_state, 

2978 expr, 

2979 entities_collection, 

2980 is_current_entities, 

2981 parent_bundle=self, 

2982 ) 

2983 elif isinstance(expr, Bundle): 

2984 _BundleEntity( 

2985 compile_state, 

2986 expr, 

2987 entities_collection, 

2988 is_current_entities, 

2989 parent_bundle=self, 

2990 ) 

2991 else: 

2992 _ORMColumnEntity._for_columns( 

2993 compile_state, 

2994 [expr], 

2995 entities_collection, 

2996 None, 

2997 is_current_entities, 

2998 parent_bundle=self, 

2999 ) 

3000 

3001 self.supports_single_entity = self.bundle.single_entity 

3002 

3003 @property 

3004 def mapper(self): 

3005 ezero = self.entity_zero 

3006 if ezero is not None: 

3007 return ezero.mapper 

3008 else: 

3009 return None 

3010 

3011 @property 

3012 def entity_zero(self): 

3013 for ent in self._entities: 

3014 ezero = ent.entity_zero 

3015 if ezero is not None: 

3016 return ezero 

3017 else: 

3018 return None 

3019 

3020 def corresponds_to(self, entity): 

3021 # TODO: we might be able to implement this but for now 

3022 # we are working around it 

3023 return False 

3024 

3025 @property 

3026 def entity_zero_or_selectable(self): 

3027 for ent in self._entities: 

3028 ezero = ent.entity_zero_or_selectable 

3029 if ezero is not None: 

3030 return ezero 

3031 else: 

3032 return None 

3033 

3034 def setup_compile_state(self, compile_state): 

3035 for ent in self._entities: 

3036 ent.setup_compile_state(compile_state) 

3037 

3038 def setup_dml_returning_compile_state( 

3039 self, 

3040 compile_state: _ORMCompileState, 

3041 adapter: Optional[_DMLReturningColFilter], 

3042 ) -> None: 

3043 return self.setup_compile_state(compile_state) 

3044 

3045 def row_processor(self, context, result): 

3046 procs, labels, extra = zip( 

3047 *[ent.row_processor(context, result) for ent in self._entities] 

3048 ) 

3049 

3050 proc = self.bundle.create_row_processor(context.query, procs, labels) 

3051 

3052 return proc, self._label_name, self._extra_entities 

3053 

3054 

3055class _ColumnEntity(_QueryEntity): 

3056 __slots__ = ( 

3057 "_fetch_column", 

3058 "_row_processor", 

3059 "raw_column_index", 

3060 "translate_raw_column", 

3061 ) 

3062 

3063 @classmethod 

3064 def _for_columns( 

3065 cls, 

3066 compile_state, 

3067 columns, 

3068 entities_collection, 

3069 raw_column_index, 

3070 is_current_entities, 

3071 parent_bundle=None, 

3072 ): 

3073 for column in columns: 

3074 annotations = column._annotations 

3075 if "parententity" in annotations: 

3076 _entity = annotations["parententity"] 

3077 else: 

3078 _entity = sql_util.extract_first_column_annotation( 

3079 column, "parententity" 

3080 ) 

3081 

3082 if _entity: 

3083 if "identity_token" in column._annotations: 

3084 _IdentityTokenEntity( 

3085 compile_state, 

3086 column, 

3087 entities_collection, 

3088 _entity, 

3089 raw_column_index, 

3090 is_current_entities, 

3091 parent_bundle=parent_bundle, 

3092 ) 

3093 else: 

3094 _ORMColumnEntity( 

3095 compile_state, 

3096 column, 

3097 entities_collection, 

3098 _entity, 

3099 raw_column_index, 

3100 is_current_entities, 

3101 parent_bundle=parent_bundle, 

3102 ) 

3103 else: 

3104 _RawColumnEntity( 

3105 compile_state, 

3106 column, 

3107 entities_collection, 

3108 raw_column_index, 

3109 is_current_entities, 

3110 parent_bundle=parent_bundle, 

3111 ) 

3112 

3113 @property 

3114 def type(self): 

3115 return self.column.type 

3116 

3117 @property 

3118 def _non_hashable_value(self): 

3119 return not self.column.type.hashable 

3120 

3121 @property 

3122 def _null_column_type(self): 

3123 return self.column.type._isnull 

3124 

3125 def row_processor(self, context, result): 

3126 compile_state = context.compile_state 

3127 

3128 # the resulting callable is entirely cacheable so just return 

3129 # it if we already made one 

3130 if self._row_processor is not None: 

3131 getter, label_name, extra_entities = self._row_processor 

3132 if self.translate_raw_column: 

3133 extra_entities += ( 

3134 context.query._raw_columns[self.raw_column_index], 

3135 ) 

3136 

3137 return getter, label_name, extra_entities 

3138 

3139 # retrieve the column that would have been set up in 

3140 # setup_compile_state, to avoid doing redundant work 

3141 if self._fetch_column is not None: 

3142 column = self._fetch_column 

3143 else: 

3144 # fetch_column will be None when we are doing a from_statement 

3145 # and setup_compile_state may not have been called. 

3146 column = self.column 

3147 

3148 # previously, the RawColumnEntity didn't look for from_obj_alias 

3149 # however I can't think of a case where we would be here and 

3150 # we'd want to ignore it if this is the from_statement use case. 

3151 # it's not really a use case to have raw columns + from_statement 

3152 if compile_state._from_obj_alias: 

3153 column = compile_state._from_obj_alias.columns[column] 

3154 

3155 if column._annotations: 

3156 # annotated columns perform more slowly in compiler and 

3157 # result due to the __eq__() method, so use deannotated 

3158 column = column._deannotate() 

3159 

3160 if compile_state.compound_eager_adapter: 

3161 column = compile_state.compound_eager_adapter.columns[column] 

3162 

3163 getter = result._getter(column) 

3164 ret = getter, self._label_name, self._extra_entities 

3165 self._row_processor = ret 

3166 

3167 if self.translate_raw_column: 

3168 extra_entities = self._extra_entities + ( 

3169 context.query._raw_columns[self.raw_column_index], 

3170 ) 

3171 return getter, self._label_name, extra_entities 

3172 else: 

3173 return ret 

3174 

3175 

3176class _RawColumnEntity(_ColumnEntity): 

3177 entity_zero = None 

3178 mapper = None 

3179 supports_single_entity = False 

3180 

3181 __slots__ = ( 

3182 "expr", 

3183 "column", 

3184 "_label_name", 

3185 "entity_zero_or_selectable", 

3186 "_extra_entities", 

3187 ) 

3188 

3189 def __init__( 

3190 self, 

3191 compile_state, 

3192 column, 

3193 entities_collection, 

3194 raw_column_index, 

3195 is_current_entities, 

3196 parent_bundle=None, 

3197 ): 

3198 self.expr = column 

3199 self.raw_column_index = raw_column_index 

3200 self.translate_raw_column = raw_column_index is not None 

3201 

3202 if column._is_star: 

3203 compile_state.compile_options += {"_is_star": True} 

3204 

3205 if not is_current_entities or column._is_text_clause: 

3206 self._label_name = None 

3207 else: 

3208 if parent_bundle: 

3209 self._label_name = column._proxy_key 

3210 else: 

3211 self._label_name = compile_state._label_convention(column) 

3212 

3213 if parent_bundle: 

3214 parent_bundle._entities.append(self) 

3215 else: 

3216 entities_collection.append(self) 

3217 

3218 self.column = column 

3219 self.entity_zero_or_selectable = ( 

3220 self.column._from_objects[0] if self.column._from_objects else None 

3221 ) 

3222 self._extra_entities = (self.expr, self.column) 

3223 self._fetch_column = self._row_processor = None 

3224 

3225 def corresponds_to(self, entity): 

3226 return False 

3227 

3228 def setup_dml_returning_compile_state( 

3229 self, 

3230 compile_state: _ORMCompileState, 

3231 adapter: Optional[_DMLReturningColFilter], 

3232 ) -> None: 

3233 return self.setup_compile_state(compile_state) 

3234 

3235 def setup_compile_state(self, compile_state): 

3236 current_adapter = compile_state._get_current_adapter() 

3237 if current_adapter: 

3238 column = current_adapter(self.column, False) 

3239 if column is None: 

3240 return 

3241 else: 

3242 column = self.column 

3243 

3244 if column._annotations: 

3245 # annotated columns perform more slowly in compiler and 

3246 # result due to the __eq__() method, so use deannotated 

3247 column = column._deannotate() 

3248 

3249 compile_state.dedupe_columns.add(column) 

3250 compile_state.primary_columns.append(column) 

3251 self._fetch_column = column 

3252 

3253 

3254class _ORMColumnEntity(_ColumnEntity): 

3255 """Column/expression based entity.""" 

3256 

3257 supports_single_entity = False 

3258 

3259 __slots__ = ( 

3260 "expr", 

3261 "mapper", 

3262 "column", 

3263 "_label_name", 

3264 "entity_zero_or_selectable", 

3265 "entity_zero", 

3266 "_extra_entities", 

3267 ) 

3268 

3269 def __init__( 

3270 self, 

3271 compile_state, 

3272 column, 

3273 entities_collection, 

3274 parententity, 

3275 raw_column_index, 

3276 is_current_entities, 

3277 parent_bundle=None, 

3278 ): 

3279 annotations = column._annotations 

3280 

3281 _entity = parententity 

3282 

3283 # an AliasedClass won't have proxy_key in the annotations for 

3284 # a column if it was acquired using the class' adapter directly, 

3285 # such as using AliasedInsp._adapt_element(). this occurs 

3286 # within internal loaders. 

3287 

3288 orm_key = annotations.get("proxy_key", None) 

3289 proxy_owner = annotations.get("proxy_owner", _entity) 

3290 if orm_key: 

3291 self.expr = getattr(proxy_owner.entity, orm_key) 

3292 self.translate_raw_column = False 

3293 else: 

3294 # if orm_key is not present, that means this is an ad-hoc 

3295 # SQL ColumnElement, like a CASE() or other expression. 

3296 # include this column position from the invoked statement 

3297 # in the ORM-level ResultSetMetaData on each execute, so that 

3298 # it can be targeted by identity after caching 

3299 self.expr = column 

3300 self.translate_raw_column = raw_column_index is not None 

3301 

3302 self.raw_column_index = raw_column_index 

3303 

3304 if is_current_entities: 

3305 if parent_bundle: 

3306 self._label_name = orm_key if orm_key else column._proxy_key 

3307 else: 

3308 self._label_name = compile_state._label_convention( 

3309 column, col_name=orm_key 

3310 ) 

3311 else: 

3312 self._label_name = None 

3313 

3314 _entity._post_inspect 

3315 self.entity_zero = self.entity_zero_or_selectable = ezero = _entity 

3316 self.mapper = mapper = _entity.mapper 

3317 

3318 if parent_bundle: 

3319 parent_bundle._entities.append(self) 

3320 else: 

3321 entities_collection.append(self) 

3322 

3323 compile_state._has_orm_entities = True 

3324 

3325 self.column = column 

3326 

3327 self._fetch_column = self._row_processor = None 

3328 

3329 self._extra_entities = (self.expr, self.column) 

3330 

3331 if mapper._should_select_with_poly_adapter: 

3332 compile_state._create_with_polymorphic_adapter( 

3333 ezero, ezero.selectable 

3334 ) 

3335 

3336 def corresponds_to(self, entity): 

3337 if _is_aliased_class(entity): 

3338 # TODO: polymorphic subclasses ? 

3339 return entity is self.entity_zero 

3340 else: 

3341 return not _is_aliased_class( 

3342 self.entity_zero 

3343 ) and entity.common_parent(self.entity_zero) 

3344 

3345 def setup_dml_returning_compile_state( 

3346 self, 

3347 compile_state: _ORMCompileState, 

3348 adapter: Optional[_DMLReturningColFilter], 

3349 ) -> None: 

3350 

3351 self._fetch_column = column = self.column 

3352 if adapter: 

3353 column = adapter(column, False) 

3354 

3355 if column is not None: 

3356 compile_state.dedupe_columns.add(column) 

3357 compile_state.primary_columns.append(column) 

3358 

3359 def setup_compile_state(self, compile_state): 

3360 current_adapter = compile_state._get_current_adapter() 

3361 if current_adapter: 

3362 column = current_adapter(self.column, False) 

3363 if column is None: 

3364 assert compile_state.is_dml_returning 

3365 self._fetch_column = self.column 

3366 return 

3367 else: 

3368 column = self.column 

3369 

3370 ezero = self.entity_zero 

3371 

3372 single_table_crit = self.mapper._single_table_criterion 

3373 if ( 

3374 single_table_crit is not None 

3375 or ("additional_entity_criteria", self.mapper) 

3376 in compile_state.global_attributes 

3377 ): 

3378 compile_state.extra_criteria_entities[ezero] = ( 

3379 ezero, 

3380 ezero._adapter if ezero.is_aliased_class else None, 

3381 ) 

3382 

3383 if column._annotations and not column._expression_label: 

3384 # annotated columns perform more slowly in compiler and 

3385 # result due to the __eq__() method, so use deannotated 

3386 column = column._deannotate() 

3387 

3388 # use entity_zero as the from if we have it. this is necessary 

3389 # for polymorphic scenarios where our FROM is based on ORM entity, 

3390 # not the FROM of the column. but also, don't use it if our column 

3391 # doesn't actually have any FROMs that line up, such as when its 

3392 # a scalar subquery. 

3393 if set(self.column._from_objects).intersection( 

3394 ezero.selectable._from_objects 

3395 ): 

3396 compile_state._fallback_from_clauses.append(ezero.selectable) 

3397 

3398 compile_state.dedupe_columns.add(column) 

3399 compile_state.primary_columns.append(column) 

3400 self._fetch_column = column 

3401 

3402 

3403class _IdentityTokenEntity(_ORMColumnEntity): 

3404 translate_raw_column = False 

3405 

3406 def setup_compile_state(self, compile_state): 

3407 pass 

3408 

3409 def row_processor(self, context, result): 

3410 def getter(row): 

3411 return context.load_options._identity_token 

3412 

3413 return getter, self._label_name, self._extra_entities