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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1287 statements  

1# orm/context.py 

2# Copyright (C) 2005-2024 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 itertools 

12from typing import Any 

13from typing import cast 

14from typing import Dict 

15from typing import Iterable 

16from typing import List 

17from typing import Optional 

18from typing import Set 

19from typing import Tuple 

20from typing import Type 

21from typing import TYPE_CHECKING 

22from typing import TypeVar 

23from typing import Union 

24 

25from . import attributes 

26from . import interfaces 

27from . import loading 

28from .base import _is_aliased_class 

29from .interfaces import ORMColumnDescription 

30from .interfaces import ORMColumnsClauseRole 

31from .path_registry import PathRegistry 

32from .util import _entity_corresponds_to 

33from .util import _ORMJoin 

34from .util import _TraceAdaptRole 

35from .util import AliasedClass 

36from .util import Bundle 

37from .util import ORMAdapter 

38from .util import ORMStatementAdapter 

39from .. import exc as sa_exc 

40from .. import future 

41from .. import inspect 

42from .. import sql 

43from .. import util 

44from ..sql import coercions 

45from ..sql import expression 

46from ..sql import roles 

47from ..sql import util as sql_util 

48from ..sql import visitors 

49from ..sql._typing import is_dml 

50from ..sql._typing import is_insert_update 

51from ..sql._typing import is_select_base 

52from ..sql.base import _select_iterables 

53from ..sql.base import CacheableOptions 

54from ..sql.base import CompileState 

55from ..sql.base import Executable 

56from ..sql.base import Generative 

57from ..sql.base import Options 

58from ..sql.dml import UpdateBase 

59from ..sql.elements import GroupedElement 

60from ..sql.elements import TextClause 

61from ..sql.selectable import CompoundSelectState 

62from ..sql.selectable import LABEL_STYLE_DISAMBIGUATE_ONLY 

63from ..sql.selectable import LABEL_STYLE_NONE 

64from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL 

65from ..sql.selectable import Select 

66from ..sql.selectable import SelectLabelStyle 

67from ..sql.selectable import SelectState 

68from ..sql.selectable import TypedReturnsRows 

69from ..sql.visitors import InternalTraversal 

70from ..util.typing import TupleAny 

71from ..util.typing import TypeVarTuple 

72from ..util.typing import Unpack 

73 

74 

75if TYPE_CHECKING: 

76 from ._typing import _InternalEntityType 

77 from ._typing import OrmExecuteOptionsParameter 

78 from .loading import PostLoad 

79 from .mapper import Mapper 

80 from .query import Query 

81 from .session import _BindArguments 

82 from .session import Session 

83 from ..engine import Result 

84 from ..engine.interfaces import _CoreSingleExecuteParams 

85 from ..sql._typing import _ColumnsClauseArgument 

86 from ..sql.compiler import SQLCompiler 

87 from ..sql.dml import _DMLTableElement 

88 from ..sql.elements import ColumnElement 

89 from ..sql.selectable import _JoinTargetElement 

90 from ..sql.selectable import _LabelConventionCallable 

91 from ..sql.selectable import _SetupJoinsElement 

92 from ..sql.selectable import ExecutableReturnsRows 

93 from ..sql.selectable import SelectBase 

94 from ..sql.type_api import TypeEngine 

95 

96_T = TypeVar("_T", bound=Any) 

97_Ts = TypeVarTuple("_Ts") 

98_path_registry = PathRegistry.root 

99 

100_EMPTY_DICT = util.immutabledict() 

101 

102 

103LABEL_STYLE_LEGACY_ORM = SelectLabelStyle.LABEL_STYLE_LEGACY_ORM 

104 

105 

106class QueryContext: 

107 __slots__ = ( 

108 "top_level_context", 

109 "compile_state", 

110 "query", 

111 "params", 

112 "load_options", 

113 "bind_arguments", 

114 "execution_options", 

115 "session", 

116 "autoflush", 

117 "populate_existing", 

118 "invoke_all_eagers", 

119 "version_check", 

120 "refresh_state", 

121 "create_eager_joins", 

122 "propagated_loader_options", 

123 "attributes", 

124 "runid", 

125 "partials", 

126 "post_load_paths", 

127 "identity_token", 

128 "yield_per", 

129 "loaders_require_buffering", 

130 "loaders_require_uniquing", 

131 ) 

132 

133 runid: int 

134 post_load_paths: Dict[PathRegistry, PostLoad] 

135 compile_state: ORMCompileState 

136 

137 class default_load_options(Options): 

138 _only_return_tuples = False 

139 _populate_existing = False 

140 _version_check = False 

141 _invoke_all_eagers = True 

142 _autoflush = True 

143 _identity_token = None 

144 _yield_per = None 

145 _refresh_state = None 

146 _lazy_loaded_from = None 

147 _legacy_uniquing = False 

148 _sa_top_level_orm_context = None 

149 _is_user_refresh = False 

150 

151 def __init__( 

152 self, 

153 compile_state: CompileState, 

154 statement: Union[ 

155 Select[Unpack[TupleAny]], 

156 FromStatement[Unpack[TupleAny]], 

157 ], 

158 params: _CoreSingleExecuteParams, 

159 session: Session, 

160 load_options: Union[ 

161 Type[QueryContext.default_load_options], 

162 QueryContext.default_load_options, 

163 ], 

164 execution_options: Optional[OrmExecuteOptionsParameter] = None, 

165 bind_arguments: Optional[_BindArguments] = None, 

166 ): 

167 self.load_options = load_options 

168 self.execution_options = execution_options or _EMPTY_DICT 

169 self.bind_arguments = bind_arguments or _EMPTY_DICT 

170 self.compile_state = compile_state 

171 self.query = statement 

172 self.session = session 

173 self.loaders_require_buffering = False 

174 self.loaders_require_uniquing = False 

175 self.params = params 

176 self.top_level_context = load_options._sa_top_level_orm_context 

177 

178 cached_options = compile_state.select_statement._with_options 

179 uncached_options = statement._with_options 

180 

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

182 # propagated loader options will be present on loaded InstanceState 

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

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

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

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

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

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

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

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

191 # AliasedClass 

192 self.propagated_loader_options = tuple( 

193 opt._adapt_cached_option_to_uncached_option(self, uncached_opt) 

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

195 if opt.propagate_to_loaders 

196 ) 

197 

198 self.attributes = dict(compile_state.attributes) 

199 

200 self.autoflush = load_options._autoflush 

201 self.populate_existing = load_options._populate_existing 

202 self.invoke_all_eagers = load_options._invoke_all_eagers 

203 self.version_check = load_options._version_check 

204 self.refresh_state = load_options._refresh_state 

205 self.yield_per = load_options._yield_per 

206 self.identity_token = load_options._identity_token 

207 

208 def _get_top_level_context(self) -> QueryContext: 

209 return self.top_level_context or self 

210 

211 

212_orm_load_exec_options = util.immutabledict( 

213 {"_result_disable_adapt_to_context": True} 

214) 

215 

216 

217class AbstractORMCompileState(CompileState): 

218 is_dml_returning = False 

219 

220 def _init_global_attributes( 

221 self, statement, compiler, *, toplevel, process_criteria_for_toplevel 

222 ): 

223 self.attributes = {} 

224 

225 if compiler is None: 

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

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

228 self.global_attributes = ga = {} 

229 assert toplevel 

230 return 

231 else: 

232 self.global_attributes = ga = compiler._global_attributes 

233 

234 if toplevel: 

235 ga["toplevel_orm"] = True 

236 

237 if process_criteria_for_toplevel: 

238 for opt in statement._with_options: 

239 if opt._is_criteria_option: 

240 opt.process_compile_state(self) 

241 

242 return 

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

244 return 

245 

246 stack_0 = compiler.stack[0] 

247 

248 try: 

249 toplevel_stmt = stack_0["selectable"] 

250 except KeyError: 

251 pass 

252 else: 

253 for opt in toplevel_stmt._with_options: 

254 if opt._is_compile_state and opt._is_criteria_option: 

255 opt.process_compile_state(self) 

256 

257 ga["toplevel_orm"] = True 

258 

259 @classmethod 

260 def create_for_statement( 

261 cls, 

262 statement: Union[Select, FromStatement], 

263 compiler: Optional[SQLCompiler], 

264 **kw: Any, 

265 ) -> AbstractORMCompileState: 

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

267 

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

269 

270 For a Select object, this would be invoked from 

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

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

273 FromStatement._compiler_dispatch() that would be called by 

274 SQLCompiler.process(). 

275 """ 

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

277 

278 @classmethod 

279 def orm_pre_session_exec( 

280 cls, 

281 session, 

282 statement, 

283 params, 

284 execution_options, 

285 bind_arguments, 

286 is_pre_event, 

287 ): 

288 raise NotImplementedError() 

289 

290 @classmethod 

291 def orm_execute_statement( 

292 cls, 

293 session, 

294 statement, 

295 params, 

296 execution_options, 

297 bind_arguments, 

298 conn, 

299 ) -> Result: 

300 result = conn.execute( 

301 statement, params or {}, execution_options=execution_options 

302 ) 

303 return cls.orm_setup_cursor_result( 

304 session, 

305 statement, 

306 params, 

307 execution_options, 

308 bind_arguments, 

309 result, 

310 ) 

311 

312 @classmethod 

313 def orm_setup_cursor_result( 

314 cls, 

315 session, 

316 statement, 

317 params, 

318 execution_options, 

319 bind_arguments, 

320 result, 

321 ): 

322 raise NotImplementedError() 

323 

324 

325class AutoflushOnlyORMCompileState(AbstractORMCompileState): 

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

327 

328 @classmethod 

329 def orm_pre_session_exec( 

330 cls, 

331 session, 

332 statement, 

333 params, 

334 execution_options, 

335 bind_arguments, 

336 is_pre_event, 

337 ): 

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

339 # in an ORMExecuteState hook 

340 ( 

341 load_options, 

342 execution_options, 

343 ) = QueryContext.default_load_options.from_execution_options( 

344 "_sa_orm_load_options", 

345 { 

346 "autoflush", 

347 }, 

348 execution_options, 

349 statement._execution_options, 

350 ) 

351 

352 if not is_pre_event and load_options._autoflush: 

353 session._autoflush() 

354 

355 return statement, execution_options 

356 

357 @classmethod 

358 def orm_setup_cursor_result( 

359 cls, 

360 session, 

361 statement, 

362 params, 

363 execution_options, 

364 bind_arguments, 

365 result, 

366 ): 

367 return result 

368 

369 

370class ORMCompileState(AbstractORMCompileState): 

371 class default_compile_options(CacheableOptions): 

372 _cache_key_traversal = [ 

373 ("_use_legacy_query_style", InternalTraversal.dp_boolean), 

374 ("_for_statement", InternalTraversal.dp_boolean), 

375 ("_bake_ok", InternalTraversal.dp_boolean), 

376 ("_current_path", InternalTraversal.dp_has_cache_key), 

377 ("_enable_single_crit", InternalTraversal.dp_boolean), 

378 ("_enable_eagerloads", InternalTraversal.dp_boolean), 

379 ("_only_load_props", InternalTraversal.dp_plain_obj), 

380 ("_set_base_alias", InternalTraversal.dp_boolean), 

381 ("_for_refresh_state", InternalTraversal.dp_boolean), 

382 ("_render_for_subquery", InternalTraversal.dp_boolean), 

383 ("_is_star", InternalTraversal.dp_boolean), 

384 ] 

385 

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

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

388 # now this basically indicates we should use tablename_columnname 

389 # style labels. Generally indicates the statement originated 

390 # from a Query object. 

391 _use_legacy_query_style = False 

392 

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

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

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

396 _for_statement = False 

397 

398 _bake_ok = True 

399 _current_path = _path_registry 

400 _enable_single_crit = True 

401 _enable_eagerloads = True 

402 _only_load_props = None 

403 _set_base_alias = False 

404 _for_refresh_state = False 

405 _render_for_subquery = False 

406 _is_star = False 

407 

408 attributes: Dict[Any, Any] 

409 global_attributes: Dict[Any, Any] 

410 

411 statement: Union[Select[Unpack[TupleAny]], FromStatement[Unpack[TupleAny]]] 

412 select_statement: Union[ 

413 Select[Unpack[TupleAny]], FromStatement[Unpack[TupleAny]] 

414 ] 

415 _entities: List[_QueryEntity] 

416 _polymorphic_adapters: Dict[_InternalEntityType, ORMAdapter] 

417 compile_options: Union[ 

418 Type[default_compile_options], default_compile_options 

419 ] 

420 _primary_entity: Optional[_QueryEntity] 

421 use_legacy_query_style: bool 

422 _label_convention: _LabelConventionCallable 

423 primary_columns: List[ColumnElement[Any]] 

424 secondary_columns: List[ColumnElement[Any]] 

425 dedupe_columns: Set[ColumnElement[Any]] 

426 create_eager_joins: List[ 

427 # TODO: this structure is set up by JoinedLoader 

428 TupleAny 

429 ] 

430 current_path: PathRegistry = _path_registry 

431 _has_mapper_entities = False 

432 

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

434 raise NotImplementedError() 

435 

436 if TYPE_CHECKING: 

437 

438 @classmethod 

439 def create_for_statement( 

440 cls, 

441 statement: Union[Select, FromStatement], 

442 compiler: Optional[SQLCompiler], 

443 **kw: Any, 

444 ) -> ORMCompileState: ... 

445 

446 def _append_dedupe_col_collection(self, obj, col_collection): 

447 dedupe = self.dedupe_columns 

448 if obj not in dedupe: 

449 dedupe.add(obj) 

450 col_collection.append(obj) 

451 

452 @classmethod 

453 def _column_naming_convention( 

454 cls, label_style: SelectLabelStyle, legacy: bool 

455 ) -> _LabelConventionCallable: 

456 if legacy: 

457 

458 def name(col, col_name=None): 

459 if col_name: 

460 return col_name 

461 else: 

462 return getattr(col, "key") 

463 

464 return name 

465 else: 

466 return SelectState._column_naming_convention(label_style) 

467 

468 @classmethod 

469 def get_column_descriptions(cls, statement): 

470 return _column_descriptions(statement) 

471 

472 @classmethod 

473 def orm_pre_session_exec( 

474 cls, 

475 session, 

476 statement, 

477 params, 

478 execution_options, 

479 bind_arguments, 

480 is_pre_event, 

481 ): 

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

483 # in an ORMExecuteState hook 

484 ( 

485 load_options, 

486 execution_options, 

487 ) = QueryContext.default_load_options.from_execution_options( 

488 "_sa_orm_load_options", 

489 { 

490 "populate_existing", 

491 "autoflush", 

492 "yield_per", 

493 "identity_token", 

494 "sa_top_level_orm_context", 

495 }, 

496 execution_options, 

497 statement._execution_options, 

498 ) 

499 

500 # default execution options for ORM results: 

501 # 1. _result_disable_adapt_to_context=True 

502 # this will disable the ResultSetMetadata._adapt_to_context() 

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

504 # against the original SELECT statement before caching. 

505 

506 if "sa_top_level_orm_context" in execution_options: 

507 ctx = execution_options["sa_top_level_orm_context"] 

508 execution_options = ctx.query._execution_options.merge_with( 

509 ctx.execution_options, execution_options 

510 ) 

511 

512 if not execution_options: 

513 execution_options = _orm_load_exec_options 

514 else: 

515 execution_options = execution_options.union(_orm_load_exec_options) 

516 

517 # would have been placed here by legacy Query only 

518 if load_options._yield_per: 

519 execution_options = execution_options.union( 

520 {"yield_per": load_options._yield_per} 

521 ) 

522 

523 if ( 

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

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

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

527 ): 

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

529 execution_options.union( 

530 { 

531 "compiled_cache": None, 

532 "_cache_disable_reason": "excess depth for " 

533 "ORM loader options", 

534 } 

535 ) 

536 ) 

537 

538 bind_arguments["clause"] = statement 

539 

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

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

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

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

544 

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

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

547 # needs to be present as well. 

548 

549 try: 

550 plugin_subject = statement._propagate_attrs["plugin_subject"] 

551 except KeyError: 

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

553 else: 

554 if plugin_subject: 

555 bind_arguments["mapper"] = plugin_subject.mapper 

556 

557 if not is_pre_event and load_options._autoflush: 

558 session._autoflush() 

559 

560 return statement, execution_options 

561 

562 @classmethod 

563 def orm_setup_cursor_result( 

564 cls, 

565 session, 

566 statement, 

567 params, 

568 execution_options, 

569 bind_arguments, 

570 result, 

571 ): 

572 execution_context = result.context 

573 compile_state = execution_context.compiled.compile_state 

574 

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

576 # were passed to session.execute: 

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

578 # see test_query->test_legacy_tuple_old_select 

579 

580 load_options = execution_options.get( 

581 "_sa_orm_load_options", QueryContext.default_load_options 

582 ) 

583 

584 if compile_state.compile_options._is_star: 

585 return result 

586 

587 querycontext = QueryContext( 

588 compile_state, 

589 statement, 

590 params, 

591 session, 

592 load_options, 

593 execution_options, 

594 bind_arguments, 

595 ) 

596 return loading.instances(result, querycontext) 

597 

598 @property 

599 def _lead_mapper_entities(self): 

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

601 

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

603 with_entities(), with_only_columns() 

604 

605 """ 

606 return [ 

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

608 ] 

609 

610 def _create_with_polymorphic_adapter(self, ext_info, selectable): 

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

612 if called for by the Mapper. 

613 

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

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

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

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

618 loading, and joined inheritance where a subquery is 

619 passed to with_polymorphic (which is completely unnecessary in modern 

620 use). 

621 

622 """ 

623 if ( 

624 not ext_info.is_aliased_class 

625 and ext_info.mapper.persist_selectable 

626 not in self._polymorphic_adapters 

627 ): 

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

629 self._mapper_loads_polymorphically_with( 

630 mp, 

631 ORMAdapter( 

632 _TraceAdaptRole.WITH_POLYMORPHIC_ADAPTER, 

633 mp, 

634 equivalents=mp._equivalent_columns, 

635 selectable=selectable, 

636 ), 

637 ) 

638 

639 def _mapper_loads_polymorphically_with(self, mapper, adapter): 

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

641 self._polymorphic_adapters[m2] = adapter 

642 

643 for m in m2.iterate_to_root(): 

644 self._polymorphic_adapters[m.local_table] = adapter 

645 

646 @classmethod 

647 def _create_entities_collection(cls, query, legacy): 

648 raise NotImplementedError( 

649 "this method only works for ORMSelectCompileState" 

650 ) 

651 

652 

653class DMLReturningColFilter: 

654 """an adapter used for the DML RETURNING case. 

655 

656 Has a subset of the interface used by 

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

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

659 DML statement. 

660 

661 """ 

662 

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

664 

665 def __init__(self, target_mapper, immediate_dml_mapper): 

666 if ( 

667 immediate_dml_mapper is not None 

668 and target_mapper.local_table 

669 is not immediate_dml_mapper.local_table 

670 ): 

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

672 self.mapper = immediate_dml_mapper 

673 else: 

674 # single inh, normal mappings, etc. 

675 self.mapper = target_mapper 

676 self.columns = self.columns = util.WeakPopulateDict( 

677 self.adapt_check_present # type: ignore 

678 ) 

679 

680 def __call__(self, col, as_filter): 

681 for cc in sql_util._find_columns(col): 

682 c2 = self.adapt_check_present(cc) 

683 if c2 is not None: 

684 return col 

685 else: 

686 return None 

687 

688 def adapt_check_present(self, col): 

689 mapper = self.mapper 

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

691 if prop is None: 

692 return None 

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

694 

695 

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

697class ORMFromStatementCompileState(ORMCompileState): 

698 _from_obj_alias = None 

699 _has_mapper_entities = False 

700 

701 statement_container: FromStatement 

702 requested_statement: Union[SelectBase, TextClause, UpdateBase] 

703 dml_table: Optional[_DMLTableElement] = None 

704 

705 _has_orm_entities = False 

706 multi_row_eager_loaders = False 

707 eager_adding_joins = False 

708 compound_eager_adapter = None 

709 

710 extra_criteria_entities = _EMPTY_DICT 

711 eager_joins = _EMPTY_DICT 

712 

713 @classmethod 

714 def create_for_statement( 

715 cls, 

716 statement_container: Union[Select, FromStatement], 

717 compiler: Optional[SQLCompiler], 

718 **kw: Any, 

719 ) -> ORMFromStatementCompileState: 

720 assert isinstance(statement_container, FromStatement) 

721 

722 if compiler is not None and compiler.stack: 

723 raise sa_exc.CompileError( 

724 "The ORM FromStatement construct only supports being " 

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

726 "define how result rows should be returned." 

727 ) 

728 

729 self = cls.__new__(cls) 

730 self._primary_entity = None 

731 

732 self.use_legacy_query_style = ( 

733 statement_container._compile_options._use_legacy_query_style 

734 ) 

735 self.statement_container = self.select_statement = statement_container 

736 self.requested_statement = statement = statement_container.element 

737 

738 if statement.is_dml: 

739 self.dml_table = statement.table 

740 self.is_dml_returning = True 

741 

742 self._entities = [] 

743 self._polymorphic_adapters = {} 

744 

745 self.compile_options = statement_container._compile_options 

746 

747 if ( 

748 self.use_legacy_query_style 

749 and isinstance(statement, expression.SelectBase) 

750 and not statement._is_textual 

751 and not statement.is_dml 

752 and statement._label_style is LABEL_STYLE_NONE 

753 ): 

754 self.statement = statement.set_label_style( 

755 LABEL_STYLE_TABLENAME_PLUS_COL 

756 ) 

757 else: 

758 self.statement = statement 

759 

760 self._label_convention = self._column_naming_convention( 

761 ( 

762 statement._label_style 

763 if not statement._is_textual and not statement.is_dml 

764 else LABEL_STYLE_NONE 

765 ), 

766 self.use_legacy_query_style, 

767 ) 

768 

769 _QueryEntity.to_compile_state( 

770 self, 

771 statement_container._raw_columns, 

772 self._entities, 

773 is_current_entities=True, 

774 ) 

775 

776 self.current_path = statement_container._compile_options._current_path 

777 

778 self._init_global_attributes( 

779 statement_container, 

780 compiler, 

781 process_criteria_for_toplevel=False, 

782 toplevel=True, 

783 ) 

784 

785 if statement_container._with_options: 

786 for opt in statement_container._with_options: 

787 if opt._is_compile_state: 

788 opt.process_compile_state(self) 

789 

790 if statement_container._with_context_options: 

791 for fn, key in statement_container._with_context_options: 

792 fn(self) 

793 

794 self.primary_columns = [] 

795 self.secondary_columns = [] 

796 self.dedupe_columns = set() 

797 self.create_eager_joins = [] 

798 self._fallback_from_clauses = [] 

799 

800 self.order_by = None 

801 

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

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

804 # we generate columns from our _QueryEntity objects, then 

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

806 self.extra_criteria_entities = {} 

807 

808 for entity in self._entities: 

809 entity.setup_compile_state(self) 

810 

811 compiler._ordered_columns = compiler._textual_ordered_columns = ( 

812 False 

813 ) 

814 

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

816 # needed by test_query.py::TextTest 

817 compiler._loose_column_name_matching = True 

818 

819 for c in self.primary_columns: 

820 compiler.process( 

821 c, 

822 within_columns_clause=True, 

823 add_to_result_map=compiler._add_to_result_map, 

824 ) 

825 else: 

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

827 # have column objects already. After much 

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

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

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

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

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

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

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

835 self._from_obj_alias = ORMStatementAdapter( 

836 _TraceAdaptRole.ADAPT_FROM_STATEMENT, 

837 self.statement, 

838 adapt_on_names=statement_container._adapt_on_names, 

839 ) 

840 

841 return self 

842 

843 def _adapt_col_list(self, cols, current_adapter): 

844 return cols 

845 

846 def _get_current_adapter(self): 

847 return None 

848 

849 def setup_dml_returning_compile_state(self, dml_mapper): 

850 """used by BulkORMInsert (and Update / Delete?) to set up a handler 

851 for RETURNING to return ORM objects and expressions 

852 

853 """ 

854 target_mapper = self.statement._propagate_attrs.get( 

855 "plugin_subject", None 

856 ) 

857 adapter = DMLReturningColFilter(target_mapper, dml_mapper) 

858 

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

860 raise sa_exc.CompileError( 

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

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

863 ) 

864 

865 for entity in self._entities: 

866 entity.setup_dml_returning_compile_state(self, adapter) 

867 

868 

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

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

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

872 

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

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

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

876 

877 """ 

878 

879 __visit_name__ = "orm_from_statement" 

880 

881 _compile_options = ORMFromStatementCompileState.default_compile_options 

882 

883 _compile_state_factory = ORMFromStatementCompileState.create_for_statement 

884 

885 _for_update_arg = None 

886 

887 element: Union[ExecutableReturnsRows, TextClause] 

888 

889 _adapt_on_names: bool 

890 

891 _traverse_internals = [ 

892 ("_raw_columns", InternalTraversal.dp_clauseelement_list), 

893 ("element", InternalTraversal.dp_clauseelement), 

894 ] + Executable._executable_traverse_internals 

895 

896 _cache_key_traversal = _traverse_internals + [ 

897 ("_compile_options", InternalTraversal.dp_has_cache_key) 

898 ] 

899 

900 is_from_statement = True 

901 

902 def __init__( 

903 self, 

904 entities: Iterable[_ColumnsClauseArgument[Any]], 

905 element: Union[ExecutableReturnsRows, TextClause], 

906 _adapt_on_names: bool = True, 

907 ): 

908 self._raw_columns = [ 

909 coercions.expect( 

910 roles.ColumnsClauseRole, 

911 ent, 

912 apply_propagate_attrs=self, 

913 post_inspect=True, 

914 ) 

915 for ent in util.to_list(entities) 

916 ] 

917 self.element = element 

918 self.is_dml = element.is_dml 

919 self.is_select = element.is_select 

920 self.is_delete = element.is_delete 

921 self.is_insert = element.is_insert 

922 self.is_update = element.is_update 

923 self._label_style = ( 

924 element._label_style if is_select_base(element) else None 

925 ) 

926 self._adapt_on_names = _adapt_on_names 

927 

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

929 """provide a fixed _compiler_dispatch method. 

930 

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

932 ``@compiles`` extension. 

933 

934 """ 

935 

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

937 

938 toplevel = not compiler.stack 

939 

940 if toplevel: 

941 compiler.compile_state = compile_state 

942 

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

944 

945 @property 

946 def column_descriptions(self): 

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

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

949 

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

951 of this feature. 

952 

953 .. seealso:: 

954 

955 :ref:`queryguide_inspection` - ORM background 

956 

957 """ 

958 meth = cast( 

959 ORMSelectCompileState, SelectState.get_plugin_class(self) 

960 ).get_column_descriptions 

961 return meth(self) 

962 

963 def _ensure_disambiguated_names(self): 

964 return self 

965 

966 def get_children(self, **kw): 

967 yield from itertools.chain.from_iterable( 

968 element._from_objects for element in self._raw_columns 

969 ) 

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

971 

972 @property 

973 def _all_selected_columns(self): 

974 return self.element._all_selected_columns 

975 

976 @property 

977 def _return_defaults(self): 

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

979 

980 @property 

981 def _returning(self): 

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

983 

984 @property 

985 def _inline(self): 

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

987 

988 

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

990class CompoundSelectCompileState( 

991 AutoflushOnlyORMCompileState, CompoundSelectState 

992): 

993 pass 

994 

995 

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

997class ORMSelectCompileState(ORMCompileState, SelectState): 

998 _already_joined_edges = () 

999 

1000 _memoized_entities = _EMPTY_DICT 

1001 

1002 _from_obj_alias = None 

1003 _has_mapper_entities = False 

1004 

1005 _has_orm_entities = False 

1006 multi_row_eager_loaders = False 

1007 eager_adding_joins = False 

1008 compound_eager_adapter = None 

1009 

1010 correlate = None 

1011 correlate_except = None 

1012 _where_criteria = () 

1013 _having_criteria = () 

1014 

1015 @classmethod 

1016 def create_for_statement( 

1017 cls, 

1018 statement: Union[Select, FromStatement], 

1019 compiler: Optional[SQLCompiler], 

1020 **kw: Any, 

1021 ) -> ORMSelectCompileState: 

1022 """compiler hook, we arrive here from compiler.visit_select() only.""" 

1023 

1024 self = cls.__new__(cls) 

1025 

1026 if compiler is not None: 

1027 toplevel = not compiler.stack 

1028 else: 

1029 toplevel = True 

1030 

1031 select_statement = statement 

1032 

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

1034 # have ORM level compile options. 

1035 statement._compile_options = cls.default_compile_options.safe_merge( 

1036 statement._compile_options 

1037 ) 

1038 

1039 if select_statement._execution_options: 

1040 # execution options should not impact the compilation of a 

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

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

1043 self.select_statement = select_statement._clone() 

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

1045 else: 

1046 self.select_statement = select_statement 

1047 

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

1049 self.for_statement = select_statement._compile_options._for_statement 

1050 

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

1052 self.use_legacy_query_style = ( 

1053 select_statement._compile_options._use_legacy_query_style 

1054 ) 

1055 

1056 self._entities = [] 

1057 self._primary_entity = None 

1058 self._polymorphic_adapters = {} 

1059 

1060 self.compile_options = select_statement._compile_options 

1061 

1062 if not toplevel: 

1063 # for subqueries, turn off eagerloads and set 

1064 # "render_for_subquery". 

1065 self.compile_options += { 

1066 "_enable_eagerloads": False, 

1067 "_render_for_subquery": True, 

1068 } 

1069 

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

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

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

1073 # for new style ORM selects too. 

1074 if ( 

1075 self.use_legacy_query_style 

1076 and self.select_statement._label_style is LABEL_STYLE_LEGACY_ORM 

1077 ): 

1078 if not self.for_statement: 

1079 self.label_style = LABEL_STYLE_TABLENAME_PLUS_COL 

1080 else: 

1081 self.label_style = LABEL_STYLE_DISAMBIGUATE_ONLY 

1082 else: 

1083 self.label_style = self.select_statement._label_style 

1084 

1085 if select_statement._memoized_select_entities: 

1086 self._memoized_entities = { 

1087 memoized_entities: _QueryEntity.to_compile_state( 

1088 self, 

1089 memoized_entities._raw_columns, 

1090 [], 

1091 is_current_entities=False, 

1092 ) 

1093 for memoized_entities in ( 

1094 select_statement._memoized_select_entities 

1095 ) 

1096 } 

1097 

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

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

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

1101 # in the columns clause 

1102 self._label_convention = self._column_naming_convention( 

1103 statement._label_style, self.use_legacy_query_style 

1104 ) 

1105 

1106 _QueryEntity.to_compile_state( 

1107 self, 

1108 select_statement._raw_columns, 

1109 self._entities, 

1110 is_current_entities=True, 

1111 ) 

1112 

1113 self.current_path = select_statement._compile_options._current_path 

1114 

1115 self.eager_order_by = () 

1116 

1117 self._init_global_attributes( 

1118 select_statement, 

1119 compiler, 

1120 toplevel=toplevel, 

1121 process_criteria_for_toplevel=False, 

1122 ) 

1123 

1124 if toplevel and ( 

1125 select_statement._with_options 

1126 or select_statement._memoized_select_entities 

1127 ): 

1128 for ( 

1129 memoized_entities 

1130 ) in select_statement._memoized_select_entities: 

1131 for opt in memoized_entities._with_options: 

1132 if opt._is_compile_state: 

1133 opt.process_compile_state_replaced_entities( 

1134 self, 

1135 [ 

1136 ent 

1137 for ent in self._memoized_entities[ 

1138 memoized_entities 

1139 ] 

1140 if isinstance(ent, _MapperEntity) 

1141 ], 

1142 ) 

1143 

1144 for opt in self.select_statement._with_options: 

1145 if opt._is_compile_state: 

1146 opt.process_compile_state(self) 

1147 

1148 # uncomment to print out the context.attributes structure 

1149 # after it's been set up above 

1150 # self._dump_option_struct() 

1151 

1152 if select_statement._with_context_options: 

1153 for fn, key in select_statement._with_context_options: 

1154 fn(self) 

1155 

1156 self.primary_columns = [] 

1157 self.secondary_columns = [] 

1158 self.dedupe_columns = set() 

1159 self.eager_joins = {} 

1160 self.extra_criteria_entities = {} 

1161 self.create_eager_joins = [] 

1162 self._fallback_from_clauses = [] 

1163 

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

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

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

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

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

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

1170 self.from_clauses = self._normalize_froms( 

1171 info.selectable for info in select_statement._from_obj 

1172 ) 

1173 

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

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

1176 # and _setup_for_generate into three or four logical sections 

1177 self._setup_for_generate() 

1178 

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

1180 return self 

1181 

1182 def _dump_option_struct(self): 

1183 print("\n---------------------------------------------------\n") 

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

1185 for key in self.attributes: 

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

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

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

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

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

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

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

1193 

1194 def _setup_for_generate(self): 

1195 query = self.select_statement 

1196 

1197 self.statement = None 

1198 self._join_entities = () 

1199 

1200 if self.compile_options._set_base_alias: 

1201 # legacy Query only 

1202 self._set_select_from_alias() 

1203 

1204 for memoized_entities in query._memoized_select_entities: 

1205 if memoized_entities._setup_joins: 

1206 self._join( 

1207 memoized_entities._setup_joins, 

1208 self._memoized_entities[memoized_entities], 

1209 ) 

1210 

1211 if query._setup_joins: 

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

1213 

1214 current_adapter = self._get_current_adapter() 

1215 

1216 if query._where_criteria: 

1217 self._where_criteria = query._where_criteria 

1218 

1219 if current_adapter: 

1220 self._where_criteria = tuple( 

1221 current_adapter(crit, True) 

1222 for crit in self._where_criteria 

1223 ) 

1224 

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

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

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

1228 self.order_by = ( 

1229 self._adapt_col_list(query._order_by_clauses, current_adapter) 

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

1231 else query._order_by_clauses 

1232 ) 

1233 

1234 if query._having_criteria: 

1235 self._having_criteria = tuple( 

1236 current_adapter(crit, True) if current_adapter else crit 

1237 for crit in query._having_criteria 

1238 ) 

1239 

1240 self.group_by = ( 

1241 self._adapt_col_list( 

1242 util.flatten_iterator(query._group_by_clauses), current_adapter 

1243 ) 

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

1245 else query._group_by_clauses or None 

1246 ) 

1247 

1248 if self.eager_order_by: 

1249 adapter = self.from_clauses[0]._target_adapter 

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

1251 

1252 if query._distinct_on: 

1253 self.distinct_on = self._adapt_col_list( 

1254 query._distinct_on, current_adapter 

1255 ) 

1256 else: 

1257 self.distinct_on = () 

1258 

1259 self.distinct = query._distinct 

1260 

1261 if query._correlate: 

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

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

1264 # tables. 

1265 self.correlate = tuple( 

1266 util.flatten_iterator( 

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

1268 for s in query._correlate 

1269 ) 

1270 ) 

1271 elif query._correlate_except is not None: 

1272 self.correlate_except = tuple( 

1273 util.flatten_iterator( 

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

1275 for s in query._correlate_except 

1276 ) 

1277 ) 

1278 elif not query._auto_correlate: 

1279 self.correlate = (None,) 

1280 

1281 # PART II 

1282 

1283 self._for_update_arg = query._for_update_arg 

1284 

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

1286 raise sa_exc.CompileError( 

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

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

1289 ) 

1290 for entity in self._entities: 

1291 entity.setup_compile_state(self) 

1292 

1293 for rec in self.create_eager_joins: 

1294 strategy = rec[0] 

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

1296 

1297 # else "load from discrete FROMs" mode, 

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

1299 

1300 if self.compile_options._enable_single_crit: 

1301 self._adjust_for_extra_criteria() 

1302 

1303 if not self.primary_columns: 

1304 if self.compile_options._only_load_props: 

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

1306 

1307 raise sa_exc.InvalidRequestError( 

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

1309 ) 

1310 

1311 if not self.from_clauses: 

1312 self.from_clauses = list(self._fallback_from_clauses) 

1313 

1314 if self.order_by is False: 

1315 self.order_by = None 

1316 

1317 if ( 

1318 self.multi_row_eager_loaders 

1319 and self.eager_adding_joins 

1320 and self._should_nest_selectable 

1321 ): 

1322 self.statement = self._compound_eager_statement() 

1323 else: 

1324 self.statement = self._simple_statement() 

1325 

1326 if self.for_statement: 

1327 ezero = self._mapper_zero() 

1328 if ezero is not None: 

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

1330 # thing 

1331 self.statement = self.statement._annotate( 

1332 {"deepentity": ezero} 

1333 ) 

1334 

1335 @classmethod 

1336 def _create_entities_collection(cls, query, legacy): 

1337 """Creates a partial ORMSelectCompileState that includes 

1338 the full collection of _MapperEntity and other _QueryEntity objects. 

1339 

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

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

1342 

1343 """ 

1344 self = cls.__new__(cls) 

1345 

1346 self._entities = [] 

1347 self._primary_entity = None 

1348 self._polymorphic_adapters = {} 

1349 

1350 self._label_convention = self._column_naming_convention( 

1351 query._label_style, legacy 

1352 ) 

1353 

1354 # entities will also set up polymorphic adapters for mappers 

1355 # that have with_polymorphic configured 

1356 _QueryEntity.to_compile_state( 

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

1358 ) 

1359 return self 

1360 

1361 @classmethod 

1362 def determine_last_joined_entity(cls, statement): 

1363 setup_joins = statement._setup_joins 

1364 

1365 return _determine_last_joined_entity(setup_joins, None) 

1366 

1367 @classmethod 

1368 def all_selected_columns(cls, statement): 

1369 for element in statement._raw_columns: 

1370 if ( 

1371 element.is_selectable 

1372 and "entity_namespace" in element._annotations 

1373 ): 

1374 ens = element._annotations["entity_namespace"] 

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

1376 yield from _select_iterables([element]) 

1377 else: 

1378 yield from _select_iterables(ens._all_column_expressions) 

1379 else: 

1380 yield from _select_iterables([element]) 

1381 

1382 @classmethod 

1383 def get_columns_clause_froms(cls, statement): 

1384 return cls._normalize_froms( 

1385 itertools.chain.from_iterable( 

1386 ( 

1387 element._from_objects 

1388 if "parententity" not in element._annotations 

1389 else [ 

1390 element._annotations[ 

1391 "parententity" 

1392 ].__clause_element__() 

1393 ] 

1394 ) 

1395 for element in statement._raw_columns 

1396 ) 

1397 ) 

1398 

1399 @classmethod 

1400 def from_statement(cls, statement, from_statement): 

1401 from_statement = coercions.expect( 

1402 roles.ReturnsRowsRole, 

1403 from_statement, 

1404 apply_propagate_attrs=statement, 

1405 ) 

1406 

1407 stmt = FromStatement(statement._raw_columns, from_statement) 

1408 

1409 stmt.__dict__.update( 

1410 _with_options=statement._with_options, 

1411 _with_context_options=statement._with_context_options, 

1412 _execution_options=statement._execution_options, 

1413 _propagate_attrs=statement._propagate_attrs, 

1414 ) 

1415 return stmt 

1416 

1417 def _set_select_from_alias(self): 

1418 """used only for legacy Query cases""" 

1419 

1420 query = self.select_statement # query 

1421 

1422 assert self.compile_options._set_base_alias 

1423 assert len(query._from_obj) == 1 

1424 

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

1426 if adapter: 

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

1428 self._from_obj_alias = adapter 

1429 

1430 def _get_select_from_alias_from_obj(self, from_obj): 

1431 """used only for legacy Query cases""" 

1432 

1433 info = from_obj 

1434 

1435 if "parententity" in info._annotations: 

1436 info = info._annotations["parententity"] 

1437 

1438 if hasattr(info, "mapper"): 

1439 if not info.is_aliased_class: 

1440 raise sa_exc.ArgumentError( 

1441 "A selectable (FromClause) instance is " 

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

1443 ) 

1444 else: 

1445 return info._adapter 

1446 

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

1448 equivs = self._all_equivs() 

1449 assert info is info.selectable 

1450 return ORMStatementAdapter( 

1451 _TraceAdaptRole.LEGACY_SELECT_FROM_ALIAS, 

1452 info.selectable, 

1453 equivalents=equivs, 

1454 ) 

1455 else: 

1456 return None 

1457 

1458 def _mapper_zero(self): 

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

1460 return self._entities[0].mapper 

1461 

1462 def _entity_zero(self): 

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

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

1465 entity if specified.""" 

1466 

1467 for ent in self.from_clauses: 

1468 if "parententity" in ent._annotations: 

1469 return ent._annotations["parententity"] 

1470 for qent in self._entities: 

1471 if qent.entity_zero: 

1472 return qent.entity_zero 

1473 

1474 return None 

1475 

1476 def _only_full_mapper_zero(self, methname): 

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

1478 raise sa_exc.InvalidRequestError( 

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

1480 "a single mapped class." % methname 

1481 ) 

1482 return self._primary_entity.entity_zero 

1483 

1484 def _only_entity_zero(self, rationale=None): 

1485 if len(self._entities) > 1: 

1486 raise sa_exc.InvalidRequestError( 

1487 rationale 

1488 or "This operation requires a Query " 

1489 "against a single mapper." 

1490 ) 

1491 return self._entity_zero() 

1492 

1493 def _all_equivs(self): 

1494 equivs = {} 

1495 

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

1497 for ent in [ 

1498 ent 

1499 for ent in memoized_entities 

1500 if isinstance(ent, _MapperEntity) 

1501 ]: 

1502 equivs.update(ent.mapper._equivalent_columns) 

1503 

1504 for ent in [ 

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

1506 ]: 

1507 equivs.update(ent.mapper._equivalent_columns) 

1508 return equivs 

1509 

1510 def _compound_eager_statement(self): 

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

1512 # wrap the query inside a select, 

1513 # then append eager joins onto that 

1514 

1515 if self.order_by: 

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

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

1518 # elements are converted into label references. For the 

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

1520 # the original expressions outside of the label references 

1521 # in order to have them render. 

1522 unwrapped_order_by = [ 

1523 ( 

1524 elem.element 

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

1526 else elem 

1527 ) 

1528 for elem in self.order_by 

1529 ] 

1530 

1531 order_by_col_expr = sql_util.expand_column_list_from_order_by( 

1532 self.primary_columns, unwrapped_order_by 

1533 ) 

1534 else: 

1535 order_by_col_expr = [] 

1536 unwrapped_order_by = None 

1537 

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

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

1540 inner = self._select_statement( 

1541 self.primary_columns 

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

1543 self.from_clauses, 

1544 self._where_criteria, 

1545 self._having_criteria, 

1546 self.label_style, 

1547 self.order_by, 

1548 for_update=self._for_update_arg, 

1549 hints=self.select_statement._hints, 

1550 statement_hints=self.select_statement._statement_hints, 

1551 correlate=self.correlate, 

1552 correlate_except=self.correlate_except, 

1553 **self._select_args, 

1554 ) 

1555 

1556 inner = inner.alias() 

1557 

1558 equivs = self._all_equivs() 

1559 

1560 self.compound_eager_adapter = ORMStatementAdapter( 

1561 _TraceAdaptRole.COMPOUND_EAGER_STATEMENT, inner, equivalents=equivs 

1562 ) 

1563 

1564 statement = future.select( 

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

1566 ) 

1567 statement._label_style = self.label_style 

1568 

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

1570 # and the Oracle dialect ignores it, plus for PostgreSQL, MySQL 

1571 # we expect that all elements of the row are locked, so also put it 

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

1573 if ( 

1574 self._for_update_arg is not None 

1575 and self._for_update_arg.of is None 

1576 ): 

1577 statement._for_update_arg = self._for_update_arg 

1578 

1579 from_clause = inner 

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

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

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

1583 # the join should be 

1584 from_clause = sql_util.splice_joins( 

1585 from_clause, eager_join, eager_join.stop_on 

1586 ) 

1587 

1588 statement.select_from.non_generative(statement, from_clause) 

1589 

1590 if unwrapped_order_by: 

1591 statement.order_by.non_generative( 

1592 statement, 

1593 *self.compound_eager_adapter.copy_and_process( 

1594 unwrapped_order_by 

1595 ), 

1596 ) 

1597 

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

1599 return statement 

1600 

1601 def _simple_statement(self): 

1602 statement = self._select_statement( 

1603 self.primary_columns + self.secondary_columns, 

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

1605 self._where_criteria, 

1606 self._having_criteria, 

1607 self.label_style, 

1608 self.order_by, 

1609 for_update=self._for_update_arg, 

1610 hints=self.select_statement._hints, 

1611 statement_hints=self.select_statement._statement_hints, 

1612 correlate=self.correlate, 

1613 correlate_except=self.correlate_except, 

1614 **self._select_args, 

1615 ) 

1616 

1617 if self.eager_order_by: 

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

1619 return statement 

1620 

1621 def _select_statement( 

1622 self, 

1623 raw_columns, 

1624 from_obj, 

1625 where_criteria, 

1626 having_criteria, 

1627 label_style, 

1628 order_by, 

1629 for_update, 

1630 hints, 

1631 statement_hints, 

1632 correlate, 

1633 correlate_except, 

1634 limit_clause, 

1635 offset_clause, 

1636 fetch_clause, 

1637 fetch_clause_options, 

1638 distinct, 

1639 distinct_on, 

1640 prefixes, 

1641 suffixes, 

1642 group_by, 

1643 independent_ctes, 

1644 independent_ctes_opts, 

1645 ): 

1646 statement = Select._create_raw_select( 

1647 _raw_columns=raw_columns, 

1648 _from_obj=from_obj, 

1649 _label_style=label_style, 

1650 ) 

1651 

1652 if where_criteria: 

1653 statement._where_criteria = where_criteria 

1654 if having_criteria: 

1655 statement._having_criteria = having_criteria 

1656 

1657 if order_by: 

1658 statement._order_by_clauses += tuple(order_by) 

1659 

1660 if distinct_on: 

1661 statement.distinct.non_generative(statement, *distinct_on) 

1662 elif distinct: 

1663 statement.distinct.non_generative(statement) 

1664 

1665 if group_by: 

1666 statement._group_by_clauses += tuple(group_by) 

1667 

1668 statement._limit_clause = limit_clause 

1669 statement._offset_clause = offset_clause 

1670 statement._fetch_clause = fetch_clause 

1671 statement._fetch_clause_options = fetch_clause_options 

1672 statement._independent_ctes = independent_ctes 

1673 statement._independent_ctes_opts = independent_ctes_opts 

1674 

1675 if prefixes: 

1676 statement._prefixes = prefixes 

1677 

1678 if suffixes: 

1679 statement._suffixes = suffixes 

1680 

1681 statement._for_update_arg = for_update 

1682 

1683 if hints: 

1684 statement._hints = hints 

1685 if statement_hints: 

1686 statement._statement_hints = statement_hints 

1687 

1688 if correlate: 

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

1690 

1691 if correlate_except is not None: 

1692 statement.correlate_except.non_generative( 

1693 statement, *correlate_except 

1694 ) 

1695 

1696 return statement 

1697 

1698 def _adapt_polymorphic_element(self, element): 

1699 if "parententity" in element._annotations: 

1700 search = element._annotations["parententity"] 

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

1702 if alias: 

1703 return alias.adapt_clause(element) 

1704 

1705 if isinstance(element, expression.FromClause): 

1706 search = element 

1707 elif hasattr(element, "table"): 

1708 search = element.table 

1709 else: 

1710 return None 

1711 

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

1713 if alias: 

1714 return alias.adapt_clause(element) 

1715 

1716 def _adapt_col_list(self, cols, current_adapter): 

1717 if current_adapter: 

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

1719 else: 

1720 return cols 

1721 

1722 def _get_current_adapter(self): 

1723 adapters = [] 

1724 

1725 if self._from_obj_alias: 

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

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

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

1729 # select_entity_from() 

1730 # 

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

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

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

1734 # to all SQL constructs. 

1735 adapters.append( 

1736 ( 

1737 True, 

1738 self._from_obj_alias.replace, 

1739 ) 

1740 ) 

1741 

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

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

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

1745 if self._polymorphic_adapters: 

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

1747 

1748 if not adapters: 

1749 return None 

1750 

1751 def _adapt_clause(clause, as_filter): 

1752 # do we adapt all expression elements or only those 

1753 # tagged as 'ORM' constructs ? 

1754 

1755 def replace(elem): 

1756 is_orm_adapt = ( 

1757 "_orm_adapt" in elem._annotations 

1758 or "parententity" in elem._annotations 

1759 ) 

1760 for always_adapt, adapter in adapters: 

1761 if is_orm_adapt or always_adapt: 

1762 e = adapter(elem) 

1763 if e is not None: 

1764 return e 

1765 

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

1767 

1768 return _adapt_clause 

1769 

1770 def _join(self, args, entities_collection): 

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

1772 isouter = flags["isouter"] 

1773 full = flags["full"] 

1774 

1775 right = inspect(right) 

1776 if onclause is not None: 

1777 onclause = inspect(onclause) 

1778 

1779 if isinstance(right, interfaces.PropComparator): 

1780 if onclause is not None: 

1781 raise sa_exc.InvalidRequestError( 

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

1783 "to a relationship path as a target" 

1784 ) 

1785 

1786 onclause = right 

1787 right = None 

1788 elif "parententity" in right._annotations: 

1789 right = right._annotations["parententity"] 

1790 

1791 if onclause is None: 

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

1793 raise sa_exc.ArgumentError( 

1794 "Expected mapped entity or " 

1795 "selectable/table as join target" 

1796 ) 

1797 

1798 of_type = None 

1799 

1800 if isinstance(onclause, interfaces.PropComparator): 

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

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

1803 

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

1805 

1806 if right is None: 

1807 if of_type: 

1808 right = of_type 

1809 else: 

1810 right = onclause.property 

1811 

1812 try: 

1813 right = right.entity 

1814 except AttributeError as err: 

1815 raise sa_exc.ArgumentError( 

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

1817 "mapped entity" % right 

1818 ) from err 

1819 

1820 left = onclause._parententity 

1821 

1822 prop = onclause.property 

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

1824 onclause = prop 

1825 

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

1827 # case. 

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

1829 continue 

1830 

1831 if from_ is not None: 

1832 if ( 

1833 from_ is not left 

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

1835 is not left 

1836 ): 

1837 raise sa_exc.InvalidRequestError( 

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

1839 "of relationship attribute %s" 

1840 % ( 

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

1842 onclause, 

1843 ) 

1844 ) 

1845 elif from_ is not None: 

1846 prop = None 

1847 left = from_ 

1848 else: 

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

1850 # what the effective "left" side is 

1851 prop = left = None 

1852 

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

1854 # ORMJoin to add to our _from_obj tuple 

1855 self._join_left_to_right( 

1856 entities_collection, 

1857 left, 

1858 right, 

1859 onclause, 

1860 prop, 

1861 isouter, 

1862 full, 

1863 ) 

1864 

1865 def _join_left_to_right( 

1866 self, 

1867 entities_collection, 

1868 left, 

1869 right, 

1870 onclause, 

1871 prop, 

1872 outerjoin, 

1873 full, 

1874 ): 

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

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

1877 our _from_obj list (or augment an existing one) 

1878 

1879 """ 

1880 

1881 if left is None: 

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

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

1884 # entities 

1885 assert prop is None 

1886 ( 

1887 left, 

1888 replace_from_obj_index, 

1889 use_entity_index, 

1890 ) = self._join_determine_implicit_left_side( 

1891 entities_collection, left, right, onclause 

1892 ) 

1893 else: 

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

1895 # Determine where in our 

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

1897 # existing entity it corresponds to. 

1898 ( 

1899 replace_from_obj_index, 

1900 use_entity_index, 

1901 ) = self._join_place_explicit_left_side(entities_collection, left) 

1902 

1903 if left is right: 

1904 raise sa_exc.InvalidRequestError( 

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

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

1907 ) 

1908 

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

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

1911 # get back the new effective "right" side 

1912 r_info, right, onclause = self._join_check_and_adapt_right_side( 

1913 left, right, onclause, prop 

1914 ) 

1915 

1916 if not r_info.is_selectable: 

1917 extra_criteria = self._get_extra_criteria(r_info) 

1918 else: 

1919 extra_criteria = () 

1920 

1921 if replace_from_obj_index is not None: 

1922 # splice into an existing element in the 

1923 # self._from_obj list 

1924 left_clause = self.from_clauses[replace_from_obj_index] 

1925 

1926 self.from_clauses = ( 

1927 self.from_clauses[:replace_from_obj_index] 

1928 + [ 

1929 _ORMJoin( 

1930 left_clause, 

1931 right, 

1932 onclause, 

1933 isouter=outerjoin, 

1934 full=full, 

1935 _extra_criteria=extra_criteria, 

1936 ) 

1937 ] 

1938 + self.from_clauses[replace_from_obj_index + 1 :] 

1939 ) 

1940 else: 

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

1942 if use_entity_index is not None: 

1943 # make use of _MapperEntity selectable, which is usually 

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

1945 # might be distinct 

1946 assert isinstance( 

1947 entities_collection[use_entity_index], _MapperEntity 

1948 ) 

1949 left_clause = entities_collection[use_entity_index].selectable 

1950 else: 

1951 left_clause = left 

1952 

1953 self.from_clauses = self.from_clauses + [ 

1954 _ORMJoin( 

1955 left_clause, 

1956 r_info, 

1957 onclause, 

1958 isouter=outerjoin, 

1959 full=full, 

1960 _extra_criteria=extra_criteria, 

1961 ) 

1962 ] 

1963 

1964 def _join_determine_implicit_left_side( 

1965 self, entities_collection, left, right, onclause 

1966 ): 

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

1968 determine if an existing FROM or entity in this query 

1969 can serve as the left hand side. 

1970 

1971 """ 

1972 

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

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

1975 # 

1976 # join(RightEntity) 

1977 # 

1978 # or 

1979 # 

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

1981 # 

1982 

1983 r_info = inspect(right) 

1984 

1985 replace_from_obj_index = use_entity_index = None 

1986 

1987 if self.from_clauses: 

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

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

1990 

1991 indexes = sql_util.find_left_clause_to_join_from( 

1992 self.from_clauses, r_info.selectable, onclause 

1993 ) 

1994 

1995 if len(indexes) == 1: 

1996 replace_from_obj_index = indexes[0] 

1997 left = self.from_clauses[replace_from_obj_index] 

1998 elif len(indexes) > 1: 

1999 raise sa_exc.InvalidRequestError( 

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

2001 "from, there are multiple FROMS which can " 

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

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

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

2005 "to help resolve the ambiguity." 

2006 ) 

2007 else: 

2008 raise sa_exc.InvalidRequestError( 

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

2010 "Please use the .select_from() " 

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

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

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

2014 ) 

2015 

2016 elif entities_collection: 

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

2018 # come from our list of entities. 

2019 

2020 potential = {} 

2021 for entity_index, ent in enumerate(entities_collection): 

2022 entity = ent.entity_zero_or_selectable 

2023 if entity is None: 

2024 continue 

2025 ent_info = inspect(entity) 

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

2027 continue 

2028 

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

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

2031 # against a series of columns from the same selectable 

2032 if isinstance(ent, _MapperEntity): 

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

2034 else: 

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

2036 

2037 all_clauses = list(potential.keys()) 

2038 indexes = sql_util.find_left_clause_to_join_from( 

2039 all_clauses, r_info.selectable, onclause 

2040 ) 

2041 

2042 if len(indexes) == 1: 

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

2044 elif len(indexes) > 1: 

2045 raise sa_exc.InvalidRequestError( 

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

2047 "from, there are multiple FROMS which can " 

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

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

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

2051 "to help resolve the ambiguity." 

2052 ) 

2053 else: 

2054 raise sa_exc.InvalidRequestError( 

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

2056 "Please use the .select_from() " 

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

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

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

2060 ) 

2061 else: 

2062 raise sa_exc.InvalidRequestError( 

2063 "No entities to join from; please use " 

2064 "select_from() to establish the left " 

2065 "entity/selectable of this join" 

2066 ) 

2067 

2068 return left, replace_from_obj_index, use_entity_index 

2069 

2070 def _join_place_explicit_left_side(self, entities_collection, left): 

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

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

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

2074 existing entities. 

2075 

2076 """ 

2077 

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

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

2080 # Relationship was given, e.g.: 

2081 # 

2082 # join(RightEntity, LeftEntity.right) 

2083 # 

2084 # or 

2085 # 

2086 # join(LeftEntity.right) 

2087 # 

2088 # as well as string forms: 

2089 # 

2090 # join(RightEntity, "right") 

2091 # 

2092 # etc. 

2093 # 

2094 

2095 replace_from_obj_index = use_entity_index = None 

2096 

2097 l_info = inspect(left) 

2098 if self.from_clauses: 

2099 indexes = sql_util.find_left_clause_that_matches_given( 

2100 self.from_clauses, l_info.selectable 

2101 ) 

2102 

2103 if len(indexes) > 1: 

2104 raise sa_exc.InvalidRequestError( 

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

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

2107 "ON clause." 

2108 ) 

2109 

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

2111 # an existing FROM in the self._from_obj tuple 

2112 if indexes: 

2113 replace_from_obj_index = indexes[0] 

2114 

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

2116 # self._from_obj tuple 

2117 

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

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

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

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

2122 if ( 

2123 replace_from_obj_index is None 

2124 and entities_collection 

2125 and hasattr(l_info, "mapper") 

2126 ): 

2127 for idx, ent in enumerate(entities_collection): 

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

2129 # matching? 

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

2131 use_entity_index = idx 

2132 break 

2133 

2134 return replace_from_obj_index, use_entity_index 

2135 

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

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

2138 according to polymorphic mapping translations, aliasing on the query 

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

2140 overlapping tables. 

2141 

2142 """ 

2143 

2144 l_info = inspect(left) 

2145 r_info = inspect(right) 

2146 

2147 overlap = False 

2148 

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

2150 # if the target is a joined inheritance mapping, 

2151 # be more liberal about auto-aliasing. 

2152 if right_mapper and ( 

2153 right_mapper.with_polymorphic 

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

2155 ): 

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

2157 if sql_util.selectables_overlap( 

2158 l_info.selectable, from_obj 

2159 ) and sql_util.selectables_overlap( 

2160 from_obj, r_info.selectable 

2161 ): 

2162 overlap = True 

2163 break 

2164 

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

2166 raise sa_exc.InvalidRequestError( 

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

2168 % l_info.selectable 

2169 ) 

2170 

2171 right_mapper, right_selectable, right_is_aliased = ( 

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

2173 r_info.selectable, 

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

2175 ) 

2176 

2177 if ( 

2178 right_mapper 

2179 and prop 

2180 and not right_mapper.common_parent(prop.mapper) 

2181 ): 

2182 raise sa_exc.InvalidRequestError( 

2183 "Join target %s does not correspond to " 

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

2185 ) 

2186 

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

2188 # purposes at the moment 

2189 if hasattr(r_info, "mapper"): 

2190 self._join_entities += (r_info,) 

2191 

2192 need_adapter = False 

2193 

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

2195 if r_info.is_clause_element: 

2196 if prop: 

2197 right_mapper = prop.mapper 

2198 

2199 if right_selectable._is_lateral: 

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

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

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

2203 current_adapter = self._get_current_adapter() 

2204 if current_adapter is not None: 

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

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

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

2208 right = current_adapter(right, True) 

2209 

2210 elif prop: 

2211 # joining to selectable with a mapper property given 

2212 # as the ON clause 

2213 

2214 if not right_selectable.is_derived_from( 

2215 right_mapper.persist_selectable 

2216 ): 

2217 raise sa_exc.InvalidRequestError( 

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

2219 % ( 

2220 right_selectable.description, 

2221 right_mapper.persist_selectable.description, 

2222 ) 

2223 ) 

2224 

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

2226 # turn it into an alias(). 

2227 if isinstance(right_selectable, expression.SelectBase): 

2228 right_selectable = coercions.expect( 

2229 roles.FromClauseRole, right_selectable 

2230 ) 

2231 need_adapter = True 

2232 

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

2234 right = AliasedClass(right_mapper, right_selectable) 

2235 

2236 util.warn_deprecated( 

2237 "An alias is being generated automatically against " 

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

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

2240 "Use the aliased() " 

2241 "construct explicitly, see the linked example." 

2242 % right_mapper, 

2243 "1.4", 

2244 code="xaj1", 

2245 ) 

2246 

2247 # test for overlap: 

2248 # orm/inheritance/relationships.py 

2249 # SelfReferentialM2MTest 

2250 aliased_entity = right_mapper and not right_is_aliased and overlap 

2251 

2252 if not need_adapter and aliased_entity: 

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

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

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

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

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

2258 # aliasing is desirable. 

2259 right = AliasedClass(right, flat=True) 

2260 need_adapter = True 

2261 

2262 util.warn( 

2263 "An alias is being generated automatically against " 

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

2265 "legacy pattern which may be " 

2266 "deprecated in a later release. Use the " 

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

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

2269 code="xaj2", 

2270 ) 

2271 

2272 if need_adapter: 

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

2274 # a warning has been emitted. 

2275 assert right_mapper 

2276 

2277 adapter = ORMAdapter( 

2278 _TraceAdaptRole.DEPRECATED_JOIN_ADAPT_RIGHT_SIDE, 

2279 inspect(right), 

2280 equivalents=right_mapper._equivalent_columns, 

2281 ) 

2282 

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

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

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

2286 # set are also adapted. 

2287 self._mapper_loads_polymorphically_with(right_mapper, adapter) 

2288 elif ( 

2289 not r_info.is_clause_element 

2290 and not right_is_aliased 

2291 and right_mapper._has_aliased_polymorphic_fromclause 

2292 ): 

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

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

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

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

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

2298 # strictly necessary. 

2299 # see test/orm/test_core_compilation.py 

2300 # ::RelNaturalAliasedJoinsTest::test_straight 

2301 # and similar 

2302 self._mapper_loads_polymorphically_with( 

2303 right_mapper, 

2304 ORMAdapter( 

2305 _TraceAdaptRole.WITH_POLYMORPHIC_ADAPTER_RIGHT_JOIN, 

2306 right_mapper, 

2307 selectable=right_mapper.selectable, 

2308 equivalents=right_mapper._equivalent_columns, 

2309 ), 

2310 ) 

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

2312 # adapters that are in place right now 

2313 if isinstance(onclause, expression.ClauseElement): 

2314 current_adapter = self._get_current_adapter() 

2315 if current_adapter: 

2316 onclause = current_adapter(onclause, True) 

2317 

2318 # if joining on a MapperProperty path, 

2319 # track the path to prevent redundant joins 

2320 if prop: 

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

2322 

2323 return inspect(right), right, onclause 

2324 

2325 @property 

2326 def _select_args(self): 

2327 return { 

2328 "limit_clause": self.select_statement._limit_clause, 

2329 "offset_clause": self.select_statement._offset_clause, 

2330 "distinct": self.distinct, 

2331 "distinct_on": self.distinct_on, 

2332 "prefixes": self.select_statement._prefixes, 

2333 "suffixes": self.select_statement._suffixes, 

2334 "group_by": self.group_by or None, 

2335 "fetch_clause": self.select_statement._fetch_clause, 

2336 "fetch_clause_options": ( 

2337 self.select_statement._fetch_clause_options 

2338 ), 

2339 "independent_ctes": self.select_statement._independent_ctes, 

2340 "independent_ctes_opts": ( 

2341 self.select_statement._independent_ctes_opts 

2342 ), 

2343 } 

2344 

2345 @property 

2346 def _should_nest_selectable(self): 

2347 kwargs = self._select_args 

2348 return ( 

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

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

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

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

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

2354 ) 

2355 

2356 def _get_extra_criteria(self, ext_info): 

2357 if ( 

2358 "additional_entity_criteria", 

2359 ext_info.mapper, 

2360 ) in self.global_attributes: 

2361 return tuple( 

2362 ae._resolve_where_criteria(ext_info) 

2363 for ae in self.global_attributes[ 

2364 ("additional_entity_criteria", ext_info.mapper) 

2365 ] 

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

2367 and ae._should_include(self) 

2368 ) 

2369 else: 

2370 return () 

2371 

2372 def _adjust_for_extra_criteria(self): 

2373 """Apply extra criteria filtering. 

2374 

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

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

2377 add criterion to the WHERE 

2378 clause of the given QueryContext such that only the appropriate 

2379 subtypes are selected from the total results. 

2380 

2381 Additionally, add WHERE criteria originating from LoaderCriteriaOptions 

2382 associated with the global context. 

2383 

2384 """ 

2385 

2386 for fromclause in self.from_clauses: 

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

2388 

2389 if ( 

2390 ext_info 

2391 and ( 

2392 ext_info.mapper._single_table_criterion is not None 

2393 or ("additional_entity_criteria", ext_info.mapper) 

2394 in self.global_attributes 

2395 ) 

2396 and ext_info not in self.extra_criteria_entities 

2397 ): 

2398 self.extra_criteria_entities[ext_info] = ( 

2399 ext_info, 

2400 ext_info._adapter if ext_info.is_aliased_class else None, 

2401 ) 

2402 

2403 search = set(self.extra_criteria_entities.values()) 

2404 

2405 for ext_info, adapter in search: 

2406 if ext_info in self._join_entities: 

2407 continue 

2408 

2409 single_crit = ext_info.mapper._single_table_criterion 

2410 

2411 if self.compile_options._for_refresh_state: 

2412 additional_entity_criteria = [] 

2413 else: 

2414 additional_entity_criteria = self._get_extra_criteria(ext_info) 

2415 

2416 if single_crit is not None: 

2417 additional_entity_criteria += (single_crit,) 

2418 

2419 current_adapter = self._get_current_adapter() 

2420 for crit in additional_entity_criteria: 

2421 if adapter: 

2422 crit = adapter.traverse(crit) 

2423 

2424 if current_adapter: 

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

2426 crit = current_adapter(crit, False) 

2427 self._where_criteria += (crit,) 

2428 

2429 

2430def _column_descriptions( 

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

2432 compile_state: Optional[ORMSelectCompileState] = None, 

2433 legacy: bool = False, 

2434) -> List[ORMColumnDescription]: 

2435 if compile_state is None: 

2436 compile_state = ORMSelectCompileState._create_entities_collection( 

2437 query_or_select_stmt, legacy=legacy 

2438 ) 

2439 ctx = compile_state 

2440 d = [ 

2441 { 

2442 "name": ent._label_name, 

2443 "type": ent.type, 

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

2445 "expr": ent.expr, 

2446 "entity": ( 

2447 getattr(insp_ent, "entity", None) 

2448 if ent.entity_zero is not None 

2449 and not insp_ent.is_clause_element 

2450 else None 

2451 ), 

2452 } 

2453 for ent, insp_ent in [ 

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

2455 ] 

2456 ] 

2457 return d 

2458 

2459 

2460def _legacy_filter_by_entity_zero( 

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

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

2463 self = query_or_augmented_select 

2464 if self._setup_joins: 

2465 _last_joined_entity = self._last_joined_entity 

2466 if _last_joined_entity is not None: 

2467 return _last_joined_entity 

2468 

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

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

2471 

2472 return _entity_from_pre_ent_zero(self) 

2473 

2474 

2475def _entity_from_pre_ent_zero( 

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

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

2478 self = query_or_augmented_select 

2479 if not self._raw_columns: 

2480 return None 

2481 

2482 ent = self._raw_columns[0] 

2483 

2484 if "parententity" in ent._annotations: 

2485 return ent._annotations["parententity"] 

2486 elif isinstance(ent, ORMColumnsClauseRole): 

2487 return ent.entity 

2488 elif "bundle" in ent._annotations: 

2489 return ent._annotations["bundle"] 

2490 else: 

2491 return ent 

2492 

2493 

2494def _determine_last_joined_entity( 

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

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

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

2498 if not setup_joins: 

2499 return None 

2500 

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

2502 

2503 if isinstance( 

2504 target, 

2505 attributes.QueryableAttribute, 

2506 ): 

2507 return target.entity 

2508 else: 

2509 return target 

2510 

2511 

2512class _QueryEntity: 

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

2514 

2515 __slots__ = () 

2516 

2517 supports_single_entity: bool 

2518 

2519 _non_hashable_value = False 

2520 _null_column_type = False 

2521 use_id_for_hash = False 

2522 

2523 _label_name: Optional[str] 

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

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

2526 entity_zero: Optional[_InternalEntityType] 

2527 

2528 def setup_compile_state(self, compile_state: ORMCompileState) -> None: 

2529 raise NotImplementedError() 

2530 

2531 def setup_dml_returning_compile_state( 

2532 self, 

2533 compile_state: ORMCompileState, 

2534 adapter: DMLReturningColFilter, 

2535 ) -> None: 

2536 raise NotImplementedError() 

2537 

2538 def row_processor(self, context, result): 

2539 raise NotImplementedError() 

2540 

2541 @classmethod 

2542 def to_compile_state( 

2543 cls, compile_state, entities, entities_collection, is_current_entities 

2544 ): 

2545 for idx, entity in enumerate(entities): 

2546 if entity._is_lambda_element: 

2547 if entity._is_sequence: 

2548 cls.to_compile_state( 

2549 compile_state, 

2550 entity._resolved, 

2551 entities_collection, 

2552 is_current_entities, 

2553 ) 

2554 continue 

2555 else: 

2556 entity = entity._resolved 

2557 

2558 if entity.is_clause_element: 

2559 if entity.is_selectable: 

2560 if "parententity" in entity._annotations: 

2561 _MapperEntity( 

2562 compile_state, 

2563 entity, 

2564 entities_collection, 

2565 is_current_entities, 

2566 ) 

2567 else: 

2568 _ColumnEntity._for_columns( 

2569 compile_state, 

2570 entity._select_iterable, 

2571 entities_collection, 

2572 idx, 

2573 is_current_entities, 

2574 ) 

2575 else: 

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

2577 _BundleEntity( 

2578 compile_state, 

2579 entity, 

2580 entities_collection, 

2581 is_current_entities, 

2582 ) 

2583 elif entity._is_clause_list: 

2584 # this is legacy only - test_composites.py 

2585 # test_query_cols_legacy 

2586 _ColumnEntity._for_columns( 

2587 compile_state, 

2588 entity._select_iterable, 

2589 entities_collection, 

2590 idx, 

2591 is_current_entities, 

2592 ) 

2593 else: 

2594 _ColumnEntity._for_columns( 

2595 compile_state, 

2596 [entity], 

2597 entities_collection, 

2598 idx, 

2599 is_current_entities, 

2600 ) 

2601 elif entity.is_bundle: 

2602 _BundleEntity(compile_state, entity, entities_collection) 

2603 

2604 return entities_collection 

2605 

2606 

2607class _MapperEntity(_QueryEntity): 

2608 """mapper/class/AliasedClass entity""" 

2609 

2610 __slots__ = ( 

2611 "expr", 

2612 "mapper", 

2613 "entity_zero", 

2614 "is_aliased_class", 

2615 "path", 

2616 "_extra_entities", 

2617 "_label_name", 

2618 "_with_polymorphic_mappers", 

2619 "selectable", 

2620 "_polymorphic_discriminator", 

2621 ) 

2622 

2623 expr: _InternalEntityType 

2624 mapper: Mapper[Any] 

2625 entity_zero: _InternalEntityType 

2626 is_aliased_class: bool 

2627 path: PathRegistry 

2628 _label_name: str 

2629 

2630 def __init__( 

2631 self, compile_state, entity, entities_collection, is_current_entities 

2632 ): 

2633 entities_collection.append(self) 

2634 if is_current_entities: 

2635 if compile_state._primary_entity is None: 

2636 compile_state._primary_entity = self 

2637 compile_state._has_mapper_entities = True 

2638 compile_state._has_orm_entities = True 

2639 

2640 entity = entity._annotations["parententity"] 

2641 entity._post_inspect 

2642 ext_info = self.entity_zero = entity 

2643 entity = ext_info.entity 

2644 

2645 self.expr = entity 

2646 self.mapper = mapper = ext_info.mapper 

2647 

2648 self._extra_entities = (self.expr,) 

2649 

2650 if ext_info.is_aliased_class: 

2651 self._label_name = ext_info.name 

2652 else: 

2653 self._label_name = mapper.class_.__name__ 

2654 

2655 self.is_aliased_class = ext_info.is_aliased_class 

2656 self.path = ext_info._path_registry 

2657 

2658 self.selectable = ext_info.selectable 

2659 self._with_polymorphic_mappers = ext_info.with_polymorphic_mappers 

2660 self._polymorphic_discriminator = ext_info.polymorphic_on 

2661 

2662 if mapper._should_select_with_poly_adapter: 

2663 compile_state._create_with_polymorphic_adapter( 

2664 ext_info, self.selectable 

2665 ) 

2666 

2667 supports_single_entity = True 

2668 

2669 _non_hashable_value = True 

2670 use_id_for_hash = True 

2671 

2672 @property 

2673 def type(self): 

2674 return self.mapper.class_ 

2675 

2676 @property 

2677 def entity_zero_or_selectable(self): 

2678 return self.entity_zero 

2679 

2680 def corresponds_to(self, entity): 

2681 return _entity_corresponds_to(self.entity_zero, entity) 

2682 

2683 def _get_entity_clauses(self, compile_state): 

2684 adapter = None 

2685 

2686 if not self.is_aliased_class: 

2687 if compile_state._polymorphic_adapters: 

2688 adapter = compile_state._polymorphic_adapters.get( 

2689 self.mapper, None 

2690 ) 

2691 else: 

2692 adapter = self.entity_zero._adapter 

2693 

2694 if adapter: 

2695 if compile_state._from_obj_alias: 

2696 ret = adapter.wrap(compile_state._from_obj_alias) 

2697 else: 

2698 ret = adapter 

2699 else: 

2700 ret = compile_state._from_obj_alias 

2701 

2702 return ret 

2703 

2704 def row_processor(self, context, result): 

2705 compile_state = context.compile_state 

2706 adapter = self._get_entity_clauses(compile_state) 

2707 

2708 if compile_state.compound_eager_adapter and adapter: 

2709 adapter = adapter.wrap(compile_state.compound_eager_adapter) 

2710 elif not adapter: 

2711 adapter = compile_state.compound_eager_adapter 

2712 

2713 if compile_state._primary_entity is self: 

2714 only_load_props = compile_state.compile_options._only_load_props 

2715 refresh_state = context.refresh_state 

2716 else: 

2717 only_load_props = refresh_state = None 

2718 

2719 _instance = loading._instance_processor( 

2720 self, 

2721 self.mapper, 

2722 context, 

2723 result, 

2724 self.path, 

2725 adapter, 

2726 only_load_props=only_load_props, 

2727 refresh_state=refresh_state, 

2728 polymorphic_discriminator=self._polymorphic_discriminator, 

2729 ) 

2730 

2731 return _instance, self._label_name, self._extra_entities 

2732 

2733 def setup_dml_returning_compile_state( 

2734 self, 

2735 compile_state: ORMCompileState, 

2736 adapter: DMLReturningColFilter, 

2737 ) -> None: 

2738 loading._setup_entity_query( 

2739 compile_state, 

2740 self.mapper, 

2741 self, 

2742 self.path, 

2743 adapter, 

2744 compile_state.primary_columns, 

2745 with_polymorphic=self._with_polymorphic_mappers, 

2746 only_load_props=compile_state.compile_options._only_load_props, 

2747 polymorphic_discriminator=self._polymorphic_discriminator, 

2748 ) 

2749 

2750 def setup_compile_state(self, compile_state): 

2751 adapter = self._get_entity_clauses(compile_state) 

2752 

2753 single_table_crit = self.mapper._single_table_criterion 

2754 if ( 

2755 single_table_crit is not None 

2756 or ("additional_entity_criteria", self.mapper) 

2757 in compile_state.global_attributes 

2758 ): 

2759 ext_info = self.entity_zero 

2760 compile_state.extra_criteria_entities[ext_info] = ( 

2761 ext_info, 

2762 ext_info._adapter if ext_info.is_aliased_class else None, 

2763 ) 

2764 

2765 loading._setup_entity_query( 

2766 compile_state, 

2767 self.mapper, 

2768 self, 

2769 self.path, 

2770 adapter, 

2771 compile_state.primary_columns, 

2772 with_polymorphic=self._with_polymorphic_mappers, 

2773 only_load_props=compile_state.compile_options._only_load_props, 

2774 polymorphic_discriminator=self._polymorphic_discriminator, 

2775 ) 

2776 compile_state._fallback_from_clauses.append(self.selectable) 

2777 

2778 

2779class _BundleEntity(_QueryEntity): 

2780 _extra_entities = () 

2781 

2782 __slots__ = ( 

2783 "bundle", 

2784 "expr", 

2785 "type", 

2786 "_label_name", 

2787 "_entities", 

2788 "supports_single_entity", 

2789 ) 

2790 

2791 _entities: List[_QueryEntity] 

2792 bundle: Bundle 

2793 type: Type[Any] 

2794 _label_name: str 

2795 supports_single_entity: bool 

2796 expr: Bundle 

2797 

2798 def __init__( 

2799 self, 

2800 compile_state, 

2801 expr, 

2802 entities_collection, 

2803 is_current_entities, 

2804 setup_entities=True, 

2805 parent_bundle=None, 

2806 ): 

2807 compile_state._has_orm_entities = True 

2808 

2809 expr = expr._annotations["bundle"] 

2810 if parent_bundle: 

2811 parent_bundle._entities.append(self) 

2812 else: 

2813 entities_collection.append(self) 

2814 

2815 if isinstance( 

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

2817 ): 

2818 bundle = expr.__clause_element__() 

2819 else: 

2820 bundle = expr 

2821 

2822 self.bundle = self.expr = bundle 

2823 self.type = type(bundle) 

2824 self._label_name = bundle.name 

2825 self._entities = [] 

2826 

2827 if setup_entities: 

2828 for expr in bundle.exprs: 

2829 if "bundle" in expr._annotations: 

2830 _BundleEntity( 

2831 compile_state, 

2832 expr, 

2833 entities_collection, 

2834 is_current_entities, 

2835 parent_bundle=self, 

2836 ) 

2837 elif isinstance(expr, Bundle): 

2838 _BundleEntity( 

2839 compile_state, 

2840 expr, 

2841 entities_collection, 

2842 is_current_entities, 

2843 parent_bundle=self, 

2844 ) 

2845 else: 

2846 _ORMColumnEntity._for_columns( 

2847 compile_state, 

2848 [expr], 

2849 entities_collection, 

2850 None, 

2851 is_current_entities, 

2852 parent_bundle=self, 

2853 ) 

2854 

2855 self.supports_single_entity = self.bundle.single_entity 

2856 

2857 @property 

2858 def mapper(self): 

2859 ezero = self.entity_zero 

2860 if ezero is not None: 

2861 return ezero.mapper 

2862 else: 

2863 return None 

2864 

2865 @property 

2866 def entity_zero(self): 

2867 for ent in self._entities: 

2868 ezero = ent.entity_zero 

2869 if ezero is not None: 

2870 return ezero 

2871 else: 

2872 return None 

2873 

2874 def corresponds_to(self, entity): 

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

2876 # we are working around it 

2877 return False 

2878 

2879 @property 

2880 def entity_zero_or_selectable(self): 

2881 for ent in self._entities: 

2882 ezero = ent.entity_zero_or_selectable 

2883 if ezero is not None: 

2884 return ezero 

2885 else: 

2886 return None 

2887 

2888 def setup_compile_state(self, compile_state): 

2889 for ent in self._entities: 

2890 ent.setup_compile_state(compile_state) 

2891 

2892 def setup_dml_returning_compile_state( 

2893 self, 

2894 compile_state: ORMCompileState, 

2895 adapter: DMLReturningColFilter, 

2896 ) -> None: 

2897 return self.setup_compile_state(compile_state) 

2898 

2899 def row_processor(self, context, result): 

2900 procs, labels, extra = zip( 

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

2902 ) 

2903 

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

2905 

2906 return proc, self._label_name, self._extra_entities 

2907 

2908 

2909class _ColumnEntity(_QueryEntity): 

2910 __slots__ = ( 

2911 "_fetch_column", 

2912 "_row_processor", 

2913 "raw_column_index", 

2914 "translate_raw_column", 

2915 ) 

2916 

2917 @classmethod 

2918 def _for_columns( 

2919 cls, 

2920 compile_state, 

2921 columns, 

2922 entities_collection, 

2923 raw_column_index, 

2924 is_current_entities, 

2925 parent_bundle=None, 

2926 ): 

2927 for column in columns: 

2928 annotations = column._annotations 

2929 if "parententity" in annotations: 

2930 _entity = annotations["parententity"] 

2931 else: 

2932 _entity = sql_util.extract_first_column_annotation( 

2933 column, "parententity" 

2934 ) 

2935 

2936 if _entity: 

2937 if "identity_token" in column._annotations: 

2938 _IdentityTokenEntity( 

2939 compile_state, 

2940 column, 

2941 entities_collection, 

2942 _entity, 

2943 raw_column_index, 

2944 is_current_entities, 

2945 parent_bundle=parent_bundle, 

2946 ) 

2947 else: 

2948 _ORMColumnEntity( 

2949 compile_state, 

2950 column, 

2951 entities_collection, 

2952 _entity, 

2953 raw_column_index, 

2954 is_current_entities, 

2955 parent_bundle=parent_bundle, 

2956 ) 

2957 else: 

2958 _RawColumnEntity( 

2959 compile_state, 

2960 column, 

2961 entities_collection, 

2962 raw_column_index, 

2963 is_current_entities, 

2964 parent_bundle=parent_bundle, 

2965 ) 

2966 

2967 @property 

2968 def type(self): 

2969 return self.column.type 

2970 

2971 @property 

2972 def _non_hashable_value(self): 

2973 return not self.column.type.hashable 

2974 

2975 @property 

2976 def _null_column_type(self): 

2977 return self.column.type._isnull 

2978 

2979 def row_processor(self, context, result): 

2980 compile_state = context.compile_state 

2981 

2982 # the resulting callable is entirely cacheable so just return 

2983 # it if we already made one 

2984 if self._row_processor is not None: 

2985 getter, label_name, extra_entities = self._row_processor 

2986 if self.translate_raw_column: 

2987 extra_entities += ( 

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

2989 ) 

2990 

2991 return getter, label_name, extra_entities 

2992 

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

2994 # setup_compile_state, to avoid doing redundant work 

2995 if self._fetch_column is not None: 

2996 column = self._fetch_column 

2997 else: 

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

2999 # and setup_compile_state may not have been called. 

3000 column = self.column 

3001 

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

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

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

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

3006 if compile_state._from_obj_alias: 

3007 column = compile_state._from_obj_alias.columns[column] 

3008 

3009 if column._annotations: 

3010 # annotated columns perform more slowly in compiler and 

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

3012 column = column._deannotate() 

3013 

3014 if compile_state.compound_eager_adapter: 

3015 column = compile_state.compound_eager_adapter.columns[column] 

3016 

3017 getter = result._getter(column) 

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

3019 self._row_processor = ret 

3020 

3021 if self.translate_raw_column: 

3022 extra_entities = self._extra_entities + ( 

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

3024 ) 

3025 return getter, self._label_name, extra_entities 

3026 else: 

3027 return ret 

3028 

3029 

3030class _RawColumnEntity(_ColumnEntity): 

3031 entity_zero = None 

3032 mapper = None 

3033 supports_single_entity = False 

3034 

3035 __slots__ = ( 

3036 "expr", 

3037 "column", 

3038 "_label_name", 

3039 "entity_zero_or_selectable", 

3040 "_extra_entities", 

3041 ) 

3042 

3043 def __init__( 

3044 self, 

3045 compile_state, 

3046 column, 

3047 entities_collection, 

3048 raw_column_index, 

3049 is_current_entities, 

3050 parent_bundle=None, 

3051 ): 

3052 self.expr = column 

3053 self.raw_column_index = raw_column_index 

3054 self.translate_raw_column = raw_column_index is not None 

3055 

3056 if column._is_star: 

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

3058 

3059 if not is_current_entities or column._is_text_clause: 

3060 self._label_name = None 

3061 else: 

3062 if parent_bundle: 

3063 self._label_name = column._proxy_key 

3064 else: 

3065 self._label_name = compile_state._label_convention(column) 

3066 

3067 if parent_bundle: 

3068 parent_bundle._entities.append(self) 

3069 else: 

3070 entities_collection.append(self) 

3071 

3072 self.column = column 

3073 self.entity_zero_or_selectable = ( 

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

3075 ) 

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

3077 self._fetch_column = self._row_processor = None 

3078 

3079 def corresponds_to(self, entity): 

3080 return False 

3081 

3082 def setup_dml_returning_compile_state( 

3083 self, 

3084 compile_state: ORMCompileState, 

3085 adapter: DMLReturningColFilter, 

3086 ) -> None: 

3087 return self.setup_compile_state(compile_state) 

3088 

3089 def setup_compile_state(self, compile_state): 

3090 current_adapter = compile_state._get_current_adapter() 

3091 if current_adapter: 

3092 column = current_adapter(self.column, False) 

3093 if column is None: 

3094 return 

3095 else: 

3096 column = self.column 

3097 

3098 if column._annotations: 

3099 # annotated columns perform more slowly in compiler and 

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

3101 column = column._deannotate() 

3102 

3103 compile_state.dedupe_columns.add(column) 

3104 compile_state.primary_columns.append(column) 

3105 self._fetch_column = column 

3106 

3107 

3108class _ORMColumnEntity(_ColumnEntity): 

3109 """Column/expression based entity.""" 

3110 

3111 supports_single_entity = False 

3112 

3113 __slots__ = ( 

3114 "expr", 

3115 "mapper", 

3116 "column", 

3117 "_label_name", 

3118 "entity_zero_or_selectable", 

3119 "entity_zero", 

3120 "_extra_entities", 

3121 ) 

3122 

3123 def __init__( 

3124 self, 

3125 compile_state, 

3126 column, 

3127 entities_collection, 

3128 parententity, 

3129 raw_column_index, 

3130 is_current_entities, 

3131 parent_bundle=None, 

3132 ): 

3133 annotations = column._annotations 

3134 

3135 _entity = parententity 

3136 

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

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

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

3140 # within internal loaders. 

3141 

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

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

3144 if orm_key: 

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

3146 self.translate_raw_column = False 

3147 else: 

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

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

3150 # include this column position from the invoked statement 

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

3152 # it can be targeted by identity after caching 

3153 self.expr = column 

3154 self.translate_raw_column = raw_column_index is not None 

3155 

3156 self.raw_column_index = raw_column_index 

3157 

3158 if is_current_entities: 

3159 if parent_bundle: 

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

3161 else: 

3162 self._label_name = compile_state._label_convention( 

3163 column, col_name=orm_key 

3164 ) 

3165 else: 

3166 self._label_name = None 

3167 

3168 _entity._post_inspect 

3169 self.entity_zero = self.entity_zero_or_selectable = ezero = _entity 

3170 self.mapper = mapper = _entity.mapper 

3171 

3172 if parent_bundle: 

3173 parent_bundle._entities.append(self) 

3174 else: 

3175 entities_collection.append(self) 

3176 

3177 compile_state._has_orm_entities = True 

3178 

3179 self.column = column 

3180 

3181 self._fetch_column = self._row_processor = None 

3182 

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

3184 

3185 if mapper._should_select_with_poly_adapter: 

3186 compile_state._create_with_polymorphic_adapter( 

3187 ezero, ezero.selectable 

3188 ) 

3189 

3190 def corresponds_to(self, entity): 

3191 if _is_aliased_class(entity): 

3192 # TODO: polymorphic subclasses ? 

3193 return entity is self.entity_zero 

3194 else: 

3195 return not _is_aliased_class( 

3196 self.entity_zero 

3197 ) and entity.common_parent(self.entity_zero) 

3198 

3199 def setup_dml_returning_compile_state( 

3200 self, 

3201 compile_state: ORMCompileState, 

3202 adapter: DMLReturningColFilter, 

3203 ) -> None: 

3204 self._fetch_column = self.column 

3205 column = adapter(self.column, False) 

3206 if column is not None: 

3207 compile_state.dedupe_columns.add(column) 

3208 compile_state.primary_columns.append(column) 

3209 

3210 def setup_compile_state(self, compile_state): 

3211 current_adapter = compile_state._get_current_adapter() 

3212 if current_adapter: 

3213 column = current_adapter(self.column, False) 

3214 if column is None: 

3215 assert compile_state.is_dml_returning 

3216 self._fetch_column = self.column 

3217 return 

3218 else: 

3219 column = self.column 

3220 

3221 ezero = self.entity_zero 

3222 

3223 single_table_crit = self.mapper._single_table_criterion 

3224 if ( 

3225 single_table_crit is not None 

3226 or ("additional_entity_criteria", self.mapper) 

3227 in compile_state.global_attributes 

3228 ): 

3229 compile_state.extra_criteria_entities[ezero] = ( 

3230 ezero, 

3231 ezero._adapter if ezero.is_aliased_class else None, 

3232 ) 

3233 

3234 if column._annotations and not column._expression_label: 

3235 # annotated columns perform more slowly in compiler and 

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

3237 column = column._deannotate() 

3238 

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

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

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

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

3243 # a scalar subquery. 

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

3245 ezero.selectable._from_objects 

3246 ): 

3247 compile_state._fallback_from_clauses.append(ezero.selectable) 

3248 

3249 compile_state.dedupe_columns.add(column) 

3250 compile_state.primary_columns.append(column) 

3251 self._fetch_column = column 

3252 

3253 

3254class _IdentityTokenEntity(_ORMColumnEntity): 

3255 translate_raw_column = False 

3256 

3257 def setup_compile_state(self, compile_state): 

3258 pass 

3259 

3260 def row_processor(self, context, result): 

3261 def getter(row): 

3262 return context.load_options._identity_token 

3263 

3264 return getter, self._label_name, self._extra_entities