Skip to main content

reifydb_core/encoded/
int.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::ptr;
5
6use num_bigint::BigInt as StdBigInt;
7use num_traits::ToPrimitive;
8use reifydb_type::value::{int::Int, r#type::Type};
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_int(&self, row: &mut EncodedRow, index: usize, value: &Int) {
22		let field = &self.fields()[index];
23		debug_assert_eq!(*field.constraint.get_type().inner_type(), Type::Int);
24
25		if let Some(i128_val) = value.0.to_i128()
26			&& (-(1i128 << 126)..(1i128 << 126)).contains(&i128_val)
27		{
28			self.remove_dynamic_data(row, index);
29
30			let packed = MODE_INLINE | ((i128_val as u128) & INLINE_VALUE_MASK);
31			unsafe {
32				ptr::write_unaligned(
33					row.make_mut().as_mut_ptr().add(field.offset as usize) as *mut u128,
34					packed.to_le(),
35				);
36			}
37			row.set_valid(index, true);
38			return;
39		}
40
41		let bytes = value.0.to_signed_bytes_le();
42		self.replace_dynamic_data(row, index, &bytes);
43	}
44
45	pub fn get_int(&self, row: &EncodedRow, index: usize) -> Int {
46		let field = &self.fields()[index];
47		debug_assert_eq!(*field.constraint.get_type().inner_type(), Type::Int);
48
49		let packed = unsafe { (row.as_ptr().add(field.offset as usize) as *const u128).read_unaligned() };
50		let packed = u128::from_le(packed);
51
52		let mode = packed & MODE_MASK;
53
54		if mode == MODE_INLINE {
55			let value = (packed & INLINE_VALUE_MASK) as i128;
56			let signed = if value & (1i128 << 126) != 0 {
57				value | (1i128 << 127)
58			} else {
59				value
60			};
61			Int::from(signed)
62		} else {
63			let offset = (packed & DYNAMIC_OFFSET_MASK) as usize;
64			let length = ((packed & DYNAMIC_LENGTH_MASK) >> 64) as usize;
65
66			let dynamic_start = self.dynamic_section_start();
67			let bigint_bytes = &row.as_slice()[dynamic_start + offset..dynamic_start + offset + length];
68
69			Int::from(StdBigInt::from_signed_bytes_le(bigint_bytes))
70		}
71	}
72
73	pub fn try_get_int(&self, row: &EncodedRow, index: usize) -> Option<Int> {
74		if row.is_defined(index) && self.fields()[index].constraint.get_type() == Type::Int {
75			Some(self.get_int(row, index))
76		} else {
77			None
78		}
79	}
80}
81
82#[cfg(test)]
83pub mod tests {
84	use num_bigint::BigInt;
85	use reifydb_type::value::{int::Int, r#type::Type};
86
87	use crate::encoded::shape::RowShape;
88
89	#[test]
90	fn test_i64_inline() {
91		let shape = RowShape::testing(&[Type::Int]);
92		let mut row = shape.allocate();
93
94		// Test small positive value
95		let small = Int::from(42i64);
96		shape.set_int(&mut row, 0, &small);
97		assert!(row.is_defined(0));
98
99		let retrieved = shape.get_int(&row, 0);
100		assert_eq!(retrieved, small);
101
102		// Test small negative value
103		let mut row2 = shape.allocate();
104		let negative = Int::from(-999999i64);
105		shape.set_int(&mut row2, 0, &negative);
106		assert_eq!(shape.get_int(&row2, 0), negative);
107	}
108
109	#[test]
110	fn test_i128_boundary() {
111		let shape = RowShape::testing(&[Type::Int]);
112		let mut row = shape.allocate();
113
114		// Value that doesn't fit in 62 bits but fits in i128
115		let large = Int::from(i64::MAX);
116		shape.set_int(&mut row, 0, &large);
117		assert!(row.is_defined(0));
118
119		let retrieved = shape.get_int(&row, 0);
120		assert_eq!(retrieved, large);
121
122		// Test i128::MAX
123		let mut row2 = shape.allocate();
124		let max_i128 = Int::from(i128::MAX);
125		shape.set_int(&mut row2, 0, &max_i128);
126		assert_eq!(shape.get_int(&row2, 0), max_i128);
127
128		// Test i128::MIN
129		let mut row3 = shape.allocate();
130		let min_i128 = Int::from(i128::MIN);
131		shape.set_int(&mut row3, 0, &min_i128);
132		assert_eq!(shape.get_int(&row3, 0), min_i128);
133	}
134
135	#[test]
136	fn test_dynamic_storage() {
137		let shape = RowShape::testing(&[Type::Int]);
138		let mut row = shape.allocate();
139
140		// Create a value larger than i128 can hold
141		let huge_str = "999999999999999999999999999999999999999999999999";
142		let huge = Int::from(BigInt::parse_bytes(huge_str.as_bytes(), 10).unwrap());
143
144		shape.set_int(&mut row, 0, &huge);
145		assert!(row.is_defined(0));
146
147		let retrieved = shape.get_int(&row, 0);
148		assert_eq!(retrieved, huge);
149		assert_eq!(retrieved.to_string(), huge_str);
150	}
151
152	#[test]
153	fn test_zero() {
154		let shape = RowShape::testing(&[Type::Int]);
155		let mut row = shape.allocate();
156
157		let zero = Int::from(0);
158		shape.set_int(&mut row, 0, &zero);
159		assert!(row.is_defined(0));
160
161		let retrieved = shape.get_int(&row, 0);
162		assert_eq!(retrieved, zero);
163	}
164
165	#[test]
166	fn test_try_get() {
167		let shape = RowShape::testing(&[Type::Int]);
168		let mut row = shape.allocate();
169
170		// Undefined initially
171		assert_eq!(shape.try_get_int(&row, 0), None);
172
173		// Set value
174		let value = Int::from(12345);
175		shape.set_int(&mut row, 0, &value);
176		assert_eq!(shape.try_get_int(&row, 0), Some(value));
177	}
178
179	#[test]
180	fn test_clone_on_write() {
181		let shape = RowShape::testing(&[Type::Int]);
182		let row1 = shape.allocate();
183		let mut row2 = row1.clone();
184
185		let value = Int::from(999999999999999i64);
186		shape.set_int(&mut row2, 0, &value);
187
188		assert!(!row1.is_defined(0));
189		assert!(row2.is_defined(0));
190		assert_ne!(row1.as_ptr(), row2.as_ptr());
191		assert_eq!(shape.get_int(&row2, 0), value);
192	}
193
194	#[test]
195	fn test_multiple_fields() {
196		let shape = RowShape::testing(&[Type::Int4, Type::Int, Type::Utf8, Type::Int]);
197		let mut row = shape.allocate();
198
199		shape.set_i32(&mut row, 0, 42);
200
201		let small = Int::from(100);
202		shape.set_int(&mut row, 1, &small);
203
204		shape.set_utf8(&mut row, 2, "test");
205
206		let large = Int::from(i128::MAX);
207		shape.set_int(&mut row, 3, &large);
208
209		assert_eq!(shape.get_i32(&row, 0), 42);
210		assert_eq!(shape.get_int(&row, 1), small);
211		assert_eq!(shape.get_utf8(&row, 2), "test");
212		assert_eq!(shape.get_int(&row, 3), large);
213	}
214
215	#[test]
216	fn test_negative_values() {
217		let shape = RowShape::testing(&[Type::Int]);
218
219		// Small negative (i64 inline)
220		let mut row1 = shape.allocate();
221		let small_neg = Int::from(-42);
222		shape.set_int(&mut row1, 0, &small_neg);
223		assert_eq!(shape.get_int(&row1, 0), small_neg);
224
225		// Large negative (i128 overflow)
226		let mut row2 = shape.allocate();
227		let large_neg = Int::from(i64::MIN);
228		shape.set_int(&mut row2, 0, &large_neg);
229		assert_eq!(shape.get_int(&row2, 0), large_neg);
230
231		// Huge negative (dynamic)
232		let mut row3 = shape.allocate();
233		let huge_neg_str = "-999999999999999999999999999999999999999999999999";
234		let huge_neg =
235			Int::from(-BigInt::parse_bytes(huge_neg_str.trim_start_matches('-').as_bytes(), 10).unwrap());
236		shape.set_int(&mut row3, 0, &huge_neg);
237		assert_eq!(shape.get_int(&row3, 0), huge_neg);
238	}
239
240	#[test]
241	fn test_try_get_int_wrong_type() {
242		let shape = RowShape::testing(&[Type::Boolean]);
243		let mut row = shape.allocate();
244
245		shape.set_bool(&mut row, 0, true);
246
247		assert_eq!(shape.try_get_int(&row, 0), None);
248	}
249
250	#[test]
251	fn test_update_int_inline_to_inline() {
252		let shape = RowShape::testing(&[Type::Int]);
253		let mut row = shape.allocate();
254
255		shape.set_int(&mut row, 0, &Int::from(42));
256		assert_eq!(shape.get_int(&row, 0), Int::from(42));
257
258		shape.set_int(&mut row, 0, &Int::from(-999));
259		assert_eq!(shape.get_int(&row, 0), Int::from(-999));
260	}
261
262	#[test]
263	fn test_update_int_inline_to_dynamic() {
264		let shape = RowShape::testing(&[Type::Int]);
265		let mut row = shape.allocate();
266
267		shape.set_int(&mut row, 0, &Int::from(42));
268		assert_eq!(shape.get_int(&row, 0), Int::from(42));
269
270		let huge = Int::from(
271			BigInt::parse_bytes(b"999999999999999999999999999999999999999999999999", 10).unwrap(),
272		);
273		shape.set_int(&mut row, 0, &huge);
274		assert_eq!(shape.get_int(&row, 0), huge);
275	}
276
277	#[test]
278	fn test_update_int_dynamic_to_inline() {
279		let shape = RowShape::testing(&[Type::Int]);
280		let mut row = shape.allocate();
281
282		let huge = Int::from(
283			BigInt::parse_bytes(b"999999999999999999999999999999999999999999999999", 10).unwrap(),
284		);
285		shape.set_int(&mut row, 0, &huge);
286		assert_eq!(shape.get_int(&row, 0), huge);
287
288		// Transition back to inline
289		shape.set_int(&mut row, 0, &Int::from(42));
290		assert_eq!(shape.get_int(&row, 0), Int::from(42));
291		// Dynamic data should be cleaned up
292		assert_eq!(row.len(), shape.total_static_size());
293	}
294
295	#[test]
296	fn test_update_int_dynamic_to_dynamic() {
297		let shape = RowShape::testing(&[Type::Int]);
298		let mut row = shape.allocate();
299
300		let huge1 = Int::from(
301			BigInt::parse_bytes(b"999999999999999999999999999999999999999999999999", 10).unwrap(),
302		);
303		shape.set_int(&mut row, 0, &huge1);
304		assert_eq!(shape.get_int(&row, 0), huge1);
305
306		let huge2 = Int::from(
307			-BigInt::parse_bytes(b"111111111111111111111111111111111111111111111111", 10).unwrap(),
308		);
309		shape.set_int(&mut row, 0, &huge2);
310		assert_eq!(shape.get_int(&row, 0), huge2);
311	}
312
313	#[test]
314	fn test_update_int_with_other_dynamic_fields() {
315		let shape = RowShape::testing(&[Type::Int, Type::Utf8, Type::Int]);
316		let mut row = shape.allocate();
317
318		let huge1 = Int::from(
319			BigInt::parse_bytes(b"999999999999999999999999999999999999999999999999", 10).unwrap(),
320		);
321		shape.set_int(&mut row, 0, &huge1);
322		shape.set_utf8(&mut row, 1, "hello");
323		let huge2 = Int::from(
324			BigInt::parse_bytes(b"111111111111111111111111111111111111111111111111", 10).unwrap(),
325		);
326		shape.set_int(&mut row, 2, &huge2);
327
328		// Update first int to inline (removes dynamic data, adjusts other refs)
329		shape.set_int(&mut row, 0, &Int::from(42));
330
331		assert_eq!(shape.get_int(&row, 0), Int::from(42));
332		assert_eq!(shape.get_utf8(&row, 1), "hello");
333		assert_eq!(shape.get_int(&row, 2), huge2);
334	}
335}