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