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::compute::cast;
24use arrow::datatypes::DataType;
25use arrow::datatypes::DataType::{
26 Date32, Date64, Duration, Time32, Time64, Timestamp, Utf8,
27};
28use arrow::datatypes::TimeUnit::{Microsecond, Millisecond, Nanosecond, Second};
29use arrow::error::ArrowError;
30use arrow::util::display::{ArrayFormatter, DurationFormat, FormatOptions};
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/date_time_functions.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, PartialEq, Eq, Hash)]
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()
183 .with_date_format(Some(format))
184 .with_datetime_format(Some(format)),
185 Date64 => FormatOptions::new().with_datetime_format(Some(format)),
186 Time32(_) => FormatOptions::new().with_time_format(Some(format)),
187 Time64(_) => FormatOptions::new().with_time_format(Some(format)),
188 Timestamp(_, _) => FormatOptions::new()
189 .with_timestamp_format(Some(format))
190 .with_timestamp_tz_format(Some(format)),
191 Duration(_) => FormatOptions::new().with_duration_format(
192 if "ISO8601".eq_ignore_ascii_case(format) {
193 DurationFormat::ISO8601
194 } else {
195 DurationFormat::Pretty
196 },
197 ),
198 other => {
199 return Err(exec_err!(
200 "to_char only supports date, time, timestamp and duration data types, received {other:?}"
201 ));
202 }
203 };
204 Ok(format_options)
205}
206
207fn to_char_scalar(
209 expression: ColumnarValue,
210 format: Option<&str>,
211) -> Result<ColumnarValue> {
212 let data_type = &expression.data_type();
215 let is_scalar_expression = matches!(&expression, ColumnarValue::Scalar(_));
216 let array = expression.clone().into_array(1)?;
217
218 if format.is_none() {
219 return if is_scalar_expression {
220 Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None)))
221 } else {
222 Ok(ColumnarValue::Array(new_null_array(&Utf8, array.len())))
223 };
224 }
225
226 let format_options = match build_format_options(data_type, format) {
227 Ok(value) => value,
228 Err(value) => return value,
229 };
230
231 let formatter = ArrayFormatter::try_new(array.as_ref(), &format_options)?;
232 let formatted: Result<Vec<Option<String>>, ArrowError> = (0..array.len())
233 .map(|i| {
234 if array.is_null(i) {
235 Ok(None)
236 } else {
237 formatter.value(i).try_to_string().map(Some)
238 }
239 })
240 .collect();
241
242 if let Ok(formatted) = formatted {
243 if is_scalar_expression {
244 Ok(ColumnarValue::Scalar(ScalarValue::Utf8(
245 formatted.first().unwrap().clone(),
246 )))
247 } else {
248 Ok(ColumnarValue::Array(
249 Arc::new(StringArray::from(formatted)) as ArrayRef
250 ))
251 }
252 } else {
253 if data_type == &Date32 {
256 return to_char_scalar(expression.clone().cast_to(&Date64, None)?, format);
257 }
258
259 exec_err!("{}", formatted.unwrap_err())
260 }
261}
262
263fn to_char_array(args: &[ColumnarValue]) -> Result<ColumnarValue> {
264 let arrays = ColumnarValue::values_to_arrays(args)?;
265 let mut results: Vec<Option<String>> = vec![];
266 let format_array = arrays[1].as_string::<i32>();
267 let data_type = arrays[0].data_type();
268
269 for idx in 0..arrays[0].len() {
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) => {
290 if data_type == &Date32 {
293 let failed_date_value = arrays[0].slice(idx, 1);
294
295 match retry_date_as_timestamp(failed_date_value, &format_options) {
296 Ok(value) => {
297 results.push(Some(value));
298 continue;
299 }
300 Err(e) => {
301 return exec_err!("{}", e);
302 }
303 }
304 }
305
306 return exec_err!("{}", e);
307 }
308 }
309 }
310
311 match args[0] {
312 ColumnarValue::Array(_) => Ok(ColumnarValue::Array(Arc::new(StringArray::from(
313 results,
314 )) as ArrayRef)),
315 ColumnarValue::Scalar(_) => match results.first().unwrap() {
316 Some(value) => Ok(ColumnarValue::Scalar(ScalarValue::Utf8(Some(
317 value.to_string(),
318 )))),
319 None => Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None))),
320 },
321 }
322}
323
324fn retry_date_as_timestamp(
325 array_ref: ArrayRef,
326 format_options: &FormatOptions,
327) -> Result<String> {
328 let target_data_type = Date64;
329
330 let date_value = cast(&array_ref, &target_data_type)?;
331 let formatter = ArrayFormatter::try_new(date_value.as_ref(), format_options)?;
332 let result = formatter.value(0).try_to_string()?;
333
334 Ok(result)
335}
336
337#[cfg(test)]
338mod tests {
339 use crate::datetime::to_char::ToCharFunc;
340 use arrow::array::{
341 Array, ArrayRef, Date32Array, Date64Array, StringArray, Time32MillisecondArray,
342 Time32SecondArray, Time64MicrosecondArray, Time64NanosecondArray,
343 TimestampMicrosecondArray, TimestampMillisecondArray, TimestampNanosecondArray,
344 TimestampSecondArray,
345 };
346 use arrow::datatypes::{DataType, Field, TimeUnit};
347 use chrono::{NaiveDateTime, Timelike};
348 use datafusion_common::config::ConfigOptions;
349 use datafusion_common::ScalarValue;
350 use datafusion_expr::{ColumnarValue, ScalarUDFImpl};
351 use std::sync::Arc;
352
353 #[test]
354 fn test_array_array() {
355 let array_array_data = vec![(
356 Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
357 StringArray::from(vec!["%Y::%m::%d", "%Y::%m::%d %S::%M::%H %f"]),
358 StringArray::from(vec!["2020::09::01", "2020::09::02 00::00::00 000000000"]),
359 )];
360
361 for (value, format, expected) in array_array_data {
362 let batch_len = value.len();
363 let value_data_type = value.data_type().clone();
364 let format_data_type = format.data_type().clone();
365
366 let args = datafusion_expr::ScalarFunctionArgs {
367 args: vec![
368 ColumnarValue::Array(value),
369 ColumnarValue::Array(Arc::new(format) as ArrayRef),
370 ],
371 arg_fields: vec![
372 Field::new("a", value_data_type, true).into(),
373 Field::new("b", format_data_type, true).into(),
374 ],
375 number_rows: batch_len,
376 return_field: Field::new("f", DataType::Utf8, true).into(),
377 config_options: Arc::clone(&Arc::new(ConfigOptions::default())),
378 lambdas: None,
379 };
380 let result = ToCharFunc::new()
381 .invoke_with_args(args)
382 .expect("that to_char parsed values without error");
383
384 if let ColumnarValue::Array(result) = result {
385 assert_eq!(result.len(), 2);
386 assert_eq!(&expected as &dyn Array, result.as_ref());
387 } else {
388 panic!("Expected an array value")
389 }
390 }
391 }
392
393 #[test]
394 fn test_to_char() {
395 let date = "2020-01-02T03:04:05"
396 .parse::<NaiveDateTime>()
397 .unwrap()
398 .with_nanosecond(12345)
399 .unwrap();
400 let date2 = "2026-07-08T09:10:11"
401 .parse::<NaiveDateTime>()
402 .unwrap()
403 .with_nanosecond(56789)
404 .unwrap();
405
406 let scalar_data = vec![
407 (
408 ScalarValue::Date32(Some(18506)),
409 ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
410 "2020::09::01".to_string(),
411 ),
412 (
413 ScalarValue::Date32(Some(18506)),
414 ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H %f".to_string())),
415 "2020::09::01 00::00::00 000000000".to_string(),
416 ),
417 (
418 ScalarValue::Date64(Some(date.and_utc().timestamp_millis())),
419 ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
420 "2020::01::02".to_string(),
421 ),
422 (
423 ScalarValue::Time32Second(Some(31851)),
424 ScalarValue::Utf8(Some("%H-%M-%S".to_string())),
425 "08-50-51".to_string(),
426 ),
427 (
428 ScalarValue::Time32Millisecond(Some(18506000)),
429 ScalarValue::Utf8(Some("%H-%M-%S".to_string())),
430 "05-08-26".to_string(),
431 ),
432 (
433 ScalarValue::Time64Microsecond(Some(12344567000)),
434 ScalarValue::Utf8(Some("%H-%M-%S %f".to_string())),
435 "03-25-44 567000000".to_string(),
436 ),
437 (
438 ScalarValue::Time64Nanosecond(Some(12344567890000)),
439 ScalarValue::Utf8(Some("%H-%M-%S %f".to_string())),
440 "03-25-44 567890000".to_string(),
441 ),
442 (
443 ScalarValue::TimestampSecond(Some(date.and_utc().timestamp()), None),
444 ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H".to_string())),
445 "2020::01::02 05::04::03".to_string(),
446 ),
447 (
448 ScalarValue::TimestampMillisecond(
449 Some(date.and_utc().timestamp_millis()),
450 None,
451 ),
452 ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H".to_string())),
453 "2020::01::02 05::04::03".to_string(),
454 ),
455 (
456 ScalarValue::TimestampMicrosecond(
457 Some(date.and_utc().timestamp_micros()),
458 None,
459 ),
460 ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H %f".to_string())),
461 "2020::01::02 05::04::03 000012000".to_string(),
462 ),
463 (
464 ScalarValue::TimestampNanosecond(
465 Some(date.and_utc().timestamp_nanos_opt().unwrap()),
466 None,
467 ),
468 ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H %f".to_string())),
469 "2020::01::02 05::04::03 000012345".to_string(),
470 ),
471 ];
472
473 for (value, format, expected) in scalar_data {
474 let arg_fields = vec![
475 Field::new("a", value.data_type(), false).into(),
476 Field::new("a", format.data_type(), false).into(),
477 ];
478 let args = datafusion_expr::ScalarFunctionArgs {
479 args: vec![ColumnarValue::Scalar(value), ColumnarValue::Scalar(format)],
480 arg_fields,
481 number_rows: 1,
482 return_field: Field::new("f", DataType::Utf8, true).into(),
483 config_options: Arc::new(ConfigOptions::default()),
484 lambdas: None,
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 scalar_array_data = vec![
498 (
499 ScalarValue::Date32(Some(18506)),
500 StringArray::from(vec!["%Y::%m::%d".to_string()]),
501 "2020::09::01".to_string(),
502 ),
503 (
504 ScalarValue::Date32(Some(18506)),
505 StringArray::from(vec!["%Y::%m::%d %S::%M::%H %f".to_string()]),
506 "2020::09::01 00::00::00 000000000".to_string(),
507 ),
508 (
509 ScalarValue::Date64(Some(date.and_utc().timestamp_millis())),
510 StringArray::from(vec!["%Y::%m::%d".to_string()]),
511 "2020::01::02".to_string(),
512 ),
513 (
514 ScalarValue::Time32Second(Some(31851)),
515 StringArray::from(vec!["%H-%M-%S".to_string()]),
516 "08-50-51".to_string(),
517 ),
518 (
519 ScalarValue::Time32Millisecond(Some(18506000)),
520 StringArray::from(vec!["%H-%M-%S".to_string()]),
521 "05-08-26".to_string(),
522 ),
523 (
524 ScalarValue::Time64Microsecond(Some(12344567000)),
525 StringArray::from(vec!["%H-%M-%S %f".to_string()]),
526 "03-25-44 567000000".to_string(),
527 ),
528 (
529 ScalarValue::Time64Nanosecond(Some(12344567890000)),
530 StringArray::from(vec!["%H-%M-%S %f".to_string()]),
531 "03-25-44 567890000".to_string(),
532 ),
533 (
534 ScalarValue::TimestampSecond(Some(date.and_utc().timestamp()), None),
535 StringArray::from(vec!["%Y::%m::%d %S::%M::%H".to_string()]),
536 "2020::01::02 05::04::03".to_string(),
537 ),
538 (
539 ScalarValue::TimestampMillisecond(
540 Some(date.and_utc().timestamp_millis()),
541 None,
542 ),
543 StringArray::from(vec!["%Y::%m::%d %S::%M::%H".to_string()]),
544 "2020::01::02 05::04::03".to_string(),
545 ),
546 (
547 ScalarValue::TimestampMicrosecond(
548 Some(date.and_utc().timestamp_micros()),
549 None,
550 ),
551 StringArray::from(vec!["%Y::%m::%d %S::%M::%H %f".to_string()]),
552 "2020::01::02 05::04::03 000012000".to_string(),
553 ),
554 (
555 ScalarValue::TimestampNanosecond(
556 Some(date.and_utc().timestamp_nanos_opt().unwrap()),
557 None,
558 ),
559 StringArray::from(vec!["%Y::%m::%d %S::%M::%H %f".to_string()]),
560 "2020::01::02 05::04::03 000012345".to_string(),
561 ),
562 ];
563
564 for (value, format, expected) in scalar_array_data {
565 let batch_len = format.len();
566 let arg_fields = vec![
567 Field::new("a", value.data_type(), false).into(),
568 Field::new("a", format.data_type().to_owned(), false).into(),
569 ];
570 let args = datafusion_expr::ScalarFunctionArgs {
571 args: vec![
572 ColumnarValue::Scalar(value),
573 ColumnarValue::Array(Arc::new(format) as ArrayRef),
574 ],
575 arg_fields,
576 number_rows: batch_len,
577 return_field: Field::new("f", DataType::Utf8, true).into(),
578 config_options: Arc::new(ConfigOptions::default()),
579 lambdas: None,
580 };
581 let result = ToCharFunc::new()
582 .invoke_with_args(args)
583 .expect("that to_char parsed values without error");
584
585 if let ColumnarValue::Scalar(ScalarValue::Utf8(date)) = result {
586 assert_eq!(expected, date.unwrap());
587 } else {
588 panic!("Expected a scalar value")
589 }
590 }
591
592 let array_scalar_data = vec![
593 (
594 Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
595 ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
596 StringArray::from(vec!["2020::09::01", "2020::09::02"]),
597 ),
598 (
599 Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
600 ScalarValue::Utf8(Some("%Y::%m::%d %S::%M::%H %f".to_string())),
601 StringArray::from(vec![
602 "2020::09::01 00::00::00 000000000",
603 "2020::09::02 00::00::00 000000000",
604 ]),
605 ),
606 (
607 Arc::new(Date64Array::from(vec![
608 date.and_utc().timestamp_millis(),
609 date2.and_utc().timestamp_millis(),
610 ])) as ArrayRef,
611 ScalarValue::Utf8(Some("%Y::%m::%d".to_string())),
612 StringArray::from(vec!["2020::01::02", "2026::07::08"]),
613 ),
614 ];
615
616 let array_array_data = vec![
617 (
618 Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
619 StringArray::from(vec!["%Y::%m::%d", "%d::%m::%Y"]),
620 StringArray::from(vec!["2020::09::01", "02::09::2020"]),
621 ),
622 (
623 Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
624 StringArray::from(vec![
625 "%Y::%m::%d %S::%M::%H %f",
626 "%Y::%m::%d %S::%M::%H %f",
627 ]),
628 StringArray::from(vec![
629 "2020::09::01 00::00::00 000000000",
630 "2020::09::02 00::00::00 000000000",
631 ]),
632 ),
633 (
634 Arc::new(Date32Array::from(vec![18506, 18507])) as ArrayRef,
635 StringArray::from(vec!["%Y::%m::%d", "%Y::%m::%d %S::%M::%H %f"]),
636 StringArray::from(vec![
637 "2020::09::01",
638 "2020::09::02 00::00::00 000000000",
639 ]),
640 ),
641 (
642 Arc::new(Date64Array::from(vec![
643 date.and_utc().timestamp_millis(),
644 date2.and_utc().timestamp_millis(),
645 ])) as ArrayRef,
646 StringArray::from(vec!["%Y::%m::%d", "%d::%m::%Y"]),
647 StringArray::from(vec!["2020::01::02", "08::07::2026"]),
648 ),
649 (
650 Arc::new(Time32MillisecondArray::from(vec![1850600, 1860700]))
651 as ArrayRef,
652 StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
653 StringArray::from(vec!["00:30:50", "00::31::00"]),
654 ),
655 (
656 Arc::new(Time32SecondArray::from(vec![18506, 18507])) as ArrayRef,
657 StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
658 StringArray::from(vec!["05:08:26", "05::08::27"]),
659 ),
660 (
661 Arc::new(Time64MicrosecondArray::from(vec![12344567000, 22244567000]))
662 as ArrayRef,
663 StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
664 StringArray::from(vec!["03:25:44", "06::10::44"]),
665 ),
666 (
667 Arc::new(Time64NanosecondArray::from(vec![
668 1234456789000,
669 2224456789000,
670 ])) as ArrayRef,
671 StringArray::from(vec!["%H:%M:%S", "%H::%M::%S"]),
672 StringArray::from(vec!["00:20:34", "00::37::04"]),
673 ),
674 (
675 Arc::new(TimestampSecondArray::from(vec![
676 date.and_utc().timestamp(),
677 date2.and_utc().timestamp(),
678 ])) as ArrayRef,
679 StringArray::from(vec!["%Y::%m::%d %S::%M::%H", "%d::%m::%Y %S-%M-%H"]),
680 StringArray::from(vec![
681 "2020::01::02 05::04::03",
682 "08::07::2026 11-10-09",
683 ]),
684 ),
685 (
686 Arc::new(TimestampMillisecondArray::from(vec![
687 date.and_utc().timestamp_millis(),
688 date2.and_utc().timestamp_millis(),
689 ])) as ArrayRef,
690 StringArray::from(vec![
691 "%Y::%m::%d %S::%M::%H %f",
692 "%d::%m::%Y %S-%M-%H %f",
693 ]),
694 StringArray::from(vec![
695 "2020::01::02 05::04::03 000000000",
696 "08::07::2026 11-10-09 000000000",
697 ]),
698 ),
699 (
700 Arc::new(TimestampMicrosecondArray::from(vec![
701 date.and_utc().timestamp_micros(),
702 date2.and_utc().timestamp_micros(),
703 ])) as ArrayRef,
704 StringArray::from(vec![
705 "%Y::%m::%d %S::%M::%H %f",
706 "%d::%m::%Y %S-%M-%H %f",
707 ]),
708 StringArray::from(vec![
709 "2020::01::02 05::04::03 000012000",
710 "08::07::2026 11-10-09 000056000",
711 ]),
712 ),
713 (
714 Arc::new(TimestampNanosecondArray::from(vec![
715 date.and_utc().timestamp_nanos_opt().unwrap(),
716 date2.and_utc().timestamp_nanos_opt().unwrap(),
717 ])) as ArrayRef,
718 StringArray::from(vec![
719 "%Y::%m::%d %S::%M::%H %f",
720 "%d::%m::%Y %S-%M-%H %f",
721 ]),
722 StringArray::from(vec![
723 "2020::01::02 05::04::03 000012345",
724 "08::07::2026 11-10-09 000056789",
725 ]),
726 ),
727 ];
728
729 for (value, format, expected) in array_scalar_data {
730 let batch_len = value.len();
731 let arg_fields = vec![
732 Field::new("a", value.data_type().clone(), false).into(),
733 Field::new("a", format.data_type(), false).into(),
734 ];
735 let args = datafusion_expr::ScalarFunctionArgs {
736 args: vec![
737 ColumnarValue::Array(value as ArrayRef),
738 ColumnarValue::Scalar(format),
739 ],
740 arg_fields,
741 number_rows: batch_len,
742 return_field: Field::new("f", DataType::Utf8, true).into(),
743 config_options: Arc::new(ConfigOptions::default()),
744 lambdas: None,
745 };
746 let result = ToCharFunc::new()
747 .invoke_with_args(args)
748 .expect("that to_char parsed values without error");
749
750 if let ColumnarValue::Array(result) = result {
751 assert_eq!(result.len(), 2);
752 assert_eq!(&expected as &dyn Array, result.as_ref());
753 } else {
754 panic!("Expected an array value")
755 }
756 }
757
758 for (value, format, expected) in array_array_data {
759 let batch_len = value.len();
760 let arg_fields = vec![
761 Field::new("a", value.data_type().clone(), false).into(),
762 Field::new("a", format.data_type().clone(), false).into(),
763 ];
764 let args = datafusion_expr::ScalarFunctionArgs {
765 args: vec![
766 ColumnarValue::Array(value),
767 ColumnarValue::Array(Arc::new(format) as ArrayRef),
768 ],
769 arg_fields,
770 number_rows: batch_len,
771 return_field: Field::new("f", DataType::Utf8, true).into(),
772 config_options: Arc::new(ConfigOptions::default()),
773 lambdas: None,
774 };
775 let result = ToCharFunc::new()
776 .invoke_with_args(args)
777 .expect("that to_char parsed values without error");
778
779 if let ColumnarValue::Array(result) = result {
780 assert_eq!(result.len(), 2);
781 assert_eq!(&expected as &dyn Array, result.as_ref());
782 } else {
783 panic!("Expected an array value")
784 }
785 }
786
787 let arg_field = Field::new("a", DataType::Int32, true).into();
793 let args = datafusion_expr::ScalarFunctionArgs {
794 args: vec![ColumnarValue::Scalar(ScalarValue::Int32(Some(1)))],
795 arg_fields: vec![arg_field],
796 number_rows: 1,
797 return_field: Field::new("f", DataType::Utf8, true).into(),
798 config_options: Arc::new(ConfigOptions::default()),
799 lambdas: None,
800 };
801 let result = ToCharFunc::new().invoke_with_args(args);
802 assert_eq!(
803 result.err().unwrap().strip_backtrace(),
804 "Execution error: to_char function requires 2 arguments, got 1"
805 );
806
807 let arg_fields = vec![
809 Field::new("a", DataType::Utf8, true).into(),
810 Field::new("a", DataType::Timestamp(TimeUnit::Nanosecond, None), true).into(),
811 ];
812 let args = datafusion_expr::ScalarFunctionArgs {
813 args: vec![
814 ColumnarValue::Scalar(ScalarValue::Int32(Some(1))),
815 ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(Some(1), None)),
816 ],
817 arg_fields,
818 number_rows: 1,
819 return_field: Field::new("f", DataType::Utf8, true).into(),
820 config_options: Arc::new(ConfigOptions::default()),
821 lambdas: None,
822 };
823 let result = ToCharFunc::new().invoke_with_args(args);
824 assert_eq!(
825 result.err().unwrap().strip_backtrace(),
826 "Execution error: Format for `to_char` must be non-null Utf8, received Timestamp(Nanosecond, None)"
827 );
828 }
829}