datafusion_functions/datetime/
to_char.rs

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
18use std::any::Any;
19use std::sync::Arc;
20
21use arrow::array::cast::AsArray;
22use arrow::array::{new_null_array, Array, ArrayRef, StringArray};
23use arrow::datatypes::DataType;
24use arrow::datatypes::DataType::{
25    Date32, Date64, Duration, Time32, Time64, Timestamp, Utf8,
26};
27use arrow::datatypes::TimeUnit::{Microsecond, Millisecond, Nanosecond, Second};
28use arrow::error::ArrowError;
29use arrow::util::display::{ArrayFormatter, DurationFormat, FormatOptions};
30
31use datafusion_common::{exec_err, utils::take_function_args, Result, ScalarValue};
32use datafusion_expr::TypeSignature::Exact;
33use datafusion_expr::{
34    ColumnarValue, Documentation, ScalarUDFImpl, Signature, Volatility, TIMEZONE_WILDCARD,
35};
36use datafusion_macros::user_doc;
37
38#[user_doc(
39    doc_section(label = "Time and Date Functions"),
40    description = "Returns a string representation of a date, time, timestamp or duration based on a [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html). Unlike the PostgreSQL equivalent of this function numerical formatting is not supported.",
41    syntax_example = "to_char(expression, format)",
42    sql_example = r#"```sql
43> select to_char('2023-03-01'::date, '%d-%m-%Y');
44+----------------------------------------------+
45| to_char(Utf8("2023-03-01"),Utf8("%d-%m-%Y")) |
46+----------------------------------------------+
47| 01-03-2023                                   |
48+----------------------------------------------+
49```
50
51Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/to_char.rs)
52"#,
53    argument(
54        name = "expression",
55        description = "Expression to operate on. Can be a constant, column, or function that results in a date, time, timestamp or duration."
56    ),
57    argument(
58        name = "format",
59        description = "A [Chrono format](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) string to use to convert the expression."
60    ),
61    argument(
62        name = "day",
63        description = "Day to use when making the date. Can be a constant, column or function, and any combination of arithmetic operators."
64    )
65)]
66#[derive(Debug)]
67pub struct ToCharFunc {
68    signature: Signature,
69    aliases: Vec<String>,
70}
71
72impl Default for ToCharFunc {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78impl ToCharFunc {
79    pub fn new() -> Self {
80        Self {
81            signature: Signature::one_of(
82                vec![
83                    Exact(vec![Date32, Utf8]),
84                    Exact(vec![Date64, Utf8]),
85                    Exact(vec![Time64(Nanosecond), Utf8]),
86                    Exact(vec![Time64(Microsecond), Utf8]),
87                    Exact(vec![Time32(Millisecond), Utf8]),
88                    Exact(vec![Time32(Second), Utf8]),
89                    Exact(vec![
90                        Timestamp(Nanosecond, Some(TIMEZONE_WILDCARD.into())),
91                        Utf8,
92                    ]),
93                    Exact(vec![Timestamp(Nanosecond, None), Utf8]),
94                    Exact(vec![
95                        Timestamp(Microsecond, Some(TIMEZONE_WILDCARD.into())),
96                        Utf8,
97                    ]),
98                    Exact(vec![Timestamp(Microsecond, None), Utf8]),
99                    Exact(vec![
100                        Timestamp(Millisecond, Some(TIMEZONE_WILDCARD.into())),
101                        Utf8,
102                    ]),
103                    Exact(vec![Timestamp(Millisecond, None), Utf8]),
104                    Exact(vec![
105                        Timestamp(Second, Some(TIMEZONE_WILDCARD.into())),
106                        Utf8,
107                    ]),
108                    Exact(vec![Timestamp(Second, None), Utf8]),
109                    Exact(vec![Duration(Nanosecond), Utf8]),
110                    Exact(vec![Duration(Microsecond), Utf8]),
111                    Exact(vec![Duration(Millisecond), Utf8]),
112                    Exact(vec![Duration(Second), Utf8]),
113                ],
114                Volatility::Immutable,
115            ),
116            aliases: vec![String::from("date_format")],
117        }
118    }
119}
120
121impl ScalarUDFImpl for ToCharFunc {
122    fn as_any(&self) -> &dyn Any {
123        self
124    }
125
126    fn name(&self) -> &str {
127        "to_char"
128    }
129
130    fn signature(&self) -> &Signature {
131        &self.signature
132    }
133
134    fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
135        Ok(Utf8)
136    }
137
138    fn invoke_with_args(
139        &self,
140        args: datafusion_expr::ScalarFunctionArgs,
141    ) -> Result<ColumnarValue> {
142        let args = args.args;
143        let [date_time, format] = take_function_args(self.name(), &args)?;
144
145        match format {
146            ColumnarValue::Scalar(ScalarValue::Utf8(None))
147            | ColumnarValue::Scalar(ScalarValue::Null) => {
148                _to_char_scalar(date_time.clone(), None)
149            }
150            // constant format
151            ColumnarValue::Scalar(ScalarValue::Utf8(Some(format))) => {
152                // invoke to_char_scalar with the known string, without converting to array
153                _to_char_scalar(date_time.clone(), Some(format))
154            }
155            ColumnarValue::Array(_) => _to_char_array(&args),
156            _ => {
157                exec_err!(
158                    "Format for `to_char` must be non-null Utf8, received {:?}",
159                    format.data_type()
160                )
161            }
162        }
163    }
164
165    fn aliases(&self) -> &[String] {
166        &self.aliases
167    }
168    fn documentation(&self) -> Option<&Documentation> {
169        self.doc()
170    }
171}
172
173fn _build_format_options<'a>(
174    data_type: &DataType,
175    format: Option<&'a str>,
176) -> Result<FormatOptions<'a>, Result<ColumnarValue>> {
177    let Some(format) = format else {
178        return Ok(FormatOptions::new());
179    };
180    let format_options = match data_type {
181        Date32 => FormatOptions::new().with_date_format(Some(format)),
182        Date64 => FormatOptions::new().with_datetime_format(Some(format)),
183        Time32(_) => FormatOptions::new().with_time_format(Some(format)),
184        Time64(_) => FormatOptions::new().with_time_format(Some(format)),
185        Timestamp(_, _) => FormatOptions::new()
186            .with_timestamp_format(Some(format))
187            .with_timestamp_tz_format(Some(format)),
188        Duration(_) => FormatOptions::new().with_duration_format(
189            if "ISO8601".eq_ignore_ascii_case(format) {
190                DurationFormat::ISO8601
191            } else {
192                DurationFormat::Pretty
193            },
194        ),
195        other => {
196            return Err(exec_err!(
197                "to_char only supports date, time, timestamp and duration data types, received {other:?}"
198            ));
199        }
200    };
201    Ok(format_options)
202}
203
204/// Special version when arg\[1] is a scalar
205fn _to_char_scalar(
206    expression: ColumnarValue,
207    format: Option<&str>,
208) -> Result<ColumnarValue> {
209    // it's possible that the expression is a scalar however because
210    // of the implementation in arrow-rs we need to convert it to an array
211    let data_type = &expression.data_type();
212    let is_scalar_expression = matches!(&expression, ColumnarValue::Scalar(_));
213    let array = expression.into_array(1)?;
214
215    // fix https://github.com/apache/datafusion/issues/14884
216    // If the input date/time is null, return a null Utf8 result.
217    if array.is_null(0) {
218        return Ok(match is_scalar_expression {
219            true => ColumnarValue::Scalar(ScalarValue::Utf8(None)),
220            false => ColumnarValue::Array(new_null_array(&Utf8, array.len())),
221        });
222    }
223    if format.is_none() {
224        if is_scalar_expression {
225            return Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None)));
226        } else {
227            return Ok(ColumnarValue::Array(new_null_array(&Utf8, array.len())));
228        }
229    }
230
231    let format_options = match _build_format_options(data_type, format) {
232        Ok(value) => value,
233        Err(value) => return value,
234    };
235
236    let formatter = ArrayFormatter::try_new(array.as_ref(), &format_options)?;
237    let formatted: Result<Vec<_>, ArrowError> = (0..array.len())
238        .map(|i| formatter.value(i).try_to_string())
239        .collect();
240
241    if let Ok(formatted) = formatted {
242        if is_scalar_expression {
243            Ok(ColumnarValue::Scalar(ScalarValue::Utf8(Some(
244                formatted.first().unwrap().to_string(),
245            ))))
246        } else {
247            Ok(ColumnarValue::Array(
248                Arc::new(StringArray::from(formatted)) as ArrayRef
249            ))
250        }
251    } else {
252        exec_err!("{}", formatted.unwrap_err())
253    }
254}
255
256fn _to_char_array(args: &[ColumnarValue]) -> Result<ColumnarValue> {
257    let arrays = ColumnarValue::values_to_arrays(args)?;
258    let mut results: Vec<Option<String>> = vec![];
259    let format_array = arrays[1].as_string::<i32>();
260    let data_type = arrays[0].data_type();
261
262    for idx in 0..arrays[0].len() {
263        // fix https://github.com/apache/datafusion/issues/14884
264        // If the date/time value is null, push None.
265        if arrays[0].is_null(idx) {
266            results.push(None);
267            continue;
268        }
269
270        let format = if format_array.is_null(idx) {
271            None
272        } else {
273            Some(format_array.value(idx))
274        };
275        if format.is_none() {
276            results.push(None);
277            continue;
278        }
279        let format_options = match _build_format_options(data_type, format) {
280            Ok(value) => value,
281            Err(value) => return value,
282        };
283        // this isn't ideal but this can't use ValueFormatter as it isn't independent
284        // from ArrayFormatter
285        let formatter = ArrayFormatter::try_new(arrays[0].as_ref(), &format_options)?;
286        let result = formatter.value(idx).try_to_string();
287        match result {
288            Ok(value) => results.push(Some(value)),
289            Err(e) => return exec_err!("{}", e),
290        }
291    }
292
293    match args[0] {
294        ColumnarValue::Array(_) => Ok(ColumnarValue::Array(Arc::new(StringArray::from(
295            results,
296        )) as ArrayRef)),
297        ColumnarValue::Scalar(_) => match results.first().unwrap() {
298            Some(value) => Ok(ColumnarValue::Scalar(ScalarValue::Utf8(Some(
299                value.to_string(),
300            )))),
301            None => Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None))),
302        },
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use crate::datetime::to_char::ToCharFunc;
309    use arrow::array::{
310        Array, ArrayRef, Date32Array, Date64Array, StringArray, Time32MillisecondArray,
311        Time32SecondArray, Time64MicrosecondArray, Time64NanosecondArray,
312        TimestampMicrosecondArray, TimestampMillisecondArray, TimestampNanosecondArray,
313        TimestampSecondArray,
314    };
315    use arrow::datatypes::DataType;
316    use chrono::{NaiveDateTime, Timelike};
317    use datafusion_common::ScalarValue;
318    use datafusion_expr::{ColumnarValue, ScalarUDFImpl};
319    use std::sync::Arc;
320
321    #[test]
322    fn test_to_char() {
323        let date = "2020-01-02T03:04:05"
324            .parse::<NaiveDateTime>()
325            .unwrap()
326            .with_nanosecond(12345)
327            .unwrap();
328        let date2 = "2026-07-08T09:10:11"
329            .parse::<NaiveDateTime>()
330            .unwrap()
331            .with_nanosecond(56789)
332            .unwrap();
333
334        let scalar_data = vec![
335            (
336                ScalarValue::Date32(Some(18506)),
337                ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
338                "2020::09::01".to_string(),
339            ),
340            (
341                ScalarValue::Date64(Some(date.and_utc().timestamp_millis())),
342                ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
343                "2020::01::02".to_string(),
344            ),
345            (
346                ScalarValue::Time32Second(Some(31851)),
347                ScalarValue::Utf8(Some("%H-%M-%S".to_string())),
348                "08-50-51".to_string(),
349            ),
350            (
351                ScalarValue::Time32Millisecond(Some(18506000)),
352                ScalarValue::Utf8(Some("%H-%M-%S".to_string())),
353                "05-08-26".to_string(),
354            ),
355            (
356                ScalarValue::Time64Microsecond(Some(12344567000)),
357                ScalarValue::Utf8(Some("%H-%M-%S %f".to_string())),
358                "03-25-44 567000000".to_string(),
359            ),
360            (
361                ScalarValue::Time64Nanosecond(Some(12344567890000)),
362                ScalarValue::Utf8(Some("%H-%M-%S %f".to_string())),
363                "03-25-44 567890000".to_string(),
364            ),
365            (
366                ScalarValue::TimestampSecond(Some(date.and_utc().timestamp()), None),
367                ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H".to_string())),
368                "2020::01::02 05::04::03".to_string(),
369            ),
370            (
371                ScalarValue::TimestampMillisecond(
372                    Some(date.and_utc().timestamp_millis()),
373                    None,
374                ),
375                ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H".to_string())),
376                "2020::01::02 05::04::03".to_string(),
377            ),
378            (
379                ScalarValue::TimestampMicrosecond(
380                    Some(date.and_utc().timestamp_micros()),
381                    None,
382                ),
383                ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H %f".to_string())),
384                "2020::01::02 05::04::03 000012000".to_string(),
385            ),
386            (
387                ScalarValue::TimestampNanosecond(
388                    Some(date.and_utc().timestamp_nanos_opt().unwrap()),
389                    None,
390                ),
391                ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H %f".to_string())),
392                "2020::01::02 05::04::03 000012345".to_string(),
393            ),
394        ];
395
396        for (value, format, expected) in scalar_data {
397            let args = datafusion_expr::ScalarFunctionArgs {
398                args: vec![ColumnarValue::Scalar(value), ColumnarValue::Scalar(format)],
399                number_rows: 1,
400                return_type: &DataType::Utf8,
401            };
402            let result = ToCharFunc::new()
403                .invoke_with_args(args)
404                .expect("that to_char parsed values without error");
405
406            if let ColumnarValue::Scalar(ScalarValue::Utf8(date)) = result {
407                assert_eq!(expected, date.unwrap());
408            } else {
409                panic!("Expected a scalar value")
410            }
411        }
412
413        let scalar_array_data = vec![
414            (
415                ScalarValue::Date32(Some(18506)),
416                StringArray::from(vec!["%Y::%m::%d".to_string()]),
417                "2020::09::01".to_string(),
418            ),
419            (
420                ScalarValue::Date64(Some(date.and_utc().timestamp_millis())),
421                StringArray::from(vec!["%Y::%m::%d".to_string()]),
422                "2020::01::02".to_string(),
423            ),
424            (
425                ScalarValue::Time32Second(Some(31851)),
426                StringArray::from(vec!["%H-%M-%S".to_string()]),
427                "08-50-51".to_string(),
428            ),
429            (
430                ScalarValue::Time32Millisecond(Some(18506000)),
431                StringArray::from(vec!["%H-%M-%S".to_string()]),
432                "05-08-26".to_string(),
433            ),
434            (
435                ScalarValue::Time64Microsecond(Some(12344567000)),
436                StringArray::from(vec!["%H-%M-%S %f".to_string()]),
437                "03-25-44 567000000".to_string(),
438            ),
439            (
440                ScalarValue::Time64Nanosecond(Some(12344567890000)),
441                StringArray::from(vec!["%H-%M-%S %f".to_string()]),
442                "03-25-44 567890000".to_string(),
443            ),
444            (
445                ScalarValue::TimestampSecond(Some(date.and_utc().timestamp()), None),
446                StringArray::from(vec!["%Y::%m::%d %S::%M::%H".to_string()]),
447                "2020::01::02 05::04::03".to_string(),
448            ),
449            (
450                ScalarValue::TimestampMillisecond(
451                    Some(date.and_utc().timestamp_millis()),
452                    None,
453                ),
454                StringArray::from(vec!["%Y::%m::%d %S::%M::%H".to_string()]),
455                "2020::01::02 05::04::03".to_string(),
456            ),
457            (
458                ScalarValue::TimestampMicrosecond(
459                    Some(date.and_utc().timestamp_micros()),
460                    None,
461                ),
462                StringArray::from(vec!["%Y::%m::%d %S::%M::%H %f".to_string()]),
463                "2020::01::02 05::04::03 000012000".to_string(),
464            ),
465            (
466                ScalarValue::TimestampNanosecond(
467                    Some(date.and_utc().timestamp_nanos_opt().unwrap()),
468                    None,
469                ),
470                StringArray::from(vec!["%Y::%m::%d %S::%M::%H %f".to_string()]),
471                "2020::01::02 05::04::03 000012345".to_string(),
472            ),
473        ];
474
475        for (value, format, expected) in scalar_array_data {
476            let batch_len = format.len();
477            let args = datafusion_expr::ScalarFunctionArgs {
478                args: vec![
479                    ColumnarValue::Scalar(value),
480                    ColumnarValue::Array(Arc::new(format) as ArrayRef),
481                ],
482                number_rows: batch_len,
483                return_type: &DataType::Utf8,
484            };
485            let result = ToCharFunc::new()
486                .invoke_with_args(args)
487                .expect("that to_char parsed values without error");
488
489            if let ColumnarValue::Scalar(ScalarValue::Utf8(date)) = result {
490                assert_eq!(expected, date.unwrap());
491            } else {
492                panic!("Expected a scalar value")
493            }
494        }
495
496        let array_scalar_data = vec![
497            (
498                Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
499                ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
500                StringArray::from(vec!["2020::09::01", "2020::09::02"]),
501            ),
502            (
503                Arc::new(Date64Array::from(vec![
504                    date.and_utc().timestamp_millis(),
505                    date2.and_utc().timestamp_millis(),
506                ])) as ArrayRef,
507                ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
508                StringArray::from(vec!["2020::01::02", "2026::07::08"]),
509            ),
510        ];
511
512        let array_array_data = vec![
513            (
514                Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
515                StringArray::from(vec!["%Y::%m::%d", "%d::%m::%Y"]),
516                StringArray::from(vec!["2020::09::01", "02::09::2020"]),
517            ),
518            (
519                Arc::new(Date64Array::from(vec![
520                    date.and_utc().timestamp_millis(),
521                    date2.and_utc().timestamp_millis(),
522                ])) as ArrayRef,
523                StringArray::from(vec!["%Y::%m::%d", "%d::%m::%Y"]),
524                StringArray::from(vec!["2020::01::02", "08::07::2026"]),
525            ),
526            (
527                Arc::new(Time32MillisecondArray::from(vec![1850600, 1860700]))
528                    as ArrayRef,
529                StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
530                StringArray::from(vec!["00:30:50", "00::31::00"]),
531            ),
532            (
533                Arc::new(Time32SecondArray::from(vec![18506, 18507])) as ArrayRef,
534                StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
535                StringArray::from(vec!["05:08:26", "05::08::27"]),
536            ),
537            (
538                Arc::new(Time64MicrosecondArray::from(vec![12344567000, 22244567000]))
539                    as ArrayRef,
540                StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
541                StringArray::from(vec!["03:25:44", "06::10::44"]),
542            ),
543            (
544                Arc::new(Time64NanosecondArray::from(vec![
545                    1234456789000,
546                    2224456789000,
547                ])) as ArrayRef,
548                StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
549                StringArray::from(vec!["00:20:34", "00::37::04"]),
550            ),
551            (
552                Arc::new(TimestampSecondArray::from(vec![
553                    date.and_utc().timestamp(),
554                    date2.and_utc().timestamp(),
555                ])) as ArrayRef,
556                StringArray::from(vec!["%Y::%m::%d %S::%M::%H", "%d::%m::%Y %S-%M-%H"]),
557                StringArray::from(vec![
558                    "2020::01::02 05::04::03",
559                    "08::07::2026 11-10-09",
560                ]),
561            ),
562            (
563                Arc::new(TimestampMillisecondArray::from(vec![
564                    date.and_utc().timestamp_millis(),
565                    date2.and_utc().timestamp_millis(),
566                ])) as ArrayRef,
567                StringArray::from(vec![
568                    "%Y::%m::%d %S::%M::%H %f",
569                    "%d::%m::%Y %S-%M-%H %f",
570                ]),
571                StringArray::from(vec![
572                    "2020::01::02 05::04::03 000000000",
573                    "08::07::2026 11-10-09 000000000",
574                ]),
575            ),
576            (
577                Arc::new(TimestampMicrosecondArray::from(vec![
578                    date.and_utc().timestamp_micros(),
579                    date2.and_utc().timestamp_micros(),
580                ])) as ArrayRef,
581                StringArray::from(vec![
582                    "%Y::%m::%d %S::%M::%H %f",
583                    "%d::%m::%Y %S-%M-%H %f",
584                ]),
585                StringArray::from(vec![
586                    "2020::01::02 05::04::03 000012000",
587                    "08::07::2026 11-10-09 000056000",
588                ]),
589            ),
590            (
591                Arc::new(TimestampNanosecondArray::from(vec![
592                    date.and_utc().timestamp_nanos_opt().unwrap(),
593                    date2.and_utc().timestamp_nanos_opt().unwrap(),
594                ])) as ArrayRef,
595                StringArray::from(vec![
596                    "%Y::%m::%d %S::%M::%H %f",
597                    "%d::%m::%Y %S-%M-%H %f",
598                ]),
599                StringArray::from(vec![
600                    "2020::01::02 05::04::03 000012345",
601                    "08::07::2026 11-10-09 000056789",
602                ]),
603            ),
604        ];
605
606        for (value, format, expected) in array_scalar_data {
607            let batch_len = value.len();
608            let args = datafusion_expr::ScalarFunctionArgs {
609                args: vec![
610                    ColumnarValue::Array(value as ArrayRef),
611                    ColumnarValue::Scalar(format),
612                ],
613                number_rows: batch_len,
614                return_type: &DataType::Utf8,
615            };
616            let result = ToCharFunc::new()
617                .invoke_with_args(args)
618                .expect("that to_char parsed values without error");
619
620            if let ColumnarValue::Array(result) = result {
621                assert_eq!(result.len(), 2);
622                assert_eq!(&expected as &dyn Array, result.as_ref());
623            } else {
624                panic!("Expected an array value")
625            }
626        }
627
628        for (value, format, expected) in array_array_data {
629            let batch_len = value.len();
630            let args = datafusion_expr::ScalarFunctionArgs {
631                args: vec![
632                    ColumnarValue::Array(value),
633                    ColumnarValue::Array(Arc::new(format) as ArrayRef),
634                ],
635                number_rows: batch_len,
636                return_type: &DataType::Utf8,
637            };
638            let result = ToCharFunc::new()
639                .invoke_with_args(args)
640                .expect("that to_char parsed values without error");
641
642            if let ColumnarValue::Array(result) = result {
643                assert_eq!(result.len(), 2);
644                assert_eq!(&expected as &dyn Array, result.as_ref());
645            } else {
646                panic!("Expected an array value")
647            }
648        }
649
650        //
651        // Fallible test cases
652        //
653
654        // invalid number of arguments
655        let args = datafusion_expr::ScalarFunctionArgs {
656            args: vec![ColumnarValue::Scalar(ScalarValue::Int32(Some(1)))],
657            number_rows: 1,
658            return_type: &DataType::Utf8,
659        };
660        let result = ToCharFunc::new().invoke_with_args(args);
661        assert_eq!(
662            result.err().unwrap().strip_backtrace(),
663            "Execution error: to_char function requires 2 arguments, got 1"
664        );
665
666        // invalid type
667        let args = datafusion_expr::ScalarFunctionArgs {
668            args: vec![
669                ColumnarValue::Scalar(ScalarValue::Int32(Some(1))),
670                ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(Some(1), None)),
671            ],
672            number_rows: 1,
673            return_type: &DataType::Utf8,
674        };
675        let result = ToCharFunc::new().invoke_with_args(args);
676        assert_eq!(
677            result.err().unwrap().strip_backtrace(),
678            "Execution error: Format for `to_char` must be non-null Utf8, received Timestamp(Nanosecond, None)"
679        );
680    }
681
682    #[test]
683    fn test_to_char_input_none_array() {
684        let date_array = Arc::new(Date32Array::from(vec![Some(18506), None])) as ArrayRef;
685        let format_array =
686            StringArray::from(vec!["%Y-%m-%d".to_string(), "%Y-%m-%d".to_string()]);
687        let args = datafusion_expr::ScalarFunctionArgs {
688            args: vec![
689                ColumnarValue::Array(date_array),
690                ColumnarValue::Array(Arc::new(format_array) as ArrayRef),
691            ],
692            number_rows: 2,
693            return_type: &DataType::Utf8,
694        };
695        let result = ToCharFunc::new()
696            .invoke_with_args(args)
697            .expect("Expected no error");
698        if let ColumnarValue::Array(result) = result {
699            let result = result.as_any().downcast_ref::<StringArray>().unwrap();
700            assert_eq!(result.len(), 2);
701            // The first element is valid, second is null.
702            assert!(!result.is_null(0));
703            assert!(result.is_null(1));
704        } else {
705            panic!("Expected an array value");
706        }
707    }
708}