Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package org.labkey.api.dataiterator;

import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.labkey.api.collections.IntHashMap;
import org.labkey.api.collections.Sets;
import org.labkey.api.data.ColumnInfo;
import org.labkey.api.data.CompareType;
import org.labkey.api.data.Container;
import org.labkey.api.data.JdbcType;
import org.labkey.api.data.SimpleFilter;
import org.labkey.api.data.TableInfo;
import org.labkey.api.data.TableSelector;
import org.labkey.api.exp.api.ExperimentService;
import org.labkey.api.exp.query.ExpDataTable;
import org.labkey.api.query.BatchValidationException;
import org.labkey.api.query.FieldKey;
import org.labkey.api.query.ValidationException;

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;

import static org.labkey.api.exp.query.ExpDataTable.Column.LSID;
import static org.labkey.api.exp.query.ExpDataTable.Column.ClassId;
import static org.labkey.api.util.IntegerUtils.asInteger;

/**
* DataIterator that adds the LSID column for DataClass update operations.
* Queries the LSID from exp.data based on the provided key (rowId or name) and dataClassId.
* The LSID is needed downstream for attachment handling.
*/
public class DataClassUpdateAddColumnsDataIterator extends WrapperDataIterator
{
private final Container _targetContainer;
private final TableInfo _tableInfo;
final CachingDataIterator _unwrapped;

private final long _dataClassId;
final int _lsidColIndex;
final ColumnInfo pkColumn;
final Supplier<Object> pkSupplier;

int lastPrefetchRowNumber = -1;
final IntHashMap<String> lsids = new IntHashMap<>();
final DataIteratorContext _context;

public DataClassUpdateAddColumnsDataIterator(DataIterator in, @NotNull DataIteratorContext context, TableInfo target, Container container, long dataClassId, String keyColumnName)
{
super(in);
this._unwrapped = (CachingDataIterator)in;
_context = context;
_tableInfo = target;
_targetContainer = container;
_dataClassId = dataClassId;

var map = DataIteratorUtil.createColumnNameMap(in);

this._lsidColIndex = map.get(ExpDataTable.Column.LSID.name());

Integer index = map.get(keyColumnName);
ColumnInfo col = target.getColumn(keyColumnName);
if (null == index || null == col)
throw new IllegalArgumentException("Key column not found: " + keyColumnName);
pkSupplier = in.getSupplier(index);
pkColumn = col;
}

@Override
public Supplier<Object> getSupplier(int i)
{
if (i != _lsidColIndex)
return _delegate.getSupplier(i);
return () -> get(i);
}

@Override
public Object get(int i)
{
Integer rowNumber = asInteger(_delegate.get(0));

if (i == _lsidColIndex)
return lsids.get(rowNumber);

return _delegate.get(i);
}

@Override
public boolean isConstant(int i)
{
if (i != _lsidColIndex)
return _delegate.isConstant(i);
return false;
}

@Override
public Object getConstantValue(int i)
{
if (i != _lsidColIndex)
return _delegate.getConstantValue(i);
return null;
}

protected void prefetchExisting() throws BatchValidationException
{
Integer rowNumber = asInteger(_delegate.get(0));
if (rowNumber <= lastPrefetchRowNumber)
return;

lsids.clear();

int rowsToFetch = 50;
String keyFieldName = pkColumn.getName();
boolean numericKey = pkColumn.isNumericType();
JdbcType jdbcType = pkColumn.getJdbcType();
Map<Integer, Object> rowKeyMap = new LinkedHashMap<>();
Map<Object, Set<Integer>> keyRowMap = new LinkedHashMap<>();
Set<Object> notFoundKeys = new HashSet<>();

do
{
lastPrefetchRowNumber = asInteger(_delegate.get(0));
Object keyObj = pkSupplier.get();
Object key = jdbcType.convert(keyObj);

if (numericKey)
{
if (null == key)
throw new IllegalArgumentException(keyFieldName + " value not provided on row " + lastPrefetchRowNumber);
}
else if (StringUtils.isEmpty((String) key))
throw new IllegalArgumentException(keyFieldName + " value not provided on row " + lastPrefetchRowNumber);

rowKeyMap.put(lastPrefetchRowNumber, key);
notFoundKeys.add(key);
// if keyRowMap doesn't contain key, add new set, then add row number to set for this key
if (!keyRowMap.containsKey(key))
keyRowMap.put(key, new HashSet<>());
keyRowMap.get(key).add(lastPrefetchRowNumber);
lsids.put(lastPrefetchRowNumber, null);
}
while (--rowsToFetch > 0 && _delegate.next());

SimpleFilter filter = new SimpleFilter(ClassId.fieldKey(), _dataClassId);
filter.addCondition(pkColumn.getFieldKey(), rowKeyMap.values(), CompareType.IN);
filter.addCondition(FieldKey.fromParts("Container"), _targetContainer);

Set<String> columns = Sets.newCaseInsensitiveHashSet(keyFieldName, LSID.name());
Map<String, Object>[] results = new TableSelector(ExperimentService.get().getTinfoData(), columns, filter, null).getMapArray();

for (Map<String, Object> result : results)
{
Object key = result.get(keyFieldName);
Object lsidObj = result.get(LSID.name());

Set<Integer> rowInds = keyRowMap.get(key);
if (lsidObj != null)
{
for (Integer rowInd : rowInds)
lsids.put(rowInd, (String) lsidObj);
notFoundKeys.remove(key);
}
}

if (!notFoundKeys.isEmpty())
_context.getErrors().addRowError(new ValidationException("Data not found for " + notFoundKeys));

// backup to where we started so caller can iterate through them one at a time
_unwrapped.reset(); // unwrapped _delegate
_delegate.next();
}

@Override
public boolean next() throws BatchValidationException
{
if (_context.getErrors().hasErrors())
return false;

// NOTE: we have to call mark() before we call next() if we want the 'next' row to be cached
_unwrapped.mark(); // unwrapped _delegate
boolean ret = super.next();
if (ret)
prefetchExisting();
return ret;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ public Supplier<Object> getSupplier(int i)
@Override
public Object get(int i)
{
assert(i <= existingColIndex);

if (i<existingColIndex)
return _delegate.get(i);
Integer rowNumber = asInteger(_delegate.get(0));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,12 @@ private void checkBackgroundException() throws BatchValidationException
public Object get(int i)
{
if (null != _keyColumnInfo.get(i))
return _keyValues.get(i);
{
Object value = _keyValues.get(i);
if (value != null)
return value;
}

return _data.get(i);
}

Expand Down
3 changes: 2 additions & 1 deletion api/src/org/labkey/api/exp/api/ExperimentService.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ public interface ExperimentService extends ExperimentRunTypeSource

String EXPERIMENTAL_FEATURE_FROM_EXPANCESTORS = "org.labkey.api.exp.api.ExperimentService#FROM_EXPANCESTORS";

String EXPERIMENTAL_FEATURE_ALLOW_ROW_ID_MERGE = "org.labkey.experiment.api.SampleTypeUpdateServiceDI#ALLOW_ROW_ID_SAMPLE_MERGE";

int SIMPLE_PROTOCOL_FIRST_STEP_SEQUENCE = 1;
int SIMPLE_PROTOCOL_CORE_STEP_SEQUENCE = 10;
int SIMPLE_PROTOCOL_EXTRA_STEP_SEQUENCE = 15;
Expand All @@ -149,7 +151,6 @@ static void setInstance(ExperimentService impl)

enum QueryOptions
{
UseLsidForUpdate,
GetSampleRecomputeCol,
SkipBulkRemapCache,
DeferRequiredLineageValidation,
Expand Down
57 changes: 31 additions & 26 deletions api/src/org/labkey/api/exp/query/ExpDataTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,42 +27,47 @@ public interface ExpDataTable extends ExpTable<ExpDataTable.Column>
{
enum Column
{
RowId,
Alias,
ContentLink,
ClassId, // database table only
CpasType, // database table only
Created,
CreatedBy,
DataClass,
DataFileUrl,
Description,
DownloadLink,
FileExtension,
FileExists,
FileSize,
Flag,
Folder,
Generated,
InlineThumbnail,
Inputs,
LastIndexed,
LSID,
Modified,
ModifiedBy,
Name,
Description,
DataClass,
ObjectId, // database table only
Outputs,
Properties,
Protocol,
SourceProtocolApplication,
SourceApplicationInput,
DataFileUrl,
ReferenceCount,
Run,
RunApplication,
RunApplicationOutput,
Created,
CreatedBy,
Modified,
ModifiedBy,
Folder,
Flag,
Alias,
DownloadLink,
ContentLink,
ViewFileLink,
RunId, // database table only
RowId,
SourceApplicationId, // database table only
SourceApplicationInput,
SourceProtocolApplication,
Thumbnail,
InlineThumbnail,
FileSize,
FileExists,
FileExtension,
ViewFileLink,
ViewOrDownload,
WebDavUrl,
WebDavUrlRelative,
Generated,
LastIndexed,
Inputs,
Outputs,
Properties;
WebDavUrlRelative;

public FieldKey fieldKey()
{
Expand Down
Loading
Loading