Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
dfbf000
fix: dispatch map lookups with normalized keys and nondeterministic n…
dwsmith1983 Sep 11, 2026
bc28e6c
Merge remote-tracking branch 'origin/main' into fix/serde-map-keys-an…
dwsmith1983 Sep 14, 2026
e79fcf8
test: pin the dispatched LAST_WIN route and read arrays from columns …
dwsmith1983 Sep 14, 2026
d49a2fb
fix: decline a nondeterministic item in array_append, since the nativ…
dwsmith1983 Sep 14, 2026
dbe55ca
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 14, 2026
2ba1e19
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 15, 2026
aa9b242
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 16, 2026
53c793c
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 16, 2026
7c3012f
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 17, 2026
323ac4f
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 17, 2026
13cf97a
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 17, 2026
ca7e3c4
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 18, 2026
e78a725
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 18, 2026
8ac093e
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 19, 2026
3dc1db2
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 19, 2026
96c148f
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 20, 2026
35fb432
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 20, 2026
0409039
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 21, 2026
a3f474f
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 22, 2026
0d61cac
fix: route array_append through the dispatcher under ANSI and name th…
dwsmith1983 Sep 22, 2026
9dc235c
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 22, 2026
cfd5943
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 22, 2026
1d0f7c2
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 22, 2026
cf90636
Merge branch 'main' into fix/serde-map-keys-and-nondeterministic-chil…
dwsmith1983 Sep 23, 2026
3d29893
Merge remote-tracking branch 'origin/main' into fix/serde-map-keys-an…
dwsmith1983 Sep 23, 2026
1b15553
test: look up a real negative zero in the map fixtures
dwsmith1983 Sep 23, 2026
93ca0f0
Merge remote-tracking branch 'origin/main' into fix/serde-map-keys-an…
dwsmith1983 Sep 24, 2026
94f4f3c
Merge remote-tracking branch 'origin/main' into fix/serde-map-keys-an…
dwsmith1983 Sep 24, 2026
7b3de95
Merge remote-tracking branch 'origin/main' into fix/serde-map-keys-an…
dwsmith1983 Sep 24, 2026
ce0db9d
Merge remote-tracking branch 'origin/main' into fix/serde-map-keys-an…
dwsmith1983 Sep 24, 2026
7127245
Merge remote-tracking branch 'origin/main' into fix/serde-map-keys-an…
dwsmith1983 Sep 24, 2026
b5d55d3
Merge remote-tracking branch 'origin/main' into fix/serde-map-keys-an…
dwsmith1983 Sep 24, 2026
a9748de
Merge remote-tracking branch 'origin/main' into fix/serde-map-keys-an…
dwsmith1983 Sep 25, 2026
e40762b
Merge remote-tracking branch 'origin/main' into fix/serde-map-keys-an…
dwsmith1983 Sep 25, 2026
d9b3eb1
Merge remote-tracking branch 'origin/main' into fix/serde-map-keys-an…
dwsmith1983 Sep 25, 2026
8c25fa8
Merge remote-tracking branch 'origin/main' into fix/serde-map-keys-an…
dwsmith1983 Sep 25, 2026
e6bc4ae
Merge remote-tracking branch 'origin/main' into fix/serde-map-keys-an…
dwsmith1983 Sep 25, 2026
4202c20
Merge remote-tracking branch 'origin/main' into fix/serde-map-keys-an…
dwsmith1983 Sep 25, 2026
9ce7675
Merge remote-tracking branch 'origin/main' into fix/serde-map-keys-an…
dwsmith1983 Sep 26, 2026
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
5 changes: 3 additions & 2 deletions docs/source/contributor-guide/sql-file-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,8 +360,9 @@ common ones include:
- **Signed zero** -- Spark parses a bare `-0.0` as `decimal(1,1)`, which has no signed
zero, so coercion to `float`/`double` yields `+0.0`. `CAST(-0.0 AS DOUBLE)` and
`CAST(-0.0 AS FLOAT)` have the same problem because the cast source is still the
decimal literal. Use `double('-0.0')` or `float('-0.0')` (equivalently
`CAST('-0.0' AS DOUBLE)`). Spark's array comparator also treats `+0.0` and `-0.0` as
decimal literal. Use the double literal `-0.0D`, `CAST(-0.0D AS FLOAT)` for a float,
or `double('-0.0')` / `float('-0.0')` (equivalently `CAST('-0.0' AS DOUBLE)`), all of
which keep the sign. Spark's array comparator also treats `+0.0` and `-0.0` as
equal, so `sort_array(...)` is not a unique projection when both signs are present
(the SQL test comparator distinguishes the bits). Prefer a sign-aware form such as
`sort_array(transform(arr, x -> cast(x AS string)))`. A `query tolerance=...` check
Expand Down
14 changes: 7 additions & 7 deletions docs/source/user-guide/latest/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ The tables below list every Spark built-in expression with its current status.
| Function | Status | Implementation | Notes |
| --- | --- | --- | --- |
| `array` | ✅ | Native | |
| `array_append` | ✅ | Native | |
| `array_append` | ✅ | Hybrid | |
| `array_compact` | ✅ | — | |
| `array_contains` | ✅ | Native | Float/double element arrays route through the JVM codegen dispatcher by default; the native path is opt-in via allowIncompatible |
| `array_distinct` | ✅ | Native | NaN/signed-zero handling may differ ([details](compatibility/floating-point.md)) |
Expand All @@ -165,8 +165,8 @@ The tables below list every Spark built-in expression with its current status.
| `array_repeat` | ✅ | Native | |
| `array_union` | ✅ | Native | NaN/signed-zero handling may differ ([details](compatibility/floating-point.md)) |
| `arrays_overlap` | ✅ | Native | |
| `arrays_zip` | ✅ | Native | |
| `element_at` | ✅ | Native | |
| `arrays_zip` | ✅ | Hybrid | |
| `element_at` | ✅ | Hybrid | |
| `flatten` | ✅ | Native | Binary/struct/map elements fall back |
| `get` | ✅ | — | |
| `sequence` | ✅ | Hybrid | Integral types run natively; date/timestamp sequences use codegen dispatch |
Expand Down Expand Up @@ -200,10 +200,10 @@ The tables below list every Spark built-in expression with its current status.
| Function | Status | Implementation | Notes |
| --- | --- | --- | --- |
| `array_size` | ✅ | — | |
| `cardinality` | ✅ | Native | |
| `cardinality` | ✅ | Hybrid | |
| `concat` | ✅ | Hybrid | Binary/array children and non-UTF8_BINARY collations route through the JVM codegen dispatcher |
| `reverse` | ✅ | Hybrid | Arrays with binary, struct, or map elements, and collated strings, route through the JVM codegen dispatcher ([details](compatibility/expressions/array.md)) |
| `size` | ✅ | Native | |
| `size` | ✅ | Hybrid | |

---

Expand Down Expand Up @@ -399,12 +399,12 @@ to Spark ([#2837](https://github.com/apache/datafusion-comet/issues/2837)). Enab

| Function | Status | Implementation | Notes |
| --- | --- | --- | --- |
| `element_at` | ✅ | Native | |
| `element_at` | ✅ | Hybrid | |
| `map` | ✅ | Codegen dispatch | Routed through the JVM codegen dispatcher |
| `map_concat` | ✅ | Codegen dispatch | |
| `map_contains_key` | ✅ | — | |
| `map_entries` | ✅ | Native | |
| `map_from_arrays` | ✅ | Native | |
| `map_from_arrays` | ✅ | Hybrid | |
| `map_from_entries` | ✅ | Hybrid | BinaryType keys/values and `spark.sql.mapKeyDedupPolicy=LAST_WIN` route through the JVM codegen dispatcher ([details](compatibility/expressions/map.md)) |
| `map_keys` | ✅ | Native | |
| `map_values` | ✅ | Native | |
Expand Down
103 changes: 85 additions & 18 deletions spark/src/main/scala/org/apache/comet/serde/arrays.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,63 @@ object CometArrayRemove
}
}

object CometArrayAppend extends CometExpressionSerde[ArrayAppend] with ArraysBase {
/**
* Shared gate for serdes whose native NULL guard (`CASE WHEN child IS NOT NULL`) serializes the
* child twice: a stateful child drifts between the two copies, so it is declined and runs through
* the JVM codegen dispatcher, where Spark evaluates it once. Nullability is not consulted: a
* non-nullable stateful child only stays in step because DataFusion skips the filter when the
* guard matches every row, which is not a contract to lean on.
*/
private[serde] object NullGuardSupport {

val nondeterministicReason: String =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This renders in the generated compatibility guide right next to MapKeySupport's reasons, which are full sentences about what Spark does and what the native path does instead. This one is a lowercase fragment about how the serde is built, which reads oddly as a user-facing bullet. Would something like "Comet has no native path for a nondeterministic operand such as rand() or monotonically_increasing_id(), because the native NULL guard would evaluate it twice" sit better there?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would something like "Comet has no native path for a nondeterministic operand such as rand() or monotonically_increasing_id(), because the native NULL guard would evaluate it twice" sit better there?

Yes. That is the reason now, word for word apart from the code formatting, and since the string is shared it changes the bullet for all four serdes in 0d61cac.

"Comet has no native path for a nondeterministic operand such as `rand()` or " +
"`monotonically_increasing_id()`, because the native `NULL` guard would evaluate it twice."

/** `Unsupported` when any of `children` is nondeterministic, otherwise `None`. */
def nondeterministicChild(children: Seq[Expression]): Option[SupportLevel] =
children
.find(child => !child.deterministic)
.map(_ => Unsupported(Some(nondeterministicReason)))
}

object CometArrayAppend
extends CometExpressionSerde[ArrayAppend]
with ArraysBase
with CodegenDispatchFallback {

override def getUnsupportedReasons(): Seq[String] =
Seq(NullGuardSupport.nondeterministicReason)

private val ansiItemNote: String =
"With `spark.sql.ansi.enabled=true` and a nullable array, the native `NULL` guard skips " +
"the item on a row whose array is `NULL`, so an item that raises there (for example a " +
"division by zero) raises in Spark but not on the native path. Such an expression runs " +
"through the JVM codegen dispatcher by default; enabling the native path can swallow " +
"that error ([#6086](https://github.com/apache/datafusion-comet/issues/6086))."

override def getIncompatibleReasons(): Seq[String] = Seq(ansiItemNote)

// Only the ANSI nullable-array case is routed through the dispatcher; every other compatible
// instance runs natively by default.
override def hasConditionalNativeDefault: Boolean = true

// The item sits inside the guard's THEN branch, and DataFusion's CaseExpr evaluates that
// branch only on the rows the guard selects, while Spark's codegen evaluates the item on
// every row. A stateful item therefore drifts the same way a stateful array does, and is
// declined. The same shape lets an item that raises under ANSI mode go unevaluated on a row
// whose array is NULL, so Spark raises where the native path returns NULL. That case is
// reported as incompatible, which routes it through the JVM codegen dispatcher by default
// and reserves the native guard for allowIncompatible=true. A non-nullable array evaluates
// the item on every row on both paths, so it stays native.
override def getSupportLevel(expr: ArrayAppend): SupportLevel =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I filed #6086 for the ANSI gap in your "What this does not cover" section, and confirmed it on main at 5ca1499. With spark.sql.ansi.enabled=true, array_append(IF(_1 % 2 = 0, array(1), CAST(NULL AS ARRAY<INT>)), 1 / (_1 - 1)) raises DIVIDE_BY_ZERO in Spark and returns cleanly in Comet. A control query in the same fixture confirms Comet's ANSI divide does raise on its own, so the test is not vacuous.

Could you link that issue from this comment? getSupportLevel reports Compatible() for the case, so without it nothing in the code or in the generated compatibility guide records the divergence.

While I was there I checked whether CometMapFromArrays has the mirror-image problem, since its guard is IsNotNull(left) AND IsNotNull(right) while Spark's nullSafeCodeGen short-circuits the values array. It does not reproduce. That one runs natively and matches Spark. The negative result is written up in the issue so nobody re-derives it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you link that issue from this comment?

Linked in 0d61cac, and the support level changed with it. With ANSI on and a nullable array, getSupportLevel now returns Incompatible with the #6086 note, so array_append runs through the codegen dispatcher by default and raises where Spark raises. The native guarded kernel only runs there under allowIncompatible, and the generated guide records the divergence as the opt-in consequence. With ANSI off, or a non-nullable array, nothing changes. A new array_append_ansi_null_array.sql fixture pins the dispatched raise and the native path for a non-nullable array. #6086 stays open for evaluating the item outside the guard, which would bring the native path back under ANSI.

NullGuardSupport.nondeterministicChild(expr.children).getOrElse {
if (SQLConf.get.ansiEnabled && expr.left.nullable) {
Incompatible(Some(ansiItemNote))
} else {
Compatible()
}
}

override def convert(
expr: ArrayAppend,
Expand Down Expand Up @@ -614,7 +670,7 @@ object CometArrayReverse extends CometExpressionSerde[Reverse] with ArraysBase {

}

object CometElementAt extends CometExpressionSerde[ElementAt] {
object CometElementAt extends CometExpressionSerde[ElementAt] with CodegenDispatchFallback {

/**
* Under ANSI, neither native shape reproduces Spark for a nullable nondeterministic operand.
Expand All @@ -623,8 +679,9 @@ object CometElementAt extends CometExpressionSerde[ElementAt] {
* whole batch first, so a throwing index fires on rows whose operand is NULL. `convert`
* reproduces the short-circuit with a `CASE WHEN <operand> IS NOT NULL` guard, but that guard
* serializes the operand twice, which a stateful operand cannot survive: the two copies advance
* its state independently and silently move values and NULLs. Declining leaves the lookup on
* Spark. Lifting this needs a native lookup that evaluates the operand once and masks the index
* its state independently and silently move values and NULLs. Declining routes the lookup
* through the JVM codegen dispatcher, where Spark's own `doGenCode` evaluates the operand once.
* Lifting this needs a native lookup that evaluates the operand once and masks the index
* evaluation with the result, at which point the guard becomes unnecessary for every operand.
*/
private val eagerIndexReason: String =
Expand All @@ -635,6 +692,8 @@ object CometElementAt extends CometExpressionSerde[ElementAt] {
private def needsNullGuard(expr: ElementAt): Boolean =
expr.failOnError && expr.left.nullable

override def getUnsupportedReasons(): Seq[String] = eagerIndexReason +: MapKeySupport.reasons

override def getSupportLevel(expr: ElementAt): SupportLevel = {
if (needsNullGuard(expr) && !expr.left.deterministic) {
Unsupported(Some(eagerIndexReason))
Expand Down Expand Up @@ -764,14 +823,19 @@ object CometArrayFilter extends CometExpressionSerde[ArrayFilter] {
}
}

object CometSize extends CometExpressionSerde[Size] {
object CometSize extends CometExpressionSerde[Size] with CodegenDispatchFallback {

override def getUnsupportedReasons(): Seq[String] =
Seq(NullGuardSupport.nondeterministicReason)

override def getSupportLevel(expr: Size): SupportLevel = {
expr.child.dataType match {
case _: ArrayType => Compatible()
case _: MapType => Compatible()
case other =>
Unsupported(Some(s"Unsupported child data type: $other"))
NullGuardSupport.nondeterministicChild(Seq(expr.child)).getOrElse {
expr.child.dataType match {
case _: ArrayType => Compatible()
case _: MapType => Compatible()
case other =>
Unsupported(Some(s"Unsupported child data type: $other"))
}
}
}

Expand Down Expand Up @@ -843,10 +907,13 @@ object CometArrayPosition extends CometExpressionSerde[ArrayPosition] with Array
}
}

object CometArraysZip extends CometExpressionSerde[ArraysZip] {
object CometArraysZip extends CometExpressionSerde[ArraysZip] with CodegenDispatchFallback {

override def getUnsupportedReasons(): Seq[String] = Seq(
"Not all input data types are supported; falls back to Spark for unsupported types")
"An array whose element type is a map, a calendar, day-time or year-month interval, a " +
"variant, a `TIME` value or a user-defined type has no native `arrays_zip` kernel, and " +
"neither does a struct or inner array that holds one of those.",
NullGuardSupport.nondeterministicReason)

private def isTypeSupported(dt: DataType): Boolean = {
import DataTypes._
Expand All @@ -862,13 +929,13 @@ object CometArraysZip extends CometExpressionSerde[ArraysZip] {
}

override def getSupportLevel(expr: ArraysZip): SupportLevel = {
val inputTypes = expr.children.map(_.dataType).toSet
for (dt <- inputTypes) {
if (!isTypeSupported(dt)) {
return Unsupported(Some(s"Unsupported child data type: $dt"))
}
NullGuardSupport.nondeterministicChild(expr.children).getOrElse {
expr.children
.map(_.dataType)
.collectFirst { case dt if !isTypeSupported(dt) => dt }
.map(dt => Unsupported(Some(s"Unsupported child data type: $dt")))
.getOrElse(Compatible())
}
Compatible()
}

override def convert(
Expand Down
26 changes: 19 additions & 7 deletions spark/src/main/scala/org/apache/comet/serde/maps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ import org.apache.comet.shims.CometTypeShim

/**
* Shared gate for the native map kernels that compare a lookup key against a map's stored keys
* (`map_extract`, reached from both `GetMapValue` and `ElementAt`).
* (`map_extract`, reached from both `GetMapValue` and `ElementAt`). Both consumers mix in
* `CodegenDispatchFallback`, so a declined key type runs through Spark's own `doGenCode`.
*/
private[serde] object MapKeySupport {

Expand All @@ -46,6 +47,8 @@ private[serde] object MapKeySupport {
"cannot reproduce Spark's equality for a complex key type (for example a `NULL` inside the " +
"lookup key aborts the cast against a non-nullable nested component)."

val reasons: Seq[String] = Seq(floatingPointReason, collationReason, complexKeyReason)

/**
* The `SupportLevel` for a map-consuming expression whose stored-key type is `keyType`. Spark
* finds a key with `TypeUtils.getInterpretedOrdering` over the keys `ArrayBasedMapBuilder`
Expand Down Expand Up @@ -114,7 +117,9 @@ object CometMapValues extends CometExpressionSerde[MapValues] {
}
}

object CometMapExtract extends CometExpressionSerde[GetMapValue] {
object CometMapExtract extends CometExpressionSerde[GetMapValue] with CodegenDispatchFallback {

override def getUnsupportedReasons(): Seq[String] = MapKeySupport.reasons

override def getSupportLevel(expr: GetMapValue): SupportLevel = expr.child.dataType match {
case MapType(keyType, _, _) => MapKeySupport.keySupport(keyType)
Expand Down Expand Up @@ -151,19 +156,26 @@ private object MapKeyDedupPolicySupport {
.equalsIgnoreCase(SQLConf.MapKeyDedupPolicy.LAST_WIN.toString)
}

object CometMapFromArrays extends CometExpressionSerde[MapFromArrays] {
object CometMapFromArrays
extends CometExpressionSerde[MapFromArrays]
with CodegenDispatchFallback {

override def getIncompatibleReasons(): Seq[String] =
Seq(MapKeyDedupPolicySupport.incompatibleReason)

override def getUnsupportedReasons(): Seq[String] =
Seq(NullGuardSupport.nondeterministicReason)

override def getCompatibleNotes(): Seq[String] =
Seq(MapKeyDedupPolicySupport.nullKeyReason)

override def getSupportLevel(expr: MapFromArrays): SupportLevel = {
if (MapKeyDedupPolicySupport.isLastWin) {
Incompatible(Some(MapKeyDedupPolicySupport.incompatibleReason))
} else {
Compatible(None)
NullGuardSupport.nondeterministicChild(expr.children).getOrElse {
if (MapKeyDedupPolicySupport.isLastWin) {
Incompatible(Some(MapKeyDedupPolicySupport.incompatibleReason))
} else {
Compatible(None)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
-- Licensed to the Apache Software Foundation (ASF) under one
-- or more contributor license agreements. See the NOTICE file
-- distributed with this work for additional information
-- regarding copyright ownership. The ASF licenses this file
-- to you under the Apache License, Version 2.0 (the
-- "License"); you may not use this file except in compliance
-- with the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing,
-- software distributed under the License is distributed on an
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-- KIND, either express or implied. See the License for the
-- specific language governing permissions and limitations
-- under the License.

-- `CometArrayAppend` reproduces Spark's NULL propagation with a `CASE WHEN array IS NOT NULL`
-- guard, and the item sits inside the guard's THEN branch. DataFusion evaluates that branch only
-- on the rows the guard selects, while Spark's codegen evaluates the item on every row. Under
-- ANSI mode an item that raises on a row whose array is NULL therefore raises in Spark and stays
-- silent on the native path. The serde reports a nullable array under ANSI as incompatible, so
-- by default the expression runs through the JVM codegen dispatcher, which raises like Spark.
-- A non-nullable array evaluates the item on every row on both paths, so it stays native.
--
-- Spark 4.0 rewrites `array_append` to `array_insert(-1)` before serde, so `CometArrayAppend` is
-- only reachable on Spark 3.x.

-- MaxSparkVersion: 3.5

-- Config: spark.sql.ansi.enabled=true

statement
CREATE TABLE test_array_append_ansi(_1 int, arr array<int>) USING parquet

statement
INSERT INTO test_array_append_ansi
SELECT id, IF(id = 1, NULL, array(id)) FROM range(0, 4)

-- The item divides by zero exactly on the row whose array is NULL, so Spark raises and the
-- dispatcher raises with it.
query expect_error(DIVIDE_BY_ZERO)
SELECT _1, array_append(arr, 1 / (_1 - 1)) AS a
FROM test_array_append_ansi

-- The divide raises on its own, so the error above is not an artifact of the fixture.
query expect_error(DIVIDE_BY_ZERO)
SELECT _1, 1 / (_1 - 1) AS d
FROM test_array_append_ansi

-- A nullable array under ANSI is routed through the dispatcher even when the item cannot raise.
query expect_dispatch(array_append)
SELECT _1, array_append(arr, _1) AS a
FROM test_array_append_ansi

-- A non-nullable array literal cannot hit the gap and keeps the native guarded path.
query expect_native(array_append)
SELECT _1, array_append(array(1), _1) AS a
FROM test_array_append_ansi
Loading