## Coverage Report

#### Created: 2025-11-22 06:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /Users/andrewlamb/Software/datafusion/datafusion/functions/src/datetime/make_date.rs

    Line
    Count
    Source
    1
    // Licensed to the Apache Software Foundation (ASF) under one
    2
    // or more contributor license agreements.  See the NOTICE file
    3
    // distributed with this work for additional information
    4
    // regarding copyright ownership.  The ASF licenses this file
    5
    // to you under the Apache License, Version 2.0 (the
    6
    // "License"); you may not use this file except in compliance
    7
    // with the License.  You may obtain a copy of the License at
    8
    //
    9
    //   http://www.apache.org/licenses/LICENSE-2.0
    10
    //
    11
    // Unless required by applicable law or agreed to in writing,
    12
    // software distributed under the License is distributed on an
    13
    // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14
    // KIND, either express or implied.  See the License for the
    15
    // specific language governing permissions and limitations
    16
    // under the License.
    17
    18
    use std::any::Any;
    19
    use std::sync::Arc;
    20
    21
    use arrow::array::builder::PrimitiveBuilder;
    22
    use arrow::array::cast::AsArray;
    23
    use arrow::array::types::{Date32Type, Int32Type};
    24
    use arrow::array::PrimitiveArray;
    25
    use arrow::datatypes::DataType;
    26
    use arrow::datatypes::DataType::{Date32, Int32, Int64, UInt32, UInt64, Utf8, Utf8View};
    27
    use chrono::prelude::*;
    28
    29
    use datafusion_common::{exec_err, utils::take_function_args, Result, ScalarValue};
    30
    use datafusion_expr::{
    31
        ColumnarValue, Documentation, ScalarUDFImpl, Signature, Volatility,
    32
    };
    33
    use datafusion_macros::user_doc;
    34
    35
    #[user_doc(
    36
        doc_section(label = "Time and Date Functions"),
    37
        description = "Make a date from year/month/day component parts.",
    38
        syntax_example = "make_date(year, month, day)",
    39
        sql_example = r#"```sql
    40
    > select make_date(2023, 1, 31);
    41
    +-------------------------------------------+
    42
    | make_date(Int64(2023),Int64(1),Int64(31)) |
    43
    +-------------------------------------------+
    44
    | 2023-01-31                                |
    45
    +-------------------------------------------+
    46
    > select make_date('2023', '01', '31');
    47
    +-----------------------------------------------+
    48
    | make_date(Utf8("2023"),Utf8("01"),Utf8("31")) |
    49
    +-----------------------------------------------+
    50
    | 2023-01-31                                    |
    51
    +-----------------------------------------------+
    52
    ```
    53
    54
    Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/date_time_functions.rs)
    55
    "#,
    56
        argument(
    57
            name = "year",
    58
            description = "Year to use when making the date. Can be a constant, column or function, and any combination of arithmetic operators."
    59
        ),
    60
        argument(
    61
            name = "month",
    62
            description = "Month to use when making the date. Can be a constant, column or function, and any combination of arithmetic operators."
    63
        ),
    64
        argument(
    65
            name = "day",
    66
            description = "Day to use when making the date. Can be a constant, column or function, and any combination of arithmetic operators."
    67
        )
    68
    )]
    69
    #[derive(Debug, PartialEq, Eq, Hash)]
    70
    pub struct MakeDateFunc {
    71
        signature: Signature,
    72
    }
    73
    74
    impl Default for MakeDateFunc {
    75
    0
        fn default() -> Self {
    76
    0
            Self::new()
    77
    0
        }
    78
    }
    79
    80
    impl MakeDateFunc {
    81
    0
        pub fn new() -> Self {
    82
    0
            Self {
    83
    0
                signature: Signature::uniform(
    84
    0
                    3,
    85
    0
                    vec![Int32, Int64, UInt32, UInt64, Utf8, Utf8View],
    86
    0
                    Volatility::Immutable,
    87
    0
                ),
    88
    0
            }
    89
    0
        }
    90
    }
    91
    92
    impl ScalarUDFImpl for MakeDateFunc {
    93
    0
        fn as_any(&self) -> &dyn Any {
    94
    0
            self
    95
    0
        }
    96
    97
    0
        fn name(&self) -> &str {
    98
    0
            "make_date"
    99
    0
        }
    100
    101
    0
        fn signature(&self) -> &Signature {
    102
    0
            &self.signature
    103
    0
        }
    104
    105
    0
        fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
    106
    0
            Ok(Date32)
    107
    0
        }
    108
    109
    0
        fn invoke_with_args(
    110
    0
            &self,
    111
    0
            args: datafusion_expr::ScalarFunctionArgs,
    112
    0
        ) -> Result<ColumnarValue> {
    113
            // first, identify if any of the arguments is an Array. If yes, store its `len`,
    114
            // as any scalar will need to be converted to an array of len `len`.
    115
    0
            let args = args.args;
    116
    0
            let len = args
    117
    0
                .iter()
    118
    0
                .fold(Option::<usize>::None, |acc, arg| match arg {
    119
    0
                    ColumnarValue::Scalar(_) => acc,
    120
    0
                    ColumnarValue::Array(a) => Some(a.len()),
    121
    0
                });
    122
    123
    0
            let [years, months, days] = take_function_args(self.name(), args)?;
    124
    125
    0
            if matches!(years, ColumnarValue::Scalar(ScalarValue::Null))
    126
    0
                || matches!(months, ColumnarValue::Scalar(ScalarValue::Null))
    127
    0
                || matches!(days, ColumnarValue::Scalar(ScalarValue::Null))
    128
            {
    129
    0
                return Ok(ColumnarValue::Scalar(ScalarValue::Null));
    130
    0
            }
    131
    132
    0
            let years = years.cast_to(&Int32, None)?;
    133
    0
            let months = months.cast_to(&Int32, None)?;
    134
    0
            let days = days.cast_to(&Int32, None)?;
    135
    136
    0
            let scalar_value_fn = |col: &ColumnarValue| -> Result<i32> {
    137
    0
                let ColumnarValue::Scalar(s) = col else {
    138
    0
                    return exec_err!("Expected scalar value");
    139
                };
    140
    0
                let ScalarValue::Int32(Some(i)) = s else {
    141
    0
                    return exec_err!("Unable to parse date from null/empty value");
    142
                };
    143
    0
                Ok(*i)
    144
    0
            };
    145
    146
    0
            let value = if let Some(array_size) = len {
    147
    0
                let to_primitive_array_fn =
    148
    0
                    |col: &ColumnarValue| -> PrimitiveArray<Int32Type> {
    149
    0
                        match col {
    150
    0
                            ColumnarValue::Array(a) => {
    151
    0
                                a.as_primitive::<Int32Type>().to_owned()
    152
                            }
    153
                            _ => {
    154
    0
                                let v = scalar_value_fn(col).unwrap();
    155
    0
                                PrimitiveArray::<Int32Type>::from_value(v, array_size)
    156
                            }
    157
                        }
    158
    0
                    };
    159
    160
    0
                let years = to_primitive_array_fn(&years);
    161
    0
                let months = to_primitive_array_fn(&months);
    162
    0
                let days = to_primitive_array_fn(&days);
    163
    164
    0
                let mut builder: PrimitiveBuilder<Date32Type> =
    165
    0
                    PrimitiveArray::builder(array_size);
    166
    0
                for i in 0..array_size {
    167
    0
                    make_date_inner(
    168
    0
                        years.value(i),
    169
    0
                        months.value(i),
    170
    0
                        days.value(i),
    171
    0
                        |days: i32| builder.append_value(days),
    172
    0
                    )?;
    173
                }
    174
    175
    0
                let arr = builder.finish();
    176
    177
    0
                ColumnarValue::Array(Arc::new(arr))
    178
            } else {
    179
                // For scalar only columns the operation is faster without using the PrimitiveArray.
    180
                // Also, keep the output as scalar since all inputs are scalar.
    181
    0
                let mut value = 0;
    182
    0
                make_date_inner(
    183
    0
                    scalar_value_fn(&years)?,
    184
    0
                    scalar_value_fn(&months)?,
    185
    0
                    scalar_value_fn(&days)?,
    186
    0
                    |days: i32| value = days,
    187
    0
                )?;
    188
    189
    0
                ColumnarValue::Scalar(ScalarValue::Date32(Some(value)))
    190
            };
    191
    192
    0
            Ok(value)
    193
    0
        }
    194
    0
        fn documentation(&self) -> Option<&Documentation> {
    195
    0
            self.doc()
    196
    0
        }
    197
    }
    198
    199
    /// Converts the year/month/day fields to an `i32` representing the days from
    200
    /// the unix epoch and invokes `date_consumer_fn` with the value
    201
    0
    fn make_date_inner<F: FnMut(i32)>(
    202
    0
        year: i32,
    203
    0
        month: i32,
    204
    0
        day: i32,
    205
    0
        mut date_consumer_fn: F,
    206
    0
    ) -> Result<()> {
    207
    0
        let Ok(m) = u32::try_from(month) else {
    208
    0
            return exec_err!("Month value '{month:?}' is out of range");
    209
        };
    210
    0
        let Ok(d) = u32::try_from(day) else {
    211
    0
            return exec_err!("Day value '{day:?}' is out of range");
    212
        };
    213
    214
    0
        if let Some(date) = NaiveDate::from_ymd_opt(year, m, d) {
    215
            // The number of days until the start of the unix epoch in the proleptic Gregorian calendar
    216
            // (with January 1, Year 1 (CE) as day 1). See [Datelike::num_days_from_ce].
    217
            const UNIX_DAYS_FROM_CE: i32 = 719_163;
    218
    219
            // since the epoch for the date32 datatype is the unix epoch
    220
            // we need to subtract the unix epoch from the current date
    221
            // note that this can result in a negative value
    222
    0
            date_consumer_fn(date.num_days_from_ce() - UNIX_DAYS_FROM_CE);
    223
    0
            Ok(())
    224
        } else {
    225
    0
            exec_err!("Unable to parse date from {year}, {month}, {day}")
    226
        }
    227
    0
    }
    228
    229
    #[cfg(test)]
    230
    mod tests {
    231
        use crate::datetime::make_date::MakeDateFunc;
    232
        use arrow::array::{Array, Date32Array, Int32Array, Int64Array, UInt32Array};
    233
        use arrow::datatypes::{DataType, Field};
    234
        use datafusion_common::config::ConfigOptions;
    235
        use datafusion_common::{DataFusionError, ScalarValue};
    236
        use datafusion_expr::{ColumnarValue, ScalarUDFImpl};
    237
        use std::sync::Arc;
    238
    239
        fn invoke_make_date_with_args(
    240
            args: Vec<ColumnarValue>,
    241
            number_rows: usize,
    242
        ) -> Result<ColumnarValue, DataFusionError> {
    243
            let arg_fields = args
    244
                .iter()
    245
                .map(|arg| Field::new("a", arg.data_type(), true).into())
    246
                .collect::<Vec<_>>();
    247
            let args = datafusion_expr::ScalarFunctionArgs {
    248
                args,
    249
                arg_fields,
    250
                number_rows,
    251
                return_field: Field::new("f", DataType::Date32, true).into(),
    252
                config_options: Arc::new(ConfigOptions::default()),
    253
            };
    254
            MakeDateFunc::new().invoke_with_args(args)
    255
        }
    256
    257
        #[test]
    258
        fn test_make_date() {
    259
            let res = invoke_make_date_with_args(
    260
                vec![
    261
                    ColumnarValue::Scalar(ScalarValue::Int32(Some(2024))),
    262
                    ColumnarValue::Scalar(ScalarValue::Int64(Some(1))),
    263
                    ColumnarValue::Scalar(ScalarValue::UInt32(Some(14))),
    264
                ],
    265
                1,
    266
            )
    267
            .expect("that make_date parsed values without error");
    268
    269
            if let ColumnarValue::Scalar(ScalarValue::Date32(date)) = res {
    270
                assert_eq!(19736, date.unwrap());
    271
            } else {
    272
                panic!("Expected a scalar value")
    273
            }
    274
    275
            let res = invoke_make_date_with_args(
    276
                vec![
    277
                    ColumnarValue::Scalar(ScalarValue::Int64(Some(2024))),
    278
                    ColumnarValue::Scalar(ScalarValue::UInt64(Some(1))),
    279
                    ColumnarValue::Scalar(ScalarValue::UInt32(Some(14))),
    280
                ],
    281
                1,
    282
            )
    283
            .expect("that make_date parsed values without error");
    284
    285
            if let ColumnarValue::Scalar(ScalarValue::Date32(date)) = res {
    286
                assert_eq!(19736, date.unwrap());
    287
            } else {
    288
                panic!("Expected a scalar value")
    289
            }
    290
    291
            let res = invoke_make_date_with_args(
    292
                vec![
    293
                    ColumnarValue::Scalar(ScalarValue::Utf8(Some("2024".to_string()))),
    294
                    ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some("1".to_string()))),
    295
                    ColumnarValue::Scalar(ScalarValue::Utf8(Some("14".to_string()))),
    296
                ],
    297
                1,
    298
            )
    299
            .expect("that make_date parsed values without error");
    300
    301
            if let ColumnarValue::Scalar(ScalarValue::Date32(date)) = res {
    302
                assert_eq!(19736, date.unwrap());
    303
            } else {
    304
                panic!("Expected a scalar value")
    305
            }
    306
    307
            let years = Arc::new((2021..2025).map(Some).collect::<Int64Array>());
    308
            let months = Arc::new((1..5).map(Some).collect::<Int32Array>());
    309
            let days = Arc::new((11..15).map(Some).collect::<UInt32Array>());
    310
            let batch_len = years.len();
    311
            let res = invoke_make_date_with_args(
    312
                vec![
    313
                    ColumnarValue::Array(years),
    314
                    ColumnarValue::Array(months),
    315
                    ColumnarValue::Array(days),
    316
                ],
    317
                batch_len,
    318
            )
    319
            .unwrap();
    320
    321
            if let ColumnarValue::Array(array) = res {
    322
                assert_eq!(array.len(), 4);
    323
                let mut builder = Date32Array::builder(4);
    324
                builder.append_value(18_638);
    325
                builder.append_value(19_035);
    326
                builder.append_value(19_429);
    327
                builder.append_value(19_827);
    328
                assert_eq!(&builder.finish() as &dyn Array, array.as_ref());
    329
            } else {
    330
                panic!("Expected a columnar array")
    331
            }
    332
    333
            //
    334
            // Fallible test cases
    335
            //
    336
    337
            // invalid number of arguments
    338
            let res = invoke_make_date_with_args(
    339
                vec![ColumnarValue::Scalar(ScalarValue::Int32(Some(1)))],
    340
                1,
    341
            );
    342
            assert_eq!(
    343
                res.err().unwrap().strip_backtrace(),
    344
                "Execution error: make_date function requires 3 arguments, got 1"
    345
            );
    346
    347
            // invalid type
    348
            let res = invoke_make_date_with_args(
    349
                vec![
    350
                    ColumnarValue::Scalar(ScalarValue::IntervalYearMonth(Some(1))),
    351
                    ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(Some(1), None)),
    352
                    ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(Some(1), None)),
    353
                ],
    354
                1,
    355
            );
    356
            assert_eq!(
    357
                res.err().unwrap().strip_backtrace(),
    358
                "Arrow error: Cast error: Casting from Interval(YearMonth) to Int32 not supported"
    359
            );
    360
    361
            // overflow of month
    362
            let res = invoke_make_date_with_args(
    363
                vec![
    364
                    ColumnarValue::Scalar(ScalarValue::Int32(Some(2023))),
    365
                    ColumnarValue::Scalar(ScalarValue::UInt64(Some(u64::MAX))),
    366
                    ColumnarValue::Scalar(ScalarValue::Int32(Some(22))),
    367
                ],
    368
                1,
    369
            );
    370
            assert_eq!(
    371
                res.err().unwrap().strip_backtrace(),
    372
                "Arrow error: Cast error: Can't cast value 18446744073709551615 to type Int32"
    373
            );
    374
    375
            // overflow of day
    376
            let res = invoke_make_date_with_args(
    377
                vec![
    378
                    ColumnarValue::Scalar(ScalarValue::Int32(Some(2023))),
    379
                    ColumnarValue::Scalar(ScalarValue::Int32(Some(22))),
    380
                    ColumnarValue::Scalar(ScalarValue::UInt32(Some(u32::MAX))),
    381
                ],
    382
                1,
    383
            );
    384
            assert_eq!(
    385
                res.err().unwrap().strip_backtrace(),
    386
                "Arrow error: Cast error: Can't cast value 4294967295 to type Int32"
    387
            );
    388
        }
    389
    390
        #[test]
    391
        fn test_make_date_null_param() {
    392
            let res = invoke_make_date_with_args(
    393
                vec![
    394
                    ColumnarValue::Scalar(ScalarValue::Null),
    395
                    ColumnarValue::Scalar(ScalarValue::Int64(Some(1))),
    396
                    ColumnarValue::Scalar(ScalarValue::UInt32(Some(14))),
    397
                ],
    398
                1,
    399
            )
    400
            .expect("that make_date parsed values without error");
    401
    402
            assert!(matches!(res, ColumnarValue::Scalar(ScalarValue::Null)));
    403
        }
    404
    }