Skip to content

Commit b7fd8e2

Browse files
authored
Merge pull request #220 from influxdata/crepererum/sandbox-error-edition
test: ensure error conversion is guarded
2 parents ebef68a + 556a3c6 commit b7fd8e2

File tree

4 files changed

+165
-0
lines changed

4 files changed

+165
-0
lines changed

guests/evil/src/complex/error.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//! Badness related to errors returned by the UDF.
2+
3+
use std::sync::Arc;
4+
5+
use arrow::datatypes::DataType;
6+
use datafusion_common::{DataFusionError, Result as DataFusionResult};
7+
use datafusion_expr::{
8+
ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, TypeSignature, Volatility,
9+
};
10+
11+
use crate::common::DynBox;
12+
13+
/// UDF that returns an error when invoked.
14+
#[derive(Debug, PartialEq, Eq, Hash)]
15+
struct ErrorUDF {
16+
/// Name.
17+
name: &'static str,
18+
19+
/// The error generator.
20+
err: DynBox<dyn Fn() -> DataFusionError + Send + Sync>,
21+
}
22+
23+
impl ErrorUDF {
24+
/// Create new UDF.
25+
fn new<F>(name: &'static str, f: F) -> Self
26+
where
27+
F: Fn() -> DataFusionError + Send + Sync + 'static,
28+
{
29+
Self {
30+
name,
31+
err: DynBox(Box::new(f)),
32+
}
33+
}
34+
}
35+
36+
impl ScalarUDFImpl for ErrorUDF {
37+
fn as_any(&self) -> &dyn std::any::Any {
38+
self
39+
}
40+
41+
fn name(&self) -> &str {
42+
self.name
43+
}
44+
45+
fn signature(&self) -> &Signature {
46+
static S: Signature = Signature {
47+
type_signature: TypeSignature::Uniform(0, vec![]),
48+
volatility: Volatility::Immutable,
49+
};
50+
51+
&S
52+
}
53+
54+
fn return_type(&self, _arg_types: &[DataType]) -> DataFusionResult<DataType> {
55+
Ok(DataType::Null)
56+
}
57+
58+
fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> DataFusionResult<ColumnarValue> {
59+
Err((self.err)())
60+
}
61+
}
62+
63+
/// Returns our evil UDFs.
64+
///
65+
/// The passed `source` is ignored.
66+
#[expect(clippy::unnecessary_wraps, reason = "public API through export! macro")]
67+
pub(crate) fn udfs(_source: String) -> DataFusionResult<Vec<Arc<dyn ScalarUDFImpl>>> {
68+
Ok(vec![
69+
Arc::new(ErrorUDF::new("long_ctx", || {
70+
let limit: usize = std::env::var("limit").unwrap().parse().unwrap();
71+
DataFusionError::Execution("foo".to_owned())
72+
.context(std::iter::repeat_n('x', limit + 1).collect::<String>())
73+
})),
74+
Arc::new(ErrorUDF::new("long_msg", || {
75+
let limit: usize = std::env::var("limit").unwrap().parse().unwrap();
76+
DataFusionError::Execution(std::iter::repeat_n('x', limit + 1).collect())
77+
})),
78+
Arc::new(ErrorUDF::new("nested_ctx", || {
79+
let limit: usize = std::env::var("limit").unwrap().parse().unwrap();
80+
(0..=limit).fold(DataFusionError::Execution("foo".to_owned()), |err, _| {
81+
err.context("bar")
82+
})
83+
})),
84+
])
85+
}

guests/evil/src/complex/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use datafusion_expr::{
66
ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, TypeSignature, Volatility,
77
};
88

9+
pub(crate) mod error;
910
pub(crate) mod udf_long_name;
1011
pub(crate) mod udfs_duplicate_names;
1112
pub(crate) mod udfs_many;

guests/evil/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ impl Evil {
3636
/// Get evil, multiplexed by env.
3737
fn get() -> Self {
3838
match std::env::var("EVIL").expect("evil specified").as_str() {
39+
"complex::error" => Self {
40+
root: Box::new(common::root_empty),
41+
udfs: Box::new(complex::error::udfs),
42+
},
3943
"complex::udf_long_name" => Self {
4044
root: Box::new(common::root_empty),
4145
udfs: Box::new(complex::udf_long_name::udfs),

host/tests/integration_tests/evil/complex.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,62 @@
1+
use std::sync::Arc;
2+
3+
use arrow::datatypes::{DataType, Field};
4+
use datafusion_common::{DataFusionError, config::ConfigOptions};
5+
use datafusion_expr::{ScalarFunctionArgs, ScalarUDFImpl, async_udf::AsyncScalarUDFImpl};
16
use datafusion_udf_wasm_host::{WasmPermissions, conversion::limits::TrustedDataLimits};
27

38
use crate::integration_tests::evil::test_utils::{try_scalar_udfs, try_scalar_udfs_with_env};
49

10+
#[tokio::test]
11+
async fn test_err_long_ctx() {
12+
let err = run_err_udf(
13+
"long_ctx",
14+
TrustedDataLimits::default().max_aux_string_length,
15+
)
16+
.await;
17+
18+
insta::assert_snapshot!(
19+
err,
20+
@r"
21+
convert error from WASI
22+
caused by
23+
Resources exhausted: auxiliary string length: got=10001, limit=10000
24+
",
25+
);
26+
}
27+
28+
#[tokio::test]
29+
async fn test_err_long_msg() {
30+
let err = run_err_udf(
31+
"long_msg",
32+
TrustedDataLimits::default().max_aux_string_length,
33+
)
34+
.await;
35+
36+
insta::assert_snapshot!(
37+
err,
38+
@r"
39+
convert error from WASI
40+
caused by
41+
Resources exhausted: auxiliary string length: got=10001, limit=10000
42+
",
43+
);
44+
}
45+
46+
#[tokio::test]
47+
async fn test_err_nested_ctx() {
48+
let err = run_err_udf("nested_ctx", TrustedDataLimits::default().max_depth as _).await;
49+
50+
insta::assert_snapshot!(
51+
err,
52+
@r"
53+
convert error from WASI
54+
caused by
55+
Resources exhausted: data structure depth: limit=10
56+
",
57+
);
58+
}
59+
560
#[tokio::test]
661
async fn test_udf_long_name() {
762
let err = try_scalar_udfs_with_env(
@@ -45,3 +100,23 @@ async fn test_udfs_many() {
45100
err,
46101
@"Resources exhausted: guest returned too many UDFs: got=21, limit=20");
47102
}
103+
104+
/// Test UDF related to Error handling.
105+
async fn run_err_udf(name: &'static str, limit: usize) -> DataFusionError {
106+
let udf = try_scalar_udfs_with_env("complex::error", &[("limit", &limit.to_string())])
107+
.await
108+
.unwrap()
109+
.into_iter()
110+
.find(|udf| udf.name() == name)
111+
.unwrap();
112+
113+
udf.invoke_async_with_args(ScalarFunctionArgs {
114+
args: vec![],
115+
arg_fields: vec![],
116+
number_rows: 1,
117+
return_field: Arc::new(Field::new("r", DataType::Null, true)),
118+
config_options: Arc::new(ConfigOptions::default()),
119+
})
120+
.await
121+
.unwrap_err()
122+
}

0 commit comments

Comments
 (0)