linkrs_storage/edge/csr_with_properties/
persistence.rs1use super::{CsrWithProperties, RowVisibility, FSST_MAX_SYMBOLS};
2use linkrs_core::types::{EdgeId, INVALID_EDGE_ID};
3use linkrs_core::{StorageError, StorageResult, Value};
4use std::collections::HashSet;
5
6impl CsrWithProperties {
7 pub fn dump(&self) -> Vec<u8> {
8 let mut buf = Vec::new();
13 buf.extend_from_slice(&(self.visibility.len() as u32).to_le_bytes());
14 for vis in &self.visibility {
15 buf.extend_from_slice(&vis.create_ts.to_le_bytes());
16 if let Some(del) = vis.delete_ts {
17 buf.push(1);
18 buf.extend_from_slice(&del.to_le_bytes());
19 } else {
20 buf.push(0);
21 }
22 }
23 buf.extend_from_slice(&(self.row_count as u32).to_le_bytes());
24 buf.extend_from_slice(&(self.edge_map_len as u32).to_le_bytes());
25 for (edge_id, pos) in self.edge_mappings() {
26 buf.extend_from_slice(&(edge_id.0).to_le_bytes());
27 buf.extend_from_slice(&pos.to_le_bytes());
28 }
29 buf.extend_from_slice(&(self.free_list.len() as u32).to_le_bytes());
30 for &off in &self.free_list {
31 buf.extend_from_slice(&off.to_le_bytes());
32 }
33 buf.extend_from_slice(&(self.property_columns.len() as u32).to_le_bytes());
37 for (idx, col) in self.property_columns.iter().enumerate() {
38 let schema = &self.property_schema[idx];
41 buf.extend_from_slice(&(schema.name.len() as u32).to_le_bytes());
42 buf.extend_from_slice(schema.name.as_bytes());
43 buf.extend_from_slice(&schema.prop_id.to_le_bytes());
44 buf.push(col.encoding_type().to_u8());
45 let rows = self.visibility.len();
46 buf.extend_from_slice(&(rows as u32).to_le_bytes());
47 buf.reserve(rows.saturating_mul(16));
48 let mut cell_scratch: Vec<u8> = Vec::with_capacity(64);
49 for row_idx in 0..rows {
50 let val = col.get(row_idx);
51 if let Some(v) = val {
52 buf.push(1);
53 cell_scratch.clear();
54 let taken = std::mem::take(&mut cell_scratch);
55 match postcard::to_extend(&v, taken) {
56 Ok(encoded) => {
57 buf.extend_from_slice(&(encoded.len() as u32).to_le_bytes());
58 buf.extend_from_slice(&encoded);
59 cell_scratch = encoded;
60 }
61 Err(_) => {
62 buf.extend_from_slice(&0u32.to_le_bytes());
63 cell_scratch = Vec::with_capacity(64);
64 }
65 }
66 } else {
67 buf.push(0);
68 }
69 }
70 match col.stats() {
71 Some(stats) => {
72 let mut stats_buf = Vec::new();
73 if stats.serialize_meta(&mut stats_buf).is_ok() {
74 buf.push(1);
75 buf.extend_from_slice(&(stats_buf.len() as u32).to_le_bytes());
76 buf.extend_from_slice(&stats_buf);
77 } else {
78 buf.push(0);
79 }
80 }
81 None => buf.push(0),
82 }
83 }
84 buf
85 }
86
87 pub fn load(&mut self, data: &[u8]) -> StorageResult<()> {
88 fn need(data: &[u8], offset: usize, len: usize, what: &str) -> StorageResult<()> {
89 if data.len().saturating_sub(offset) < len {
90 return Err(StorageError::deserialize_error(format!(
91 "properties payload too short for {}",
92 what
93 )));
94 }
95 Ok(())
96 }
97 if data.is_empty() {
98 return Err(StorageError::deserialize_error(
99 "properties payload is empty",
100 ));
101 }
102 let mut offset = 0usize;
103 need(data, offset, 4, "visibility length")?;
104 let vis_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize;
105 offset += 4;
106 self.visibility.clear();
107 self.visibility.reserve(vis_len);
108 for _ in 0..vis_len {
109 need(data, offset, 8, "row creation stamp")?;
110 let create = u64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
111 offset += 8;
112 need(data, offset, 1, "row deletion flag")?;
113 let has_del = data[offset];
114 offset += 1;
115 let del = if has_del == 1 {
116 need(data, offset, 8, "row deletion stamp")?;
117 let d = u64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
118 offset += 8;
119 Some(d)
120 } else if has_del == 0 {
121 None
122 } else {
123 return Err(StorageError::deserialize_error(format!(
124 "invalid row deletion flag: {}",
125 has_del
126 )));
127 };
128 if del.is_some() && !linkrs_core::types::is_allocatable_timestamp(create) {
132 return Err(StorageError::deserialize_error(format!(
133 "property row carries reserved creation stamp {} with a deletion mark",
134 create
135 )));
136 }
137 self.visibility.push(RowVisibility {
138 create_ts: create,
139 delete_ts: del,
140 });
141 }
142 need(data, offset, 4, "row count")?;
143 self.row_count = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize;
144 offset += 4;
145 need(data, offset, 4, "edge map length")?;
146 let map_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize;
147 offset += 4;
148 self.edge_map_segments.clear();
149 self.edge_map_len = 0;
150 let vis_len = self.visibility.len();
154 for _ in 0..map_len {
155 need(data, offset, 12, "edge map entry")?;
156 let eid = u64::from_le_bytes(data[offset..offset + 8].try_into().unwrap());
157 offset += 8;
158 let pos = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
159 offset += 4;
160 if eid == INVALID_EDGE_ID.0 || (pos as usize) >= vis_len {
161 return Err(StorageError::deserialize_error(
162 "edge map entry outside the restored row window",
163 ));
164 }
165 if self.mapped_row(EdgeId(eid)).is_some() {
166 return Err(StorageError::deserialize_error(
167 "duplicate edge map entry in properties payload",
168 ));
169 }
170 self.map_insert(EdgeId(eid), pos as usize)?;
171 }
172 for (edge_id, pos) in self.edge_mappings() {
175 let owned = self
176 .visibility
177 .get(pos as usize)
178 .map(|vis| linkrs_core::types::is_allocatable_timestamp(vis.create_ts))
179 .unwrap_or(false);
180 if !owned {
181 return Err(StorageError::deserialize_error(format!(
182 "property mapping for edge {:?} lands on a free row",
183 edge_id
184 )));
185 }
186 }
187 need(data, offset, 4, "free list length")?;
188 let free_len = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize;
189 offset += 4;
190 self.free_list.clear();
191 for _ in 0..free_len {
192 need(data, offset, 4, "free list entry")?;
193 let off = u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
194 offset += 4;
195 self.free_list.push(off);
196 }
197 need(data, offset, 4, "column count")?;
198 {
199 let col_count =
200 u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize;
201 offset += 4;
202 let mut seen_names = HashSet::new();
203 let mut seen_ids = HashSet::new();
204 for _ in 0..col_count {
205 need(data, offset, 4, "column name length")?;
206 let name_len =
207 u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize;
208 offset += 4;
209 need(data, offset, name_len, "column name")?;
210 let name = String::from_utf8_lossy(&data[offset..offset + name_len]).to_string();
211 offset += name_len;
212 if !seen_names.insert(name.clone()) {
213 return Err(StorageError::deserialize_error(format!(
214 "duplicate column in properties payload: {}",
215 name
216 )));
217 }
218 need(data, offset, 4, "column identifier")?;
219 let prop_id = i32::from_le_bytes(data[offset..offset + 4].try_into().unwrap());
220 offset += 4;
221 if !seen_ids.insert(prop_id) {
222 return Err(StorageError::deserialize_error(format!(
223 "duplicate column identifier in properties payload: {}",
224 prop_id
225 )));
226 }
227 need(data, offset, 1, "column encoding")?;
228 let encoding_tag = data[offset];
229 offset += 1;
230 if encoding_tag > 6 {
231 return Err(StorageError::deserialize_error(format!(
232 "unknown column encoding tag: {}",
233 encoding_tag
234 )));
235 }
236 let encoding = crate::encoding::EncodingType::from_u8(encoding_tag);
237 need(data, offset, 4, "column row count")?;
238 let rows =
239 u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap()) as usize;
240 offset += 4;
241 if let Some(&col_idx) = self.column_index.get(name.as_str()) {
242 self.property_schema[col_idx].prop_id = prop_id;
243 let col = &mut self.property_columns[col_idx];
244 col.col_id = prop_id;
245 if col.len() < rows {
246 col.resize(rows);
247 }
248 for row_idx in 0..rows {
249 need(data, offset, 1, "column cell flag")?;
250 let has = data[offset];
251 offset += 1;
252 if has == 1 {
253 need(data, offset, 4, "column cell length")?;
254 let vlen =
255 u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap())
256 as usize;
257 offset += 4;
258 need(data, offset, vlen, "column cell value")?;
259 let vbytes = &data[offset..offset + vlen];
260 offset += vlen;
261 if let Ok(val) = postcard::from_bytes::<Value>(vbytes) {
262 let _ = col.set(row_idx, Some(&val));
263 }
264 } else if has == 0 {
265 let _ = col.set(row_idx, None);
266 } else {
267 return Err(StorageError::deserialize_error(format!(
268 "invalid column cell flag: {}",
269 has
270 )));
271 }
272 }
273 need(data, offset, 1, "column stats flag")?;
274 let has_stats = data[offset];
275 offset += 1;
276 if has_stats == 1 {
277 need(data, offset, 4, "column stats length")?;
278 let stats_len =
279 u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap())
280 as usize;
281 offset += 4;
282 need(data, offset, stats_len, "column stats")?;
283 let stats_bytes = &data[offset..offset + stats_len];
284 offset += stats_len;
285 let mut cursor = stats_bytes;
286 let stats =
287 crate::column_stats::ColumnStats::deserialize_meta(&mut cursor)?;
288 if !cursor.is_empty() {
289 return Err(StorageError::deserialize_error(
290 "unexpected trailing data in column stats".to_string(),
291 ));
292 }
293 if encoding != crate::encoding::EncodingType::None {
294 self.apply_encoding_to_column(&name, encoding, FSST_MAX_SYMBOLS)?;
295 }
296 let col = &mut self.property_columns[col_idx];
297 col.set_stats(stats);
298 } else if has_stats == 0 {
299 if encoding != crate::encoding::EncodingType::None {
300 self.apply_encoding_to_column(&name, encoding, FSST_MAX_SYMBOLS)?;
301 }
302 } else {
303 return Err(StorageError::deserialize_error(format!(
304 "invalid column stats flag: {}",
305 has_stats
306 )));
307 }
308 } else {
309 for _ in 0..rows {
310 need(data, offset, 1, "unknown column cell flag")?;
311 let has = data[offset];
312 offset += 1;
313 if has == 1 {
314 need(data, offset, 4, "unknown column cell length")?;
315 let vlen =
316 u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap())
317 as usize;
318 offset += 4;
319 need(data, offset, vlen, "unknown column cell value")?;
320 offset += vlen;
321 } else if has != 0 {
322 return Err(StorageError::deserialize_error(format!(
323 "invalid unknown column cell flag: {}",
324 has
325 )));
326 }
327 }
328 need(data, offset, 1, "unknown column stats flag")?;
331 let unknown_stats = data[offset];
332 offset += 1;
333 if unknown_stats == 1 {
334 need(data, offset, 4, "unknown column stats length")?;
335 let stats_len =
336 u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap())
337 as usize;
338 offset += 4;
339 need(data, offset, stats_len, "unknown column stats")?;
340 offset += stats_len;
341 } else if unknown_stats != 0 {
342 return Err(StorageError::deserialize_error(format!(
343 "invalid unknown column stats flag: {}",
344 unknown_stats
345 )));
346 }
347 }
348 }
349 }
350 if offset != data.len() {
351 return Err(StorageError::deserialize_error(
352 "unexpected trailing data in properties payload".to_string(),
353 ));
354 }
355 self.rebuild_aux_indexes();
356 self.dirty_columns.clear();
357 self.recompute_history_floor();
358 Ok(())
359 }
360
361 pub(crate) fn recompute_history_floor(&mut self) {
367 self.history_floor = self
368 .visibility
369 .iter()
370 .map(|vis| vis.create_ts)
371 .filter(|ts| linkrs_core::types::is_allocatable_timestamp(*ts))
372 .max()
373 .unwrap_or(0);
374 }
375
376 fn rebuild_aux_indexes(&mut self) {
377 self.row_to_edge.clear();
378 self.row_to_edge.resize(self.visibility.len(), None);
379 let mappings: Vec<(EdgeId, u32)> = self.edge_mappings().collect();
380 for (edge_id, pos) in mappings {
381 let idx = pos as usize;
382 if idx < self.row_to_edge.len() {
383 self.row_to_edge[idx] = Some(edge_id);
384 }
385 }
386 self.rebuild_schema_indexes();
387 let max_id = self
388 .property_schema
389 .iter()
390 .map(|s| s.prop_id)
391 .max()
392 .unwrap_or(-1);
393 self.next_prop_id = max_id
394 .saturating_add(1)
395 .max(self.property_schema.len() as i32);
396 }
397}