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::value::{decode_value as decode_value_codec, encode_value as encode_value_codec};

pub fn encode_value(value: &Value) -> Vec<u8> {
	encode_value_codec(value).unwrap()
}

pub fn decode_value(bytes: &[u8]) -> Value {
	decode_value_codec(bytes).unwrap()
}

use std::f64::consts::E;

use reifydb_codec::row::shape::{RowFamily, RowShape};
use reifydb_runtime::context::{
	clock::{Clock, MockClock},
	rng::Rng,
};
use reifydb_value::value::{
	Value,
	blob::Blob,
	date::Date,
	datetime::DateTime,
	ordered_f32::OrderedF32,
	ordered_f64::OrderedF64,
	time::Time,
	uuid::{Uuid4, Uuid7},
	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_any_boolean() {
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::Any]);
	let mut row = shape.allocate_pod();
	shape.set_any(&mut row, 0, &Value::Boolean(true));
	assert_eq!(shape.get_any(&row, 0), Value::Boolean(true));
}

#[test]
fn test_any_integers() {
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::Any]);

	let cases: &[Value] = &[
		Value::Int1(-42),
		Value::Int2(-1000),
		Value::Int4(-100000),
		Value::Int8(i64::MIN),
		Value::Int16(i128::MAX),
		Value::Uint1(255),
		Value::Uint2(65535),
		Value::Uint4(u32::MAX),
		Value::Uint8(u64::MAX),
		Value::Uint16(u128::MAX),
	];

	for case in cases {
		let mut row = shape.allocate_pod();
		shape.set_any(&mut row, 0, case);
		assert_eq!(&shape.get_any(&row, 0), case);
	}
}

#[test]
fn test_any_floats() {
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::Any]);

	let f4 = Value::Float4(OrderedF32::try_from(3.14f32).unwrap());
	let mut row = shape.allocate_pod();
	shape.set_any(&mut row, 0, &f4);
	assert_eq!(shape.get_any(&row, 0), f4);

	let f8 = Value::Float8(OrderedF64::try_from(E).unwrap());
	let mut row2 = shape.allocate_pod();
	shape.set_any(&mut row2, 0, &f8);
	assert_eq!(shape.get_any(&row2, 0), f8);
}

#[test]
fn test_any_temporal() {
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::Any]);

	let date = Value::Date(Date::new(2025, 7, 4).unwrap());
	let mut row = shape.allocate_pod();
	shape.set_any(&mut row, 0, &date);
	assert_eq!(shape.get_any(&row, 0), date);

	let dt = Value::DateTime(DateTime::new(2025, 1, 1, 12, 0, 0, 0).unwrap());
	let mut row2 = shape.allocate_pod();
	shape.set_any(&mut row2, 0, &dt);
	assert_eq!(shape.get_any(&row2, 0), dt);

	let t = Value::Time(Time::new(14, 30, 45, 123456789).unwrap());
	let mut row3 = shape.allocate_pod();
	shape.set_any(&mut row3, 0, &t);
	assert_eq!(shape.get_any(&row3, 0), t);

	let dur = Value::duration_seconds(3600);
	let mut row4 = shape.allocate_pod();
	shape.set_any(&mut row4, 0, &dur);
	assert_eq!(shape.get_any(&row4, 0), dur);
}

#[test]
fn test_any_uuid() {
	let (_, clock, rng) = test_clock_and_rng();
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::Any]);

	let u4 = Value::Uuid4(Uuid4::generate());
	let mut row = shape.allocate_pod();
	shape.set_any(&mut row, 0, &u4);
	assert_eq!(shape.get_any(&row, 0), u4);

	let u7 = Value::Uuid7(Uuid7::generate(&clock, &rng));
	let mut row2 = shape.allocate_pod();
	shape.set_any(&mut row2, 0, &u7);
	assert_eq!(shape.get_any(&row2, 0), u7);
}

#[test]
fn test_any_utf8() {
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::Any]);
	let v = Value::Utf8("hello, world!".to_string());
	let mut row = shape.allocate_pod();
	shape.set_any(&mut row, 0, &v);
	assert_eq!(shape.get_any(&row, 0), v);
}

#[test]
fn test_any_blob() {
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::Any]);
	let v = Value::Blob(Blob::from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]));
	let mut row = shape.allocate_pod();
	shape.set_any(&mut row, 0, &v);
	assert_eq!(shape.get_any(&row, 0), v);
}

#[test]
fn test_any_none_via_set_value() {
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::Any]);
	let mut row = shape.allocate_pod();
	shape.set_value(&mut row, 0, &Value::none());
	assert!(!row.is_defined(0));
	assert_eq!(shape.get_value(&row, 0), Value::none());
}

#[test]
fn test_any_roundtrip_via_set_get_value() {
	let shape = RowShape::testing(RowFamily::Pod, &[ValueType::Any]);

	let cases: &[Value] =
		&[Value::Boolean(false), Value::Int4(42), Value::Utf8("test".to_string()), Value::Uint8(1234567890)];

	for inner in cases {
		let wrapped = Value::any(inner.clone());
		let mut row = shape.allocate_pod();
		shape.set_value(&mut row, 0, &wrapped);
		let retrieved = shape.get_value(&row, 0);
		assert_eq!(retrieved, wrapped, "roundtrip failed for {:?}", inner);
	}
}

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

	shape.set_any(&mut row, 0, &Value::Utf8("first".to_string()));
	shape.set::<i32>(&mut row, 1, 99i32);
	shape.set_any(&mut row, 2, &Value::Boolean(true));

	assert_eq!(shape.get_any(&row, 0), Value::Utf8("first".to_string()));
	assert_eq!(shape.get::<i32>(&row, 1), 99);
	assert_eq!(shape.get_any(&row, 2), Value::Boolean(true));
}

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

	shape.set_any(&mut row, 0, &Value::Int4(42));
	assert_eq!(shape.get_any(&row, 0), Value::Int4(42));

	// An Any field is dynamic, so overwriting it with a different type must resize the
	// stored slice rather than reinterpret the old bytes.
	shape.set_any(&mut row, 0, &Value::Utf8("hello".to_string()));
	assert_eq!(shape.get_any(&row, 0), Value::Utf8("hello".to_string()));

	shape.set_any(&mut row, 0, &Value::Boolean(true));
	assert_eq!(shape.get_any(&row, 0), Value::Boolean(true));
}

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

	shape.set_any(&mut row, 0, &Value::Int4(1));
	shape.set_utf8(&mut row, 1, "middle");
	shape.set_any(&mut row, 2, &Value::Boolean(false));

	// Growing the first dynamic field must shift the later ones' offsets, not corrupt them.
	shape.set_any(&mut row, 0, &Value::Utf8("a long string value".to_string()));

	assert_eq!(shape.get_any(&row, 0), Value::Utf8("a long string value".to_string()));
	assert_eq!(shape.get_utf8(&row, 1), "middle");
	assert_eq!(shape.get_any(&row, 2), Value::Boolean(false));
}

#[test]
fn test_encode_decode_none_various_inner_types() {
	// A none is a stored value carrying its inner type, so the Any tag must round-trip that
	// type as well as the absence itself.
	let cases: &[ValueType] = &[
		ValueType::Boolean,
		ValueType::Int4,
		ValueType::Uint8,
		ValueType::Utf8,
		ValueType::Blob,
		ValueType::Date,
		ValueType::DateTime,
		ValueType::Time,
		ValueType::Duration,
		ValueType::Uuid4,
	];

	for ty in cases {
		let encoded = encode_value(&Value::none_of(ty.clone()));
		match decode_value(&encoded) {
			Value::None {
				inner,
			} => assert_eq!(&inner, ty, "inner type lost for {ty}"),
			other => panic!("expected Value::None for {ty}, got {other:?}"),
		}
	}
}

#[test]
fn test_encode_decode_none_nested_option_duration() {
	// The inner type must carry the full nesting, not collapse to the base scalar.
	let inner_ty = ValueType::Option(Box::new(ValueType::Duration));

	let encoded = encode_value(&Value::none_of(inner_ty.clone()));
	match decode_value(&encoded) {
		Value::None {
			inner,
		} => assert_eq!(inner, inner_ty),
		other => panic!("expected Value::None, got {other:?}"),
	}
}

#[test]
fn test_encode_decode_none_triple_nested_option() {
	// Depth 3 is MAX_OPTION_DEPTH, the deepest nesting the two depth bits of the type tag
	// can hold before it has to fall back to the extended tag.
	let inner_ty = ValueType::Option(Box::new(ValueType::Option(Box::new(ValueType::Option(Box::new(
		ValueType::Duration,
	))))));

	let encoded = encode_value(&Value::none_of(inner_ty.clone()));
	match decode_value(&encoded) {
		Value::None {
			inner,
		} => assert_eq!(inner, inner_ty),
		other => panic!("expected Value::None, got {other:?}"),
	}
}

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

	shape.set_any(&mut row, 0, &Value::none_of(ValueType::Duration));

	assert!(row.is_defined(0), "an Any field holding a None-of-Duration is a stored value, not an unset field");
	match shape.get_any(&row, 0) {
		Value::None {
			inner,
		} => assert_eq!(inner, ValueType::Duration),
		other => panic!("expected Value::None, got {other:?}"),
	}
}

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

	let inner_ty = ValueType::Option(Box::new(ValueType::Duration));
	shape.set_any(&mut row, 0, &Value::none_of(inner_ty.clone()));

	match shape.get_any(&row, 0) {
		Value::None {
			inner,
		} => assert_eq!(inner, inner_ty),
		other => panic!("expected Value::None, got {other:?}"),
	}
}