vibesql_storage/
lib.rs

1//! Storage - In-Memory Data Storage
2//!
3//! This crate provides in-memory storage for database tables and rows.
4
5// Allow approximate constants in tests (e.g., 3.14 for PI) as they are test data values
6#![cfg_attr(test, allow(clippy::approx_constant))]
7
8pub mod backend;
9pub mod blob;
10pub mod btree;
11pub mod buffer;
12pub mod change_events;
13pub mod columnar;
14pub mod columnar_cache;
15pub mod database;
16pub mod error;
17pub mod index;
18pub mod page;
19pub mod persistence;
20pub mod progress;
21pub mod query_buffer_pool;
22pub mod row;
23pub mod statistics;
24pub mod table;
25pub mod wal;
26
27// Platform-specific exports
28#[cfg(not(target_arch = "wasm32"))]
29pub use backend::{NativeFile, NativeStorage};
30#[cfg(target_arch = "wasm32")]
31pub use backend::{OpfsFile, OpfsStorage};
32pub use backend::{StorageBackend, StorageFile};
33pub use blob::{BlobId, BlobMetadata, BlobStorageConfig, BlobStorageService};
34pub use buffer::{BufferPool, BufferPoolStats};
35pub use change_events::{
36    channel as change_event_channel, ChangeEvent, ChangeEventReceiver, ChangeEventSender,
37    RecvError as ChangeEventRecvError, DEFAULT_CHANNEL_CAPACITY,
38};
39pub use columnar::{ColumnData, ColumnarTable};
40pub use columnar_cache::{CacheStats, ColumnarCache};
41pub use database::{
42    print_delete_profile_summary, reset_delete_profile_stats, Database, DatabaseConfig,
43    DeleteProfileStats, IndexData, IndexManager, IndexMetadata, OwnedStreamingRangeScan,
44    SpatialIndexMetadata, SpillPolicy, TransactionState, DELETE_PROFILE_STATS,
45};
46pub use error::{StorageError, StorageResult};
47pub use index::{extract_mbr_from_sql_value, SpatialIndex, SpatialIndexEntry};
48pub use persistence::load::{parse_sql_statements, read_sql_dump};
49pub use query_buffer_pool::{
50    QueryBufferPool, QueryBufferPoolStats, RowBufferGuard, ValueBufferGuard,
51};
52pub use row::{Row, RowValues, ROW_INLINE_CAPACITY};
53pub use statistics::{ColumnStatistics, TableIndexInfo, TableStatistics};
54pub use table::{DeleteResult, Table};
55pub use wal::{
56    DurabilityConfig, DurabilityMode, Lsn, PersistenceConfig, PersistenceEngine, PersistenceStats,
57    TransactionDurability, WalEntry, WalOp, WalOpTag,
58};
59
60#[cfg(test)]
61mod tests {
62    use vibesql_catalog::{ColumnSchema, TableSchema};
63    use vibesql_types::{DataType, SqlValue};
64
65    use super::*;
66    use crate::Row;
67
68    #[test]
69    fn test_hash_indexes_primary_key() {
70        let schema = TableSchema::with_primary_key(
71            "users".to_string(),
72            vec![
73                ColumnSchema::new("id".to_string(), DataType::Integer, false),
74                ColumnSchema::new(
75                    "name".to_string(),
76                    DataType::Varchar { max_length: Some(100) },
77                    false,
78                ),
79            ],
80            vec!["id".to_string()],
81        );
82
83        let mut table = Table::new(schema);
84
85        // Insert some rows
86        for i in 0..10 {
87            let row = Row::new(vec![
88                SqlValue::Integer(i),
89                SqlValue::Varchar(arcstr::ArcStr::from(format!("User {}", i))),
90            ]);
91            table.insert(row).unwrap();
92        }
93
94        // Check that primary key index exists and has entries
95        assert!(table.primary_key_index().is_some());
96        assert_eq!(table.primary_key_index().as_ref().unwrap().len(), 10);
97
98        // Try to insert duplicate - should work at table level (constraint check is in executor)
99        let duplicate_row = Row::new(vec![
100            SqlValue::Integer(0),
101            SqlValue::Varchar(arcstr::ArcStr::from("Duplicate User")),
102        ]);
103        table.insert(duplicate_row).unwrap(); // This succeeds because constraint checking is in
104                                              // executor
105    }
106
107    #[test]
108    fn test_hash_indexes_unique_constraints() {
109        let schema = TableSchema::with_unique_constraints(
110            "products".to_string(),
111            vec![
112                ColumnSchema::new("id".to_string(), DataType::Integer, false),
113                ColumnSchema::new(
114                    "sku".to_string(),
115                    DataType::Varchar { max_length: Some(50) },
116                    false,
117                ),
118            ],
119            vec![vec!["sku".to_string()]], // Unique constraint on sku
120        );
121
122        let mut table = Table::new(schema);
123
124        // Insert some rows
125        for i in 0..5 {
126            let row = Row::new(vec![
127                SqlValue::Integer(i),
128                SqlValue::Varchar(arcstr::ArcStr::from(format!("SKU{}", i))),
129            ]);
130            table.insert(row).unwrap();
131        }
132
133        // Check that unique index exists and has entries
134        assert_eq!(table.unique_indexes().len(), 1);
135        assert_eq!(table.unique_indexes()[0].len(), 5);
136    }
137
138    #[test]
139    fn test_update_row_selective_non_indexed_column() {
140        // Create table with primary key and unique constraint
141        let schema = TableSchema::with_all_constraints(
142            "users".to_string(),
143            vec![
144                ColumnSchema::new("id".to_string(), DataType::Integer, false),
145                ColumnSchema::new(
146                    "email".to_string(),
147                    DataType::Varchar { max_length: Some(100) },
148                    false,
149                ),
150                ColumnSchema::new(
151                    "name".to_string(),
152                    DataType::Varchar { max_length: Some(100) },
153                    false,
154                ),
155            ],
156            Some(vec!["id".to_string()]),
157            vec![vec!["email".to_string()]],
158        );
159        let mut table = Table::new(schema);
160
161        // Insert initial row
162        let row1 = Row::new(vec![
163            SqlValue::Integer(1),
164            SqlValue::Varchar(arcstr::ArcStr::from("alice@example.com")),
165            SqlValue::Varchar(arcstr::ArcStr::from("Alice")),
166        ]);
167        table.insert(row1).unwrap();
168
169        // Update only the 'name' column (non-indexed)
170        let updated_row = Row::new(vec![
171            SqlValue::Integer(1),
172            SqlValue::Varchar(arcstr::ArcStr::from("alice@example.com")),
173            SqlValue::Varchar(arcstr::ArcStr::from("Alice Smith")),
174        ]);
175        let mut changed_columns = std::collections::HashSet::new();
176        changed_columns.insert(2); // 'name' column index
177
178        table.update_row_selective(0, updated_row, &changed_columns).unwrap();
179
180        // Verify row was updated
181        let row = table.scan().iter().next().unwrap();
182        assert_eq!(row.get(2), Some(&SqlValue::Varchar(arcstr::ArcStr::from("Alice Smith"))));
183    }
184
185    #[test]
186    fn test_update_row_selective_primary_key_column() {
187        // Create table with primary key
188        let schema = TableSchema::with_primary_key(
189            "users".to_string(),
190            vec![
191                ColumnSchema::new("id".to_string(), DataType::Integer, false),
192                ColumnSchema::new(
193                    "name".to_string(),
194                    DataType::Varchar { max_length: Some(100) },
195                    false,
196                ),
197            ],
198            vec!["id".to_string()],
199        );
200        let mut table = Table::new(schema);
201
202        // Insert initial rows
203        table
204            .insert(Row::new(vec![
205                SqlValue::Integer(1),
206                SqlValue::Varchar(arcstr::ArcStr::from("Alice")),
207            ]))
208            .unwrap();
209        table
210            .insert(Row::new(vec![
211                SqlValue::Integer(2),
212                SqlValue::Varchar(arcstr::ArcStr::from("Bob")),
213            ]))
214            .unwrap();
215
216        // Update primary key column
217        let updated_row = Row::new(vec![
218            SqlValue::Integer(10), // Changed PK
219            SqlValue::Varchar(arcstr::ArcStr::from("Alice")),
220        ]);
221        let mut changed_columns = std::collections::HashSet::new();
222        changed_columns.insert(0); // 'id' column index
223
224        table.update_row_selective(0, updated_row, &changed_columns).unwrap();
225
226        // Verify primary key index was updated
227        assert_eq!(table.row_count(), 2);
228        let row = table.scan().iter().next().unwrap();
229        assert_eq!(row.get(0), Some(&SqlValue::Integer(10)));
230    }
231
232    #[test]
233    fn test_update_row_selective_unique_constraint_column() {
234        // Create table with unique constraint
235        let schema = TableSchema::with_unique_constraints(
236            "users".to_string(),
237            vec![
238                ColumnSchema::new("id".to_string(), DataType::Integer, false),
239                ColumnSchema::new(
240                    "email".to_string(),
241                    DataType::Varchar { max_length: Some(100) },
242                    false,
243                ),
244                ColumnSchema::new(
245                    "name".to_string(),
246                    DataType::Varchar { max_length: Some(100) },
247                    false,
248                ),
249            ],
250            vec![vec!["email".to_string()]],
251        );
252        let mut table = Table::new(schema);
253
254        // Insert initial rows
255        table
256            .insert(Row::new(vec![
257                SqlValue::Integer(1),
258                SqlValue::Varchar(arcstr::ArcStr::from("alice@example.com")),
259                SqlValue::Varchar(arcstr::ArcStr::from("Alice")),
260            ]))
261            .unwrap();
262
263        // Update unique constraint column
264        let updated_row = Row::new(vec![
265            SqlValue::Integer(1),
266            SqlValue::Varchar(arcstr::ArcStr::from("alice.smith@example.com")), // Changed email
267            SqlValue::Varchar(arcstr::ArcStr::from("Alice")),
268        ]);
269        let mut changed_columns = std::collections::HashSet::new();
270        changed_columns.insert(1); // 'email' column index
271
272        table.update_row_selective(0, updated_row, &changed_columns).unwrap();
273
274        // Verify unique index was updated
275        let row = table.scan().iter().next().unwrap();
276        assert_eq!(
277            row.get(1),
278            Some(&SqlValue::Varchar(arcstr::ArcStr::from("alice.smith@example.com")))
279        );
280    }
281
282    #[test]
283    fn test_update_row_selective_vs_full_correctness() {
284        // Verify both methods produce the same result
285        let schema1 = TableSchema::with_all_constraints(
286            "users".to_string(),
287            vec![
288                ColumnSchema::new("id".to_string(), DataType::Integer, false),
289                ColumnSchema::new(
290                    "email".to_string(),
291                    DataType::Varchar { max_length: Some(100) },
292                    false,
293                ),
294                ColumnSchema::new(
295                    "name".to_string(),
296                    DataType::Varchar { max_length: Some(100) },
297                    false,
298                ),
299            ],
300            Some(vec!["id".to_string()]),
301            vec![vec!["email".to_string()]],
302        );
303        let mut table1 = Table::new(schema1.clone());
304
305        let schema2 = schema1.clone();
306        let mut table2 = Table::new(schema2);
307
308        // Insert same initial row into both tables
309        let initial_row = Row::new(vec![
310            SqlValue::Integer(1),
311            SqlValue::Varchar(arcstr::ArcStr::from("alice@example.com")),
312            SqlValue::Varchar(arcstr::ArcStr::from("Alice")),
313        ]);
314        table1.insert(initial_row.clone()).unwrap();
315        table2.insert(initial_row).unwrap();
316
317        // Update with selective method
318        let updated_row1 = Row::new(vec![
319            SqlValue::Integer(1),
320            SqlValue::Varchar(arcstr::ArcStr::from("alice@example.com")),
321            SqlValue::Varchar(arcstr::ArcStr::from("Alice Smith")),
322        ]);
323        let mut changed_columns = std::collections::HashSet::new();
324        changed_columns.insert(2); // 'name' column
325        table1.update_row_selective(0, updated_row1.clone(), &changed_columns).unwrap();
326
327        // Update with full method
328        table2.update_row(0, updated_row1).unwrap();
329
330        // Both tables should be identical
331        let row1 = table1.scan().iter().next().unwrap();
332        let row2 = table2.scan().iter().next().unwrap();
333        assert_eq!(row1.get(0), row2.get(0));
334        assert_eq!(row1.get(1), row2.get(1));
335        assert_eq!(row1.get(2), row2.get(2));
336    }
337}