def sort(self):
        # The topological sort modifies inbound, but we can't do a deepcopy
        # since that would deepcopy the key too.
        inbound = {}
        for key, depends in self.inbound.items():
            inbound[key] = set(depends)

        toprocess = [v for v in self.ops if not inbound[v]]
        inorder = []
        while toprocess:
            op = toprocess.pop(0)
            inorder.insert(0, op)

            for depop in self.outbound[op]:
                inbound[depop].remove(op)
                if not inbound[depop]:
                    toprocess.insert(0, depop)

            del inbound[op]

        # Anything remaining in inbound is a dependency loop
        if inbound:
            msg = _('Dependency loop exists in database migrations')
            raise exception.DatabaseMigrationError(reason=msg)

        return inorder
    def _column_changes(self, diffs):
        # Column change (type, nullable, etc)
        table_name = diffs[0][2]
        column_name = diffs[0][3]

        args = {}
        for diff in diffs:
            cmd = diff[0]

            if cmd == 'modify_nullable':
                # ('modify_nullable', None, table_name, column_name,
                #  {'existing_server_default': None,
                #   'existing_type': VARCHAR(length=36)},
                #  conn_nullable, metadata_nullable)
                existing_type = diff[4]['existing_type']
                nullable = diff[6]

                args['existing_type'] = existing_type
                args['nullable'] = nullable
            elif cmd == 'modify_type':
                # ('modify_type', None, table_name, column_name,
                #  {'existing_nullable': True,
                #   'existing_server_default': None},
                #  TINYINT(display_width=1), Boolean())
                existing_nullable = diff[4]['existing_nullable']
                new_type = diff[6]

                if 'nullable' not in args:
                    args['nullable'] = existing_nullable
                args['type_'] = new_type
            else:
                msg = _('Unknown alembic cmd %s') % cmd
                raise exception.DatabaseMigrationError(reason=msg)

        yield AlterColumn(table_name, column_name, args)
def db_contract(dryrun=False, database='main'):
    context = _create_migration_context(as_sql=False)
    expand, migrate, contract = _schedule_schema_changes(context)

    if expand:
        msg = _('expand phase still has operations that need to be executed')
        raise exception.DatabaseMigrationError(reason=msg)
    if migrate:
        msg = _('migrate phase still has operations that need to be executed')
        raise exception.DatabaseMigrationError(reason=msg)

    context = _create_migration_context(as_sql=dryrun)
    ddlop = alembic.operations.Operations(context)
    for op in contract:
        op.execute(ddlop)

    repository = _find_migrate_repo(database)
    _set_db_sync_lock(repository, locked=False)
    _set_db_sync_version(repository, repository.latest)
def db_migrate(dryrun=False, database='main'):
    context = _create_migration_context(as_sql=False)
    expand, migrate, contract = _schedule_schema_changes(context)

    if expand:
        msg = _('expand phase still has operations that need to be executed')
        raise exception.DatabaseMigrationError(reason=msg)

    context = _create_migration_context(as_sql=dryrun)
    ddlop = alembic.operations.Operations(context)
    for op in migrate:
        op.execute(ddlop)
def db_sync(version=None, database='main'):
    if version is not None:
        try:
            version = int(version)
        except ValueError:
            raise exception.NovaException(_("version should be an integer"))

    current_version = db_version(database)
    repository = _find_migrate_repo(database)

    if _db_sync_locked(repository):
        msg = _("Cannot run 'db sync' until 'db contract' is run")
        raise exception.DatabaseMigrationError(reason=msg)

    if version is None or version > current_version:
        return versioning_api.upgrade(get_engine(database), repository,
                version)
    else:
        return versioning_api.downgrade(get_engine(database), repository,
                version)
    def convert_alembic(self, diffs):
        ops = []
        for diff in diffs:
            # Parse out the format into something easier to use than the
            # tuple/list format that alembic returns

            if isinstance(diff, list):
                ret = self._column_changes(diff)
            else:
                cmd = diff[0]

                handler = getattr(self, '_handle_%s' % cmd, None)
                if handler is None:
                    msg = _('Unknown alembic cmd %s') % cmd
                    raise exception.DatabaseMigrationError(reason=msg)

                ret = handler(*diff[1:])

            ops.extend(list(ret))

        return ops