summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2015-08-03 19:18:30 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2015-08-03 19:18:30 -0400
commite155fa69a628a89215f2ef843393d4f9f4dde758 (patch)
treeb2d35bf4918b845b8877b3c2a1522707f8860c3c
parent0d38962a5e29a591e8e7c5788be05e13eef6f452 (diff)
downloadalembic-e155fa69a628a89215f2ef843393d4f9f4dde758.tar.gz
- 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. fixes #314
-rw-r--r--alembic/script/revision.py3
-rw-r--r--alembic/util/__init__.py2
-rw-r--r--alembic/util/langhelpers.py18
-rw-r--r--docs/build/changelog.rst9
-rw-r--r--tests/test_command.py14
-rw-r--r--tests/test_revision.py17
6 files changed, 61 insertions, 2 deletions
diff --git a/alembic/script/revision.py b/alembic/script/revision.py
index e618c4d..c1750a0 100644
--- a/alembic/script/revision.py
+++ b/alembic/script/revision.py
@@ -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()
diff --git a/alembic/util/__init__.py b/alembic/util/__init__.py
index ff08ad9..a111000 100644
--- a/alembic/util/__init__.py
+++ b/alembic/util/__init__.py
@@ -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)
diff --git a/alembic/util/langhelpers.py b/alembic/util/langhelpers.py
index 6c92e3c..9445949 100644
--- a/alembic/util/langhelpers.py
+++ b/alembic/util/langhelpers.py
@@ -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."""
diff --git a/docs/build/changelog.rst b/docs/build/changelog.rst
index e69732e..e1a5a76 100644
--- a/docs/build/changelog.rst
+++ b/docs/build/changelog.rst
@@ -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
diff --git a/tests/test_command.py b/tests/test_command.py
index aa8efa4..b57e3b3 100644
--- a/tests/test_command.py
+++ b/tests/test_command.py
@@ -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)
diff --git a/tests/test_revision.py b/tests/test_revision.py
index a96aa5b..45687ea 100644
--- a/tests/test_revision.py
+++ b/tests/test_revision.py
@@ -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(