⎇ 85795c42cbd49259b2fef73a15989c73ac319141
03_ukupacha/minga/minga-store/src/node_store.rs
Historia
Quién tocó qué
crudo
1//! Almacén persistente de `StoredNode`s indexados por `ContentHash`.
2//!
3//! Cada nodo se serializa con postcard y se inserta en una `sled::Tree`
4//! cuya clave son los 32 bytes del hash. La operación `put` es
5//! recursiva sobre los hijos (igual que `MemStore::put`): cada
6//! subárbol se hashea y persiste exactamente una vez.
7
8use minga_core::{cas, hash_stored, ContentHash, SemanticNode, StoredNode};
9use sled::{Db, Tree};
10
11use crate::error::StoreError;
12
13pub struct SledNodeStore {
14 tree: Tree,
15}
16
17impl SledNodeStore {
18 pub fn open_tree(db: &Db, name: &str) -> Result<Self, StoreError> {
19 Ok(Self {
20 tree: db.open_tree(name)?,
21 })
22 }
23
24 /// Inserta un árbol completo. Recursivamente desempaqueta hijos.
25 /// Devuelve el hash de la raíz. Idempotente: insertar el mismo
26 /// árbol dos veces no añade entradas nuevas.
27 pub fn put(&self, node: &SemanticNode) -> Result<ContentHash, StoreError> {
28 let mut child_hashes = Vec::with_capacity(node.children.len());
29 for c in &node.children {
30 child_hashes.push(self.put(c)?);
31 }
32 let h = cas::hash_components(
33 &node.kind,
34 node.field_name.as_deref(),
35 node.leaf_text.as_deref(),
36 &child_hashes,
37 );
38 if !self.tree.contains_key(h.0)? {
39 let stored = StoredNode {
40 kind: node.kind.clone(),
41 field_name: node.field_name.clone(),
42 leaf_text: node.leaf_text.clone(),
43 children: child_hashes,
44 };
45 let bytes = postcard::to_allocvec(&stored)?;
46 self.tree.insert(h.0, bytes)?;
47 }
48 Ok(h)
49 }
50
51 /// Inserta un nodo ya troceado por hash. Verifica que el hash
52 /// coincida con `hash_stored(stored)` antes de insertar — sin
53 /// esa verificación no podemos confiar en la integridad de lo
54 /// que viene del wire.
55 pub fn put_chunked(
56 &self,
57 hash: ContentHash,
58 stored: &StoredNode,
59 ) -> Result<(), StoreError> {
60 if hash_stored(stored) != hash {
61 return Err(StoreError::HashMismatch);
62 }
63 if !self.tree.contains_key(hash.0)? {
64 let bytes = postcard::to_allocvec(stored)?;
65 self.tree.insert(hash.0, bytes)?;
66 }
67 Ok(())
68 }
69
70 pub fn get(&self, h: &ContentHash) -> Result<Option<StoredNode>, StoreError> {
71 match self.tree.get(h.0)? {
72 Some(bytes) => Ok(Some(postcard::from_bytes(&bytes)?)),
73 None => Ok(None),
74 }
75 }
76
77 pub fn contains(&self, h: &ContentHash) -> Result<bool, StoreError> {
78 Ok(self.tree.contains_key(h.0)?)
79 }
80
81 /// Reconstruye un `SemanticNode` resolviendo recursivamente todos
82 /// los hijos. `Ok(None)` si algún hash no está en el store
83 /// (almacén incompleto).
84 pub fn reconstruct(&self, h: &ContentHash) -> Result<Option<SemanticNode>, StoreError> {
85 let stored = match self.get(h)? {
86 Some(s) => s,
87 None => return Ok(None),
88 };
89 let mut children = Vec::with_capacity(stored.children.len());
90 for ch in &stored.children {
91 match self.reconstruct(ch)? {
92 Some(n) => children.push(n),
93 None => return Ok(None),
94 }
95 }
96 Ok(Some(SemanticNode {
97 kind: stored.kind,
98 field_name: stored.field_name,
99 leaf_text: stored.leaf_text,
100 children,
101 }))
102 }
103
104 pub fn len(&self) -> usize {
105 self.tree.len()
106 }
107
108 pub fn is_empty(&self) -> bool {
109 self.tree.is_empty()
110 }
111
112 pub fn flush(&self) -> Result<(), StoreError> {
113 self.tree.flush()?;
114 Ok(())
115 }
116
117 /// Elimina un nodo del store por su hash. **Cuidado**: los hijos no
118 /// se borran en cascada (otros nodos pueden referenciarlos). El
119 /// caller es responsable de la consistencia (típicamente: usar
120 /// mark-sweep sobre raíces vivas).
121 pub fn remove(&self, h: &ContentHash) -> Result<bool, StoreError> {
122 Ok(self.tree.remove(h.0)?.is_some())
123 }
124
125 /// Itera sólo los hashes (sin deserializar el valor). Más liviano
126 /// que `iter` cuando sólo se necesitan las claves — útil para
127 /// mark-sweep del GC.
128 pub fn iter_hashes(&self) -> impl Iterator<Item = Result<ContentHash, StoreError>> + '_ {
129 self.tree.iter().map(|kv| {
130 let (k, _) = kv?;
131 if k.len() != 32 {
132 return Err(StoreError::HashMismatch);
133 }
134 let mut bytes = [0u8; 32];
135 bytes.copy_from_slice(&k);
136 Ok(ContentHash(bytes))
137 })
138 }
139
140 /// Lee sólo los hashes de los hijos de un nodo (sin reconstruir
141 /// `StoredNode` completo más allá del shape del header postcard).
142 /// Optimización del walk del mark-sweep: para visitar el subárbol
143 /// no necesitamos `kind`/`field_name`/`leaf_text`.
144 pub fn children_of(&self, h: &ContentHash) -> Result<Option<Vec<ContentHash>>, StoreError> {
145 match self.tree.get(h.0)? {
146 Some(bytes) => {
147 let stored: StoredNode = postcard::from_bytes(&bytes)?;
148 Ok(Some(stored.children))
149 }
150 None => Ok(None),
151 }
152 }
153
154 /// Itera todos los pares `(hash, stored_node)` persistidos. Sin
155 /// orden garantizado más allá del lexicográfico de sled. Usado al
156 /// arrancar para volcar el contenido a un `MemStore` en memoria.
157 pub fn iter(
158 &self,
159 ) -> impl Iterator<Item = Result<(ContentHash, StoredNode), StoreError>> + '_ {
160 self.tree.iter().map(|kv| {
161 let (k, v) = kv?;
162 if k.len() != 32 {
163 return Err(StoreError::HashMismatch);
164 }
165 let mut bytes = [0u8; 32];
166 bytes.copy_from_slice(&k);
167 let stored: StoredNode = postcard::from_bytes(&v)?;
168 Ok((ContentHash(bytes), stored))
169 })
170 }
171}