Skip to content

Commit 3f0f4e3

Browse files
timsaucerclaude
andcommitted
feat: expose Spark-compatible functions (apache#1482)
Add `datafusion.functions.spark` module exposing the upstream `datafusion-spark` crate's UDF/UDAF library (~87 functions across string, math, datetime, hash, array, aggregate, bitwise, bitmap, conditional, collection, conversion, json, map, url categories). For DataFrame use, import the typed Python wrappers from `datafusion.functions.spark`. For SQL use, call `SessionContext.enable_spark_functions()` to register the Spark UDFs by name (overriding DataFusion built-ins of the same name with their Spark semantics — NULL-propagating `concat`, 1-indexed `substring`, HALF_UP `round`, etc.). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3d4c56c commit 3f0f4e3

15 files changed

Lines changed: 2256 additions & 1 deletion

File tree

Cargo.lock

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ datafusion-catalog = { version = "54", default-features = false }
4848
datafusion-common = { version = "54", default-features = false }
4949
datafusion-functions-aggregate = { version = "54" }
5050
datafusion-functions-window = { version = "54" }
51+
datafusion-spark = { version = "54" }
5152
datafusion-expr = { version = "54" }
5253
prost = "0.14.3"
5354
serde_json = "1"
@@ -79,4 +80,5 @@ datafusion-catalog = { git = "https://github.com/apache/datafusion", rev = "1321
7980
datafusion-common = { git = "https://github.com/apache/datafusion", rev = "1321d60cc37ee487d1e7ce7f501357c3236b2542" }
8081
datafusion-functions-aggregate = { git = "https://github.com/apache/datafusion", rev = "1321d60cc37ee487d1e7ce7f501357c3236b2542" }
8182
datafusion-functions-window = { git = "https://github.com/apache/datafusion", rev = "1321d60cc37ee487d1e7ce7f501357c3236b2542" }
83+
datafusion-spark = { git = "https://github.com/apache/datafusion", rev = "1321d60cc37ee487d1e7ce7f501357c3236b2542" }
8284
datafusion-expr = { git = "https://github.com/apache/datafusion", rev = "1321d60cc37ee487d1e7ce7f501357c3236b2542" }

crates/core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ datafusion = { workspace = true, features = ["avro", "unicode_expressions"] }
5353
datafusion-substrait = { workspace = true, optional = true }
5454
datafusion-proto = { workspace = true }
5555
datafusion-ffi = { workspace = true }
56+
datafusion-spark = { workspace = true }
5657
prost = { workspace = true } # keep in line with `datafusion-substrait`
5758
serde_json = { workspace = true }
5859
uuid = { workspace = true, features = ["v4"] }

crates/core/src/context.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,6 +1059,21 @@ impl PySessionContext {
10591059
Ok(())
10601060
}
10611061

1062+
/// Register all `datafusion-spark` UDFs/UDAFs/UDWFs, overriding any built-in
1063+
/// DataFusion functions of the same name with their Spark-semantics version.
1064+
pub fn enable_spark_functions(&self) -> PyResult<()> {
1065+
for udf in datafusion_spark::all_default_scalar_functions() {
1066+
self.ctx.register_udf((*udf).clone());
1067+
}
1068+
for udaf in datafusion_spark::all_default_aggregate_functions() {
1069+
self.ctx.register_udaf((*udaf).clone());
1070+
}
1071+
for udwf in datafusion_spark::all_default_window_functions() {
1072+
self.ctx.register_udwf((*udwf).clone());
1073+
}
1074+
Ok(())
1075+
}
1076+
10621077
pub fn deregister_udaf(&self, name: &str) {
10631078
self.ctx.deregister_udaf(name);
10641079
}

crates/core/src/functions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use crate::expr::conditional_expr::PyCaseBuilder;
3131
use crate::expr::sort_expr::{PySortExpr, to_sort_expressions};
3232
use crate::expr::window::PyWindowFrame;
3333

34-
fn add_builder_fns_to_aggregate(
34+
pub(crate) fn add_builder_fns_to_aggregate(
3535
agg_fn: Expr,
3636
distinct: Option<bool>,
3737
filter: Option<PyExpr>,

crates/core/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ pub mod physical_plan;
4848
mod pyarrow_filter_expression;
4949
pub mod pyarrow_util;
5050
mod record_batch;
51+
#[allow(clippy::borrow_deref_ref)]
52+
mod spark_functions;
5153
pub mod sql;
5254
pub mod store;
5355
pub mod table;
@@ -123,6 +125,10 @@ fn _internal(py: Python, m: Bound<'_, PyModule>) -> PyResult<()> {
123125
// Register the functions as a submodule
124126
let funcs = PyModule::new(py, "functions")?;
125127
functions::init_module(&funcs)?;
128+
// Spark-compatible functions live under `functions.spark`.
129+
let spark_funcs = PyModule::new(py, "spark")?;
130+
spark_functions::init_module(&spark_funcs)?;
131+
funcs.add_submodule(&spark_funcs)?;
126132
m.add_submodule(&funcs)?;
127133

128134
let store = PyModule::new(py, "object_store")?;

0 commit comments

Comments
 (0)