]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
Add primary key constraint to alembic_version
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 11 Jan 2017 14:33:01 +0000 (09:33 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 13 Jan 2017 20:49:14 +0000 (15:49 -0500)
The alembic_version table, when initially created, now establishes a
primary key constraint on the "version_num" column, to suit database
engines that don't support tables without primary keys.   This behavior
can be controlled using the parameter
:paramref:`.EnvironmentContext.configure.version_table_pk`.  Note that
this change only applies to the initial creation of the alembic_version
table; it does not impact any existing alembic_version table already
present.

Change-Id: Ic947f0f97373b2e6695e06c9b2ad6c8a9789fcb2
Fixes: #406
alembic/runtime/environment.py
alembic/runtime/migration.py
docs/build/changelog.rst
tests/test_command.py
tests/test_version_table.py

index b2d0ba7bb981e87ff32167015e1cb2f4c933af54..30c8ab92b0332b66f8bb9d06e03c7613c8db1ba1 100644 (file)
@@ -405,6 +405,16 @@ class EnvironmentContext(util.ModuleClsProxy):
          The default is ``'alembic_version'``.
         :param version_table_schema: Optional schema to place version
          table within.
+        :param version_table_pk: boolean, whether the Alembic version table
+         should use a primary key constraint for the "value" column; this
+         only takes effect when the table is first created.
+         Defaults to True; setting to False should not be necessary and is
+         here for backwards compatibility reasons.
+
+         .. versionadded:: 0.8.10  Added the
+            :paramref:`.EnvironmentContext.configure.version_table_pk`
+            flag and additionally established that the Alembic version table
+            has a primary key constraint by default.
 
         Parameters specific to the autogenerate feature, when
         ``alembic revision`` is run with the ``--autogenerate`` feature:
index a0b386df44a40e8d01cb0a8b2d1b9f3b4af71df7..33cd74fb282e3667531e8b8a9cef009d33794ee3 100644 (file)
@@ -2,7 +2,8 @@ import logging
 import sys
 from contextlib import contextmanager
 
-from sqlalchemy import MetaData, Table, Column, String, literal_column
+from sqlalchemy import MetaData, Table, Column, String, literal_column,\
+    PrimaryKeyConstraint
 from sqlalchemy.engine.strategies import MockEngineStrategy
 from sqlalchemy.engine import url as sqla_url
 
@@ -98,6 +99,12 @@ class MigrationContext(object):
             version_table, MetaData(),
             Column('version_num', String(32), nullable=False),
             schema=version_table_schema)
+        if opts.get("version_table_pk", True):
+            self._version.append_constraint(
+                PrimaryKeyConstraint(
+                    'version_num', name="%s_pkc" % version_table
+                )
+            )
 
         self._start_from_rev = opts.get("starting_rev")
         self.impl = ddl.DefaultImpl.get_by_dialect(dialect)(
index b09579699f885aa7e68425f27b43e8d7c10566df..e485c11234fb7b94c6a9fc24de478560ddc4371c 100644 (file)
@@ -6,6 +6,19 @@ Changelog
 .. changelog::
     :version: 0.8.10
 
+    .. change:: 406
+      :tags: bug, versioning
+      :tickets: 406
+
+      The alembic_version table, when initially created, now establishes a
+      primary key constraint on the "version_num" column, to suit database
+      engines that don't support tables without primary keys.   This behavior
+      can be controlled using the parameter
+      :paramref:`.EnvironmentContext.configure.version_table_pk`.  Note that
+      this change only applies to the initial creation of the alembic_version
+      table; it does not impact any existing alembic_version table already
+      present.
+
     .. change:: 402
       :tags: bug, batch
       :tickets: 402
index 49cdaf2aa08fdeaebed73808a9f586b5ac5067c8..55152c9a9e48efdf886b311f55029184bd033d1c 100644 (file)
@@ -5,10 +5,11 @@ from alembic.testing.fixtures import TestBase, capture_context_buffer
 from alembic.testing.env import staging_env, _sqlite_testing_config, \
     three_rev_fixture, clear_staging_env, _no_sql_testing_config, \
     _sqlite_file_db, write_script, env_file_fixture
-from alembic.testing import eq_, assert_raises_message, mock
+from alembic.testing import eq_, assert_raises_message, mock, assert_raises
 from alembic import util
 from contextlib import contextmanager
 import re
+from sqlalchemy import exc as sqla_exc
 
 
 class _BufMixin(object):
@@ -163,7 +164,7 @@ class RevisionTest(TestBase):
     def tearDown(self):
         clear_staging_env()
 
-    def _env_fixture(self):
+    def _env_fixture(self, version_table_pk=True):
         env_file_fixture("""
 
 from sqlalchemy import MetaData, engine_from_config
@@ -175,7 +176,10 @@ engine = engine_from_config(
 
 connection = engine.connect()
 
-context.configure(connection=connection, target_metadata=target_metadata)
+context.configure(
+    connection=connection, target_metadata=target_metadata,
+    version_table_pk=%r
+)
 
 try:
     with context.begin_transaction():
@@ -183,7 +187,7 @@ try:
 finally:
     connection.close()
 
-""")
+""" % (version_table_pk, ))
 
     def test_create_rev_plain_db_not_up_to_date(self):
         self._env_fixture()
@@ -249,12 +253,24 @@ finally:
             command.revision, self.cfg, autogenerate=True
         )
 
-    def test_err_correctly_raised_on_dupe_rows(self):
+    def test_pk_constraint_normally_prevents_dupe_rows(self):
         self._env_fixture()
         command.revision(self.cfg)
         r2 = command.revision(self.cfg)
         db = _sqlite_file_db()
         command.upgrade(self.cfg, "head")
+        assert_raises(
+            sqla_exc.IntegrityError,
+            db.execute,
+            "insert into alembic_version values ('%s')" % r2.revision
+        )
+
+    def test_err_correctly_raised_on_dupe_rows_no_pk(self):
+        self._env_fixture(version_table_pk=False)
+        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,
index 45a30dfa7cf7cdd224183728ae7e1e653aeddeb3..29530c0ec4001855381691f5a15af8fa4eb33c0c 100644 (file)
@@ -58,12 +58,24 @@ class TestMigrationContext(TestBase):
         context = self.make_one(dialect_name='sqlite',
                                 opts={'version_table': 'explicit'})
         eq_(context._version.name, 'explicit')
+        eq_(context._version.primary_key.name, 'explicit_pkc')
 
     def test_config_explicit_version_table_schema(self):
         context = self.make_one(dialect_name='sqlite',
                                 opts={'version_table_schema': 'explicit'})
         eq_(context._version.schema, 'explicit')
 
+    def test_config_explicit_no_pk(self):
+        context = self.make_one(dialect_name='sqlite',
+                                opts={'version_table_pk': False})
+        eq_(len(context._version.primary_key), 0)
+
+    def test_config_explicit_w_pk(self):
+        context = self.make_one(dialect_name='sqlite',
+                                opts={'version_table_pk': True})
+        eq_(len(context._version.primary_key), 1)
+        eq_(context._version.primary_key.name, "alembic_version_pkc")
+
     def test_get_current_revision_doesnt_create_version_table(self):
         context = self.make_one(connection=self.connection,
                                 opts={'version_table': 'version_table'})