summaryrefslogtreecommitdiff
path: root/src/mailman/database/base.py
diff options
context:
space:
mode:
authorBarry Warsaw2012-02-12 10:01:07 -0500
committerBarry Warsaw2012-02-12 10:01:07 -0500
commitfaa56a174328af3ab76ccd1a5b4c9d630ac9779f (patch)
tree4a91273e2f7b7ae833827a37e1ac71ab90c251fb /src/mailman/database/base.py
parent125ba2db2ba59ad693e6142e9c761d4bad2f478c (diff)
downloadmailman-faa56a174328af3ab76ccd1a5b4c9d630ac9779f.tar.gz
mailman-faa56a174328af3ab76ccd1a5b4c9d630ac9779f.tar.zst
mailman-faa56a174328af3ab76ccd1a5b4c9d630ac9779f.zip
Diffstat (limited to 'src/mailman/database/base.py')
-rw-r--r--src/mailman/database/base.py105
1 files changed, 73 insertions, 32 deletions
diff --git a/src/mailman/database/base.py b/src/mailman/database/base.py
index e28ca1893..a69e99395 100644
--- a/src/mailman/database/base.py
+++ b/src/mailman/database/base.py
@@ -24,18 +24,18 @@ __all__ = [
import os
+import sys
import logging
from flufl.lock import Lock
from lazr.config import as_boolean
+from pkg_resources import resource_listdir, resource_string
from storm.cache import GenerationalCache
from storm.locals import create_database, Store
from zope.interface import implements
-import mailman.version
-
from mailman.config import config
-from mailman.interfaces.database import IDatabase, SchemaVersionMismatchError
+from mailman.interfaces.database import IDatabase
from mailman.model.version import Version
from mailman.utilities.string import expand
@@ -51,6 +51,10 @@ class StormBaseDatabase:
Use this as a base class for your DB-specific derived classes.
"""
+ # Tag used to distinguish the database being used. Override this in base
+ # classes.
+ TAG = ''
+
implements(IDatabase)
def __init__(self):
@@ -87,15 +91,6 @@ class StormBaseDatabase:
"""
raise NotImplementedError
- def _get_schema(self):
- """Return the database schema as a string.
-
- This will be loaded into the database when it is first created.
-
- Base classes *must* override this.
- """
- raise NotImplementedError
-
def _pre_reset(self, store):
"""Clean up method for testing.
@@ -147,34 +142,80 @@ class StormBaseDatabase:
store = Store(database, GenerationalCache())
database.DEBUG = (as_boolean(config.database.debug)
if debug is None else debug)
- # Check the master / schema database to see if the version table
- # exists. If so, then we assume the database schema is correctly
- # initialized. Storm does not currently provide schema creation.
- if not self._database_exists(store):
- # Initialize the database. Start by getting the schema and
- # discarding all blank and comment lines.
- lines = self._get_schema().splitlines()
- lines = (line for line in lines
+ self.store = store
+ self.load_migrations()
+ store.commit()
+
+ def load_migrations(self):
+ """Load all not-yet loaded migrations."""
+ migrations_path = config.database.migrations_path
+ if '.' in migrations_path:
+ parent, dot, child = migrations_path.rpartition('.')
+ else:
+ parent = migrations_path
+ child =''
+ # If the database does not yet exist, load the base schema.
+ filenames = sorted(resource_listdir(parent, child))
+ # Find out which schema migrations have already been loaded.
+ if self._database_exists(self.store):
+ versions = set(version.version for version in
+ self.store.find(Version, component='schema'))
+ else:
+ versions = set()
+ for filename in filenames:
+ module_fn, extension = os.path.splitext(filename)
+ if extension != '.py':
+ continue
+ parts = module_fn.split('_')
+ if len(parts) < 2:
+ continue
+ version = parts[1]
+ if version in versions:
+ # This one is already loaded.
+ continue
+ module_path = migrations_path + '.' + module_fn
+ __import__(module_path)
+ upgrade = getattr(sys.modules[module_path], 'upgrade', None)
+ if upgrade is None:
+ continue
+ upgrade(self, self.store, version, module_path)
+
+ def load_schema(self, store, version, filename, module_path):
+ """Load the schema from a file.
+
+ This is a helper method for migration classes to call.
+
+ :param store: The Storm store to load the schema into.
+ :type store: storm.locals.Store`
+ :param version: The schema version identifier of the form
+ YYYYMMDDHHMMSS.
+ :type version: string
+ :param filename: The file name containing the schema to load. Pass
+ `None` if there is no schema file to load.
+ :type filename: string
+ :param module_path: The fully qualified Python module path to the
+ migration module being loaded. This is used to record information
+ for use by the test suite.
+ :type module_path: string
+ """
+ if filename is not None:
+ contents = resource_string('mailman.database.schema', filename)
+ # Discard all blank and comment lines.
+ lines = (line for line in contents.splitlines()
if line.strip() != '' and line.strip()[:2] != '--')
sql = NL.join(lines)
for statement in sql.split(';'):
if statement.strip() != '':
store.execute(statement + ';')
- # Validate schema version.
- v = store.find(Version, component='schema').one()
- if not v:
- # Database has not yet been initialized
- v = Version(component='schema',
- version=mailman.version.DATABASE_SCHEMA_VERSION)
- store.add(v)
- elif v.version <> mailman.version.DATABASE_SCHEMA_VERSION:
- # XXX Update schema
- raise SchemaVersionMismatchError(v.version)
- self.store = store
- store.commit()
+ # Add a marker that indicates the migration version being applied.
+ store.add(Version(component='schema', version=version))
+ # Add a marker so that the module name can be found later. This is
+ # used by the test suite to reset the database between tests.
+ store.add(Version(component=version, version=module_path))
def _reset(self):
"""See `IDatabase`."""
from mailman.database.model import ModelMeta
self.store.rollback()
ModelMeta._reset(self.store)
+ self.store.commit()