diff options
| author | Barry Warsaw | 2016-07-29 19:28:46 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2016-07-29 19:28:46 -0400 |
| commit | febb5289e82c4424cdbcc2297e967bd894cbc8cf (patch) | |
| tree | bd71f4cbf6988049ac4d5dd65ceb7d5cc51902e7 /src/mailman/database | |
| parent | 90e84bee5f47cbcdb9e9c367c60a877e325ef3e7 (diff) | |
| download | mailman-febb5289e82c4424cdbcc2297e967bd894cbc8cf.tar.gz mailman-febb5289e82c4424cdbcc2297e967bd894cbc8cf.tar.zst mailman-febb5289e82c4424cdbcc2297e967bd894cbc8cf.zip | |
Diffstat (limited to 'src/mailman/database')
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) |
