Skip to main content

reifydb_core/encoded/
uint.rs

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