///|
/// Represents a single key-value record stored on disk.
pub struct Record {
checksum : Int
timestamp : Int64
expires_at : Int64
key : Bytes
value : Bytes
is_tombstone : Bool
}
///|
/// Creates a new active Record.
pub fn Record::new(key : Bytes, value : Bytes, timestamp : Int64) -> Record {
{ checksum: 0, timestamp, expires_at: 0L, key, value, is_tombstone: false }
}
///|
/// Creates a new active Record with a Time-To-Live (expires at timestamp + ttl).
pub fn Record::new_ttl(
key : Bytes,
value : Bytes,
timestamp : Int64,
ttl : Int64,
) -> Record {
{
checksum: 0,
timestamp,
expires_at: timestamp + ttl,
key,
value,
is_tombstone: false,
}
}
///|
/// Creates a new tombstone (deleted) Record.
pub fn Record::new_tombstone(key : Bytes, timestamp : Int64) -> Record {
{
checksum: 0,
timestamp,
expires_at: 0L,
key,
value: Bytes::make(0, (0).to_byte()),
is_tombstone: true,
}
}
///|
/// Serializes the Record to its on-disk binary representation.
pub fn Record::serialize(self : Record) -> Bytes {
let key_sz = self.key.length()
let val_sz = if self.is_tombstone { -1 } else { self.value.length() }
let val_len = if self.is_tombstone { 0 } else { self.value.length() }
let total_sz = 28 + key_sz + val_len
let arr = Array::make(total_sz, (0).to_byte())
// Write timestamp (offset 4)
set_int64(arr, 4, self.timestamp)
// Write expires_at (offset 12)
set_int64(arr, 12, self.expires_at)
// Write key_sz (offset 20)
set_int32(arr, 20, key_sz)
// Write value_sz (offset 24)
set_int32(arr, 24, val_sz)
// Write key (offset 28)
for i = 0; i < key_sz; i = i + 1 {
arr[28 + i] = self.key[i]
}
// Write value (offset 28 + key_sz)
for i = 0; i < val_len; i = i + 1 {
arr[28 + key_sz + i] = self.value[i]
}
// Compute checksum of the rest of the array (from offset 4 to end)
let checksum = fnv1a(arr, 4, total_sz - 4)
set_int32(arr, 0, checksum)
Bytes::from_array(arr)
}
///| Decodes record metadata from a 28-byte header.
///|
/// Returns (checksum, timestamp, expires_at, key_sz, val_sz).
pub fn decode_header(
header : Bytes,
) -> (Int, Int64, Int64, Int, Int) raise MoonKVError {
if header.length() < 28 {
raise MoonKVError::InvalidRecord(
"Header too short: \{header.length()} bytes",
)
}
let checksum = get_int32(header, 0)
let timestamp = get_int64(header, 4)
let expires_at = get_int64(header, 12)
let key_sz = get_int32(header, 20)
let val_sz = get_int32(header, 24)
(checksum, timestamp, expires_at, key_sz, val_sz)
}
///|
/// Deserializes a complete Record from raw bytes, verifying its checksum.
pub fn deserialize_record(bytes : Bytes) -> Record raise MoonKVError {
let total_sz = bytes.length()
if total_sz < 28 {
raise MoonKVError::InvalidRecord("Record too short: \{total_sz} bytes")
}
let checksum = get_int32(bytes, 0)
let timestamp = get_int64(bytes, 4)
let expires_at = get_int64(bytes, 12)
let key_sz = get_int32(bytes, 20)
let val_sz = get_int32(bytes, 24)
if key_sz < 0 {
raise MoonKVError::InvalidRecord("Negative key size: \{key_sz}")
}
let is_tombstone = val_sz < 0
if val_sz < -1 {
raise MoonKVError::InvalidRecord("Invalid negative value size: \{val_sz}")
}
let val_len = if is_tombstone { 0 } else { val_sz }
if key_sz > total_sz - 28 {
raise MoonKVError::InvalidRecord(
"Key size exceeds record bounds: \{key_sz}",
)
}
if !is_tombstone && val_len > total_sz - 28 - key_sz {
raise MoonKVError::InvalidRecord(
"Value size exceeds record bounds: \{val_len}",
)
}
let expected_sz = 28 + key_sz + val_len
if total_sz != expected_sz {
raise MoonKVError::InvalidRecord(
"Record size mismatch: got \{total_sz}, expected \{expected_sz}",
)
}
// Verify checksum
let computed_checksum = fnv1a_bytes(bytes, 4, expected_sz - 4)
if checksum != computed_checksum {
raise MoonKVError::CorruptedDatabase(
"Checksum mismatch: got \{checksum}, computed \{computed_checksum}",
)
}
// Extract key
let key_arr = Array::make(key_sz, (0).to_byte())
for i = 0; i < key_sz; i = i + 1 {
key_arr[i] = bytes[28 + i]
}
let key = Bytes::from_array(key_arr)
// Extract value
let value = if is_tombstone {
Bytes::make(0, (0).to_byte())
} else {
let val_arr = Array::make(val_len, (0).to_byte())
for i = 0; i < val_len; i = i + 1 {
val_arr[i] = bytes[28 + key_sz + i]
}
Bytes::from_array(val_arr)
}
{ checksum, timestamp, expires_at, key, value, is_tombstone }
}