reifydb_routine/function/duration/
format.rs1use reifydb_core::value::column::{ColumnWithName, buffer::ColumnBuffer, columns::Columns};
5use reifydb_type::value::{constraint::bytes::MaxBytes, container::utf8::Utf8Container, r#type::Type};
6
7use crate::routine::{Function, FunctionKind, Routine, RoutineInfo, context::FunctionContext, error::RoutineError};
8
9pub struct DurationFormat {
10 info: RoutineInfo,
11}
12
13impl Default for DurationFormat {
14 fn default() -> Self {
15 Self::new()
16 }
17}
18
19impl DurationFormat {
20 pub fn new() -> Self {
21 Self {
22 info: RoutineInfo::new("duration::format"),
23 }
24 }
25}
26
27fn format_duration(months: i32, days: i32, nanos: i64, fmt: &str) -> Result<String, String> {
28 let years = months / 12;
29 let remaining_months = months % 12;
30
31 let total_seconds = nanos / 1_000_000_000;
32 let remaining_nanos = (nanos % 1_000_000_000).unsigned_abs();
33
34 let hours = (total_seconds / 3600) % 24;
35 let minutes = (total_seconds % 3600) / 60;
36 let seconds = total_seconds % 60;
37
38 let mut result = String::new();
39 let mut chars = fmt.chars().peekable();
40
41 while let Some(ch) = chars.next() {
42 if ch == '%' {
43 match chars.next() {
44 Some('Y') => result.push_str(&format!("{}", years)),
45 Some('M') => result.push_str(&format!("{}", remaining_months)),
46 Some('D') => result.push_str(&format!("{}", days)),
47 Some('h') => result.push_str(&format!("{}", hours)),
48 Some('m') => result.push_str(&format!("{}", minutes)),
49 Some('s') => result.push_str(&format!("{}", seconds)),
50 Some('f') => result.push_str(&format!("{:09}", remaining_nanos)),
51 Some('%') => result.push('%'),
52 Some(c) => return Err(format!("invalid format specifier: '%{}'", c)),
53 None => return Err("unexpected end of format string after '%'".to_string()),
54 }
55 } else {
56 result.push(ch);
57 }
58 }
59
60 Ok(result)
61}
62
63impl<'a> Routine<FunctionContext<'a>> for DurationFormat {
64 fn info(&self) -> &RoutineInfo {
65 &self.info
66 }
67
68 fn return_type(&self, _input_types: &[Type]) -> Type {
69 Type::Utf8
70 }
71
72 fn execute(&self, ctx: &mut FunctionContext<'a>, args: &Columns) -> Result<Columns, RoutineError> {
73 if args.len() != 2 {
74 return Err(RoutineError::FunctionArityMismatch {
75 function: ctx.fragment.clone(),
76 expected: 2,
77 actual: args.len(),
78 });
79 }
80
81 let dur_col = &args[0];
82 let fmt_col = &args[1];
83
84 let (dur_data, dur_bv) = dur_col.unwrap_option();
85 let (fmt_data, _) = fmt_col.unwrap_option();
86
87 match (dur_data, fmt_data) {
88 (
89 ColumnBuffer::Duration(dur_container),
90 ColumnBuffer::Utf8 {
91 container: fmt_container,
92 ..
93 },
94 ) => {
95 let row_count = dur_data.len();
96 let mut result_data = Vec::with_capacity(row_count);
97
98 for i in 0..row_count {
99 match (dur_container.get(i), fmt_container.is_defined(i)) {
100 (Some(d), true) => {
101 let fmt_str = fmt_container.get(i).unwrap();
102 match format_duration(
103 d.get_months(),
104 d.get_days(),
105 d.get_nanos(),
106 fmt_str,
107 ) {
108 Ok(formatted) => {
109 result_data.push(formatted);
110 }
111 Err(reason) => {
112 return Err(
113 RoutineError::FunctionExecutionFailed {
114 function: ctx.fragment.clone(),
115 reason,
116 },
117 );
118 }
119 }
120 }
121 _ => {
122 result_data.push(String::new());
123 }
124 }
125 }
126
127 let mut final_data = ColumnBuffer::Utf8 {
128 container: Utf8Container::new(result_data),
129 max_bytes: MaxBytes::MAX,
130 };
131 if let Some(bv) = dur_bv {
132 final_data = ColumnBuffer::Option {
133 inner: Box::new(final_data),
134 bitvec: bv.clone(),
135 };
136 }
137 Ok(Columns::new(vec![ColumnWithName::new(ctx.fragment.clone(), final_data)]))
138 }
139 (ColumnBuffer::Duration(_), other) => Err(RoutineError::FunctionInvalidArgumentType {
140 function: ctx.fragment.clone(),
141 argument_index: 1,
142 expected: vec![Type::Utf8],
143 actual: other.get_type(),
144 }),
145 (other, _) => Err(RoutineError::FunctionInvalidArgumentType {
146 function: ctx.fragment.clone(),
147 argument_index: 0,
148 expected: vec![Type::Duration],
149 actual: other.get_type(),
150 }),
151 }
152 }
153}
154
155impl Function for DurationFormat {
156 fn kinds(&self) -> &[FunctionKind] {
157 &[FunctionKind::Scalar]
158 }
159}