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

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

1339 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 ExecutableStatement 

58from ..sql.base import Generative 

59from ..sql.base import Options 

60from ..sql.dml import UpdateBase 

61from ..sql.elements import GroupedElement 

62from ..sql.elements import TextClause 

63from ..sql.selectable import CompoundSelectState 

64from ..sql.selectable import LABEL_STYLE_DISAMBIGUATE_ONLY 

65from ..sql.selectable import LABEL_STYLE_NONE 

66from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL 

67from ..sql.selectable import Select 

68from ..sql.selectable import SelectLabelStyle 

69from ..sql.selectable import SelectState 

70from ..sql.selectable import TypedReturnsRows 

71from ..sql.visitors import InternalTraversal 

72from ..util.typing import TupleAny 

73from ..util.typing import TypeVarTuple 

74from ..util.typing import Unpack 

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 

101LABEL_STYLE_LEGACY_ORM = SelectLabelStyle.LABEL_STYLE_LEGACY_ORM 

102 

103 

104class QueryContext: 

105 __slots__ = ( 

106 "top_level_context", 

107 "compile_state", 

108 "query", 

109 "user_passed_query", 

110 "params", 

111 "load_options", 

112 "bind_arguments", 

113 "execution_options", 

114 "session", 

115 "autoflush", 

116 "populate_existing", 

117 "invoke_all_eagers", 

118 "version_check", 

119 "refresh_state", 

120 "create_eager_joins", 

121 "propagated_loader_options", 

122 "attributes", 

123 "runid", 

124 "partials", 

125 "post_load_paths", 

126 "identity_token", 

127 "yield_per", 

128 "loaders_require_buffering", 

129 "loaders_require_uniquing", 

130 ) 

131 

132 runid: int 

133 post_load_paths: Dict[PathRegistry, _PostLoad] 

134 compile_state: _ORMCompileState 

135 

136 class default_load_options(Options): 

137 _only_return_tuples = False 

138 _populate_existing = False 

139 _version_check = False 

140 _invoke_all_eagers = True 

141 _autoflush = True 

142 _identity_token = None 

143 _yield_per = None 

144 _refresh_state = None 

145 _lazy_loaded_from = None 

146 _legacy_uniquing = False 

147 _sa_top_level_orm_context = None 

148 _is_user_refresh = False 

149 

150 def __init__( 

151 self, 

152 compile_state: CompileState, 

153 statement: Union[ 

154 Select[Unpack[TupleAny]], 

155 FromStatement[Unpack[TupleAny]], 

156 UpdateBase, 

157 ], 

158 user_passed_query: Union[ 

159 Select[Unpack[TupleAny]], 

160 FromStatement[Unpack[TupleAny]], 

161 UpdateBase, 

162 ], 

163 params: _CoreSingleExecuteParams, 

164 session: Session, 

165 load_options: Union[ 

166 Type[QueryContext.default_load_options], 

167 QueryContext.default_load_options, 

168 ], 

169 execution_options: Optional[OrmExecuteOptionsParameter] = None, 

170 bind_arguments: Optional[_BindArguments] = None, 

171 ): 

172 self.load_options = load_options 

173 self.execution_options = execution_options or util.EMPTY_DICT 

174 self.bind_arguments = bind_arguments or util.EMPTY_DICT 

175 self.compile_state = compile_state 

176 self.query = statement 

177 

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

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

180 # routines where a separate FromStatement is manufactured in the 

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

182 self.user_passed_query = user_passed_query 

183 

184 self.session = session 

185 self.loaders_require_buffering = False 

186 self.loaders_require_uniquing = False 

187 self.params = params 

188 self.top_level_context = load_options._sa_top_level_orm_context 

189 

190 cached_options = compile_state.select_statement._with_options 

191 uncached_options = user_passed_query._with_options 

192 

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

194 # propagated loader options will be present on loaded InstanceState 

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

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

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

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

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

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

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

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

203 # AliasedClass 

204 self.propagated_loader_options = tuple( 

205 opt._adapt_cached_option_to_uncached_option(self, uncached_opt) 

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

207 if opt.propagate_to_loaders 

208 ) 

209 

210 self.attributes = dict(compile_state.attributes) 

211 

212 self.autoflush = load_options._autoflush 

213 self.populate_existing = load_options._populate_existing 

214 self.invoke_all_eagers = load_options._invoke_all_eagers 

215 self.version_check = load_options._version_check 

216 self.refresh_state = load_options._refresh_state 

217 self.yield_per = load_options._yield_per 

218 self.identity_token = load_options._identity_token 

219 

220 def _get_top_level_context(self) -> QueryContext: 

221 return self.top_level_context or self 

222 

223 

224_orm_load_exec_options = util.immutabledict( 

225 {"_result_disable_adapt_to_context": True} 

226) 

227 

228 

229class _AbstractORMCompileState(CompileState): 

230 is_dml_returning = False 

231 

232 def _init_global_attributes( 

233 self, statement, compiler, *, toplevel, process_criteria_for_toplevel 

234 ): 

235 self.attributes = {} 

236 

237 if compiler is None: 

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

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

240 self.global_attributes = {} 

241 assert toplevel 

242 return 

243 else: 

244 self.global_attributes = ga = compiler._global_attributes 

245 

246 if toplevel: 

247 ga["toplevel_orm"] = True 

248 

249 if process_criteria_for_toplevel: 

250 for opt in statement._with_options: 

251 if opt._is_criteria_option: 

252 opt.process_compile_state(self) 

253 

254 return 

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

256 return 

257 

258 stack_0 = compiler.stack[0] 

259 

260 try: 

261 toplevel_stmt = stack_0["selectable"] 

262 except KeyError: 

263 pass 

264 else: 

265 for opt in toplevel_stmt._with_options: 

266 if opt._is_compile_state and opt._is_criteria_option: 

267 opt.process_compile_state(self) 

268 

269 ga["toplevel_orm"] = True 

270 

271 @classmethod 

272 def create_for_statement( 

273 cls, 

274 statement: Executable, 

275 compiler: SQLCompiler, 

276 **kw: Any, 

277 ) -> CompileState: 

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

279 

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

281 

282 For a Select object, this would be invoked from 

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

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

285 FromStatement._compiler_dispatch() that would be called by 

286 SQLCompiler.process(). 

287 """ 

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

289 

290 @classmethod 

291 def orm_pre_session_exec( 

292 cls, 

293 session, 

294 statement, 

295 params, 

296 execution_options, 

297 bind_arguments, 

298 is_pre_event, 

299 ): 

300 raise NotImplementedError() 

301 

302 @classmethod 

303 def orm_execute_statement( 

304 cls, 

305 session, 

306 statement, 

307 params, 

308 execution_options, 

309 bind_arguments, 

310 conn, 

311 ) -> Result: 

312 result = conn.execute( 

313 statement, params or {}, execution_options=execution_options 

314 ) 

315 return cls.orm_setup_cursor_result( 

316 session, 

317 statement, 

318 params, 

319 execution_options, 

320 bind_arguments, 

321 result, 

322 ) 

323 

324 @classmethod 

325 def orm_setup_cursor_result( 

326 cls, 

327 session, 

328 statement, 

329 params, 

330 execution_options, 

331 bind_arguments, 

332 result, 

333 ): 

334 raise NotImplementedError() 

335 

336 

337class _AutoflushOnlyORMCompileState(_AbstractORMCompileState): 

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

339 

340 @classmethod 

341 def orm_pre_session_exec( 

342 cls, 

343 session, 

344 statement, 

345 params, 

346 execution_options, 

347 bind_arguments, 

348 is_pre_event, 

349 ): 

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

351 # in an ORMExecuteState hook 

352 ( 

353 load_options, 

354 execution_options, 

355 ) = QueryContext.default_load_options.from_execution_options( 

356 "_sa_orm_load_options", 

357 { 

358 "autoflush", 

359 }, 

360 execution_options, 

361 statement._execution_options, 

362 ) 

363 

364 if not is_pre_event and load_options._autoflush: 

365 session._autoflush() 

366 

367 return statement, execution_options, params 

368 

369 @classmethod 

370 def orm_setup_cursor_result( 

371 cls, 

372 session, 

373 statement, 

374 params, 

375 execution_options, 

376 bind_arguments, 

377 result, 

378 ): 

379 return result 

380 

381 

382class _ORMCompileState(_AbstractORMCompileState): 

383 class default_compile_options(CacheableOptions): 

384 _cache_key_traversal = [ 

385 ("_use_legacy_query_style", InternalTraversal.dp_boolean), 

386 ("_for_statement", InternalTraversal.dp_boolean), 

387 ("_bake_ok", InternalTraversal.dp_boolean), 

388 ("_current_path", InternalTraversal.dp_has_cache_key), 

389 ("_enable_single_crit", InternalTraversal.dp_boolean), 

390 ("_enable_eagerloads", InternalTraversal.dp_boolean), 

391 ("_only_load_props", InternalTraversal.dp_plain_obj), 

392 ("_set_base_alias", InternalTraversal.dp_boolean), 

393 ("_for_refresh_state", InternalTraversal.dp_boolean), 

394 ("_render_for_subquery", InternalTraversal.dp_boolean), 

395 ("_is_star", InternalTraversal.dp_boolean), 

396 ] 

397 

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

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

400 # now this basically indicates we should use tablename_columnname 

401 # style labels. Generally indicates the statement originated 

402 # from a Query object. 

403 _use_legacy_query_style = False 

404 

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

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

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

408 _for_statement = False 

409 

410 _bake_ok = True 

411 _current_path = _path_registry 

412 _enable_single_crit = True 

413 _enable_eagerloads = True 

414 _only_load_props = None 

415 _set_base_alias = False 

416 _for_refresh_state = False 

417 _render_for_subquery = False 

418 _is_star = False 

419 

420 attributes: Dict[Any, Any] 

421 global_attributes: Dict[Any, Any] 

422 

423 statement: Union[ 

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

425 ] 

426 select_statement: Union[ 

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

428 ] 

429 _entities: List[_QueryEntity] 

430 _polymorphic_adapters: Dict[_InternalEntityType, ORMAdapter] 

431 compile_options: Union[ 

432 Type[default_compile_options], default_compile_options 

433 ] 

434 _primary_entity: Optional[_QueryEntity] 

435 use_legacy_query_style: bool 

436 _label_convention: _LabelConventionCallable 

437 primary_columns: List[ColumnElement[Any]] 

438 secondary_columns: List[ColumnElement[Any]] 

439 dedupe_columns: Set[ColumnElement[Any]] 

440 create_eager_joins: List[ 

441 # TODO: this structure is set up by JoinedLoader 

442 TupleAny 

443 ] 

444 current_path: PathRegistry = _path_registry 

445 _has_mapper_entities = False 

446 

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

448 raise NotImplementedError() 

449 

450 @classmethod 

451 def create_for_statement( 

452 cls, 

453 statement: Executable, 

454 compiler: SQLCompiler, 

455 **kw: Any, 

456 ) -> _ORMCompileState: 

457 return cls._create_orm_context( 

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

459 toplevel=not compiler.stack, 

460 compiler=compiler, 

461 **kw, 

462 ) 

463 

464 @classmethod 

465 def _create_orm_context( 

466 cls, 

467 statement: Union[Select, FromStatement], 

468 *, 

469 toplevel: bool, 

470 compiler: Optional[SQLCompiler], 

471 **kw: Any, 

472 ) -> _ORMCompileState: 

473 raise NotImplementedError() 

474 

475 def _append_dedupe_col_collection(self, obj, col_collection): 

476 dedupe = self.dedupe_columns 

477 if obj not in dedupe: 

478 dedupe.add(obj) 

479 col_collection.append(obj) 

480 

481 @classmethod 

482 def _column_naming_convention( 

483 cls, label_style: SelectLabelStyle, legacy: bool 

484 ) -> _LabelConventionCallable: 

485 if legacy: 

486 

487 def name(col, col_name=None): 

488 if col_name: 

489 return col_name 

490 else: 

491 return getattr(col, "key") 

492 

493 return name 

494 else: 

495 return SelectState._column_naming_convention(label_style) 

496 

497 @classmethod 

498 def get_column_descriptions(cls, statement): 

499 return _column_descriptions(statement) 

500 

501 @classmethod 

502 def orm_pre_session_exec( 

503 cls, 

504 session, 

505 statement, 

506 params, 

507 execution_options, 

508 bind_arguments, 

509 is_pre_event, 

510 ): 

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

512 # in an ORMExecuteState hook 

513 ( 

514 load_options, 

515 execution_options, 

516 ) = QueryContext.default_load_options.from_execution_options( 

517 "_sa_orm_load_options", 

518 { 

519 "populate_existing", 

520 "autoflush", 

521 "yield_per", 

522 "identity_token", 

523 "sa_top_level_orm_context", 

524 }, 

525 execution_options, 

526 statement._execution_options, 

527 ) 

528 

529 # default execution options for ORM results: 

530 # 1. _result_disable_adapt_to_context=True 

531 # this will disable the ResultSetMetadata._adapt_to_context() 

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

533 # against the original SELECT statement before caching. 

534 

535 if "sa_top_level_orm_context" in execution_options: 

536 ctx = execution_options["sa_top_level_orm_context"] 

537 execution_options = ctx.query._execution_options.merge_with( 

538 ctx.execution_options, execution_options 

539 ) 

540 

541 if not execution_options: 

542 execution_options = _orm_load_exec_options 

543 else: 

544 execution_options = execution_options.union(_orm_load_exec_options) 

545 

546 # would have been placed here by legacy Query only 

547 if load_options._yield_per: 

548 execution_options = execution_options.union( 

549 {"yield_per": load_options._yield_per} 

550 ) 

551 

552 if ( 

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

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

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

556 ): 

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

558 execution_options.union( 

559 { 

560 "compiled_cache": None, 

561 "_cache_disable_reason": "excess depth for " 

562 "ORM loader options", 

563 } 

564 ) 

565 ) 

566 

567 bind_arguments["clause"] = statement 

568 

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

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

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

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

573 

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

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

576 # needs to be present as well. 

577 

578 try: 

579 plugin_subject = statement._propagate_attrs["plugin_subject"] 

580 except KeyError: 

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

582 else: 

583 if plugin_subject: 

584 bind_arguments["mapper"] = plugin_subject.mapper 

585 

586 if not is_pre_event and load_options._autoflush: 

587 session._autoflush() 

588 

589 return statement, execution_options, params 

590 

591 @classmethod 

592 def orm_setup_cursor_result( 

593 cls, 

594 session, 

595 statement, 

596 params, 

597 execution_options, 

598 bind_arguments, 

599 result, 

600 ): 

601 execution_context = result.context 

602 compile_state = execution_context.compiled.compile_state 

603 

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

605 # were passed to session.execute: 

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

607 # see test_query->test_legacy_tuple_old_select 

608 

609 load_options = execution_options.get( 

610 "_sa_orm_load_options", QueryContext.default_load_options 

611 ) 

612 

613 if compile_state.compile_options._is_star: 

614 return result 

615 

616 querycontext = QueryContext( 

617 compile_state, 

618 statement, 

619 statement, 

620 params, 

621 session, 

622 load_options, 

623 execution_options, 

624 bind_arguments, 

625 ) 

626 return loading.instances(result, querycontext) 

627 

628 @property 

629 def _lead_mapper_entities(self): 

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

631 

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

633 with_entities(), with_only_columns() 

634 

635 """ 

636 return [ 

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

638 ] 

639 

640 def _create_with_polymorphic_adapter(self, ext_info, selectable): 

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

642 if called for by the Mapper. 

643 

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

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

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

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

648 loading, and joined inheritance where a subquery is 

649 passed to with_polymorphic (which is completely unnecessary in modern 

650 use). 

651 

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

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

654 to an alias or subquery "legacy" ? 

655 

656 """ 

657 if ( 

658 not ext_info.is_aliased_class 

659 and ext_info.mapper.persist_selectable 

660 not in self._polymorphic_adapters 

661 ): 

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

663 self._mapper_loads_polymorphically_with( 

664 mp, 

665 ORMAdapter( 

666 _TraceAdaptRole.WITH_POLYMORPHIC_ADAPTER, 

667 mp, 

668 equivalents=mp._equivalent_columns, 

669 selectable=selectable, 

670 ), 

671 ) 

672 

673 def _mapper_loads_polymorphically_with(self, mapper, adapter): 

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

675 self._polymorphic_adapters[m2] = adapter 

676 

677 for m in m2.iterate_to_root(): 

678 self._polymorphic_adapters[m.local_table] = adapter 

679 

680 @classmethod 

681 def _create_entities_collection(cls, query, legacy): 

682 raise NotImplementedError( 

683 "this method only works for ORMSelectCompileState" 

684 ) 

685 

686 

687class _DMLReturningColFilter: 

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

689 

690 Has a subset of the interface used by 

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

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

693 DML statement. 

694 

695 """ 

696 

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

698 

699 def __init__(self, target_mapper, immediate_dml_mapper): 

700 if ( 

701 immediate_dml_mapper is not None 

702 and target_mapper.local_table 

703 is not immediate_dml_mapper.local_table 

704 ): 

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

706 self.mapper = immediate_dml_mapper 

707 else: 

708 # single inh, normal mappings, etc. 

709 self.mapper = target_mapper 

710 self.columns = self.columns = util.WeakPopulateDict( 

711 self.adapt_check_present # type: ignore 

712 ) 

713 

714 def __call__(self, col, as_filter): 

715 for cc in sql_util._find_columns(col): 

716 c2 = self.adapt_check_present(cc) 

717 if c2 is not None: 

718 return col 

719 else: 

720 return None 

721 

722 def adapt_check_present(self, col): 

723 raise NotImplementedError() 

724 

725 

726class _DMLBulkInsertReturningColFilter(_DMLReturningColFilter): 

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

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

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

730 example right now) 

731 

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

733 mapped table in a hierarchy. 

734 

735 """ 

736 

737 def adapt_check_present(self, col): 

738 mapper = self.mapper 

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

740 if prop is None: 

741 return None 

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

743 

744 

745class _DMLUpdateDeleteReturningColFilter(_DMLReturningColFilter): 

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

747 for ORM enabled UPDATE/DELETE 

748 

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

750 only direct persisted columns from the immediate selectable, not 

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

752 mappers for the UPDATE..FROM use case. 

753 

754 """ 

755 

756 def adapt_check_present(self, col): 

757 mapper = self.mapper 

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

759 if prop is not None: 

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

761 # column, not any kind of column_property expression 

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

763 

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

765 # user knows what they are doing 

766 return col 

767 

768 

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

770class _ORMFromStatementCompileState(_ORMCompileState): 

771 _from_obj_alias = None 

772 _has_mapper_entities = False 

773 

774 statement_container: FromStatement 

775 requested_statement: Union[SelectBase, TextClause, UpdateBase] 

776 dml_table: Optional[_DMLTableElement] = None 

777 

778 _has_orm_entities = False 

779 multi_row_eager_loaders = False 

780 eager_adding_joins = False 

781 compound_eager_adapter = None 

782 

783 extra_criteria_entities = util.EMPTY_DICT 

784 eager_joins = util.EMPTY_DICT 

785 

786 @classmethod 

787 def _create_orm_context( 

788 cls, 

789 statement: Union[Select, FromStatement], 

790 *, 

791 toplevel: bool, 

792 compiler: Optional[SQLCompiler], 

793 **kw: Any, 

794 ) -> _ORMFromStatementCompileState: 

795 statement_container = statement 

796 

797 assert isinstance(statement_container, FromStatement) 

798 

799 if compiler is not None and compiler.stack: 

800 raise sa_exc.CompileError( 

801 "The ORM FromStatement construct only supports being " 

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

803 "define how result rows should be returned." 

804 ) 

805 

806 self = cls.__new__(cls) 

807 self._primary_entity = None 

808 

809 self.use_legacy_query_style = ( 

810 statement_container._compile_options._use_legacy_query_style 

811 ) 

812 self.statement_container = self.select_statement = statement_container 

813 self.requested_statement = statement = statement_container.element 

814 

815 if statement.is_dml: 

816 self.dml_table = statement.table 

817 self.is_dml_returning = True 

818 

819 self._entities = [] 

820 self._polymorphic_adapters = {} 

821 

822 self.compile_options = statement_container._compile_options 

823 

824 if ( 

825 self.use_legacy_query_style 

826 and isinstance(statement, expression.SelectBase) 

827 and not statement._is_textual 

828 and not statement.is_dml 

829 and statement._label_style is LABEL_STYLE_NONE 

830 ): 

831 self.statement = statement.set_label_style( 

832 LABEL_STYLE_TABLENAME_PLUS_COL 

833 ) 

834 else: 

835 self.statement = statement 

836 

837 self._label_convention = self._column_naming_convention( 

838 ( 

839 statement._label_style 

840 if not statement._is_textual and not statement.is_dml 

841 else LABEL_STYLE_NONE 

842 ), 

843 self.use_legacy_query_style, 

844 ) 

845 

846 _QueryEntity.to_compile_state( 

847 self, 

848 statement_container._raw_columns, 

849 self._entities, 

850 is_current_entities=True, 

851 ) 

852 

853 self.current_path = statement_container._compile_options._current_path 

854 

855 self._init_global_attributes( 

856 statement_container, 

857 compiler, 

858 process_criteria_for_toplevel=False, 

859 toplevel=True, 

860 ) 

861 

862 if statement_container._with_options: 

863 for opt in statement_container._with_options: 

864 if opt._is_compile_state: 

865 opt.process_compile_state(self) 

866 

867 if statement_container._compile_state_funcs: 

868 for fn, key in statement_container._compile_state_funcs: 

869 fn(self) 

870 

871 self.primary_columns = [] 

872 self.secondary_columns = [] 

873 self.dedupe_columns = set() 

874 self.create_eager_joins = [] 

875 self._fallback_from_clauses = [] 

876 

877 self.order_by = None 

878 

879 if self.statement._is_text_clause: 

880 # AbstractTextClause (TextClause, TString) has no "column" 

881 # objects at all. for this case, we generate columns from our 

882 # _QueryEntity objects, then flip on all the 

883 # "please match no matter what" parameters. 

884 self.extra_criteria_entities = {} 

885 

886 for entity in self._entities: 

887 entity.setup_compile_state(self) 

888 

889 compiler._ordered_columns = compiler._textual_ordered_columns = ( 

890 False 

891 ) 

892 

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

894 # needed by test_query.py::TextTest 

895 compiler._loose_column_name_matching = True 

896 

897 for c in self.primary_columns: 

898 compiler.process( 

899 c, 

900 within_columns_clause=True, 

901 add_to_result_map=compiler._add_to_result_map, 

902 ) 

903 else: 

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

905 # have column objects already. After much 

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

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

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

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

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

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

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

913 self._from_obj_alias = ORMStatementAdapter( 

914 _TraceAdaptRole.ADAPT_FROM_STATEMENT, 

915 self.statement, 

916 adapt_on_names=statement_container._adapt_on_names, 

917 ) 

918 

919 return self 

920 

921 def _adapt_col_list(self, cols, current_adapter): 

922 return cols 

923 

924 def _get_current_adapter(self): 

925 return None 

926 

927 def setup_dml_returning_compile_state(self, dml_mapper): 

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

929 for RETURNING to return ORM objects and expressions 

930 

931 """ 

932 target_mapper = self.statement._propagate_attrs.get( 

933 "plugin_subject", None 

934 ) 

935 

936 if self.statement.is_insert: 

937 adapter = _DMLBulkInsertReturningColFilter( 

938 target_mapper, dml_mapper 

939 ) 

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

941 adapter = _DMLUpdateDeleteReturningColFilter( 

942 target_mapper, dml_mapper 

943 ) 

944 else: 

945 adapter = None 

946 

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

948 raise sa_exc.CompileError( 

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

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

951 ) 

952 

953 for entity in self._entities: 

954 entity.setup_dml_returning_compile_state(self, adapter) 

955 

956 

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

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

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

960 

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

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

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

964 

965 """ 

966 

967 __visit_name__ = "orm_from_statement" 

968 

969 _compile_options = _ORMFromStatementCompileState.default_compile_options 

970 

971 _compile_state_factory = _ORMFromStatementCompileState.create_for_statement 

972 

973 _for_update_arg = None 

974 

975 element: Union[ExecutableReturnsRows, TextClause] 

976 

977 _adapt_on_names: bool 

978 

979 _traverse_internals = [ 

980 ("_raw_columns", InternalTraversal.dp_clauseelement_list), 

981 ("element", InternalTraversal.dp_clauseelement), 

982 ] + ExecutableStatement._executable_traverse_internals 

983 

984 _cache_key_traversal = _traverse_internals + [ 

985 ("_compile_options", InternalTraversal.dp_has_cache_key) 

986 ] 

987 

988 is_from_statement = True 

989 

990 def __init__( 

991 self, 

992 entities: Iterable[_ColumnsClauseArgument[Any]], 

993 element: Union[ExecutableReturnsRows, TextClause], 

994 _adapt_on_names: bool = True, 

995 ): 

996 self._raw_columns = [ 

997 coercions.expect( 

998 roles.ColumnsClauseRole, 

999 ent, 

1000 apply_propagate_attrs=self, 

1001 post_inspect=True, 

1002 ) 

1003 for ent in util.to_list(entities) 

1004 ] 

1005 self.element = element 

1006 self.is_dml = element.is_dml 

1007 self.is_select = element.is_select 

1008 self.is_delete = element.is_delete 

1009 self.is_insert = element.is_insert 

1010 self.is_update = element.is_update 

1011 self._label_style = ( 

1012 element._label_style if is_select_base(element) else None 

1013 ) 

1014 self._adapt_on_names = _adapt_on_names 

1015 

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

1017 """provide a fixed _compiler_dispatch method. 

1018 

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

1020 ``@compiles`` extension. 

1021 

1022 """ 

1023 

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

1025 

1026 toplevel = not compiler.stack 

1027 

1028 if toplevel: 

1029 compiler.compile_state = compile_state 

1030 

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

1032 

1033 @property 

1034 def column_descriptions(self): 

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

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

1037 

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

1039 of this feature. 

1040 

1041 .. seealso:: 

1042 

1043 :ref:`queryguide_inspection` - ORM background 

1044 

1045 """ 

1046 meth = cast( 

1047 _ORMSelectCompileState, SelectState.get_plugin_class(self) 

1048 ).get_column_descriptions 

1049 return meth(self) 

1050 

1051 def _ensure_disambiguated_names(self): 

1052 return self 

1053 

1054 def get_children(self, **kw): 

1055 yield from itertools.chain.from_iterable( 

1056 element._from_objects for element in self._raw_columns 

1057 ) 

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

1059 

1060 @property 

1061 def _all_selected_columns(self): 

1062 return self.element._all_selected_columns 

1063 

1064 @property 

1065 def _return_defaults(self): 

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

1067 

1068 @property 

1069 def _returning(self): 

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

1071 

1072 @property 

1073 def _inline(self): 

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

1075 

1076 

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

1078class _CompoundSelectCompileState( 

1079 _AutoflushOnlyORMCompileState, CompoundSelectState 

1080): 

1081 pass 

1082 

1083 

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

1085class _ORMSelectCompileState(_ORMCompileState, SelectState): 

1086 _already_joined_edges = () 

1087 

1088 _memoized_entities = util.EMPTY_DICT 

1089 

1090 _from_obj_alias = None 

1091 _has_mapper_entities = False 

1092 

1093 _has_orm_entities = False 

1094 multi_row_eager_loaders = False 

1095 eager_adding_joins = False 

1096 compound_eager_adapter = None 

1097 

1098 correlate = None 

1099 correlate_except = None 

1100 _where_criteria = () 

1101 _having_criteria = () 

1102 

1103 @classmethod 

1104 def _create_orm_context( 

1105 cls, 

1106 statement: Union[Select, FromStatement], 

1107 *, 

1108 toplevel: bool, 

1109 compiler: Optional[SQLCompiler], 

1110 **kw: Any, 

1111 ) -> _ORMSelectCompileState: 

1112 

1113 self = cls.__new__(cls) 

1114 

1115 select_statement = statement 

1116 

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

1118 # have ORM level compile options. 

1119 statement._compile_options = cls.default_compile_options.safe_merge( 

1120 statement._compile_options 

1121 ) 

1122 

1123 if select_statement._execution_options: 

1124 # execution options should not impact the compilation of a 

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

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

1127 self.select_statement = select_statement._clone() 

1128 self.select_statement._execution_options = util.EMPTY_DICT 

1129 else: 

1130 self.select_statement = select_statement 

1131 

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

1133 self.for_statement = select_statement._compile_options._for_statement 

1134 

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

1136 self.use_legacy_query_style = ( 

1137 select_statement._compile_options._use_legacy_query_style 

1138 ) 

1139 

1140 self._entities = [] 

1141 self._primary_entity = None 

1142 self._polymorphic_adapters = {} 

1143 

1144 self.compile_options = select_statement._compile_options 

1145 

1146 if not toplevel: 

1147 # for subqueries, turn off eagerloads and set 

1148 # "render_for_subquery". 

1149 self.compile_options += { 

1150 "_enable_eagerloads": False, 

1151 "_render_for_subquery": True, 

1152 } 

1153 

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

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

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

1157 # for new style ORM selects too. 

1158 if ( 

1159 self.use_legacy_query_style 

1160 and self.select_statement._label_style is LABEL_STYLE_LEGACY_ORM 

1161 ): 

1162 if not self.for_statement: 

1163 self.label_style = LABEL_STYLE_TABLENAME_PLUS_COL 

1164 else: 

1165 self.label_style = LABEL_STYLE_DISAMBIGUATE_ONLY 

1166 else: 

1167 self.label_style = self.select_statement._label_style 

1168 

1169 if select_statement._memoized_select_entities: 

1170 self._memoized_entities = { 

1171 memoized_entities: _QueryEntity.to_compile_state( 

1172 self, 

1173 memoized_entities._raw_columns, 

1174 [], 

1175 is_current_entities=False, 

1176 ) 

1177 for memoized_entities in ( 

1178 select_statement._memoized_select_entities 

1179 ) 

1180 } 

1181 

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

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

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

1185 # in the columns clause 

1186 self._label_convention = self._column_naming_convention( 

1187 statement._label_style, self.use_legacy_query_style 

1188 ) 

1189 

1190 _QueryEntity.to_compile_state( 

1191 self, 

1192 select_statement._raw_columns, 

1193 self._entities, 

1194 is_current_entities=True, 

1195 ) 

1196 

1197 self.current_path = select_statement._compile_options._current_path 

1198 

1199 self.eager_order_by = () 

1200 

1201 self._init_global_attributes( 

1202 select_statement, 

1203 compiler, 

1204 toplevel=toplevel, 

1205 process_criteria_for_toplevel=False, 

1206 ) 

1207 

1208 if toplevel and ( 

1209 select_statement._with_options 

1210 or select_statement._memoized_select_entities 

1211 ): 

1212 for ( 

1213 memoized_entities 

1214 ) in select_statement._memoized_select_entities: 

1215 for opt in memoized_entities._with_options: 

1216 if opt._is_compile_state: 

1217 opt.process_compile_state_replaced_entities( 

1218 self, 

1219 [ 

1220 ent 

1221 for ent in self._memoized_entities[ 

1222 memoized_entities 

1223 ] 

1224 if isinstance(ent, _MapperEntity) 

1225 ], 

1226 ) 

1227 

1228 for opt in self.select_statement._with_options: 

1229 if opt._is_compile_state: 

1230 opt.process_compile_state(self) 

1231 

1232 # uncomment to print out the context.attributes structure 

1233 # after it's been set up above 

1234 # self._dump_option_struct() 

1235 

1236 if select_statement._compile_state_funcs: 

1237 for fn, key in select_statement._compile_state_funcs: 

1238 fn(self) 

1239 

1240 self.primary_columns = [] 

1241 self.secondary_columns = [] 

1242 self.dedupe_columns = set() 

1243 self.eager_joins = {} 

1244 self.extra_criteria_entities = {} 

1245 self.create_eager_joins = [] 

1246 self._fallback_from_clauses = [] 

1247 

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

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

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

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

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

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

1254 self.from_clauses = self._normalize_froms( 

1255 info.selectable for info in select_statement._from_obj 

1256 ) 

1257 

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

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

1260 # and _setup_for_generate into three or four logical sections 

1261 self._setup_for_generate() 

1262 

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

1264 return self 

1265 

1266 def _dump_option_struct(self): 

1267 print("\n---------------------------------------------------\n") 

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

1269 for key in self.attributes: 

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

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

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

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

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

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

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

1277 

1278 def _setup_for_generate(self): 

1279 query = self.select_statement 

1280 

1281 self.statement = None 

1282 self._join_entities = () 

1283 

1284 if self.compile_options._set_base_alias: 

1285 # legacy Query only 

1286 self._set_select_from_alias() 

1287 

1288 for memoized_entities in query._memoized_select_entities: 

1289 if memoized_entities._setup_joins: 

1290 self._join( 

1291 memoized_entities._setup_joins, 

1292 self._memoized_entities[memoized_entities], 

1293 ) 

1294 

1295 if query._setup_joins: 

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

1297 

1298 current_adapter = self._get_current_adapter() 

1299 

1300 if query._where_criteria: 

1301 self._where_criteria = query._where_criteria 

1302 

1303 if current_adapter: 

1304 self._where_criteria = tuple( 

1305 current_adapter(crit, True) 

1306 for crit in self._where_criteria 

1307 ) 

1308 

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

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

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

1312 self.order_by = ( 

1313 self._adapt_col_list(query._order_by_clauses, current_adapter) 

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

1315 else query._order_by_clauses 

1316 ) 

1317 

1318 if query._having_criteria: 

1319 self._having_criteria = tuple( 

1320 current_adapter(crit, True) if current_adapter else crit 

1321 for crit in query._having_criteria 

1322 ) 

1323 

1324 self.group_by = ( 

1325 self._adapt_col_list( 

1326 util.flatten_iterator(query._group_by_clauses), current_adapter 

1327 ) 

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

1329 else query._group_by_clauses or None 

1330 ) 

1331 

1332 if self.eager_order_by: 

1333 adapter = self.from_clauses[0]._target_adapter 

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

1335 

1336 if query._distinct_on: 

1337 self.distinct_on = self._adapt_col_list( 

1338 query._distinct_on, current_adapter 

1339 ) 

1340 else: 

1341 self.distinct_on = () 

1342 

1343 self.distinct = query._distinct 

1344 

1345 self.syntax_extensions = { 

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

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

1348 } 

1349 

1350 if query._correlate: 

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

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

1353 # tables. 

1354 self.correlate = tuple( 

1355 util.flatten_iterator( 

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

1357 for s in query._correlate 

1358 ) 

1359 ) 

1360 elif query._correlate_except is not None: 

1361 self.correlate_except = tuple( 

1362 util.flatten_iterator( 

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

1364 for s in query._correlate_except 

1365 ) 

1366 ) 

1367 elif not query._auto_correlate: 

1368 self.correlate = (None,) 

1369 

1370 # PART II 

1371 

1372 self._for_update_arg = query._for_update_arg 

1373 

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

1375 raise sa_exc.CompileError( 

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

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

1378 ) 

1379 for entity in self._entities: 

1380 entity.setup_compile_state(self) 

1381 

1382 for rec in self.create_eager_joins: 

1383 strategy = rec[0] 

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

1385 

1386 # else "load from discrete FROMs" mode, 

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

1388 

1389 if self.compile_options._enable_single_crit: 

1390 self._adjust_for_extra_criteria() 

1391 

1392 if not self.primary_columns: 

1393 if self.compile_options._only_load_props: 

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

1395 

1396 raise sa_exc.InvalidRequestError( 

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

1398 ) 

1399 

1400 if not self.from_clauses: 

1401 self.from_clauses = list(self._fallback_from_clauses) 

1402 

1403 if self.order_by is False: 

1404 self.order_by = None 

1405 

1406 if self._should_nest_selectable: 

1407 self.statement = self._compound_eager_statement() 

1408 else: 

1409 self.statement = self._simple_statement() 

1410 

1411 if self.for_statement: 

1412 ezero = self._mapper_zero() 

1413 if ezero is not None: 

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

1415 # thing 

1416 self.statement = self.statement._annotate( 

1417 {"deepentity": ezero} 

1418 ) 

1419 

1420 @classmethod 

1421 def _create_entities_collection(cls, query, legacy): 

1422 """Creates a partial ORMSelectCompileState that includes 

1423 the full collection of _MapperEntity and other _QueryEntity objects. 

1424 

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

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

1427 

1428 """ 

1429 self = cls.__new__(cls) 

1430 

1431 self._entities = [] 

1432 self._primary_entity = None 

1433 self._polymorphic_adapters = {} 

1434 

1435 self._label_convention = self._column_naming_convention( 

1436 query._label_style, legacy 

1437 ) 

1438 

1439 # entities will also set up polymorphic adapters for mappers 

1440 # that have with_polymorphic configured 

1441 _QueryEntity.to_compile_state( 

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

1443 ) 

1444 return self 

1445 

1446 @classmethod 

1447 def _get_filter_by_entities(cls, statement): 

1448 """Return all ORM entities for filter_by() searches. 

1449 

1450 the ORM version for Select is special vs. update/delete since it needs 

1451 to navigate along select.join() paths which have ORM specific 

1452 directives. 

1453 

1454 beyond that, it delivers other entities as the Mapper or Aliased 

1455 object rather than the Table or Alias, which mostly affects 

1456 how error messages regarding ambiguous entities or entity not 

1457 found are rendered; class-specific attributes like hybrid, 

1458 column_property() etc. work either way since 

1459 _entity_namespace_key_search_all() uses _entity_namespace(). 

1460 

1461 DML Update and Delete objects, even though they also have filter_by() 

1462 and also accept ORM objects, don't use this routine since they 

1463 typically just have a single table, and if they have multiple tables 

1464 it's only via WHERE clause, which interestingly do not maintain ORM 

1465 annotations when used (that is, (User.name == 

1466 'foo').left.table._annotations is empty; the ORMness of User.name is 

1467 lost in the expression construction process, since we don't annotate 

1468 (copy) Column objects with ORM entities the way we do for Table. 

1469 

1470 .. versionadded:: 2.1 

1471 """ 

1472 

1473 def _setup_join_targets(collection): 

1474 for (target, *_) in collection: 

1475 if isinstance(target, attributes.QueryableAttribute): 

1476 yield target.entity 

1477 elif "_no_filter_by" not in target._annotations: 

1478 yield target 

1479 

1480 entities = set(_setup_join_targets(statement._setup_joins)) 

1481 

1482 for memoized in statement._memoized_select_entities: 

1483 entities.update(_setup_join_targets(memoized._setup_joins)) 

1484 

1485 entities.update( 

1486 ( 

1487 from_obj._annotations["parententity"] 

1488 if "parententity" in from_obj._annotations 

1489 else from_obj 

1490 ) 

1491 for from_obj in statement._from_obj 

1492 if "_no_filter_by" not in from_obj._annotations 

1493 ) 

1494 

1495 for element in statement._raw_columns: 

1496 if "entity_namespace" in element._annotations: 

1497 ens = element._annotations["entity_namespace"] 

1498 entities.add(ens) 

1499 elif "_no_filter_by" not in element._annotations: 

1500 entities.update(element._from_objects) 

1501 

1502 return entities 

1503 

1504 @classmethod 

1505 def all_selected_columns(cls, statement): 

1506 for element in statement._raw_columns: 

1507 if ( 

1508 element.is_selectable 

1509 and "entity_namespace" in element._annotations 

1510 ): 

1511 ens = element._annotations["entity_namespace"] 

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

1513 yield from _select_iterables([element]) 

1514 else: 

1515 yield from _select_iterables(ens._all_column_expressions) 

1516 else: 

1517 yield from _select_iterables([element]) 

1518 

1519 @classmethod 

1520 def get_columns_clause_froms(cls, statement): 

1521 return cls._normalize_froms( 

1522 itertools.chain.from_iterable( 

1523 ( 

1524 element._from_objects 

1525 if "parententity" not in element._annotations 

1526 else [ 

1527 element._annotations[ 

1528 "parententity" 

1529 ].__clause_element__() 

1530 ] 

1531 ) 

1532 for element in statement._raw_columns 

1533 ) 

1534 ) 

1535 

1536 @classmethod 

1537 def from_statement(cls, statement, from_statement): 

1538 from_statement = coercions.expect( 

1539 roles.ReturnsRowsRole, 

1540 from_statement, 

1541 apply_propagate_attrs=statement, 

1542 ) 

1543 

1544 stmt = FromStatement(statement._raw_columns, from_statement) 

1545 

1546 stmt.__dict__.update( 

1547 _with_options=statement._with_options, 

1548 _compile_state_funcs=statement._compile_state_funcs, 

1549 _execution_options=statement._execution_options, 

1550 _propagate_attrs=statement._propagate_attrs, 

1551 ) 

1552 return stmt 

1553 

1554 def _set_select_from_alias(self): 

1555 """used only for legacy Query cases""" 

1556 

1557 query = self.select_statement # query 

1558 

1559 assert self.compile_options._set_base_alias 

1560 assert len(query._from_obj) == 1 

1561 

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

1563 if adapter: 

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

1565 self._from_obj_alias = adapter 

1566 

1567 def _get_select_from_alias_from_obj(self, from_obj): 

1568 """used only for legacy Query cases""" 

1569 

1570 info = from_obj 

1571 

1572 if "parententity" in info._annotations: 

1573 info = info._annotations["parententity"] 

1574 

1575 if hasattr(info, "mapper"): 

1576 if not info.is_aliased_class: 

1577 raise sa_exc.ArgumentError( 

1578 "A selectable (FromClause) instance is " 

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

1580 ) 

1581 else: 

1582 return info._adapter 

1583 

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

1585 equivs = self._all_equivs() 

1586 assert info is info.selectable 

1587 return ORMStatementAdapter( 

1588 _TraceAdaptRole.LEGACY_SELECT_FROM_ALIAS, 

1589 info.selectable, 

1590 equivalents=equivs, 

1591 ) 

1592 else: 

1593 return None 

1594 

1595 def _mapper_zero(self): 

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

1597 return self._entities[0].mapper 

1598 

1599 def _entity_zero(self): 

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

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

1602 entity if specified.""" 

1603 

1604 for ent in self.from_clauses: 

1605 if "parententity" in ent._annotations: 

1606 return ent._annotations["parententity"] 

1607 for qent in self._entities: 

1608 if qent.entity_zero: 

1609 return qent.entity_zero 

1610 

1611 return None 

1612 

1613 def _only_full_mapper_zero(self, methname): 

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

1615 raise sa_exc.InvalidRequestError( 

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

1617 "a single mapped class." % methname 

1618 ) 

1619 return self._primary_entity.entity_zero 

1620 

1621 def _only_entity_zero(self, rationale=None): 

1622 if len(self._entities) > 1: 

1623 raise sa_exc.InvalidRequestError( 

1624 rationale 

1625 or "This operation requires a Query " 

1626 "against a single mapper." 

1627 ) 

1628 return self._entity_zero() 

1629 

1630 def _all_equivs(self): 

1631 equivs = {} 

1632 

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

1634 for ent in [ 

1635 ent 

1636 for ent in memoized_entities 

1637 if isinstance(ent, _MapperEntity) 

1638 ]: 

1639 equivs.update(ent.mapper._equivalent_columns) 

1640 

1641 for ent in [ 

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

1643 ]: 

1644 equivs.update(ent.mapper._equivalent_columns) 

1645 return equivs 

1646 

1647 def _compound_eager_statement(self): 

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

1649 # wrap the query inside a select, 

1650 # then append eager joins onto that 

1651 

1652 if self.order_by: 

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

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

1655 # elements are converted into label references. For the 

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

1657 # the original expressions outside of the label references 

1658 # in order to have them render. 

1659 unwrapped_order_by = [ 

1660 ( 

1661 elem.element 

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

1663 else elem 

1664 ) 

1665 for elem in self.order_by 

1666 ] 

1667 

1668 order_by_col_expr = sql_util.expand_column_list_from_order_by( 

1669 self.primary_columns, unwrapped_order_by 

1670 ) 

1671 else: 

1672 order_by_col_expr = [] 

1673 unwrapped_order_by = None 

1674 

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

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

1677 inner = self._select_statement( 

1678 self.primary_columns 

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

1680 self.from_clauses, 

1681 self._where_criteria, 

1682 self._having_criteria, 

1683 self.label_style, 

1684 self.order_by, 

1685 for_update=self._for_update_arg, 

1686 hints=self.select_statement._hints, 

1687 statement_hints=self.select_statement._statement_hints, 

1688 correlate=self.correlate, 

1689 correlate_except=self.correlate_except, 

1690 **self._select_args, 

1691 ) 

1692 

1693 inner = inner.alias() 

1694 

1695 equivs = self._all_equivs() 

1696 

1697 self.compound_eager_adapter = ORMStatementAdapter( 

1698 _TraceAdaptRole.COMPOUND_EAGER_STATEMENT, inner, equivalents=equivs 

1699 ) 

1700 

1701 statement = future.select( 

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

1703 ) 

1704 statement._label_style = self.label_style 

1705 

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

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

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

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

1710 if ( 

1711 self._for_update_arg is not None 

1712 and self._for_update_arg.of is None 

1713 ): 

1714 statement._for_update_arg = self._for_update_arg 

1715 

1716 from_clause = inner 

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

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

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

1720 # the join should be 

1721 from_clause = sql_util.splice_joins( 

1722 from_clause, eager_join, eager_join.stop_on 

1723 ) 

1724 

1725 statement.select_from.non_generative(statement, from_clause) 

1726 

1727 if unwrapped_order_by: 

1728 statement.order_by.non_generative( 

1729 statement, 

1730 *self.compound_eager_adapter.copy_and_process( 

1731 unwrapped_order_by 

1732 ), 

1733 ) 

1734 

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

1736 return statement 

1737 

1738 def _simple_statement(self): 

1739 statement = self._select_statement( 

1740 self.primary_columns + self.secondary_columns, 

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

1742 self._where_criteria, 

1743 self._having_criteria, 

1744 self.label_style, 

1745 self.order_by, 

1746 for_update=self._for_update_arg, 

1747 hints=self.select_statement._hints, 

1748 statement_hints=self.select_statement._statement_hints, 

1749 correlate=self.correlate, 

1750 correlate_except=self.correlate_except, 

1751 **self._select_args, 

1752 ) 

1753 

1754 if self.eager_order_by: 

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

1756 return statement 

1757 

1758 def _select_statement( 

1759 self, 

1760 raw_columns, 

1761 from_obj, 

1762 where_criteria, 

1763 having_criteria, 

1764 label_style, 

1765 order_by, 

1766 for_update, 

1767 hints, 

1768 statement_hints, 

1769 correlate, 

1770 correlate_except, 

1771 limit_clause, 

1772 offset_clause, 

1773 fetch_clause, 

1774 fetch_clause_options, 

1775 distinct, 

1776 distinct_on, 

1777 prefixes, 

1778 suffixes, 

1779 group_by, 

1780 independent_ctes, 

1781 independent_ctes_opts, 

1782 syntax_extensions, 

1783 ): 

1784 statement = Select._create_raw_select( 

1785 _raw_columns=raw_columns, 

1786 _from_obj=from_obj, 

1787 _label_style=label_style, 

1788 ) 

1789 

1790 if where_criteria: 

1791 statement._where_criteria = where_criteria 

1792 if having_criteria: 

1793 statement._having_criteria = having_criteria 

1794 

1795 if order_by: 

1796 statement._order_by_clauses += tuple(order_by) 

1797 

1798 if distinct_on: 

1799 statement._distinct = True 

1800 statement._distinct_on = distinct_on 

1801 elif distinct: 

1802 statement._distinct = True 

1803 

1804 if group_by: 

1805 statement._group_by_clauses += tuple(group_by) 

1806 

1807 statement._limit_clause = limit_clause 

1808 statement._offset_clause = offset_clause 

1809 statement._fetch_clause = fetch_clause 

1810 statement._fetch_clause_options = fetch_clause_options 

1811 statement._independent_ctes = independent_ctes 

1812 statement._independent_ctes_opts = independent_ctes_opts 

1813 if syntax_extensions: 

1814 statement._set_syntax_extensions(**syntax_extensions) 

1815 

1816 if prefixes: 

1817 statement._prefixes = prefixes 

1818 

1819 if suffixes: 

1820 statement._suffixes = suffixes 

1821 

1822 statement._for_update_arg = for_update 

1823 

1824 if hints: 

1825 statement._hints = hints 

1826 if statement_hints: 

1827 statement._statement_hints = statement_hints 

1828 

1829 if correlate: 

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

1831 

1832 if correlate_except is not None: 

1833 statement.correlate_except.non_generative( 

1834 statement, *correlate_except 

1835 ) 

1836 

1837 return statement 

1838 

1839 def _adapt_polymorphic_element(self, element): 

1840 if "parententity" in element._annotations: 

1841 search = element._annotations["parententity"] 

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

1843 if alias: 

1844 return alias.adapt_clause(element) 

1845 

1846 if isinstance(element, expression.FromClause): 

1847 search = element 

1848 elif hasattr(element, "table"): 

1849 search = element.table 

1850 else: 

1851 return None 

1852 

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

1854 if alias: 

1855 return alias.adapt_clause(element) 

1856 

1857 def _adapt_col_list(self, cols, current_adapter): 

1858 if current_adapter: 

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

1860 else: 

1861 return cols 

1862 

1863 def _get_current_adapter(self): 

1864 adapters = [] 

1865 

1866 if self._from_obj_alias: 

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

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

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

1870 # select_entity_from() 

1871 # 

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

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

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

1875 # to all SQL constructs. 

1876 adapters.append( 

1877 self._from_obj_alias.replace, 

1878 ) 

1879 

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

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

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

1883 if self._polymorphic_adapters: 

1884 adapters.append(self._adapt_polymorphic_element) 

1885 

1886 if not adapters: 

1887 return None 

1888 

1889 def _adapt_clause(clause, as_filter): 

1890 # do we adapt all expression elements or only those 

1891 # tagged as 'ORM' constructs ? 

1892 

1893 def replace(elem): 

1894 for adapter in adapters: 

1895 e = adapter(elem) 

1896 if e is not None: 

1897 return e 

1898 

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

1900 

1901 return _adapt_clause 

1902 

1903 def _join(self, args, entities_collection): 

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

1905 isouter = flags["isouter"] 

1906 full = flags["full"] 

1907 

1908 right = inspect(right) 

1909 if onclause is not None: 

1910 onclause = inspect(onclause) 

1911 

1912 if isinstance(right, interfaces.PropComparator): 

1913 if onclause is not None: 

1914 raise sa_exc.InvalidRequestError( 

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

1916 "to a relationship path as a target" 

1917 ) 

1918 

1919 onclause = right 

1920 right = None 

1921 elif "parententity" in right._annotations: 

1922 right = right._annotations["parententity"] 

1923 

1924 if onclause is None: 

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

1926 raise sa_exc.ArgumentError( 

1927 "Expected mapped entity or " 

1928 "selectable/table as join target" 

1929 ) 

1930 

1931 if isinstance(onclause, interfaces.PropComparator): 

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

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

1934 

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

1936 

1937 if right is None: 

1938 if of_type: 

1939 right = of_type 

1940 else: 

1941 right = onclause.property 

1942 

1943 try: 

1944 right = right.entity 

1945 except AttributeError as err: 

1946 raise sa_exc.ArgumentError( 

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

1948 "mapped entity" % right 

1949 ) from err 

1950 

1951 left = onclause._parententity 

1952 

1953 prop = onclause.property 

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

1955 onclause = prop 

1956 

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

1958 # case. 

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

1960 continue 

1961 

1962 if from_ is not None: 

1963 if ( 

1964 from_ is not left 

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

1966 is not left 

1967 ): 

1968 raise sa_exc.InvalidRequestError( 

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

1970 "of relationship attribute %s" 

1971 % ( 

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

1973 onclause, 

1974 ) 

1975 ) 

1976 elif from_ is not None: 

1977 prop = None 

1978 left = from_ 

1979 else: 

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

1981 # what the effective "left" side is 

1982 prop = left = None 

1983 

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

1985 # ORMJoin to add to our _from_obj tuple 

1986 self._join_left_to_right( 

1987 entities_collection, 

1988 left, 

1989 right, 

1990 onclause, 

1991 prop, 

1992 isouter, 

1993 full, 

1994 ) 

1995 

1996 def _join_left_to_right( 

1997 self, 

1998 entities_collection, 

1999 left, 

2000 right, 

2001 onclause, 

2002 prop, 

2003 outerjoin, 

2004 full, 

2005 ): 

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

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

2008 our _from_obj list (or augment an existing one) 

2009 

2010 """ 

2011 

2012 explicit_left = left 

2013 if left is None: 

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

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

2016 # entities 

2017 assert prop is None 

2018 ( 

2019 left, 

2020 replace_from_obj_index, 

2021 use_entity_index, 

2022 ) = self._join_determine_implicit_left_side( 

2023 entities_collection, left, right, onclause 

2024 ) 

2025 else: 

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

2027 # Determine where in our 

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

2029 # existing entity it corresponds to. 

2030 ( 

2031 replace_from_obj_index, 

2032 use_entity_index, 

2033 ) = self._join_place_explicit_left_side(entities_collection, left) 

2034 

2035 if left is right: 

2036 raise sa_exc.InvalidRequestError( 

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

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

2039 ) 

2040 

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

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

2043 # get back the new effective "right" side 

2044 r_info, right, onclause = self._join_check_and_adapt_right_side( 

2045 left, right, onclause, prop 

2046 ) 

2047 

2048 if not r_info.is_selectable: 

2049 extra_criteria = self._get_extra_criteria(r_info) 

2050 else: 

2051 extra_criteria = () 

2052 

2053 if replace_from_obj_index is not None: 

2054 # splice into an existing element in the 

2055 # self._from_obj list 

2056 left_clause = self.from_clauses[replace_from_obj_index] 

2057 

2058 if explicit_left is not None and onclause is None: 

2059 onclause = _ORMJoin._join_condition(explicit_left, right) 

2060 

2061 self.from_clauses = ( 

2062 self.from_clauses[:replace_from_obj_index] 

2063 + [ 

2064 _ORMJoin( 

2065 left_clause, 

2066 right, 

2067 onclause, 

2068 isouter=outerjoin, 

2069 full=full, 

2070 _extra_criteria=extra_criteria, 

2071 ) 

2072 ] 

2073 + self.from_clauses[replace_from_obj_index + 1 :] 

2074 ) 

2075 else: 

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

2077 if use_entity_index is not None: 

2078 # make use of _MapperEntity selectable, which is usually 

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

2080 # might be distinct 

2081 assert isinstance( 

2082 entities_collection[use_entity_index], _MapperEntity 

2083 ) 

2084 left_clause = entities_collection[use_entity_index].selectable 

2085 else: 

2086 left_clause = left 

2087 

2088 self.from_clauses = self.from_clauses + [ 

2089 _ORMJoin( 

2090 left_clause, 

2091 r_info, 

2092 onclause, 

2093 isouter=outerjoin, 

2094 full=full, 

2095 _extra_criteria=extra_criteria, 

2096 ) 

2097 ] 

2098 

2099 def _join_determine_implicit_left_side( 

2100 self, entities_collection, left, right, onclause 

2101 ): 

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

2103 determine if an existing FROM or entity in this query 

2104 can serve as the left hand side. 

2105 

2106 """ 

2107 

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

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

2110 # 

2111 # join(RightEntity) 

2112 # 

2113 # or 

2114 # 

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

2116 # 

2117 

2118 r_info = inspect(right) 

2119 

2120 replace_from_obj_index = use_entity_index = None 

2121 

2122 if self.from_clauses: 

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

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

2125 

2126 indexes = sql_util.find_left_clause_to_join_from( 

2127 self.from_clauses, r_info.selectable, onclause 

2128 ) 

2129 

2130 if len(indexes) == 1: 

2131 replace_from_obj_index = indexes[0] 

2132 left = self.from_clauses[replace_from_obj_index] 

2133 elif len(indexes) > 1: 

2134 raise sa_exc.InvalidRequestError( 

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

2136 "from, there are multiple FROMS which can " 

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

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

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

2140 "to help resolve the ambiguity." 

2141 ) 

2142 else: 

2143 raise sa_exc.InvalidRequestError( 

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

2145 "Please use the .select_from() " 

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

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

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

2149 ) 

2150 

2151 elif entities_collection: 

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

2153 # come from our list of entities. 

2154 

2155 potential = {} 

2156 for entity_index, ent in enumerate(entities_collection): 

2157 entity = ent.entity_zero_or_selectable 

2158 if entity is None: 

2159 continue 

2160 ent_info = inspect(entity) 

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

2162 continue 

2163 

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

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

2166 # against a series of columns from the same selectable 

2167 if isinstance(ent, _MapperEntity): 

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

2169 else: 

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

2171 

2172 all_clauses = list(potential.keys()) 

2173 indexes = sql_util.find_left_clause_to_join_from( 

2174 all_clauses, r_info.selectable, onclause 

2175 ) 

2176 

2177 if len(indexes) == 1: 

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

2179 elif len(indexes) > 1: 

2180 raise sa_exc.InvalidRequestError( 

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

2182 "from, there are multiple FROMS which can " 

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

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

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

2186 "to help resolve the ambiguity." 

2187 ) 

2188 else: 

2189 raise sa_exc.InvalidRequestError( 

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

2191 "Please use the .select_from() " 

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

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

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

2195 ) 

2196 else: 

2197 raise sa_exc.InvalidRequestError( 

2198 "No entities to join from; please use " 

2199 "select_from() to establish the left " 

2200 "entity/selectable of this join" 

2201 ) 

2202 

2203 return left, replace_from_obj_index, use_entity_index 

2204 

2205 def _join_place_explicit_left_side(self, entities_collection, left): 

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

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

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

2209 existing entities. 

2210 

2211 """ 

2212 

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

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

2215 # Relationship was given, e.g.: 

2216 # 

2217 # join(RightEntity, LeftEntity.right) 

2218 # 

2219 # or 

2220 # 

2221 # join(LeftEntity.right) 

2222 # 

2223 # as well as string forms: 

2224 # 

2225 # join(RightEntity, "right") 

2226 # 

2227 # etc. 

2228 # 

2229 

2230 replace_from_obj_index = use_entity_index = None 

2231 

2232 l_info = inspect(left) 

2233 if self.from_clauses: 

2234 indexes = sql_util.find_left_clause_that_matches_given( 

2235 self.from_clauses, l_info.selectable 

2236 ) 

2237 

2238 if len(indexes) > 1: 

2239 raise sa_exc.InvalidRequestError( 

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

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

2242 "ON clause." 

2243 ) 

2244 

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

2246 # an existing FROM in the self._from_obj tuple 

2247 if indexes: 

2248 replace_from_obj_index = indexes[0] 

2249 

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

2251 # self._from_obj tuple 

2252 

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

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

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

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

2257 if ( 

2258 replace_from_obj_index is None 

2259 and entities_collection 

2260 and hasattr(l_info, "mapper") 

2261 ): 

2262 for idx, ent in enumerate(entities_collection): 

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

2264 # matching? 

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

2266 use_entity_index = idx 

2267 break 

2268 

2269 return replace_from_obj_index, use_entity_index 

2270 

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

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

2273 according to polymorphic mapping translations, aliasing on the query 

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

2275 overlapping tables. 

2276 

2277 """ 

2278 

2279 l_info = inspect(left) 

2280 r_info = inspect(right) 

2281 

2282 overlap = False 

2283 

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

2285 # if the target is a joined inheritance mapping, 

2286 # be more liberal about auto-aliasing. 

2287 if right_mapper and ( 

2288 right_mapper.with_polymorphic 

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

2290 ): 

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

2292 if sql_util.selectables_overlap( 

2293 l_info.selectable, from_obj 

2294 ) and sql_util.selectables_overlap( 

2295 from_obj, r_info.selectable 

2296 ): 

2297 overlap = True 

2298 break 

2299 

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

2301 raise sa_exc.InvalidRequestError( 

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

2303 % l_info.selectable 

2304 ) 

2305 

2306 right_mapper, right_selectable, right_is_aliased = ( 

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

2308 r_info.selectable, 

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

2310 ) 

2311 

2312 if ( 

2313 right_mapper 

2314 and prop 

2315 and not right_mapper.common_parent(prop.mapper) 

2316 ): 

2317 raise sa_exc.InvalidRequestError( 

2318 "Join target %s does not correspond to " 

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

2320 ) 

2321 

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

2323 # purposes at the moment 

2324 if hasattr(r_info, "mapper"): 

2325 self._join_entities += (r_info,) 

2326 

2327 need_adapter = False 

2328 

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

2330 if r_info.is_clause_element: 

2331 if prop: 

2332 right_mapper = prop.mapper 

2333 

2334 if right_selectable._is_lateral: 

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

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

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

2338 current_adapter = self._get_current_adapter() 

2339 if current_adapter is not None: 

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

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

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

2343 right = current_adapter(right, True) 

2344 

2345 elif prop: 

2346 # joining to selectable with a mapper property given 

2347 # as the ON clause 

2348 

2349 if not right_selectable.is_derived_from( 

2350 right_mapper.persist_selectable 

2351 ): 

2352 raise sa_exc.InvalidRequestError( 

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

2354 % ( 

2355 right_selectable.description, 

2356 right_mapper.persist_selectable.description, 

2357 ) 

2358 ) 

2359 

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

2361 # turn it into an alias(). 

2362 if isinstance(right_selectable, expression.SelectBase): 

2363 right_selectable = coercions.expect( 

2364 roles.FromClauseRole, right_selectable 

2365 ) 

2366 need_adapter = True 

2367 

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

2369 right = AliasedClass(right_mapper, right_selectable) 

2370 

2371 util.warn_deprecated( 

2372 "An alias is being generated automatically against " 

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

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

2375 "Use the aliased() " 

2376 "construct explicitly, see the linked example." 

2377 % right_mapper, 

2378 "1.4", 

2379 code="xaj1", 

2380 ) 

2381 

2382 # test for overlap: 

2383 # orm/inheritance/relationships.py 

2384 # SelfReferentialM2MTest 

2385 aliased_entity = right_mapper and not right_is_aliased and overlap 

2386 

2387 if not need_adapter and aliased_entity: 

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

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

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

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

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

2393 # aliasing is desirable. 

2394 right = AliasedClass(right, flat=True) 

2395 need_adapter = True 

2396 

2397 util.warn( 

2398 "An alias is being generated automatically against " 

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

2400 "legacy pattern which may be " 

2401 "deprecated in a later release. Use the " 

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

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

2404 code="xaj2", 

2405 ) 

2406 

2407 if need_adapter: 

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

2409 # a warning has been emitted. 

2410 assert right_mapper 

2411 

2412 adapter = ORMAdapter( 

2413 _TraceAdaptRole.DEPRECATED_JOIN_ADAPT_RIGHT_SIDE, 

2414 inspect(right), 

2415 equivalents=right_mapper._equivalent_columns, 

2416 ) 

2417 

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

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

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

2421 # set are also adapted. 

2422 self._mapper_loads_polymorphically_with(right_mapper, adapter) 

2423 elif ( 

2424 not r_info.is_clause_element 

2425 and not right_is_aliased 

2426 and right_mapper._has_aliased_polymorphic_fromclause 

2427 ): 

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

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

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

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

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

2433 # strictly necessary. 

2434 # see test/orm/test_core_compilation.py 

2435 # ::RelNaturalAliasedJoinsTest::test_straight 

2436 # and similar 

2437 self._mapper_loads_polymorphically_with( 

2438 right_mapper, 

2439 ORMAdapter( 

2440 _TraceAdaptRole.WITH_POLYMORPHIC_ADAPTER_RIGHT_JOIN, 

2441 right_mapper, 

2442 selectable=right_mapper.selectable, 

2443 equivalents=right_mapper._equivalent_columns, 

2444 ), 

2445 ) 

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

2447 # adapters that are in place right now 

2448 if isinstance(onclause, expression.ClauseElement): 

2449 current_adapter = self._get_current_adapter() 

2450 if current_adapter: 

2451 onclause = current_adapter(onclause, True) 

2452 

2453 # if joining on a MapperProperty path, 

2454 # track the path to prevent redundant joins 

2455 if prop: 

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

2457 

2458 return inspect(right), right, onclause 

2459 

2460 @property 

2461 def _select_args(self): 

2462 return { 

2463 "limit_clause": self.select_statement._limit_clause, 

2464 "offset_clause": self.select_statement._offset_clause, 

2465 "distinct": self.distinct, 

2466 "distinct_on": self.distinct_on, 

2467 "prefixes": self.select_statement._prefixes, 

2468 "suffixes": self.select_statement._suffixes, 

2469 "group_by": self.group_by or None, 

2470 "fetch_clause": self.select_statement._fetch_clause, 

2471 "fetch_clause_options": ( 

2472 self.select_statement._fetch_clause_options 

2473 ), 

2474 "independent_ctes": self.select_statement._independent_ctes, 

2475 "independent_ctes_opts": ( 

2476 self.select_statement._independent_ctes_opts 

2477 ), 

2478 "syntax_extensions": self.syntax_extensions, 

2479 } 

2480 

2481 @property 

2482 def _should_nest_selectable(self): 

2483 kwargs = self._select_args 

2484 

2485 if not self.eager_adding_joins: 

2486 return False 

2487 

2488 return ( 

2489 ( 

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

2491 and self.multi_row_eager_loaders 

2492 ) 

2493 or ( 

2494 kwargs.get("offset_clause") is not None 

2495 and self.multi_row_eager_loaders 

2496 ) 

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

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

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

2500 ) 

2501 

2502 def _get_extra_criteria(self, ext_info): 

2503 if ( 

2504 "additional_entity_criteria", 

2505 ext_info.mapper, 

2506 ) in self.global_attributes: 

2507 return tuple( 

2508 ae._resolve_where_criteria(ext_info) 

2509 for ae in self.global_attributes[ 

2510 ("additional_entity_criteria", ext_info.mapper) 

2511 ] 

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

2513 and ae._should_include(self) 

2514 ) 

2515 else: 

2516 return () 

2517 

2518 def _adjust_for_extra_criteria(self): 

2519 """Apply extra criteria filtering. 

2520 

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

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

2523 add criterion to the WHERE 

2524 clause of the given QueryContext such that only the appropriate 

2525 subtypes are selected from the total results. 

2526 

2527 Additionally, add WHERE criteria originating from LoaderCriteriaOptions 

2528 associated with the global context. 

2529 

2530 """ 

2531 

2532 for fromclause in self.from_clauses: 

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

2534 

2535 if ( 

2536 ext_info 

2537 and ( 

2538 ext_info.mapper._single_table_criterion is not None 

2539 or ("additional_entity_criteria", ext_info.mapper) 

2540 in self.global_attributes 

2541 ) 

2542 and ext_info not in self.extra_criteria_entities 

2543 ): 

2544 self.extra_criteria_entities[ext_info] = ( 

2545 ext_info, 

2546 ext_info._adapter if ext_info.is_aliased_class else None, 

2547 ) 

2548 

2549 _where_criteria_to_add = () 

2550 

2551 merged_single_crit = collections.defaultdict( 

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

2553 ) 

2554 

2555 for ext_info, adapter in util.OrderedSet( 

2556 self.extra_criteria_entities.values() 

2557 ): 

2558 if ext_info in self._join_entities: 

2559 continue 

2560 

2561 # assemble single table inheritance criteria. 

2562 if ( 

2563 ext_info.is_aliased_class 

2564 and ext_info._base_alias()._is_with_polymorphic 

2565 ): 

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

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

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

2569 # of joined inheritance. 

2570 hierarchy_root = ext_info._base_alias() 

2571 else: 

2572 hierarchy_root = ext_info 

2573 

2574 single_crit_component = ( 

2575 hierarchy_root.mapper._single_table_criteria_component 

2576 ) 

2577 

2578 if single_crit_component is not None: 

2579 polymorphic_on, criteria = single_crit_component 

2580 

2581 polymorphic_on = polymorphic_on._annotate( 

2582 { 

2583 "parententity": hierarchy_root, 

2584 "parentmapper": hierarchy_root.mapper, 

2585 } 

2586 ) 

2587 

2588 list_of_single_crits, adapters = merged_single_crit[ 

2589 (hierarchy_root, polymorphic_on) 

2590 ] 

2591 list_of_single_crits.update(criteria) 

2592 if adapter: 

2593 adapters.add(adapter) 

2594 

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

2596 # with_loader_criteria() options 

2597 if not self.compile_options._for_refresh_state: 

2598 additional_entity_criteria = self._get_extra_criteria(ext_info) 

2599 _where_criteria_to_add += tuple( 

2600 adapter.traverse(crit) if adapter else crit 

2601 for crit in additional_entity_criteria 

2602 ) 

2603 

2604 # merge together single table inheritance criteria keyed to 

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

2606 for (ext_info, polymorphic_on), ( 

2607 merged_crit, 

2608 adapters, 

2609 ) in merged_single_crit.items(): 

2610 new_crit = polymorphic_on.in_(merged_crit) 

2611 for adapter in adapters: 

2612 new_crit = adapter.traverse(new_crit) 

2613 _where_criteria_to_add += (new_crit,) 

2614 

2615 current_adapter = self._get_current_adapter() 

2616 if current_adapter: 

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

2618 # have one, and concatenate to final WHERE criteria 

2619 for crit in _where_criteria_to_add: 

2620 crit = current_adapter(crit, False) 

2621 self._where_criteria += (crit,) 

2622 else: 

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

2624 self._where_criteria += _where_criteria_to_add 

2625 

2626 

2627def _column_descriptions( 

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

2629 compile_state: Optional[_ORMSelectCompileState] = None, 

2630 legacy: bool = False, 

2631) -> List[ORMColumnDescription]: 

2632 if compile_state is None: 

2633 compile_state = _ORMSelectCompileState._create_entities_collection( 

2634 query_or_select_stmt, legacy=legacy 

2635 ) 

2636 ctx = compile_state 

2637 d = [ 

2638 { 

2639 "name": ent._label_name, 

2640 "type": ent.type, 

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

2642 "expr": ent.expr, 

2643 "entity": ( 

2644 getattr(insp_ent, "entity", None) 

2645 if ent.entity_zero is not None 

2646 and not insp_ent.is_clause_element 

2647 else None 

2648 ), 

2649 } 

2650 for ent, insp_ent in [ 

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

2652 ] 

2653 ] 

2654 return d 

2655 

2656 

2657def _legacy_filter_by_entity_zero( 

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

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

2660 self = query_or_augmented_select 

2661 if self._setup_joins: 

2662 _last_joined_entity = self._last_joined_entity 

2663 if _last_joined_entity is not None: 

2664 return _last_joined_entity 

2665 

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

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

2668 

2669 return _entity_from_pre_ent_zero(self) 

2670 

2671 

2672def _entity_from_pre_ent_zero( 

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

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

2675 self = query_or_augmented_select 

2676 if not self._raw_columns: 

2677 return None 

2678 

2679 ent = self._raw_columns[0] 

2680 

2681 if "parententity" in ent._annotations: 

2682 return ent._annotations["parententity"] 

2683 elif isinstance(ent, ORMColumnsClauseRole): 

2684 return ent.entity 

2685 elif "bundle" in ent._annotations: 

2686 return ent._annotations["bundle"] 

2687 else: 

2688 return ent 

2689 

2690 

2691def _determine_last_joined_entity( 

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

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

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

2695 if not setup_joins: 

2696 return None 

2697 

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

2699 

2700 if isinstance( 

2701 target, 

2702 attributes.QueryableAttribute, 

2703 ): 

2704 return target.entity 

2705 else: 

2706 return target 

2707 

2708 

2709class _QueryEntity: 

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

2711 

2712 __slots__ = () 

2713 

2714 supports_single_entity: bool 

2715 

2716 _non_hashable_value = False 

2717 _null_column_type = False 

2718 use_id_for_hash = False 

2719 

2720 _label_name: Optional[str] 

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

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

2723 entity_zero: Optional[_InternalEntityType] 

2724 

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

2726 raise NotImplementedError() 

2727 

2728 def setup_dml_returning_compile_state( 

2729 self, 

2730 compile_state: _ORMCompileState, 

2731 adapter: Optional[_DMLReturningColFilter], 

2732 ) -> None: 

2733 raise NotImplementedError() 

2734 

2735 def row_processor(self, context, result): 

2736 raise NotImplementedError() 

2737 

2738 @classmethod 

2739 def to_compile_state( 

2740 cls, compile_state, entities, entities_collection, is_current_entities 

2741 ): 

2742 for idx, entity in enumerate(entities): 

2743 if entity._is_lambda_element: 

2744 if entity._is_sequence: 

2745 cls.to_compile_state( 

2746 compile_state, 

2747 entity._resolved, 

2748 entities_collection, 

2749 is_current_entities, 

2750 ) 

2751 continue 

2752 else: 

2753 entity = entity._resolved 

2754 

2755 if entity.is_clause_element: 

2756 if entity.is_selectable: 

2757 if "parententity" in entity._annotations: 

2758 _MapperEntity( 

2759 compile_state, 

2760 entity, 

2761 entities_collection, 

2762 is_current_entities, 

2763 ) 

2764 else: 

2765 _ColumnEntity._for_columns( 

2766 compile_state, 

2767 entity._select_iterable, 

2768 entities_collection, 

2769 idx, 

2770 is_current_entities, 

2771 ) 

2772 else: 

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

2774 _BundleEntity( 

2775 compile_state, 

2776 entity, 

2777 entities_collection, 

2778 is_current_entities, 

2779 ) 

2780 elif entity._is_clause_list: 

2781 # this is legacy only - test_composites.py 

2782 # test_query_cols_legacy 

2783 _ColumnEntity._for_columns( 

2784 compile_state, 

2785 entity._select_iterable, 

2786 entities_collection, 

2787 idx, 

2788 is_current_entities, 

2789 ) 

2790 else: 

2791 _ColumnEntity._for_columns( 

2792 compile_state, 

2793 [entity], 

2794 entities_collection, 

2795 idx, 

2796 is_current_entities, 

2797 ) 

2798 elif entity.is_bundle: 

2799 _BundleEntity(compile_state, entity, entities_collection) 

2800 

2801 return entities_collection 

2802 

2803 

2804class _MapperEntity(_QueryEntity): 

2805 """mapper/class/AliasedClass entity""" 

2806 

2807 __slots__ = ( 

2808 "expr", 

2809 "mapper", 

2810 "entity_zero", 

2811 "is_aliased_class", 

2812 "path", 

2813 "_extra_entities", 

2814 "_label_name", 

2815 "_with_polymorphic_mappers", 

2816 "selectable", 

2817 "_polymorphic_discriminator", 

2818 ) 

2819 

2820 expr: _InternalEntityType 

2821 mapper: Mapper[Any] 

2822 entity_zero: _InternalEntityType 

2823 is_aliased_class: bool 

2824 path: PathRegistry 

2825 _label_name: str 

2826 

2827 def __init__( 

2828 self, compile_state, entity, entities_collection, is_current_entities 

2829 ): 

2830 entities_collection.append(self) 

2831 if is_current_entities: 

2832 if compile_state._primary_entity is None: 

2833 compile_state._primary_entity = self 

2834 compile_state._has_mapper_entities = True 

2835 compile_state._has_orm_entities = True 

2836 

2837 entity = entity._annotations["parententity"] 

2838 entity._post_inspect 

2839 ext_info = self.entity_zero = entity 

2840 entity = ext_info.entity 

2841 

2842 self.expr = entity 

2843 self.mapper = mapper = ext_info.mapper 

2844 

2845 self._extra_entities = (self.expr,) 

2846 

2847 if ext_info.is_aliased_class: 

2848 self._label_name = ext_info.name 

2849 else: 

2850 self._label_name = mapper.class_.__name__ 

2851 

2852 self.is_aliased_class = ext_info.is_aliased_class 

2853 self.path = ext_info._path_registry 

2854 

2855 self.selectable = ext_info.selectable 

2856 self._with_polymorphic_mappers = ext_info.with_polymorphic_mappers 

2857 self._polymorphic_discriminator = ext_info.polymorphic_on 

2858 

2859 if mapper._should_select_with_poly_adapter: 

2860 compile_state._create_with_polymorphic_adapter( 

2861 ext_info, self.selectable 

2862 ) 

2863 

2864 supports_single_entity = True 

2865 

2866 _non_hashable_value = True 

2867 use_id_for_hash = True 

2868 

2869 @property 

2870 def type(self): 

2871 return self.mapper.class_ 

2872 

2873 @property 

2874 def entity_zero_or_selectable(self): 

2875 return self.entity_zero 

2876 

2877 def corresponds_to(self, entity): 

2878 return _entity_corresponds_to(self.entity_zero, entity) 

2879 

2880 def _get_entity_clauses(self, compile_state): 

2881 adapter = None 

2882 

2883 if not self.is_aliased_class: 

2884 if compile_state._polymorphic_adapters: 

2885 adapter = compile_state._polymorphic_adapters.get( 

2886 self.mapper, None 

2887 ) 

2888 else: 

2889 adapter = self.entity_zero._adapter 

2890 

2891 if adapter: 

2892 if compile_state._from_obj_alias: 

2893 ret = adapter.wrap(compile_state._from_obj_alias) 

2894 else: 

2895 ret = adapter 

2896 else: 

2897 ret = compile_state._from_obj_alias 

2898 

2899 return ret 

2900 

2901 def row_processor(self, context, result): 

2902 compile_state = context.compile_state 

2903 adapter = self._get_entity_clauses(compile_state) 

2904 

2905 if compile_state.compound_eager_adapter and adapter: 

2906 adapter = adapter.wrap(compile_state.compound_eager_adapter) 

2907 elif not adapter: 

2908 adapter = compile_state.compound_eager_adapter 

2909 

2910 if compile_state._primary_entity is self: 

2911 only_load_props = compile_state.compile_options._only_load_props 

2912 refresh_state = context.refresh_state 

2913 else: 

2914 only_load_props = refresh_state = None 

2915 

2916 _instance = loading._instance_processor( 

2917 self, 

2918 self.mapper, 

2919 context, 

2920 result, 

2921 self.path, 

2922 adapter, 

2923 only_load_props=only_load_props, 

2924 refresh_state=refresh_state, 

2925 polymorphic_discriminator=self._polymorphic_discriminator, 

2926 ) 

2927 

2928 return _instance, self._label_name, self._extra_entities 

2929 

2930 def setup_dml_returning_compile_state( 

2931 self, 

2932 compile_state: _ORMCompileState, 

2933 adapter: Optional[_DMLReturningColFilter], 

2934 ) -> None: 

2935 loading._setup_entity_query( 

2936 compile_state, 

2937 self.mapper, 

2938 self, 

2939 self.path, 

2940 adapter, 

2941 compile_state.primary_columns, 

2942 with_polymorphic=self._with_polymorphic_mappers, 

2943 only_load_props=compile_state.compile_options._only_load_props, 

2944 polymorphic_discriminator=self._polymorphic_discriminator, 

2945 ) 

2946 

2947 def setup_compile_state(self, compile_state): 

2948 adapter = self._get_entity_clauses(compile_state) 

2949 

2950 single_table_crit = self.mapper._single_table_criterion 

2951 if ( 

2952 single_table_crit is not None 

2953 or ("additional_entity_criteria", self.mapper) 

2954 in compile_state.global_attributes 

2955 ): 

2956 ext_info = self.entity_zero 

2957 compile_state.extra_criteria_entities[ext_info] = ( 

2958 ext_info, 

2959 ext_info._adapter if ext_info.is_aliased_class else None, 

2960 ) 

2961 

2962 loading._setup_entity_query( 

2963 compile_state, 

2964 self.mapper, 

2965 self, 

2966 self.path, 

2967 adapter, 

2968 compile_state.primary_columns, 

2969 with_polymorphic=self._with_polymorphic_mappers, 

2970 only_load_props=compile_state.compile_options._only_load_props, 

2971 polymorphic_discriminator=self._polymorphic_discriminator, 

2972 ) 

2973 compile_state._fallback_from_clauses.append(self.selectable) 

2974 

2975 

2976class _BundleEntity(_QueryEntity): 

2977 _extra_entities = () 

2978 

2979 __slots__ = ( 

2980 "bundle", 

2981 "expr", 

2982 "type", 

2983 "_label_name", 

2984 "_entities", 

2985 "supports_single_entity", 

2986 ) 

2987 

2988 _entities: List[_QueryEntity] 

2989 bundle: Bundle 

2990 type: Type[Any] 

2991 _label_name: str 

2992 supports_single_entity: bool 

2993 expr: Bundle 

2994 

2995 def __init__( 

2996 self, 

2997 compile_state, 

2998 expr, 

2999 entities_collection, 

3000 is_current_entities, 

3001 setup_entities=True, 

3002 parent_bundle=None, 

3003 ): 

3004 compile_state._has_orm_entities = True 

3005 

3006 expr = expr._annotations["bundle"] 

3007 if parent_bundle: 

3008 parent_bundle._entities.append(self) 

3009 else: 

3010 entities_collection.append(self) 

3011 

3012 if isinstance( 

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

3014 ): 

3015 bundle = expr.__clause_element__() 

3016 else: 

3017 bundle = expr 

3018 

3019 self.bundle = self.expr = bundle 

3020 self.type = type(bundle) 

3021 self._label_name = bundle.name 

3022 self._entities = [] 

3023 

3024 if setup_entities: 

3025 for expr in bundle.exprs: 

3026 if "bundle" in expr._annotations: 

3027 _BundleEntity( 

3028 compile_state, 

3029 expr, 

3030 entities_collection, 

3031 is_current_entities, 

3032 parent_bundle=self, 

3033 ) 

3034 elif isinstance(expr, Bundle): 

3035 _BundleEntity( 

3036 compile_state, 

3037 expr, 

3038 entities_collection, 

3039 is_current_entities, 

3040 parent_bundle=self, 

3041 ) 

3042 else: 

3043 _ORMColumnEntity._for_columns( 

3044 compile_state, 

3045 [expr], 

3046 entities_collection, 

3047 None, 

3048 is_current_entities, 

3049 parent_bundle=self, 

3050 ) 

3051 

3052 self.supports_single_entity = self.bundle.single_entity 

3053 

3054 @property 

3055 def mapper(self): 

3056 ezero = self.entity_zero 

3057 if ezero is not None: 

3058 return ezero.mapper 

3059 else: 

3060 return None 

3061 

3062 @property 

3063 def entity_zero(self): 

3064 for ent in self._entities: 

3065 ezero = ent.entity_zero 

3066 if ezero is not None: 

3067 return ezero 

3068 else: 

3069 return None 

3070 

3071 def corresponds_to(self, entity): 

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

3073 # we are working around it 

3074 return False 

3075 

3076 @property 

3077 def entity_zero_or_selectable(self): 

3078 for ent in self._entities: 

3079 ezero = ent.entity_zero_or_selectable 

3080 if ezero is not None: 

3081 return ezero 

3082 else: 

3083 return None 

3084 

3085 def setup_compile_state(self, compile_state): 

3086 for ent in self._entities: 

3087 ent.setup_compile_state(compile_state) 

3088 

3089 def setup_dml_returning_compile_state( 

3090 self, 

3091 compile_state: _ORMCompileState, 

3092 adapter: Optional[_DMLReturningColFilter], 

3093 ) -> None: 

3094 return self.setup_compile_state(compile_state) 

3095 

3096 def row_processor(self, context, result): 

3097 procs, labels, extra = zip( 

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

3099 ) 

3100 

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

3102 

3103 return proc, self._label_name, self._extra_entities 

3104 

3105 

3106class _ColumnEntity(_QueryEntity): 

3107 __slots__ = ( 

3108 "_fetch_column", 

3109 "_row_processor", 

3110 "raw_column_index", 

3111 "translate_raw_column", 

3112 ) 

3113 

3114 @classmethod 

3115 def _for_columns( 

3116 cls, 

3117 compile_state, 

3118 columns, 

3119 entities_collection, 

3120 raw_column_index, 

3121 is_current_entities, 

3122 parent_bundle=None, 

3123 ): 

3124 for column in columns: 

3125 annotations = column._annotations 

3126 if "parententity" in annotations: 

3127 _entity = annotations["parententity"] 

3128 else: 

3129 _entity = sql_util.extract_first_column_annotation( 

3130 column, "parententity" 

3131 ) 

3132 

3133 if _entity: 

3134 if "identity_token" in column._annotations: 

3135 _IdentityTokenEntity( 

3136 compile_state, 

3137 column, 

3138 entities_collection, 

3139 _entity, 

3140 raw_column_index, 

3141 is_current_entities, 

3142 parent_bundle=parent_bundle, 

3143 ) 

3144 else: 

3145 _ORMColumnEntity( 

3146 compile_state, 

3147 column, 

3148 entities_collection, 

3149 _entity, 

3150 raw_column_index, 

3151 is_current_entities, 

3152 parent_bundle=parent_bundle, 

3153 ) 

3154 else: 

3155 _RawColumnEntity( 

3156 compile_state, 

3157 column, 

3158 entities_collection, 

3159 raw_column_index, 

3160 is_current_entities, 

3161 parent_bundle=parent_bundle, 

3162 ) 

3163 

3164 @property 

3165 def type(self): 

3166 return self.column.type 

3167 

3168 @property 

3169 def _non_hashable_value(self): 

3170 return not self.column.type.hashable 

3171 

3172 @property 

3173 def _null_column_type(self): 

3174 return self.column.type._isnull 

3175 

3176 def row_processor(self, context, result): 

3177 compile_state = context.compile_state 

3178 

3179 # the resulting callable is entirely cacheable so just return 

3180 # it if we already made one 

3181 if self._row_processor is not None: 

3182 getter, label_name, extra_entities = self._row_processor 

3183 if self.translate_raw_column: 

3184 extra_entities += ( 

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

3186 ) 

3187 

3188 return getter, label_name, extra_entities 

3189 

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

3191 # setup_compile_state, to avoid doing redundant work 

3192 if self._fetch_column is not None: 

3193 column = self._fetch_column 

3194 else: 

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

3196 # and setup_compile_state may not have been called. 

3197 column = self.column 

3198 

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

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

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

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

3203 if compile_state._from_obj_alias: 

3204 column = compile_state._from_obj_alias.columns[column] 

3205 

3206 if column._annotations: 

3207 # annotated columns perform more slowly in compiler and 

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

3209 column = column._deannotate() 

3210 

3211 if compile_state.compound_eager_adapter: 

3212 column = compile_state.compound_eager_adapter.columns[column] 

3213 

3214 getter = result._getter(column) 

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

3216 self._row_processor = ret 

3217 

3218 if self.translate_raw_column: 

3219 extra_entities = self._extra_entities + ( 

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

3221 ) 

3222 return getter, self._label_name, extra_entities 

3223 else: 

3224 return ret 

3225 

3226 

3227class _RawColumnEntity(_ColumnEntity): 

3228 entity_zero = None 

3229 mapper = None 

3230 supports_single_entity = False 

3231 

3232 __slots__ = ( 

3233 "expr", 

3234 "column", 

3235 "_label_name", 

3236 "entity_zero_or_selectable", 

3237 "_extra_entities", 

3238 ) 

3239 

3240 def __init__( 

3241 self, 

3242 compile_state, 

3243 column, 

3244 entities_collection, 

3245 raw_column_index, 

3246 is_current_entities, 

3247 parent_bundle=None, 

3248 ): 

3249 self.expr = column 

3250 self.raw_column_index = raw_column_index 

3251 self.translate_raw_column = raw_column_index is not None 

3252 

3253 if column._is_star: 

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

3255 

3256 if not is_current_entities or column._is_text_clause: 

3257 self._label_name = None 

3258 else: 

3259 if parent_bundle: 

3260 self._label_name = column._proxy_key 

3261 else: 

3262 self._label_name = compile_state._label_convention(column) 

3263 

3264 if parent_bundle: 

3265 parent_bundle._entities.append(self) 

3266 else: 

3267 entities_collection.append(self) 

3268 

3269 self.column = column 

3270 self.entity_zero_or_selectable = ( 

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

3272 ) 

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

3274 self._fetch_column = self._row_processor = None 

3275 

3276 def corresponds_to(self, entity): 

3277 return False 

3278 

3279 def setup_dml_returning_compile_state( 

3280 self, 

3281 compile_state: _ORMCompileState, 

3282 adapter: Optional[_DMLReturningColFilter], 

3283 ) -> None: 

3284 return self.setup_compile_state(compile_state) 

3285 

3286 def setup_compile_state(self, compile_state): 

3287 current_adapter = compile_state._get_current_adapter() 

3288 if current_adapter: 

3289 column = current_adapter(self.column, False) 

3290 if column is None: 

3291 return 

3292 else: 

3293 column = self.column 

3294 

3295 if column._annotations: 

3296 # annotated columns perform more slowly in compiler and 

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

3298 column = column._deannotate() 

3299 

3300 compile_state.dedupe_columns.add(column) 

3301 compile_state.primary_columns.append(column) 

3302 self._fetch_column = column 

3303 

3304 

3305class _ORMColumnEntity(_ColumnEntity): 

3306 """Column/expression based entity.""" 

3307 

3308 supports_single_entity = False 

3309 

3310 __slots__ = ( 

3311 "expr", 

3312 "mapper", 

3313 "column", 

3314 "_label_name", 

3315 "entity_zero_or_selectable", 

3316 "entity_zero", 

3317 "_extra_entities", 

3318 ) 

3319 

3320 def __init__( 

3321 self, 

3322 compile_state, 

3323 column, 

3324 entities_collection, 

3325 parententity, 

3326 raw_column_index, 

3327 is_current_entities, 

3328 parent_bundle=None, 

3329 ): 

3330 annotations = column._annotations 

3331 

3332 _entity = parententity 

3333 

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

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

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

3337 # within internal loaders. 

3338 

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

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

3341 if orm_key: 

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

3343 self.translate_raw_column = False 

3344 else: 

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

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

3347 # include this column position from the invoked statement 

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

3349 # it can be targeted by identity after caching 

3350 self.expr = column 

3351 self.translate_raw_column = raw_column_index is not None 

3352 

3353 self.raw_column_index = raw_column_index 

3354 

3355 if is_current_entities: 

3356 if parent_bundle: 

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

3358 else: 

3359 self._label_name = compile_state._label_convention( 

3360 column, col_name=orm_key 

3361 ) 

3362 else: 

3363 self._label_name = None 

3364 

3365 _entity._post_inspect 

3366 self.entity_zero = self.entity_zero_or_selectable = ezero = _entity 

3367 self.mapper = mapper = _entity.mapper 

3368 

3369 if parent_bundle: 

3370 parent_bundle._entities.append(self) 

3371 else: 

3372 entities_collection.append(self) 

3373 

3374 compile_state._has_orm_entities = True 

3375 

3376 self.column = column 

3377 

3378 self._fetch_column = self._row_processor = None 

3379 

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

3381 

3382 if mapper._should_select_with_poly_adapter: 

3383 compile_state._create_with_polymorphic_adapter( 

3384 ezero, ezero.selectable 

3385 ) 

3386 

3387 def corresponds_to(self, entity): 

3388 if _is_aliased_class(entity): 

3389 # TODO: polymorphic subclasses ? 

3390 return entity is self.entity_zero 

3391 else: 

3392 return not _is_aliased_class( 

3393 self.entity_zero 

3394 ) and entity.common_parent(self.entity_zero) 

3395 

3396 def setup_dml_returning_compile_state( 

3397 self, 

3398 compile_state: _ORMCompileState, 

3399 adapter: Optional[_DMLReturningColFilter], 

3400 ) -> None: 

3401 

3402 self._fetch_column = column = self.column 

3403 if adapter: 

3404 column = adapter(column, False) 

3405 

3406 if column is not None: 

3407 compile_state.dedupe_columns.add(column) 

3408 compile_state.primary_columns.append(column) 

3409 

3410 def setup_compile_state(self, compile_state): 

3411 current_adapter = compile_state._get_current_adapter() 

3412 if current_adapter: 

3413 column = current_adapter(self.column, False) 

3414 if column is None: 

3415 assert compile_state.is_dml_returning 

3416 self._fetch_column = self.column 

3417 return 

3418 else: 

3419 column = self.column 

3420 

3421 ezero = self.entity_zero 

3422 

3423 single_table_crit = self.mapper._single_table_criterion 

3424 if ( 

3425 single_table_crit is not None 

3426 or ("additional_entity_criteria", self.mapper) 

3427 in compile_state.global_attributes 

3428 ): 

3429 compile_state.extra_criteria_entities[ezero] = ( 

3430 ezero, 

3431 ezero._adapter if ezero.is_aliased_class else None, 

3432 ) 

3433 

3434 if column._annotations and not column._expression_label: 

3435 # annotated columns perform more slowly in compiler and 

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

3437 column = column._deannotate() 

3438 

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

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

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

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

3443 # a scalar subquery. 

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

3445 ezero.selectable._from_objects 

3446 ): 

3447 compile_state._fallback_from_clauses.append(ezero.selectable) 

3448 

3449 compile_state.dedupe_columns.add(column) 

3450 compile_state.primary_columns.append(column) 

3451 self._fetch_column = column 

3452 

3453 

3454class _IdentityTokenEntity(_ORMColumnEntity): 

3455 translate_raw_column = False 

3456 

3457 def setup_compile_state(self, compile_state): 

3458 pass 

3459 

3460 def row_processor(self, context, result): 

3461 def getter(row): 

3462 return context.load_options._identity_token 

3463 

3464 return getter, self._label_name, self._extra_entities