"'%s' is not available due to lazy='%s'" % (self, lazy)
)
- def _load_for_state(self, state, passive, loadopt=None):
+ def _load_for_state(self, state, passive, loadopt=None, extra_criteria=()):
if not state.key and (
(
return attributes.PASSIVE_NO_RESULT
return self._emit_lazyload(
- session, state, primary_key_identity, passive, loadopt
+ session,
+ state,
+ primary_key_identity,
+ passive,
+ loadopt,
+ extra_criteria,
)
def _get_ident_for_use_get(self, session, state, passive):
@util.preload_module("sqlalchemy.orm.strategy_options")
def _emit_lazyload(
- self, session, state, primary_key_identity, passive, loadopt
+ self,
+ session,
+ state,
+ primary_key_identity,
+ passive,
+ loadopt,
+ extra_criteria,
):
strategy_options = util.preloaded.orm_strategy_options
use_get = self.use_get
if state.load_options or (loadopt and loadopt._extra_criteria):
-
effective_path = state.load_path[self.parent_property]
opts = list(state.load_options)
if loadopt and loadopt._extra_criteria:
use_get = False
opts += (
- orm_util.LoaderCriteriaOption(
- self.entity, sql.and_(*loadopt._extra_criteria)
- ),
+ orm_util.LoaderCriteriaOption(self.entity, extra_criteria),
)
stmt += lambda stmt: stmt.options(*opts)
# class-level lazyloader installed.
set_lazy_callable = (
InstanceState._instance_level_callable_processor
- )(mapper.class_manager, LoadLazyAttribute(key, self, loadopt), key)
+ )(
+ mapper.class_manager,
+ LoadLazyAttribute(
+ key,
+ self,
+ loadopt,
+ loadopt._generate_extra_criteria(context)
+ if loadopt._extra_criteria
+ else None,
+ ),
+ key,
+ )
populators["new"].append((self.key, set_lazy_callable))
elif context.populate_existing or mapper.always_refresh:
class LoadLazyAttribute(object):
- """serializable loader object used by LazyLoader"""
+ """semi-serializable loader object used by LazyLoader
+
+ Historically, this object would be carried along with instances that
+ needed to run lazyloaders, so it had to be serializable to support
+ cached instances.
- def __init__(self, key, initiating_strategy, loadopt):
+ this is no longer a general requirement, and the case where this object
+ is used is exactly the case where we can't really serialize easily,
+ which is when extra criteria in the loader option is present.
+
+ We can't reliably serialize that as it refers to mapped entities and
+ AliasedClass objects that are local to the current process, which would
+ need to be matched up on deserialize e.g. the sqlalchemy.ext.serializer
+ approach.
+
+ """
+
+ def __init__(self, key, initiating_strategy, loadopt, extra_criteria):
self.key = key
self.strategy_key = initiating_strategy.strategy_key
self.loadopt = loadopt
+ self.extra_criteria = extra_criteria
+
+ def __getstate__(self):
+ if self.extra_criteria is not None:
+ util.warn(
+ "Can't reliably serialize a lazyload() option that "
+ "contains additional criteria; please use eager loading "
+ "for this case"
+ )
+ return {
+ "key": self.key,
+ "strategy_key": self.strategy_key,
+ "loadopt": self.loadopt,
+ "extra_criteria": (),
+ }
def __call__(self, state, passive=attributes.PASSIVE_OFF):
key = self.key
prop = instance_mapper._props[key]
strategy = prop._strategies[self.strategy_key]
- return strategy._load_for_state(state, passive, loadopt=self.loadopt)
+ return strategy._load_for_state(
+ state,
+ passive,
+ loadopt=self.loadopt,
+ extra_criteria=self.extra_criteria,
+ )
class PostLoader(AbstractRelationshipLoader):
def _setup_options(
self,
+ context,
q,
subq_path,
rewritten_path,
effective_entity,
loadopt,
):
-
opts = orig_query._with_options
if loadopt and loadopt._extra_criteria:
+
opts += (
orm_util.LoaderCriteriaOption(
- self.entity, sql.and_(*loadopt._extra_criteria)
+ self.entity,
+ loadopt._generate_extra_criteria(context),
),
)
)
q = self._setup_options(
- q, subq_path, rewritten_path, orig_query, effective_entity, loadopt
+ context,
+ q,
+ subq_path,
+ rewritten_path,
+ orig_query,
+ effective_entity,
+ loadopt,
)
q = self._setup_outermost_orderby(q)
effective_path = path[self.parent_property]
options = orig_query._with_options
+
if loadopt and loadopt._extra_criteria:
options += (
orm_util.LoaderCriteriaOption(
- effective_entity, sql.and_(*loadopt._extra_criteria)
+ effective_entity,
+ loadopt._generate_extra_criteria(context),
),
)
User(addresses=[], id=10, name="chuck"),
]
+ def _user_minus_edlala(self, User, Address):
+ return [
+ User(
+ addresses=[
+ Address(email_address="jack@bean.com", id=1, user_id=7)
+ ],
+ id=7,
+ name="jack",
+ ),
+ User(
+ addresses=[
+ Address(email_address="ed@wood.com", id=2, user_id=8),
+ Address(
+ email_address="ed@bettyboop.com",
+ id=3,
+ user_id=8,
+ ),
+ ],
+ id=8,
+ name="ed",
+ ),
+ User(
+ addresses=[
+ Address(email_address="fred@fred.com", id=5, user_id=9)
+ ],
+ id=9,
+ name="fred",
+ ),
+ User(addresses=[], id=10, name="chuck"),
+ ]
+
def test_joinedload_local_criteria(self, user_address_fixture):
User, Address = user_address_fixture
s = Session(testing.db, future=True)
- stmt = (
- select(User)
- .options(
- joinedload(
- User.addresses.and_(Address.email_address != "ed@wood.com")
- ),
+ def go(value):
+ stmt = (
+ select(User)
+ .options(
+ joinedload(
+ User.addresses.and_(Address.email_address != value)
+ ),
+ )
+ .order_by(User.id)
)
- .order_by(User.id)
- )
+ result = s.execute(stmt)
+ return result
- with self.sql_execution_asserter() as asserter:
+ for value in "ed@wood.com", "ed@lala.com":
+ with self.sql_execution_asserter() as asserter:
- result = s.execute(stmt)
+ result = go(value)
- eq_(
- result.scalars().unique().all(),
- self._user_minus_edwood(*user_address_fixture),
- )
+ eq_(
+ result.scalars().unique().all(),
+ self._user_minus_edwood(*user_address_fixture)
+ if value == "ed@wood.com"
+ else self._user_minus_edlala(*user_address_fixture),
+ )
- asserter.assert_(
- CompiledSQL(
- "SELECT users.id, users.name, addresses_1.id AS id_1, "
- "addresses_1.user_id, addresses_1.email_address FROM "
- "users LEFT OUTER JOIN addresses AS addresses_1 "
- "ON users.id = addresses_1.user_id "
- "AND addresses_1.email_address != :email_address_1 "
- "ORDER BY users.id, addresses_1.id",
- [{"email_address_1": "ed@wood.com"}],
- ),
- )
+ asserter.assert_(
+ CompiledSQL(
+ "SELECT users.id, users.name, addresses_1.id AS id_1, "
+ "addresses_1.user_id, addresses_1.email_address FROM "
+ "users LEFT OUTER JOIN addresses AS addresses_1 "
+ "ON users.id = addresses_1.user_id "
+ "AND addresses_1.email_address != :email_address_1 "
+ "ORDER BY users.id, addresses_1.id",
+ [{"email_address_1": value}],
+ ),
+ )
def test_selectinload_local_criteria(self, user_address_fixture):
User, Address = user_address_fixture
s = Session(testing.db, future=True)
- stmt = (
- select(User)
- .options(
- selectinload(
- User.addresses.and_(Address.email_address != "ed@wood.com")
- ),
+ def go(value):
+ stmt = (
+ select(User)
+ .options(
+ selectinload(
+ User.addresses.and_(Address.email_address != value)
+ ),
+ )
+ .order_by(User.id)
)
- .order_by(User.id)
- )
+ result = s.execute(stmt)
+ return result
- with self.sql_execution_asserter() as asserter:
+ for value in "ed@wood.com", "ed@lala.com":
+ with self.sql_execution_asserter() as asserter:
+ result = go(value)
- result = s.execute(stmt)
+ eq_(
+ result.scalars().unique().all(),
+ self._user_minus_edwood(*user_address_fixture)
+ if value == "ed@wood.com"
+ else self._user_minus_edlala(*user_address_fixture),
+ )
- eq_(
- result.scalars().unique().all(),
- self._user_minus_edwood(*user_address_fixture),
+ asserter.assert_(
+ CompiledSQL(
+ "SELECT users.id, users.name FROM users ORDER BY users.id"
+ ),
+ CompiledSQL(
+ "SELECT addresses.user_id AS addresses_user_id, "
+ "addresses.id AS addresses_id, addresses.email_address "
+ "AS addresses_email_address FROM addresses "
+ "WHERE addresses.user_id IN ([POSTCOMPILE_primary_keys]) "
+ "AND addresses.email_address != :email_address_1 "
+ "ORDER BY addresses.id",
+ [
+ {
+ "primary_keys": [7, 8, 9, 10],
+ "email_address_1": value,
+ }
+ ],
+ ),
)
- asserter.assert_(
- CompiledSQL(
- "SELECT users.id, users.name FROM users ORDER BY users.id"
- ),
- CompiledSQL(
- "SELECT addresses.user_id AS addresses_user_id, "
- "addresses.id AS addresses_id, addresses.email_address "
- "AS addresses_email_address FROM addresses "
- "WHERE addresses.user_id IN ([POSTCOMPILE_primary_keys]) "
- "AND addresses.email_address != :email_address_1 "
- "ORDER BY addresses.id",
- [
- {
- "primary_keys": [7, 8, 9, 10],
- "email_address_1": "ed@wood.com",
- }
- ],
- ),
- )
-
def test_lazyload_local_criteria(self, user_address_fixture):
User, Address = user_address_fixture
s = Session(testing.db, future=True)
- stmt = (
- select(User)
- .options(
- lazyload(
- User.addresses.and_(Address.email_address != "ed@wood.com")
- ),
+ def go(value):
+
+ stmt = (
+ select(User)
+ .options(
+ lazyload(
+ User.addresses.and_(Address.email_address != value)
+ ),
+ )
+ .order_by(User.id)
)
- .order_by(User.id)
- )
+ result = s.execute(stmt)
+ return result
- with self.sql_execution_asserter() as asserter:
+ for value in "ed@wood.com", "ed@lala.com":
+ with self.sql_execution_asserter() as asserter:
- result = s.execute(stmt)
+ result = go(value)
- eq_(
- result.scalars().unique().all(),
- self._user_minus_edwood(*user_address_fixture),
- )
+ eq_(
+ result.scalars().unique().all(),
+ self._user_minus_edwood(*user_address_fixture)
+ if value == "ed@wood.com"
+ else self._user_minus_edlala(*user_address_fixture),
+ )
- asserter.assert_(
- CompiledSQL(
- "SELECT users.id, users.name FROM users ORDER BY users.id"
- ),
- CompiledSQL(
- "SELECT addresses.id AS addresses_id, "
- "addresses.user_id AS addresses_user_id, "
- "addresses.email_address AS addresses_email_address "
- "FROM addresses WHERE :param_1 = addresses.user_id "
- "AND addresses.email_address != :email_address_1 "
- "ORDER BY addresses.id",
- [{"param_1": 7, "email_address_1": "ed@wood.com"}],
- ),
- CompiledSQL(
- "SELECT addresses.id AS addresses_id, "
- "addresses.user_id AS addresses_user_id, "
- "addresses.email_address AS addresses_email_address "
- "FROM addresses WHERE :param_1 = addresses.user_id "
- "AND addresses.email_address != :email_address_1 "
- "ORDER BY addresses.id",
- [{"param_1": 8, "email_address_1": "ed@wood.com"}],
- ),
- CompiledSQL(
- "SELECT addresses.id AS addresses_id, "
- "addresses.user_id AS addresses_user_id, "
- "addresses.email_address AS addresses_email_address "
- "FROM addresses WHERE :param_1 = addresses.user_id "
- "AND addresses.email_address != :email_address_1 "
- "ORDER BY addresses.id",
- [{"param_1": 9, "email_address_1": "ed@wood.com"}],
- ),
- CompiledSQL(
- "SELECT addresses.id AS addresses_id, "
- "addresses.user_id AS addresses_user_id, "
- "addresses.email_address AS addresses_email_address "
- "FROM addresses WHERE :param_1 = addresses.user_id "
- "AND addresses.email_address != :email_address_1 "
- "ORDER BY addresses.id",
- [{"param_1": 10, "email_address_1": "ed@wood.com"}],
- ),
- )
+ asserter.assert_(
+ CompiledSQL(
+ "SELECT users.id, users.name FROM users ORDER BY users.id"
+ ),
+ CompiledSQL(
+ "SELECT addresses.id AS addresses_id, "
+ "addresses.user_id AS addresses_user_id, "
+ "addresses.email_address AS addresses_email_address "
+ "FROM addresses WHERE :param_1 = addresses.user_id "
+ "AND addresses.email_address != :email_address_1 "
+ "ORDER BY addresses.id",
+ [{"param_1": 7, "email_address_1": value}],
+ ),
+ CompiledSQL(
+ "SELECT addresses.id AS addresses_id, "
+ "addresses.user_id AS addresses_user_id, "
+ "addresses.email_address AS addresses_email_address "
+ "FROM addresses WHERE :param_1 = addresses.user_id "
+ "AND addresses.email_address != :email_address_1 "
+ "ORDER BY addresses.id",
+ [{"param_1": 8, "email_address_1": value}],
+ ),
+ CompiledSQL(
+ "SELECT addresses.id AS addresses_id, "
+ "addresses.user_id AS addresses_user_id, "
+ "addresses.email_address AS addresses_email_address "
+ "FROM addresses WHERE :param_1 = addresses.user_id "
+ "AND addresses.email_address != :email_address_1 "
+ "ORDER BY addresses.id",
+ [{"param_1": 9, "email_address_1": value}],
+ ),
+ CompiledSQL(
+ "SELECT addresses.id AS addresses_id, "
+ "addresses.user_id AS addresses_user_id, "
+ "addresses.email_address AS addresses_email_address "
+ "FROM addresses WHERE :param_1 = addresses.user_id "
+ "AND addresses.email_address != :email_address_1 "
+ "ORDER BY addresses.id",
+ [{"param_1": 10, "email_address_1": value}],
+ ),
+ )
def test_subqueryload_local_criteria(self, user_address_fixture):
User, Address = user_address_fixture
s = Session(testing.db, future=True)
- stmt = (
- select(User)
- .options(
- subqueryload(
- User.addresses.and_(Address.email_address != "ed@wood.com")
- ),
+ def go(value):
+ stmt = (
+ select(User)
+ .options(
+ subqueryload(
+ User.addresses.and_(Address.email_address != value)
+ ),
+ )
+ .order_by(User.id)
)
- .order_by(User.id)
- )
+ result = s.execute(stmt)
+ return result
- with self.sql_execution_asserter() as asserter:
+ for value in "ed@wood.com", "ed@lala.com":
+ with self.sql_execution_asserter() as asserter:
- result = s.execute(stmt)
+ result = go(value)
- eq_(
- result.scalars().unique().all(),
- self._user_minus_edwood(*user_address_fixture),
- )
+ eq_(
+ result.scalars().unique().all(),
+ self._user_minus_edwood(*user_address_fixture)
+ if value == "ed@wood.com"
+ else self._user_minus_edlala(*user_address_fixture),
+ )
- asserter.assert_(
- CompiledSQL(
- "SELECT users.id, users.name FROM users ORDER BY users.id"
- ),
- CompiledSQL(
- "SELECT addresses.id AS addresses_id, addresses.user_id "
- "AS addresses_user_id, addresses.email_address "
- "AS addresses_email_address, anon_1.users_id "
- "AS anon_1_users_id FROM (SELECT users.id AS users_id "
- "FROM users) AS anon_1 JOIN addresses ON anon_1.users_id = "
- "addresses.user_id AND "
- "addresses.email_address != :email_address_1 "
- "ORDER BY addresses.id",
- [{"email_address_1": "ed@wood.com"}],
- ),
- )
+ asserter.assert_(
+ CompiledSQL(
+ "SELECT users.id, users.name FROM users ORDER BY users.id"
+ ),
+ CompiledSQL(
+ "SELECT addresses.id AS addresses_id, addresses.user_id "
+ "AS addresses_user_id, addresses.email_address "
+ "AS addresses_email_address, anon_1.users_id "
+ "AS anon_1_users_id FROM (SELECT users.id AS users_id "
+ "FROM users) AS anon_1 "
+ "JOIN addresses ON anon_1.users_id = "
+ "addresses.user_id AND "
+ "addresses.email_address != :email_address_1 "
+ "ORDER BY addresses.id",
+ [{"email_address_1": value}],
+ ),
+ )
def test_query_join_local_criteria(self, user_address_fixture):
User, Address = user_address_fixture