diff --git a/audit/src/org/labkey/audit/AuditModule.java b/audit/src/org/labkey/audit/AuditModule.java index 5be6326ee39..ac6788c32d2 100644 --- a/audit/src/org/labkey/audit/AuditModule.java +++ b/audit/src/org/labkey/audit/AuditModule.java @@ -19,6 +19,13 @@ import org.jetbrains.annotations.NotNull; import org.labkey.api.audit.AuditLogService; import org.labkey.api.audit.provider.SiteSettingsAuditProvider; +import org.labkey.api.data.ColumnInfo; +import org.labkey.api.data.JdbcType; +import org.labkey.api.data.SQLFragment; +import org.labkey.api.data.TableInfo; +import org.labkey.api.data.WrappedColumn; +import org.labkey.api.migration.DatabaseMigrationService; +import org.labkey.api.migration.MigrationTableHandler; import org.labkey.api.module.DefaultModule; import org.labkey.api.module.ModuleContext; import org.labkey.api.view.WebPartFactory; @@ -27,6 +34,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Set; +import java.util.TreeSet; public class AuditModule extends DefaultModule { @@ -89,4 +97,57 @@ public Set getProvisionedSchemaNames() { return Collections.singleton(AuditSchema.SCHEMA_NAME); } + + private final Set _registeredMigrationTables = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + + @Override + public void registerMigrationHandlers(@NotNull DatabaseMigrationService service) + { + service.registerSchemaContributor(AuditSchema.SCHEMA_NAME, schema -> + schema.getTableNames().stream() + .map(schema::getTable) + .filter(table -> table != null && _registeredMigrationTables.add(table.getSelectName())) + .forEach(table -> service.registerTableHandler(new MigrationTableHandler() + { + @Override + public TableInfo getTableInfo() + { + return table; + } + + @Override + public ColumnInfo handleColumn(ColumnInfo col) + { + if ("hasdetails".equalsIgnoreCase(col.getName())) + return new HasDetailsCastColumn(col); + return col; + } + }))); + } + + // Some legacy source databases store the DatasetAuditDomain "HasDetails" column as varchar + // while the target was provisioned as boolean. CAST to BIT in the source SELECT and report + // BOOLEAN as the JdbcType so the migration's INSERT parameter binds cleanly into the + // target's boolean column (otherwise the parameter inherits the source column's VARCHAR + // type and PG rejects the bound value). + private static final class HasDetailsCastColumn extends WrappedColumn + { + private HasDetailsCastColumn(ColumnInfo col) + { + super(col, col.getName()); + } + + @Override + @NotNull + public JdbcType getJdbcType() + { + return JdbcType.BOOLEAN; + } + + @Override + public SQLFragment getValueSql(String tableAlias) + { + return new SQLFragment("CAST(").append(super.getValueSql(tableAlias)).append(" AS BIT)"); + } + } }