reifydb_function/time/
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 TimeFormat;
10
11impl TimeFormat {
12 pub fn new() -> Self {
13 Self
14 }
15}
16
17fn format_time(hour: u32, minute: u32, second: u32, nanosecond: u32, fmt: &str) -> Result<String, String> {
18 let mut result = String::new();
19 let mut chars = fmt.chars().peekable();
20
21 while let Some(ch) = chars.next() {
22 if ch == '%' {
23 match chars.peek() {
24 Some('H') => {
25 chars.next();
26 result.push_str(&format!("{:02}", hour));
27 }
28 Some('M') => {
29 chars.next();
30 result.push_str(&format!("{:02}", minute));
31 }
32 Some('S') => {
33 chars.next();
34 result.push_str(&format!("{:02}", second));
35 }
36 Some('f') => {
37 chars.next();
38 result.push_str(&format!("{:09}", nanosecond));
39 }
40 Some('3') => {
41 chars.next();
42 if chars.peek() == Some(&'f') {
43 chars.next();
44 result.push_str(&format!("{:03}", nanosecond / 1_000_000));
45 } else {
46 return Err(
47 "invalid format specifier: '%3' (expected '%3f')".to_string()
48 );
49 }
50 }
51 Some('6') => {
52 chars.next();
53 if chars.peek() == Some(&'f') {
54 chars.next();
55 result.push_str(&format!("{:06}", nanosecond / 1_000));
56 } else {
57 return Err(
58 "invalid format specifier: '%6' (expected '%6f')".to_string()
59 );
60 }
61 }
62 Some('%') => {
63 chars.next();
64 result.push('%');
65 }
66 Some(c) => {
67 let c = *c;
68 return Err(format!("invalid format specifier: '%{}'", c));
69 }
70 None => return Err("unexpected end of format string after '%'".to_string()),
71 }
72 } else {
73 result.push(ch);
74 }
75 }
76
77 Ok(result)
78}
79
80impl ScalarFunction for TimeFormat {
81 fn scalar(&self, ctx: ScalarFunctionContext) -> crate::error::ScalarFunctionResult<ColumnData> {
82 if let Some(result) = propagate_options(self, &ctx) {
83 return result;
84 }
85 let columns = ctx.columns;
86 let row_count = ctx.row_count;
87
88 if columns.len() != 2 {
89 return Err(ScalarFunctionError::ArityMismatch {
90 function: ctx.fragment.clone(),
91 expected: 2,
92 actual: columns.len(),
93 });
94 }
95
96 let time_col = columns.get(0).unwrap();
97 let fmt_col = columns.get(1).unwrap();
98
99 match (time_col.data(), fmt_col.data()) {
100 (
101 ColumnData::Time(time_container),
102 ColumnData::Utf8 {
103 container: fmt_container,
104 ..
105 },
106 ) => {
107 let mut result_data = Vec::with_capacity(row_count);
108
109 for i in 0..row_count {
110 match (time_container.get(i), fmt_container.is_defined(i)) {
111 (Some(t), true) => {
112 let fmt_str = &fmt_container[i];
113 match format_time(
114 t.hour(),
115 t.minute(),
116 t.second(),
117 t.nanosecond(),
118 fmt_str,
119 ) {
120 Ok(formatted) => {
121 result_data.push(formatted);
122 }
123 Err(reason) => {
124 return Err(
125 ScalarFunctionError::ExecutionFailed {
126 function: ctx.fragment.clone(),
127 reason,
128 },
129 );
130 }
131 }
132 }
133 _ => {
134 result_data.push(String::new());
135 }
136 }
137 }
138
139 Ok(ColumnData::Utf8 {
140 container: Utf8Container::new(result_data),
141 max_bytes: MaxBytes::MAX,
142 })
143 }
144 (ColumnData::Time(_), other) => Err(ScalarFunctionError::InvalidArgumentType {
145 function: ctx.fragment.clone(),
146 argument_index: 1,
147 expected: vec![Type::Utf8],
148 actual: other.get_type(),
149 }),
150 (other, _) => Err(ScalarFunctionError::InvalidArgumentType {
151 function: ctx.fragment.clone(),
152 argument_index: 0,
153 expected: vec![Type::Time],
154 actual: other.get_type(),
155 }),
156 }
157 }
158
159 fn return_type(&self, _input_types: &[Type]) -> Type {
160 Type::Utf8
161 }
162}