fix(spark): accept every integer width in factorial, matching Spark's implicit cast - #24943
fix(spark): accept every integer width in factorial, matching Spark's implicit cast#24943ShayanGho wants to merge 2 commits into
factorial, matching Spark's implicit cast#24943Conversation
… implicit cast Spark's Factorial extends ImplicitCastInputTypes, so TINYINT through BIGINT (and an untyped literal, which is Int64 in DataFusion) are cast to INT before evaluation. Replace the Exact(Int32) signature with a coercible one that casts the Integer type class to Int32, and replace the test asserting the BIGINT coercion error with Spark-verified expectations. STRING, DECIMAL and floating-point inputs remain rejected; tracked in apache#24941. Closes apache#24940 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Hello, This is my first DataFusion PR, so the CI needs a committer to trigger it. @andygrove @comphead could one of you please approve the workflows when you have a moment? Context: the changfe came out of running the audit-datafusion-spark-expression skill on factorial. Spark 4.2.0 (verified with PySpark) accepts every integer width via |
kumarUjjawal
left a comment
There was a problem hiding this comment.
Thank you @ShayanGho for working on this. I left few comments please take a look.
| Self { | ||
| signature: Signature::exact(vec![Int32], Volatility::Immutable), | ||
| signature: Signature::coercible( | ||
| vec![Coercion::new_implicit( |
There was a problem hiding this comment.
Could we use Coercion::new_implicit_native(logical_int32(), vec![TypeSignatureClass::Integer]) here? That constructor was added for native-target coercions so the desired Int32 type and NativeType::Int32 cannot accidentally diverge. It would also remove the NativeType import.
There was a problem hiding this comment.
Switched to Coercion::new_implicit_native and removed the NativeType import.
| query error Error during planning: Failed to coerce arguments to satisfy a call to 'factorial' function | ||
| SELECT factorial(5::BIGINT); | ||
| # Spark declares factorial(INT) with ImplicitCastInputTypes, so every integer width is | ||
| # accepted; an untyped literal (Int64 in DataFusion) must work too. Values from Spark 4.2.0. |
There was a problem hiding this comment.
Could we clarify the test provenance here? This row includes UInt64, but Spark has no unsigned integer type, so that expected value cannot come from Spark 4.2.0. I suggest identifying the signed cases as Spark-verified and the unsigned case as DataFusion-specific coverage, or removing the unsigned case.
There was a problem hiding this comment.
Removed the UInt64 case and updated the query signature and expected output. The remaining cases use types available in Spark.
Done! |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #24943 +/- ##
==========================================
- Coverage 81.63% 81.57% -0.07%
==========================================
Files 1123 1123
Lines 410298 410796 +498
Branches 410298 410796 +498
==========================================
+ Hits 334965 335118 +153
- Misses 55642 55919 +277
- Partials 19691 19759 +68 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| NULL | ||
| NULL | ||
|
|
||
| # DataFusion always fails at the Int32 cast here (this function does not consult |
There was a problem hiding this comment.
lets concise comments, for this particular thing we should create another follow up issue and link it to #23929
| # DataFusion always fails at the Int32 cast here (this function does not consult | ||
| # datafusion.execution.enable_ansi_mode). Spark 4.2.0 raises CAST_OVERFLOW under | ||
| # ANSI mode but returns NULL when ANSI is off. | ||
| query error Can't cast value 5000000000 to type Int32 |
There was a problem hiding this comment.
Removed this overflow assertion from the PR as well.
| SELECT factorial(5000000000::BIGINT); | ||
|
|
||
| # Spark 4.2.0 also accepts STRING, DECIMAL and floating-point inputs through ImplicitCastInputTypes | ||
| # (strings and overflow are ANSI dependent there). DataFusion rejects these types at planning time. |
There was a problem hiding this comment.
this test is prob not needed. Spark functions are not designed to be called directly from DF, its rather DF extension for Spark, and Spark already provides correct datatype
There was a problem hiding this comment.
Removed the string-rejection test. The tests now focus on integer coercion, factorial boundaries, and NULL handling.
Thank you for checking my PR and running the CI |
Which issue does this PR close?
Rationale for this change
In Spark mode,
SELECT factorial(5)fails to plan: an untyped integer literal isInt64inDataFusion and the function's signature was
Exact(Int32). Spark'sFactorialdeclaresinputTypes = Seq(IntegerType)but extendsImplicitCastInputTypes, so a real Spark castsTINYINT, SMALLINT and BIGINT arguments to INT before evaluating. Verified against
pyspark==4.2.0under both ANSI settings: every integer width returns the same values.The narrow signature was chosen deliberately in #16125 from the Databricks reference, which
documents the parameter as an INTEGER expression. That describes the declared parameter type;
the accepted set is wider because of the implicit cast, which is only visible by running Spark.
What changes are included in this PR?
SparkFactorialnow usesSignature::coerciblewith an implicit coercion from the Integertype class to
Int32, the same constructround,width_bucketandbit_getin this crateuse. Planning inserts the cast, so
spark_factorialstill only receivesInt32.factorial.slt: replaces the assertion thatfactorial(5::BIGINT)must fail with two blocks: onealiased row covering the untyped literal, TINYINT, SMALLINT, BIGINT and a bare NULL, and an
array-shaped BIGINT query covering a negative value, both range boundaries, an above-range value
and NULL. Every expected value was observed from PySpark 4.2.0, not from DataFusion output.
acceptance of STRING / DECIMAL / FLOAT inputs is tracked in [Bug] spark factorial rejects STRING, DECIMAL and floating-point inputs that Spark implicitly casts to INT #24941, and the ANSI-dependent
handling of out-of-range integers in [Bug] spark factorial: BIGINT overflow should wrap in non-ANSI mode and raise CAST_OVERFLOW in ANSI mode, as in Spark #24950 (part of [EPIC] Support ANSI mode #23929).
What is the testing strategy for this PR?
The new
.sltqueries fail onmainand pass with this change, verified by checking out thepre-fix implementation file and re-running
cargo test --test sqllogictests -- spark/math/factorial:coercion from Int64 to the signature Exact(Int32) failed.Progress: 1/1 files completed (100%).cargo test -p datafusion-spark factorial,cargo fmt --all -- --checkandcargo clippy -p datafusion-spark --all-targets -- -D warningspass. The existing unit tests infactorial.rsare unchanged.Are there any user-facing changes?
factorialindatafusion-sparknow accepts all integer widths. No breaking API change.🤖 Generated with Claude Code