Skip to main content

reifydb_codec/log/
index.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_value::{byte_size::ByteSize, reifydb_assertions, value::datetime::DateTime};
5
6use crate::log::{LogIndex, LogVersion, Position};
7
8pub const HEADER_BYTES: usize = 36;
9
10pub const ENTRY_BYTES: usize = 12;
11
12pub const MAGIC: u32 = u32::from_le_bytes(*b"RIDX");
13
14pub const DEFAULT_INTERVAL: ByteSize = ByteSize::from_kib(4);
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct TimestampRange {
18	pub min: DateTime,
19	pub max: DateTime,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct Header {
24	pub magic: u32,
25	pub base_version: LogVersion,
26	pub base_index: LogIndex,
27	pub timestamps: Option<TimestampRange>,
28}
29
30impl Header {
31	pub fn new(base_version: LogVersion, base_index: LogIndex) -> Self {
32		Self {
33			magic: MAGIC,
34			base_version,
35			base_index,
36			timestamps: None,
37		}
38	}
39
40	pub fn decode(buf: &[u8; HEADER_BYTES]) -> Self {
41		let min = DateTime::from_bits(u64::from_le_bytes(buf[20..28].try_into().unwrap()));
42		let max = DateTime::from_bits(u64::from_le_bytes(buf[28..36].try_into().unwrap()));
43		Self {
44			magic: u32::from_le_bytes(buf[0..4].try_into().unwrap()),
45			base_version: LogVersion::new(u64::from_le_bytes(buf[4..12].try_into().unwrap())),
46			base_index: LogIndex::new(u64::from_le_bytes(buf[12..20].try_into().unwrap())),
47			timestamps: (min <= max).then_some(TimestampRange {
48				min,
49				max,
50			}),
51		}
52	}
53
54	pub fn encode(&self) -> [u8; HEADER_BYTES] {
55		let (min, max) = match self.timestamps {
56			Some(range) => (range.min, range.max),
57			None => (DateTime::MAX, DateTime::EPOCH),
58		};
59		let mut out = [0u8; HEADER_BYTES];
60		out[0..4].copy_from_slice(&self.magic.to_le_bytes());
61		out[4..12].copy_from_slice(&self.base_version.as_u64().to_le_bytes());
62		out[12..20].copy_from_slice(&self.base_index.as_u64().to_le_bytes());
63		out[20..28].copy_from_slice(&min.to_bits().to_le_bytes());
64		out[28..36].copy_from_slice(&max.to_bits().to_le_bytes());
65		out
66	}
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct Entry {
71	pub version: LogVersion,
72	pub index: LogIndex,
73	pub position: Position,
74}
75
76pub fn encode_entry(header: &Header, entry: Entry) -> [u8; ENTRY_BYTES] {
77	reifydb_assertions! {
78		assert!(
79			entry.version >= header.base_version,
80			"version {} is below the base version {} the index was created with, and the delta wraps \
81			 silently into an entry no lookup can follow",
82			entry.version,
83			header.base_version
84		);
85		assert!(
86			entry.version.as_u64() - header.base_version.as_u64() <= u32::MAX as u64,
87			"a delta of {} overflows the four byte delta field (limit={})",
88			entry.version.as_u64() - header.base_version.as_u64(),
89			u32::MAX
90		);
91		assert!(
92			entry.index >= header.base_index,
93			"index {} is below the base index {} the index was created with, and the delta wraps \
94			 silently into an entry no lookup can follow",
95			entry.index,
96			header.base_index
97		);
98		assert!(
99			entry.index.as_u64() - header.base_index.as_u64() <= u32::MAX as u64,
100			"an index delta of {} overflows the four byte delta field (limit={})",
101			entry.index.as_u64() - header.base_index.as_u64(),
102			u32::MAX
103		);
104		assert!(
105			entry.position.as_u64() <= u32::MAX as u64,
106			"a position of {} overflows the four byte position field, so the segment must stay under \
107			 {} bytes",
108			entry.position,
109			u32::MAX
110		);
111	}
112	let mut out = [0u8; ENTRY_BYTES];
113	out[0..4].copy_from_slice(&((entry.version.as_u64() - header.base_version.as_u64()) as u32).to_le_bytes());
114	out[4..8].copy_from_slice(&((entry.index.as_u64() - header.base_index.as_u64()) as u32).to_le_bytes());
115	out[8..12].copy_from_slice(&(entry.position.as_u64() as u32).to_le_bytes());
116	out
117}
118
119pub fn decode_entry(header: &Header, buf: &[u8; ENTRY_BYTES]) -> Entry {
120	Entry {
121		version: LogVersion::new(
122			header.base_version.as_u64() + u32::from_le_bytes(buf[0..4].try_into().unwrap()) as u64,
123		),
124		index: LogIndex::new(
125			header.base_index.as_u64() + u32::from_le_bytes(buf[4..8].try_into().unwrap()) as u64,
126		),
127		position: Position::new(u32::from_le_bytes(buf[8..12].try_into().unwrap()) as u64),
128	}
129}