1use 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}