This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch 
gh-readonly-queue/main/pr-23470-360a56dfe48edfb5b7391b911b75d22959e33c6a
in repository https://gitbox.apache.org/repos/asf/datafusion.git

commit f87e8174c107f61740606fa8c38b8f446911a1e8
Author: Andy Grove <[email protected]>
AuthorDate: Sun Jul 12 13:35:39 2026 -0600

    perf: optimize make_date in datafusion-functions (#23470)
    
    ## Which issue does this PR close?
    
    N/A
    ## Rationale for this change
    
    Optimize existing expression.
    
    ## What changes are included in this PR?
    
    Replaced make_date's per-element triple is_null check + PrimitiveBuilder
    with a single unioned NullBuffer, raw value-slice reads, and direct
    PrimitiveArray::new construction (matching the already-optimized
    make_time), cutting per-row overhead on the hot valid-input path.
    
    ## Are these changes tested?
    
    Existing tests + new tests.
    
    Benchmark (criterion):
    
    - make_date_scalar_col_col_8192: 19.979% faster (base 31210ns -> cand
    24974ns)
    - make_date_col_col_col_8192: 16.931% faster (base 30849ns -> cand
    25626ns)
    - make_date_scalar_scalar_col_8192: 22.999% faster (base 31551ns -> cand
    24295ns)
    - make_date_scalar_scalar_scalar: 1.271% faster (base 48ns -> cand 48ns)
    
    
    ## Are there any user-facing changes?
    
    No
    
    <!--
    If there are user-facing changes then we may require documentation to be
    updated before approving the PR.
    -->
    
    <!--
    If there are any breaking changes to public APIs, please add the `api
    change` label.
    -->
---
 datafusion/functions/src/datetime/make_date.rs | 102 +++++++++++++++++++++++--
 1 file changed, 95 insertions(+), 7 deletions(-)

diff --git a/datafusion/functions/src/datetime/make_date.rs 
b/datafusion/functions/src/datetime/make_date.rs
index dc1328742f..3a6b76ed86 100644
--- a/datafusion/functions/src/datetime/make_date.rs
+++ b/datafusion/functions/src/datetime/make_date.rs
@@ -17,10 +17,10 @@
 
 use std::sync::Arc;
 
-use arrow::array::builder::PrimitiveBuilder;
 use arrow::array::cast::AsArray;
 use arrow::array::types::{Date32Type, Int32Type};
 use arrow::array::{Array, PrimitiveArray};
+use arrow::buffer::NullBuffer;
 use arrow::datatypes::DataType;
 use arrow::datatypes::DataType::Date32;
 use chrono::prelude::*;
@@ -139,24 +139,27 @@ impl ScalarUDFImpl for MakeDateFunc {
                 let months = months.as_primitive::<Int32Type>();
                 let days = days.as_primitive::<Int32Type>();
 
-                let mut builder: PrimitiveBuilder<Date32Type> =
-                    PrimitiveArray::builder(len);
+                let nulls =
+                    NullBuffer::union_many([years.nulls(), months.nulls(), 
days.nulls()]);
 
+                let mut values = Vec::with_capacity(len);
                 for i in 0..len {
                     // match postgresql behaviour which returns null for any 
null input
-                    if years.is_null(i) || months.is_null(i) || 
days.is_null(i) {
-                        builder.append_null();
+                    if nulls.as_ref().is_some_and(|n| n.is_null(i)) {
+                        values.push(0);
                     } else {
                         make_date_inner(
                             years.value(i),
                             months.value(i),
                             days.value(i),
-                            |days: i32| builder.append_value(days),
+                            |days: i32| values.push(days),
                         )?;
                     }
                 }
 
-                Ok(ColumnarValue::Array(Arc::new(builder.finish())))
+                Ok(ColumnarValue::Array(Arc::new(
+                    PrimitiveArray::<Date32Type>::new(values.into(), nulls),
+                )))
             }
         }
     }
@@ -197,3 +200,88 @@ fn make_date_inner<F: FnMut(i32)>(
         exec_err!("Unable to parse date from {year}, {month}, {day}")
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use arrow::array::Int32Array;
+    use arrow::datatypes::Field;
+    use datafusion_common::config::ConfigOptions;
+
+    fn invoke(args: Vec<ColumnarValue>, number_rows: usize) -> 
Result<ColumnarValue> {
+        let arg_fields = args
+            .iter()
+            .map(|a| Field::new("a", a.data_type(), true).into())
+            .collect::<Vec<_>>();
+        MakeDateFunc::new().invoke_with_args(ScalarFunctionArgs {
+            args,
+            arg_fields,
+            number_rows,
+            return_field: Field::new("f", Date32, true).into(),
+            config_options: Arc::new(ConfigOptions::default()),
+        })
+    }
+
+    #[test]
+    fn test_make_date_array() {
+        let years = ColumnarValue::Array(Arc::new(Int32Array::from(vec![
+            Some(1970),
+            Some(1970),
+        ])));
+        let months =
+            ColumnarValue::Array(Arc::new(Int32Array::from(vec![Some(1), 
Some(1)])));
+        let days =
+            ColumnarValue::Array(Arc::new(Int32Array::from(vec![Some(1), 
Some(2)])));
+
+        let ColumnarValue::Array(arr) = invoke(vec![years, months, days], 
2).unwrap()
+        else {
+            panic!("expected array result");
+        };
+        let arr = arr.as_primitive::<Date32Type>();
+        // Days since the unix epoch.
+        assert_eq!(arr.value(0), 0);
+        assert_eq!(arr.value(1), 1);
+    }
+
+    #[test]
+    fn test_make_date_null_propagation() {
+        // A NULL in any component column yields a NULL row.
+        let years =
+            ColumnarValue::Array(Arc::new(Int32Array::from(vec![Some(2000), 
None])));
+        let months =
+            ColumnarValue::Array(Arc::new(Int32Array::from(vec![Some(6), 
Some(6)])));
+        let days = ColumnarValue::Array(Arc::new(Int32Array::from(vec![None, 
Some(15)])));
+
+        let ColumnarValue::Array(arr) = invoke(vec![years, months, days], 
2).unwrap()
+        else {
+            panic!("expected array result");
+        };
+        let arr = arr.as_primitive::<Date32Type>();
+        assert!(arr.is_null(0));
+        assert!(arr.is_null(1));
+    }
+
+    #[test]
+    fn test_make_date_scalar_array_mix() {
+        let year = ColumnarValue::Scalar(ScalarValue::Int32(Some(1970)));
+        let month = ColumnarValue::Scalar(ScalarValue::Int32(Some(1)));
+        let days =
+            ColumnarValue::Array(Arc::new(Int32Array::from(vec![Some(1), 
Some(3)])));
+
+        let ColumnarValue::Array(arr) = invoke(vec![year, month, days], 
2).unwrap()
+        else {
+            panic!("expected array result");
+        };
+        let arr = arr.as_primitive::<Date32Type>();
+        assert_eq!(arr.value(0), 0);
+        assert_eq!(arr.value(1), 2);
+    }
+
+    #[test]
+    fn test_make_date_out_of_range_errors() {
+        let years = 
ColumnarValue::Array(Arc::new(Int32Array::from(vec![Some(2000)])));
+        let months = 
ColumnarValue::Array(Arc::new(Int32Array::from(vec![Some(13)])));
+        let days = 
ColumnarValue::Array(Arc::new(Int32Array::from(vec![Some(1)])));
+        assert!(invoke(vec![years, months, days], 1).is_err());
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to