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

1342 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 @property 

137 def requires_uniquing(self) -> bool: 

138 return bool(self.compile_state.multi_row_eager_loaders) 

139 

140 class default_load_options(Options): 

141 _only_return_tuples = False 

142 _populate_existing = False 

143 _version_check = False 

144 _invoke_all_eagers = True 

145 _autoflush = True 

146 _identity_token = None 

147 _yield_per = None 

148 _refresh_state = None 

149 _lazy_loaded_from = None 

150 _legacy_uniquing = False 

151 _sa_top_level_orm_context = None 

152 _is_user_refresh = False 

153 

154 def __init__( 

155 self, 

156 compile_state: CompileState, 

157 statement: Union[ 

158 Select[Unpack[TupleAny]], 

159 FromStatement[Unpack[TupleAny]], 

160 UpdateBase, 

161 ], 

162 user_passed_query: Union[ 

163 Select[Unpack[TupleAny]], 

164 FromStatement[Unpack[TupleAny]], 

165 UpdateBase, 

166 ], 

167 params: _CoreSingleExecuteParams, 

168 session: Session, 

169 load_options: Union[ 

170 Type[QueryContext.default_load_options], 

171 QueryContext.default_load_options, 

172 ], 

173 execution_options: Optional[OrmExecuteOptionsParameter] = None, 

174 bind_arguments: Optional[_BindArguments] = None, 

175 ): 

176 self.load_options = load_options 

177 self.execution_options = execution_options or util.EMPTY_DICT 

178 self.bind_arguments = bind_arguments or util.EMPTY_DICT 

179 self.compile_state = compile_state 

180 self.query = statement 

181 

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

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

184 # routines where a separate FromStatement is manufactured in the 

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

186 self.user_passed_query = user_passed_query 

187 

188 self.session = session 

189 self.loaders_require_buffering = False 

190 self.loaders_require_uniquing = False 

191 self.params = params 

192 self.top_level_context = load_options._sa_top_level_orm_context 

193 

194 cached_options = compile_state.select_statement._with_options 

195 uncached_options = user_passed_query._with_options 

196 

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

198 # propagated loader options will be present on loaded InstanceState 

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

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

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

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

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

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

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

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

207 # AliasedClass 

208 self.propagated_loader_options = tuple( 

209 opt._adapt_cached_option_to_uncached_option(self, uncached_opt) 

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

211 if opt.propagate_to_loaders 

212 ) 

213 

214 self.attributes = dict(compile_state.attributes) 

215 

216 self.autoflush = load_options._autoflush 

217 self.populate_existing = load_options._populate_existing 

218 self.invoke_all_eagers = load_options._invoke_all_eagers 

219 self.version_check = load_options._version_check 

220 self.refresh_state = load_options._refresh_state 

221 self.yield_per = load_options._yield_per 

222 self.identity_token = load_options._identity_token 

223 

224 def _get_top_level_context(self) -> QueryContext: 

225 return self.top_level_context or self 

226 

227 

228_orm_load_exec_options = util.immutabledict( 

229 {"_result_disable_adapt_to_context": True} 

230) 

231 

232 

233class _AbstractORMCompileState(CompileState): 

234 is_dml_returning = False 

235 

236 def _init_global_attributes( 

237 self, statement, compiler, *, toplevel, process_criteria_for_toplevel 

238 ): 

239 self.attributes = {} 

240 

241 if compiler is None: 

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

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

244 self.global_attributes = {} 

245 assert toplevel 

246 return 

247 else: 

248 self.global_attributes = ga = compiler._global_attributes 

249 

250 if toplevel: 

251 ga["toplevel_orm"] = True 

252 

253 if process_criteria_for_toplevel: 

254 for opt in statement._with_options: 

255 if opt._is_criteria_option: 

256 opt.process_compile_state(self) 

257 

258 return 

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

260 return 

261 

262 stack_0 = compiler.stack[0] 

263 

264 try: 

265 toplevel_stmt = stack_0["selectable"] 

266 except KeyError: 

267 pass 

268 else: 

269 for opt in toplevel_stmt._with_options: 

270 if opt._is_compile_state and opt._is_criteria_option: 

271 opt.process_compile_state(self) 

272 

273 ga["toplevel_orm"] = True 

274 

275 @classmethod 

276 def create_for_statement( 

277 cls, 

278 statement: Executable, 

279 compiler: SQLCompiler, 

280 **kw: Any, 

281 ) -> CompileState: 

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

283 

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

285 

286 For a Select object, this would be invoked from 

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

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

289 FromStatement._compiler_dispatch() that would be called by 

290 SQLCompiler.process(). 

291 """ 

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

293 

294 @classmethod 

295 def orm_pre_session_exec( 

296 cls, 

297 session, 

298 statement, 

299 params, 

300 execution_options, 

301 bind_arguments, 

302 is_pre_event, 

303 ): 

304 raise NotImplementedError() 

305 

306 @classmethod 

307 def orm_execute_statement( 

308 cls, 

309 session, 

310 statement, 

311 params, 

312 execution_options, 

313 bind_arguments, 

314 conn, 

315 ) -> Result: 

316 result = conn.execute( 

317 statement, params or {}, execution_options=execution_options 

318 ) 

319 return cls.orm_setup_cursor_result( 

320 session, 

321 statement, 

322 params, 

323 execution_options, 

324 bind_arguments, 

325 result, 

326 ) 

327 

328 @classmethod 

329 def orm_setup_cursor_result( 

330 cls, 

331 session, 

332 statement, 

333 params, 

334 execution_options, 

335 bind_arguments, 

336 result, 

337 ): 

338 raise NotImplementedError() 

339 

340 

341class _AutoflushOnlyORMCompileState(_AbstractORMCompileState): 

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

343 

344 @classmethod 

345 def orm_pre_session_exec( 

346 cls, 

347 session, 

348 statement, 

349 params, 

350 execution_options, 

351 bind_arguments, 

352 is_pre_event, 

353 ): 

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

355 # in an ORMExecuteState hook 

356 ( 

357 load_options, 

358 execution_options, 

359 ) = QueryContext.default_load_options.from_execution_options( 

360 "_sa_orm_load_options", 

361 { 

362 "autoflush", 

363 }, 

364 execution_options, 

365 statement._execution_options, 

366 ) 

367 

368 if not is_pre_event and load_options._autoflush: 

369 session._autoflush() 

370 

371 return statement, execution_options, params 

372 

373 @classmethod 

374 def orm_setup_cursor_result( 

375 cls, 

376 session, 

377 statement, 

378 params, 

379 execution_options, 

380 bind_arguments, 

381 result, 

382 ): 

383 return result 

384 

385 

386class _ORMCompileState(_AbstractORMCompileState): 

387 class default_compile_options(CacheableOptions): 

388 _cache_key_traversal = [ 

389 ("_use_legacy_query_style", InternalTraversal.dp_boolean), 

390 ("_for_statement", InternalTraversal.dp_boolean), 

391 ("_bake_ok", InternalTraversal.dp_boolean), 

392 ("_current_path", InternalTraversal.dp_has_cache_key), 

393 ("_enable_single_crit", InternalTraversal.dp_boolean), 

394 ("_enable_eagerloads", InternalTraversal.dp_boolean), 

395 ("_only_load_props", InternalTraversal.dp_plain_obj), 

396 ("_set_base_alias", InternalTraversal.dp_boolean), 

397 ("_for_refresh_state", InternalTraversal.dp_boolean), 

398 ("_render_for_subquery", InternalTraversal.dp_boolean), 

399 ("_is_star", InternalTraversal.dp_boolean), 

400 ] 

401 

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

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

404 # now this basically indicates we should use tablename_columnname 

405 # style labels. Generally indicates the statement originated 

406 # from a Query object. 

407 _use_legacy_query_style = False 

408 

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

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

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

412 _for_statement = False 

413 

414 _bake_ok = True 

415 _current_path = _path_registry 

416 _enable_single_crit = True 

417 _enable_eagerloads = True 

418 _only_load_props = None 

419 _set_base_alias = False 

420 _for_refresh_state = False 

421 _render_for_subquery = False 

422 _is_star = False 

423 

424 attributes: Dict[Any, Any] 

425 global_attributes: Dict[Any, Any] 

426 

427 statement: Union[ 

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

429 ] 

430 select_statement: Union[ 

431 Select[Unpack[TupleAny]], FromStatement[Unpack[TupleAny]] 

432 ] 

433 _entities: List[_QueryEntity] 

434 _polymorphic_adapters: Dict[_InternalEntityType, ORMAdapter] 

435 compile_options: Union[ 

436 Type[default_compile_options], default_compile_options 

437 ] 

438 _primary_entity: Optional[_QueryEntity] 

439 use_legacy_query_style: bool 

440 _label_convention: _LabelConventionCallable 

441 primary_columns: List[ColumnElement[Any]] 

442 secondary_columns: List[ColumnElement[Any]] 

443 dedupe_columns: Set[ColumnElement[Any]] 

444 create_eager_joins: List[ 

445 # TODO: this structure is set up by JoinedLoader 

446 TupleAny 

447 ] 

448 current_path: PathRegistry = _path_registry 

449 _has_mapper_entities = False 

450 

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

452 raise NotImplementedError() 

453 

454 @classmethod 

455 def create_for_statement( 

456 cls, 

457 statement: Executable, 

458 compiler: SQLCompiler, 

459 **kw: Any, 

460 ) -> _ORMCompileState: 

461 return cls._create_orm_context( 

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

463 toplevel=not compiler.stack, 

464 compiler=compiler, 

465 **kw, 

466 ) 

467 

468 @classmethod 

469 def _create_orm_context( 

470 cls, 

471 statement: Union[Select, FromStatement], 

472 *, 

473 toplevel: bool, 

474 compiler: Optional[SQLCompiler], 

475 **kw: Any, 

476 ) -> _ORMCompileState: 

477 raise NotImplementedError() 

478 

479 def _append_dedupe_col_collection(self, obj, col_collection): 

480 dedupe = self.dedupe_columns 

481 if obj not in dedupe: 

482 dedupe.add(obj) 

483 col_collection.append(obj) 

484 

485 @classmethod 

486 def _column_naming_convention( 

487 cls, label_style: SelectLabelStyle, legacy: bool 

488 ) -> _LabelConventionCallable: 

489 if legacy: 

490 

491 def name(col, col_name=None): 

492 if col_name: 

493 return col_name 

494 else: 

495 return getattr(col, "key") 

496 

497 return name 

498 else: 

499 return SelectState._column_naming_convention(label_style) 

500 

501 @classmethod 

502 def get_column_descriptions(cls, statement): 

503 return _column_descriptions(statement) 

504 

505 @classmethod 

506 def orm_pre_session_exec( 

507 cls, 

508 session, 

509 statement, 

510 params, 

511 execution_options, 

512 bind_arguments, 

513 is_pre_event, 

514 ): 

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

516 # in an ORMExecuteState hook 

517 ( 

518 load_options, 

519 execution_options, 

520 ) = QueryContext.default_load_options.from_execution_options( 

521 "_sa_orm_load_options", 

522 { 

523 "populate_existing", 

524 "autoflush", 

525 "yield_per", 

526 "identity_token", 

527 "sa_top_level_orm_context", 

528 }, 

529 execution_options, 

530 statement._execution_options, 

531 ) 

532 

533 # default execution options for ORM results: 

534 # 1. _result_disable_adapt_to_context=True 

535 # this will disable the ResultSetMetadata._adapt_to_context() 

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

537 # against the original SELECT statement before caching. 

538 

539 if "sa_top_level_orm_context" in execution_options: 

540 ctx = execution_options["sa_top_level_orm_context"] 

541 execution_options = ctx.query._execution_options.merge_with( 

542 ctx.execution_options, execution_options 

543 ) 

544 

545 if not execution_options: 

546 execution_options = _orm_load_exec_options 

547 else: 

548 execution_options = execution_options.union(_orm_load_exec_options) 

549 

550 # would have been placed here by legacy Query only 

551 if load_options._yield_per: 

552 execution_options = execution_options.union( 

553 {"yield_per": load_options._yield_per} 

554 ) 

555 

556 if ( 

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

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

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

560 ): 

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

562 execution_options.union( 

563 { 

564 "compiled_cache": None, 

565 "_cache_disable_reason": "excess depth for " 

566 "ORM loader options", 

567 } 

568 ) 

569 ) 

570 

571 bind_arguments["clause"] = statement 

572 

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

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

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

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

577 

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

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

580 # needs to be present as well. 

581 

582 try: 

583 plugin_subject = statement._propagate_attrs["plugin_subject"] 

584 except KeyError: 

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

586 else: 

587 if plugin_subject: 

588 bind_arguments["mapper"] = plugin_subject.mapper 

589 

590 if not is_pre_event and load_options._autoflush: 

591 session._autoflush() 

592 

593 return statement, execution_options, params 

594 

595 @classmethod 

596 def orm_setup_cursor_result( 

597 cls, 

598 session, 

599 statement, 

600 params, 

601 execution_options, 

602 bind_arguments, 

603 result, 

604 ): 

605 execution_context = result.context 

606 compile_state = execution_context.compiled.compile_state 

607 

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

609 # were passed to session.execute: 

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

611 # see test_query->test_legacy_tuple_old_select 

612 

613 load_options = execution_options.get( 

614 "_sa_orm_load_options", QueryContext.default_load_options 

615 ) 

616 

617 if compile_state.compile_options._is_star: 

618 return result 

619 

620 querycontext = QueryContext( 

621 compile_state, 

622 statement, 

623 statement, 

624 params, 

625 session, 

626 load_options, 

627 execution_options, 

628 bind_arguments, 

629 ) 

630 return loading.instances(result, querycontext) 

631 

632 @property 

633 def _lead_mapper_entities(self): 

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

635 

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

637 with_entities(), with_only_columns() 

638 

639 """ 

640 return [ 

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

642 ] 

643 

644 def _create_with_polymorphic_adapter(self, ext_info, selectable): 

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

646 if called for by the Mapper. 

647 

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

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

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

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

652 loading, and joined inheritance where a subquery is 

653 passed to with_polymorphic (which is completely unnecessary in modern 

654 use). 

655 

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

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

658 to an alias or subquery "legacy" ? 

659 

660 """ 

661 if ( 

662 not ext_info.is_aliased_class 

663 and ext_info.mapper.persist_selectable 

664 not in self._polymorphic_adapters 

665 ): 

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

667 self._mapper_loads_polymorphically_with( 

668 mp, 

669 ORMAdapter( 

670 _TraceAdaptRole.WITH_POLYMORPHIC_ADAPTER, 

671 mp, 

672 equivalents=mp._equivalent_columns, 

673 selectable=selectable, 

674 ), 

675 ) 

676 

677 def _mapper_loads_polymorphically_with(self, mapper, adapter): 

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

679 self._polymorphic_adapters[m2] = adapter 

680 

681 for m in m2.iterate_to_root(): 

682 self._polymorphic_adapters[m.local_table] = adapter 

683 

684 @classmethod 

685 def _create_entities_collection(cls, query, legacy): 

686 raise NotImplementedError( 

687 "this method only works for ORMSelectCompileState" 

688 ) 

689 

690 

691class _DMLReturningColFilter: 

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

693 

694 Has a subset of the interface used by 

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

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

697 DML statement. 

698 

699 """ 

700 

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

702 

703 def __init__(self, target_mapper, immediate_dml_mapper): 

704 if ( 

705 immediate_dml_mapper is not None 

706 and target_mapper.local_table 

707 is not immediate_dml_mapper.local_table 

708 ): 

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

710 self.mapper = immediate_dml_mapper 

711 else: 

712 # single inh, normal mappings, etc. 

713 self.mapper = target_mapper 

714 self.columns = self.columns = util.WeakPopulateDict( 

715 self.adapt_check_present # type: ignore 

716 ) 

717 

718 def __call__(self, col, as_filter): 

719 for cc in sql_util._find_columns(col): 

720 c2 = self.adapt_check_present(cc) 

721 if c2 is not None: 

722 return col 

723 else: 

724 return None 

725 

726 def adapt_check_present(self, col): 

727 raise NotImplementedError() 

728 

729 

730class _DMLBulkInsertReturningColFilter(_DMLReturningColFilter): 

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

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

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

734 example right now) 

735 

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

737 mapped table in a hierarchy. 

738 

739 """ 

740 

741 def adapt_check_present(self, col): 

742 mapper = self.mapper 

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

744 if prop is None: 

745 return None 

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

747 

748 

749class _DMLUpdateDeleteReturningColFilter(_DMLReturningColFilter): 

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

751 for ORM enabled UPDATE/DELETE 

752 

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

754 only direct persisted columns from the immediate selectable, not 

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

756 mappers for the UPDATE..FROM use case. 

757 

758 """ 

759 

760 def adapt_check_present(self, col): 

761 mapper = self.mapper 

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

763 if prop is not None: 

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

765 # column, not any kind of column_property expression 

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

767 

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

769 # user knows what they are doing 

770 return col 

771 

772 

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

774class _ORMFromStatementCompileState(_ORMCompileState): 

775 _from_obj_alias = None 

776 _has_mapper_entities = False 

777 

778 statement_container: FromStatement 

779 requested_statement: Union[SelectBase, TextClause, UpdateBase] 

780 dml_table: Optional[_DMLTableElement] = None 

781 

782 _has_orm_entities = False 

783 multi_row_eager_loaders = False 

784 eager_adding_joins = False 

785 compound_eager_adapter = None 

786 

787 extra_criteria_entities = util.EMPTY_DICT 

788 eager_joins = util.EMPTY_DICT 

789 

790 @classmethod 

791 def _create_orm_context( 

792 cls, 

793 statement: Union[Select, FromStatement], 

794 *, 

795 toplevel: bool, 

796 compiler: Optional[SQLCompiler], 

797 **kw: Any, 

798 ) -> _ORMFromStatementCompileState: 

799 statement_container = statement 

800 

801 assert isinstance(statement_container, FromStatement) 

802 

803 if compiler is not None and compiler.stack: 

804 raise sa_exc.CompileError( 

805 "The ORM FromStatement construct only supports being " 

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

807 "define how result rows should be returned." 

808 ) 

809 

810 self = cls.__new__(cls) 

811 self._primary_entity = None 

812 

813 self.use_legacy_query_style = ( 

814 statement_container._compile_options._use_legacy_query_style 

815 ) 

816 self.statement_container = self.select_statement = statement_container 

817 self.requested_statement = statement = statement_container.element 

818 

819 if statement.is_dml: 

820 self.dml_table = statement.table 

821 self.is_dml_returning = True 

822 

823 self._entities = [] 

824 self._polymorphic_adapters = {} 

825 

826 self.compile_options = statement_container._compile_options 

827 

828 if ( 

829 self.use_legacy_query_style 

830 and isinstance(statement, expression.SelectBase) 

831 and not statement._is_textual 

832 and not statement.is_dml 

833 and statement._label_style is LABEL_STYLE_NONE 

834 ): 

835 self.statement = statement.set_label_style( 

836 LABEL_STYLE_TABLENAME_PLUS_COL 

837 ) 

838 else: 

839 self.statement = statement 

840 

841 self._label_convention = self._column_naming_convention( 

842 ( 

843 statement._label_style 

844 if not statement._is_textual and not statement.is_dml 

845 else LABEL_STYLE_NONE 

846 ), 

847 self.use_legacy_query_style, 

848 ) 

849 

850 _QueryEntity.to_compile_state( 

851 self, 

852 statement_container._raw_columns, 

853 self._entities, 

854 is_current_entities=True, 

855 ) 

856 

857 self.current_path = statement_container._compile_options._current_path 

858 

859 self._init_global_attributes( 

860 statement_container, 

861 compiler, 

862 process_criteria_for_toplevel=False, 

863 toplevel=True, 

864 ) 

865 

866 if statement_container._with_options: 

867 for opt in statement_container._with_options: 

868 if opt._is_compile_state: 

869 opt.process_compile_state(self) 

870 

871 if statement_container._compile_state_funcs: 

872 for fn, key in statement_container._compile_state_funcs: 

873 fn(self) 

874 

875 self.primary_columns = [] 

876 self.secondary_columns = [] 

877 self.dedupe_columns = set() 

878 self.create_eager_joins = [] 

879 self._fallback_from_clauses = [] 

880 

881 self.order_by = None 

882 

883 if self.statement._is_text_clause: 

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

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

886 # _QueryEntity objects, then flip on all the 

887 # "please match no matter what" parameters. 

888 self.extra_criteria_entities = {} 

889 

890 for entity in self._entities: 

891 entity.setup_compile_state(self) 

892 

893 compiler._ordered_columns = compiler._textual_ordered_columns = ( 

894 False 

895 ) 

896 

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

898 # needed by test_query.py::TextTest 

899 compiler._loose_column_name_matching = True 

900 

901 for c in self.primary_columns: 

902 compiler.process( 

903 c, 

904 within_columns_clause=True, 

905 add_to_result_map=compiler._add_to_result_map, 

906 ) 

907 else: 

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

909 # have column objects already. After much 

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

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

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

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

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

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

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

917 self._from_obj_alias = ORMStatementAdapter( 

918 _TraceAdaptRole.ADAPT_FROM_STATEMENT, 

919 self.statement, 

920 adapt_on_names=statement_container._adapt_on_names, 

921 ) 

922 

923 return self 

924 

925 def _adapt_col_list(self, cols, current_adapter): 

926 return cols 

927 

928 def _get_current_adapter(self): 

929 return None 

930 

931 def setup_dml_returning_compile_state(self, dml_mapper): 

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

933 for RETURNING to return ORM objects and expressions 

934 

935 """ 

936 target_mapper = self.statement._propagate_attrs.get( 

937 "plugin_subject", None 

938 ) 

939 

940 if self.statement.is_insert: 

941 adapter = _DMLBulkInsertReturningColFilter( 

942 target_mapper, dml_mapper 

943 ) 

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

945 adapter = _DMLUpdateDeleteReturningColFilter( 

946 target_mapper, dml_mapper 

947 ) 

948 else: 

949 adapter = None 

950 

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

952 raise sa_exc.CompileError( 

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

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

955 ) 

956 

957 for entity in self._entities: 

958 entity.setup_dml_returning_compile_state(self, adapter) 

959 

960 

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

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

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

964 

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

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

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

968 

969 """ 

970 

971 __visit_name__ = "orm_from_statement" 

972 

973 _compile_options = _ORMFromStatementCompileState.default_compile_options 

974 

975 _compile_state_factory = _ORMFromStatementCompileState.create_for_statement 

976 

977 _for_update_arg = None 

978 

979 element: Union[ExecutableReturnsRows, TextClause] 

980 

981 _adapt_on_names: bool 

982 

983 _traverse_internals = [ 

984 ("_raw_columns", InternalTraversal.dp_clauseelement_list), 

985 ("element", InternalTraversal.dp_clauseelement), 

986 ] + ExecutableStatement._executable_traverse_internals 

987 

988 _cache_key_traversal = _traverse_internals + [ 

989 ("_compile_options", InternalTraversal.dp_has_cache_key) 

990 ] 

991 

992 is_from_statement = True 

993 

994 def __init__( 

995 self, 

996 entities: Iterable[_ColumnsClauseArgument[Any]], 

997 element: Union[ExecutableReturnsRows, TextClause], 

998 _adapt_on_names: bool = True, 

999 ): 

1000 self._raw_columns = [ 

1001 coercions.expect( 

1002 roles.ColumnsClauseRole, 

1003 ent, 

1004 apply_propagate_attrs=self, 

1005 post_inspect=True, 

1006 ) 

1007 for ent in util.to_list(entities) 

1008 ] 

1009 self.element = element 

1010 self.is_dml = element.is_dml 

1011 self.is_select = element.is_select 

1012 self.is_delete = element.is_delete 

1013 self.is_insert = element.is_insert 

1014 self.is_update = element.is_update 

1015 self._label_style = ( 

1016 element._label_style if is_select_base(element) else None 

1017 ) 

1018 self._adapt_on_names = _adapt_on_names 

1019 

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

1021 """provide a fixed _compiler_dispatch method. 

1022 

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

1024 ``@compiles`` extension. 

1025 

1026 """ 

1027 

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

1029 

1030 toplevel = not compiler.stack 

1031 

1032 if toplevel: 

1033 compiler.compile_state = compile_state 

1034 

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

1036 

1037 @property 

1038 def column_descriptions(self): 

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

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

1041 

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

1043 of this feature. 

1044 

1045 .. seealso:: 

1046 

1047 :ref:`queryguide_inspection` - ORM background 

1048 

1049 """ 

1050 meth = cast( 

1051 _ORMSelectCompileState, SelectState.get_plugin_class(self) 

1052 ).get_column_descriptions 

1053 return meth(self) 

1054 

1055 def _ensure_disambiguated_names(self): 

1056 return self 

1057 

1058 def get_children(self, **kw): 

1059 yield from itertools.chain.from_iterable( 

1060 element._from_objects for element in self._raw_columns 

1061 ) 

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

1063 

1064 @property 

1065 def _all_selected_columns(self): 

1066 return self.element._all_selected_columns 

1067 

1068 @property 

1069 def _return_defaults(self): 

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

1071 

1072 @property 

1073 def _returning(self): 

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

1075 

1076 @property 

1077 def _inline(self): 

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

1079 

1080 

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

1082class _CompoundSelectCompileState( 

1083 _AutoflushOnlyORMCompileState, CompoundSelectState 

1084): 

1085 pass 

1086 

1087 

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

1089class _ORMSelectCompileState(_ORMCompileState, SelectState): 

1090 _already_joined_edges = () 

1091 

1092 _memoized_entities = util.EMPTY_DICT 

1093 

1094 _from_obj_alias = None 

1095 _has_mapper_entities = False 

1096 

1097 _has_orm_entities = False 

1098 multi_row_eager_loaders = False 

1099 eager_adding_joins = False 

1100 compound_eager_adapter = None 

1101 

1102 correlate = None 

1103 correlate_except = None 

1104 _where_criteria = () 

1105 _having_criteria = () 

1106 

1107 @classmethod 

1108 def _create_orm_context( 

1109 cls, 

1110 statement: Union[Select, FromStatement], 

1111 *, 

1112 toplevel: bool, 

1113 compiler: Optional[SQLCompiler], 

1114 **kw: Any, 

1115 ) -> _ORMSelectCompileState: 

1116 

1117 self = cls.__new__(cls) 

1118 

1119 select_statement = statement 

1120 

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

1122 # have ORM level compile options. 

1123 statement._compile_options = cls.default_compile_options.safe_merge( 

1124 statement._compile_options 

1125 ) 

1126 

1127 if select_statement._execution_options: 

1128 # execution options should not impact the compilation of a 

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

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

1131 self.select_statement = select_statement._clone() 

1132 self.select_statement._execution_options = util.EMPTY_DICT 

1133 else: 

1134 self.select_statement = select_statement 

1135 

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

1137 self.for_statement = select_statement._compile_options._for_statement 

1138 

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

1140 self.use_legacy_query_style = ( 

1141 select_statement._compile_options._use_legacy_query_style 

1142 ) 

1143 

1144 self._entities = [] 

1145 self._primary_entity = None 

1146 self._polymorphic_adapters = {} 

1147 

1148 self.compile_options = select_statement._compile_options 

1149 

1150 if not toplevel: 

1151 # for subqueries, turn off eagerloads and set 

1152 # "render_for_subquery". 

1153 self.compile_options += { 

1154 "_enable_eagerloads": False, 

1155 "_render_for_subquery": True, 

1156 } 

1157 

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

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

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

1161 # for new style ORM selects too. 

1162 if ( 

1163 self.use_legacy_query_style 

1164 and self.select_statement._label_style is LABEL_STYLE_LEGACY_ORM 

1165 ): 

1166 if not self.for_statement: 

1167 self.label_style = LABEL_STYLE_TABLENAME_PLUS_COL 

1168 else: 

1169 self.label_style = LABEL_STYLE_DISAMBIGUATE_ONLY 

1170 else: 

1171 self.label_style = self.select_statement._label_style 

1172 

1173 if select_statement._memoized_select_entities: 

1174 self._memoized_entities = { 

1175 memoized_entities: _QueryEntity.to_compile_state( 

1176 self, 

1177 memoized_entities._raw_columns, 

1178 [], 

1179 is_current_entities=False, 

1180 ) 

1181 for memoized_entities in ( 

1182 select_statement._memoized_select_entities 

1183 ) 

1184 } 

1185 

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

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

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

1189 # in the columns clause 

1190 self._label_convention = self._column_naming_convention( 

1191 statement._label_style, self.use_legacy_query_style 

1192 ) 

1193 

1194 _QueryEntity.to_compile_state( 

1195 self, 

1196 select_statement._raw_columns, 

1197 self._entities, 

1198 is_current_entities=True, 

1199 ) 

1200 

1201 self.current_path = select_statement._compile_options._current_path 

1202 

1203 self.eager_order_by = () 

1204 

1205 self._init_global_attributes( 

1206 select_statement, 

1207 compiler, 

1208 toplevel=toplevel, 

1209 process_criteria_for_toplevel=False, 

1210 ) 

1211 

1212 if toplevel and ( 

1213 select_statement._with_options 

1214 or select_statement._memoized_select_entities 

1215 ): 

1216 for ( 

1217 memoized_entities 

1218 ) in select_statement._memoized_select_entities: 

1219 for opt in memoized_entities._with_options: 

1220 if opt._is_compile_state: 

1221 opt.process_compile_state_replaced_entities( 

1222 self, 

1223 [ 

1224 ent 

1225 for ent in self._memoized_entities[ 

1226 memoized_entities 

1227 ] 

1228 if isinstance(ent, _MapperEntity) 

1229 ], 

1230 ) 

1231 

1232 for opt in self.select_statement._with_options: 

1233 if opt._is_compile_state: 

1234 opt.process_compile_state(self) 

1235 

1236 # uncomment to print out the context.attributes structure 

1237 # after it's been set up above 

1238 # self._dump_option_struct() 

1239 

1240 if select_statement._compile_state_funcs: 

1241 for fn, key in select_statement._compile_state_funcs: 

1242 fn(self) 

1243 

1244 self.primary_columns = [] 

1245 self.secondary_columns = [] 

1246 self.dedupe_columns = set() 

1247 self.eager_joins = {} 

1248 self.extra_criteria_entities = {} 

1249 self.create_eager_joins = [] 

1250 self._fallback_from_clauses = [] 

1251 

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

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

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

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

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

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

1258 self.from_clauses = self._normalize_froms( 

1259 info.selectable for info in select_statement._from_obj 

1260 ) 

1261 

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

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

1264 # and _setup_for_generate into three or four logical sections 

1265 self._setup_for_generate() 

1266 

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

1268 return self 

1269 

1270 def _dump_option_struct(self): 

1271 print("\n---------------------------------------------------\n") 

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

1273 for key in self.attributes: 

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

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

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

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

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

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

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

1281 

1282 def _setup_for_generate(self): 

1283 query = self.select_statement 

1284 

1285 self.statement = None 

1286 self._join_entities = () 

1287 

1288 if self.compile_options._set_base_alias: 

1289 # legacy Query only 

1290 self._set_select_from_alias() 

1291 

1292 for memoized_entities in query._memoized_select_entities: 

1293 if memoized_entities._setup_joins: 

1294 self._join( 

1295 memoized_entities._setup_joins, 

1296 self._memoized_entities[memoized_entities], 

1297 ) 

1298 

1299 if query._setup_joins: 

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

1301 

1302 current_adapter = self._get_current_adapter() 

1303 

1304 if query._where_criteria: 

1305 self._where_criteria = query._where_criteria 

1306 

1307 if current_adapter: 

1308 self._where_criteria = tuple( 

1309 current_adapter(crit, True) 

1310 for crit in self._where_criteria 

1311 ) 

1312 

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

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

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

1316 self.order_by = ( 

1317 self._adapt_col_list(query._order_by_clauses, current_adapter) 

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

1319 else query._order_by_clauses 

1320 ) 

1321 

1322 if query._having_criteria: 

1323 self._having_criteria = tuple( 

1324 current_adapter(crit, True) if current_adapter else crit 

1325 for crit in query._having_criteria 

1326 ) 

1327 

1328 self.group_by = ( 

1329 self._adapt_col_list( 

1330 util.flatten_iterator(query._group_by_clauses), current_adapter 

1331 ) 

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

1333 else query._group_by_clauses or None 

1334 ) 

1335 

1336 if self.eager_order_by: 

1337 adapter = self.from_clauses[0]._target_adapter 

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

1339 

1340 if query._distinct_on: 

1341 self.distinct_on = self._adapt_col_list( 

1342 query._distinct_on, current_adapter 

1343 ) 

1344 else: 

1345 self.distinct_on = () 

1346 

1347 self.distinct = query._distinct 

1348 

1349 self.syntax_extensions = { 

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

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

1352 } 

1353 

1354 if query._correlate: 

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

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

1357 # tables. 

1358 self.correlate = tuple( 

1359 util.flatten_iterator( 

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

1361 for s in query._correlate 

1362 ) 

1363 ) 

1364 elif query._correlate_except is not None: 

1365 self.correlate_except = tuple( 

1366 util.flatten_iterator( 

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

1368 for s in query._correlate_except 

1369 ) 

1370 ) 

1371 elif not query._auto_correlate: 

1372 self.correlate = (None,) 

1373 

1374 # PART II 

1375 

1376 self._for_update_arg = query._for_update_arg 

1377 

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

1379 raise sa_exc.CompileError( 

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

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

1382 ) 

1383 for entity in self._entities: 

1384 entity.setup_compile_state(self) 

1385 

1386 for rec in self.create_eager_joins: 

1387 strategy = rec[0] 

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

1389 

1390 # else "load from discrete FROMs" mode, 

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

1392 

1393 if self.compile_options._enable_single_crit: 

1394 self._adjust_for_extra_criteria() 

1395 

1396 if not self.primary_columns: 

1397 if self.compile_options._only_load_props: 

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

1399 

1400 raise sa_exc.InvalidRequestError( 

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

1402 ) 

1403 

1404 if not self.from_clauses: 

1405 self.from_clauses = list(self._fallback_from_clauses) 

1406 

1407 if self.order_by is False: 

1408 self.order_by = None 

1409 

1410 if self._should_nest_selectable: 

1411 self.statement = self._compound_eager_statement() 

1412 else: 

1413 self.statement = self._simple_statement() 

1414 

1415 if self.for_statement: 

1416 ezero = self._mapper_zero() 

1417 if ezero is not None: 

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

1419 # thing 

1420 self.statement = self.statement._annotate( 

1421 {"deepentity": ezero} 

1422 ) 

1423 

1424 @classmethod 

1425 def _create_entities_collection(cls, query, legacy): 

1426 """Creates a partial ORMSelectCompileState that includes 

1427 the full collection of _MapperEntity and other _QueryEntity objects. 

1428 

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

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

1431 

1432 """ 

1433 self = cls.__new__(cls) 

1434 

1435 self._entities = [] 

1436 self._primary_entity = None 

1437 self._polymorphic_adapters = {} 

1438 

1439 self._label_convention = self._column_naming_convention( 

1440 query._label_style, legacy 

1441 ) 

1442 

1443 # entities will also set up polymorphic adapters for mappers 

1444 # that have with_polymorphic configured 

1445 _QueryEntity.to_compile_state( 

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

1447 ) 

1448 return self 

1449 

1450 @classmethod 

1451 def _get_filter_by_entities(cls, statement): 

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

1453 

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

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

1456 directives. 

1457 

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

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

1460 how error messages regarding ambiguous entities or entity not 

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

1462 column_property() etc. work either way since 

1463 _entity_namespace_key_search_all() uses _entity_namespace(). 

1464 

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

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

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

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

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

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

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

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

1473 

1474 .. versionadded:: 2.1 

1475 """ 

1476 

1477 def _setup_join_targets(collection): 

1478 for (target, *_) in collection: 

1479 if isinstance(target, attributes.QueryableAttribute): 

1480 yield target.entity 

1481 elif "_no_filter_by" not in target._annotations: 

1482 yield target 

1483 

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

1485 

1486 for memoized in statement._memoized_select_entities: 

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

1488 

1489 entities.update( 

1490 ( 

1491 from_obj._annotations["parententity"] 

1492 if "parententity" in from_obj._annotations 

1493 else from_obj 

1494 ) 

1495 for from_obj in statement._from_obj 

1496 if "_no_filter_by" not in from_obj._annotations 

1497 ) 

1498 

1499 for element in statement._raw_columns: 

1500 if "entity_namespace" in element._annotations: 

1501 ens = element._annotations["entity_namespace"] 

1502 entities.add(ens) 

1503 elif "_no_filter_by" not in element._annotations: 

1504 entities.update(element._from_objects) 

1505 

1506 return entities 

1507 

1508 @classmethod 

1509 def all_selected_columns(cls, statement): 

1510 for element in statement._raw_columns: 

1511 if ( 

1512 element.is_selectable 

1513 and "entity_namespace" in element._annotations 

1514 ): 

1515 ens = element._annotations["entity_namespace"] 

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

1517 yield from _select_iterables([element]) 

1518 else: 

1519 yield from _select_iterables(ens._all_column_expressions) 

1520 else: 

1521 yield from _select_iterables([element]) 

1522 

1523 @classmethod 

1524 def get_columns_clause_froms(cls, statement): 

1525 return cls._normalize_froms( 

1526 itertools.chain.from_iterable( 

1527 ( 

1528 element._from_objects 

1529 if "parententity" not in element._annotations 

1530 else [ 

1531 element._annotations[ 

1532 "parententity" 

1533 ].__clause_element__() 

1534 ] 

1535 ) 

1536 for element in statement._raw_columns 

1537 ) 

1538 ) 

1539 

1540 @classmethod 

1541 def from_statement(cls, statement, from_statement): 

1542 from_statement = coercions.expect( 

1543 roles.ReturnsRowsRole, 

1544 from_statement, 

1545 apply_propagate_attrs=statement, 

1546 ) 

1547 

1548 stmt = FromStatement(statement._raw_columns, from_statement) 

1549 

1550 stmt.__dict__.update( 

1551 _with_options=statement._with_options, 

1552 _compile_state_funcs=statement._compile_state_funcs, 

1553 _execution_options=statement._execution_options, 

1554 _propagate_attrs=statement._propagate_attrs, 

1555 ) 

1556 return stmt 

1557 

1558 def _set_select_from_alias(self): 

1559 """used only for legacy Query cases""" 

1560 

1561 query = self.select_statement # query 

1562 

1563 assert self.compile_options._set_base_alias 

1564 assert len(query._from_obj) == 1 

1565 

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

1567 if adapter: 

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

1569 self._from_obj_alias = adapter 

1570 

1571 def _get_select_from_alias_from_obj(self, from_obj): 

1572 """used only for legacy Query cases""" 

1573 

1574 info = from_obj 

1575 

1576 if "parententity" in info._annotations: 

1577 info = info._annotations["parententity"] 

1578 

1579 if hasattr(info, "mapper"): 

1580 if not info.is_aliased_class: 

1581 raise sa_exc.ArgumentError( 

1582 "A selectable (FromClause) instance is " 

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

1584 ) 

1585 else: 

1586 return info._adapter 

1587 

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

1589 equivs = self._all_equivs() 

1590 assert info is info.selectable 

1591 return ORMStatementAdapter( 

1592 _TraceAdaptRole.LEGACY_SELECT_FROM_ALIAS, 

1593 info.selectable, 

1594 equivalents=equivs, 

1595 ) 

1596 else: 

1597 return None 

1598 

1599 def _mapper_zero(self): 

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

1601 return self._entities[0].mapper 

1602 

1603 def _entity_zero(self): 

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

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

1606 entity if specified.""" 

1607 

1608 for ent in self.from_clauses: 

1609 if "parententity" in ent._annotations: 

1610 return ent._annotations["parententity"] 

1611 for qent in self._entities: 

1612 if qent.entity_zero: 

1613 return qent.entity_zero 

1614 

1615 return None 

1616 

1617 def _only_full_mapper_zero(self, methname): 

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

1619 raise sa_exc.InvalidRequestError( 

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

1621 "a single mapped class." % methname 

1622 ) 

1623 return self._primary_entity.entity_zero 

1624 

1625 def _only_entity_zero(self, rationale=None): 

1626 if len(self._entities) > 1: 

1627 raise sa_exc.InvalidRequestError( 

1628 rationale 

1629 or "This operation requires a Query " 

1630 "against a single mapper." 

1631 ) 

1632 return self._entity_zero() 

1633 

1634 def _all_equivs(self): 

1635 equivs = {} 

1636 

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

1638 for ent in [ 

1639 ent 

1640 for ent in memoized_entities 

1641 if isinstance(ent, _MapperEntity) 

1642 ]: 

1643 equivs.update(ent.mapper._equivalent_columns) 

1644 

1645 for ent in [ 

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

1647 ]: 

1648 equivs.update(ent.mapper._equivalent_columns) 

1649 return equivs 

1650 

1651 def _compound_eager_statement(self): 

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

1653 # wrap the query inside a select, 

1654 # then append eager joins onto that 

1655 

1656 if self.order_by: 

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

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

1659 # elements are converted into label references. For the 

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

1661 # the original expressions outside of the label references 

1662 # in order to have them render. 

1663 unwrapped_order_by = [ 

1664 ( 

1665 elem.element 

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

1667 else elem 

1668 ) 

1669 for elem in self.order_by 

1670 ] 

1671 

1672 order_by_col_expr = sql_util.expand_column_list_from_order_by( 

1673 self.primary_columns, unwrapped_order_by 

1674 ) 

1675 else: 

1676 order_by_col_expr = [] 

1677 unwrapped_order_by = None 

1678 

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

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

1681 inner = self._select_statement( 

1682 self.primary_columns 

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

1684 self.from_clauses, 

1685 self._where_criteria, 

1686 self._having_criteria, 

1687 self.label_style, 

1688 self.order_by, 

1689 for_update=self._for_update_arg, 

1690 hints=self.select_statement._hints, 

1691 statement_hints=self.select_statement._statement_hints, 

1692 correlate=self.correlate, 

1693 correlate_except=self.correlate_except, 

1694 **self._select_args, 

1695 ) 

1696 

1697 inner = inner.alias() 

1698 

1699 equivs = self._all_equivs() 

1700 

1701 self.compound_eager_adapter = ORMStatementAdapter( 

1702 _TraceAdaptRole.COMPOUND_EAGER_STATEMENT, inner, equivalents=equivs 

1703 ) 

1704 

1705 statement = future.select( 

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

1707 ) 

1708 statement._label_style = self.label_style 

1709 

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

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

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

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

1714 if ( 

1715 self._for_update_arg is not None 

1716 and self._for_update_arg.of is None 

1717 ): 

1718 statement._for_update_arg = self._for_update_arg 

1719 

1720 from_clause = inner 

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

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

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

1724 # the join should be 

1725 from_clause = sql_util.splice_joins( 

1726 from_clause, eager_join, eager_join.stop_on 

1727 ) 

1728 

1729 statement.select_from.non_generative(statement, from_clause) 

1730 

1731 if unwrapped_order_by: 

1732 statement.order_by.non_generative( 

1733 statement, 

1734 *self.compound_eager_adapter.copy_and_process( 

1735 unwrapped_order_by 

1736 ), 

1737 ) 

1738 

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

1740 return statement 

1741 

1742 def _simple_statement(self): 

1743 statement = self._select_statement( 

1744 self.primary_columns + self.secondary_columns, 

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

1746 self._where_criteria, 

1747 self._having_criteria, 

1748 self.label_style, 

1749 self.order_by, 

1750 for_update=self._for_update_arg, 

1751 hints=self.select_statement._hints, 

1752 statement_hints=self.select_statement._statement_hints, 

1753 correlate=self.correlate, 

1754 correlate_except=self.correlate_except, 

1755 **self._select_args, 

1756 ) 

1757 

1758 if self.eager_order_by: 

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

1760 return statement 

1761 

1762 def _select_statement( 

1763 self, 

1764 raw_columns, 

1765 from_obj, 

1766 where_criteria, 

1767 having_criteria, 

1768 label_style, 

1769 order_by, 

1770 for_update, 

1771 hints, 

1772 statement_hints, 

1773 correlate, 

1774 correlate_except, 

1775 limit_clause, 

1776 offset_clause, 

1777 fetch_clause, 

1778 fetch_clause_options, 

1779 distinct, 

1780 distinct_on, 

1781 prefixes, 

1782 suffixes, 

1783 group_by, 

1784 independent_ctes, 

1785 independent_ctes_opts, 

1786 syntax_extensions, 

1787 ): 

1788 statement = Select._create_raw_select( 

1789 _raw_columns=raw_columns, 

1790 _from_obj=from_obj, 

1791 _label_style=label_style, 

1792 ) 

1793 

1794 if where_criteria: 

1795 statement._where_criteria = where_criteria 

1796 if having_criteria: 

1797 statement._having_criteria = having_criteria 

1798 

1799 if order_by: 

1800 statement._order_by_clauses += tuple(order_by) 

1801 

1802 if distinct_on: 

1803 statement._distinct = True 

1804 statement._distinct_on = distinct_on 

1805 elif distinct: 

1806 statement._distinct = True 

1807 

1808 if group_by: 

1809 statement._group_by_clauses += tuple(group_by) 

1810 

1811 statement._limit_clause = limit_clause 

1812 statement._offset_clause = offset_clause 

1813 statement._fetch_clause = fetch_clause 

1814 statement._fetch_clause_options = fetch_clause_options 

1815 statement._independent_ctes = independent_ctes 

1816 statement._independent_ctes_opts = independent_ctes_opts 

1817 if syntax_extensions: 

1818 statement._set_syntax_extensions(**syntax_extensions) 

1819 

1820 if prefixes: 

1821 statement._prefixes = prefixes 

1822 

1823 if suffixes: 

1824 statement._suffixes = suffixes 

1825 

1826 statement._for_update_arg = for_update 

1827 

1828 if hints: 

1829 statement._hints = hints 

1830 if statement_hints: 

1831 statement._statement_hints = statement_hints 

1832 

1833 if correlate: 

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

1835 

1836 if correlate_except is not None: 

1837 statement.correlate_except.non_generative( 

1838 statement, *correlate_except 

1839 ) 

1840 

1841 return statement 

1842 

1843 def _adapt_polymorphic_element(self, element): 

1844 if "parententity" in element._annotations: 

1845 search = element._annotations["parententity"] 

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

1847 if alias: 

1848 return alias.adapt_clause(element) 

1849 

1850 if isinstance(element, expression.FromClause): 

1851 search = element 

1852 elif hasattr(element, "table"): 

1853 search = element.table 

1854 else: 

1855 return None 

1856 

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

1858 if alias: 

1859 return alias.adapt_clause(element) 

1860 

1861 def _adapt_col_list(self, cols, current_adapter): 

1862 if current_adapter: 

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

1864 else: 

1865 return cols 

1866 

1867 def _get_current_adapter(self): 

1868 adapters = [] 

1869 

1870 if self._from_obj_alias: 

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

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

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

1874 # select_entity_from() 

1875 # 

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

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

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

1879 # to all SQL constructs. 

1880 adapters.append( 

1881 self._from_obj_alias.replace, 

1882 ) 

1883 

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

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

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

1887 if self._polymorphic_adapters: 

1888 adapters.append(self._adapt_polymorphic_element) 

1889 

1890 if not adapters: 

1891 return None 

1892 

1893 def _adapt_clause(clause, as_filter): 

1894 # do we adapt all expression elements or only those 

1895 # tagged as 'ORM' constructs ? 

1896 

1897 def replace(elem): 

1898 for adapter in adapters: 

1899 e = adapter(elem) 

1900 if e is not None: 

1901 return e 

1902 

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

1904 

1905 return _adapt_clause 

1906 

1907 def _join(self, args, entities_collection): 

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

1909 isouter = flags["isouter"] 

1910 full = flags["full"] 

1911 

1912 right = inspect(right) 

1913 if onclause is not None: 

1914 onclause = inspect(onclause) 

1915 

1916 if isinstance(right, interfaces.PropComparator): 

1917 if onclause is not None: 

1918 raise sa_exc.InvalidRequestError( 

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

1920 "to a relationship path as a target" 

1921 ) 

1922 

1923 onclause = right 

1924 right = None 

1925 elif "parententity" in right._annotations: 

1926 right = right._annotations["parententity"] 

1927 

1928 if onclause is None: 

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

1930 raise sa_exc.ArgumentError( 

1931 "Expected mapped entity or " 

1932 "selectable/table as join target" 

1933 ) 

1934 

1935 if isinstance(onclause, interfaces.PropComparator): 

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

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

1938 

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

1940 

1941 if right is None: 

1942 if of_type: 

1943 right = of_type 

1944 else: 

1945 right = onclause.property 

1946 

1947 try: 

1948 right = right.entity 

1949 except AttributeError as err: 

1950 raise sa_exc.ArgumentError( 

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

1952 "mapped entity" % right 

1953 ) from err 

1954 

1955 left = onclause._parententity 

1956 

1957 prop = onclause.property 

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

1959 onclause = prop 

1960 

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

1962 # case. 

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

1964 continue 

1965 

1966 if from_ is not None: 

1967 if ( 

1968 from_ is not left 

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

1970 is not left 

1971 ): 

1972 raise sa_exc.InvalidRequestError( 

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

1974 "of relationship attribute %s" 

1975 % ( 

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

1977 onclause, 

1978 ) 

1979 ) 

1980 elif from_ is not None: 

1981 prop = None 

1982 left = from_ 

1983 else: 

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

1985 # what the effective "left" side is 

1986 prop = left = None 

1987 

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

1989 # ORMJoin to add to our _from_obj tuple 

1990 self._join_left_to_right( 

1991 entities_collection, 

1992 left, 

1993 right, 

1994 onclause, 

1995 prop, 

1996 isouter, 

1997 full, 

1998 ) 

1999 

2000 def _join_left_to_right( 

2001 self, 

2002 entities_collection, 

2003 left, 

2004 right, 

2005 onclause, 

2006 prop, 

2007 outerjoin, 

2008 full, 

2009 ): 

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

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

2012 our _from_obj list (or augment an existing one) 

2013 

2014 """ 

2015 

2016 explicit_left = left 

2017 if left is None: 

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

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

2020 # entities 

2021 assert prop is None 

2022 ( 

2023 left, 

2024 replace_from_obj_index, 

2025 use_entity_index, 

2026 ) = self._join_determine_implicit_left_side( 

2027 entities_collection, left, right, onclause 

2028 ) 

2029 else: 

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

2031 # Determine where in our 

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

2033 # existing entity it corresponds to. 

2034 ( 

2035 replace_from_obj_index, 

2036 use_entity_index, 

2037 ) = self._join_place_explicit_left_side(entities_collection, left) 

2038 

2039 if left is right: 

2040 raise sa_exc.InvalidRequestError( 

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

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

2043 ) 

2044 

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

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

2047 # get back the new effective "right" side 

2048 r_info, right, onclause = self._join_check_and_adapt_right_side( 

2049 left, right, onclause, prop 

2050 ) 

2051 

2052 if not r_info.is_selectable: 

2053 extra_criteria = self._get_extra_criteria(r_info) 

2054 else: 

2055 extra_criteria = () 

2056 

2057 if replace_from_obj_index is not None: 

2058 # splice into an existing element in the 

2059 # self._from_obj list 

2060 left_clause = self.from_clauses[replace_from_obj_index] 

2061 

2062 if explicit_left is not None and onclause is None: 

2063 onclause = _ORMJoin._join_condition(explicit_left, right) 

2064 

2065 self.from_clauses = ( 

2066 self.from_clauses[:replace_from_obj_index] 

2067 + [ 

2068 _ORMJoin( 

2069 left_clause, 

2070 right, 

2071 onclause, 

2072 isouter=outerjoin, 

2073 full=full, 

2074 _extra_criteria=extra_criteria, 

2075 ) 

2076 ] 

2077 + self.from_clauses[replace_from_obj_index + 1 :] 

2078 ) 

2079 else: 

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

2081 if use_entity_index is not None: 

2082 # make use of _MapperEntity selectable, which is usually 

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

2084 # might be distinct 

2085 assert isinstance( 

2086 entities_collection[use_entity_index], _MapperEntity 

2087 ) 

2088 left_clause = entities_collection[use_entity_index].selectable 

2089 else: 

2090 left_clause = left 

2091 

2092 self.from_clauses = self.from_clauses + [ 

2093 _ORMJoin( 

2094 left_clause, 

2095 r_info, 

2096 onclause, 

2097 isouter=outerjoin, 

2098 full=full, 

2099 _extra_criteria=extra_criteria, 

2100 ) 

2101 ] 

2102 

2103 def _join_determine_implicit_left_side( 

2104 self, entities_collection, left, right, onclause 

2105 ): 

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

2107 determine if an existing FROM or entity in this query 

2108 can serve as the left hand side. 

2109 

2110 """ 

2111 

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

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

2114 # 

2115 # join(RightEntity) 

2116 # 

2117 # or 

2118 # 

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

2120 # 

2121 

2122 r_info = inspect(right) 

2123 

2124 replace_from_obj_index = use_entity_index = None 

2125 

2126 if self.from_clauses: 

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

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

2129 

2130 indexes = sql_util.find_left_clause_to_join_from( 

2131 self.from_clauses, r_info.selectable, onclause 

2132 ) 

2133 

2134 if len(indexes) == 1: 

2135 replace_from_obj_index = indexes[0] 

2136 left = self.from_clauses[replace_from_obj_index] 

2137 elif len(indexes) > 1: 

2138 raise sa_exc.InvalidRequestError( 

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

2140 "from, there are multiple FROMS which can " 

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

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

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

2144 "to help resolve the ambiguity." 

2145 ) 

2146 else: 

2147 raise sa_exc.InvalidRequestError( 

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

2149 "Please use the .select_from() " 

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

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

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

2153 ) 

2154 

2155 elif entities_collection: 

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

2157 # come from our list of entities. 

2158 

2159 potential = {} 

2160 for entity_index, ent in enumerate(entities_collection): 

2161 entity = ent.entity_zero_or_selectable 

2162 if entity is None: 

2163 continue 

2164 ent_info = inspect(entity) 

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

2166 continue 

2167 

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

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

2170 # against a series of columns from the same selectable 

2171 if isinstance(ent, _MapperEntity): 

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

2173 else: 

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

2175 

2176 all_clauses = list(potential.keys()) 

2177 indexes = sql_util.find_left_clause_to_join_from( 

2178 all_clauses, r_info.selectable, onclause 

2179 ) 

2180 

2181 if len(indexes) == 1: 

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

2183 elif len(indexes) > 1: 

2184 raise sa_exc.InvalidRequestError( 

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

2186 "from, there are multiple FROMS which can " 

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

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

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

2190 "to help resolve the ambiguity." 

2191 ) 

2192 else: 

2193 raise sa_exc.InvalidRequestError( 

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

2195 "Please use the .select_from() " 

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

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

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

2199 ) 

2200 else: 

2201 raise sa_exc.InvalidRequestError( 

2202 "No entities to join from; please use " 

2203 "select_from() to establish the left " 

2204 "entity/selectable of this join" 

2205 ) 

2206 

2207 return left, replace_from_obj_index, use_entity_index 

2208 

2209 def _join_place_explicit_left_side(self, entities_collection, left): 

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

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

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

2213 existing entities. 

2214 

2215 """ 

2216 

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

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

2219 # Relationship was given, e.g.: 

2220 # 

2221 # join(RightEntity, LeftEntity.right) 

2222 # 

2223 # or 

2224 # 

2225 # join(LeftEntity.right) 

2226 # 

2227 # as well as string forms: 

2228 # 

2229 # join(RightEntity, "right") 

2230 # 

2231 # etc. 

2232 # 

2233 

2234 replace_from_obj_index = use_entity_index = None 

2235 

2236 l_info = inspect(left) 

2237 if self.from_clauses: 

2238 indexes = sql_util.find_left_clause_that_matches_given( 

2239 self.from_clauses, l_info.selectable 

2240 ) 

2241 

2242 if len(indexes) > 1: 

2243 raise sa_exc.InvalidRequestError( 

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

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

2246 "ON clause." 

2247 ) 

2248 

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

2250 # an existing FROM in the self._from_obj tuple 

2251 if indexes: 

2252 replace_from_obj_index = indexes[0] 

2253 

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

2255 # self._from_obj tuple 

2256 

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

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

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

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

2261 if ( 

2262 replace_from_obj_index is None 

2263 and entities_collection 

2264 and hasattr(l_info, "mapper") 

2265 ): 

2266 for idx, ent in enumerate(entities_collection): 

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

2268 # matching? 

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

2270 use_entity_index = idx 

2271 break 

2272 

2273 return replace_from_obj_index, use_entity_index 

2274 

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

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

2277 according to polymorphic mapping translations, aliasing on the query 

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

2279 overlapping tables. 

2280 

2281 """ 

2282 

2283 l_info = inspect(left) 

2284 r_info = inspect(right) 

2285 

2286 overlap = False 

2287 

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

2289 # if the target is a joined inheritance mapping, 

2290 # be more liberal about auto-aliasing. 

2291 if right_mapper and ( 

2292 right_mapper.with_polymorphic 

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

2294 ): 

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

2296 if sql_util.selectables_overlap( 

2297 l_info.selectable, from_obj 

2298 ) and sql_util.selectables_overlap( 

2299 from_obj, r_info.selectable 

2300 ): 

2301 overlap = True 

2302 break 

2303 

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

2305 raise sa_exc.InvalidRequestError( 

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

2307 % l_info.selectable 

2308 ) 

2309 

2310 right_mapper, right_selectable, right_is_aliased = ( 

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

2312 r_info.selectable, 

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

2314 ) 

2315 

2316 if ( 

2317 right_mapper 

2318 and prop 

2319 and not right_mapper.common_parent(prop.mapper) 

2320 ): 

2321 raise sa_exc.InvalidRequestError( 

2322 "Join target %s does not correspond to " 

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

2324 ) 

2325 

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

2327 # purposes at the moment 

2328 if hasattr(r_info, "mapper"): 

2329 self._join_entities += (r_info,) 

2330 

2331 need_adapter = False 

2332 

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

2334 if r_info.is_clause_element: 

2335 if prop: 

2336 right_mapper = prop.mapper 

2337 

2338 if right_selectable._is_lateral: 

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

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

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

2342 current_adapter = self._get_current_adapter() 

2343 if current_adapter is not None: 

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

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

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

2347 right = current_adapter(right, True) 

2348 

2349 elif prop: 

2350 # joining to selectable with a mapper property given 

2351 # as the ON clause 

2352 

2353 if not right_selectable.is_derived_from( 

2354 right_mapper.persist_selectable 

2355 ): 

2356 raise sa_exc.InvalidRequestError( 

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

2358 % ( 

2359 right_selectable.description, 

2360 right_mapper.persist_selectable.description, 

2361 ) 

2362 ) 

2363 

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

2365 # turn it into an alias(). 

2366 if isinstance(right_selectable, expression.SelectBase): 

2367 right_selectable = coercions.expect( 

2368 roles.FromClauseRole, right_selectable 

2369 ) 

2370 need_adapter = True 

2371 

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

2373 right = AliasedClass(right_mapper, right_selectable) 

2374 

2375 util.warn_deprecated( 

2376 "An alias is being generated automatically against " 

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

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

2379 "Use the aliased() " 

2380 "construct explicitly, see the linked example." 

2381 % right_mapper, 

2382 "1.4", 

2383 code="xaj1", 

2384 ) 

2385 

2386 # test for overlap: 

2387 # orm/inheritance/relationships.py 

2388 # SelfReferentialM2MTest 

2389 aliased_entity = right_mapper and not right_is_aliased and overlap 

2390 

2391 if not need_adapter and aliased_entity: 

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

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

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

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

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

2397 # aliasing is desirable. 

2398 right = AliasedClass(right, flat=True) 

2399 need_adapter = True 

2400 

2401 util.warn( 

2402 "An alias is being generated automatically against " 

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

2404 "legacy pattern which may be " 

2405 "deprecated in a later release. Use the " 

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

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

2408 code="xaj2", 

2409 ) 

2410 

2411 if need_adapter: 

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

2413 # a warning has been emitted. 

2414 assert right_mapper 

2415 

2416 adapter = ORMAdapter( 

2417 _TraceAdaptRole.DEPRECATED_JOIN_ADAPT_RIGHT_SIDE, 

2418 inspect(right), 

2419 equivalents=right_mapper._equivalent_columns, 

2420 ) 

2421 

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

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

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

2425 # set are also adapted. 

2426 self._mapper_loads_polymorphically_with(right_mapper, adapter) 

2427 elif ( 

2428 not r_info.is_clause_element 

2429 and not right_is_aliased 

2430 and right_mapper._has_aliased_polymorphic_fromclause 

2431 ): 

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

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

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

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

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

2437 # strictly necessary. 

2438 # see test/orm/test_core_compilation.py 

2439 # ::RelNaturalAliasedJoinsTest::test_straight 

2440 # and similar 

2441 self._mapper_loads_polymorphically_with( 

2442 right_mapper, 

2443 ORMAdapter( 

2444 _TraceAdaptRole.WITH_POLYMORPHIC_ADAPTER_RIGHT_JOIN, 

2445 right_mapper, 

2446 selectable=right_mapper.selectable, 

2447 equivalents=right_mapper._equivalent_columns, 

2448 ), 

2449 ) 

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

2451 # adapters that are in place right now 

2452 if isinstance(onclause, expression.ClauseElement): 

2453 current_adapter = self._get_current_adapter() 

2454 if current_adapter: 

2455 onclause = current_adapter(onclause, True) 

2456 

2457 # if joining on a MapperProperty path, 

2458 # track the path to prevent redundant joins 

2459 if prop: 

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

2461 

2462 return inspect(right), right, onclause 

2463 

2464 @property 

2465 def _select_args(self): 

2466 return { 

2467 "limit_clause": self.select_statement._limit_clause, 

2468 "offset_clause": self.select_statement._offset_clause, 

2469 "distinct": self.distinct, 

2470 "distinct_on": self.distinct_on, 

2471 "prefixes": self.select_statement._prefixes, 

2472 "suffixes": self.select_statement._suffixes, 

2473 "group_by": self.group_by or None, 

2474 "fetch_clause": self.select_statement._fetch_clause, 

2475 "fetch_clause_options": ( 

2476 self.select_statement._fetch_clause_options 

2477 ), 

2478 "independent_ctes": self.select_statement._independent_ctes, 

2479 "independent_ctes_opts": ( 

2480 self.select_statement._independent_ctes_opts 

2481 ), 

2482 "syntax_extensions": self.syntax_extensions, 

2483 } 

2484 

2485 @property 

2486 def _should_nest_selectable(self): 

2487 kwargs = self._select_args 

2488 

2489 if not self.eager_adding_joins: 

2490 return False 

2491 

2492 return ( 

2493 ( 

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

2495 and self.multi_row_eager_loaders 

2496 ) 

2497 or ( 

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

2499 and self.multi_row_eager_loaders 

2500 ) 

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

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

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

2504 ) 

2505 

2506 def _get_extra_criteria(self, ext_info): 

2507 if ( 

2508 "additional_entity_criteria", 

2509 ext_info.mapper, 

2510 ) in self.global_attributes: 

2511 return tuple( 

2512 ae._resolve_where_criteria(ext_info) 

2513 for ae in self.global_attributes[ 

2514 ("additional_entity_criteria", ext_info.mapper) 

2515 ] 

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

2517 and ae._should_include(self) 

2518 ) 

2519 else: 

2520 return () 

2521 

2522 def _adjust_for_extra_criteria(self): 

2523 """Apply extra criteria filtering. 

2524 

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

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

2527 add criterion to the WHERE 

2528 clause of the given QueryContext such that only the appropriate 

2529 subtypes are selected from the total results. 

2530 

2531 Additionally, add WHERE criteria originating from LoaderCriteriaOptions 

2532 associated with the global context. 

2533 

2534 """ 

2535 ext_infos = [ 

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

2537 for fromclause in self.from_clauses 

2538 ] + [ 

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

2540 for where_crit in self.select_statement._where_criteria 

2541 for elem in sql_util.surface_expressions(where_crit) 

2542 ] 

2543 

2544 for ext_info in ext_infos: 

2545 

2546 if ( 

2547 ext_info 

2548 and ( 

2549 ext_info.mapper._single_table_criterion is not None 

2550 or ("additional_entity_criteria", ext_info.mapper) 

2551 in self.global_attributes 

2552 ) 

2553 and ext_info not in self.extra_criteria_entities 

2554 ): 

2555 self.extra_criteria_entities[ext_info] = ( 

2556 ext_info, 

2557 ext_info._adapter if ext_info.is_aliased_class else None, 

2558 ) 

2559 

2560 _where_criteria_to_add = () 

2561 

2562 merged_single_crit = collections.defaultdict( 

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

2564 ) 

2565 

2566 for ext_info, adapter in util.OrderedSet( 

2567 self.extra_criteria_entities.values() 

2568 ): 

2569 if ext_info in self._join_entities: 

2570 continue 

2571 

2572 # assemble single table inheritance criteria. 

2573 if ( 

2574 ext_info.is_aliased_class 

2575 and ext_info._base_alias()._is_with_polymorphic 

2576 ): 

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

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

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

2580 # of joined inheritance. 

2581 hierarchy_root = ext_info._base_alias() 

2582 else: 

2583 hierarchy_root = ext_info 

2584 

2585 single_crit_component = ( 

2586 hierarchy_root.mapper._single_table_criteria_component 

2587 ) 

2588 

2589 if single_crit_component is not None: 

2590 polymorphic_on, criteria = single_crit_component 

2591 

2592 polymorphic_on = polymorphic_on._annotate( 

2593 { 

2594 "parententity": hierarchy_root, 

2595 "parentmapper": hierarchy_root.mapper, 

2596 } 

2597 ) 

2598 

2599 list_of_single_crits, adapters = merged_single_crit[ 

2600 (hierarchy_root, polymorphic_on) 

2601 ] 

2602 list_of_single_crits.update(criteria) 

2603 if adapter: 

2604 adapters.add(adapter) 

2605 

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

2607 # with_loader_criteria() options 

2608 if not self.compile_options._for_refresh_state: 

2609 additional_entity_criteria = self._get_extra_criteria(ext_info) 

2610 _where_criteria_to_add += tuple( 

2611 adapter.traverse(crit) if adapter else crit 

2612 for crit in additional_entity_criteria 

2613 ) 

2614 

2615 # merge together single table inheritance criteria keyed to 

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

2617 for (ext_info, polymorphic_on), ( 

2618 merged_crit, 

2619 adapters, 

2620 ) in merged_single_crit.items(): 

2621 new_crit = polymorphic_on.in_(merged_crit) 

2622 for adapter in adapters: 

2623 new_crit = adapter.traverse(new_crit) 

2624 _where_criteria_to_add += (new_crit,) 

2625 

2626 current_adapter = self._get_current_adapter() 

2627 if current_adapter: 

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

2629 # have one, and concatenate to final WHERE criteria 

2630 for crit in _where_criteria_to_add: 

2631 crit = current_adapter(crit, False) 

2632 self._where_criteria += (crit,) 

2633 else: 

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

2635 self._where_criteria += _where_criteria_to_add 

2636 

2637 

2638def _column_descriptions( 

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

2640 compile_state: Optional[_ORMSelectCompileState] = None, 

2641 legacy: bool = False, 

2642) -> List[ORMColumnDescription]: 

2643 if compile_state is None: 

2644 compile_state = _ORMSelectCompileState._create_entities_collection( 

2645 query_or_select_stmt, legacy=legacy 

2646 ) 

2647 ctx = compile_state 

2648 d = [ 

2649 { 

2650 "name": ent._label_name, 

2651 "type": ent.type, 

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

2653 "expr": ent.expr, 

2654 "entity": ( 

2655 getattr(insp_ent, "entity", None) 

2656 if ent.entity_zero is not None 

2657 and not insp_ent.is_clause_element 

2658 else None 

2659 ), 

2660 } 

2661 for ent, insp_ent in [ 

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

2663 ] 

2664 ] 

2665 return d 

2666 

2667 

2668def _legacy_filter_by_entity_zero( 

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

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

2671 self = query_or_augmented_select 

2672 if self._setup_joins: 

2673 _last_joined_entity = self._last_joined_entity 

2674 if _last_joined_entity is not None: 

2675 return _last_joined_entity 

2676 

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

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

2679 

2680 return _entity_from_pre_ent_zero(self) 

2681 

2682 

2683def _entity_from_pre_ent_zero( 

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

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

2686 self = query_or_augmented_select 

2687 if not self._raw_columns: 

2688 return None 

2689 

2690 ent = self._raw_columns[0] 

2691 

2692 if "parententity" in ent._annotations: 

2693 return ent._annotations["parententity"] 

2694 elif isinstance(ent, ORMColumnsClauseRole): 

2695 return ent.entity 

2696 elif "bundle" in ent._annotations: 

2697 return ent._annotations["bundle"] 

2698 else: 

2699 return ent 

2700 

2701 

2702def _determine_last_joined_entity( 

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

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

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

2706 if not setup_joins: 

2707 return None 

2708 

2709 target, onclause, from_, flags = setup_joins[-1] 

2710 

2711 if isinstance( 

2712 target, 

2713 attributes.QueryableAttribute, 

2714 ): 

2715 return target.entity 

2716 else: 

2717 return target 

2718 

2719 

2720class _QueryEntity: 

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

2722 

2723 __slots__ = () 

2724 

2725 supports_single_entity: bool 

2726 

2727 _non_hashable_value = False 

2728 _null_column_type = False 

2729 use_id_for_hash = False 

2730 

2731 _label_name: Optional[str] 

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

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

2734 entity_zero: Optional[_InternalEntityType] 

2735 

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

2737 raise NotImplementedError() 

2738 

2739 def setup_dml_returning_compile_state( 

2740 self, 

2741 compile_state: _ORMCompileState, 

2742 adapter: Optional[_DMLReturningColFilter], 

2743 ) -> None: 

2744 raise NotImplementedError() 

2745 

2746 def row_processor(self, context, result): 

2747 raise NotImplementedError() 

2748 

2749 @classmethod 

2750 def to_compile_state( 

2751 cls, compile_state, entities, entities_collection, is_current_entities 

2752 ): 

2753 for idx, entity in enumerate(entities): 

2754 if entity._is_lambda_element: 

2755 if entity._is_sequence: 

2756 cls.to_compile_state( 

2757 compile_state, 

2758 entity._resolved, 

2759 entities_collection, 

2760 is_current_entities, 

2761 ) 

2762 continue 

2763 else: 

2764 entity = entity._resolved 

2765 

2766 if entity.is_clause_element: 

2767 if entity.is_selectable: 

2768 if "parententity" in entity._annotations: 

2769 _MapperEntity( 

2770 compile_state, 

2771 entity, 

2772 entities_collection, 

2773 is_current_entities, 

2774 ) 

2775 else: 

2776 _ColumnEntity._for_columns( 

2777 compile_state, 

2778 entity._select_iterable, 

2779 entities_collection, 

2780 idx, 

2781 is_current_entities, 

2782 ) 

2783 else: 

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

2785 _BundleEntity( 

2786 compile_state, 

2787 entity, 

2788 entities_collection, 

2789 is_current_entities, 

2790 ) 

2791 elif entity._is_clause_list: 

2792 # this is legacy only - test_composites.py 

2793 # test_query_cols_legacy 

2794 _ColumnEntity._for_columns( 

2795 compile_state, 

2796 entity._select_iterable, 

2797 entities_collection, 

2798 idx, 

2799 is_current_entities, 

2800 ) 

2801 else: 

2802 _ColumnEntity._for_columns( 

2803 compile_state, 

2804 [entity], 

2805 entities_collection, 

2806 idx, 

2807 is_current_entities, 

2808 ) 

2809 elif entity.is_bundle: 

2810 _BundleEntity(compile_state, entity, entities_collection) 

2811 

2812 return entities_collection 

2813 

2814 

2815class _MapperEntity(_QueryEntity): 

2816 """mapper/class/AliasedClass entity""" 

2817 

2818 __slots__ = ( 

2819 "expr", 

2820 "mapper", 

2821 "entity_zero", 

2822 "is_aliased_class", 

2823 "path", 

2824 "_extra_entities", 

2825 "_label_name", 

2826 "_with_polymorphic_mappers", 

2827 "selectable", 

2828 "_polymorphic_discriminator", 

2829 ) 

2830 

2831 expr: _InternalEntityType 

2832 mapper: Mapper[Any] 

2833 entity_zero: _InternalEntityType 

2834 is_aliased_class: bool 

2835 path: PathRegistry 

2836 _label_name: str 

2837 

2838 def __init__( 

2839 self, compile_state, entity, entities_collection, is_current_entities 

2840 ): 

2841 entities_collection.append(self) 

2842 if is_current_entities: 

2843 if compile_state._primary_entity is None: 

2844 compile_state._primary_entity = self 

2845 compile_state._has_mapper_entities = True 

2846 compile_state._has_orm_entities = True 

2847 

2848 entity = entity._annotations["parententity"] 

2849 entity._post_inspect 

2850 ext_info = self.entity_zero = entity 

2851 entity = ext_info.entity 

2852 

2853 self.expr = entity 

2854 self.mapper = mapper = ext_info.mapper 

2855 

2856 self._extra_entities = (self.expr,) 

2857 

2858 if ext_info.is_aliased_class: 

2859 self._label_name = ext_info.name 

2860 else: 

2861 self._label_name = mapper.class_.__name__ 

2862 

2863 self.is_aliased_class = ext_info.is_aliased_class 

2864 self.path = ext_info._path_registry 

2865 

2866 self.selectable = ext_info.selectable 

2867 self._with_polymorphic_mappers = ext_info.with_polymorphic_mappers 

2868 self._polymorphic_discriminator = ext_info.polymorphic_on 

2869 

2870 if mapper._should_select_with_poly_adapter: 

2871 compile_state._create_with_polymorphic_adapter( 

2872 ext_info, self.selectable 

2873 ) 

2874 

2875 supports_single_entity = True 

2876 

2877 _non_hashable_value = True 

2878 use_id_for_hash = True 

2879 

2880 @property 

2881 def type(self): 

2882 return self.mapper.class_ 

2883 

2884 @property 

2885 def entity_zero_or_selectable(self): 

2886 return self.entity_zero 

2887 

2888 def corresponds_to(self, entity): 

2889 return _entity_corresponds_to(self.entity_zero, entity) 

2890 

2891 def _get_entity_clauses(self, compile_state): 

2892 adapter = None 

2893 

2894 if not self.is_aliased_class: 

2895 if compile_state._polymorphic_adapters: 

2896 adapter = compile_state._polymorphic_adapters.get( 

2897 self.mapper, None 

2898 ) 

2899 else: 

2900 adapter = self.entity_zero._adapter 

2901 

2902 if adapter: 

2903 if compile_state._from_obj_alias: 

2904 ret = adapter.wrap(compile_state._from_obj_alias) 

2905 else: 

2906 ret = adapter 

2907 else: 

2908 ret = compile_state._from_obj_alias 

2909 

2910 return ret 

2911 

2912 def row_processor(self, context, result): 

2913 compile_state = context.compile_state 

2914 adapter = self._get_entity_clauses(compile_state) 

2915 

2916 if compile_state.compound_eager_adapter and adapter: 

2917 adapter = adapter.wrap(compile_state.compound_eager_adapter) 

2918 elif not adapter: 

2919 adapter = compile_state.compound_eager_adapter 

2920 

2921 if compile_state._primary_entity is self: 

2922 only_load_props = compile_state.compile_options._only_load_props 

2923 refresh_state = context.refresh_state 

2924 else: 

2925 only_load_props = refresh_state = None 

2926 

2927 _instance = loading._instance_processor( 

2928 self, 

2929 self.mapper, 

2930 context, 

2931 result, 

2932 self.path, 

2933 adapter, 

2934 only_load_props=only_load_props, 

2935 refresh_state=refresh_state, 

2936 polymorphic_discriminator=self._polymorphic_discriminator, 

2937 ) 

2938 

2939 return _instance, self._label_name, self._extra_entities 

2940 

2941 def setup_dml_returning_compile_state( 

2942 self, 

2943 compile_state: _ORMCompileState, 

2944 adapter: Optional[_DMLReturningColFilter], 

2945 ) -> None: 

2946 loading._setup_entity_query( 

2947 compile_state, 

2948 self.mapper, 

2949 self, 

2950 self.path, 

2951 adapter, 

2952 compile_state.primary_columns, 

2953 with_polymorphic=self._with_polymorphic_mappers, 

2954 only_load_props=compile_state.compile_options._only_load_props, 

2955 polymorphic_discriminator=self._polymorphic_discriminator, 

2956 ) 

2957 

2958 def setup_compile_state(self, compile_state): 

2959 adapter = self._get_entity_clauses(compile_state) 

2960 

2961 single_table_crit = self.mapper._single_table_criterion 

2962 if ( 

2963 single_table_crit is not None 

2964 or ("additional_entity_criteria", self.mapper) 

2965 in compile_state.global_attributes 

2966 ): 

2967 ext_info = self.entity_zero 

2968 compile_state.extra_criteria_entities[ext_info] = ( 

2969 ext_info, 

2970 ext_info._adapter if ext_info.is_aliased_class else None, 

2971 ) 

2972 

2973 loading._setup_entity_query( 

2974 compile_state, 

2975 self.mapper, 

2976 self, 

2977 self.path, 

2978 adapter, 

2979 compile_state.primary_columns, 

2980 with_polymorphic=self._with_polymorphic_mappers, 

2981 only_load_props=compile_state.compile_options._only_load_props, 

2982 polymorphic_discriminator=self._polymorphic_discriminator, 

2983 ) 

2984 compile_state._fallback_from_clauses.append(self.selectable) 

2985 

2986 

2987class _BundleEntity(_QueryEntity): 

2988 _extra_entities = () 

2989 

2990 __slots__ = ( 

2991 "bundle", 

2992 "expr", 

2993 "type", 

2994 "_label_name", 

2995 "_entities", 

2996 "supports_single_entity", 

2997 ) 

2998 

2999 _entities: List[_QueryEntity] 

3000 bundle: Bundle 

3001 type: Type[Any] 

3002 _label_name: str 

3003 supports_single_entity: bool 

3004 expr: Bundle 

3005 

3006 def __init__( 

3007 self, 

3008 compile_state, 

3009 expr, 

3010 entities_collection, 

3011 is_current_entities, 

3012 setup_entities=True, 

3013 parent_bundle=None, 

3014 ): 

3015 compile_state._has_orm_entities = True 

3016 

3017 expr = expr._annotations["bundle"] 

3018 if parent_bundle: 

3019 parent_bundle._entities.append(self) 

3020 else: 

3021 entities_collection.append(self) 

3022 

3023 if isinstance( 

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

3025 ): 

3026 bundle = expr.__clause_element__() 

3027 else: 

3028 bundle = expr 

3029 

3030 self.bundle = self.expr = bundle 

3031 self.type = type(bundle) 

3032 self._label_name = bundle.name 

3033 self._entities = [] 

3034 

3035 if setup_entities: 

3036 for expr in bundle.exprs: 

3037 if "bundle" in expr._annotations: 

3038 _BundleEntity( 

3039 compile_state, 

3040 expr, 

3041 entities_collection, 

3042 is_current_entities, 

3043 parent_bundle=self, 

3044 ) 

3045 elif isinstance(expr, Bundle): 

3046 _BundleEntity( 

3047 compile_state, 

3048 expr, 

3049 entities_collection, 

3050 is_current_entities, 

3051 parent_bundle=self, 

3052 ) 

3053 else: 

3054 _ORMColumnEntity._for_columns( 

3055 compile_state, 

3056 [expr], 

3057 entities_collection, 

3058 None, 

3059 is_current_entities, 

3060 parent_bundle=self, 

3061 ) 

3062 

3063 self.supports_single_entity = self.bundle.single_entity 

3064 

3065 @property 

3066 def mapper(self): 

3067 ezero = self.entity_zero 

3068 if ezero is not None: 

3069 return ezero.mapper 

3070 else: 

3071 return None 

3072 

3073 @property 

3074 def entity_zero(self): 

3075 for ent in self._entities: 

3076 ezero = ent.entity_zero 

3077 if ezero is not None: 

3078 return ezero 

3079 else: 

3080 return None 

3081 

3082 def corresponds_to(self, entity): 

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

3084 # we are working around it 

3085 return False 

3086 

3087 @property 

3088 def entity_zero_or_selectable(self): 

3089 for ent in self._entities: 

3090 ezero = ent.entity_zero_or_selectable 

3091 if ezero is not None: 

3092 return ezero 

3093 else: 

3094 return None 

3095 

3096 def setup_compile_state(self, compile_state): 

3097 for ent in self._entities: 

3098 ent.setup_compile_state(compile_state) 

3099 

3100 def setup_dml_returning_compile_state( 

3101 self, 

3102 compile_state: _ORMCompileState, 

3103 adapter: Optional[_DMLReturningColFilter], 

3104 ) -> None: 

3105 return self.setup_compile_state(compile_state) 

3106 

3107 def row_processor(self, context, result): 

3108 procs, labels, extra = zip( 

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

3110 ) 

3111 

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

3113 

3114 return proc, self._label_name, self._extra_entities 

3115 

3116 

3117class _ColumnEntity(_QueryEntity): 

3118 __slots__ = ( 

3119 "_fetch_column", 

3120 "_row_processor", 

3121 "raw_column_index", 

3122 "translate_raw_column", 

3123 ) 

3124 

3125 @classmethod 

3126 def _for_columns( 

3127 cls, 

3128 compile_state, 

3129 columns, 

3130 entities_collection, 

3131 raw_column_index, 

3132 is_current_entities, 

3133 parent_bundle=None, 

3134 ): 

3135 for column in columns: 

3136 annotations = column._annotations 

3137 if "parententity" in annotations: 

3138 _entity = annotations["parententity"] 

3139 else: 

3140 _entity = sql_util.extract_first_column_annotation( 

3141 column, "parententity" 

3142 ) 

3143 

3144 if _entity: 

3145 if "identity_token" in column._annotations: 

3146 _IdentityTokenEntity( 

3147 compile_state, 

3148 column, 

3149 entities_collection, 

3150 _entity, 

3151 raw_column_index, 

3152 is_current_entities, 

3153 parent_bundle=parent_bundle, 

3154 ) 

3155 else: 

3156 _ORMColumnEntity( 

3157 compile_state, 

3158 column, 

3159 entities_collection, 

3160 _entity, 

3161 raw_column_index, 

3162 is_current_entities, 

3163 parent_bundle=parent_bundle, 

3164 ) 

3165 else: 

3166 _RawColumnEntity( 

3167 compile_state, 

3168 column, 

3169 entities_collection, 

3170 raw_column_index, 

3171 is_current_entities, 

3172 parent_bundle=parent_bundle, 

3173 ) 

3174 

3175 @property 

3176 def type(self): 

3177 return self.column.type 

3178 

3179 @property 

3180 def _non_hashable_value(self): 

3181 return not self.column.type.hashable 

3182 

3183 @property 

3184 def _null_column_type(self): 

3185 return self.column.type._isnull 

3186 

3187 def row_processor(self, context, result): 

3188 compile_state = context.compile_state 

3189 

3190 # the resulting callable is entirely cacheable so just return 

3191 # it if we already made one 

3192 if self._row_processor is not None: 

3193 getter, label_name, extra_entities = self._row_processor 

3194 if self.translate_raw_column: 

3195 extra_entities += ( 

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

3197 ) 

3198 

3199 return getter, label_name, extra_entities 

3200 

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

3202 # setup_compile_state, to avoid doing redundant work 

3203 if self._fetch_column is not None: 

3204 column = self._fetch_column 

3205 else: 

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

3207 # and setup_compile_state may not have been called. 

3208 column = self.column 

3209 

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

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

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

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

3214 if compile_state._from_obj_alias: 

3215 column = compile_state._from_obj_alias.columns[column] 

3216 

3217 if column._annotations: 

3218 # annotated columns perform more slowly in compiler and 

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

3220 column = column._deannotate() 

3221 

3222 if compile_state.compound_eager_adapter: 

3223 column = compile_state.compound_eager_adapter.columns[column] 

3224 

3225 getter = result._getter(column) 

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

3227 self._row_processor = ret 

3228 

3229 if self.translate_raw_column: 

3230 extra_entities = self._extra_entities + ( 

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

3232 ) 

3233 return getter, self._label_name, extra_entities 

3234 else: 

3235 return ret 

3236 

3237 

3238class _RawColumnEntity(_ColumnEntity): 

3239 entity_zero = None 

3240 mapper = None 

3241 supports_single_entity = False 

3242 

3243 __slots__ = ( 

3244 "expr", 

3245 "column", 

3246 "_label_name", 

3247 "entity_zero_or_selectable", 

3248 "_extra_entities", 

3249 ) 

3250 

3251 def __init__( 

3252 self, 

3253 compile_state, 

3254 column, 

3255 entities_collection, 

3256 raw_column_index, 

3257 is_current_entities, 

3258 parent_bundle=None, 

3259 ): 

3260 self.expr = column 

3261 self.raw_column_index = raw_column_index 

3262 self.translate_raw_column = raw_column_index is not None 

3263 

3264 if column._is_star: 

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

3266 

3267 if not is_current_entities or column._is_text_clause: 

3268 self._label_name = None 

3269 else: 

3270 if parent_bundle: 

3271 self._label_name = column._proxy_key 

3272 else: 

3273 self._label_name = compile_state._label_convention(column) 

3274 

3275 if parent_bundle: 

3276 parent_bundle._entities.append(self) 

3277 else: 

3278 entities_collection.append(self) 

3279 

3280 self.column = column 

3281 self.entity_zero_or_selectable = ( 

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

3283 ) 

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

3285 self._fetch_column = self._row_processor = None 

3286 

3287 def corresponds_to(self, entity): 

3288 return False 

3289 

3290 def setup_dml_returning_compile_state( 

3291 self, 

3292 compile_state: _ORMCompileState, 

3293 adapter: Optional[_DMLReturningColFilter], 

3294 ) -> None: 

3295 return self.setup_compile_state(compile_state) 

3296 

3297 def setup_compile_state(self, compile_state): 

3298 current_adapter = compile_state._get_current_adapter() 

3299 if current_adapter: 

3300 column = current_adapter(self.column, False) 

3301 if column is None: 

3302 return 

3303 else: 

3304 column = self.column 

3305 

3306 if column._annotations: 

3307 # annotated columns perform more slowly in compiler and 

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

3309 column = column._deannotate() 

3310 

3311 compile_state.dedupe_columns.add(column) 

3312 compile_state.primary_columns.append(column) 

3313 self._fetch_column = column 

3314 

3315 

3316class _ORMColumnEntity(_ColumnEntity): 

3317 """Column/expression based entity.""" 

3318 

3319 supports_single_entity = False 

3320 

3321 __slots__ = ( 

3322 "expr", 

3323 "mapper", 

3324 "column", 

3325 "_label_name", 

3326 "entity_zero_or_selectable", 

3327 "entity_zero", 

3328 "_extra_entities", 

3329 ) 

3330 

3331 def __init__( 

3332 self, 

3333 compile_state, 

3334 column, 

3335 entities_collection, 

3336 parententity, 

3337 raw_column_index, 

3338 is_current_entities, 

3339 parent_bundle=None, 

3340 ): 

3341 annotations = column._annotations 

3342 

3343 _entity = parententity 

3344 

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

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

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

3348 # within internal loaders. 

3349 

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

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

3352 if orm_key: 

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

3354 self.translate_raw_column = False 

3355 else: 

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

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

3358 # include this column position from the invoked statement 

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

3360 # it can be targeted by identity after caching 

3361 self.expr = column 

3362 self.translate_raw_column = raw_column_index is not None 

3363 

3364 self.raw_column_index = raw_column_index 

3365 

3366 if is_current_entities: 

3367 if parent_bundle: 

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

3369 else: 

3370 self._label_name = compile_state._label_convention( 

3371 column, col_name=orm_key 

3372 ) 

3373 else: 

3374 self._label_name = None 

3375 

3376 _entity._post_inspect 

3377 self.entity_zero = self.entity_zero_or_selectable = ezero = _entity 

3378 self.mapper = mapper = _entity.mapper 

3379 

3380 if parent_bundle: 

3381 parent_bundle._entities.append(self) 

3382 else: 

3383 entities_collection.append(self) 

3384 

3385 compile_state._has_orm_entities = True 

3386 

3387 self.column = column 

3388 

3389 self._fetch_column = self._row_processor = None 

3390 

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

3392 

3393 if mapper._should_select_with_poly_adapter: 

3394 compile_state._create_with_polymorphic_adapter( 

3395 ezero, ezero.selectable 

3396 ) 

3397 

3398 def corresponds_to(self, entity): 

3399 if _is_aliased_class(entity): 

3400 # TODO: polymorphic subclasses ? 

3401 return entity is self.entity_zero 

3402 else: 

3403 return not _is_aliased_class( 

3404 self.entity_zero 

3405 ) and entity.common_parent(self.entity_zero) 

3406 

3407 def setup_dml_returning_compile_state( 

3408 self, 

3409 compile_state: _ORMCompileState, 

3410 adapter: Optional[_DMLReturningColFilter], 

3411 ) -> None: 

3412 

3413 self._fetch_column = column = self.column 

3414 if adapter: 

3415 column = adapter(column, False) 

3416 

3417 if column is not None: 

3418 compile_state.dedupe_columns.add(column) 

3419 compile_state.primary_columns.append(column) 

3420 

3421 def setup_compile_state(self, compile_state): 

3422 current_adapter = compile_state._get_current_adapter() 

3423 if current_adapter: 

3424 column = current_adapter(self.column, False) 

3425 if column is None: 

3426 assert compile_state.is_dml_returning 

3427 self._fetch_column = self.column 

3428 return 

3429 else: 

3430 column = self.column 

3431 

3432 ezero = self.entity_zero 

3433 

3434 single_table_crit = self.mapper._single_table_criterion 

3435 if ( 

3436 single_table_crit is not None 

3437 or ("additional_entity_criteria", self.mapper) 

3438 in compile_state.global_attributes 

3439 ): 

3440 compile_state.extra_criteria_entities[ezero] = ( 

3441 ezero, 

3442 ezero._adapter if ezero.is_aliased_class else None, 

3443 ) 

3444 

3445 if column._annotations and not column._expression_label: 

3446 # annotated columns perform more slowly in compiler and 

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

3448 column = column._deannotate() 

3449 

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

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

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

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

3454 # a scalar subquery. 

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

3456 ezero.selectable._from_objects 

3457 ): 

3458 compile_state._fallback_from_clauses.append(ezero.selectable) 

3459 

3460 compile_state.dedupe_columns.add(column) 

3461 compile_state.primary_columns.append(column) 

3462 self._fetch_column = column 

3463 

3464 

3465class _IdentityTokenEntity(_ORMColumnEntity): 

3466 translate_raw_column = False 

3467 

3468 def setup_compile_state(self, compile_state): 

3469 pass 

3470 

3471 def row_processor(self, context, result): 

3472 def getter(row): 

3473 return context.load_options._identity_token 

3474 

3475 return getter, self._label_name, self._extra_entities