summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/orm/interfaces.py4
-rw-r--r--lib/sqlalchemy/orm/session.py41
-rw-r--r--lib/sqlalchemy/orm/util.py42
3 files changed, 75 insertions, 12 deletions
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py
index 64f561cbd..bacec422c 100644
--- a/lib/sqlalchemy/orm/interfaces.py
+++ b/lib/sqlalchemy/orm/interfaces.py
@@ -724,8 +724,8 @@ class ORMOption(ExecutableOption):
propagate_to_loaders = False
"""if True, indicate this option should be carried along
- to "secondary" Query objects produced during lazy loads
- or refresh operations.
+ to "secondary" SELECT statements that occur for relationship
+ lazy loaders as well as attribute load / refresh operations.
"""
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index 2a531b118..f6943cc5f 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -272,6 +272,8 @@ class ORMExecuteState(util.MemoizedSlots):
self.local_execution_options = self.local_execution_options.union(opts)
def _orm_compile_options(self):
+ if not self.is_select:
+ return None
opts = self.statement._compile_options
if isinstance(opts, context.ORMCompileState.default_compile_options):
return opts
@@ -308,6 +310,33 @@ class ORMExecuteState(util.MemoizedSlots):
return None
@property
+ def is_column_load(self):
+ """Return True if the operation is refreshing column-oriented
+ attributes on an existing ORM object.
+
+ This occurs during operations such as :meth:`_orm.Session.refresh`,
+ as well as when an attribute deferred by :func:`_orm.defer` is
+ being loaded, or an attribute that was expired either directly
+ by :meth:`_orm.Session.expire` or via a commit operation is being
+ loaded.
+
+ Handlers will very likely not want to add any options to queries
+ when such an operation is occurring as the query should be a straight
+ primary key fetch which should not have any additional WHERE criteria,
+ and loader options travelling with the instance
+ will have already been added to the query.
+
+ .. versionadded:: 1.4.0b2
+
+ .. seealso::
+
+ :attr:`_orm.ORMExecuteState.is_relationship_load`
+
+ """
+ opts = self._orm_compile_options()
+ return opts is not None and opts._for_refresh_state
+
+ @property
def is_relationship_load(self):
"""Return True if this load is loading objects on behalf of a
relationship.
@@ -317,7 +346,19 @@ class ORMExecuteState(util.MemoizedSlots):
SELECT statement being emitted is on behalf of a relationship
load.
+ Handlers will very likely not want to add any options to queries
+ when such an operation is occurring, as loader options are already
+ capable of being propigated to relationship loaders and should
+ be already present.
+
+ .. seealso::
+
+ :attr:`_orm.ORMExecuteState.is_column_load`
+
"""
+ opts = self._orm_compile_options()
+ if opts is None:
+ return False
path = self.loader_strategy_path
return path is not None and not path.is_root
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index 886ae9a11..c9437d1b2 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -935,17 +935,38 @@ class LoaderCriteriaOption(CriteriaOption):
@event.listens_for("do_orm_execute", session)
def _add_filtering_criteria(execute_state):
- execute_state.statement = execute_state.statement.options(
- with_loader_criteria(
- SecurityRole,
- lambda cls: cls.role.in_(['some_role']),
- include_aliases=True
- )
- )
- The given class will expand to include all mapped subclass and
- need not itself be a mapped class.
+ if (
+ execute_state.is_select
+ and not execute_state.is_column_load
+ and not execute_state.is_relationship_load
+ ):
+ execute_state.statement = execute_state.statement.options(
+ with_loader_criteria(
+ SecurityRole,
+ lambda cls: cls.role.in_(['some_role']),
+ include_aliases=True
+ )
+ )
+ In the above example, the :meth:`_orm.SessionEvents.do_orm_execute`
+ event will intercept all queries emitted using the
+ :class:`_orm.Session`. For those queries which are SELECT statements
+ and are not attribute or relationship loads a custom
+ :func:`_orm.with_loader_criteria` option is added to the query. The
+ :func:`_orm.with_loader_criteria` option will be used in the given
+ statement and will also be automatically propagated to all relationship
+ loads that descend from this query.
+
+ The criteria argument given is a ``lambda`` that accepts a ``cls``
+ argument. The given class will expand to include all mapped subclass
+ and need not itself be a mapped class.
+
+ .. warning:: The use of a lambda inside of the call to
+ :func:`_orm.with_loader_criteria` is only invoked **once per unique
+ class**. Custom functions should not be invoked within this lambda.
+ See :ref:`engine_lambda_caching` for an overview of the "lambda SQL"
+ feature, which is for advanced use only.
:param entity_or_base: a mapped class, or a class that is a super
class of a particular set of mapped classes, to which the rule
@@ -1028,7 +1049,8 @@ class LoaderCriteriaOption(CriteriaOption):
# if options to limit the criteria to immediate query only,
# use compile_state.attributes instead
- self.get_global_criteria(compile_state.global_attributes)
+ if not compile_state.compile_options._for_refresh_state:
+ self.get_global_criteria(compile_state.global_attributes)
def get_global_criteria(self, attributes):
for mp in self._all_mappers():