Skip to main content

wasm_dbms/transaction/
overlay.rs

1// Rust guideline compliant 2026-02-28
2
3//! Database overlay for tracking uncommitted transaction changes.
4
5mod reader;
6mod table;
7
8use std::collections::HashMap;
9
10use wasm_dbms_api::prelude::{ColumnDef, DbmsError, DbmsResult, QueryError, TableSchema, Value};
11use wasm_dbms_memory::prelude::{MemoryAccess, TableReader};
12
13pub use self::reader::DatabaseOverlayReader;
14pub use self::table::IndexOverlay;
15pub(crate) use self::table::TableOverlay;
16
17/// Manages uncommitted changes during a transaction.
18///
19/// Provides an overlay over the existing database state to track
20/// uncommitted inserts, updates, and deletes per table.
21#[derive(Debug, Default, Clone)]
22pub struct DatabaseOverlay {
23    tables: HashMap<String, TableOverlay>,
24}
25
26impl DatabaseOverlay {
27    /// Returns a reader that merges base table data with overlay changes.
28    pub fn reader<'a, T, MA>(
29        &'a mut self,
30        table_reader: TableReader<'a, T, MA>,
31    ) -> DatabaseOverlayReader<'a, T, MA>
32    where
33        T: TableSchema,
34        MA: MemoryAccess,
35    {
36        let table_name = T::table_name();
37        let table_overlay = self
38            .tables
39            .entry(table_name.to_string())
40            .or_insert_with(|| TableOverlay::new(T::indexes()));
41        DatabaseOverlayReader::new(table_overlay, table_reader)
42    }
43
44    /// Inserts a record into the overlay for the specified table.
45    pub fn insert<T>(&mut self, values: Vec<(ColumnDef, Value)>) -> DbmsResult<()>
46    where
47        T: TableSchema,
48    {
49        let table_name = T::table_name();
50        let pk = T::primary_key();
51        let pk = Self::primary_key(pk, &values)?;
52        let overlay = self
53            .tables
54            .entry(table_name.to_string())
55            .or_insert_with(|| TableOverlay::new(T::indexes()));
56        overlay.insert(pk, values);
57
58        Ok(())
59    }
60
61    /// Updates a record in the overlay for the specified table.
62    ///
63    /// `current_row` is the full row before the update, used to track old indexed values.
64    pub fn update<T>(
65        &mut self,
66        pk: Value,
67        updates: Vec<(&'static str, Value)>,
68        current_row: &[(ColumnDef, Value)],
69    ) where
70        T: TableSchema,
71    {
72        let table_name = T::table_name();
73        let overlay = self
74            .tables
75            .entry(table_name.to_string())
76            .or_insert_with(|| TableOverlay::new(T::indexes()));
77        overlay.update(pk, updates, current_row);
78    }
79
80    /// Deletes a record in the overlay for the specified table.
81    ///
82    /// `current_row` is the full row being deleted, used to track removed indexed values.
83    pub fn delete<T>(&mut self, pk: Value, current_row: &[(ColumnDef, Value)])
84    where
85        T: TableSchema,
86    {
87        let table_name = T::table_name();
88        let overlay = self
89            .tables
90            .entry(table_name.to_string())
91            .or_insert_with(|| TableOverlay::new(T::indexes()));
92        overlay.delete(pk, current_row);
93    }
94
95    /// Retrieves the index overlay for a given table, if it exists.
96    pub fn index_overlay(&self, table: &str) -> Option<&IndexOverlay> {
97        self.tables.get(table).map(|t| &t.index_overlay)
98    }
99
100    /// Retrieves the table overlay for a given table, if it exists.
101    pub(crate) fn table_overlay(&self, table: &str) -> Option<&TableOverlay> {
102        self.tables.get(table)
103    }
104
105    fn primary_key(pk: &'static str, values: &[(ColumnDef, Value)]) -> DbmsResult<Value> {
106        for (col_def, value) in values {
107            if col_def.name == pk {
108                return Ok(value.clone());
109            }
110        }
111        Err(DbmsError::Query(QueryError::MissingNonNullableField(
112            pk.to_string(),
113        )))
114    }
115}