]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
- Fixed bug where in the erroneous case that alembic_version contains
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 3 Aug 2015 23:18:30 +0000 (19:18 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 3 Aug 2015 23:18:30 +0000 (19:18 -0400)
duplicate revisions, some commands would fail to process the
version history correctly and end up with a KeyError.   The fix
allows the versioning logic to proceed, however a clear error is
emitted later when attempting to update the alembic_version table.
fixes #314

alembic/script/revision.py
alembic/util/__init__.py
alembic/util/langhelpers.py
docs/build/changelog.rst
tests/test_command.py
tests/test_revision.py

index e618c4d051a5e2f5a9ebfa41c17e394b39b85389..c1750a066f2669f9131ff4906af8f10107fc6d69 100644 (file)
@@ -618,7 +618,8 @@ class RevisionMap(object):
         limit_to_lower_branch = \
             isinstance(lower, compat.string_types) and lower.endswith('@base')
 
-        uppers = self.get_revisions(upper)
+        uppers = util.dedupe_tuple(self.get_revisions(upper))
+
         if not uppers and not requested_lowers:
             raise StopIteration()
 
index ff08ad9c4fbc7f9be15e7fb4d1bbd1d4bc93cd8a..a1110008b3d5d984bcec14010a5559d5e9e8771d 100644 (file)
@@ -1,5 +1,5 @@
 from .langhelpers import (  # noqa
-    asbool, rev_id, to_tuple, to_list, memoized_property,
+    asbool, rev_id, to_tuple, to_list, memoized_property, dedupe_tuple,
     immutabledict, _with_legacy_names, Dispatcher, ModuleClsProxy)
 from .messaging import (  # noqa
     write_outstream, status, err, obfuscate_url_pw, warn, msg, format_as_comma)
index 6c92e3c695ced79895727ce5186cff296a02c2ce..9445949ed14850793f886d0ce61d73e52ea46678 100644 (file)
@@ -208,6 +208,24 @@ def to_tuple(x, default=None):
         raise ValueError("Don't know how to turn %r into a tuple" % x)
 
 
+def unique_list(seq, hashfunc=None):
+    seen = set()
+    seen_add = seen.add
+    if not hashfunc:
+        return [x for x in seq
+                if x not in seen
+                and not seen_add(x)]
+    else:
+        return [x for x in seq
+                if hashfunc(x) not in seen
+                and not seen_add(hashfunc(x))]
+
+
+def dedupe_tuple(tup):
+    return tuple(unique_list(tup))
+
+
+
 class memoized_property(object):
 
     """A read-only @property that is only evaluated once."""
index e69732e7449a6efe86d40e2526bf5ea6ebf94c10..e1a5a76041b65079c145f28d6944627f4da5ca1e 100644 (file)
@@ -124,6 +124,15 @@ Changelog
 
         :ref:`alembic.autogenerate.toplevel`
 
+    .. change::
+      :tags: bug, versioning
+      :tickets: 314
+
+      Fixed bug where in the erroneous case that alembic_version contains
+      duplicate revisions, some commands would fail to process the
+      version history correctly and end up with a KeyError.   The fix
+      allows the versioning logic to proceed, however a clear error is
+      emitted later when attempting to update the alembic_version table.
 
 .. changelog::
     :version: 0.7.7
index aa8efa49cc9083c0bc7ee439ce22ec9efcdec41a..b57e3b34cc50b688e8d4423ea29bc2d8b5d391be 100644 (file)
@@ -184,6 +184,20 @@ finally:
             command.revision, self.cfg, autogenerate=True
         )
 
+    def test_err_correctly_raised_on_dupe_rows(self):
+        self._env_fixture()
+        command.revision(self.cfg)
+        r2 = command.revision(self.cfg)
+        db = _sqlite_file_db()
+        command.upgrade(self.cfg, "head")
+        db.execute("insert into alembic_version values ('%s')" % r2.revision)
+        assert_raises_message(
+            util.CommandError,
+            "Online migration expected to match one row when "
+            "updating .* in 'alembic_version'; 2 found",
+            command.downgrade, self.cfg, "-1"
+        )
+
     def test_create_rev_plain_need_to_select_head(self):
         self._env_fixture()
         command.revision(self.cfg)
index a96aa5b2ec7ccee60c973b0b87b5d211e33a5aca..45687eab8c2e102d8a1e095da13aed0a602ebd4d 100644 (file)
@@ -94,6 +94,23 @@ class APITest(TestBase):
         )
         eq_(map_.get_revision('base'), None)
 
+    def test_iterate_tolerates_dupe_targets(self):
+        map_ = RevisionMap(
+            lambda: [
+                Revision('a', ()),
+                Revision('b', ('a',)),
+                Revision('c', ('b',)),
+            ]
+        )
+
+        eq_(
+            [
+                r.revision for r in
+                map_._iterate_revisions(('c', 'c'), 'a')
+            ],
+            ['c', 'b', 'a']
+        )
+
 
 class DownIterateTest(TestBase):
     def _assert_iteration(