123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030 |
- # orm/context.py
- # Copyright (C) 2005-2022 the SQLAlchemy authors and contributors
- # <see AUTHORS file>
- #
- # This module is part of SQLAlchemy and is released under
- # the MIT License: https://www.opensource.org/licenses/mit-license.php
- import itertools
- from . import attributes
- from . import interfaces
- from . import loading
- from .base import _is_aliased_class
- from .interfaces import ORMColumnsClauseRole
- from .path_registry import PathRegistry
- from .util import _entity_corresponds_to
- from .util import _ORMJoin
- from .util import aliased
- from .util import Bundle
- from .util import ORMAdapter
- from .. import exc as sa_exc
- from .. import future
- from .. import inspect
- from .. import sql
- from .. import util
- from ..sql import coercions
- from ..sql import expression
- from ..sql import roles
- from ..sql import util as sql_util
- from ..sql import visitors
- from ..sql.base import _entity_namespace_key
- from ..sql.base import _select_iterables
- from ..sql.base import CacheableOptions
- from ..sql.base import CompileState
- from ..sql.base import Options
- from ..sql.selectable import LABEL_STYLE_DISAMBIGUATE_ONLY
- from ..sql.selectable import LABEL_STYLE_NONE
- from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
- from ..sql.selectable import SelectState
- from ..sql.visitors import ExtendedInternalTraversal
- from ..sql.visitors import InternalTraversal
- _path_registry = PathRegistry.root
- _EMPTY_DICT = util.immutabledict()
- LABEL_STYLE_LEGACY_ORM = util.symbol("LABEL_STYLE_LEGACY_ORM")
- class QueryContext(object):
- __slots__ = (
- "compile_state",
- "query",
- "params",
- "load_options",
- "bind_arguments",
- "execution_options",
- "session",
- "autoflush",
- "populate_existing",
- "invoke_all_eagers",
- "version_check",
- "refresh_state",
- "create_eager_joins",
- "propagated_loader_options",
- "attributes",
- "runid",
- "partials",
- "post_load_paths",
- "identity_token",
- "yield_per",
- "loaders_require_buffering",
- "loaders_require_uniquing",
- )
- class default_load_options(Options):
- _only_return_tuples = False
- _populate_existing = False
- _version_check = False
- _invoke_all_eagers = True
- _autoflush = True
- _refresh_identity_token = None
- _yield_per = None
- _refresh_state = None
- _lazy_loaded_from = None
- _legacy_uniquing = False
- def __init__(
- self,
- compile_state,
- statement,
- params,
- session,
- load_options,
- execution_options=None,
- bind_arguments=None,
- ):
- self.load_options = load_options
- self.execution_options = execution_options or _EMPTY_DICT
- self.bind_arguments = bind_arguments or _EMPTY_DICT
- self.compile_state = compile_state
- self.query = statement
- self.session = session
- self.loaders_require_buffering = False
- self.loaders_require_uniquing = False
- self.params = params
- self.propagated_loader_options = {
- # issue 7447.
- # propagated loader options will be present on loaded InstanceState
- # objects under state.load_options and are typically used by
- # LazyLoader to apply options to the SELECT statement it emits.
- # For compile state options (i.e. loader strategy options), these
- # need to line up with the ".load_path" attribute which in
- # loader.py is pulled from context.compile_state.current_path.
- # so, this means these options have to be the ones from the
- # *cached* statement that's travelling with compile_state, not the
- # *current* statement which won't match up for an ad-hoc
- # AliasedClass
- cached_o
- for cached_o in compile_state.select_statement._with_options
- if cached_o.propagate_to_loaders and cached_o._is_compile_state
- } | {
- # for user defined loader options that are not "compile state",
- # those just need to be present as they are
- uncached_o
- for uncached_o in statement._with_options
- if uncached_o.propagate_to_loaders
- and not uncached_o._is_compile_state
- }
- self.attributes = dict(compile_state.attributes)
- self.autoflush = load_options._autoflush
- self.populate_existing = load_options._populate_existing
- self.invoke_all_eagers = load_options._invoke_all_eagers
- self.version_check = load_options._version_check
- self.refresh_state = load_options._refresh_state
- self.yield_per = load_options._yield_per
- self.identity_token = load_options._refresh_identity_token
- if self.yield_per and compile_state._no_yield_pers:
- raise sa_exc.InvalidRequestError(
- "The yield_per Query option is currently not "
- "compatible with %s eager loading. Please "
- "specify lazyload('*') or query.enable_eagerloads(False) in "
- "order to "
- "proceed with query.yield_per()."
- % ", ".join(compile_state._no_yield_pers)
- )
- _orm_load_exec_options = util.immutabledict(
- {"_result_disable_adapt_to_context": True, "future_result": True}
- )
- class ORMCompileState(CompileState):
- # note this is a dictionary, but the
- # default_compile_options._with_polymorphic_adapt_map is a tuple
- _with_polymorphic_adapt_map = _EMPTY_DICT
- class default_compile_options(CacheableOptions):
- _cache_key_traversal = [
- ("_use_legacy_query_style", InternalTraversal.dp_boolean),
- ("_for_statement", InternalTraversal.dp_boolean),
- ("_bake_ok", InternalTraversal.dp_boolean),
- (
- "_with_polymorphic_adapt_map",
- ExtendedInternalTraversal.dp_has_cache_key_tuples,
- ),
- ("_current_path", InternalTraversal.dp_has_cache_key),
- ("_enable_single_crit", InternalTraversal.dp_boolean),
- ("_enable_eagerloads", InternalTraversal.dp_boolean),
- ("_orm_only_from_obj_alias", InternalTraversal.dp_boolean),
- ("_only_load_props", InternalTraversal.dp_plain_obj),
- ("_set_base_alias", InternalTraversal.dp_boolean),
- ("_for_refresh_state", InternalTraversal.dp_boolean),
- ("_render_for_subquery", InternalTraversal.dp_boolean),
- ]
- # set to True by default from Query._statement_20(), to indicate
- # the rendered query should look like a legacy ORM query. right
- # now this basically indicates we should use tablename_columnname
- # style labels. Generally indicates the statement originated
- # from a Query object.
- _use_legacy_query_style = False
- # set *only* when we are coming from the Query.statement
- # accessor, or a Query-level equivalent such as
- # query.subquery(). this supersedes "toplevel".
- _for_statement = False
- _bake_ok = True
- _with_polymorphic_adapt_map = ()
- _current_path = _path_registry
- _enable_single_crit = True
- _enable_eagerloads = True
- _orm_only_from_obj_alias = True
- _only_load_props = None
- _set_base_alias = False
- _for_refresh_state = False
- _render_for_subquery = False
- current_path = _path_registry
- def __init__(self, *arg, **kw):
- raise NotImplementedError()
- def _append_dedupe_col_collection(self, obj, col_collection):
- dedupe = self.dedupe_columns
- if obj not in dedupe:
- dedupe.add(obj)
- col_collection.append(obj)
- @classmethod
- def _column_naming_convention(cls, label_style, legacy):
- if legacy:
- def name(col, col_name=None):
- if col_name:
- return col_name
- else:
- return getattr(col, "key")
- return name
- else:
- return SelectState._column_naming_convention(label_style)
- @classmethod
- def create_for_statement(cls, statement_container, compiler, **kw):
- """Create a context for a statement given a :class:`.Compiler`.
- This method is always invoked in the context of SQLCompiler.process().
- For a Select object, this would be invoked from
- SQLCompiler.visit_select(). For the special FromStatement object used
- by Query to indicate "Query.from_statement()", this is called by
- FromStatement._compiler_dispatch() that would be called by
- SQLCompiler.process().
- """
- raise NotImplementedError()
- @classmethod
- def get_column_descriptions(cls, statement):
- return _column_descriptions(statement)
- @classmethod
- def orm_pre_session_exec(
- cls,
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- is_reentrant_invoke,
- ):
- if is_reentrant_invoke:
- return statement, execution_options
- (
- load_options,
- execution_options,
- ) = QueryContext.default_load_options.from_execution_options(
- "_sa_orm_load_options",
- {"populate_existing", "autoflush", "yield_per"},
- execution_options,
- statement._execution_options,
- )
- # default execution options for ORM results:
- # 1. _result_disable_adapt_to_context=True
- # this will disable the ResultSetMetadata._adapt_to_context()
- # step which we don't need, as we have result processors cached
- # against the original SELECT statement before caching.
- # 2. future_result=True. The ORM should **never** resolve columns
- # in a result set based on names, only on Column objects that
- # are correctly adapted to the context. W the legacy result
- # it will still attempt name-based resolution and also emit a
- # warning.
- if not execution_options:
- execution_options = _orm_load_exec_options
- else:
- execution_options = execution_options.union(_orm_load_exec_options)
- if "yield_per" in execution_options or load_options._yield_per:
- execution_options = execution_options.union(
- {
- "stream_results": True,
- "max_row_buffer": execution_options.get(
- "yield_per", load_options._yield_per
- ),
- }
- )
- bind_arguments["clause"] = statement
- # new in 1.4 - the coercions system is leveraged to allow the
- # "subject" mapper of a statement be propagated to the top
- # as the statement is built. "subject" mapper is the generally
- # standard object used as an identifier for multi-database schemes.
- # we are here based on the fact that _propagate_attrs contains
- # "compile_state_plugin": "orm". The "plugin_subject"
- # needs to be present as well.
- try:
- plugin_subject = statement._propagate_attrs["plugin_subject"]
- except KeyError:
- assert False, "statement had 'orm' plugin but no plugin_subject"
- else:
- if plugin_subject:
- bind_arguments["mapper"] = plugin_subject.mapper
- if load_options._autoflush:
- session._autoflush()
- return statement, execution_options
- @classmethod
- def orm_setup_cursor_result(
- cls,
- session,
- statement,
- params,
- execution_options,
- bind_arguments,
- result,
- ):
- execution_context = result.context
- compile_state = execution_context.compiled.compile_state
- # cover edge case where ORM entities used in legacy select
- # were passed to session.execute:
- # session.execute(legacy_select([User.id, User.name]))
- # see test_query->test_legacy_tuple_old_select
- load_options = execution_options.get(
- "_sa_orm_load_options", QueryContext.default_load_options
- )
- querycontext = QueryContext(
- compile_state,
- statement,
- params,
- session,
- load_options,
- execution_options,
- bind_arguments,
- )
- return loading.instances(result, querycontext)
- @property
- def _lead_mapper_entities(self):
- """return all _MapperEntity objects in the lead entities collection.
- Does **not** include entities that have been replaced by
- with_entities(), with_only_columns()
- """
- return [
- ent for ent in self._entities if isinstance(ent, _MapperEntity)
- ]
- def _create_with_polymorphic_adapter(self, ext_info, selectable):
- if (
- not ext_info.is_aliased_class
- and ext_info.mapper.persist_selectable
- not in self._polymorphic_adapters
- ):
- for mp in ext_info.mapper.iterate_to_root():
- self._mapper_loads_polymorphically_with(
- mp,
- sql_util.ColumnAdapter(selectable, mp._equivalent_columns),
- )
- def _mapper_loads_polymorphically_with(self, mapper, adapter):
- for m2 in mapper._with_polymorphic_mappers or [mapper]:
- self._polymorphic_adapters[m2] = adapter
- for m in m2.iterate_to_root(): # TODO: redundant ?
- self._polymorphic_adapters[m.local_table] = adapter
- @classmethod
- def _create_entities_collection(cls, query, legacy):
- raise NotImplementedError(
- "this method only works for ORMSelectCompileState"
- )
- @sql.base.CompileState.plugin_for("orm", "orm_from_statement")
- class ORMFromStatementCompileState(ORMCompileState):
- _aliased_generations = util.immutabledict()
- _from_obj_alias = None
- _has_mapper_entities = False
- _has_orm_entities = False
- multi_row_eager_loaders = False
- compound_eager_adapter = None
- extra_criteria_entities = _EMPTY_DICT
- eager_joins = _EMPTY_DICT
- @classmethod
- def create_for_statement(cls, statement_container, compiler, **kw):
- if compiler is not None:
- toplevel = not compiler.stack
- else:
- toplevel = True
- self = cls.__new__(cls)
- self._primary_entity = None
- self.use_legacy_query_style = (
- statement_container._compile_options._use_legacy_query_style
- )
- self.statement_container = self.select_statement = statement_container
- self.requested_statement = statement = statement_container.element
- if statement.is_dml:
- self.dml_table = statement.table
- self._entities = []
- self._polymorphic_adapters = {}
- self._no_yield_pers = set()
- self.compile_options = statement_container._compile_options
- if (
- self.use_legacy_query_style
- and isinstance(statement, expression.SelectBase)
- and not statement._is_textual
- and not statement.is_dml
- and statement._label_style is LABEL_STYLE_NONE
- ):
- self.statement = statement.set_label_style(
- LABEL_STYLE_TABLENAME_PLUS_COL
- )
- else:
- self.statement = statement
- self._label_convention = self._column_naming_convention(
- statement._label_style
- if not statement._is_textual and not statement.is_dml
- else LABEL_STYLE_NONE,
- self.use_legacy_query_style,
- )
- _QueryEntity.to_compile_state(
- self,
- statement_container._raw_columns,
- self._entities,
- is_current_entities=True,
- )
- self.current_path = statement_container._compile_options._current_path
- if toplevel and statement_container._with_options:
- self.attributes = {"_unbound_load_dedupes": set()}
- self.global_attributes = compiler._global_attributes
- for opt in statement_container._with_options:
- if opt._is_compile_state:
- opt.process_compile_state(self)
- else:
- self.attributes = {}
- self.global_attributes = compiler._global_attributes
- if statement_container._with_context_options:
- for fn, key in statement_container._with_context_options:
- fn(self)
- self.primary_columns = []
- self.secondary_columns = []
- self.dedupe_columns = set()
- self.create_eager_joins = []
- self._fallback_from_clauses = []
- self.order_by = None
- if isinstance(
- self.statement, (expression.TextClause, expression.UpdateBase)
- ):
- self.extra_criteria_entities = {}
- # setup for all entities. Currently, this is not useful
- # for eager loaders, as the eager loaders that work are able
- # to do their work entirely in row_processor.
- for entity in self._entities:
- entity.setup_compile_state(self)
- # we did the setup just to get primary columns.
- self.statement = expression.TextualSelect(
- self.statement, self.primary_columns, positional=False
- )
- else:
- # allow TextualSelect with implicit columns as well
- # as select() with ad-hoc columns, see test_query::TextTest
- self._from_obj_alias = sql.util.ColumnAdapter(
- self.statement, adapt_on_names=True
- )
- # set up for eager loaders, however if we fix subqueryload
- # it should not need to do this here. the model of eager loaders
- # that can work entirely in row_processor might be interesting
- # here though subqueryloader has a lot of upfront work to do
- # see test/orm/test_query.py -> test_related_eagerload_against_text
- # for where this part makes a difference. would rather have
- # subqueryload figure out what it needs more intelligently.
- # for entity in self._entities:
- # entity.setup_compile_state(self)
- return self
- def _adapt_col_list(self, cols, current_adapter):
- return cols
- def _get_current_adapter(self):
- return None
- @sql.base.CompileState.plugin_for("orm", "select")
- class ORMSelectCompileState(ORMCompileState, SelectState):
- _joinpath = _joinpoint = _EMPTY_DICT
- _memoized_entities = _EMPTY_DICT
- _from_obj_alias = None
- _has_mapper_entities = False
- _has_orm_entities = False
- multi_row_eager_loaders = False
- compound_eager_adapter = None
- correlate = None
- correlate_except = None
- _where_criteria = ()
- _having_criteria = ()
- @classmethod
- def create_for_statement(cls, statement, compiler, **kw):
- """compiler hook, we arrive here from compiler.visit_select() only."""
- self = cls.__new__(cls)
- if compiler is not None:
- toplevel = not compiler.stack
- self.global_attributes = compiler._global_attributes
- else:
- toplevel = True
- self.global_attributes = {}
- select_statement = statement
- # if we are a select() that was never a legacy Query, we won't
- # have ORM level compile options.
- statement._compile_options = cls.default_compile_options.safe_merge(
- statement._compile_options
- )
- if select_statement._execution_options:
- # execution options should not impact the compilation of a
- # query, and at the moment subqueryloader is putting some things
- # in here that we explicitly don't want stuck in a cache.
- self.select_statement = select_statement._clone()
- self.select_statement._execution_options = util.immutabledict()
- else:
- self.select_statement = select_statement
- # indicates this select() came from Query.statement
- self.for_statement = select_statement._compile_options._for_statement
- # generally if we are from Query or directly from a select()
- self.use_legacy_query_style = (
- select_statement._compile_options._use_legacy_query_style
- )
- self._entities = []
- self._primary_entity = None
- self._aliased_generations = {}
- self._polymorphic_adapters = {}
- self._no_yield_pers = set()
- # legacy: only for query.with_polymorphic()
- if select_statement._compile_options._with_polymorphic_adapt_map:
- self._with_polymorphic_adapt_map = dict(
- select_statement._compile_options._with_polymorphic_adapt_map
- )
- self._setup_with_polymorphics()
- self.compile_options = select_statement._compile_options
- if not toplevel:
- # for subqueries, turn off eagerloads and set
- # "render_for_subquery".
- self.compile_options += {
- "_enable_eagerloads": False,
- "_render_for_subquery": True,
- }
- # determine label style. we can make different decisions here.
- # at the moment, trying to see if we can always use DISAMBIGUATE_ONLY
- # rather than LABEL_STYLE_NONE, and if we can use disambiguate style
- # for new style ORM selects too.
- if (
- self.use_legacy_query_style
- and self.select_statement._label_style is LABEL_STYLE_LEGACY_ORM
- ):
- if not self.for_statement:
- self.label_style = LABEL_STYLE_TABLENAME_PLUS_COL
- else:
- self.label_style = LABEL_STYLE_DISAMBIGUATE_ONLY
- else:
- self.label_style = self.select_statement._label_style
- self._label_convention = self._column_naming_convention(
- statement._label_style, self.use_legacy_query_style
- )
- if select_statement._memoized_select_entities:
- self._memoized_entities = {
- memoized_entities: _QueryEntity.to_compile_state(
- self,
- memoized_entities._raw_columns,
- [],
- is_current_entities=False,
- )
- for memoized_entities in (
- select_statement._memoized_select_entities
- )
- }
- _QueryEntity.to_compile_state(
- self,
- select_statement._raw_columns,
- self._entities,
- is_current_entities=True,
- )
- self.current_path = select_statement._compile_options._current_path
- self.eager_order_by = ()
- if toplevel and (
- select_statement._with_options
- or select_statement._memoized_select_entities
- ):
- self.attributes = {"_unbound_load_dedupes": set()}
- for (
- memoized_entities
- ) in select_statement._memoized_select_entities:
- for opt in memoized_entities._with_options:
- if opt._is_compile_state:
- opt.process_compile_state_replaced_entities(
- self,
- [
- ent
- for ent in self._memoized_entities[
- memoized_entities
- ]
- if isinstance(ent, _MapperEntity)
- ],
- )
- for opt in self.select_statement._with_options:
- if opt._is_compile_state:
- opt.process_compile_state(self)
- else:
- self.attributes = {}
- if select_statement._with_context_options:
- for fn, key in select_statement._with_context_options:
- fn(self)
- self.primary_columns = []
- self.secondary_columns = []
- self.dedupe_columns = set()
- self.eager_joins = {}
- self.extra_criteria_entities = {}
- self.create_eager_joins = []
- self._fallback_from_clauses = []
- # normalize the FROM clauses early by themselves, as this makes
- # it an easier job when we need to assemble a JOIN onto these,
- # for select.join() as well as joinedload(). As of 1.4 there are now
- # potentially more complex sets of FROM objects here as the use
- # of lambda statements for lazyload, load_on_pk etc. uses more
- # cloning of the select() construct. See #6495
- self.from_clauses = self._normalize_froms(
- info.selectable for info in select_statement._from_obj
- )
- # this is a fairly arbitrary break into a second method,
- # so it might be nicer to break up create_for_statement()
- # and _setup_for_generate into three or four logical sections
- self._setup_for_generate()
- SelectState.__init__(self, self.statement, compiler, **kw)
- return self
- def _setup_for_generate(self):
- query = self.select_statement
- self.statement = None
- self._join_entities = ()
- if self.compile_options._set_base_alias:
- self._set_select_from_alias()
- for memoized_entities in query._memoized_select_entities:
- if memoized_entities._setup_joins:
- self._join(
- memoized_entities._setup_joins,
- self._memoized_entities[memoized_entities],
- )
- if memoized_entities._legacy_setup_joins:
- self._legacy_join(
- memoized_entities._legacy_setup_joins,
- self._memoized_entities[memoized_entities],
- )
- if query._setup_joins:
- self._join(query._setup_joins, self._entities)
- if query._legacy_setup_joins:
- self._legacy_join(query._legacy_setup_joins, self._entities)
- current_adapter = self._get_current_adapter()
- if query._where_criteria:
- self._where_criteria = query._where_criteria
- if current_adapter:
- self._where_criteria = tuple(
- current_adapter(crit, True)
- for crit in self._where_criteria
- )
- # TODO: some complexity with order_by here was due to mapper.order_by.
- # now that this is removed we can hopefully make order_by /
- # group_by act identically to how they are in Core select.
- self.order_by = (
- self._adapt_col_list(query._order_by_clauses, current_adapter)
- if current_adapter and query._order_by_clauses not in (None, False)
- else query._order_by_clauses
- )
- if query._having_criteria:
- self._having_criteria = tuple(
- current_adapter(crit, True) if current_adapter else crit
- for crit in query._having_criteria
- )
- self.group_by = (
- self._adapt_col_list(
- util.flatten_iterator(query._group_by_clauses), current_adapter
- )
- if current_adapter and query._group_by_clauses not in (None, False)
- else query._group_by_clauses or None
- )
- if self.eager_order_by:
- adapter = self.from_clauses[0]._target_adapter
- self.eager_order_by = adapter.copy_and_process(self.eager_order_by)
- if query._distinct_on:
- self.distinct_on = self._adapt_col_list(
- query._distinct_on, current_adapter
- )
- else:
- self.distinct_on = ()
- self.distinct = query._distinct
- if query._correlate:
- # ORM mapped entities that are mapped to joins can be passed
- # to .correlate, so here they are broken into their component
- # tables.
- self.correlate = tuple(
- util.flatten_iterator(
- sql_util.surface_selectables(s) if s is not None else None
- for s in query._correlate
- )
- )
- elif query._correlate_except is not None:
- self.correlate_except = tuple(
- util.flatten_iterator(
- sql_util.surface_selectables(s) if s is not None else None
- for s in query._correlate_except
- )
- )
- elif not query._auto_correlate:
- self.correlate = (None,)
- # PART II
- self._for_update_arg = query._for_update_arg
- for entity in self._entities:
- entity.setup_compile_state(self)
- for rec in self.create_eager_joins:
- strategy = rec[0]
- strategy(self, *rec[1:])
- # else "load from discrete FROMs" mode,
- # i.e. when each _MappedEntity has its own FROM
- if self.compile_options._enable_single_crit:
- self._adjust_for_extra_criteria()
- if not self.primary_columns:
- if self.compile_options._only_load_props:
- raise sa_exc.InvalidRequestError(
- "No column-based properties specified for "
- "refresh operation. Use session.expire() "
- "to reload collections and related items."
- )
- else:
- raise sa_exc.InvalidRequestError(
- "Query contains no columns with which to SELECT from."
- )
- if not self.from_clauses:
- self.from_clauses = list(self._fallback_from_clauses)
- if self.order_by is False:
- self.order_by = None
- if self.multi_row_eager_loaders and self._should_nest_selectable:
- self.statement = self._compound_eager_statement()
- else:
- self.statement = self._simple_statement()
- if self.for_statement:
- ezero = self._mapper_zero()
- if ezero is not None:
- # TODO: this goes away once we get rid of the deep entity
- # thing
- self.statement = self.statement._annotate(
- {"deepentity": ezero}
- )
- @classmethod
- def _create_entities_collection(cls, query, legacy):
- """Creates a partial ORMSelectCompileState that includes
- the full collection of _MapperEntity and other _QueryEntity objects.
- Supports a few remaining use cases that are pre-compilation
- but still need to gather some of the column / adaption information.
- """
- self = cls.__new__(cls)
- self._entities = []
- self._primary_entity = None
- self._aliased_generations = {}
- self._polymorphic_adapters = {}
- compile_options = cls.default_compile_options.safe_merge(
- query._compile_options
- )
- # legacy: only for query.with_polymorphic()
- if compile_options._with_polymorphic_adapt_map:
- self._with_polymorphic_adapt_map = dict(
- compile_options._with_polymorphic_adapt_map
- )
- self._setup_with_polymorphics()
- self._label_convention = self._column_naming_convention(
- query._label_style, legacy
- )
- # entities will also set up polymorphic adapters for mappers
- # that have with_polymorphic configured
- _QueryEntity.to_compile_state(
- self, query._raw_columns, self._entities, is_current_entities=True
- )
- return self
- @classmethod
- def determine_last_joined_entity(cls, statement):
- setup_joins = statement._setup_joins
- if not setup_joins:
- return None
- (target, onclause, from_, flags) = setup_joins[-1]
- if isinstance(target, interfaces.PropComparator):
- return target.entity
- else:
- return target
- @classmethod
- def all_selected_columns(cls, statement):
- for element in statement._raw_columns:
- if (
- element.is_selectable
- and "entity_namespace" in element._annotations
- ):
- ens = element._annotations["entity_namespace"]
- if not ens.is_mapper and not ens.is_aliased_class:
- for elem in _select_iterables([element]):
- yield elem
- else:
- for elem in _select_iterables(ens._all_column_expressions):
- yield elem
- else:
- for elem in _select_iterables([element]):
- yield elem
- @classmethod
- def get_columns_clause_froms(cls, statement):
- return cls._normalize_froms(
- itertools.chain.from_iterable(
- element._from_objects
- if "parententity" not in element._annotations
- else [
- element._annotations["parententity"].__clause_element__()
- ]
- for element in statement._raw_columns
- )
- )
- @classmethod
- @util.preload_module("sqlalchemy.orm.query")
- def from_statement(cls, statement, from_statement):
- query = util.preloaded.orm_query
- from_statement = coercions.expect(
- roles.ReturnsRowsRole,
- from_statement,
- apply_propagate_attrs=statement,
- )
- stmt = query.FromStatement(statement._raw_columns, from_statement)
- stmt.__dict__.update(
- _with_options=statement._with_options,
- _with_context_options=statement._with_context_options,
- _execution_options=statement._execution_options,
- _propagate_attrs=statement._propagate_attrs,
- )
- return stmt
- def _setup_with_polymorphics(self):
- # legacy: only for query.with_polymorphic()
- for ext_info, wp in self._with_polymorphic_adapt_map.items():
- self._mapper_loads_polymorphically_with(ext_info, wp._adapter)
- def _set_select_from_alias(self):
- query = self.select_statement # query
- assert self.compile_options._set_base_alias
- assert len(query._from_obj) == 1
- adapter = self._get_select_from_alias_from_obj(query._from_obj[0])
- if adapter:
- self.compile_options += {"_enable_single_crit": False}
- self._from_obj_alias = adapter
- def _get_select_from_alias_from_obj(self, from_obj):
- info = from_obj
- if "parententity" in info._annotations:
- info = info._annotations["parententity"]
- if hasattr(info, "mapper"):
- if not info.is_aliased_class:
- raise sa_exc.ArgumentError(
- "A selectable (FromClause) instance is "
- "expected when the base alias is being set."
- )
- else:
- return info._adapter
- elif isinstance(info.selectable, sql.selectable.AliasedReturnsRows):
- equivs = self._all_equivs()
- return sql_util.ColumnAdapter(info, equivs)
- else:
- return None
- def _mapper_zero(self):
- """return the Mapper associated with the first QueryEntity."""
- return self._entities[0].mapper
- def _entity_zero(self):
- """Return the 'entity' (mapper or AliasedClass) associated
- with the first QueryEntity, or alternatively the 'select from'
- entity if specified."""
- for ent in self.from_clauses:
- if "parententity" in ent._annotations:
- return ent._annotations["parententity"]
- for qent in self._entities:
- if qent.entity_zero:
- return qent.entity_zero
- return None
- def _only_full_mapper_zero(self, methname):
- if self._entities != [self._primary_entity]:
- raise sa_exc.InvalidRequestError(
- "%s() can only be used against "
- "a single mapped class." % methname
- )
- return self._primary_entity.entity_zero
- def _only_entity_zero(self, rationale=None):
- if len(self._entities) > 1:
- raise sa_exc.InvalidRequestError(
- rationale
- or "This operation requires a Query "
- "against a single mapper."
- )
- return self._entity_zero()
- def _all_equivs(self):
- equivs = {}
- for memoized_entities in self._memoized_entities.values():
- for ent in [
- ent
- for ent in memoized_entities
- if isinstance(ent, _MapperEntity)
- ]:
- equivs.update(ent.mapper._equivalent_columns)
- for ent in [
- ent for ent in self._entities if isinstance(ent, _MapperEntity)
- ]:
- equivs.update(ent.mapper._equivalent_columns)
- return equivs
- def _compound_eager_statement(self):
- # for eager joins present and LIMIT/OFFSET/DISTINCT,
- # wrap the query inside a select,
- # then append eager joins onto that
- if self.order_by:
- # the default coercion for ORDER BY is now the OrderByRole,
- # which adds an additional post coercion to ByOfRole in that
- # elements are converted into label references. For the
- # eager load / subquery wrapping case, we need to un-coerce
- # the original expressions outside of the label references
- # in order to have them render.
- unwrapped_order_by = [
- elem.element
- if isinstance(elem, sql.elements._label_reference)
- else elem
- for elem in self.order_by
- ]
- order_by_col_expr = sql_util.expand_column_list_from_order_by(
- self.primary_columns, unwrapped_order_by
- )
- else:
- order_by_col_expr = []
- unwrapped_order_by = None
- # put FOR UPDATE on the inner query, where MySQL will honor it,
- # as well as if it has an OF so PostgreSQL can use it.
- inner = self._select_statement(
- self.primary_columns
- + [c for c in order_by_col_expr if c not in self.dedupe_columns],
- self.from_clauses,
- self._where_criteria,
- self._having_criteria,
- self.label_style,
- self.order_by,
- for_update=self._for_update_arg,
- hints=self.select_statement._hints,
- statement_hints=self.select_statement._statement_hints,
- correlate=self.correlate,
- correlate_except=self.correlate_except,
- **self._select_args
- )
- inner = inner.alias()
- equivs = self._all_equivs()
- self.compound_eager_adapter = sql_util.ColumnAdapter(inner, equivs)
- statement = future.select(
- *([inner] + self.secondary_columns) # use_labels=self.labels
- )
- statement._label_style = self.label_style
- # Oracle however does not allow FOR UPDATE on the subquery,
- # and the Oracle dialect ignores it, plus for PostgreSQL, MySQL
- # we expect that all elements of the row are locked, so also put it
- # on the outside (except in the case of PG when OF is used)
- if (
- self._for_update_arg is not None
- and self._for_update_arg.of is None
- ):
- statement._for_update_arg = self._for_update_arg
- from_clause = inner
- for eager_join in self.eager_joins.values():
- # EagerLoader places a 'stop_on' attribute on the join,
- # giving us a marker as to where the "splice point" of
- # the join should be
- from_clause = sql_util.splice_joins(
- from_clause, eager_join, eager_join.stop_on
- )
- statement.select_from.non_generative(statement, from_clause)
- if unwrapped_order_by:
- statement.order_by.non_generative(
- statement,
- *self.compound_eager_adapter.copy_and_process(
- unwrapped_order_by
- )
- )
- statement.order_by.non_generative(statement, *self.eager_order_by)
- return statement
- def _simple_statement(self):
- if (
- self.compile_options._use_legacy_query_style
- and (self.distinct and not self.distinct_on)
- and self.order_by
- ):
- to_add = sql_util.expand_column_list_from_order_by(
- self.primary_columns, self.order_by
- )
- if to_add:
- util.warn_deprecated_20(
- "ORDER BY columns added implicitly due to "
- "DISTINCT is deprecated and will be removed in "
- "SQLAlchemy 2.0. SELECT statements with DISTINCT "
- "should be written to explicitly include the appropriate "
- "columns in the columns clause"
- )
- self.primary_columns += to_add
- statement = self._select_statement(
- self.primary_columns + self.secondary_columns,
- tuple(self.from_clauses) + tuple(self.eager_joins.values()),
- self._where_criteria,
- self._having_criteria,
- self.label_style,
- self.order_by,
- for_update=self._for_update_arg,
- hints=self.select_statement._hints,
- statement_hints=self.select_statement._statement_hints,
- correlate=self.correlate,
- correlate_except=self.correlate_except,
- **self._select_args
- )
- if self.eager_order_by:
- statement.order_by.non_generative(statement, *self.eager_order_by)
- return statement
- def _select_statement(
- self,
- raw_columns,
- from_obj,
- where_criteria,
- having_criteria,
- label_style,
- order_by,
- for_update,
- hints,
- statement_hints,
- correlate,
- correlate_except,
- limit_clause,
- offset_clause,
- distinct,
- distinct_on,
- prefixes,
- suffixes,
- group_by,
- ):
- Select = future.Select
- statement = Select._create_raw_select(
- _raw_columns=raw_columns,
- _from_obj=from_obj,
- _label_style=label_style,
- )
- if where_criteria:
- statement._where_criteria = where_criteria
- if having_criteria:
- statement._having_criteria = having_criteria
- if order_by:
- statement._order_by_clauses += tuple(order_by)
- if distinct_on:
- statement.distinct.non_generative(statement, *distinct_on)
- elif distinct:
- statement.distinct.non_generative(statement)
- if group_by:
- statement._group_by_clauses += tuple(group_by)
- statement._limit_clause = limit_clause
- statement._offset_clause = offset_clause
- if prefixes:
- statement._prefixes = prefixes
- if suffixes:
- statement._suffixes = suffixes
- statement._for_update_arg = for_update
- if hints:
- statement._hints = hints
- if statement_hints:
- statement._statement_hints = statement_hints
- if correlate:
- statement.correlate.non_generative(statement, *correlate)
- if correlate_except is not None:
- statement.correlate_except.non_generative(
- statement, *correlate_except
- )
- return statement
- def _adapt_polymorphic_element(self, element):
- if "parententity" in element._annotations:
- search = element._annotations["parententity"]
- alias = self._polymorphic_adapters.get(search, None)
- if alias:
- return alias.adapt_clause(element)
- if isinstance(element, expression.FromClause):
- search = element
- elif hasattr(element, "table"):
- search = element.table
- else:
- return None
- alias = self._polymorphic_adapters.get(search, None)
- if alias:
- return alias.adapt_clause(element)
- def _adapt_aliased_generation(self, element):
- # this is crazy logic that I look forward to blowing away
- # when aliased=True is gone :)
- if "aliased_generation" in element._annotations:
- for adapter in self._aliased_generations.get(
- element._annotations["aliased_generation"], ()
- ):
- replaced_elem = adapter.replace(element)
- if replaced_elem is not None:
- return replaced_elem
- return None
- def _adapt_col_list(self, cols, current_adapter):
- if current_adapter:
- return [current_adapter(o, True) for o in cols]
- else:
- return cols
- def _get_current_adapter(self):
- adapters = []
- if self._from_obj_alias:
- # used for legacy going forward for query set_ops, e.g.
- # union(), union_all(), etc.
- # 1.4 and previously, also used for from_self(),
- # select_entity_from()
- #
- # for the "from obj" alias, apply extra rule to the
- # 'ORM only' check, if this query were generated from a
- # subquery of itself, i.e. _from_selectable(), apply adaption
- # to all SQL constructs.
- adapters.append(
- (
- False
- if self.compile_options._orm_only_from_obj_alias
- else True,
- self._from_obj_alias.replace,
- )
- )
- # vvvvvvvvvvvvvvv legacy vvvvvvvvvvvvvvvvvv
- # this can totally go away when we remove join(..., aliased=True)
- if self._aliased_generations:
- adapters.append((False, self._adapt_aliased_generation))
- # ^^^^^^^^^^^^^ legacy ^^^^^^^^^^^^^^^^^^^^^
- # this was *hopefully* the only adapter we were going to need
- # going forward...however, we unfortunately need _from_obj_alias
- # for query.union(), which we can't drop
- if self._polymorphic_adapters:
- adapters.append((False, self._adapt_polymorphic_element))
- if not adapters:
- return None
- def _adapt_clause(clause, as_filter):
- # do we adapt all expression elements or only those
- # tagged as 'ORM' constructs ?
- def replace(elem):
- is_orm_adapt = (
- "_orm_adapt" in elem._annotations
- or "parententity" in elem._annotations
- )
- for always_adapt, adapter in adapters:
- if is_orm_adapt or always_adapt:
- e = adapter(elem)
- if e is not None:
- return e
- return visitors.replacement_traverse(clause, {}, replace)
- return _adapt_clause
- def _join(self, args, entities_collection):
- for (right, onclause, from_, flags) in args:
- isouter = flags["isouter"]
- full = flags["full"]
- # maybe?
- self._reset_joinpoint()
- right = inspect(right)
- if onclause is not None:
- onclause = inspect(onclause)
- if onclause is None and isinstance(
- right, interfaces.PropComparator
- ):
- # determine onclause/right_entity. still need to think
- # about how to best organize this since we are getting:
- #
- #
- # q.join(Entity, Parent.property)
- # q.join(Parent.property)
- # q.join(Parent.property.of_type(Entity))
- # q.join(some_table)
- # q.join(some_table, some_parent.c.id==some_table.c.parent_id)
- #
- # is this still too many choices? how do we handle this
- # when sometimes "right" is implied and sometimes not?
- #
- onclause = right
- right = None
- elif "parententity" in right._annotations:
- right = right._annotations["parententity"]
- if onclause is None:
- if not right.is_selectable and not hasattr(right, "mapper"):
- raise sa_exc.ArgumentError(
- "Expected mapped entity or "
- "selectable/table as join target"
- )
- of_type = None
- if isinstance(onclause, interfaces.PropComparator):
- # descriptor/property given (or determined); this tells us
- # explicitly what the expected "left" side of the join is.
- of_type = getattr(onclause, "_of_type", None)
- if right is None:
- if of_type:
- right = of_type
- else:
- right = onclause.property
- try:
- right = right.entity
- except AttributeError as err:
- util.raise_(
- sa_exc.ArgumentError(
- "Join target %s does not refer to a "
- "mapped entity" % right
- ),
- replace_context=err,
- )
- left = onclause._parententity
- alias = self._polymorphic_adapters.get(left, None)
- # could be None or could be ColumnAdapter also
- if isinstance(alias, ORMAdapter) and alias.mapper.isa(left):
- left = alias.aliased_class
- onclause = getattr(left, onclause.key)
- prop = onclause.property
- if not isinstance(onclause, attributes.QueryableAttribute):
- onclause = prop
- # TODO: this is where "check for path already present"
- # would occur. see if this still applies?
- if from_ is not None:
- if (
- from_ is not left
- and from_._annotations.get("parententity", None)
- is not left
- ):
- raise sa_exc.InvalidRequestError(
- "explicit from clause %s does not match left side "
- "of relationship attribute %s"
- % (
- from_._annotations.get("parententity", from_),
- onclause,
- )
- )
- elif from_ is not None:
- prop = None
- left = from_
- else:
- # no descriptor/property given; we will need to figure out
- # what the effective "left" side is
- prop = left = None
- # figure out the final "left" and "right" sides and create an
- # ORMJoin to add to our _from_obj tuple
- self._join_left_to_right(
- entities_collection,
- left,
- right,
- onclause,
- prop,
- False,
- False,
- isouter,
- full,
- )
- def _legacy_join(self, args, entities_collection):
- """consumes arguments from join() or outerjoin(), places them into a
- consistent format with which to form the actual JOIN constructs.
- """
- for (right, onclause, left, flags) in args:
- outerjoin = flags["isouter"]
- create_aliases = flags["aliased"]
- from_joinpoint = flags["from_joinpoint"]
- full = flags["full"]
- aliased_generation = flags["aliased_generation"]
- # do a quick inspect to accommodate for a lambda
- if right is not None and not isinstance(right, util.string_types):
- right = inspect(right)
- if onclause is not None and not isinstance(
- onclause, util.string_types
- ):
- onclause = inspect(onclause)
- # legacy vvvvvvvvvvvvvvvvvvvvvvvvvv
- if not from_joinpoint:
- self._reset_joinpoint()
- else:
- prev_aliased_generation = self._joinpoint.get(
- "aliased_generation", None
- )
- if not aliased_generation:
- aliased_generation = prev_aliased_generation
- elif prev_aliased_generation:
- self._aliased_generations[
- aliased_generation
- ] = self._aliased_generations.get(
- prev_aliased_generation, ()
- )
- # legacy ^^^^^^^^^^^^^^^^^^^^^^^^^^^
- if (
- isinstance(
- right, (interfaces.PropComparator, util.string_types)
- )
- and onclause is None
- ):
- onclause = right
- right = None
- elif "parententity" in right._annotations:
- right = right._annotations["parententity"]
- if onclause is None:
- if not right.is_selectable and not hasattr(right, "mapper"):
- raise sa_exc.ArgumentError(
- "Expected mapped entity or "
- "selectable/table as join target"
- )
- if isinstance(onclause, interfaces.PropComparator):
- of_type = getattr(onclause, "_of_type", None)
- else:
- of_type = None
- if isinstance(onclause, util.string_types):
- # string given, e.g. query(Foo).join("bar").
- # we look to the left entity or what we last joined
- # towards
- onclause = _entity_namespace_key(
- inspect(self._joinpoint_zero()), onclause
- )
- # legacy vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
- # check for q.join(Class.propname, from_joinpoint=True)
- # and Class corresponds at the mapper level to the current
- # joinpoint. this match intentionally looks for a non-aliased
- # class-bound descriptor as the onclause and if it matches the
- # current joinpoint at the mapper level, it's used. This
- # is a very old use case that is intended to make it easier
- # to work with the aliased=True flag, which is also something
- # that probably shouldn't exist on join() due to its high
- # complexity/usefulness ratio
- elif from_joinpoint and isinstance(
- onclause, interfaces.PropComparator
- ):
- jp0 = self._joinpoint_zero()
- info = inspect(jp0)
- if getattr(info, "mapper", None) is onclause._parententity:
- onclause = _entity_namespace_key(info, onclause.key)
- # legacy ^^^^^^^^^^^^^^^^^^^^^^^^^^^
- if isinstance(onclause, interfaces.PropComparator):
- # descriptor/property given (or determined); this tells us
- # explicitly what the expected "left" side of the join is.
- if right is None:
- if of_type:
- right = of_type
- else:
- right = onclause.property
- try:
- right = right.entity
- except AttributeError as err:
- util.raise_(
- sa_exc.ArgumentError(
- "Join target %s does not refer to a "
- "mapped entity" % right
- ),
- replace_context=err,
- )
- left = onclause._parententity
- alias = self._polymorphic_adapters.get(left, None)
- # could be None or could be ColumnAdapter also
- if isinstance(alias, ORMAdapter) and alias.mapper.isa(left):
- left = alias.aliased_class
- onclause = getattr(left, onclause.key)
- prop = onclause.property
- if not isinstance(onclause, attributes.QueryableAttribute):
- onclause = prop
- if not create_aliases:
- # check for this path already present.
- # don't render in that case.
- edge = (left, right, prop.key)
- if edge in self._joinpoint:
- # The child's prev reference might be stale --
- # it could point to a parent older than the
- # current joinpoint. If this is the case,
- # then we need to update it and then fix the
- # tree's spine with _update_joinpoint. Copy
- # and then mutate the child, which might be
- # shared by a different query object.
- jp = self._joinpoint[edge].copy()
- jp["prev"] = (edge, self._joinpoint)
- self._update_joinpoint(jp)
- continue
- else:
- # no descriptor/property given; we will need to figure out
- # what the effective "left" side is
- prop = left = None
- # figure out the final "left" and "right" sides and create an
- # ORMJoin to add to our _from_obj tuple
- self._join_left_to_right(
- entities_collection,
- left,
- right,
- onclause,
- prop,
- create_aliases,
- aliased_generation,
- outerjoin,
- full,
- )
- def _joinpoint_zero(self):
- return self._joinpoint.get("_joinpoint_entity", self._entity_zero())
- def _join_left_to_right(
- self,
- entities_collection,
- left,
- right,
- onclause,
- prop,
- create_aliases,
- aliased_generation,
- outerjoin,
- full,
- ):
- """given raw "left", "right", "onclause" parameters consumed from
- a particular key within _join(), add a real ORMJoin object to
- our _from_obj list (or augment an existing one)
- """
- if left is None:
- # left not given (e.g. no relationship object/name specified)
- # figure out the best "left" side based on our existing froms /
- # entities
- assert prop is None
- (
- left,
- replace_from_obj_index,
- use_entity_index,
- ) = self._join_determine_implicit_left_side(
- entities_collection, left, right, onclause
- )
- else:
- # left is given via a relationship/name, or as explicit left side.
- # Determine where in our
- # "froms" list it should be spliced/appended as well as what
- # existing entity it corresponds to.
- (
- replace_from_obj_index,
- use_entity_index,
- ) = self._join_place_explicit_left_side(entities_collection, left)
- if left is right and not create_aliases:
- raise sa_exc.InvalidRequestError(
- "Can't construct a join from %s to %s, they "
- "are the same entity" % (left, right)
- )
- # the right side as given often needs to be adapted. additionally
- # a lot of things can be wrong with it. handle all that and
- # get back the new effective "right" side
- r_info, right, onclause = self._join_check_and_adapt_right_side(
- left, right, onclause, prop, create_aliases, aliased_generation
- )
- if not r_info.is_selectable:
- extra_criteria = self._get_extra_criteria(r_info)
- else:
- extra_criteria = ()
- if replace_from_obj_index is not None:
- # splice into an existing element in the
- # self._from_obj list
- left_clause = self.from_clauses[replace_from_obj_index]
- self.from_clauses = (
- self.from_clauses[:replace_from_obj_index]
- + [
- _ORMJoin(
- left_clause,
- right,
- onclause,
- isouter=outerjoin,
- full=full,
- _extra_criteria=extra_criteria,
- )
- ]
- + self.from_clauses[replace_from_obj_index + 1 :]
- )
- else:
- # add a new element to the self._from_obj list
- if use_entity_index is not None:
- # make use of _MapperEntity selectable, which is usually
- # entity_zero.selectable, but if with_polymorphic() were used
- # might be distinct
- assert isinstance(
- entities_collection[use_entity_index], _MapperEntity
- )
- left_clause = entities_collection[use_entity_index].selectable
- else:
- left_clause = left
- self.from_clauses = self.from_clauses + [
- _ORMJoin(
- left_clause,
- r_info,
- onclause,
- isouter=outerjoin,
- full=full,
- _extra_criteria=extra_criteria,
- )
- ]
- def _join_determine_implicit_left_side(
- self, entities_collection, left, right, onclause
- ):
- """When join conditions don't express the left side explicitly,
- determine if an existing FROM or entity in this query
- can serve as the left hand side.
- """
- # when we are here, it means join() was called without an ORM-
- # specific way of telling us what the "left" side is, e.g.:
- #
- # join(RightEntity)
- #
- # or
- #
- # join(RightEntity, RightEntity.foo == LeftEntity.bar)
- #
- r_info = inspect(right)
- replace_from_obj_index = use_entity_index = None
- if self.from_clauses:
- # we have a list of FROMs already. So by definition this
- # join has to connect to one of those FROMs.
- indexes = sql_util.find_left_clause_to_join_from(
- self.from_clauses, r_info.selectable, onclause
- )
- if len(indexes) == 1:
- replace_from_obj_index = indexes[0]
- left = self.from_clauses[replace_from_obj_index]
- elif len(indexes) > 1:
- raise sa_exc.InvalidRequestError(
- "Can't determine which FROM clause to join "
- "from, there are multiple FROMS which can "
- "join to this entity. Please use the .select_from() "
- "method to establish an explicit left side, as well as "
- "providing an explicit ON clause if not present already "
- "to help resolve the ambiguity."
- )
- else:
- raise sa_exc.InvalidRequestError(
- "Don't know how to join to %r. "
- "Please use the .select_from() "
- "method to establish an explicit left side, as well as "
- "providing an explicit ON clause if not present already "
- "to help resolve the ambiguity." % (right,)
- )
- elif entities_collection:
- # we have no explicit FROMs, so the implicit left has to
- # come from our list of entities.
- potential = {}
- for entity_index, ent in enumerate(entities_collection):
- entity = ent.entity_zero_or_selectable
- if entity is None:
- continue
- ent_info = inspect(entity)
- if ent_info is r_info: # left and right are the same, skip
- continue
- # by using a dictionary with the selectables as keys this
- # de-duplicates those selectables as occurs when the query is
- # against a series of columns from the same selectable
- if isinstance(ent, _MapperEntity):
- potential[ent.selectable] = (entity_index, entity)
- else:
- potential[ent_info.selectable] = (None, entity)
- all_clauses = list(potential.keys())
- indexes = sql_util.find_left_clause_to_join_from(
- all_clauses, r_info.selectable, onclause
- )
- if len(indexes) == 1:
- use_entity_index, left = potential[all_clauses[indexes[0]]]
- elif len(indexes) > 1:
- raise sa_exc.InvalidRequestError(
- "Can't determine which FROM clause to join "
- "from, there are multiple FROMS which can "
- "join to this entity. Please use the .select_from() "
- "method to establish an explicit left side, as well as "
- "providing an explicit ON clause if not present already "
- "to help resolve the ambiguity."
- )
- else:
- raise sa_exc.InvalidRequestError(
- "Don't know how to join to %r. "
- "Please use the .select_from() "
- "method to establish an explicit left side, as well as "
- "providing an explicit ON clause if not present already "
- "to help resolve the ambiguity." % (right,)
- )
- else:
- raise sa_exc.InvalidRequestError(
- "No entities to join from; please use "
- "select_from() to establish the left "
- "entity/selectable of this join"
- )
- return left, replace_from_obj_index, use_entity_index
- def _join_place_explicit_left_side(self, entities_collection, left):
- """When join conditions express a left side explicitly, determine
- where in our existing list of FROM clauses we should join towards,
- or if we need to make a new join, and if so is it from one of our
- existing entities.
- """
- # when we are here, it means join() was called with an indicator
- # as to an exact left side, which means a path to a
- # RelationshipProperty was given, e.g.:
- #
- # join(RightEntity, LeftEntity.right)
- #
- # or
- #
- # join(LeftEntity.right)
- #
- # as well as string forms:
- #
- # join(RightEntity, "right")
- #
- # etc.
- #
- replace_from_obj_index = use_entity_index = None
- l_info = inspect(left)
- if self.from_clauses:
- indexes = sql_util.find_left_clause_that_matches_given(
- self.from_clauses, l_info.selectable
- )
- if len(indexes) > 1:
- raise sa_exc.InvalidRequestError(
- "Can't identify which entity in which to assign the "
- "left side of this join. Please use a more specific "
- "ON clause."
- )
- # have an index, means the left side is already present in
- # an existing FROM in the self._from_obj tuple
- if indexes:
- replace_from_obj_index = indexes[0]
- # no index, means we need to add a new element to the
- # self._from_obj tuple
- # no from element present, so we will have to add to the
- # self._from_obj tuple. Determine if this left side matches up
- # with existing mapper entities, in which case we want to apply the
- # aliasing / adaptation rules present on that entity if any
- if (
- replace_from_obj_index is None
- and entities_collection
- and hasattr(l_info, "mapper")
- ):
- for idx, ent in enumerate(entities_collection):
- # TODO: should we be checking for multiple mapper entities
- # matching?
- if isinstance(ent, _MapperEntity) and ent.corresponds_to(left):
- use_entity_index = idx
- break
- return replace_from_obj_index, use_entity_index
- def _join_check_and_adapt_right_side(
- self, left, right, onclause, prop, create_aliases, aliased_generation
- ):
- """transform the "right" side of the join as well as the onclause
- according to polymorphic mapping translations, aliasing on the query
- or on the join, special cases where the right and left side have
- overlapping tables.
- """
- l_info = inspect(left)
- r_info = inspect(right)
- overlap = False
- if not create_aliases:
- right_mapper = getattr(r_info, "mapper", None)
- # if the target is a joined inheritance mapping,
- # be more liberal about auto-aliasing.
- if right_mapper and (
- right_mapper.with_polymorphic
- or isinstance(right_mapper.persist_selectable, expression.Join)
- ):
- for from_obj in self.from_clauses or [l_info.selectable]:
- if sql_util.selectables_overlap(
- l_info.selectable, from_obj
- ) and sql_util.selectables_overlap(
- from_obj, r_info.selectable
- ):
- overlap = True
- break
- if (
- overlap or not create_aliases
- ) and l_info.selectable is r_info.selectable:
- raise sa_exc.InvalidRequestError(
- "Can't join table/selectable '%s' to itself"
- % l_info.selectable
- )
- right_mapper, right_selectable, right_is_aliased = (
- getattr(r_info, "mapper", None),
- r_info.selectable,
- getattr(r_info, "is_aliased_class", False),
- )
- if (
- right_mapper
- and prop
- and not right_mapper.common_parent(prop.mapper)
- ):
- raise sa_exc.InvalidRequestError(
- "Join target %s does not correspond to "
- "the right side of join condition %s" % (right, onclause)
- )
- # _join_entities is used as a hint for single-table inheritance
- # purposes at the moment
- if hasattr(r_info, "mapper"):
- self._join_entities += (r_info,)
- need_adapter = False
- # test for joining to an unmapped selectable as the target
- if r_info.is_clause_element:
- if prop:
- right_mapper = prop.mapper
- if right_selectable._is_lateral:
- # orm_only is disabled to suit the case where we have to
- # adapt an explicit correlate(Entity) - the select() loses
- # the ORM-ness in this case right now, ideally it would not
- current_adapter = self._get_current_adapter()
- if current_adapter is not None:
- # TODO: we had orm_only=False here before, removing
- # it didn't break things. if we identify the rationale,
- # may need to apply "_orm_only" annotation here.
- right = current_adapter(right, True)
- elif prop:
- # joining to selectable with a mapper property given
- # as the ON clause
- if not right_selectable.is_derived_from(
- right_mapper.persist_selectable
- ):
- raise sa_exc.InvalidRequestError(
- "Selectable '%s' is not derived from '%s'"
- % (
- right_selectable.description,
- right_mapper.persist_selectable.description,
- )
- )
- # if the destination selectable is a plain select(),
- # turn it into an alias().
- if isinstance(right_selectable, expression.SelectBase):
- right_selectable = coercions.expect(
- roles.FromClauseRole, right_selectable
- )
- need_adapter = True
- # make the right hand side target into an ORM entity
- right = aliased(right_mapper, right_selectable)
- util.warn_deprecated(
- "An alias is being generated automatically against "
- "joined entity %s for raw clauseelement, which is "
- "deprecated and will be removed in a later release. "
- "Use the aliased() "
- "construct explicitly, see the linked example."
- % right_mapper,
- "1.4",
- code="xaj1",
- )
- elif create_aliases:
- # it *could* work, but it doesn't right now and I'd rather
- # get rid of aliased=True completely
- raise sa_exc.InvalidRequestError(
- "The aliased=True parameter on query.join() only works "
- "with an ORM entity, not a plain selectable, as the "
- "target."
- )
- # test for overlap:
- # orm/inheritance/relationships.py
- # SelfReferentialM2MTest
- aliased_entity = right_mapper and not right_is_aliased and overlap
- if not need_adapter and (create_aliases or aliased_entity):
- # there are a few places in the ORM that automatic aliasing
- # is still desirable, and can't be automatic with a Core
- # only approach. For illustrations of "overlaps" see
- # test/orm/inheritance/test_relationships.py. There are also
- # general overlap cases with many-to-many tables where automatic
- # aliasing is desirable.
- right = aliased(right, flat=True)
- need_adapter = True
- if not create_aliases:
- util.warn(
- "An alias is being generated automatically against "
- "joined entity %s due to overlapping tables. This is a "
- "legacy pattern which may be "
- "deprecated in a later release. Use the "
- "aliased(<entity>, flat=True) "
- "construct explicitly, see the linked example."
- % right_mapper,
- code="xaj2",
- )
- if need_adapter:
- assert right_mapper
- adapter = ORMAdapter(
- right, equivalents=right_mapper._equivalent_columns
- )
- # if an alias() on the right side was generated,
- # which is intended to wrap a the right side in a subquery,
- # ensure that columns retrieved from this target in the result
- # set are also adapted.
- if not create_aliases:
- self._mapper_loads_polymorphically_with(right_mapper, adapter)
- elif aliased_generation:
- adapter._debug = True
- self._aliased_generations[aliased_generation] = (
- adapter,
- ) + self._aliased_generations.get(aliased_generation, ())
- elif (
- not r_info.is_clause_element
- and not right_is_aliased
- and right_mapper.with_polymorphic
- and isinstance(
- right_mapper._with_polymorphic_selectable,
- expression.AliasedReturnsRows,
- )
- ):
- # for the case where the target mapper has a with_polymorphic
- # set up, ensure an adapter is set up for criteria that works
- # against this mapper. Previously, this logic used to
- # use the "create_aliases or aliased_entity" case to generate
- # an aliased() object, but this creates an alias that isn't
- # strictly necessary.
- # see test/orm/test_core_compilation.py
- # ::RelNaturalAliasedJoinsTest::test_straight
- # and similar
- self._mapper_loads_polymorphically_with(
- right_mapper,
- sql_util.ColumnAdapter(
- right_mapper.selectable,
- right_mapper._equivalent_columns,
- ),
- )
- # if the onclause is a ClauseElement, adapt it with any
- # adapters that are in place right now
- if isinstance(onclause, expression.ClauseElement):
- current_adapter = self._get_current_adapter()
- if current_adapter:
- onclause = current_adapter(onclause, True)
- # if joining on a MapperProperty path,
- # track the path to prevent redundant joins
- if not create_aliases and prop:
- self._update_joinpoint(
- {
- "_joinpoint_entity": right,
- "prev": ((left, right, prop.key), self._joinpoint),
- "aliased_generation": aliased_generation,
- }
- )
- else:
- self._joinpoint = {
- "_joinpoint_entity": right,
- "aliased_generation": aliased_generation,
- }
- return inspect(right), right, onclause
- def _update_joinpoint(self, jp):
- self._joinpoint = jp
- # copy backwards to the root of the _joinpath
- # dict, so that no existing dict in the path is mutated
- while "prev" in jp:
- f, prev = jp["prev"]
- prev = dict(prev)
- prev[f] = jp.copy()
- jp["prev"] = (f, prev)
- jp = prev
- self._joinpath = jp
- def _reset_joinpoint(self):
- self._joinpoint = self._joinpath
- @property
- def _select_args(self):
- return {
- "limit_clause": self.select_statement._limit_clause,
- "offset_clause": self.select_statement._offset_clause,
- "distinct": self.distinct,
- "distinct_on": self.distinct_on,
- "prefixes": self.select_statement._prefixes,
- "suffixes": self.select_statement._suffixes,
- "group_by": self.group_by or None,
- }
- @property
- def _should_nest_selectable(self):
- kwargs = self._select_args
- return (
- kwargs.get("limit_clause") is not None
- or kwargs.get("offset_clause") is not None
- or kwargs.get("distinct", False)
- or kwargs.get("distinct_on", ())
- or kwargs.get("group_by", False)
- )
- def _get_extra_criteria(self, ext_info):
- if (
- "additional_entity_criteria",
- ext_info.mapper,
- ) in self.global_attributes:
- return tuple(
- ae._resolve_where_criteria(ext_info)
- for ae in self.global_attributes[
- ("additional_entity_criteria", ext_info.mapper)
- ]
- if (ae.include_aliases or ae.entity is ext_info)
- and ae._should_include(self)
- )
- else:
- return ()
- def _adjust_for_extra_criteria(self):
- """Apply extra criteria filtering.
- For all distinct single-table-inheritance mappers represented in
- the columns clause of this query, as well as the "select from entity",
- add criterion to the WHERE
- clause of the given QueryContext such that only the appropriate
- subtypes are selected from the total results.
- Additionally, add WHERE criteria originating from LoaderCriteriaOptions
- associated with the global context.
- """
- for fromclause in self.from_clauses:
- ext_info = fromclause._annotations.get("parententity", None)
- if (
- ext_info
- and (
- ext_info.mapper._single_table_criterion is not None
- or ("additional_entity_criteria", ext_info.mapper)
- in self.global_attributes
- )
- and ext_info not in self.extra_criteria_entities
- ):
- self.extra_criteria_entities[ext_info] = (
- ext_info,
- ext_info._adapter if ext_info.is_aliased_class else None,
- )
- search = set(self.extra_criteria_entities.values())
- for (ext_info, adapter) in search:
- if ext_info in self._join_entities:
- continue
- single_crit = ext_info.mapper._single_table_criterion
- additional_entity_criteria = self._get_extra_criteria(ext_info)
- if single_crit is not None:
- additional_entity_criteria += (single_crit,)
- current_adapter = self._get_current_adapter()
- for crit in additional_entity_criteria:
- if adapter:
- crit = adapter.traverse(crit)
- if current_adapter:
- crit = sql_util._deep_annotate(crit, {"_orm_adapt": True})
- crit = current_adapter(crit, False)
- self._where_criteria += (crit,)
- def _column_descriptions(
- query_or_select_stmt, compile_state=None, legacy=False
- ):
- if compile_state is None:
- compile_state = ORMSelectCompileState._create_entities_collection(
- query_or_select_stmt, legacy=legacy
- )
- ctx = compile_state
- return [
- {
- "name": ent._label_name,
- "type": ent.type,
- "aliased": getattr(insp_ent, "is_aliased_class", False),
- "expr": ent.expr,
- "entity": getattr(insp_ent, "entity", None)
- if ent.entity_zero is not None and not insp_ent.is_clause_element
- else None,
- }
- for ent, insp_ent in [
- (
- _ent,
- (
- inspect(_ent.entity_zero)
- if _ent.entity_zero is not None
- else None
- ),
- )
- for _ent in ctx._entities
- ]
- ]
- def _legacy_filter_by_entity_zero(query_or_augmented_select):
- self = query_or_augmented_select
- if self._legacy_setup_joins:
- _last_joined_entity = self._last_joined_entity
- if _last_joined_entity is not None:
- return _last_joined_entity
- if self._from_obj and "parententity" in self._from_obj[0]._annotations:
- return self._from_obj[0]._annotations["parententity"]
- return _entity_from_pre_ent_zero(self)
- def _entity_from_pre_ent_zero(query_or_augmented_select):
- self = query_or_augmented_select
- if not self._raw_columns:
- return None
- ent = self._raw_columns[0]
- if "parententity" in ent._annotations:
- return ent._annotations["parententity"]
- elif isinstance(ent, ORMColumnsClauseRole):
- return ent.entity
- elif "bundle" in ent._annotations:
- return ent._annotations["bundle"]
- else:
- return ent
- def _legacy_determine_last_joined_entity(setup_joins, entity_zero):
- """given the legacy_setup_joins collection at a point in time,
- figure out what the "filter by entity" would be in terms
- of those joins.
- in 2.0 this logic should hopefully be much simpler as there will
- be far fewer ways to specify joins with the ORM
- """
- if not setup_joins:
- return entity_zero
- # CAN BE REMOVED IN 2.0:
- # 1. from_joinpoint
- # 2. aliased_generation
- # 3. aliased
- # 4. any treating of prop as str
- # 5. tuple madness
- # 6. won't need recursive call anymore without #4
- # 7. therefore can pass in just the last setup_joins record,
- # don't need entity_zero
- (right, onclause, left_, flags) = setup_joins[-1]
- from_joinpoint = flags["from_joinpoint"]
- if onclause is None and isinstance(
- right, (str, interfaces.PropComparator)
- ):
- onclause = right
- right = None
- if right is not None and "parententity" in right._annotations:
- right = right._annotations["parententity"].entity
- if right is not None:
- last_entity = right
- insp = inspect(last_entity)
- if insp.is_clause_element or insp.is_aliased_class or insp.is_mapper:
- return insp
- last_entity = onclause
- if isinstance(last_entity, interfaces.PropComparator):
- return last_entity.entity
- # legacy vvvvvvvvvvvvvvvvvvvvvvvvvvv
- if isinstance(onclause, str):
- if from_joinpoint:
- prev = _legacy_determine_last_joined_entity(
- setup_joins[0:-1], entity_zero
- )
- else:
- prev = entity_zero
- if prev is None:
- return None
- prev = inspect(prev)
- attr = getattr(prev.entity, onclause, None)
- if attr is not None:
- return attr.property.entity
- # legacy ^^^^^^^^^^^^^^^^^^^^^^^^^^^
- return None
- class _QueryEntity(object):
- """represent an entity column returned within a Query result."""
- __slots__ = ()
- _non_hashable_value = False
- _null_column_type = False
- use_id_for_hash = False
- @classmethod
- def to_compile_state(
- cls, compile_state, entities, entities_collection, is_current_entities
- ):
- for idx, entity in enumerate(entities):
- if entity._is_lambda_element:
- if entity._is_sequence:
- cls.to_compile_state(
- compile_state,
- entity._resolved,
- entities_collection,
- is_current_entities,
- )
- continue
- else:
- entity = entity._resolved
- if entity.is_clause_element:
- if entity.is_selectable:
- if "parententity" in entity._annotations:
- _MapperEntity(
- compile_state,
- entity,
- entities_collection,
- is_current_entities,
- )
- else:
- _ColumnEntity._for_columns(
- compile_state,
- entity._select_iterable,
- entities_collection,
- idx,
- )
- else:
- if entity._annotations.get("bundle", False):
- _BundleEntity(
- compile_state, entity, entities_collection
- )
- elif entity._is_clause_list:
- # this is legacy only - test_composites.py
- # test_query_cols_legacy
- _ColumnEntity._for_columns(
- compile_state,
- entity._select_iterable,
- entities_collection,
- idx,
- )
- else:
- _ColumnEntity._for_columns(
- compile_state, [entity], entities_collection, idx
- )
- elif entity.is_bundle:
- _BundleEntity(compile_state, entity, entities_collection)
- return entities_collection
- class _MapperEntity(_QueryEntity):
- """mapper/class/AliasedClass entity"""
- __slots__ = (
- "expr",
- "mapper",
- "entity_zero",
- "is_aliased_class",
- "path",
- "_extra_entities",
- "_label_name",
- "_with_polymorphic_mappers",
- "selectable",
- "_polymorphic_discriminator",
- )
- def __init__(
- self, compile_state, entity, entities_collection, is_current_entities
- ):
- entities_collection.append(self)
- if is_current_entities:
- if compile_state._primary_entity is None:
- compile_state._primary_entity = self
- compile_state._has_mapper_entities = True
- compile_state._has_orm_entities = True
- entity = entity._annotations["parententity"]
- entity._post_inspect
- ext_info = self.entity_zero = entity
- entity = ext_info.entity
- self.expr = entity
- self.mapper = mapper = ext_info.mapper
- self._extra_entities = (self.expr,)
- if ext_info.is_aliased_class:
- self._label_name = ext_info.name
- else:
- self._label_name = mapper.class_.__name__
- self.is_aliased_class = ext_info.is_aliased_class
- self.path = ext_info._path_registry
- if ext_info in compile_state._with_polymorphic_adapt_map:
- # this codepath occurs only if query.with_polymorphic() were
- # used
- wp = inspect(compile_state._with_polymorphic_adapt_map[ext_info])
- if self.is_aliased_class:
- # TODO: invalidrequest ?
- raise NotImplementedError(
- "Can't use with_polymorphic() against an Aliased object"
- )
- mappers, from_obj = mapper._with_polymorphic_args(
- wp.with_polymorphic_mappers, wp.selectable
- )
- self._with_polymorphic_mappers = mappers
- self.selectable = from_obj
- self._polymorphic_discriminator = wp.polymorphic_on
- else:
- self.selectable = ext_info.selectable
- self._with_polymorphic_mappers = ext_info.with_polymorphic_mappers
- self._polymorphic_discriminator = ext_info.polymorphic_on
- if (
- mapper.with_polymorphic
- # controversy - only if inheriting mapper is also
- # polymorphic?
- # or (mapper.inherits and mapper.inherits.with_polymorphic)
- or mapper.inherits
- or mapper._requires_row_aliasing
- ):
- compile_state._create_with_polymorphic_adapter(
- ext_info, self.selectable
- )
- supports_single_entity = True
- _non_hashable_value = True
- use_id_for_hash = True
- @property
- def type(self):
- return self.mapper.class_
- @property
- def entity_zero_or_selectable(self):
- return self.entity_zero
- def corresponds_to(self, entity):
- return _entity_corresponds_to(self.entity_zero, entity)
- def _get_entity_clauses(self, compile_state):
- adapter = None
- if not self.is_aliased_class:
- if compile_state._polymorphic_adapters:
- adapter = compile_state._polymorphic_adapters.get(
- self.mapper, None
- )
- else:
- adapter = self.entity_zero._adapter
- if adapter:
- if compile_state._from_obj_alias:
- ret = adapter.wrap(compile_state._from_obj_alias)
- else:
- ret = adapter
- else:
- ret = compile_state._from_obj_alias
- return ret
- def row_processor(self, context, result):
- compile_state = context.compile_state
- adapter = self._get_entity_clauses(compile_state)
- if compile_state.compound_eager_adapter and adapter:
- adapter = adapter.wrap(compile_state.compound_eager_adapter)
- elif not adapter:
- adapter = compile_state.compound_eager_adapter
- if compile_state._primary_entity is self:
- only_load_props = compile_state.compile_options._only_load_props
- refresh_state = context.refresh_state
- else:
- only_load_props = refresh_state = None
- _instance = loading._instance_processor(
- self,
- self.mapper,
- context,
- result,
- self.path,
- adapter,
- only_load_props=only_load_props,
- refresh_state=refresh_state,
- polymorphic_discriminator=self._polymorphic_discriminator,
- )
- return _instance, self._label_name, self._extra_entities
- def setup_compile_state(self, compile_state):
- adapter = self._get_entity_clauses(compile_state)
- single_table_crit = self.mapper._single_table_criterion
- if (
- single_table_crit is not None
- or ("additional_entity_criteria", self.mapper)
- in compile_state.global_attributes
- ):
- ext_info = self.entity_zero
- compile_state.extra_criteria_entities[ext_info] = (
- ext_info,
- ext_info._adapter if ext_info.is_aliased_class else None,
- )
- loading._setup_entity_query(
- compile_state,
- self.mapper,
- self,
- self.path,
- adapter,
- compile_state.primary_columns,
- with_polymorphic=self._with_polymorphic_mappers,
- only_load_props=compile_state.compile_options._only_load_props,
- polymorphic_discriminator=self._polymorphic_discriminator,
- )
- compile_state._fallback_from_clauses.append(self.selectable)
- class _BundleEntity(_QueryEntity):
- _extra_entities = ()
- __slots__ = (
- "bundle",
- "expr",
- "type",
- "_label_name",
- "_entities",
- "supports_single_entity",
- )
- def __init__(
- self,
- compile_state,
- expr,
- entities_collection,
- setup_entities=True,
- parent_bundle=None,
- ):
- compile_state._has_orm_entities = True
- expr = expr._annotations["bundle"]
- if parent_bundle:
- parent_bundle._entities.append(self)
- else:
- entities_collection.append(self)
- if isinstance(
- expr, (attributes.QueryableAttribute, interfaces.PropComparator)
- ):
- bundle = expr.__clause_element__()
- else:
- bundle = expr
- self.bundle = self.expr = bundle
- self.type = type(bundle)
- self._label_name = bundle.name
- self._entities = []
- if setup_entities:
- for expr in bundle.exprs:
- if "bundle" in expr._annotations:
- _BundleEntity(
- compile_state,
- expr,
- entities_collection,
- parent_bundle=self,
- )
- elif isinstance(expr, Bundle):
- _BundleEntity(
- compile_state,
- expr,
- entities_collection,
- parent_bundle=self,
- )
- else:
- _ORMColumnEntity._for_columns(
- compile_state,
- [expr],
- entities_collection,
- None,
- parent_bundle=self,
- )
- self.supports_single_entity = self.bundle.single_entity
- if (
- self.supports_single_entity
- and not compile_state.compile_options._use_legacy_query_style
- ):
- util.warn_deprecated_20(
- "The Bundle.single_entity flag has no effect when "
- "using 2.0 style execution."
- )
- @property
- def mapper(self):
- ezero = self.entity_zero
- if ezero is not None:
- return ezero.mapper
- else:
- return None
- @property
- def entity_zero(self):
- for ent in self._entities:
- ezero = ent.entity_zero
- if ezero is not None:
- return ezero
- else:
- return None
- def corresponds_to(self, entity):
- # TODO: we might be able to implement this but for now
- # we are working around it
- return False
- @property
- def entity_zero_or_selectable(self):
- for ent in self._entities:
- ezero = ent.entity_zero_or_selectable
- if ezero is not None:
- return ezero
- else:
- return None
- def setup_compile_state(self, compile_state):
- for ent in self._entities:
- ent.setup_compile_state(compile_state)
- def row_processor(self, context, result):
- procs, labels, extra = zip(
- *[ent.row_processor(context, result) for ent in self._entities]
- )
- proc = self.bundle.create_row_processor(context.query, procs, labels)
- return proc, self._label_name, self._extra_entities
- class _ColumnEntity(_QueryEntity):
- __slots__ = (
- "_fetch_column",
- "_row_processor",
- "raw_column_index",
- "translate_raw_column",
- )
- @classmethod
- def _for_columns(
- cls,
- compile_state,
- columns,
- entities_collection,
- raw_column_index,
- parent_bundle=None,
- ):
- for column in columns:
- annotations = column._annotations
- if "parententity" in annotations:
- _entity = annotations["parententity"]
- else:
- _entity = sql_util.extract_first_column_annotation(
- column, "parententity"
- )
- if _entity:
- if "identity_token" in column._annotations:
- _IdentityTokenEntity(
- compile_state,
- column,
- entities_collection,
- _entity,
- raw_column_index,
- parent_bundle=parent_bundle,
- )
- else:
- _ORMColumnEntity(
- compile_state,
- column,
- entities_collection,
- _entity,
- raw_column_index,
- parent_bundle=parent_bundle,
- )
- else:
- _RawColumnEntity(
- compile_state,
- column,
- entities_collection,
- raw_column_index,
- parent_bundle=parent_bundle,
- )
- @property
- def type(self):
- return self.column.type
- @property
- def _non_hashable_value(self):
- return not self.column.type.hashable
- @property
- def _null_column_type(self):
- return self.column.type._isnull
- def row_processor(self, context, result):
- compile_state = context.compile_state
- # the resulting callable is entirely cacheable so just return
- # it if we already made one
- if self._row_processor is not None:
- getter, label_name, extra_entities = self._row_processor
- if self.translate_raw_column:
- extra_entities += (
- result.context.invoked_statement._raw_columns[
- self.raw_column_index
- ],
- )
- return getter, label_name, extra_entities
- # retrieve the column that would have been set up in
- # setup_compile_state, to avoid doing redundant work
- if self._fetch_column is not None:
- column = self._fetch_column
- else:
- # fetch_column will be None when we are doing a from_statement
- # and setup_compile_state may not have been called.
- column = self.column
- # previously, the RawColumnEntity didn't look for from_obj_alias
- # however I can't think of a case where we would be here and
- # we'd want to ignore it if this is the from_statement use case.
- # it's not really a use case to have raw columns + from_statement
- if compile_state._from_obj_alias:
- column = compile_state._from_obj_alias.columns[column]
- if column._annotations:
- # annotated columns perform more slowly in compiler and
- # result due to the __eq__() method, so use deannotated
- column = column._deannotate()
- if compile_state.compound_eager_adapter:
- column = compile_state.compound_eager_adapter.columns[column]
- getter = result._getter(column)
- ret = getter, self._label_name, self._extra_entities
- self._row_processor = ret
- if self.translate_raw_column:
- extra_entities = self._extra_entities + (
- result.context.invoked_statement._raw_columns[
- self.raw_column_index
- ],
- )
- return getter, self._label_name, extra_entities
- else:
- return ret
- class _RawColumnEntity(_ColumnEntity):
- entity_zero = None
- mapper = None
- supports_single_entity = False
- __slots__ = (
- "expr",
- "column",
- "_label_name",
- "entity_zero_or_selectable",
- "_extra_entities",
- )
- def __init__(
- self,
- compile_state,
- column,
- entities_collection,
- raw_column_index,
- parent_bundle=None,
- ):
- self.expr = column
- self.raw_column_index = raw_column_index
- self.translate_raw_column = raw_column_index is not None
- if column._is_text_clause:
- self._label_name = None
- else:
- self._label_name = compile_state._label_convention(column)
- if parent_bundle:
- parent_bundle._entities.append(self)
- else:
- entities_collection.append(self)
- self.column = column
- self.entity_zero_or_selectable = (
- self.column._from_objects[0] if self.column._from_objects else None
- )
- self._extra_entities = (self.expr, self.column)
- self._fetch_column = self._row_processor = None
- def corresponds_to(self, entity):
- return False
- def setup_compile_state(self, compile_state):
- current_adapter = compile_state._get_current_adapter()
- if current_adapter:
- column = current_adapter(self.column, False)
- else:
- column = self.column
- if column._annotations:
- # annotated columns perform more slowly in compiler and
- # result due to the __eq__() method, so use deannotated
- column = column._deannotate()
- compile_state.dedupe_columns.add(column)
- compile_state.primary_columns.append(column)
- self._fetch_column = column
- class _ORMColumnEntity(_ColumnEntity):
- """Column/expression based entity."""
- supports_single_entity = False
- __slots__ = (
- "expr",
- "mapper",
- "column",
- "_label_name",
- "entity_zero_or_selectable",
- "entity_zero",
- "_extra_entities",
- )
- def __init__(
- self,
- compile_state,
- column,
- entities_collection,
- parententity,
- raw_column_index,
- parent_bundle=None,
- ):
- annotations = column._annotations
- _entity = parententity
- # an AliasedClass won't have proxy_key in the annotations for
- # a column if it was acquired using the class' adapter directly,
- # such as using AliasedInsp._adapt_element(). this occurs
- # within internal loaders.
- orm_key = annotations.get("proxy_key", None)
- proxy_owner = annotations.get("proxy_owner", _entity)
- if orm_key:
- self.expr = getattr(proxy_owner.entity, orm_key)
- self.translate_raw_column = False
- else:
- # if orm_key is not present, that means this is an ad-hoc
- # SQL ColumnElement, like a CASE() or other expression.
- # include this column position from the invoked statement
- # in the ORM-level ResultSetMetaData on each execute, so that
- # it can be targeted by identity after caching
- self.expr = column
- self.translate_raw_column = raw_column_index is not None
- self.raw_column_index = raw_column_index
- self._label_name = compile_state._label_convention(
- column, col_name=orm_key
- )
- _entity._post_inspect
- self.entity_zero = self.entity_zero_or_selectable = ezero = _entity
- self.mapper = mapper = _entity.mapper
- if parent_bundle:
- parent_bundle._entities.append(self)
- else:
- entities_collection.append(self)
- compile_state._has_orm_entities = True
- self.column = column
- self._fetch_column = self._row_processor = None
- self._extra_entities = (self.expr, self.column)
- if (
- mapper.with_polymorphic
- or mapper.inherits
- or mapper._requires_row_aliasing
- ):
- compile_state._create_with_polymorphic_adapter(
- ezero, ezero.selectable
- )
- def corresponds_to(self, entity):
- if _is_aliased_class(entity):
- # TODO: polymorphic subclasses ?
- return entity is self.entity_zero
- else:
- return not _is_aliased_class(
- self.entity_zero
- ) and entity.common_parent(self.entity_zero)
- def setup_compile_state(self, compile_state):
- current_adapter = compile_state._get_current_adapter()
- if current_adapter:
- column = current_adapter(self.column, False)
- else:
- column = self.column
- ezero = self.entity_zero
- single_table_crit = self.mapper._single_table_criterion
- if (
- single_table_crit is not None
- or ("additional_entity_criteria", self.mapper)
- in compile_state.global_attributes
- ):
- compile_state.extra_criteria_entities[ezero] = (
- ezero,
- ezero._adapter if ezero.is_aliased_class else None,
- )
- if column._annotations and not column._expression_label:
- # annotated columns perform more slowly in compiler and
- # result due to the __eq__() method, so use deannotated
- column = column._deannotate()
- # use entity_zero as the from if we have it. this is necessary
- # for polymorphic scenarios where our FROM is based on ORM entity,
- # not the FROM of the column. but also, don't use it if our column
- # doesn't actually have any FROMs that line up, such as when its
- # a scalar subquery.
- if set(self.column._from_objects).intersection(
- ezero.selectable._from_objects
- ):
- compile_state._fallback_from_clauses.append(ezero.selectable)
- compile_state.dedupe_columns.add(column)
- compile_state.primary_columns.append(column)
- self._fetch_column = column
- class _IdentityTokenEntity(_ORMColumnEntity):
- translate_raw_column = False
- def setup_compile_state(self, compile_state):
- pass
- def row_processor(self, context, result):
- def getter(row):
- return context.load_options._refresh_identity_token
- return getter, self._label_name, self._extra_entities
|