.. changelog::
:version: 1.1.5
+ .. change:: 3872
+ :tags: bug, examples
+ :tickets: 3872
+
+ Fixed two issues with the versioned_history example, one is that
+ the history table now gets autoincrement=False to avoid 1.1's new
+ errors regarding composite primary keys with autoincrement; the other
+ is that the sqlite_autoincrement flag is now used to ensure on SQLite,
+ unique identifiers are used for the lifespan of a table even if
+ some rows are deleted. Pull request courtesy Carlos García Montoro.
+
.. change:: 3882
:tags: bug, sql
:tikets: 3882
orig.info['history_copy'] = col
col.unique = False
col.default = col.server_default = None
+ col.autoincrement = False
return col
properties = util.OrderedDict()
return mp
return map
+ __table_args__ = {'sqlite_autoincrement': True}
+ """Use sqlite_autoincrement, to ensure unique integer values
+ are used for new rows even for rows taht have been deleted."""
+
def versioned_objects(iter):
for obj in iter:
ForeignKey, Boolean, select
from sqlalchemy.orm import clear_mappers, Session, deferred, relationship, \
column_property
-from sqlalchemy.testing import AssertsCompiledSQL, eq_, assert_raises
+from sqlalchemy.testing import AssertsCompiledSQL, eq_, assert_raises, ne_
from sqlalchemy.testing.entities import ComparableEntity
from sqlalchemy.orm import exc as orm_exc
import warnings
self.assertEqual(v1.id, v2.id)
self.assertEqual(v2.description_, 'Bar')
self.assertEqual(v1.description_, 'Foo')
+
+ def test_unique_identifiers_across_deletes(self):
+ """Ensure unique integer values are used for the primary table.
+
+ Checks whether the database assigns the same identifier twice
+ within the span of a table. SQLite will do this if
+ sqlite_autoincrement is not set (e.g. SQLite's AUTOINCREMENT flag).
+
+ """
+
+ class SomeClass(Versioned, self.Base, ComparableEntity):
+ __tablename__ = 'sometable'
+
+ id = Column(Integer, primary_key=True)
+ name = Column(String(50))
+
+ self.create_tables()
+ sess = self.session
+ sc = SomeClass(name='sc1')
+ sess.add(sc)
+ sess.commit()
+
+ sess.delete(sc)
+ sess.commit()
+
+ sc2 = SomeClass(name='sc2')
+ sess.add(sc2)
+ sess.commit()
+
+ SomeClassHistory = SomeClass.__history_mapper__.class_
+
+ # only one entry should exist in the history table; one()
+ # ensures that
+ scdeleted = sess.query(SomeClassHistory).one()
+
+ # If sc2 has the same id that deleted sc1 had,
+ # it will fail when modified or deleted
+ # because of the violation of the uniqueness of the primary key on
+ # sometable_history
+ ne_(sc2.id, scdeleted.id)
+
+ # If previous assertion fails, this will also fail:
+ sc2.name = 'sc2 modified'
+ sess.commit()