Skip to main content

reifydb_core/encoded/
uint.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright (c) 2026 ReifyDB
3
4use std::ptr;
5
6use num_bigint::{BigInt, BigUint};
7use num_traits::ToPrimitive;
8use reifydb_value::value::{uint::Uint, value_type::ValueType};
9
10use crate::encoded::{row::EncodedRow, shape::RowShape};
11
12const MODE_INLINE: u128 = 0x00000000000000000000000000000000;
13const MODE_MASK: u128 = 0x80000000000000000000000000000000;
14
15const INLINE_VALUE_MASK: u128 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
16
17const DYNAMIC_OFFSET_MASK: u128 = 0x0000000000000000FFFFFFFFFFFFFFFF;
18const DYNAMIC_LENGTH_MASK: u128 = 0x7FFFFFFFFFFFFFFF0000000000000000;
19
20impl RowShape {
21	pub fn set_uint(&self, row: &mut EncodedRow, index: usize, value: &Uint) {
22		let field = &self.fields()[index];
23		debug_assert_eq!(*field.constraint.get_type().inner_type(), ValueType::Uint);
24
25		let unsigned_value = value.0.to_biguint().unwrap_or(BigUint::from(0u32));
26
27		if let Some(u128_val) = unsigned_value.to_u128()
28			&& u128_val < (1u128 << 127)
29		{
30			self.remove_dynamic_data(row, index);
31
32			let packed = MODE_INLINE | (u128_val & INLINE_VALUE_MASK);
33			unsafe {
34				ptr::write_unaligned(
35					row.make_mut().as_mut_ptr().add(field.offset as usize) as *mut u128,
36					packed.to_le(),
37				);
38			}
39			row.set_valid(index, true);
40			return;
41		}
42
43		let bytes = unsigned_value.to_bytes_le();
44		self.replace_dynamic_data(row, index, &bytes);
45	}
46
47	pub fn get_uint(&self, row: &EncodedRow, index: usize) -> Uint {
48		let field = &self.fields()[index];
49		debug_assert_eq!(*field.constraint.get_type().inner_type(), ValueType::Uint);
50
51		let packed = unsafe { (row.as_ptr().add(field.offset as usize) as *const u128).read_unaligned() };
52		let packed = u128::from_le(packed);
53
54		let mode = packed & MODE_MASK;
55
56		if mode == MODE_INLINE {
57			let value = packed & INLINE_VALUE_MASK;
58
59			let unsigned = BigUint::from(value);
60			Uint::from(BigInt::from(unsigned))
61		} else {
62			let offset = (packed & DYNAMIC_OFFSET_MASK) as usize;
63			let length = ((packed & DYNAMIC_LENGTH_MASK) >> 64) as usize;
64
65			let dynamic_start = self.dynamic_section_start();
66			let data_bytes = &row.as_slice()[dynamic_start + offset..dynamic_start + offset + length];
67
68			let unsigned = BigUint::from_bytes_le(data_bytes);
69			Uint::from(BigInt::from(unsigned))
70		}
71	}
72
73	pub fn try_get_uint(&self, row: &EncodedRow, index: usize) -> Option<Uint> {
74		if row.is_defined(index) && self.fields()[index].constraint.get_type() == ValueType::Uint {
75			Some(self.get_uint(row, index))
76		} else {
77			None
78		}
79	}
80}
81
82#[cfg(test)]
83pub mod tests {
84	use num_bigint::BigInt;
85	use num_traits::Zero;
86	use reifydb_value::value::{uint::Uint, value_type::ValueType};
87
88	use crate::encoded::shape::RowShape;
89
90	#[test]
91	fn test_u64_inline() {
92		let shape = RowShape::testing(&[ValueType::Uint]);
93		let mut row = shape.allocate();
94
95		// Test simple unsigned value
96		let small = Uint::from(42u64);
97		shape.set_uint(&mut row, 0, &small);
98		assert!(row.is_defined(0));
99
100		let retrieved = shape.get_uint(&row, 0);
101		assert_eq!(retrieved, small);
102
103		// Test larger unsigned value
104		let mut row2 = shape.allocate();
105		let large = Uint::from(999999999999u64);
106		shape.set_uint(&mut row2, 0, &large);
107		assert_eq!(shape.get_uint(&row2, 0), large);
108	}
109
110	#[test]
111	fn test_u128_boundary() {
112		let shape = RowShape::testing(&[ValueType::Uint]);
113		let mut row = shape.allocate();
114
115		// Value that needs u128 storage
116		let large = Uint::from(u64::MAX);
117		shape.set_uint(&mut row, 0, &large);
118		assert!(row.is_defined(0));
119
120		let retrieved = shape.get_uint(&row, 0);
121		assert_eq!(retrieved, large);
122
123		// Test max u128 that fits in 127 bits
124		let mut row2 = shape.allocate();
125		let max_u127 = Uint::from(u128::MAX >> 1); // 127 bits
126		shape.set_uint(&mut row2, 0, &max_u127);
127		assert_eq!(shape.get_uint(&row2, 0), max_u127);
128	}
129
130	#[test]
131	fn test_dynamic_storage() {
132		let shape = RowShape::testing(&[ValueType::Uint]);
133		let mut row = shape.allocate();
134
135		// Create a value that requires dynamic storage (>127 bits)
136		// Using string representation for very large numbers
137		let huge = Uint::from(
138			BigInt::parse_bytes(b"123456789012345678901234567890123456789012345678901234567890", 10)
139				.unwrap(),
140		);
141
142		shape.set_uint(&mut row, 0, &huge);
143		assert!(row.is_defined(0));
144
145		let retrieved = shape.get_uint(&row, 0);
146		assert_eq!(retrieved, huge);
147	}
148
149	#[test]
150	fn test_zero() {
151		let shape = RowShape::testing(&[ValueType::Uint]);
152		let mut row = shape.allocate();
153
154		let zero = Uint::from(0);
155		shape.set_uint(&mut row, 0, &zero);
156		assert!(row.is_defined(0));
157
158		let retrieved = shape.get_uint(&row, 0);
159		assert!(retrieved.is_zero());
160	}
161
162	#[test]
163	fn test_try_get() {
164		let shape = RowShape::testing(&[ValueType::Uint]);
165		let mut row = shape.allocate();
166
167		// Undefined initially
168		assert_eq!(shape.try_get_uint(&row, 0), None);
169
170		// Set value
171		let value = Uint::from(12345u64);
172		shape.set_uint(&mut row, 0, &value);
173		assert_eq!(shape.try_get_uint(&row, 0), Some(value));
174	}
175
176	#[test]
177	fn test_clone_on_write() {
178		let shape = RowShape::testing(&[ValueType::Uint]);
179		let row1 = shape.allocate();
180		let mut row2 = row1.clone();
181
182		let value = Uint::from(999999999999999u64);
183		shape.set_uint(&mut row2, 0, &value);
184
185		assert!(!row1.is_defined(0));
186		assert!(row2.is_defined(0));
187		assert_ne!(row1.as_ptr(), row2.as_ptr());
188		assert_eq!(shape.get_uint(&row2, 0), value);
189	}
190
191	#[test]
192	fn test_multiple_fields() {
193		let shape = RowShape::testing(&[
194			ValueType::Boolean,
195			ValueType::Uint,
196			ValueType::Utf8,
197			ValueType::Uint,
198			ValueType::Int4,
199		]);
200		let mut row = shape.allocate();
201
202		shape.set_bool(&mut row, 0, true);
203
204		let small = Uint::from(100u64);
205		shape.set_uint(&mut row, 1, &small);
206
207		shape.set_utf8(&mut row, 2, "test");
208
209		let large = Uint::from(u128::MAX >> 1);
210		shape.set_uint(&mut row, 3, &large);
211
212		shape.set_i32(&mut row, 4, 42);
213
214		assert_eq!(shape.get_bool(&row, 0), true);
215		assert_eq!(shape.get_uint(&row, 1), small);
216		assert_eq!(shape.get_utf8(&row, 2), "test");
217		assert_eq!(shape.get_uint(&row, 3), large);
218		assert_eq!(shape.get_i32(&row, 4), 42);
219	}
220
221	#[test]
222	fn test_negative_input_handling() {
223		let shape = RowShape::testing(&[ValueType::Uint]);
224
225		// Test how negative values are handled (should be converted to
226		// 0 or error)
227		let mut row1 = shape.allocate();
228		let negative = Uint::from(-42); // This creates a negative BigInt
229		shape.set_uint(&mut row1, 0, &negative);
230
231		// Should store as 0 since Uint can't handle negative values
232		let retrieved = shape.get_uint(&row1, 0);
233		assert_eq!(retrieved, Uint::from(0));
234	}
235
236	#[test]
237	fn test_try_get_uint_wrong_type() {
238		let shape = RowShape::testing(&[ValueType::Boolean]);
239		let mut row = shape.allocate();
240
241		shape.set_bool(&mut row, 0, true);
242
243		assert_eq!(shape.try_get_uint(&row, 0), None);
244	}
245
246	#[test]
247	fn test_update_uint_inline_to_inline() {
248		let shape = RowShape::testing(&[ValueType::Uint]);
249		let mut row = shape.allocate();
250
251		shape.set_uint(&mut row, 0, &Uint::from(42u64));
252		assert_eq!(shape.get_uint(&row, 0), Uint::from(42u64));
253
254		shape.set_uint(&mut row, 0, &Uint::from(999u64));
255		assert_eq!(shape.get_uint(&row, 0), Uint::from(999u64));
256	}
257
258	#[test]
259	fn test_update_uint_inline_to_dynamic() {
260		let shape = RowShape::testing(&[ValueType::Uint]);
261		let mut row = shape.allocate();
262
263		shape.set_uint(&mut row, 0, &Uint::from(42u64));
264
265		let huge = Uint::from(
266			BigInt::parse_bytes(b"999999999999999999999999999999999999999999999999", 10).unwrap(),
267		);
268		shape.set_uint(&mut row, 0, &huge);
269		assert_eq!(shape.get_uint(&row, 0), huge);
270	}
271
272	#[test]
273	fn test_update_uint_dynamic_to_inline() {
274		let shape = RowShape::testing(&[ValueType::Uint]);
275		let mut row = shape.allocate();
276
277		let huge = Uint::from(
278			BigInt::parse_bytes(b"999999999999999999999999999999999999999999999999", 10).unwrap(),
279		);
280		shape.set_uint(&mut row, 0, &huge);
281
282		shape.set_uint(&mut row, 0, &Uint::from(42u64));
283		assert_eq!(shape.get_uint(&row, 0), Uint::from(42u64));
284		assert_eq!(row.len(), shape.total_static_size());
285	}
286
287	#[test]
288	fn test_update_uint_with_other_dynamic_fields() {
289		let shape = RowShape::testing(&[ValueType::Uint, ValueType::Utf8]);
290		let mut row = shape.allocate();
291
292		let huge = Uint::from(
293			BigInt::parse_bytes(b"999999999999999999999999999999999999999999999999", 10).unwrap(),
294		);
295		shape.set_uint(&mut row, 0, &huge);
296		shape.set_utf8(&mut row, 1, "hello");
297
298		// Update uint to inline, verify utf8 still works
299		shape.set_uint(&mut row, 0, &Uint::from(1u64));
300		assert_eq!(shape.get_uint(&row, 0), Uint::from(1u64));
301		assert_eq!(shape.get_utf8(&row, 1), "hello");
302	}
303}