summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/orm')
-rw-r--r--lib/sqlalchemy/orm/context.py10
-rw-r--r--lib/sqlalchemy/orm/loading.py130
-rw-r--r--lib/sqlalchemy/orm/mapper.py36
-rw-r--r--lib/sqlalchemy/orm/query.py3
-rw-r--r--lib/sqlalchemy/orm/strategies.py131
-rw-r--r--lib/sqlalchemy/orm/util.py12
6 files changed, 124 insertions, 198 deletions
diff --git a/lib/sqlalchemy/orm/context.py b/lib/sqlalchemy/orm/context.py
index 603477819..83b6586cc 100644
--- a/lib/sqlalchemy/orm/context.py
+++ b/lib/sqlalchemy/orm/context.py
@@ -1150,11 +1150,11 @@ class ORMSelectCompileState(ORMCompileState, SelectState):
):
Select = future.Select
- statement = Select.__new__(Select)
- statement._raw_columns = raw_columns
- statement._from_obj = from_obj
-
- statement._label_style = label_style
+ 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
diff --git a/lib/sqlalchemy/orm/loading.py b/lib/sqlalchemy/orm/loading.py
index abc8780ed..42ece864c 100644
--- a/lib/sqlalchemy/orm/loading.py
+++ b/lib/sqlalchemy/orm/loading.py
@@ -32,6 +32,7 @@ from ..engine.result import FrozenResult
from ..engine.result import SimpleResultMetaData
from ..sql import util as sql_util
from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
+from ..sql.selectable import SelectState
_new_runid = util.counter()
@@ -431,7 +432,7 @@ def load_on_pk_identity(
query = statement
q = query._clone()
- is_lambda = q._is_lambda_element
+ assert not q._is_lambda_element
# TODO: fix these imports ....
from .context import QueryContext, ORMCompileState
@@ -439,7 +440,13 @@ def load_on_pk_identity(
if load_options is None:
load_options = QueryContext.default_load_options
- compile_options = ORMCompileState.default_compile_options
+ if (
+ statement._compile_options
+ is SelectState.default_select_compile_options
+ ):
+ compile_options = ORMCompileState.default_compile_options
+ else:
+ compile_options = statement._compile_options
if primary_key_identity is not None:
mapper = query._propagate_attrs["plugin_subject"]
@@ -468,24 +475,9 @@ def load_on_pk_identity(
"release."
)
- if is_lambda:
- q = q.add_criteria(
- lambda q: q.where(
- sql_util._deep_annotate(_get_clause, {"_orm_adapt": True})
- ),
- # this track_on will allow the lambda to refresh if
- # _get_clause goes stale due to reconfigured mapper.
- # however, it's not needed as the lambda otherwise tracks
- # on the SQL cache key of the expression. the main thing
- # is that the bindparam.key stays the same if the cache key
- # stays the same, as we are referring to the .key explicitly
- # in the params.
- # track_on=[id(_get_clause)]
- )
- else:
- q._where_criteria = (
- sql_util._deep_annotate(_get_clause, {"_orm_adapt": True}),
- )
+ q._where_criteria = (
+ sql_util._deep_annotate(_get_clause, {"_orm_adapt": True}),
+ )
params = dict(
[
@@ -498,57 +490,32 @@ def load_on_pk_identity(
else:
params = None
- if is_lambda:
- if with_for_update is not None or refresh_state or only_load_props:
- raise NotImplementedError(
- "refresh operation not supported with lambda statement"
- )
-
+ if with_for_update is not None:
+ version_check = True
+ q._for_update_arg = with_for_update
+ elif query._for_update_arg is not None:
+ version_check = True
+ q._for_update_arg = query._for_update_arg
+ else:
version_check = False
- _, load_options = _set_get_options(
- compile_options,
- load_options,
- version_check=version_check,
- only_load_props=only_load_props,
- refresh_state=refresh_state,
- identity_token=identity_token,
- )
+ if refresh_state and refresh_state.load_options:
+ compile_options += {"_current_path": refresh_state.load_path.parent}
+ q = q.options(*refresh_state.load_options)
- if no_autoflush:
- load_options += {"_autoflush": False}
- else:
- if with_for_update is not None:
- version_check = True
- q._for_update_arg = with_for_update
- elif query._for_update_arg is not None:
- version_check = True
- q._for_update_arg = query._for_update_arg
- else:
- version_check = False
-
- if refresh_state and refresh_state.load_options:
- compile_options += {
- "_current_path": refresh_state.load_path.parent
- }
- q = q.options(*refresh_state.load_options)
-
- # TODO: most of the compile_options that are not legacy only involve
- # this function, so try to see if handling of them can mostly be local
- # to here
-
- q._compile_options, load_options = _set_get_options(
- compile_options,
- load_options,
- version_check=version_check,
- only_load_props=only_load_props,
- refresh_state=refresh_state,
- identity_token=identity_token,
- )
- q._order_by = None
+ new_compile_options, load_options = _set_get_options(
+ compile_options,
+ load_options,
+ version_check=version_check,
+ only_load_props=only_load_props,
+ refresh_state=refresh_state,
+ identity_token=identity_token,
+ )
+ q._compile_options = new_compile_options
+ q._order_by = None
- if no_autoflush:
- load_options += {"_autoflush": False}
+ if no_autoflush:
+ load_options += {"_autoflush": False}
execution_options = util.EMPTY_DICT.merge_with(
execution_options, {"_sa_orm_load_options": load_options}
@@ -1110,21 +1077,24 @@ def _load_subclass_via_in(context, path, entity):
def do_load(context, path, states, load_only, effective_entity):
orig_query = context.query
- q2 = q._with_lazyload_options(
- (enable_opt,) + orig_query._with_options + (disable_opt,),
- path.parent,
- cache_path=path,
- )
+ options = (enable_opt,) + orig_query._with_options + (disable_opt,)
+ q2 = q.options(*options)
- if context.populate_existing:
- q2.add_criteria(lambda q: q.populate_existing())
+ q2._compile_options = context.compile_state.default_compile_options
+ q2._compile_options += {"_current_path": path.parent}
- q2(context.session).params(
- primary_keys=[
- state.key[1][0] if zero_idx else state.key[1]
- for state, load_attrs in states
- ]
- ).all()
+ if context.populate_existing:
+ q2 = q2.execution_options(populate_existing=True)
+
+ context.session.execute(
+ q2,
+ dict(
+ primary_keys=[
+ state.key[1][0] if zero_idx else state.key[1]
+ for state, load_attrs in states
+ ]
+ ),
+ ).unique().scalars().all()
return do_load
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index 530c0a112..5eee134d5 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -3047,16 +3047,13 @@ class Mapper(
return None
- @util.preload_module(
- "sqlalchemy.ext.baked", "sqlalchemy.orm.strategy_options"
- )
+ @util.preload_module("sqlalchemy.orm.strategy_options")
def _subclass_load_via_in(self, entity):
- """Assemble a BakedQuery that can load the columns local to
+ """Assemble a that can load the columns local to
this subclass as a SELECT with IN.
"""
strategy_options = util.preloaded.orm_strategy_options
- baked = util.preloaded.ext_baked
assert self.inherits
@@ -3094,24 +3091,23 @@ class Mapper(
if entity.is_aliased_class:
assert entity.mapper is self
- q = baked.BakedQuery(
- self._compiled_cache,
- lambda session: session.query(entity).select_entity_from(
- entity.selectable
- ),
- (self,),
+ q = sql.select(entity).set_label_style(
+ LABEL_STYLE_TABLENAME_PLUS_COL
)
- q.spoil()
+
+ in_expr = entity._adapter.traverse(in_expr)
+ primary_key = [entity._adapter.traverse(k) for k in primary_key]
+ q = q.where(
+ in_expr.in_(sql.bindparam("primary_keys", expanding=True))
+ ).order_by(*primary_key)
else:
- q = baked.BakedQuery(
- self._compiled_cache,
- lambda session: session.query(self),
- (self,),
- )
- q += lambda q: q.filter(
- in_expr.in_(sql.bindparam("primary_keys", expanding=True))
- ).order_by(*primary_key)
+ q = sql.select(self).set_label_style(
+ LABEL_STYLE_TABLENAME_PLUS_COL
+ )
+ q = q.where(
+ in_expr.in_(sql.bindparam("primary_keys", expanding=True))
+ ).order_by(*primary_key)
return q, enable_opt, disable_opt
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index 9a97d37b0..a1fb16a3a 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -444,8 +444,7 @@ class Query(
)
else:
# Query / select() internal attributes are 99% cross-compatible
- stmt = Select.__new__(Select)
- stmt.__dict__.update(self.__dict__)
+ stmt = Select._create_raw_select(**self.__dict__)
stmt.__dict__.update(
_label_style=self._label_style,
_compile_options=compile_options,
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 587daa332..955cd6dd2 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -43,6 +43,7 @@ from .. import util
from ..sql import util as sql_util
from ..sql import visitors
from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
+from ..sql.selectable import Select
def _register_attribute(
@@ -631,7 +632,6 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
"_simple_lazy_clause",
"_raise_always",
"_raise_on_sql",
- "_lambda_cache",
)
def __init__(self, parent, strategy_key):
@@ -913,13 +913,6 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
for pk in self.mapper.primary_key
]
- def _memoized_attr__lambda_cache(self):
- # cache is per lazy loader, and is used for caching of
- # sqlalchemy.sql.lambdas.AnalyzedCode and
- # sqlalchemy.sql.lambdas.AnalyzedFunction objects which are generated
- # from the StatementLambda used.
- return util.LRUCache(30)
-
@util.preload_module("sqlalchemy.orm.strategy_options")
def _emit_lazyload(
self,
@@ -932,18 +925,13 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
):
strategy_options = util.preloaded.orm_strategy_options
- stmt = sql.lambda_stmt(
- lambda: sql.select(self.entity)
- .set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
- ._set_compile_options(ORMCompileState.default_compile_options),
- global_track_bound_values=False,
- lambda_cache=self._lambda_cache,
- track_on=(self,),
+ clauseelement = self.entity.__clause_element__()
+ stmt = Select._create_raw_select(
+ _raw_columns=[clauseelement],
+ _propagate_attrs=clauseelement._propagate_attrs,
+ _label_style=LABEL_STYLE_TABLENAME_PLUS_COL,
+ _compile_options=ORMCompileState.default_compile_options,
)
-
- if not self.parent_property.bake_queries:
- stmt = stmt.spoil()
-
load_options = QueryContext.default_load_options
load_options += {
@@ -952,18 +940,15 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
}
if self.parent_property.secondary is not None:
- stmt = stmt.add_criteria(
- lambda stmt: stmt.select_from(
- self.mapper, self.parent_property.secondary
- ),
- track_on=[self.parent_property],
+ stmt = stmt.select_from(
+ self.mapper, self.parent_property.secondary
)
pending = not state.key
# don't autoflush on pending
if pending or passive & attributes.NO_AUTOFLUSH:
- stmt += lambda stmt: stmt.execution_options(autoflush=False)
+ stmt._execution_options = util.immutabledict({"autoflush": False})
use_get = self.use_get
@@ -978,15 +963,13 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
orm_util.LoaderCriteriaOption(self.entity, extra_criteria),
)
- stmt += lambda stmt: stmt.options(*opts)
+ stmt._with_options = opts
else:
# this path is used if there are not already any options
# in the query, but an event may want to add them
effective_path = state.mapper._path_registry[self.parent_property]
- stmt += lambda stmt: stmt._update_compile_options(
- {"_current_path": effective_path}
- )
+ stmt._compile_options += {"_current_path": effective_path}
if use_get:
if self._raise_on_sql and not passive & attributes.NO_RAISE:
@@ -997,9 +980,7 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
)
if self._order_by:
- stmt = stmt.add_criteria(
- lambda stmt: stmt.order_by(*self._order_by), track_on=[self]
- )
+ stmt._order_by_clauses = self._order_by
def _lazyload_reverse(compile_context):
for rev in self.parent_property._reverse_property:
@@ -1016,11 +997,8 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
]
).lazyload(rev).process_compile_state(compile_context)
- stmt = stmt.add_criteria(
- lambda stmt: stmt._add_context_option(
- _lazyload_reverse, self.parent_property
- ),
- track_on=[self],
+ stmt._with_context_options += (
+ (_lazyload_reverse, self.parent_property),
)
lazy_clause, params = self._generate_lazy_clause(state, passive)
@@ -1045,9 +1023,7 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
if self._raise_on_sql and not passive & attributes.NO_RAISE:
self._invoke_raise_load(state, passive, "raise_on_sql")
- stmt = stmt.add_criteria(
- lambda stmt: stmt.where(lazy_clause), enable_tracking=False
- )
+ stmt._where_criteria = (lazy_clause,)
result = session.execute(
stmt, params, execution_options=execution_options
@@ -2634,7 +2610,6 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
"_parent_alias",
"_query_info",
"_fallback_query_info",
- "_lambda_cache",
)
query_info = collections.namedtuple(
@@ -2738,13 +2713,6 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
(("lazy", "select"),)
).init_class_attribute(mapper)
- def _memoized_attr__lambda_cache(self):
- # cache is per lazy loader, and is used for caching of
- # sqlalchemy.sql.lambdas.AnalyzedCode and
- # sqlalchemy.sql.lambdas.AnalyzedFunction objects which are generated
- # from the StatementLambda used.
- return util.LRUCache(30)
-
def create_row_processor(
self,
context,
@@ -2879,25 +2847,19 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
]
in_expr = effective_entity._adapt_element(in_expr)
- q = sql.lambda_stmt(
- lambda: sql.select(
- orm_util.Bundle("pk", *pk_cols), effective_entity
- )
- .set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
- ._set_compile_options(ORMCompileState.default_compile_options)
- ._set_propagate_attrs(
- {
- "compile_state_plugin": "orm",
- "plugin_subject": effective_entity,
- }
- ),
- lambda_cache=self._lambda_cache,
- global_track_bound_values=False,
- track_on=(self, effective_entity) + (tuple(pk_cols),),
- )
+ bundle_ent = orm_util.Bundle("pk", *pk_cols)
+ bundle_sql = bundle_ent.__clause_element__()
- if not self.parent_property.bake_queries:
- q = q.spoil()
+ entity_sql = effective_entity.__clause_element__()
+ q = Select._create_raw_select(
+ _raw_columns=[bundle_sql, entity_sql],
+ _label_style=LABEL_STYLE_TABLENAME_PLUS_COL,
+ _compile_options=ORMCompileState.default_compile_options,
+ _propagate_attrs={
+ "compile_state_plugin": "orm",
+ "plugin_subject": effective_entity,
+ },
+ )
if not query_info.load_with_join:
# the Bundle we have in the "omit_join" case is against raw, non
@@ -2905,23 +2867,19 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
# entity, we add it explicitly. If we made the Bundle against
# annotated columns, we hit a performance issue in this specific
# case, which is detailed in issue #4347.
- q = q.add_criteria(lambda q: q.select_from(effective_entity))
+ q = q.select_from(effective_entity)
else:
# in the non-omit_join case, the Bundle is against the annotated/
# mapped column of the parent entity, but the #4347 issue does not
# occur in this case.
- q = q.add_criteria(
- lambda q: q.select_from(self._parent_alias).join(
- getattr(
- self._parent_alias, self.parent_property.key
- ).of_type(effective_entity)
- ),
- track_on=[self],
+ q = q.select_from(self._parent_alias).join(
+ getattr(self._parent_alias, self.parent_property.key).of_type(
+ effective_entity
+ )
)
- q = q.add_criteria(
- lambda q: q.filter(in_expr.in_(sql.bindparam("primary_keys")))
- )
+ q = q.filter(in_expr.in_(sql.bindparam("primary_keys")))
+
# a test which exercises what these comments talk about is
# test_selectin_relations.py -> test_twolevel_selectin_w_polymorphic
#
@@ -2968,16 +2926,12 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
),
)
- q = q.add_criteria(
- lambda q: q.options(*options)._update_compile_options(
- {"_current_path": effective_path}
- )
+ q = q.options(*options)._update_compile_options(
+ {"_current_path": effective_path}
)
if context.populate_existing:
- q = q.add_criteria(
- lambda q: q.execution_options(populate_existing=True)
- )
+ q = q.execution_options(populate_existing=True)
if self.parent_property.order_by:
if not query_info.load_with_join:
@@ -2987,7 +2941,7 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
effective_entity._adapt_element(elem)
for elem in eager_order_by
]
- q = q.add_criteria(lambda q: q.order_by(*eager_order_by))
+ q = q.order_by(*eager_order_by)
else:
def _setup_outermost_orderby(compile_context):
@@ -2995,11 +2949,8 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
util.to_list(self.parent_property.order_by)
)
- q = q.add_criteria(
- lambda q: q._add_context_option(
- _setup_outermost_orderby, self.parent_property
- ),
- track_on=[self],
+ q = q._add_context_option(
+ _setup_outermost_orderby, self.parent_property
)
if query_info.load_only_child:
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index 46bb3c943..01a8becc3 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -1345,7 +1345,12 @@ def with_polymorphic(
@inspection._self_inspects
-class Bundle(ORMColumnsClauseRole, SupportsCloneAnnotations, InspectionAttr):
+class Bundle(
+ ORMColumnsClauseRole,
+ SupportsCloneAnnotations,
+ sql_base.MemoizedHasCacheKey,
+ InspectionAttr,
+):
"""A grouping of SQL expressions that are returned by a :class:`.Query`
under one namespace.
@@ -1412,6 +1417,11 @@ class Bundle(ORMColumnsClauseRole, SupportsCloneAnnotations, InspectionAttr):
)
self.single_entity = kw.pop("single_entity", self.single_entity)
+ def _gen_cache_key(self, anon_map, bindparams):
+ return (self.__class__, self.name, self.single_entity) + tuple(
+ [expr._gen_cache_key(anon_map, bindparams) for expr in self.exprs]
+ )
+
@property
def mapper(self):
return self.exprs[0]._annotations.get("parentmapper", None)