Skip to main content

reifydb_value/value/container/
any_array.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::borrow::Borrow;
5
6use arrow_array::{
7	Array, LargeBinaryArray,
8	builder::{ArrayBuilder, LargeBinaryBuilder},
9};
10use postcard::{from_bytes, to_allocvec};
11
12use crate::{
13	Result,
14	util::{bitmap, kernel},
15	value::{Value, container::varlen_array, value_type::ValueType},
16};
17
18fn encode(value: &Value, row: usize) -> Vec<u8> {
19	if let Value::None {
20		..
21	} = value
22	{
23		panic!("an Any cell can not hold a none, row {row} must be a null row");
24	}
25	to_allocvec(value).expect("postcard serialization of a Value is total")
26}
27
28fn decode(row: &[u8]) -> Value {
29	if row.is_empty() {
30		panic!("empty Any row");
31	}
32	match from_bytes(row) {
33		Ok(Value::None {
34			..
35		}) => panic!("corrupt Any row {row:02x?}: an encoded none, a none is a null row"),
36		Ok(value) => value,
37		Err(error) => panic!("corrupt Any row {row:02x?}: {error}"),
38	}
39}
40
41pub fn any_array<B: Borrow<Value>>(values: impl IntoIterator<Item = B>) -> LargeBinaryArray {
42	let mut builder = LargeBinaryBuilder::new();
43	for (row, value) in values.into_iter().enumerate() {
44		builder.append_value(encode(value.borrow(), row));
45	}
46	builder.finish()
47}
48
49pub fn any_array_optional<B: Borrow<Value>>(values: impl IntoIterator<Item = Option<B>>) -> LargeBinaryArray {
50	let mut builder = LargeBinaryBuilder::new();
51	for (row, value) in values.into_iter().enumerate() {
52		match value {
53			Some(value) => builder.append_value(encode(value.borrow(), row)),
54			None => builder.append_null(),
55		}
56	}
57	builder.finish()
58}
59
60pub fn push_any(builder: &mut LargeBinaryBuilder, value: &Value) {
61	let row = builder.len();
62	builder.append_value(encode(value, row));
63}
64
65pub fn get(array: &LargeBinaryArray, index: usize) -> Option<Value> {
66	varlen_array::get(array, index).filter(|_| array.is_valid(index)).map(decode)
67}
68
69pub fn values(array: &LargeBinaryArray) -> Vec<Value> {
70	(0..array.len()).map(|index| get(array, index).unwrap_or_else(Value::none)).collect()
71}
72
73pub fn get_value(array: &LargeBinaryArray, declared_type: Option<&ValueType>, index: usize) -> Value {
74	match (get(array, index), declared_type) {
75		(Some(value), Some(ValueType::List(_)) | Some(ValueType::Record(_))) => value,
76		(Some(value), _) => Value::Any(Box::new(value)),
77		(None, _) => Value::none(),
78	}
79}
80
81pub fn as_string(array: &LargeBinaryArray, index: usize) -> String {
82	match get(array, index) {
83		Some(value) => format!("{}", value),
84		None => "none".to_string(),
85	}
86}
87
88pub fn reorder(array: &LargeBinaryArray, indices: &[usize]) -> Result<LargeBinaryArray> {
89	kernel::rows_in_range(indices, array.len())?;
90	let mut builder = LargeBinaryBuilder::with_capacity(indices.len(), varlen_array::compact_parts(array).0.len());
91	for &index in indices {
92		builder.append_value(array.value(index));
93	}
94	Ok(varlen_array::attach_nulls(builder.finish(), bitmap::reorder_nulls(array.logical_nulls().as_ref(), indices)))
95}
96
97pub fn equals(left: &LargeBinaryArray, right: &LargeBinaryArray) -> bool {
98	left.len() == right.len() && (0..left.len()).all(|index| get(left, index) == get(right, index))
99}
100
101#[cfg(test)]
102mod tests {
103	use ::uuid::Uuid as StdUuid;
104	use arrow_buffer::i256;
105	use postcard::to_allocvec;
106
107	use super::*;
108	use crate::value::{
109		blob::Blob,
110		date::Date,
111		datetime::DateTime,
112		decimal::Decimal,
113		dictionary::DictionaryEntryId,
114		digest::Digest,
115		duration::Duration,
116		identity::IdentityId,
117		ordered_f32::OrderedF32,
118		ordered_f64::OrderedF64,
119		time::Time,
120		uuid::{Uuid4, Uuid7},
121	};
122
123	fn digest() -> Digest {
124		let mut digest = Digest::new(ValueType::Float8, 10_000).unwrap();
125		for value in [1.0, 2.5, -4.0] {
126			digest.add_value(&Value::float8(value)).unwrap();
127		}
128		digest
129	}
130
131	fn every_variant() -> Vec<Value> {
132		vec![
133			Value::Boolean(true),
134			Value::Float4(OrderedF32::try_from(1.5f32).unwrap()),
135			Value::Float8(OrderedF64::try_from(-0.0f64).unwrap()),
136			Value::Int1(-8),
137			Value::Int2(-1_600),
138			Value::Int4(-320_000),
139			Value::Int8(-64_000_000_000),
140			Value::Int16(i128::MIN),
141			Value::Utf8("state".to_string()),
142			Value::Uint1(8),
143			Value::Uint2(1_600),
144			Value::Uint4(320_000),
145			Value::Uint8(64_000_000_000),
146			Value::Uint16(u128::MAX),
147			Value::Date(Date::new(2026, 7, 20).unwrap()),
148			Value::DateTime(DateTime::new(2026, 7, 20, 12, 34, 56, 789).unwrap()),
149			Value::Time(Time::new(23, 59, 59, 1).unwrap()),
150			Value::Duration(Duration::new(1, 2, 3).unwrap()),
151			Value::IdentityId(IdentityId(Uuid7(StdUuid::from_bytes([
152				0x01, 0x8F, 0x2A, 0x3B, 0x4C, 0x5D, 0x70, 0x07, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00,
153				0x00, 0x07,
154			])))),
155			Value::Uuid4(Uuid4(StdUuid::from_u128(4))),
156			Value::Uuid7(Uuid7(StdUuid::from_u128(77))),
157			Value::Blob(Blob::new(vec![1, 2, 3])),
158			Value::Decimal(Decimal::from_parts(i256::from_i128(150), 2).unwrap()),
159			Value::Any(Box::new(Value::Boolean(false))),
160			Value::DictionaryId(DictionaryEntryId::U16(u128::MAX)),
161			Value::Type(ValueType::Record(vec![("k".to_string(), ValueType::Int4)])),
162			Value::List(vec![Value::Int4(1), Value::none_of(ValueType::Int4)]),
163			Value::Record(vec![("k".to_string(), Value::Int8(9))]),
164			Value::Tuple(vec![Value::Boolean(true), Value::none()]),
165			Value::Digest(Box::new(digest())),
166		]
167	}
168
169	#[test]
170	fn every_value_variant_comes_back_from_its_row_exactly() {
171		// A lossy row silently changes an Any cell; re-encoding catches -0.0 and 1.50 that == would hide.
172		let cells = every_variant();
173		let array = any_array(&cells);
174		assert!(array.logical_nulls().is_none());
175		for (index, value) in cells.iter().enumerate() {
176			let back = get(&array, index).unwrap();
177			assert_eq!(&back, value);
178			assert_eq!(to_allocvec(&back).unwrap(), to_allocvec(value).unwrap(), "row {index}");
179		}
180	}
181
182	#[test]
183	fn get_value_wraps_rows_unless_the_column_is_a_list_or_a_record() {
184		// List and Record columns hand out the stored value; every other Any column wraps it once.
185		let list = Value::List(vec![Value::Int4(1)]);
186		let array = any_array([list.clone()]);
187		assert_eq!(get_value(&array, None, 0), Value::Any(Box::new(list.clone())));
188		assert_eq!(get_value(&array, Some(&ValueType::Any), 0), Value::Any(Box::new(list.clone())));
189		assert_eq!(get_value(&array, Some(&ValueType::List(Box::new(ValueType::Int4))), 0), list);
190		let record = Value::Record(vec![("a".to_string(), Value::Int4(1))]);
191		let record_type = ValueType::Record(vec![("a".to_string(), ValueType::Int4)]);
192		assert_eq!(get_value(&any_array([record.clone()]), Some(&record_type), 0), record);
193		assert_eq!(get_value(&array, None, 1), Value::none());
194	}
195
196	#[test]
197	fn a_wrapped_row_stays_wrapped() {
198		// Adding or removing a wrap here would change what an Any column holds.
199		let wrapped = Value::Any(Box::new(Value::Int4(3)));
200		let array = any_array([wrapped.clone()]);
201		assert_eq!(get(&array, 0), Some(wrapped.clone()));
202		assert_eq!(get_value(&array, None, 0), Value::Any(Box::new(wrapped)));
203	}
204
205	#[test]
206	fn reorder_with_an_out_of_range_row_fails() {
207		// An out of range row is a bug, so it must fail naming the row and length, never read as a none.
208		let error = reorder(&any_array([Value::Int4(1)]), &[3, 0]).unwrap_err();
209		assert_eq!(error.diagnostic().message, "row index 3 out of range for a column of 1 rows");
210	}
211
212	#[test]
213	fn any_columns_are_equal_by_value_not_by_row_bytes() {
214		// Postcard bytes of 1.5 and 1.50 differ; equality must still compare values.
215		let one_and_a_half = |mantissa: i128, scale: u8| {
216			Value::Decimal(Decimal::from_parts(i256::from_i128(mantissa), scale).unwrap())
217		};
218		assert!(equals(&any_array([one_and_a_half(15, 1)]), &any_array([one_and_a_half(150, 2)])));
219		assert!(!equals(&any_array([Value::Int4(1)]), &any_array([Value::Int4(2)])));
220		assert!(!equals(&any_array([Value::Int4(1)]), &any_array([Value::Int4(1), Value::Int4(1)])));
221	}
222
223	#[test]
224	#[should_panic(expected = "empty Any row")]
225	fn an_empty_any_row_panics_naming_the_type() {
226		// An empty row under a set valid bit is corrupt, so reading one must fail loudly instead of giving a
227		// none.
228		get(&LargeBinaryArray::from_iter_values([b"".as_slice()]), 0);
229	}
230}