Skip to main content

reifydb_value/value/container/
utf8.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{
5	fmt::{self, Debug},
6	result::Result as StdResult,
7	str,
8};
9
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11
12use crate::{
13	Result, reifydb_assertions,
14	storage::{Cow, Storage},
15	value::{Value, container::varlen::VarlenContainer, value_type::ValueType},
16};
17
18pub struct Utf8Container<S: Storage = Cow> {
19	inner: VarlenContainer<S>,
20}
21
22impl<S: Storage> Clone for Utf8Container<S> {
23	fn clone(&self) -> Self {
24		Self {
25			inner: self.inner.clone(),
26		}
27	}
28}
29
30impl<S: Storage> Debug for Utf8Container<S>
31where
32	VarlenContainer<S>: Debug,
33{
34	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35		f.debug_struct("Utf8Container").field("inner", &self.inner).finish()
36	}
37}
38
39impl<S: Storage> PartialEq for Utf8Container<S>
40where
41	VarlenContainer<S>: PartialEq,
42{
43	fn eq(&self, other: &Self) -> bool {
44		self.inner == other.inner
45	}
46}
47
48impl Serialize for Utf8Container<Cow> {
49	fn serialize<Ser: Serializer>(&self, serializer: Ser) -> StdResult<Ser::Ok, Ser::Error> {
50		self.inner.serialize(serializer)
51	}
52}
53
54impl<'de> Deserialize<'de> for Utf8Container<Cow> {
55	fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
56		let inner = VarlenContainer::deserialize(deserializer)?;
57		Ok(Self {
58			inner,
59		})
60	}
61}
62
63impl Utf8Container<Cow> {
64	pub fn new(data: Vec<String>) -> Self {
65		Self::from_vec(data)
66	}
67
68	pub fn from_vec(data: Vec<String>) -> Self {
69		let inner = VarlenContainer::from_byte_slices(data.iter().map(|s| s.as_bytes()));
70		Self {
71			inner,
72		}
73	}
74
75	pub fn from_repeated_str(value: &str, count: usize) -> Self {
76		Self {
77			inner: VarlenContainer::from_repeated_bytes(value.as_bytes(), count),
78		}
79	}
80
81	pub fn with_capacity(capacity: usize) -> Self {
82		Self {
83			inner: VarlenContainer::with_capacity(capacity, capacity * 16),
84		}
85	}
86
87	pub fn from_raw_parts(data: Vec<String>) -> Self {
88		Self::from_vec(data)
89	}
90
91	pub fn from_bytes_offsets(data: Vec<u8>, offsets: Vec<u64>) -> Self {
92		reifydb_assertions! {
93			assert!(str::from_utf8(&data).is_ok(), "Utf8Container data must be valid UTF-8");
94		}
95		Self {
96			inner: VarlenContainer::from_raw_parts(data, offsets),
97		}
98	}
99
100	pub fn try_into_raw_parts(self) -> Option<Vec<String>> {
101		Some(self.iter().map(|s| s.unwrap().to_string()).collect())
102	}
103}
104
105impl<S: Storage> Utf8Container<S> {
106	pub fn from_inner(inner: VarlenContainer<S>) -> Self {
107		Self {
108			inner,
109		}
110	}
111
112	pub fn from_storage_parts(data: S::Vec<u8>, offsets: S::Vec<u64>) -> Self {
113		Self {
114			inner: VarlenContainer::from_storage_parts(data, offsets),
115		}
116	}
117
118	pub fn data_storage(&self) -> &S::Vec<u8> {
119		self.inner.data()
120	}
121
122	pub fn offsets_storage(&self) -> &S::Vec<u64> {
123		self.inner.offsets_data()
124	}
125
126	pub fn len(&self) -> usize {
127		self.inner.len()
128	}
129
130	pub fn capacity(&self) -> usize {
131		self.inner.capacity()
132	}
133
134	pub fn is_empty(&self) -> bool {
135		self.inner.is_empty()
136	}
137
138	pub fn clear(&mut self) {
139		self.inner.clear_generic();
140	}
141
142	pub fn get(&self, index: usize) -> Option<&str> {
143		let bytes = self.inner.get_bytes(index)?;
144		// SAFETY: All push paths validate UTF-8 (push(&str) takes a
145
146		Some(unsafe { str::from_utf8_unchecked(bytes) })
147	}
148
149	pub fn is_defined(&self, idx: usize) -> bool {
150		idx < self.len()
151	}
152
153	pub fn is_fully_defined(&self) -> bool {
154		true
155	}
156
157	pub fn data_bytes(&self) -> &[u8] {
158		self.inner.data_bytes()
159	}
160
161	pub fn offsets(&self) -> &[u64] {
162		self.inner.offsets()
163	}
164
165	pub fn inner(&self) -> &VarlenContainer<S> {
166		&self.inner
167	}
168
169	pub fn as_string(&self, index: usize) -> String {
170		self.get(index).map(str::to_string).unwrap_or_else(|| "none".to_string())
171	}
172
173	pub fn get_value(&self, index: usize) -> Value {
174		match self.get(index) {
175			Some(s) => Value::Utf8(s.to_string()),
176			None => Value::none_of(ValueType::Utf8),
177		}
178	}
179
180	pub fn iter(&self) -> impl Iterator<Item = Option<&str>> + '_ {
181		(0..self.len()).map(|i| self.get(i))
182	}
183
184	pub fn iter_str(&self) -> impl Iterator<Item = &str> + '_ {
185		(0..self.len()).map(|i| self.get(i).unwrap())
186	}
187}
188
189impl Utf8Container<Cow> {
190	pub fn push(&mut self, value: String) {
191		self.inner.push_bytes(value.as_bytes());
192	}
193
194	pub fn push_str(&mut self, value: &str) {
195		self.inner.push_bytes(value.as_bytes());
196	}
197
198	pub fn push_default(&mut self) {
199		self.inner.push_bytes(&[]);
200	}
201
202	pub fn extend(&mut self, other: &Self) -> Result<()> {
203		self.inner.extend_from(&other.inner);
204		Ok(())
205	}
206
207	pub fn slice(&self, start: usize, end: usize) -> Self {
208		Self {
209			inner: self.inner.slice(start, end),
210		}
211	}
212
213	pub fn filter(&mut self, mask: &<Cow as Storage>::BitVec) {
214		let bits: Vec<bool> = mask.iter().collect();
215		self.inner.filter_in_place(|i| bits.get(i).copied().unwrap_or(false));
216	}
217
218	pub fn reorder(&mut self, indices: &[usize]) {
219		self.inner.reorder_in_place(indices);
220	}
221
222	pub fn take(&self, num: usize) -> Self {
223		Self {
224			inner: self.inner.take_n(num),
225		}
226	}
227}
228
229impl Default for Utf8Container<Cow> {
230	fn default() -> Self {
231		Self::with_capacity(0)
232	}
233}
234
235#[cfg(test)]
236pub mod tests {
237	use postcard::to_allocvec as postcard_to_allocvec;
238
239	use super::*;
240	use crate::util::bitvec::BitVec;
241
242	#[test]
243	fn test_new() {
244		let data = vec!["hello".to_string(), "world".to_string(), "test".to_string()];
245		let container = Utf8Container::new(data.clone());
246
247		assert_eq!(container.len(), 3);
248		assert_eq!(container.get(0), Some("hello"));
249		assert_eq!(container.get(1), Some("world"));
250		assert_eq!(container.get(2), Some("test"));
251	}
252
253	#[test]
254	fn test_from_vec() {
255		let data = vec!["foo".to_string(), "bar".to_string(), "baz".to_string()];
256		let container = Utf8Container::from_vec(data);
257
258		assert_eq!(container.len(), 3);
259		assert_eq!(container.get(0), Some("foo"));
260		assert_eq!(container.get(1), Some("bar"));
261		assert_eq!(container.get(2), Some("baz"));
262
263		for i in 0..3 {
264			assert!(container.is_defined(i));
265		}
266	}
267
268	#[test]
269	fn test_from_repeated_str() {
270		let container = Utf8Container::from_repeated_str("mint", 3);
271		let explicit =
272			Utf8Container::from_vec(vec!["mint".to_string(), "mint".to_string(), "mint".to_string()]);
273		assert_eq!(container, explicit);
274		assert_eq!(container.len(), 3);
275		assert_eq!(container.get(0), Some("mint"));
276		assert_eq!(container.get(2), Some("mint"));
277		for i in 0..3 {
278			assert!(container.is_defined(i));
279		}
280	}
281
282	#[test]
283	fn test_with_capacity() {
284		let container = Utf8Container::with_capacity(10);
285		assert_eq!(container.len(), 0);
286		assert!(container.is_empty());
287		assert!(container.capacity() >= 10);
288	}
289
290	#[test]
291	fn test_push() {
292		let mut container = Utf8Container::with_capacity(3);
293
294		container.push("first".to_string());
295		container.push("second".to_string());
296		container.push_default();
297
298		assert_eq!(container.len(), 3);
299		assert_eq!(container.get(0), Some("first"));
300		assert_eq!(container.get(1), Some("second"));
301		assert_eq!(container.get(2), Some(""));
302
303		assert!(container.is_defined(0));
304		assert!(container.is_defined(1));
305		assert!(container.is_defined(2));
306	}
307
308	#[test]
309	fn test_extend() {
310		let mut container1 = Utf8Container::from_vec(vec!["a".to_string(), "b".to_string()]);
311		let container2 = Utf8Container::from_vec(vec!["c".to_string(), "d".to_string()]);
312
313		container1.extend(&container2).unwrap();
314
315		assert_eq!(container1.len(), 4);
316		assert_eq!(container1.get(0), Some("a"));
317		assert_eq!(container1.get(1), Some("b"));
318		assert_eq!(container1.get(2), Some("c"));
319		assert_eq!(container1.get(3), Some("d"));
320	}
321
322	#[test]
323	fn test_iter() {
324		let data = vec!["x".to_string(), "y".to_string(), "z".to_string()];
325		let container = Utf8Container::new(data);
326
327		let collected: Vec<Option<&str>> = container.iter().collect();
328		assert_eq!(collected, vec![Some("x"), Some("y"), Some("z")]);
329	}
330
331	#[test]
332	fn test_slice() {
333		let container = Utf8Container::from_vec(vec![
334			"one".to_string(),
335			"two".to_string(),
336			"three".to_string(),
337			"four".to_string(),
338		]);
339		let sliced = container.slice(1, 3);
340
341		assert_eq!(sliced.len(), 2);
342		assert_eq!(sliced.get(0), Some("two"));
343		assert_eq!(sliced.get(1), Some("three"));
344	}
345
346	#[test]
347	fn test_filter() {
348		let mut container = Utf8Container::from_vec(vec![
349			"keep".to_string(),
350			"drop".to_string(),
351			"keep".to_string(),
352			"drop".to_string(),
353		]);
354		let mask = BitVec::from_slice(&[true, false, true, false]);
355
356		container.filter(&mask);
357
358		assert_eq!(container.len(), 2);
359		assert_eq!(container.get(0), Some("keep"));
360		assert_eq!(container.get(1), Some("keep"));
361	}
362
363	#[test]
364	fn test_reorder() {
365		let mut container =
366			Utf8Container::from_vec(vec!["first".to_string(), "second".to_string(), "third".to_string()]);
367		let indices = [2, 0, 1];
368
369		container.reorder(&indices);
370
371		assert_eq!(container.len(), 3);
372		assert_eq!(container.get(0), Some("third"));
373		assert_eq!(container.get(1), Some("first"));
374		assert_eq!(container.get(2), Some("second"));
375	}
376
377	#[test]
378	fn test_reorder_with_out_of_bounds() {
379		let mut container = Utf8Container::from_vec(vec!["a".to_string(), "b".to_string()]);
380		let indices = [1, 5, 0];
381
382		container.reorder(&indices);
383
384		assert_eq!(container.len(), 3);
385		assert_eq!(container.get(0), Some("b"));
386		assert_eq!(container.get(1), Some(""));
387		assert_eq!(container.get(2), Some("a"));
388	}
389
390	#[test]
391	fn test_empty_strings() {
392		let mut container = Utf8Container::with_capacity(2);
393		container.push("".to_string());
394		container.push_default();
395
396		assert_eq!(container.len(), 2);
397		assert_eq!(container.get(0), Some(""));
398		assert_eq!(container.get(1), Some(""));
399
400		assert!(container.is_defined(0));
401		assert!(container.is_defined(1));
402	}
403
404	#[test]
405	fn testault() {
406		let container = Utf8Container::default();
407		assert_eq!(container.len(), 0);
408		assert!(container.is_empty());
409	}
410
411	#[test]
412	fn test_data_bytes_and_offsets_match_zero_copy_layout() {
413		let container = Utf8Container::from_vec(vec!["aa".to_string(), "bb".to_string()]);
414		assert_eq!(container.data_bytes(), b"aabb");
415		assert_eq!(container.offsets(), &[0u64, 2, 4]);
416	}
417
418	#[test]
419	fn test_postcard_wire_compat() {
420		// The postcard byte form must match what `Vec<String>` would
421		// produce so on-disk state and CDC streams stay readable.
422		let strings = vec!["hello".to_string(), "world".to_string()];
423		let strings_bytes: Vec<u8> = postcard_to_allocvec(&strings).unwrap();
424
425		let container = Utf8Container::from_vec(strings.clone());
426		let container_bytes: Vec<u8> = postcard_to_allocvec(&container).unwrap();
427
428		assert_eq!(strings_bytes, container_bytes);
429	}
430}