summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-08-01 18:24:35 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2010-08-01 18:24:35 -0400
commitc09b79d61eaba130efcd676db5e27ac3635535d2 (patch)
tree689b89d21811d0fb880f0e69a5d028178a589c21 /lib
parent62b12e4266e5d2305f7dbc17b44bab6b2b05c622 (diff)
downloadsqlalchemy-c09b79d61eaba130efcd676db5e27ac3635535d2.tar.gz
- The name ConcurrentModificationError has been
changed to StaleDataError, and descriptive error messages have been revised to reflect exactly what the issue is. Both names will remain available for the forseeable future for schemes that may be specifying ConcurrentModificationError in an "except:" clause.
Diffstat (limited to 'lib')
-rw-r--r--lib/sqlalchemy/orm/__init__.py2
-rw-r--r--lib/sqlalchemy/orm/dependency.py28
-rw-r--r--lib/sqlalchemy/orm/exc.py24
-rw-r--r--lib/sqlalchemy/orm/mapper.py21
-rw-r--r--lib/sqlalchemy/test/requires.py6
5 files changed, 51 insertions, 30 deletions
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index 1252baa07..4c9efb714 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -782,7 +782,7 @@ def mapper(class_, local_table=None, *args, **params):
a running *version id* of mapped entities in the database. this is
used during save operations to ensure that no other thread or process
has updated the instance during the lifetime of the entity, else a
- ``ConcurrentModificationError`` exception is thrown.
+ :class:`StaleDataError` exception is thrown.
:param version_id_generator: A callable which defines the algorithm used to generate new version
ids. Defaults to an integer generator. Can be replaced with one that
diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py
index e96eed28e..376afd88d 100644
--- a/lib/sqlalchemy/orm/dependency.py
+++ b/lib/sqlalchemy/orm/dependency.py
@@ -1032,14 +1032,12 @@ class ManyToManyDP(DependencyProcessor):
if result.supports_sane_multi_rowcount() and \
result.rowcount != len(secondary_delete):
- raise exc.ConcurrentModificationError(
- "Deleted rowcount %d does not match number of "
- "secondary table rows deleted from table '%s': %d" %
- (
- result.rowcount,
- self.secondary.description,
- len(secondary_delete))
- )
+ raise exc.StaleDataError(
+ "DELETE statement on table '%s' expected to delete %d row(s); "
+ "Only %d were matched." %
+ (self.secondary.description, len(secondary_delete),
+ result.rowcount)
+ )
if secondary_update:
associationrow = secondary_update[0]
@@ -1051,14 +1049,12 @@ class ManyToManyDP(DependencyProcessor):
result = connection.execute(statement, secondary_update)
if result.supports_sane_multi_rowcount() and \
result.rowcount != len(secondary_update):
- raise exc.ConcurrentModificationError(
- "Updated rowcount %d does not match number of "
- "secondary table rows updated from table '%s': %d" %
- (
- result.rowcount,
- self.secondary.description,
- len(secondary_update))
- )
+ raise exc.StaleDataError(
+ "UPDATE statement on table '%s' expected to update %d row(s); "
+ "Only %d were matched." %
+ (self.secondary.description, len(secondary_update),
+ result.rowcount)
+ )
if secondary_insert:
statement = self.secondary.insert()
diff --git a/lib/sqlalchemy/orm/exc.py b/lib/sqlalchemy/orm/exc.py
index 431acc15c..3f28a3dd3 100644
--- a/lib/sqlalchemy/orm/exc.py
+++ b/lib/sqlalchemy/orm/exc.py
@@ -12,8 +12,25 @@ import sqlalchemy as sa
NO_STATE = (AttributeError, KeyError)
"""Exception types that may be raised by instrumentation implementations."""
-class ConcurrentModificationError(sa.exc.SQLAlchemyError):
- """Rows have been modified outside of the unit of work."""
+class StaleDataError(sa.exc.SQLAlchemyError):
+ """An operation encountered database state that is unaccounted for.
+
+ Two conditions cause this to happen:
+
+ * A flush may have attempted to update or delete rows
+ and an unexpected number of rows were matched during
+ the UPDATE or DELETE statement. Note that when
+ version_id_col is used, rows in UPDATE or DELETE statements
+ are also matched against the current known version
+ identifier.
+
+ * A mapped object with version_id_col was refreshed,
+ and the version number coming back from the database does
+ not match that of the object itself.
+
+ """
+
+ConcurrentModificationError = StaleDataError
class FlushError(sa.exc.SQLAlchemyError):
@@ -24,7 +41,8 @@ class UnmappedError(sa.exc.InvalidRequestError):
"""TODO"""
class DetachedInstanceError(sa.exc.SQLAlchemyError):
- """An attempt to access unloaded attributes on a mapped instance that is detached."""
+ """An attempt to access unloaded attributes on a
+ mapped instance that is detached."""
class UnmappedInstanceError(UnmappedError):
"""An mapping operation was requested for an unknown instance."""
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index 6c25b89ca..9cb358151 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -1818,10 +1818,10 @@ class Mapper(object):
if connection.dialect.supports_sane_rowcount:
if rows != len(update):
- raise orm_exc.ConcurrentModificationError(
- "Updated rowcount %d does not match number "
- "of objects updated %d" %
- (rows, len(update)))
+ raise orm_exc.StaleDataError(
+ "UPDATE statement on table '%s' expected to update %d row(s); "
+ "%d were matched." %
+ (table.description, len(update), rows))
elif needs_version_id:
util.warn("Dialect %s does not support updated rowcount "
@@ -2050,10 +2050,10 @@ class Mapper(object):
rows = c.rowcount
if rows != -1 and rows != len(del_objects):
- raise orm_exc.ConcurrentModificationError(
- "Deleted rowcount %d does not match "
- "number of objects deleted %d" %
- (c.rowcount, len(del_objects))
+ raise orm_exc.StaleDataError(
+ "DELETE statement on table '%s' expected to delete %d row(s); "
+ "%d were matched." %
+ (table.description, len(del_objects), c.rowcount)
)
for state, state_dict, mapper, has_identity, connection in tups:
@@ -2180,8 +2180,9 @@ class Mapper(object):
self.version_id_col) != \
row[version_id_col]:
- raise orm_exc.ConcurrentModificationError(
- "Instance '%s' version of %s does not match %s"
+ raise orm_exc.StaleDataError(
+ "Instance '%s' has version id '%s' which "
+ "does not match database-loaded version id '%s'."
% (state_str(state),
self._get_state_attr_by_column(
state, dict_,
diff --git a/lib/sqlalchemy/test/requires.py b/lib/sqlalchemy/test/requires.py
index 1b9052fd8..fefb00330 100644
--- a/lib/sqlalchemy/test/requires.py
+++ b/lib/sqlalchemy/test/requires.py
@@ -247,6 +247,12 @@ def sane_rowcount(fn):
skip_if(lambda: not testing.db.dialect.supports_sane_rowcount)
)
+def sane_multi_rowcount(fn):
+ return _chain_decorators_on(
+ fn,
+ skip_if(lambda: not testing.db.dialect.supports_sane_multi_rowcount)
+ )
+
def reflects_pk_names(fn):
"""Target driver reflects the name of primary key constraints."""
return _chain_decorators_on(