diff --git a/api/src/org/labkey/api/data/DatabaseMigrationService.java b/api/src/org/labkey/api/data/DatabaseMigrationService.java index 09bed1b6008..4717a69f32a 100644 --- a/api/src/org/labkey/api/data/DatabaseMigrationService.java +++ b/api/src/org/labkey/api/data/DatabaseMigrationService.java @@ -9,10 +9,12 @@ import org.labkey.api.data.SimpleFilter.FilterClause; import org.labkey.api.data.SimpleFilter.InClause; import org.labkey.api.data.SimpleFilter.OrClause; +import org.labkey.api.data.SimpleFilter.SQLClause; import org.labkey.api.query.FieldKey; import org.labkey.api.query.SchemaKey; import org.labkey.api.query.TableSorter; import org.labkey.api.services.ServiceRegistry; +import org.labkey.api.util.ConfigurationException; import org.labkey.api.util.GUID; import org.labkey.api.util.logging.LogHelper; import org.labkey.vfs.FileLike; @@ -30,7 +32,7 @@ public interface DatabaseMigrationService { Logger LOG = LogHelper.getLogger(DatabaseMigrationService.class, "Information about database migration"); - record DomainFilter(Set containers, String column, FilterClause condition) {} + record DataFilter(Set containers, String column, FilterClause condition) {} static @NotNull DatabaseMigrationService get() { @@ -84,15 +86,25 @@ record Sequence(String schemaName, String tableName, String columnName, long las List getTablesToCopy(); + // Create a filter clause that selects from all specified containers and (in some overrides) applies table-specific filters + FilterClause getTableFilter(TableInfo sourceTable, FieldKey containerFieldKey, Set containers); + + // Create a filter clause that selects from all specified containers FilterClause getContainerClause(TableInfo sourceTable, FieldKey containerFieldKey, Set containers); + // Return the FieldKey that can be used to filter this table by container. Special values SITE_WIDE_TABLE and + // DUMMY_FIELD_KEY can be returned for special behaviors. DUMMY_FIELD_KEY ensures that the handler's custom + // getContainerClause() is always called. SITE_WIDE_TABLE is used to select all rows. @Nullable FieldKey getContainerFieldKey(TableInfo sourceTable); - void addDomainDataFilter(OrClause orClause, DomainFilter filter, TableInfo sourceTable, FieldKey fKey, Set selectColumnNames); + // Create a filter clause that selects all rows from unfiltered containers plus filtered rows from the filtered containers + FilterClause getDomainDataFilter(Set copyContainers, Set filteredContainers, List domainFilters, TableInfo sourceTable, FieldKey containerFieldKey, Set selectColumnNames); + + void addDomainDataFilter(OrClause orClause, DataFilter filter, TableInfo sourceTable, FieldKey fKey, Set selectColumnNames); // Do any necessary clean up after the target table has been populated. notCopiedFilter selects all rows in the - // source table that were NOT copied to the target table. (For example, they were filtered out due to container - // and/or domain data filtering.) + // source table that were NOT copied to the target table. (For example, rows in a global table not copied due to + // container filtering or rows in a provisioned table not copied due to domain data filtering.) void afterTable(TableInfo sourceTable, TableInfo targetTable, SimpleFilter notCopiedFilter); void afterSchema(DatabaseMigrationConfiguration configuration, DbSchema sourceSchema, DbSchema targetSchema, Map> sequenceMap); @@ -144,9 +156,18 @@ public List getTablesToCopy() .collect(Collectors.toCollection(ArrayList::new)); // Ensure mutable } + @Override + public FilterClause getTableFilter(TableInfo sourceTable, FieldKey containerFieldKey, Set containers) + { + return getContainerClause(sourceTable, containerFieldKey, containers); + } + @Override public FilterClause getContainerClause(TableInfo sourceTable, FieldKey containerFieldKey, Set containers) { + if (containerFieldKey == SITE_WIDE_TABLE || containerFieldKey == DUMMY_FIELD_KEY) + throw new IllegalStateException("Should not be supplying " + containerFieldKey + " to the default getContainerClause() method"); + return new InClause(containerFieldKey, containers); } @@ -192,14 +213,39 @@ public FilterClause getContainerClause(TableInfo sourceTable, FieldKey container } @Override - public void addDomainDataFilter(OrClause orClause, DomainFilter filter, TableInfo sourceTable, FieldKey fKey, Set selectColumnNames) + public FilterClause getDomainDataFilter(Set copyContainers, Set filteredContainers, List domainFilters, TableInfo sourceTable, FieldKey fKey, Set selectColumnNames) { - addDomainDataStandardFilter(orClause, filter, sourceTable, fKey, selectColumnNames); + // Filtered case: remove the filtered containers from the unconditional container set + Set otherContainers = new HashSet<>(copyContainers); + otherContainers.removeAll(filteredContainers); + FilterClause ret = getContainerClause(sourceTable, fKey, otherContainers); + + OrClause orClause = new OrClause(); + + // Delegate to the MigrationSchemaHandler to add domain-filtered containers back with their special filter applied + domainFilters.forEach(filter -> addDomainDataFilter(orClause, filter, sourceTable, fKey, selectColumnNames)); + + if (!orClause.getClauses().isEmpty()) + { + orClause.addClause(ret); + ret = orClause; + } + + return ret; + } + + @Override + public void addDomainDataFilter(OrClause orClause, DataFilter filter, TableInfo sourceTable, FieldKey fKey, Set selectColumnNames) + { + addDataFilter(orClause, filter, sourceTable, fKey, selectColumnNames); } - protected void addDomainDataStandardFilter(OrClause orClause, DomainFilter filter, TableInfo sourceTable, FieldKey fKey, Set selectColumnNames) + // Add a filter and return true if the column exists directly on the table + protected boolean addDataFilter(OrClause orClause, DataFilter filter, TableInfo sourceTable, FieldKey fKey, Set selectColumnNames) { - if (selectColumnNames.contains(filter.column())) + boolean columnExists = selectColumnNames.contains(filter.column()); + + if (columnExists) { // Select all rows in this domain-filtered container that meet its criteria orClause.addClause( @@ -209,37 +255,44 @@ protected void addDomainDataStandardFilter(OrClause orClause, DomainFilter filte ) ); } + + return columnExists; } - // Special domain data filter method for provisioned tables that have a built-in Flag field - protected void addDomainDataFlagFilter(OrClause orClause, DomainFilter filter, TableInfo sourceTable, FieldKey fKey, Set selectColumnNames) + // Add a filter to select all rows where the object property with equals the filter value + protected void addObjectPropertyFilter(OrClause orClause, DataFilter filter, TableInfo sourceTable, FieldKey fKey, int propertyId) + { + SQLFragment flagWhere = new SQLFragment("lsid IN (SELECT ObjectURI FROM exp.Object WHERE ObjectId IN (SELECT ObjectId FROM exp.ObjectProperty WHERE StringValue = ? AND PropertyId = ?))", filter.condition().getParamVals()[0], propertyId); + + orClause.addClause( + new AndClause( + getContainerClause(sourceTable, fKey, filter.containers()), + new SQLClause(flagWhere) + ) + ); + } + + // Special domain data filter method for provisioned tables that have a built-in Flag field (currently used by data classes) + protected void addDomainDataFlagFilter(OrClause orClause, DataFilter filter, TableInfo sourceTable, FieldKey fKey, Set selectColumnNames) { if (filter.column().equalsIgnoreCase("Flag")) { - SQLFragment flagWhere = new SQLFragment("lsid IN (SELECT ObjectURI FROM exp.Object WHERE ObjectId IN (SELECT ObjectId FROM exp.ObjectProperty WHERE StringValue = ? AND PropertyId = ?))", filter.condition().getParamVals()[0], getCommentPropertyId(sourceTable)); - - // Select all rows where the built-in flag column equals the filter value - orClause.addClause( - new AndClause( - getContainerClause(sourceTable, fKey, filter.containers()), - new SimpleFilter.SQLClause(flagWhere) - ) - ); + addObjectPropertyFilter(orClause, filter, sourceTable, fKey, getCommentPropertyId(sourceTable.getSchema().getScope())); } else { - addDomainDataStandardFilter(orClause, filter, sourceTable, fKey, selectColumnNames); + addDataFilter(orClause, filter, sourceTable, fKey, selectColumnNames); } } private Integer _commentPropertyId = null; - protected synchronized int getCommentPropertyId(TableInfo sourceTable) + protected synchronized int getCommentPropertyId(DbScope scope) { if (_commentPropertyId == null) { // Get the exp.PropertyDescriptor table from the source scope - TableInfo propertyDescriptor = sourceTable.getSchema().getScope().getSchema("exp", DbSchemaType.Migration).getTable("PropertyDescriptor"); + TableInfo propertyDescriptor = scope.getSchema("exp", DbSchemaType.Migration).getTable("PropertyDescriptor"); // Select the PropertyId associated with built-in Flag fields ("urn:exp.labkey.org/#Comment") Integer propertyId = new TableSelector(propertyDescriptor, Collections.singleton("PropertyId"), new SimpleFilter(FieldKey.fromParts("PropertyURI"), "urn:exp.labkey.org/#Comment"), null).getObject(Integer.class); if (propertyId == null) @@ -274,22 +327,6 @@ interface MigrationTableHandler FilterClause getAdditionalFilterClause(Set containers); } - abstract class DefaultMigrationTableHandler implements MigrationTableHandler - { - private final TableInfo _tableInfo; - - public DefaultMigrationTableHandler(TableInfo tableInfo) - { - _tableInfo = tableInfo; - } - - @Override - public TableInfo getTableInfo() - { - return _tableInfo; - } - } - /** * A MigrationFilter adds support for the named filter property in the migration configuration file. If present, * saveFilter() is called with the container guid and property value. Modules can register these to present @@ -301,4 +338,26 @@ interface MigrationFilter // Implementations should validate guid nullity void saveFilter(@Nullable GUID guid, String value); } + + // Helper method that parses a data filter then adds it and its container to the provided collections, coalescing + // cases where multiple containers specify the same filter + static void addDataFilter(String filterName, List dataFilters, Set filteredContainers, GUID guid, String filter) + { + String[] filterParts = filter.split("="); + if (filterParts.length != 2) + throw new ConfigurationException("Bad " + filterName + " value; expected =: " + filter); + + if (!filteredContainers.add(guid)) + throw new ConfigurationException("Duplicate " + filterName + " entry for container " + guid); + + String column = filterParts[0]; + String value = filterParts[1]; + FilterClause clause = CompareType.EQUAL.createFilterClause(new FieldKey(null, column), value); + // If another container is already using this filter clause, then simply add this guid to that domain filter. + // Otherwise, add a new domain filter to the list. + dataFilters.stream() + .filter(df -> df.column().equals(column) && df.condition().equals(clause)) + .findFirst() + .ifPresentOrElse(df -> df.containers().add(guid), () -> dataFilters.add(new DataFilter(new HashSet<>(Set.of(guid)), filterParts[0], clause))); + } } diff --git a/api/src/org/labkey/api/issues/AbstractIssuesListDefDomainKind.java b/api/src/org/labkey/api/issues/AbstractIssuesListDefDomainKind.java index 50bed3bd758..66520e9f0d3 100644 --- a/api/src/org/labkey/api/issues/AbstractIssuesListDefDomainKind.java +++ b/api/src/org/labkey/api/issues/AbstractIssuesListDefDomainKind.java @@ -65,7 +65,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; diff --git a/api/src/org/labkey/api/issues/IssuesSchema.java b/api/src/org/labkey/api/issues/IssuesSchema.java index 0152c01fec7..bd40f64179a 100644 --- a/api/src/org/labkey/api/issues/IssuesSchema.java +++ b/api/src/org/labkey/api/issues/IssuesSchema.java @@ -17,14 +17,10 @@ package org.labkey.api.issues; import org.labkey.api.data.DbSchema; +import org.labkey.api.data.DbSchemaType; import org.labkey.api.data.TableInfo; import org.labkey.api.data.dialect.SqlDialect; -/** - * User: Tamra Myers - * Date: Sep 29, 2006 - * Time: 12:44:32 PM - */ public class IssuesSchema { private static final IssuesSchema instance = new IssuesSchema(); @@ -48,7 +44,7 @@ public String getSchemaName() public DbSchema getSchema() { - return DbSchema.get(SCHEMA_NAME); + return DbSchema.get(SCHEMA_NAME, DbSchemaType.Module); } public SqlDialect getSqlDialect() diff --git a/core/src/org/labkey/core/CoreMigrationSchemaHandler.java b/core/src/org/labkey/core/CoreMigrationSchemaHandler.java index 8a786b2be35..53ac07fd310 100644 --- a/core/src/org/labkey/core/CoreMigrationSchemaHandler.java +++ b/core/src/org/labkey/core/CoreMigrationSchemaHandler.java @@ -10,7 +10,10 @@ import org.labkey.api.data.DbScope; import org.labkey.api.data.PropertySchema; import org.labkey.api.data.SQLFragment; -import org.labkey.api.data.SimpleFilter; +import org.labkey.api.data.SimpleFilter.AndClause; +import org.labkey.api.data.SimpleFilter.FilterClause; +import org.labkey.api.data.SimpleFilter.OrClause; +import org.labkey.api.data.SimpleFilter.SQLClause; import org.labkey.api.data.SqlExecutor; import org.labkey.api.data.Table; import org.labkey.api.data.TableInfo; @@ -113,19 +116,13 @@ public List getTablesToCopy() } @Override - public SimpleFilter.FilterClause getContainerClause(TableInfo sourceTable, FieldKey containerFieldKey, Set containers) + public FilterClause getTableFilter(TableInfo sourceTable, FieldKey containerFieldKey, Set containers) { - SimpleFilter.FilterClause containerClause = super.getContainerClause(sourceTable, containerFieldKey, containers); + FilterClause filterClause = getContainerClause(sourceTable, containerFieldKey, containers); String tableName = sourceTable.getName(); - // Users and root groups have container == null, so add that as an OR clause if ("Principals".equals(tableName) || "Members".equals(tableName)) { - SimpleFilter.OrClause orClause = new SimpleFilter.OrClause(); - orClause.addClause(containerClause); - orClause.addClause(new CompareType.CompareClause(containerFieldKey, CompareType.ISBLANK, null)); - containerClause = orClause; - if (_groupFilterCondition != null) { SQLFragment groupFilterFragment = new SQLFragment(); @@ -144,7 +141,7 @@ public SimpleFilter.FilterClause getContainerClause(TableInfo sourceTable, Field .append(_groupFilterCondition); } - containerClause = new SimpleFilter.AndClause(containerClause, new SimpleFilter.SQLClause(groupFilterFragment)); + filterClause = new AndClause(filterClause, new SQLClause(groupFilterFragment)); } } @@ -153,7 +150,25 @@ public SimpleFilter.FilterClause getContainerClause(TableInfo sourceTable, Field SQLFragment groupFilterFragment = new SQLFragment("UserId IN (SELECT UserId FROM core.Principals WHERE Type <> 'g' OR (type = 'g' AND UserId ") .append(_groupFilterCondition) .append("))"); - containerClause = new SimpleFilter.AndClause(containerClause, new SimpleFilter.SQLClause(groupFilterFragment)); + filterClause = new AndClause(filterClause, new SQLClause(groupFilterFragment)); + } + + return filterClause; + } + + @Override + public FilterClause getContainerClause(TableInfo sourceTable, FieldKey containerFieldKey, Set containers) + { + FilterClause containerClause = super.getContainerClause(sourceTable, containerFieldKey, containers); + String tableName = sourceTable.getName(); + + if ("Principals".equals(tableName) || "Members".equals(tableName)) + { + // Users and root groups have container == null, so add that as an OR clause + OrClause orClause = new OrClause(); + orClause.addClause(containerClause); + orClause.addClause(new CompareType.CompareClause(containerFieldKey, CompareType.ISBLANK, null)); + containerClause = orClause; } return containerClause; @@ -180,7 +195,7 @@ public String getName() public void saveFilter(@Nullable GUID guid, String groupFilter) { if (guid != null) - throw new ConfigurationException("GUID should not be provided to GroupFilter"); + throw new ConfigurationException("GroupFilter is applied globally; you cannot specify a GUID"); _groupFilterCondition = new SQLFragment(groupFilter); } diff --git a/experiment/src/org/labkey/experiment/DataClassMigrationSchemaHandler.java b/experiment/src/org/labkey/experiment/DataClassMigrationSchemaHandler.java index 4d0d8a09f12..24492cf2bb1 100644 --- a/experiment/src/org/labkey/experiment/DataClassMigrationSchemaHandler.java +++ b/experiment/src/org/labkey/experiment/DataClassMigrationSchemaHandler.java @@ -6,7 +6,7 @@ import org.labkey.api.data.DatabaseMigrationConfiguration; import org.labkey.api.data.DatabaseMigrationService; import org.labkey.api.data.DatabaseMigrationService.DefaultMigrationSchemaHandler; -import org.labkey.api.data.DatabaseMigrationService.DomainFilter; +import org.labkey.api.data.DatabaseMigrationService.DataFilter; import org.labkey.api.data.DbSchema; import org.labkey.api.data.DbSchemaType; import org.labkey.api.data.DbScope; @@ -54,33 +54,34 @@ public FilterClause getContainerClause(TableInfo sourceTable, FieldKey container { final FilterClause clause; - if (containerFieldKey != DUMMY_FIELD_KEY) - { - clause = super.getContainerClause(sourceTable, containerFieldKey, containers); - } - else + if (containerFieldKey == DUMMY_FIELD_KEY) { // There are a couple bad data class provisioned tables that lack an FK to exp.Data. In that case, craft the - // FilterClause explicitly. + // container FilterClause explicitly. clause = new SQLClause( new SQLFragment("LSID IN (SELECT LSID FROM exp.Data WHERE Container") .appendInClause(containers, sourceTable.getSqlDialect()) .append(")") ); } + else + { + clause = super.getContainerClause(sourceTable, containerFieldKey, containers); + } return clause; } @Override - public void addDomainDataFilter(OrClause orClause, DomainFilter filter, TableInfo sourceTable, FieldKey fKey, Set selectColumnNames) + public void addDomainDataFilter(OrClause orClause, DataFilter filter, TableInfo sourceTable, FieldKey fKey, Set selectColumnNames) { // Data classes have a built-in Flag field addDomainDataFlagFilter(orClause, filter, sourceTable, fKey, selectColumnNames); } private static final Set SEQUENCE_TABLES = Sets.newCaseInsensitiveHashSet("protsequence", "nucsequence", "molecule"); - private static final Set SEQUENCE_IDS = new HashSet<>(); + + private final Set SEQUENCE_IDS = new HashSet<>(); @Override public void afterTable(TableInfo sourceTable, TableInfo targetTable, SimpleFilter notCopiedFilter) @@ -142,7 +143,7 @@ public void afterTable(TableInfo sourceTable, TableInfo targetTable, SimpleFilte } }) .forEach(SEQUENCE_IDS::add); - LOG.info(" {} unique SequenceIds were added to the SequenceIdentity set", SEQUENCE_IDS.size() - startSize); + LOG.info(" {} unique SequenceIds were added to the SequenceIdentity set", Formats.commaf0.format(SEQUENCE_IDS.size() - startSize)); } } @@ -164,8 +165,9 @@ public void afterSchema(DatabaseMigrationConfiguration configuration, DbSchema s DatabaseMigrationService.get().copySourceTableToTargetTable(configuration, sourceTable, targetTable, DbSchemaType.Module, sequenceMap.get("biologics"), new DefaultMigrationSchemaHandler(biologicsTargetSchema) { @Override - public FilterClause getContainerClause(TableInfo sourceTable, FieldKey containerFieldKey, Set containers) + public FilterClause getTableFilter(TableInfo sourceTable, FieldKey containerFieldKey, Set containers) { + // This is a global table, so no container clause. Just query and copy the sequence IDs referenced by data class rows we copied. return new InClause(FieldKey.fromParts("SequenceId"), SEQUENCE_IDS); } }); diff --git a/experiment/src/org/labkey/experiment/SampleTypeMigrationSchemaHandler.java b/experiment/src/org/labkey/experiment/SampleTypeMigrationSchemaHandler.java index e15367bf323..1209df3e966 100644 --- a/experiment/src/org/labkey/experiment/SampleTypeMigrationSchemaHandler.java +++ b/experiment/src/org/labkey/experiment/SampleTypeMigrationSchemaHandler.java @@ -3,7 +3,7 @@ import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; import org.labkey.api.data.DatabaseMigrationService.DefaultMigrationSchemaHandler; -import org.labkey.api.data.DatabaseMigrationService.DomainFilter; +import org.labkey.api.data.DatabaseMigrationService.DataFilter; import org.labkey.api.data.SQLFragment; import org.labkey.api.data.Selector; import org.labkey.api.data.SimpleFilter; @@ -57,7 +57,7 @@ public FilterClause getContainerClause(TableInfo sourceTable, FieldKey container } @Override - public void addDomainDataFilter(OrClause orClause, DomainFilter filter, TableInfo sourceTable, FieldKey fKey, Set selectColumnNames) + public void addDomainDataFilter(OrClause orClause, DataFilter filter, TableInfo sourceTable, FieldKey fKey, Set selectColumnNames) { // Sample-type-specific optimization - joining to exp.Material instead of exp.Object is much faster if (filter.column().equalsIgnoreCase("Flag")) @@ -74,13 +74,13 @@ public void addDomainDataFilter(OrClause orClause, DomainFilter filter, TableInf .appendInClause(filter.containers(), sourceTable.getSqlDialect()) .append(" AND ObjectId IN (SELECT ObjectId FROM exp.ObjectProperty WHERE StringValue = ? AND PropertyId = ?))") .add(filter.condition().getParamVals()[0]) - .add(getCommentPropertyId(sourceTable)) + .add(getCommentPropertyId(sourceTable.getSchema().getScope())) ) ); } else { - addDomainDataStandardFilter(orClause, filter, sourceTable, fKey, selectColumnNames); + addDataFilter(orClause, filter, sourceTable, fKey, selectColumnNames); } } diff --git a/experiment/src/org/labkey/experiment/api/ExpDataClassTableImpl.java b/experiment/src/org/labkey/experiment/api/ExpDataClassTableImpl.java index 1cec6c28f5e..890d7f28be1 100644 --- a/experiment/src/org/labkey/experiment/api/ExpDataClassTableImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpDataClassTableImpl.java @@ -189,6 +189,7 @@ private ExprColumn createDataCountColumn(@NotNull ContainerFilter dataCountConta .append(")"); ExprColumn column = new ExprColumn(this, "DataCount", sql, JdbcType.INTEGER); column.setDescription("Contains the number of data currently stored in this data class"); + column.setFormat("#,##0"); return column; } diff --git a/experiment/src/org/labkey/experiment/api/ExpSampleTypeTableImpl.java b/experiment/src/org/labkey/experiment/api/ExpSampleTypeTableImpl.java index e844b9e20a2..094a6c9b012 100644 --- a/experiment/src/org/labkey/experiment/api/ExpSampleTypeTableImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpSampleTypeTableImpl.java @@ -127,6 +127,7 @@ private ExprColumn createSampleCountColumn(@NotNull ContainerFilter sampleCountC .append(")"); ExprColumn column = new ExprColumn(this, "SampleCount", sql, JdbcType.INTEGER); column.setDescription("Contains the number of samples currently stored in this sample type"); + column.setFormat("#,##0"); return column; } diff --git a/issues/src/org/labkey/issue/IssueMigrationSchemaHandler.java b/issues/src/org/labkey/issue/IssueMigrationSchemaHandler.java new file mode 100644 index 00000000000..d744555cafa --- /dev/null +++ b/issues/src/org/labkey/issue/IssueMigrationSchemaHandler.java @@ -0,0 +1,80 @@ +package org.labkey.issue; + +import org.apache.logging.log4j.Logger; +import org.labkey.api.collections.CsvSet; +import org.labkey.api.data.DatabaseMigrationConfiguration; +import org.labkey.api.data.DatabaseMigrationService.DefaultMigrationSchemaHandler; +import org.labkey.api.data.DbSchema; +import org.labkey.api.data.DbSchemaType; +import org.labkey.api.data.SQLFragment; +import org.labkey.api.data.SimpleFilter; +import org.labkey.api.data.SimpleFilter.InClause; +import org.labkey.api.data.SimpleFilter.NotClause; +import org.labkey.api.data.SimpleFilter.SQLClause; +import org.labkey.api.data.Table; +import org.labkey.api.data.TableInfo; +import org.labkey.api.data.TableSelector; +import org.labkey.api.issues.IssuesSchema; +import org.labkey.api.query.FieldKey; +import org.labkey.api.util.Formats; +import org.labkey.api.util.logging.LogHelper; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class IssueMigrationSchemaHandler extends DefaultMigrationSchemaHandler +{ + private static final Logger LOG = LogHelper.getLogger(IssueMigrationSchemaHandler.class, "Issue migration status"); + + private final Set ISSUE_IDS = new HashSet<>(); + + public IssueMigrationSchemaHandler() + { + super(DbSchema.get(IssuesSchema.ISSUE_DEF_SCHEMA_NAME, DbSchemaType.Provisioned)); + } + + @Override + public void afterTable(TableInfo sourceTable, TableInfo targetTable, SimpleFilter notCopiedFilter) + { + // Collect the issue IDs that were copied into the target table. We're assuming this set is much smaller than + // the set of issues IDs that *weren't* copied. + int startSize = ISSUE_IDS.size(); + + // Join the provisioned table to the issues table to get the IssueIds associated with the rows that were copied + SQLClause joinOnEntityId = new SQLClause( + new SQLFragment("EntityId IN (SELECT EntityId FROM ") + .append(targetTable) + .append(")") + ); + + new TableSelector(IssuesSchema.getInstance().getTableInfoIssues(), new CsvSet("IssueId, EntityId"), new SimpleFilter(joinOnEntityId), null).stream(Integer.class) + .forEach(ISSUE_IDS::add); + LOG.info(" {} IssueIds were added to the IssueId set", Formats.commaf0.format(ISSUE_IDS.size() - startSize)); + } + + @Override + public void afterSchema(DatabaseMigrationConfiguration configuration, DbSchema sourceSchema, DbSchema targetSchema, Map> sequenceMap) + { + LOG.info(" Deleting related issues, comments, and issues rows associated with {} issues", ISSUE_IDS.size()); + + if (!ISSUE_IDS.isEmpty()) + { + // Delete all issues, comments, and related issues that were NOT copied + SimpleFilter deleteRelatedFilter = new SimpleFilter( + new NotClause( + new InClause(FieldKey.fromParts("RelatedIssueId"), ISSUE_IDS) + ) + ); + Table.delete(IssuesSchema.getInstance().getTableInfoRelatedIssues(), deleteRelatedFilter); + SimpleFilter deleteFilter = new SimpleFilter( + new NotClause( + new InClause(FieldKey.fromParts("IssueId"), ISSUE_IDS) + ) + ); + Table.delete(IssuesSchema.getInstance().getTableInfoRelatedIssues(), deleteFilter); + Table.delete(IssuesSchema.getInstance().getTableInfoComments(), deleteFilter); + Table.delete(IssuesSchema.getInstance().getTableInfoIssues(), deleteFilter); + } + } +} diff --git a/issues/src/org/labkey/issue/IssuesModule.java b/issues/src/org/labkey/issue/IssuesModule.java index 2962f2bb49e..64591419cf5 100644 --- a/issues/src/org/labkey/issue/IssuesModule.java +++ b/issues/src/org/labkey/issue/IssuesModule.java @@ -22,6 +22,7 @@ import org.labkey.api.data.Container; import org.labkey.api.data.ContainerManager; import org.labkey.api.data.DataRegion; +import org.labkey.api.data.DatabaseMigrationService; import org.labkey.api.data.SqlExecutor; import org.labkey.api.data.SqlSelector; import org.labkey.api.exp.property.PropertyService; @@ -29,7 +30,6 @@ import org.labkey.api.issues.IssuesListDefService; import org.labkey.api.issues.IssuesSchema; import org.labkey.api.module.DefaultModule; -import org.labkey.api.module.Module; import org.labkey.api.module.ModuleContext; import org.labkey.api.query.QueryService; import org.labkey.api.query.QuerySettings; @@ -41,6 +41,7 @@ import org.labkey.api.security.UserManager; import org.labkey.api.services.ServiceRegistry; import org.labkey.api.usageMetrics.UsageMetricsService; +import org.labkey.api.util.StringUtilsLabKey; import org.labkey.api.util.emailTemplate.EmailTemplateService; import org.labkey.api.view.ActionURL; import org.labkey.api.view.BaseWebPartFactory; @@ -161,6 +162,8 @@ public void doStartup(ModuleContext moduleContext) return metric; }); } + + DatabaseMigrationService.get().registerSchemaHandler(new IssueMigrationSchemaHandler()); } @NotNull @@ -170,16 +173,10 @@ public Collection getSummary(Container c) Collection list = new LinkedList<>(); long count = IssueManager.getIssueCount(c); if (count > 0) - list.add("" + count + " Issue" + (count > 1 ? "s" : "")); + list.add(StringUtilsLabKey.pluralize(count, " Issue")); return list; } - @Override - public TabDisplayMode getTabDisplayMode() - { - return Module.TabDisplayMode.DISPLAY_USER_PREFERENCE; - } - @Override public ActionURL getTabURL(Container c, User user) {