1use 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 ColumnarValue::Scalar(ScalarValue::Utf8(Some(format))) => {
152 _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
205fn _to_char_scalar(
207 expression: ColumnarValue,
208 format: Option<&str>,
209) -> Result<ColumnarValue> {
210 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 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 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 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}