From: Mike Bayer Date: Wed, 11 Jan 2017 14:33:01 +0000 (-0500) Subject: Add primary key constraint to alembic_version X-Git-Tag: rel_0_8_10~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b57b164177f18d09386097cacc3d425bb20c2be8;p=thirdparty%2Fsqlalchemy%2Falembic.git Add primary key constraint to alembic_version 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 --- diff --git a/alembic/runtime/environment.py b/alembic/runtime/environment.py index b2d0ba7b..30c8ab92 100644 --- a/alembic/runtime/environment.py +++ b/alembic/runtime/environment.py @@ -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: diff --git a/alembic/runtime/migration.py b/alembic/runtime/migration.py index a0b386df..33cd74fb 100644 --- a/alembic/runtime/migration.py +++ b/alembic/runtime/migration.py @@ -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)( diff --git a/docs/build/changelog.rst b/docs/build/changelog.rst index b0957969..e485c112 100644 --- a/docs/build/changelog.rst +++ b/docs/build/changelog.rst @@ -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 diff --git a/tests/test_command.py b/tests/test_command.py index 49cdaf2a..55152c9a 100644 --- a/tests/test_command.py +++ b/tests/test_command.py @@ -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, diff --git a/tests/test_version_table.py b/tests/test_version_table.py index 45a30dfa..29530c0e 100644 --- a/tests/test_version_table.py +++ b/tests/test_version_table.py @@ -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'})