Skip to main content

reifydb_value/value/container/
varlen_array.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::borrow::Cow;
5
6use arrow_array::{
7	Array, GenericByteArray, LargeBinaryArray, LargeStringArray, builder::GenericByteBuilder, types::ByteArrayType,
8};
9use arrow_buffer::{BooleanBuffer, Buffer, NullBuffer, OffsetBuffer};
10use arrow_select::filter::FilterPredicate;
11
12use crate::{
13	Result,
14	util::{bitmap, kernel},
15	value::{Value, blob::Blob, value_type::ValueType},
16};
17
18pub fn get<T>(array: &GenericByteArray<T>, index: usize) -> Option<&T::Native>
19where
20	T: ByteArrayType<Offset = i64>,
21{
22	if index < array.len() {
23		Some(array.value(index))
24	} else {
25		None
26	}
27}
28
29fn row_bytes<T>(array: &GenericByteArray<T>, index: usize) -> &[u8]
30where
31	T: ByteArrayType<Offset = i64>,
32{
33	<T::Native as AsRef<[u8]>>::as_ref(array.value(index))
34}
35
36pub fn equals<T>(left: &GenericByteArray<T>, right: &GenericByteArray<T>) -> bool
37where
38	T: ByteArrayType<Offset = i64>,
39{
40	left.len() == right.len() && (0..left.len()).all(|i| row_bytes(left, i) == row_bytes(right, i))
41}
42
43pub fn empty<T>() -> GenericByteArray<T>
44where
45	T: ByteArrayType<Offset = i64>,
46{
47	GenericByteArray::new(OffsetBuffer::new_empty(), Buffer::from_vec(Vec::<u8>::new()), None)
48}
49
50fn data_byte_len<T>(array: &GenericByteArray<T>) -> usize
51where
52	T: ByteArrayType<Offset = i64>,
53{
54	let offsets = array.value_offsets();
55	(offsets[offsets.len() - 1] - offsets[0]) as usize
56}
57
58pub fn compact_parts<T>(array: &GenericByteArray<T>) -> (&[u8], Cow<'_, [i64]>)
59where
60	T: ByteArrayType<Offset = i64>,
61{
62	let offsets = array.value_offsets();
63	let first = offsets[0];
64	let last = offsets[offsets.len() - 1];
65	let data = &array.value_data()[first as usize..last as usize];
66	let offsets = if first == 0 {
67		Cow::Borrowed(offsets)
68	} else {
69		Cow::Owned(offsets.iter().map(|offset| offset - first).collect())
70	};
71	(data, offsets)
72}
73
74pub fn slice<T>(array: &GenericByteArray<T>, start: usize, end: usize) -> GenericByteArray<T>
75where
76	T: ByteArrayType<Offset = i64>,
77{
78	let len = array.len();
79	let start = start.min(len);
80	let end = end.min(len);
81	if start >= end {
82		return attach_nulls(empty(), bitmap::slice_nulls(array.logical_nulls().as_ref(), start, end));
83	}
84	array.slice(start, end - start)
85}
86
87pub fn take<T>(array: &GenericByteArray<T>, num: usize) -> GenericByteArray<T>
88where
89	T: ByteArrayType<Offset = i64>,
90{
91	slice(array, 0, num)
92}
93
94pub fn filter<T>(array: &GenericByteArray<T>, mask: &BooleanBuffer) -> GenericByteArray<T>
95where
96	T: ByteArrayType<Offset = i64>,
97{
98	filter_with(array, &kernel::predicate(mask, array.len()))
99}
100
101pub fn filter_with<T>(array: &GenericByteArray<T>, predicate: &FilterPredicate) -> GenericByteArray<T>
102where
103	T: ByteArrayType<Offset = i64>,
104{
105	let selected = kernel::filtered(array, predicate);
106	let nulls = kernel::kept_nulls(array, &selected);
107	attach_nulls(selected, nulls)
108}
109
110pub fn reorder<T>(array: &GenericByteArray<T>, indices: &[usize]) -> Result<GenericByteArray<T>>
111where
112	T: ByteArrayType<Offset = i64>,
113{
114	kernel::rows_in_range(indices, array.len())?;
115	let mut builder = GenericByteBuilder::<T>::with_capacity(indices.len(), data_byte_len(array));
116	for &idx in indices {
117		builder.append_value(array.value(idx));
118	}
119	Ok(attach_nulls(builder.finish(), bitmap::reorder_nulls(array.logical_nulls().as_ref(), indices)))
120}
121
122pub fn attach_nulls<T>(array: GenericByteArray<T>, nulls: Option<NullBuffer>) -> GenericByteArray<T>
123where
124	T: ByteArrayType<Offset = i64>,
125{
126	bitmap::assert_nulls_len(nulls.as_ref(), array.len());
127	let (offsets, values, _) = array.into_parts();
128	// SAFETY: offsets and values come from a valid array of this type, and the validity length matches its rows.
129	unsafe { GenericByteArray::new_unchecked(offsets, values, nulls) }
130}
131
132pub fn capacity<T>(array: &GenericByteArray<T>) -> usize
133where
134	T: ByteArrayType<Offset = i64>,
135{
136	let offsets = array.offsets().inner().inner();
137	if offsets.strong_count() == 1 {
138		(offsets.capacity() / size_of::<i64>()).saturating_sub(1)
139	} else {
140		array.len()
141	}
142}
143
144pub fn heap_size<T>(array: &GenericByteArray<T>) -> usize
145where
146	T: ByteArrayType<Offset = i64>,
147{
148	let values = array.values();
149	let data = if values.strong_count() == 1 {
150		values.capacity()
151	} else {
152		data_byte_len(array)
153	};
154	data + (capacity(array) + 1) * size_of::<i64>()
155}
156
157pub fn blob_array(values: &[Blob]) -> LargeBinaryArray {
158	LargeBinaryArray::from_iter_values(values.iter().map(|blob| blob.as_bytes()))
159}
160
161pub fn utf8_get_value(array: &LargeStringArray, index: usize) -> Value {
162	match get(array, index) {
163		Some(s) => Value::Utf8(s.to_string()),
164		None => Value::none_of(ValueType::Utf8),
165	}
166}
167
168pub fn utf8_as_string(array: &LargeStringArray, index: usize) -> String {
169	match get(array, index) {
170		Some(s) => s.to_string(),
171		None => "none".to_string(),
172	}
173}
174
175pub fn blob_get_value(array: &LargeBinaryArray, index: usize) -> Value {
176	match get(array, index) {
177		Some(bytes) => Value::Blob(Blob::new(bytes.to_vec())),
178		None => Value::none_of(ValueType::Blob),
179	}
180}
181
182pub fn blob_as_string(array: &LargeBinaryArray, index: usize) -> String {
183	match get(array, index) {
184		Some(bytes) => Blob::new(bytes.to_vec()).to_string(),
185		None => "none".to_string(),
186	}
187}
188
189#[cfg(test)]
190mod tests {
191	use arrow_array::builder::LargeStringBuilder;
192
193	use super::*;
194
195	mod utf8 {
196		use super::*;
197
198		#[test]
199		fn test_slice() {
200			// slice must return exactly the rows in start..end.
201			let container = LargeStringArray::from(vec![
202				"one".to_string(),
203				"two".to_string(),
204				"three".to_string(),
205				"four".to_string(),
206			]);
207			let sliced = slice(&container, 1, 3);
208
209			assert_eq!(sliced.len(), 2);
210			assert_eq!(get(&sliced, 0), Some("two"));
211			assert_eq!(get(&sliced, 1), Some("three"));
212		}
213
214		#[test]
215		fn test_filter() {
216			// filter must keep exactly the rows whose mask bit is set, in order.
217			let container = LargeStringArray::from(vec![
218				"keep".to_string(),
219				"drop".to_string(),
220				"keep".to_string(),
221				"drop".to_string(),
222			]);
223			let mask = BooleanBuffer::from(vec![true, false, true, false]);
224
225			let container = filter(&container, &mask);
226
227			assert_eq!(container.len(), 2);
228			assert_eq!(get(&container, 0), Some("keep"));
229			assert_eq!(get(&container, 1), Some("keep"));
230		}
231
232		#[test]
233		fn test_reorder() {
234			// reorder must place row indices[i] at position i.
235			let container = LargeStringArray::from(vec![
236				"first".to_string(),
237				"second".to_string(),
238				"third".to_string(),
239			]);
240			let indices = [2, 0, 1];
241
242			let container = reorder(&container, &indices).unwrap();
243
244			assert_eq!(container.len(), 3);
245			assert_eq!(get(&container, 0), Some("third"));
246			assert_eq!(get(&container, 1), Some("first"));
247			assert_eq!(get(&container, 2), Some("second"));
248		}
249
250		#[test]
251		fn test_reorder_with_out_of_bounds() {
252			// An out of range index must fail, never yield an empty row that reads as a real value.
253			let container = LargeStringArray::from(vec!["a".to_string(), "b".to_string()]);
254			let error = reorder(&container, &[1, 5, 0]).unwrap_err();
255			assert_eq!(error.diagnostic().message, "row index 5 out of range for a column of 2 rows");
256		}
257
258		#[test]
259		fn test_empty_strings() {
260			// An empty row must read back as a defined empty string, never as a missing row.
261			let mut builder = LargeStringBuilder::with_capacity(2, 0);
262			builder.append_value("");
263			builder.append_value("");
264			let container = builder.finish();
265
266			assert_eq!(container.len(), 2);
267			assert_eq!(get(&container, 0), Some(""));
268			assert_eq!(get(&container, 1), Some(""));
269
270			assert!(0 < container.len());
271			assert!(1 < container.len());
272		}
273
274		#[test]
275		fn test_data_bytes_and_offsets_match_zero_copy_layout() {
276			// compact_parts must hand out exactly the row bytes and offsets starting at zero.
277			let container = LargeStringArray::from(vec!["aa".to_string(), "bb".to_string()]);
278			assert_eq!(compact_parts(&container).0, b"aabb");
279			assert_eq!(&*compact_parts(&container).1, &[0i64, 2, 4]);
280		}
281	}
282
283	mod blob {
284		use super::*;
285
286		#[test]
287		fn test_data_bytes_and_offsets_match_zero_copy_layout() {
288			// compact_parts must hand out exactly the row bytes and offsets starting at zero.
289			let container = blob_array(&[Blob::new(vec![0xAA, 0xBB]), Blob::new(vec![0xCC])]);
290			assert_eq!(compact_parts(&container).0, &[0xAAu8, 0xBB, 0xCC]);
291			assert_eq!(&*compact_parts(&container).1, &[0i64, 2, 3]);
292		}
293	}
294
295	mod varlen {
296		use super::*;
297
298		fn abcd() -> LargeBinaryArray {
299			LargeBinaryArray::from_iter_values([b"aa".as_slice(), b"bb", b"cc", b"dd"])
300		}
301
302		#[test]
303		fn filter_in_place_keeps_matching_elements() {
304			// filter must keep exactly the rows whose mask bit is set, in order.
305			let c = LargeBinaryArray::from_iter_values([b"yes".as_slice(), b"no", b"yes", b"no"]);
306			let c = filter(&c, &BooleanBuffer::collect_bool(4, |i| i % 2 == 0));
307			assert_eq!(c.len(), 2);
308			assert_eq!(get(&c, 0), Some(b"yes".as_slice()));
309			assert_eq!(get(&c, 1), Some(b"yes".as_slice()));
310		}
311
312		#[test]
313		fn reorder_refuses_an_out_of_range_index() {
314			// An out of range index must fail, never yield an empty row that reads as a real value.
315			let c = LargeBinaryArray::from_iter_values([b"a".as_slice(), b"b"]);
316			let error = reorder(&c, &[1, 100, 0]).unwrap_err();
317			assert_eq!(error.diagnostic().message, "row index 100 out of range for a column of 2 rows");
318		}
319
320		#[test]
321		fn take_n_truncates() {
322			// take must keep exactly the first n rows.
323			let c = LargeBinaryArray::from_iter_values([b"a".as_slice(), b"b", b"c"]);
324			let t = take(&c, 2);
325			assert_eq!(t.len(), 2);
326			assert_eq!(get(&t, 0), Some(b"a".as_slice()));
327			assert_eq!(get(&t, 1), Some(b"b".as_slice()));
328		}
329
330		#[test]
331		fn slice_extracts_subrange_with_rebased_offsets() {
332			// A slice must read its own rows and hand out offsets rebased exactly to zero.
333			let c = LargeBinaryArray::from_iter_values([b"aa".as_slice(), b"bb", b"cc", b"dd"]);
334			let s = slice(&c, 1, 3);
335			assert_eq!(s.len(), 2);
336			assert_eq!(get(&s, 0), Some(b"bb".as_slice()));
337			assert_eq!(get(&s, 1), Some(b"cc".as_slice()));
338			assert_eq!(&*compact_parts(&s).1, &[0i64, 2, 4]);
339		}
340
341		#[test]
342		fn equality_compares_logical_content() {
343			// Two arrays with the same rows must compare equal.
344			let a = LargeBinaryArray::from_iter_values([b"x".as_slice(), b"y"]);
345			let b = LargeBinaryArray::from_iter_values([b"x".as_slice(), b"y"]);
346			assert!(equals(&a, &b));
347		}
348
349		#[test]
350		fn frozen_slice_compact_parts_are_rebased() {
351			// A guest must see only the referenced bytes and a first offset of 0, never the parent buffer.
352			let c = abcd();
353			let s = slice(&c, 1, 3);
354			let (data, offsets) = compact_parts(&s);
355			assert_eq!(data, b"bbcc");
356			assert_eq!(&*offsets, &[0i64, 2, 4]);
357			assert!(matches!(offsets, Cow::Owned(_)));
358		}
359
360		#[test]
361		fn unsliced_compact_parts_borrow_offsets() {
362			// Unsliced containers must stay zero-copy across the ABI.
363			let c = abcd();
364			let (data, offsets) = compact_parts(&c);
365			assert_eq!(data, b"aabbccdd");
366			assert_eq!(&*offsets, &[0i64, 2, 4, 6, 8]);
367			assert!(matches!(offsets, Cow::Borrowed(_)));
368		}
369
370		#[test]
371		fn empty_range_slice_of_frozen_container_is_empty() {
372			// An empty range must yield len 0 and no bytes, even when the clamp lands mid-buffer.
373			let c = abcd();
374			for (start, end) in [(2, 2), (3, 1), (10, 20)] {
375				let s = slice(&c, start, end);
376				assert_eq!(s.len(), 0);
377				assert_eq!(data_byte_len(&s), 0);
378				assert_eq!(compact_parts(&s).0, b"");
379			}
380		}
381
382		#[test]
383		fn filter_on_frozen_slice_keeps_right_rows() {
384			// filter must read rows through absolute offsets, not assume the buffer starts at the slice.
385			let s = slice(&abcd(), 1, 4);
386			let s = filter(&s, &BooleanBuffer::collect_bool(3, |i| i != 1));
387			assert_eq!(s.len(), 2);
388			assert_eq!(get(&s, 0), Some(b"bb".as_slice()));
389			assert_eq!(get(&s, 1), Some(b"dd".as_slice()));
390			assert_eq!(s.value_data(), b"bbdd");
391			assert_eq!(s.value_offsets(), &[0i64, 2, 4]);
392		}
393	}
394}