diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-04-29 14:00:16 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-04-29 14:00:16 -0400 |
commit | b0be9211c9a2d9032b659b63888ffc76f64d4247 (patch) | |
tree | 96b55f9441f1e30d0643cee89e7518bd8a87031d | |
parent | 93b5eae9843d423378f68be928a4f1e6fcacfb87 (diff) | |
download | sqlalchemy-b0be9211c9a2d9032b659b63888ffc76f64d4247.tar.gz |
- Fixed regression within the flush process when an attribute were
set to a SQL expression for an UPDATE, and the SQL expression when
compared to the previous value of the attribute would produce a SQL
comparison other than ``==`` or ``!=``, the exception "Boolean value
of this clause is not defined" would raise. The fix ensures that
the unit of work will not interpret the SQL expression in this way.
fixes #3402
-rw-r--r-- | doc/build/changelog/changelog_10.rst | 11 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/persistence.py | 9 | ||||
-rw-r--r-- | test/orm/test_unitofwork.py | 40 |
3 files changed, 53 insertions, 7 deletions
diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index fd791ea97..1e67221f5 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -19,6 +19,17 @@ :version: 1.0.3 .. change:: + :tags: bug, orm + :tickets: 3402 + + Fixed regression within the flush process when an attribute were + set to a SQL expression for an UPDATE, and the SQL expression when + compared to the previous value of the attribute would produce a SQL + comparison other than ``==`` or ``!=``, the exception "Boolean value + of this clause is not defined" would raise. The fix ensures that + the unit of work will not interpret the SQL expression in this way. + + .. change:: :tags: bug, ext :tickets: 3397 diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py index 34c37dab6..c3974ed6d 100644 --- a/lib/sqlalchemy/orm/persistence.py +++ b/lib/sqlalchemy/orm/persistence.py @@ -452,12 +452,11 @@ def _collect_update_commands( value = state_dict[propkey] col = propkey_to_col[propkey] - if not state.manager[propkey].impl.is_equal( + if isinstance(value, sql.ClauseElement): + value_params[col] = value + elif not state.manager[propkey].impl.is_equal( value, state.committed_state[propkey]): - if isinstance(value, sql.ClauseElement): - value_params[col] = value - else: - params[col.key] = value + params[col.key] = value if update_version_id is not None and \ mapper.version_id_col in mapper._cols_by_table[table]: diff --git a/test/orm/test_unitofwork.py b/test/orm/test_unitofwork.py index ae5a8ef60..5a47903f0 100644 --- a/test/orm/test_unitofwork.py +++ b/test/orm/test_unitofwork.py @@ -7,7 +7,8 @@ from sqlalchemy.orm import mapper as orm_mapper import sqlalchemy as sa from sqlalchemy.util import u, ue, b -from sqlalchemy import Integer, String, ForeignKey, literal_column, event +from sqlalchemy import Integer, String, ForeignKey, \ + literal_column, event, Boolean from sqlalchemy.testing import engines from sqlalchemy import testing from sqlalchemy.testing.schema import Table @@ -18,6 +19,7 @@ from sqlalchemy.testing import fixtures from test.orm import _fixtures from sqlalchemy.testing.assertsql import AllOf, CompiledSQL + class UnitOfWorkTest(object): pass @@ -383,16 +385,26 @@ class ClauseAttributesTest(fixtures.MappedTest): Column('name', String(30)), Column('counter', Integer, default=1)) + Table('boolean_t', metadata, + Column('id', Integer, primary_key=True, + test_needs_autoincrement=True), + Column('value', Boolean), + ) + @classmethod def setup_classes(cls): class User(cls.Comparable): pass + class HasBoolean(cls.Comparable): + pass + @classmethod def setup_mappers(cls): User, users_t = cls.classes.User, cls.tables.users_t - + HasBoolean, boolean_t = cls.classes.HasBoolean, cls.tables.boolean_t mapper(User, users_t) + mapper(HasBoolean, boolean_t) def test_update(self): User = self.classes.User @@ -446,6 +458,30 @@ class ClauseAttributesTest(fixtures.MappedTest): assert (u.counter == 5) is True + def test_update_special_comparator(self): + HasBoolean = self.classes.HasBoolean + + # make sure the comparison we're shooting + # for is invalid, otherwise we need to + # test something else here + assert_raises_message( + TypeError, + "Boolean value of this clause is not defined", + bool, None == sa.false() + ) + s = create_session() + hb = HasBoolean(value=None) + s.add(hb) + s.flush() + + hb.value = sa.false() + + s.flush() + + # needs to be refreshed + assert 'value' not in hb.__dict__ + eq_(hb.value, False) + class PassiveDeletesTest(fixtures.MappedTest): __requires__ = ('foreign_keys',) |