Skip to main content

nodedb_lite/storage/pagedb_storage/segment_ext/
array.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! `ArraySegmentExt` over pagedb segments.
4
5use async_trait::async_trait;
6use pagedb::PagedbError;
7use pagedb::vfs::Vfs;
8
9use crate::error::LiteError;
10use crate::storage::pagedb_storage::types::PagedbStorage;
11
12#[async_trait]
13impl<V: Vfs + Clone + Send + Sync + 'static> crate::storage::array_segment_ext::ArraySegmentExt
14    for PagedbStorage<V>
15where
16    <V as Vfs>::LockHandle: Sync,
17    <V as Vfs>::File: Sync,
18{
19    async fn write_array_segment(
20        &self,
21        array_name: &str,
22        seg_id: u64,
23        bytes: &[u8],
24    ) -> Result<(), LiteError> {
25        use pagedb::{RealmId, SegmentKind};
26
27        // Prepend an 8-byte little-endian length so reads can recover the
28        // exact byte count after pagedb pads the last page to a full page size.
29        let byte_len = bytes.len() as u64;
30        let mut payload = Vec::with_capacity(8 + bytes.len());
31        payload.extend_from_slice(&byte_len.to_le_bytes());
32        payload.extend_from_slice(bytes);
33
34        // Chunk the length-prefixed payload into pagedb page-body-sized pieces.
35        // Page body capacity = 4096 - ENVELOPE_OVERHEAD (40).
36        const PAGE_BODY_CAP: usize = 4096 - 40;
37        let chunks: Vec<&[u8]> = payload.chunks(PAGE_BODY_CAP).collect();
38
39        let realm = RealmId::new([0u8; 16]);
40        let segment_name = format!("arr/tile/{array_name}/{seg_id}");
41
42        let mut writer = self
43            .db
44            .create_segment(realm, SegmentKind::Unspecified)
45            .await
46            .map_err(LiteError::from)?;
47        writer
48            .append_extent(&chunks)
49            .await
50            .map_err(LiteError::from)?;
51        let meta = writer.seal().await.map_err(LiteError::from)?;
52
53        let mut txn = self.db.begin_write().await.map_err(LiteError::from)?;
54
55        // Atomically replace if a segment already exists under this name.
56        let link_result = txn.link_segment(&segment_name, &meta).await;
57        match link_result {
58            Ok(()) => {}
59            Err(PagedbError::AlreadyLinked) => {
60                txn.replace_segment(&segment_name, &meta)
61                    .await
62                    .map_err(LiteError::from)?;
63            }
64            Err(e) => return Err(LiteError::from(e)),
65        }
66
67        txn.commit().await.map(|_| ()).map_err(LiteError::from)
68    }
69
70    async fn open_array_segment(
71        &self,
72        array_name: &str,
73        seg_id: u64,
74    ) -> Result<Option<Box<[u8]>>, LiteError> {
75        let segment_name = format!("arr/tile/{array_name}/{seg_id}");
76        let txn = self.db.begin_read().await.map_err(LiteError::from)?;
77
78        let reader = match txn.open_segment(&segment_name).await {
79            Ok(r) => r,
80            Err(PagedbError::NotFound) => return Ok(None),
81            Err(e) => return Err(LiteError::from(e)),
82        };
83
84        // Read all data pages and concatenate into a flat byte buffer.
85        let meta_page_count = reader.meta().page_count;
86        let index_pages = u64::from(reader.index_page_count());
87        let data_page_count = meta_page_count
88            .checked_sub(2 + index_pages)
89            .ok_or_else(|| LiteError::Storage {
90                detail: format!(
91                    "array segment page_count={meta_page_count} too small \
92                     (index_pages={index_pages})"
93                ),
94            })?;
95
96        if data_page_count == 0 {
97            return Ok(Some(Box::default()));
98        }
99
100        let count_u32 = u32::try_from(data_page_count).map_err(|_| LiteError::Storage {
101            detail: format!("array segment has too many data pages: {data_page_count}"),
102        })?;
103
104        let pages = reader
105            .read_range(1, count_u32)
106            .await
107            .map_err(|e| LiteError::Storage {
108                detail: format!("pagedb array segment read_range failed: {e}"),
109            })?;
110
111        let total: usize = pages.iter().map(|p| p.len()).sum();
112        let mut flat = Vec::with_capacity(total);
113        for page in pages {
114            flat.extend_from_slice(&page);
115        }
116
117        // Strip the 8-byte length prefix written by `write_array_segment`.
118        if flat.len() < 8 {
119            return Err(LiteError::Storage {
120                detail: format!(
121                    "array segment {array_name}/{seg_id} too small to contain length prefix: \
122                     {} bytes",
123                    flat.len()
124                ),
125            });
126        }
127        let byte_len = u64::from_le_bytes(flat[..8].try_into().expect("8-byte slice")) as usize;
128        let end = 8_usize
129            .checked_add(byte_len)
130            .ok_or_else(|| LiteError::Storage {
131                detail: format!(
132                    "array segment {array_name}/{seg_id} length prefix overflows: {byte_len}"
133                ),
134            })?;
135        if end > flat.len() {
136            return Err(LiteError::Storage {
137                detail: format!(
138                    "array segment {array_name}/{seg_id} declared byte_len={byte_len} \
139                     exceeds available data ({} bytes after prefix)",
140                    flat.len() - 8
141                ),
142            });
143        }
144        let segment_bytes = flat[8..end].to_vec();
145
146        Ok(Some(segment_bytes.into_boxed_slice()))
147    }
148
149    async fn delete_array_segment(&self, array_name: &str, seg_id: u64) -> Result<(), LiteError> {
150        let segment_name = format!("arr/tile/{array_name}/{seg_id}");
151        let mut txn = self.db.begin_write().await.map_err(LiteError::from)?;
152        match txn.unlink_segment(&segment_name).await {
153            Ok(()) => {}
154            Err(PagedbError::NotLinked) => return Ok(()), // already gone
155            Err(e) => return Err(LiteError::from(e)),
156        }
157        txn.commit().await.map(|_| ()).map_err(LiteError::from)
158    }
159}