From: Carlos García Montoro Date: Mon, 9 Jan 2017 22:46:25 +0000 (-0500) Subject: Set autoincrement to False; use sqlite_autoincrement in versioned_history X-Git-Tag: rel_1_1_5~15 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c703b9ce89483b6f44b97d1fbf56f8df8b14305a;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Set autoincrement to False; use sqlite_autoincrement in versioned_history Ensure that the history table sets autoincrement=False, since these values are copied in all cases; the flag will emit an error as of 1.1 if the primary key is composite. Additionally, use the sqlite_autoincrement flag so that SQLite uses unique primary key identifiers for new rows even if some rows have been deleted. Fixes: #3872 Change-Id: I65912eb394b3b69d7f4e3c098f4f948b0a7a5374 Pull-request: https://bitbucket.org/zzzeek/sqlalchemy/pull-requests/93 --- diff --git a/doc/build/changelog/changelog_11.rst b/doc/build/changelog/changelog_11.rst index 1554987c10..153ac3823f 100644 --- a/doc/build/changelog/changelog_11.rst +++ b/doc/build/changelog/changelog_11.rst @@ -21,6 +21,17 @@ .. 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 diff --git a/examples/versioned_history/history_meta.py b/examples/versioned_history/history_meta.py index 866f2d473a..bad60a3982 100644 --- a/examples/versioned_history/history_meta.py +++ b/examples/versioned_history/history_meta.py @@ -41,6 +41,7 @@ def _history_mapper(local_mapper): orig.info['history_copy'] = col col.unique = False col.default = col.server_default = None + col.autoincrement = False return col properties = util.OrderedDict() @@ -157,6 +158,10 @@ class Versioned(object): 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: diff --git a/examples/versioned_history/test_versioning.py b/examples/versioned_history/test_versioning.py index 3ea240e113..37ef739366 100644 --- a/examples/versioned_history/test_versioning.py +++ b/examples/versioned_history/test_versioning.py @@ -8,7 +8,7 @@ from sqlalchemy import create_engine, Column, Integer, String, \ 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 @@ -679,3 +679,47 @@ class TestVersioning(TestCase, AssertsCompiledSQL): 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()