Skip to main content

reifydb_value/value/container/
digest_array.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::borrow::Borrow;
5
6use arrow_array::{Array, LargeBinaryArray, builder::LargeBinaryBuilder};
7
8use crate::value::{Value, container::varlen_array, digest::Digest};
9
10fn decode(row: &[u8]) -> Digest {
11	Digest::decode(row).unwrap_or_else(|error| panic!("corrupt Digest row {row:02x?}: {error}"))
12}
13
14pub fn digest_array<B: Borrow<Digest>>(values: impl IntoIterator<Item = Option<B>>) -> LargeBinaryArray {
15	let mut builder = LargeBinaryBuilder::new();
16	for value in values {
17		match value {
18			Some(digest) => push_digest(&mut builder, digest.borrow()),
19			None => builder.append_null(),
20		}
21	}
22	builder.finish()
23}
24
25pub fn push_digest(builder: &mut LargeBinaryBuilder, digest: &Digest) {
26	builder.append_value(digest.encode());
27}
28
29pub fn get(array: &LargeBinaryArray, index: usize) -> Option<Digest> {
30	varlen_array::get(array, index).filter(|_| array.is_valid(index)).map(decode)
31}
32
33pub fn iter(array: &LargeBinaryArray) -> impl Iterator<Item = Option<Digest>> + '_ {
34	(0..array.len()).map(move |index| get(array, index))
35}
36
37pub fn is_defined(array: &LargeBinaryArray, index: usize) -> bool {
38	index < array.len() && array.is_valid(index)
39}
40
41pub fn get_value(array: &LargeBinaryArray, index: usize) -> Value {
42	match get(array, index) {
43		Some(digest) => Value::Digest(Box::new(digest)),
44		None => Value::none(),
45	}
46}
47
48pub fn as_string(array: &LargeBinaryArray, index: usize) -> String {
49	get_value(array, index).to_string()
50}
51
52#[cfg(test)]
53mod tests {
54
55	use super::*;
56	use crate::value::digest::tests::built;
57
58	#[test]
59	fn an_empty_row_is_the_none_slot() {
60		// Aggregates skip undefined digests; an empty row read as a digest would crash or count twice.
61		let digest = built(10_000, &[1.0, 2.5, -4.0]);
62		let array = digest_array([Some(&digest), None]);
63		assert!(array.is_null(1));
64		assert_eq!(array.null_count(), 1);
65		assert!(is_defined(&array, 0));
66		assert!(!is_defined(&array, 1));
67		assert!(!is_defined(&array, 2));
68		assert_eq!(get(&array, 0), Some(digest.clone()));
69		assert_eq!(get(&array, 1), None);
70		assert_eq!(get_value(&array, 0), Value::Digest(Box::new(digest)));
71		assert_eq!(get_value(&array, 1), Value::none());
72		assert_eq!(as_string(&array, 1), Value::none().to_string());
73		assert_eq!(iter(&array).filter(Option::is_some).count(), 1);
74	}
75
76	#[test]
77	fn reorder_past_the_end_fails() {
78		// An out of range row is a bug, so it must fail naming the row and length, never read as a none digest.
79		let error = varlen_array::reorder(&digest_array([Some(built(10_000, &[1.0]))]), &[4, 0]).unwrap_err();
80		assert_eq!(error.diagnostic().message, "row index 4 out of range for a column of 1 rows");
81	}
82}