From 793e2e088337cd6a3eece0eb2b4d9d02416c88b1 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Sun, 3 Aug 2025 08:20:49 -0700 Subject: [PATCH 1/2] Move database migration to a premium service --- .../api/data/DatabaseMigrationService.java | 83 ++++ .../labkey/api/module/DatabaseMigration.java | 376 ------------------ .../org/labkey/api/module/ModuleLoader.java | 3 +- core/src/org/labkey/core/CoreModule.java | 6 +- .../labkey/experiment/ExperimentModule.java | 6 +- .../labkey/pipeline/PipelineController.java | 2 - wiki/src/org/labkey/wiki/WikiModule.java | 6 +- 7 files changed, 94 insertions(+), 388 deletions(-) create mode 100644 api/src/org/labkey/api/data/DatabaseMigrationService.java delete mode 100644 api/src/org/labkey/api/module/DatabaseMigration.java diff --git a/api/src/org/labkey/api/data/DatabaseMigrationService.java b/api/src/org/labkey/api/data/DatabaseMigrationService.java new file mode 100644 index 00000000000..cc74683afc0 --- /dev/null +++ b/api/src/org/labkey/api/data/DatabaseMigrationService.java @@ -0,0 +1,83 @@ +package org.labkey.api.data; + +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.labkey.api.query.TableSorter; +import org.labkey.api.services.ServiceRegistry; +import org.labkey.api.util.logging.LogHelper; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public interface DatabaseMigrationService +{ + Logger LOG = LogHelper.getLogger(DatabaseMigrationService.class, "Information about database migration"); + + static @NotNull DatabaseMigrationService get() + { + DatabaseMigrationService ret = ServiceRegistry.get().getService(DatabaseMigrationService.class); + return ret != null ? ret : new DatabaseMigrationService() {}; + } + + static void setInstance(DatabaseMigrationService impl) + { + ServiceRegistry.get().registerService(DatabaseMigrationService.class, impl); + } + + // By default, no-op implementation that simply logs + default void migrate(boolean shouldInsertData, @Nullable String migrationDataSource) + { + LOG.warn("Database migration service is not present; database migration is a premium feature."); + } + + // By default, no-op implementation + default void registerHandler(DbSchema schema, MigrationHandler handler) {} + + interface MigrationHandler + { + void beforeSchema(DbSchema targetSchema); + + List getTablesToCopy(DbSchema targetSchema); + + void afterSchema(DbSchema targetSchema); + } + + class DefaultMigrationHandler implements MigrationHandler + { + @Override + public void beforeSchema(DbSchema targetSchema) + { + } + + @Override + public List getTablesToCopy(DbSchema targetSchema) + { + Set sortedTables = new LinkedHashSet<>(TableSorter.sort(targetSchema, true)); + + Set allTables = targetSchema.getTableNames().stream() + .map(targetSchema::getTable) + .collect(Collectors.toCollection(HashSet::new)); + allTables.removeAll(sortedTables); + + if (!allTables.isEmpty()) + { + LOG.info("These tables were removed by TableSorter: {}", allTables); + } + + return sortedTables.stream() + // Skip all views and virtual tables (e.g., test.Containers2, which is a table on SS but a view on PG) + .filter(table -> table.getTableType() == DatabaseTableType.TABLE) + .collect(Collectors.toCollection(ArrayList::new)); // Ensure mutable + } + + @Override + public void afterSchema(DbSchema targetSchema) + { + } + } +} diff --git a/api/src/org/labkey/api/module/DatabaseMigration.java b/api/src/org/labkey/api/module/DatabaseMigration.java deleted file mode 100644 index f3132d5bc92..00000000000 --- a/api/src/org/labkey/api/module/DatabaseMigration.java +++ /dev/null @@ -1,376 +0,0 @@ -package org.labkey.api.module; - -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; -import org.jetbrains.annotations.Nullable; -import org.labkey.api.collections.CaseInsensitiveHashSet; -import org.labkey.api.collections.CopyOnWriteCaseInsensitiveHashMap; -import org.labkey.api.collections.LabKeyCollectors; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.Container; -import org.labkey.api.data.ContainerManager; -import org.labkey.api.data.CoreSchema; -import org.labkey.api.data.DatabaseTableType; -import org.labkey.api.data.DbSchema; -import org.labkey.api.data.DbSchemaType; -import org.labkey.api.data.DbScope; -import org.labkey.api.data.SQLFragment; -import org.labkey.api.data.SchemaTableInfo; -import org.labkey.api.data.SqlExecutor; -import org.labkey.api.data.SqlSelector; -import org.labkey.api.data.Table; -import org.labkey.api.data.TableInfo; -import org.labkey.api.data.TableSelector; -import org.labkey.api.exp.api.StorageProvisioner; -import org.labkey.api.exp.property.Domain; -import org.labkey.api.exp.property.PropertyService; -import org.labkey.api.query.TableSorter; -import org.labkey.api.util.ConfigurationException; -import org.labkey.api.util.StringUtilsLabKey; -import org.labkey.api.util.logging.LogHelper; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.function.BiFunction; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -// Handles SQL Server to PostgreSQL data migration -public class DatabaseMigration -{ - private static final Logger LOG = LogHelper.getLogger(DatabaseMigration.class, "Progress of SQL Server to PostgreSQL database migration"); - - // If associated properties are set: clear schemas, verify empty schemas, and migrate data from the external SQL - // Server data source into the just-created empty PostgreSQL schemas. - public static void migrate(boolean shouldInsertData, @Nullable String migrationDataSource) - { - if (!shouldInsertData) - { - clearSchemas(); - verifyEmptySchemas(); - - if (migrationDataSource != null) - migrateDatabase(migrationDataSource); - - System.exit(0); - } - } - - // Clear containers needed for bootstrap - private static void clearSchemas() - { - TableInfo containers = CoreSchema.getInstance().getTableInfoContainers(); - Table.delete(containers); // Now that we've bootstrapped, delete root and shared containers - DbScope targetScope = DbScope.getLabKeyScope(); - new SqlExecutor(targetScope).execute("ALTER SEQUENCE core.containers_rowid_seq RESTART"); // Reset Containers sequence - } - - // Verify that no data rows were inserted and no sequences were incremented - private static void verifyEmptySchemas() - { - DbScope scope = DbScope.getLabKeyScope(); - - Map> schemaMap = scope.getSchemaNames().stream() - .map(name -> scope.getSchema(name, DbSchemaType.Unknown)) - .collect(Collectors.partitioningBy(schema -> schema.getModule() != null && schema.getModule().getSupportedDatabasesSet().contains(SupportedDatabase.mssql))); - - List targetSchemas = schemaMap.get(true); - List tableWarnings = targetSchemas.stream() - .flatMap(schema -> schema.getTableNames().stream() - .map(schema::getTable) - .filter(table -> table.getTableType() != DatabaseTableType.NOT_IN_DB) - ) - .map(table -> { - long rowCount = new TableSelector(table).getRowCount(); - if (rowCount > 0) - return table.getSelectName() + " has " + StringUtilsLabKey.pluralize(rowCount, "row"); - else - return null; - }) - .filter(Objects::nonNull) - .toList(); - - if (!tableWarnings.isEmpty()) - { - LOG.warn("{} rows", StringUtilsLabKey.pluralize(tableWarnings.size(), "table has", "tables have")); - tableWarnings.forEach(LOG::warn); - } - - List schemasToIgnore = schemaMap.get(false).stream() - .map(DbSchema::getName) - .toList(); - String qs = StringUtils.join(Collections.nCopies(schemasToIgnore.size(), "?"), ", "); - List sequenceWarnings = new SqlSelector(scope, new SQLFragment( - "SELECT schemaname || '.' || sequencename FROM pg_sequences WHERE last_value IS NOT NULL AND schemaname NOT IN (" + qs + ")", - schemasToIgnore - )) - .stream(String.class) - .toList(); - - if (!sequenceWarnings.isEmpty()) - { - LOG.warn("{} a value:", StringUtilsLabKey.pluralize(sequenceWarnings.size(), "sequence has", "sequences have")); - sequenceWarnings.forEach(LOG::warn); - } - } - - private record Sequence(String schemaName, String tableName, String columnName, int lastValue) {} - - private static void migrateDatabase(String migrationDataSource) - { - LOG.info("Starting database migration"); - - DbScope targetScope = DbScope.getLabKeyScope(); - DbScope sourceScope = DbScope.getDbScope(migrationDataSource); - if (null == sourceScope) - throw new ConfigurationException("Migration data source not found: " + migrationDataSource); - if (!sourceScope.getSqlDialect().isSqlServer()) - throw new ConfigurationException("Migration data source is not SQL Server: " + migrationDataSource); - - // Verify that all sequences in the target schema have an increment of 1, since that's an assumption below - Collection sequencesNonOneIncrement = new SqlSelector(targetScope, new SQLFragment("SELECT schemaname || '.' || sequencename || ': ' || increment_by FROM pg_sequences WHERE increment_by != 1")).getCollection(String.class); - if (!sequencesNonOneIncrement.isEmpty()) - { - throw new IllegalStateException(StringUtilsLabKey.pluralize(sequencesNonOneIncrement.size(), "sequence has", "sequences have") + " an increment other than 1: " + sequencesNonOneIncrement); - } - - // Select the SQL Server sequences with non-null last value. We'll use the results to set PostgreSQL sequences after copying data. - String sequenceQuery = """ - SELECT - OBJECT_SCHEMA_NAME(tables.object_id, db_id()) AS SchemaName, - tables.name AS TableName, - identity_columns.name AS ColumnName, - identity_columns.seed_value, - identity_columns.increment_value, - identity_columns.last_value - FROM - sys.tables tables - JOIN - sys.identity_columns identity_columns ON tables.object_id = identity_columns.object_id - WHERE last_value IS NOT NULL"""; - Map> sequenceMap = new HashMap<>(); - new SqlSelector(sourceScope, sequenceQuery).forEach(rs -> { - Sequence sequence = new Sequence(rs.getString("SchemaName"), rs.getString("TableName"), rs.getString("ColumnName"), rs.getInt("last_value")); - Map schemaMap = sequenceMap.computeIfAbsent(sequence.schemaName(), s -> new HashMap<>()); - schemaMap.put(sequence.tableName(), sequence); - }); - - // Get the target module schemas in module order, which helps with foreign key relationships - List targetModuleSchemas = ModuleLoader.getInstance().getModules().stream() - .flatMap(module -> module.getSchemaNames().stream().filter(name -> !module.getProvisionedSchemaNames().contains(name))) - .map(name -> targetScope.getSchema(name, DbSchemaType.Module)) - .toList(); - - // Migrate all data in the module schemas - migrateSchemas(migrationDataSource, sourceScope, targetScope, targetModuleSchemas, (schema, handler) -> handler.getTablesToCopy(schema), sequenceMap); - - // Create all provisioned tables listed in exp.DomainDescriptor - PropertyService svc = PropertyService.get(); - StorageProvisioner provisioner = StorageProvisioner.get(); - new SqlSelector(targetScope, "SELECT Container, DomainURI, Name FROM exp.DomainDescriptor WHERE StorageSchemaName IS NOT NULL").forEach(rs -> { - Container c = ContainerManager.getForId(rs.getString("Container")); - if (c != null) - { - String domainURI = rs.getString("DomainURI"); - String name = rs.getString("Name"); - Domain d = svc.ensureDomain(c, null, domainURI, name); - provisioner.createStorageTable(d, d.getDomainKind(), targetScope); - } - }); - - // Get the target provisioned schemas - List targetProvisionedSchemas = ModuleLoader.getInstance().getModules().stream() - .flatMap(module -> module.getSchemaNames().stream().filter(name -> module.getProvisionedSchemaNames().contains(name))) - .map(name -> targetScope.getSchema(name, DbSchemaType.Bare)) - .toList(); - - // Migrate all data in the provisioned schemas - migrateSchemas(migrationDataSource, sourceScope, targetScope, targetProvisionedSchemas, (schema, handler) -> getTables(schema), sequenceMap); - - LOG.info("Database migration is complete"); - } - - private static void migrateSchemas(String migrationDataSource, DbScope sourceScope, DbScope targetScope, List targetSchemas, BiFunction> tableProducer, Map> sequenceMap) - { - for (DbSchema targetSchema : targetSchemas) - { - DbSchema sourceSchema = sourceScope.getSchema(targetSchema.getName(), DbSchemaType.Bare); - if (!sourceSchema.existsInDatabase()) - { - LOG.warn("{} has no schema named '{}'", migrationDataSource, targetSchema.getName()); - } - else - { - MigrationHandler handler = getHandler(targetSchema); - handler.beforeSchema(targetSchema); - - Set sourceTableNames = getTables(sourceSchema).stream().map(TableInfo::getName).collect(LabKeyCollectors.toCaseInsensitiveHashSet()); - Set targetTableNames = getTables(targetSchema).stream().map(TableInfo::getName).collect(LabKeyCollectors.toCaseInsensitiveHashSet()); - Set sourceTableNamesCopy = new CaseInsensitiveHashSet(sourceTableNames); - sourceTableNames.removeAll(targetTableNames); - targetTableNames.removeAll(sourceTableNamesCopy); - if (!sourceTableNames.isEmpty() || !targetTableNames.isEmpty()) - LOG.warn("Table differences in {} schema: {} and {}", sourceSchema.getName(), sourceTableNames, targetTableNames); - - Map schemaSequenceMap = sequenceMap.getOrDefault(sourceSchema.getName(), Map.of()); - - for (TableInfo targetTable : tableProducer.apply(targetSchema, handler)) - { - String targetTableName = targetTable.getName(); - SchemaTableInfo sourceTable = sourceSchema.getTable(targetTableName); - - if (sourceTable == null) - { - LOG.warn("Source schema has no table named '{}'", targetTableName); - } - else - { - LOG.info("Migrating '{}'", targetSchema.getName() + "." + targetTableName); - // Inspect target table to determine column names to select from source table - Set selectColumnNames = targetTable.getColumns().stream() - .filter(column -> column.getWrappedColumnName() == null) // Ignore wrapped columns - .map(ColumnInfo::getName) - .filter(name -> !name.equals("_ts")) - .collect(Collectors.toSet()); - - TableSelector sourceSelector = new TableSelector(sourceTable, selectColumnNames).setJdbcCaching(false); - - try (Stream> mapStream = sourceSelector.uncachedMapStream(); Connection conn = targetScope.getConnection()) - { - Collection sourceColumns = sourceSelector.getSelectedColumns(); - // Map the selected source columns to the target columns so we get the right order and casing for INSERT, etc. - Collection targetColumns = sourceColumns.stream() - .map(sourceCol -> targetTable.getColumn(sourceCol.getName())) - .toList(); - String q = StringUtils.join(Collections.nCopies(sourceColumns.size(), "?"), ", "); - SQLFragment sql = new SQLFragment("INSERT INTO ") - .append(targetTable) - .append("("); - - String sep = ""; - for (ColumnInfo targetColumn : targetColumns) - { - sql.append(sep) - .appendIdentifier(targetColumn.getSelectIdentifier()); - sep = ", "; - } - - sql.append(") VALUES (") - .append(q) - .append(")"); - - PreparedStatement statement = conn.prepareStatement(sql.getRawSQL()); - - mapStream.forEach(map -> { - try - { - int i = 1; - - for (ColumnInfo col : sourceColumns) - statement.setObject(i++, col.getValue(map)); - - statement.execute(); - } - catch (SQLException e) - { - throw new RuntimeException("Exception while migrating data from " + sourceTable, e); - } - }); - - Sequence sequence = schemaSequenceMap.get(targetTable.getName()); - if (sequence != null) - { - ColumnInfo targetColumn = targetTable.getColumn(sequence.columnName()); - String sequenceName = new SqlSelector(targetSchema, "SELECT pg_get_serial_sequence(?, ?)", targetSchema.getName() + "." + targetTable.getName(), targetColumn.getSelectIdentifier().getId()) - .getObject(String.class); - new SqlExecutor(targetScope).execute("SELECT setval(?, ?)", sequenceName, sequence.lastValue()); - } - } - catch (Exception e) - { - LOG.error("Exception: ", e); - } - } - } - - handler.afterSchema(targetSchema); - } - } - } - - private static List getTables(DbSchema schema) - { - return new ArrayList<>(schema.getTableNames().stream() - .map(schema::getTable) - .filter(table -> table.getTableType() == DatabaseTableType.TABLE) - .toList()); - } - - public interface MigrationHandler - { - void beforeSchema(DbSchema targetSchema); - - List getTablesToCopy(DbSchema targetSchema); - - void afterSchema(DbSchema targetSchema); - } - - public static class DefaultMigrationHandler implements MigrationHandler - { - @Override - public void beforeSchema(DbSchema targetSchema) - { - } - - @Override - public List getTablesToCopy(DbSchema targetSchema) - { - Set sortedTables = new LinkedHashSet<>(TableSorter.sort(targetSchema, true)); - - Set allTables = targetSchema.getTableNames().stream() - .map(targetSchema::getTable) - .collect(Collectors.toCollection(HashSet::new)); - allTables.removeAll(sortedTables); - - if (!allTables.isEmpty()) - { - LOG.info("These tables were removed by TableSorter: {}", allTables); - } - - return sortedTables.stream() - // Skip all views and virtual tables (e.g., test.Containers2, which is a table on SS but a view on PG) - .filter(table -> table.getTableType() == DatabaseTableType.TABLE) - .collect(Collectors.toCollection(ArrayList::new)); // Ensure mutable - } - - @Override - public void afterSchema(DbSchema targetSchema) - { - } - } - - private static final Map MIGRATION_HANDLERS = new CopyOnWriteCaseInsensitiveHashMap<>(); - private static final MigrationHandler DEFAULT_MIGRATION_HANDLER = new DefaultMigrationHandler(); - - public static void registerHandler(DbSchema schema, MigrationHandler handler) - { - MIGRATION_HANDLERS.put(schema.getName(), handler); - } - - private static MigrationHandler getHandler(DbSchema schema) - { - MigrationHandler handler = MIGRATION_HANDLERS.get(schema.getName()); - return handler != null ? handler : DEFAULT_MIGRATION_HANDLER; - } -} diff --git a/api/src/org/labkey/api/module/ModuleLoader.java b/api/src/org/labkey/api/module/ModuleLoader.java index 35d7f87e908..0844bbc76a1 100644 --- a/api/src/org/labkey/api/module/ModuleLoader.java +++ b/api/src/org/labkey/api/module/ModuleLoader.java @@ -39,6 +39,7 @@ import org.labkey.api.data.Container; import org.labkey.api.data.ConvertHelper; import org.labkey.api.data.CoreSchema; +import org.labkey.api.data.DatabaseMigrationService; import org.labkey.api.data.DatabaseTableType; import org.labkey.api.data.DbSchema; import org.labkey.api.data.DbSchemaType; @@ -1897,7 +1898,7 @@ private void afterUpgrade(File lockFile) lockFile.delete(); verifyRequiredModules(); - DatabaseMigration.migrate(shouldInsertData(), getMigrationDataSource()); + DatabaseMigrationService.get().migrate(shouldInsertData(), getMigrationDataSource()); } // If the "requiredModules" parameter is present in application.properties then fail startup if any specified module is missing. diff --git a/core/src/org/labkey/core/CoreModule.java b/core/src/org/labkey/core/CoreModule.java index 165dc6b85a0..8242f328618 100644 --- a/core/src/org/labkey/core/CoreModule.java +++ b/core/src/org/labkey/core/CoreModule.java @@ -57,6 +57,8 @@ import org.labkey.api.data.CoreSchema; import org.labkey.api.data.DataColumn; import org.labkey.api.data.DataRegion; +import org.labkey.api.data.DatabaseMigrationService; +import org.labkey.api.data.DatabaseMigrationService.DefaultMigrationHandler; import org.labkey.api.data.DbSchema; import org.labkey.api.data.DbScope; import org.labkey.api.data.FileSqlScriptProvider; @@ -89,8 +91,6 @@ import org.labkey.api.files.FileContentService; import org.labkey.api.markdown.MarkdownService; import org.labkey.api.message.settings.MessageConfigService; -import org.labkey.api.module.DatabaseMigration; -import org.labkey.api.module.DatabaseMigration.DefaultMigrationHandler; import org.labkey.api.module.FolderType; import org.labkey.api.module.FolderTypeManager; import org.labkey.api.module.Module; @@ -1268,7 +1268,7 @@ public void moduleStartupComplete(ServletContext servletContext) ContainerManager.addContainerListener(new EmailPreferenceContainerListener()); UserManager.addUserListener(new EmailPreferenceUserListener()); - DatabaseMigration.registerHandler(CoreSchema.getInstance().getSchema(), new DefaultMigrationHandler() + DatabaseMigrationService.get().registerHandler(CoreSchema.getInstance().getSchema(), new DefaultMigrationHandler() { @Override public List getTablesToCopy(DbSchema targetSchema) diff --git a/experiment/src/org/labkey/experiment/ExperimentModule.java b/experiment/src/org/labkey/experiment/ExperimentModule.java index cc805c6acff..dd46628e45c 100644 --- a/experiment/src/org/labkey/experiment/ExperimentModule.java +++ b/experiment/src/org/labkey/experiment/ExperimentModule.java @@ -28,6 +28,8 @@ import org.labkey.api.data.ContainerFilter; import org.labkey.api.data.ContainerManager; import org.labkey.api.data.CoreSchema; +import org.labkey.api.data.DatabaseMigrationService; +import org.labkey.api.data.DatabaseMigrationService.DefaultMigrationHandler; import org.labkey.api.data.DbSchema; import org.labkey.api.data.JdbcType; import org.labkey.api.data.NameGenerator; @@ -71,8 +73,6 @@ import org.labkey.api.exp.xar.LsidUtils; import org.labkey.api.files.FileContentService; import org.labkey.api.files.TableUpdaterFileListener; -import org.labkey.api.module.DatabaseMigration; -import org.labkey.api.module.DatabaseMigration.DefaultMigrationHandler; import org.labkey.api.module.ModuleContext; import org.labkey.api.module.ModuleLoader; import org.labkey.api.module.SpringModule; @@ -856,7 +856,7 @@ HAVING COUNT(*) > 1 } // Work around foreign key cycle between ExperimentRun <-> ProtocolApplication by temporarily dropping FK_Run_WorfklowTask - DatabaseMigration.registerHandler(OntologyManager.getExpSchema(), new DefaultMigrationHandler() + DatabaseMigrationService.get().registerHandler(OntologyManager.getExpSchema(), new DefaultMigrationHandler() { @Override public void beforeSchema(DbSchema targetSchema) diff --git a/pipeline/src/org/labkey/pipeline/PipelineController.java b/pipeline/src/org/labkey/pipeline/PipelineController.java index bb3f466f0c5..f3f4ff322e9 100644 --- a/pipeline/src/org/labkey/pipeline/PipelineController.java +++ b/pipeline/src/org/labkey/pipeline/PipelineController.java @@ -28,7 +28,6 @@ import org.labkey.api.action.FormArrayList; import org.labkey.api.action.FormHandlerAction; import org.labkey.api.action.FormViewAction; -import org.labkey.api.action.GWTServiceAction; import org.labkey.api.action.LabKeyError; import org.labkey.api.action.MutatingApiAction; import org.labkey.api.action.ReadOnlyApiAction; @@ -53,7 +52,6 @@ import org.labkey.api.files.view.FilesWebPart; import org.labkey.api.gwt.client.model.GWTDomain; import org.labkey.api.gwt.client.model.GWTPropertyDescriptor; -import org.labkey.api.gwt.server.BaseRemoteService; import org.labkey.api.module.Module; import org.labkey.api.pipeline.PipeRoot; import org.labkey.api.pipeline.PipelineAction; diff --git a/wiki/src/org/labkey/wiki/WikiModule.java b/wiki/src/org/labkey/wiki/WikiModule.java index 322e160d3a5..b4c422d089a 100644 --- a/wiki/src/org/labkey/wiki/WikiModule.java +++ b/wiki/src/org/labkey/wiki/WikiModule.java @@ -25,12 +25,12 @@ import org.labkey.api.attachments.AttachmentService; import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; +import org.labkey.api.data.DatabaseMigrationService; +import org.labkey.api.data.DatabaseMigrationService.DefaultMigrationHandler; import org.labkey.api.data.DbSchema; import org.labkey.api.data.SqlExecutor; import org.labkey.api.data.TableInfo; import org.labkey.api.module.CodeOnlyModule; -import org.labkey.api.module.DatabaseMigration; -import org.labkey.api.module.DatabaseMigration.DefaultMigrationHandler; import org.labkey.api.module.ModuleContext; import org.labkey.api.module.ModuleLoader; import org.labkey.api.search.SearchService; @@ -124,7 +124,7 @@ public void doStartup(ModuleContext moduleContext) WikiSchema.register(this); WikiController.registerAdminConsoleLinks(); - DatabaseMigration.registerHandler(CommSchema.getInstance().getSchema(), new DefaultMigrationHandler() + DatabaseMigrationService.get().registerHandler(CommSchema.getInstance().getSchema(), new DefaultMigrationHandler() { @Override public void beforeSchema(DbSchema targetSchema) From 18c5e144c8ada4f6c753fb8a044aada52fc116ee Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Sun, 3 Aug 2025 09:34:11 -0700 Subject: [PATCH 2/2] Move core-specific handling to core MigrationHandler --- .../labkey/api/data/DatabaseMigrationService.java | 7 +++++++ core/src/org/labkey/core/CoreModule.java | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/api/src/org/labkey/api/data/DatabaseMigrationService.java b/api/src/org/labkey/api/data/DatabaseMigrationService.java index cc74683afc0..f6c06ac8c47 100644 --- a/api/src/org/labkey/api/data/DatabaseMigrationService.java +++ b/api/src/org/labkey/api/data/DatabaseMigrationService.java @@ -40,6 +40,8 @@ default void registerHandler(DbSchema schema, MigrationHandler handler) {} interface MigrationHandler { + void beforeVerification(DbSchema targetSchema); + void beforeSchema(DbSchema targetSchema); List getTablesToCopy(DbSchema targetSchema); @@ -49,6 +51,11 @@ interface MigrationHandler class DefaultMigrationHandler implements MigrationHandler { + @Override + public void beforeVerification(DbSchema targetSchema) + { + } + @Override public void beforeSchema(DbSchema targetSchema) { diff --git a/core/src/org/labkey/core/CoreModule.java b/core/src/org/labkey/core/CoreModule.java index 8242f328618..48ed299a4d4 100644 --- a/core/src/org/labkey/core/CoreModule.java +++ b/core/src/org/labkey/core/CoreModule.java @@ -1270,6 +1270,18 @@ public void moduleStartupComplete(ServletContext servletContext) DatabaseMigrationService.get().registerHandler(CoreSchema.getInstance().getSchema(), new DefaultMigrationHandler() { + @Override + public void beforeVerification(DbSchema targetSchema) + { + super.beforeVerification(targetSchema); + + // Delete root and shared containers that were needed for bootstrapping + TableInfo containers = CoreSchema.getInstance().getTableInfoContainers(); + Table.delete(containers); + DbScope targetScope = DbScope.getLabKeyScope(); + new SqlExecutor(targetScope).execute("ALTER SEQUENCE core.containers_rowid_seq RESTART"); // Reset Containers sequence + } + @Override public List getTablesToCopy(DbSchema targetSchema) {