summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/orm')
-rw-r--r--lib/sqlalchemy/orm/loading.py23
-rw-r--r--lib/sqlalchemy/orm/query.py60
-rw-r--r--lib/sqlalchemy/orm/strategies.py31
3 files changed, 87 insertions, 27 deletions
diff --git a/lib/sqlalchemy/orm/loading.py b/lib/sqlalchemy/orm/loading.py
index 3599aa3e7..1728b2d37 100644
--- a/lib/sqlalchemy/orm/loading.py
+++ b/lib/sqlalchemy/orm/loading.py
@@ -180,23 +180,37 @@ def load_on_ident(query, key,
else:
ident = None
+ return load_on_pk_identity(
+ query, ident, refresh_state=refresh_state,
+ with_for_update=with_for_update,
+ only_load_props=only_load_props
+ )
+
+
+def load_on_pk_identity(query, primary_key_identity,
+ refresh_state=None, with_for_update=None,
+ only_load_props=None):
+
+ """Load the given primary key identity from the database."""
+
if refresh_state is None:
q = query._clone()
q._get_condition()
else:
q = query._clone()
- if ident is not None:
+ if primary_key_identity is not None:
mapper = query._mapper_zero()
(_get_clause, _get_params) = mapper._get_clause
# None present in ident - turn those comparisons
# into "IS NULL"
- if None in ident:
+ if None in primary_key_identity:
nones = set([
_get_params[col].key for col, value in
- zip(mapper.primary_key, ident) if value is None
+ zip(mapper.primary_key, primary_key_identity)
+ if value is None
])
_get_clause = sql_util.adapt_criterion_to_null(
_get_clause, nones)
@@ -206,7 +220,8 @@ def load_on_ident(query, key,
params = dict([
(_get_params[primary_key].key, id_val)
- for id_val, primary_key in zip(ident, mapper.primary_key)
+ for id_val, primary_key
+ in zip(primary_key_identity, mapper.primary_key)
])
q._params = params
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index 42f1b2673..6d2b144e3 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -879,32 +879,68 @@ class Query(object):
"""
return self._get_impl(
- ident, loading.load_on_ident)
+ ident, loading.load_on_pk_identity)
- def _get_impl(self, ident, fallback_fn, identity_token=None):
+ @classmethod
+ def _identity_lookup(
+ cls, session, mapper, primary_key_identity, identity_token=None,
+ passive=attributes.PASSIVE_OFF):
+ """Locate an object in the identity map.
+
+ Given a primary key identity, constructs an identity key and then
+ looks in the session's identity map. If present, the object may
+ be run through unexpiration rules (e.g. load unloaded attributes,
+ check if was deleted).
+
+ :param session: Session in use
+ :param mapper: target mapper
+ :param primary_key_identity: the primary key we are searching for, as
+ a tuple.
+ :param identity_token: identity token that should be used to create
+ the identity key. Used as is, however overriding subclasses can
+ repurpose this in order to interpret the value in a special way,
+ such as if None then look among multple target tokens.
+ :param passive: passive load flag passed to
+ :func:`.loading.get_from_identity`, which impacts the behavior if
+ the object is found; the object may be validated and/or unexpired
+ if the flag allows for SQL to be emitted.
+ :return: None if the object is not found in the identity map, *or*
+ if the object was unexpired and found to have been deleted.
+ if passive flags disallow SQL and the object is expired, returns
+ PASSIVE_NO_RESULT. In all other cases the instance is returned.
+
+ .. versionadded:: 1.2.7
+
+ """
+ key = mapper.identity_key_from_primary_key(
+ primary_key_identity, identity_token=identity_token)
+ return loading.get_from_identity(
+ session, key, passive)
+
+ def _get_impl(
+ self, primary_key_identity, db_load_fn, identity_token=None):
# convert composite types to individual args
- if hasattr(ident, '__composite_values__'):
- ident = ident.__composite_values__()
+ if hasattr(primary_key_identity, '__composite_values__'):
+ primary_key_identity = primary_key_identity.__composite_values__()
- ident = util.to_list(ident)
+ primary_key_identity = util.to_list(primary_key_identity)
mapper = self._only_full_mapper_zero("get")
- if len(ident) != len(mapper.primary_key):
+ if len(primary_key_identity) != len(mapper.primary_key):
raise sa_exc.InvalidRequestError(
"Incorrect number of values in identifier to formulate "
"primary key for query.get(); primary key columns are %s" %
','.join("'%s'" % c for c in mapper.primary_key))
- key = mapper.identity_key_from_primary_key(
- ident, identity_token=identity_token)
-
if not self._populate_existing and \
not mapper.always_refresh and \
self._for_update_arg is None:
- instance = loading.get_from_identity(
- self.session, key, attributes.PASSIVE_OFF)
+ instance = self._identity_lookup(
+ self.session, mapper, primary_key_identity,
+ identity_token=identity_token)
+
if instance is not None:
self._get_existing_condition()
# reject calls for id in identity map but class
@@ -913,7 +949,7 @@ class Query(object):
return None
return instance
- return fallback_fn(self, key)
+ return db_load_fn(self, primary_key_identity)
@_generative()
def correlate(self, *args):
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 4312747ac..00c83cea4 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -576,7 +576,7 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
return attributes.ATTR_EMPTY
pending = not state.key
- ident_key = None
+ primary_key_identity = None
if (
(not passive & attributes.SQL_OK and not self.use_get)
@@ -599,28 +599,36 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
# if we have a simple primary key load, check the
# identity map without generating a Query at all
if self.use_get:
- ident = self._get_ident_for_use_get(
+ primary_key_identity = self._get_ident_for_use_get(
session,
state,
passive
)
- if attributes.PASSIVE_NO_RESULT in ident:
+ if attributes.PASSIVE_NO_RESULT in primary_key_identity:
return attributes.PASSIVE_NO_RESULT
- elif attributes.NEVER_SET in ident:
+ elif attributes.NEVER_SET in primary_key_identity:
return attributes.NEVER_SET
- if _none_set.issuperset(ident):
+ if _none_set.issuperset(primary_key_identity):
return None
- ident_key = self.mapper.identity_key_from_primary_key(ident)
- instance = loading.get_from_identity(session, ident_key, passive)
+ # look for this identity in the identity map. Delegate to the
+ # Query class in use, as it may have special rules for how it
+ # does this, including how it decides what the correct
+ # identity_token would be for this identity
+ instance = session._query_cls._identity_lookup(
+ session, self.mapper, primary_key_identity,
+ passive=passive
+ )
+
if instance is not None:
return instance
elif not passive & attributes.SQL_OK or \
not passive & attributes.RELATED_OBJECT_OK:
return attributes.PASSIVE_NO_RESULT
- return self._emit_lazyload(session, state, ident_key, passive)
+ return self._emit_lazyload(
+ session, state, primary_key_identity, passive)
def _get_ident_for_use_get(self, session, state, passive):
instance_mapper = state.manager.mapper
@@ -648,7 +656,8 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
@util.dependencies(
"sqlalchemy.orm.strategy_options")
def _emit_lazyload(
- self, strategy_options, session, state, ident_key, passive):
+ self, strategy_options, session, state,
+ primary_key_identity, passive):
# emit lazy load now using BakedQuery, to cut way down on the overhead
# of generating queries.
# there are two big things we are trying to guard against here:
@@ -707,8 +716,8 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
if self.use_get:
if self._raise_on_sql:
self._invoke_raise_load(state, passive, "raise_on_sql")
- return q(session)._load_on_ident(
- session.query(self.mapper), ident_key)
+ return q(session)._load_on_pk_identity(
+ session.query(self.mapper), primary_key_identity)
if self.parent_property.order_by:
q.add_criteria(