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

1209 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +0000

1# orm/context.py 

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

7import itertools 

8 

9from . import attributes 

10from . import interfaces 

11from . import loading 

12from .base import _is_aliased_class 

13from .interfaces import ORMColumnsClauseRole 

14from .path_registry import PathRegistry 

15from .util import _entity_corresponds_to 

16from .util import _ORMJoin 

17from .util import aliased 

18from .util import Bundle 

19from .util import ORMAdapter 

20from .. import exc as sa_exc 

21from .. import future 

22from .. import inspect 

23from .. import sql 

24from .. import util 

25from ..sql import ClauseElement 

26from ..sql import coercions 

27from ..sql import expression 

28from ..sql import roles 

29from ..sql import util as sql_util 

30from ..sql import visitors 

31from ..sql.base import _entity_namespace_key 

32from ..sql.base import _select_iterables 

33from ..sql.base import CacheableOptions 

34from ..sql.base import CompileState 

35from ..sql.base import Options 

36from ..sql.selectable import LABEL_STYLE_DISAMBIGUATE_ONLY 

37from ..sql.selectable import LABEL_STYLE_NONE 

38from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL 

39from ..sql.selectable import SelectState 

40from ..sql.visitors import ExtendedInternalTraversal 

41from ..sql.visitors import InternalTraversal 

42 

43_path_registry = PathRegistry.root 

44 

45_EMPTY_DICT = util.immutabledict() 

46 

47 

48LABEL_STYLE_LEGACY_ORM = util.symbol("LABEL_STYLE_LEGACY_ORM") 

49 

50 

51class QueryContext(object): 

52 __slots__ = ( 

53 "compile_state", 

54 "query", 

55 "params", 

56 "load_options", 

57 "bind_arguments", 

58 "execution_options", 

59 "session", 

60 "autoflush", 

61 "populate_existing", 

62 "invoke_all_eagers", 

63 "version_check", 

64 "refresh_state", 

65 "create_eager_joins", 

66 "propagated_loader_options", 

67 "attributes", 

68 "runid", 

69 "partials", 

70 "post_load_paths", 

71 "identity_token", 

72 "yield_per", 

73 "loaders_require_buffering", 

74 "loaders_require_uniquing", 

75 ) 

76 

77 class default_load_options(Options): 

78 _only_return_tuples = False 

79 _populate_existing = False 

80 _version_check = False 

81 _invoke_all_eagers = True 

82 _autoflush = True 

83 _refresh_identity_token = None 

84 _yield_per = None 

85 _refresh_state = None 

86 _lazy_loaded_from = None 

87 _legacy_uniquing = False 

88 

89 def __init__( 

90 self, 

91 compile_state, 

92 statement, 

93 params, 

94 session, 

95 load_options, 

96 execution_options=None, 

97 bind_arguments=None, 

98 ): 

99 self.load_options = load_options 

100 self.execution_options = execution_options or _EMPTY_DICT 

101 self.bind_arguments = bind_arguments or _EMPTY_DICT 

102 self.compile_state = compile_state 

103 self.query = statement 

104 self.session = session 

105 self.loaders_require_buffering = False 

106 self.loaders_require_uniquing = False 

107 self.params = params 

108 

109 cached_options = compile_state.select_statement._with_options 

110 uncached_options = statement._with_options 

111 

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

113 # propagated loader options will be present on loaded InstanceState 

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

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

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

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

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

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

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

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

122 # AliasedClass 

123 self.propagated_loader_options = tuple( 

124 opt._adapt_cached_option_to_uncached_option(self, uncached_opt) 

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

126 if opt.propagate_to_loaders 

127 ) 

128 

129 self.attributes = dict(compile_state.attributes) 

130 

131 self.autoflush = load_options._autoflush 

132 self.populate_existing = load_options._populate_existing 

133 self.invoke_all_eagers = load_options._invoke_all_eagers 

134 self.version_check = load_options._version_check 

135 self.refresh_state = load_options._refresh_state 

136 self.yield_per = load_options._yield_per 

137 self.identity_token = load_options._refresh_identity_token 

138 

139 if self.yield_per and compile_state._no_yield_pers: 

140 raise sa_exc.InvalidRequestError( 

141 "The yield_per Query option is currently not " 

142 "compatible with %s eager loading. Please " 

143 "specify lazyload('*') or query.enable_eagerloads(False) in " 

144 "order to " 

145 "proceed with query.yield_per()." 

146 % ", ".join(compile_state._no_yield_pers) 

147 ) 

148 

149 

150_orm_load_exec_options = util.immutabledict( 

151 {"_result_disable_adapt_to_context": True, "future_result": True} 

152) 

153 

154 

155class ORMCompileState(CompileState): 

156 # note this is a dictionary, but the 

157 # default_compile_options._with_polymorphic_adapt_map is a tuple 

158 _with_polymorphic_adapt_map = _EMPTY_DICT 

159 

160 class default_compile_options(CacheableOptions): 

161 _cache_key_traversal = [ 

162 ("_use_legacy_query_style", InternalTraversal.dp_boolean), 

163 ("_for_statement", InternalTraversal.dp_boolean), 

164 ("_bake_ok", InternalTraversal.dp_boolean), 

165 ( 

166 "_with_polymorphic_adapt_map", 

167 ExtendedInternalTraversal.dp_has_cache_key_tuples, 

168 ), 

169 ("_current_path", InternalTraversal.dp_has_cache_key), 

170 ("_enable_single_crit", InternalTraversal.dp_boolean), 

171 ("_enable_eagerloads", InternalTraversal.dp_boolean), 

172 ("_orm_only_from_obj_alias", InternalTraversal.dp_boolean), 

173 ("_only_load_props", InternalTraversal.dp_plain_obj), 

174 ("_set_base_alias", InternalTraversal.dp_boolean), 

175 ("_for_refresh_state", InternalTraversal.dp_boolean), 

176 ("_render_for_subquery", InternalTraversal.dp_boolean), 

177 ("_is_star", InternalTraversal.dp_boolean), 

178 ] 

179 

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

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

182 # now this basically indicates we should use tablename_columnname 

183 # style labels. Generally indicates the statement originated 

184 # from a Query object. 

185 _use_legacy_query_style = False 

186 

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

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

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

190 _for_statement = False 

191 

192 _bake_ok = True 

193 _with_polymorphic_adapt_map = () 

194 _current_path = _path_registry 

195 _enable_single_crit = True 

196 _enable_eagerloads = True 

197 _orm_only_from_obj_alias = True 

198 _only_load_props = None 

199 _set_base_alias = False 

200 _for_refresh_state = False 

201 _render_for_subquery = False 

202 _is_star = False 

203 

204 current_path = _path_registry 

205 

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

207 raise NotImplementedError() 

208 

209 def _append_dedupe_col_collection(self, obj, col_collection): 

210 dedupe = self.dedupe_columns 

211 if obj not in dedupe: 

212 dedupe.add(obj) 

213 col_collection.append(obj) 

214 

215 @classmethod 

216 def _column_naming_convention(cls, label_style, legacy): 

217 

218 if legacy: 

219 

220 def name(col, col_name=None): 

221 if col_name: 

222 return col_name 

223 else: 

224 return getattr(col, "key") 

225 

226 return name 

227 else: 

228 return SelectState._column_naming_convention(label_style) 

229 

230 @classmethod 

231 def create_for_statement(cls, statement_container, compiler, **kw): 

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

233 

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

235 

236 For a Select object, this would be invoked from 

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

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

239 FromStatement._compiler_dispatch() that would be called by 

240 SQLCompiler.process(). 

241 

242 """ 

243 raise NotImplementedError() 

244 

245 @classmethod 

246 def get_column_descriptions(cls, statement): 

247 return _column_descriptions(statement) 

248 

249 @classmethod 

250 def orm_pre_session_exec( 

251 cls, 

252 session, 

253 statement, 

254 params, 

255 execution_options, 

256 bind_arguments, 

257 is_reentrant_invoke, 

258 ): 

259 if is_reentrant_invoke: 

260 return statement, execution_options 

261 

262 ( 

263 load_options, 

264 execution_options, 

265 ) = QueryContext.default_load_options.from_execution_options( 

266 "_sa_orm_load_options", 

267 {"populate_existing", "autoflush", "yield_per"}, 

268 execution_options, 

269 statement._execution_options, 

270 ) 

271 

272 # default execution options for ORM results: 

273 # 1. _result_disable_adapt_to_context=True 

274 # this will disable the ResultSetMetadata._adapt_to_context() 

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

276 # against the original SELECT statement before caching. 

277 # 2. future_result=True. The ORM should **never** resolve columns 

278 # in a result set based on names, only on Column objects that 

279 # are correctly adapted to the context. W the legacy result 

280 # it will still attempt name-based resolution and also emit a 

281 # warning. 

282 if not execution_options: 

283 execution_options = _orm_load_exec_options 

284 else: 

285 execution_options = execution_options.union(_orm_load_exec_options) 

286 

287 if load_options._yield_per: 

288 execution_options = execution_options.union( 

289 {"yield_per": load_options._yield_per} 

290 ) 

291 

292 bind_arguments["clause"] = statement 

293 

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

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

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

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

298 

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

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

301 # needs to be present as well. 

302 

303 try: 

304 plugin_subject = statement._propagate_attrs["plugin_subject"] 

305 except KeyError: 

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

307 else: 

308 if plugin_subject: 

309 bind_arguments["mapper"] = plugin_subject.mapper 

310 

311 if load_options._autoflush: 

312 session._autoflush() 

313 

314 return statement, execution_options 

315 

316 @classmethod 

317 def orm_setup_cursor_result( 

318 cls, 

319 session, 

320 statement, 

321 params, 

322 execution_options, 

323 bind_arguments, 

324 result, 

325 ): 

326 execution_context = result.context 

327 compile_state = execution_context.compiled.compile_state 

328 

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

330 # were passed to session.execute: 

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

332 # see test_query->test_legacy_tuple_old_select 

333 

334 load_options = execution_options.get( 

335 "_sa_orm_load_options", QueryContext.default_load_options 

336 ) 

337 if compile_state.compile_options._is_star: 

338 return result 

339 

340 querycontext = QueryContext( 

341 compile_state, 

342 statement, 

343 params, 

344 session, 

345 load_options, 

346 execution_options, 

347 bind_arguments, 

348 ) 

349 return loading.instances(result, querycontext) 

350 

351 @property 

352 def _lead_mapper_entities(self): 

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

354 

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

356 with_entities(), with_only_columns() 

357 

358 """ 

359 return [ 

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

361 ] 

362 

363 def _create_with_polymorphic_adapter(self, ext_info, selectable): 

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

365 if appropriate 

366 

367 """ 

368 if ( 

369 not ext_info.is_aliased_class 

370 and ext_info.mapper.persist_selectable 

371 not in self._polymorphic_adapters 

372 ): 

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

374 self._mapper_loads_polymorphically_with( 

375 mp, 

376 sql_util.ColumnAdapter(selectable, mp._equivalent_columns), 

377 ) 

378 

379 def _mapper_loads_polymorphically_with(self, mapper, adapter): 

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

381 self._polymorphic_adapters[m2] = adapter 

382 for m in m2.iterate_to_root(): # TODO: redundant ? 

383 self._polymorphic_adapters[m.local_table] = adapter 

384 

385 @classmethod 

386 def _create_entities_collection(cls, query, legacy): 

387 raise NotImplementedError( 

388 "this method only works for ORMSelectCompileState" 

389 ) 

390 

391 

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

393class ORMFromStatementCompileState(ORMCompileState): 

394 _aliased_generations = util.immutabledict() 

395 _from_obj_alias = None 

396 _has_mapper_entities = False 

397 

398 _has_orm_entities = False 

399 multi_row_eager_loaders = False 

400 eager_adding_joins = False 

401 compound_eager_adapter = None 

402 

403 extra_criteria_entities = _EMPTY_DICT 

404 eager_joins = _EMPTY_DICT 

405 

406 @classmethod 

407 def create_for_statement(cls, statement_container, compiler, **kw): 

408 

409 if compiler is not None: 

410 toplevel = not compiler.stack 

411 else: 

412 toplevel = True 

413 

414 self = cls.__new__(cls) 

415 self._primary_entity = None 

416 

417 self.use_legacy_query_style = ( 

418 statement_container._compile_options._use_legacy_query_style 

419 ) 

420 self.statement_container = self.select_statement = statement_container 

421 self.requested_statement = statement = statement_container.element 

422 

423 if statement.is_dml: 

424 self.dml_table = statement.table 

425 

426 self._entities = [] 

427 self._polymorphic_adapters = {} 

428 self._no_yield_pers = set() 

429 

430 self.compile_options = statement_container._compile_options 

431 

432 if ( 

433 self.use_legacy_query_style 

434 and isinstance(statement, expression.SelectBase) 

435 and not statement._is_textual 

436 and not statement.is_dml 

437 and statement._label_style is LABEL_STYLE_NONE 

438 ): 

439 self.statement = statement.set_label_style( 

440 LABEL_STYLE_TABLENAME_PLUS_COL 

441 ) 

442 else: 

443 self.statement = statement 

444 

445 self._label_convention = self._column_naming_convention( 

446 statement._label_style 

447 if not statement._is_textual and not statement.is_dml 

448 else LABEL_STYLE_NONE, 

449 self.use_legacy_query_style, 

450 ) 

451 

452 _QueryEntity.to_compile_state( 

453 self, 

454 statement_container._raw_columns, 

455 self._entities, 

456 is_current_entities=True, 

457 ) 

458 

459 self.current_path = statement_container._compile_options._current_path 

460 

461 if toplevel and statement_container._with_options: 

462 self.attributes = {"_unbound_load_dedupes": set()} 

463 self.global_attributes = compiler._global_attributes 

464 

465 for opt in statement_container._with_options: 

466 if opt._is_compile_state: 

467 opt.process_compile_state(self) 

468 

469 else: 

470 self.attributes = {} 

471 self.global_attributes = compiler._global_attributes 

472 

473 if statement_container._with_context_options: 

474 for fn, key in statement_container._with_context_options: 

475 fn(self) 

476 

477 self.primary_columns = [] 

478 self.secondary_columns = [] 

479 self.dedupe_columns = set() 

480 self.create_eager_joins = [] 

481 self._fallback_from_clauses = [] 

482 

483 self.order_by = None 

484 

485 if isinstance( 

486 self.statement, (expression.TextClause, expression.UpdateBase) 

487 ): 

488 

489 self.extra_criteria_entities = {} 

490 

491 # setup for all entities. Currently, this is not useful 

492 # for eager loaders, as the eager loaders that work are able 

493 # to do their work entirely in row_processor. 

494 for entity in self._entities: 

495 entity.setup_compile_state(self) 

496 

497 # we did the setup just to get primary columns. 

498 self.statement = _AdHocColumnsStatement( 

499 self.statement, self.primary_columns 

500 ) 

501 else: 

502 # allow TextualSelect with implicit columns as well 

503 # as select() with ad-hoc columns, see test_query::TextTest 

504 self._from_obj_alias = sql.util.ColumnAdapter( 

505 self.statement, adapt_on_names=True 

506 ) 

507 # set up for eager loaders, however if we fix subqueryload 

508 # it should not need to do this here. the model of eager loaders 

509 # that can work entirely in row_processor might be interesting 

510 # here though subqueryloader has a lot of upfront work to do 

511 # see test/orm/test_query.py -> test_related_eagerload_against_text 

512 # for where this part makes a difference. would rather have 

513 # subqueryload figure out what it needs more intelligently. 

514 # for entity in self._entities: 

515 # entity.setup_compile_state(self) 

516 

517 return self 

518 

519 def _adapt_col_list(self, cols, current_adapter): 

520 return cols 

521 

522 def _get_current_adapter(self): 

523 return None 

524 

525 

526class _AdHocColumnsStatement(ClauseElement): 

527 """internal object created to somewhat act like a SELECT when we 

528 are selecting columns from a DML RETURNING. 

529 

530 

531 """ 

532 

533 __visit_name__ = None 

534 

535 def __init__(self, text, columns): 

536 self.element = text 

537 self.column_args = [ 

538 coercions.expect(roles.ColumnsClauseRole, c) for c in columns 

539 ] 

540 

541 def _generate_cache_key(self): 

542 raise NotImplementedError() 

543 

544 def _gen_cache_key(self, anon_map, bindparams): 

545 raise NotImplementedError() 

546 

547 def _compiler_dispatch( 

548 self, compiler, compound_index=None, asfrom=False, **kw 

549 ): 

550 """provide a fixed _compiler_dispatch method.""" 

551 

552 toplevel = not compiler.stack 

553 entry = ( 

554 compiler._default_stack_entry if toplevel else compiler.stack[-1] 

555 ) 

556 

557 populate_result_map = ( 

558 toplevel 

559 # these two might not be needed 

560 or ( 

561 compound_index == 0 

562 and entry.get("need_result_map_for_compound", False) 

563 ) 

564 or entry.get("need_result_map_for_nested", False) 

565 ) 

566 

567 if populate_result_map: 

568 compiler._ordered_columns = ( 

569 compiler._textual_ordered_columns 

570 ) = False 

571 

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

573 # needed by test_query.py::TextTest 

574 compiler._loose_column_name_matching = True 

575 

576 for c in self.column_args: 

577 compiler.process( 

578 c, 

579 within_columns_clause=True, 

580 add_to_result_map=compiler._add_to_result_map, 

581 ) 

582 return compiler.process(self.element, **kw) 

583 

584 

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

586class ORMSelectCompileState(ORMCompileState, SelectState): 

587 _joinpath = _joinpoint = _EMPTY_DICT 

588 

589 _memoized_entities = _EMPTY_DICT 

590 

591 _from_obj_alias = None 

592 _has_mapper_entities = False 

593 

594 _has_orm_entities = False 

595 multi_row_eager_loaders = False 

596 eager_adding_joins = False 

597 compound_eager_adapter = None 

598 

599 correlate = None 

600 correlate_except = None 

601 _where_criteria = () 

602 _having_criteria = () 

603 

604 @classmethod 

605 def create_for_statement(cls, statement, compiler, **kw): 

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

607 

608 self = cls.__new__(cls) 

609 

610 if compiler is not None: 

611 toplevel = not compiler.stack 

612 self.global_attributes = compiler._global_attributes 

613 else: 

614 toplevel = True 

615 self.global_attributes = {} 

616 

617 select_statement = statement 

618 

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

620 # have ORM level compile options. 

621 statement._compile_options = cls.default_compile_options.safe_merge( 

622 statement._compile_options 

623 ) 

624 

625 if select_statement._execution_options: 

626 # execution options should not impact the compilation of a 

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

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

629 self.select_statement = select_statement._clone() 

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

631 else: 

632 self.select_statement = select_statement 

633 

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

635 self.for_statement = select_statement._compile_options._for_statement 

636 

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

638 self.use_legacy_query_style = ( 

639 select_statement._compile_options._use_legacy_query_style 

640 ) 

641 

642 self._entities = [] 

643 self._primary_entity = None 

644 self._aliased_generations = {} 

645 self._polymorphic_adapters = {} 

646 self._no_yield_pers = set() 

647 

648 # legacy: only for query.with_polymorphic() 

649 if select_statement._compile_options._with_polymorphic_adapt_map: 

650 self._with_polymorphic_adapt_map = dict( 

651 select_statement._compile_options._with_polymorphic_adapt_map 

652 ) 

653 self._setup_with_polymorphics() 

654 

655 self.compile_options = select_statement._compile_options 

656 

657 if not toplevel: 

658 # for subqueries, turn off eagerloads and set 

659 # "render_for_subquery". 

660 self.compile_options += { 

661 "_enable_eagerloads": False, 

662 "_render_for_subquery": True, 

663 } 

664 

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

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

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

668 # for new style ORM selects too. 

669 if ( 

670 self.use_legacy_query_style 

671 and self.select_statement._label_style is LABEL_STYLE_LEGACY_ORM 

672 ): 

673 if not self.for_statement: 

674 self.label_style = LABEL_STYLE_TABLENAME_PLUS_COL 

675 else: 

676 self.label_style = LABEL_STYLE_DISAMBIGUATE_ONLY 

677 else: 

678 self.label_style = self.select_statement._label_style 

679 

680 if select_statement._memoized_select_entities: 

681 self._memoized_entities = { 

682 memoized_entities: _QueryEntity.to_compile_state( 

683 self, 

684 memoized_entities._raw_columns, 

685 [], 

686 is_current_entities=False, 

687 ) 

688 for memoized_entities in ( 

689 select_statement._memoized_select_entities 

690 ) 

691 } 

692 

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

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

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

696 # in the columns clause 

697 self._label_convention = self._column_naming_convention( 

698 statement._label_style, self.use_legacy_query_style 

699 ) 

700 

701 _QueryEntity.to_compile_state( 

702 self, 

703 select_statement._raw_columns, 

704 self._entities, 

705 is_current_entities=True, 

706 ) 

707 

708 self.current_path = select_statement._compile_options._current_path 

709 

710 self.eager_order_by = () 

711 

712 if toplevel and ( 

713 select_statement._with_options 

714 or select_statement._memoized_select_entities 

715 ): 

716 self.attributes = {"_unbound_load_dedupes": set()} 

717 

718 for ( 

719 memoized_entities 

720 ) in select_statement._memoized_select_entities: 

721 for opt in memoized_entities._with_options: 

722 if opt._is_compile_state: 

723 opt.process_compile_state_replaced_entities( 

724 self, 

725 [ 

726 ent 

727 for ent in self._memoized_entities[ 

728 memoized_entities 

729 ] 

730 if isinstance(ent, _MapperEntity) 

731 ], 

732 ) 

733 

734 for opt in self.select_statement._with_options: 

735 if opt._is_compile_state: 

736 opt.process_compile_state(self) 

737 else: 

738 self.attributes = {} 

739 

740 if select_statement._with_context_options: 

741 for fn, key in select_statement._with_context_options: 

742 fn(self) 

743 

744 self.primary_columns = [] 

745 self.secondary_columns = [] 

746 self.dedupe_columns = set() 

747 self.eager_joins = {} 

748 self.extra_criteria_entities = {} 

749 self.create_eager_joins = [] 

750 self._fallback_from_clauses = [] 

751 

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

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

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

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

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

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

758 self.from_clauses = self._normalize_froms( 

759 info.selectable for info in select_statement._from_obj 

760 ) 

761 

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

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

764 # and _setup_for_generate into three or four logical sections 

765 self._setup_for_generate() 

766 

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

768 

769 return self 

770 

771 def _setup_for_generate(self): 

772 query = self.select_statement 

773 

774 self.statement = None 

775 self._join_entities = () 

776 

777 if self.compile_options._set_base_alias: 

778 self._set_select_from_alias() 

779 

780 for memoized_entities in query._memoized_select_entities: 

781 if memoized_entities._setup_joins: 

782 self._join( 

783 memoized_entities._setup_joins, 

784 self._memoized_entities[memoized_entities], 

785 ) 

786 if memoized_entities._legacy_setup_joins: 

787 self._legacy_join( 

788 memoized_entities._legacy_setup_joins, 

789 self._memoized_entities[memoized_entities], 

790 ) 

791 

792 if query._setup_joins: 

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

794 

795 if query._legacy_setup_joins: 

796 self._legacy_join(query._legacy_setup_joins, self._entities) 

797 

798 current_adapter = self._get_current_adapter() 

799 

800 if query._where_criteria: 

801 self._where_criteria = query._where_criteria 

802 

803 if current_adapter: 

804 self._where_criteria = tuple( 

805 current_adapter(crit, True) 

806 for crit in self._where_criteria 

807 ) 

808 

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

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

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

812 self.order_by = ( 

813 self._adapt_col_list(query._order_by_clauses, current_adapter) 

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

815 else query._order_by_clauses 

816 ) 

817 

818 if query._having_criteria: 

819 self._having_criteria = tuple( 

820 current_adapter(crit, True) if current_adapter else crit 

821 for crit in query._having_criteria 

822 ) 

823 

824 self.group_by = ( 

825 self._adapt_col_list( 

826 util.flatten_iterator(query._group_by_clauses), current_adapter 

827 ) 

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

829 else query._group_by_clauses or None 

830 ) 

831 

832 if self.eager_order_by: 

833 adapter = self.from_clauses[0]._target_adapter 

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

835 

836 if query._distinct_on: 

837 self.distinct_on = self._adapt_col_list( 

838 query._distinct_on, current_adapter 

839 ) 

840 else: 

841 self.distinct_on = () 

842 

843 self.distinct = query._distinct 

844 

845 if query._correlate: 

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

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

848 # tables. 

849 self.correlate = tuple( 

850 util.flatten_iterator( 

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

852 for s in query._correlate 

853 ) 

854 ) 

855 elif query._correlate_except is not None: 

856 self.correlate_except = tuple( 

857 util.flatten_iterator( 

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

859 for s in query._correlate_except 

860 ) 

861 ) 

862 elif not query._auto_correlate: 

863 self.correlate = (None,) 

864 

865 # PART II 

866 

867 self._for_update_arg = query._for_update_arg 

868 

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

870 raise sa_exc.CompileError( 

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

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

873 ) 

874 for entity in self._entities: 

875 entity.setup_compile_state(self) 

876 

877 for rec in self.create_eager_joins: 

878 strategy = rec[0] 

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

880 

881 # else "load from discrete FROMs" mode, 

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

883 

884 if self.compile_options._enable_single_crit: 

885 self._adjust_for_extra_criteria() 

886 

887 if not self.primary_columns: 

888 if self.compile_options._only_load_props: 

889 raise sa_exc.InvalidRequestError( 

890 "No column-based properties specified for " 

891 "refresh operation. Use session.expire() " 

892 "to reload collections and related items." 

893 ) 

894 else: 

895 raise sa_exc.InvalidRequestError( 

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

897 ) 

898 

899 if not self.from_clauses: 

900 self.from_clauses = list(self._fallback_from_clauses) 

901 

902 if self.order_by is False: 

903 self.order_by = None 

904 

905 if ( 

906 self.multi_row_eager_loaders 

907 and self.eager_adding_joins 

908 and self._should_nest_selectable 

909 ): 

910 self.statement = self._compound_eager_statement() 

911 else: 

912 self.statement = self._simple_statement() 

913 

914 if self.for_statement: 

915 ezero = self._mapper_zero() 

916 if ezero is not None: 

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

918 # thing 

919 self.statement = self.statement._annotate( 

920 {"deepentity": ezero} 

921 ) 

922 

923 @classmethod 

924 def _create_entities_collection(cls, query, legacy): 

925 """Creates a partial ORMSelectCompileState that includes 

926 the full collection of _MapperEntity and other _QueryEntity objects. 

927 

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

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

930 

931 """ 

932 self = cls.__new__(cls) 

933 

934 self._entities = [] 

935 self._primary_entity = None 

936 self._aliased_generations = {} 

937 self._polymorphic_adapters = {} 

938 

939 compile_options = cls.default_compile_options.safe_merge( 

940 query._compile_options 

941 ) 

942 # legacy: only for query.with_polymorphic() 

943 if compile_options._with_polymorphic_adapt_map: 

944 self._with_polymorphic_adapt_map = dict( 

945 compile_options._with_polymorphic_adapt_map 

946 ) 

947 self._setup_with_polymorphics() 

948 

949 self._label_convention = self._column_naming_convention( 

950 query._label_style, legacy 

951 ) 

952 

953 # entities will also set up polymorphic adapters for mappers 

954 # that have with_polymorphic configured 

955 _QueryEntity.to_compile_state( 

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

957 ) 

958 return self 

959 

960 @classmethod 

961 def determine_last_joined_entity(cls, statement): 

962 setup_joins = statement._setup_joins 

963 

964 if not setup_joins: 

965 return None 

966 

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

968 

969 if isinstance(target, interfaces.PropComparator): 

970 return target.entity 

971 else: 

972 return target 

973 

974 @classmethod 

975 def all_selected_columns(cls, statement): 

976 for element in statement._raw_columns: 

977 if ( 

978 element.is_selectable 

979 and "entity_namespace" in element._annotations 

980 ): 

981 ens = element._annotations["entity_namespace"] 

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

983 for elem in _select_iterables([element]): 

984 yield elem 

985 else: 

986 for elem in _select_iterables(ens._all_column_expressions): 

987 yield elem 

988 else: 

989 for elem in _select_iterables([element]): 

990 yield elem 

991 

992 @classmethod 

993 def get_columns_clause_froms(cls, statement): 

994 return cls._normalize_froms( 

995 itertools.chain.from_iterable( 

996 element._from_objects 

997 if "parententity" not in element._annotations 

998 else [ 

999 element._annotations["parententity"].__clause_element__() 

1000 ] 

1001 for element in statement._raw_columns 

1002 ) 

1003 ) 

1004 

1005 @classmethod 

1006 @util.preload_module("sqlalchemy.orm.query") 

1007 def from_statement(cls, statement, from_statement): 

1008 query = util.preloaded.orm_query 

1009 

1010 from_statement = coercions.expect( 

1011 roles.ReturnsRowsRole, 

1012 from_statement, 

1013 apply_propagate_attrs=statement, 

1014 ) 

1015 

1016 stmt = query.FromStatement(statement._raw_columns, from_statement) 

1017 

1018 stmt.__dict__.update( 

1019 _with_options=statement._with_options, 

1020 _with_context_options=statement._with_context_options, 

1021 _execution_options=statement._execution_options, 

1022 _propagate_attrs=statement._propagate_attrs, 

1023 ) 

1024 return stmt 

1025 

1026 def _setup_with_polymorphics(self): 

1027 # legacy: only for query.with_polymorphic() 

1028 for ext_info, wp in self._with_polymorphic_adapt_map.items(): 

1029 self._mapper_loads_polymorphically_with(ext_info, wp._adapter) 

1030 

1031 def _set_select_from_alias(self): 

1032 

1033 query = self.select_statement # query 

1034 

1035 assert self.compile_options._set_base_alias 

1036 assert len(query._from_obj) == 1 

1037 

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

1039 if adapter: 

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

1041 self._from_obj_alias = adapter 

1042 

1043 def _get_select_from_alias_from_obj(self, from_obj): 

1044 info = from_obj 

1045 

1046 if "parententity" in info._annotations: 

1047 info = info._annotations["parententity"] 

1048 

1049 if hasattr(info, "mapper"): 

1050 if not info.is_aliased_class: 

1051 raise sa_exc.ArgumentError( 

1052 "A selectable (FromClause) instance is " 

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

1054 ) 

1055 else: 

1056 return info._adapter 

1057 

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

1059 equivs = self._all_equivs() 

1060 return sql_util.ColumnAdapter(info, equivs) 

1061 else: 

1062 return None 

1063 

1064 def _mapper_zero(self): 

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

1066 return self._entities[0].mapper 

1067 

1068 def _entity_zero(self): 

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

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

1071 entity if specified.""" 

1072 

1073 for ent in self.from_clauses: 

1074 if "parententity" in ent._annotations: 

1075 return ent._annotations["parententity"] 

1076 for qent in self._entities: 

1077 if qent.entity_zero: 

1078 return qent.entity_zero 

1079 

1080 return None 

1081 

1082 def _only_full_mapper_zero(self, methname): 

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

1084 raise sa_exc.InvalidRequestError( 

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

1086 "a single mapped class." % methname 

1087 ) 

1088 return self._primary_entity.entity_zero 

1089 

1090 def _only_entity_zero(self, rationale=None): 

1091 if len(self._entities) > 1: 

1092 raise sa_exc.InvalidRequestError( 

1093 rationale 

1094 or "This operation requires a Query " 

1095 "against a single mapper." 

1096 ) 

1097 return self._entity_zero() 

1098 

1099 def _all_equivs(self): 

1100 equivs = {} 

1101 

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

1103 for ent in [ 

1104 ent 

1105 for ent in memoized_entities 

1106 if isinstance(ent, _MapperEntity) 

1107 ]: 

1108 equivs.update(ent.mapper._equivalent_columns) 

1109 

1110 for ent in [ 

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

1112 ]: 

1113 equivs.update(ent.mapper._equivalent_columns) 

1114 return equivs 

1115 

1116 def _compound_eager_statement(self): 

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

1118 # wrap the query inside a select, 

1119 # then append eager joins onto that 

1120 

1121 if self.order_by: 

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

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

1124 # elements are converted into label references. For the 

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

1126 # the original expressions outside of the label references 

1127 # in order to have them render. 

1128 unwrapped_order_by = [ 

1129 elem.element 

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

1131 else elem 

1132 for elem in self.order_by 

1133 ] 

1134 

1135 order_by_col_expr = sql_util.expand_column_list_from_order_by( 

1136 self.primary_columns, unwrapped_order_by 

1137 ) 

1138 else: 

1139 order_by_col_expr = [] 

1140 unwrapped_order_by = None 

1141 

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

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

1144 inner = self._select_statement( 

1145 self.primary_columns 

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

1147 self.from_clauses, 

1148 self._where_criteria, 

1149 self._having_criteria, 

1150 self.label_style, 

1151 self.order_by, 

1152 for_update=self._for_update_arg, 

1153 hints=self.select_statement._hints, 

1154 statement_hints=self.select_statement._statement_hints, 

1155 correlate=self.correlate, 

1156 correlate_except=self.correlate_except, 

1157 **self._select_args 

1158 ) 

1159 

1160 inner = inner.alias() 

1161 

1162 equivs = self._all_equivs() 

1163 

1164 self.compound_eager_adapter = sql_util.ColumnAdapter(inner, equivs) 

1165 

1166 statement = future.select( 

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

1168 ) 

1169 statement._label_style = self.label_style 

1170 

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

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

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

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

1175 if ( 

1176 self._for_update_arg is not None 

1177 and self._for_update_arg.of is None 

1178 ): 

1179 statement._for_update_arg = self._for_update_arg 

1180 

1181 from_clause = inner 

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

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

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

1185 # the join should be 

1186 from_clause = sql_util.splice_joins( 

1187 from_clause, eager_join, eager_join.stop_on 

1188 ) 

1189 

1190 statement.select_from.non_generative(statement, from_clause) 

1191 

1192 if unwrapped_order_by: 

1193 statement.order_by.non_generative( 

1194 statement, 

1195 *self.compound_eager_adapter.copy_and_process( 

1196 unwrapped_order_by 

1197 ) 

1198 ) 

1199 

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

1201 return statement 

1202 

1203 def _simple_statement(self): 

1204 

1205 if ( 

1206 self.compile_options._use_legacy_query_style 

1207 and (self.distinct and not self.distinct_on) 

1208 and self.order_by 

1209 ): 

1210 to_add = sql_util.expand_column_list_from_order_by( 

1211 self.primary_columns, self.order_by 

1212 ) 

1213 if to_add: 

1214 util.warn_deprecated_20( 

1215 "ORDER BY columns added implicitly due to " 

1216 "DISTINCT is deprecated and will be removed in " 

1217 "SQLAlchemy 2.0. SELECT statements with DISTINCT " 

1218 "should be written to explicitly include the appropriate " 

1219 "columns in the columns clause" 

1220 ) 

1221 self.primary_columns += to_add 

1222 

1223 statement = self._select_statement( 

1224 self.primary_columns + self.secondary_columns, 

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

1226 self._where_criteria, 

1227 self._having_criteria, 

1228 self.label_style, 

1229 self.order_by, 

1230 for_update=self._for_update_arg, 

1231 hints=self.select_statement._hints, 

1232 statement_hints=self.select_statement._statement_hints, 

1233 correlate=self.correlate, 

1234 correlate_except=self.correlate_except, 

1235 **self._select_args 

1236 ) 

1237 

1238 if self.eager_order_by: 

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

1240 return statement 

1241 

1242 def _select_statement( 

1243 self, 

1244 raw_columns, 

1245 from_obj, 

1246 where_criteria, 

1247 having_criteria, 

1248 label_style, 

1249 order_by, 

1250 for_update, 

1251 hints, 

1252 statement_hints, 

1253 correlate, 

1254 correlate_except, 

1255 limit_clause, 

1256 offset_clause, 

1257 fetch_clause, 

1258 fetch_clause_options, 

1259 distinct, 

1260 distinct_on, 

1261 prefixes, 

1262 suffixes, 

1263 group_by, 

1264 ): 

1265 

1266 Select = future.Select 

1267 statement = Select._create_raw_select( 

1268 _raw_columns=raw_columns, 

1269 _from_obj=from_obj, 

1270 _label_style=label_style, 

1271 ) 

1272 

1273 if where_criteria: 

1274 statement._where_criteria = where_criteria 

1275 if having_criteria: 

1276 statement._having_criteria = having_criteria 

1277 

1278 if order_by: 

1279 statement._order_by_clauses += tuple(order_by) 

1280 

1281 if distinct_on: 

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

1283 elif distinct: 

1284 statement.distinct.non_generative(statement) 

1285 

1286 if group_by: 

1287 statement._group_by_clauses += tuple(group_by) 

1288 

1289 statement._limit_clause = limit_clause 

1290 statement._offset_clause = offset_clause 

1291 statement._fetch_clause = fetch_clause 

1292 statement._fetch_clause_options = fetch_clause_options 

1293 

1294 if prefixes: 

1295 statement._prefixes = prefixes 

1296 

1297 if suffixes: 

1298 statement._suffixes = suffixes 

1299 

1300 statement._for_update_arg = for_update 

1301 

1302 if hints: 

1303 statement._hints = hints 

1304 if statement_hints: 

1305 statement._statement_hints = statement_hints 

1306 

1307 if correlate: 

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

1309 

1310 if correlate_except is not None: 

1311 statement.correlate_except.non_generative( 

1312 statement, *correlate_except 

1313 ) 

1314 

1315 return statement 

1316 

1317 def _adapt_polymorphic_element(self, element): 

1318 if "parententity" in element._annotations: 

1319 search = element._annotations["parententity"] 

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

1321 if alias: 

1322 return alias.adapt_clause(element) 

1323 

1324 if isinstance(element, expression.FromClause): 

1325 search = element 

1326 elif hasattr(element, "table"): 

1327 search = element.table 

1328 else: 

1329 return None 

1330 

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

1332 if alias: 

1333 return alias.adapt_clause(element) 

1334 

1335 def _adapt_aliased_generation(self, element): 

1336 # this is crazy logic that I look forward to blowing away 

1337 # when aliased=True is gone :) 

1338 if "aliased_generation" in element._annotations: 

1339 for adapter in self._aliased_generations.get( 

1340 element._annotations["aliased_generation"], () 

1341 ): 

1342 replaced_elem = adapter.replace(element) 

1343 if replaced_elem is not None: 

1344 return replaced_elem 

1345 

1346 return None 

1347 

1348 def _adapt_col_list(self, cols, current_adapter): 

1349 if current_adapter: 

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

1351 else: 

1352 return cols 

1353 

1354 def _get_current_adapter(self): 

1355 

1356 adapters = [] 

1357 

1358 if self._from_obj_alias: 

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

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

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

1362 # select_entity_from() 

1363 # 

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

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

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

1367 # to all SQL constructs. 

1368 adapters.append( 

1369 ( 

1370 False 

1371 if self.compile_options._orm_only_from_obj_alias 

1372 else True, 

1373 self._from_obj_alias.replace, 

1374 ) 

1375 ) 

1376 

1377 # vvvvvvvvvvvvvvv legacy vvvvvvvvvvvvvvvvvv 

1378 # this can totally go away when we remove join(..., aliased=True) 

1379 if self._aliased_generations: 

1380 adapters.append((False, self._adapt_aliased_generation)) 

1381 # ^^^^^^^^^^^^^ legacy ^^^^^^^^^^^^^^^^^^^^^ 

1382 

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

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

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

1386 if self._polymorphic_adapters: 

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

1388 

1389 if not adapters: 

1390 return None 

1391 

1392 def _adapt_clause(clause, as_filter): 

1393 # do we adapt all expression elements or only those 

1394 # tagged as 'ORM' constructs ? 

1395 

1396 def replace(elem): 

1397 is_orm_adapt = ( 

1398 "_orm_adapt" in elem._annotations 

1399 or "parententity" in elem._annotations 

1400 ) 

1401 for always_adapt, adapter in adapters: 

1402 if is_orm_adapt or always_adapt: 

1403 e = adapter(elem) 

1404 if e is not None: 

1405 return e 

1406 

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

1408 

1409 return _adapt_clause 

1410 

1411 def _join(self, args, entities_collection): 

1412 for (right, onclause, from_, flags) in args: 

1413 isouter = flags["isouter"] 

1414 full = flags["full"] 

1415 # maybe? 

1416 self._reset_joinpoint() 

1417 

1418 right = inspect(right) 

1419 if onclause is not None: 

1420 onclause = inspect(onclause) 

1421 

1422 if onclause is None and isinstance( 

1423 right, interfaces.PropComparator 

1424 ): 

1425 # determine onclause/right_entity. still need to think 

1426 # about how to best organize this since we are getting: 

1427 # 

1428 # 

1429 # q.join(Entity, Parent.property) 

1430 # q.join(Parent.property) 

1431 # q.join(Parent.property.of_type(Entity)) 

1432 # q.join(some_table) 

1433 # q.join(some_table, some_parent.c.id==some_table.c.parent_id) 

1434 # 

1435 # is this still too many choices? how do we handle this 

1436 # when sometimes "right" is implied and sometimes not? 

1437 # 

1438 onclause = right 

1439 right = None 

1440 elif "parententity" in right._annotations: 

1441 right = right._annotations["parententity"] 

1442 

1443 if onclause is None: 

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

1445 raise sa_exc.ArgumentError( 

1446 "Expected mapped entity or " 

1447 "selectable/table as join target" 

1448 ) 

1449 

1450 of_type = None 

1451 

1452 if isinstance(onclause, interfaces.PropComparator): 

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

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

1455 

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

1457 

1458 if right is None: 

1459 if of_type: 

1460 right = of_type 

1461 else: 

1462 right = onclause.property 

1463 

1464 try: 

1465 right = right.entity 

1466 except AttributeError as err: 

1467 util.raise_( 

1468 sa_exc.ArgumentError( 

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

1470 "mapped entity" % right 

1471 ), 

1472 replace_context=err, 

1473 ) 

1474 

1475 left = onclause._parententity 

1476 

1477 alias = self._polymorphic_adapters.get(left, None) 

1478 

1479 # could be None or could be ColumnAdapter also 

1480 if isinstance(alias, ORMAdapter) and alias.mapper.isa(left): 

1481 left = alias.aliased_class 

1482 onclause = getattr(left, onclause.key) 

1483 

1484 prop = onclause.property 

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

1486 onclause = prop 

1487 

1488 # TODO: this is where "check for path already present" 

1489 # would occur. see if this still applies? 

1490 

1491 if from_ is not None: 

1492 if ( 

1493 from_ is not left 

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

1495 is not left 

1496 ): 

1497 raise sa_exc.InvalidRequestError( 

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

1499 "of relationship attribute %s" 

1500 % ( 

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

1502 onclause, 

1503 ) 

1504 ) 

1505 elif from_ is not None: 

1506 prop = None 

1507 left = from_ 

1508 else: 

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

1510 # what the effective "left" side is 

1511 prop = left = None 

1512 

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

1514 # ORMJoin to add to our _from_obj tuple 

1515 self._join_left_to_right( 

1516 entities_collection, 

1517 left, 

1518 right, 

1519 onclause, 

1520 prop, 

1521 False, 

1522 False, 

1523 isouter, 

1524 full, 

1525 ) 

1526 

1527 def _legacy_join(self, args, entities_collection): 

1528 """consumes arguments from join() or outerjoin(), places them into a 

1529 consistent format with which to form the actual JOIN constructs. 

1530 

1531 """ 

1532 for (right, onclause, left, flags) in args: 

1533 

1534 outerjoin = flags["isouter"] 

1535 create_aliases = flags["aliased"] 

1536 from_joinpoint = flags["from_joinpoint"] 

1537 full = flags["full"] 

1538 aliased_generation = flags["aliased_generation"] 

1539 

1540 # do a quick inspect to accommodate for a lambda 

1541 if right is not None and not isinstance(right, util.string_types): 

1542 right = inspect(right) 

1543 if onclause is not None and not isinstance( 

1544 onclause, util.string_types 

1545 ): 

1546 onclause = inspect(onclause) 

1547 

1548 # legacy vvvvvvvvvvvvvvvvvvvvvvvvvv 

1549 if not from_joinpoint: 

1550 self._reset_joinpoint() 

1551 else: 

1552 prev_aliased_generation = self._joinpoint.get( 

1553 "aliased_generation", None 

1554 ) 

1555 if not aliased_generation: 

1556 aliased_generation = prev_aliased_generation 

1557 elif prev_aliased_generation: 

1558 self._aliased_generations[ 

1559 aliased_generation 

1560 ] = self._aliased_generations.get( 

1561 prev_aliased_generation, () 

1562 ) 

1563 # legacy ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

1564 

1565 if ( 

1566 isinstance( 

1567 right, (interfaces.PropComparator, util.string_types) 

1568 ) 

1569 and onclause is None 

1570 ): 

1571 onclause = right 

1572 right = None 

1573 elif "parententity" in right._annotations: 

1574 right = right._annotations["parententity"] 

1575 

1576 if onclause is None: 

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

1578 raise sa_exc.ArgumentError( 

1579 "Expected mapped entity or " 

1580 "selectable/table as join target" 

1581 ) 

1582 

1583 if isinstance(onclause, interfaces.PropComparator): 

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

1585 else: 

1586 of_type = None 

1587 

1588 if isinstance(onclause, util.string_types): 

1589 # string given, e.g. query(Foo).join("bar"). 

1590 # we look to the left entity or what we last joined 

1591 # towards 

1592 onclause = _entity_namespace_key( 

1593 inspect(self._joinpoint_zero()), onclause 

1594 ) 

1595 

1596 # legacy vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv 

1597 # check for q.join(Class.propname, from_joinpoint=True) 

1598 # and Class corresponds at the mapper level to the current 

1599 # joinpoint. this match intentionally looks for a non-aliased 

1600 # class-bound descriptor as the onclause and if it matches the 

1601 # current joinpoint at the mapper level, it's used. This 

1602 # is a very old use case that is intended to make it easier 

1603 # to work with the aliased=True flag, which is also something 

1604 # that probably shouldn't exist on join() due to its high 

1605 # complexity/usefulness ratio 

1606 elif from_joinpoint and isinstance( 

1607 onclause, interfaces.PropComparator 

1608 ): 

1609 jp0 = self._joinpoint_zero() 

1610 info = inspect(jp0) 

1611 

1612 if getattr(info, "mapper", None) is onclause._parententity: 

1613 onclause = _entity_namespace_key(info, onclause.key) 

1614 # legacy ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

1615 

1616 if isinstance(onclause, interfaces.PropComparator): 

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

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

1619 if right is None: 

1620 if of_type: 

1621 right = of_type 

1622 else: 

1623 right = onclause.property 

1624 

1625 try: 

1626 right = right.entity 

1627 except AttributeError as err: 

1628 util.raise_( 

1629 sa_exc.ArgumentError( 

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

1631 "mapped entity" % right 

1632 ), 

1633 replace_context=err, 

1634 ) 

1635 

1636 left = onclause._parententity 

1637 

1638 alias = self._polymorphic_adapters.get(left, None) 

1639 

1640 # could be None or could be ColumnAdapter also 

1641 if isinstance(alias, ORMAdapter) and alias.mapper.isa(left): 

1642 left = alias.aliased_class 

1643 onclause = getattr(left, onclause.key) 

1644 

1645 prop = onclause.property 

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

1647 onclause = prop 

1648 

1649 if not create_aliases: 

1650 # check for this path already present. 

1651 # don't render in that case. 

1652 edge = (left, right, prop.key) 

1653 if edge in self._joinpoint: 

1654 # The child's prev reference might be stale -- 

1655 # it could point to a parent older than the 

1656 # current joinpoint. If this is the case, 

1657 # then we need to update it and then fix the 

1658 # tree's spine with _update_joinpoint. Copy 

1659 # and then mutate the child, which might be 

1660 # shared by a different query object. 

1661 jp = self._joinpoint[edge].copy() 

1662 jp["prev"] = (edge, self._joinpoint) 

1663 self._update_joinpoint(jp) 

1664 

1665 continue 

1666 

1667 else: 

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

1669 # what the effective "left" side is 

1670 prop = left = None 

1671 

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

1673 # ORMJoin to add to our _from_obj tuple 

1674 self._join_left_to_right( 

1675 entities_collection, 

1676 left, 

1677 right, 

1678 onclause, 

1679 prop, 

1680 create_aliases, 

1681 aliased_generation, 

1682 outerjoin, 

1683 full, 

1684 ) 

1685 

1686 def _joinpoint_zero(self): 

1687 return self._joinpoint.get("_joinpoint_entity", self._entity_zero()) 

1688 

1689 def _join_left_to_right( 

1690 self, 

1691 entities_collection, 

1692 left, 

1693 right, 

1694 onclause, 

1695 prop, 

1696 create_aliases, 

1697 aliased_generation, 

1698 outerjoin, 

1699 full, 

1700 ): 

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

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

1703 our _from_obj list (or augment an existing one) 

1704 

1705 """ 

1706 

1707 if left is None: 

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

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

1710 # entities 

1711 assert prop is None 

1712 ( 

1713 left, 

1714 replace_from_obj_index, 

1715 use_entity_index, 

1716 ) = self._join_determine_implicit_left_side( 

1717 entities_collection, left, right, onclause 

1718 ) 

1719 else: 

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

1721 # Determine where in our 

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

1723 # existing entity it corresponds to. 

1724 ( 

1725 replace_from_obj_index, 

1726 use_entity_index, 

1727 ) = self._join_place_explicit_left_side(entities_collection, left) 

1728 

1729 if left is right and not create_aliases: 

1730 raise sa_exc.InvalidRequestError( 

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

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

1733 ) 

1734 

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

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

1737 # get back the new effective "right" side 

1738 r_info, right, onclause = self._join_check_and_adapt_right_side( 

1739 left, right, onclause, prop, create_aliases, aliased_generation 

1740 ) 

1741 

1742 if not r_info.is_selectable: 

1743 extra_criteria = self._get_extra_criteria(r_info) 

1744 else: 

1745 extra_criteria = () 

1746 

1747 if replace_from_obj_index is not None: 

1748 # splice into an existing element in the 

1749 # self._from_obj list 

1750 left_clause = self.from_clauses[replace_from_obj_index] 

1751 

1752 self.from_clauses = ( 

1753 self.from_clauses[:replace_from_obj_index] 

1754 + [ 

1755 _ORMJoin( 

1756 left_clause, 

1757 right, 

1758 onclause, 

1759 isouter=outerjoin, 

1760 full=full, 

1761 _extra_criteria=extra_criteria, 

1762 ) 

1763 ] 

1764 + self.from_clauses[replace_from_obj_index + 1 :] 

1765 ) 

1766 else: 

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

1768 if use_entity_index is not None: 

1769 # make use of _MapperEntity selectable, which is usually 

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

1771 # might be distinct 

1772 assert isinstance( 

1773 entities_collection[use_entity_index], _MapperEntity 

1774 ) 

1775 left_clause = entities_collection[use_entity_index].selectable 

1776 else: 

1777 left_clause = left 

1778 

1779 self.from_clauses = self.from_clauses + [ 

1780 _ORMJoin( 

1781 left_clause, 

1782 r_info, 

1783 onclause, 

1784 isouter=outerjoin, 

1785 full=full, 

1786 _extra_criteria=extra_criteria, 

1787 ) 

1788 ] 

1789 

1790 def _join_determine_implicit_left_side( 

1791 self, entities_collection, left, right, onclause 

1792 ): 

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

1794 determine if an existing FROM or entity in this query 

1795 can serve as the left hand side. 

1796 

1797 """ 

1798 

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

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

1801 # 

1802 # join(RightEntity) 

1803 # 

1804 # or 

1805 # 

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

1807 # 

1808 

1809 r_info = inspect(right) 

1810 

1811 replace_from_obj_index = use_entity_index = None 

1812 

1813 if self.from_clauses: 

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

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

1816 

1817 indexes = sql_util.find_left_clause_to_join_from( 

1818 self.from_clauses, r_info.selectable, onclause 

1819 ) 

1820 

1821 if len(indexes) == 1: 

1822 replace_from_obj_index = indexes[0] 

1823 left = self.from_clauses[replace_from_obj_index] 

1824 elif len(indexes) > 1: 

1825 raise sa_exc.InvalidRequestError( 

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

1827 "from, there are multiple FROMS which can " 

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

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

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

1831 "to help resolve the ambiguity." 

1832 ) 

1833 else: 

1834 raise sa_exc.InvalidRequestError( 

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

1836 "Please use the .select_from() " 

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

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

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

1840 ) 

1841 

1842 elif entities_collection: 

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

1844 # come from our list of entities. 

1845 

1846 potential = {} 

1847 for entity_index, ent in enumerate(entities_collection): 

1848 entity = ent.entity_zero_or_selectable 

1849 if entity is None: 

1850 continue 

1851 ent_info = inspect(entity) 

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

1853 continue 

1854 

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

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

1857 # against a series of columns from the same selectable 

1858 if isinstance(ent, _MapperEntity): 

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

1860 else: 

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

1862 

1863 all_clauses = list(potential.keys()) 

1864 indexes = sql_util.find_left_clause_to_join_from( 

1865 all_clauses, r_info.selectable, onclause 

1866 ) 

1867 

1868 if len(indexes) == 1: 

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

1870 elif len(indexes) > 1: 

1871 raise sa_exc.InvalidRequestError( 

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

1873 "from, there are multiple FROMS which can " 

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

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

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

1877 "to help resolve the ambiguity." 

1878 ) 

1879 else: 

1880 raise sa_exc.InvalidRequestError( 

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

1882 "Please use the .select_from() " 

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

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

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

1886 ) 

1887 else: 

1888 raise sa_exc.InvalidRequestError( 

1889 "No entities to join from; please use " 

1890 "select_from() to establish the left " 

1891 "entity/selectable of this join" 

1892 ) 

1893 

1894 return left, replace_from_obj_index, use_entity_index 

1895 

1896 def _join_place_explicit_left_side(self, entities_collection, left): 

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

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

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

1900 existing entities. 

1901 

1902 """ 

1903 

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

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

1906 # RelationshipProperty was given, e.g.: 

1907 # 

1908 # join(RightEntity, LeftEntity.right) 

1909 # 

1910 # or 

1911 # 

1912 # join(LeftEntity.right) 

1913 # 

1914 # as well as string forms: 

1915 # 

1916 # join(RightEntity, "right") 

1917 # 

1918 # etc. 

1919 # 

1920 

1921 replace_from_obj_index = use_entity_index = None 

1922 

1923 l_info = inspect(left) 

1924 if self.from_clauses: 

1925 indexes = sql_util.find_left_clause_that_matches_given( 

1926 self.from_clauses, l_info.selectable 

1927 ) 

1928 

1929 if len(indexes) > 1: 

1930 raise sa_exc.InvalidRequestError( 

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

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

1933 "ON clause." 

1934 ) 

1935 

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

1937 # an existing FROM in the self._from_obj tuple 

1938 if indexes: 

1939 replace_from_obj_index = indexes[0] 

1940 

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

1942 # self._from_obj tuple 

1943 

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

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

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

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

1948 if ( 

1949 replace_from_obj_index is None 

1950 and entities_collection 

1951 and hasattr(l_info, "mapper") 

1952 ): 

1953 for idx, ent in enumerate(entities_collection): 

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

1955 # matching? 

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

1957 use_entity_index = idx 

1958 break 

1959 

1960 return replace_from_obj_index, use_entity_index 

1961 

1962 def _join_check_and_adapt_right_side( 

1963 self, left, right, onclause, prop, create_aliases, aliased_generation 

1964 ): 

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

1966 according to polymorphic mapping translations, aliasing on the query 

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

1968 overlapping tables. 

1969 

1970 """ 

1971 

1972 l_info = inspect(left) 

1973 r_info = inspect(right) 

1974 

1975 overlap = False 

1976 if not create_aliases: 

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

1978 # if the target is a joined inheritance mapping, 

1979 # be more liberal about auto-aliasing. 

1980 if right_mapper and ( 

1981 right_mapper.with_polymorphic 

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

1983 ): 

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

1985 if sql_util.selectables_overlap( 

1986 l_info.selectable, from_obj 

1987 ) and sql_util.selectables_overlap( 

1988 from_obj, r_info.selectable 

1989 ): 

1990 overlap = True 

1991 break 

1992 

1993 if ( 

1994 overlap or not create_aliases 

1995 ) and l_info.selectable is r_info.selectable: 

1996 raise sa_exc.InvalidRequestError( 

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

1998 % l_info.selectable 

1999 ) 

2000 

2001 right_mapper, right_selectable, right_is_aliased = ( 

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

2003 r_info.selectable, 

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

2005 ) 

2006 

2007 if ( 

2008 right_mapper 

2009 and prop 

2010 and not right_mapper.common_parent(prop.mapper) 

2011 ): 

2012 raise sa_exc.InvalidRequestError( 

2013 "Join target %s does not correspond to " 

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

2015 ) 

2016 

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

2018 # purposes at the moment 

2019 if hasattr(r_info, "mapper"): 

2020 self._join_entities += (r_info,) 

2021 

2022 need_adapter = False 

2023 

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

2025 if r_info.is_clause_element: 

2026 

2027 if prop: 

2028 right_mapper = prop.mapper 

2029 

2030 if right_selectable._is_lateral: 

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

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

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

2034 current_adapter = self._get_current_adapter() 

2035 if current_adapter is not None: 

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

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

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

2039 right = current_adapter(right, True) 

2040 

2041 elif prop: 

2042 # joining to selectable with a mapper property given 

2043 # as the ON clause 

2044 

2045 if not right_selectable.is_derived_from( 

2046 right_mapper.persist_selectable 

2047 ): 

2048 raise sa_exc.InvalidRequestError( 

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

2050 % ( 

2051 right_selectable.description, 

2052 right_mapper.persist_selectable.description, 

2053 ) 

2054 ) 

2055 

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

2057 # turn it into an alias(). 

2058 if isinstance(right_selectable, expression.SelectBase): 

2059 right_selectable = coercions.expect( 

2060 roles.FromClauseRole, right_selectable 

2061 ) 

2062 need_adapter = True 

2063 

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

2065 right = aliased(right_mapper, right_selectable) 

2066 

2067 util.warn_deprecated( 

2068 "An alias is being generated automatically against " 

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

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

2071 "Use the aliased() " 

2072 "construct explicitly, see the linked example." 

2073 % right_mapper, 

2074 "1.4", 

2075 code="xaj1", 

2076 ) 

2077 

2078 elif create_aliases: 

2079 # it *could* work, but it doesn't right now and I'd rather 

2080 # get rid of aliased=True completely 

2081 raise sa_exc.InvalidRequestError( 

2082 "The aliased=True parameter on query.join() only works " 

2083 "with an ORM entity, not a plain selectable, as the " 

2084 "target." 

2085 ) 

2086 

2087 # test for overlap: 

2088 # orm/inheritance/relationships.py 

2089 # SelfReferentialM2MTest 

2090 aliased_entity = right_mapper and not right_is_aliased and overlap 

2091 

2092 if not need_adapter and (create_aliases or aliased_entity): 

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

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

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

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

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

2098 # aliasing is desirable. 

2099 right = aliased(right, flat=True) 

2100 need_adapter = True 

2101 

2102 if not create_aliases: 

2103 util.warn( 

2104 "An alias is being generated automatically against " 

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

2106 "legacy pattern which may be " 

2107 "deprecated in a later release. Use the " 

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

2109 "construct explicitly, see the linked example." 

2110 % right_mapper, 

2111 code="xaj2", 

2112 ) 

2113 

2114 if need_adapter: 

2115 assert right_mapper 

2116 

2117 adapter = ORMAdapter( 

2118 right, equivalents=right_mapper._equivalent_columns 

2119 ) 

2120 

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

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

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

2124 # set are also adapted. 

2125 if not create_aliases: 

2126 self._mapper_loads_polymorphically_with(right_mapper, adapter) 

2127 elif aliased_generation: 

2128 adapter._debug = True 

2129 self._aliased_generations[aliased_generation] = ( 

2130 adapter, 

2131 ) + self._aliased_generations.get(aliased_generation, ()) 

2132 elif ( 

2133 not r_info.is_clause_element 

2134 and not right_is_aliased 

2135 and right_mapper.with_polymorphic 

2136 and isinstance( 

2137 right_mapper._with_polymorphic_selectable, 

2138 expression.AliasedReturnsRows, 

2139 ) 

2140 ): 

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

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

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

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

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

2146 # strictly necessary. 

2147 # see test/orm/test_core_compilation.py 

2148 # ::RelNaturalAliasedJoinsTest::test_straight 

2149 # and similar 

2150 self._mapper_loads_polymorphically_with( 

2151 right_mapper, 

2152 sql_util.ColumnAdapter( 

2153 right_mapper.selectable, 

2154 right_mapper._equivalent_columns, 

2155 ), 

2156 ) 

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

2158 # adapters that are in place right now 

2159 if isinstance(onclause, expression.ClauseElement): 

2160 current_adapter = self._get_current_adapter() 

2161 if current_adapter: 

2162 onclause = current_adapter(onclause, True) 

2163 

2164 # if joining on a MapperProperty path, 

2165 # track the path to prevent redundant joins 

2166 if not create_aliases and prop: 

2167 self._update_joinpoint( 

2168 { 

2169 "_joinpoint_entity": right, 

2170 "prev": ((left, right, prop.key), self._joinpoint), 

2171 "aliased_generation": aliased_generation, 

2172 } 

2173 ) 

2174 else: 

2175 self._joinpoint = { 

2176 "_joinpoint_entity": right, 

2177 "aliased_generation": aliased_generation, 

2178 } 

2179 

2180 return inspect(right), right, onclause 

2181 

2182 def _update_joinpoint(self, jp): 

2183 self._joinpoint = jp 

2184 # copy backwards to the root of the _joinpath 

2185 # dict, so that no existing dict in the path is mutated 

2186 while "prev" in jp: 

2187 f, prev = jp["prev"] 

2188 prev = dict(prev) 

2189 prev[f] = jp.copy() 

2190 jp["prev"] = (f, prev) 

2191 jp = prev 

2192 self._joinpath = jp 

2193 

2194 def _reset_joinpoint(self): 

2195 self._joinpoint = self._joinpath 

2196 

2197 @property 

2198 def _select_args(self): 

2199 return { 

2200 "limit_clause": self.select_statement._limit_clause, 

2201 "offset_clause": self.select_statement._offset_clause, 

2202 "distinct": self.distinct, 

2203 "distinct_on": self.distinct_on, 

2204 "prefixes": self.select_statement._prefixes, 

2205 "suffixes": self.select_statement._suffixes, 

2206 "group_by": self.group_by or None, 

2207 "fetch_clause": self.select_statement._fetch_clause, 

2208 "fetch_clause_options": ( 

2209 self.select_statement._fetch_clause_options 

2210 ), 

2211 } 

2212 

2213 @property 

2214 def _should_nest_selectable(self): 

2215 kwargs = self._select_args 

2216 return ( 

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

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

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

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

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

2222 ) 

2223 

2224 def _get_extra_criteria(self, ext_info): 

2225 if ( 

2226 "additional_entity_criteria", 

2227 ext_info.mapper, 

2228 ) in self.global_attributes: 

2229 return tuple( 

2230 ae._resolve_where_criteria(ext_info) 

2231 for ae in self.global_attributes[ 

2232 ("additional_entity_criteria", ext_info.mapper) 

2233 ] 

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

2235 and ae._should_include(self) 

2236 ) 

2237 else: 

2238 return () 

2239 

2240 def _adjust_for_extra_criteria(self): 

2241 """Apply extra criteria filtering. 

2242 

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

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

2245 add criterion to the WHERE 

2246 clause of the given QueryContext such that only the appropriate 

2247 subtypes are selected from the total results. 

2248 

2249 Additionally, add WHERE criteria originating from LoaderCriteriaOptions 

2250 associated with the global context. 

2251 

2252 """ 

2253 

2254 for fromclause in self.from_clauses: 

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

2256 

2257 if ( 

2258 ext_info 

2259 and ( 

2260 ext_info.mapper._single_table_criterion is not None 

2261 or ("additional_entity_criteria", ext_info.mapper) 

2262 in self.global_attributes 

2263 ) 

2264 and ext_info not in self.extra_criteria_entities 

2265 ): 

2266 

2267 self.extra_criteria_entities[ext_info] = ( 

2268 ext_info, 

2269 ext_info._adapter if ext_info.is_aliased_class else None, 

2270 ) 

2271 

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

2273 

2274 for (ext_info, adapter) in search: 

2275 if ext_info in self._join_entities: 

2276 continue 

2277 

2278 single_crit = ext_info.mapper._single_table_criterion 

2279 

2280 if self.compile_options._for_refresh_state: 

2281 additional_entity_criteria = [] 

2282 else: 

2283 additional_entity_criteria = self._get_extra_criteria(ext_info) 

2284 

2285 if single_crit is not None: 

2286 additional_entity_criteria += (single_crit,) 

2287 

2288 current_adapter = self._get_current_adapter() 

2289 for crit in additional_entity_criteria: 

2290 if adapter: 

2291 crit = adapter.traverse(crit) 

2292 

2293 if current_adapter: 

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

2295 crit = current_adapter(crit, False) 

2296 self._where_criteria += (crit,) 

2297 

2298 

2299def _column_descriptions( 

2300 query_or_select_stmt, compile_state=None, legacy=False 

2301): 

2302 if compile_state is None: 

2303 compile_state = ORMSelectCompileState._create_entities_collection( 

2304 query_or_select_stmt, legacy=legacy 

2305 ) 

2306 ctx = compile_state 

2307 return [ 

2308 { 

2309 "name": ent._label_name, 

2310 "type": ent.type, 

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

2312 "expr": ent.expr, 

2313 "entity": getattr(insp_ent, "entity", None) 

2314 if ent.entity_zero is not None and not insp_ent.is_clause_element 

2315 else None, 

2316 } 

2317 for ent, insp_ent in [ 

2318 ( 

2319 _ent, 

2320 ( 

2321 inspect(_ent.entity_zero) 

2322 if _ent.entity_zero is not None 

2323 else None 

2324 ), 

2325 ) 

2326 for _ent in ctx._entities 

2327 ] 

2328 ] 

2329 

2330 

2331def _legacy_filter_by_entity_zero(query_or_augmented_select): 

2332 self = query_or_augmented_select 

2333 if self._legacy_setup_joins: 

2334 _last_joined_entity = self._last_joined_entity 

2335 if _last_joined_entity is not None: 

2336 return _last_joined_entity 

2337 

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

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

2340 

2341 return _entity_from_pre_ent_zero(self) 

2342 

2343 

2344def _entity_from_pre_ent_zero(query_or_augmented_select): 

2345 self = query_or_augmented_select 

2346 if not self._raw_columns: 

2347 return None 

2348 

2349 ent = self._raw_columns[0] 

2350 

2351 if "parententity" in ent._annotations: 

2352 return ent._annotations["parententity"] 

2353 elif isinstance(ent, ORMColumnsClauseRole): 

2354 return ent.entity 

2355 elif "bundle" in ent._annotations: 

2356 return ent._annotations["bundle"] 

2357 else: 

2358 return ent 

2359 

2360 

2361def _legacy_determine_last_joined_entity(setup_joins, entity_zero): 

2362 """given the legacy_setup_joins collection at a point in time, 

2363 figure out what the "filter by entity" would be in terms 

2364 of those joins. 

2365 

2366 in 2.0 this logic should hopefully be much simpler as there will 

2367 be far fewer ways to specify joins with the ORM 

2368 

2369 """ 

2370 

2371 if not setup_joins: 

2372 return entity_zero 

2373 

2374 # CAN BE REMOVED IN 2.0: 

2375 # 1. from_joinpoint 

2376 # 2. aliased_generation 

2377 # 3. aliased 

2378 # 4. any treating of prop as str 

2379 # 5. tuple madness 

2380 # 6. won't need recursive call anymore without #4 

2381 # 7. therefore can pass in just the last setup_joins record, 

2382 # don't need entity_zero 

2383 

2384 (right, onclause, left_, flags) = setup_joins[-1] 

2385 

2386 from_joinpoint = flags["from_joinpoint"] 

2387 

2388 if onclause is None and isinstance( 

2389 right, (str, interfaces.PropComparator) 

2390 ): 

2391 onclause = right 

2392 right = None 

2393 

2394 if right is not None and "parententity" in right._annotations: 

2395 right = right._annotations["parententity"].entity 

2396 

2397 if right is not None: 

2398 last_entity = right 

2399 insp = inspect(last_entity) 

2400 if insp.is_clause_element or insp.is_aliased_class or insp.is_mapper: 

2401 return insp 

2402 

2403 last_entity = onclause 

2404 if isinstance(last_entity, interfaces.PropComparator): 

2405 return last_entity.entity 

2406 

2407 # legacy vvvvvvvvvvvvvvvvvvvvvvvvvvv 

2408 if isinstance(onclause, str): 

2409 if from_joinpoint: 

2410 prev = _legacy_determine_last_joined_entity( 

2411 setup_joins[0:-1], entity_zero 

2412 ) 

2413 else: 

2414 prev = entity_zero 

2415 

2416 if prev is None: 

2417 return None 

2418 

2419 prev = inspect(prev) 

2420 attr = getattr(prev.entity, onclause, None) 

2421 if attr is not None: 

2422 return attr.property.entity 

2423 # legacy ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

2424 

2425 return None 

2426 

2427 

2428class _QueryEntity(object): 

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

2430 

2431 __slots__ = () 

2432 

2433 _non_hashable_value = False 

2434 _null_column_type = False 

2435 use_id_for_hash = False 

2436 

2437 @classmethod 

2438 def to_compile_state( 

2439 cls, compile_state, entities, entities_collection, is_current_entities 

2440 ): 

2441 

2442 for idx, entity in enumerate(entities): 

2443 if entity._is_lambda_element: 

2444 if entity._is_sequence: 

2445 cls.to_compile_state( 

2446 compile_state, 

2447 entity._resolved, 

2448 entities_collection, 

2449 is_current_entities, 

2450 ) 

2451 continue 

2452 else: 

2453 entity = entity._resolved 

2454 

2455 if entity.is_clause_element: 

2456 if entity.is_selectable: 

2457 if "parententity" in entity._annotations: 

2458 _MapperEntity( 

2459 compile_state, 

2460 entity, 

2461 entities_collection, 

2462 is_current_entities, 

2463 ) 

2464 else: 

2465 _ColumnEntity._for_columns( 

2466 compile_state, 

2467 entity._select_iterable, 

2468 entities_collection, 

2469 idx, 

2470 is_current_entities, 

2471 ) 

2472 else: 

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

2474 _BundleEntity( 

2475 compile_state, 

2476 entity, 

2477 entities_collection, 

2478 is_current_entities, 

2479 ) 

2480 elif entity._is_clause_list: 

2481 # this is legacy only - test_composites.py 

2482 # test_query_cols_legacy 

2483 _ColumnEntity._for_columns( 

2484 compile_state, 

2485 entity._select_iterable, 

2486 entities_collection, 

2487 idx, 

2488 is_current_entities, 

2489 ) 

2490 else: 

2491 _ColumnEntity._for_columns( 

2492 compile_state, 

2493 [entity], 

2494 entities_collection, 

2495 idx, 

2496 is_current_entities, 

2497 ) 

2498 elif entity.is_bundle: 

2499 _BundleEntity(compile_state, entity, entities_collection) 

2500 

2501 return entities_collection 

2502 

2503 

2504class _MapperEntity(_QueryEntity): 

2505 """mapper/class/AliasedClass entity""" 

2506 

2507 __slots__ = ( 

2508 "expr", 

2509 "mapper", 

2510 "entity_zero", 

2511 "is_aliased_class", 

2512 "path", 

2513 "_extra_entities", 

2514 "_label_name", 

2515 "_with_polymorphic_mappers", 

2516 "selectable", 

2517 "_polymorphic_discriminator", 

2518 ) 

2519 

2520 def __init__( 

2521 self, compile_state, entity, entities_collection, is_current_entities 

2522 ): 

2523 entities_collection.append(self) 

2524 if is_current_entities: 

2525 if compile_state._primary_entity is None: 

2526 compile_state._primary_entity = self 

2527 compile_state._has_mapper_entities = True 

2528 compile_state._has_orm_entities = True 

2529 

2530 entity = entity._annotations["parententity"] 

2531 entity._post_inspect 

2532 ext_info = self.entity_zero = entity 

2533 entity = ext_info.entity 

2534 

2535 self.expr = entity 

2536 self.mapper = mapper = ext_info.mapper 

2537 

2538 self._extra_entities = (self.expr,) 

2539 

2540 if ext_info.is_aliased_class: 

2541 self._label_name = ext_info.name 

2542 else: 

2543 self._label_name = mapper.class_.__name__ 

2544 

2545 self.is_aliased_class = ext_info.is_aliased_class 

2546 self.path = ext_info._path_registry 

2547 

2548 if ext_info in compile_state._with_polymorphic_adapt_map: 

2549 # this codepath occurs only if query.with_polymorphic() were 

2550 # used 

2551 

2552 wp = inspect(compile_state._with_polymorphic_adapt_map[ext_info]) 

2553 

2554 if self.is_aliased_class: 

2555 # TODO: invalidrequest ? 

2556 raise NotImplementedError( 

2557 "Can't use with_polymorphic() against an Aliased object" 

2558 ) 

2559 

2560 mappers, from_obj = mapper._with_polymorphic_args( 

2561 wp.with_polymorphic_mappers, wp.selectable 

2562 ) 

2563 

2564 self._with_polymorphic_mappers = mappers 

2565 self.selectable = from_obj 

2566 self._polymorphic_discriminator = wp.polymorphic_on 

2567 

2568 else: 

2569 self.selectable = ext_info.selectable 

2570 self._with_polymorphic_mappers = ext_info.with_polymorphic_mappers 

2571 self._polymorphic_discriminator = ext_info.polymorphic_on 

2572 

2573 if mapper._should_select_with_poly_adapter: 

2574 compile_state._create_with_polymorphic_adapter( 

2575 ext_info, self.selectable 

2576 ) 

2577 

2578 supports_single_entity = True 

2579 

2580 _non_hashable_value = True 

2581 use_id_for_hash = True 

2582 

2583 @property 

2584 def type(self): 

2585 return self.mapper.class_ 

2586 

2587 @property 

2588 def entity_zero_or_selectable(self): 

2589 return self.entity_zero 

2590 

2591 def corresponds_to(self, entity): 

2592 return _entity_corresponds_to(self.entity_zero, entity) 

2593 

2594 def _get_entity_clauses(self, compile_state): 

2595 

2596 adapter = None 

2597 

2598 if not self.is_aliased_class: 

2599 if compile_state._polymorphic_adapters: 

2600 adapter = compile_state._polymorphic_adapters.get( 

2601 self.mapper, None 

2602 ) 

2603 else: 

2604 adapter = self.entity_zero._adapter 

2605 

2606 if adapter: 

2607 if compile_state._from_obj_alias: 

2608 ret = adapter.wrap(compile_state._from_obj_alias) 

2609 else: 

2610 ret = adapter 

2611 else: 

2612 ret = compile_state._from_obj_alias 

2613 

2614 return ret 

2615 

2616 def row_processor(self, context, result): 

2617 compile_state = context.compile_state 

2618 adapter = self._get_entity_clauses(compile_state) 

2619 

2620 if compile_state.compound_eager_adapter and adapter: 

2621 adapter = adapter.wrap(compile_state.compound_eager_adapter) 

2622 elif not adapter: 

2623 adapter = compile_state.compound_eager_adapter 

2624 

2625 if compile_state._primary_entity is self: 

2626 only_load_props = compile_state.compile_options._only_load_props 

2627 refresh_state = context.refresh_state 

2628 else: 

2629 only_load_props = refresh_state = None 

2630 

2631 _instance = loading._instance_processor( 

2632 self, 

2633 self.mapper, 

2634 context, 

2635 result, 

2636 self.path, 

2637 adapter, 

2638 only_load_props=only_load_props, 

2639 refresh_state=refresh_state, 

2640 polymorphic_discriminator=self._polymorphic_discriminator, 

2641 ) 

2642 

2643 return _instance, self._label_name, self._extra_entities 

2644 

2645 def setup_compile_state(self, compile_state): 

2646 

2647 adapter = self._get_entity_clauses(compile_state) 

2648 

2649 single_table_crit = self.mapper._single_table_criterion 

2650 if ( 

2651 single_table_crit is not None 

2652 or ("additional_entity_criteria", self.mapper) 

2653 in compile_state.global_attributes 

2654 ): 

2655 ext_info = self.entity_zero 

2656 compile_state.extra_criteria_entities[ext_info] = ( 

2657 ext_info, 

2658 ext_info._adapter if ext_info.is_aliased_class else None, 

2659 ) 

2660 

2661 loading._setup_entity_query( 

2662 compile_state, 

2663 self.mapper, 

2664 self, 

2665 self.path, 

2666 adapter, 

2667 compile_state.primary_columns, 

2668 with_polymorphic=self._with_polymorphic_mappers, 

2669 only_load_props=compile_state.compile_options._only_load_props, 

2670 polymorphic_discriminator=self._polymorphic_discriminator, 

2671 ) 

2672 

2673 compile_state._fallback_from_clauses.append(self.selectable) 

2674 

2675 

2676class _BundleEntity(_QueryEntity): 

2677 

2678 _extra_entities = () 

2679 

2680 __slots__ = ( 

2681 "bundle", 

2682 "expr", 

2683 "type", 

2684 "_label_name", 

2685 "_entities", 

2686 "supports_single_entity", 

2687 ) 

2688 

2689 def __init__( 

2690 self, 

2691 compile_state, 

2692 expr, 

2693 entities_collection, 

2694 is_current_entities, 

2695 setup_entities=True, 

2696 parent_bundle=None, 

2697 ): 

2698 compile_state._has_orm_entities = True 

2699 

2700 expr = expr._annotations["bundle"] 

2701 if parent_bundle: 

2702 parent_bundle._entities.append(self) 

2703 else: 

2704 entities_collection.append(self) 

2705 

2706 if isinstance( 

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

2708 ): 

2709 bundle = expr.__clause_element__() 

2710 else: 

2711 bundle = expr 

2712 

2713 self.bundle = self.expr = bundle 

2714 self.type = type(bundle) 

2715 self._label_name = bundle.name 

2716 self._entities = [] 

2717 

2718 if setup_entities: 

2719 for expr in bundle.exprs: 

2720 if "bundle" in expr._annotations: 

2721 _BundleEntity( 

2722 compile_state, 

2723 expr, 

2724 entities_collection, 

2725 is_current_entities, 

2726 parent_bundle=self, 

2727 ) 

2728 elif isinstance(expr, Bundle): 

2729 _BundleEntity( 

2730 compile_state, 

2731 expr, 

2732 entities_collection, 

2733 is_current_entities, 

2734 parent_bundle=self, 

2735 ) 

2736 else: 

2737 _ORMColumnEntity._for_columns( 

2738 compile_state, 

2739 [expr], 

2740 entities_collection, 

2741 None, 

2742 is_current_entities, 

2743 parent_bundle=self, 

2744 ) 

2745 

2746 self.supports_single_entity = self.bundle.single_entity 

2747 if ( 

2748 self.supports_single_entity 

2749 and not compile_state.compile_options._use_legacy_query_style 

2750 ): 

2751 util.warn_deprecated_20( 

2752 "The Bundle.single_entity flag has no effect when " 

2753 "using 2.0 style execution." 

2754 ) 

2755 

2756 @property 

2757 def mapper(self): 

2758 ezero = self.entity_zero 

2759 if ezero is not None: 

2760 return ezero.mapper 

2761 else: 

2762 return None 

2763 

2764 @property 

2765 def entity_zero(self): 

2766 for ent in self._entities: 

2767 ezero = ent.entity_zero 

2768 if ezero is not None: 

2769 return ezero 

2770 else: 

2771 return None 

2772 

2773 def corresponds_to(self, entity): 

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

2775 # we are working around it 

2776 return False 

2777 

2778 @property 

2779 def entity_zero_or_selectable(self): 

2780 for ent in self._entities: 

2781 ezero = ent.entity_zero_or_selectable 

2782 if ezero is not None: 

2783 return ezero 

2784 else: 

2785 return None 

2786 

2787 def setup_compile_state(self, compile_state): 

2788 for ent in self._entities: 

2789 ent.setup_compile_state(compile_state) 

2790 

2791 def row_processor(self, context, result): 

2792 procs, labels, extra = zip( 

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

2794 ) 

2795 

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

2797 

2798 return proc, self._label_name, self._extra_entities 

2799 

2800 

2801class _ColumnEntity(_QueryEntity): 

2802 __slots__ = ( 

2803 "_fetch_column", 

2804 "_row_processor", 

2805 "raw_column_index", 

2806 "translate_raw_column", 

2807 ) 

2808 

2809 @classmethod 

2810 def _for_columns( 

2811 cls, 

2812 compile_state, 

2813 columns, 

2814 entities_collection, 

2815 raw_column_index, 

2816 is_current_entities, 

2817 parent_bundle=None, 

2818 ): 

2819 for column in columns: 

2820 annotations = column._annotations 

2821 if "parententity" in annotations: 

2822 _entity = annotations["parententity"] 

2823 else: 

2824 _entity = sql_util.extract_first_column_annotation( 

2825 column, "parententity" 

2826 ) 

2827 

2828 if _entity: 

2829 if "identity_token" in column._annotations: 

2830 _IdentityTokenEntity( 

2831 compile_state, 

2832 column, 

2833 entities_collection, 

2834 _entity, 

2835 raw_column_index, 

2836 is_current_entities, 

2837 parent_bundle=parent_bundle, 

2838 ) 

2839 else: 

2840 _ORMColumnEntity( 

2841 compile_state, 

2842 column, 

2843 entities_collection, 

2844 _entity, 

2845 raw_column_index, 

2846 is_current_entities, 

2847 parent_bundle=parent_bundle, 

2848 ) 

2849 else: 

2850 _RawColumnEntity( 

2851 compile_state, 

2852 column, 

2853 entities_collection, 

2854 raw_column_index, 

2855 is_current_entities, 

2856 parent_bundle=parent_bundle, 

2857 ) 

2858 

2859 @property 

2860 def type(self): 

2861 return self.column.type 

2862 

2863 @property 

2864 def _non_hashable_value(self): 

2865 return not self.column.type.hashable 

2866 

2867 @property 

2868 def _null_column_type(self): 

2869 return self.column.type._isnull 

2870 

2871 def row_processor(self, context, result): 

2872 compile_state = context.compile_state 

2873 

2874 # the resulting callable is entirely cacheable so just return 

2875 # it if we already made one 

2876 if self._row_processor is not None: 

2877 getter, label_name, extra_entities = self._row_processor 

2878 if self.translate_raw_column: 

2879 extra_entities += ( 

2880 result.context.invoked_statement._raw_columns[ 

2881 self.raw_column_index 

2882 ], 

2883 ) 

2884 

2885 return getter, label_name, extra_entities 

2886 

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

2888 # setup_compile_state, to avoid doing redundant work 

2889 if self._fetch_column is not None: 

2890 column = self._fetch_column 

2891 else: 

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

2893 # and setup_compile_state may not have been called. 

2894 column = self.column 

2895 

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

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

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

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

2900 if compile_state._from_obj_alias: 

2901 column = compile_state._from_obj_alias.columns[column] 

2902 

2903 if column._annotations: 

2904 # annotated columns perform more slowly in compiler and 

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

2906 column = column._deannotate() 

2907 

2908 if compile_state.compound_eager_adapter: 

2909 column = compile_state.compound_eager_adapter.columns[column] 

2910 

2911 getter = result._getter(column) 

2912 

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

2914 self._row_processor = ret 

2915 

2916 if self.translate_raw_column: 

2917 extra_entities = self._extra_entities + ( 

2918 result.context.invoked_statement._raw_columns[ 

2919 self.raw_column_index 

2920 ], 

2921 ) 

2922 return getter, self._label_name, extra_entities 

2923 else: 

2924 return ret 

2925 

2926 

2927class _RawColumnEntity(_ColumnEntity): 

2928 entity_zero = None 

2929 mapper = None 

2930 supports_single_entity = False 

2931 

2932 __slots__ = ( 

2933 "expr", 

2934 "column", 

2935 "_label_name", 

2936 "entity_zero_or_selectable", 

2937 "_extra_entities", 

2938 ) 

2939 

2940 def __init__( 

2941 self, 

2942 compile_state, 

2943 column, 

2944 entities_collection, 

2945 raw_column_index, 

2946 is_current_entities, 

2947 parent_bundle=None, 

2948 ): 

2949 self.expr = column 

2950 self.raw_column_index = raw_column_index 

2951 self.translate_raw_column = raw_column_index is not None 

2952 

2953 if column._is_star: 

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

2955 

2956 if not is_current_entities or column._is_text_clause: 

2957 self._label_name = None 

2958 else: 

2959 self._label_name = compile_state._label_convention(column) 

2960 

2961 if parent_bundle: 

2962 parent_bundle._entities.append(self) 

2963 else: 

2964 entities_collection.append(self) 

2965 

2966 self.column = column 

2967 self.entity_zero_or_selectable = ( 

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

2969 ) 

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

2971 self._fetch_column = self._row_processor = None 

2972 

2973 def corresponds_to(self, entity): 

2974 return False 

2975 

2976 def setup_compile_state(self, compile_state): 

2977 current_adapter = compile_state._get_current_adapter() 

2978 if current_adapter: 

2979 column = current_adapter(self.column, False) 

2980 else: 

2981 column = self.column 

2982 

2983 if column._annotations: 

2984 # annotated columns perform more slowly in compiler and 

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

2986 column = column._deannotate() 

2987 

2988 compile_state.dedupe_columns.add(column) 

2989 compile_state.primary_columns.append(column) 

2990 self._fetch_column = column 

2991 

2992 

2993class _ORMColumnEntity(_ColumnEntity): 

2994 """Column/expression based entity.""" 

2995 

2996 supports_single_entity = False 

2997 

2998 __slots__ = ( 

2999 "expr", 

3000 "mapper", 

3001 "column", 

3002 "_label_name", 

3003 "entity_zero_or_selectable", 

3004 "entity_zero", 

3005 "_extra_entities", 

3006 ) 

3007 

3008 def __init__( 

3009 self, 

3010 compile_state, 

3011 column, 

3012 entities_collection, 

3013 parententity, 

3014 raw_column_index, 

3015 is_current_entities, 

3016 parent_bundle=None, 

3017 ): 

3018 annotations = column._annotations 

3019 

3020 _entity = parententity 

3021 

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

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

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

3025 # within internal loaders. 

3026 

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

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

3029 if orm_key: 

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

3031 self.translate_raw_column = False 

3032 else: 

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

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

3035 # include this column position from the invoked statement 

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

3037 # it can be targeted by identity after caching 

3038 self.expr = column 

3039 self.translate_raw_column = raw_column_index is not None 

3040 

3041 self.raw_column_index = raw_column_index 

3042 

3043 if is_current_entities: 

3044 self._label_name = compile_state._label_convention( 

3045 column, col_name=orm_key 

3046 ) 

3047 else: 

3048 self._label_name = None 

3049 

3050 _entity._post_inspect 

3051 self.entity_zero = self.entity_zero_or_selectable = ezero = _entity 

3052 self.mapper = mapper = _entity.mapper 

3053 

3054 if parent_bundle: 

3055 parent_bundle._entities.append(self) 

3056 else: 

3057 entities_collection.append(self) 

3058 

3059 compile_state._has_orm_entities = True 

3060 

3061 self.column = column 

3062 

3063 self._fetch_column = self._row_processor = None 

3064 

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

3066 

3067 if mapper._should_select_with_poly_adapter: 

3068 compile_state._create_with_polymorphic_adapter( 

3069 ezero, ezero.selectable 

3070 ) 

3071 

3072 def corresponds_to(self, entity): 

3073 if _is_aliased_class(entity): 

3074 # TODO: polymorphic subclasses ? 

3075 return entity is self.entity_zero 

3076 else: 

3077 return not _is_aliased_class( 

3078 self.entity_zero 

3079 ) and entity.common_parent(self.entity_zero) 

3080 

3081 def setup_compile_state(self, compile_state): 

3082 current_adapter = compile_state._get_current_adapter() 

3083 if current_adapter: 

3084 column = current_adapter(self.column, False) 

3085 else: 

3086 column = self.column 

3087 

3088 ezero = self.entity_zero 

3089 

3090 single_table_crit = self.mapper._single_table_criterion 

3091 if ( 

3092 single_table_crit is not None 

3093 or ("additional_entity_criteria", self.mapper) 

3094 in compile_state.global_attributes 

3095 ): 

3096 

3097 compile_state.extra_criteria_entities[ezero] = ( 

3098 ezero, 

3099 ezero._adapter if ezero.is_aliased_class else None, 

3100 ) 

3101 

3102 if column._annotations and not column._expression_label: 

3103 # annotated columns perform more slowly in compiler and 

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

3105 column = column._deannotate() 

3106 

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

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

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

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

3111 # a scalar subquery. 

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

3113 ezero.selectable._from_objects 

3114 ): 

3115 compile_state._fallback_from_clauses.append(ezero.selectable) 

3116 

3117 compile_state.dedupe_columns.add(column) 

3118 compile_state.primary_columns.append(column) 

3119 self._fetch_column = column 

3120 

3121 

3122class _IdentityTokenEntity(_ORMColumnEntity): 

3123 translate_raw_column = False 

3124 

3125 def setup_compile_state(self, compile_state): 

3126 pass 

3127 

3128 def row_processor(self, context, result): 

3129 def getter(row): 

3130 return context.load_options._refresh_identity_token 

3131 

3132 return getter, self._label_name, self._extra_entities