Skip to main content

sim_kernel/catalog/
table_backend.rs

1use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
2
3use crate::{
4    Cx, Error, Expr, Result, Symbol, Value,
5    id::CORE_TABLE_CLASS_ID,
6    object::{ClassRef, Object},
7    table::{Table, TableBackend},
8};
9
10use super::{
11    CatalogRow, CatalogSnapshot, CatalogStore, CatalogTableSpec, CatalogTx, CatalogWritePolicy,
12};
13
14// sim-non-citizen(reason = "mutable registry catalog table handle; descriptor data is exposed through catalog view rows", kind = "handle", descriptor = "core/Table")
15/// Mutable runtime [`Table`] object wrapping a private [`CatalogStore`].
16///
17/// This is the opt-in catalog semantics for ordinary runtime users who want a
18/// transactional table without registry authority; it is a library-style
19/// backend, not kernel registry behavior.
20pub struct CatalogTable {
21    store: RwLock<CatalogStore>,
22}
23
24impl CatalogTable {
25    /// Creates an empty catalog-backed table.
26    pub fn new() -> Result<Self> {
27        Ok(Self {
28            store: RwLock::new(new_store()?),
29        })
30    }
31
32    /// Creates a catalog-backed table seeded with `entries`.
33    pub fn with_entries(entries: Vec<(Symbol, Value)>) -> Result<Self> {
34        let table = Self::new()?;
35        for (key, value) in entries {
36            table.put_value(key, value)?;
37        }
38        Ok(table)
39    }
40
41    /// Returns a snapshot of the table's underlying catalog store.
42    pub fn snapshot(&self) -> Result<CatalogSnapshot> {
43        self.read_store()
44            .map(|store| CatalogSnapshot::from_store(&store))
45    }
46
47    fn put_value(&self, key: Symbol, value: Value) -> Result<()> {
48        let mut tx = CatalogTx::new();
49        tx.put_row(row_for_value(key, value));
50        let mut store = self.write_store()?;
51        tx.commit(&mut store).map(|_| ())
52    }
53
54    fn read_store(&self) -> Result<RwLockReadGuard<'_, CatalogStore>> {
55        self.store
56            .read()
57            .map_err(|_| Error::PoisonedLock("catalog table"))
58    }
59
60    fn write_store(&self) -> Result<RwLockWriteGuard<'_, CatalogStore>> {
61        self.store
62            .write()
63            .map_err(|_| Error::PoisonedLock("catalog table"))
64    }
65}
66
67impl Object for CatalogTable {
68    fn display(&self, _cx: &mut Cx) -> Result<String> {
69        let len = self
70            .read_store()?
71            .rows(&entries_table())
72            .map_or(0, |rows| rows.len());
73        Ok(format!("catalog-table[{len}]"))
74    }
75
76    fn as_any(&self) -> &dyn std::any::Any {
77        self
78    }
79}
80
81impl crate::ObjectCompat for CatalogTable {
82    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
83        let symbol = Symbol::qualified("core", "Table");
84        if let Some(value) = cx.registry().class_by_symbol(&symbol) {
85            return Ok(value.clone());
86        }
87        cx.factory().class_stub(CORE_TABLE_CLASS_ID, symbol)
88    }
89
90    fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
91        self.as_table_expr(cx)
92    }
93
94    fn truth(&self, cx: &mut Cx) -> Result<bool> {
95        Ok(!self.is_empty(cx)?)
96    }
97
98    fn as_table_impl(&self) -> Option<&dyn Table> {
99        Some(self)
100    }
101}
102
103impl Table for CatalogTable {
104    fn backend_symbol(&self) -> Symbol {
105        Symbol::qualified("table", "catalog")
106    }
107
108    fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
109        match self
110            .read_store()?
111            .row(&entries_table(), &key)
112            .map(|row| row_value(cx, row))
113            .transpose()?
114        {
115            Some(value) => Ok(value),
116            None => cx.factory().nil(),
117        }
118    }
119
120    fn set(&self, _cx: &mut Cx, key: Symbol, value: Value) -> Result<()> {
121        self.put_value(key, value)
122    }
123
124    fn has(&self, _cx: &mut Cx, key: Symbol) -> Result<bool> {
125        Ok(self.read_store()?.row(&entries_table(), &key).is_some())
126    }
127
128    fn del(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
129        let mut store = self.write_store()?;
130        let Some(value) = store
131            .row(&entries_table(), &key)
132            .map(|row| row_value(cx, row))
133            .transpose()?
134        else {
135            return cx.factory().nil();
136        };
137
138        let mut tx = CatalogTx::new();
139        tx.delete_row(entries_table(), key);
140        tx.commit(&mut store)?;
141        Ok(value)
142    }
143
144    fn keys(&self, _cx: &mut Cx) -> Result<Vec<Symbol>> {
145        Ok(self
146            .read_store()?
147            .rows(&entries_table())
148            .map(|rows| rows.keys().cloned().collect())
149            .unwrap_or_default())
150    }
151
152    fn entries(&self, cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
153        self.read_store()?
154            .rows(&entries_table())
155            .into_iter()
156            .flat_map(|rows| rows.iter())
157            .map(|(key, row)| row_value(cx, row).map(|value| (key.clone(), value)))
158            .collect()
159    }
160
161    fn len(&self, _cx: &mut Cx) -> Result<usize> {
162        Ok(self
163            .read_store()?
164            .rows(&entries_table())
165            .map_or(0, |rows| rows.len()))
166    }
167
168    fn clear(&self, _cx: &mut Cx) -> Result<()> {
169        let keys = self
170            .read_store()?
171            .rows(&entries_table())
172            .map(|rows| rows.keys().cloned().collect::<Vec<_>>())
173            .unwrap_or_default();
174        if keys.is_empty() {
175            return Ok(());
176        }
177
178        let mut tx = CatalogTx::new();
179        for key in keys {
180            tx.delete_row(entries_table(), key);
181        }
182        let mut store = self.write_store()?;
183        tx.commit(&mut store).map(|_| ())
184    }
185}
186
187/// [`TableBackend`] that constructs [`CatalogTable`]s under the
188/// `table/catalog` backend name.
189pub struct CatalogBackend;
190
191impl TableBackend for CatalogBackend {
192    fn name(&self) -> &str {
193        "table/catalog"
194    }
195
196    fn new_table(&self, cx: &mut Cx, entries: Vec<(Symbol, Value)>) -> Result<Value> {
197        cx.factory()
198            .opaque(Arc::new(CatalogTable::with_entries(entries)?))
199    }
200}
201
202fn new_store() -> Result<CatalogStore> {
203    let mut store = CatalogStore::new();
204    store.install_table(
205        CatalogTableSpec::new(entries_table(), CatalogWritePolicy::Mutable)
206            .with_required_fields(vec![field("key"), field("value")]),
207    )?;
208    Ok(store)
209}
210
211fn row_for_value(key: Symbol, value: Value) -> CatalogRow {
212    let mut row =
213        CatalogRow::new(entries_table(), key.clone()).with_data(field("key"), Expr::Symbol(key));
214    row.insert_live_value(field("value"), value);
215    row
216}
217
218fn row_value(cx: &mut Cx, row: &CatalogRow) -> Result<Value> {
219    if let Some(value) = row.live_value(&field("value")) {
220        Ok(value.clone())
221    } else if let Some(expr) = row.data.get(&field("value")) {
222        cx.factory().expr(expr.clone())
223    } else {
224        cx.factory().nil()
225    }
226}
227
228fn entries_table() -> Symbol {
229    Symbol::qualified("table", "entries")
230}
231
232fn field(name: &'static str) -> Symbol {
233    Symbol::new(name)
234}