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-2026 the SQLAlchemy authors and contributors 

3# <see AUTHORS file> 

4# 

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

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

7# mypy: ignore-errors 

8 

9from __future__ import annotations 

10 

11import 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 ext_infos = [ 

2532 fromclause._annotations.get("parententity", None) 

2533 for fromclause in self.from_clauses 

2534 ] + [ 

2535 elem._annotations.get("parententity", None) 

2536 for where_crit in self.select_statement._where_criteria 

2537 for elem in sql_util.surface_expressions(where_crit) 

2538 ] 

2539 

2540 for ext_info in ext_infos: 

2541 

2542 if ( 

2543 ext_info 

2544 and ( 

2545 ext_info.mapper._single_table_criterion is not None 

2546 or ("additional_entity_criteria", ext_info.mapper) 

2547 in self.global_attributes 

2548 ) 

2549 and ext_info not in self.extra_criteria_entities 

2550 ): 

2551 self.extra_criteria_entities[ext_info] = ( 

2552 ext_info, 

2553 ext_info._adapter if ext_info.is_aliased_class else None, 

2554 ) 

2555 

2556 _where_criteria_to_add = () 

2557 

2558 merged_single_crit = collections.defaultdict( 

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

2560 ) 

2561 

2562 for ext_info, adapter in util.OrderedSet( 

2563 self.extra_criteria_entities.values() 

2564 ): 

2565 if ext_info in self._join_entities: 

2566 continue 

2567 

2568 # assemble single table inheritance criteria. 

2569 if ( 

2570 ext_info.is_aliased_class 

2571 and ext_info._base_alias()._is_with_polymorphic 

2572 ): 

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

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

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

2576 # of joined inheritance. 

2577 hierarchy_root = ext_info._base_alias() 

2578 else: 

2579 hierarchy_root = ext_info 

2580 

2581 single_crit_component = ( 

2582 hierarchy_root.mapper._single_table_criteria_component 

2583 ) 

2584 

2585 if single_crit_component is not None: 

2586 polymorphic_on, criteria = single_crit_component 

2587 

2588 polymorphic_on = polymorphic_on._annotate( 

2589 { 

2590 "parententity": hierarchy_root, 

2591 "parentmapper": hierarchy_root.mapper, 

2592 } 

2593 ) 

2594 

2595 list_of_single_crits, adapters = merged_single_crit[ 

2596 (hierarchy_root, polymorphic_on) 

2597 ] 

2598 list_of_single_crits.update(criteria) 

2599 if adapter: 

2600 adapters.add(adapter) 

2601 

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

2603 # with_loader_criteria() options 

2604 if not self.compile_options._for_refresh_state: 

2605 additional_entity_criteria = self._get_extra_criteria(ext_info) 

2606 _where_criteria_to_add += tuple( 

2607 adapter.traverse(crit) if adapter else crit 

2608 for crit in additional_entity_criteria 

2609 ) 

2610 

2611 # merge together single table inheritance criteria keyed to 

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

2613 for (ext_info, polymorphic_on), ( 

2614 merged_crit, 

2615 adapters, 

2616 ) in merged_single_crit.items(): 

2617 new_crit = polymorphic_on.in_(merged_crit) 

2618 for adapter in adapters: 

2619 new_crit = adapter.traverse(new_crit) 

2620 _where_criteria_to_add += (new_crit,) 

2621 

2622 current_adapter = self._get_current_adapter() 

2623 if current_adapter: 

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

2625 # have one, and concatenate to final WHERE criteria 

2626 for crit in _where_criteria_to_add: 

2627 crit = current_adapter(crit, False) 

2628 self._where_criteria += (crit,) 

2629 else: 

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

2631 self._where_criteria += _where_criteria_to_add 

2632 

2633 

2634def _column_descriptions( 

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

2636 compile_state: Optional[_ORMSelectCompileState] = None, 

2637 legacy: bool = False, 

2638) -> List[ORMColumnDescription]: 

2639 if compile_state is None: 

2640 compile_state = _ORMSelectCompileState._create_entities_collection( 

2641 query_or_select_stmt, legacy=legacy 

2642 ) 

2643 ctx = compile_state 

2644 d = [ 

2645 { 

2646 "name": ent._label_name, 

2647 "type": ent.type, 

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

2649 "expr": ent.expr, 

2650 "entity": ( 

2651 getattr(insp_ent, "entity", None) 

2652 if ent.entity_zero is not None 

2653 and not insp_ent.is_clause_element 

2654 else None 

2655 ), 

2656 } 

2657 for ent, insp_ent in [ 

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

2659 ] 

2660 ] 

2661 return d 

2662 

2663 

2664def _legacy_filter_by_entity_zero( 

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

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

2667 self = query_or_augmented_select 

2668 if self._setup_joins: 

2669 _last_joined_entity = self._last_joined_entity 

2670 if _last_joined_entity is not None: 

2671 return _last_joined_entity 

2672 

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

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

2675 

2676 return _entity_from_pre_ent_zero(self) 

2677 

2678 

2679def _entity_from_pre_ent_zero( 

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

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

2682 self = query_or_augmented_select 

2683 if not self._raw_columns: 

2684 return None 

2685 

2686 ent = self._raw_columns[0] 

2687 

2688 if "parententity" in ent._annotations: 

2689 return ent._annotations["parententity"] 

2690 elif isinstance(ent, ORMColumnsClauseRole): 

2691 return ent.entity 

2692 elif "bundle" in ent._annotations: 

2693 return ent._annotations["bundle"] 

2694 else: 

2695 return ent 

2696 

2697 

2698def _determine_last_joined_entity( 

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

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

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

2702 if not setup_joins: 

2703 return None 

2704 

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

2706 

2707 if isinstance( 

2708 target, 

2709 attributes.QueryableAttribute, 

2710 ): 

2711 return target.entity 

2712 else: 

2713 return target 

2714 

2715 

2716class _QueryEntity: 

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

2718 

2719 __slots__ = () 

2720 

2721 supports_single_entity: bool 

2722 

2723 _non_hashable_value = False 

2724 _null_column_type = False 

2725 use_id_for_hash = False 

2726 

2727 _label_name: Optional[str] 

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

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

2730 entity_zero: Optional[_InternalEntityType] 

2731 

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

2733 raise NotImplementedError() 

2734 

2735 def setup_dml_returning_compile_state( 

2736 self, 

2737 compile_state: _ORMCompileState, 

2738 adapter: Optional[_DMLReturningColFilter], 

2739 ) -> None: 

2740 raise NotImplementedError() 

2741 

2742 def row_processor(self, context, result): 

2743 raise NotImplementedError() 

2744 

2745 @classmethod 

2746 def to_compile_state( 

2747 cls, compile_state, entities, entities_collection, is_current_entities 

2748 ): 

2749 for idx, entity in enumerate(entities): 

2750 if entity._is_lambda_element: 

2751 if entity._is_sequence: 

2752 cls.to_compile_state( 

2753 compile_state, 

2754 entity._resolved, 

2755 entities_collection, 

2756 is_current_entities, 

2757 ) 

2758 continue 

2759 else: 

2760 entity = entity._resolved 

2761 

2762 if entity.is_clause_element: 

2763 if entity.is_selectable: 

2764 if "parententity" in entity._annotations: 

2765 _MapperEntity( 

2766 compile_state, 

2767 entity, 

2768 entities_collection, 

2769 is_current_entities, 

2770 ) 

2771 else: 

2772 _ColumnEntity._for_columns( 

2773 compile_state, 

2774 entity._select_iterable, 

2775 entities_collection, 

2776 idx, 

2777 is_current_entities, 

2778 ) 

2779 else: 

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

2781 _BundleEntity( 

2782 compile_state, 

2783 entity, 

2784 entities_collection, 

2785 is_current_entities, 

2786 ) 

2787 elif entity._is_clause_list: 

2788 # this is legacy only - test_composites.py 

2789 # test_query_cols_legacy 

2790 _ColumnEntity._for_columns( 

2791 compile_state, 

2792 entity._select_iterable, 

2793 entities_collection, 

2794 idx, 

2795 is_current_entities, 

2796 ) 

2797 else: 

2798 _ColumnEntity._for_columns( 

2799 compile_state, 

2800 [entity], 

2801 entities_collection, 

2802 idx, 

2803 is_current_entities, 

2804 ) 

2805 elif entity.is_bundle: 

2806 _BundleEntity(compile_state, entity, entities_collection) 

2807 

2808 return entities_collection 

2809 

2810 

2811class _MapperEntity(_QueryEntity): 

2812 """mapper/class/AliasedClass entity""" 

2813 

2814 __slots__ = ( 

2815 "expr", 

2816 "mapper", 

2817 "entity_zero", 

2818 "is_aliased_class", 

2819 "path", 

2820 "_extra_entities", 

2821 "_label_name", 

2822 "_with_polymorphic_mappers", 

2823 "selectable", 

2824 "_polymorphic_discriminator", 

2825 ) 

2826 

2827 expr: _InternalEntityType 

2828 mapper: Mapper[Any] 

2829 entity_zero: _InternalEntityType 

2830 is_aliased_class: bool 

2831 path: PathRegistry 

2832 _label_name: str 

2833 

2834 def __init__( 

2835 self, compile_state, entity, entities_collection, is_current_entities 

2836 ): 

2837 entities_collection.append(self) 

2838 if is_current_entities: 

2839 if compile_state._primary_entity is None: 

2840 compile_state._primary_entity = self 

2841 compile_state._has_mapper_entities = True 

2842 compile_state._has_orm_entities = True 

2843 

2844 entity = entity._annotations["parententity"] 

2845 entity._post_inspect 

2846 ext_info = self.entity_zero = entity 

2847 entity = ext_info.entity 

2848 

2849 self.expr = entity 

2850 self.mapper = mapper = ext_info.mapper 

2851 

2852 self._extra_entities = (self.expr,) 

2853 

2854 if ext_info.is_aliased_class: 

2855 self._label_name = ext_info.name 

2856 else: 

2857 self._label_name = mapper.class_.__name__ 

2858 

2859 self.is_aliased_class = ext_info.is_aliased_class 

2860 self.path = ext_info._path_registry 

2861 

2862 self.selectable = ext_info.selectable 

2863 self._with_polymorphic_mappers = ext_info.with_polymorphic_mappers 

2864 self._polymorphic_discriminator = ext_info.polymorphic_on 

2865 

2866 if mapper._should_select_with_poly_adapter: 

2867 compile_state._create_with_polymorphic_adapter( 

2868 ext_info, self.selectable 

2869 ) 

2870 

2871 supports_single_entity = True 

2872 

2873 _non_hashable_value = True 

2874 use_id_for_hash = True 

2875 

2876 @property 

2877 def type(self): 

2878 return self.mapper.class_ 

2879 

2880 @property 

2881 def entity_zero_or_selectable(self): 

2882 return self.entity_zero 

2883 

2884 def corresponds_to(self, entity): 

2885 return _entity_corresponds_to(self.entity_zero, entity) 

2886 

2887 def _get_entity_clauses(self, compile_state): 

2888 adapter = None 

2889 

2890 if not self.is_aliased_class: 

2891 if compile_state._polymorphic_adapters: 

2892 adapter = compile_state._polymorphic_adapters.get( 

2893 self.mapper, None 

2894 ) 

2895 else: 

2896 adapter = self.entity_zero._adapter 

2897 

2898 if adapter: 

2899 if compile_state._from_obj_alias: 

2900 ret = adapter.wrap(compile_state._from_obj_alias) 

2901 else: 

2902 ret = adapter 

2903 else: 

2904 ret = compile_state._from_obj_alias 

2905 

2906 return ret 

2907 

2908 def row_processor(self, context, result): 

2909 compile_state = context.compile_state 

2910 adapter = self._get_entity_clauses(compile_state) 

2911 

2912 if compile_state.compound_eager_adapter and adapter: 

2913 adapter = adapter.wrap(compile_state.compound_eager_adapter) 

2914 elif not adapter: 

2915 adapter = compile_state.compound_eager_adapter 

2916 

2917 if compile_state._primary_entity is self: 

2918 only_load_props = compile_state.compile_options._only_load_props 

2919 refresh_state = context.refresh_state 

2920 else: 

2921 only_load_props = refresh_state = None 

2922 

2923 _instance = loading._instance_processor( 

2924 self, 

2925 self.mapper, 

2926 context, 

2927 result, 

2928 self.path, 

2929 adapter, 

2930 only_load_props=only_load_props, 

2931 refresh_state=refresh_state, 

2932 polymorphic_discriminator=self._polymorphic_discriminator, 

2933 ) 

2934 

2935 return _instance, self._label_name, self._extra_entities 

2936 

2937 def setup_dml_returning_compile_state( 

2938 self, 

2939 compile_state: _ORMCompileState, 

2940 adapter: Optional[_DMLReturningColFilter], 

2941 ) -> None: 

2942 loading._setup_entity_query( 

2943 compile_state, 

2944 self.mapper, 

2945 self, 

2946 self.path, 

2947 adapter, 

2948 compile_state.primary_columns, 

2949 with_polymorphic=self._with_polymorphic_mappers, 

2950 only_load_props=compile_state.compile_options._only_load_props, 

2951 polymorphic_discriminator=self._polymorphic_discriminator, 

2952 ) 

2953 

2954 def setup_compile_state(self, compile_state): 

2955 adapter = self._get_entity_clauses(compile_state) 

2956 

2957 single_table_crit = self.mapper._single_table_criterion 

2958 if ( 

2959 single_table_crit is not None 

2960 or ("additional_entity_criteria", self.mapper) 

2961 in compile_state.global_attributes 

2962 ): 

2963 ext_info = self.entity_zero 

2964 compile_state.extra_criteria_entities[ext_info] = ( 

2965 ext_info, 

2966 ext_info._adapter if ext_info.is_aliased_class else None, 

2967 ) 

2968 

2969 loading._setup_entity_query( 

2970 compile_state, 

2971 self.mapper, 

2972 self, 

2973 self.path, 

2974 adapter, 

2975 compile_state.primary_columns, 

2976 with_polymorphic=self._with_polymorphic_mappers, 

2977 only_load_props=compile_state.compile_options._only_load_props, 

2978 polymorphic_discriminator=self._polymorphic_discriminator, 

2979 ) 

2980 compile_state._fallback_from_clauses.append(self.selectable) 

2981 

2982 

2983class _BundleEntity(_QueryEntity): 

2984 _extra_entities = () 

2985 

2986 __slots__ = ( 

2987 "bundle", 

2988 "expr", 

2989 "type", 

2990 "_label_name", 

2991 "_entities", 

2992 "supports_single_entity", 

2993 ) 

2994 

2995 _entities: List[_QueryEntity] 

2996 bundle: Bundle 

2997 type: Type[Any] 

2998 _label_name: str 

2999 supports_single_entity: bool 

3000 expr: Bundle 

3001 

3002 def __init__( 

3003 self, 

3004 compile_state, 

3005 expr, 

3006 entities_collection, 

3007 is_current_entities, 

3008 setup_entities=True, 

3009 parent_bundle=None, 

3010 ): 

3011 compile_state._has_orm_entities = True 

3012 

3013 expr = expr._annotations["bundle"] 

3014 if parent_bundle: 

3015 parent_bundle._entities.append(self) 

3016 else: 

3017 entities_collection.append(self) 

3018 

3019 if isinstance( 

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

3021 ): 

3022 bundle = expr.__clause_element__() 

3023 else: 

3024 bundle = expr 

3025 

3026 self.bundle = self.expr = bundle 

3027 self.type = type(bundle) 

3028 self._label_name = bundle.name 

3029 self._entities = [] 

3030 

3031 if setup_entities: 

3032 for expr in bundle.exprs: 

3033 if "bundle" in expr._annotations: 

3034 _BundleEntity( 

3035 compile_state, 

3036 expr, 

3037 entities_collection, 

3038 is_current_entities, 

3039 parent_bundle=self, 

3040 ) 

3041 elif isinstance(expr, Bundle): 

3042 _BundleEntity( 

3043 compile_state, 

3044 expr, 

3045 entities_collection, 

3046 is_current_entities, 

3047 parent_bundle=self, 

3048 ) 

3049 else: 

3050 _ORMColumnEntity._for_columns( 

3051 compile_state, 

3052 [expr], 

3053 entities_collection, 

3054 None, 

3055 is_current_entities, 

3056 parent_bundle=self, 

3057 ) 

3058 

3059 self.supports_single_entity = self.bundle.single_entity 

3060 

3061 @property 

3062 def mapper(self): 

3063 ezero = self.entity_zero 

3064 if ezero is not None: 

3065 return ezero.mapper 

3066 else: 

3067 return None 

3068 

3069 @property 

3070 def entity_zero(self): 

3071 for ent in self._entities: 

3072 ezero = ent.entity_zero 

3073 if ezero is not None: 

3074 return ezero 

3075 else: 

3076 return None 

3077 

3078 def corresponds_to(self, entity): 

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

3080 # we are working around it 

3081 return False 

3082 

3083 @property 

3084 def entity_zero_or_selectable(self): 

3085 for ent in self._entities: 

3086 ezero = ent.entity_zero_or_selectable 

3087 if ezero is not None: 

3088 return ezero 

3089 else: 

3090 return None 

3091 

3092 def setup_compile_state(self, compile_state): 

3093 for ent in self._entities: 

3094 ent.setup_compile_state(compile_state) 

3095 

3096 def setup_dml_returning_compile_state( 

3097 self, 

3098 compile_state: _ORMCompileState, 

3099 adapter: Optional[_DMLReturningColFilter], 

3100 ) -> None: 

3101 return self.setup_compile_state(compile_state) 

3102 

3103 def row_processor(self, context, result): 

3104 procs, labels, extra = zip( 

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

3106 ) 

3107 

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

3109 

3110 return proc, self._label_name, self._extra_entities 

3111 

3112 

3113class _ColumnEntity(_QueryEntity): 

3114 __slots__ = ( 

3115 "_fetch_column", 

3116 "_row_processor", 

3117 "raw_column_index", 

3118 "translate_raw_column", 

3119 ) 

3120 

3121 @classmethod 

3122 def _for_columns( 

3123 cls, 

3124 compile_state, 

3125 columns, 

3126 entities_collection, 

3127 raw_column_index, 

3128 is_current_entities, 

3129 parent_bundle=None, 

3130 ): 

3131 for column in columns: 

3132 annotations = column._annotations 

3133 if "parententity" in annotations: 

3134 _entity = annotations["parententity"] 

3135 else: 

3136 _entity = sql_util.extract_first_column_annotation( 

3137 column, "parententity" 

3138 ) 

3139 

3140 if _entity: 

3141 if "identity_token" in column._annotations: 

3142 _IdentityTokenEntity( 

3143 compile_state, 

3144 column, 

3145 entities_collection, 

3146 _entity, 

3147 raw_column_index, 

3148 is_current_entities, 

3149 parent_bundle=parent_bundle, 

3150 ) 

3151 else: 

3152 _ORMColumnEntity( 

3153 compile_state, 

3154 column, 

3155 entities_collection, 

3156 _entity, 

3157 raw_column_index, 

3158 is_current_entities, 

3159 parent_bundle=parent_bundle, 

3160 ) 

3161 else: 

3162 _RawColumnEntity( 

3163 compile_state, 

3164 column, 

3165 entities_collection, 

3166 raw_column_index, 

3167 is_current_entities, 

3168 parent_bundle=parent_bundle, 

3169 ) 

3170 

3171 @property 

3172 def type(self): 

3173 return self.column.type 

3174 

3175 @property 

3176 def _non_hashable_value(self): 

3177 return not self.column.type.hashable 

3178 

3179 @property 

3180 def _null_column_type(self): 

3181 return self.column.type._isnull 

3182 

3183 def row_processor(self, context, result): 

3184 compile_state = context.compile_state 

3185 

3186 # the resulting callable is entirely cacheable so just return 

3187 # it if we already made one 

3188 if self._row_processor is not None: 

3189 getter, label_name, extra_entities = self._row_processor 

3190 if self.translate_raw_column: 

3191 extra_entities += ( 

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

3193 ) 

3194 

3195 return getter, label_name, extra_entities 

3196 

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

3198 # setup_compile_state, to avoid doing redundant work 

3199 if self._fetch_column is not None: 

3200 column = self._fetch_column 

3201 else: 

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

3203 # and setup_compile_state may not have been called. 

3204 column = self.column 

3205 

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

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

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

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

3210 if compile_state._from_obj_alias: 

3211 column = compile_state._from_obj_alias.columns[column] 

3212 

3213 if column._annotations: 

3214 # annotated columns perform more slowly in compiler and 

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

3216 column = column._deannotate() 

3217 

3218 if compile_state.compound_eager_adapter: 

3219 column = compile_state.compound_eager_adapter.columns[column] 

3220 

3221 getter = result._getter(column) 

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

3223 self._row_processor = ret 

3224 

3225 if self.translate_raw_column: 

3226 extra_entities = self._extra_entities + ( 

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

3228 ) 

3229 return getter, self._label_name, extra_entities 

3230 else: 

3231 return ret 

3232 

3233 

3234class _RawColumnEntity(_ColumnEntity): 

3235 entity_zero = None 

3236 mapper = None 

3237 supports_single_entity = False 

3238 

3239 __slots__ = ( 

3240 "expr", 

3241 "column", 

3242 "_label_name", 

3243 "entity_zero_or_selectable", 

3244 "_extra_entities", 

3245 ) 

3246 

3247 def __init__( 

3248 self, 

3249 compile_state, 

3250 column, 

3251 entities_collection, 

3252 raw_column_index, 

3253 is_current_entities, 

3254 parent_bundle=None, 

3255 ): 

3256 self.expr = column 

3257 self.raw_column_index = raw_column_index 

3258 self.translate_raw_column = raw_column_index is not None 

3259 

3260 if column._is_star: 

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

3262 

3263 if not is_current_entities or column._is_text_clause: 

3264 self._label_name = None 

3265 else: 

3266 if parent_bundle: 

3267 self._label_name = column._proxy_key 

3268 else: 

3269 self._label_name = compile_state._label_convention(column) 

3270 

3271 if parent_bundle: 

3272 parent_bundle._entities.append(self) 

3273 else: 

3274 entities_collection.append(self) 

3275 

3276 self.column = column 

3277 self.entity_zero_or_selectable = ( 

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

3279 ) 

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

3281 self._fetch_column = self._row_processor = None 

3282 

3283 def corresponds_to(self, entity): 

3284 return False 

3285 

3286 def setup_dml_returning_compile_state( 

3287 self, 

3288 compile_state: _ORMCompileState, 

3289 adapter: Optional[_DMLReturningColFilter], 

3290 ) -> None: 

3291 return self.setup_compile_state(compile_state) 

3292 

3293 def setup_compile_state(self, compile_state): 

3294 current_adapter = compile_state._get_current_adapter() 

3295 if current_adapter: 

3296 column = current_adapter(self.column, False) 

3297 if column is None: 

3298 return 

3299 else: 

3300 column = self.column 

3301 

3302 if column._annotations: 

3303 # annotated columns perform more slowly in compiler and 

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

3305 column = column._deannotate() 

3306 

3307 compile_state.dedupe_columns.add(column) 

3308 compile_state.primary_columns.append(column) 

3309 self._fetch_column = column 

3310 

3311 

3312class _ORMColumnEntity(_ColumnEntity): 

3313 """Column/expression based entity.""" 

3314 

3315 supports_single_entity = False 

3316 

3317 __slots__ = ( 

3318 "expr", 

3319 "mapper", 

3320 "column", 

3321 "_label_name", 

3322 "entity_zero_or_selectable", 

3323 "entity_zero", 

3324 "_extra_entities", 

3325 ) 

3326 

3327 def __init__( 

3328 self, 

3329 compile_state, 

3330 column, 

3331 entities_collection, 

3332 parententity, 

3333 raw_column_index, 

3334 is_current_entities, 

3335 parent_bundle=None, 

3336 ): 

3337 annotations = column._annotations 

3338 

3339 _entity = parententity 

3340 

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

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

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

3344 # within internal loaders. 

3345 

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

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

3348 if orm_key: 

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

3350 self.translate_raw_column = False 

3351 else: 

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

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

3354 # include this column position from the invoked statement 

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

3356 # it can be targeted by identity after caching 

3357 self.expr = column 

3358 self.translate_raw_column = raw_column_index is not None 

3359 

3360 self.raw_column_index = raw_column_index 

3361 

3362 if is_current_entities: 

3363 if parent_bundle: 

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

3365 else: 

3366 self._label_name = compile_state._label_convention( 

3367 column, col_name=orm_key 

3368 ) 

3369 else: 

3370 self._label_name = None 

3371 

3372 _entity._post_inspect 

3373 self.entity_zero = self.entity_zero_or_selectable = ezero = _entity 

3374 self.mapper = mapper = _entity.mapper 

3375 

3376 if parent_bundle: 

3377 parent_bundle._entities.append(self) 

3378 else: 

3379 entities_collection.append(self) 

3380 

3381 compile_state._has_orm_entities = True 

3382 

3383 self.column = column 

3384 

3385 self._fetch_column = self._row_processor = None 

3386 

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

3388 

3389 if mapper._should_select_with_poly_adapter: 

3390 compile_state._create_with_polymorphic_adapter( 

3391 ezero, ezero.selectable 

3392 ) 

3393 

3394 def corresponds_to(self, entity): 

3395 if _is_aliased_class(entity): 

3396 # TODO: polymorphic subclasses ? 

3397 return entity is self.entity_zero 

3398 else: 

3399 return not _is_aliased_class( 

3400 self.entity_zero 

3401 ) and entity.common_parent(self.entity_zero) 

3402 

3403 def setup_dml_returning_compile_state( 

3404 self, 

3405 compile_state: _ORMCompileState, 

3406 adapter: Optional[_DMLReturningColFilter], 

3407 ) -> None: 

3408 

3409 self._fetch_column = column = self.column 

3410 if adapter: 

3411 column = adapter(column, False) 

3412 

3413 if column is not None: 

3414 compile_state.dedupe_columns.add(column) 

3415 compile_state.primary_columns.append(column) 

3416 

3417 def setup_compile_state(self, compile_state): 

3418 current_adapter = compile_state._get_current_adapter() 

3419 if current_adapter: 

3420 column = current_adapter(self.column, False) 

3421 if column is None: 

3422 assert compile_state.is_dml_returning 

3423 self._fetch_column = self.column 

3424 return 

3425 else: 

3426 column = self.column 

3427 

3428 ezero = self.entity_zero 

3429 

3430 single_table_crit = self.mapper._single_table_criterion 

3431 if ( 

3432 single_table_crit is not None 

3433 or ("additional_entity_criteria", self.mapper) 

3434 in compile_state.global_attributes 

3435 ): 

3436 compile_state.extra_criteria_entities[ezero] = ( 

3437 ezero, 

3438 ezero._adapter if ezero.is_aliased_class else None, 

3439 ) 

3440 

3441 if column._annotations and not column._expression_label: 

3442 # annotated columns perform more slowly in compiler and 

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

3444 column = column._deannotate() 

3445 

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

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

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

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

3450 # a scalar subquery. 

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

3452 ezero.selectable._from_objects 

3453 ): 

3454 compile_state._fallback_from_clauses.append(ezero.selectable) 

3455 

3456 compile_state.dedupe_columns.add(column) 

3457 compile_state.primary_columns.append(column) 

3458 self._fetch_column = column 

3459 

3460 

3461class _IdentityTokenEntity(_ORMColumnEntity): 

3462 translate_raw_column = False 

3463 

3464 def setup_compile_state(self, compile_state): 

3465 pass 

3466 

3467 def row_processor(self, context, result): 

3468 def getter(row): 

3469 return context.load_options._identity_token 

3470 

3471 return getter, self._label_name, self._extra_entities