Spec gaps
Behaviour Substrait’s extension YAMLs leave unspecified, though Substrait could express it with an option, an option’s value or a default, where engines differ (core-semantics.md).
Substrait
ST-1: shift_left and shift_right (functions_arithmetic.yaml) do not say what a shift count below 0 or at …
shift_left and shift_right (functions_arithmetic.yaml) do not say what a shift count below 0 or at least the base’s width gives, nor whether shift_left may shift bits out. They have no option for it. A negative base is allowed by the test cases (shift_left(-3::i64, 1::i32) = -6::i64).
Reproduction:
shift_left(1::i64, 64),shift_right(-5::i64, 64),shift_left(-1::i64, 1),shift_left(2^62::i64, 2): the YAML’s descriptions define none of them.- DuckDB 2.0:
1 << 64raises,-1 << 1raises,(2^62) << 2raises (a bit shifted out),-5 >> 64is0.
How sverify handles it:
- The core allows each engine’s recorded behaviour (raise,
0, drop the bits) as the optionshift_range(docs/core-semantics.md, D3). The reference drops the bits shifted out and raises for a count outside[0, 63]; DuckDB’s written plan guards both shifts. - Could be expressed as: An option such as
on_shift_overflow: [ERROR, ZERO, WRAP]for the count, and the existingoverflowfor bits shifted out ofshift_left.
Status: Still open on main (a4d8e00342d0, = v0.108.0): functions_arithmetic.yaml L982-L1046 define the shifts by description only, with no option and nothing on a count outside [0, width) or bits shifted out of shift_left. The test cases allow a negative base (shift_left.test L9-L12: shift_left(-3::i64, 1::i32) = -6::i64). No existing issue; options in general: #254.
See: facts duckdb-R15-shift-left, duckdb-R15-shift-right, polars-int-shift; issues #444; upstream https://github.com/substrait-io/substrait/pull/799; files docs/core-semantics.md.
Found by the design pass of the loose core (#444), docs/core-semantics.md.
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions 0.106.0: the extension YAMLs and site/docs on main; the gap is there
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions v0.108.0: functions_arithmetic.yaml shift_* and tests/cases/arithmetic/shift_*.test on main; the gap is there, except a negative base, which the test cases allow
ST-2: atan2 takes on_domain_error: [NAN, ERROR], but the YAML does not say what its domain is, so whether …
atan2 takes on_domain_error: [NAN, ERROR], but the YAML does not say what its domain is, so whether atan2(0, 0) is a domain error is open. IEEE 754 and C’s atan2 give ±0 (or ±π) there.
Reproduction:
atan2(0.0::fp64, 0.0::fp64)withon_domain_error: ERROR: raise, or0?- DuckDB 2.0:
atan2(0, 0)is0under every setting.
How sverify handles it:
- The reference raises (
Issue.domain); the core allows0as well. DuckDB’s written plan raises at(0, 0). - Could be expressed as: State the domain of
atan2in its description (all pairs, following IEEE 754, or all but(0, 0)).
Status: Still open on main (a4d8e00342d0): functions_arithmetic.yaml L702-L726 gives atan2 on_domain_error: [NAN, ERROR] without saying what its domain is; atan2.test has no (0, 0) case. No existing issue; options undocumented in general: #254.
See: facts duckdb-R15-atan2, polars-atan2; issues #444; upstream https://github.com/substrait-io/substrait/issues/254; files docs/core-semantics.md.
Found by the design pass of the loose core (#444), docs/core-semantics.md.
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions 0.106.0: the extension YAMLs and site/docs on main; the gap is there
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions v0.108.0: functions_arithmetic.yaml atan2 and tests/cases/arithmetic/atan2.test on main; the gap is there
ST-3: corr says what no input gives (null); variance and std_dev say nothing about it, though they return …
corr says what no input gives (null); variance and std_dev say nothing about it, though they return nullable types. None of them says anything about the degenerate cases: corr of one pair or of a side whose values are all equal, and any of them over a group with ±inf or NaN. Engines give NULL or NaN there.
Reproduction:
corr(x, y)over(1, 2)alone, or over(1, 1), (1, 2).- DuckDB 2.0 and Polars 2.1: NaN for both; Spark 4.3: NULL for both. DuckDB:
var_sampover[inf, 1]is NaN.
How sverify handles it:
- The core allows NULL, NaN or a raise (
stat_degenerate); the reference gives NULL forcorr, as the SQL standard does, and raises for an infinite value.semantics="duckdb"gives NaN. - Could be expressed as: An option, such as
on_degenerate: [NULL, NAN]and the existingon_domain_errorfor non-finite input, or a sentence in each description.
Status: Still open on main (a4d8e00342d0): functions_arithmetic.yaml L1492-L1516 says only that corr of no input is null, and functions_arithmetic.yaml L1394-L1491 (std_dev, variance) say nothing beyond their names; no option or sentence covers one pair, a constant side, or ±inf/NaN input. No existing issue.
See: facts duckdb-corr, spark-corr, polars-corr, duckdb-nonfinite-aggregates, duckdb-var-stddev, polars-nonfinite-aggregates; issues #444; files docs/core-semantics.md.
Found by the design pass of the loose core (#444), docs/core-semantics.md.
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions 0.106.0: the extension YAMLs and site/docs on main; the gap is there
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions v0.108.0: functions_arithmetic.yaml corr, std_dev, variance on main; the gap is there
ST-4: fp32 and fp64 have IEEE 754’s range and precision (type_classes.md), and equal is x == y, but …
fp32 and fp64 have IEEE 754’s range and precision (type_classes.md), and equal is x == y, but nothing says how NaN and -0.0 compare, group, sort or deduplicate. index_in alone has an option for it (nan_equality). Sorting follows the type’s “quality function”, which is not defined for NaN or -0.0.
Reproduction:
GROUP BY x,DISTINCT, a join onx,ORDER BY xandequal(x, x)withxin{NaN, -0.0, 0.0}.- DuckDB 2.0: one NaN, equal to itself and above
+inf;-0.0sorts before0.0;signbitsees a NaN’s sign.
How sverify handles it:
- The core leaves
nan_equality,nan_order,zero_signandnan_kindopen; the reference has one NaN, equal to itself and greatest, and one zero (#89). - Could be expressed as: Extend
nan_equalitytoequal, the comparisons and the relations that compare keys, and add an option or a stated rule for NaN’s place in a sort and for-0.0.
Status: Still open on main (a4d8e00342d0): type_classes.md L20-L21 gives fp32/fp64 IEEE 754’s range and precision, functions_comparison.yaml L23-L27 defines equal as x == y, only index_in has nan_equality (functions_set.yaml L8-L28), and the sort’s “quality function” (basics.md L64-L71) is defined nowhere. No existing issue; the open draft #1322 leaves NaN in RANGE frames implementation-defined.
See: facts duckdb-R14, duckdb-signbit, duckdb-arg-min-nulls, duckdb-quantile-disc, polars-zero-sign, polars-nan-sign, polars-nan-equality, polars-nan-order, polars-min-max-nan, polars-zero-order, duckdb-nan-equality; issues #444; upstream https://github.com/substrait-io/substrait/pull/1322; files docs/core-semantics.md.
Found by the design pass of the loose core (#444), docs/core-semantics.md.
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions 0.106.0: the extension YAMLs and site/docs on main; the gap is there
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions v0.108.0: type_classes.md, functions_comparison.yaml, functions_set.yaml, relations/basics.md on main; the gap is there
ST-5: add, subtract and multiply on fp32/fp64 have only rounding
add, subtract and multiply on fp32/fp64 have only rounding. inf - inf, inf + (-inf) and 0 * inf have no IEEE value, and the YAML neither says they give NaN nor offers on_domain_error, as divide and sqrt do.
Reproduction:
subtract(inf::fp64, inf::fp64),multiply(0.0::fp64, inf::fp64).- DuckDB 2.0: NaN under every setting.
How sverify handles it:
- The core allows NaN or a raise (
nan_creation); the reference raises (#257), and DuckDB’s written plan guards these cases. - Could be expressed as:
on_domain_error: [NAN, NULL, ERROR]on the floating-pointadd,subtract,multiplyandsum, asdividehas.
Status: Still open on main (a4d8e00342d0): the fp impls of add, subtract and multiply (functions_arithmetic.yaml L45-L62, functions_arithmetic.yaml L103-L120, functions_arithmetic.yaml L161-L178) take rounding only, while divide (functions_arithmetic.yaml L180-L187) has on_domain_error; nothing says inf - inf or 0 * inf gives NaN. Closest issue #1275 (IEEE divide); see also #996 (options inconsistent across functions).
See: facts duckdb-R15-double-arith, duckdb-nonfinite-aggregates, polars-double-arith, polars-nonfinite-aggregates; issues #444; upstream https://github.com/substrait-io/substrait/issues/1275, https://github.com/substrait-io/substrait/issues/996, https://github.com/substrait-io/substrait/issues/254; files docs/core-semantics.md.
Found by the design pass of the loose core (#444), docs/core-semantics.md.
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions 0.106.0: the extension YAMLs and site/docs on main; the gap is there
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions v0.108.0: functions_arithmetic.yaml fp add/subtract/multiply/divide and their test cases on main; the gap is there
ST-6: The cast expression (specialized_record_expressions.md, Cast in algebra.proto) does not say how a …
The cast expression (specialized_record_expressions.md, Cast in algebra.proto) does not say how a cast from fp32/fp64 or a decimal to an integer rounds, and FAILURE_BEHAVIOR_UNSPECIFIED is not described.
Reproduction:
CAST(0.55::fp64 AS i32)andCAST(-0.55::fp64 AS i64):0, or1and-1?- DuckDB 2.0: rounds half away from zero (
1,-1); DataFusion truncates (0,0).
How sverify handles it:
- The core leaves float_to_int open and reads an unspecified failure behaviour as either; the reference truncates toward zero (#209) and throws. DuckDB’s written plan casts
trunc(x). - Could be expressed as: A rounding field on the cast (the
roundingoption’s values), and a sentence on whatFAILURE_BEHAVIOR_UNSPECIFIEDmeans.
Status: Still open on main (a4d8e00342d0): specialized_record_expressions.md L14-L17 describes the cast’s failure behaviour and nothing on how fp or decimal to integer rounds; algebra.proto L1502-L1512 leaves FAILURE_BEHAVIOR_UNSPECIFIED undescribed. No existing issue on rounding; #1069 (semantically meaningful *_UNSPECIFIED) does not list the cast.
See: facts duckdb-R9, duckdb-R8, polars-cast-double-to-int; issues #444; upstream https://github.com/substrait-io/substrait/issues/1069; files docs/core-semantics.md.
Found by the design pass of the loose core (#444), docs/core-semantics.md.
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions 0.106.0: the extension YAMLs and site/docs on main; the gap is there
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions v0.108.0: specialized_record_expressions.md and algebra.proto Cast on main; the gap is there
ST-7: A sort field’s direction is required (relations/basics.md), and the directions it lists are the four of …
A sort field’s direction is required (relations/basics.md), and the directions it lists are the four of NULL placement, a custom function and clustered. SORT_DIRECTION_UNSPECIFIED, the enum’s default value, is not described: an error, or a direction the consumer picks?
Reproduction:
- A
SortRelwhose field hasdirection: SORT_DIRECTION_UNSPECIFIEDover a column with NULLs.
How sverify handles it:
- The core reads it as ascending with the NULL placement left open (docs/core-semantics.md, D6); the reference and each engine fix one (the reference and DuckDB: NULLs last, D8). The parser reads it in a
SortRel(SubstraitLean/Parse.lean,parseSortKey; #444 step 1c) and refuses it in a window’s or a measure’s order. - Could be expressed as: Say whether
SORT_DIRECTION_UNSPECIFIEDis invalid or means a consumer-chosen direction and NULL placement.
Status: Still open on main (a4d8e00342d0): basics.md L58-L73 requires a direction and lists none for SORT_DIRECTION_UNSPECIFIED (algebra.proto L1842-L1857). Existing issue: #213 asks exactly this (question 5, 2022; comments lean to “reject the plan”), unresolved; #1069 covers *_UNSPECIFIED defaults but does not list SortDirection.
See: facts duckdb-F2, polars-null-order; issues #444; upstream https://github.com/substrait-io/substrait/issues/213, https://github.com/substrait-io/substrait/issues/1069; files docs/core-semantics.md, SubstraitLean/Parse.lean.
Found by the design pass of the loose core (#444), docs/core-semantics.md.
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions 0.106.0: the extension YAMLs and site/docs on main; the gap is there
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions v0.108.0: relations/basics.md orderedness and algebra.proto SortField on main; the gap is there
ST-8: functions_arithmetic_decimal.yaml gives each function’s result type, and nothing about how a result with …
functions_arithmetic_decimal.yaml gives each function’s result type, and nothing about how a result with more digits than the result’s scale is rounded: always for divide, and for add, subtract, multiply when the precision passes 38 and the scale is borrowed. Casts between decimal scales (specialized_record_expressions.md) leave it open too. Engines round half to even (Polars) or half away from zero (DuckDB).
Reproduction:
multiply(DECIMAL<10,2> 0.05, DECIMAL<10,1> 0.5)at a result scale of 2 (an engine’s own result type, or a cast toDECIMAL<10,2>): Polars 2.1.0 gives 0.02, half away from zero gives 0.03.divide(DECIMAL<10,2> 1.00, DECIMAL<10,0> 8)at scale 2: 0.12 or 0.13.
How sverify handles it:
- The core’s
decimal_rescaleoption allowsTIE_AWAY_FROM_ZERO,TIE_TO_EVENandTRUNCATE(docs/core-semantics.md); the reference rounds half away from zero. Decimal division is refused in every profile until #27. - Could be expressed as: a
roundingoption on the decimal functions and casts, with the values ofround’s.
Status: Still open on main (a4d8e00342d0): functions_arithmetic_decimal.yaml L49-L87 (and add/subtract L8-L47) give result types and overflow only, with no rounding. Existing issue: #1224 asks exactly this for the precision-38 borrow; divide’s always-inexact result and casts between scales are not in it. Related: #1318 (decimal handling, vbarua), #1223, #1274.
See: facts polars-decimal-arith, duckdb-decimal-arith; issues #27, #444; upstream https://github.com/substrait-io/substrait/issues/1224, https://github.com/substrait-io/substrait/issues/1318, https://github.com/substrait-io/substrait/pull/1223, https://github.com/substrait-io/substrait/pull/1274, https://github.com/substrait-io/substrait/pull/1213, https://github.com/substrait-io/substrait/issues/1216; files docs/core-semantics.md, characterization/polars/probes.json.
Found by the Polars characterization (sverify_dev.probe_polars, characterization/polars/probes.json).
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions 0.106.0: functions_arithmetic_decimal.yaml on main: result types only, no rounding and no options
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions v0.108.0: functions_arithmetic_decimal.yaml and the cast docs on main; the gap is there
ST-9: The fp32 and fp64 implementations of add, subtract, multiply, divide, power, exp and the …
The fp32 and fp64 implementations of add, subtract, multiply, divide, power, exp and the other elementary functions have no option for a result too large for the type: only rounding (and on_domain_error, on_division_by_zero where they apply). The test cases expect IEEE 754’s ±inf with the default options (multiply.test, since substrait#738), but nothing lets a plan ask for an error instead. sum and avg of fp64 take overflow: SILENT | SATURATE | ERROR, which the YAML defines for integers only.
Reproduction:
multiply(fp64 1e308, fp64 10),exp(fp64 1000),power(fp64 10, fp64 400), andsumof1e308and1e308.- DuckDB 2.0 gives
inffor all of them (fact duckdb-double-overflow); Polars givesinf(fact polars-double-arith); sverify’s reference raises (#575).
How sverify handles it:
- The reference raises when an operation on finite doubles gives
±inf(Issue.infinite): an inferred precondition the written plan checks on the data, as for aSUMthat leaves 64 bits. The core allows both behaviours. - Could be expressed as: an
overflowoption on the floating-point implementations, withIEEE(±inf) andERROR, and the meaning ofsum’s existingoverflowvalues stated forfp32andfp64(SATURATEto the largest finite value).
Status: Narrower than recorded, still open on main (a4d8e00342d0): the test cases expect IEEE ±inf on overflow with the default options (multiply.test L19-L21, likewise add, subtract, divide, sum, exp, since #738), but no fp implementation offers raising instead (functions_arithmetic.yaml L45-L62, functions_arithmetic.yaml L401-L412, functions_arithmetic.yaml L445-L468), and sum’s overflow values (functions_arithmetic.yaml L1092-L1111) are undefined for floats. No existing issue; #254 (options undocumented).
See: facts duckdb-double-overflow, polars-double-arith; issues #444, #575; upstream https://github.com/substrait-io/substrait/issues/254, https://github.com/substrait-io/substrait/issues/996; files SubstraitLean/Safety.lean, docs/core-semantics.md, docs/reference-semantics.md.
Found by the terminology of the loose core (#574), docs/core-semantics.md.
- Checked 2026-11-04 against substrait-extensions 0.106.0: functions_arithmetic.yaml: the fp implementations of add, subtract, multiply, divide, power and exp have
roundingonly; sum and avg of fp64 haveoverflow - Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions v0.108.0: functions_arithmetic.yaml fp impls and tests/cases/arithmetic/{add,subtract,multiply,divide,sum,exp}.test on main; the tests answer the default (±inf), the ERROR option and sum’s overflow on floats are open
ST-10: upper and lower with char_set: UTF8 say the implementation “should follow the utf8_unicode_ci …
upper and lower with char_set: UTF8 say the implementation “should follow the utf8_unicode_ci collations” of the Unicode Collation Algorithm (UTR #10), which is a collation, not a case mapping. Nothing says whether the mapping is Unicode’s simple one (one code point to one, the length kept) or the full one of SpecialCasing.txt (ß to SS, fi to FI, İ to i̇, a final sigma). Engines differ: DuckDB maps code point by code point, Rust’s str::to_uppercase (Polars, DataFusion) maps in full. The varchar<L1> and fixedchar<L1> signatures return the input’s length, which a full mapping can exceed.
Reproduction:
upper('ß'): DuckDB 2.0.0 givesẞ(U+1E9E), Polars 2.1.0 givesSS.lower('ΣΑΣ'): DuckDBσασ, Polarsσας.upper('fi'): DuckDBfi, PolarsFI.lower('İ'): DuckDBi(one character), Polarsi̇(two).
How sverify handles it:
- The reference defines
upperandloweras black boxes with Rust’s full mappings (Text.upper,Text.lower), and the written SQL raises on non-ASCII text for every engine (ascii_only, #70), where every engine agrees. - Could be expressed as: a
case_mappingoption (SIMPLE,FULL) besidechar_set, orUTF8defined as one of the two, with the length rule for thevarchar<L1>andfixedchar<L1>signatures.
Status: Still open on main (a4d8e00342d0): functions_string.yaml L985-L1037 point lower/upper at the utf8_unicode_ci collation and return varchar<L1> for varchar<L1>, with nothing on simple or full case mapping; the test cases are ASCII only. No existing issue.
See: facts duckdb-case-mapping, polars-case; issues #70, #444; files python/src/sverify/sql/core/strings.py, characterization/duckdb/probes.json.
Found by the DuckDB characterization (sverify_dev.probe observations, characterization/duckdb/probes.json).
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions 0.106.0: functions_string.yaml on main: upper, lower with char_set [UTF8, ASCII_ONLY] and the utf8_unicode_ci sentence, nothing on simple or full mappings
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions v0.108.0: functions_string.yaml lower/upper and their test cases on main; the gap is there
ST-11: power of i64 (functions_arithmetic.yaml) returns i64 and takes only overflow
power of i64 (functions_arithmetic.yaml) returns i64 and takes only overflow. Nothing says what a negative exponent gives, whose result is not an integer: power(2, -1) could be 0 (truncated), an error, or a domain error, and the integer implementation has no on_domain_error.
Reproduction:
power(2::i64, -1::i64)andpower(-2::i64, -3::i64): the YAML’s description, “Take the power with x as the base and y as exponent”, defines neither.- Polars 2.1: a negative integer exponent raises (
InvalidOperationError, fact polars-int-arith); sverify’s reference raises (Issue.negExponent).
How sverify handles it:
- The reference raises on a negative exponent (
Issue.negExponent,SubstraitLean/Safety.lean), an inferred precondition. The core allows raising anywhere (D2), so no option of the catalogue names it;Profile.substraitaccepts anyon_domain_errorpreference for integerpower, which the YAML does not give it. - Could be expressed as:
on_domain_error: [NULL, ERROR]on the integer implementations ofpower, as integerdividehas, with a negative exponent as its domain error, or a sentence in the description.
Status: Still open on main (a4d8e00342d0): functions_arithmetic.yaml L389-L412 gives power(i64, i64) -> i64 with overflow only and nothing on a negative exponent; power.test has positive integer exponents only. No existing issue; #1001 (closed) proposed fp64 results and was declined to keep int, int -> int.
See: facts polars-int-arith; issues #444; upstream https://github.com/substrait-io/substrait/pull/1001; files SubstraitLean/SubstraitProfile.lean, SubstraitLean/Safety.lean.
Found by writing Profile.substrait (#444 step 2), SubstraitLean/SubstraitProfile.lean.
- Checked 2026-11-04 against substrait-extensions 0.106.0: functions_arithmetic.yaml: power(i64, i64) has
overflowonly; the fp implementations have no options - Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions v0.108.0: functions_arithmetic.yaml power and tests/cases/arithmetic/power.test on main; the gap is there
ST-12: IfThen and Switch short-circuit and coalesce stops at the first non-null argument, so the spec makes …
IfThen and Switch short-circuit and coalesce stops at the first non-null argument, so the spec makes those lazy. It does not say whether and and or evaluate an operand that does not decide the result (it gives Kleene truth tables only), or whether a filter or join condition may be evaluated on rows that a relation below it removes. Both decide whether a function that raises (an integer division by zero, a failing cast, an overflow) raises for a plan. DuckDB’s AND/OR are lazy as conditions and eager as values; Polars’s & and | evaluate every operand (#604). Polars’s eager when/then/otherwise (under group_by().agg and over()) and coalesce depart from the spec’s lazy IfThen and coalesce.
Reproduction:
and(x <> 0, 10 / x > 1)overx = 0withon_division_by_zero: ERROR, as a projected value: DuckDB 2.0.0’sSELECT (x <> 0) AND (10 // x > 1)raises atx = 0, and Polars 2.1.0’s(pl.col('x') != 0) & (10 // pl.col('x') > 1)with a strict cast in place of the division raises too; a reference that evaluatesandlazily returnsfalse(#604).
How sverify handles it:
- sverify’s guards look for raises on the rows of
Rel.loose, the plan without the filters an engine may reorder (Rel.HasIssue,SubstraitLean/Guards.lean), so a filter pushed below a join is covered; whether a raise inside anIfThenbranch counts on rows its condition excludes is for the round-2 source check. Theexecutionarea’sevaluation_orderitem records what each engine does (duckdb-exec-short-circuit, duckdb-exec-filter-pushdown, polars-exec-when-eager, polars-exec-predicate-pushdown). - Could be expressed as: a statement in the expression semantics that a function’s raise counts only on rows where its value is needed (lazy
IfThen,coalesce,and,or), and that a filter is evaluated only on the rows of its input; or an option on those functions (evaluation: LAZY | EAGER) for producers to ask for.
Status: Partly specified on main (a4d8e00342d0): IfThen and Switch short-circuit, so a branch not taken cannot raise (specialized_record_expressions.md L30-L53), and coalesce does not evaluate the arguments after the first non-null (functions_comparison.yaml L262-L267). Still open: and/or give Kleene truth tables only (functions_boolean.yaml L8-L66), and nothing says whether a filter or join condition may be evaluated on rows a relation below removes (logical_relations.md L124-L142). No existing issue.
See: facts duckdb-exec-short-circuit, duckdb-exec-filter-pushdown, polars-exec-when-eager, polars-exec-predicate-pushdown; issues #444, #604; files characterization/areas.json, SubstraitLean/Guards.lean.
Found by the characterization pipeline, round 1 (execution area).
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions 0.106.0: Not checked against the spec text yet; to confirm in round 2 (expressions/conditionals, functions_boolean.yaml).
- Checked 2026-11-04 against substrait main@a4d8e00342d0, substrait-extensions v0.108.0: specialized_record_expressions.md (IfThen, Switch), functions_comparison.yaml (coalesce), functions_boolean.yaml, relations/logical_relations.md (Filter, Join) on main; IfThen/Switch/coalesce are specified lazy, and/or and relation evaluation order are open
ST-16: on_log_zero lists NAN, ERROR and MINUS_INFINITY
on_log_zero lists NAN, ERROR and MINUS_INFINITY. Spark (and Hive before it) returns NULL for the logarithm of zero, as it does for a negative argument, which on_domain_error: NULL covers. Substrait cannot express Spark’s ln(0).
Reproduction:
ln(0.0::fp64)on Spark 4.3.0: NULL, under ANSI and without it (probe double-math-domain).- DuckDB 2.0 and Polars 2.1.0:
-inf(duckdb-R15-math-domain, polars-math-domain).
How sverify handles it:
- The reference raises (
on_log_zero: ERROR); the Spark profile guardsx = 0before the log, so a plan never sees Spark’s NULL. - Could be expressed as: add
NULLtoon_log_zero’s values, ason_domain_errorhas it.
Status: Open on main (a4d8e00342d0): functions_logarithmic.yaml gives ln, log10, log2, logb and log1p on_log_zero: [NAN, ERROR, MINUS_INFINITY], while their on_domain_error lists NULL. No existing issue found (gh issue list --repo substrait-io/substrait --search "on_log_zero null", “log of zero null”: none); not yet checked against Substrait’s issues in depth.
See: facts spark-math-domain, polars-math-domain, duckdb-R15-math-domain; issues #738, #215; files docs/core-semantics.md, characterization/spark/probes.json.
Found by the Spark characterization (#738): sverify_dev.probe_spark, probe double-math-domain.
- Checked 2026-11-05 against substrait main@a4d8e00342d0: extensions/functions_logarithmic.yaml on main: on_log_zero values [NAN, ERROR, MINUS_INFINITY] for every logarithm