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 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 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 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 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 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 assert!(!result.is_null(0));
703 assert!(result.is_null(1));
704 } else {
705 panic!("Expected an array value");
706 }
707 }
708}