docs: document known correctness issues in the compatibility guide (#5085)
Adds compat notes and expression-serde `getCompatibleNotes` /
`getIncompatibleReasons` entries for each open `correctness`+`bug` issue,
so the auto-generated compatibility guide surfaces them:
- Expression-level (per-category pages):
- `Round` (Long/scale<=-19 overflow) #5070
- `IntegralDivide` (Long.MinValue div -1) #5065
- `MakeDecimal` (ignores fail_on_error) #5066
- `NextDay`, `MakeDate` (wrong ANSI exception class) #5073
- `MapFromArrays`, `MapFromEntries` (null key not rejected) #4680
- `GetJsonObject` (duplicate keys) #4947
- Static sections in `compatibility/index.md`:
- ANSI-mode error classes / messages (#5071, #5072, #5073, #4967)
- Known result-value divergences (boolean->decimal cast #5068,
string->boolean ISO controls #4959, RANGE window overflow #5022,
null IN () legacy behavior #4786)
- Object store cache key drops ABFS container #4993
- `compatibility/scans.md`: expand the datetime-rebase note (wider than
"written before Spark 3.0", `datetimeRebaseModeInRead` is not honoured,
`spark.comet.exceptionOnDatetimeRebase` is currently dead code) and
link the tracker #5010.
Not covered here (already handled elsewhere):
- #5067 (Remainder Float/Double NaN) - fixed by 9bd5a6d05
- #4764 (EPIC UTF-8 ingress) - already documented in index.mddiff --git a/docs/source/user-guide/latest/compatibility/index.md b/docs/source/user-guide/latest/compatibility/index.md
index b08d9a3..95f496b 100644
--- a/docs/source/user-guide/latest/compatibility/index.md
+++ b/docs/source/user-guide/latest/compatibility/index.md
@@ -102,3 +102,59 @@
Separately, Comet's native Parquet scan currently rejects string columns whose stored bytes are not
valid UTF-8 rather than reading them like Spark
([#4121](https://github.com/apache/datafusion-comet/issues/4121)).
+
+## ANSI-mode error classes and messages
+
+Under `spark.sql.ansi.enabled=true`, several native error paths raise the error at the correct
+input but with a different exception type, error class, SQLSTATE, or message text than Spark.
+Code that catches `SparkException` and only asserts on message substrings is unaffected; code that
+inspects the exception class, `getCondition()`, or the parameterised error class will observe
+divergence:
+
+- Byte / Short `Add`, `Subtract`, and `Multiply` overflow raises `ARITHMETIC_OVERFLOW` where Spark
+ 4.1 raises `BINARY_ARITHMETIC_OVERFLOW`, and Long overflow surfaces as `"integer overflow"`
+ rather than `"long overflow"`. `Abs` uses Rust type names (`Int8`, `Int64`, ...) in the message
+ instead of Spark's SQL type names, and the scalar path of `UnaryMinus` on Byte / Short emits a
+ malformed message. The `try_` suggestion is omitted from all of these
+ ([#5071](https://github.com/apache/datafusion-comet/issues/5071)).
+- Wide-decimal arithmetic overflow, decimal divide-by-zero, and decimal-to-decimal cast overflow
+ raise raw Arrow errors that bypass `SparkErrorConverter` and surface as `CometNativeException`
+ rather than `SparkArithmeticException` with the proper error class and query context
+ ([#5072](https://github.com/apache/datafusion-comet/issues/5072)).
+- `next_day` and `make_date` throw at the correct inputs but surface as `CometNativeException`
+ instead of `SparkIllegalArgumentException [ILLEGAL_DAY_OF_WEEK]` /
+ `SparkDateTimeException [DATETIME_FIELD_OUT_OF_BOUNDS.WITH_SUGGESTION]`
+ ([#5073](https://github.com/apache/datafusion-comet/issues/5073)).
+- Spark 4.2 introduced additional ANSI arithmetic overflow behavior differences that Comet does
+ not yet track ([#4967](https://github.com/apache/datafusion-comet/issues/4967)).
+
+## Known result-value divergences
+
+The following native paths silently return values that differ from Spark for edge-case inputs.
+Most also have entries in the per-category expression pages linked above; they are collected here
+so users hunting an unexpected value have a single place to check:
+
+- `CAST(boolean AS DECIMAL(p, s))` where `10^s` exceeds the target precision (e.g.
+ `DECIMAL(1, 1)`) throws `NUMERIC_VALUE_OUT_OF_RANGE` regardless of the eval mode. Spark returns
+ `NULL` under legacy and try mode, and only throws under ANSI
+ ([#5068](https://github.com/apache/datafusion-comet/issues/5068)).
+- `CAST(string AS boolean)` trims only Unicode whitespace. Spark also trims the ASCII control
+ bytes `0x00-0x08`, `0x0E-0x1F`, and `0x7F`, so a string like `"true"` casts to
+ `true` in Spark and to `NULL` in Comet (or throws under ANSI)
+ ([#4959](https://github.com/apache/datafusion-comet/issues/4959)).
+- Native `RANGE` window frames with an explicit `PRECEDING` / `FOLLOWING` offset diverge from
+ Spark when the boundary arithmetic overflows for `DATE` or `DECIMAL` `ORDER BY` columns
+ ([#5022](https://github.com/apache/datafusion-comet/issues/5022)).
+- For an empty `IN` list, Comet always returns `false` for a `NULL` operand. Spark returns `NULL`
+ when `spark.sql.legacy.nullInEmptyListBehavior=true` (Spark 4.0+)
+ ([#4786](https://github.com/apache/datafusion-comet/issues/4786)).
+
+## Object store cache
+
+When Comet's native scan reads Parquet files, it caches one object store instance per
+`(scheme + host + port, hadoop-config-hash)` key. For `abfss://container@account.dfs.core.windows.net/...`
+URLs, the container lives in the URL userinfo, not the host, so two containers in one storage
+account currently collide on the same cache entry. Within a single executor process, reading from
+a second container after a first can be served by the first container's store instance and return
+its data. S3, GCS, and HDFS are unaffected because their bucket / host lives in the URL host
+component. Tracked by [#4993](https://github.com/apache/datafusion-comet/issues/4993).
diff --git a/docs/source/user-guide/latest/compatibility/scans.md b/docs/source/user-guide/latest/compatibility/scans.md
index b7dca43..0c07b5c 100644
--- a/docs/source/user-guide/latest/compatibility/scans.md
+++ b/docs/source/user-guide/latest/compatibility/scans.md
@@ -51,10 +51,16 @@
The following limitation may produce incorrect results without falling back to Spark:
-- No support for datetime rebasing. When reading Parquet files containing dates or timestamps written before
- Spark 3.0 (which used a hybrid Julian/Gregorian calendar), dates/timestamps will be read as if they were
- written using the Proleptic Gregorian calendar. This may produce incorrect results for dates before
- October 15, 1582.
+- No support for datetime rebasing. When reading Parquet files containing dates or timestamps
+ written with `spark.sql.parquet.datetimeRebaseModeInWrite=LEGACY` (which is Spark's default for
+ data written before Spark 3.0, using the hybrid Julian/Gregorian calendar), Comet reads them as
+ if they were written using the Proleptic Gregorian calendar. This produces silently-wrong
+ values for dates before October 15, 1582 in both projections and predicates. Comet also
+ ignores `spark.sql.parquet.datetimeRebaseModeInRead` and the file-level
+ `org.apache.spark.legacyDateTime` metadata that would tell it to rebase. The
+ `spark.comet.exceptionOnDatetimeRebase` config is currently dead code and does not raise on
+ legacy-calendar data. Tracked by
+ [#5010](https://github.com/apache/datafusion-comet/issues/5010).
The following limitations raise an error at scan time rather than falling back to Spark:
diff --git a/spark/src/main/scala/org/apache/comet/serde/arithmetic.scala b/spark/src/main/scala/org/apache/comet/serde/arithmetic.scala
index 6d1e230..e67d4f6 100644
--- a/spark/src/main/scala/org/apache/comet/serde/arithmetic.scala
+++ b/spark/src/main/scala/org/apache/comet/serde/arithmetic.scala
@@ -291,6 +291,12 @@
object CometIntegralDivide extends CometExpressionSerde[IntegralDivide] with MathBase {
+ override def getCompatibleNotes(): Seq[String] = Seq(
+ "On `LongType` input, `Long.MinValue div -1` does not overflow-check: Comet's decimal-backed" +
+ " path returns the wrapped value `Long.MinValue` under legacy mode and silently returns" +
+ " the same wrapped value under ANSI, where Spark raises `ARITHMETIC_OVERFLOW`" +
+ " ([#5065](https://github.com/apache/datafusion-comet/issues/5065)).")
+
override def getSupportLevel(expr: IntegralDivide): SupportLevel =
mathDataTypeSupportLevel(expr.left.dataType)
@@ -375,6 +381,12 @@
object CometRound extends CometExpressionSerde[Round] {
+ override def getCompatibleNotes(): Seq[String] = Seq(
+ "On `LongType` input with a negative `scale` of `-19` or lower, `10^(-scale)` overflows the" +
+ " native integer type and Comet returns `0` instead of Spark's overflowed value. Under" +
+ " ANSI mode Spark raises `ARITHMETIC_OVERFLOW` at the same input; Comet still returns `0`" +
+ " ([#5070](https://github.com/apache/datafusion-comet/issues/5070)).")
+
override def getSupportLevel(expr: Round): SupportLevel = expr.child.dataType match {
case t: DecimalType if t.scale < 0 => // Spark disallows negative scale SPARK-30252
Unsupported(Some("Decimal type has negative scale"))
diff --git a/spark/src/main/scala/org/apache/comet/serde/datetime.scala b/spark/src/main/scala/org/apache/comet/serde/datetime.scala
index d968c00..a2600bf 100644
--- a/spark/src/main/scala/org/apache/comet/serde/datetime.scala
+++ b/spark/src/main/scala/org/apache/comet/serde/datetime.scala
@@ -450,6 +450,12 @@
override def getIncompatibleReasons(): Seq[String] =
DatetimeCollation.incompatibleReasons("next_day")
+ override def getCompatibleNotes(): Seq[String] = Seq(
+ "Under ANSI mode, an invalid `dayOfWeek` surfaces as `CometNativeException` rather than" +
+ " Spark's `SparkIllegalArgumentException` with error class `ILLEGAL_DAY_OF_WEEK`. The" +
+ " throw/NULL decision is correct; only the exception class and error class differ" +
+ " ([#5073](https://github.com/apache/datafusion-comet/issues/5073)).")
+
override def getSupportLevel(expr: NextDay): SupportLevel = {
if (DatetimeCollation.hasNonDefaultCollation(expr)) {
Incompatible(Some(collationReason))
@@ -475,6 +481,14 @@
* `(year, month, day)` triple rather than returning NULL. The resolved flag is passed to native
* via the `ScalarFunc.fail_on_error` field.
*/
+
+ override def getCompatibleNotes(): Seq[String] = Seq(
+ "Under ANSI mode, an out-of-range `(year, month, day)` triple surfaces as" +
+ " `CometNativeException` rather than Spark's `SparkDateTimeException` with error class" +
+ " `DATETIME_FIELD_OUT_OF_BOUNDS.WITH_SUGGESTION`. The throw/NULL decision is correct;" +
+ " only the exception class and error class differ" +
+ " ([#5073](https://github.com/apache/datafusion-comet/issues/5073)).")
+
override def convert(expr: MakeDate, inputs: Seq[Attribute], binding: Boolean): Option[Expr] = {
val childExpr = expr.children.map(exprToProtoInternal(_, inputs, binding))
val optExpr = scalarFunctionExprToProtoWithReturnType(
diff --git a/spark/src/main/scala/org/apache/comet/serde/decimalExpressions.scala b/spark/src/main/scala/org/apache/comet/serde/decimalExpressions.scala
index f41e0fa..3af32e1 100644
--- a/spark/src/main/scala/org/apache/comet/serde/decimalExpressions.scala
+++ b/spark/src/main/scala/org/apache/comet/serde/decimalExpressions.scala
@@ -41,6 +41,14 @@
override def getUnsupportedReasons(): Seq[String] = Seq("Only `LongType` input is supported")
+ override def getCompatibleNotes(): Seq[String] = Seq(
+ "The native implementation silently returns `NULL` on overflow regardless of the" +
+ " `nullOnOverflow` flag, so under ANSI mode Comet returns `NULL` where Spark raises" +
+ " `NUMERIC_VALUE_OUT_OF_RANGE`. `MakeDecimal` is inserted by the `DecimalAggregates`" +
+ " optimizer rule, so this affects `sum`/`avg` over low-precision decimals when the" +
+ " unscaled long overflows the target precision" +
+ " ([#5066](https://github.com/apache/datafusion-comet/issues/5066)).")
+
override def getSupportLevel(expr: MakeDecimal): SupportLevel = {
expr.child.dataType match {
case LongType => Compatible()
diff --git a/spark/src/main/scala/org/apache/comet/serde/maps.scala b/spark/src/main/scala/org/apache/comet/serde/maps.scala
index c830444..817792e 100644
--- a/spark/src/main/scala/org/apache/comet/serde/maps.scala
+++ b/spark/src/main/scala/org/apache/comet/serde/maps.scala
@@ -81,6 +81,12 @@
s"`${SQLConf.MapKeyDedupPolicy.LAST_WIN}`; Comet's native map construction " +
"does not implement LAST_WIN dedup semantics."
+ val nullKeyReason: String =
+ "Spark rejects a `NULL` element inside the keys array with a `RuntimeException`" +
+ " (`Cannot use null as map key`); Comet's native `map_from_arrays` / `map_from_entries`" +
+ " does not detect a per-element `NULL` key and produces a map with a `NULL` key instead" +
+ " ([#4680](https://github.com/apache/datafusion-comet/issues/4680))."
+
def isLastWin: Boolean =
SQLConf.get
.getConf(SQLConf.MAP_KEY_DEDUP_POLICY)
@@ -93,6 +99,9 @@
override def getIncompatibleReasons(): Seq[String] =
Seq(MapKeyDedupPolicySupport.incompatibleReason)
+ override def getCompatibleNotes(): Seq[String] =
+ Seq(MapKeyDedupPolicySupport.nullKeyReason)
+
override def getSupportLevel(expr: MapFromArrays): SupportLevel = {
if (MapKeyDedupPolicySupport.isLastWin) {
Incompatible(Some(MapKeyDedupPolicySupport.incompatibleReason))
@@ -153,6 +162,9 @@
override def getIncompatibleReasons(): Seq[String] =
Seq(keyUnsupportedReason, valueUnsupportedReason, MapKeyDedupPolicySupport.incompatibleReason)
+ override def getCompatibleNotes(): Seq[String] =
+ Seq(MapKeyDedupPolicySupport.nullKeyReason)
+
override def getSupportLevel(expr: MapFromEntries): SupportLevel = {
if (SupportLevel.containsType(expr.dataType.keyType, classOf[BinaryType])) {
Incompatible(Some(keyUnsupportedReason))
diff --git a/spark/src/main/scala/org/apache/comet/serde/predicates.scala b/spark/src/main/scala/org/apache/comet/serde/predicates.scala
index c9a70a8..6a08f10 100644
--- a/spark/src/main/scala/org/apache/comet/serde/predicates.scala
+++ b/spark/src/main/scala/org/apache/comet/serde/predicates.scala
@@ -249,6 +249,11 @@
override def getUnsupportedReasons(): Seq[String] =
Seq(ComparisonUtils.nonDefaultCollationDocReason)
+ override def getCompatibleNotes(): Seq[String] = Seq(
+ "For an empty `IN` list, Comet always returns `false` for a `NULL` operand. Spark returns" +
+ " `NULL` when `spark.sql.legacy.nullInEmptyListBehavior=true` (Spark 4.0+)" +
+ " ([#4786](https://github.com/apache/datafusion-comet/issues/4786)).")
+
override def convert(
expr: In,
inputs: Seq[Attribute],
diff --git a/spark/src/main/scala/org/apache/comet/serde/strings.scala b/spark/src/main/scala/org/apache/comet/serde/strings.scala
index 1482d3e..7422dad 100644
--- a/spark/src/main/scala/org/apache/comet/serde/strings.scala
+++ b/spark/src/main/scala/org/apache/comet/serde/strings.scala
@@ -647,7 +647,10 @@
override def getIncompatibleReasons(): Seq[String] =
Seq(
"Spark allows single-quoted JSON and unescaped control characters" +
- " which Comet does not support")
+ " which Comet does not support",
+ "For JSON objects containing duplicate keys, Spark returns the value of the first" +
+ " occurrence while Comet's native implementation returns the last occurrence" +
+ " ([#4947](https://github.com/apache/datafusion-comet/issues/4947))")
override def getSupportLevel(expr: GetJsonObject): SupportLevel =
if (!CometConf.isExprAllowIncompat(getExprConfigName(expr))) {