comphead opened a new issue, #6909: URL: https://github.com/apache/datafusion-comet/issues/6909
### What is the problem the feature request solves? `CometMin` and `CometMax` accept only boolean, integral, floating-point, decimal, `DATE` and `TIMESTAMP` input (`AggSerde.minMaxDataTypeSupported`). Over a `STRING` or `BINARY` column the aggregate is `Unsupported`, so the whole aggregate operator falls back to Spark, and no setting changes that. This rarely mattered before #4565. A `min`/`max` buffer has the input's type, and Spark plans `HashAggregateExec` only when every buffer field is fixed-width, so a string or binary `min`/`max` gets `SortAggregateExec`, which Comet did not support. Such an aggregate reached Comet only next to a `TypedImperativeAggregate` such as `collect_list`, which switches Spark to `ObjectHashAggregateExec`. Now that #4565 runs `SortAggregateExec` natively, string `min`/`max` is what keeps this shape on Spark. String `first`/`last` under `SortAggregateExec` already run natively (`first_last.sql`), while `min_max.sql` still expects the fallback: ``` -- spark/src/test/resources/sql-tests/expressions/aggregate/min_max.sql query expect_fallback(Unsupported data type: StringType) SELECT min(i), max(i), min(d), max(d), min(s), max(s) FROM test_min_max ``` A common case is a grouped aggregate that takes `min`/`max` of a date stored as a string. Both the partial and the final `SortAggregate` fall back, so the plan sorts the full input on every grouping key, converts it to rows for the partial aggregate, shuffles it through the JVM columnar shuffle, then sorts and converts it again for the final aggregate. With many grouping keys and few duplicate groups, those sorts dominate the query and spill heavily. A grouped repro: ```sql CREATE TABLE t (k INT, s STRING, b BINARY) USING parquet; INSERT INTO t VALUES (1, 'b', X'02'), (1, 'a', X'01'), (2, NULL, NULL), (2, 'c', X'80'); SELECT k, min(s), max(s), min(b), max(b) FROM t GROUP BY k; ``` ### Describe the potential solution 1. Accept `StringType` and `BinaryType` in `AggSerde.minMaxSupportLevel`, which only `CometMin` and `CometMax` use. Leave `minMaxDataTypeSupported` as it is: `max_by`/`min_by` and the window `min`/`max` check in `CometWindowExec` call it directly, and they can follow separately. 2. Keep collated strings on Spark. From Spark 4.0, `min`/`max` compare a string with `binaryCompare` only when its collation supports binary ordering, and with `semanticCompare` otherwise. `serializeDataType` maps every `StringType` to the same proto type, so the check has to be explicit, as `supportedScalarSortElementType` does with `isStringCollationType`. 3. I expect no native change. `min_max_udaf` sends non-floating-point types to DataFusion's `min_udaf`/`max_udaf`, which in DataFusion 55.2 have a groups accumulator for `Utf8`, `LargeUtf8`, `Utf8View`, `Binary`, `LargeBinary` and `BinaryView` (`MinMaxBytesAccumulator`). It compares raw bytes. That matches Spark 3.4 through 4.1, where a default-collation string and a `BinaryType` value both compare with `ByteArray.compareBinary`: unsigned, byte by byte, shorter prefix first. The planner already casts the child to the declared type, so dictionary-encoded input reaches the accumulator as `Utf8`. Tests, in `min_max.sql` (turning the `expect_fallback` above into `query`) or a new file: - grouped and global aggregates, plus one next to `collect_list` so the `ObjectHashAggregateExec` shape is covered too - NULLs inside a group, an all-NULL group, an empty input, and `''` next to NULL - prefixes (`'a'`, `'ab'`), case (`'A'`, `'a'`), and characters whose UTF-8 byte order differs from their UTF-16 order, such as `'😀'` (U+1F600) and `'ア'` (U+FF71) - binary values on both sides of `X'7F'`/`X'80'`, which a signed comparison would order the other way - a low-cardinality string column, which Parquet writes dictionary-encoded - on Spark 4.0 and later, `min(s COLLATE UTF8_LCASE)`, which must fall back `CometMin` and `CometMax` also declare `supportsSparkPartialToNativeFinal` and `supportsNativePartialToSparkFinal`. Both should be re-checked with a string buffer. ### Additional context Not part of this issue: even when native, this shape still sorts its input twice, because Spark adds the sorts that `SortAggregateExec` requires. DataFusion's hash aggregate can hold string state, so a follow-up could drop the sort below a `CometSortAggregateExec` and let the native planner sort the aggregate's output instead, which it already does when the input ordering comes from outside the native plan. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
