vibesql_executor/
persistence.rs1use std::path::Path;
8
9use vibesql_storage::Database;
10
11use crate::{
12 CreateIndexExecutor, CreateTableExecutor, ExecutorError, InsertExecutor, RoleExecutor,
13 SchemaExecutor, UpdateExecutor, ViewExecutor,
14};
15
16pub fn load_sql_dump<P: AsRef<Path>>(path: P) -> Result<Database, ExecutorError> {
40 let sql_content = vibesql_storage::read_sql_dump(&path).map_err(|e| {
42 ExecutorError::Other(format!(
43 "Failed to read database file {:?}: {}",
44 path.as_ref(),
45 e
46 ))
47 })?;
48
49 let statements = vibesql_storage::parse_sql_statements(&sql_content).map_err(|e| {
51 ExecutorError::Other(format!("Failed to parse SQL dump: {}", e))
52 })?;
53
54 let mut db = Database::new();
56
57 for (idx, stmt_sql) in statements.iter().enumerate() {
59 let trimmed = stmt_sql.trim();
61 if trimmed.is_empty() || trimmed.starts_with("--") {
62 continue;
63 }
64
65 let statement = vibesql_parser::Parser::parse_sql(trimmed).map_err(|e| {
67 ExecutorError::Other(format!(
68 "Failed to parse statement {} in {:?}: {}\nStatement: {}",
69 idx + 1,
70 path.as_ref(),
71 e,
72 truncate_for_error(trimmed, 100)
73 ))
74 })?;
75
76 execute_statement_for_load(&mut db, statement).map_err(|e| {
78 ExecutorError::Other(format!(
79 "Failed to execute statement {} in {:?}: {}\nStatement: {}",
80 idx + 1,
81 path.as_ref(),
82 e,
83 truncate_for_error(trimmed, 100)
84 ))
85 })?;
86 }
87
88 Ok(db)
89}
90
91fn execute_statement_for_load(
96 db: &mut Database,
97 statement: vibesql_ast::Statement,
98) -> Result<(), ExecutorError> {
99 match statement {
100 vibesql_ast::Statement::CreateSchema(schema_stmt) => {
101 SchemaExecutor::execute_create_schema(&schema_stmt, db)?;
102 }
103 vibesql_ast::Statement::CreateTable(create_stmt) => {
104 CreateTableExecutor::execute(&create_stmt, db)?;
105 }
106 vibesql_ast::Statement::CreateIndex(index_stmt) => {
107 CreateIndexExecutor::execute(&index_stmt, db)?;
108 }
109 vibesql_ast::Statement::CreateView(view_stmt) => {
110 ViewExecutor::execute_create_view(&view_stmt, db)?;
111 }
112 vibesql_ast::Statement::CreateRole(role_stmt) => {
113 RoleExecutor::execute_create_role(&role_stmt, db)?;
114 }
115 vibesql_ast::Statement::Insert(insert_stmt) => {
116 InsertExecutor::execute(db, &insert_stmt)?;
117 }
118 vibesql_ast::Statement::Update(update_stmt) => {
119 UpdateExecutor::execute(&update_stmt, db)?;
120 }
121 _ => {
122 return Err(ExecutorError::Other(format!(
123 "Statement type not supported in database load: {:?}",
124 statement
125 )));
126 }
127 }
128 Ok(())
129}
130
131fn truncate_for_error(s: &str, max_len: usize) -> String {
133 if s.len() <= max_len {
134 s.to_string()
135 } else {
136 format!("{}...", &s[..max_len])
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use std::fs;
144 use std::io::Write;
145
146 #[test]
147 fn test_load_simple_database() {
148 let temp_file = "/tmp/test_load_simple.sql";
150 let sql_dump = r#"
151-- Test database
152CREATE TABLE users (id INTEGER, name VARCHAR(50));
153INSERT INTO users VALUES (1, 'Alice');
154INSERT INTO users VALUES (2, 'Bob');
155"#;
156
157 fs::write(temp_file, sql_dump).unwrap();
158
159 let db = load_sql_dump(temp_file).unwrap();
161
162 assert!(db.get_table("USERS").is_some());
164
165 let table = db.get_table("USERS").unwrap();
167 assert_eq!(table.row_count(), 2);
168
169 fs::remove_file(temp_file).unwrap();
171 }
172
173 #[test]
174 fn test_load_with_schema() {
175 let temp_file = "/tmp/test_load_schema.sql";
176 let sql_dump = r#"
179CREATE SCHEMA test_schema;
180CREATE TABLE test_schema.products (id INTEGER, price REAL);
181"#;
182
183 fs::write(temp_file, sql_dump).unwrap();
184
185 let db = load_sql_dump(temp_file).unwrap();
186
187 assert!(db.get_table("TEST_SCHEMA.PRODUCTS").is_some());
189
190 fs::remove_file(temp_file).unwrap();
191 }
192
193 #[test]
194 fn test_load_nonexistent_file() {
195 let result = load_sql_dump("/tmp/nonexistent_file.sql");
196 assert!(result.is_err());
197 assert!(result
198 .unwrap_err()
199 .to_string()
200 .contains("does not exist"));
201 }
202
203 #[test]
204 fn test_load_invalid_sql() {
205 let temp_file = "/tmp/test_load_invalid.sql";
206 fs::write(temp_file, "THIS IS NOT VALID SQL;").unwrap();
207
208 let result = load_sql_dump(temp_file);
209 assert!(result.is_err());
210 assert!(result
211 .unwrap_err()
212 .to_string()
213 .contains("Failed to parse"));
214
215 fs::remove_file(temp_file).unwrap();
216 }
217
218 #[test]
219 fn test_load_binary_file_error() {
220 let temp_file = "/tmp/test_load_binary.db";
221 let mut file = fs::File::create(temp_file).unwrap();
222 file.write_all(b"SQLite format 3\0").unwrap();
223 file.write_all(&[0xFF, 0xFE, 0xFD]).unwrap();
224
225 let result = load_sql_dump(temp_file);
226 assert!(result.is_err());
227 assert!(result
228 .unwrap_err()
229 .to_string()
230 .contains("binary SQLite database"));
231
232 fs::remove_file(temp_file).unwrap();
233 }
234
235 #[test]
236 fn test_load_with_indexes() {
237 let temp_file = "/tmp/test_load_indexes.sql";
238 let sql_dump = r#"
239CREATE TABLE employees (id INTEGER, name VARCHAR(100), dept VARCHAR(50));
240INSERT INTO employees VALUES (1, 'Alice', 'Engineering');
241INSERT INTO employees VALUES (2, 'Bob', 'Sales');
242CREATE INDEX idx_dept ON employees (dept Asc);
243"#;
244
245 fs::write(temp_file, sql_dump).unwrap();
246
247 let db = load_sql_dump(temp_file).unwrap();
248
249 assert!(db.get_table("EMPLOYEES").is_some());
251 let table = db.get_table("EMPLOYEES").unwrap();
252 assert_eq!(table.row_count(), 2);
253
254 assert!(db.get_index("IDX_DEPT").is_some());
256
257 fs::remove_file(temp_file).unwrap();
258 }
259
260 #[test]
261 fn test_load_with_roles() {
262 let temp_file = "/tmp/test_load_roles.sql";
263 let sql_dump = r#"
264CREATE ROLE admin;
265CREATE ROLE user;
266CREATE TABLE data (id INTEGER);
267"#;
268
269 fs::write(temp_file, sql_dump).unwrap();
270
271 let db = load_sql_dump(temp_file).unwrap();
272
273 assert!(db.get_table("DATA").is_some());
275
276 fs::remove_file(temp_file).unwrap();
277 }
278}