Skip to main content

linkrs_storage/edge/node_group/
manifest.rs

1//! Per-table group layout manifest: the existing-group record that drives
2//! sparse checkpoint and load.
3
4use linkrs_core::{StorageError, StorageResult};
5
6use super::validate_group_bits;
7
8/// Per-table group layout shared by both directions.
9///
10/// Records existing group ids rather than contiguous counts:
11/// sparse endpoints materialize only groups holding rows, missing groups
12/// read as empty and never produce files.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct TableShardManifest {
15    pub group_bits: u32,
16    pub out_groups: Vec<u32>,
17    pub in_groups: Vec<u32>,
18}
19
20impl TableShardManifest {
21    pub fn encode(&self) -> Vec<u8> {
22        let mut out_groups = self.out_groups.clone();
23        out_groups.sort_unstable();
24        out_groups.dedup();
25        let mut in_groups = self.in_groups.clone();
26        in_groups.sort_unstable();
27        in_groups.dedup();
28        let mut out = Vec::with_capacity(12 + (out_groups.len() + in_groups.len()) * 4);
29        out.extend_from_slice(&self.group_bits.to_le_bytes());
30        out.extend_from_slice(&(out_groups.len() as u32).to_le_bytes());
31        for gid in &out_groups {
32            out.extend_from_slice(&gid.to_le_bytes());
33        }
34        out.extend_from_slice(&(in_groups.len() as u32).to_le_bytes());
35        for gid in &in_groups {
36            out.extend_from_slice(&gid.to_le_bytes());
37        }
38        out
39    }
40
41    pub fn decode(data: &[u8]) -> StorageResult<Self> {
42        let bad_slice = || StorageError::deserialize_error("group manifest slice too short");
43        if data.len() < 8 {
44            return Err(StorageError::deserialize_error(format!(
45                "group manifest too short: got {}",
46                data.len()
47            )));
48        }
49        let group_bits = u32::from_le_bytes(data[0..4].try_into().map_err(|_| bad_slice())?);
50        validate_group_bits(group_bits)?;
51        let mut cursor = 4usize;
52        let take_u32 = |data: &[u8], cursor: &mut usize| -> StorageResult<u32> {
53            if data.len() - *cursor < 4 {
54                return Err(StorageError::deserialize_error(
55                    "group manifest slice too short",
56                ));
57            }
58            let value = u32::from_le_bytes(
59                data[*cursor..*cursor + 4]
60                    .try_into()
61                    .map_err(|_| bad_slice())?,
62            );
63            *cursor += 4;
64            Ok(value)
65        };
66        let out_len = take_u32(data, &mut cursor)? as usize;
67        if data.len() - cursor < out_len * 4 + 4 {
68            return Err(StorageError::deserialize_error(format!(
69                "group manifest too short for {} out groups",
70                out_len
71            )));
72        }
73        let mut out_groups = Vec::with_capacity(out_len);
74        for _ in 0..out_len {
75            out_groups.push(take_u32(data, &mut cursor)?);
76        }
77        let in_len = take_u32(data, &mut cursor)? as usize;
78        if data.len() - cursor != in_len * 4 {
79            return Err(StorageError::deserialize_error(format!(
80                "group manifest trailing bytes: expected {} in groups, {} bytes remain",
81                in_len,
82                data.len() - cursor
83            )));
84        }
85        let mut in_groups = Vec::with_capacity(in_len);
86        for _ in 0..in_len {
87            in_groups.push(take_u32(data, &mut cursor)?);
88        }
89        out_groups.sort_unstable();
90        out_groups.dedup();
91        in_groups.sort_unstable();
92        in_groups.dedup();
93        Ok(Self {
94            group_bits,
95            out_groups,
96            in_groups,
97        })
98    }
99}