Skip to main content

reifydb_codec/encoded/
utf8.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::str;
5
6use reifydb_value::{reifydb_assertions, value::value_type::ValueType};
7
8use crate::encoded::{row::EncodedRow, shape::RowShape};
9
10impl RowShape {
11	pub fn set_utf8(&self, row: &mut EncodedRow, index: usize, value: impl AsRef<str>) {
12		reifydb_assertions! {
13			assert!(
14				row.len() >= self.total_static_size(),
15				"row/shape size mismatch: row.len()={} < total_static_size()={}",
16				row.len(),
17				self.total_static_size()
18			);
19			assert_eq!(*self.fields()[index].constraint.get_type().inner_type(), ValueType::Utf8);
20		}
21		self.replace_dynamic_data(row, index, value.as_ref().as_bytes());
22	}
23
24	pub fn get_utf8<'a>(&'a self, row: &'a EncodedRow, index: usize) -> &'a str {
25		let field = &self.fields()[index];
26		reifydb_assertions! {
27			assert!(
28				row.len() >= self.total_static_size(),
29				"row/shape size mismatch: row.len()={} < total_static_size()={}",
30				row.len(),
31				self.total_static_size()
32			);
33			assert_eq!(*field.constraint.get_type().inner_type(), ValueType::Utf8);
34		}
35
36		let ref_slice = &row.as_slice()[field.offset as usize..field.offset as usize + 8];
37		let offset = u32::from_le_bytes([ref_slice[0], ref_slice[1], ref_slice[2], ref_slice[3]]) as usize;
38		let length = u32::from_le_bytes([ref_slice[4], ref_slice[5], ref_slice[6], ref_slice[7]]) as usize;
39
40		let dynamic_start = self.dynamic_section_start();
41		let string_start = dynamic_start + offset;
42		let string_slice = &row.as_slice()[string_start..string_start + length];
43
44		unsafe { str::from_utf8_unchecked(string_slice) }
45	}
46
47	pub fn try_get_utf8<'a>(&'a self, row: &'a EncodedRow, index: usize) -> Option<&'a str> {
48		if row.is_defined(index) && self.fields()[index].constraint.get_type() == ValueType::Utf8 {
49			Some(self.get_utf8(row, index))
50		} else {
51			None
52		}
53	}
54}
55
56#[cfg(test)]
57pub mod tests {
58	use reifydb_value::value::value_type::ValueType;
59
60	use crate::encoded::shape::RowShape;
61
62	#[test]
63	fn test_set_get_utf8() {
64		let shape = RowShape::testing(&[ValueType::Utf8]);
65		let mut row = shape.allocate();
66		shape.set_utf8(&mut row, 0, "reifydb");
67		assert_eq!(shape.get_utf8(&row, 0), "reifydb");
68	}
69
70	#[test]
71	fn test_try_get_utf8() {
72		let shape = RowShape::testing(&[ValueType::Utf8]);
73		let mut row = shape.allocate();
74
75		assert_eq!(shape.try_get_utf8(&row, 0), None);
76
77		shape.set_utf8(&mut row, 0, "reifydb");
78		assert_eq!(shape.try_get_utf8(&row, 0), Some("reifydb"));
79	}
80
81	#[test]
82	fn test_empty_string() {
83		let shape = RowShape::testing(&[ValueType::Utf8]);
84		let mut row = shape.allocate();
85		shape.set_utf8(&mut row, 0, "");
86		assert_eq!(shape.get_utf8(&row, 0), "");
87		assert_eq!(shape.try_get_utf8(&row, 0), Some(""));
88	}
89
90	#[test]
91	fn test_unicode() {
92		let shape = RowShape::testing(&[ValueType::Utf8]);
93		let mut row = shape.allocate();
94
95		let unicode_text = "πŸš€βœ¨πŸŒŸ Hello δΈ–η•Œ πŸŽ‰";
96		shape.set_utf8(&mut row, 0, unicode_text);
97		assert_eq!(shape.get_utf8(&row, 0), unicode_text);
98		assert_eq!(shape.try_get_utf8(&row, 0), Some(unicode_text));
99	}
100
101	#[test]
102	fn test_large_string() {
103		let shape = RowShape::testing(&[ValueType::Utf8]);
104		let mut row = shape.allocate();
105
106		let large_string = "A".repeat(1000);
107		shape.set_utf8(&mut row, 0, &large_string);
108		assert_eq!(shape.get_utf8(&row, 0), large_string);
109		assert_eq!(shape.try_get_utf8(&row, 0), Some(large_string.as_str()));
110	}
111
112	#[test]
113	fn test_multiple_fields() {
114		let shape = RowShape::testing(&[ValueType::Utf8, ValueType::Utf8, ValueType::Utf8]);
115		let mut row = shape.allocate();
116
117		shape.set_utf8(&mut row, 0, "first");
118		shape.set_utf8(&mut row, 1, "second string");
119		shape.set_utf8(&mut row, 2, "third");
120
121		assert_eq!(shape.get_utf8(&row, 0), "first");
122		assert_eq!(shape.get_utf8(&row, 1), "second string");
123		assert_eq!(shape.get_utf8(&row, 2), "third");
124	}
125
126	#[test]
127	fn test_mixed_with_static_fields() {
128		let shape = RowShape::testing(&[ValueType::Boolean, ValueType::Utf8, ValueType::Int4, ValueType::Utf8]);
129		let mut row = shape.allocate();
130
131		shape.set_bool(&mut row, 0, true);
132		shape.set_utf8(&mut row, 1, "hello world");
133		shape.set_i32(&mut row, 2, 42);
134		shape.set_utf8(&mut row, 3, "goodbye");
135
136		assert_eq!(shape.get_bool(&row, 0), true);
137		assert_eq!(shape.get_utf8(&row, 1), "hello world");
138		assert_eq!(shape.get_i32(&row, 2), 42);
139		assert_eq!(shape.get_utf8(&row, 3), "goodbye");
140	}
141
142	#[test]
143	fn test_different_sizes() {
144		let shape = RowShape::testing(&[ValueType::Utf8, ValueType::Utf8, ValueType::Utf8]);
145		let mut row = shape.allocate();
146
147		shape.set_utf8(&mut row, 0, "");
148		shape.set_utf8(&mut row, 1, "medium length string here");
149		shape.set_utf8(&mut row, 2, "x");
150
151		assert_eq!(shape.get_utf8(&row, 0), "");
152		assert_eq!(shape.get_utf8(&row, 1), "medium length string here");
153		assert_eq!(shape.get_utf8(&row, 2), "x");
154	}
155
156	#[test]
157	fn test_arbitrary_setting_order() {
158		let shape = RowShape::testing(&[ValueType::Utf8, ValueType::Utf8, ValueType::Utf8, ValueType::Utf8]);
159		let mut row = shape.allocate();
160
161		// Set in reverse order
162		shape.set_utf8(&mut row, 3, "fourth");
163		shape.set_utf8(&mut row, 1, "second");
164		shape.set_utf8(&mut row, 0, "first");
165		shape.set_utf8(&mut row, 2, "third");
166
167		assert_eq!(shape.get_utf8(&row, 0), "first");
168		assert_eq!(shape.get_utf8(&row, 1), "second");
169		assert_eq!(shape.get_utf8(&row, 2), "third");
170		assert_eq!(shape.get_utf8(&row, 3), "fourth");
171	}
172
173	#[test]
174	fn test_special_characters() {
175		let shape = RowShape::testing(&[ValueType::Utf8]);
176
177		let special_strings = [
178			"",
179			" ",
180			"\n",
181			"\t",
182			"\r\n",
183			"\"quoted\"",
184			"'single quotes'",
185			"line1\nline2\nline3",
186			"tabs\there\tand\there",
187			"mixed\twhite\n \r\n\tspace",
188		];
189
190		for special_str in special_strings {
191			let mut row = shape.allocate();
192			shape.set_utf8(&mut row, 0, special_str);
193			assert_eq!(shape.get_utf8(&row, 0), special_str);
194		}
195	}
196
197	#[test]
198	fn test_undefined_handling() {
199		let shape = RowShape::testing(&[ValueType::Utf8, ValueType::Utf8, ValueType::Utf8]);
200		let mut row = shape.allocate();
201
202		// Set only some fields
203		shape.set_utf8(&mut row, 0, "defined");
204		shape.set_utf8(&mut row, 2, "also defined");
205
206		assert_eq!(shape.try_get_utf8(&row, 0), Some("defined"));
207		assert_eq!(shape.try_get_utf8(&row, 1), None);
208		assert_eq!(shape.try_get_utf8(&row, 2), Some("also defined"));
209
210		// Set field as undefined
211		shape.set_none(&mut row, 0);
212		assert_eq!(shape.try_get_utf8(&row, 0), None);
213		assert_eq!(shape.try_get_utf8(&row, 2), Some("also defined"));
214	}
215
216	#[test]
217	fn test_try_get_utf8_wrong_type() {
218		let shape = RowShape::testing(&[ValueType::Boolean]);
219		let mut row = shape.allocate();
220
221		shape.set_bool(&mut row, 0, true);
222
223		assert_eq!(shape.try_get_utf8(&row, 0), None);
224	}
225
226	#[test]
227	fn test_update_utf8() {
228		let shape = RowShape::testing(&[ValueType::Utf8]);
229		let mut row = shape.allocate();
230
231		shape.set_utf8(&mut row, 0, "hello");
232		assert_eq!(shape.get_utf8(&row, 0), "hello");
233		let size_after_first = row.len();
234
235		// Overwrite with shorter string
236		shape.set_utf8(&mut row, 0, "hi");
237		assert_eq!(shape.get_utf8(&row, 0), "hi");
238		assert_eq!(row.len(), size_after_first - 3); // "hello"(5) -> "hi"(2)
239
240		// Overwrite with longer string
241		shape.set_utf8(&mut row, 0, "hello world");
242		assert_eq!(shape.get_utf8(&row, 0), "hello world");
243
244		// Overwrite with empty string
245		shape.set_utf8(&mut row, 0, "");
246		assert_eq!(shape.get_utf8(&row, 0), "");
247		assert_eq!(row.len(), shape.total_static_size());
248	}
249
250	#[test]
251	fn test_update_utf8_with_other_dynamic_fields() {
252		let shape = RowShape::testing(&[ValueType::Utf8, ValueType::Utf8, ValueType::Utf8]);
253		let mut row = shape.allocate();
254
255		shape.set_utf8(&mut row, 0, "first");
256		shape.set_utf8(&mut row, 1, "second");
257		shape.set_utf8(&mut row, 2, "third");
258
259		// Update middle field with a longer string
260		shape.set_utf8(&mut row, 1, "much longer second string");
261
262		// All fields should read correctly
263		assert_eq!(shape.get_utf8(&row, 0), "first");
264		assert_eq!(shape.get_utf8(&row, 1), "much longer second string");
265		assert_eq!(shape.get_utf8(&row, 2), "third");
266
267		// Update first field with shorter string
268		shape.set_utf8(&mut row, 0, "f");
269		assert_eq!(shape.get_utf8(&row, 0), "f");
270		assert_eq!(shape.get_utf8(&row, 1), "much longer second string");
271		assert_eq!(shape.get_utf8(&row, 2), "third");
272
273		// No orphan data: total size = static + sum of current strings
274		let expected = shape.total_static_size() + 1 + 25 + 5;
275		assert_eq!(row.len(), expected);
276	}
277}