nodedb_lite/engine/columnar/store/
lifecycle.rs1use super::decode::rebuild_pk_from_column;
6use super::segments::load_segment_bytes;
7use super::state::{CollectionMap, CollectionState, ColumnarEngine, SegmentMeta};
8use crate::error::LiteError;
9use crate::storage::engine::StorageEngine;
10#[cfg(not(target_arch = "wasm32"))]
11use crate::sync::outbound::columnar::ColumnarOutbound;
12#[cfg(not(target_arch = "wasm32"))]
13use crate::sync::outbound::timeseries::TimeseriesOutbound;
14use nodedb_columnar::delete_bitmap::DeleteBitmap;
15use nodedb_columnar::mutation::MutationEngine;
16use nodedb_columnar::reader::SegmentReader;
17use nodedb_mem::ScopedMemory;
18use nodedb_types::Namespace;
19use nodedb_types::columnar::{ColumnarProfile, ColumnarSchema};
20use std::collections::HashMap;
21use std::sync::{Arc, Mutex, RwLock};
22
23pub(super) const META_COLUMNAR_SCHEMA_PREFIX: &str = "columnar_schema:";
24pub(super) const META_COLUMNAR_COLLECTIONS: &[u8] = b"meta:columnar_collections";
25
26impl<S: StorageEngine> ColumnarEngine<S> {
27 pub fn new(storage: Arc<S>, memory: ScopedMemory) -> Self {
29 Self {
30 storage,
31 collections: RwLock::new(HashMap::new()),
32 #[cfg(not(target_arch = "wasm32"))]
33 outbound: None,
34 #[cfg(not(target_arch = "wasm32"))]
35 timeseries_outbound: None,
36 memory,
37 }
38 }
39
40 #[cfg(not(target_arch = "wasm32"))]
44 pub fn set_outbound(&mut self, outbound: Arc<ColumnarOutbound<S>>) {
45 self.outbound = Some(outbound);
46 }
47
48 #[cfg(not(target_arch = "wasm32"))]
54 pub fn set_timeseries_outbound(&mut self, outbound: Arc<TimeseriesOutbound<S>>) {
55 self.timeseries_outbound = Some(outbound);
56 }
57
58 pub async fn restore(storage: Arc<S>, memory: ScopedMemory) -> Result<Self, LiteError> {
60 let engine = Self::new(Arc::clone(&storage), memory);
61
62 let list_bytes = storage
63 .get(Namespace::Meta, META_COLUMNAR_COLLECTIONS)
64 .await?;
65 let names: Vec<String> = match list_bytes {
66 Some(bytes) => zerompk::from_msgpack(&bytes).map_err(|e| LiteError::Storage {
67 detail: format!("columnar collection list deserialization: {e}"),
68 })?,
69 None => Vec::new(),
70 };
71
72 let mut loaded: CollectionMap = HashMap::new();
73 for name in names {
74 let meta_key = format!("{META_COLUMNAR_SCHEMA_PREFIX}{name}");
75 #[derive(serde::Deserialize, zerompk::ToMessagePack, zerompk::FromMessagePack)]
76 struct StoredSchema {
77 schema: ColumnarSchema,
78 profile: ColumnarProfile,
79 #[serde(default)]
80 bitemporal: bool,
81 }
82 if let Some(schema_bytes) = storage.get(Namespace::Meta, meta_key.as_bytes()).await?
83 && let Ok(stored) = zerompk::from_msgpack::<StoredSchema>(&schema_bytes)
84 {
85 let seg_meta_key = format!("{name}:meta");
86 let segments: Vec<SegmentMeta> = storage
87 .get(Namespace::Columnar, seg_meta_key.as_bytes())
88 .await?
89 .and_then(|b| zerompk::from_msgpack(&b).ok())
90 .unwrap_or_default();
91
92 let next_id = segments.iter().map(|s| s.segment_id + 1).max().unwrap_or(1);
93
94 let mut mutation = MutationEngine::new(name.clone(), stored.schema.clone());
95
96 for seg_meta in &segments {
97 if seg_meta.fully_deleted_at_ms.is_some() {
99 continue;
100 }
101
102 if let Some(seg_bytes) =
103 load_segment_bytes(&*storage, &name, seg_meta.segment_id).await?
104 && let Ok(reader) = SegmentReader::open(&seg_bytes)
105 && let Ok(pk_col) = reader.read_column(0)
106 {
107 rebuild_pk_from_column(&mut mutation, &pk_col, seg_meta.segment_id);
108 }
109
110 let del_key = format!("{name}:del:{}", seg_meta.segment_id);
111 if let Some(del_bytes) =
112 storage.get(Namespace::Columnar, del_key.as_bytes()).await?
113 && let Ok(bitmap) = DeleteBitmap::from_bytes(&del_bytes)
114 {
115 for row_idx in bitmap.iter() {
116 let _ = row_idx;
117 }
118 }
119 }
120
121 loaded.insert(
122 name,
123 Arc::new(Mutex::new(CollectionState {
124 mutation,
125 profile: stored.profile,
126 bitemporal: stored.bitemporal,
127 segments,
128 next_segment_id: next_id,
129 })),
130 );
131 }
132 }
133
134 *engine
135 .collections
136 .write()
137 .map_err(|_| LiteError::LockPoisoned)? = loaded;
138
139 Ok(engine)
141 }
142
143 pub(in crate::engine::columnar) fn lookup(
146 &self,
147 name: &str,
148 ) -> Result<Arc<Mutex<CollectionState>>, LiteError> {
149 let guard = self
150 .collections
151 .read()
152 .map_err(|_| LiteError::LockPoisoned)?;
153 guard
154 .get(name)
155 .cloned()
156 .ok_or_else(|| LiteError::collection_not_found("columnar", name))
157 }
158
159 pub(in crate::engine::columnar) fn lock_state<'a>(
160 state: &'a Arc<Mutex<CollectionState>>,
161 ) -> Result<std::sync::MutexGuard<'a, CollectionState>, LiteError> {
162 state.lock().map_err(|_| LiteError::LockPoisoned)
163 }
164}