From 97da270545eeeb38565cae7c038fb53aa5c67bc0 Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Thu, 9 Jul 2026 18:09:21 -0700 Subject: [PATCH] fix: avoid panic in array_position start_from near i64::MIN Signed-off-by: Sai Asish Y --- datafusion/functions-nested/src/position.rs | 44 +++++++++++++++++---- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/datafusion/functions-nested/src/position.rs b/datafusion/functions-nested/src/position.rs index d65620ede38e6..af47be777d125 100644 --- a/datafusion/functions-nested/src/position.rs +++ b/datafusion/functions-nested/src/position.rs @@ -207,14 +207,16 @@ fn resolve_start_from( match third_arg { None => Ok(vec![0i64; num_rows]), Some(ColumnarValue::Scalar(ScalarValue::Int64(Some(v)))) => { - Ok(vec![v - 1; num_rows]) + Ok(vec![v.saturating_sub(1); num_rows]) } Some(ColumnarValue::Scalar(s)) => { exec_err!("array_position expected Int64 for start_from, got {s}") } - Some(ColumnarValue::Array(a)) => { - Ok(as_int64_array(a)?.values().iter().map(|&x| x - 1).collect()) - } + Some(ColumnarValue::Array(a)) => Ok(as_int64_array(a)? + .values() + .iter() + .map(|&x| x.saturating_sub(1)) + .collect()), } } @@ -273,7 +275,7 @@ fn array_position_scalar( let from = arr_from[i]; let row_len = end - start; if !(from >= 0 && (from as usize) <= row_len) { - return exec_err!("start_from out of bounds: {}", from + 1); + return exec_err!("start_from out of bounds: {}", from.saturating_add(1)); } let search_start = start + from as usize; @@ -309,7 +311,7 @@ fn general_position_dispatch(args: &[ArrayRef]) -> Result>() } else { vec![0; haystack.len()] @@ -317,7 +319,7 @@ fn general_position_dispatch(args: &[ArrayRef]) -> Result= 0 && (from as usize) <= row.len()) { - return exec_err!("start_from out of bounds: {}", from + 1); + return exec_err!("start_from out of bounds: {}", from.saturating_add(1)); } } @@ -750,4 +752,32 @@ mod tests { Ok(()) } + + #[test] + fn test_array_position_start_from_i64_min() { + // start_from of i64::MIN used to overflow while converting the + // 1-indexed argument to a 0-indexed position; it should now surface + // an error instead of panicking. + let list = + ListArray::from_iter_primitive::(vec![Some(vec![Some(1)])]); + let haystack_field = + Arc::new(Field::new("haystack", list.data_type().clone(), true)); + let needle_field = Arc::new(Field::new("needle", DataType::Int32, true)); + let start_field = Arc::new(Field::new("start_from", DataType::Int64, true)); + let return_field = Arc::new(Field::new("return", UInt64, true)); + + let result = ArrayPosition::new().invoke_with_args(ScalarFunctionArgs { + args: vec![ + ColumnarValue::Array(Arc::new(list)), + ColumnarValue::Scalar(ScalarValue::Int32(Some(1))), + ColumnarValue::Scalar(ScalarValue::Int64(Some(i64::MIN))), + ], + arg_fields: vec![haystack_field, needle_field, start_field], + number_rows: 1, + return_field, + config_options: Arc::new(ConfigOptions::default()), + }); + + assert!(result.is_err()); + } }