1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
// ============================================================================
// Database Constructors and Initialization
// ============================================================================
#![allow(clippy::clone_on_copy)]
use std::{
collections::HashMap,
path::PathBuf,
sync::{atomic::AtomicU64, Arc},
};
use super::{
config::{DatabaseConfig, DEFAULT_COLUMNAR_CACHE_BUDGET},
core::Database,
lifecycle::Lifecycle,
metadata::Metadata,
operations::Operations,
};
use crate::{columnar_cache::ColumnarCache, QueryBufferPool};
impl Clone for Database {
fn clone(&self) -> Self {
Database {
catalog: self.catalog.clone(),
lifecycle: self.lifecycle.clone(),
metadata: self.metadata.clone(),
operations: self.operations.clone(),
tables: self.tables.clone(),
sql_mode: self.sql_mode.clone(),
query_buffer_pool: self.query_buffer_pool.clone(),
// Clone creates a new cache with same config but empty data
// This is intentional - cloned databases shouldn't share cache state
columnar_cache: Arc::new(ColumnarCache::new(self.columnar_cache.max_memory())),
// Clone does not inherit change event sender - cloned databases are independent
change_sender: None,
// Clone resets last_insert_rowid - each database instance tracks independently
last_insert_rowid: 0,
// Clone resets last_changes_count - each database instance tracks independently
last_changes_count: 0,
// Clone resets total_changes_count - each database instance tracks independently
total_changes_count: 0,
// Clone resets search_count - each database instance tracks independently
search_count: AtomicU64::new(0),
// Clone does not inherit persistence engine - cloned databases are independent
persistence_engine: None,
// Preserve table ID counter for consistency
next_table_id: self.next_table_id,
// Clone resets reserved rowids - each database instance tracks independently
reserved_rowids: HashMap::new(),
}
}
}
impl Database {
/// Create a new empty database
///
/// Note: Security is disabled by default for backward compatibility with existing code.
/// Call `enable_security()` to turn on access control enforcement.
pub fn new() -> Self {
Database {
catalog: vibesql_catalog::Catalog::new(),
lifecycle: Lifecycle::new(),
metadata: Metadata::new(),
operations: Operations::new(),
tables: HashMap::new(),
sql_mode: vibesql_types::SqlMode::default(),
query_buffer_pool: QueryBufferPool::new(),
columnar_cache: Arc::new(ColumnarCache::new(DEFAULT_COLUMNAR_CACHE_BUDGET)),
change_sender: None,
last_insert_rowid: 0,
last_changes_count: 0,
total_changes_count: 0,
search_count: AtomicU64::new(0),
persistence_engine: None,
next_table_id: 1,
reserved_rowids: HashMap::new(),
}
}
/// Create a new database with a specific storage path
///
/// The provided path will be used as the root directory for database files.
/// Index files will be stored in `<path>/data/indexes/`.
///
/// # Example
/// ```rust
/// use std::path::PathBuf;
///
/// use vibesql_storage::Database;
///
/// let db = Database::with_path(PathBuf::from("/var/lib/myapp/db"));
/// // Index files will be stored in /var/lib/myapp/db/data/indexes/
/// ```
pub fn with_path(path: PathBuf) -> Self {
let mut db = Self::new();
db.operations.set_database_path(path.join("data"));
db
}
/// Create a new database with a specific configuration
///
/// Allows setting memory budgets, disk budgets, and spill policy for adaptive
/// index management.
///
/// # Example
/// ```rust
/// use vibesql_storage::{Database, DatabaseConfig};
///
/// // Browser environment with limited memory
/// let db = Database::with_config(DatabaseConfig::browser_default());
///
/// // Server environment with abundant memory
/// let db = Database::with_config(DatabaseConfig::server_default());
/// ```
pub fn with_config(config: DatabaseConfig) -> Self {
let columnar_cache_budget = config.columnar_cache_budget;
let mut db = Self::new();
db.sql_mode = config.sql_mode.clone();
db.columnar_cache = Arc::new(ColumnarCache::new(columnar_cache_budget));
db.operations.set_config(config);
db
}
/// Create a new database with both path and configuration
///
/// # Example
/// ```rust
/// use std::path::PathBuf;
///
/// use vibesql_storage::{Database, DatabaseConfig};
///
/// let db = Database::with_path_and_config(
/// PathBuf::from("/var/lib/myapp/db"),
/// DatabaseConfig::server_default(),
/// );
/// ```
pub fn with_path_and_config(path: PathBuf, config: DatabaseConfig) -> Self {
let columnar_cache_budget = config.columnar_cache_budget;
let mut db = Self::new();
db.sql_mode = config.sql_mode.clone();
db.columnar_cache = Arc::new(ColumnarCache::new(columnar_cache_budget));
db.operations.set_database_path(path.join("data"));
db.operations.set_config(config);
db
}
/// Create a new database with both path and configuration (async version for WASM)
///
/// This async version is required for WASM to properly initialize OPFS storage
/// without blocking the event loop.
///
/// # Example
/// ```rust,no_run
/// # use std::path::PathBuf;
/// # use vibesql_storage::{Database, DatabaseConfig};
/// # async fn example() {
/// let db = Database::with_path_and_config_async(
/// PathBuf::from("/vibesql-data"),
/// DatabaseConfig::browser_default(),
/// )
/// .await
/// .unwrap();
/// # }
/// ```
#[cfg(target_arch = "wasm32")]
pub async fn with_path_and_config_async(
path: PathBuf,
config: DatabaseConfig,
) -> Result<Self, crate::StorageError> {
let columnar_cache_budget = config.columnar_cache_budget;
let mut db = Self::new();
db.sql_mode = config.sql_mode.clone();
db.columnar_cache = Arc::new(ColumnarCache::new(columnar_cache_budget));
db.operations.set_database_path(path.join("data"));
db.operations.set_config(config);
// Initialize OPFS storage asynchronously
db.operations.init_opfs_async().await?;
Ok(db)
}
/// Reset the database to empty state (more efficient than creating a new instance).
///
/// Clears all tables, resets catalog to default state, and clears all indexes and transactions.
/// Useful for test scenarios where you need to reuse a Database instance.
/// Preserves database configuration (path, storage backend, memory budgets) across resets.
/// Note: Persistence engine is preserved (WAL remains active if enabled).
pub fn reset(&mut self) {
self.catalog = vibesql_catalog::Catalog::new();
self.lifecycle.reset();
self.metadata = Metadata::new();
// Reset operations in place to preserve database_path, storage backend, and config
self.operations.reset();
self.tables.clear();
// Clear the columnar cache
self.columnar_cache.clear();
// Reset table ID counter
self.next_table_id = 1;
}
}
impl Default for Database {
fn default() -> Self {
Self::new()
}
}