¡@

Home 

OpenStack Study: migration.py

OpenStack Index

**** CubicPower OpenStack Study ****

def _get_unique_constraints(self, table):

    """Retrieve information about existing unique constraints of the table

    This feature is needed for _recreate_table() to work properly.

    Unfortunately, it's not available in sqlalchemy 0.7.x/0.8.x.

    """

    data = table.metadata.bind.execute(

        """SELECT sql

           FROM sqlite_master

           WHERE

               type='table' AND

               name=:table_name""",

        table_name=table.name

    ).fetchone()[0]

    UNIQUE_PATTERN = "CONSTRAINT (\w+) UNIQUE \(([^\)]+)\)"

    return [

        UniqueConstraint(

            *[getattr(table.columns, c.strip(' "')) for c in cols.split(",")],

            name=name

        )

        for name, cols in re.findall(UNIQUE_PATTERN, data)

    ]

**** CubicPower OpenStack Study ****

def _recreate_table(self, table, column=None, delta=None, omit_uniques=None):

    """Recreate the table properly

    Unlike the corresponding original method of sqlalchemy-migrate this one

    doesn't drop existing unique constraints when creating a new one.

    """

    table_name = self.preparer.format_table(table)

    # we remove all indexes so as not to have

    # problems during copy and re-create

    for index in table.indexes:

        index.drop()

    # reflect existing unique constraints

    for uc in self._get_unique_constraints(table):

        table.append_constraint(uc)

    # omit given unique constraints when creating a new table if required

    table.constraints = set([

        cons for cons in table.constraints

        if omit_uniques is None or cons.name not in omit_uniques

    ])

    self.append('ALTER TABLE %s RENAME TO migration_tmp' % table_name)

    self.execute()

    insertion_string = self._modify_table(table, column, delta)

    table.create(bind=self.connection)

    self.append(insertion_string % {'table_name': table_name})

    self.execute()

    self.append('DROP TABLE migration_tmp')

    self.execute()

**** CubicPower OpenStack Study ****

def _visit_migrate_unique_constraint(self, *p, **k):

    """Drop the given unique constraint

    The corresponding original method of sqlalchemy-migrate just

    raises NotImplemented error

    """

    self.recreate_table(p[0].table, omit_uniques=[p[0].name])

**** CubicPower OpenStack Study ****

def patch_migrate():

    """A workaround for SQLite's inability to alter things

    SQLite abilities to alter tables are very limited (please read

    http://www.sqlite.org/lang_altertable.html for more details).

    E. g. one can't drop a column or a constraint in SQLite. The

    workaround for this is to recreate the original table omitting

    the corresponding constraint (or column).

    sqlalchemy-migrate library has recreate_table() method that

    implements this workaround, but it does it wrong:

        - information about unique constraints of a table

          is not retrieved. So if you have a table with one

          unique constraint and a migration adding another one

          you will end up with a table that has only the

          latter unique constraint, and the former will be lost

        - dropping of unique constraints is not supported at all

    The proper way to fix this is to provide a pull-request to

    sqlalchemy-migrate, but the project seems to be dead. So we

    can go on with monkey-patching of the lib at least for now.

    """

    # this patch is needed to ensure that recreate_table() doesn't drop

    # existing unique constraints of the table when creating a new one

    helper_cls = sqlite.SQLiteHelper

    helper_cls.recreate_table = _recreate_table

    helper_cls._get_unique_constraints = _get_unique_constraints

    # this patch is needed to be able to drop existing unique constraints

    constraint_cls = sqlite.SQLiteConstraintDropper

    constraint_cls.visit_migrate_unique_constraint = \

        _visit_migrate_unique_constraint

    constraint_cls.__bases__ = (ansisql.ANSIColumnDropper,

                                sqlite.SQLiteConstraintGenerator)

**** CubicPower OpenStack Study ****

def db_sync(engine, abs_path, version=None, init_version=0, sanity_check=True):

    """Upgrade or downgrade a database.

    Function runs the upgrade() or downgrade() functions in change scripts.

    :param engine:       SQLAlchemy engine instance for a given database

    :param abs_path:     Absolute path to migrate repository.

    :param version:      Database will upgrade/downgrade until this version.

                         If None - database will update to the latest

                         available version.

    :param init_version: Initial database version

    :param sanity_check: Require schema sanity checking for all tables

    """

    if version is not None:

        try:

            version = int(version)

        except ValueError:

            raise exception.DbMigrationError(

                message=_("version should be an integer"))

    current_version = db_version(engine, abs_path, init_version)

    repository = _find_migrate_repo(abs_path)

    if sanity_check:

        _db_schema_sanity_check(engine)

    if version is None or version > current_version:

        return versioning_api.upgrade(engine, repository, version)

    else:

        return versioning_api.downgrade(engine, repository,

                                        version)

**** CubicPower OpenStack Study ****

def _db_schema_sanity_check(engine):

    """Ensure all database tables were created with required parameters.

    :param engine:  SQLAlchemy engine instance for a given database

    """

    if engine.name == 'mysql':

        onlyutf8_sql = ('SELECT TABLE_NAME,TABLE_COLLATION '

                        'from information_schema.TABLES '

                        'where TABLE_SCHEMA=%s and '

                        'TABLE_COLLATION NOT LIKE "%%utf8%%"')

        # NOTE(morganfainberg): exclude the sqlalchemy-migrate and alembic

        # versioning tables from the tables we need to verify utf8 status on.

        # Non-standard table names are not supported.

        EXCLUDED_TABLES = ['migrate_version', 'alembic_version']

        table_names = [res[0] for res in

                       engine.execute(onlyutf8_sql, engine.url.database) if

                       res[0].lower() not in EXCLUDED_TABLES]

        if len(table_names) > 0:

            raise ValueError(_('Tables "%s" have non utf8 collation, '

                               'please make sure all tables are CHARSET=utf8'

                               ) % ','.join(table_names))

**** CubicPower OpenStack Study ****

def db_version(engine, abs_path, init_version):

    """Show the current version of the repository.

    :param engine:  SQLAlchemy engine instance for a given database

    :param abs_path: Absolute path to migrate repository

    :param version:  Initial database version

    """

    repository = _find_migrate_repo(abs_path)

    try:

        return versioning_api.db_version(engine, repository)

    except versioning_exceptions.DatabaseNotControlledError:

        meta = sqlalchemy.MetaData()

        meta.reflect(bind=engine)

        tables = meta.tables

        if len(tables) == 0 or 'alembic_version' in tables:

            db_version_control(engine, abs_path, version=init_version)

            return versioning_api.db_version(engine, repository)

        else:

            raise exception.DbMigrationError(

                message=_(

                    "The database is not under version control, but has "

                    "tables. Please stamp the current version of the schema "

                    "manually."))

**** CubicPower OpenStack Study ****

def db_version_control(engine, abs_path, version=None):

    """Mark a database as under this repository's version control.

    Once a database is under version control, schema changes should

    only be done via change scripts in this repository.

    :param engine:  SQLAlchemy engine instance for a given database

    :param abs_path: Absolute path to migrate repository

    :param version:  Initial database version

    """

    repository = _find_migrate_repo(abs_path)

    versioning_api.version_control(engine, repository, version)

    return version

**** CubicPower OpenStack Study ****

def _find_migrate_repo(abs_path):

    """Get the project's change script repository

    :param abs_path: Absolute path to migrate repository

    """

    if not os.path.exists(abs_path):

        raise exception.DbMigrationError("Path %s not found" % abs_path)

    return Repository(abs_path)