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