reifydb-codec 0.9.0

Unified encoding and decoding for all ReifyDB values, frames, rows, keys, and boundaries
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2026 ReifyDB

use reifydb_codec::row::shape::{RowFamily, RowShape};
use reifydb_runtime::context::{
	clock::{Clock, MockClock},
	rng::Rng,
};
use reifydb_value::value::{identity::IdentityId, value_type::ValueType};

fn test_clock_and_rng() -> (MockClock, Clock, Rng) {
	let mock = MockClock::from_millis(1000);
	let clock = Clock::Mock(mock.clone());
	let rng = Rng::seeded(42);
	(mock, clock, rng)
}

#[test]
fn test_set_get_identity_id() {
	let (_mock, clock, rng) = test_clock_and_rng();
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::IdentityId]);
	let mut row = shape.allocate_pod();

	let id = IdentityId::generate(&clock, &rng);
	shape.set::<IdentityId>(&mut row, 0, id.clone());
	assert_eq!(shape.get::<IdentityId>(&row, 0), id);
}

#[test]
fn test_try_get_identity_id() {
	let (_mock, clock, rng) = test_clock_and_rng();
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::IdentityId]);
	let mut row = shape.allocate_pod();

	assert_eq!(shape.try_get::<IdentityId>(&row, 0), None);

	let id = IdentityId::generate(&clock, &rng);
	shape.set::<IdentityId>(&mut row, 0, id.clone());
	assert_eq!(shape.try_get::<IdentityId>(&row, 0), Some(id));
}

#[test]
fn test_multiple_generations() {
	let (mock, clock, rng) = test_clock_and_rng();
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::IdentityId]);

	let mut ids = Vec::new();
	for _ in 0..10 {
		let mut row = shape.allocate_pod();
		let id = IdentityId::generate(&clock, &rng);
		shape.set::<IdentityId>(&mut row, 0, id.clone());
		let retrieved = shape.get::<IdentityId>(&row, 0);
		assert_eq!(retrieved, id);
		ids.push(id);
		mock.advance_millis(1);
	}

	for i in 0..ids.len() {
		for j in (i + 1)..ids.len() {
			assert_ne!(ids[i], ids[j], "Identity IDs should be unique");
		}
	}
}

#[test]
fn test_uuid7_properties() {
	let (_mock, clock, rng) = test_clock_and_rng();
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::IdentityId]);
	let mut row = shape.allocate_pod();

	let id = IdentityId::generate(&clock, &rng);
	shape.set::<IdentityId>(&mut row, 0, id.clone());
	let retrieved = shape.get::<IdentityId>(&row, 0);

	// The version nibble must survive the row slot, since ordering depends on UUID7 layout.
	assert_eq!(retrieved.get_version_num(), 7);
	assert_eq!(id.get_version_num(), 7);
}

#[test]
fn test_timestamp_ordering() {
	let (mock, clock, rng) = test_clock_and_rng();
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::IdentityId]);

	// UUID7 puts the timestamp in the leading bytes, so byte order has to match generation
	// order for these ids to be usable as sortable keys.
	let mut ids = Vec::new();
	for _ in 0..5 {
		let mut row = shape.allocate_pod();
		let id = IdentityId::generate(&clock, &rng);
		shape.set::<IdentityId>(&mut row, 0, id.clone());
		let retrieved = shape.get::<IdentityId>(&row, 0);
		assert_eq!(retrieved, id);
		ids.push(id);

		mock.advance_millis(1);
	}

	for i in 1..ids.len() {
		assert!(ids[i].as_bytes() >= ids[i - 1].as_bytes(), "Identity IDs should be timestamp-ordered");
	}
}

#[test]
fn test_mixed_with_other_types() {
	let (mock, clock, rng) = test_clock_and_rng();
	let shape = RowShape::testing(
		RowFamily::Pod,
		&[ValueType::IdentityId, ValueType::Boolean, ValueType::IdentityId, ValueType::Int4],
	);
	let mut row = shape.allocate_pod();

	let id1 = IdentityId::generate(&clock, &rng);
	mock.advance_millis(1);
	let id2 = IdentityId::generate(&clock, &rng);

	shape.set::<IdentityId>(&mut row, 0, id1.clone());
	shape.set::<bool>(&mut row, 1, true);
	shape.set::<IdentityId>(&mut row, 2, id2.clone());
	shape.set::<i32>(&mut row, 3, 42i32);

	assert_eq!(shape.get::<IdentityId>(&row, 0), id1);
	assert_eq!(shape.get::<bool>(&row, 1), true);
	assert_eq!(shape.get::<IdentityId>(&row, 2), id2);
	assert_eq!(shape.get::<i32>(&row, 3), 42);
}

#[test]
fn test_undefined_handling() {
	let (_mock, clock, rng) = test_clock_and_rng();
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::IdentityId, ValueType::IdentityId]);
	let mut row = shape.allocate_pod();

	let id = IdentityId::generate(&clock, &rng);
	shape.set::<IdentityId>(&mut row, 0, id.clone());

	assert_eq!(shape.try_get::<IdentityId>(&row, 0), Some(id));
	assert_eq!(shape.try_get::<IdentityId>(&row, 1), None);

	shape.set_none(&mut row, 0);
	assert_eq!(shape.try_get::<IdentityId>(&row, 0), None);
}

#[test]
fn test_persistence() {
	let (_mock, clock, rng) = test_clock_and_rng();
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::IdentityId]);
	let mut row = shape.allocate_pod();

	let id = IdentityId::generate(&clock, &rng);
	let id_string = id.to_string();

	shape.set::<IdentityId>(&mut row, 0, id.clone());
	let retrieved = shape.get::<IdentityId>(&row, 0);

	assert_eq!(retrieved, id);
	assert_eq!(retrieved.to_string(), id_string);
	assert_eq!(retrieved.as_bytes(), id.as_bytes());
}

#[test]
fn test_clone_consistency() {
	let (_mock, clock, rng) = test_clock_and_rng();
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::IdentityId]);
	let mut row = shape.allocate_pod();

	let original_id = IdentityId::generate(&clock, &rng);
	shape.set::<IdentityId>(&mut row, 0, original_id.clone());

	let retrieved_id = shape.get::<IdentityId>(&row, 0);
	assert_eq!(retrieved_id, original_id);

	assert_eq!(retrieved_id.as_bytes(), original_id.as_bytes());
}

#[test]
fn test_multiple_fields() {
	let (mock, clock, rng) = test_clock_and_rng();
	let shape = RowShape::testing(
		RowFamily::Pod,
		&[ValueType::IdentityId, ValueType::IdentityId, ValueType::IdentityId],
	);
	let mut row = shape.allocate_pod();

	let id1 = IdentityId::generate(&clock, &rng);
	mock.advance_millis(1);
	let id2 = IdentityId::generate(&clock, &rng);
	mock.advance_millis(1);
	let id3 = IdentityId::generate(&clock, &rng);

	shape.set::<IdentityId>(&mut row, 0, id1.clone());
	shape.set::<IdentityId>(&mut row, 1, id2.clone());
	shape.set::<IdentityId>(&mut row, 2, id3.clone());

	assert_eq!(shape.get::<IdentityId>(&row, 0), id1);
	assert_eq!(shape.get::<IdentityId>(&row, 1), id2);
	assert_eq!(shape.get::<IdentityId>(&row, 2), id3);

	assert_ne!(id1, id2);
	assert_ne!(id1, id3);
	assert_ne!(id2, id3);
}

#[test]
fn test_format_consistency() {
	let (_mock, clock, rng) = test_clock_and_rng();
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::IdentityId]);
	let mut row = shape.allocate_pod();

	let id = IdentityId::generate(&clock, &rng);
	let original_string = id.to_string();

	shape.set::<IdentityId>(&mut row, 0, id.clone());
	let retrieved = shape.get::<IdentityId>(&row, 0);
	let retrieved_string = retrieved.to_string();

	assert_eq!(original_string, retrieved_string);

	// 36 chars with 4 hyphens is the 8-4-4-4-12 UUID rendering.
	assert_eq!(original_string.len(), 36);
	assert_eq!(original_string.matches('-').count(), 4);
}

#[test]
fn test_byte_level_storage() {
	let (_mock, clock, rng) = test_clock_and_rng();
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::IdentityId]);
	let mut row = shape.allocate_pod();

	let id = IdentityId::generate(&clock, &rng);
	let original_bytes = *id.as_bytes();

	shape.set::<IdentityId>(&mut row, 0, id.clone());
	let retrieved = shape.get::<IdentityId>(&row, 0);
	let retrieved_bytes = *retrieved.as_bytes();

	assert_eq!(original_bytes, retrieved_bytes);

	assert_eq!(original_bytes.len(), 16);
	assert_eq!(retrieved_bytes.len(), 16);
}

#[test]
fn test_time_based_properties() {
	let (mock, clock, rng) = test_clock_and_rng();
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::IdentityId]);

	let id1 = IdentityId::generate(&clock, &rng);
	mock.advance_millis(2);
	let id2 = IdentityId::generate(&clock, &rng);

	let mut row1 = shape.allocate_pod();
	let mut row2 = shape.allocate_pod();

	shape.set::<IdentityId>(&mut row1, 0, id1.clone());
	shape.set::<IdentityId>(&mut row2, 0, id2.clone());

	let retrieved1 = shape.get::<IdentityId>(&row1, 0);
	let retrieved2 = shape.get::<IdentityId>(&row2, 0);

	// The later id must compare greater by raw bytes, which is what makes it index-ordered.
	assert!(retrieved2.as_bytes() > retrieved1.as_bytes());
}

#[test]
fn test_as_primary_key() {
	let (_mock, clock, rng) = test_clock_and_rng();
	let shape = RowShape::testing(
		RowFamily::Pod,
		&[
			ValueType::IdentityId, // Primary key
			ValueType::Utf8,       // Name field
			ValueType::Int4,       // Age field
		],
	);
	let mut row = shape.allocate_pod();

	let primary_key = IdentityId::generate(&clock, &rng);
	shape.set::<IdentityId>(&mut row, 0, primary_key.clone());
	shape.set_utf8(&mut row, 1, "John Doe");
	shape.set::<i32>(&mut row, 2, 30i32);

	assert_eq!(shape.get::<IdentityId>(&row, 0), primary_key);
	assert_eq!(shape.get_utf8(&row, 1), "John Doe");
	assert_eq!(shape.get::<i32>(&row, 2), 30);

	assert_eq!(primary_key.get_version_num(), 7);
}

#[test]
fn test_try_get_identity_id_wrong_type() {
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::Boolean]);
	let mut row = shape.allocate_pod();

	shape.set::<bool>(&mut row, 0, true);

	assert_eq!(shape.try_get::<IdentityId>(&row, 0), None);
}