--- /dev/null
+.. change::
+ :tags: bug, orm, regression
+ :tickets: 7239
+
+ Fixed 1.4 regression where :meth:`_orm.Query.filter_by` would not function
+ correctly on a :class:`_orm.Query` that was produced from
+ :meth:`_orm.Query.union`, :meth:`_orm.Query.from_self` or similar.
return None
def _filter_by_zero(self):
+ """for the filter_by() method, return the target entity for which
+ we will attempt to derive an expression from based on string name.
+
+ """
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:
+ # discussion related to #7239
+ # special check determines if we should try to derive attributes
+ # for filter_by() from the "from object", i.e., if the user
+ # called query.select_from(some selectable).filter_by(some_attr=value).
+ # We don't want to do that in the case that methods like
+ # from_self(), select_entity_from(), or a set op like union() were
+ # called; while these methods also place a
+ # selectable in the _from_obj collection, they also set up
+ # the _set_base_alias boolean which turns on the whole "adapt the
+ # entity to this selectable" thing, meaning the query still continues
+ # to construct itself in terms of the lead entity that was passed
+ # to query(), e.g. query(User).from_self() is still in terms of User,
+ # and not the subquery that from_self() created. This feature of
+ # "implicitly adapt all occurrences of entity X to some arbitrary
+ # subquery" is the main thing I am trying to do away with in 2.0 as
+ # users should now used aliased() for that, but I can't entirely get
+ # rid of it due to query.union() and other set ops relying upon it.
+ #
+ # compare this to the base Select()._filter_by_zero() which can
+ # just return self._from_obj[0] if present, because there is no
+ # "_set_base_alias" feature.
+ #
+ # IOW, this conditional essentially detects if
+ # "select_from(some_selectable)" has been called, as opposed to
+ # "select_entity_from()", "from_self()"
+ # or "union() / some_set_op()".
+ if self._from_obj and not self._compile_options._set_base_alias:
return self._from_obj[0]
return self._raw_columns[0]
from sqlalchemy import inspect
from sqlalchemy import Integer
from sqlalchemy import join
+from sqlalchemy import LABEL_STYLE_TABLENAME_PLUS_COL
from sqlalchemy import lateral
from sqlalchemy import literal_column
from sqlalchemy import MetaData
from sqlalchemy.testing.mock import Mock
from sqlalchemy.testing.schema import Column
from sqlalchemy.testing.schema import Table
+from sqlalchemy.testing.util import resolve_lambda
from sqlalchemy.util import pickle
from . import _fixtures
from .inheritance import _poly_fixtures
{},
)
+ def test_basic_filter_by(self):
+ """test #7239"""
+
+ User = self.classes.User
+
+ s = fixture_session()
+
+ with self._from_self_deprecated():
+ q = s.query(User).from_self()
+
+ self.assert_compile(
+ q.filter_by(id=5),
+ "SELECT anon_1.users_id AS anon_1_users_id, anon_1.users_name "
+ "AS anon_1_users_name FROM (SELECT users.id AS users_id, "
+ "users.name AS users_name FROM users) AS anon_1 "
+ "WHERE anon_1.users_id = :id_1",
+ )
+
def test_columns_augmented_distinct_on(self):
User, Address = self.classes.User, self.classes.Address
"AS anon_1",
)
+ @testing.combinations(
+ (
+ lambda users: users.select().where(users.c.id.in_([7, 8])),
+ "SELECT anon_1.id AS anon_1_id, anon_1.name AS anon_1_name "
+ "FROM (SELECT users.id AS id, users.name AS name "
+ "FROM users WHERE users.id IN ([POSTCOMPILE_id_1])) AS anon_1 "
+ "WHERE anon_1.name = :name_1",
+ ),
+ (
+ lambda users: users.select()
+ .where(users.c.id.in_([7, 8]))
+ .set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL),
+ "SELECT anon_1.users_id AS anon_1_users_id, anon_1.users_name "
+ "AS anon_1_users_name FROM (SELECT users.id AS users_id, "
+ "users.name AS users_name FROM users "
+ "WHERE users.id IN ([POSTCOMPILE_id_1])) AS anon_1 "
+ "WHERE anon_1.users_name = :name_1",
+ ),
+ (
+ lambda User, sess: sess.query(User).where(User.id.in_([7, 8])),
+ "SELECT anon_1.id AS anon_1_id, anon_1.name AS anon_1_name "
+ "FROM (SELECT users.id AS id, users.name AS name "
+ "FROM users WHERE users.id IN ([POSTCOMPILE_id_1])) AS anon_1 "
+ "WHERE anon_1.name = :name_1",
+ ),
+ )
+ def test_filter_by(self, query_fn, expected):
+ """test #7239"""
+
+ User = self.classes.User
+ sess = fixture_session()
+
+ User, users = self.classes.User, self.tables.users
+
+ self.mapper_registry.map_imperatively(User, users)
+
+ sel = resolve_lambda(query_fn, User=User, users=users, sess=sess)
+
+ sess = fixture_session()
+
+ with assertions.expect_deprecated_20(sef_dep):
+ q = sess.query(User).select_entity_from(sel.subquery())
+
+ self.assert_compile(q.filter_by(name="ed"), expected)
+ eq_(q.filter_by(name="ed").all(), [User(name="ed")])
+
def test_join_no_order_by(self):
User, users = self.classes.User, self.tables.users
"WHERE users.name = :name_1",
)
+ def test_filter_by_against_union_legacy(self):
+ """test #7239"""
+
+ User = self.classes.User
+ sess = fixture_session()
+
+ q = (
+ sess.query(User)
+ .filter(User.id == 2)
+ .union(sess.query(User).filter(User.id == 5))
+ )
+
+ self.assert_compile(
+ q.filter_by(id=5),
+ "SELECT anon_1.users_id AS anon_1_users_id, anon_1.users_name "
+ "AS anon_1_users_name FROM (SELECT users.id AS users_id, "
+ "users.name AS users_name FROM users WHERE users.id = :id_1 "
+ "UNION SELECT users.id AS users_id, users.name AS users_name "
+ "FROM users WHERE users.id = :id_2) AS anon_1 "
+ "WHERE anon_1.users_id = :id_3",
+ )
+
+ def test_filter_by_against_union_newstyle(self):
+ """test #7239"""
+
+ User = self.classes.User
+
+ u = union(
+ select(User).filter(User.id == 2),
+ select(User).filter(User.id == 5),
+ )
+ ua = aliased(User, u.subquery())
+ stmt = select(ua)
+ self.assert_compile(
+ stmt.filter_by(id=5),
+ "SELECT anon_1.id, anon_1.name FROM (SELECT users.id AS id, "
+ "users.name AS name FROM users WHERE users.id = :id_1 "
+ "UNION SELECT users.id AS id, users.name AS name FROM users "
+ "WHERE users.id = :id_2) AS anon_1 WHERE anon_1.id = :id_3",
+ )
+
def test_filter_by_against_cast(self):
"""test #6414"""
User = self.classes.User