From bf9b643a1e3ac329b10011241c8478703231c985 Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Tue, 14 Oct 2025 08:50:44 -0700 Subject: [PATCH 01/10] PostgreSql: Use string_agg(), ::text for group concat --- .../data/dialect/BasePostgreSqlDialect.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java b/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java index 4d297f83581..d61d5ceffa5 100644 --- a/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java +++ b/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java @@ -487,36 +487,37 @@ public SQLFragment getGroupConcat(SQLFragment sql, boolean distinct, boolean sor { // Sort function might not exist in external datasource; skip that syntax if not boolean useSortFunction = sorted && _arraySortFunctionExists.get(); + SQLFragment result = new SQLFragment(); - // TODO: Use "string_agg()" in place of array_to_string(array_agg())? - SQLFragment result = new SQLFragment("array_to_string("); if (useSortFunction) { + result.append("array_to_string("); result.append("core.sort("); // TODO: Switch to use ORDER BY option inside array aggregate instead of our custom function + result.append("array_agg("); } - result.append("array_agg("); - if (distinct) + else { - result.append("DISTINCT "); + result.append("string_agg("); } - if (includeNulls) + + if (distinct) { - result.append("COALESCE(CAST("); + result.append("DISTINCT "); } result.append(sql); if (includeNulls) { - result.append(" AS VARCHAR), '')"); + result.append("::text"); } - result.append(")"); if (useSortFunction) { - result.append(")"); + result.append(")"); // array_agg + result.append(")"); // core.sort } result.append(", "); result.append(delimiterSQL); - result.append(")"); + result.append(")"); // array_to_string | string_agg return result; } From 97358111e8dc155eb45fda1a1ef988694c7e4c14 Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Tue, 14 Oct 2025 09:21:23 -0700 Subject: [PATCH 02/10] exp.alias: use inner join --- .../labkey/experiment/api/ExpRunItemTableImpl.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/experiment/src/org/labkey/experiment/api/ExpRunItemTableImpl.java b/experiment/src/org/labkey/experiment/api/ExpRunItemTableImpl.java index d41010bd976..1f587e9acfc 100644 --- a/experiment/src/org/labkey/experiment/api/ExpRunItemTableImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpRunItemTableImpl.java @@ -19,8 +19,12 @@ import org.jetbrains.annotations.Nullable; import org.labkey.api.collections.CaseInsensitiveHashMap; import org.labkey.api.collections.CaseInsensitiveHashSet; +import org.labkey.api.data.ColumnInfo; import org.labkey.api.data.ContainerFilter; +import org.labkey.api.data.ForeignKey; +import org.labkey.api.data.LookupColumn; import org.labkey.api.data.MultiValuedForeignKey; +import org.labkey.api.data.MultiValuedLookupColumn; import org.labkey.api.data.MutableColumnInfo; import org.labkey.api.data.ParameterMapStatement; import org.labkey.api.data.SQLFragment; @@ -95,6 +99,13 @@ public TableInfo getLookupTableInfo() } }, "Alias") { + @Override + protected MultiValuedLookupColumn createMultiValuedLookupColumn(ColumnInfo lookupColumn, ColumnInfo parent, ColumnInfo childKey, ColumnInfo junctionKey, ForeignKey fk) + { + ((LookupColumn)lookupColumn)._joinType = LookupColumn.JoinType.inner; + return super.createMultiValuedLookupColumn(lookupColumn, parent, childKey, junctionKey, fk); + } + @Override public boolean isMultiSelectInput() { From 8e34ac7d519e96492fdad006df551fc6a8c609c8 Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Tue, 14 Oct 2025 17:19:43 -0700 Subject: [PATCH 03/10] Issue 53567: Convert alias from MVFK to inline --- .../api/query/AbstractQueryUpdateService.java | 68 ++++++++++--------- .../api/AliasDisplayColumnFactory.java | 25 ++----- .../experiment/api/ExpRunItemTableImpl.java | 37 ++++------ .../api/SampleTypeUpdateServiceDI.java | 30 +------- query/src/org/labkey/query/MultiValueTest.jsp | 11 --- 5 files changed, 58 insertions(+), 113 deletions(-) diff --git a/api/src/org/labkey/api/query/AbstractQueryUpdateService.java b/api/src/org/labkey/api/query/AbstractQueryUpdateService.java index 2984e741704..2aa0db58b79 100644 --- a/api/src/org/labkey/api/query/AbstractQueryUpdateService.java +++ b/api/src/org/labkey/api/query/AbstractQueryUpdateService.java @@ -20,6 +20,7 @@ import org.apache.logging.log4j.LogManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.json.JSONArray; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; @@ -716,6 +717,40 @@ public List> insertRows(User user, Container container, List return null; } + protected Object coerceTypesValue(ColumnInfo col, Map providedValues, String key, Object value) + { + if (col != null && value != null && + !col.getJavaObjectClass().isInstance(value) && + !(value instanceof AttachmentFile) && + !(value instanceof MultipartFile) && + !(value instanceof JSONArray) && + !(value instanceof String[]) && + !(col.getFk() instanceof MultiValuedForeignKey)) + { + try + { + if (PropertyType.FILE_LINK.equals(col.getPropertyType())) + value = ExpDataFileConverter.convert(value); + else if (col.getKindOfQuantity() != null) + { + providedValues.put(key, value); + value = Quantity.convert(value, col.getKindOfQuantity().getStorageUnit()); + } + else + value = col.getConvertFn().apply(value); + } + catch (ConvertHelper.FileConversionException e) + { + throw e; + } + catch (ConversionException e) + { + // That's OK, the transformation script may be able to fix up the value before it gets inserted + } + } + + return value; + } /** Attempt to make the passed in types match the expected types so the script doesn't have to do the conversion */ @Deprecated @@ -726,42 +761,13 @@ protected Map coerceTypes(Map row, Map entry : row.entrySet()) { ColumnInfo col = columnMap.get(entry.getKey()); - - Object value = entry.getValue(); - if (col != null && value != null && - !col.getJavaObjectClass().isInstance(value) && - !(value instanceof AttachmentFile) && - !(value instanceof MultipartFile) && - !(value instanceof String[]) && - !(col.getFk() instanceof MultiValuedForeignKey)) - { - try - { - if (PropertyType.FILE_LINK.equals(col.getPropertyType())) - value = ExpDataFileConverter.convert(value); - else if (col.getKindOfQuantity() != null) - { - providedValues.put(entry.getKey(), value); - value = Quantity.convert(value, col.getKindOfQuantity().getStorageUnit()); - } - else - value = col.getConvertFn().apply(value); - } - catch (ConvertHelper.FileConversionException e) - { - throw e; - } - catch (ConversionException e) - { - // That's OK, the transformation script may be able to fix up the value before it gets inserted - } - } + Object value = coerceTypesValue(col, providedValues, entry.getKey(), entry.getValue()); result.put(entry.getKey(), value); } + return result; } - protected abstract Map updateRow(User user, Container container, Map row, @NotNull Map oldRow, @Nullable Map configParameters) throws InvalidKeyException, ValidationException, QueryUpdateServiceException, SQLException; diff --git a/experiment/src/org/labkey/experiment/api/AliasDisplayColumnFactory.java b/experiment/src/org/labkey/experiment/api/AliasDisplayColumnFactory.java index 6f78bf7f455..050f4f07e36 100644 --- a/experiment/src/org/labkey/experiment/api/AliasDisplayColumnFactory.java +++ b/experiment/src/org/labkey/experiment/api/AliasDisplayColumnFactory.java @@ -6,10 +6,7 @@ import org.labkey.api.data.DisplayColumnFactory; import org.labkey.api.data.MultiValuedDisplayColumn; import org.labkey.api.data.RenderContext; -import org.labkey.api.data.TableSelector; -import org.labkey.api.exp.api.ExperimentService; -import java.util.Collections; import java.util.List; class AliasDisplayColumnFactory implements DisplayColumnFactory @@ -17,32 +14,18 @@ class AliasDisplayColumnFactory implements DisplayColumnFactory @Override public DisplayColumn createRenderer(ColumnInfo colInfo) { - DataColumn dataColumn = new DataColumn(colInfo); + DataColumn dataColumn = new DataColumn(colInfo, false); dataColumn.setInputType("text"); - return new MultiValuedDisplayColumn(dataColumn, true) + return new MultiValuedDisplayColumn(dataColumn) { @Override public Object getInputValue(RenderContext ctx) { Object value = super.getInputValue(ctx); - StringBuilder sb = new StringBuilder(); if (value instanceof List) - { - String delim = ""; - for (Object item : (List) value) - { - if (item != null) - { - String name = new TableSelector(ExperimentService.get().getTinfoAlias(), Collections.singleton("Name")).getObject(item, String.class); - - sb.append(delim); - sb.append(name); - delim = ", "; - } - } - } - return sb.toString(); + return String.join(", ", (List) value); + return ""; } }; } diff --git a/experiment/src/org/labkey/experiment/api/ExpRunItemTableImpl.java b/experiment/src/org/labkey/experiment/api/ExpRunItemTableImpl.java index 1f587e9acfc..70da3f783d4 100644 --- a/experiment/src/org/labkey/experiment/api/ExpRunItemTableImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpRunItemTableImpl.java @@ -21,10 +21,7 @@ import org.labkey.api.collections.CaseInsensitiveHashSet; import org.labkey.api.data.ColumnInfo; import org.labkey.api.data.ContainerFilter; -import org.labkey.api.data.ForeignKey; -import org.labkey.api.data.LookupColumn; -import org.labkey.api.data.MultiValuedForeignKey; -import org.labkey.api.data.MultiValuedLookupColumn; +import org.labkey.api.data.MultiValuedRenderContext; import org.labkey.api.data.MutableColumnInfo; import org.labkey.api.data.ParameterMapStatement; import org.labkey.api.data.SQLFragment; @@ -36,6 +33,7 @@ import org.labkey.api.exp.property.PropertyService; import org.labkey.api.exp.query.ExpMaterialInputTable; import org.labkey.api.exp.query.ExpSchema; +import org.labkey.api.query.AliasedColumn; import org.labkey.api.query.FieldKey; import org.labkey.api.query.FilteredTable; import org.labkey.api.query.LookupForeignKey; @@ -88,30 +86,23 @@ else if (!getContainer().hasPermission(user, perm)) protected MutableColumnInfo createAliasColumn(String alias, Supplier aliasMapTable) { - var aliasCol = wrapColumn("Alias", getRealTable().getColumn("LSID")); - aliasCol.setDescription("Contains the list of aliases for this data object"); - aliasCol.setFk(new MultiValuedForeignKey(new LookupForeignKey("LSID") - { - @Override - public TableInfo getLookupTableInfo() - { - return aliasMapTable.get(); - } - }, "Alias") + ColumnInfo lsidCol = getRealTable().getColumn("LSID"); + MutableColumnInfo aliasCol = new AliasedColumn(this, alias, lsidCol) { @Override - protected MultiValuedLookupColumn createMultiValuedLookupColumn(ColumnInfo lookupColumn, ColumnInfo parent, ColumnInfo childKey, ColumnInfo junctionKey, ForeignKey fk) + public SQLFragment getValueSql(String tableAlias) { - ((LookupColumn)lookupColumn)._joinType = LookupColumn.JoinType.inner; - return super.createMultiValuedLookupColumn(lookupColumn, parent, childKey, junctionKey, fk); + return new SQLFragment("(SELECT ") + .append(getSqlDialect().getGroupConcat(new SQLFragment("AA.name"), false, false, new SQLFragment().appendStringLiteral(MultiValuedRenderContext.VALUE_DELIMITER, getSqlDialect()), true)) + .append(" FROM ").append(ExperimentServiceImpl.get().getTinfoAlias(), "AA") + .append(" INNER JOIN ").append(aliasMapTable.get(), "MM") + .append(" ON AA.rowId = MM.alias") + .append(" WHERE MM.lsid = ").append(lsidCol.getValueSql(tableAlias)) + .append(")"); } + }; - @Override - public boolean isMultiSelectInput() - { - return false; - } - }); + aliasCol.setDescription("Contains the list of aliases for this data object"); aliasCol.setCalculated(false); aliasCol.setNullable(true); aliasCol.setRequired(false); diff --git a/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java b/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java index 021036ffdfb..50550fda396 100644 --- a/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java +++ b/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java @@ -657,7 +657,6 @@ protected Map coerceTypes(Map row, Map entry : row.entrySet()) @@ -673,34 +672,11 @@ else if (col != null && col == amountCol) { value = _SamplesCoerceDataIterator.SampleAmountConvertColumn.getValue(amountVal, unitsCol != null, unitsVal, baseUnit, _sampleType == null ? null : _sampleType.getName()); } - else if (col != null && value != null && - !col.getJavaObjectClass().isInstance(value) && - !(value instanceof AttachmentFile) && - !(value instanceof MultipartFile) && - !(value instanceof String[]) && - !(col.getFk() instanceof MultiValuedForeignKey)) + else { - try - { - if (PropertyType.FILE_LINK.equals(col.getPropertyType())) - value = ExpDataFileConverter.convert(value); - else if (col.getKindOfQuantity() != null) - { - providedValues.put(entry.getKey(), value); - value = Quantity.convert(value, col.getKindOfQuantity().getStorageUnit()); - } - else - value = col.getConvertFn().apply(value); - } - catch (ConvertHelper.FileConversionException e) - { - throw e; - } - catch (ConversionException e) - { - // That's OK, the transformation script may be able to fix up the value before it gets inserted - } + value = coerceTypesValue(col, providedValues, entry.getKey(), value); } + result.put(entry.getKey(), value); } return result; diff --git a/query/src/org/labkey/query/MultiValueTest.jsp b/query/src/org/labkey/query/MultiValueTest.jsp index 7f5b978a93f..e82836b0ff1 100644 --- a/query/src/org/labkey/query/MultiValueTest.jsp +++ b/query/src/org/labkey/query/MultiValueTest.jsp @@ -1,5 +1,4 @@ <%@ page import="com.fasterxml.jackson.databind.JsonNode" %> -<%@ page import="com.fasterxml.jackson.databind.ObjectMapper" %> <%@ page import="com.fasterxml.jackson.databind.node.ArrayNode" %> <%@ page import="com.fasterxml.jackson.databind.node.ObjectNode" %> <%@ page import="com.google.common.collect.ImmutableList" %> @@ -16,7 +15,6 @@ <%@ page import="org.labkey.api.data.TableInfo" %> <%@ page import="org.labkey.api.exp.api.ExpMaterial" %> <%@ page import="org.labkey.api.exp.api.ExperimentService" %> -<%@ page import="org.labkey.api.exp.query.ExpMaterialTable" %> <%@ page import="org.labkey.api.module.Module" %> <%@ page import="org.labkey.api.module.ModuleLoader" %> <%@ page import="org.labkey.api.query.BatchValidationException" %> @@ -37,17 +35,8 @@ <%@ page import="java.util.Map" %> <%@ page import="static org.hamcrest.CoreMatchers.hasItems" %> <%@ page import="static org.junit.Assert.assertThat" %> -<%@ page import="static org.junit.Assert.assertEquals" %> <%@ page import="java.util.Set" %> -<%@ page import="static org.labkey.api.exp.query.ExpMaterialTable.Column.Alias" %> -<%@ page import="static org.labkey.api.exp.query.ExpMaterialTable.Column.Name" %> -<%@ page import="com.fasterxml.jackson.core.JsonPointer" %> -<%@ page import="org.labkey.api.study.MasterPatientIndexService" %> -<%@ page import="org.labkey.api.settings.FolderSettingsCache" %> <%@ page import="org.labkey.api.settings.LookAndFeelProperties" %> -<%@ page import="org.labkey.api.settings.AppProps" %> -<%@ page import="static org.junit.Assert.assertNotNull" %> -<%@ page import="static org.junit.Assert.assertTrue" %> <%@ page import="org.labkey.api.util.JsonUtil" %> <%@ page import="static org.labkey.api.exp.api.ExperimentService.asInteger" %> <%@ page import="static org.labkey.api.exp.api.ExperimentService.asLong" %> From 442ea3de6942772175deea1c53a42f1d38704aad Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Thu, 23 Oct 2025 15:35:12 -0700 Subject: [PATCH 04/10] Update tests --- .../labkey/experiment/api/ExpDataClassDataTestCase.jsp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/experiment/src/org/labkey/experiment/api/ExpDataClassDataTestCase.jsp b/experiment/src/org/labkey/experiment/api/ExpDataClassDataTestCase.jsp index 716214b0022..42aeed754d7 100644 --- a/experiment/src/org/labkey/experiment/api/ExpDataClassDataTestCase.jsp +++ b/experiment/src/org/labkey/experiment/api/ExpDataClassDataTestCase.jsp @@ -482,15 +482,15 @@ private void verifyAliasesViaSelectRows(String schemaName, String queryName, lon // "value": "JUNIT-5-50", "url": "..." // }, // "Alias": [{ - // "displayValue": "a", "value": 4 + // "value": "a" // },{ - // "displayValue": "c", "value": 6 + // "value": "b" // },{ - // "displayValue": "b", "value": 989 + // "value": "c" // }] //} List> row0aliases = (List>)row0data.get(ExpDataTable.Column.Alias.name()); - Set aliases = row0aliases.stream().map(m -> (String)m.get("displayValue")).collect(Collectors.toSet()); + Set aliases = row0aliases.stream().map(m -> (String)m.get("value")).collect(Collectors.toSet()); assertEquals(aliases, expectedAliases); } } @@ -1107,7 +1107,7 @@ public void testInsertOptionUpdate() throws Exception ts = new TableSelector(table, columnNames, null, new Sort("Name")); ts.setForDisplay(true); - String aliasAlias = "alias$alias$name"; + String aliasAlias = "alias"; String flagAlias = "flag$"; List> rows = Arrays.asList(ts.getMapArray()); From 761c65b174baf53c21e5cc4cd6761bbc453ec13e Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Fri, 24 Oct 2025 10:11:43 -0700 Subject: [PATCH 05/10] ColumnInfo.isMultiValued --- .../org/labkey/api/audit/AuditHandler.java | 4 +- .../api/data/AbstractWrappedColumnInfo.java | 6 ++ .../org/labkey/api/data/BaseColumnInfo.java | 14 +++ api/src/org/labkey/api/data/ColumnInfo.java | 2 + .../api/data/MultiValuedLookupColumn.java | 6 ++ .../api/dataiterator/CoerceDataIterator.java | 2 +- .../api/dataiterator/SimpleTranslator.java | 2 +- .../api/query/AbstractQueryUpdateService.java | 4 +- .../experiment/api/ExpRunItemTableImpl.java | 14 +++ .../api/SampleTypeUpdateServiceDI.java | 6 +- query/src/org/labkey/query/MultiValueTest.jsp | 89 ------------------- 11 files changed, 48 insertions(+), 101 deletions(-) diff --git a/api/src/org/labkey/api/audit/AuditHandler.java b/api/src/org/labkey/api/audit/AuditHandler.java index a09036300f4..9c0f1f8f973 100644 --- a/api/src/org/labkey/api/audit/AuditHandler.java +++ b/api/src/org/labkey/api/audit/AuditHandler.java @@ -87,7 +87,7 @@ static Pair, Map> getOldAndNewRecordForMerge String key = entry.getKey(); // getDatasetRows() (at least) should return key==column.getName(), expect getColumn(name) to work ColumnInfo col = null==table ? null : table.getColumn(key); - if (col != null && col.getFk() instanceof MultiValuedForeignKey) + if (col != null && (col.isMultiValued() || col.getFk() instanceof MultiValuedForeignKey)) isMultiValued = true; String nameFromAlias = key; @@ -99,7 +99,7 @@ static Pair, Map> getOldAndNewRecordForMerge if (aliasColumn != null) { - if (aliasColumn.getFk() != null && aliasColumn.getFk() instanceof MultiValuedForeignKey) + if (aliasColumn.getFk() != null && (aliasColumn.isMultiValued() || aliasColumn.getFk() instanceof MultiValuedForeignKey)) isMultiValued = true; nameFromAlias = aliasColumn.getName(); } diff --git a/api/src/org/labkey/api/data/AbstractWrappedColumnInfo.java b/api/src/org/labkey/api/data/AbstractWrappedColumnInfo.java index 7e990fff7ce..991bb009676 100644 --- a/api/src/org/labkey/api/data/AbstractWrappedColumnInfo.java +++ b/api/src/org/labkey/api/data/AbstractWrappedColumnInfo.java @@ -842,4 +842,10 @@ public boolean isScannable() { return delegate.isScannable(); } + + @Override + public boolean isMultiValued() + { + return delegate.isMultiValued(); + } } diff --git a/api/src/org/labkey/api/data/BaseColumnInfo.java b/api/src/org/labkey/api/data/BaseColumnInfo.java index b9bf8250798..f3c53e25d9c 100644 --- a/api/src/org/labkey/api/data/BaseColumnInfo.java +++ b/api/src/org/labkey/api/data/BaseColumnInfo.java @@ -97,6 +97,7 @@ public class BaseColumnInfo extends ColumnRenderPropertiesImpl implements Mutabl private String _jdbcDefaultValue = null; // TODO: Merge with defaultValue, see #17646 private boolean _isAutoIncrement = false; private boolean _hasDbSequence = false; + private boolean _isMultiValued = false; private boolean _isRootDbSequence = false; private boolean _isKeyField = false; private boolean _isReadOnly = false; @@ -324,6 +325,7 @@ public void copyAttributesFrom(ColumnInfo col) // and the remaining setUserEditable(col.isUserEditable()); + setIsMultiValued(col.isMultiValued()); setNullable(col.isNullable()); setRequired(col.isRequiredSet()); setAutoIncrement(col.isAutoIncrement()); @@ -445,6 +447,7 @@ public void setExtraAttributesFrom(ColumnInfo col) setDerivationDataScope(col.getDerivationDataScope()); setScannable(col.isScannable()); + setIsMultiValued(col.isMultiValued()); } /* @@ -988,6 +991,17 @@ public void setIsRootDbSequence(boolean isRootDbSequence) _isRootDbSequence = isRootDbSequence; } + public boolean isMultiValued() + { + return _isMultiValued; + } + + public void setIsMultiValued(boolean isMultiValued) + { + checkLocked(); + _isMultiValued = isMultiValued; + } + @Override public boolean isReadOnly() { diff --git a/api/src/org/labkey/api/data/ColumnInfo.java b/api/src/org/labkey/api/data/ColumnInfo.java index c2eb03e754a..5eca1e3b17c 100644 --- a/api/src/org/labkey/api/data/ColumnInfo.java +++ b/api/src/org/labkey/api/data/ColumnInfo.java @@ -287,6 +287,8 @@ default boolean isSortable() */ boolean isKeyField(); + boolean isMultiValued(); + @Override boolean isMvEnabled(); diff --git a/api/src/org/labkey/api/data/MultiValuedLookupColumn.java b/api/src/org/labkey/api/data/MultiValuedLookupColumn.java index e107012669e..a1c1a2240a0 100644 --- a/api/src/org/labkey/api/data/MultiValuedLookupColumn.java +++ b/api/src/org/labkey/api/data/MultiValuedLookupColumn.java @@ -211,4 +211,10 @@ protected SQLFragment getAggregateFunction(SQLFragment sql) // Can't sort because we need to make sure that all the multi-value columns come back in the same order return getSqlDialect().getGroupConcat(sql, false, false, new SQLFragment().appendStringLiteral(MultiValuedRenderContext.VALUE_DELIMITER, getSqlDialect()), true); } + + @Override + public boolean isMultiValued() + { + return true; + } } diff --git a/api/src/org/labkey/api/dataiterator/CoerceDataIterator.java b/api/src/org/labkey/api/dataiterator/CoerceDataIterator.java index 3e5234a8ade..591c2005a7b 100644 --- a/api/src/org/labkey/api/dataiterator/CoerceDataIterator.java +++ b/api/src/org/labkey/api/dataiterator/CoerceDataIterator.java @@ -65,7 +65,7 @@ void init(TableInfo target, boolean useImportAliases, boolean outputAllColumns) seen.add(to.getName()); if (to.getPropertyType() == PropertyType.ATTACHMENT || to.getPropertyType() == PropertyType.FILE_LINK) addColumn(to, i); - else if (to.getFk() instanceof MultiValuedForeignKey) + else if (to.isMultiValued() || to.getFk() instanceof MultiValuedForeignKey) addColumn(to.getName(), i); // pass-through multi-value columns -- converting will stringify a collection else addConvertColumn(to.getName(), i, to.getJdbcType(), to.getFk(), RemapMissingBehavior.Error, true); diff --git a/api/src/org/labkey/api/dataiterator/SimpleTranslator.java b/api/src/org/labkey/api/dataiterator/SimpleTranslator.java index 4fe081a51d9..e4e861b5ff5 100644 --- a/api/src/org/labkey/api/dataiterator/SimpleTranslator.java +++ b/api/src/org/labkey/api/dataiterator/SimpleTranslator.java @@ -1356,7 +1356,7 @@ private SimpleConvertColumn createConvertColumn(@NotNull ColumnInfo col, int fro c = new RemappingConvertColumn(c, fromIndex, col, missing, true, lookupResolutionType); } - boolean multiValue = fk instanceof MultiValuedForeignKey; + boolean multiValue = col.isMultiValued() || fk instanceof MultiValuedForeignKey; if (multiValue) { // convert input into Collection of jdbcType values diff --git a/api/src/org/labkey/api/query/AbstractQueryUpdateService.java b/api/src/org/labkey/api/query/AbstractQueryUpdateService.java index 2aa0db58b79..b1175306ddd 100644 --- a/api/src/org/labkey/api/query/AbstractQueryUpdateService.java +++ b/api/src/org/labkey/api/query/AbstractQueryUpdateService.java @@ -20,7 +20,6 @@ import org.apache.logging.log4j.LogManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.json.JSONArray; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; @@ -723,9 +722,8 @@ protected Object coerceTypesValue(ColumnInfo col, Map providedVa !col.getJavaObjectClass().isInstance(value) && !(value instanceof AttachmentFile) && !(value instanceof MultipartFile) && - !(value instanceof JSONArray) && !(value instanceof String[]) && - !(col.getFk() instanceof MultiValuedForeignKey)) + !(col.isMultiValued() || col.getFk() instanceof MultiValuedForeignKey)) { try { diff --git a/experiment/src/org/labkey/experiment/api/ExpRunItemTableImpl.java b/experiment/src/org/labkey/experiment/api/ExpRunItemTableImpl.java index 70da3f783d4..b3587370485 100644 --- a/experiment/src/org/labkey/experiment/api/ExpRunItemTableImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpRunItemTableImpl.java @@ -21,6 +21,7 @@ import org.labkey.api.collections.CaseInsensitiveHashSet; import org.labkey.api.data.ColumnInfo; import org.labkey.api.data.ContainerFilter; +import org.labkey.api.data.ForeignKey; import org.labkey.api.data.MultiValuedRenderContext; import org.labkey.api.data.MutableColumnInfo; import org.labkey.api.data.ParameterMapStatement; @@ -89,6 +90,13 @@ protected MutableColumnInfo createAliasColumn(String alias, Supplier ColumnInfo lsidCol = getRealTable().getColumn("LSID"); MutableColumnInfo aliasCol = new AliasedColumn(this, alias, lsidCol) { + @Override + public ForeignKey getFk() + { + // Do not traverse lookup + return null; + } + @Override public SQLFragment getValueSql(String tableAlias) { @@ -100,6 +108,12 @@ public SQLFragment getValueSql(String tableAlias) .append(" WHERE MM.lsid = ").append(lsidCol.getValueSql(tableAlias)) .append(")"); } + + @Override + public boolean isMultiValued() + { + return true; + } }; aliasCol.setDescription("Contains the list of aliases for this data object"); diff --git a/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java b/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java index 50550fda396..9cb45a4f468 100644 --- a/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java +++ b/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java @@ -26,7 +26,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.labkey.api.assay.AssayFileWriter; -import org.labkey.api.attachments.AttachmentFile; import org.labkey.api.audit.AuditLogService; import org.labkey.api.audit.TransactionAuditProvider; import org.labkey.api.collections.CaseInsensitiveHashMap; @@ -42,10 +41,8 @@ import org.labkey.api.data.ContainerFilter; import org.labkey.api.data.ContainerManager; import org.labkey.api.data.ConversionExceptionWithMessage; -import org.labkey.api.data.ConvertHelper; import org.labkey.api.data.DbScope; import org.labkey.api.data.DbSequence; -import org.labkey.api.data.ExpDataFileConverter; import org.labkey.api.data.Filter; import org.labkey.api.data.ForeignKey; import org.labkey.api.data.ImportAliasable; @@ -118,7 +115,6 @@ import org.labkey.api.view.UnauthorizedException; import org.labkey.experiment.ExpDataIterators; import org.labkey.experiment.SampleTypeAuditProvider; -import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.nio.file.Path; @@ -1944,7 +1940,7 @@ else if (amountImportAliasSet.contains(from.getName())) else addColumn(to, i); } - else if (to.getFk() instanceof MultiValuedForeignKey) + else if (to.isMultiValued() || to.getFk() instanceof MultiValuedForeignKey) { // pass-through multi-value columns -- converting will stringify a collection if (isScopedField) diff --git a/query/src/org/labkey/query/MultiValueTest.jsp b/query/src/org/labkey/query/MultiValueTest.jsp index e82836b0ff1..c2f90629e65 100644 --- a/query/src/org/labkey/query/MultiValueTest.jsp +++ b/query/src/org/labkey/query/MultiValueTest.jsp @@ -13,7 +13,6 @@ <%@ page import="org.labkey.api.data.SimpleFilter" %> <%@ page import="org.labkey.api.data.Table" %> <%@ page import="org.labkey.api.data.TableInfo" %> -<%@ page import="org.labkey.api.exp.api.ExpMaterial" %> <%@ page import="org.labkey.api.exp.api.ExperimentService" %> <%@ page import="org.labkey.api.module.Module" %> <%@ page import="org.labkey.api.module.ModuleLoader" %> @@ -24,7 +23,6 @@ <%@ page import="org.labkey.api.security.Group" %> <%@ page import="org.labkey.api.security.SecurityManager" %> <%@ page import="org.labkey.api.security.User" %> -<%@ page import="org.labkey.api.util.GUID" %> <%@ page import="org.labkey.api.util.TestContext" %> <%@ page import="org.labkey.api.view.ActionURL" %> <%@ page import="org.labkey.api.view.ViewServlet" %> @@ -33,10 +31,7 @@ <%@ page import="java.util.HashSet" %> <%@ page import="java.util.List" %> <%@ page import="java.util.Map" %> -<%@ page import="static org.hamcrest.CoreMatchers.hasItems" %> -<%@ page import="static org.junit.Assert.assertThat" %> <%@ page import="java.util.Set" %> -<%@ page import="org.labkey.api.settings.LookAndFeelProperties" %> <%@ page import="org.labkey.api.util.JsonUtil" %> <%@ page import="static org.labkey.api.exp.api.ExperimentService.asInteger" %> <%@ page import="static org.labkey.api.exp.api.ExperimentService.asLong" %> @@ -238,89 +233,6 @@ Assert.assertEquals(speciesId4, verifySpecies4); } - // Issue 43241: SM: Display of dates from Input columns for sample types does not use project date format - @Test - public void testSampleAlias() throws Exception - { - // - // SETUP - create a Sample with >1 alias - // - - // set a date format - var props = LookAndFeelProperties.getWriteableInstance(c); - props.setDefaultDateTimeFormat("'kevink' dd-MM-yyyy"); - props.save(); - - String materialLsid = ExperimentService.get().generateLSID(c, ExpMaterial.class, "TestMaterial"); - var material = ExperimentService.get().createExpMaterial(c, materialLsid, "TestMaterial"); - material.save(getUser()); - - // insert some aliases - var alias1 = Table.insert(getUser(), ExperimentService.get().getTinfoAlias(), CaseInsensitiveHashMap.of("name", aliasPrefix + "1-" + new GUID())); - var alias2 = Table.insert(getUser(), ExperimentService.get().getTinfoAlias(), CaseInsensitiveHashMap.of("name", aliasPrefix + "2-" + new GUID())); - - // insert sample -> alias junction entries - ExperimentService.get().getTinfoMaterialAliasMap(); - Table.insert(getUser(), ExperimentService.get().getTinfoMaterialAliasMap(), CaseInsensitiveHashMap.of( - "container", c.getEntityId(), - "alias", alias1.get("rowId"), - "lsid", materialLsid - )); - Table.insert(getUser(), ExperimentService.get().getTinfoMaterialAliasMap(), CaseInsensitiveHashMap.of( - "container", c.getEntityId(), - "alias", alias2.get("rowId"), - "lsid", materialLsid - )); - - // verify we have set up correctly - var aliases = material.getAliases(); - assertThat(aliases, hasItems(alias1.get("name"), alias2.get("name"))); - - // - // VERIFY - query the aliases and verify the date is formatted correctly - // - - ActionURL selectUrl = new ActionURL(QueryController.SelectRowsAction.class, c); - selectUrl.addParameter(QueryParam.schemaName, "exp"); - selectUrl.addParameter(QueryParam.queryName, "Materials"); - selectUrl.addParameter("query." + QueryParam.columns, "Alias/Created"); - selectUrl.addFilter("query", FieldKey.fromParts("rowId"), CompareType.EQUAL, material.getRowId()); - selectUrl.addParameter("includeMetadata", false); - // enable the multi-value array response and "formattedValue" - selectUrl.addParameter("apiVersion", "17.1"); - - MockHttpServletResponse resp = ViewServlet.GET(selectUrl, getUser(), null); - Assert.assertEquals(200, resp.getStatus()); - String content = resp.getContentAsString(); - //System.out.println("query response:\n" + content); - - ObjectNode n = JsonUtil.DEFAULT_MAPPER.readValue(content, ObjectNode.class); - Assert.assertEquals("Expected only one row", 1, n.get("rowCount").asInt()); - ArrayNode rows = n.withArray("rows"); - - JsonNode row0 = rows.get(0); - // JSON Pointer uses "~1" to encode "/" in "Alias/Created" - ArrayNode row0aliases = (ArrayNode)row0.at("/data/Alias~1Created"); - Assert.assertEquals("Expected two aliases, got:\n" + row0aliases, - 2, row0aliases.size()); - - for (var row0alias : row0aliases) - { - // JSON formatted date value - String value = row0alias.get("value").asText(); - Assert.assertNotNull(value); - - // formatted with container date format - String formattedValue = row0alias.get("formattedValue").asText(); - Assert.assertTrue("Expected date format to be applied, got: " + formattedValue, - formattedValue.startsWith("kevink ")); - - // don't care what the json format looks like, as long as it is different - Assert.assertNotEquals(value, formattedValue); - } - - } - private void throwErrors(BatchValidationException errors) throws BatchValidationException { if (errors.hasErrors()) @@ -328,5 +240,4 @@ throw errors; } } - %> From 32599bb03bfb116294e00aac46a2ebf35f299fdd Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Sun, 26 Oct 2025 12:35:27 -0700 Subject: [PATCH 06/10] Test updates --- .../src/org/labkey/experiment/api/ExpRunItemTableImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/experiment/src/org/labkey/experiment/api/ExpRunItemTableImpl.java b/experiment/src/org/labkey/experiment/api/ExpRunItemTableImpl.java index b3587370485..381f9c15355 100644 --- a/experiment/src/org/labkey/experiment/api/ExpRunItemTableImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpRunItemTableImpl.java @@ -22,6 +22,7 @@ import org.labkey.api.data.ColumnInfo; import org.labkey.api.data.ContainerFilter; import org.labkey.api.data.ForeignKey; +import org.labkey.api.data.JdbcType; import org.labkey.api.data.MultiValuedRenderContext; import org.labkey.api.data.MutableColumnInfo; import org.labkey.api.data.ParameterMapStatement; @@ -116,6 +117,7 @@ public boolean isMultiValued() } }; + aliasCol.setSqlTypeName(getSqlDialect().getSqlTypeName(JdbcType.VARCHAR)); aliasCol.setDescription("Contains the list of aliases for this data object"); aliasCol.setCalculated(false); aliasCol.setNullable(true); From b4d84f49929fa957017ed345cfc4818c84deacbd Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Mon, 27 Oct 2025 10:35:17 -0700 Subject: [PATCH 07/10] Issue 43241: reintroduce test coverage --- .../experiment/api/ExpSampleTypeTestCase.jsp | 177 ++++++++---------- .../experiment/api/ExperimentServiceImpl.java | 10 +- 2 files changed, 84 insertions(+), 103 deletions(-) diff --git a/experiment/src/org/labkey/experiment/api/ExpSampleTypeTestCase.jsp b/experiment/src/org/labkey/experiment/api/ExpSampleTypeTestCase.jsp index c3fc1591d95..f8ce588465b 100644 --- a/experiment/src/org/labkey/experiment/api/ExpSampleTypeTestCase.jsp +++ b/experiment/src/org/labkey/experiment/api/ExpSampleTypeTestCase.jsp @@ -25,7 +25,6 @@ <%@ page import="org.labkey.api.data.CompareType" %> <%@ page import="org.labkey.api.data.Container" %> <%@ page import="org.labkey.api.data.ContainerManager" %> -<%@ page import="org.labkey.api.data.DbScope" %> <%@ page import="org.labkey.api.data.JdbcType" %> <%@ page import="org.labkey.api.data.SQLFragment" %> <%@ page import="org.labkey.api.data.SimpleFilter" %> @@ -70,9 +69,7 @@ <%@ page import="org.labkey.api.util.PageFlowUtil" %> <%@ page import="org.labkey.api.util.TestContext" %> <%@ page import="org.labkey.experiment.api.ExpProvisionedTableTestHelper" %> -<%@ page import="org.labkey.experiment.api.ExpSampleTypeImpl" %> <%@ page import="org.labkey.experiment.api.ExperimentServiceImpl" %> -<%@ page import="org.labkey.experiment.api.SampleTypeServiceImpl" %> <%@ page import="java.io.StringBufferInputStream" %> <%@ page import="java.util.ArrayList" %> <%@ page import="java.util.Arrays" %> @@ -91,6 +88,13 @@ <%@ page import="org.labkey.api.dataiterator.MapDataIterator" %> <%@ page import="static org.labkey.api.exp.api.ExperimentService.asInteger" %> <%@ page import="static org.labkey.api.exp.api.ExperimentService.asLong" %> +<%@ page import="static java.util.Collections.emptyList" %> +<%@ page import="org.jetbrains.annotations.Nullable" %> +<%@ page import="org.labkey.api.view.ActionURL" %> +<%@ page import="org.labkey.api.query.QueryParam" %> +<%@ page import="org.labkey.api.view.ViewServlet" %> +<%@ page import="org.labkey.api.util.JsonUtil" %> +<%@ page import="org.labkey.api.settings.LookAndFeelProperties" %> <%@ page extends="org.labkey.api.jsp.JspTest.BVT" %> <%! @@ -128,16 +132,12 @@ private void assertExpectedName(ExpSampleType st, String expectedName) @Test public void nameNotNull() throws Exception { - final User user = TestContext.get().getUser(); - try { List props = new ArrayList<>(); props.add(new GWTPropertyDescriptor("name", "string")); - final ExpSampleType st = SampleTypeService.get().createSampleType(c, user, - null, null, props, Collections.emptyList(), - -1, -1, -1, -1, null, null); + createSampleType(null, props, null); } catch (ApiUsageException ee) { @@ -148,16 +148,12 @@ public void nameNotNull() throws Exception @Test // Issue 51321 public void reservedNameFirst() throws Exception { - final User user = TestContext.get().getUser(); - try { List props = new ArrayList<>(); props.add(new GWTPropertyDescriptor("name", "string")); - final ExpSampleType st = SampleTypeService.get().createSampleType(c, user, - "First", null, props, Collections.emptyList(), - -1, -1, -1, -1, null, null); + createSampleType("First", props, null); } catch (ApiUsageException ee) { @@ -168,16 +164,12 @@ public void reservedNameFirst() throws Exception @Test // Issue 51321 public void reservedNameAll() throws Exception { - final User user = TestContext.get().getUser(); - try { List props = new ArrayList<>(); props.add(new GWTPropertyDescriptor("name", "string")); - final ExpSampleType st = SampleTypeService.get().createSampleType(c, user, - "All", null, props, Collections.emptyList(), - -1, -1, -1, -1, null, null); + createSampleType("All", props, null); } catch (ApiUsageException ee) { @@ -189,18 +181,12 @@ public void reservedNameAll() throws Exception @Test public void nameScale() throws Exception { - final User user = TestContext.get().getUser(); - try { List props = new ArrayList<>(); props.add(new GWTPropertyDescriptor("name", "string")); - String name = StringUtils.repeat("a", 1000); - - final ExpSampleType st = SampleTypeService.get().createSampleType(c, user, - name, null, props, Collections.emptyList(), - -1, -1, -1, -1, null, null); + createSampleType(StringUtils.repeat("a", 1000), props, null); } catch (ApiUsageException ee) { @@ -212,8 +198,6 @@ public void nameScale() throws Exception @Test public void nameExpressionScale() throws Exception { - final User user = TestContext.get().getUser(); - try { List props = new ArrayList<>(); @@ -221,11 +205,7 @@ public void nameExpressionScale() throws Exception props.add(new GWTPropertyDescriptor("prop", "string")); props.add(new GWTPropertyDescriptor("age", "int")); - String nameExpression = StringUtils.repeat("a", 1000); - - final ExpSampleType st = SampleTypeService.get().createSampleType(c, user, - "Samples", null, props, Collections.emptyList(), - -1, -1, -1, -1, nameExpression, null); + createSampleType("Samples", props, StringUtils.repeat("a", 1000)); } catch (ApiUsageException ee) { @@ -237,17 +217,13 @@ public void nameExpressionScale() throws Exception @Test public void idColsUnset_nameExpressionNull_noNameProperty() throws Exception { - final User user = TestContext.get().getUser(); - try { List props = new ArrayList<>(); props.add(new GWTPropertyDescriptor("notName", "string")); props.add(new GWTPropertyDescriptor("age", "int")); - final ExpSampleType st = SampleTypeService.get().createSampleType(c, user, - "Samples", null, props, Collections.emptyList(), - -1, -1, -1, -1, null, null); + createSampleType("Samples", props, null); fail("Expected exception"); } catch (ApiUsageException ee) @@ -260,15 +236,11 @@ public void idColsUnset_nameExpressionNull_noNameProperty() throws Exception @Test public void idColsUnset_nameExpressionNull_hasNameProperty() throws Exception { - final User user = TestContext.get().getUser(); - List props = new ArrayList<>(); props.add(new GWTPropertyDescriptor("name", "string")); props.add(new GWTPropertyDescriptor("age", "int")); - final ExpSampleType st = SampleTypeService.get().createSampleType(c, user, - "Samples", null, props, Collections.emptyList(), - -1, -1, -1, -1, null, null); + final ExpSampleType st = createSampleType("Samples", props, null); ExpMaterial sample = st.getSample(c, "bob"); assertNull(sample); @@ -285,18 +257,12 @@ public void idColsUnset_nameExpressionNull_hasNameProperty() throws Exception @Test public void idColsUnset_nameExpression_hasNameProperty() throws Exception { - final User user = TestContext.get().getUser(); - List props = new ArrayList<>(); props.add(new GWTPropertyDescriptor("name", "string")); props.add(new GWTPropertyDescriptor("prop", "string")); props.add(new GWTPropertyDescriptor("age", "int")); - final String nameExpression = "S-${prop}.${age}"; - - final ExpSampleType st = SampleTypeService.get().createSampleType(c, user, - "Samples", null, props, Collections.emptyList(), - -1, -1, -1, -1, nameExpression, null); + createSampleType("Samples", props, "S-${prop}.${age}"); } // idCols not null, nameExpression null, no 'name' property -- ok @@ -364,9 +330,7 @@ public void idColsSet_nameExpressionNull_hasNameProperty() throws Exception props.add(new GWTPropertyDescriptor("prop", "string")); props.add(new GWTPropertyDescriptor("age", "int")); - final ExpSampleType st = SampleTypeService.get().createSampleType(c, user, - "Samples", null, props, Collections.emptyList(), - 0, -1, -1, -1, null, null); + final ExpSampleType st = createSampleType("Samples", props, null); final String expectedName1 = "bob"; ExpMaterial sample1 = st.getSample(c, expectedName1); @@ -428,11 +392,7 @@ public void testNameExpression() throws Exception props.add(new GWTPropertyDescriptor("age", "int")); final String sampleTypeName = "Samples"; - final String nameExpression = "S-${prop}.${age}.${genId:number('000')}"; - - final ExpSampleType st = SampleTypeService.get().createSampleType(c, user, - sampleTypeName, null, props, Collections.emptyList(), - -1, -1, -1, -1, nameExpression, null); + final ExpSampleType st = createSampleType(sampleTypeName, props, "S-${prop}.${age}.${genId:number('000')}"); final String expectedName1 = "bob"; final String expectedName2 = "S-red.11.002"; @@ -547,16 +507,12 @@ public void testNameExpression() throws Exception @Test public void testAliases() throws Exception { - final User user = TestContext.get().getUser(); - // setup List props = new ArrayList<>(); props.add(new GWTPropertyDescriptor("name", "string")); props.add(new GWTPropertyDescriptor("age", "int")); - final ExpSampleType st = SampleTypeService.get().createSampleType(c, user, - "Samples", null, props, Collections.emptyList(), - -1, -1, -1, -1, null, null); + final ExpSampleType st = createSampleType("Samples", props, null); List> rows = new ArrayList<>(); Map row = new CaseInsensitiveHashMap<>(); @@ -584,11 +540,7 @@ public void testBlankRows() throws Exception props.add(new GWTPropertyDescriptor("name", "string")); props.add(new GWTPropertyDescriptor("age", "int")); - final String nameExpression = "S-${now:date}-${dailySampleCount}"; - - final ExpSampleTypeImpl st = SampleTypeServiceImpl.get().createSampleType(c, user, - "Samples", null, props, Collections.emptyList(), - -1, -1, -1, -1, nameExpression, null); + final ExpSampleType st = createSampleType("Samples", props, "S-${now:date}-${dailySampleCount}"); List allSamples = st.getSamples(c); assertTrue("Expected no samples", allSamples.isEmpty()); @@ -685,18 +637,9 @@ public void testUpdateSomeParents() throws Exception List props = new ArrayList<>(); props.add(new GWTPropertyDescriptor("name", "string")); props.add(new GWTPropertyDescriptor("age", "int")); - final ExpSampleTypeImpl childType = SampleTypeServiceImpl.get().createSampleType(c, user, - "ChildSamples", null, props, Collections.emptyList(), - -1, -1, -1, -1, null, null); - - final ExpSampleTypeImpl parent1Type = SampleTypeServiceImpl.get().createSampleType(c, user, - "Parent1Samples", null, props, Collections.emptyList(), - -1, -1, -1, -1, null, null); - - final ExpSampleTypeImpl parent2Type = SampleTypeServiceImpl.get().createSampleType(c, user, - "Parent2Samples", null, props, Collections.emptyList(), - -1, -1, -1, -1, null, null); - + final ExpSampleType childType = createSampleType("ChildSamples", props, null); + final ExpSampleType parent1Type = createSampleType("Parent1Samples", props, null); + final ExpSampleType parent2Type = createSampleType("Parent2Samples", props, null); UserSchema schema = QueryService.get().getUserSchema(user, c, SchemaKey.fromParts("Samples")); List> rows = new ArrayList<>(); @@ -827,13 +770,12 @@ public void testParentColAndDataInputDerivation() throws Exception props.add(new GWTPropertyDescriptor("data", "int")); props.add(new GWTPropertyDescriptor("parent", "string")); - final ExpSampleTypeImpl st = SampleTypeServiceImpl.get().createSampleType(c, user, - "Samples", null, props, Collections.emptyList(), - 0, -1, -1, 2, null, null); + String sampleTypeName = "Samples"; + final ExpSampleType st = createSampleType(sampleTypeName, props, null); // insert and derive with both 'parent' column and 'DataInputs/Samples' - UserSchema schema = QueryService.get().getUserSchema(user, c, SchemaKey.fromParts("Samples")); - TableInfo table = schema.getTable("Samples"); + UserSchema schema = QueryService.get().getUserSchema(user, c, SamplesSchema.SCHEMA_SAMPLES); + TableInfo table = schema.getTable(sampleTypeName); QueryUpdateService svc = table.getUpdateService(); List> rows = new ArrayList<>(); @@ -955,6 +897,48 @@ public void testParentColAndDataInputDerivation() throws Exception assertFalse(oldDerivationRun.getMaterialInputs().containsKey(E)); assertFalse(oldDerivationRun.getMaterialOutputs().contains(D)); assertTrue(oldDerivationRun.getMaterialOutputs().contains(E)); + + // Issue 43241: Display of dates from Input/Output columns for sample types does not use the project date format + { + var folderProps = LookAndFeelProperties.getWriteableInstance(c); + folderProps.setDefaultDateTimeFormat("'kevink' dd-MM-yyyy"); + folderProps.save(); + + var multiValueColumn = "Outputs/Materials/" + sampleTypeName + "/Created"; + var url = new ActionURL("query", "selectRows", c); + url.addParameter(QueryParam.schemaName, schema.getName()); + url.addParameter("query." + QueryParam.queryName, sampleTypeName); + url.addParameter("query." + QueryParam.columns, "Name, " + multiValueColumn); + url.addFilter("query", FieldKey.fromParts("Name"), CompareType.EQUAL, "A"); + url.addParameter("includeMetadata", false); + url.addParameter("apiVersion", "17.1"); + + var response = ViewServlet.GET(url, user, null); + assertEquals(200, response.getStatus()); + + var json = JsonUtil.DEFAULT_MAPPER.readTree(response.getContentAsString()); + var resultRows = json.get("rows"); + assertEquals(1, resultRows.size()); + + var createdValues = resultRows.get(0).get("data").get(multiValueColumn); + assertEquals(4, createdValues.size()); + + for (var data : createdValues) + { + var value = data.get("value").asText(); + assertNotNull(value); + + // Formatted with container date format + var formattedValue = data.get("formattedValue").asText(); + assertTrue("Expected date format not applied", formattedValue.startsWith("kevink ")); + + // Do not care what the JSON format looks like, as long as it is different + assertNotEquals(value, formattedValue); + } + + folderProps.clearDefaultDateTimeFormat(); + folderProps.save(); + } } @Test @@ -971,12 +955,8 @@ public void testSampleTypeWithVocabularyProperties() throws Exception Domain mockDomain = helper.createVocabularyTestDomain(user, c); Map vocabularyPropertyURIs = helper.getVocabularyPropertyURIS(mockDomain); - //create sample type - ExpSampleTypeImpl st = SampleTypeServiceImpl.get().createSampleType(c, user, - sampleName, null, List.of(new GWTPropertyDescriptor("name", "string")), Collections.emptyList(), - -1, -1, -1, -1, null, null); - - assertNotNull(st); + // create a sample type + createSampleType(sampleName, List.of(new GWTPropertyDescriptor("name", "string")), null); UserSchema schema = QueryService.get().getUserSchema(user, c, SchemaKey.fromParts("Samples")); @@ -1038,9 +1018,7 @@ public void testDetailedAuditLog() throws Exception props.add(new GWTPropertyDescriptor("Name", "string")); props.add(new GWTPropertyDescriptor("Measure", "string")); props.add(new GWTPropertyDescriptor("Value", "float")); - final ExpSampleTypeImpl st = SampleTypeServiceImpl.get().createSampleType(c, user, - "SamplesDAL", null, props, Collections.emptyList(), - -1, -1, -1, -1, null, null); + final ExpSampleType st = createSampleType("SamplesDAL", props, null); QuerySchema samplesSchema = DefaultSchema.get(user, c, "samples"); assertNotNull(samplesSchema); @@ -1130,10 +1108,8 @@ public void testExpMaterialPermissions() throws Exception User user = TestContext.get().getUser(); var schema = QueryService.get().getUserSchema(user, c, ExpSchema.SCHEMA_EXP); - // create sample type - ExpSampleTypeImpl st = SampleTypeServiceImpl.get().createSampleType(c, user, - "MySamples", null, List.of(new GWTPropertyDescriptor("name", "string")), Collections.emptyList(), - -1, -1, -1, -1, null, null); + // create a sample type + ExpSampleType st = createSampleType("MySamples", List.of(new GWTPropertyDescriptor("name", "string")), null); // insert a sample var errors = new BatchValidationException(); @@ -1214,7 +1190,7 @@ public void testInsertOptionUpdate() throws Exception props.add(new GWTPropertyDescriptor(longFieldName, "string")); final String sampleTypeName = "TestSamplesWithRequired"; - ExpSampleType sampleType = SampleTypeService.get().createSampleType(c, user, sampleTypeName, null, props, Collections.emptyList(), -1, -1, -1, -1, null); + ExpSampleType sampleType = createSampleType(sampleTypeName, props, null); TableInfo table = getSampleTypeTable(sampleTypeName); QueryUpdateService qus = table.getUpdateService(); @@ -1353,6 +1329,11 @@ public void testInsertOptionUpdate() throws Exception assertNull(rows.get(2).get("AliquotedFromLSID")); } +private ExpSampleType createSampleType(String sampleTypeName, List props, @Nullable String nameExpression) throws Exception +{ + return SampleTypeService.get().createSampleType(c, TestContext.get().getUser(), sampleTypeName, null, props, emptyList(), -1, -1, -1, -1, nameExpression, null); +} + private @NotNull TableInfo getSampleTypeTable(String sampleType) { UserSchema schema = QueryService.get().getUserSchema(TestContext.get().getUser(), c, SamplesSchema.SCHEMA_SAMPLES); diff --git a/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java b/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java index f3d51bf42b7..08b1b38f81d 100644 --- a/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java @@ -4739,7 +4739,7 @@ public int deleteMaterialByRowIds( Container container, Collection selectedMaterialIds, boolean deleteRunsUsingMaterials, - @Nullable ExpSampleTypeImpl stDeleteFrom, + @Nullable ExpSampleType stDeleteFrom, boolean ignoreStatus, boolean truncateContainer ) @@ -4763,7 +4763,7 @@ public int deleteMaterialBySqlFilter( SQLFragment materialFilterSQL, boolean deleteRunsUsingMaterials, boolean deleteFromAllSampleTypes, - @Nullable ExpSampleTypeImpl stDeleteFrom, + @Nullable ExpSampleType stDeleteFrom, boolean ignoreStatus, boolean truncateContainer ) @@ -4777,7 +4777,7 @@ public int deleteMaterialBySqlFilter( { Map> sampleTypeAliquotRoots = new HashMap<>(); - Map sampleTypes = new HashMap<>(); + Map sampleTypes = new HashMap<>(); if (null != stDeleteFrom) sampleTypes.put(stDeleteFrom.getLSID(), stDeleteFrom); @@ -4934,13 +4934,13 @@ public int deleteMaterialBySqlFilter( try (Timing ignored = MiniProfiler.step("expsampletype materialized tables")) { - for (ExpSampleTypeImpl st : sampleTypes.values()) + for (ExpSampleType st : sampleTypes.values()) { // Material may have been orphaned from its SampleType if (st == null) continue; - TableInfo dbTinfo = st.getTinfo(); + TableInfo dbTinfo = ((ExpSampleTypeImpl) st).getTinfo(); // NOTE: study specimens don't have a domain for their samples, so no table if (null != dbTinfo) { From 22eb3e7aa6508d3cfa2bdcb280dcae9796ca3d1b Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Mon, 3 Nov 2025 11:53:07 -0800 Subject: [PATCH 08/10] Bump @labkey/components --- assay/package-lock.json | 8 ++++---- assay/package.json | 2 +- core/package-lock.json | 8 ++++---- core/package.json | 2 +- experiment/package-lock.json | 8 ++++---- experiment/package.json | 2 +- pipeline/package-lock.json | 8 ++++---- pipeline/package.json | 2 +- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/assay/package-lock.json b/assay/package-lock.json index 05353f7b726..02ea67ff10a 100644 --- a/assay/package-lock.json +++ b/assay/package-lock.json @@ -8,7 +8,7 @@ "name": "assay", "version": "0.0.0", "dependencies": { - "@labkey/components": "6.68.1" + "@labkey/components": "6.68.2-fb-alias-perf-53567.0" }, "devDependencies": { "@labkey/build": "8.6.0", @@ -2458,9 +2458,9 @@ } }, "node_modules/@labkey/components": { - "version": "6.68.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-6.68.1.tgz", - "integrity": "sha512-8YphakuF2oRaunHegtGkGhyu7duXSWTy7CbA4UI5phzQrWwZIDrKcjE3WUuCpiaMiahuvADG2v8RgzbwsWOEbA==", + "version": "6.68.2-fb-alias-perf-53567.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-6.68.2-fb-alias-perf-53567.0.tgz", + "integrity": "sha512-9mbvdWY3Z4z3h3IHRpYjV9XXrUMRbfiq8rYt2zwrssNm2vVd4XV2qlh1mBla/C/ehq9NfsLubsLv5kKEEebZFA==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/assay/package.json b/assay/package.json index 42fe53474fb..d0cc6b723da 100644 --- a/assay/package.json +++ b/assay/package.json @@ -12,7 +12,7 @@ "clean": "rimraf resources/web/assay/gen && rimraf resources/views/gen && rimraf resources/web/gen" }, "dependencies": { - "@labkey/components": "6.68.1" + "@labkey/components": "6.68.2-fb-alias-perf-53567.0" }, "devDependencies": { "@labkey/build": "8.6.0", diff --git a/core/package-lock.json b/core/package-lock.json index ddd43508597..daf5577446d 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -8,7 +8,7 @@ "name": "labkey-core", "version": "0.0.0", "dependencies": { - "@labkey/components": "6.68.1", + "@labkey/components": "6.68.2-fb-alias-perf-53567.0", "@labkey/themes": "1.4.2" }, "devDependencies": { @@ -3504,9 +3504,9 @@ } }, "node_modules/@labkey/components": { - "version": "6.68.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-6.68.1.tgz", - "integrity": "sha512-8YphakuF2oRaunHegtGkGhyu7duXSWTy7CbA4UI5phzQrWwZIDrKcjE3WUuCpiaMiahuvADG2v8RgzbwsWOEbA==", + "version": "6.68.2-fb-alias-perf-53567.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-6.68.2-fb-alias-perf-53567.0.tgz", + "integrity": "sha512-9mbvdWY3Z4z3h3IHRpYjV9XXrUMRbfiq8rYt2zwrssNm2vVd4XV2qlh1mBla/C/ehq9NfsLubsLv5kKEEebZFA==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/core/package.json b/core/package.json index f23594ab7fc..e6c27f9355e 100644 --- a/core/package.json +++ b/core/package.json @@ -53,7 +53,7 @@ } }, "dependencies": { - "@labkey/components": "6.68.1", + "@labkey/components": "6.68.2-fb-alias-perf-53567.0", "@labkey/themes": "1.4.2" }, "devDependencies": { diff --git a/experiment/package-lock.json b/experiment/package-lock.json index c18f2c8f767..148ce0d658f 100644 --- a/experiment/package-lock.json +++ b/experiment/package-lock.json @@ -8,7 +8,7 @@ "name": "experiment", "version": "0.0.0", "dependencies": { - "@labkey/components": "6.68.1" + "@labkey/components": "6.68.2-fb-alias-perf-53567.0" }, "devDependencies": { "@labkey/build": "8.6.0", @@ -3247,9 +3247,9 @@ } }, "node_modules/@labkey/components": { - "version": "6.68.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-6.68.1.tgz", - "integrity": "sha512-8YphakuF2oRaunHegtGkGhyu7duXSWTy7CbA4UI5phzQrWwZIDrKcjE3WUuCpiaMiahuvADG2v8RgzbwsWOEbA==", + "version": "6.68.2-fb-alias-perf-53567.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-6.68.2-fb-alias-perf-53567.0.tgz", + "integrity": "sha512-9mbvdWY3Z4z3h3IHRpYjV9XXrUMRbfiq8rYt2zwrssNm2vVd4XV2qlh1mBla/C/ehq9NfsLubsLv5kKEEebZFA==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/experiment/package.json b/experiment/package.json index ef74e50dfa2..5a4237cad98 100644 --- a/experiment/package.json +++ b/experiment/package.json @@ -13,7 +13,7 @@ "test-integration": "cross-env NODE_ENV=test jest --ci --runInBand -c test/js/jest.config.integration.js" }, "dependencies": { - "@labkey/components": "6.68.1" + "@labkey/components": "6.68.2-fb-alias-perf-53567.0" }, "devDependencies": { "@labkey/build": "8.6.0", diff --git a/pipeline/package-lock.json b/pipeline/package-lock.json index f34fdb9772b..dc7f9c881c6 100644 --- a/pipeline/package-lock.json +++ b/pipeline/package-lock.json @@ -8,7 +8,7 @@ "name": "pipeline", "version": "0.0.0", "dependencies": { - "@labkey/components": "6.68.1" + "@labkey/components": "6.68.2-fb-alias-perf-53567.0" }, "devDependencies": { "@labkey/build": "8.6.0", @@ -2716,9 +2716,9 @@ } }, "node_modules/@labkey/components": { - "version": "6.68.1", - "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-6.68.1.tgz", - "integrity": "sha512-8YphakuF2oRaunHegtGkGhyu7duXSWTy7CbA4UI5phzQrWwZIDrKcjE3WUuCpiaMiahuvADG2v8RgzbwsWOEbA==", + "version": "6.68.2-fb-alias-perf-53567.0", + "resolved": "https://labkey.jfrog.io/artifactory/api/npm/libs-client/@labkey/components/-/@labkey/components-6.68.2-fb-alias-perf-53567.0.tgz", + "integrity": "sha512-9mbvdWY3Z4z3h3IHRpYjV9XXrUMRbfiq8rYt2zwrssNm2vVd4XV2qlh1mBla/C/ehq9NfsLubsLv5kKEEebZFA==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/pipeline/package.json b/pipeline/package.json index dfbfff62758..c460dae74df 100644 --- a/pipeline/package.json +++ b/pipeline/package.json @@ -14,7 +14,7 @@ "build-prod": "npm run clean && cross-env NODE_ENV=production PROD_SOURCE_MAP=source-map webpack --config node_modules/@labkey/build/webpack/prod.config.js --color --progress --profile" }, "dependencies": { - "@labkey/components": "6.68.1" + "@labkey/components": "6.68.2-fb-alias-perf-53567.0" }, "devDependencies": { "@labkey/build": "8.6.0", From f62322a8f02d714bb9f38fdbb8032b44a885a56b Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Mon, 3 Nov 2025 12:08:26 -0800 Subject: [PATCH 09/10] Use PageFlowUtil.joinValuesToStringForExport --- .../org/labkey/experiment/api/AliasDisplayColumnFactory.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/experiment/src/org/labkey/experiment/api/AliasDisplayColumnFactory.java b/experiment/src/org/labkey/experiment/api/AliasDisplayColumnFactory.java index 050f4f07e36..fb490e576aa 100644 --- a/experiment/src/org/labkey/experiment/api/AliasDisplayColumnFactory.java +++ b/experiment/src/org/labkey/experiment/api/AliasDisplayColumnFactory.java @@ -6,6 +6,7 @@ import org.labkey.api.data.DisplayColumnFactory; import org.labkey.api.data.MultiValuedDisplayColumn; import org.labkey.api.data.RenderContext; +import org.labkey.api.util.PageFlowUtil; import java.util.List; @@ -24,7 +25,7 @@ public Object getInputValue(RenderContext ctx) { Object value = super.getInputValue(ctx); if (value instanceof List) - return String.join(", ", (List) value); + return PageFlowUtil.joinValuesToStringForExport((List) value); return ""; } }; From 1caa7e7c5389cc360ffbc5199f1d35d22a8b63a9 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 5 Nov 2025 16:39:22 -0800 Subject: [PATCH 10/10] Add alias column support to DataGenerator --- .../api/data/generator/DataGenerator.java | 67 +++++++++++++++---- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/api/src/org/labkey/api/data/generator/DataGenerator.java b/api/src/org/labkey/api/data/generator/DataGenerator.java index 4e02ba7b802..c36e53b6460 100644 --- a/api/src/org/labkey/api/data/generator/DataGenerator.java +++ b/api/src/org/labkey/api/data/generator/DataGenerator.java @@ -450,7 +450,7 @@ public int generateAliquots(ExpSampleType sampleType, QueryUpdateService svc, in { checkAlive(_job); List> parents = getRandomSamples(sampleType, Math.min(10, Math.max(quantity, quantity / 100))); - numGenerated = generateAliquotsForParents(parents, svc, quantity - totalAliquots, 0, 1, randomInt(1, _config.getMaxGenerations()), sampleStatuses); + numGenerated = generateAliquotsForParents(sampleType, parents, svc, quantity - totalAliquots, 0, 1, randomInt(1, _config.getMaxGenerations()), sampleStatuses); totalAliquots += numGenerated; _log.info("... " + totalAliquots); iterations++; @@ -463,7 +463,7 @@ public int generateAliquots(ExpSampleType sampleType, QueryUpdateService svc, in return totalAliquots; } - private int generateAliquotsForParents(List> parents, QueryUpdateService svc, int quantity, int numGenerated, int generation, int maxGenerations, List sampleStatuses) throws SQLException, BatchValidationException, QueryUpdateServiceException, DuplicateKeyException + private int generateAliquotsForParents(ExpSampleType sampleType, List> parents, QueryUpdateService svc, int quantity, int numGenerated, int generation, int maxGenerations, List sampleStatuses) throws SQLException, BatchValidationException, QueryUpdateServiceException, DuplicateKeyException { int generatedCount = 0; List> aliquots = new ArrayList<>(); @@ -492,6 +492,7 @@ private int generateAliquotsForParents(List> parents, QueryU if (randomInt(0, 10) == 5) // set amount for 10% { row.put("StoredAmount", p + (i % 15) * (1.2)); + row.put("Units", getUnitsValue(sampleType)); } rows.add(row); } @@ -511,7 +512,7 @@ private int generateAliquotsForParents(List> parents, QueryU { List> nextParents = aliquots.isEmpty() ? parents : aliquots; if (!nextParents.isEmpty()) - generatedCount += generateAliquotsForParents(nextParents.subList(randomInt(0, nextParents.size() / 2), randomInt(nextParents.size() / 2, nextParents.size())), svc, quantity - generatedCount, numGenerated + generatedCount, generation + 1, maxGenerations, sampleStatuses); + generatedCount += generateAliquotsForParents(sampleType, nextParents.subList(randomInt(0, nextParents.size() / 2), randomInt(nextParents.size() / 2, nextParents.size())), svc, quantity - generatedCount, numGenerated + generatedCount, generation + 1, maxGenerations, sampleStatuses); } return generatedCount; } @@ -836,12 +837,10 @@ protected void addDomainProperties(List props, int numFie private static String getUnitsValue(ExpSampleType sampleType) { + Unit sampleTypeUnit = Unit.fromName(sampleType.getMetricUnit()); String units; - if (!StringUtils.isEmpty(sampleType.getMetricUnit())) - { - Unit unit = Unit.fromName(sampleType.getMetricUnit()); - units = randomIndex(unit.getKindOfQuantity().getCommonUnits()).name(); - } + if (sampleTypeUnit != null) + units = randomIndex(sampleTypeUnit.getKindOfQuantity().getCommonUnits()).name(); else units = randomIndex(UNITS); @@ -854,10 +853,7 @@ public static Map createSampleRow(ExpSampleType sampleType, int if (randomInt(0, 4) < 3) // set amount for 75% - TODO parameterize { row.put("StoredAmount", randomDouble(0, 100)); - // add a unit for some percentage - String units = getUnitsValue(sampleType); - if (units != null) - row.put("Units", units); + row.put("Units", getUnitsValue(sampleType)); } return row; } @@ -897,13 +893,31 @@ protected List> createRows(int numRows, Domain domain) protected List> createSampleRows(int numRows, ExpSampleType sampleType) { List> rows = new ArrayList<>(); + int samplesWithAliases = Math.round(numRows * _config.getPctAlias()); for (int i = 1; i <= numRows; i++) { - rows.add(createSampleRow(sampleType, i)); + Map row = createSampleRow(sampleType, i); + if (i < samplesWithAliases) + { + row.put("Alias", randomAlias("alias_")); + } + rows.add(row); } return rows; } + private String randomAlias(String prefix) + { + StringBuilder sb = new StringBuilder(); + for (int i = 1; i <= randomInt(1, _config._maxAliases); i++) + { + if (!sb.isEmpty()) + sb.append(", "); + sb.append(prefix).append(i); + } + return sb.toString(); + } + public static String randomDate() { var startDate = new Date(112 /* 2012 */, Calendar.JANUARY, 1); @@ -962,6 +976,8 @@ public static class Config public static final String MIN_DATA_CLASS_PARENT_TYPES_PER_SAMPLE = "minDataClassParentTypesPerSample"; public static final String MAX_DATA_CLASS_PARENT_TYPES_PER_SAMPLE = "maxDataClassParentTypesPerSample"; public static final String AUDIT_BEHAVIOR_TYPE = "auditBehaviorType"; + public static final String PCT_ALIAS = "percentWithAliases"; + public static final String MAX_ALIAS = "maxAliases"; int _numFolders; int _numSampleTypes; @@ -977,6 +993,8 @@ public static class Config int _maxChildrenPerParent; int _minFields; int _maxFields; + float _pctAlias; + int _maxAliases; List _sampleTypeNames; List _dataClassParents; List _sampleTypeParents; @@ -1010,6 +1028,9 @@ public Config(Properties properties) _maxAliquotsPerParent = Integer.parseInt(properties.getProperty(MAX_ALIQUOTS_PER_SAMPLE, "0")); _maxChildrenPerParent = Integer.parseInt(properties.getProperty(MAX_CHILDREN_PER_PARENT, "1")); + _pctAlias = Float.parseFloat(properties.getProperty(PCT_ALIAS, "0.0")); + _maxAliases = Integer.parseInt(properties.getProperty(MAX_ALIAS, "1")); + _minFields = Integer.parseInt(properties.getProperty(MIN_NUM_FIELDS, "1")); _maxFields = Math.max(Integer.parseInt(properties.getProperty(MAX_NUM_FIELDS, "1")), _minFields); @@ -1235,6 +1256,26 @@ public void setAuditBehaviorType(AuditBehaviorType auditBehaviorType) { _auditBehaviorType = auditBehaviorType; } + + public float getPctAlias() + { + return _pctAlias; + } + + public void setPctAlias(float pctAlias) + { + _pctAlias = pctAlias; + } + + public int getMaxAliases() + { + return _maxAliases; + } + + public void setMaxAliases(int maxAliases) + { + _maxAliases = maxAliases; + } } public interface DataGenerationDriver