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