diff --git a/datafusion/functions/src/datetime/common.rs b/datafusion/functions/src/datetime/common.rs index 4f48ab188403e..1a25c06723847 100644 --- a/datafusion/functions/src/datetime/common.rs +++ b/datafusion/functions/src/datetime/common.rs @@ -155,6 +155,7 @@ pub(crate) fn handle<'a, O, F, S>( args: &'a [ColumnarValue], op: F, name: &str, + safe: bool, ) -> Result where O: ArrowPrimitiveType, @@ -164,14 +165,25 @@ where match &args[0] { ColumnarValue::Array(a) => match a.data_type() { DataType::Utf8 | DataType::LargeUtf8 => Ok(ColumnarValue::Array(Arc::new( - unary_string_to_primitive_function::(&[a.as_ref()], op, name)?, + unary_string_to_primitive_function::( + &[a.as_ref()], + op, + name, + safe, + )?, ))), other => exec_err!("Unsupported data type {other:?} for function {name}"), }, ColumnarValue::Scalar(scalar) => match scalar { ScalarValue::Utf8(a) | ScalarValue::LargeUtf8(a) => { - let result = a.as_ref().map(|x| (op)(x)).transpose()?; - Ok(ColumnarValue::Scalar(S::scalar(result))) + let result = a.as_ref().map(|x| op(x)).transpose(); + if let Ok(v) = result { + Ok(ColumnarValue::Scalar(S::scalar(v))) + } else if safe { + Ok(ColumnarValue::Scalar(S::scalar(None))) + } else { + Err(result.err().unwrap()) + } } other => exec_err!("Unsupported data type {other:?} for function {name}"), }, @@ -186,6 +198,7 @@ pub(crate) fn handle_multiple<'a, O, F, S, M>( op: F, op2: M, name: &str, + safe: bool, ) -> Result where O: ArrowPrimitiveType, @@ -217,7 +230,9 @@ where } Ok(ColumnarValue::Array(Arc::new( - strings_to_primitive_function::(args, op, op2, name)?, + strings_to_primitive_function::( + args, op, op2, name, safe, + )?, ))) } other => { @@ -264,8 +279,13 @@ where if let Some(v) = val { v + } else if safe { + Ok(ColumnarValue::Scalar(S::scalar(None))) } else { - Err(err.unwrap()) + match err { + Some(e) => Err(e), + None => Ok(ColumnarValue::Scalar(S::scalar(None))), + } } } other => { @@ -285,12 +305,13 @@ where /// This function errors iff: /// * the number of arguments is not > 1 or /// * the array arguments are not castable to a `GenericStringArray` or -/// * the function `op` errors for all input +/// * the function `op` errors for all input and safe is false pub(crate) fn strings_to_primitive_function<'a, T, O, F, F2>( args: &'a [ColumnarValue], op: F, op2: F2, name: &str, + safe: bool, ) -> Result> where O: ArrowPrimitiveType, @@ -360,6 +381,7 @@ where }; val.transpose() + .or_else(|e| if safe { Ok(None) } else { Err(e) }) }) .collect() } @@ -371,11 +393,12 @@ where /// This function errors iff: /// * the number of arguments is not 1 or /// * the first argument is not castable to a `GenericStringArray` or -/// * the function `op` errors +/// * the function `op` errors and safe is false fn unary_string_to_primitive_function<'a, T, O, F>( args: &[&'a dyn Array], op: F, name: &str, + safe: bool, ) -> Result> where O: ArrowPrimitiveType, @@ -393,5 +416,12 @@ where let array = as_generic_string_array::(args[0])?; // first map is the iterator, second is for the `Option<_>` - array.iter().map(|x| x.map(&op).transpose()).collect() + array + .iter() + .map(|x| { + x.map(&op) + .transpose() + .or_else(|e| if safe { Ok(None) } else { Err(e) }) + }) + .collect() } diff --git a/datafusion/functions/src/datetime/to_date.rs b/datafusion/functions/src/datetime/to_date.rs index e491c0b555083..4ce6a513b7389 100644 --- a/datafusion/functions/src/datetime/to_date.rs +++ b/datafusion/functions/src/datetime/to_date.rs @@ -18,6 +18,7 @@ use std::any::Any; use arrow::array::types::Date32Type; +use arrow::compute::CastOptions; use arrow::datatypes::DataType; use arrow::datatypes::DataType::Date32; @@ -28,6 +29,8 @@ use datafusion_expr::{ColumnarValue, ScalarUDFImpl, Signature, Volatility}; #[derive(Debug)] pub struct ToDateFunc { signature: Signature, + /// how to handle cast or parsing failures, either return NULL (safe=true) or return ERR (safe=false) + safe: bool, } impl Default for ToDateFunc { @@ -40,6 +43,14 @@ impl ToDateFunc { pub fn new() -> Self { Self { signature: Signature::variadic_any(Volatility::Immutable), + safe: false, + } + } + + pub fn new_with_safe(safe: bool) -> Self { + Self { + signature: Signature::variadic_any(Volatility::Immutable), + safe, } } @@ -57,6 +68,7 @@ impl ToDateFunc { }) }, "to_date", + self.safe, ), n if n >= 2 => handle_multiple::( args, @@ -71,6 +83,7 @@ impl ToDateFunc { }, |n| n, "to_date", + self.safe, ), _ => exec_err!("Unsupported 0 argument count for function to_date"), } @@ -110,7 +123,10 @@ impl ScalarUDFImpl for ToDateFunc { | DataType::Null | DataType::Float64 | DataType::Date32 - | DataType::Date64 => args[0].cast_to(&DataType::Date32, None), + | DataType::Date64 => match self.safe { + true => args[0].cast_to(&DataType::Date32, Some(&CastOptions::default())), + false => args[0].cast_to(&DataType::Date32, None), + }, DataType::Utf8 => self.to_date(args), other => { exec_err!("Unsupported data type {:?} for function to_date", other) diff --git a/datafusion/functions/src/datetime/to_timestamp.rs b/datafusion/functions/src/datetime/to_timestamp.rs index 4cb91447f3867..6a8a592c6e19a 100644 --- a/datafusion/functions/src/datetime/to_timestamp.rs +++ b/datafusion/functions/src/datetime/to_timestamp.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use arrow::compute::CastOptions; use std::any::Any; use arrow::datatypes::DataType::Timestamp; @@ -32,26 +33,36 @@ use crate::datetime::common::*; #[derive(Debug)] pub struct ToTimestampFunc { signature: Signature, + /// how to handle cast or parsing failures, either return NULL (safe=true) or return ERR (safe=false) + pub safe: bool, } #[derive(Debug)] pub struct ToTimestampSecondsFunc { signature: Signature, + /// how to handle cast or parsing failures, either return NULL (safe=true) or return ERR (safe=false) + pub safe: bool, } #[derive(Debug)] pub struct ToTimestampMillisFunc { signature: Signature, + /// how to handle cast or parsing failures, either return NULL (safe=true) or return ERR (safe=false) + pub safe: bool, } #[derive(Debug)] pub struct ToTimestampMicrosFunc { signature: Signature, + /// how to handle cast or parsing failures, either return NULL (safe=true) or return ERR (safe=false) + pub safe: bool, } #[derive(Debug)] pub struct ToTimestampNanosFunc { signature: Signature, + /// how to handle cast or parsing failures, either return NULL (safe=true) or return ERR (safe=false) + pub safe: bool, } impl Default for ToTimestampFunc { @@ -64,6 +75,14 @@ impl ToTimestampFunc { pub fn new() -> Self { Self { signature: Signature::variadic_any(Volatility::Immutable), + safe: false, + } + } + + pub fn new_with_safe(safe: bool) -> Self { + Self { + signature: Signature::variadic_any(Volatility::Immutable), + safe, } } } @@ -78,6 +97,14 @@ impl ToTimestampSecondsFunc { pub fn new() -> Self { Self { signature: Signature::variadic_any(Volatility::Immutable), + safe: false, + } + } + + pub fn new_with_safe(safe: bool) -> Self { + Self { + signature: Signature::variadic_any(Volatility::Immutable), + safe, } } } @@ -92,6 +119,14 @@ impl ToTimestampMillisFunc { pub fn new() -> Self { Self { signature: Signature::variadic_any(Volatility::Immutable), + safe: false, + } + } + + pub fn new_with_safe(safe: bool) -> Self { + Self { + signature: Signature::variadic_any(Volatility::Immutable), + safe, } } } @@ -106,6 +141,14 @@ impl ToTimestampMicrosFunc { pub fn new() -> Self { Self { signature: Signature::variadic_any(Volatility::Immutable), + safe: false, + } + } + + pub fn new_with_safe(safe: bool) -> Self { + Self { + signature: Signature::variadic_any(Volatility::Immutable), + safe, } } } @@ -120,6 +163,14 @@ impl ToTimestampNanosFunc { pub fn new() -> Self { Self { signature: Signature::variadic_any(Volatility::Immutable), + safe: false, + } + } + + pub fn new_with_safe(safe: bool) -> Self { + Self { + signature: Signature::variadic_any(Volatility::Immutable), + safe, } } } @@ -161,18 +212,29 @@ impl ScalarUDFImpl for ToTimestampFunc { } match args[0].data_type() { - DataType::Int32 | DataType::Int64 => args[0] - .cast_to(&Timestamp(Second, None), None)? - .cast_to(&Timestamp(Nanosecond, None), None), - DataType::Null | DataType::Float64 | Timestamp(_, None) => { - args[0].cast_to(&Timestamp(Nanosecond, None), None) - } - DataType::Timestamp(_, Some(tz)) => { - args[0].cast_to(&Timestamp(Nanosecond, Some(tz)), None) - } - DataType::Utf8 => { - to_timestamp_impl::(args, "to_timestamp") - } + DataType::Int32 | DataType::Int64 => match self.safe { + true => args[0] + .cast_to(&Timestamp(Second, None), Some(&CastOptions::default()))? + .cast_to(&Timestamp(Nanosecond, None), Some(&CastOptions::default())), + false => args[0] + .cast_to(&Timestamp(Second, None), None)? + .cast_to(&Timestamp(Nanosecond, None), None), + }, + DataType::Null | DataType::Float64 => match self.safe { + true => args[0] + .cast_to(&Timestamp(Nanosecond, None), Some(&CastOptions::default())), + false => args[0].cast_to(&Timestamp(Nanosecond, None), None), + }, + Timestamp(_, tz) => match self.safe { + true => args[0] + .cast_to(&Timestamp(Nanosecond, tz), Some(&CastOptions::default())), + false => args[0].cast_to(&Timestamp(Nanosecond, tz), None), + }, + DataType::Utf8 => to_timestamp_impl::( + args, + "to_timestamp", + self.safe, + ), other => { exec_err!( "Unsupported data type {:?} for function to_timestamp", @@ -214,15 +276,22 @@ impl ScalarUDFImpl for ToTimestampSecondsFunc { } match args[0].data_type() { - DataType::Null | DataType::Int32 | DataType::Int64 | Timestamp(_, None) => { - args[0].cast_to(&Timestamp(Second, None), None) - } - DataType::Timestamp(_, Some(tz)) => { - args[0].cast_to(&Timestamp(Second, Some(tz)), None) - } - DataType::Utf8 => { - to_timestamp_impl::(args, "to_timestamp_seconds") - } + DataType::Null | DataType::Int32 | DataType::Int64 => match self.safe { + true => args[0] + .cast_to(&Timestamp(Second, None), Some(&CastOptions::default())), + false => args[0].cast_to(&Timestamp(Second, None), None), + }, + Timestamp(_, tz) => match self.safe { + true => { + args[0].cast_to(&Timestamp(Second, tz), Some(&CastOptions::default())) + } + false => args[0].cast_to(&Timestamp(Second, tz), None), + }, + DataType::Utf8 => to_timestamp_impl::( + args, + "to_timestamp_seconds", + self.safe, + ), other => { exec_err!( "Unsupported data type {:?} for function to_timestamp_seconds", @@ -264,15 +333,23 @@ impl ScalarUDFImpl for ToTimestampMillisFunc { } match args[0].data_type() { - DataType::Null | DataType::Int32 | DataType::Int64 | Timestamp(_, None) => { - args[0].cast_to(&Timestamp(Millisecond, None), None) - } - DataType::Timestamp(_, Some(tz)) => { - args[0].cast_to(&Timestamp(Millisecond, Some(tz)), None) - } - DataType::Utf8 => { - to_timestamp_impl::(args, "to_timestamp_millis") - } + DataType::Null | DataType::Int32 | DataType::Int64 => match self.safe { + true => args[0].cast_to( + &Timestamp(Millisecond, None), + Some(&CastOptions::default()), + ), + false => args[0].cast_to(&Timestamp(Millisecond, None), None), + }, + Timestamp(_, tz) => match self.safe { + true => args[0] + .cast_to(&Timestamp(Millisecond, tz), Some(&CastOptions::default())), + false => args[0].cast_to(&Timestamp(Millisecond, tz), None), + }, + DataType::Utf8 => to_timestamp_impl::( + args, + "to_timestamp_millis", + self.safe, + ), other => { exec_err!( "Unsupported data type {:?} for function to_timestamp_millis", @@ -314,15 +391,23 @@ impl ScalarUDFImpl for ToTimestampMicrosFunc { } match args[0].data_type() { - DataType::Null | DataType::Int32 | DataType::Int64 | Timestamp(_, None) => { - args[0].cast_to(&Timestamp(Microsecond, None), None) - } - DataType::Timestamp(_, Some(tz)) => { - args[0].cast_to(&Timestamp(Microsecond, Some(tz)), None) - } - DataType::Utf8 => { - to_timestamp_impl::(args, "to_timestamp_micros") - } + DataType::Null | DataType::Int32 | DataType::Int64 => match self.safe { + true => args[0].cast_to( + &Timestamp(Microsecond, None), + Some(&CastOptions::default()), + ), + false => args[0].cast_to(&Timestamp(Microsecond, None), None), + }, + Timestamp(_, tz) => match self.safe { + true => args[0] + .cast_to(&Timestamp(Microsecond, tz), Some(&CastOptions::default())), + false => args[0].cast_to(&Timestamp(Microsecond, tz), None), + }, + DataType::Utf8 => to_timestamp_impl::( + args, + "to_timestamp_micros", + self.safe, + ), other => { exec_err!( "Unsupported data type {:?} for function to_timestamp_micros", @@ -364,15 +449,21 @@ impl ScalarUDFImpl for ToTimestampNanosFunc { } match args[0].data_type() { - DataType::Null | DataType::Int32 | DataType::Int64 | Timestamp(_, None) => { - args[0].cast_to(&Timestamp(Nanosecond, None), None) - } - DataType::Timestamp(_, Some(tz)) => { - args[0].cast_to(&Timestamp(Nanosecond, Some(tz)), None) - } - DataType::Utf8 => { - to_timestamp_impl::(args, "to_timestamp_nanos") - } + DataType::Null | DataType::Int32 | DataType::Int64 => match self.safe { + true => args[0] + .cast_to(&Timestamp(Nanosecond, None), Some(&CastOptions::default())), + false => args[0].cast_to(&Timestamp(Nanosecond, None), None), + }, + Timestamp(_, tz) => match self.safe { + true => args[0] + .cast_to(&Timestamp(Nanosecond, tz), Some(&CastOptions::default())), + false => args[0].cast_to(&Timestamp(Nanosecond, tz), None), + }, + DataType::Utf8 => to_timestamp_impl::( + args, + "to_timestamp_nanos", + self.safe, + ), other => { exec_err!( "Unsupported data type {:?} for function to_timestamp_nanos", @@ -395,6 +486,7 @@ fn return_type_for(arg: &DataType, unit: TimeUnit) -> DataType { fn to_timestamp_impl>( args: &[ColumnarValue], name: &str, + safe: bool, ) -> Result { let factor = match T::UNIT { Second => 1_000_000_000, @@ -408,12 +500,14 @@ fn to_timestamp_impl>( args, |s| string_to_timestamp_nanos_shim(s).map(|n| n / factor), name, + safe, ), n if n >= 2 => handle_multiple::( args, string_to_timestamp_nanos_formatted, |n| n / factor, name, + safe, ), _ => exec_err!("Unsupported 0 argument count for function {name}"), } @@ -437,28 +531,32 @@ mod tests { use super::*; - fn to_timestamp(args: &[ColumnarValue]) -> Result { - to_timestamp_impl::(args, "to_timestamp") + fn to_timestamp_unsafe(args: &[ColumnarValue]) -> Result { + to_timestamp_impl::(args, "to_timestamp", false) + } + + fn to_timestamp_safe(args: &[ColumnarValue]) -> Result { + to_timestamp_impl::(args, "to_timestamp", true) } /// to_timestamp_millis SQL function fn to_timestamp_millis(args: &[ColumnarValue]) -> Result { - to_timestamp_impl::(args, "to_timestamp_millis") + to_timestamp_impl::(args, "to_timestamp_millis", false) } /// to_timestamp_micros SQL function fn to_timestamp_micros(args: &[ColumnarValue]) -> Result { - to_timestamp_impl::(args, "to_timestamp_micros") + to_timestamp_impl::(args, "to_timestamp_micros", false) } /// to_timestamp_nanos SQL function fn to_timestamp_nanos(args: &[ColumnarValue]) -> Result { - to_timestamp_impl::(args, "to_timestamp_nanos") + to_timestamp_impl::(args, "to_timestamp_nanos", false) } /// to_timestamp_seconds SQL function fn to_timestamp_seconds(args: &[ColumnarValue]) -> Result { - to_timestamp_impl::(args, "to_timestamp_seconds") + to_timestamp_impl::(args, "to_timestamp_seconds", false) } #[test] @@ -477,7 +575,7 @@ mod tests { let string_array = ColumnarValue::Array(Arc::new(string_builder.finish()) as ArrayRef); - let parsed_timestamps = to_timestamp(&[string_array]) + let parsed_timestamps = to_timestamp_unsafe(&[string_array]) .expect("that to_timestamp parsed values without error"); if let ColumnarValue::Array(parsed_array) = parsed_timestamps { assert_eq!(parsed_array.len(), 2); @@ -518,7 +616,7 @@ mod tests { ColumnarValue::Array(Arc::new(format2_builder.finish()) as ArrayRef), ColumnarValue::Array(Arc::new(format3_builder.finish()) as ArrayRef), ]; - let parsed_timestamps = to_timestamp(&string_array) + let parsed_timestamps = to_timestamp_unsafe(&string_array) .expect("that to_timestamp with format args parsed values without error"); if let ColumnarValue::Array(parsed_array) = parsed_timestamps { assert_eq!(parsed_array.len(), 2); @@ -540,7 +638,7 @@ mod tests { let expected_err = "Execution error: Unsupported data type Int64 for function to_timestamp"; - match to_timestamp(&[int64array]) { + match to_timestamp_unsafe(&[int64array]) { Ok(_) => panic!("Expected error but got success"), Err(e) => { assert!( @@ -566,7 +664,7 @@ mod tests { let expected_err = "Execution error: Unsupported data type Int64 for function to_timestamp"; - match to_timestamp(&int64array) { + match to_timestamp_unsafe(&int64array) { Ok(_) => panic!("Expected error but got success"), Err(e) => { assert!( @@ -579,11 +677,9 @@ mod tests { } #[test] - fn to_timestamp_with_unparseable_data() -> Result<()> { + fn to_timestamp_with_unparseable_data_with_safe_off() -> Result<()> { let mut date_string_builder = StringBuilder::with_capacity(2, 1024); - date_string_builder.append_null(); - date_string_builder.append_value("2020-09-08 - 13:42:29.19085Z"); let string_array = @@ -591,7 +687,7 @@ mod tests { let expected_err = "Arrow error: Parser error: Error parsing timestamp from '2020-09-08 - 13:42:29.19085Z': error parsing time"; - match to_timestamp(&[string_array]) { + match to_timestamp_unsafe(&[string_array]) { Ok(_) => panic!("Expected error but got success"), Err(e) => { assert!( @@ -604,11 +700,36 @@ mod tests { } #[test] - fn to_timestamp_with_invalid_tz() -> Result<()> { + fn to_timestamp_with_unparseable_data_with_safe_on() -> Result<()> { let mut date_string_builder = StringBuilder::with_capacity(2, 1024); - date_string_builder.append_null(); + date_string_builder.append_value("2020-09-08 - 13:42:29.19085Z"); + + let string_array = + ColumnarValue::Array(Arc::new(date_string_builder.finish()) as ArrayRef); + + let parsed_timestamps = to_timestamp_safe(&[string_array]) + .expect("that to_timestamp with format args parsed values without error when safe is on"); + + let mut ts_builder = TimestampNanosecondArray::builder(2); + ts_builder.append_null(); + ts_builder.append_null(); + let expected_timestamps = &ts_builder.finish() as &dyn Array; + + if let ColumnarValue::Array(parsed_array) = parsed_timestamps { + assert_eq!(parsed_array.len(), 2); + assert_eq!(expected_timestamps, parsed_array.as_ref()); + } else { + panic!("Expected a columnar array") + } + + Ok(()) + } + #[test] + fn to_timestamp_with_invalid_tz_with_safe_off() -> Result<()> { + let mut date_string_builder = StringBuilder::with_capacity(2, 1024); + date_string_builder.append_null(); date_string_builder.append_value("2020-09-08T13:42:29ZZ"); let string_array = @@ -616,7 +737,7 @@ mod tests { let expected_err = "Arrow error: Parser error: Invalid timezone \"ZZ\": failed to parse timezone"; - match to_timestamp(&[string_array]) { + match to_timestamp_unsafe(&[string_array]) { Ok(_) => panic!("Expected error but got success"), Err(e) => { assert!( @@ -629,7 +750,34 @@ mod tests { } #[test] - fn to_timestamp_with_no_matching_formats() -> Result<()> { + fn to_timestamp_with_invalid_tz_with_safe_on() -> Result<()> { + let mut date_string_builder = StringBuilder::with_capacity(2, 1024); + date_string_builder.append_null(); + date_string_builder.append_value("2020-09-08T13:42:29ZZ"); + + let string_array = + ColumnarValue::Array(Arc::new(date_string_builder.finish()) as ArrayRef); + + let parsed_timestamps = to_timestamp_safe(&[string_array]) + .expect("that to_timestamp with format args parsed values without error when safe is on"); + + let mut ts_builder = TimestampNanosecondArray::builder(2); + ts_builder.append_null(); + ts_builder.append_null(); + let expected_timestamps = &ts_builder.finish() as &dyn Array; + + if let ColumnarValue::Array(parsed_array) = parsed_timestamps { + assert_eq!(parsed_array.len(), 2); + assert_eq!(expected_timestamps, parsed_array.as_ref()); + } else { + panic!("Expected a columnar array") + } + + Ok(()) + } + + #[test] + fn to_timestamp_with_no_matching_formats_with_safe_off() -> Result<()> { let mut date_string_builder = StringBuilder::with_capacity(2, 1024); let mut format1_builder = StringBuilder::with_capacity(2, 1024); let mut format2_builder = StringBuilder::with_capacity(2, 1024); @@ -654,7 +802,7 @@ mod tests { let expected_err = "Execution error: Error parsing timestamp from '2020-09-08T13:42:29.19085Z' using format '%H:%M:%S': input contains invalid characters"; - match to_timestamp(&string_array) { + match to_timestamp_unsafe(&string_array) { Ok(_) => panic!("Expected error but got success"), Err(e) => { assert!( @@ -666,6 +814,54 @@ mod tests { Ok(()) } + #[test] + fn to_timestamp_with_no_matching_formats_with_safe_on() -> Result<()> { + let mut date_string_builder = StringBuilder::with_capacity(2, 1024); + let mut format1_builder = StringBuilder::with_capacity(2, 1024); + let mut format2_builder = StringBuilder::with_capacity(2, 1024); + let mut format3_builder = StringBuilder::with_capacity(2, 1024); + + date_string_builder.append_null(); + format1_builder.append_null(); + format2_builder.append_null(); + format3_builder.append_null(); + + date_string_builder.append_value("2020-09-08T13:42:29.19085Z"); + format1_builder.append_value("%s"); + format2_builder.append_value("%c"); + format3_builder.append_value("%H:%M:%S"); + + date_string_builder.append_value("2020-09-08T13:42:29"); + format1_builder.append_value("%s"); + format2_builder.append_value("%c"); + format3_builder.append_value("%Y-%m-%dT%H:%M:%S"); + + let string_array = [ + ColumnarValue::Array(Arc::new(date_string_builder.finish()) as ArrayRef), + ColumnarValue::Array(Arc::new(format1_builder.finish()) as ArrayRef), + ColumnarValue::Array(Arc::new(format2_builder.finish()) as ArrayRef), + ColumnarValue::Array(Arc::new(format3_builder.finish()) as ArrayRef), + ]; + + let parsed_timestamps = to_timestamp_safe(&string_array) + .expect("that to_timestamp with format args parsed values without error when safe is on"); + + let mut ts_builder = TimestampNanosecondArray::builder(2); + ts_builder.append_null(); + ts_builder.append_null(); + ts_builder.append_value(1599572549000000000); + let expected_timestamps = &ts_builder.finish() as &dyn Array; + + if let ColumnarValue::Array(parsed_array) = parsed_timestamps { + assert_eq!(parsed_array.len(), 3); + assert_eq!(expected_timestamps, parsed_array.as_ref()); + } else { + panic!("Expected a columnar array") + } + + Ok(()) + } + #[test] fn string_to_timestamp_formatted() { // Explicit timezone @@ -869,7 +1065,7 @@ mod tests { let data = date_string_builder.finish(); let funcs: Vec<(ScalarFunctionImplementation, TimeUnit)> = vec![ - (Arc::new(to_timestamp), Nanosecond), + (Arc::new(to_timestamp_unsafe), Nanosecond), (Arc::new(to_timestamp_micros), Microsecond), (Arc::new(to_timestamp_millis), Millisecond), (Arc::new(to_timestamp_nanos), Nanosecond),