Skip to main content

akar_storage/
local_storage.rs

1//! Local storage — per-transaction write buffer before commit.
2//!
3//! During a transaction, write operations are buffered in `LocalStorage`.
4//! On commit, `flush_to_tables()` applies buffered inserts, deletes, and
5//! updates to the actual `NodeTable`/`RelTable` via the `TableCatalog`.
6//! On rollback, `clear()` discards all buffers.
7
8use crate::table::{NodeTable, RelTable, TableCatalog};
9use akar_common::error::StorageError;
10use akar_transaction::UndoRecord;
11use std::collections::HashMap;
12use std::sync::Arc;
13
14/// A local table insert/update buffer for an in-progress transaction.
15///
16/// Stores rows as serialised byte vectors (compatible with the `Value` binary
17/// format used by `Column`). The `flush_to_tables()` method deserialises and
18/// applies them.
19#[derive(Debug, Default)]
20pub struct LocalTableData {
21    inserted_rows: Vec<Vec<u8>>,
22    deleted_row_ids: Vec<u64>,
23    updated_rows: HashMap<u64, Vec<u8>>,
24}
25
26impl LocalTableData {
27    pub fn new() -> Self {
28        Self::default()
29    }
30
31    pub fn insert(&mut self, row_data: Vec<u8>) {
32        self.inserted_rows.push(row_data);
33    }
34
35    pub fn delete(&mut self, row_id: u64) {
36        self.deleted_row_ids.push(row_id);
37    }
38
39    pub fn update(&mut self, row_id: u64, row_data: Vec<u8>) {
40        self.updated_rows.insert(row_id, row_data);
41    }
42
43    pub fn inserted_rows(&self) -> &[Vec<u8>] {
44        &self.inserted_rows
45    }
46
47    pub fn deleted_row_ids(&self) -> &[u64] {
48        &self.deleted_row_ids
49    }
50
51    pub fn updated_rows(&self) -> &HashMap<u64, Vec<u8>> {
52        &self.updated_rows
53    }
54
55    /// Number of buffered mutations.
56    pub fn len(&self) -> usize {
57        self.inserted_rows.len() + self.deleted_row_ids.len() + self.updated_rows.len()
58    }
59
60    pub fn is_empty(&self) -> bool {
61        self.inserted_rows.is_empty() && self.deleted_row_ids.is_empty() && self.updated_rows.is_empty()
62    }
63
64    /// Flush this table's buffered data to a `NodeTable`, returning undo records.
65    ///
66    /// Deserialises each buffered row from binary format and calls
67    /// `node_table.insert_row_with_txn()`.
68    /// When `txn_id` is `Some(...)`, inserts/deletes are recorded in VersionInfo.
69    /// Returns undo records so the caller can store them for potential rollback.
70    pub fn flush_to_node_table(
71        &self,
72        table_id: u64,
73        node_table: &mut NodeTable,
74        txn_id: Option<u64>,
75    ) -> Result<Vec<UndoRecord>, StorageError> {
76        use crate::column::Column;
77        let mut undo_records = Vec::new();
78
79        for row_bytes in &self.inserted_rows {
80            let values = crate::deserialize_values_from_bytes(row_bytes, node_table.columns.len());
81            let row_id = node_table.insert_row_with_txn(values, txn_id)?;
82            if txn_id.is_some() {
83                undo_records.push(UndoRecord::insert(table_id, row_id));
84            }
85        }
86
87        for row_id in &self.deleted_row_ids {
88            let num_cols = node_table.columns.len();
89            let mut old_row_data = Vec::new();
90            for col_idx in 0..num_cols {
91                let val = node_table
92                    .get_value(*row_id as usize, col_idx)
93                    .cloned()
94                    .unwrap_or(akar_common::types::Value::Null);
95                old_row_data.extend_from_slice(&Column::serialize_value(&val));
96            }
97            node_table.delete_row_with_txn(*row_id, txn_id)?;
98            if txn_id.is_some() {
99                undo_records.push(UndoRecord::delete(table_id, *row_id, old_row_data));
100            }
101        }
102
103        for (row_id, row_bytes) in &self.updated_rows {
104            let values = crate::deserialize_values_from_bytes(row_bytes, node_table.columns.len());
105            for (col_idx, val) in values.into_iter().enumerate() {
106                node_table.update_cell(*row_id, col_idx, val)?;
107            }
108        }
109
110        Ok(undo_records)
111    }
112
113    /// Flush this table's buffered data to a `RelTable`.
114    pub fn flush_to_rel_table(&self, rel_table: &mut RelTable) -> Result<(), StorageError> {
115        for row_bytes in &self.inserted_rows {
116            let values = crate::deserialize_values_from_bytes(row_bytes, rel_table.columns.len());
117            rel_table.insert_row(values)?;
118        }
119
120        for (row_id, row_bytes) in &self.updated_rows {
121            let values = crate::deserialize_values_from_bytes(row_bytes, rel_table.columns.len());
122            for (col_idx, val) in values.into_iter().enumerate() {
123                rel_table.update_cell(*row_id as usize, col_idx, val)?;
124            }
125        }
126
127        for row_id in &self.deleted_row_ids {
128            rel_table.delete_edge(*row_id as usize)?;
129        }
130
131        Ok(())
132    }
133
134    pub fn clear(&mut self) {
135        self.inserted_rows.clear();
136        self.deleted_row_ids.clear();
137        self.updated_rows.clear();
138    }
139}
140
141/// Per-transaction local storage for all modified tables.
142#[derive(Debug, Default)]
143pub struct LocalStorage {
144    tables: HashMap<u64, LocalTableData>,
145}
146
147impl LocalStorage {
148    pub fn new() -> Self {
149        Self::default()
150    }
151
152    pub fn get_or_create_table(&mut self, table_id: u64) -> &mut LocalTableData {
153        self.tables.entry(table_id).or_default()
154    }
155
156    pub fn get_table(&self, table_id: u64) -> Option<&LocalTableData> {
157        self.tables.get(&table_id)
158    }
159
160    /// Number of tables with buffered data.
161    pub fn len(&self) -> usize {
162        self.tables.len()
163    }
164
165    pub fn is_empty(&self) -> bool {
166        self.tables.is_empty()
167    }
168
169    /// Flush all buffered writes to the actual tables via the `TableCatalog`.
170    ///
171    /// Called on commit. After a successful flush, the transaction's writes
172    /// are visible to subsequent transactions.
173    /// When `txn_id` is `Some(...)`, inserts/deletes are recorded in VersionInfo.
174    /// Returns undo records for potential rollback.
175    pub fn flush_to_tables(
176        &self,
177        catalog: &Arc<TableCatalog>,
178        txn_id: Option<u64>,
179    ) -> Result<Vec<UndoRecord>, StorageError> {
180        let mut all_undo_records = Vec::new();
181        for (&table_id, table_data) in &self.tables {
182            if table_data.is_empty() {
183                continue;
184            }
185            if let Some(mut node_table) = catalog.get_node_table_mut(table_id) {
186                let undo_records = table_data.flush_to_node_table(table_id, &mut node_table, txn_id)?;
187                all_undo_records.extend(undo_records);
188            } else if let Some(mut rel_table) = catalog.get_rel_table_mut(table_id) {
189                table_data.flush_to_rel_table(&mut rel_table)?;
190            }
191        }
192        Ok(all_undo_records)
193    }
194
195    /// Clear all buffered data (called on rollback).
196    pub fn clear(&mut self) {
197        self.tables.clear();
198    }
199}