summaryrefslogtreecommitdiff
path: root/src/mailman/database
diff options
context:
space:
mode:
authorBarry Warsaw2016-07-29 19:28:46 -0400
committerBarry Warsaw2016-07-29 19:28:46 -0400
commitfebb5289e82c4424cdbcc2297e967bd894cbc8cf (patch)
treebd71f4cbf6988049ac4d5dd65ceb7d5cc51902e7 /src/mailman/database
parent90e84bee5f47cbcdb9e9c367c60a877e325ef3e7 (diff)
downloadmailman-febb5289e82c4424cdbcc2297e967bd894cbc8cf.tar.gz
mailman-febb5289e82c4424cdbcc2297e967bd894cbc8cf.tar.zst
mailman-febb5289e82c4424cdbcc2297e967bd894cbc8cf.zip
Diffstat (limited to 'src/mailman/database')
-rw-r--r--src/mailman/database/alembic/versions/2bb9b382198_workflow_state_table.py9
-rw-r--r--src/mailman/database/alembic/versions/33bc0099223_add_member_indexes.py31
-rw-r--r--src/mailman/database/alembic/versions/33e1f5f6fa8_.py3
-rw-r--r--src/mailman/database/alembic/versions/42756496720_header_matches.py12
-rw-r--r--src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py5
-rw-r--r--src/mailman/database/alembic/versions/47294d3a604_pendable_indexes.py5
-rw-r--r--src/mailman/database/alembic/versions/70af5a4e5790_digests.py9
-rw-r--r--src/mailman/database/alembic/versions/7b254d88f122_members_and_list_moderation_action.py6
-rw-r--r--src/mailman/database/alembic/versions/d4fbb4fd34ca_header_match_order.py32
-rw-r--r--src/mailman/database/alembic/versions/fa0d96e28631_template_manager.py55
-rw-r--r--src/mailman/database/base.py2
-rw-r--r--src/mailman/database/helpers.py5
-rw-r--r--src/mailman/database/mysql.py34
-rw-r--r--src/mailman/database/tests/test_factory.py33
-rw-r--r--src/mailman/database/tests/test_migrations.py14
-rw-r--r--src/mailman/database/types.py46
16 files changed, 212 insertions, 89 deletions
diff --git a/src/mailman/database/alembic/versions/2bb9b382198_workflow_state_table.py b/src/mailman/database/alembic/versions/2bb9b382198_workflow_state_table.py
index 64def0253..df7a10e00 100644
--- a/src/mailman/database/alembic/versions/2bb9b382198_workflow_state_table.py
+++ b/src/mailman/database/alembic/versions/2bb9b382198_workflow_state_table.py
@@ -9,6 +9,7 @@ Create Date: 2015-03-25 18:09:18.338790
import sqlalchemy as sa
from alembic import op
+from mailman.database.types import SAUnicode
# Revision identifiers, used by Alembic.
@@ -19,10 +20,10 @@ down_revision = '16c2b25c7b'
def upgrade():
op.create_table(
'workflowstate',
- sa.Column('name', sa.Unicode(), nullable=False),
- sa.Column('token', sa.Unicode(), nullable=False),
- sa.Column('step', sa.Unicode(), nullable=True),
- sa.Column('data', sa.Unicode(), nullable=True),
+ sa.Column('name', SAUnicode(), nullable=False),
+ sa.Column('token', SAUnicode(), nullable=False),
+ sa.Column('step', SAUnicode(), nullable=True),
+ sa.Column('data', SAUnicode(), nullable=True),
sa.PrimaryKeyConstraint('name', 'token')
)
diff --git a/src/mailman/database/alembic/versions/33bc0099223_add_member_indexes.py b/src/mailman/database/alembic/versions/33bc0099223_add_member_indexes.py
index ce8f2f3f1..5f01f4a66 100644
--- a/src/mailman/database/alembic/versions/33bc0099223_add_member_indexes.py
+++ b/src/mailman/database/alembic/versions/33bc0099223_add_member_indexes.py
@@ -7,6 +7,7 @@ Create Date: 2015-11-19 23:04:42.449553
"""
from alembic import op
+from mailman.database.helpers import is_mysql
# Revision identifiers, used by Alembic.
@@ -15,22 +16,28 @@ down_revision = '42756496720'
def upgrade():
- op.create_index(op.f('ix_member_address_id'),
- 'member', ['address_id'],
- unique=False)
- op.create_index(op.f('ix_member_preferences_id'),
- 'member', ['preferences_id'],
- unique=False)
- op.create_index(op.f('ix_member_user_id'),
- 'member', ['user_id'],
- unique=False)
op.create_index(op.f('ix_address_email'),
'address', ['email'],
unique=False)
+ # MySQL automatically creates the indexes for primary keys so don't need
+ # to do it explicitly again.
+ if not is_mysql(op.get_bind()):
+ op.create_index(op.f('ix_member_address_id'),
+ 'member', ['address_id'],
+ unique=False)
+ op.create_index(op.f('ix_member_preferences_id'),
+ 'member', ['preferences_id'],
+ unique=False)
+ op.create_index(op.f('ix_member_user_id'),
+ 'member', ['user_id'],
+ unique=False)
def downgrade():
op.drop_index(op.f('ix_address_email'), table_name='address')
- op.drop_index(op.f('ix_member_user_id'), table_name='member')
- op.drop_index(op.f('ix_member_preferences_id'), table_name='member')
- op.drop_index(op.f('ix_member_address_id'), table_name='member')
+ # MySQL automatically creates and removes the indexes for primary keys.
+ # So, you cannot drop it without removing the foreign key constraint.
+ if not is_mysql(op.get_bind()):
+ op.drop_index(op.f('ix_member_user_id'), table_name='member')
+ op.drop_index(op.f('ix_member_preferences_id'), table_name='member')
+ op.drop_index(op.f('ix_member_address_id'), table_name='member')
diff --git a/src/mailman/database/alembic/versions/33e1f5f6fa8_.py b/src/mailman/database/alembic/versions/33e1f5f6fa8_.py
index a8b572518..72754d3c5 100644
--- a/src/mailman/database/alembic/versions/33e1f5f6fa8_.py
+++ b/src/mailman/database/alembic/versions/33e1f5f6fa8_.py
@@ -29,6 +29,7 @@ import sqlalchemy as sa
from alembic import op
from mailman.database.helpers import is_sqlite
+from mailman.database.types import SAUnicode
# Revision identifiers, used by Alembic.
@@ -50,7 +51,7 @@ def upgrade():
# SQLite does not support altering columns.
return
for table, column in COLUMNS_TO_CHANGE:
- op.alter_column(table, column, type_=sa.Unicode)
+ op.alter_column(table, column, type_=SAUnicode)
def downgrade():
diff --git a/src/mailman/database/alembic/versions/42756496720_header_matches.py b/src/mailman/database/alembic/versions/42756496720_header_matches.py
index 4514fb398..d1308a134 100644
--- a/src/mailman/database/alembic/versions/42756496720_header_matches.py
+++ b/src/mailman/database/alembic/versions/42756496720_header_matches.py
@@ -10,7 +10,7 @@ import sqlalchemy as sa
from alembic import op
from mailman.database.helpers import exists_in_db, is_sqlite
-
+from mailman.database.types import SAUnicode
# Revision identifiers, used by Alembic.
revision = '42756496720'
@@ -23,9 +23,9 @@ def upgrade():
'headermatch',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('mailing_list_id', sa.Integer(), nullable=True),
- sa.Column('header', sa.Unicode(), nullable=False),
- sa.Column('pattern', sa.Unicode(), nullable=False),
- sa.Column('chain', sa.Unicode(), nullable=True),
+ sa.Column('header', SAUnicode(), nullable=False),
+ sa.Column('pattern', SAUnicode(), nullable=False),
+ sa.Column('chain', SAUnicode(), nullable=True),
sa.ForeignKeyConstraint(['mailing_list_id'], ['mailinglist.id'], ),
sa.PrimaryKeyConstraint('id')
)
@@ -73,8 +73,8 @@ def downgrade():
header_match_table = sa.sql.table(
'headermatch',
sa.sql.column('mailing_list_id', sa.Integer),
- sa.sql.column('header', sa.Unicode),
- sa.sql.column('pattern', sa.Unicode),
+ sa.sql.column('header', SAUnicode),
+ sa.sql.column('pattern', SAUnicode),
)
for mlist_id, header, pattern in connection.execute(
header_match_table.select()).fetchall():
diff --git a/src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py b/src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py
index 1c689670a..bf734934b 100644
--- a/src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py
+++ b/src/mailman/database/alembic/versions/46e92facee7_add_serverowner_domainowner.py
@@ -56,8 +56,9 @@ def downgrade():
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.
+ # SQLite may not have removed it. Add a fixed length VARCHAR for
+ # MySQL.
op.add_column(
'domain',
- sa.Column('contact_address', sa.VARCHAR(), nullable=True))
+ sa.Column('contact_address', sa.VARCHAR(255), nullable=True))
op.drop_table('domain_owner')
diff --git a/src/mailman/database/alembic/versions/47294d3a604_pendable_indexes.py b/src/mailman/database/alembic/versions/47294d3a604_pendable_indexes.py
index 5e465af24..4aafb4613 100644
--- a/src/mailman/database/alembic/versions/47294d3a604_pendable_indexes.py
+++ b/src/mailman/database/alembic/versions/47294d3a604_pendable_indexes.py
@@ -13,6 +13,7 @@ import json
import sqlalchemy as sa
from alembic import op
+from mailman.database.types import SAUnicode
# revision identifiers, used by Alembic.
@@ -34,8 +35,8 @@ pended_table = sa.sql.table(
keyvalue_table = sa.sql.table(
'pendedkeyvalue',
sa.sql.column('id', sa.Integer),
- sa.sql.column('key', sa.Unicode),
- sa.sql.column('value', sa.Unicode),
+ sa.sql.column('key', SAUnicode),
+ sa.sql.column('value', SAUnicode),
sa.sql.column('pended_id', sa.Integer),
)
diff --git a/src/mailman/database/alembic/versions/70af5a4e5790_digests.py b/src/mailman/database/alembic/versions/70af5a4e5790_digests.py
index 50c87ccf3..1f9a93bcd 100644
--- a/src/mailman/database/alembic/versions/70af5a4e5790_digests.py
+++ b/src/mailman/database/alembic/versions/70af5a4e5790_digests.py
@@ -20,7 +20,10 @@ down_revision = '47294d3a604'
def upgrade():
with op.batch_alter_table('mailinglist') as batch_op:
- batch_op.alter_column('digestable', new_column_name='digests_enabled')
+ batch_op.alter_column('digestable',
+ new_column_name='digests_enabled',
+ existing_type=sa.Boolean)
+ # All column modifications require existing types for Mysql.
batch_op.drop_column('nondigestable')
# Non-database migration: rename the list's data-path.
for dirname in os.listdir(config.LIST_DATA_DIR):
@@ -34,7 +37,9 @@ def upgrade():
def downgrade():
with op.batch_alter_table('mailinglist') as batch_op:
- batch_op.alter_column('digests_enabled', new_column_name='digestable')
+ batch_op.alter_column('digests_enabled',
+ new_column_name='digestable',
+ existing_type=sa.Boolean)
# The data for this column is lost, it's not used anyway.
batch_op.add_column(sa.Column('nondigestable', sa.Boolean))
for dirname in os.listdir(config.LIST_DATA_DIR):
diff --git a/src/mailman/database/alembic/versions/7b254d88f122_members_and_list_moderation_action.py b/src/mailman/database/alembic/versions/7b254d88f122_members_and_list_moderation_action.py
index 6b4d7bfd1..9f19e3278 100644
--- a/src/mailman/database/alembic/versions/7b254d88f122_members_and_list_moderation_action.py
+++ b/src/mailman/database/alembic/versions/7b254d88f122_members_and_list_moderation_action.py
@@ -13,7 +13,7 @@ fallback to the list's default.
import sqlalchemy as sa
from alembic import op
-from mailman.database.types import Enum
+from mailman.database.types import Enum, SAUnicode
from mailman.interfaces.action import Action
from mailman.interfaces.member import MemberRole
@@ -26,7 +26,7 @@ down_revision = 'd4fbb4fd34ca'
mailinglist_table = sa.sql.table(
'mailinglist',
sa.sql.column('id', sa.Integer),
- sa.sql.column('list_id', sa.Unicode),
+ sa.sql.column('list_id', SAUnicode),
sa.sql.column('default_member_action', Enum(Action)),
sa.sql.column('default_nonmember_action', Enum(Action)),
)
@@ -35,7 +35,7 @@ mailinglist_table = sa.sql.table(
member_table = sa.sql.table(
'member',
sa.sql.column('id', sa.Integer),
- sa.sql.column('list_id', sa.Unicode),
+ sa.sql.column('list_id', SAUnicode),
sa.sql.column('role', Enum(MemberRole)),
sa.sql.column('moderation_action', Enum(Action)),
)
diff --git a/src/mailman/database/alembic/versions/d4fbb4fd34ca_header_match_order.py b/src/mailman/database/alembic/versions/d4fbb4fd34ca_header_match_order.py
index 2608bb812..313963b86 100644
--- a/src/mailman/database/alembic/versions/d4fbb4fd34ca_header_match_order.py
+++ b/src/mailman/database/alembic/versions/d4fbb4fd34ca_header_match_order.py
@@ -9,6 +9,7 @@ Create Date: 2016-02-01 15:57:09.807678
import sqlalchemy as sa
from alembic import op
+from mailman.database.helpers import is_mysql
# Revision identifiers, used by Alembic.
@@ -20,19 +21,34 @@ def upgrade():
with op.batch_alter_table('headermatch') as batch_op:
batch_op.add_column(
sa.Column('position', sa.Integer(), nullable=True))
- batch_op.alter_column(
- 'mailing_list_id', existing_type=sa.INTEGER(), nullable=False)
batch_op.create_index(
op.f('ix_headermatch_position'), ['position'], unique=False)
- batch_op.create_index(
- op.f('ix_headermatch_mailing_list_id'), ['mailing_list_id'],
- unique=False)
+ if not is_mysql(op.get_bind()):
+ # MySQL automatically creates indexes for primary keys.
+ batch_op.create_index(
+ op.f('ix_headermatch_mailing_list_id'), ['mailing_list_id'],
+ unique=False)
+ # MySQL doesn't allow changing columns used in a foreign key
+ # constrains since MySQL version 5.6. We need to drop the
+ # constraint before changing the column. But, since the
+ # constraint name is auto-generated, we can't really hardcode the
+ # name here to use batch_op.drop_constraint(). Until we have a
+ # better fix for this, it should be safe to skip this.
+ batch_op.alter_column(
+ 'mailing_list_id', existing_type=sa.INTEGER(), nullable=False)
def downgrade():
with op.batch_alter_table('headermatch') as batch_op:
- batch_op.drop_index(op.f('ix_headermatch_mailing_list_id'))
batch_op.drop_index(op.f('ix_headermatch_position'))
- batch_op.alter_column(
- 'mailing_list_id', existing_type=sa.INTEGER(), nullable=True)
batch_op.drop_column('position')
+
+ if not is_mysql(op.get_bind()):
+ # MySQL automatically creates and removes the indexes for primary
+ # keys. So, you cannot drop it without removing the foreign key
+ # constraint.
+ batch_op.drop_index(op.f('ix_headermatch_mailing_list_id'))
+ # MySQL doesn't allow changing columns used in foreign_key
+ # constraints.
+ batch_op.alter_column(
+ 'mailing_list_id', existing_type=sa.INTEGER(), nullable=True)
diff --git a/src/mailman/database/alembic/versions/fa0d96e28631_template_manager.py b/src/mailman/database/alembic/versions/fa0d96e28631_template_manager.py
index 5920dd4e4..58d688453 100644
--- a/src/mailman/database/alembic/versions/fa0d96e28631_template_manager.py
+++ b/src/mailman/database/alembic/versions/fa0d96e28631_template_manager.py
@@ -12,6 +12,7 @@ import sqlalchemy as sa
from alembic import op
from mailman.config import config
from mailman.database.helpers import exists_in_db
+from mailman.database.types import SAUnicode
# revision identifiers, used by Alembic.
@@ -35,8 +36,8 @@ def upgrade():
op.create_table(
'file_cache',
sa.Column('id', sa.Integer(), nullable=False),
- sa.Column('key', sa.Unicode(), nullable=False),
- sa.Column('file_id', sa.Unicode(), nullable=True),
+ sa.Column('key', SAUnicode(), nullable=False),
+ sa.Column('file_id', SAUnicode(), nullable=True),
sa.Column('is_bytes', sa.Boolean(), nullable=False),
sa.Column('created_on', sa.DateTime(), nullable=False),
sa.Column('expires_on', sa.DateTime(), nullable=False),
@@ -45,10 +46,10 @@ def upgrade():
template_table = op.create_table(
'template',
sa.Column('id', sa.Integer(), nullable=False),
- sa.Column('name', sa.Unicode(), nullable=False),
- sa.Column('context', sa.Unicode(), nullable=True),
- sa.Column('uri', sa.Unicode(), nullable=False),
- sa.Column('username', sa.Unicode(), nullable=True),
+ sa.Column('name', SAUnicode(), nullable=False),
+ sa.Column('context', SAUnicode(), nullable=True),
+ sa.Column('uri', SAUnicode(), nullable=False),
+ sa.Column('username', SAUnicode(), nullable=True),
sa.Column('password', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
@@ -60,13 +61,13 @@ def upgrade():
mlist_table = sa.sql.table(
'mailinglist',
sa.sql.column('id', sa.Integer),
- sa.sql.column('list_id', sa.Unicode),
- sa.sql.column('digest_footer_uri', sa.Unicode),
- sa.sql.column('digest_header_uri', sa.Unicode),
- sa.sql.column('footer_uri', sa.Unicode),
- sa.sql.column('header_uri', sa.Unicode),
- sa.sql.column('goodbye_message_uri', sa.Unicode),
- sa.sql.column('welcome_message_uri', sa.Unicode),
+ sa.sql.column('list_id', SAUnicode),
+ sa.sql.column('digest_footer_uri', SAUnicode),
+ sa.sql.column('digest_header_uri', SAUnicode),
+ sa.sql.column('footer_uri', SAUnicode),
+ sa.sql.column('header_uri', SAUnicode),
+ sa.sql.column('goodbye_message_uri', SAUnicode),
+ sa.sql.column('welcome_message_uri', SAUnicode),
)
for (mlist_id, list_id,
digest_footer_uri, digest_header_uri,
@@ -131,30 +132,30 @@ def downgrade():
if not exists_in_db(op.get_bind(), 'mailinglist', column):
op.add_column(
'mailinglist',
- sa.Column(column, sa.Unicode, nullable=True))
- op.add_column('domain', sa.Column('base_url', sa.Unicode))
+ sa.Column(column, SAUnicode, nullable=True))
+ op.add_column('domain', sa.Column('base_url', SAUnicode))
# Put all the templates with a context mapping the list-id back into the
# mailinglist table. No other contexts are supported, so just throw those
# away.
template_table = sa.sql.table(
'template',
sa.sql.column('id', sa.Integer),
- sa.sql.column('name', sa.Unicode),
- sa.sql.column('context', sa.Unicode),
- sa.sql.column('uri', sa.Unicode),
- sa.sql.column('username', sa.Unicode),
- sa.sql.column('password', sa.Unicode),
+ sa.sql.column('name', SAUnicode),
+ sa.sql.column('context', SAUnicode),
+ sa.sql.column('uri', SAUnicode),
+ sa.sql.column('username', SAUnicode),
+ sa.sql.column('password', SAUnicode),
)
mlist_table = sa.sql.table(
'mailinglist',
sa.sql.column('id', sa.Integer),
- sa.sql.column('list_id', sa.Unicode),
- sa.sql.column('digest_footer_uri', sa.Unicode),
- sa.sql.column('digest_header_uri', sa.Unicode),
- sa.sql.column('footer_uri', sa.Unicode),
- sa.sql.column('header_uri', sa.Unicode),
- sa.sql.column('goodbye_message_uri', sa.Unicode),
- sa.sql.column('welcome_message_uri', sa.Unicode),
+ sa.sql.column('list_id', SAUnicode),
+ sa.sql.column('digest_footer_uri', SAUnicode),
+ sa.sql.column('digest_header_uri', SAUnicode),
+ sa.sql.column('footer_uri', SAUnicode),
+ sa.sql.column('header_uri', SAUnicode),
+ sa.sql.column('goodbye_message_uri', SAUnicode),
+ sa.sql.column('welcome_message_uri', SAUnicode),
)
connection = op.get_bind()
for (table_id, name, context, uri, username, password
diff --git a/src/mailman/database/base.py b/src/mailman/database/base.py
index dda3665af..f57228029 100644
--- a/src/mailman/database/base.py
+++ b/src/mailman/database/base.py
@@ -103,7 +103,7 @@ class SABaseDatabase:
# engines, and yes, we could have chmod'd the file after the fact, but
# half dozen and all...
self.url = url
- self.engine = create_engine(url)
+ self.engine = create_engine(url, isolation_level='READ UNCOMMITTED')
session = sessionmaker(bind=self.engine)
self.store = session()
self.store.commit()
diff --git a/src/mailman/database/helpers.py b/src/mailman/database/helpers.py
index f58d559c5..b1dc381df 100644
--- a/src/mailman/database/helpers.py
+++ b/src/mailman/database/helpers.py
@@ -28,6 +28,11 @@ def is_sqlite(bind):
@public
+def is_mysql(bind):
+ return bind.dialect.name == 'mysql'
+
+
+@public
def exists_in_db(bind, tablename, columnname=None):
md = sa.MetaData()
md.reflect(bind=bind)
diff --git a/src/mailman/database/mysql.py b/src/mailman/database/mysql.py
new file mode 100644
index 000000000..0c0de54a9
--- /dev/null
+++ b/src/mailman/database/mysql.py
@@ -0,0 +1,34 @@
+# Copyright (C) 2016 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/>.
+
+"""MySQL database support"""
+
+from mailman import public
+from mailman.database.base import SABaseDatabase
+from mailman.database.model import Model
+
+
+@public
+class MySQLDatabase(SABaseDatabase):
+ """Database class for MySQL."""
+
+ def _post_reset(self, store):
+ """Reset AUTO_INCREMENT counters for all the tables."""
+ super()._post_reset(store)
+ tables = reversed(Model.metadata.sorted_tables)
+ for table in tables:
+ store.execute('ALTER TABLE {} AUTO_INCREMENT = 1;'.format(table))
diff --git a/src/mailman/database/tests/test_factory.py b/src/mailman/database/tests/test_factory.py
index 74b45e43a..b0aa89fc4 100644
--- a/src/mailman/database/tests/test_factory.py
+++ b/src/mailman/database/tests/test_factory.py
@@ -24,10 +24,12 @@ from contextlib import suppress
from mailman.config import config
from mailman.database.alembic import alembic_cfg
from mailman.database.factory import LAST_STORM_SCHEMA_VERSION, SchemaManager
+from mailman.database.helpers import is_mysql
from mailman.database.model import Model
+from mailman.database.types import SAUnicode
from mailman.interfaces.database import DatabaseError
from mailman.testing.layers import ConfigLayer
-from sqlalchemy import Column, Integer, MetaData, Table, Unicode
+from sqlalchemy import Column, Integer, MetaData, Table
from sqlalchemy.exc import OperationalError, ProgrammingError
from sqlalchemy.schema import Index
from unittest.mock import patch
@@ -60,8 +62,8 @@ class TestSchemaManager(unittest.TestCase):
version_table = Table(
'version', Model.metadata,
Column('id', Integer, primary_key=True),
- Column('component', Unicode),
- Column('version', Unicode),
+ Column('component', SAUnicode),
+ Column('version', SAUnicode),
)
version_table.create(config.db.engine)
config.db.store.execute(version_table.insert().values(
@@ -71,12 +73,15 @@ class TestSchemaManager(unittest.TestCase):
# all DB engines...
config.db.engine.execute(
'ALTER TABLE mailinglist ADD COLUMN acceptable_aliases_id INT')
- Index('ix_user__user_id').drop(bind=config.db.engine)
- # Don't pollute our main metadata object, create a new one.
- md = MetaData()
- user_table = Model.metadata.tables['user'].tometadata(md)
- Index('ix_user_user_id', user_table.c._user_id).create(
- bind=config.db.engine)
+ # In case of MySQL, you cannot create/drop indexes on primary keys
+ # manually as it is handled automatically by MySQL.
+ if not is_mysql(config.db.engine):
+ Index('ix_user__user_id').drop(bind=config.db.engine)
+ # Don't pollute our main metadata object, create a new one.
+ md = MetaData()
+ user_table = Model.metadata.tables['user'].tometadata(md)
+ Index('ix_user_user_id', user_table.c._user_id).create(
+ bind=config.db.engine)
config.db.commit()
def _drop_storm_database(self):
@@ -88,10 +93,12 @@ class TestSchemaManager(unittest.TestCase):
version = Model.metadata.tables['version']
version.drop(config.db.engine, checkfirst=True)
Model.metadata.remove(version)
- # If it's nonexistent, PostgreSQL raises a ProgrammingError, while
- # SQLite raises an OperationalError.
- with suppress(ProgrammingError, OperationalError):
- Index('ix_user_user_id').drop(bind=config.db.engine)
+ # If it's nonexistent, PostgreSQL raises a ProgrammingError while
+ # SQLite raises an OperationalError. Since MySQL automatically handles
+ # indexes for primary keys, don't try doing it with that backend.
+ if not is_mysql(config.db.engine):
+ with suppress(ProgrammingError, OperationalError):
+ Index('ix_user_user_id').drop(bind=config.db.engine)
config.db.commit()
def test_current_database(self):
diff --git a/src/mailman/database/tests/test_migrations.py b/src/mailman/database/tests/test_migrations.py
index 88a2a30ab..2ad41ae53 100644
--- a/src/mailman/database/tests/test_migrations.py
+++ b/src/mailman/database/tests/test_migrations.py
@@ -28,7 +28,7 @@ from mailman.database.alembic import alembic_cfg
from mailman.database.helpers import exists_in_db
from mailman.database.model import Model
from mailman.database.transaction import transaction
-from mailman.database.types import Enum
+from mailman.database.types import Enum, SAUnicode
from mailman.interfaces.action import Action
from mailman.interfaces.cache import ICacheManager
from mailman.interfaces.member import MemberRole
@@ -83,8 +83,8 @@ class TestMigrations(unittest.TestCase):
header_match_table = sa.sql.table(
'headermatch',
sa.sql.column('mailing_list_id', sa.Integer),
- sa.sql.column('header', sa.Unicode),
- sa.sql.column('pattern', sa.Unicode),
+ sa.sql.column('header', SAUnicode),
+ sa.sql.column('pattern', SAUnicode),
)
# Bring the DB to the revision that is being tested.
alembic.command.downgrade(alembic_cfg, '42756496720')
@@ -126,8 +126,8 @@ class TestMigrations(unittest.TestCase):
keyvalue_table = sa.sql.table(
'pendedkeyvalue',
sa.sql.column('id', sa.Integer),
- sa.sql.column('key', sa.Unicode),
- sa.sql.column('value', sa.Unicode),
+ sa.sql.column('key', SAUnicode),
+ sa.sql.column('value', SAUnicode),
sa.sql.column('pended_id', sa.Integer),
)
def get_from_db(): # noqa: E301
@@ -230,14 +230,14 @@ class TestMigrations(unittest.TestCase):
sa.sql.table(
'mailinglist',
sa.sql.column('id', sa.Integer),
- sa.sql.column('list_id', sa.Unicode),
+ sa.sql.column('list_id', SAUnicode),
sa.sql.column('default_member_action', Enum(Action)),
sa.sql.column('default_nonmember_action', Enum(Action)),
)
member_table = sa.sql.table(
'member',
sa.sql.column('id', sa.Integer),
- sa.sql.column('list_id', sa.Unicode),
+ sa.sql.column('list_id', SAUnicode),
sa.sql.column('address_id', sa.Integer),
sa.sql.column('role', Enum(MemberRole)),
sa.sql.column('moderation_action', Enum(Action)),
diff --git a/src/mailman/database/types.py b/src/mailman/database/types.py
index 28b261514..bbd040d96 100644
--- a/src/mailman/database/types.py
+++ b/src/mailman/database/types.py
@@ -22,7 +22,8 @@ import uuid
from mailman import public
from sqlalchemy import Integer
from sqlalchemy.dialects import postgresql
-from sqlalchemy.types import CHAR, TypeDecorator
+from sqlalchemy.ext.compiler import compiles
+from sqlalchemy.types import CHAR, TypeDecorator, Unicode
@public
@@ -80,3 +81,46 @@ class UUID(TypeDecorator):
return value
else:
return uuid.UUID(value)
+
+
+@public
+class SAUnicode(TypeDecorator):
+ """Unicode datatype to support fixed length VARCHAR in MySQL.
+
+ This type compiles to VARCHAR(255) in case of MySQL, and in case of
+ other dailects defaults to the Unicode type. This was created so
+ that we don't have to alter the output of the default Unicode data
+ type and it can still be used if needed in the codebase.
+ """
+ impl = Unicode
+
+
+@compiles(SAUnicode)
+def default_sa_unicode(element, compiler, **kw):
+ return compiler.visit_Unicode(element, **kw)
+
+
+@compiles(SAUnicode, 'mysql')
+def compile_sa_unicode(element, compiler, **kw):
+ # We hardcode the collate here to make string comparison case sensitive.
+ return 'VARCHAR(255) COLLATE utf8_bin'
+
+
+@public
+class SAUnicodeLarge(TypeDecorator):
+ """Similar to SAUnicode type, but compiles to VARCHAR(510).
+
+ This is double size of SAUnicode defined above.
+ """
+ impl = Unicode
+
+
+@compiles(SAUnicodeLarge, 'mysql')
+def compile_sa_unicode_large(element, compiler, **kw):
+ # We hardcode the collate here to make string comparison case sensitive.
+ return 'VARCHAR(510) COLLATE utf8_bin'
+
+
+@compiles(SAUnicode)
+def defalt_sa_unicode_large(element, compiler, **kw):
+ return compiler.visit_unicode(element, **kw)