1# orm/context.py 2# Copyright (C) 2005-2021 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 coercions 26from ..sql import expression 27from ..sql import roles 28from ..sql import util as sql_util 29from ..sql import visitors 30from ..sql.base import _entity_namespace_key 31from ..sql.base import _select_iterables 32from ..sql.base import CacheableOptions 33from ..sql.base import CompileState 34from ..sql.base import Options 35from ..sql.selectable import LABEL_STYLE_DISAMBIGUATE_ONLY 36from ..sql.selectable import LABEL_STYLE_NONE 37from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL 38from ..sql.selectable import SelectState 39from ..sql.visitors import ExtendedInternalTraversal 40from ..sql.visitors import InternalTraversal 41 42_path_registry = PathRegistry.root 43 44_EMPTY_DICT = util.immutabledict() 45 46 47LABEL_STYLE_LEGACY_ORM = util.symbol("LABEL_STYLE_LEGACY_ORM") 48 49 50class QueryContext(object): 51 __slots__ = ( 52 "compile_state", 53 "query", 54 "params", 55 "load_options", 56 "bind_arguments", 57 "execution_options", 58 "session", 59 "autoflush", 60 "populate_existing", 61 "invoke_all_eagers", 62 "version_check", 63 "refresh_state", 64 "create_eager_joins", 65 "propagated_loader_options", 66 "attributes", 67 "runid", 68 "partials", 69 "post_load_paths", 70 "identity_token", 71 "yield_per", 72 "loaders_require_buffering", 73 "loaders_require_uniquing", 74 ) 75 76 class default_load_options(Options): 77 _only_return_tuples = False 78 _populate_existing = False 79 _version_check = False 80 _invoke_all_eagers = True 81 _autoflush = True 82 _refresh_identity_token = None 83 _yield_per = None 84 _refresh_state = None 85 _lazy_loaded_from = None 86 _legacy_uniquing = False 87 88 def __init__( 89 self, 90 compile_state, 91 statement, 92 params, 93 session, 94 load_options, 95 execution_options=None, 96 bind_arguments=None, 97 ): 98 self.load_options = load_options 99 self.execution_options = execution_options or _EMPTY_DICT 100 self.bind_arguments = bind_arguments or _EMPTY_DICT 101 self.compile_state = compile_state 102 self.query = statement 103 self.session = session 104 self.loaders_require_buffering = False 105 self.loaders_require_uniquing = False 106 self.params = params 107 108 self.propagated_loader_options = { 109 o for o in statement._with_options if o.propagate_to_loaders 110 } 111 112 self.attributes = dict(compile_state.attributes) 113 114 self.autoflush = load_options._autoflush 115 self.populate_existing = load_options._populate_existing 116 self.invoke_all_eagers = load_options._invoke_all_eagers 117 self.version_check = load_options._version_check 118 self.refresh_state = load_options._refresh_state 119 self.yield_per = load_options._yield_per 120 self.identity_token = load_options._refresh_identity_token 121 122 if self.yield_per and compile_state._no_yield_pers: 123 raise sa_exc.InvalidRequestError( 124 "The yield_per Query option is currently not " 125 "compatible with %s eager loading. Please " 126 "specify lazyload('*') or query.enable_eagerloads(False) in " 127 "order to " 128 "proceed with query.yield_per()." 129 % ", ".join(compile_state._no_yield_pers) 130 ) 131 132 133_orm_load_exec_options = util.immutabledict( 134 {"_result_disable_adapt_to_context": True, "future_result": True} 135) 136 137 138class ORMCompileState(CompileState): 139 # note this is a dictionary, but the 140 # default_compile_options._with_polymorphic_adapt_map is a tuple 141 _with_polymorphic_adapt_map = _EMPTY_DICT 142 143 class default_compile_options(CacheableOptions): 144 _cache_key_traversal = [ 145 ("_use_legacy_query_style", InternalTraversal.dp_boolean), 146 ("_for_statement", InternalTraversal.dp_boolean), 147 ("_bake_ok", InternalTraversal.dp_boolean), 148 ( 149 "_with_polymorphic_adapt_map", 150 ExtendedInternalTraversal.dp_has_cache_key_tuples, 151 ), 152 ("_current_path", InternalTraversal.dp_has_cache_key), 153 ("_enable_single_crit", InternalTraversal.dp_boolean), 154 ("_enable_eagerloads", InternalTraversal.dp_boolean), 155 ("_orm_only_from_obj_alias", InternalTraversal.dp_boolean), 156 ("_only_load_props", InternalTraversal.dp_plain_obj), 157 ("_set_base_alias", InternalTraversal.dp_boolean), 158 ("_for_refresh_state", InternalTraversal.dp_boolean), 159 ("_render_for_subquery", InternalTraversal.dp_boolean), 160 ] 161 162 # set to True by default from Query._statement_20(), to indicate 163 # the rendered query should look like a legacy ORM query. right 164 # now this basically indicates we should use tablename_columnname 165 # style labels. Generally indicates the statement originated 166 # from a Query object. 167 _use_legacy_query_style = False 168 169 # set *only* when we are coming from the Query.statement 170 # accessor, or a Query-level equivalent such as 171 # query.subquery(). this supersedes "toplevel". 172 _for_statement = False 173 174 _bake_ok = True 175 _with_polymorphic_adapt_map = () 176 _current_path = _path_registry 177 _enable_single_crit = True 178 _enable_eagerloads = True 179 _orm_only_from_obj_alias = True 180 _only_load_props = None 181 _set_base_alias = False 182 _for_refresh_state = False 183 _render_for_subquery = False 184 185 current_path = _path_registry 186 187 def __init__(self, *arg, **kw): 188 raise NotImplementedError() 189 190 def _append_dedupe_col_collection(self, obj, col_collection): 191 dedupe = self.dedupe_columns 192 if obj not in dedupe: 193 dedupe.add(obj) 194 col_collection.append(obj) 195 196 @classmethod 197 def _column_naming_convention(cls, label_style, legacy): 198 199 if legacy: 200 201 def name(col, col_name=None): 202 if col_name: 203 return col_name 204 else: 205 return getattr(col, "key") 206 207 return name 208 else: 209 return SelectState._column_naming_convention(label_style) 210 211 @classmethod 212 def create_for_statement(cls, statement_container, compiler, **kw): 213 """Create a context for a statement given a :class:`.Compiler`. 214 215 This method is always invoked in the context of SQLCompiler.process(). 216 217 For a Select object, this would be invoked from 218 SQLCompiler.visit_select(). For the special FromStatement object used 219 by Query to indicate "Query.from_statement()", this is called by 220 FromStatement._compiler_dispatch() that would be called by 221 SQLCompiler.process(). 222 223 """ 224 raise NotImplementedError() 225 226 @classmethod 227 def get_column_descriptions(cls, statement): 228 return _column_descriptions(statement) 229 230 @classmethod 231 def orm_pre_session_exec( 232 cls, 233 session, 234 statement, 235 params, 236 execution_options, 237 bind_arguments, 238 is_reentrant_invoke, 239 ): 240 if is_reentrant_invoke: 241 return statement, execution_options 242 243 ( 244 load_options, 245 execution_options, 246 ) = QueryContext.default_load_options.from_execution_options( 247 "_sa_orm_load_options", 248 {"populate_existing", "autoflush", "yield_per"}, 249 execution_options, 250 statement._execution_options, 251 ) 252 253 # default execution options for ORM results: 254 # 1. _result_disable_adapt_to_context=True 255 # this will disable the ResultSetMetadata._adapt_to_context() 256 # step which we don't need, as we have result processors cached 257 # against the original SELECT statement before caching. 258 # 2. future_result=True. The ORM should **never** resolve columns 259 # in a result set based on names, only on Column objects that 260 # are correctly adapted to the context. W the legacy result 261 # it will still attempt name-based resolution and also emit a 262 # warning. 263 if not execution_options: 264 execution_options = _orm_load_exec_options 265 else: 266 execution_options = execution_options.union(_orm_load_exec_options) 267 268 if "yield_per" in execution_options or load_options._yield_per: 269 execution_options = execution_options.union( 270 { 271 "stream_results": True, 272 "max_row_buffer": execution_options.get( 273 "yield_per", load_options._yield_per 274 ), 275 } 276 ) 277 278 bind_arguments["clause"] = statement 279 280 # new in 1.4 - the coercions system is leveraged to allow the 281 # "subject" mapper of a statement be propagated to the top 282 # as the statement is built. "subject" mapper is the generally 283 # standard object used as an identifier for multi-database schemes. 284 285 # we are here based on the fact that _propagate_attrs contains 286 # "compile_state_plugin": "orm". The "plugin_subject" 287 # needs to be present as well. 288 289 try: 290 plugin_subject = statement._propagate_attrs["plugin_subject"] 291 except KeyError: 292 assert False, "statement had 'orm' plugin but no plugin_subject" 293 else: 294 if plugin_subject: 295 bind_arguments["mapper"] = plugin_subject.mapper 296 297 if load_options._autoflush: 298 session._autoflush() 299 300 return statement, execution_options 301 302 @classmethod 303 def orm_setup_cursor_result( 304 cls, 305 session, 306 statement, 307 params, 308 execution_options, 309 bind_arguments, 310 result, 311 ): 312 execution_context = result.context 313 compile_state = execution_context.compiled.compile_state 314 315 # cover edge case where ORM entities used in legacy select 316 # were passed to session.execute: 317 # session.execute(legacy_select([User.id, User.name])) 318 # see test_query->test_legacy_tuple_old_select 319 320 load_options = execution_options.get( 321 "_sa_orm_load_options", QueryContext.default_load_options 322 ) 323 324 querycontext = QueryContext( 325 compile_state, 326 statement, 327 params, 328 session, 329 load_options, 330 execution_options, 331 bind_arguments, 332 ) 333 return loading.instances(result, querycontext) 334 335 @property 336 def _lead_mapper_entities(self): 337 """return all _MapperEntity objects in the lead entities collection. 338 339 Does **not** include entities that have been replaced by 340 with_entities(), with_only_columns() 341 342 """ 343 return [ 344 ent for ent in self._entities if isinstance(ent, _MapperEntity) 345 ] 346 347 def _create_with_polymorphic_adapter(self, ext_info, selectable): 348 if ( 349 not ext_info.is_aliased_class 350 and ext_info.mapper.persist_selectable 351 not in self._polymorphic_adapters 352 ): 353 for mp in ext_info.mapper.iterate_to_root(): 354 self._mapper_loads_polymorphically_with( 355 mp, 356 sql_util.ColumnAdapter(selectable, mp._equivalent_columns), 357 ) 358 359 def _mapper_loads_polymorphically_with(self, mapper, adapter): 360 for m2 in mapper._with_polymorphic_mappers or [mapper]: 361 self._polymorphic_adapters[m2] = adapter 362 for m in m2.iterate_to_root(): # TODO: redundant ? 363 self._polymorphic_adapters[m.local_table] = adapter 364 365 366@sql.base.CompileState.plugin_for("orm", "orm_from_statement") 367class ORMFromStatementCompileState(ORMCompileState): 368 _aliased_generations = util.immutabledict() 369 _from_obj_alias = None 370 _has_mapper_entities = False 371 372 _has_orm_entities = False 373 multi_row_eager_loaders = False 374 compound_eager_adapter = None 375 376 extra_criteria_entities = _EMPTY_DICT 377 eager_joins = _EMPTY_DICT 378 379 @classmethod 380 def create_for_statement(cls, statement_container, compiler, **kw): 381 382 if compiler is not None: 383 toplevel = not compiler.stack 384 else: 385 toplevel = True 386 387 self = cls.__new__(cls) 388 self._primary_entity = None 389 390 self.use_legacy_query_style = ( 391 statement_container._compile_options._use_legacy_query_style 392 ) 393 self.statement_container = self.select_statement = statement_container 394 self.requested_statement = statement = statement_container.element 395 396 if statement.is_dml: 397 self.dml_table = statement.table 398 399 self._entities = [] 400 self._polymorphic_adapters = {} 401 self._no_yield_pers = set() 402 403 self.compile_options = statement_container._compile_options 404 405 if ( 406 self.use_legacy_query_style 407 and isinstance(statement, expression.SelectBase) 408 and not statement._is_textual 409 and not statement.is_dml 410 and statement._label_style is LABEL_STYLE_NONE 411 ): 412 self.statement = statement.set_label_style( 413 LABEL_STYLE_TABLENAME_PLUS_COL 414 ) 415 else: 416 self.statement = statement 417 418 self._label_convention = self._column_naming_convention( 419 statement._label_style 420 if not statement._is_textual and not statement.is_dml 421 else LABEL_STYLE_NONE, 422 self.use_legacy_query_style, 423 ) 424 425 _QueryEntity.to_compile_state( 426 self, 427 statement_container._raw_columns, 428 self._entities, 429 is_current_entities=True, 430 ) 431 432 self.current_path = statement_container._compile_options._current_path 433 434 if toplevel and statement_container._with_options: 435 self.attributes = {"_unbound_load_dedupes": set()} 436 self.global_attributes = compiler._global_attributes 437 438 for opt in statement_container._with_options: 439 if opt._is_compile_state: 440 opt.process_compile_state(self) 441 442 else: 443 self.attributes = {} 444 self.global_attributes = compiler._global_attributes 445 446 if statement_container._with_context_options: 447 for fn, key in statement_container._with_context_options: 448 fn(self) 449 450 self.primary_columns = [] 451 self.secondary_columns = [] 452 self.dedupe_columns = set() 453 self.create_eager_joins = [] 454 self._fallback_from_clauses = [] 455 456 self.order_by = None 457 458 if isinstance( 459 self.statement, (expression.TextClause, expression.UpdateBase) 460 ): 461 462 self.extra_criteria_entities = {} 463 464 # setup for all entities. Currently, this is not useful 465 # for eager loaders, as the eager loaders that work are able 466 # to do their work entirely in row_processor. 467 for entity in self._entities: 468 entity.setup_compile_state(self) 469 470 # we did the setup just to get primary columns. 471 self.statement = expression.TextualSelect( 472 self.statement, self.primary_columns, positional=False 473 ) 474 else: 475 # allow TextualSelect with implicit columns as well 476 # as select() with ad-hoc columns, see test_query::TextTest 477 self._from_obj_alias = sql.util.ColumnAdapter( 478 self.statement, adapt_on_names=True 479 ) 480 # set up for eager loaders, however if we fix subqueryload 481 # it should not need to do this here. the model of eager loaders 482 # that can work entirely in row_processor might be interesting 483 # here though subqueryloader has a lot of upfront work to do 484 # see test/orm/test_query.py -> test_related_eagerload_against_text 485 # for where this part makes a difference. would rather have 486 # subqueryload figure out what it needs more intelligently. 487 # for entity in self._entities: 488 # entity.setup_compile_state(self) 489 490 return self 491 492 def _adapt_col_list(self, cols, current_adapter): 493 return cols 494 495 def _get_current_adapter(self): 496 return None 497 498 499@sql.base.CompileState.plugin_for("orm", "select") 500class ORMSelectCompileState(ORMCompileState, SelectState): 501 _joinpath = _joinpoint = _EMPTY_DICT 502 503 _memoized_entities = _EMPTY_DICT 504 505 _from_obj_alias = None 506 _has_mapper_entities = False 507 508 _has_orm_entities = False 509 multi_row_eager_loaders = False 510 compound_eager_adapter = None 511 512 correlate = None 513 correlate_except = None 514 _where_criteria = () 515 _having_criteria = () 516 517 @classmethod 518 def create_for_statement(cls, statement, compiler, **kw): 519 """compiler hook, we arrive here from compiler.visit_select() only.""" 520 521 self = cls.__new__(cls) 522 523 if compiler is not None: 524 toplevel = not compiler.stack 525 self.global_attributes = compiler._global_attributes 526 else: 527 toplevel = True 528 self.global_attributes = {} 529 530 select_statement = statement 531 532 # if we are a select() that was never a legacy Query, we won't 533 # have ORM level compile options. 534 statement._compile_options = cls.default_compile_options.safe_merge( 535 statement._compile_options 536 ) 537 538 if select_statement._execution_options: 539 # execution options should not impact the compilation of a 540 # query, and at the moment subqueryloader is putting some things 541 # in here that we explicitly don't want stuck in a cache. 542 self.select_statement = select_statement._clone() 543 self.select_statement._execution_options = util.immutabledict() 544 else: 545 self.select_statement = select_statement 546 547 # indicates this select() came from Query.statement 548 self.for_statement = select_statement._compile_options._for_statement 549 550 # generally if we are from Query or directly from a select() 551 self.use_legacy_query_style = ( 552 select_statement._compile_options._use_legacy_query_style 553 ) 554 555 self._entities = [] 556 self._primary_entity = None 557 self._aliased_generations = {} 558 self._polymorphic_adapters = {} 559 self._no_yield_pers = set() 560 561 # legacy: only for query.with_polymorphic() 562 if select_statement._compile_options._with_polymorphic_adapt_map: 563 self._with_polymorphic_adapt_map = dict( 564 select_statement._compile_options._with_polymorphic_adapt_map 565 ) 566 self._setup_with_polymorphics() 567 568 self.compile_options = select_statement._compile_options 569 570 if not toplevel: 571 # for subqueries, turn off eagerloads and set 572 # "render_for_subquery". 573 self.compile_options += { 574 "_enable_eagerloads": False, 575 "_render_for_subquery": True, 576 } 577 578 # determine label style. we can make different decisions here. 579 # at the moment, trying to see if we can always use DISAMBIGUATE_ONLY 580 # rather than LABEL_STYLE_NONE, and if we can use disambiguate style 581 # for new style ORM selects too. 582 if ( 583 self.use_legacy_query_style 584 and self.select_statement._label_style is LABEL_STYLE_LEGACY_ORM 585 ): 586 if not self.for_statement: 587 self.label_style = LABEL_STYLE_TABLENAME_PLUS_COL 588 else: 589 self.label_style = LABEL_STYLE_DISAMBIGUATE_ONLY 590 else: 591 self.label_style = self.select_statement._label_style 592 593 self._label_convention = self._column_naming_convention( 594 statement._label_style, self.use_legacy_query_style 595 ) 596 597 if select_statement._memoized_select_entities: 598 self._memoized_entities = { 599 memoized_entities: _QueryEntity.to_compile_state( 600 self, 601 memoized_entities._raw_columns, 602 [], 603 is_current_entities=False, 604 ) 605 for memoized_entities in ( 606 select_statement._memoized_select_entities 607 ) 608 } 609 610 _QueryEntity.to_compile_state( 611 self, 612 select_statement._raw_columns, 613 self._entities, 614 is_current_entities=True, 615 ) 616 617 self.current_path = select_statement._compile_options._current_path 618 619 self.eager_order_by = () 620 621 if toplevel and ( 622 select_statement._with_options 623 or select_statement._memoized_select_entities 624 ): 625 self.attributes = {"_unbound_load_dedupes": set()} 626 627 for ( 628 memoized_entities 629 ) in select_statement._memoized_select_entities: 630 for opt in memoized_entities._with_options: 631 if opt._is_compile_state: 632 opt.process_compile_state_replaced_entities( 633 self, 634 [ 635 ent 636 for ent in self._memoized_entities[ 637 memoized_entities 638 ] 639 if isinstance(ent, _MapperEntity) 640 ], 641 ) 642 643 for opt in self.select_statement._with_options: 644 if opt._is_compile_state: 645 opt.process_compile_state(self) 646 else: 647 self.attributes = {} 648 649 if select_statement._with_context_options: 650 for fn, key in select_statement._with_context_options: 651 fn(self) 652 653 self.primary_columns = [] 654 self.secondary_columns = [] 655 self.dedupe_columns = set() 656 self.eager_joins = {} 657 self.extra_criteria_entities = {} 658 self.create_eager_joins = [] 659 self._fallback_from_clauses = [] 660 661 # normalize the FROM clauses early by themselves, as this makes 662 # it an easier job when we need to assemble a JOIN onto these, 663 # for select.join() as well as joinedload(). As of 1.4 there are now 664 # potentially more complex sets of FROM objects here as the use 665 # of lambda statements for lazyload, load_on_pk etc. uses more 666 # cloning of the select() construct. See #6495 667 self.from_clauses = self._normalize_froms( 668 info.selectable for info in select_statement._from_obj 669 ) 670 671 # this is a fairly arbitrary break into a second method, 672 # so it might be nicer to break up create_for_statement() 673 # and _setup_for_generate into three or four logical sections 674 self._setup_for_generate() 675 676 SelectState.__init__(self, self.statement, compiler, **kw) 677 678 return self 679 680 def _setup_for_generate(self): 681 query = self.select_statement 682 683 self.statement = None 684 self._join_entities = () 685 686 if self.compile_options._set_base_alias: 687 self._set_select_from_alias() 688 689 for memoized_entities in query._memoized_select_entities: 690 if memoized_entities._setup_joins: 691 self._join( 692 memoized_entities._setup_joins, 693 self._memoized_entities[memoized_entities], 694 ) 695 if memoized_entities._legacy_setup_joins: 696 self._legacy_join( 697 memoized_entities._legacy_setup_joins, 698 self._memoized_entities[memoized_entities], 699 ) 700 701 if query._setup_joins: 702 self._join(query._setup_joins, self._entities) 703 704 if query._legacy_setup_joins: 705 self._legacy_join(query._legacy_setup_joins, self._entities) 706 707 current_adapter = self._get_current_adapter() 708 709 if query._where_criteria: 710 self._where_criteria = query._where_criteria 711 712 if current_adapter: 713 self._where_criteria = tuple( 714 current_adapter(crit, True) 715 for crit in self._where_criteria 716 ) 717 718 # TODO: some complexity with order_by here was due to mapper.order_by. 719 # now that this is removed we can hopefully make order_by / 720 # group_by act identically to how they are in Core select. 721 self.order_by = ( 722 self._adapt_col_list(query._order_by_clauses, current_adapter) 723 if current_adapter and query._order_by_clauses not in (None, False) 724 else query._order_by_clauses 725 ) 726 727 if query._having_criteria: 728 self._having_criteria = tuple( 729 current_adapter(crit, True) if current_adapter else crit 730 for crit in query._having_criteria 731 ) 732 733 self.group_by = ( 734 self._adapt_col_list( 735 util.flatten_iterator(query._group_by_clauses), current_adapter 736 ) 737 if current_adapter and query._group_by_clauses not in (None, False) 738 else query._group_by_clauses or None 739 ) 740 741 if self.eager_order_by: 742 adapter = self.from_clauses[0]._target_adapter 743 self.eager_order_by = adapter.copy_and_process(self.eager_order_by) 744 745 if query._distinct_on: 746 self.distinct_on = self._adapt_col_list( 747 query._distinct_on, current_adapter 748 ) 749 else: 750 self.distinct_on = () 751 752 self.distinct = query._distinct 753 754 if query._correlate: 755 # ORM mapped entities that are mapped to joins can be passed 756 # to .correlate, so here they are broken into their component 757 # tables. 758 self.correlate = tuple( 759 util.flatten_iterator( 760 sql_util.surface_selectables(s) if s is not None else None 761 for s in query._correlate 762 ) 763 ) 764 elif query._correlate_except: 765 self.correlate_except = tuple( 766 util.flatten_iterator( 767 sql_util.surface_selectables(s) if s is not None else None 768 for s in query._correlate_except 769 ) 770 ) 771 elif not query._auto_correlate: 772 self.correlate = (None,) 773 774 # PART II 775 776 self._for_update_arg = query._for_update_arg 777 778 for entity in self._entities: 779 entity.setup_compile_state(self) 780 781 for rec in self.create_eager_joins: 782 strategy = rec[0] 783 strategy(self, *rec[1:]) 784 785 # else "load from discrete FROMs" mode, 786 # i.e. when each _MappedEntity has its own FROM 787 788 if self.compile_options._enable_single_crit: 789 self._adjust_for_extra_criteria() 790 791 if not self.primary_columns: 792 if self.compile_options._only_load_props: 793 raise sa_exc.InvalidRequestError( 794 "No column-based properties specified for " 795 "refresh operation. Use session.expire() " 796 "to reload collections and related items." 797 ) 798 else: 799 raise sa_exc.InvalidRequestError( 800 "Query contains no columns with which to SELECT from." 801 ) 802 803 if not self.from_clauses: 804 self.from_clauses = list(self._fallback_from_clauses) 805 806 if self.order_by is False: 807 self.order_by = None 808 809 if self.multi_row_eager_loaders and self._should_nest_selectable: 810 self.statement = self._compound_eager_statement() 811 else: 812 self.statement = self._simple_statement() 813 814 if self.for_statement: 815 ezero = self._mapper_zero() 816 if ezero is not None: 817 # TODO: this goes away once we get rid of the deep entity 818 # thing 819 self.statement = self.statement._annotate( 820 {"deepentity": ezero} 821 ) 822 823 @classmethod 824 def _create_entities_collection(cls, query, legacy): 825 """Creates a partial ORMSelectCompileState that includes 826 the full collection of _MapperEntity and other _QueryEntity objects. 827 828 Supports a few remaining use cases that are pre-compilation 829 but still need to gather some of the column / adaption information. 830 831 """ 832 self = cls.__new__(cls) 833 834 self._entities = [] 835 self._primary_entity = None 836 self._aliased_generations = {} 837 self._polymorphic_adapters = {} 838 839 compile_options = cls.default_compile_options.safe_merge( 840 query._compile_options 841 ) 842 # legacy: only for query.with_polymorphic() 843 if compile_options._with_polymorphic_adapt_map: 844 self._with_polymorphic_adapt_map = dict( 845 compile_options._with_polymorphic_adapt_map 846 ) 847 self._setup_with_polymorphics() 848 849 self._label_convention = self._column_naming_convention( 850 query._label_style, legacy 851 ) 852 853 # entities will also set up polymorphic adapters for mappers 854 # that have with_polymorphic configured 855 _QueryEntity.to_compile_state( 856 self, query._raw_columns, self._entities, is_current_entities=True 857 ) 858 return self 859 860 @classmethod 861 def determine_last_joined_entity(cls, statement): 862 setup_joins = statement._setup_joins 863 864 if not setup_joins: 865 return None 866 867 (target, onclause, from_, flags) = setup_joins[-1] 868 869 if isinstance(target, interfaces.PropComparator): 870 return target.entity 871 else: 872 return target 873 874 @classmethod 875 def all_selected_columns(cls, statement): 876 for element in statement._raw_columns: 877 if ( 878 element.is_selectable 879 and "entity_namespace" in element._annotations 880 ): 881 ens = element._annotations["entity_namespace"] 882 if not ens.is_mapper and not ens.is_aliased_class: 883 for elem in _select_iterables([element]): 884 yield elem 885 else: 886 for elem in _select_iterables(ens._all_column_expressions): 887 yield elem 888 else: 889 for elem in _select_iterables([element]): 890 yield elem 891 892 @classmethod 893 def get_columns_clause_froms(cls, statement): 894 return cls._normalize_froms( 895 itertools.chain.from_iterable( 896 element._from_objects 897 if "parententity" not in element._annotations 898 else [ 899 element._annotations["parententity"].__clause_element__() 900 ] 901 for element in statement._raw_columns 902 ) 903 ) 904 905 @classmethod 906 @util.preload_module("sqlalchemy.orm.query") 907 def from_statement(cls, statement, from_statement): 908 query = util.preloaded.orm_query 909 910 from_statement = coercions.expect( 911 roles.ReturnsRowsRole, 912 from_statement, 913 apply_propagate_attrs=statement, 914 ) 915 916 stmt = query.FromStatement(statement._raw_columns, from_statement) 917 918 stmt.__dict__.update( 919 _with_options=statement._with_options, 920 _with_context_options=statement._with_context_options, 921 _execution_options=statement._execution_options, 922 _propagate_attrs=statement._propagate_attrs, 923 ) 924 return stmt 925 926 def _setup_with_polymorphics(self): 927 # legacy: only for query.with_polymorphic() 928 for ext_info, wp in self._with_polymorphic_adapt_map.items(): 929 self._mapper_loads_polymorphically_with(ext_info, wp._adapter) 930 931 def _set_select_from_alias(self): 932 933 query = self.select_statement # query 934 935 assert self.compile_options._set_base_alias 936 assert len(query._from_obj) == 1 937 938 adapter = self._get_select_from_alias_from_obj(query._from_obj[0]) 939 if adapter: 940 self.compile_options += {"_enable_single_crit": False} 941 self._from_obj_alias = adapter 942 943 def _get_select_from_alias_from_obj(self, from_obj): 944 info = from_obj 945 946 if "parententity" in info._annotations: 947 info = info._annotations["parententity"] 948 949 if hasattr(info, "mapper"): 950 if not info.is_aliased_class: 951 raise sa_exc.ArgumentError( 952 "A selectable (FromClause) instance is " 953 "expected when the base alias is being set." 954 ) 955 else: 956 return info._adapter 957 958 elif isinstance(info.selectable, sql.selectable.AliasedReturnsRows): 959 equivs = self._all_equivs() 960 return sql_util.ColumnAdapter(info, equivs) 961 else: 962 return None 963 964 def _mapper_zero(self): 965 """return the Mapper associated with the first QueryEntity.""" 966 return self._entities[0].mapper 967 968 def _entity_zero(self): 969 """Return the 'entity' (mapper or AliasedClass) associated 970 with the first QueryEntity, or alternatively the 'select from' 971 entity if specified.""" 972 973 for ent in self.from_clauses: 974 if "parententity" in ent._annotations: 975 return ent._annotations["parententity"] 976 for qent in self._entities: 977 if qent.entity_zero: 978 return qent.entity_zero 979 980 return None 981 982 def _only_full_mapper_zero(self, methname): 983 if self._entities != [self._primary_entity]: 984 raise sa_exc.InvalidRequestError( 985 "%s() can only be used against " 986 "a single mapped class." % methname 987 ) 988 return self._primary_entity.entity_zero 989 990 def _only_entity_zero(self, rationale=None): 991 if len(self._entities) > 1: 992 raise sa_exc.InvalidRequestError( 993 rationale 994 or "This operation requires a Query " 995 "against a single mapper." 996 ) 997 return self._entity_zero() 998 999 def _all_equivs(self): 1000 equivs = {} 1001 1002 for memoized_entities in self._memoized_entities.values(): 1003 for ent in [ 1004 ent 1005 for ent in memoized_entities 1006 if isinstance(ent, _MapperEntity) 1007 ]: 1008 equivs.update(ent.mapper._equivalent_columns) 1009 1010 for ent in [ 1011 ent for ent in self._entities if isinstance(ent, _MapperEntity) 1012 ]: 1013 equivs.update(ent.mapper._equivalent_columns) 1014 return equivs 1015 1016 def _compound_eager_statement(self): 1017 # for eager joins present and LIMIT/OFFSET/DISTINCT, 1018 # wrap the query inside a select, 1019 # then append eager joins onto that 1020 1021 if self.order_by: 1022 # the default coercion for ORDER BY is now the OrderByRole, 1023 # which adds an additional post coercion to ByOfRole in that 1024 # elements are converted into label references. For the 1025 # eager load / subquery wrapping case, we need to un-coerce 1026 # the original expressions outside of the label references 1027 # in order to have them render. 1028 unwrapped_order_by = [ 1029 elem.element 1030 if isinstance(elem, sql.elements._label_reference) 1031 else elem 1032 for elem in self.order_by 1033 ] 1034 1035 order_by_col_expr = sql_util.expand_column_list_from_order_by( 1036 self.primary_columns, unwrapped_order_by 1037 ) 1038 else: 1039 order_by_col_expr = [] 1040 unwrapped_order_by = None 1041 1042 # put FOR UPDATE on the inner query, where MySQL will honor it, 1043 # as well as if it has an OF so PostgreSQL can use it. 1044 inner = self._select_statement( 1045 self.primary_columns 1046 + [c for c in order_by_col_expr if c not in self.dedupe_columns], 1047 self.from_clauses, 1048 self._where_criteria, 1049 self._having_criteria, 1050 self.label_style, 1051 self.order_by, 1052 for_update=self._for_update_arg, 1053 hints=self.select_statement._hints, 1054 statement_hints=self.select_statement._statement_hints, 1055 correlate=self.correlate, 1056 correlate_except=self.correlate_except, 1057 **self._select_args 1058 ) 1059 1060 inner = inner.alias() 1061 1062 equivs = self._all_equivs() 1063 1064 self.compound_eager_adapter = sql_util.ColumnAdapter(inner, equivs) 1065 1066 statement = future.select( 1067 *([inner] + self.secondary_columns) # use_labels=self.labels 1068 ) 1069 statement._label_style = self.label_style 1070 1071 # Oracle however does not allow FOR UPDATE on the subquery, 1072 # and the Oracle dialect ignores it, plus for PostgreSQL, MySQL 1073 # we expect that all elements of the row are locked, so also put it 1074 # on the outside (except in the case of PG when OF is used) 1075 if ( 1076 self._for_update_arg is not None 1077 and self._for_update_arg.of is None 1078 ): 1079 statement._for_update_arg = self._for_update_arg 1080 1081 from_clause = inner 1082 for eager_join in self.eager_joins.values(): 1083 # EagerLoader places a 'stop_on' attribute on the join, 1084 # giving us a marker as to where the "splice point" of 1085 # the join should be 1086 from_clause = sql_util.splice_joins( 1087 from_clause, eager_join, eager_join.stop_on 1088 ) 1089 1090 statement.select_from.non_generative(statement, from_clause) 1091 1092 if unwrapped_order_by: 1093 statement.order_by.non_generative( 1094 statement, 1095 *self.compound_eager_adapter.copy_and_process( 1096 unwrapped_order_by 1097 ) 1098 ) 1099 1100 statement.order_by.non_generative(statement, *self.eager_order_by) 1101 return statement 1102 1103 def _simple_statement(self): 1104 1105 if ( 1106 self.compile_options._use_legacy_query_style 1107 and (self.distinct and not self.distinct_on) 1108 and self.order_by 1109 ): 1110 to_add = sql_util.expand_column_list_from_order_by( 1111 self.primary_columns, self.order_by 1112 ) 1113 if to_add: 1114 util.warn_deprecated_20( 1115 "ORDER BY columns added implicitly due to " 1116 "DISTINCT is deprecated and will be removed in " 1117 "SQLAlchemy 2.0. SELECT statements with DISTINCT " 1118 "should be written to explicitly include the appropriate " 1119 "columns in the columns clause" 1120 ) 1121 self.primary_columns += to_add 1122 1123 statement = self._select_statement( 1124 self.primary_columns + self.secondary_columns, 1125 tuple(self.from_clauses) + tuple(self.eager_joins.values()), 1126 self._where_criteria, 1127 self._having_criteria, 1128 self.label_style, 1129 self.order_by, 1130 for_update=self._for_update_arg, 1131 hints=self.select_statement._hints, 1132 statement_hints=self.select_statement._statement_hints, 1133 correlate=self.correlate, 1134 correlate_except=self.correlate_except, 1135 **self._select_args 1136 ) 1137 1138 if self.eager_order_by: 1139 statement.order_by.non_generative(statement, *self.eager_order_by) 1140 return statement 1141 1142 def _select_statement( 1143 self, 1144 raw_columns, 1145 from_obj, 1146 where_criteria, 1147 having_criteria, 1148 label_style, 1149 order_by, 1150 for_update, 1151 hints, 1152 statement_hints, 1153 correlate, 1154 correlate_except, 1155 limit_clause, 1156 offset_clause, 1157 distinct, 1158 distinct_on, 1159 prefixes, 1160 suffixes, 1161 group_by, 1162 ): 1163 1164 Select = future.Select 1165 statement = Select._create_raw_select( 1166 _raw_columns=raw_columns, 1167 _from_obj=from_obj, 1168 _label_style=label_style, 1169 ) 1170 1171 if where_criteria: 1172 statement._where_criteria = where_criteria 1173 if having_criteria: 1174 statement._having_criteria = having_criteria 1175 1176 if order_by: 1177 statement._order_by_clauses += tuple(order_by) 1178 1179 if distinct_on: 1180 statement.distinct.non_generative(statement, *distinct_on) 1181 elif distinct: 1182 statement.distinct.non_generative(statement) 1183 1184 if group_by: 1185 statement._group_by_clauses += tuple(group_by) 1186 1187 statement._limit_clause = limit_clause 1188 statement._offset_clause = offset_clause 1189 1190 if prefixes: 1191 statement._prefixes = prefixes 1192 1193 if suffixes: 1194 statement._suffixes = suffixes 1195 1196 statement._for_update_arg = for_update 1197 1198 if hints: 1199 statement._hints = hints 1200 if statement_hints: 1201 statement._statement_hints = statement_hints 1202 1203 if correlate: 1204 statement.correlate.non_generative(statement, *correlate) 1205 1206 if correlate_except: 1207 statement.correlate_except.non_generative( 1208 statement, *correlate_except 1209 ) 1210 1211 return statement 1212 1213 def _adapt_polymorphic_element(self, element): 1214 if "parententity" in element._annotations: 1215 search = element._annotations["parententity"] 1216 alias = self._polymorphic_adapters.get(search, None) 1217 if alias: 1218 return alias.adapt_clause(element) 1219 1220 if isinstance(element, expression.FromClause): 1221 search = element 1222 elif hasattr(element, "table"): 1223 search = element.table 1224 else: 1225 return None 1226 1227 alias = self._polymorphic_adapters.get(search, None) 1228 if alias: 1229 return alias.adapt_clause(element) 1230 1231 def _adapt_aliased_generation(self, element): 1232 # this is crazy logic that I look forward to blowing away 1233 # when aliased=True is gone :) 1234 if "aliased_generation" in element._annotations: 1235 for adapter in self._aliased_generations.get( 1236 element._annotations["aliased_generation"], () 1237 ): 1238 replaced_elem = adapter.replace(element) 1239 if replaced_elem is not None: 1240 return replaced_elem 1241 1242 return None 1243 1244 def _adapt_col_list(self, cols, current_adapter): 1245 if current_adapter: 1246 return [current_adapter(o, True) for o in cols] 1247 else: 1248 return cols 1249 1250 def _get_current_adapter(self): 1251 1252 adapters = [] 1253 1254 if self._from_obj_alias: 1255 # used for legacy going forward for query set_ops, e.g. 1256 # union(), union_all(), etc. 1257 # 1.4 and previously, also used for from_self(), 1258 # select_entity_from() 1259 # 1260 # for the "from obj" alias, apply extra rule to the 1261 # 'ORM only' check, if this query were generated from a 1262 # subquery of itself, i.e. _from_selectable(), apply adaption 1263 # to all SQL constructs. 1264 adapters.append( 1265 ( 1266 False 1267 if self.compile_options._orm_only_from_obj_alias 1268 else True, 1269 self._from_obj_alias.replace, 1270 ) 1271 ) 1272 1273 # vvvvvvvvvvvvvvv legacy vvvvvvvvvvvvvvvvvv 1274 # this can totally go away when we remove join(..., aliased=True) 1275 if self._aliased_generations: 1276 adapters.append((False, self._adapt_aliased_generation)) 1277 # ^^^^^^^^^^^^^ legacy ^^^^^^^^^^^^^^^^^^^^^ 1278 1279 # this was *hopefully* the only adapter we were going to need 1280 # going forward...however, we unfortunately need _from_obj_alias 1281 # for query.union(), which we can't drop 1282 if self._polymorphic_adapters: 1283 adapters.append((False, self._adapt_polymorphic_element)) 1284 1285 if not adapters: 1286 return None 1287 1288 def _adapt_clause(clause, as_filter): 1289 # do we adapt all expression elements or only those 1290 # tagged as 'ORM' constructs ? 1291 1292 def replace(elem): 1293 is_orm_adapt = ( 1294 "_orm_adapt" in elem._annotations 1295 or "parententity" in elem._annotations 1296 ) 1297 for always_adapt, adapter in adapters: 1298 if is_orm_adapt or always_adapt: 1299 e = adapter(elem) 1300 if e is not None: 1301 return e 1302 1303 return visitors.replacement_traverse(clause, {}, replace) 1304 1305 return _adapt_clause 1306 1307 def _join(self, args, entities_collection): 1308 for (right, onclause, from_, flags) in args: 1309 isouter = flags["isouter"] 1310 full = flags["full"] 1311 # maybe? 1312 self._reset_joinpoint() 1313 1314 right = inspect(right) 1315 if onclause is not None: 1316 onclause = inspect(onclause) 1317 1318 if onclause is None and isinstance( 1319 right, interfaces.PropComparator 1320 ): 1321 # determine onclause/right_entity. still need to think 1322 # about how to best organize this since we are getting: 1323 # 1324 # 1325 # q.join(Entity, Parent.property) 1326 # q.join(Parent.property) 1327 # q.join(Parent.property.of_type(Entity)) 1328 # q.join(some_table) 1329 # q.join(some_table, some_parent.c.id==some_table.c.parent_id) 1330 # 1331 # is this still too many choices? how do we handle this 1332 # when sometimes "right" is implied and sometimes not? 1333 # 1334 onclause = right 1335 right = None 1336 elif "parententity" in right._annotations: 1337 right = right._annotations["parententity"] 1338 1339 if onclause is None: 1340 if not right.is_selectable and not hasattr(right, "mapper"): 1341 raise sa_exc.ArgumentError( 1342 "Expected mapped entity or " 1343 "selectable/table as join target" 1344 ) 1345 1346 of_type = None 1347 1348 if isinstance(onclause, interfaces.PropComparator): 1349 # descriptor/property given (or determined); this tells us 1350 # explicitly what the expected "left" side of the join is. 1351 1352 of_type = getattr(onclause, "_of_type", None) 1353 1354 if right is None: 1355 if of_type: 1356 right = of_type 1357 else: 1358 right = onclause.property 1359 1360 try: 1361 right = right.entity 1362 except AttributeError as err: 1363 util.raise_( 1364 sa_exc.ArgumentError( 1365 "Join target %s does not refer to a " 1366 "mapped entity" % right 1367 ), 1368 replace_context=err, 1369 ) 1370 1371 left = onclause._parententity 1372 1373 alias = self._polymorphic_adapters.get(left, None) 1374 1375 # could be None or could be ColumnAdapter also 1376 if isinstance(alias, ORMAdapter) and alias.mapper.isa(left): 1377 left = alias.aliased_class 1378 onclause = getattr(left, onclause.key) 1379 1380 prop = onclause.property 1381 if not isinstance(onclause, attributes.QueryableAttribute): 1382 onclause = prop 1383 1384 # TODO: this is where "check for path already present" 1385 # would occur. see if this still applies? 1386 1387 if from_ is not None: 1388 if ( 1389 from_ is not left 1390 and from_._annotations.get("parententity", None) 1391 is not left 1392 ): 1393 raise sa_exc.InvalidRequestError( 1394 "explicit from clause %s does not match left side " 1395 "of relationship attribute %s" 1396 % ( 1397 from_._annotations.get("parententity", from_), 1398 onclause, 1399 ) 1400 ) 1401 elif from_ is not None: 1402 prop = None 1403 left = from_ 1404 else: 1405 # no descriptor/property given; we will need to figure out 1406 # what the effective "left" side is 1407 prop = left = None 1408 1409 # figure out the final "left" and "right" sides and create an 1410 # ORMJoin to add to our _from_obj tuple 1411 self._join_left_to_right( 1412 entities_collection, 1413 left, 1414 right, 1415 onclause, 1416 prop, 1417 False, 1418 False, 1419 isouter, 1420 full, 1421 ) 1422 1423 def _legacy_join(self, args, entities_collection): 1424 """consumes arguments from join() or outerjoin(), places them into a 1425 consistent format with which to form the actual JOIN constructs. 1426 1427 """ 1428 for (right, onclause, left, flags) in args: 1429 1430 outerjoin = flags["isouter"] 1431 create_aliases = flags["aliased"] 1432 from_joinpoint = flags["from_joinpoint"] 1433 full = flags["full"] 1434 aliased_generation = flags["aliased_generation"] 1435 1436 # do a quick inspect to accommodate for a lambda 1437 if right is not None and not isinstance(right, util.string_types): 1438 right = inspect(right) 1439 if onclause is not None and not isinstance( 1440 onclause, util.string_types 1441 ): 1442 onclause = inspect(onclause) 1443 1444 # legacy vvvvvvvvvvvvvvvvvvvvvvvvvv 1445 if not from_joinpoint: 1446 self._reset_joinpoint() 1447 else: 1448 prev_aliased_generation = self._joinpoint.get( 1449 "aliased_generation", None 1450 ) 1451 if not aliased_generation: 1452 aliased_generation = prev_aliased_generation 1453 elif prev_aliased_generation: 1454 self._aliased_generations[ 1455 aliased_generation 1456 ] = self._aliased_generations.get( 1457 prev_aliased_generation, () 1458 ) 1459 # legacy ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1460 1461 if ( 1462 isinstance( 1463 right, (interfaces.PropComparator, util.string_types) 1464 ) 1465 and onclause is None 1466 ): 1467 onclause = right 1468 right = None 1469 elif "parententity" in right._annotations: 1470 right = right._annotations["parententity"] 1471 1472 if onclause is None: 1473 if not right.is_selectable and not hasattr(right, "mapper"): 1474 raise sa_exc.ArgumentError( 1475 "Expected mapped entity or " 1476 "selectable/table as join target" 1477 ) 1478 1479 if isinstance(onclause, interfaces.PropComparator): 1480 of_type = getattr(onclause, "_of_type", None) 1481 else: 1482 of_type = None 1483 1484 if isinstance(onclause, util.string_types): 1485 # string given, e.g. query(Foo).join("bar"). 1486 # we look to the left entity or what we last joined 1487 # towards 1488 onclause = _entity_namespace_key( 1489 inspect(self._joinpoint_zero()), onclause 1490 ) 1491 1492 # legacy vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv 1493 # check for q.join(Class.propname, from_joinpoint=True) 1494 # and Class corresponds at the mapper level to the current 1495 # joinpoint. this match intentionally looks for a non-aliased 1496 # class-bound descriptor as the onclause and if it matches the 1497 # current joinpoint at the mapper level, it's used. This 1498 # is a very old use case that is intended to make it easier 1499 # to work with the aliased=True flag, which is also something 1500 # that probably shouldn't exist on join() due to its high 1501 # complexity/usefulness ratio 1502 elif from_joinpoint and isinstance( 1503 onclause, interfaces.PropComparator 1504 ): 1505 jp0 = self._joinpoint_zero() 1506 info = inspect(jp0) 1507 1508 if getattr(info, "mapper", None) is onclause._parententity: 1509 onclause = _entity_namespace_key(info, onclause.key) 1510 # legacy ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1511 1512 if isinstance(onclause, interfaces.PropComparator): 1513 # descriptor/property given (or determined); this tells us 1514 # explicitly what the expected "left" side of the join is. 1515 if right is None: 1516 if of_type: 1517 right = of_type 1518 else: 1519 right = onclause.property 1520 1521 try: 1522 right = right.entity 1523 except AttributeError as err: 1524 util.raise_( 1525 sa_exc.ArgumentError( 1526 "Join target %s does not refer to a " 1527 "mapped entity" % right 1528 ), 1529 replace_context=err, 1530 ) 1531 1532 left = onclause._parententity 1533 1534 alias = self._polymorphic_adapters.get(left, None) 1535 1536 # could be None or could be ColumnAdapter also 1537 if isinstance(alias, ORMAdapter) and alias.mapper.isa(left): 1538 left = alias.aliased_class 1539 onclause = getattr(left, onclause.key) 1540 1541 prop = onclause.property 1542 if not isinstance(onclause, attributes.QueryableAttribute): 1543 onclause = prop 1544 1545 if not create_aliases: 1546 # check for this path already present. 1547 # don't render in that case. 1548 edge = (left, right, prop.key) 1549 if edge in self._joinpoint: 1550 # The child's prev reference might be stale -- 1551 # it could point to a parent older than the 1552 # current joinpoint. If this is the case, 1553 # then we need to update it and then fix the 1554 # tree's spine with _update_joinpoint. Copy 1555 # and then mutate the child, which might be 1556 # shared by a different query object. 1557 jp = self._joinpoint[edge].copy() 1558 jp["prev"] = (edge, self._joinpoint) 1559 self._update_joinpoint(jp) 1560 1561 continue 1562 1563 else: 1564 # no descriptor/property given; we will need to figure out 1565 # what the effective "left" side is 1566 prop = left = None 1567 1568 # figure out the final "left" and "right" sides and create an 1569 # ORMJoin to add to our _from_obj tuple 1570 self._join_left_to_right( 1571 entities_collection, 1572 left, 1573 right, 1574 onclause, 1575 prop, 1576 create_aliases, 1577 aliased_generation, 1578 outerjoin, 1579 full, 1580 ) 1581 1582 def _joinpoint_zero(self): 1583 return self._joinpoint.get("_joinpoint_entity", self._entity_zero()) 1584 1585 def _join_left_to_right( 1586 self, 1587 entities_collection, 1588 left, 1589 right, 1590 onclause, 1591 prop, 1592 create_aliases, 1593 aliased_generation, 1594 outerjoin, 1595 full, 1596 ): 1597 """given raw "left", "right", "onclause" parameters consumed from 1598 a particular key within _join(), add a real ORMJoin object to 1599 our _from_obj list (or augment an existing one) 1600 1601 """ 1602 1603 if left is None: 1604 # left not given (e.g. no relationship object/name specified) 1605 # figure out the best "left" side based on our existing froms / 1606 # entities 1607 assert prop is None 1608 ( 1609 left, 1610 replace_from_obj_index, 1611 use_entity_index, 1612 ) = self._join_determine_implicit_left_side( 1613 entities_collection, left, right, onclause 1614 ) 1615 else: 1616 # left is given via a relationship/name, or as explicit left side. 1617 # Determine where in our 1618 # "froms" list it should be spliced/appended as well as what 1619 # existing entity it corresponds to. 1620 ( 1621 replace_from_obj_index, 1622 use_entity_index, 1623 ) = self._join_place_explicit_left_side(entities_collection, left) 1624 1625 if left is right and not create_aliases: 1626 raise sa_exc.InvalidRequestError( 1627 "Can't construct a join from %s to %s, they " 1628 "are the same entity" % (left, right) 1629 ) 1630 1631 # the right side as given often needs to be adapted. additionally 1632 # a lot of things can be wrong with it. handle all that and 1633 # get back the new effective "right" side 1634 r_info, right, onclause = self._join_check_and_adapt_right_side( 1635 left, right, onclause, prop, create_aliases, aliased_generation 1636 ) 1637 1638 if not r_info.is_selectable: 1639 extra_criteria = self._get_extra_criteria(r_info) 1640 else: 1641 extra_criteria = () 1642 1643 if replace_from_obj_index is not None: 1644 # splice into an existing element in the 1645 # self._from_obj list 1646 left_clause = self.from_clauses[replace_from_obj_index] 1647 1648 self.from_clauses = ( 1649 self.from_clauses[:replace_from_obj_index] 1650 + [ 1651 _ORMJoin( 1652 left_clause, 1653 right, 1654 onclause, 1655 isouter=outerjoin, 1656 full=full, 1657 _extra_criteria=extra_criteria, 1658 ) 1659 ] 1660 + self.from_clauses[replace_from_obj_index + 1 :] 1661 ) 1662 else: 1663 # add a new element to the self._from_obj list 1664 if use_entity_index is not None: 1665 # make use of _MapperEntity selectable, which is usually 1666 # entity_zero.selectable, but if with_polymorphic() were used 1667 # might be distinct 1668 assert isinstance( 1669 entities_collection[use_entity_index], _MapperEntity 1670 ) 1671 left_clause = entities_collection[use_entity_index].selectable 1672 else: 1673 left_clause = left 1674 1675 self.from_clauses = self.from_clauses + [ 1676 _ORMJoin( 1677 left_clause, 1678 r_info, 1679 onclause, 1680 isouter=outerjoin, 1681 full=full, 1682 _extra_criteria=extra_criteria, 1683 ) 1684 ] 1685 1686 def _join_determine_implicit_left_side( 1687 self, entities_collection, left, right, onclause 1688 ): 1689 """When join conditions don't express the left side explicitly, 1690 determine if an existing FROM or entity in this query 1691 can serve as the left hand side. 1692 1693 """ 1694 1695 # when we are here, it means join() was called without an ORM- 1696 # specific way of telling us what the "left" side is, e.g.: 1697 # 1698 # join(RightEntity) 1699 # 1700 # or 1701 # 1702 # join(RightEntity, RightEntity.foo == LeftEntity.bar) 1703 # 1704 1705 r_info = inspect(right) 1706 1707 replace_from_obj_index = use_entity_index = None 1708 1709 if self.from_clauses: 1710 # we have a list of FROMs already. So by definition this 1711 # join has to connect to one of those FROMs. 1712 1713 indexes = sql_util.find_left_clause_to_join_from( 1714 self.from_clauses, r_info.selectable, onclause 1715 ) 1716 1717 if len(indexes) == 1: 1718 replace_from_obj_index = indexes[0] 1719 left = self.from_clauses[replace_from_obj_index] 1720 elif len(indexes) > 1: 1721 raise sa_exc.InvalidRequestError( 1722 "Can't determine which FROM clause to join " 1723 "from, there are multiple FROMS which can " 1724 "join to this entity. Please use the .select_from() " 1725 "method to establish an explicit left side, as well as " 1726 "providing an explicit ON clause if not present already " 1727 "to help resolve the ambiguity." 1728 ) 1729 else: 1730 raise sa_exc.InvalidRequestError( 1731 "Don't know how to join to %r. " 1732 "Please use the .select_from() " 1733 "method to establish an explicit left side, as well as " 1734 "providing an explicit ON clause if not present already " 1735 "to help resolve the ambiguity." % (right,) 1736 ) 1737 1738 elif entities_collection: 1739 # we have no explicit FROMs, so the implicit left has to 1740 # come from our list of entities. 1741 1742 potential = {} 1743 for entity_index, ent in enumerate(entities_collection): 1744 entity = ent.entity_zero_or_selectable 1745 if entity is None: 1746 continue 1747 ent_info = inspect(entity) 1748 if ent_info is r_info: # left and right are the same, skip 1749 continue 1750 1751 # by using a dictionary with the selectables as keys this 1752 # de-duplicates those selectables as occurs when the query is 1753 # against a series of columns from the same selectable 1754 if isinstance(ent, _MapperEntity): 1755 potential[ent.selectable] = (entity_index, entity) 1756 else: 1757 potential[ent_info.selectable] = (None, entity) 1758 1759 all_clauses = list(potential.keys()) 1760 indexes = sql_util.find_left_clause_to_join_from( 1761 all_clauses, r_info.selectable, onclause 1762 ) 1763 1764 if len(indexes) == 1: 1765 use_entity_index, left = potential[all_clauses[indexes[0]]] 1766 elif len(indexes) > 1: 1767 raise sa_exc.InvalidRequestError( 1768 "Can't determine which FROM clause to join " 1769 "from, there are multiple FROMS which can " 1770 "join to this entity. Please use the .select_from() " 1771 "method to establish an explicit left side, as well as " 1772 "providing an explicit ON clause if not present already " 1773 "to help resolve the ambiguity." 1774 ) 1775 else: 1776 raise sa_exc.InvalidRequestError( 1777 "Don't know how to join to %r. " 1778 "Please use the .select_from() " 1779 "method to establish an explicit left side, as well as " 1780 "providing an explicit ON clause if not present already " 1781 "to help resolve the ambiguity." % (right,) 1782 ) 1783 else: 1784 raise sa_exc.InvalidRequestError( 1785 "No entities to join from; please use " 1786 "select_from() to establish the left " 1787 "entity/selectable of this join" 1788 ) 1789 1790 return left, replace_from_obj_index, use_entity_index 1791 1792 def _join_place_explicit_left_side(self, entities_collection, left): 1793 """When join conditions express a left side explicitly, determine 1794 where in our existing list of FROM clauses we should join towards, 1795 or if we need to make a new join, and if so is it from one of our 1796 existing entities. 1797 1798 """ 1799 1800 # when we are here, it means join() was called with an indicator 1801 # as to an exact left side, which means a path to a 1802 # RelationshipProperty was given, e.g.: 1803 # 1804 # join(RightEntity, LeftEntity.right) 1805 # 1806 # or 1807 # 1808 # join(LeftEntity.right) 1809 # 1810 # as well as string forms: 1811 # 1812 # join(RightEntity, "right") 1813 # 1814 # etc. 1815 # 1816 1817 replace_from_obj_index = use_entity_index = None 1818 1819 l_info = inspect(left) 1820 if self.from_clauses: 1821 indexes = sql_util.find_left_clause_that_matches_given( 1822 self.from_clauses, l_info.selectable 1823 ) 1824 1825 if len(indexes) > 1: 1826 raise sa_exc.InvalidRequestError( 1827 "Can't identify which entity in which to assign the " 1828 "left side of this join. Please use a more specific " 1829 "ON clause." 1830 ) 1831 1832 # have an index, means the left side is already present in 1833 # an existing FROM in the self._from_obj tuple 1834 if indexes: 1835 replace_from_obj_index = indexes[0] 1836 1837 # no index, means we need to add a new element to the 1838 # self._from_obj tuple 1839 1840 # no from element present, so we will have to add to the 1841 # self._from_obj tuple. Determine if this left side matches up 1842 # with existing mapper entities, in which case we want to apply the 1843 # aliasing / adaptation rules present on that entity if any 1844 if ( 1845 replace_from_obj_index is None 1846 and entities_collection 1847 and hasattr(l_info, "mapper") 1848 ): 1849 for idx, ent in enumerate(entities_collection): 1850 # TODO: should we be checking for multiple mapper entities 1851 # matching? 1852 if isinstance(ent, _MapperEntity) and ent.corresponds_to(left): 1853 use_entity_index = idx 1854 break 1855 1856 return replace_from_obj_index, use_entity_index 1857 1858 def _join_check_and_adapt_right_side( 1859 self, left, right, onclause, prop, create_aliases, aliased_generation 1860 ): 1861 """transform the "right" side of the join as well as the onclause 1862 according to polymorphic mapping translations, aliasing on the query 1863 or on the join, special cases where the right and left side have 1864 overlapping tables. 1865 1866 """ 1867 1868 l_info = inspect(left) 1869 r_info = inspect(right) 1870 1871 overlap = False 1872 if not create_aliases: 1873 right_mapper = getattr(r_info, "mapper", None) 1874 # if the target is a joined inheritance mapping, 1875 # be more liberal about auto-aliasing. 1876 if right_mapper and ( 1877 right_mapper.with_polymorphic 1878 or isinstance(right_mapper.persist_selectable, expression.Join) 1879 ): 1880 for from_obj in self.from_clauses or [l_info.selectable]: 1881 if sql_util.selectables_overlap( 1882 l_info.selectable, from_obj 1883 ) and sql_util.selectables_overlap( 1884 from_obj, r_info.selectable 1885 ): 1886 overlap = True 1887 break 1888 1889 if ( 1890 overlap or not create_aliases 1891 ) and l_info.selectable is r_info.selectable: 1892 raise sa_exc.InvalidRequestError( 1893 "Can't join table/selectable '%s' to itself" 1894 % l_info.selectable 1895 ) 1896 1897 right_mapper, right_selectable, right_is_aliased = ( 1898 getattr(r_info, "mapper", None), 1899 r_info.selectable, 1900 getattr(r_info, "is_aliased_class", False), 1901 ) 1902 1903 if ( 1904 right_mapper 1905 and prop 1906 and not right_mapper.common_parent(prop.mapper) 1907 ): 1908 raise sa_exc.InvalidRequestError( 1909 "Join target %s does not correspond to " 1910 "the right side of join condition %s" % (right, onclause) 1911 ) 1912 1913 # _join_entities is used as a hint for single-table inheritance 1914 # purposes at the moment 1915 if hasattr(r_info, "mapper"): 1916 self._join_entities += (r_info,) 1917 1918 need_adapter = False 1919 1920 # test for joining to an unmapped selectable as the target 1921 if r_info.is_clause_element: 1922 1923 if prop: 1924 right_mapper = prop.mapper 1925 1926 if right_selectable._is_lateral: 1927 # orm_only is disabled to suit the case where we have to 1928 # adapt an explicit correlate(Entity) - the select() loses 1929 # the ORM-ness in this case right now, ideally it would not 1930 current_adapter = self._get_current_adapter() 1931 if current_adapter is not None: 1932 # TODO: we had orm_only=False here before, removing 1933 # it didn't break things. if we identify the rationale, 1934 # may need to apply "_orm_only" annotation here. 1935 right = current_adapter(right, True) 1936 1937 elif prop: 1938 # joining to selectable with a mapper property given 1939 # as the ON clause 1940 1941 if not right_selectable.is_derived_from( 1942 right_mapper.persist_selectable 1943 ): 1944 raise sa_exc.InvalidRequestError( 1945 "Selectable '%s' is not derived from '%s'" 1946 % ( 1947 right_selectable.description, 1948 right_mapper.persist_selectable.description, 1949 ) 1950 ) 1951 1952 # if the destination selectable is a plain select(), 1953 # turn it into an alias(). 1954 if isinstance(right_selectable, expression.SelectBase): 1955 right_selectable = coercions.expect( 1956 roles.FromClauseRole, right_selectable 1957 ) 1958 need_adapter = True 1959 1960 # make the right hand side target into an ORM entity 1961 right = aliased(right_mapper, right_selectable) 1962 1963 util.warn_deprecated( 1964 "An alias is being generated automatically against " 1965 "joined entity %s for raw clauseelement, which is " 1966 "deprecated and will be removed in a later release. " 1967 "Use the aliased() " 1968 "construct explicitly, see the linked example." 1969 % right_mapper, 1970 "1.4", 1971 code="xaj1", 1972 ) 1973 1974 elif create_aliases: 1975 # it *could* work, but it doesn't right now and I'd rather 1976 # get rid of aliased=True completely 1977 raise sa_exc.InvalidRequestError( 1978 "The aliased=True parameter on query.join() only works " 1979 "with an ORM entity, not a plain selectable, as the " 1980 "target." 1981 ) 1982 1983 # test for overlap: 1984 # orm/inheritance/relationships.py 1985 # SelfReferentialM2MTest 1986 aliased_entity = right_mapper and not right_is_aliased and overlap 1987 1988 if not need_adapter and (create_aliases or aliased_entity): 1989 # there are a few places in the ORM that automatic aliasing 1990 # is still desirable, and can't be automatic with a Core 1991 # only approach. For illustrations of "overlaps" see 1992 # test/orm/inheritance/test_relationships.py. There are also 1993 # general overlap cases with many-to-many tables where automatic 1994 # aliasing is desirable. 1995 right = aliased(right, flat=True) 1996 need_adapter = True 1997 1998 if not create_aliases: 1999 util.warn( 2000 "An alias is being generated automatically against " 2001 "joined entity %s due to overlapping tables. This is a " 2002 "legacy pattern which may be " 2003 "deprecated in a later release. Use the " 2004 "aliased(<entity>, flat=True) " 2005 "construct explicitly, see the linked example." 2006 % right_mapper, 2007 code="xaj2", 2008 ) 2009 2010 if need_adapter: 2011 assert right_mapper 2012 2013 adapter = ORMAdapter( 2014 right, equivalents=right_mapper._equivalent_columns 2015 ) 2016 2017 # if an alias() on the right side was generated, 2018 # which is intended to wrap a the right side in a subquery, 2019 # ensure that columns retrieved from this target in the result 2020 # set are also adapted. 2021 if not create_aliases: 2022 self._mapper_loads_polymorphically_with(right_mapper, adapter) 2023 elif aliased_generation: 2024 adapter._debug = True 2025 self._aliased_generations[aliased_generation] = ( 2026 adapter, 2027 ) + self._aliased_generations.get(aliased_generation, ()) 2028 elif ( 2029 not r_info.is_clause_element 2030 and not right_is_aliased 2031 and right_mapper.with_polymorphic 2032 and isinstance( 2033 right_mapper._with_polymorphic_selectable, 2034 expression.AliasedReturnsRows, 2035 ) 2036 ): 2037 # for the case where the target mapper has a with_polymorphic 2038 # set up, ensure an adapter is set up for criteria that works 2039 # against this mapper. Previously, this logic used to 2040 # use the "create_aliases or aliased_entity" case to generate 2041 # an aliased() object, but this creates an alias that isn't 2042 # strictly necessary. 2043 # see test/orm/test_core_compilation.py 2044 # ::RelNaturalAliasedJoinsTest::test_straight 2045 # and similar 2046 self._mapper_loads_polymorphically_with( 2047 right_mapper, 2048 sql_util.ColumnAdapter( 2049 right_mapper.selectable, 2050 right_mapper._equivalent_columns, 2051 ), 2052 ) 2053 # if the onclause is a ClauseElement, adapt it with any 2054 # adapters that are in place right now 2055 if isinstance(onclause, expression.ClauseElement): 2056 current_adapter = self._get_current_adapter() 2057 if current_adapter: 2058 onclause = current_adapter(onclause, True) 2059 2060 # if joining on a MapperProperty path, 2061 # track the path to prevent redundant joins 2062 if not create_aliases and prop: 2063 self._update_joinpoint( 2064 { 2065 "_joinpoint_entity": right, 2066 "prev": ((left, right, prop.key), self._joinpoint), 2067 "aliased_generation": aliased_generation, 2068 } 2069 ) 2070 else: 2071 self._joinpoint = { 2072 "_joinpoint_entity": right, 2073 "aliased_generation": aliased_generation, 2074 } 2075 2076 return inspect(right), right, onclause 2077 2078 def _update_joinpoint(self, jp): 2079 self._joinpoint = jp 2080 # copy backwards to the root of the _joinpath 2081 # dict, so that no existing dict in the path is mutated 2082 while "prev" in jp: 2083 f, prev = jp["prev"] 2084 prev = dict(prev) 2085 prev[f] = jp.copy() 2086 jp["prev"] = (f, prev) 2087 jp = prev 2088 self._joinpath = jp 2089 2090 def _reset_joinpoint(self): 2091 self._joinpoint = self._joinpath 2092 2093 @property 2094 def _select_args(self): 2095 return { 2096 "limit_clause": self.select_statement._limit_clause, 2097 "offset_clause": self.select_statement._offset_clause, 2098 "distinct": self.distinct, 2099 "distinct_on": self.distinct_on, 2100 "prefixes": self.select_statement._prefixes, 2101 "suffixes": self.select_statement._suffixes, 2102 "group_by": self.group_by or None, 2103 } 2104 2105 @property 2106 def _should_nest_selectable(self): 2107 kwargs = self._select_args 2108 return ( 2109 kwargs.get("limit_clause") is not None 2110 or kwargs.get("offset_clause") is not None 2111 or kwargs.get("distinct", False) 2112 or kwargs.get("distinct_on", ()) 2113 or kwargs.get("group_by", False) 2114 ) 2115 2116 def _get_extra_criteria(self, ext_info): 2117 if ( 2118 "additional_entity_criteria", 2119 ext_info.mapper, 2120 ) in self.global_attributes: 2121 return tuple( 2122 ae._resolve_where_criteria(ext_info) 2123 for ae in self.global_attributes[ 2124 ("additional_entity_criteria", ext_info.mapper) 2125 ] 2126 if ae.include_aliases or ae.entity is ext_info 2127 ) 2128 else: 2129 return () 2130 2131 def _adjust_for_extra_criteria(self): 2132 """Apply extra criteria filtering. 2133 2134 For all distinct single-table-inheritance mappers represented in 2135 the columns clause of this query, as well as the "select from entity", 2136 add criterion to the WHERE 2137 clause of the given QueryContext such that only the appropriate 2138 subtypes are selected from the total results. 2139 2140 Additionally, add WHERE criteria originating from LoaderCriteriaOptions 2141 associated with the global context. 2142 2143 """ 2144 2145 for fromclause in self.from_clauses: 2146 ext_info = fromclause._annotations.get("parententity", None) 2147 if ( 2148 ext_info 2149 and ( 2150 ext_info.mapper._single_table_criterion is not None 2151 or ("additional_entity_criteria", ext_info.mapper) 2152 in self.global_attributes 2153 ) 2154 and ext_info not in self.extra_criteria_entities 2155 ): 2156 2157 self.extra_criteria_entities[ext_info] = ( 2158 ext_info, 2159 ext_info._adapter if ext_info.is_aliased_class else None, 2160 ) 2161 2162 search = set(self.extra_criteria_entities.values()) 2163 2164 for (ext_info, adapter) in search: 2165 if ext_info in self._join_entities: 2166 continue 2167 2168 single_crit = ext_info.mapper._single_table_criterion 2169 2170 additional_entity_criteria = self._get_extra_criteria(ext_info) 2171 2172 if single_crit is not None: 2173 additional_entity_criteria += (single_crit,) 2174 2175 current_adapter = self._get_current_adapter() 2176 for crit in additional_entity_criteria: 2177 if adapter: 2178 crit = adapter.traverse(crit) 2179 2180 if current_adapter: 2181 crit = sql_util._deep_annotate(crit, {"_orm_adapt": True}) 2182 crit = current_adapter(crit, False) 2183 self._where_criteria += (crit,) 2184 2185 2186def _column_descriptions( 2187 query_or_select_stmt, compile_state=None, legacy=False 2188): 2189 if compile_state is None: 2190 compile_state = ORMSelectCompileState._create_entities_collection( 2191 query_or_select_stmt, legacy=legacy 2192 ) 2193 ctx = compile_state 2194 return [ 2195 { 2196 "name": ent._label_name, 2197 "type": ent.type, 2198 "aliased": getattr(insp_ent, "is_aliased_class", False), 2199 "expr": ent.expr, 2200 "entity": getattr(insp_ent, "entity", None) 2201 if ent.entity_zero is not None and not insp_ent.is_clause_element 2202 else None, 2203 } 2204 for ent, insp_ent in [ 2205 ( 2206 _ent, 2207 ( 2208 inspect(_ent.entity_zero) 2209 if _ent.entity_zero is not None 2210 else None 2211 ), 2212 ) 2213 for _ent in ctx._entities 2214 ] 2215 ] 2216 2217 2218def _legacy_filter_by_entity_zero(query_or_augmented_select): 2219 self = query_or_augmented_select 2220 if self._legacy_setup_joins: 2221 _last_joined_entity = self._last_joined_entity 2222 if _last_joined_entity is not None: 2223 return _last_joined_entity 2224 2225 if self._from_obj and "parententity" in self._from_obj[0]._annotations: 2226 return self._from_obj[0]._annotations["parententity"] 2227 2228 return _entity_from_pre_ent_zero(self) 2229 2230 2231def _entity_from_pre_ent_zero(query_or_augmented_select): 2232 self = query_or_augmented_select 2233 if not self._raw_columns: 2234 return None 2235 2236 ent = self._raw_columns[0] 2237 2238 if "parententity" in ent._annotations: 2239 return ent._annotations["parententity"] 2240 elif isinstance(ent, ORMColumnsClauseRole): 2241 return ent.entity 2242 elif "bundle" in ent._annotations: 2243 return ent._annotations["bundle"] 2244 else: 2245 return ent 2246 2247 2248def _legacy_determine_last_joined_entity(setup_joins, entity_zero): 2249 """given the legacy_setup_joins collection at a point in time, 2250 figure out what the "filter by entity" would be in terms 2251 of those joins. 2252 2253 in 2.0 this logic should hopefully be much simpler as there will 2254 be far fewer ways to specify joins with the ORM 2255 2256 """ 2257 2258 if not setup_joins: 2259 return entity_zero 2260 2261 # CAN BE REMOVED IN 2.0: 2262 # 1. from_joinpoint 2263 # 2. aliased_generation 2264 # 3. aliased 2265 # 4. any treating of prop as str 2266 # 5. tuple madness 2267 # 6. won't need recursive call anymore without #4 2268 # 7. therefore can pass in just the last setup_joins record, 2269 # don't need entity_zero 2270 2271 (right, onclause, left_, flags) = setup_joins[-1] 2272 2273 from_joinpoint = flags["from_joinpoint"] 2274 2275 if onclause is None and isinstance( 2276 right, (str, interfaces.PropComparator) 2277 ): 2278 onclause = right 2279 right = None 2280 2281 if right is not None and "parententity" in right._annotations: 2282 right = right._annotations["parententity"].entity 2283 2284 if right is not None: 2285 last_entity = right 2286 insp = inspect(last_entity) 2287 if insp.is_clause_element or insp.is_aliased_class or insp.is_mapper: 2288 return insp 2289 2290 last_entity = onclause 2291 if isinstance(last_entity, interfaces.PropComparator): 2292 return last_entity.entity 2293 2294 # legacy vvvvvvvvvvvvvvvvvvvvvvvvvvv 2295 if isinstance(onclause, str): 2296 if from_joinpoint: 2297 prev = _legacy_determine_last_joined_entity( 2298 setup_joins[0:-1], entity_zero 2299 ) 2300 else: 2301 prev = entity_zero 2302 2303 if prev is None: 2304 return None 2305 2306 prev = inspect(prev) 2307 attr = getattr(prev.entity, onclause, None) 2308 if attr is not None: 2309 return attr.property.entity 2310 # legacy ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2311 2312 return None 2313 2314 2315class _QueryEntity(object): 2316 """represent an entity column returned within a Query result.""" 2317 2318 __slots__ = () 2319 2320 _non_hashable_value = False 2321 _null_column_type = False 2322 use_id_for_hash = False 2323 2324 @classmethod 2325 def to_compile_state( 2326 cls, compile_state, entities, entities_collection, is_current_entities 2327 ): 2328 2329 for idx, entity in enumerate(entities): 2330 if entity._is_lambda_element: 2331 if entity._is_sequence: 2332 cls.to_compile_state( 2333 compile_state, 2334 entity._resolved, 2335 entities_collection, 2336 is_current_entities, 2337 ) 2338 continue 2339 else: 2340 entity = entity._resolved 2341 2342 if entity.is_clause_element: 2343 if entity.is_selectable: 2344 if "parententity" in entity._annotations: 2345 _MapperEntity( 2346 compile_state, 2347 entity, 2348 entities_collection, 2349 is_current_entities, 2350 ) 2351 else: 2352 _ColumnEntity._for_columns( 2353 compile_state, 2354 entity._select_iterable, 2355 entities_collection, 2356 idx, 2357 ) 2358 else: 2359 if entity._annotations.get("bundle", False): 2360 _BundleEntity( 2361 compile_state, entity, entities_collection 2362 ) 2363 elif entity._is_clause_list: 2364 # this is legacy only - test_composites.py 2365 # test_query_cols_legacy 2366 _ColumnEntity._for_columns( 2367 compile_state, 2368 entity._select_iterable, 2369 entities_collection, 2370 idx, 2371 ) 2372 else: 2373 _ColumnEntity._for_columns( 2374 compile_state, [entity], entities_collection, idx 2375 ) 2376 elif entity.is_bundle: 2377 _BundleEntity(compile_state, entity, entities_collection) 2378 2379 return entities_collection 2380 2381 2382class _MapperEntity(_QueryEntity): 2383 """mapper/class/AliasedClass entity""" 2384 2385 __slots__ = ( 2386 "expr", 2387 "mapper", 2388 "entity_zero", 2389 "is_aliased_class", 2390 "path", 2391 "_extra_entities", 2392 "_label_name", 2393 "_with_polymorphic_mappers", 2394 "selectable", 2395 "_polymorphic_discriminator", 2396 ) 2397 2398 def __init__( 2399 self, compile_state, entity, entities_collection, is_current_entities 2400 ): 2401 entities_collection.append(self) 2402 if is_current_entities: 2403 if compile_state._primary_entity is None: 2404 compile_state._primary_entity = self 2405 compile_state._has_mapper_entities = True 2406 compile_state._has_orm_entities = True 2407 2408 entity = entity._annotations["parententity"] 2409 entity._post_inspect 2410 ext_info = self.entity_zero = entity 2411 entity = ext_info.entity 2412 2413 self.expr = entity 2414 self.mapper = mapper = ext_info.mapper 2415 2416 self._extra_entities = (self.expr,) 2417 2418 if ext_info.is_aliased_class: 2419 self._label_name = ext_info.name 2420 else: 2421 self._label_name = mapper.class_.__name__ 2422 2423 self.is_aliased_class = ext_info.is_aliased_class 2424 self.path = ext_info._path_registry 2425 2426 if ext_info in compile_state._with_polymorphic_adapt_map: 2427 # this codepath occurs only if query.with_polymorphic() were 2428 # used 2429 2430 wp = inspect(compile_state._with_polymorphic_adapt_map[ext_info]) 2431 2432 if self.is_aliased_class: 2433 # TODO: invalidrequest ? 2434 raise NotImplementedError( 2435 "Can't use with_polymorphic() against an Aliased object" 2436 ) 2437 2438 mappers, from_obj = mapper._with_polymorphic_args( 2439 wp.with_polymorphic_mappers, wp.selectable 2440 ) 2441 2442 self._with_polymorphic_mappers = mappers 2443 self.selectable = from_obj 2444 self._polymorphic_discriminator = wp.polymorphic_on 2445 2446 else: 2447 self.selectable = ext_info.selectable 2448 self._with_polymorphic_mappers = ext_info.with_polymorphic_mappers 2449 self._polymorphic_discriminator = ext_info.polymorphic_on 2450 2451 if ( 2452 mapper.with_polymorphic 2453 # controversy - only if inheriting mapper is also 2454 # polymorphic? 2455 # or (mapper.inherits and mapper.inherits.with_polymorphic) 2456 or mapper.inherits 2457 or mapper._requires_row_aliasing 2458 ): 2459 compile_state._create_with_polymorphic_adapter( 2460 ext_info, self.selectable 2461 ) 2462 2463 supports_single_entity = True 2464 2465 _non_hashable_value = True 2466 use_id_for_hash = True 2467 2468 @property 2469 def type(self): 2470 return self.mapper.class_ 2471 2472 @property 2473 def entity_zero_or_selectable(self): 2474 return self.entity_zero 2475 2476 def corresponds_to(self, entity): 2477 return _entity_corresponds_to(self.entity_zero, entity) 2478 2479 def _get_entity_clauses(self, compile_state): 2480 2481 adapter = None 2482 2483 if not self.is_aliased_class: 2484 if compile_state._polymorphic_adapters: 2485 adapter = compile_state._polymorphic_adapters.get( 2486 self.mapper, None 2487 ) 2488 else: 2489 adapter = self.entity_zero._adapter 2490 2491 if adapter: 2492 if compile_state._from_obj_alias: 2493 ret = adapter.wrap(compile_state._from_obj_alias) 2494 else: 2495 ret = adapter 2496 else: 2497 ret = compile_state._from_obj_alias 2498 2499 return ret 2500 2501 def row_processor(self, context, result): 2502 compile_state = context.compile_state 2503 adapter = self._get_entity_clauses(compile_state) 2504 2505 if compile_state.compound_eager_adapter and adapter: 2506 adapter = adapter.wrap(compile_state.compound_eager_adapter) 2507 elif not adapter: 2508 adapter = compile_state.compound_eager_adapter 2509 2510 if compile_state._primary_entity is self: 2511 only_load_props = compile_state.compile_options._only_load_props 2512 refresh_state = context.refresh_state 2513 else: 2514 only_load_props = refresh_state = None 2515 2516 _instance = loading._instance_processor( 2517 self, 2518 self.mapper, 2519 context, 2520 result, 2521 self.path, 2522 adapter, 2523 only_load_props=only_load_props, 2524 refresh_state=refresh_state, 2525 polymorphic_discriminator=self._polymorphic_discriminator, 2526 ) 2527 2528 return _instance, self._label_name, self._extra_entities 2529 2530 def setup_compile_state(self, compile_state): 2531 2532 adapter = self._get_entity_clauses(compile_state) 2533 2534 single_table_crit = self.mapper._single_table_criterion 2535 if ( 2536 single_table_crit is not None 2537 or ("additional_entity_criteria", self.mapper) 2538 in compile_state.global_attributes 2539 ): 2540 ext_info = self.entity_zero 2541 compile_state.extra_criteria_entities[ext_info] = ( 2542 ext_info, 2543 ext_info._adapter if ext_info.is_aliased_class else None, 2544 ) 2545 2546 loading._setup_entity_query( 2547 compile_state, 2548 self.mapper, 2549 self, 2550 self.path, 2551 adapter, 2552 compile_state.primary_columns, 2553 with_polymorphic=self._with_polymorphic_mappers, 2554 only_load_props=compile_state.compile_options._only_load_props, 2555 polymorphic_discriminator=self._polymorphic_discriminator, 2556 ) 2557 2558 compile_state._fallback_from_clauses.append(self.selectable) 2559 2560 2561class _BundleEntity(_QueryEntity): 2562 2563 _extra_entities = () 2564 2565 __slots__ = ( 2566 "bundle", 2567 "expr", 2568 "type", 2569 "_label_name", 2570 "_entities", 2571 "supports_single_entity", 2572 ) 2573 2574 def __init__( 2575 self, 2576 compile_state, 2577 expr, 2578 entities_collection, 2579 setup_entities=True, 2580 parent_bundle=None, 2581 ): 2582 compile_state._has_orm_entities = True 2583 2584 expr = expr._annotations["bundle"] 2585 if parent_bundle: 2586 parent_bundle._entities.append(self) 2587 else: 2588 entities_collection.append(self) 2589 2590 if isinstance( 2591 expr, (attributes.QueryableAttribute, interfaces.PropComparator) 2592 ): 2593 bundle = expr.__clause_element__() 2594 else: 2595 bundle = expr 2596 2597 self.bundle = self.expr = bundle 2598 self.type = type(bundle) 2599 self._label_name = bundle.name 2600 self._entities = [] 2601 2602 if setup_entities: 2603 for expr in bundle.exprs: 2604 if "bundle" in expr._annotations: 2605 _BundleEntity( 2606 compile_state, 2607 expr, 2608 entities_collection, 2609 parent_bundle=self, 2610 ) 2611 elif isinstance(expr, Bundle): 2612 _BundleEntity( 2613 compile_state, 2614 expr, 2615 entities_collection, 2616 parent_bundle=self, 2617 ) 2618 else: 2619 _ORMColumnEntity._for_columns( 2620 compile_state, 2621 [expr], 2622 entities_collection, 2623 None, 2624 parent_bundle=self, 2625 ) 2626 2627 self.supports_single_entity = self.bundle.single_entity 2628 if ( 2629 self.supports_single_entity 2630 and not compile_state.compile_options._use_legacy_query_style 2631 ): 2632 util.warn_deprecated_20( 2633 "The Bundle.single_entity flag has no effect when " 2634 "using 2.0 style execution." 2635 ) 2636 2637 @property 2638 def mapper(self): 2639 ezero = self.entity_zero 2640 if ezero is not None: 2641 return ezero.mapper 2642 else: 2643 return None 2644 2645 @property 2646 def entity_zero(self): 2647 for ent in self._entities: 2648 ezero = ent.entity_zero 2649 if ezero is not None: 2650 return ezero 2651 else: 2652 return None 2653 2654 def corresponds_to(self, entity): 2655 # TODO: we might be able to implement this but for now 2656 # we are working around it 2657 return False 2658 2659 @property 2660 def entity_zero_or_selectable(self): 2661 for ent in self._entities: 2662 ezero = ent.entity_zero_or_selectable 2663 if ezero is not None: 2664 return ezero 2665 else: 2666 return None 2667 2668 def setup_compile_state(self, compile_state): 2669 for ent in self._entities: 2670 ent.setup_compile_state(compile_state) 2671 2672 def row_processor(self, context, result): 2673 procs, labels, extra = zip( 2674 *[ent.row_processor(context, result) for ent in self._entities] 2675 ) 2676 2677 proc = self.bundle.create_row_processor(context.query, procs, labels) 2678 2679 return proc, self._label_name, self._extra_entities 2680 2681 2682class _ColumnEntity(_QueryEntity): 2683 __slots__ = ( 2684 "_fetch_column", 2685 "_row_processor", 2686 "raw_column_index", 2687 "translate_raw_column", 2688 ) 2689 2690 @classmethod 2691 def _for_columns( 2692 cls, 2693 compile_state, 2694 columns, 2695 entities_collection, 2696 raw_column_index, 2697 parent_bundle=None, 2698 ): 2699 for column in columns: 2700 annotations = column._annotations 2701 if "parententity" in annotations: 2702 _entity = annotations["parententity"] 2703 else: 2704 _entity = sql_util.extract_first_column_annotation( 2705 column, "parententity" 2706 ) 2707 2708 if _entity: 2709 if "identity_token" in column._annotations: 2710 _IdentityTokenEntity( 2711 compile_state, 2712 column, 2713 entities_collection, 2714 _entity, 2715 raw_column_index, 2716 parent_bundle=parent_bundle, 2717 ) 2718 else: 2719 _ORMColumnEntity( 2720 compile_state, 2721 column, 2722 entities_collection, 2723 _entity, 2724 raw_column_index, 2725 parent_bundle=parent_bundle, 2726 ) 2727 else: 2728 _RawColumnEntity( 2729 compile_state, 2730 column, 2731 entities_collection, 2732 raw_column_index, 2733 parent_bundle=parent_bundle, 2734 ) 2735 2736 @property 2737 def type(self): 2738 return self.column.type 2739 2740 @property 2741 def _non_hashable_value(self): 2742 return not self.column.type.hashable 2743 2744 @property 2745 def _null_column_type(self): 2746 return self.column.type._isnull 2747 2748 def row_processor(self, context, result): 2749 compile_state = context.compile_state 2750 2751 # the resulting callable is entirely cacheable so just return 2752 # it if we already made one 2753 if self._row_processor is not None: 2754 getter, label_name, extra_entities = self._row_processor 2755 if self.translate_raw_column: 2756 extra_entities += ( 2757 result.context.invoked_statement._raw_columns[ 2758 self.raw_column_index 2759 ], 2760 ) 2761 2762 return getter, label_name, extra_entities 2763 2764 # retrieve the column that would have been set up in 2765 # setup_compile_state, to avoid doing redundant work 2766 if self._fetch_column is not None: 2767 column = self._fetch_column 2768 else: 2769 # fetch_column will be None when we are doing a from_statement 2770 # and setup_compile_state may not have been called. 2771 column = self.column 2772 2773 # previously, the RawColumnEntity didn't look for from_obj_alias 2774 # however I can't think of a case where we would be here and 2775 # we'd want to ignore it if this is the from_statement use case. 2776 # it's not really a use case to have raw columns + from_statement 2777 if compile_state._from_obj_alias: 2778 column = compile_state._from_obj_alias.columns[column] 2779 2780 if column._annotations: 2781 # annotated columns perform more slowly in compiler and 2782 # result due to the __eq__() method, so use deannotated 2783 column = column._deannotate() 2784 2785 if compile_state.compound_eager_adapter: 2786 column = compile_state.compound_eager_adapter.columns[column] 2787 2788 getter = result._getter(column) 2789 2790 ret = getter, self._label_name, self._extra_entities 2791 self._row_processor = ret 2792 2793 if self.translate_raw_column: 2794 extra_entities = self._extra_entities + ( 2795 result.context.invoked_statement._raw_columns[ 2796 self.raw_column_index 2797 ], 2798 ) 2799 return getter, self._label_name, extra_entities 2800 else: 2801 return ret 2802 2803 2804class _RawColumnEntity(_ColumnEntity): 2805 entity_zero = None 2806 mapper = None 2807 supports_single_entity = False 2808 2809 __slots__ = ( 2810 "expr", 2811 "column", 2812 "_label_name", 2813 "entity_zero_or_selectable", 2814 "_extra_entities", 2815 ) 2816 2817 def __init__( 2818 self, 2819 compile_state, 2820 column, 2821 entities_collection, 2822 raw_column_index, 2823 parent_bundle=None, 2824 ): 2825 self.expr = column 2826 self.raw_column_index = raw_column_index 2827 self.translate_raw_column = raw_column_index is not None 2828 if column._is_text_clause: 2829 self._label_name = None 2830 else: 2831 self._label_name = compile_state._label_convention(column) 2832 2833 if parent_bundle: 2834 parent_bundle._entities.append(self) 2835 else: 2836 entities_collection.append(self) 2837 2838 self.column = column 2839 self.entity_zero_or_selectable = ( 2840 self.column._from_objects[0] if self.column._from_objects else None 2841 ) 2842 self._extra_entities = (self.expr, self.column) 2843 self._fetch_column = self._row_processor = None 2844 2845 def corresponds_to(self, entity): 2846 return False 2847 2848 def setup_compile_state(self, compile_state): 2849 current_adapter = compile_state._get_current_adapter() 2850 if current_adapter: 2851 column = current_adapter(self.column, False) 2852 else: 2853 column = self.column 2854 2855 if column._annotations: 2856 # annotated columns perform more slowly in compiler and 2857 # result due to the __eq__() method, so use deannotated 2858 column = column._deannotate() 2859 2860 compile_state.dedupe_columns.add(column) 2861 compile_state.primary_columns.append(column) 2862 self._fetch_column = column 2863 2864 2865class _ORMColumnEntity(_ColumnEntity): 2866 """Column/expression based entity.""" 2867 2868 supports_single_entity = False 2869 2870 __slots__ = ( 2871 "expr", 2872 "mapper", 2873 "column", 2874 "_label_name", 2875 "entity_zero_or_selectable", 2876 "entity_zero", 2877 "_extra_entities", 2878 ) 2879 2880 def __init__( 2881 self, 2882 compile_state, 2883 column, 2884 entities_collection, 2885 parententity, 2886 raw_column_index, 2887 parent_bundle=None, 2888 ): 2889 annotations = column._annotations 2890 2891 _entity = parententity 2892 2893 # an AliasedClass won't have proxy_key in the annotations for 2894 # a column if it was acquired using the class' adapter directly, 2895 # such as using AliasedInsp._adapt_element(). this occurs 2896 # within internal loaders. 2897 2898 orm_key = annotations.get("proxy_key", None) 2899 proxy_owner = annotations.get("proxy_owner", _entity) 2900 if orm_key: 2901 self.expr = getattr(proxy_owner.entity, orm_key) 2902 self.translate_raw_column = False 2903 else: 2904 # if orm_key is not present, that means this is an ad-hoc 2905 # SQL ColumnElement, like a CASE() or other expression. 2906 # include this column position from the invoked statement 2907 # in the ORM-level ResultSetMetaData on each execute, so that 2908 # it can be targeted by identity after caching 2909 self.expr = column 2910 self.translate_raw_column = raw_column_index is not None 2911 2912 self.raw_column_index = raw_column_index 2913 self._label_name = compile_state._label_convention( 2914 column, col_name=orm_key 2915 ) 2916 2917 _entity._post_inspect 2918 self.entity_zero = self.entity_zero_or_selectable = ezero = _entity 2919 self.mapper = mapper = _entity.mapper 2920 2921 if parent_bundle: 2922 parent_bundle._entities.append(self) 2923 else: 2924 entities_collection.append(self) 2925 2926 compile_state._has_orm_entities = True 2927 2928 self.column = column 2929 2930 self._fetch_column = self._row_processor = None 2931 2932 self._extra_entities = (self.expr, self.column) 2933 2934 if ( 2935 mapper.with_polymorphic 2936 or mapper.inherits 2937 or mapper._requires_row_aliasing 2938 ): 2939 compile_state._create_with_polymorphic_adapter( 2940 ezero, ezero.selectable 2941 ) 2942 2943 def corresponds_to(self, entity): 2944 if _is_aliased_class(entity): 2945 # TODO: polymorphic subclasses ? 2946 return entity is self.entity_zero 2947 else: 2948 return not _is_aliased_class( 2949 self.entity_zero 2950 ) and entity.common_parent(self.entity_zero) 2951 2952 def setup_compile_state(self, compile_state): 2953 current_adapter = compile_state._get_current_adapter() 2954 if current_adapter: 2955 column = current_adapter(self.column, False) 2956 else: 2957 column = self.column 2958 2959 ezero = self.entity_zero 2960 2961 single_table_crit = self.mapper._single_table_criterion 2962 if ( 2963 single_table_crit is not None 2964 or ("additional_entity_criteria", self.mapper) 2965 in compile_state.global_attributes 2966 ): 2967 2968 compile_state.extra_criteria_entities[ezero] = ( 2969 ezero, 2970 ezero._adapter if ezero.is_aliased_class else None, 2971 ) 2972 2973 if column._annotations and not column._expression_label: 2974 # annotated columns perform more slowly in compiler and 2975 # result due to the __eq__() method, so use deannotated 2976 column = column._deannotate() 2977 2978 # use entity_zero as the from if we have it. this is necessary 2979 # for polymorphic scenarios where our FROM is based on ORM entity, 2980 # not the FROM of the column. but also, don't use it if our column 2981 # doesn't actually have any FROMs that line up, such as when its 2982 # a scalar subquery. 2983 if set(self.column._from_objects).intersection( 2984 ezero.selectable._from_objects 2985 ): 2986 compile_state._fallback_from_clauses.append(ezero.selectable) 2987 2988 compile_state.dedupe_columns.add(column) 2989 compile_state.primary_columns.append(column) 2990 self._fetch_column = column 2991 2992 2993class _IdentityTokenEntity(_ORMColumnEntity): 2994 translate_raw_column = False 2995 2996 def setup_compile_state(self, compile_state): 2997 pass 2998 2999 def row_processor(self, context, result): 3000 def getter(row): 3001 return context.load_options._refresh_identity_token 3002 3003 return getter, self._label_name, self._extra_entities 3004