reifydb_engine/function/blob/
hex.rs1use reifydb_core::value::column::ColumnData;
5use reifydb_type::{OwnedFragment, value::Blob};
6
7use crate::function::{ScalarFunction, ScalarFunctionContext};
8
9pub struct BlobHex;
10
11impl BlobHex {
12 pub fn new() -> Self {
13 Self
14 }
15}
16
17impl ScalarFunction for BlobHex {
18 fn scalar(&self, ctx: ScalarFunctionContext) -> crate::Result<ColumnData> {
19 let columns = ctx.columns;
20 let row_count = ctx.row_count;
21 let column = columns.get(0).unwrap();
22
23 match &column.data() {
24 ColumnData::Utf8 {
25 container,
26 ..
27 } => {
28 let mut result_data = Vec::with_capacity(container.data().len());
29
30 for i in 0..row_count {
31 if container.is_defined(i) {
32 let hex_str = &container[i];
33 let blob = Blob::from_hex(OwnedFragment::internal(hex_str))?;
34 result_data.push(blob);
35 } else {
36 result_data.push(Blob::empty())
37 }
38 }
39
40 Ok(ColumnData::blob_with_bitvec(result_data, container.bitvec().clone()))
41 }
42 _ => unimplemented!("BlobHex only supports text input"),
43 }
44 }
45}
46
47#[cfg(test)]
48mod tests {
49 use reifydb_core::value::{
50 column::{Column, Columns},
51 container::Utf8Container,
52 };
53 use reifydb_type::{Fragment, value::constraint::bytes::MaxBytes};
54
55 use super::*;
56 use crate::function::ScalarFunctionContext;
57
58 #[test]
59 fn test_blob_hex_valid_input() {
60 let function = BlobHex::new();
61
62 let hex_data = vec!["deadbeef".to_string()];
63 let bitvec = vec![true];
64 let input_column = Column {
65 name: Fragment::borrowed_internal("input"),
66 data: ColumnData::Utf8 {
67 container: Utf8Container::new(hex_data, bitvec.into()),
68 max_bytes: MaxBytes::MAX,
69 },
70 };
71
72 let columns = Columns::new(vec![input_column]);
73 let ctx = ScalarFunctionContext {
74 columns: &columns,
75 row_count: 1,
76 };
77 let result = function.scalar(ctx).unwrap();
78
79 let ColumnData::Blob {
80 container,
81 ..
82 } = result
83 else {
84 panic!("Expected BLOB column data");
85 };
86 assert_eq!(container.len(), 1);
87 assert!(container.is_defined(0));
88 assert_eq!(container[0].as_bytes(), &[0xde, 0xad, 0xbe, 0xef]);
89 }
90
91 #[test]
92 fn test_blob_hex_empty_string() {
93 let function = BlobHex::new();
94
95 let hex_data = vec!["".to_string()];
96 let bitvec = vec![true];
97 let input_column = Column {
98 name: Fragment::borrowed_internal("input"),
99 data: ColumnData::Utf8 {
100 container: Utf8Container::new(hex_data, bitvec.into()),
101 max_bytes: MaxBytes::MAX,
102 },
103 };
104
105 let columns = Columns::new(vec![input_column]);
106 let ctx = ScalarFunctionContext {
107 columns: &columns,
108 row_count: 1,
109 };
110 let result = function.scalar(ctx).unwrap();
111
112 let ColumnData::Blob {
113 container,
114 ..
115 } = result
116 else {
117 panic!("Expected BLOB column data");
118 };
119 assert_eq!(container.len(), 1);
120 assert!(container.is_defined(0));
121 assert_eq!(container[0].as_bytes(), &[] as &[u8]);
122 }
123
124 #[test]
125 fn test_blob_hex_uppercase() {
126 let function = BlobHex::new();
127
128 let hex_data = vec!["DEADBEEF".to_string()];
129 let bitvec = vec![true];
130 let input_column = Column {
131 name: Fragment::borrowed_internal("input"),
132 data: ColumnData::Utf8 {
133 container: Utf8Container::new(hex_data, bitvec.into()),
134 max_bytes: MaxBytes::MAX,
135 },
136 };
137
138 let columns = Columns::new(vec![input_column]);
139 let ctx = ScalarFunctionContext {
140 columns: &columns,
141 row_count: 1,
142 };
143 let result = function.scalar(ctx).unwrap();
144
145 let ColumnData::Blob {
146 container,
147 ..
148 } = result
149 else {
150 panic!("Expected BLOB column data");
151 };
152 assert_eq!(container.len(), 1);
153 assert!(container.is_defined(0));
154 assert_eq!(container[0].as_bytes(), &[0xde, 0xad, 0xbe, 0xef]);
155 }
156
157 #[test]
158 fn test_blob_hex_mixed_case() {
159 let function = BlobHex::new();
160
161 let hex_data = vec!["DeAdBeEf".to_string()];
162 let bitvec = vec![true];
163 let input_column = Column {
164 name: Fragment::borrowed_internal("input"),
165 data: ColumnData::Utf8 {
166 container: Utf8Container::new(hex_data, bitvec.into()),
167 max_bytes: MaxBytes::MAX,
168 },
169 };
170
171 let columns = Columns::new(vec![input_column]);
172 let ctx = ScalarFunctionContext {
173 columns: &columns,
174 row_count: 1,
175 };
176 let result = function.scalar(ctx).unwrap();
177
178 let ColumnData::Blob {
179 container,
180 ..
181 } = result
182 else {
183 panic!("Expected BLOB column data");
184 };
185 assert_eq!(container.len(), 1);
186 assert!(container.is_defined(0));
187 assert_eq!(container[0].as_bytes(), &[0xde, 0xad, 0xbe, 0xef]);
188 }
189
190 #[test]
191 fn test_blob_hex_multiple_rows() {
192 let function = BlobHex::new();
193
194 let hex_data = vec!["ff".to_string(), "00".to_string(), "deadbeef".to_string()];
195 let bitvec = vec![true, true, true];
196 let input_column = Column {
197 name: Fragment::borrowed_internal("input"),
198 data: ColumnData::Utf8 {
199 container: Utf8Container::new(hex_data, bitvec.into()),
200 max_bytes: MaxBytes::MAX,
201 },
202 };
203
204 let columns = Columns::new(vec![input_column]);
205 let ctx = ScalarFunctionContext {
206 columns: &columns,
207 row_count: 3,
208 };
209 let result = function.scalar(ctx).unwrap();
210
211 let ColumnData::Blob {
212 container,
213 ..
214 } = result
215 else {
216 panic!("Expected BLOB column data");
217 };
218 assert_eq!(container.len(), 3);
219 assert!(container.is_defined(0));
220 assert!(container.is_defined(1));
221 assert!(container.is_defined(2));
222
223 assert_eq!(container[0].as_bytes(), &[0xff]);
224 assert_eq!(container[1].as_bytes(), &[0x00]);
225 assert_eq!(container[2].as_bytes(), &[0xde, 0xad, 0xbe, 0xef]);
226 }
227
228 #[test]
229 fn test_blob_hex_with_null_data() {
230 let function = BlobHex::new();
231
232 let hex_data = vec!["ff".to_string(), "".to_string(), "deadbeef".to_string()];
233 let bitvec = vec![true, false, true];
234 let input_column = Column {
235 name: Fragment::borrowed_internal("input"),
236 data: ColumnData::Utf8 {
237 container: Utf8Container::new(hex_data, bitvec.into()),
238 max_bytes: MaxBytes::MAX,
239 },
240 };
241
242 let columns = Columns::new(vec![input_column]);
243 let ctx = ScalarFunctionContext {
244 columns: &columns,
245 row_count: 3,
246 };
247 let result = function.scalar(ctx).unwrap();
248
249 let ColumnData::Blob {
250 container,
251 ..
252 } = result
253 else {
254 panic!("Expected BLOB column data");
255 };
256 assert_eq!(container.len(), 3);
257 assert!(container.is_defined(0));
258 assert!(!container.is_defined(1));
259 assert!(container.is_defined(2));
260
261 assert_eq!(container[0].as_bytes(), &[0xff]);
262 assert_eq!(container[1].as_bytes(), [].as_slice() as &[u8]);
263 assert_eq!(container[2].as_bytes(), &[0xde, 0xad, 0xbe, 0xef]);
264 }
265
266 #[test]
267 fn test_blob_hex_invalid_input_should_error() {
268 let function = BlobHex::new();
269
270 let hex_data = vec!["invalid_hex".to_string()];
271 let bitvec = vec![true];
272 let input_column = Column {
273 name: Fragment::borrowed_internal("input"),
274 data: ColumnData::Utf8 {
275 container: Utf8Container::new(hex_data, bitvec.into()),
276 max_bytes: MaxBytes::MAX,
277 },
278 };
279
280 let columns = Columns::new(vec![input_column]);
281 let ctx = ScalarFunctionContext {
282 columns: &columns,
283 row_count: 1,
284 };
285 let result = function.scalar(ctx);
286 assert!(result.is_err(), "Expected error for invalid hex input");
287 }
288
289 #[test]
290 fn test_blob_hex_odd_length_should_error() {
291 let function = BlobHex::new();
292
293 let hex_data = vec!["abc".to_string()];
294 let bitvec = vec![true];
295 let input_column = Column {
296 name: Fragment::borrowed_internal("input"),
297 data: ColumnData::Utf8 {
298 container: Utf8Container::new(hex_data, bitvec.into()),
299 max_bytes: MaxBytes::MAX,
300 },
301 };
302
303 let columns = Columns::new(vec![input_column]);
304 let ctx = ScalarFunctionContext {
305 columns: &columns,
306 row_count: 1,
307 };
308 let result = function.scalar(ctx);
309 assert!(result.is_err(), "Expected error for odd length hex string");
310 }
311}