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}