Skip to content

Add collect/merge_map aggregations and binary merge optimizations. - #2388

Open
platinumhamburg wants to merge 1 commit into
apache:mainfrom
platinumhamburg:complex-functions
Open

platinumhamburg wants to merge 1 commit into
apache:mainfrom
platinumhamburg:complex-functions

Conversation

@platinumhamburg

Copy link
Copy Markdown
Contributor

Extend tests and docs to cover new functions and array/map utilities.

Purpose

Linked issue: close #2387

Brief change log

Tests

API and Format

Documentation

Extend tests and docs to cover new functions and array/map utilities.
@platinumhamburg

Copy link
Copy Markdown
Contributor Author

Hi @fresh-borzoni, would you have time to review this PR? It adds new aggregate functions to the aggregation merge engine. Given your work on last_n, your feedback on the function semantics, edge cases, and fit with the existing aggregation framework would be especially helpful. Thanks!

return null;
}

if (accumulator == null) {

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.

With distinct=true, the first write for a key is stored as-is, so duplicates survive until the next update. Maybe override requiresAggOnFirstWrite() to return distinct, and skip this shortcut when distinct is on?

collectDistinctBinary(collection, seen, seenNull, inputArray);
return new GenericArray(collection.toArray());
} else {
Set<Object> seen = new HashSet<>(capacity);

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.

HashSet doesn't keep insertion order, so the array can be reshuffled on every update. Consumers would see wrong changes. The other two paths keep insertion order. Maybe use a LinkedHashSet?

for (int i = 0; i < keyArray.size(); i++) {
Object key = keyGetter.getElementOrNull(keyArray, i);
Object value = valueGetter.getElementOrNull(valueArray, i);
map.put(key, value);

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.

For BYTES/BINARY keys, byte[] is compared by reference, so the same key can appear twice in the merged map. One option is to wrap the keys the way collect does. Another is to reject binary key types in the factory.


protected final DataType fieldType;
protected final DataTypeRoot typeRoot;
protected BinaryRowFormat rowFormat = BinaryRowFormat.COMPACTED;

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.

This adds mutable state to the base class, and only collect uses it. Do we have numbers showing the direct binary concat helps? If the gain is small, returning a GenericArray would let us remove this field and the setter loop in AggregationContext. If we keep it, passing the format through the factory or constructor might be cleaner.

* @return the parsed boolean value or defaultValue if missing
* @throws IllegalArgumentException if the parameter value is not "true" or "false"
*/
public boolean getBooleanParameter(String key, boolean defaultValue) {

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.

Parameters are already validated at table creation through validateParameter, so this strict check repeats that work. Maybe the factory could parse the value directly, as last_n does, and we avoid adding a new public method here?

) WITH (
'table.merge-engine' = 'aggregation',
'fields.tags.agg' = 'collect',
'fields.tags.distinct' = 'true'

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.

I thing the parser expects fields.<col>.<func>.<param>, so this would be fields.tags.collect.distinct?

Collects array elements into a single array.

- **Supported Data Types**: `ARRAY<T>`
- **Behavior**: Concatenates array elements; with `distinct=true`, duplicates are removed

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.

It might be worth adding a note like the one on last_n about write and changelog amplification, since collect has no size limit. An optional max-size parameter could also be worth considering.

// ===================================================================================

@Test
void testCollectAggregationNonDistinct() {

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.

It would be great to have tests for the issues above. Coverage for non-primitive element types would also help. A Flink ITCase, like the last_n one, might also help

}

@Override
public Object aggReversed(Object accumulator, Object inputField) {

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.

Nit: I think a reversed collect should put the input first. The base default already does that, so could we drop this override?


private InternalArrayUtils() {}

public static Object[] toObjectArray(InternalArray array, DataType elementType) {

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.

This method and InternalMapUtils seem to be used only by tests. Should they live in test utils instead of fluss-common main?

@polyzos

polyzos commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Thanks @platinumhamburg.

I've added a few comments and suggestions. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Aggregation] Add collect/merge_map aggregation support

2 participants