Skip to main content

reifydb_value/value/container/
wide_int_array.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{fmt::Display, slice::ChunksExact};
5
6use arrow_array::{Array, FixedSizeBinaryArray};
7use arrow_buffer::MutableBuffer;
8
9use crate::value::{Value, container::fixed_array, to_value::ToValue, value_type::ValueType};
10
11pub trait WideInt: Copy + Default + ToValue {
12	const WIDTH: usize;
13	const VALUE_TYPE: ValueType;
14
15	fn to_ordered(self, row: &mut [u8]);
16
17	fn from_ordered(row: &[u8]) -> Self;
18}
19
20impl WideInt for i128 {
21	const WIDTH: usize = 16;
22	const VALUE_TYPE: ValueType = ValueType::Int16;
23
24	fn to_ordered(self, row: &mut [u8]) {
25		row.copy_from_slice(&self.to_be_bytes());
26		row[0] ^= 0x80;
27	}
28
29	fn from_ordered(row: &[u8]) -> Self {
30		let mut bytes = [0u8; Self::WIDTH];
31		bytes.copy_from_slice(row);
32		bytes[0] ^= 0x80;
33		i128::from_be_bytes(bytes)
34	}
35}
36
37impl WideInt for u128 {
38	const WIDTH: usize = 16;
39	const VALUE_TYPE: ValueType = ValueType::Uint16;
40
41	fn to_ordered(self, row: &mut [u8]) {
42		row.copy_from_slice(&self.to_be_bytes());
43	}
44
45	fn from_ordered(row: &[u8]) -> Self {
46		let mut bytes = [0u8; Self::WIDTH];
47		bytes.copy_from_slice(row);
48		u128::from_be_bytes(bytes)
49	}
50}
51
52fn rows<T: WideInt>(array: &FixedSizeBinaryArray) -> ChunksExact<'_, u8> {
53	assert_eq!(
54		array.value_length() as usize,
55		T::WIDTH,
56		"wide int column must hold {} byte values, found {}",
57		T::WIDTH,
58		array.value_length()
59	);
60	array.value_data()[..array.len() * T::WIDTH].chunks_exact(T::WIDTH)
61}
62
63pub fn wide_array<T: WideInt>(values: impl IntoIterator<Item = T>) -> FixedSizeBinaryArray {
64	let values = values.into_iter();
65	let mut buffer = MutableBuffer::with_capacity(values.size_hint().0 * T::WIDTH);
66	for value in values {
67		push_wide(&mut buffer, value);
68	}
69	fixed_array::from_buffer(T::WIDTH, buffer)
70}
71
72pub fn push_wide<T: WideInt>(buffer: &mut MutableBuffer, value: T) {
73	let start = buffer.len();
74	buffer.extend_zeros(T::WIDTH);
75	value.to_ordered(&mut buffer.as_slice_mut()[start..]);
76}
77
78pub fn wide_at<T: WideInt>(array: &FixedSizeBinaryArray, index: usize) -> Option<T> {
79	rows::<T>(array).nth(index).map(T::from_ordered)
80}
81
82pub fn wides<T: WideInt>(array: &FixedSizeBinaryArray) -> Vec<T> {
83	rows::<T>(array).map(T::from_ordered).collect()
84}
85
86pub fn get_value<T: WideInt>(array: &FixedSizeBinaryArray, index: usize) -> Value {
87	match wide_at::<T>(array, index) {
88		Some(value) => value.to_value(),
89		None => Value::none_of(T::VALUE_TYPE),
90	}
91}
92
93pub fn as_string<T: WideInt + Display>(array: &FixedSizeBinaryArray, index: usize) -> String {
94	match wide_at::<T>(array, index) {
95		Some(value) => value.to_string(),
96		None => "none".to_string(),
97	}
98}
99
100#[cfg(test)]
101mod tests {
102	use super::*;
103
104	const INT16_EXTREMES: [i128; 5] = [i128::MIN, -1, 0, 1, i128::MAX];
105
106	const UINT16_BOUNDARIES: [u128; 6] = [0, u64::MAX as u128, 1 << 64, (1 << 127) - 1, 1 << 127, u128::MAX];
107
108	#[test]
109	fn int16_round_trips_the_i128_extremes() {
110		// A narrowing or a missed sign flip would clip or mirror the values only a 128 bit column can hold.
111		let array = wide_array(INT16_EXTREMES);
112		assert_eq!(array.value_length() as usize, 16);
113		assert_eq!(wides::<i128>(&array), INT16_EXTREMES);
114		for (i, value) in INT16_EXTREMES.iter().enumerate() {
115			assert_eq!(wide_at::<i128>(&array, i), Some(*value));
116			assert_eq!(get_value::<i128>(&array, i), Value::Int16(*value));
117		}
118	}
119
120	#[test]
121	fn uint16_round_trips_every_width_boundary() {
122		// Values at and above 2^127 must never pick up a sign, which a signed decode of the top bit would add.
123		let array = wide_array(UINT16_BOUNDARIES);
124		assert_eq!(wides::<u128>(&array), UINT16_BOUNDARIES);
125		for (i, value) in UINT16_BOUNDARIES.iter().enumerate() {
126			assert_eq!(wide_at::<u128>(&array, i), Some(*value));
127			assert_eq!(get_value::<u128>(&array, i), Value::Uint16(*value));
128		}
129	}
130
131	#[test]
132	fn u128_at_reads_u128_max_and_nothing_past_the_end() {
133		// Reading the top bit as a sign would turn u128::MAX into a negative number or a none.
134		let array = wide_array([u128::MAX]);
135		assert_eq!(wide_at::<u128>(&array, 0), Some(u128::MAX));
136		assert_eq!(as_string::<u128>(&array, 0), u128::MAX.to_string());
137		assert_eq!(wide_at::<u128>(&array, 1), None);
138		assert_eq!(get_value::<u128>(&array, 1), Value::none_of(ValueType::Uint16));
139		assert_eq!(as_string::<u128>(&array, 1), "none");
140		let array = wide_array([i128::MIN]);
141		assert_eq!(as_string::<i128>(&array, 0), i128::MIN.to_string());
142		assert_eq!(get_value::<i128>(&array, 1), Value::none_of(ValueType::Int16));
143	}
144
145	#[test]
146	fn row_bytes_sort_like_the_numbers() {
147		// Arrow compares these rows byte by byte, so any other order sorts and compares wide ints wrongly.
148		let signed = wide_array(INT16_EXTREMES);
149		for i in 1..INT16_EXTREMES.len() {
150			assert!(
151				signed.value(i - 1) < signed.value(i),
152				"{} must sort below {}",
153				INT16_EXTREMES[i - 1],
154				INT16_EXTREMES[i]
155			);
156		}
157		let unsigned = wide_array(UINT16_BOUNDARIES);
158		for i in 1..UINT16_BOUNDARIES.len() {
159			assert!(
160				unsigned.value(i - 1) < unsigned.value(i),
161				"{} must sort below {}",
162				UINT16_BOUNDARIES[i - 1],
163				UINT16_BOUNDARIES[i]
164			);
165		}
166	}
167}