Barry Warsaw pushed to branch master at mailman / Mailman

Commits:
16903aae by Aurélien Bompard at 2015-09-15T01:33:36Z
Test the schema migrations with Alembic

- - - - -
d8e96d95 by Aurélien Bompard at 2015-09-15T01:33:37Z
Fixes for the review comments

- - - - -
2b091495 by Aurélien Bompard at 2015-09-15T01:33:37Z
Fix it with PostgreSQL

- - - - -


7 changed files:

- src/mailman/database/alembic/script.py.mako
- src/mailman/database/alembic/versions/16c2b25c7b_list_subscription_policy.py
- src/mailman/database/alembic/versions/33e1f5f6fa8_.py
- 
src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py
- src/mailman/database/alembic/versions/51b7f92bd06c_initial.py
- + src/mailman/database/helpers.py
- + src/mailman/database/tests/test_migrations.py


Changes:

=====================================
src/mailman/database/alembic/script.py.mako
=====================================
--- a/src/mailman/database/alembic/script.py.mako
+++ b/src/mailman/database/alembic/script.py.mako
@@ -10,8 +10,9 @@ Create Date: ${create_date}
 revision = ${repr(up_revision)}
 down_revision = ${repr(down_revision)}
 
-from alembic import op
 import sqlalchemy as sa
+from alembic import op
+from mailman.database.helpers import is_sqlite, exists_in_db
 ${imports if imports else ""}
 
 def upgrade():


=====================================
src/mailman/database/alembic/versions/16c2b25c7b_list_subscription_policy.py
=====================================
--- 
a/src/mailman/database/alembic/versions/16c2b25c7b_list_subscription_policy.py
+++ 
b/src/mailman/database/alembic/versions/16c2b25c7b_list_subscription_policy.py
@@ -14,14 +14,17 @@ from alembic import op
 import sqlalchemy as sa
 
 from mailman.database.types import Enum
+from mailman.database.helpers import is_sqlite, exists_in_db
 from mailman.interfaces.mailinglist import SubscriptionPolicy
 
 
 def upgrade():
 
     ### Update the schema
-    op.add_column('mailinglist', sa.Column(
-        'subscription_policy', Enum(SubscriptionPolicy), nullable=True))
+    if not exists_in_db(op.get_bind(), 'mailinglist', 'subscription_policy'):
+        # SQLite may not have removed it when downgrading.
+        op.add_column('mailinglist', sa.Column(
+            'subscription_policy', Enum(SubscriptionPolicy), nullable=True))
 
     ### Now migrate the data
     # don't import the table definition from the models, it may break this
@@ -36,6 +39,6 @@ def upgrade():
 
 
 def downgrade():
-    if op.get_bind().dialect.name != 'sqlite':
+    if not is_sqlite(op.get_bind()):
         # SQLite does not support dropping columns.
         op.drop_column('mailinglist', 'subscription_policy')


=====================================
src/mailman/database/alembic/versions/33e1f5f6fa8_.py
=====================================
--- a/src/mailman/database/alembic/versions/33e1f5f6fa8_.py
+++ b/src/mailman/database/alembic/versions/33e1f5f6fa8_.py
@@ -33,6 +33,7 @@ __all__ = [
 
 from alembic import op
 import sqlalchemy as sa
+from mailman.database.helpers import is_sqlite
 
 
 # revision identifiers, used by Alembic.
@@ -40,21 +41,33 @@ revision = '33e1f5f6fa8'
 down_revision = '51b7f92bd06c'
 
 
+COLUMNS_TO_CHANGE = (
+    ('message', 'message_id_hash'),
+    ('message', 'path'),
+    ('pended', 'token'),
+    ('_request', 'data_hash'),
+    ('user', 'password'),
+    )
+
+
 def upgrade():
-    if op.get_bind().dialect.name != 'sqlite':
+    if is_sqlite(op.get_bind()):
         # SQLite does not support altering columns.
-        op.alter_column('message', 'message_id_hash', type_=sa.Unicode)
-        op.alter_column('message', 'path', type_=sa.Unicode)
-        op.alter_column('pended', 'token', type_=sa.Unicode)
-        op.alter_column('_request', 'data_hash', type_=sa.Unicode)
-        op.alter_column('user', 'password', type_=sa.Unicode)
+        return
+    for table, column in COLUMNS_TO_CHANGE:
+        op.alter_column(table, column, type_=sa.Unicode)
 
 
 def downgrade():
-    if op.get_bind().dialect.name != 'sqlite':
+    if is_sqlite(op.get_bind()):
         # SQLite does not support altering columns.
-        op.alter_column('message', 'message_id_hash', type_=sa.LargeBinary)
-        op.alter_column('message', 'path', type_=sa.LargeBinary)
-        op.alter_column('pended', 'token', type_=sa.LargeBinary)
-        op.alter_column('_request', 'data_hash', type_=sa.LargeBinary)
-        op.alter_column('user', 'password', type_=sa.LargeBinary)
+        return
+    for table, column in COLUMNS_TO_CHANGE:
+        if op.get_bind().dialect.name == 'postgresql':
+            # PostgreSQL needs the USING clause that Alembic does not support
+            # yet.
+            op.execute(('ALTER TABLE "{table}" ALTER COLUMN "{column}" '
+                        'TYPE BYTEA USING decode("{column}", 
\'UTF8\')').format(
+                       table=table, column=column))
+        else:
+            op.alter_column(table, column, type_=sa.LargeBinary)


=====================================
src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py
=====================================
--- 
a/src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py
+++ 
b/src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py
@@ -29,6 +29,7 @@ down_revision = '33e1f5f6fa8'
 
 from alembic import op
 import sqlalchemy as sa
+from mailman.database.helpers import is_sqlite, exists_in_db
 
 
 def upgrade():
@@ -40,16 +41,20 @@ def upgrade():
         sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
         sa.PrimaryKeyConstraint('user_id', 'domain_id')
         )
-    op.add_column(
-        'user',
-        sa.Column('is_server_owner', sa.Boolean(), nullable=True))
-    if op.get_bind().dialect.name != 'sqlite':
+    if not exists_in_db(op.get_bind(), 'user', 'is_server_owner'):
+        # SQLite may not have removed it when downgrading.
+        op.add_column(
+            'user',
+            sa.Column('is_server_owner', sa.Boolean(), nullable=True))
+    if not is_sqlite(op.get_bind()):
         op.drop_column('domain', 'contact_address')
 
 
 def downgrade():
-    if op.get_bind().dialect.name != 'sqlite':
+    if not is_sqlite(op.get_bind()):
         op.drop_column('user', 'is_server_owner')
+    if not exists_in_db(op.get_bind(), 'domain', 'contact_address'):
+        # SQLite may not have removed it.
         op.add_column(
             'domain',
             sa.Column('contact_address', sa.VARCHAR(), nullable=True))


=====================================
src/mailman/database/alembic/versions/51b7f92bd06c_initial.py
=====================================
--- a/src/mailman/database/alembic/versions/51b7f92bd06c_initial.py
+++ b/src/mailman/database/alembic/versions/51b7f92bd06c_initial.py
@@ -35,8 +35,9 @@ __all__ = [
     ]
 
 
-from alembic import op
 import sqlalchemy as sa
+from alembic import op
+from mailman.database.helpers import is_sqlite, exists_in_db
 
 
 # Revision identifiers, used by Alembic.
@@ -46,7 +47,7 @@ down_revision = None
 
 def upgrade():
     op.drop_table('version')
-    if op.get_bind().dialect.name != 'sqlite':
+    if not is_sqlite(op.get_bind()):
         # SQLite does not support dropping columns.
         op.drop_column('mailinglist', 'acceptable_aliases_id')
     op.create_index(op.f('ix_user__user_id'), 'user',
@@ -58,6 +59,7 @@ def downgrade():
     op.create_table('version')
     op.create_index('ix_user_user_id', 'user', ['_user_id'], unique=False)
     op.drop_index(op.f('ix_user__user_id'), table_name='user')
-    op.add_column(
-        'mailinglist',
-        sa.Column('acceptable_aliases_id', sa.INTEGER(), nullable=True))
+    if not exists_in_db(op.get_bind(), 'mailinglist', 'acceptable_aliases_id'):
+        op.add_column(
+            'mailinglist',
+            sa.Column('acceptable_aliases_id', sa.INTEGER(), nullable=True))


=====================================
src/mailman/database/helpers.py
=====================================
--- /dev/null
+++ b/src/mailman/database/helpers.py
@@ -0,0 +1,41 @@
+# Copyright (C) 2015 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Common database helpers."""
+
+__all__ = [
+    'exists_in_db',
+    'is_sqlite',
+    ]
+
+import sqlalchemy as sa
+
+
+def is_sqlite(bind):
+    return bind.dialect.name == 'sqlite'
+
+
+def exists_in_db(bind, tablename, columnname=None):
+    md = sa.MetaData()
+    md.reflect(bind=bind)
+    if columnname is None:
+        return tablename in md.tables
+    else:
+        return (
+            tablename in md.tables and
+            columnname in [c.name for c in md.tables[tablename].columns]
+            )


=====================================
src/mailman/database/tests/test_migrations.py
=====================================
--- /dev/null
+++ b/src/mailman/database/tests/test_migrations.py
@@ -0,0 +1,58 @@
+# Copyright (C) 2015 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Test database schema migrations with Alembic"""
+
+__all__ = [
+    'TestMigrations',
+    ]
+
+
+import alembic.command
+import sqlalchemy as sa
+import unittest
+
+from mailman.config import config
+from mailman.database.alembic import alembic_cfg
+from mailman.database.model import Model
+from mailman.testing.layers import ConfigLayer
+
+
+
+class TestMigrations(unittest.TestCase):
+
+    layer = ConfigLayer
+
+    def setUp(self):
+        alembic.command.stamp(alembic_cfg, 'head')
+
+    def tearDown(self):
+        # Drop and restore a virgin database.
+        md = sa.MetaData(bind=config.db.engine)
+        md.reflect()
+        md.drop_all()
+        Model.metadata.create_all(config.db.engine)
+
+    def test_all_migrations(self):
+        script_dir = alembic.script.ScriptDirectory.from_config(alembic_cfg)
+        revisions = [sc.revision for sc in
+                     script_dir.walk_revisions('base', 'heads')]
+        for revision in revisions:
+            alembic.command.downgrade(alembic_cfg, revision)
+        revisions.reverse()
+        for revision in revisions:
+            alembic.command.upgrade(alembic_cfg, revision)



View it on GitLab: 
https://gitlab.com/mailman/mailman/compare/2f6a250ca38a8846c1d417c380d6311de35c3ee7...2b09149598144e49cf06e6293b3b9de5364bba2a
_______________________________________________
Mailman-checkins mailing list
Mailman-checkins@python.org
Unsubscribe: 
https://mail.python.org/mailman/options/mailman-checkins/archive%40jab.org

Reply via email to