Skip to content

Commit ce14fac

Browse files
committed
feat: ensure that UDF names are unique
1 parent 05deb99 commit ce14fac

File tree

6 files changed

+86
-1
lines changed

6 files changed

+86
-1
lines changed

guests/evil/src/complex/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
//! Overly complex data emitted by the payload.
2+
3+
pub(crate) mod udfs_duplicate_names;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//! Duplicate UDF names.
2+
use std::sync::Arc;
3+
4+
use arrow::datatypes::DataType;
5+
use datafusion_common::{Result as DataFusionResult, ScalarValue};
6+
use datafusion_expr::{
7+
ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, TypeSignature, Volatility,
8+
};
9+
10+
/// UDF with a name
11+
#[derive(Debug, PartialEq, Eq, Hash)]
12+
struct NamedUdf(&'static str);
13+
14+
impl ScalarUDFImpl for NamedUdf {
15+
fn as_any(&self) -> &dyn std::any::Any {
16+
self
17+
}
18+
19+
fn name(&self) -> &str {
20+
self.0
21+
}
22+
23+
fn signature(&self) -> &Signature {
24+
static S: Signature = Signature {
25+
type_signature: TypeSignature::Uniform(0, vec![]),
26+
volatility: Volatility::Immutable,
27+
};
28+
29+
&S
30+
}
31+
32+
fn return_type(&self, _arg_types: &[DataType]) -> DataFusionResult<DataType> {
33+
Ok(DataType::Null)
34+
}
35+
36+
fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> DataFusionResult<ColumnarValue> {
37+
Ok(ColumnarValue::Scalar(ScalarValue::Null))
38+
}
39+
}
40+
41+
/// Returns our evil UDFs.
42+
///
43+
/// The passed `source` is ignored.
44+
#[expect(clippy::unnecessary_wraps, reason = "public API through export! macro")]
45+
pub(crate) fn udfs(_source: String) -> DataFusionResult<Vec<Arc<dyn ScalarUDFImpl>>> {
46+
Ok(vec![
47+
Arc::new(NamedUdf("foo")),
48+
Arc::new(NamedUdf("bar")),
49+
Arc::new(NamedUdf("foo")),
50+
])
51+
}

guests/evil/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use datafusion_expr::ScalarUDFImpl;
88
use datafusion_udf_wasm_guest::export;
99

1010
mod common;
11+
mod complex;
1112
mod env;
1213
mod fs;
1314
mod net;
@@ -34,6 +35,10 @@ impl Evil {
3435
/// Get evil, multiplexed by env.
3536
fn get() -> Self {
3637
match std::env::var("EVIL").expect("evil specified").as_str() {
38+
"complex::udfs_duplicate_names" => Self {
39+
root: Box::new(common::root_empty),
40+
udfs: Box::new(complex::udfs_duplicate_names::udfs),
41+
},
3742
"env" => Self {
3843
root: Box::new(common::root_empty),
3944
udfs: Box::new(env::udfs),

host/src/lib.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22
//!
33
//!
44
//! [DataFusion]: https://datafusion.apache.org/
5-
use std::{any::Any, collections::BTreeMap, hash::Hash, ops::DerefMut, sync::Arc, time::Duration};
5+
use std::{
6+
any::Any,
7+
collections::{BTreeMap, HashSet},
8+
hash::Hash,
9+
ops::DerefMut,
10+
sync::Arc,
11+
time::Duration,
12+
};
613

714
use ::http::HeaderName;
815
use arrow::datatypes::DataType;
@@ -575,6 +582,7 @@ impl WasmScalarUdf {
575582
let store = Arc::new(Mutex::new(store));
576583

577584
let mut udfs = Vec::with_capacity(udf_resources.len());
585+
let mut names_seen = HashSet::with_capacity(udf_resources.len());
578586
for resource in udf_resources {
579587
let mut store_guard = store.lock().await;
580588
let store2: &mut Store<WasmStateImpl> = &mut store_guard;
@@ -587,6 +595,11 @@ impl WasmScalarUdf {
587595
"call ScalarUdf::name",
588596
Some(&store_guard.data().stderr.contents()),
589597
)?;
598+
if !names_seen.insert(name.clone()) {
599+
return Err(DataFusionError::External(
600+
format!("non-unique UDF name: '{name}'").into(),
601+
));
602+
}
590603

591604
let store2: &mut Store<WasmStateImpl> = &mut store_guard;
592605
let signature: Signature = bindings
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use crate::integration_tests::evil::test_utils::try_scalar_udfs;
2+
3+
#[tokio::test]
4+
async fn test_udfs_duplicate_names() {
5+
let err = try_scalar_udfs("complex::udfs_duplicate_names")
6+
.await
7+
.unwrap_err();
8+
9+
insta::assert_snapshot!(
10+
err,
11+
@"External error: non-unique UDF name: 'foo'");
12+
}

host/tests/integration_tests/evil/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod complex;
12
mod env;
23
mod fs;
34
mod net;

0 commit comments

Comments
 (0)