]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Set autoincrement to False; use sqlite_autoincrement in versioned_history
authorCarlos García Montoro <TrilceAC@gmail.com>
Mon, 9 Jan 2017 22:46:25 +0000 (17:46 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 9 Jan 2017 23:00:33 +0000 (18:00 -0500)
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

doc/build/changelog/changelog_11.rst
examples/versioned_history/history_meta.py
examples/versioned_history/test_versioning.py

index 1554987c10701aeb8dd392254be78289d13b75a7..153ac3823fc4b7201bcd48e2e564fa22ee467cb4 100644 (file)
 .. 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
index 866f2d473a46932093b05e0cc442a1c65283fdf9..bad60a3982a31f54c277086beacf3f07b3dd170f 100644 (file)
@@ -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:
index 3ea240e1131e20ff923cb035b6c4b76c82320300..37ef739366ad564340a2710b82db40dac8dba2c9 100644 (file)
@@ -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()