From 979c9323dce30c3a12552668fc93958cba566cca Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 9 Jan 2008 18:52:35 +0000 Subject: [PATCH] - fixed bug with session.dirty when using "mutable scalars" (such as PickleTypes) - added a more descriptive error message when flushing on a relation() that has non-locally-mapped columns in its primary or secondary join condition --- CHANGES | 8 ++++++++ lib/sqlalchemy/exceptions.py | 5 ++++- lib/sqlalchemy/orm/mapper.py | 4 ++-- lib/sqlalchemy/orm/sync.py | 30 ++++++++++++++++++++++++------ lib/sqlalchemy/orm/unitofwork.py | 2 +- test/orm/unitofwork.py | 1 + 6 files changed, 40 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index cbceea4d99..f808b369b9 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,14 @@ CHANGES - fixed bug in union() so that select() statements which don't derive from FromClause objects can be unioned + +- orm + - fixed bug with session.dirty when using "mutable scalars" + (such as PickleTypes) + + - added a more descriptive error message when flushing on a + relation() that has non-locally-mapped columns in its primary or + secondary join condition - dialects - Fixed reflection of mysql empty string column defaults. diff --git a/lib/sqlalchemy/exceptions.py b/lib/sqlalchemy/exceptions.py index 8dbbb91c84..02cee50632 100644 --- a/lib/sqlalchemy/exceptions.py +++ b/lib/sqlalchemy/exceptions.py @@ -46,7 +46,10 @@ class InvalidRequestError(SQLAlchemyError): This error generally corresponds to runtime state errors. """ - +class UnmappedColumnError(InvalidRequestError): + """A mapper was asked to return mapped information about a column + which it does not map""" + class NoSuchTableError(InvalidRequestError): """SQLAlchemy was asked to load a table's definition from the database, but the table doesn't exist. diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index fc62c773e9..2df7926aa2 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -901,9 +901,9 @@ class Mapper(object): except KeyError: prop = self.__props.get(column.key, None) if prop: - raise exceptions.InvalidRequestError("Column '%s.%s' is not available, due to conflicting property '%s':%s" % (column.table.name, column.name, column.key, repr(prop))) + raise exceptions.UnmappedColumnError("Column '%s.%s' is not available, due to conflicting property '%s':%s" % (column.table.name, column.name, column.key, repr(prop))) else: - raise exceptions.InvalidRequestError("No column %s.%s is configured on mapper %s..." % (column.table.name, column.name, str(self))) + raise exceptions.UnmappedColumnError("No column %s.%s is configured on mapper %s..." % (column.table.name, column.name, str(self))) def _get_state_attr_by_column(self, state, column): return self._get_col_to_prop(column).getattr(state, column) diff --git a/lib/sqlalchemy/orm/sync.py b/lib/sqlalchemy/orm/sync.py index 678e5e7bd0..a80252c847 100644 --- a/lib/sqlalchemy/orm/sync.py +++ b/lib/sqlalchemy/orm/sync.py @@ -117,7 +117,7 @@ class SyncRule(object): self.issecondary = issecondary self.dest_mapper = dest_mapper self.dest_column = dest_column - + #print "SyncRule", source_mapper, source_column, dest_column, dest_mapper def dest_primary_key(self): @@ -129,8 +129,17 @@ class SyncRule(object): self._dest_primary_key = self.dest_mapper is not None and self.dest_column in self.dest_mapper._pks_by_table[self.dest_column.table] and not self.dest_mapper.allow_null_pks return self._dest_primary_key + def _raise_col_to_prop(self, isdest): + if isdest: + raise exceptions.UnmappedColumnError("Can't execute sync rule for destination column '%s'; mapper '%s' does not map this column. Try using an explicit `foreign_keys` collection which does not include this column (or use a viewonly=True relation)." % (self.dest_column, self.dest_mapper)) + else: + raise exceptions.UnmappedColumnError("Can't execute sync rule for source column '%s'; mapper '%s' does not map this column. Try using an explicit `foreign_keys` collection which does not include destination column '%s' (or use a viewonly=True relation)." % (self.source_column, self.source_mapper, self.dest_column)) + def source_changes(self, uowcommit, source): - prop = self.source_mapper._columntoproperty[self.source_column] + try: + prop = self.source_mapper._get_col_to_prop(self.source_column) + except exceptions.UnmappedColumnError: + self._raise_col_to_prop(False) (added, unchanged, deleted) = uowcommit.get_attribute_history(source, prop.key, passive=True) return bool(added and deleted) @@ -139,8 +148,11 @@ class SyncRule(object): source = parent elif self.issecondary is True: source = child - oldvalue = self.source_mapper._get_committed_attr_by_column(source.obj(), self.source_column) - value = self.source_mapper._get_state_attr_by_column(source, self.source_column) + try: + oldvalue = self.source_mapper._get_committed_attr_by_column(source.obj(), self.source_column) + value = self.source_mapper._get_state_attr_by_column(source, self.source_column) + except exceptions.UnmappedColumnError: + self._raise_col_to_prop(False) dest[self.dest_column.key] = value dest[old_prefix + self.dest_column.key] = oldvalue @@ -156,7 +168,10 @@ class SyncRule(object): value = None clearkeys = True else: - value = self.source_mapper._get_state_attr_by_column(source, self.source_column) + try: + value = self.source_mapper._get_state_attr_by_column(source, self.source_column) + except exceptions.UnmappedColumnError: + self._raise_col_to_prop(False) if isinstance(dest, dict): dest[self.dest_column.key] = value else: @@ -165,7 +180,10 @@ class SyncRule(object): if logging.is_debug_enabled(self.logger): self.logger.debug("execute() instances: %s(%s)->%s(%s) ('%s')" % (mapperutil.state_str(source), str(self.source_column), mapperutil.state_str(dest), str(self.dest_column), value)) - self.dest_mapper._set_state_attr_by_column(dest, self.dest_column, value) + try: + self.dest_mapper._set_state_attr_by_column(dest, self.dest_column, value) + except exceptions.UnmappedColumnError: + self._raise_col_to_prop(True) SyncRule.logger = logging.class_logger(SyncRule) diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index e71488547d..05c9388b30 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -158,7 +158,7 @@ class UnitOfWork(object): if x._state not in self.deleted and ( x._state.modified - or (x.__class__._class_state.has_mutable_scalars and x.state.is_modified()) + or (x.__class__._class_state.has_mutable_scalars and x._state.is_modified()) ) ]) diff --git a/test/orm/unitofwork.py b/test/orm/unitofwork.py index 47ce70fa7a..1ff3bda85e 100644 --- a/test/orm/unitofwork.py +++ b/test/orm/unitofwork.py @@ -272,6 +272,7 @@ class MutableTypesTest(ORMTest): f2 = Session.query(Foo).get_by(id=f1.id) assert f2.data == f1.data f2.data.y = 19 + assert f2 in Session.dirty Session.commit() Session.close() f3 = Session.query(Foo).get_by(id=f1.id) -- 2.47.3