Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
a066d53
Support field type conversion for Multi Choice fields
XingY Jan 22, 2026
fc22b72
crlf
XingY Jan 22, 2026
cbb199b
Port Is empty filter changes from fb_mvtc_empty branch
XingY Jan 23, 2026
98f19f2
fix display
XingY Jan 24, 2026
beefa2a
Merge remote-tracking branch 'origin/develop' into fb_mvtc_convert
XingY Jan 25, 2026
9fa7270
Handle special characters
XingY Jan 26, 2026
e717afa
Merge remote-tracking branch 'origin/develop' into fb_mvtc_convert
XingY Jan 26, 2026
4c4d519
clean
XingY Jan 27, 2026
b8a163d
Merge remote-tracking branch 'origin/develop' into fb_mvtc_convert
XingY Jan 27, 2026
5a79008
Merge remote-tracking branch 'origin/develop' into fb_mvtc_convert
XingY Jan 28, 2026
6d55319
merge from develop
XingY Jan 28, 2026
002fe51
Merge branch 'develop' into fb_mvtc_convert
labkey-danield Jan 29, 2026
1837728
Merge remote-tracking branch 'origin/develop' into fb_mvtc_convert
XingY Jan 30, 2026
0674ec9
fix column name with space
XingY Jan 30, 2026
4273019
fix schema name
XingY Jan 30, 2026
3bb0c5f
fix encoding
XingY Jan 30, 2026
f6a4e81
merge from develop
XingY Feb 1, 2026
d205e32
Enable MVTC fields in sample finder. Remove experimental flag
XingY Feb 1, 2026
6aa0ee7
crlf
XingY Feb 2, 2026
e75c688
skip sql server
XingY Feb 2, 2026
dc1bd5d
merge from develop
XingY Feb 3, 2026
4934b5e
Switch to use columnInfo.convert
XingY Feb 3, 2026
2a293ed
Code review changes
XingY Feb 3, 2026
daff069
Fix type
XingY Feb 3, 2026
e3d9a7c
Fix biologics vector selection BiologicsAPITest.setVectorAliasViaLega…
XingY Feb 4, 2026
646a3bf
Fix dataclass update
XingY Feb 4, 2026
4e66d33
bug fixes
XingY Feb 5, 2026
79863e8
fix date format in name expression
XingY Feb 5, 2026
bd11124
bug fixes
XingY Feb 5, 2026
4a5a3e1
merge from develop
XingY Feb 5, 2026
901cd43
bug fixes: natural sorting with upper case first, remove default valu…
XingY Feb 6, 2026
c34f952
Disallow multi choice
XingY Feb 6, 2026
c0b4c59
api test for dataclass
XingY Feb 6, 2026
b5df9c3
Fix dataclass attachment on update
XingY Feb 6, 2026
51d9fd2
merge from develop
XingY Feb 6, 2026
1e2d8ee
merge from develop
XingY Feb 6, 2026
2030ab1
fix allow multi check
XingY Feb 6, 2026
2bc2ff7
Fix mvfk input
XingY Feb 7, 2026
0d8a398
Update warning msg on data type convert
XingY Feb 7, 2026
3c6ec8e
Bug fix: edit in grid includes unchanged mvtc in audit, saved custom …
XingY Feb 10, 2026
156969e
Merge remote-tracking branch 'origin/develop' into fb_mvtc_convert
XingY Feb 10, 2026
adc4b4a
Bug fix: edit in grid includes unchanged mvtc in audit, saved custom …
XingY Feb 10, 2026
66ad632
merge from develop
XingY Feb 13, 2026
37cf9c4
publish
XingY Feb 13, 2026
2974700
fix empty
XingY Feb 13, 2026
7714466
Merge remote-tracking branch 'origin/develop' into fb_mvtc_convert
XingY Feb 13, 2026
4a95b50
add comment
XingY Feb 13, 2026
48ad37a
bug fixes
XingY Feb 13, 2026
dc315d6
bug fixes
XingY Feb 14, 2026
8039bd0
Test multi choice field for Dataset (#7411)
DariaBod Feb 17, 2026
76795c4
Merge branch 'develop' into fb_mvtc_convert
labkey-danield Feb 17, 2026
a24910a
merge from develop
XingY Feb 17, 2026
fa527fa
Python does have a filter operator "enum"
labkey-tchad Feb 17, 2026
aacb456
LKS dataregion display
XingY Feb 17, 2026
7a0e443
bring back experimental flag
XingY Feb 17, 2026
4cd42ea
merge from develop
XingY Feb 17, 2026
c19b7af
fix more filter display
XingY Feb 17, 2026
4e71006
Revert convertType to use DBTable instead of QueryTable due to MVIndi…
XingY Feb 18, 2026
482f94e
Merge remote-tracking branch 'origin/develop' into fb_mvtc_convert
XingY Feb 18, 2026
195dac8
merge from develop
XingY Feb 18, 2026
b9667d4
Fix update lock value warning
XingY Feb 18, 2026
8d2df4c
Fix alias
XingY Feb 18, 2026
b685f9d
Merge remote-tracking branch 'origin/develop' into fb_mvtc_convert
XingY Feb 18, 2026
6a52b48
merge from develop
XingY Feb 18, 2026
a2c8deb
publish
XingY Feb 18, 2026
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
99 changes: 97 additions & 2 deletions api/src/org/labkey/api/data/CompareType.java
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,39 @@ protected Collection getCollectionParam(Object value)
* <xsd:enumeration value="arrayisempty"/>
* <xsd:enumeration value="arrayisnotempty"/>
*/


public static final CompareType ARRAY_IS_EMPTY = new CompareType("Is Empty", "arrayisempty", "ARRAYISEMPTY", false, null, OperatorType.ARRAYISEMPTY)
{
@Override
public ArrayIsEmptyClause createFilterClause(@NotNull FieldKey fieldKey, Object value)
{
return new ArrayIsEmptyClause(fieldKey);
}

@Override
public boolean meetsCriteria(ColumnRenderProperties col, Object value, Object[] filterValues)
{
throw new UnsupportedOperationException("Conditional formatting not yet supported for Multi Choices");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like "arrays" would be the more general term (vs. "Multi Choices") since these operators could be used in other scenarios. But I see that "Multi Choices" is what we're using in all the existing CompareTypes, so I guess it's fine.

}
};


public static final CompareType ARRAY_IS_NOT_EMPTY = new CompareType("Is Not Empty", "arrayisnotempty", "ARRAYISNOTEMPTY", false, null, OperatorType.ARRAYISNOTEMPTY)
{
@Override
public ArrayIsEmptyClause createFilterClause(@NotNull FieldKey fieldKey, Object value)
{
return new ArrayIsNotEmptyClause(fieldKey);
}

@Override
public boolean meetsCriteria(ColumnRenderProperties col, Object value, Object[] filterValues)
{
throw new UnsupportedOperationException("Conditional formatting not yet supported for Multi Choices");
}
};

public static final CompareType ARRAY_CONTAINS_ALL = new CompareType("Contains All", "arraycontainsall", "ARRAYCONTAINSALL", true, null, OperatorType.ARRAYCONTAINSALL)
{
@Override
Expand Down Expand Up @@ -934,7 +967,7 @@ public String getValueSeparator()

public static abstract class ArrayClause extends SimpleFilter.MultiValuedFilterClause
{
public static final String ARRAY_VALUE_SEPARATOR = ",";
public static final String ARRAY_VALUE_SEPARATOR = ";";

public ArrayClause(@NotNull FieldKey fieldKey, CompareType comparison, Collection<?> params, boolean negated)
{
Expand All @@ -958,7 +991,7 @@ public SQLFragment[] getParamSQLFragments(SqlDialect dialect)
}

for (int i = 0; i < params.length; i++)
fragments[i] = new SQLFragment().append(escapeLabKeySqlValue(params[i], type));
fragments[i] = SQLFragment.unsafe(escapeLabKeySqlValue(params[i], type));

return fragments;
}
Expand All @@ -981,6 +1014,68 @@ public Pair<SQLFragment, SQLFragment> getSqlFragments(Map<FieldKey, ? extends Co

}

private static class ArrayIsEmptyClause extends ArrayClause
{
public ArrayIsEmptyClause(@NotNull FieldKey fieldKey, CompareType comparison, boolean negated)
{
super(fieldKey, comparison, null, negated);
}

public ArrayIsEmptyClause(@NotNull FieldKey fieldKey)
{
this(fieldKey, CompareType.ARRAY_IS_EMPTY, false);
}

@Override
public SQLFragment toSQLFragment(Map<FieldKey, ? extends ColumnInfo> columnMap, SqlDialect dialect)
{
ColumnInfo colInfo = columnMap != null ? columnMap.get(_fieldKey) : null;
var alias = SimpleFilter.getAliasForColumnFilter(dialect, colInfo, _fieldKey);

SQLFragment columnFragment = new SQLFragment().appendIdentifier(alias);

SQLFragment sql = dialect.array_is_empty(columnFragment);
if (!_negated)
return sql;
return new SQLFragment(" NOT (").append(sql).append(")");
}

@Override
public String getLabKeySQLWhereClause(Map<FieldKey, ? extends ColumnInfo> columnMap)
{
return "array_is_empty(" + getLabKeySQLColName(_fieldKey) + ")";
}

@Override
public void appendFilterText(StringBuilder sb, ColumnNameFormatter formatter)
{
sb.append("is empty");
}

}

private static class ArrayIsNotEmptyClause extends ArrayIsEmptyClause
{

public ArrayIsNotEmptyClause(@NotNull FieldKey fieldKey)
{
super(fieldKey, CompareType.ARRAY_IS_NOT_EMPTY, true);
}

@Override
public String getLabKeySQLWhereClause(Map<FieldKey, ? extends ColumnInfo> columnMap)
{
return "NOT array_is_empty(" + getLabKeySQLColName(_fieldKey) + ")";
}

@Override
public void appendFilterText(StringBuilder sb, ColumnNameFormatter formatter)
{
sb.append("is not empty");
}

}

private static class ArrayContainsAllClause extends ArrayClause
{

Expand Down
8 changes: 8 additions & 0 deletions api/src/org/labkey/api/data/MultiValuedRenderContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.apache.commons.collections4.iterators.ArrayIterator;
import org.junit.Assert;
import org.junit.Test;
import org.labkey.api.exp.PropertyType;
import org.labkey.api.query.FieldKey;

import java.util.HashMap;
Expand Down Expand Up @@ -113,6 +114,13 @@ public Object get(Object key)
if (getFieldMap() != null)
{
ColumnInfo columnInfo = getFieldMap().get(key);
if (columnInfo != null && columnInfo.getPropertyType() == PropertyType.MULTI_CHOICE && value instanceof String strVal)
{
// Multi-choice values array is converted to string: "{value1,value2,...}", so strip off the braces before converting
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these comma separated or semi-colon separated?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This '{a, b}' format is what's returned by MVFK when casting array to string array_to_string(core.sort(array_agg(field))). @labkey-matthewb did mention possibly have MVFK to return ARRAY[] instead of string for the array type columns in the future.

if (strVal.startsWith("{") && strVal.endsWith("}"))
return ConvertUtils.convert(strVal.substring(1, strVal.length() - 1), columnInfo.getJavaClass());
// TODO: return columnInfo.convert(strVal.substring(1, strVal.length() - 1));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this TODO for later? Or can it be removed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to be updated after merging in the related branch that refactored ConverterUtil.

}
// The value was concatenated with others, so it's become a string.
// Do conversion to switch it back to the expected type.
if (value != null && columnInfo != null && !columnInfo.getJavaClass().isInstance(value))
Expand Down
2 changes: 1 addition & 1 deletion api/src/org/labkey/api/data/SimpleFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ public static abstract class MultiValuedFilterClause extends CompareType.Abstrac
public MultiValuedFilterClause(@NotNull FieldKey fieldKey, CompareType comparison, Collection<?> params, boolean negated)
{
super(fieldKey);
params = new ArrayList<>(params); // possibly immutable
params = params == null ? new ArrayList<>() : new ArrayList<>(params); // possibly immutable
if (params.contains(null)) //params.size() == 0 ||
{
_includeNull = true;
Expand Down
12 changes: 12 additions & 0 deletions api/src/org/labkey/api/data/TableChange.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.labkey.api.data.PropertyStorageSpec.Index;
import org.labkey.api.data.TableInfo.IndexDefinition;
import org.labkey.api.exp.PropertyDescriptor;
import org.labkey.api.exp.PropertyType;
import org.labkey.api.exp.property.Domain;
import org.labkey.api.exp.property.DomainKind;
import org.labkey.api.util.logging.LogHelper;
Expand Down Expand Up @@ -58,6 +59,7 @@ public class TableChange
private Collection<Constraint> _constraints;
private Set<String> _indicesToBeDroppedByName;
private IndexSizeMode _sizeMode = IndexSizeMode.Auto;
private Map<String, PropertyType> _oldPropTypes;

/** In most cases, domain knows the storage table name **/
public TableChange(Domain domain, ChangeType changeType)
Expand Down Expand Up @@ -329,6 +331,11 @@ public void setForeignKeys(Collection<PropertyStorageSpec.ForeignKey> foreignKey
_foreignKeys = foreignKeys;
}

public Map<String, PropertyType> getOldPropTypes()
{
return _oldPropTypes;
}

public final List<PropertyStorageSpec> toSpecs(Collection<String> columnNames)
{
final Domain domain = _domain;
Expand All @@ -349,6 +356,11 @@ public final List<PropertyStorageSpec> toSpecs(Collection<String> columnNames)
.collect(Collectors.toList());
}

public void setOldPropertyTypes(Map<String, PropertyType> oldPropTypes)
{
_oldPropTypes = oldPropTypes;
}
Comment on lines +359 to +362
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate method definition. The method setOldPropertyTypes is defined twice in this class - once at lines 339-342 as setOldPropTypes and again at lines 364-367 as setOldPropertyTypes. Remove one of these duplicate methods.

Copilot uses AI. Check for mistakes.

public enum ChangeType
{
CreateTable,
Expand Down
6 changes: 6 additions & 0 deletions api/src/org/labkey/api/data/dialect/SqlDialect.java
Original file line number Diff line number Diff line change
Expand Up @@ -2200,6 +2200,12 @@ public SQLFragment array_construct(SQLFragment[] elements)
throw new UnsupportedOperationException(getClass().getSimpleName() + " does not implement");
}

public SQLFragment array_is_empty(SQLFragment a)
{
assert !supportsArrays();
throw new UnsupportedOperationException(getClass().getSimpleName() + " does not implement");
}

// element a is in array b
public SQLFragment element_in_array(SQLFragment a, SQLFragment b)
{
Expand Down
2 changes: 0 additions & 2 deletions api/src/org/labkey/api/exp/property/DomainUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -435,8 +435,6 @@ public static boolean allowMultiChoice(DomainKind<?> kind)
{
if (!kind.allowMultiChoiceProperties())
return false;
if (!OptionalFeatureService.get().isFeatureEnabled(AppProps.MULTI_VALUE_TEXT_CHOICE))
return false;
return CoreSchema.getInstance().getSqlDialect().isPostgreSQL();
}

Expand Down
6 changes: 3 additions & 3 deletions api/src/org/labkey/api/query/AbstractQueryChangeListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ public void queryCreated(User user, Container container, ContainerFilter scope,
protected abstract void queryCreated(User user, Container container, ContainerFilter scope, SchemaKey schema, String query);

@Override
public void queryChanged(User user, Container container, ContainerFilter scope, SchemaKey schema, @NotNull QueryProperty property, @NotNull Collection<QueryPropertyChange<?>> changes)
public void queryChanged(User user, Container container, ContainerFilter scope, SchemaKey schema, String queryName, @NotNull QueryProperty property, @NotNull Collection<QueryPropertyChange<?>> changes)
{
for (QueryPropertyChange<?> change : changes)
queryChanged(user, container, scope, schema, change);
queryChanged(user, container, scope, schema, queryName, change);
}

protected abstract void queryChanged(User user, Container container, ContainerFilter scope, SchemaKey schema, QueryPropertyChange<?> change);
protected abstract void queryChanged(User user, Container container, ContainerFilter scope, SchemaKey schema, String queryName, QueryPropertyChange<?> change);

@Override
public void queryDeleted(User user, Container container, ContainerFilter scope, SchemaKey schema, @NotNull Collection<String> queries)
Expand Down
Loading
Loading