nodedb_lite/sync/array/inbound/
apply.rs1use std::collections::HashMap;
6use std::future::Future;
7use std::sync::{Arc, Mutex};
8
9use nodedb_array::error::ArrayError;
10use nodedb_array::sync::apply::ApplyEngine;
11use nodedb_array::sync::hlc::Hlc;
12use nodedb_array::sync::op::ArrayOp;
13use nodedb_array::sync::op_log::OpLog;
14use nodedb_array::types::coord::value::CoordValue;
15use nodedb_types::Namespace;
16
17use crate::engine::array::engine::{ArrayEngineState, CellWrite};
18use crate::storage::engine::StorageEngine;
19use crate::sync::array::op_log_store::KvOpLogStore;
20use crate::sync::array::schema_registry::SchemaRegistry;
21
22const LAST_APPLIED_PREFIX: &str = "array.last_applied:";
24
25fn block<F, T>(f: F) -> T
30where
31 F: Future<Output = T>,
32{
33 tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(f))
34}
35
36pub struct LiteApplyEngine<S: StorageEngine> {
51 pub(super) storage: Arc<S>,
52 pub(super) array_state: Arc<tokio::sync::Mutex<ArrayEngineState>>,
53 pub(super) schemas: Arc<SchemaRegistry<S>>,
54 pub(super) op_log: Arc<KvOpLogStore<S>>,
55 last_applied: Mutex<HashMap<String, Hlc>>,
58}
59
60impl<S: StorageEngine> LiteApplyEngine<S> {
61 pub async fn new(
63 storage: Arc<S>,
64 array_state: Arc<tokio::sync::Mutex<ArrayEngineState>>,
65 schemas: Arc<SchemaRegistry<S>>,
66 op_log: Arc<KvOpLogStore<S>>,
67 ) -> Self {
68 let last_applied = Self::load_last_applied(&storage).await;
69 Self {
70 storage,
71 array_state,
72 schemas,
73 op_log,
74 last_applied: Mutex::new(last_applied),
75 }
76 }
77
78 async fn load_last_applied(storage: &Arc<S>) -> HashMap<String, Hlc> {
79 let prefix = LAST_APPLIED_PREFIX.as_bytes();
80 let Ok(pairs) = storage
81 .scan_range(Namespace::Meta, prefix, usize::MAX)
82 .await
83 else {
84 return HashMap::new();
85 };
86 let mut map = HashMap::new();
87 for (key, value) in pairs {
88 if !key.starts_with(prefix) {
89 break;
90 }
91 let name_bytes = &key[prefix.len()..];
92 let Ok(name) = std::str::from_utf8(name_bytes) else {
93 continue;
94 };
95 if value.len() == 18 {
96 let bytes: [u8; 18] = match value.try_into() {
97 Ok(b) => b,
98 Err(_) => continue,
99 };
100 map.insert(name.to_owned(), Hlc::from_bytes(&bytes));
101 }
102 }
103 map
104 }
105
106 pub fn last_applied_hlc(&self, array: &str) -> Option<Hlc> {
108 self.last_applied.lock().ok()?.get(array).copied()
109 }
110
111 fn record_applied_hlc(&self, array: &str, hlc: Hlc) {
116 let should_persist = {
117 let mut map = match self.last_applied.lock() {
118 Ok(m) => m,
119 Err(_) => return,
120 };
121 let current = map.get(array).copied().unwrap_or(Hlc::ZERO);
122 if hlc > current {
123 map.insert(array.to_owned(), hlc);
124 true
125 } else {
126 false
127 }
128 };
129 if should_persist {
130 let key = format!("{LAST_APPLIED_PREFIX}{array}").into_bytes();
131 let storage = Arc::clone(&self.storage);
132 let bytes = hlc.to_bytes();
133 let _ = block(async move { storage.put(Namespace::Meta, &key, &bytes).await });
134 }
135 }
136}
137
138impl<S: StorageEngine> ApplyEngine for &LiteApplyEngine<S> {
149 fn schema_hlc(&self, array: &str) -> nodedb_array::error::ArrayResult<Option<Hlc>> {
150 Ok(self.schemas.schema_hlc(array))
151 }
152
153 fn already_seen(&self, array: &str, hlc: Hlc) -> nodedb_array::error::ArrayResult<bool> {
154 let mut iter = self.op_log.scan_range(array, hlc, hlc)?;
155 Ok(iter.next().is_some())
156 }
157
158 fn apply_put(&mut self, op: &ArrayOp) -> nodedb_array::error::ArrayResult<()> {
159 let system_from_ms = op.header.system_from_ms;
160 let attrs = op.attrs.clone().unwrap_or_default();
161 let array_state = Arc::clone(&self.array_state);
162 let storage = Arc::clone(&self.storage);
163 let array = op.header.array.clone();
164 let coord = op.coord.clone();
165 let valid_from_ms = op.header.valid_from_ms;
166 let valid_until_ms = op.header.valid_until_ms;
167 block(async move {
168 let mut state = array_state.lock().await;
169 state
170 .put_cell(
171 &storage,
172 &array,
173 CellWrite {
174 coord,
175 attrs,
176 system_from_ms,
177 valid_from_ms,
178 valid_until_ms,
179 },
180 )
181 .await
182 .map_err(|e| ArrayError::SegmentCorruption {
183 detail: format!("apply_put: {e}"),
184 })
185 })?;
186 self.op_log.append(op)?;
188 self.record_applied_hlc(&op.header.array, op.header.hlc);
189 Ok(())
190 }
191
192 fn apply_delete(&mut self, op: &ArrayOp) -> nodedb_array::error::ArrayResult<()> {
193 {
194 let mut state = self.array_state.blocking_lock();
195 state
196 .delete_cell(&op.header.array, op.coord.clone(), op.header.system_from_ms)
197 .map_err(|e| ArrayError::SegmentCorruption {
198 detail: format!("apply_delete: {e}"),
199 })?;
200 }
201 self.op_log.append(op)?;
202 self.record_applied_hlc(&op.header.array, op.header.hlc);
203 Ok(())
204 }
205
206 fn apply_erase(&mut self, op: &ArrayOp) -> nodedb_array::error::ArrayResult<()> {
207 let array_state = Arc::clone(&self.array_state);
208 let storage = Arc::clone(&self.storage);
209 let array = op.header.array.clone();
210 let coord = op.coord.clone();
211 let system_from_ms = op.header.system_from_ms;
212 block(async move {
213 let mut state = array_state.lock().await;
214 state
215 .gdpr_erase_cell(&storage, &array, coord, system_from_ms)
216 .await
217 .map_err(|e| ArrayError::SegmentCorruption {
218 detail: format!("apply_erase: {e}"),
219 })
220 })?;
221 self.op_log.append(op)?;
222 self.record_applied_hlc(&op.header.array, op.header.hlc);
223 Ok(())
224 }
225
226 fn invalidate_tile(
231 &mut self,
232 _array: &str,
233 _coord: &[CoordValue],
234 ) -> nodedb_array::error::ArrayResult<()> {
235 Ok(())
236 }
237}