Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
51 changes: 50 additions & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,53 @@ This project includes code from Apache Aurora.

Copyright: 2016 The Apache Software Foundation.
Home page: https://aurora.apache.org/
License: http://www.apache.org/licenses/LICENSE-2.0
License: http://www.apache.org/licenses/LICENSE-2.0

--------------------------------------------------------------------------------

This project includes Unicode 17 lowercase mapping additions from ICU 78.3 in
native/spark-expr/src/array_funcs/array_extrema.rs. The source data is available at
https://github.com/unicode-org/icu/blob/21d1eb0f306e1141c10931e914dfc038c06121da/icu4c/source/data/unidata/UnicodeData.txt
and is distributed under the following Unicode License V3.

UNICODE LICENSE V3

COPYRIGHT AND PERMISSION NOTICE

Copyright © 2016-2025 Unicode, Inc.

NOTICE TO USER: Carefully read the following legal agreement. BY
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.

Permission is hereby granted, free of charge, to any person obtaining a
copy of data files and any associated documentation (the "Data Files") or
software and any associated documentation (the "Software") to deal in the
Data Files or Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, and/or sell
copies of the Data Files or Software, and to permit persons to whom the
Data Files or Software are furnished to do so, provided that either (a)
this copyright and permission notice appear with all copies of the Data
Files or Software, or (b) this copyright and permission notice appear in
associated Documentation.

THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
THIRD PARTY RIGHTS.

IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
FILES OR SOFTWARE.

Except as contained in this notice, the name of a copyright holder shall
not be used in advertising or otherwise to promote the sale, use or other
dealings in these Data Files or Software without prior written
authorization of the copyright holder.

SPDX-License-Identifier: Unicode-3.0
20 changes: 10 additions & 10 deletions docs/source/contributor-guide/expression-audits/array_funcs.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,19 +91,19 @@

## array_max

- Spark 3.4.3 (audited 2026-05-27): identical to 3.5.8.
- Spark 3.5.8 (audited 2026-05-27): baseline. `ArrayMax(child) extends UnaryExpression with ImplicitCastInputTypes`; skips NULL elements; for float/double Spark's `SQLOrderingUtil` treats NaN as greater than any non-NaN. Wired as `CometScalarFunction("array_max")`.
- Spark 4.0.1 (audited 2026-05-27): `NullIntolerant` -> `nullIntolerant` field refactor.
- Spark 4.1.1 (audited 2026-05-27): identical to 4.0.1.
- Float/double arrays containing NaN match Spark: NaN is treated as greater than any non-NaN value.
- Spark 3.4.3 (audited 2026-08-22): identical to 3.5.8.
- Spark 3.5.8 (audited 2026-08-22): `ArrayMax` skips NULL elements and returns NULL for an empty or all-NULL array. `SQLOrderingUtil` treats all NaNs as equal and greater than non-NaN values, and signed zeros as equal. The first equal maximum is retained. Nested arrays and structs compare lexicographically, with NULL fields or elements ordered first.
- Spark 4.0.1 (audited 2026-08-22): `NullIntolerant` becomes a `nullIntolerant` field. Extrema semantics are unchanged; string ordering can use non-default collations.
- Spark 4.1.1 (audited 2026-08-22): identical to 4.0.1.
- Current status: `CometArrayMax` uses the native `SparkArrayExtrema` UDF. Typed float/double scans and recursive array/struct comparisons follow Spark's ordering and preserve the original first equal element, including its zero sign and NaN representation. Both floating-point modes run natively. String comparisons support `UTF8_BINARY`, `UTF8_BINARY_RTRIM`, `UTF8_LCASE`, and `UTF8_LCASE_RTRIM`, including nested fields; LCASE requires Spark's Unicode version to be 16 or 17. Other scalar element types retain the existing DataFusion implementation. Unsupported collations use Spark's JVM codegen dispatcher by default, or fall back to Spark when it is disabled, unless incompatible native execution is explicitly enabled ([#4496](https://github.com/apache/datafusion-comet/issues/4496)). Input casts retain their existing support boundaries.

## array_min

- Spark 3.4.3 (audited 2026-05-27): identical to 3.5.8.
- Spark 3.5.8 (audited 2026-05-27): mirror of `ArrayMax` with `evalInternal` returning the minimum. Same NULL-skip and NaN-ordering semantics. Wired as `CometScalarFunction("array_min")`.
- Spark 4.0.1 (audited 2026-05-27): same trait refactor as `array_max`.
- Spark 4.1.1 (audited 2026-05-27): identical to 4.0.1.
- Float/double arrays containing NaN match Spark, mirroring `array_max`.
- Spark 3.4.3 (audited 2026-08-22): identical to 3.5.8.
- Spark 3.5.8 (audited 2026-08-22): mirrors `ArrayMax`, retaining the first equal minimum. The NULL, NaN, signed-zero, and nested comparison rules are the same.
- Spark 4.0.1 (audited 2026-08-22): same trait refactor and collation support as `array_max`, with no change in floating-point extrema semantics.
- Spark 4.1.1 (audited 2026-08-22): identical to 4.0.1.
- Current status: `CometArrayMin` shares the native `SparkArrayExtrema` implementation and collation support boundary with `array_max`. Both floating-point modes use Spark-compatible native ordering, preserving the original first equal minimum. Supported UTF8 collations run natively; unsupported collations use the same JVM dispatch and fallback as `array_max` ([#4496](https://github.com/apache/datafusion-comet/issues/4496)).

## array_position

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,11 @@ under the License.

# Array Expressions

`array_min` and `array_max` execute `UTF8_BINARY`, `UTF8_BINARY_RTRIM`, `UTF8_LCASE`,
and `UTF8_LCASE_RTRIM` comparisons natively, including strings nested in arrays and structs.
They return the original winning value and retain the first value when comparisons are equal.
`UTF8_LCASE` uses the Unicode 16 or 17 rules from Spark's ICU runtime; other Unicode versions
and ICU locale-sensitive collations use the JVM codegen dispatcher.

<!--BEGIN:EXPR_COMPAT[array]-->
<!--END:EXPR_COMPAT-->
7 changes: 7 additions & 0 deletions docs/source/user-guide/latest/compatibility/floating-point.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,10 @@ longer forces a fallback for them: scalar `FLOAT` and `DOUBLE` sort keys, window
keys, and range partitioning keys all stay native under strict mode. Floating-point values nested
in arrays, structs, or maps still fall back under strict mode, because their ordering is the raw
total ordering described above.

`array_min` and `array_max` use Spark-compatible native comparisons in both strict and non-strict
floating-point modes. Signed zeros compare equal, and all NaN representations compare equal and
greater than non-NaN values. The original first equal element is retained: for example,
`array_min(array(0.0D, -0.0D))` returns `0.0`, while reversing those elements returns `-0.0`.
The same ordering applies recursively to floating-point fields in arrays and structs. These
expressions do not require Spark's codegen dispatcher for floating-point compatibility.
4 changes: 2 additions & 2 deletions docs/source/user-guide/latest/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ The tables below list every Spark built-in expression with its current status.
| `array_insert` | ✅ | Native | |
| `array_intersect` | ✅ | Hybrid | Routes through the JVM codegen dispatcher by default; the incompatible native path is opt-in via allowIncompatible ([details](compatibility/expressions/array.md)) |
| `array_join` | ✅ | Hybrid | Native for literal or column delimiter and null replacement; other cases and non-UTF8_BINARY collations use the JVM codegen dispatcher ([details](compatibility/expressions/array.md)) |
| `array_max` | ✅ | Native | NaN ordering may differ ([details](compatibility/floating-point.md)) |
| `array_min` | ✅ | Native | NaN ordering may differ ([details](compatibility/floating-point.md)) |
| `array_max` | ✅ | Hybrid | Native Spark-compatible floating-point, nested, and UTF8_BINARY/UTF8_LCASE ordering, including RTRIM; ICU collations use the JVM codegen dispatcher ([details](compatibility/expressions/array.md)) |
| `array_min` | ✅ | Hybrid | Native Spark-compatible floating-point, nested, and UTF8_BINARY/UTF8_LCASE ordering, including RTRIM; ICU collations use the JVM codegen dispatcher ([details](compatibility/expressions/array.md)) |
| `array_position` | ✅ | Native | Binary/struct/map/null elements fall back |
| `array_prepend` | ✅ | — | |
| `array_remove` | ✅ | Native | |
Expand Down
7 changes: 7 additions & 0 deletions native/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions native/core/src/execution/jni_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2101,6 +2101,7 @@ mod tests {
}],
return_type: None,
fail_on_error: false,
..Default::default()
})),
query_context: None,
expr_id: None,
Expand Down
26 changes: 18 additions & 8 deletions native/core/src/execution/planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ use datafusion::{
use datafusion_comet_spark_expr::{
create_comet_physical_fun, create_comet_physical_fun_with_eval_mode, BinaryOutputStyle,
BloomFilterAgg, BloomFilterMightContain, CometCollectList, CometCollectSet, CsvWriteOptions,
EvalMode, SparkArraysZipFunc, SparkBloomFilterVersion, SparkListAgg, SparkPercentile,
SumInteger, ToCsv,
EvalMode, SparkArrayExtrema, SparkArraysZipFunc, SparkBloomFilterVersion, SparkListAgg,
SparkPercentile, SumInteger, ToCsv,
};
use iceberg::expr::Bind;

Expand Down Expand Up @@ -3772,12 +3772,20 @@ impl PhysicalPlanner {
}
};

let fun_expr = create_comet_physical_fun(
fun_name,
data_type.clone(),
&self.session_ctx.state(),
Some(expr.fail_on_error),
)?;
let fun_expr = if matches!(fun_name.as_str(), "array_min" | "array_max") {
Arc::new(ScalarUDF::from(SparkArrayExtrema::with_collations(
fun_name == "array_min",
&expr.string_collations,
expr.collation_unicode_version,
)?))
} else {
create_comet_physical_fun(
fun_name,
data_type.clone(),
&self.session_ctx.state(),
Some(expr.fail_on_error),
)?
};

let args = args
.into_iter()
Expand Down Expand Up @@ -6501,6 +6509,7 @@ mod tests {
args: vec![array_col, array_col_1],
return_type: None,
fail_on_error: false,
..Default::default()
})),
query_context: None,
expr_id: None,
Expand Down Expand Up @@ -6627,6 +6636,7 @@ mod tests {
args: vec![array_col, array_col_1],
return_type: None,
fail_on_error: false,
..Default::default()
})),
query_context: None,
expr_id: None,
Expand Down
4 changes: 4 additions & 0 deletions native/proto/src/proto/expr.proto
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,10 @@ message ScalarFunc {
repeated Expr args = 2;
DataType return_type = 3;
bool fail_on_error = 4;
// array_min/max only: collations of every string leaf in the element type,
// visiting array elements and struct fields depth-first. Empty means binary ordering.
repeated string string_collations = 5;
uint32 collation_unicode_version = 6;
}

message CaseWhen {
Expand Down
6 changes: 6 additions & 0 deletions native/spark-expr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ base64 = "0.23.0"
# float/double to decimal conversion
ryu = "1.0"
uuid = "1.23.3"
# Pin Unicode 16 for Spark collation compatibility; newer tables can change ordering.
unicode-case-mapping = "=1.0.0"

[dev-dependencies]
arrow = {workspace = true}
Expand Down Expand Up @@ -259,6 +261,10 @@ harness = false
name = "arrays_overlap"
harness = false

[[bench]]
name = "array_extrema"
harness = false

[[bench]]
name = "checked_arithmetic"
harness = false
Expand Down
Loading
Loading