agg_funcs Expression Audits#
Audit notes for expressions in this category that have been audited. Absence of an entry means the expression has not been audited yet, not that it is unsupported. See the user guide Spark Expression Support for current support status.
approx_count_distinct#
Spark 3.4.3 (2026-07-03): registered as
expression[HyperLogLogPlusPlus]("approx_count_distinct"), anImperativeAggregatethat hashes each non-null input withXxHash64(seed 42, floats normalized viaNormalizeNaNAndZero) and keeps a HyperLogLog++ register buffer ofnumWordsLongs (10 six-bit registers per word). The cardinality is estimated with linear counting for small inputs and bias-corrected HLL otherwise. Comet portsHyperLogLogPlusPlusHelperexactly, including the bias-correction tables, reuses Comet’s Spark-compatiblexxhash64for hashing, and stores the register buffer in Spark’s identical packed-Longlayout, so results are bit-identical to Spark and the partial-aggregation state matches Spark’saggBufferSchema(enabling mixed Comet/Spark partial and final aggregation).relativeSD(default 0.05) sets the precisionp. Comet supports the input types itsxxhash64hashes identically to Spark: boolean, integral, floating-point,DecimalTypewith precision <= 18, date/time, default-collation (UTF8_BINARY) string, and binary. Wider decimals (hashed throughBigDecimal) and collated strings (hashed via the collation sort key) fall back to Spark.Spark 3.5.8 (2026-07-03): algorithm and tables identical to 3.4.3.
Spark 4.0.1 (2026-07-03):
HyperLogLogPlusPlusHelpermoved tocatalyst.utilandXxHash64Function.hashgained collation parameters, but for the defaultUTF8_BINARYcollation and non-string types the hash value is unchanged, so results match 3.4.3.Spark 4.1.1 (2026-07-03): identical to 4.0.1.
any#
Spark 3.4.3 (audited 2026-05-26): registered as a SQL alias of
BoolOr, which extendsRuntimeReplaceableAggregatewithreplacement = Max(child). Catalyst rewritesany(x)tomax(x)before Comet sees the plan, soanyis served byCometMaxon aBooleanTypecolumn.Spark 3.5.8 (audited 2026-05-26): identical to 3.4.3.
Spark 4.0.1 (audited 2026-05-26): identical to 3.4.3.
approx_percentile#
Spark 3.4.3, 3.5.8, 4.0.1, 4.1.1 (audited 2026-07-02):
ApproximatePercentile(child, percentageExpression, accuracyExpression)is aTypedImperativeAggregatebacked by a Greenwald-KhannaPercentileDigestquantile summary with relative error1.0 / accuracy.childacceptsNumericType,DateType,TimestampType,TimestampNTZType, and interval types (all cast todoubleinternally);percentageis a single literal or literal array in[0.0, 1.0];accuracyis a positive literal (default 10000). NULL inputs are skipped; an empty or all-null group returns NULL.approx_percentileis a SQL alias for the primary function namepercentile_approx.CometApproxPercentilemaps the byte, short, int, long, float, and double input forms to a native Greenwald-Khanna quantile summary port with the same insert/compress/merge/query algorithm and relative error, casting the result back to the input type.percentageandaccuracymust be foldable literals, matching Spark. Date, timestamp, interval, and decimal inputs fall back to Spark.
avg#
Spark 3.4.3 (2026-05-26)
Spark 3.5.8 (2026-05-26): aggregate logic identical to 3.4.3
Spark 4.0.1 (2026-05-26): aggregate logic identical to 3.5.8; only
QueryContextimport path differs.YearMonthIntervalTypeandDayTimeIntervalTypeinputs (supported by Spark) fall back to Spark in Comet.
bit_and#
Spark 3.4.3 (2026-05-26)
Spark 3.5.8 (2026-05-26)
Spark 4.0.1 (2026-05-26)
collect_list#
Spark 3.4.3 (audited 2026-06-24):
CollectListextendsCollect[ArrayBuffer[Any]], returnsArrayType(child.dataType, containsNull = false), ignores NULL inputs inupdate()(Hive-compatible semantics), and yields an empty array asdefaultResult.nullable = false. NocheckInputDataTypesoverride, so any input type is accepted (including STRUCT, ARRAY, MAP). Registered as bothcollect_listandarray_aggaliases inFunctionRegistry.Spark 3.5.8 (audited 2026-06-24): identical to 3.4.3.
Spark 4.0.1 (audited 2026-06-24): only structural change is adding
with UnaryLike[Expression]to the case class (no behavior change).Spark 4.1.1 (audited 2026-06-24): identical to 4.0.1.
Comet implementation:
CometCollectList(native/spark-expr/src/agg_funcs/collect.rs) delegates the ungrouped path todatafusion_spark::function::aggregate::collect::SparkCollectList, which wrapsArrayAggAccumulatorwithignore_nulls = trueand converts a final NULL accumulator state to an empty array (matching Spark’sdefaultResult); grouped aggregation uses Comet’s ownGroupsAccumulator, which retains the input arrays and gathers them into group order on emit. The native return type isList(Field, containsNull = true), while Spark usescontainsNull = false. Because nulls are filtered before insertion, no nulls actually appear in the array, so this is a schema-shape difference only and tests usingcheckSparkAnswerAndOperatoraccept it (the same pattern applies to collect_set).Buffer shape (applies equally to
collect_set): both areTypedImperativeAggregates, so Spark’saggBufferAttributesdeclares the intermediate buffer asBinaryType(serialized state) while the native accumulator’sstate_fieldsis aList.CometBaseAggregate.adjustOutputForNativeStaterewrites the Comet-side Partial output to the list shape. Neither collector can split a Partial and Final across Comet and Spark, and a multi-stage distinct rewrite (which inserts aPartialMergestage) forces the whole chain back to Spark (#4724).Performance (tuned 2026-09-09, PR #5803): grouped
collect_listused to run through DataFusion’sGroupsAccumulatorAdapter, which keeps one boxedAccumulatorper group and slices every batch into a per-groupupdate_batchcall.CollectListGroupsAccumulatorinstead records a(group, row range)contribution per batch and rearranges them with a counting sort on emit, gathering long runs withconcatand scattered rows withinterleave. 16-99% faster (#5797). Benchmark:benches/collect.rs.Spark 4.2 (preview):
CollectListandCollectSetgain anignoreNullsfield (defaulttrue);RESPECT NULLSsets it tofalseand keeps null elements. The native path always drops nulls, soCometCollectShimreads the field per Spark version (alwaystrueon 3.4-4.1) andCometCollectList/CometCollectSetreportUnsupportedwhen it isfalse, falling back to Spark.
collect_set#
Spark 3.4.3 (audited 2026-07-27):
CollectSetextendsCollect[mutable.HashSet[Any]], returnsArrayType(child.dataType, containsNull = false), ignores NULL inputs inupdate()(the same Hive-compatible semantics ascollect_list), and yields an empty array asdefaultResult.nullable = false. UnlikeCollectListit overridescheckInputDataTypesand rejects any input whose type recursively contains aMapType(UNSUPPORTED_INPUT_TYPE).convertToBufferElementcopies the value withInternalRow.copyValue, except forBinaryType, which is wrapped in anUnsafeArrayDataso that byte arrays dedup by content rather than by identity. Deduplication is Scalamutable.HashSetequality on the boxed value, which for floating-point types is numeric==: repeatedNaNs are each kept as separate elements, while0.0and-0.0collapse to one. Registered only ascollect_setinFunctionRegistry(there is no second alias, unlikecollect_list/array_agg).Spark 3.5.8 (audited 2026-07-27): identical to 3.4.3.
Spark 4.0.1 (audited 2026-07-27): adds
with UnaryLike[Expression]to the case class, andcheckInputDataTypesadditionally requiresUnsafeRowUtils.isBinaryStable(child.dataType), so non-default-collation strings are rejected along with maps. Deduplication semantics unchanged.Spark 4.1.1 (audited 2026-07-27): identical to 4.0.1.
Comet implementation:
CometCollectSet(native/spark-expr/src/agg_funcs/collect.rs) delegates the ungrouped path todatafusion_spark::function::aggregate::collect::SparkCollectSet, which wrapsDistinctArrayAggAccumulatorwithignore_nulls = truein aNullToEmptyListAccumulatorso a final NULL accumulator state becomes an empty array; grouped aggregation uses Comet’s ownGroupsAccumulator, which keeps the distinct values row-encoded in one arena keyed by(group, value). Deduplication is stillarrow::rowencoded-byte equality, so which values collapse together is unchanged; the emitted order is now insertion order rather than hash-table order. ThecontainsNullmismatch against Spark’s declared output type, and its rationale, are identical to collect_list.Performance (tuned 2026-09-09, PR #5803): as for collect_list, grouped
collect_setno longer goes throughGroupsAccumulatorAdapter.CollectSetGroupsAccumulatorencodes each batch once for all of its groups and deduplicates against an open-addressed index over a shared arena, replacing one hash table plus one ownedRowper distinct value per group. 60-93% faster (#5797). Benchmark:benches/collect.rs.CometCollectSetreportsIncompatiblefor float and double input whenspark.comet.exec.strictFloatingPoint=true, because the native distinct comparison treatsNaN == NaNand collapses repeatedNaNs into a single element while Spark keeps each one. The native path for floating-point input is then opt-in viaspark.comet.expression.CollectSet.allowIncompatible=true. All other input types areCompatible.
max_by#
Spark 3.4.3 (2026-07-03):
MaxByis a 2-argumentDeclarativeAggregateregistered asexpression[MaxBy]("max_by"). Buffer is(valueWithExtremumOrdering, extremumOrdering); null orderings are ignored, the value paired with the maximum ordering is returned (and may itself be null), and an all-null-ordering group yields null. Comet implements a nativemax_byaggregate. Only fixed-length value and ordering types are accelerated: a variable-length or nested type (string, binary, struct) falls back to Spark. On its own such a type never reaches Comet, because Spark plans it asSortAggregate, which Comet does not convert. The serde check still matters when aTypedImperativeAggregatein the same aggregate switches Spark toObjectHashAggregate, since Arrow’s row format would compare a string ordering as raw UTF-8 bytes where Spark compares collation sort keys.max_byis non-deterministic when several rows tie on the maximum ordering, matching Spark’s documented behavior.Spark 3.5.8 (2026-07-03): aggregate logic identical to 3.4.3.
Spark 4.0.1 (2026-07-03): aggregate logic identical to 3.4.3; only the
@ExpressionDescriptionexample and note text differ.Spark 4.1.1 (2026-07-03): aggregate logic identical to 3.4.3. The 3-argument top-k form
max_by(x, y, k)arrived in Spark 4.2 (MaxMinByK.scala, absent onbranch-4.1), so it is absent from 3.4 through 4.1 and present on the 4.2 profile this repo builds.MaxByBuilder.buildstill returns a plainMaxByfor the 2-argument call, and the 3-argument call becomesMaxMinByK, a different class with no serde registration, so Comet handles only the 2-argument form and the top-k form falls back.
median#
Spark 3.4.3 (audited 2026-06-24):
Median(child)is aRuntimeReplaceableAggregatewithreplacement = Percentile(child, Literal(0.5)). Catalyst rewritesmedian(x)topercentile(x, 0.5)before Comet sees the plan, so it is served byCometPercentile.Spark 3.5.8 (audited 2026-06-24): identical to 3.4.3.
Spark 4.0.1 (audited 2026-06-24):
replacementbecomeslazy val; semantics unchanged.Spark 4.1.1 (audited 2026-06-24): identical to 4.0.1.
min_by#
Spark 3.4.3 (2026-07-03):
MinByshares the abstractMaxMinByDeclarativeAggregatewithMaxBy, differing only in the comparison direction (least/<instead ofgreatest/>). Registered asexpression[MinBy]("min_by"). Null orderings are ignored, the value paired with the minimum ordering is returned (and may itself be null), and an all-null-ordering group yields null. Comet serves it through the same nativeMaxMinByaggregate asmax_by, with the same fixed-length value and ordering restriction (variable-length or nested types fall back to Spark). Non-deterministic on ties, matching Spark.Spark 3.5.8 (2026-07-03): aggregate logic identical to 3.4.3.
Spark 4.0.1 (2026-07-03): aggregate logic identical to 3.4.3; only the
@ExpressionDescriptionexample and note text differ.Spark 4.1.1 (2026-07-03): aggregate logic identical to 3.4.3. The 3-argument top-k form
min_by(x, y, k)arrived in Spark 4.2 alongsidemax_by(x, y, k);MinByBuilder.buildreturns a plainMinByfor the 2-argument call, so Comet handles only the 2-argument form and the top-k form falls back.
percentile#
Spark 3.4.3 (audited 2026-06-24):
Percentile(child, percentageExpression, frequencyExpression, ..., reverse)overPercentileBase. Exact percentile usingindex = p * (n - 1)linear interpolation, NULL inputs skipped, empty/all-null group returns NULL.CometPercentilemaps the single-literal-percentage, default-frequency, numeric-input, ascending form to DataFusion’spercentile_cont(same interpolation). Array-of-percentages, a non-default frequency argument, descending order, and interval inputs fall back to Spark.Spark 3.5.8 (audited 2026-06-24): ordering centralized via
PhysicalDataType.ordering; behavior identical to 3.4.3.Spark 4.0.1 (audited 2026-06-24): adds
PercentileCont/PercentileDiscbuilders andSupportsOrderingWithinGroup, enablingpercentile_cont(p) WITHIN GROUP (ORDER BY col), which rewrites toPercentile(col, p, reverse). The ascending form runs natively; theDESCform setsreverse = trueand falls back to Spark because the nativepercentile_contalways interpolates in ascending order.Spark 4.1.1 (audited 2026-06-24): identical to 4.0.1.
CometPercentilereportsCompatiblefor the single-literal-percentage, default-frequency, numeric-input, ascending form and runs it natively by default. Every other form isUnsupportedand falls back to Spark.