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
169    fn documentation(&self) -> Option<&Documentation> {
170        self.doc()
171    }
172}
173
174fn _build_format_options<'a>(
175    data_type: &DataType,
176    format: Option<&'a str>,
177) -> Result<FormatOptions<'a>, Result<ColumnarValue>> {
178    let Some(format) = format else {
179        return Ok(FormatOptions::new());
180    };
181    let format_options = match data_type {
182        Date32 => FormatOptions::new().with_date_format(Some(format)),
183        Date64 => FormatOptions::new().with_datetime_format(Some(format)),
184        Time32(_) => FormatOptions::new().with_time_format(Some(format)),
185        Time64(_) => FormatOptions::new().with_time_format(Some(format)),
186        Timestamp(_, _) => FormatOptions::new()
187            .with_timestamp_format(Some(format))
188            .with_timestamp_tz_format(Some(format)),
189        Duration(_) => FormatOptions::new().with_duration_format(
190            if "ISO8601".eq_ignore_ascii_case(format) {
191                DurationFormat::ISO8601
192            } else {
193                DurationFormat::Pretty
194            },
195        ),
196        other => {
197            return Err(exec_err!(
198                "to_char only supports date, time, timestamp and duration data types, received {other:?}"
199            ));
200        }
201    };
202    Ok(format_options)
203}
204
205/// Special version when arg\[1] is a scalar
206fn _to_char_scalar(
207    expression: ColumnarValue,
208    format: Option<&str>,
209) -> Result<ColumnarValue> {
210    // it's possible that the expression is a scalar however because
211    // of the implementation in arrow-rs we need to convert it to an array
212    let data_type = &expression.data_type();
213    let is_scalar_expression = matches!(&expression, ColumnarValue::Scalar(_));
214    let array = expression.into_array(1)?;
215
216    if format.is_none() {
217        if is_scalar_expression {
218            return Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None)));
219        } else {
220            return Ok(ColumnarValue::Array(new_null_array(&Utf8, array.len())));
221        }
222    }
223
224    let format_options = match _build_format_options(data_type, format) {
225        Ok(value) => value,
226        Err(value) => return value,
227    };
228
229    let formatter = ArrayFormatter::try_new(array.as_ref(), &format_options)?;
230    let formatted: Result<Vec<Option<String>>, ArrowError> = (0..array.len())
231        .map(|i| {
232            if array.is_null(i) {
233                Ok(None)
234            } else {
235                formatter.value(i).try_to_string().map(Some)
236            }
237        })
238        .collect();
239
240    if let Ok(formatted) = formatted {
241        if is_scalar_expression {
242            Ok(ColumnarValue::Scalar(ScalarValue::Utf8(
243                formatted.first().unwrap().clone(),
244            )))
245        } else {
246            Ok(ColumnarValue::Array(
247                Arc::new(StringArray::from(formatted)) as ArrayRef
248            ))
249        }
250    } else {
251        exec_err!("{}", formatted.unwrap_err())
252    }
253}
254
255fn _to_char_array(args: &[ColumnarValue]) -> Result<ColumnarValue> {
256    let arrays = ColumnarValue::values_to_arrays(args)?;
257    let mut results: Vec<Option<String>> = vec![];
258    let format_array = arrays[1].as_string::<i32>();
259    let data_type = arrays[0].data_type();
260
261    for idx in 0..arrays[0].len() {
262        let format = if format_array.is_null(idx) {
263            None
264        } else {
265            Some(format_array.value(idx))
266        };
267        if format.is_none() {
268            results.push(None);
269            continue;
270        }
271        let format_options = match _build_format_options(data_type, format) {
272            Ok(value) => value,
273            Err(value) => return value,
274        };
275        // this isn't ideal but this can't use ValueFormatter as it isn't independent
276        // from ArrayFormatter
277        let formatter = ArrayFormatter::try_new(arrays[0].as_ref(), &format_options)?;
278        let result = formatter.value(idx).try_to_string();
279        match result {
280            Ok(value) => results.push(Some(value)),
281            Err(e) => return exec_err!("{}", e),
282        }
283    }
284
285    match args[0] {
286        ColumnarValue::Array(_) => Ok(ColumnarValue::Array(Arc::new(StringArray::from(
287            results,
288        )) as ArrayRef)),
289        ColumnarValue::Scalar(_) => match results.first().unwrap() {
290            Some(value) => Ok(ColumnarValue::Scalar(ScalarValue::Utf8(Some(
291                value.to_string(),
292            )))),
293            None => Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None))),
294        },
295    }
296}
297
298#[cfg(test)]
299mod tests {
300    use crate::datetime::to_char::ToCharFunc;
301    use arrow::array::{
302        Array, ArrayRef, Date32Array, Date64Array, StringArray, Time32MillisecondArray,
303        Time32SecondArray, Time64MicrosecondArray, Time64NanosecondArray,
304        TimestampMicrosecondArray, TimestampMillisecondArray, TimestampNanosecondArray,
305        TimestampSecondArray,
306    };
307    use arrow::datatypes::{DataType, Field, TimeUnit};
308    use chrono::{NaiveDateTime, Timelike};
309    use datafusion_common::ScalarValue;
310    use datafusion_expr::{ColumnarValue, ScalarUDFImpl};
311    use std::sync::Arc;
312
313    #[test]
314    fn test_to_char() {
315        let date = "2020-01-02T03:04:05"
316            .parse::<NaiveDateTime>()
317            .unwrap()
318            .with_nanosecond(12345)
319            .unwrap();
320        let date2 = "2026-07-08T09:10:11"
321            .parse::<NaiveDateTime>()
322            .unwrap()
323            .with_nanosecond(56789)
324            .unwrap();
325
326        let scalar_data = vec![
327            (
328                ScalarValue::Date32(Some(18506)),
329                ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
330                "2020::09::01".to_string(),
331            ),
332            (
333                ScalarValue::Date64(Some(date.and_utc().timestamp_millis())),
334                ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
335                "2020::01::02".to_string(),
336            ),
337            (
338                ScalarValue::Time32Second(Some(31851)),
339                ScalarValue::Utf8(Some("%H-%M-%S".to_string())),
340                "08-50-51".to_string(),
341            ),
342            (
343                ScalarValue::Time32Millisecond(Some(18506000)),
344                ScalarValue::Utf8(Some("%H-%M-%S".to_string())),
345                "05-08-26".to_string(),
346            ),
347            (
348                ScalarValue::Time64Microsecond(Some(12344567000)),
349                ScalarValue::Utf8(Some("%H-%M-%S %f".to_string())),
350                "03-25-44 567000000".to_string(),
351            ),
352            (
353                ScalarValue::Time64Nanosecond(Some(12344567890000)),
354                ScalarValue::Utf8(Some("%H-%M-%S %f".to_string())),
355                "03-25-44 567890000".to_string(),
356            ),
357            (
358                ScalarValue::TimestampSecond(Some(date.and_utc().timestamp()), None),
359                ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H".to_string())),
360                "2020::01::02 05::04::03".to_string(),
361            ),
362            (
363                ScalarValue::TimestampMillisecond(
364                    Some(date.and_utc().timestamp_millis()),
365                    None,
366                ),
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::TimestampMicrosecond(
372                    Some(date.and_utc().timestamp_micros()),
373                    None,
374                ),
375                ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H %f".to_string())),
376                "2020::01::02 05::04::03 000012000".to_string(),
377            ),
378            (
379                ScalarValue::TimestampNanosecond(
380                    Some(date.and_utc().timestamp_nanos_opt().unwrap()),
381                    None,
382                ),
383                ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H %f".to_string())),
384                "2020::01::02 05::04::03 000012345".to_string(),
385            ),
386        ];
387
388        for (value, format, expected) in scalar_data {
389            let arg_fields = vec![
390                Field::new("a", value.data_type(), false).into(),
391                Field::new("a", format.data_type(), false).into(),
392            ];
393            let args = datafusion_expr::ScalarFunctionArgs {
394                args: vec![ColumnarValue::Scalar(value), ColumnarValue::Scalar(format)],
395                arg_fields,
396                number_rows: 1,
397                return_field: Field::new("f", DataType::Utf8, true).into(),
398            };
399            let result = ToCharFunc::new()
400                .invoke_with_args(args)
401                .expect("that to_char parsed values without error");
402
403            if let ColumnarValue::Scalar(ScalarValue::Utf8(date)) = result {
404                assert_eq!(expected, date.unwrap());
405            } else {
406                panic!("Expected a scalar value")
407            }
408        }
409
410        let scalar_array_data = vec![
411            (
412                ScalarValue::Date32(Some(18506)),
413                StringArray::from(vec!["%Y::%m::%d".to_string()]),
414                "2020::09::01".to_string(),
415            ),
416            (
417                ScalarValue::Date64(Some(date.and_utc().timestamp_millis())),
418                StringArray::from(vec!["%Y::%m::%d".to_string()]),
419                "2020::01::02".to_string(),
420            ),
421            (
422                ScalarValue::Time32Second(Some(31851)),
423                StringArray::from(vec!["%H-%M-%S".to_string()]),
424                "08-50-51".to_string(),
425            ),
426            (
427                ScalarValue::Time32Millisecond(Some(18506000)),
428                StringArray::from(vec!["%H-%M-%S".to_string()]),
429                "05-08-26".to_string(),
430            ),
431            (
432                ScalarValue::Time64Microsecond(Some(12344567000)),
433                StringArray::from(vec!["%H-%M-%S %f".to_string()]),
434                "03-25-44 567000000".to_string(),
435            ),
436            (
437                ScalarValue::Time64Nanosecond(Some(12344567890000)),
438                StringArray::from(vec!["%H-%M-%S %f".to_string()]),
439                "03-25-44 567890000".to_string(),
440            ),
441            (
442                ScalarValue::TimestampSecond(Some(date.and_utc().timestamp()), None),
443                StringArray::from(vec!["%Y::%m::%d %S::%M::%H".to_string()]),
444                "2020::01::02 05::04::03".to_string(),
445            ),
446            (
447                ScalarValue::TimestampMillisecond(
448                    Some(date.and_utc().timestamp_millis()),
449                    None,
450                ),
451                StringArray::from(vec!["%Y::%m::%d %S::%M::%H".to_string()]),
452                "2020::01::02 05::04::03".to_string(),
453            ),
454            (
455                ScalarValue::TimestampMicrosecond(
456                    Some(date.and_utc().timestamp_micros()),
457                    None,
458                ),
459                StringArray::from(vec!["%Y::%m::%d %S::%M::%H %f".to_string()]),
460                "2020::01::02 05::04::03 000012000".to_string(),
461            ),
462            (
463                ScalarValue::TimestampNanosecond(
464                    Some(date.and_utc().timestamp_nanos_opt().unwrap()),
465                    None,
466                ),
467                StringArray::from(vec!["%Y::%m::%d %S::%M::%H %f".to_string()]),
468                "2020::01::02 05::04::03 000012345".to_string(),
469            ),
470        ];
471
472        for (value, format, expected) in scalar_array_data {
473            let batch_len = format.len();
474            let arg_fields = vec![
475                Field::new("a", value.data_type(), false).into(),
476                Field::new("a", format.data_type().to_owned(), false).into(),
477            ];
478            let args = datafusion_expr::ScalarFunctionArgs {
479                args: vec![
480                    ColumnarValue::Scalar(value),
481                    ColumnarValue::Array(Arc::new(format) as ArrayRef),
482                ],
483                arg_fields,
484                number_rows: batch_len,
485                return_field: Field::new("f", DataType::Utf8, true).into(),
486            };
487            let result = ToCharFunc::new()
488                .invoke_with_args(args)
489                .expect("that to_char parsed values without error");
490
491            if let ColumnarValue::Scalar(ScalarValue::Utf8(date)) = result {
492                assert_eq!(expected, date.unwrap());
493            } else {
494                panic!("Expected a scalar value")
495            }
496        }
497
498        let array_scalar_data = vec![
499            (
500                Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
501                ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
502                StringArray::from(vec!["2020::09::01", "2020::09::02"]),
503            ),
504            (
505                Arc::new(Date64Array::from(vec![
506                    date.and_utc().timestamp_millis(),
507                    date2.and_utc().timestamp_millis(),
508                ])) as ArrayRef,
509                ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
510                StringArray::from(vec!["2020::01::02", "2026::07::08"]),
511            ),
512        ];
513
514        let array_array_data = vec![
515            (
516                Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
517                StringArray::from(vec!["%Y::%m::%d", "%d::%m::%Y"]),
518                StringArray::from(vec!["2020::09::01", "02::09::2020"]),
519            ),
520            (
521                Arc::new(Date64Array::from(vec![
522                    date.and_utc().timestamp_millis(),
523                    date2.and_utc().timestamp_millis(),
524                ])) as ArrayRef,
525                StringArray::from(vec!["%Y::%m::%d", "%d::%m::%Y"]),
526                StringArray::from(vec!["2020::01::02", "08::07::2026"]),
527            ),
528            (
529                Arc::new(Time32MillisecondArray::from(vec![1850600, 1860700]))
530                    as ArrayRef,
531                StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
532                StringArray::from(vec!["00:30:50", "00::31::00"]),
533            ),
534            (
535                Arc::new(Time32SecondArray::from(vec![18506, 18507])) as ArrayRef,
536                StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
537                StringArray::from(vec!["05:08:26", "05::08::27"]),
538            ),
539            (
540                Arc::new(Time64MicrosecondArray::from(vec![12344567000, 22244567000]))
541                    as ArrayRef,
542                StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
543                StringArray::from(vec!["03:25:44", "06::10::44"]),
544            ),
545            (
546                Arc::new(Time64NanosecondArray::from(vec![
547                    1234456789000,
548                    2224456789000,
549                ])) as ArrayRef,
550                StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
551                StringArray::from(vec!["00:20:34", "00::37::04"]),
552            ),
553            (
554                Arc::new(TimestampSecondArray::from(vec![
555                    date.and_utc().timestamp(),
556                    date2.and_utc().timestamp(),
557                ])) as ArrayRef,
558                StringArray::from(vec!["%Y::%m::%d %S::%M::%H", "%d::%m::%Y %S-%M-%H"]),
559                StringArray::from(vec![
560                    "2020::01::02 05::04::03",
561                    "08::07::2026 11-10-09",
562                ]),
563            ),
564            (
565                Arc::new(TimestampMillisecondArray::from(vec![
566                    date.and_utc().timestamp_millis(),
567                    date2.and_utc().timestamp_millis(),
568                ])) as ArrayRef,
569                StringArray::from(vec![
570                    "%Y::%m::%d %S::%M::%H %f",
571                    "%d::%m::%Y %S-%M-%H %f",
572                ]),
573                StringArray::from(vec![
574                    "2020::01::02 05::04::03 000000000",
575                    "08::07::2026 11-10-09 000000000",
576                ]),
577            ),
578            (
579                Arc::new(TimestampMicrosecondArray::from(vec![
580                    date.and_utc().timestamp_micros(),
581                    date2.and_utc().timestamp_micros(),
582                ])) as ArrayRef,
583                StringArray::from(vec![
584                    "%Y::%m::%d %S::%M::%H %f",
585                    "%d::%m::%Y %S-%M-%H %f",
586                ]),
587                StringArray::from(vec![
588                    "2020::01::02 05::04::03 000012000",
589                    "08::07::2026 11-10-09 000056000",
590                ]),
591            ),
592            (
593                Arc::new(TimestampNanosecondArray::from(vec![
594                    date.and_utc().timestamp_nanos_opt().unwrap(),
595                    date2.and_utc().timestamp_nanos_opt().unwrap(),
596                ])) as ArrayRef,
597                StringArray::from(vec![
598                    "%Y::%m::%d %S::%M::%H %f",
599                    "%d::%m::%Y %S-%M-%H %f",
600                ]),
601                StringArray::from(vec![
602                    "2020::01::02 05::04::03 000012345",
603                    "08::07::2026 11-10-09 000056789",
604                ]),
605            ),
606        ];
607
608        for (value, format, expected) in array_scalar_data {
609            let batch_len = value.len();
610            let arg_fields = vec![
611                Field::new("a", value.data_type().clone(), false).into(),
612                Field::new("a", format.data_type(), false).into(),
613            ];
614            let args = datafusion_expr::ScalarFunctionArgs {
615                args: vec![
616                    ColumnarValue::Array(value as ArrayRef),
617                    ColumnarValue::Scalar(format),
618                ],
619                arg_fields,
620                number_rows: batch_len,
621                return_field: Field::new("f", DataType::Utf8, true).into(),
622            };
623            let result = ToCharFunc::new()
624                .invoke_with_args(args)
625                .expect("that to_char parsed values without error");
626
627            if let ColumnarValue::Array(result) = result {
628                assert_eq!(result.len(), 2);
629                assert_eq!(&expected as &dyn Array, result.as_ref());
630            } else {
631                panic!("Expected an array value")
632            }
633        }
634
635        for (value, format, expected) in array_array_data {
636            let batch_len = value.len();
637            let arg_fields = vec![
638                Field::new("a", value.data_type().clone(), false).into(),
639                Field::new("a", format.data_type().clone(), false).into(),
640            ];
641            let args = datafusion_expr::ScalarFunctionArgs {
642                args: vec![
643                    ColumnarValue::Array(value),
644                    ColumnarValue::Array(Arc::new(format) as ArrayRef),
645                ],
646                arg_fields,
647                number_rows: batch_len,
648                return_field: Field::new("f", DataType::Utf8, true).into(),
649            };
650            let result = ToCharFunc::new()
651                .invoke_with_args(args)
652                .expect("that to_char parsed values without error");
653
654            if let ColumnarValue::Array(result) = result {
655                assert_eq!(result.len(), 2);
656                assert_eq!(&expected as &dyn Array, result.as_ref());
657            } else {
658                panic!("Expected an array value")
659            }
660        }
661
662        //
663        // Fallible test cases
664        //
665
666        // invalid number of arguments
667        let arg_field = Field::new("a", DataType::Int32, true).into();
668        let args = datafusion_expr::ScalarFunctionArgs {
669            args: vec![ColumnarValue::Scalar(ScalarValue::Int32(Some(1)))],
670            arg_fields: vec![arg_field],
671            number_rows: 1,
672            return_field: Field::new("f", DataType::Utf8, true).into(),
673        };
674        let result = ToCharFunc::new().invoke_with_args(args);
675        assert_eq!(
676            result.err().unwrap().strip_backtrace(),
677            "Execution error: to_char function requires 2 arguments, got 1"
678        );
679
680        // invalid type
681        let arg_fields = vec![
682            Field::new("a", DataType::Utf8, true).into(),
683            Field::new("a", DataType::Timestamp(TimeUnit::Nanosecond, None), true).into(),
684        ];
685        let args = datafusion_expr::ScalarFunctionArgs {
686            args: vec![
687                ColumnarValue::Scalar(ScalarValue::Int32(Some(1))),
688                ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(Some(1), None)),
689            ],
690            arg_fields,
691            number_rows: 1,
692            return_field: Field::new("f", DataType::Utf8, true).into(),
693        };
694        let result = ToCharFunc::new().invoke_with_args(args);
695        assert_eq!(
696            result.err().unwrap().strip_backtrace(),
697            "Execution error: Format for `to_char` must be non-null Utf8, received Timestamp(Nanosecond, None)"
698        );
699    }
700}