vibesql_storage/database/
persistence_api.rs

1// ============================================================================
2// WAL Persistence API
3// ============================================================================
4//
5// This module provides WAL (Write-Ahead Log) persistence methods for the
6// Database struct. Enables durable storage through async persistence.
7
8use super::Database;
9use crate::wal::{PersistenceEngine, WalOp};
10use crate::StorageError;
11
12impl Database {
13    // ============================================================================
14    // WAL Persistence Support
15    // ============================================================================
16
17    /// Enable WAL-based async persistence
18    ///
19    /// Creates a persistence engine that writes changes to a WAL file in the background.
20    /// All subsequent DML and DDL operations will be logged to the WAL for durability.
21    ///
22    /// # Arguments
23    /// * `engine` - A pre-configured PersistenceEngine instance
24    ///
25    /// # Example
26    /// ```text
27    /// use vibesql_storage::{Database, PersistenceEngine, PersistenceConfig};
28    ///
29    /// let mut db = Database::new();
30    /// let engine = PersistenceEngine::new("/path/to/wal.log", PersistenceConfig::default())?;
31    /// db.enable_persistence(engine);
32    /// ```
33    pub fn enable_persistence(&mut self, engine: PersistenceEngine) {
34        self.persistence_engine = Some(engine);
35    }
36
37    /// Check if WAL persistence is enabled
38    pub fn persistence_enabled(&self) -> bool {
39        self.persistence_engine.is_some()
40    }
41
42    /// Get persistence statistics (if enabled)
43    pub fn persistence_stats(&self) -> Option<crate::wal::PersistenceStats> {
44        self.persistence_engine.as_ref().map(|e| e.stats())
45    }
46
47    /// Emit a WAL operation to the persistence engine (if enabled)
48    ///
49    /// This is a no-op if persistence is not enabled, providing zero overhead
50    /// when WAL is disabled.
51    pub(super) fn emit_wal_op(&self, op: WalOp) {
52        if let Some(engine) = &self.persistence_engine {
53            if let Err(e) = engine.send(op) {
54                log::error!("Failed to emit WAL op: {}", e);
55            }
56        }
57    }
58
59    /// Get the next table ID and increment the counter
60    pub(super) fn next_table_id(&mut self) -> u32 {
61        let id = self.next_table_id;
62        self.next_table_id += 1;
63        id
64    }
65
66    /// Compute a table ID from table name using hash (for consistent mapping)
67    ///
68    /// This is used when we don't have a monotonic table ID assigned at creation time,
69    /// such as for tables created before WAL was enabled.
70    pub(super) fn table_name_to_id(&self, name: &str) -> u32 {
71        use std::hash::{Hash, Hasher};
72        let mut hasher = std::collections::hash_map::DefaultHasher::new();
73        name.hash(&mut hasher);
74        hasher.finish() as u32
75    }
76
77    /// Sync all pending WAL entries to disk
78    ///
79    /// Blocks until all pending entries have been written and flushed.
80    /// This is useful for ensuring durability before returning to the user.
81    pub fn sync_persistence(&self) -> Result<(), StorageError> {
82        if let Some(engine) = &self.persistence_engine {
83            engine.sync()
84        } else {
85            Ok(())
86        }
87    }
88
89    /// Emit a WAL delete entry for persistence
90    ///
91    /// Called by the DELETE executor before rows are removed.
92    /// Captures old_values for recovery replay.
93    pub fn emit_wal_delete(
94        &self,
95        table_name: &str,
96        row_id: u64,
97        old_values: Vec<vibesql_types::SqlValue>,
98    ) {
99        self.emit_wal_op(WalOp::Delete {
100            table_id: self.table_name_to_id(table_name),
101            row_id,
102            old_values,
103        });
104    }
105
106    /// Emit a WAL create index entry for persistence
107    ///
108    /// Called by the CREATE INDEX executor after index is created.
109    pub fn emit_wal_create_index(
110        &self,
111        index_id: u32,
112        index_name: &str,
113        table_name: &str,
114        column_indices: Vec<u32>,
115        is_unique: bool,
116    ) {
117        self.emit_wal_op(WalOp::CreateIndex {
118            index_id,
119            index_name: index_name.to_string(),
120            table_id: self.table_name_to_id(table_name),
121            column_indices,
122            is_unique,
123        });
124    }
125
126    /// Emit a WAL drop index entry for persistence
127    ///
128    /// Called by the DROP INDEX executor before index is dropped.
129    pub fn emit_wal_drop_index(&self, index_id: u32, index_name: &str) {
130        self.emit_wal_op(WalOp::DropIndex { index_id, index_name: index_name.to_string() });
131    }
132
133    // ============================================================================
134    // AUTO_INCREMENT / LAST_INSERT_ROWID Support
135    // ============================================================================
136
137    /// Get the last auto-generated ID from an INSERT operation
138    ///
139    /// Returns the most recent value generated by AUTO_INCREMENT during an INSERT.
140    /// This is used to implement LAST_INSERT_ROWID() and LAST_INSERT_ID() functions.
141    ///
142    /// Returns 0 if no auto-generated values have been produced yet.
143    ///
144    /// # Example
145    /// ```text
146    /// // Create table with AUTO_INCREMENT
147    /// db.execute("CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100))")?;
148    ///
149    /// // Insert a row (ID is auto-generated)
150    /// db.execute("INSERT INTO users (name) VALUES ('Alice')")?;
151    ///
152    /// // Get the generated ID
153    /// let id = db.last_insert_rowid();
154    /// assert_eq!(id, 1);
155    /// ```
156    pub fn last_insert_rowid(&self) -> i64 {
157        self.last_insert_rowid
158    }
159
160    /// Set the last auto-generated ID
161    ///
162    /// This is called internally by the INSERT executor when a sequence value
163    /// is generated for an AUTO_INCREMENT column.
164    ///
165    /// For multi-row inserts, this will be the ID of the *first* row inserted
166    /// (following MySQL semantics for batch inserts).
167    pub fn set_last_insert_rowid(&mut self, id: i64) {
168        self.last_insert_rowid = id;
169    }
170
171    // ============================================================================
172    // changes() Support (Row Modification Count)
173    // ============================================================================
174
175    /// Get the number of rows changed by the last INSERT/UPDATE/DELETE statement
176    ///
177    /// Returns the count of rows affected by the most recent DML operation.
178    /// This is used to implement the SQLite changes() function.
179    ///
180    /// Returns 0 if no DML operations have been performed yet.
181    ///
182    /// # Example
183    /// ```text
184    /// // Insert multiple rows
185    /// db.execute("INSERT INTO users (name) VALUES ('Alice'), ('Bob'), ('Carol')")?;
186    ///
187    /// // Get the number of rows inserted
188    /// let changes = db.last_changes_count();
189    /// assert_eq!(changes, 3);
190    ///
191    /// // Delete some rows
192    /// db.execute("DELETE FROM users WHERE name = 'Alice'")?;
193    /// assert_eq!(db.last_changes_count(), 1);
194    /// ```
195    pub fn last_changes_count(&self) -> usize {
196        self.last_changes_count
197    }
198
199    /// Set the number of rows changed by the last DML statement
200    ///
201    /// This is called internally by INSERT, UPDATE, and DELETE executors
202    /// after completing their operations.
203    pub fn set_last_changes_count(&mut self, count: usize) {
204        self.last_changes_count = count;
205    }
206
207    // ============================================================================
208    // total_changes() Support (Cumulative Row Modification Count)
209    // ============================================================================
210
211    /// Get the total number of rows changed since the database connection was opened
212    ///
213    /// Returns the cumulative count of rows affected by all INSERT, UPDATE, and DELETE
214    /// operations since the database was created. This is used to implement the
215    /// SQLite total_changes() function.
216    ///
217    /// Returns 0 for a new database connection.
218    ///
219    /// # Example
220    /// ```text
221    /// // Insert rows
222    /// db.execute("INSERT INTO users (name) VALUES ('Alice'), ('Bob')")?;
223    /// assert_eq!(db.last_changes_count(), 2);  // Last operation: 2 rows
224    ///
225    /// // Delete a row
226    /// db.execute("DELETE FROM users WHERE name = 'Alice'")?;
227    /// assert_eq!(db.last_changes_count(), 1);  // Last operation: 1 row
228    ///
229    /// // Total changes accumulates
230    /// assert_eq!(db.total_changes_count(), 3); // 2 + 1 = 3 rows total
231    /// ```
232    pub fn total_changes_count(&self) -> usize {
233        self.total_changes_count
234    }
235
236    /// Increment the total changes count by the specified amount
237    ///
238    /// This is called internally by INSERT, UPDATE, and DELETE executors
239    /// after completing their operations, in addition to set_last_changes_count().
240    pub fn increment_total_changes_count(&mut self, count: usize) {
241        self.total_changes_count += count;
242    }
243
244    // ============================================================================
245    // sqlite_search_count Support (TCL Test Compatibility)
246    // ============================================================================
247
248    /// Get the current search count
249    ///
250    /// Returns the number of rows examined during query execution.
251    /// This is used to implement sqlite_search_count() for TCL test compatibility.
252    ///
253    /// In SQLite, this tracks "MoveTo" and "Next" VDBE operations.
254    /// In VibeSQL, this tracks rows read during table/index scans.
255    ///
256    /// # Example
257    /// ```text
258    /// // Reset before query
259    /// db.reset_search_count();
260    ///
261    /// // Execute query...
262    /// db.execute("SELECT * FROM users WHERE id = 1")?;
263    ///
264    /// // Get count of rows examined
265    /// let count = db.search_count();
266    /// ```
267    pub fn search_count(&self) -> u64 {
268        self.search_count.load(std::sync::atomic::Ordering::Relaxed)
269    }
270
271    /// Reset the search count to zero
272    ///
273    /// Call this before executing a query to measure how many rows
274    /// were examined by that specific query.
275    pub fn reset_search_count(&self) {
276        self.search_count.store(0, std::sync::atomic::Ordering::Relaxed);
277    }
278
279    /// Increment the search count by a specified amount
280    ///
281    /// Called internally by the executor when rows are examined during
282    /// table scans, index scans, or other row-reading operations.
283    ///
284    /// # Arguments
285    /// * `count` - Number of rows examined (typically 1 for row-by-row, or batch size for columnar)
286    pub fn increment_search_count(&self, count: u64) {
287        self.search_count.fetch_add(count, std::sync::atomic::Ordering::Relaxed);
288    }
289}