.. changelog::
:version: 1.2.0b1
+ .. change:: 3366
+ :tags: bug, orm
+ :tickets: 3366
+
+ The "evaluate" strategy used by :meth:`.Query.update` and
+ :meth:`.Query.delete` can now accommodate a simple
+ object comparison from a many-to-one relationship to an instance,
+ when the attribute names of the primary key / foreign key columns
+ don't match the actual names of the columns. Previously this would
+ do a simple name-based match and fail with an AttributeError.
+
.. change:: 3938
:tags: bug, engine
:tickets: 3938
)
key = parentmapper._columntoproperty[clause].key
else:
- key = clause.key
+ raise UnevaluatableError(
+ "Cannot evaluate column: %s" % clause
+ )
get_corresponding_attr = operator.attrgetter(key)
return lambda obj: get_corresponding_attr(obj)
support_sync=not self.viewonly,
can_be_synced_fn=self._columns_are_mapped
)
- self.primaryjoin = jc.deannotated_primaryjoin
- self.secondaryjoin = jc.deannotated_secondaryjoin
+ self.primaryjoin = jc.primaryjoin
+ self.secondaryjoin = jc.secondaryjoin
self.direction = jc.direction
self.local_remote_pairs = jc.local_remote_pairs
self.remote_side = jc.remote_columns
self._annotate_fks()
self._annotate_remote()
self._annotate_local()
+ self._annotate_parentmapper()
self._setup_pairs()
self._check_foreign_cols(self.primaryjoin, True)
if self.secondaryjoin is not None:
self.primaryjoin, {}, locals_
)
+ def _annotate_parentmapper(self):
+ if self.prop is None:
+ return
+
+ def parentmappers_(elem):
+ if "remote" in elem._annotations:
+ return elem._annotate({"parentmapper": self.prop.mapper})
+ elif "local" in elem._annotations:
+ return elem._annotate({"parentmapper": self.prop.parent})
+ self.primaryjoin = visitors.replacement_traverse(
+ self.primaryjoin, {}, parentmappers_
+ )
+
def _check_remote_side(self):
if not self.local_remote_pairs:
raise sa_exc.ArgumentError(
def foreign_key_columns(self):
return self._gather_join_annotations("foreign")
- @util.memoized_property
- def deannotated_primaryjoin(self):
- return _deep_deannotate(self.primaryjoin)
-
- @util.memoized_property
- def deannotated_secondaryjoin(self):
- if self.secondaryjoin is not None:
- return _deep_deannotate(self.secondaryjoin)
- else:
- return None
-
def _gather_join_annotations(self, annotation):
s = set(
self._gather_columns_with_annotation(
bind_to_col = dict((binds[col].key, col) for col in binds)
- # this is probably not necessary
- lazywhere = _deep_deannotate(lazywhere)
-
return lazywhere, bind_to_col, equated_columns
from sqlalchemy.orm import evaluator
from sqlalchemy.orm import mapper
+from sqlalchemy.orm import relationship, Session
+from sqlalchemy import ForeignKey
+from sqlalchemy.orm import exc as orm_exc
+from sqlalchemy.testing import assert_raises_message
+from sqlalchemy.testing import is_
+from sqlalchemy import inspect
+
+
compiler = evaluator.EvaluatorCompiler()
eval_eq(User.name == None, # noqa
testcases=[(User(name='foo'), False), (User(name=None), True)])
+ def test_raise_on_unannotated_column(self):
+ User = self.classes.User
+
+ assert_raises_message(
+ evaluator.UnevaluatableError,
+ "Cannot evaluate column: foo",
+ compiler.process, User.id == Column('foo', Integer)
+ )
+
+ # if we let the above method through as we did
+ # prior to [ticket:3366], we would get
+ # AttributeError: 'User' object has no attribute 'foo'
+ # u1 = User(id=5)
+ # meth(u1)
+
def test_true_false(self):
User = self.classes.User
(User(id=None, name='foo'), None),
(User(id=None, name=None), None),
])
+
+
+class M2OEvaluateTest(fixtures.DeclarativeMappedTest):
+ @classmethod
+ def setup_classes(cls):
+ Base = cls.DeclarativeBasic
+
+ class Parent(Base):
+ __tablename__ = "parent"
+ id = Column(Integer, primary_key=True)
+
+ class Child(Base):
+ __tablename__ = "child"
+ _id_parent = Column(
+ "id_parent", Integer, ForeignKey(Parent.id), primary_key=True)
+ name = Column(String(50), primary_key=True)
+ parent = relationship(Parent)
+
+ def test_delete(self):
+ Parent, Child = self.classes('Parent', 'Child')
+
+ session = Session()
+
+ p = Parent(id=1)
+ session.add(p)
+ session.commit()
+
+ c = Child(name="foo", parent=p)
+ session.add(c)
+ session.commit()
+
+ session.query(Child).filter(Child.parent == p).delete("evaluate")
+
+ is_(
+ inspect(c).deleted, True
+ )