diff --git a/api/src/org/labkey/api/exp/api/ExperimentService.java b/api/src/org/labkey/api/exp/api/ExperimentService.java index 5e369154827..83e01403208 100644 --- a/api/src/org/labkey/api/exp/api/ExperimentService.java +++ b/api/src/org/labkey/api/exp/api/ExperimentService.java @@ -1155,6 +1155,8 @@ List getExpProtocolsWithParameterValue( Map> getDomainMetrics(); + boolean checkDuplicateName(@NotNull String newName, @NotNull TableInfo tableInfo); + class XarExportOptions { String _lsidRelativizer = LSID_OPTION_FOLDER_RELATIVE; diff --git a/experiment/src/org/labkey/experiment/ExpDataIterators.java b/experiment/src/org/labkey/experiment/ExpDataIterators.java index 1bdb4385312..582ab33c544 100644 --- a/experiment/src/org/labkey/experiment/ExpDataIterators.java +++ b/experiment/src/org/labkey/experiment/ExpDataIterators.java @@ -2370,9 +2370,15 @@ else if (isMergeOrUpdate) step2c = LoggingDataIterator.wrap(new ExpDataIterators.SampleStatusCheckIteratorBuilder(step2b, _container)); } + DataIteratorBuilder step2d = step2c; + if (canUpdateNames && !dontUpdate.contains("name")) + { + step2d = LoggingDataIterator.wrap(new ExpDataIterators.DuplicateNameCheckIteratorBuilder(step2c, _propertiesTable)); + } + // Insert into exp.data then the provisioned table // Use embargo data iterator to ensure rows are committed before being sent along Issue 26082 (row at a time, reselect rowid) - DataIteratorBuilder step3 = LoggingDataIterator.wrap(new TableInsertDataIteratorBuilder(step2c, _expTable, _container) + DataIteratorBuilder step3 = LoggingDataIterator.wrap(new TableInsertDataIteratorBuilder(step2d, _expTable, _container) .setKeyColumns(keyColumns) .setDontUpdate(dontUpdate) .setAddlSkipColumns(_excludedColumns) @@ -3155,6 +3161,73 @@ public static void incrementCounts(Map currentCounts, Map map = DataIteratorUtil.createColumnNameMap(di); + _nameCol = map.get(NAME_FIELD); + } + + @Override + public boolean next() throws BatchValidationException + { + boolean hasNext = super.next(); + if (!hasNext) + return false; + + if (_context.getErrors().hasErrors()) + return hasNext; + + if (_nameCol == null) + return hasNext; + + Object nameObj = get(_nameCol); + if (nameObj == null) + return hasNext; + + String newName = String.valueOf(nameObj); + if (StringUtils.isEmpty(newName)) + return hasNext; + + Map existingValues = getExistingRecord(); + if (existingValues != null && !existingValues.isEmpty() && existingValues.get(NAME_FIELD).equals(newName)) + return hasNext; + + if (ExperimentService.get().checkDuplicateName(newName, _tableInfo)) + _context.getErrors().addRowError(new ValidationException(String.format("Name '%s' already exist.", newName))); + + return hasNext; + } + } + public static class SampleStatusCheckIteratorBuilder implements DataIteratorBuilder { private final DataIteratorBuilder _in; diff --git a/experiment/src/org/labkey/experiment/api/ExpDataClassDataTableImpl.java b/experiment/src/org/labkey/experiment/api/ExpDataClassDataTableImpl.java index 90408498d8b..bef3d49adeb 100644 --- a/experiment/src/org/labkey/experiment/api/ExpDataClassDataTableImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpDataClassDataTableImpl.java @@ -1323,6 +1323,11 @@ protected Map _update(User user, Container c, Map rowStripped = new CaseInsensitiveHashMap<>(); diff --git a/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java b/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java index c0e44cad601..317f9c0d8e1 100644 --- a/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExperimentServiceImpl.java @@ -9161,6 +9161,20 @@ public Map> getDomainMetrics() return metrics; } + @Override + public boolean checkDuplicateName(@NotNull String newName, @NotNull TableInfo tableInfo) + { + SQLFragment dataRowSQL = new SQLFragment("SELECT name FROM ") + .append(tableInfo) + .append(" WHERE LOWER(name) = LOWER(?) AND name <> ?") + .add(newName) + .add(newName); + if (tableInfo.getSqlDialect().isSqlServer()) + dataRowSQL.append(" COLLATE Latin1_General_BIN"); // force case sensitive comparison + + return new SqlSelector(ExperimentService.get().getSchema(), dataRowSQL).exists(); + } + private Map getImportTemplatesMetrics() { DbSchema dbSchema = CoreSchema.getInstance().getSchema(); diff --git a/experiment/src/org/labkey/experiment/api/SampleTypeServiceImpl.java b/experiment/src/org/labkey/experiment/api/SampleTypeServiceImpl.java index 1a6f7a2bad8..90c2e1e7aaa 100644 --- a/experiment/src/org/labkey/experiment/api/SampleTypeServiceImpl.java +++ b/experiment/src/org/labkey/experiment/api/SampleTypeServiceImpl.java @@ -118,6 +118,7 @@ import java.util.Optional; import java.util.Set; import java.util.SortedSet; +import java.util.TreeMap; import java.util.TreeSet; import java.util.function.Function; import java.util.function.Predicate; @@ -1699,7 +1700,7 @@ private Map> getSampleAliquotCounts(Collection> sampleAliquotCounts = new HashMap<>(); + Map> sampleAliquotCounts = new TreeMap<>(); // Order sample by rowId to reduce probability of deadlock with search indexer try (ResultSet rs = new SqlSelector(dbSchema, sql).getResultSet()) { while (rs.next()) @@ -1760,7 +1761,7 @@ SELECT RootMaterialRowId as rootRowId, COUNT(*) as aliquotCount } dialect.appendInClauseSql(sql, sampleIds); - Map> sampleAliquotCounts = new HashMap<>(); + Map> sampleAliquotCounts = new TreeMap<>(); // Order by rowId to reduce deadlock with search indexer try (ResultSet rs = new SqlSelector(dbSchema, sql).getResultSet()) { while (rs.next()) diff --git a/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java b/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java index 0e12b71b23b..1b1ee775654 100644 --- a/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java +++ b/experiment/src/org/labkey/experiment/api/SampleTypeUpdateServiceDI.java @@ -712,6 +712,12 @@ protected Map _update(User user, Container c, Map