Skip to main content

reifydb_codec/encoded/
identity.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::ptr;
5
6use reifydb_value::{
7	reifydb_assertions,
8	value::{identity::IdentityId, uuid::Uuid7, value_type::ValueType},
9};
10use uuid::Uuid;
11
12use crate::encoded::{row::EncodedRow, shape::RowShape};
13
14impl RowShape {
15	pub fn set_identity_id(&self, row: &mut EncodedRow, index: usize, value: IdentityId) {
16		let field = &self.fields()[index];
17		reifydb_assertions! {
18			assert!(
19				row.len() >= self.total_static_size(),
20				"row/shape size mismatch: row.len()={} < total_static_size()={}",
21				row.len(),
22				self.total_static_size()
23			);
24			assert_eq!(*field.constraint.get_type().inner_type(), ValueType::IdentityId);
25		}
26		row.set_valid(index, true);
27		unsafe {
28			ptr::write_unaligned(
29				row.make_mut().as_mut_ptr().add(field.offset as usize) as *mut [u8; 16],
30				*value.as_bytes(),
31			);
32		}
33	}
34
35	pub fn get_identity_id(&self, row: &EncodedRow, index: usize) -> IdentityId {
36		let field = &self.fields()[index];
37		reifydb_assertions! {
38			assert!(
39				row.len() >= self.total_static_size(),
40				"row/shape size mismatch: row.len()={} < total_static_size()={}",
41				row.len(),
42				self.total_static_size()
43			);
44			assert_eq!(*field.constraint.get_type().inner_type(), ValueType::IdentityId);
45		}
46		unsafe {
47			let bytes: [u8; 16] =
48				ptr::read_unaligned(row.as_ptr().add(field.offset as usize) as *const [u8; 16]);
49			let uuid = Uuid::from_bytes(bytes);
50			let uuid7 = Uuid7::from(uuid);
51			IdentityId::from(uuid7)
52		}
53	}
54
55	pub fn try_get_identity_id(&self, row: &EncodedRow, index: usize) -> Option<IdentityId> {
56		if row.is_defined(index) && self.fields()[index].constraint.get_type() == ValueType::IdentityId {
57			Some(self.get_identity_id(row, index))
58		} else {
59			None
60		}
61	}
62}
63
64#[cfg(test)]
65pub mod tests {
66	use reifydb_runtime::context::{
67		clock::{Clock, MockClock},
68		rng::Rng,
69	};
70	use reifydb_value::value::{identity::IdentityId, value_type::ValueType};
71
72	use crate::encoded::shape::RowShape;
73
74	fn test_clock_and_rng() -> (MockClock, Clock, Rng) {
75		let mock = MockClock::from_millis(1000);
76		let clock = Clock::Mock(mock.clone());
77		let rng = Rng::seeded(42);
78		(mock, clock, rng)
79	}
80
81	#[test]
82	fn test_set_get_identity_id() {
83		let (_mock, clock, rng) = test_clock_and_rng();
84		let shape = RowShape::testing(&[ValueType::IdentityId]);
85		let mut row = shape.allocate();
86
87		let id = IdentityId::generate(&clock, &rng);
88		shape.set_identity_id(&mut row, 0, id.clone());
89		assert_eq!(shape.get_identity_id(&row, 0), id);
90	}
91
92	#[test]
93	fn test_try_get_identity_id() {
94		let (_mock, clock, rng) = test_clock_and_rng();
95		let shape = RowShape::testing(&[ValueType::IdentityId]);
96		let mut row = shape.allocate();
97
98		assert_eq!(shape.try_get_identity_id(&row, 0), None);
99
100		let id = IdentityId::generate(&clock, &rng);
101		shape.set_identity_id(&mut row, 0, id.clone());
102		assert_eq!(shape.try_get_identity_id(&row, 0), Some(id));
103	}
104
105	#[test]
106	fn test_multiple_generations() {
107		let (mock, clock, rng) = test_clock_and_rng();
108		let shape = RowShape::testing(&[ValueType::IdentityId]);
109
110		// Generate multiple Identity IDs and ensure they're different
111		let mut ids = Vec::new();
112		for _ in 0..10 {
113			let mut row = shape.allocate();
114			let id = IdentityId::generate(&clock, &rng);
115			shape.set_identity_id(&mut row, 0, id.clone());
116			let retrieved = shape.get_identity_id(&row, 0);
117			assert_eq!(retrieved, id);
118			ids.push(id);
119			mock.advance_millis(1);
120		}
121
122		// Ensure all generated Identity IDs are unique
123		for i in 0..ids.len() {
124			for j in (i + 1)..ids.len() {
125				assert_ne!(ids[i], ids[j], "Identity IDs should be unique");
126			}
127		}
128	}
129
130	#[test]
131	fn test_uuid7_properties() {
132		let (_mock, clock, rng) = test_clock_and_rng();
133		let shape = RowShape::testing(&[ValueType::IdentityId]);
134		let mut row = shape.allocate();
135
136		let id = IdentityId::generate(&clock, &rng);
137		shape.set_identity_id(&mut row, 0, id.clone());
138		let retrieved = shape.get_identity_id(&row, 0);
139
140		// Verify it's backed by a version 7 UUID
141		assert_eq!(retrieved.get_version_num(), 7);
142		assert_eq!(id.get_version_num(), 7);
143	}
144
145	#[test]
146	fn test_timestamp_ordering() {
147		let (mock, clock, rng) = test_clock_and_rng();
148		let shape = RowShape::testing(&[ValueType::IdentityId]);
149
150		// Generate Identity IDs in sequence - they should be ordered by
151		// timestamp
152		let mut ids = Vec::new();
153		for _ in 0..5 {
154			let mut row = shape.allocate();
155			let id = IdentityId::generate(&clock, &rng);
156			shape.set_identity_id(&mut row, 0, id.clone());
157			let retrieved = shape.get_identity_id(&row, 0);
158			assert_eq!(retrieved, id);
159			ids.push(id);
160
161			// Advance clock to ensure different timestamps
162			mock.advance_millis(1);
163		}
164
165		// Verify that Identity IDs are ordered (timestamp-based)
166		for i in 1..ids.len() {
167			assert!(ids[i].as_bytes() >= ids[i - 1].as_bytes(), "Identity IDs should be timestamp-ordered");
168		}
169	}
170
171	#[test]
172	fn test_mixed_with_other_types() {
173		let (mock, clock, rng) = test_clock_and_rng();
174		let shape = RowShape::testing(&[
175			ValueType::IdentityId,
176			ValueType::Boolean,
177			ValueType::IdentityId,
178			ValueType::Int4,
179		]);
180		let mut row = shape.allocate();
181
182		let id1 = IdentityId::generate(&clock, &rng);
183		mock.advance_millis(1);
184		let id2 = IdentityId::generate(&clock, &rng);
185
186		shape.set_identity_id(&mut row, 0, id1.clone());
187		shape.set_bool(&mut row, 1, true);
188		shape.set_identity_id(&mut row, 2, id2.clone());
189		shape.set_i32(&mut row, 3, 42);
190
191		assert_eq!(shape.get_identity_id(&row, 0), id1);
192		assert_eq!(shape.get_bool(&row, 1), true);
193		assert_eq!(shape.get_identity_id(&row, 2), id2);
194		assert_eq!(shape.get_i32(&row, 3), 42);
195	}
196
197	#[test]
198	fn test_undefined_handling() {
199		let (_mock, clock, rng) = test_clock_and_rng();
200		let shape = RowShape::testing(&[ValueType::IdentityId, ValueType::IdentityId]);
201		let mut row = shape.allocate();
202
203		let id = IdentityId::generate(&clock, &rng);
204		shape.set_identity_id(&mut row, 0, id.clone());
205
206		assert_eq!(shape.try_get_identity_id(&row, 0), Some(id));
207		assert_eq!(shape.try_get_identity_id(&row, 1), None);
208
209		shape.set_none(&mut row, 0);
210		assert_eq!(shape.try_get_identity_id(&row, 0), None);
211	}
212
213	#[test]
214	fn test_persistence() {
215		let (_mock, clock, rng) = test_clock_and_rng();
216		let shape = RowShape::testing(&[ValueType::IdentityId]);
217		let mut row = shape.allocate();
218
219		let id = IdentityId::generate(&clock, &rng);
220		let id_string = id.to_string();
221
222		shape.set_identity_id(&mut row, 0, id.clone());
223		let retrieved = shape.get_identity_id(&row, 0);
224
225		assert_eq!(retrieved, id);
226		assert_eq!(retrieved.to_string(), id_string);
227		assert_eq!(retrieved.as_bytes(), id.as_bytes());
228	}
229
230	#[test]
231	fn test_clone_consistency() {
232		let (_mock, clock, rng) = test_clock_and_rng();
233		let shape = RowShape::testing(&[ValueType::IdentityId]);
234		let mut row = shape.allocate();
235
236		let original_id = IdentityId::generate(&clock, &rng);
237		shape.set_identity_id(&mut row, 0, original_id.clone());
238
239		let retrieved_id = shape.get_identity_id(&row, 0);
240		assert_eq!(retrieved_id, original_id);
241
242		// Verify that the underlying UUID7 byte representation is
243		// identical
244		assert_eq!(retrieved_id.as_bytes(), original_id.as_bytes());
245	}
246
247	#[test]
248	fn test_multiple_fields() {
249		let (mock, clock, rng) = test_clock_and_rng();
250		let shape = RowShape::testing(&[ValueType::IdentityId, ValueType::IdentityId, ValueType::IdentityId]);
251		let mut row = shape.allocate();
252
253		let id1 = IdentityId::generate(&clock, &rng);
254		mock.advance_millis(1);
255		let id2 = IdentityId::generate(&clock, &rng);
256		mock.advance_millis(1);
257		let id3 = IdentityId::generate(&clock, &rng);
258
259		shape.set_identity_id(&mut row, 0, id1.clone());
260		shape.set_identity_id(&mut row, 1, id2.clone());
261		shape.set_identity_id(&mut row, 2, id3.clone());
262
263		assert_eq!(shape.get_identity_id(&row, 0), id1);
264		assert_eq!(shape.get_identity_id(&row, 1), id2);
265		assert_eq!(shape.get_identity_id(&row, 2), id3);
266
267		// Ensure all Identity IDs are different
268		assert_ne!(id1, id2);
269		assert_ne!(id1, id3);
270		assert_ne!(id2, id3);
271	}
272
273	#[test]
274	fn test_format_consistency() {
275		let (_mock, clock, rng) = test_clock_and_rng();
276		let shape = RowShape::testing(&[ValueType::IdentityId]);
277		let mut row = shape.allocate();
278
279		let id = IdentityId::generate(&clock, &rng);
280		let original_string = id.to_string();
281
282		shape.set_identity_id(&mut row, 0, id.clone());
283		let retrieved = shape.get_identity_id(&row, 0);
284		let retrieved_string = retrieved.to_string();
285
286		assert_eq!(original_string, retrieved_string);
287
288		// Verify UUID string format (8-4-4-4-12) since IdentityId is
289		// based on UUID7
290		assert_eq!(original_string.len(), 36);
291		assert_eq!(original_string.matches('-').count(), 4);
292	}
293
294	#[test]
295	fn test_byte_level_storage() {
296		let (_mock, clock, rng) = test_clock_and_rng();
297		let shape = RowShape::testing(&[ValueType::IdentityId]);
298		let mut row = shape.allocate();
299
300		let id = IdentityId::generate(&clock, &rng);
301		let original_bytes = *id.as_bytes();
302
303		shape.set_identity_id(&mut row, 0, id.clone());
304		let retrieved = shape.get_identity_id(&row, 0);
305		let retrieved_bytes = *retrieved.as_bytes();
306
307		assert_eq!(original_bytes, retrieved_bytes);
308
309		// Verify that it's exactly 16 bytes
310		assert_eq!(original_bytes.len(), 16);
311		assert_eq!(retrieved_bytes.len(), 16);
312	}
313
314	#[test]
315	fn test_time_based_properties() {
316		let (mock, clock, rng) = test_clock_and_rng();
317		let shape = RowShape::testing(&[ValueType::IdentityId]);
318
319		// Generate Identity IDs at different times
320		let id1 = IdentityId::generate(&clock, &rng);
321		mock.advance_millis(2);
322		let id2 = IdentityId::generate(&clock, &rng);
323
324		let mut row1 = shape.allocate();
325		let mut row2 = shape.allocate();
326
327		shape.set_identity_id(&mut row1, 0, id1.clone());
328		shape.set_identity_id(&mut row2, 0, id2.clone());
329
330		let retrieved1 = shape.get_identity_id(&row1, 0);
331		let retrieved2 = shape.get_identity_id(&row2, 0);
332
333		// The second Identity ID should be "greater" due to timestamp
334		// ordering
335		assert!(retrieved2.as_bytes() > retrieved1.as_bytes());
336	}
337
338	#[test]
339	fn test_as_primary_key() {
340		let (_mock, clock, rng) = test_clock_and_rng();
341		let shape = RowShape::testing(&[
342			ValueType::IdentityId, // Primary key
343			ValueType::Utf8,       // Name field
344			ValueType::Int4,       // Age field
345		]);
346		let mut row = shape.allocate();
347
348		// Simulate a database record with Identity ID as primary key
349		let primary_key = IdentityId::generate(&clock, &rng);
350		shape.set_identity_id(&mut row, 0, primary_key.clone());
351		shape.set_utf8(&mut row, 1, "John Doe");
352		shape.set_i32(&mut row, 2, 30);
353
354		assert_eq!(shape.get_identity_id(&row, 0), primary_key);
355		assert_eq!(shape.get_utf8(&row, 1), "John Doe");
356		assert_eq!(shape.get_i32(&row, 2), 30);
357
358		// Verify that the primary key is suitable for ordering/indexing
359		assert_eq!(primary_key.get_version_num(), 7);
360	}
361
362	#[test]
363	fn test_try_get_identity_id_wrong_type() {
364		let shape = RowShape::testing(&[ValueType::Boolean]);
365		let mut row = shape.allocate();
366
367		shape.set_bool(&mut row, 0, true);
368
369		assert_eq!(shape.try_get_identity_id(&row, 0), None);
370	}
371}