reifydb_function/duration/
format.rs1use reifydb_core::value::column::data::ColumnData;
5use reifydb_type::value::{constraint::bytes::MaxBytes, container::utf8::Utf8Container, r#type::Type};
6
7use crate::{ScalarFunction, ScalarFunctionContext, error::ScalarFunctionError, propagate_options};
8
9pub struct DurationFormat;
10
11impl DurationFormat {
12 pub fn new() -> Self {
13 Self
14 }
15}
16
17fn format_duration(months: i32, days: i32, nanos: i64, fmt: &str) -> Result<String, String> {
18 let years = months / 12;
19 let remaining_months = months % 12;
20
21 let total_seconds = nanos / 1_000_000_000;
22 let remaining_nanos = (nanos % 1_000_000_000).unsigned_abs();
23
24 let hours = (total_seconds / 3600) % 24;
25 let minutes = (total_seconds % 3600) / 60;
26 let seconds = total_seconds % 60;
27
28 let mut result = String::new();
29 let mut chars = fmt.chars().peekable();
30
31 while let Some(ch) = chars.next() {
32 if ch == '%' {
33 match chars.next() {
34 Some('Y') => result.push_str(&format!("{}", years)),
35 Some('M') => result.push_str(&format!("{}", remaining_months)),
36 Some('D') => result.push_str(&format!("{}", days)),
37 Some('h') => result.push_str(&format!("{}", hours)),
38 Some('m') => result.push_str(&format!("{}", minutes)),
39 Some('s') => result.push_str(&format!("{}", seconds)),
40 Some('f') => result.push_str(&format!("{:09}", remaining_nanos)),
41 Some('%') => result.push('%'),
42 Some(c) => return Err(format!("invalid format specifier: '%{}'", c)),
43 None => return Err("unexpected end of format string after '%'".to_string()),
44 }
45 } else {
46 result.push(ch);
47 }
48 }
49
50 Ok(result)
51}
52
53impl ScalarFunction for DurationFormat {
54 fn scalar(&self, ctx: ScalarFunctionContext) -> crate::error::ScalarFunctionResult<ColumnData> {
55 if let Some(result) = propagate_options(self, &ctx) {
56 return result;
57 }
58 let columns = ctx.columns;
59 let row_count = ctx.row_count;
60
61 if columns.len() != 2 {
62 return Err(ScalarFunctionError::ArityMismatch {
63 function: ctx.fragment.clone(),
64 expected: 2,
65 actual: columns.len(),
66 });
67 }
68
69 let dur_col = columns.get(0).unwrap();
70 let fmt_col = columns.get(1).unwrap();
71
72 match (dur_col.data(), fmt_col.data()) {
73 (
74 ColumnData::Duration(dur_container),
75 ColumnData::Utf8 {
76 container: fmt_container,
77 ..
78 },
79 ) => {
80 let mut result_data = Vec::with_capacity(row_count);
81
82 for i in 0..row_count {
83 match (dur_container.get(i), fmt_container.is_defined(i)) {
84 (Some(d), true) => {
85 let fmt_str = &fmt_container[i];
86 match format_duration(
87 d.get_months(),
88 d.get_days(),
89 d.get_nanos(),
90 fmt_str,
91 ) {
92 Ok(formatted) => {
93 result_data.push(formatted);
94 }
95 Err(reason) => {
96 return Err(
97 ScalarFunctionError::ExecutionFailed {
98 function: ctx.fragment.clone(),
99 reason,
100 },
101 );
102 }
103 }
104 }
105 _ => {
106 result_data.push(String::new());
107 }
108 }
109 }
110
111 Ok(ColumnData::Utf8 {
112 container: Utf8Container::new(result_data),
113 max_bytes: MaxBytes::MAX,
114 })
115 }
116 (ColumnData::Duration(_), other) => Err(ScalarFunctionError::InvalidArgumentType {
117 function: ctx.fragment.clone(),
118 argument_index: 1,
119 expected: vec![Type::Utf8],
120 actual: other.get_type(),
121 }),
122 (other, _) => Err(ScalarFunctionError::InvalidArgumentType {
123 function: ctx.fragment.clone(),
124 argument_index: 0,
125 expected: vec![Type::Duration],
126 actual: other.get_type(),
127 }),
128 }
129 }
130
131 fn return_type(&self, _input_types: &[Type]) -> Type {
132 Type::Utf8
133 }
134}