From a68f33ad41f5775648f4ee236a6257639bee5e0e Mon Sep 17 00:00:00 2001 From: theirix Date: Fri, 10 Jul 2026 09:39:07 +0100 Subject: [PATCH 1/4] perf: optimisation for some date_part paths --- .../functions/src/datetime/date_part.rs | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/datafusion/functions/src/datetime/date_part.rs b/datafusion/functions/src/datetime/date_part.rs index 3c405d388bcab..455b14ff9f2e6 100644 --- a/datafusion/functions/src/datetime/date_part.rs +++ b/datafusion/functions/src/datetime/date_part.rs @@ -19,7 +19,9 @@ use std::str::FromStr; use std::sync::Arc; use arrow::array::timezone::Tz; -use arrow::array::{Array, ArrayRef, Float64Array, Int32Array, Int64Array}; +use arrow::array::{ + Array, ArrayRef, Date32Array, Date64Array, Float64Array, Int32Array, Int64Array, +}; use arrow::compute::kernels::cast_utils::IntervalUnit; use arrow::compute::{DatePart, binary, date_part}; use arrow::datatypes::DataType::{ @@ -406,6 +408,32 @@ fn seconds_as_i32(array: &dyn Array, unit: TimeUnit) -> Result { return not_impl_err!("Date part {unit:?} not supported"); } + // Fast path with seconds - no need to compute nanoseconds + if unit == Second { + return Ok(date_part(array, DatePart::Second)?); + } + + // Fast path for Date32 and Date64 - no seconds + match array.data_type() { + Date32 => { + return if array.null_count() == 0 { + Ok(Arc::new(Int32Array::from_value(0, array.len()))) + } else { + let r: Date32Array = as_date32_array(array)?.unary(|_| 0); + Ok(Arc::new(r)) + }; + } + Date64 => { + return if array.null_count() == 0 { + Ok(Arc::new(Int32Array::from_value(0, array.len()))) + } else { + let r: Date64Array = as_date64_array(array)?.unary(|_| 0); + Ok(Arc::new(r)) + }; + } + _ => {} + } + let conversion_factor = match unit { Second => 1_000_000_000, Millisecond => 1_000_000, @@ -547,6 +575,27 @@ fn epoch(array: &dyn Array) -> Result { /// `nanosecond`s in each second, so representing up to 60 seconds as /// nanoseconds can be values up to 60 billion, which does not fit in Int32. fn seconds_ns(array: &dyn Array) -> Result { + // Fast path for Date32 and Date64 - no nanoseconds + match array.data_type() { + Date32 => { + return if array.null_count() == 0 { + Ok(Arc::new(Int64Array::from_value(0, array.len()))) + } else { + let r: Int64Array = as_date32_array(array)?.unary(|_| 0); + Ok(Arc::new(r)) + }; + } + Date64 => { + return if array.null_count() == 0 { + Ok(Arc::new(Int64Array::from_value(0, array.len()))) + } else { + let r: Int64Array = as_date64_array(array)?.unary(|_| 0); + Ok(Arc::new(r)) + }; + } + _ => {} + } + let secs = date_part(array, DatePart::Second)?; // This assumes array is primitive and not a dictionary let secs = as_int32_array(secs.as_ref())?; From 7eadff6ca3165f824b9f416ec0bcb6b850ce6525 Mon Sep 17 00:00:00 2001 From: theirix Date: Sat, 11 Jul 2026 14:57:40 +0100 Subject: [PATCH 2/4] Fix return type --- datafusion/functions/src/datetime/date_part.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/datafusion/functions/src/datetime/date_part.rs b/datafusion/functions/src/datetime/date_part.rs index 455b14ff9f2e6..fd4d0de445d12 100644 --- a/datafusion/functions/src/datetime/date_part.rs +++ b/datafusion/functions/src/datetime/date_part.rs @@ -19,9 +19,7 @@ use std::str::FromStr; use std::sync::Arc; use arrow::array::timezone::Tz; -use arrow::array::{ - Array, ArrayRef, Date32Array, Date64Array, Float64Array, Int32Array, Int64Array, -}; +use arrow::array::{Array, ArrayRef, Float64Array, Int32Array, Int64Array}; use arrow::compute::kernels::cast_utils::IntervalUnit; use arrow::compute::{DatePart, binary, date_part}; use arrow::datatypes::DataType::{ @@ -400,7 +398,7 @@ fn part_normalization(part: &str) -> &str { /// Invoke [`date_part`] on an `array` (e.g. Timestamp) and convert the /// result to a total number of seconds, milliseconds, microseconds or -/// nanoseconds +/// nanoseconds as an `Int32Array` fn seconds_as_i32(array: &dyn Array, unit: TimeUnit) -> Result { // Nanosecond is neither supported in Postgres nor DuckDB, to avoid dealing // with overflow and precision issue we don't support nanosecond @@ -419,7 +417,7 @@ fn seconds_as_i32(array: &dyn Array, unit: TimeUnit) -> Result { return if array.null_count() == 0 { Ok(Arc::new(Int32Array::from_value(0, array.len()))) } else { - let r: Date32Array = as_date32_array(array)?.unary(|_| 0); + let r: Int32Array = as_date32_array(array)?.unary(|_| 0); Ok(Arc::new(r)) }; } @@ -427,7 +425,7 @@ fn seconds_as_i32(array: &dyn Array, unit: TimeUnit) -> Result { return if array.null_count() == 0 { Ok(Arc::new(Int32Array::from_value(0, array.len()))) } else { - let r: Date64Array = as_date64_array(array)?.unary(|_| 0); + let r: Int32Array = as_date64_array(array)?.unary(|_| 0); Ok(Arc::new(r)) }; } @@ -581,7 +579,7 @@ fn seconds_ns(array: &dyn Array) -> Result { return if array.null_count() == 0 { Ok(Arc::new(Int64Array::from_value(0, array.len()))) } else { - let r: Int64Array = as_date32_array(array)?.unary(|_| 0); + let r: Int64Array = as_date32_array(array)?.unary(|_| 0i64); Ok(Arc::new(r)) }; } @@ -589,7 +587,7 @@ fn seconds_ns(array: &dyn Array) -> Result { return if array.null_count() == 0 { Ok(Arc::new(Int64Array::from_value(0, array.len()))) } else { - let r: Int64Array = as_date64_array(array)?.unary(|_| 0); + let r: Int64Array = as_date64_array(array)?.unary(|_| 0i64); Ok(Arc::new(r)) }; } From c371b72818d1bf89d1c2ac3d55a70201c4e52d1d Mon Sep 17 00:00:00 2001 From: theirix Date: Sat, 11 Jul 2026 14:57:52 +0100 Subject: [PATCH 3/4] Add tests for date_part with date32/date64 --- .../test_files/datetime/date_part.slt | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/datafusion/sqllogictest/test_files/datetime/date_part.slt b/datafusion/sqllogictest/test_files/datetime/date_part.slt index 891319f9e2cd2..0a992b2d78a22 100644 --- a/datafusion/sqllogictest/test_files/datetime/date_part.slt +++ b/datafusion/sqllogictest/test_files/datetime/date_part.slt @@ -838,6 +838,40 @@ SELECT extract(millisecond from arrow_cast('23:32:50.123456789'::time, 'Time64(N ---- 50123 +# date32 and date64 + +statement ok +CREATE TABLE source_dt AS +with t as (values + ('1970-01-01'), + ('2020-06-02'), + ('2026-02-28'), + (NULL) +) +SELECT + arrow_cast(column1, 'Date32') as date32, + arrow_cast(column1, 'Date64') as date64, +FROM t; + +query IIIIIIIIII +SELECT date_part('year', date32), date_part('month', date32), date_part('week', date32), date_part('day', date32), date_part('hour', date32), date_part('minute', date32), date_part('second', date32), date_part('millisecond', date32), date_part('microsecond', date32), date_part('nanosecond', date32) FROM source_dt; +---- +1970 1 1 1 0 0 0 0 0 0 +2020 6 23 2 0 0 0 0 0 0 +2026 2 9 28 0 0 0 0 0 0 +NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL + +query IIIIIIIIII +SELECT date_part('year', date64), date_part('month', date64), date_part('week', date64), date_part('day', date64), date_part('hour', date64), date_part('minute', date64), date_part('second', date64), date_part('millisecond', date64), date_part('microsecond', date64), date_part('nanosecond', date64) FROM source_dt; +---- +1970 1 1 1 0 0 0 0 0 0 +2020 6 23 2 0 0 0 0 0 0 +2026 2 9 28 0 0 0 0 0 0 +NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL + +statement ok +drop table source_dt; + # just some floating point stuff happening in the result here query I SELECT date_part('microsecond', arrow_cast('23:32:50.123456789'::time, 'Time64(Nanosecond)')) From ba046e5e34c30a894fc4e829e6f76257803f84c4 Mon Sep 17 00:00:00 2001 From: theirix Date: Sun, 12 Jul 2026 09:05:01 +0100 Subject: [PATCH 4/4] Collapse code into one array construction --- .../functions/src/datetime/date_part.rs | 47 +++++-------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/datafusion/functions/src/datetime/date_part.rs b/datafusion/functions/src/datetime/date_part.rs index fd4d0de445d12..ddc5fd2558548 100644 --- a/datafusion/functions/src/datetime/date_part.rs +++ b/datafusion/functions/src/datetime/date_part.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use std::iter::repeat_n; use std::str::FromStr; use std::sync::Arc; @@ -412,24 +413,11 @@ fn seconds_as_i32(array: &dyn Array, unit: TimeUnit) -> Result { } // Fast path for Date32 and Date64 - no seconds - match array.data_type() { - Date32 => { - return if array.null_count() == 0 { - Ok(Arc::new(Int32Array::from_value(0, array.len()))) - } else { - let r: Int32Array = as_date32_array(array)?.unary(|_| 0); - Ok(Arc::new(r)) - }; - } - Date64 => { - return if array.null_count() == 0 { - Ok(Arc::new(Int32Array::from_value(0, array.len()))) - } else { - let r: Int32Array = as_date64_array(array)?.unary(|_| 0); - Ok(Arc::new(r)) - }; - } - _ => {} + if array.data_type() == &Date32 || array.data_type() == &Date64 { + return Ok(Arc::new(Int32Array::from_iter_values_with_nulls( + repeat_n(0, array.len()), + array.nulls().cloned(), + ))); } let conversion_factor = match unit { @@ -574,24 +562,11 @@ fn epoch(array: &dyn Array) -> Result { /// nanoseconds can be values up to 60 billion, which does not fit in Int32. fn seconds_ns(array: &dyn Array) -> Result { // Fast path for Date32 and Date64 - no nanoseconds - match array.data_type() { - Date32 => { - return if array.null_count() == 0 { - Ok(Arc::new(Int64Array::from_value(0, array.len()))) - } else { - let r: Int64Array = as_date32_array(array)?.unary(|_| 0i64); - Ok(Arc::new(r)) - }; - } - Date64 => { - return if array.null_count() == 0 { - Ok(Arc::new(Int64Array::from_value(0, array.len()))) - } else { - let r: Int64Array = as_date64_array(array)?.unary(|_| 0i64); - Ok(Arc::new(r)) - }; - } - _ => {} + if array.data_type() == &Date32 || array.data_type() == &Date64 { + return Ok(Arc::new(Int64Array::from_iter_values_with_nulls( + repeat_n(0, array.len()), + array.nulls().cloned(), + ))); } let secs = date_part(array, DatePart::Second)?;