This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new b93b6b5bfb [core] Support nested sequence fields in
FieldNestedUpdateAgg operator (#8048)
b93b6b5bfb is described below
commit b93b6b5bfb6768fdfc1720cff5afa25c5643bf34
Author: AuroraVoyage <[email protected]>
AuthorDate: Sun May 31 21:21:47 2026 +0800
[core] Support nested sequence fields in FieldNestedUpdateAgg operator
(#8048)
---
.../primary-key-table/merge-engine/aggregation.mdx | 2 +
.../main/java/org/apache/paimon/CoreOptions.java | 14 ++
.../compact/aggregate/FieldNestedUpdateAgg.java | 69 +++++-
.../factory/FieldNestedUpdateAggFactory.java | 13 +-
.../compact/aggregate/FieldAggregatorTest.java | 255 +++++++++++++++++++++
5 files changed, 350 insertions(+), 3 deletions(-)
diff --git a/docs/docs/primary-key-table/merge-engine/aggregation.mdx
b/docs/docs/primary-key-table/merge-engine/aggregation.mdx
index cb88297205..96e6e4f3f0 100644
--- a/docs/docs/primary-key-table/merge-engine/aggregation.mdx
+++ b/docs/docs/primary-key-table/merge-engine/aggregation.mdx
@@ -311,6 +311,8 @@ public static class BitmapContainsUDF extends
ScalarFunction {
Use `fields.<field-name>.nested-key=pk0,pk1,...` to specify the primary keys
of the nested table. If no keys, row will be appended to array\<row>.
+ Use `fields.<field-name>.nested-sequence-field=seq0,seq1,...` to control the
update sequence of a nested table, you must configure
`fields.<field-name>.nested-key` when using it.
+
Use `fields.<field-name>.count-limit=<Integer>` to specify the maximum
number of rows in the nested table. When no nested-key, it will select data
sequentially up to limit; but if nested-key is specified, it cannot
guarantee the correctness of the aggregation result. This option can be used to
avoid abnormal input.
diff --git a/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
b/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
index 24556d61c0..f09a6edb4a 100644
--- a/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
+++ b/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
@@ -80,6 +80,8 @@ public class CoreOptions implements Serializable {
public static final String NESTED_KEY = "nested-key";
+ public static final String NESTED_SEQUENCE_FIELD = "nested-sequence-field";
+
public static final String COUNT_LIMIT = "count-limit";
public static final String DISTINCT = "distinct";
@@ -2740,6 +2742,18 @@ public class CoreOptions implements Serializable {
return
Arrays.stream(keyString.split(",")).map(String::trim).collect(Collectors.toList());
}
+ public List<String> fieldNestedUpdateAggNestedSequenceField(String
fieldName) {
+ String keyString =
+ options.get(
+ key(FIELDS_PREFIX + "." + fieldName + "." +
NESTED_SEQUENCE_FIELD)
+ .stringType()
+ .noDefaultValue());
+ if (keyString == null) {
+ return Collections.emptyList();
+ }
+ return
Arrays.stream(keyString.split(",")).map(String::trim).collect(Collectors.toList());
+ }
+
public int fieldNestedUpdateAggCountLimit(String fieldName) {
return options.get(
key(FIELDS_PREFIX + "." + fieldName + "." + COUNT_LIMIT)
diff --git
a/paimon-core/src/main/java/org/apache/paimon/mergetree/compact/aggregate/FieldNestedUpdateAgg.java
b/paimon-core/src/main/java/org/apache/paimon/mergetree/compact/aggregate/FieldNestedUpdateAgg.java
index b2848b35b4..13b19e6e9b 100644
---
a/paimon-core/src/main/java/org/apache/paimon/mergetree/compact/aggregate/FieldNestedUpdateAgg.java
+++
b/paimon-core/src/main/java/org/apache/paimon/mergetree/compact/aggregate/FieldNestedUpdateAgg.java
@@ -19,24 +19,29 @@
package org.apache.paimon.mergetree.compact.aggregate;
import org.apache.paimon.codegen.Projection;
+import org.apache.paimon.codegen.RecordComparator;
import org.apache.paimon.codegen.RecordEqualiser;
import org.apache.paimon.data.BinaryRow;
import org.apache.paimon.data.GenericArray;
import org.apache.paimon.data.InternalArray;
import org.apache.paimon.data.InternalRow;
import org.apache.paimon.types.ArrayType;
+import org.apache.paimon.types.DataType;
import org.apache.paimon.types.RowType;
import javax.annotation.Nullable;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.apache.paimon.codegen.CodeGenUtils.newProjection;
+import static org.apache.paimon.codegen.CodeGenUtils.newRecordComparator;
import static org.apache.paimon.codegen.CodeGenUtils.newRecordEqualiser;
import static org.apache.paimon.options.ConfigOptions.key;
+import static org.apache.paimon.utils.Preconditions.checkArgument;
import static org.apache.paimon.utils.Preconditions.checkNotNull;
/**
@@ -52,10 +57,23 @@ public class FieldNestedUpdateAgg extends FieldAggregator {
@Nullable private final Projection keyProjection;
@Nullable private final RecordEqualiser elementEqualiser;
+ @Nullable private final Projection sequenceProjection;
+ @Nullable private final RecordComparator sequenceComparator;
+ private final boolean hasSequenceField;
+
private final int countLimit;
public FieldNestedUpdateAgg(
String name, ArrayType dataType, List<String> nestedKey, int
countLimit) {
+ this(name, dataType, nestedKey, Collections.emptyList(), countLimit);
+ }
+
+ public FieldNestedUpdateAgg(
+ String name,
+ ArrayType dataType,
+ List<String> nestedKey,
+ List<String> nestedSequenceField,
+ int countLimit) {
super(name, dataType);
RowType nestedType = (RowType) dataType.getElementType();
this.nestedFields = nestedType.getFieldCount();
@@ -67,6 +85,31 @@ public class FieldNestedUpdateAgg extends FieldAggregator {
this.elementEqualiser = null;
}
+ // If nestedSequenceField is set, we need to compare sequence fields
to determine
+ // whether to update. Only update when the new sequence is greater
than the old one.
+ if (!nestedSequenceField.isEmpty()) {
+ checkArgument(
+ this.keyProjection != null,
+ "nested-sequence-field requires nested-key to be set.");
+ this.sequenceProjection = newProjection(nestedType,
nestedSequenceField);
+ this.hasSequenceField = true;
+
+ // Extract the data types of the sequence fields to generate a
native record comparator
+ int sequenceFields = nestedSequenceField.size();
+ List<DataType> seqTypes = new ArrayList<>(sequenceFields);
+ int[] sortFields = new int[sequenceFields];
+ for (int i = 0; i < sequenceFields; i++) {
+ String fieldName = nestedSequenceField.get(i);
+
seqTypes.add(nestedType.getTypeAt(nestedType.getFieldIndex(fieldName)));
+ sortFields[i] = i;
+ }
+ this.sequenceComparator = newRecordComparator(seqTypes,
sortFields);
+ } else {
+ this.sequenceProjection = null;
+ this.sequenceComparator = null;
+ this.hasSequenceField = false;
+ }
+
// If deduplicate key is set, we don't guarantee that the result is
exactly right
this.countLimit = countLimit;
}
@@ -94,7 +137,15 @@ public class FieldNestedUpdateAgg extends FieldAggregator {
Map<BinaryRow, InternalRow> map = new HashMap<>();
for (InternalRow row : rows) {
BinaryRow key = keyProjection.apply(row).copy();
- map.put(key, row);
+ if (hasSequenceField) {
+ // When sequence field is configured, only update if the
new sequence is greater
+ InternalRow existing = map.get(key);
+ if (existing == null || compareSequence(row, existing) >=
0) {
+ map.put(key, row);
+ }
+ } else {
+ map.put(key, row);
+ }
}
rows = new ArrayList<>(map.values());
@@ -146,6 +197,22 @@ public class FieldNestedUpdateAgg extends FieldAggregator {
}
}
+ private int compareSequence(InternalRow newRow, InternalRow oldRow) {
+ checkNotNull(
+ sequenceComparator,
+ "sequenceComparator should not be null when hasSequenceField
is true.");
+ checkNotNull(
+ sequenceProjection,
+ "sequenceProjection should not be null when hasSequenceField
is true.");
+
+ // Project the rows into sub-rows containing only sequence fields
+ BinaryRow newSeqRow = sequenceProjection.apply(newRow).copy();
+ BinaryRow oldSeqRow = sequenceProjection.apply(oldRow).copy();
+
+ // Triggers native CodeGen comparison (Nulls First by default)
+ return sequenceComparator.compare(newSeqRow, oldSeqRow);
+ }
+
private void addNonNullRows(InternalArray array, List<InternalRow> rows) {
for (int i = 0; i < array.size(); i++) {
if (array.isNullAt(i)) {
diff --git
a/paimon-core/src/main/java/org/apache/paimon/mergetree/compact/aggregate/factory/FieldNestedUpdateAggFactory.java
b/paimon-core/src/main/java/org/apache/paimon/mergetree/compact/aggregate/factory/FieldNestedUpdateAggFactory.java
index 070931e011..ca0d961498 100644
---
a/paimon-core/src/main/java/org/apache/paimon/mergetree/compact/aggregate/factory/FieldNestedUpdateAggFactory.java
+++
b/paimon-core/src/main/java/org/apache/paimon/mergetree/compact/aggregate/factory/FieldNestedUpdateAggFactory.java
@@ -39,6 +39,7 @@ public class FieldNestedUpdateAggFactory implements
FieldAggregatorFactory {
return createFieldNestedUpdateAgg(
fieldType,
options.fieldNestedUpdateAggNestedKey(field),
+ options.fieldNestedUpdateAggNestedSequenceField(field),
options.fieldNestedUpdateAggCountLimit(field));
}
@@ -48,17 +49,25 @@ public class FieldNestedUpdateAggFactory implements
FieldAggregatorFactory {
}
private FieldNestedUpdateAgg createFieldNestedUpdateAgg(
- DataType fieldType, List<String> nestedKey, int countLimit) {
+ DataType fieldType,
+ List<String> nestedKey,
+ List<String> nestedSequenceField,
+ int countLimit) {
if (nestedKey == null) {
nestedKey = Collections.emptyList();
}
+ if (nestedSequenceField == null) {
+ nestedSequenceField = Collections.emptyList();
+ }
+
String typeErrorMsg =
"Data type for nested table column must be 'Array<Row>' but
was '%s'.";
checkArgument(fieldType instanceof ArrayType, typeErrorMsg, fieldType);
ArrayType arrayType = (ArrayType) fieldType;
checkArgument(arrayType.getElementType() instanceof RowType,
typeErrorMsg, fieldType);
- return new FieldNestedUpdateAgg(identifier(), arrayType, nestedKey,
countLimit);
+ return new FieldNestedUpdateAgg(
+ identifier(), arrayType, nestedKey, nestedSequenceField,
countLimit);
}
}
diff --git
a/paimon-core/src/test/java/org/apache/paimon/mergetree/compact/aggregate/FieldAggregatorTest.java
b/paimon-core/src/test/java/org/apache/paimon/mergetree/compact/aggregate/FieldAggregatorTest.java
index 1bdddcb846..a6a2794c36 100644
---
a/paimon-core/src/test/java/org/apache/paimon/mergetree/compact/aggregate/FieldAggregatorTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/mergetree/compact/aggregate/FieldAggregatorTest.java
@@ -749,6 +749,252 @@ public class FieldAggregatorTest {
.containsExactlyInAnyOrderElementsOf(Arrays.asList(row(0, 1,
"B"), row(0, 1, "b")));
}
+ @Test
+ public void testFieldNestedUpdateAggWithSequenceField() {
+ DataType elementRowType =
+ DataTypes.ROW(
+ DataTypes.FIELD(0, "k0", DataTypes.INT()),
+ DataTypes.FIELD(1, "k1", DataTypes.INT()),
+ DataTypes.FIELD(2, "v", DataTypes.STRING()),
+ DataTypes.FIELD(3, "seq", DataTypes.INT()));
+ FieldNestedUpdateAgg agg =
+ new FieldNestedUpdateAgg(
+ FieldNestedUpdateAggFactory.NAME,
+ DataTypes.ARRAY(elementRowType),
+ Arrays.asList("k0", "k1"),
+ Collections.singletonList("seq"),
+ Integer.MAX_VALUE);
+
+ InternalArray accumulator;
+ InternalArray.ElementGetter elementGetter =
+ InternalArray.createElementGetter(elementRowType);
+
+ InternalRow current = row(0, 0, "A", 1);
+ accumulator = (InternalArray) agg.agg(null, singletonArray(current));
+ assertThat(unnest(accumulator, elementGetter))
+
.containsExactlyInAnyOrderElementsOf(Collections.singletonList(current));
+
+ current = row(0, 1, "B", 2);
+ accumulator = (InternalArray) agg.agg(accumulator,
singletonArray(current));
+ assertThat(unnest(accumulator, elementGetter))
+ .containsExactlyInAnyOrderElementsOf(
+ Arrays.asList(row(0, 0, "A", 1), row(0, 1, "B", 2)));
+
+ current = row(0, 1, "b", 3);
+ accumulator = (InternalArray) agg.agg(accumulator,
singletonArray(current));
+ assertThat(unnest(accumulator, elementGetter))
+ .containsExactlyInAnyOrderElementsOf(
+ Arrays.asList(row(0, 0, "A", 1), row(0, 1, "b", 3)));
+
+ current = row(0, 1, "B_late", 2);
+ accumulator = (InternalArray) agg.agg(accumulator,
singletonArray(current));
+ assertThat(unnest(accumulator, elementGetter))
+ .containsExactlyInAnyOrderElementsOf(
+ Arrays.asList(row(0, 0, "A", 1), row(0, 1, "b", 3)));
+
+ current = row(0, 1, "b", 3);
+ accumulator = (InternalArray) agg.retract(accumulator,
singletonArray(current));
+ assertThat(unnest(accumulator, elementGetter))
+
.containsExactlyInAnyOrderElementsOf(Collections.singletonList(row(0, 0, "A",
1)));
+ }
+
+ @Test
+ public void testFieldNestedUpdateAggWithMultipleSequenceFields() {
+ DataType elementRowType =
+ DataTypes.ROW(
+ DataTypes.FIELD(0, "k0", DataTypes.INT()),
+ DataTypes.FIELD(1, "k1", DataTypes.INT()),
+ DataTypes.FIELD(2, "v", DataTypes.STRING()),
+ DataTypes.FIELD(3, "seq", DataTypes.INT()),
+ DataTypes.FIELD(4, "ts", DataTypes.TIMESTAMP(3)));
+
+ FieldNestedUpdateAgg agg =
+ new FieldNestedUpdateAgg(
+ FieldNestedUpdateAggFactory.NAME,
+ DataTypes.ARRAY(elementRowType),
+ Arrays.asList("k0", "k1"),
+ Arrays.asList("seq", "ts"),
+ Integer.MAX_VALUE);
+
+ InternalArray accumulator = null;
+ InternalArray.ElementGetter elementGetter =
+ InternalArray.createElementGetter(elementRowType);
+
+ org.apache.paimon.data.Timestamp ts1 =
+ org.apache.paimon.data.Timestamp.fromEpochMillis(1000L);
+ org.apache.paimon.data.Timestamp ts2 =
+ org.apache.paimon.data.Timestamp.fromEpochMillis(2000L);
+ org.apache.paimon.data.Timestamp ts3 =
+ org.apache.paimon.data.Timestamp.fromEpochMillis(3000L);
+
+ InternalRow current = row(1, 0, "A", 1, ts2);
+ accumulator = (InternalArray) agg.agg(accumulator,
singletonArray(current));
+ current = row(0, 1, "B", 2, ts1);
+ accumulator = (InternalArray) agg.agg(accumulator,
singletonArray(current));
+ assertThat(unnest(accumulator, elementGetter))
+ .containsExactlyInAnyOrderElementsOf(
+ Arrays.asList(row(1, 0, "A", 1, ts2), row(0, 1, "B",
2, ts1)));
+
+ current = row(1, 1, "C", 1, ts2);
+ accumulator = (InternalArray) agg.agg(accumulator,
singletonArray(current));
+ assertThat(unnest(accumulator, elementGetter))
+ .containsExactlyInAnyOrderElementsOf(
+ Arrays.asList(
+ row(1, 0, "A", 1, ts2),
+ row(0, 1, "B", 2, ts1),
+ row(1, 1, "C", 1, ts2)));
+
+ current = row(1, 0, "A_late_updated_by_ts", 1, ts1);
+ accumulator = (InternalArray) agg.agg(accumulator,
singletonArray(current));
+ assertThat(unnest(accumulator, elementGetter))
+ .containsExactlyInAnyOrderElementsOf(
+ Arrays.asList(
+ row(1, 0, "A", 1, ts2),
+ row(0, 1, "B", 2, ts1),
+ row(1, 1, "C", 1, ts2)));
+
+ current = row(1, 0, "A_updated_by_ts", 1, ts3);
+ accumulator = (InternalArray) agg.agg(accumulator,
singletonArray(current));
+ assertThat(unnest(accumulator, elementGetter))
+ .containsExactlyInAnyOrderElementsOf(
+ Arrays.asList(
+ row(1, 0, "A_updated_by_ts", 1, ts3),
+ row(0, 1, "B", 2, ts1),
+ row(1, 1, "C", 1, ts2)));
+
+ // Try to update with a smaller 1st seq, even if the 2nd seq (ts) is
larger
+ // Result: Should be IGNORED because the 1st seq field (1 < 2) takes
higher priority.
+ current = row(0, 1, "b_ignored", 1, ts3);
+ accumulator = (InternalArray) agg.agg(accumulator,
singletonArray(current));
+ assertThat(unnest(accumulator, elementGetter))
+ .containsExactlyInAnyOrderElementsOf(
+ Arrays.asList(
+ row(1, 0, "A_updated_by_ts", 1, ts3),
+ row(0, 1, "B", 2, ts1),
+ row(1, 1, "C", 1, ts2)));
+
+ // Update with the SAME 1st seq, but a larger 2nd seq (ts)
+ // Result: Should be SUCCESSFULLY UPDATED because seq (2 == 2) and ts
(ts2 > ts1).
+ current = row(0, 1, "B_updated_by_ts", 2, ts2);
+ accumulator = (InternalArray) agg.agg(accumulator,
singletonArray(current));
+ assertThat(unnest(accumulator, elementGetter))
+ .containsExactlyInAnyOrderElementsOf(
+ Arrays.asList(
+ row(1, 0, "A_updated_by_ts", 1, ts3),
+ row(0, 1, "B_updated_by_ts", 2, ts2),
+ row(1, 1, "C", 1, ts2)));
+
+ // Update with a larger 1st seq, even if the 2nd seq (ts) is smaller
+ // Result: Should be SUCCESSFULLY UPDATED because the 1st seq field (3
> 2) wins.
+ current = row(0, 1, "B_updated_by_seq", 3, ts1);
+ accumulator = (InternalArray) agg.agg(accumulator,
singletonArray(current));
+ assertThat(unnest(accumulator, elementGetter))
+ .containsExactlyInAnyOrderElementsOf(
+ Arrays.asList(
+ row(1, 0, "A_updated_by_ts", 1, ts3),
+ row(0, 1, "B_updated_by_seq", 3, ts1),
+ row(1, 1, "C", 1, ts2)));
+
+ // Retract the latest row matching the current state
+ current = row(0, 1, "B_updated_by_seq", 3, ts1);
+ accumulator = (InternalArray) agg.retract(accumulator,
singletonArray(current));
+ assertThat(unnest(accumulator, elementGetter))
+ .containsExactlyInAnyOrderElementsOf(
+ Arrays.asList(
+ row(1, 0, "A_updated_by_ts", 1, ts3), row(1,
1, "C", 1, ts2)));
+ }
+
+ @Test
+ public void testFieldNestedUpdateAggWithSequenceFieldWithoutNestedKey() {
+ DataType elementRowType =
+ DataTypes.ROW(
+ DataTypes.FIELD(0, "k0", DataTypes.INT()),
+ DataTypes.FIELD(1, "k1", DataTypes.INT()),
+ DataTypes.FIELD(2, "v", DataTypes.STRING()),
+ DataTypes.FIELD(3, "seq", DataTypes.INT()));
+
+ org.assertj.core.api.Assertions.assertThatThrownBy(
+ () ->
+ new FieldNestedUpdateAgg(
+ FieldNestedUpdateAggFactory.NAME,
+ DataTypes.ARRAY(elementRowType),
+ Collections.emptyList(),
+ Collections.singletonList("seq"),
+ Integer.MAX_VALUE))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("nested-sequence-field requires nested-key to be
set.");
+ }
+
+ @Test
+ public void
testFieldNestedUpdateAggWithCountLimitWithSequenceFieldWithoutNestedKey() {
+ DataType elementRowType =
+ DataTypes.ROW(
+ DataTypes.FIELD(0, "k0", DataTypes.INT()),
+ DataTypes.FIELD(1, "k1", DataTypes.INT()),
+ DataTypes.FIELD(2, "v", DataTypes.STRING()),
+ DataTypes.FIELD(3, "seq", DataTypes.INT()));
+
+ // Verify that the same precondition check applies even when a count
limit is specified
+ org.assertj.core.api.Assertions.assertThatThrownBy(
+ () ->
+ new FieldNestedUpdateAgg(
+ FieldNestedUpdateAggFactory.NAME,
+ DataTypes.ARRAY(elementRowType),
+ Collections.emptyList(),
+ Collections.singletonList("seq"),
+ 2))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("nested-sequence-field requires nested-key to be
set.");
+ }
+
+ @Test
+ public void testFieldNestedUpdateAggWithCountLimitWithSequenceField() {
+ DataType elementRowType =
+ DataTypes.ROW(
+ DataTypes.FIELD(0, "k0", DataTypes.INT()),
+ DataTypes.FIELD(1, "k1", DataTypes.INT()),
+ DataTypes.FIELD(2, "v", DataTypes.STRING()),
+ DataTypes.FIELD(3, "seq", DataTypes.INT()));
+
+ FieldNestedUpdateAgg agg =
+ new FieldNestedUpdateAgg(
+ FieldNestedUpdateAggFactory.NAME,
+ DataTypes.ARRAY(elementRowType),
+ Arrays.asList("k0", "k1"),
+ Collections.singletonList("seq"),
+ 2); // Enforce count limit = 2
+
+ InternalArray accumulator = null;
+ InternalArray.ElementGetter elementGetter =
+ InternalArray.createElementGetter(elementRowType);
+
+ InternalRow current = row(0, 1, "B", 1);
+ accumulator = (InternalArray) agg.agg(accumulator,
singletonArray(current));
+ assertThat(unnest(accumulator, elementGetter))
+
.containsExactlyInAnyOrderElementsOf(Collections.singletonList(row(0, 1, "B",
1)));
+
+ current = row(0, 1, "B_updated", 2);
+ accumulator = (InternalArray) agg.agg(accumulator,
singletonArray(current));
+ // The existing row should be updated, and the total size remains 1
+ assertThat(unnest(accumulator, elementGetter))
+ .containsExactlyInAnyOrderElementsOf(
+ Collections.singletonList(row(0, 1, "B_updated", 2)));
+
+ current = row(1, 2, "C", 3); // Different nested key (1, 2)
+ accumulator = (InternalArray) agg.agg(accumulator,
singletonArray(current));
+ assertThat(unnest(accumulator, elementGetter))
+ .containsExactlyInAnyOrderElementsOf(
+ Arrays.asList(row(0, 1, "B_updated", 2), row(1, 2,
"C", 3)));
+
+ current = row(0, 3, "D", 4); // Another different nested key (0, 3)
+ accumulator = (InternalArray) agg.agg(accumulator,
singletonArray(current));
+
+ // count limit is 2, so the third element will be dropped
+ assertThat(unnest(accumulator, elementGetter))
+ .containsExactlyInAnyOrderElementsOf(
+ Arrays.asList(row(0, 1, "B_updated", 2), row(1, 2,
"C", 3)));
+ }
+
private List<Object> unnest(InternalArray array,
InternalArray.ElementGetter elementGetter) {
return IntStream.range(0, array.size())
.mapToObj(i -> elementGetter.getElementOrNull(array, i))
@@ -763,6 +1009,15 @@ public class FieldAggregatorTest {
return GenericRow.of(k0, k1, BinaryString.fromString(v));
}
+ private InternalRow row(Integer k0, Integer k1, String v, Integer seq) {
+ return GenericRow.of(k0, k1, BinaryString.fromString(v), seq);
+ }
+
+ private InternalRow row(
+ Object k0, Object k1, String v, Object seq,
org.apache.paimon.data.Timestamp ts) {
+ return GenericRow.of(k0, k1, BinaryString.fromString(v), seq, ts);
+ }
+
@Test
public void testFieldCollectAggWithDistinct() {
FieldCollectAgg agg =