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