1use std::{
18 fs::{self, File},
19 io::{BufWriter, Write},
20 path::Path,
21};
22
23use crate::{Database, StorageError};
24
25impl Database {
26 pub fn save_sql_dump<P: AsRef<Path>>(&self, path: P) -> Result<(), StorageError> {
52 let path_ref = path.as_ref();
53
54 let temp_path = {
57 let parent = path_ref.parent().unwrap_or(Path::new("."));
58 let file_name = path_ref
59 .file_name()
60 .map(|s| s.to_string_lossy().to_string());
61 let temp_name = format!(
62 ".{}.tmp.{}",
63 file_name.unwrap_or_else(|| "database".to_string()),
64 std::process::id()
65 );
66 parent.join(temp_name)
67 };
68
69 let result = self.write_sql_dump_to_file(&temp_path);
71 if let Err(e) = &result {
72 let _ = fs::remove_file(&temp_path);
74 return Err(e.clone());
75 }
76
77 fs::rename(&temp_path, path_ref).map_err(|e| {
79 let _ = fs::remove_file(&temp_path);
81 StorageError::NotImplemented(format!("Failed to rename temp file to target: {}", e))
82 })?;
83
84 Ok(())
85 }
86
87 fn write_sql_dump_to_file(&self, path: &Path) -> Result<(), StorageError> {
89 let file = File::create(path).map_err(|e| {
90 StorageError::NotImplemented(format!("Failed to create temp file: {}", e))
91 })?;
92
93 let mut writer = BufWriter::new(file);
94
95 writeln!(writer, "-- VibeSQL Database Dump")
97 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
98 writeln!(writer, "-- Generated: {}", chrono::Utc::now())
99 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
100 writeln!(writer, "--")
101 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
102 writeln!(writer)
103 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
104
105 writeln!(writer, "-- Schemas")
107 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
108 for schema_name in &self.catalog.list_schemas() {
109 if schema_name != vibesql_catalog::DEFAULT_SCHEMA
112 && !vibesql_catalog::Catalog::is_temp_schema(schema_name)
113 {
114 writeln!(writer, "CREATE SCHEMA {};", schema_name)
115 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
116 }
117 }
118 writeln!(writer)
119 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
120
121 writeln!(writer, "-- Roles")
123 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
124 for role_name in &self.catalog.list_roles() {
125 writeln!(writer, "CREATE ROLE {};", role_name)
126 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
127 }
128 writeln!(writer)
129 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
130
131 writeln!(writer, "-- Tables and Data")
133 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
134
135 for schema_name in &self.catalog.list_schemas() {
139 if vibesql_catalog::Catalog::is_temp_schema(schema_name) {
141 continue;
142 }
143
144 let schema_tables = if let Some(schema) = self.catalog.get_schema(schema_name) {
146 schema.list_tables()
147 } else {
148 continue;
149 };
150
151 for table_name in &schema_tables {
152 let qualified_name = format!("{}.{}", schema_name, table_name);
154 let Some(table) = self.tables.get(&qualified_name) else {
155 continue;
156 };
157
158 let output_name = if schema_name == vibesql_catalog::DEFAULT_SCHEMA {
160 table_name.clone()
161 } else {
162 qualified_name.clone()
163 };
164 let schema = &table.schema;
166 write!(writer, "CREATE TABLE {} (", &output_name)
167 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
168
169 for (i, col) in schema.columns.iter().enumerate() {
170 if i > 0 {
171 write!(writer, ", ").map_err(|e| {
172 StorageError::NotImplemented(format!("Write error: {}", e))
173 })?;
174 }
175 let type_str = format_column_type(&col.data_type, col.is_exact_integer_type);
177 write!(writer, "{} {}", col.name, type_str)
178 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
179
180 if let Some(ref generated_expr) = col.generated_expr {
182 use vibesql_ast::pretty_print::ToSql;
183 write!(writer, " AS ({})", generated_expr.to_sql()).map_err(|e| {
184 StorageError::NotImplemented(format!("Write error: {}", e))
185 })?;
186 } else {
187 if let Some(ref default_expr) = col.default_value {
190 use vibesql_ast::pretty_print::ToSql;
191 write!(writer, " DEFAULT {}", default_expr.to_sql()).map_err(|e| {
192 StorageError::NotImplemented(format!("Write error: {}", e))
193 })?;
194 }
195 if let Some(ref collation) = col.collation {
197 write!(writer, " COLLATE {}", collation).map_err(|e| {
198 StorageError::NotImplemented(format!("Write error: {}", e))
199 })?;
200 }
201 if !col.nullable {
202 write!(writer, " NOT NULL").map_err(|e| {
203 StorageError::NotImplemented(format!("Write error: {}", e))
204 })?;
205 }
206 }
207 }
208
209 if let Some(pk_cols) = &schema.primary_key {
211 write!(writer, ", PRIMARY KEY ({})", pk_cols.join(", "))
212 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
213 }
214
215 for unique_cols in &schema.unique_constraints {
217 write!(writer, ", UNIQUE ({})", unique_cols.join(", "))
218 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
219 }
220
221 for (constraint_name, check_expr) in &schema.check_constraints {
226 use vibesql_ast::pretty_print::ToSql;
227 let expr_text = check_expr.to_sql();
228 if constraint_name != &expr_text {
231 write!(writer, ", CONSTRAINT {} CHECK ({})", constraint_name, expr_text)
232 .map_err(|e| {
233 StorageError::NotImplemented(format!("Write error: {}", e))
234 })?;
235 } else {
236 write!(writer, ", CHECK ({})", expr_text)
237 .map_err(|e| {
238 StorageError::NotImplemented(format!("Write error: {}", e))
239 })?;
240 }
241 }
242
243 write!(writer, ")")
245 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
246
247 if schema.without_rowid {
249 write!(writer, " WITHOUT ROWID")
250 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
251 }
252
253 writeln!(writer, ";")
254 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
255
256 if table.row_count() > 0 {
260 writeln!(writer)
261 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
262
263 let non_generated_indices: Vec<usize> = schema
265 .columns
266 .iter()
267 .enumerate()
268 .filter(|(_, col)| col.generated_expr.is_none())
269 .map(|(i, _)| i)
270 .collect();
271
272 let has_generated_columns = non_generated_indices.len() < schema.columns.len();
274 let column_list: String = if has_generated_columns {
275 let col_names: Vec<&str> = non_generated_indices
276 .iter()
277 .map(|&i| schema.columns[i].name.as_str())
278 .collect();
279 format!(" ({})", col_names.join(", "))
280 } else {
281 String::new()
282 };
283
284 for (_idx, row) in table.scan_live() {
285 write!(writer, "INSERT INTO {}{} VALUES (", &output_name, column_list)
286 .map_err(|e| {
287 StorageError::NotImplemented(format!("Write error: {}", e))
288 })?;
289
290 let mut first = true;
292 for &col_idx in &non_generated_indices {
293 if !first {
294 write!(writer, ", ").map_err(|e| {
295 StorageError::NotImplemented(format!("Write error: {}", e))
296 })?;
297 }
298 first = false;
299 write!(writer, "{}", sql_value_to_literal(&row.values[col_idx]))
300 .map_err(|e| {
301 StorageError::NotImplemented(format!("Write error: {}", e))
302 })?;
303 }
304 writeln!(writer, ");").map_err(|e| {
305 StorageError::NotImplemented(format!("Write error: {}", e))
306 })?;
307 }
308 }
309
310 writeln!(writer)
311 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
312 }
313 }
314
315 writeln!(writer, "-- Indexes")
317 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
318 for index_name in self.list_indexes() {
319 let lower_name = index_name.to_lowercase();
324 if lower_name.starts_with("pk_") || lower_name.starts_with("sqlite_autoindex_") {
325 continue;
326 }
327 let metadata = self.get_index(&index_name).unwrap();
328 write!(writer, "CREATE")
329 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
330 if metadata.unique {
331 write!(writer, " UNIQUE")
332 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
333 }
334 write!(writer, " INDEX {} ON {} (", index_name, metadata.table_name)
335 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
336
337 for (i, col) in metadata.columns.iter().enumerate() {
338 if i > 0 {
339 write!(writer, ", ")
340 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
341 }
342 use vibesql_ast::IndexColumn;
344 match col {
345 IndexColumn::Column { column_name, .. } => {
346 write!(writer, "{}", column_name)
347 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
348 }
349 IndexColumn::Expression { expr, .. } => {
350 use vibesql_ast::pretty_print::ToSql;
351 write!(writer, "{}", expr.to_sql())
352 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
353 }
354 }
355 write!(writer, " {:?}", col.direction())
356 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
357 }
358
359 writeln!(writer, ");")
360 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
361 }
362 writeln!(writer)
363 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
364
365 writeln!(writer, "-- Views")
367 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
368 for view_name in self.catalog.list_views() {
369 if let Some(view_def) = self.catalog.get_view(&view_name) {
370 let sql = view_def.sql_definition.as_ref().map_or_else(
372 || {
373 format!("CREATE VIEW {} AS {:?}", view_def.name, view_def.query)
376 },
377 |s| s.clone(),
378 );
379 let sql = sql.trim_end_matches(';').trim();
381 writeln!(writer, "{};", sql)
382 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
383 }
384 }
385 writeln!(writer)
386 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
387
388 writeln!(writer, "-- End of dump")
389 .map_err(|e| StorageError::NotImplemented(format!("Write error: {}", e)))?;
390
391 writer
394 .flush()
395 .map_err(|e| StorageError::NotImplemented(format!("Failed to flush buffer: {}", e)))?;
396
397 let file = writer
399 .into_inner()
400 .map_err(|e| StorageError::NotImplemented(format!("Failed to get file: {}", e)))?;
401 file.sync_all()
402 .map_err(|e| StorageError::NotImplemented(format!("Failed to sync file: {}", e)))?;
403
404 Ok(())
405 }
406}
407
408fn format_column_type(data_type: &vibesql_types::DataType, is_exact_integer_type: bool) -> String {
411 use vibesql_types::DataType;
412
413 match data_type {
414 DataType::Integer => {
415 if is_exact_integer_type {
416 "INTEGER".to_string()
417 } else {
418 "INT".to_string()
419 }
420 }
421 _ => format_data_type(data_type),
422 }
423}
424
425pub(super) fn format_data_type(data_type: &vibesql_types::DataType) -> String {
426 use vibesql_types::DataType;
427
428 match data_type {
429 DataType::Integer => "INTEGER".to_string(),
430 DataType::Smallint => "SMALLINT".to_string(),
431 DataType::Bigint => "BIGINT".to_string(),
432 DataType::Unsigned => "BIGINT UNSIGNED".to_string(),
433 DataType::Float { precision } => format!("FLOAT({})", precision),
434 DataType::Real => "REAL".to_string(),
435 DataType::DoublePrecision => "DOUBLE PRECISION".to_string(),
436 DataType::Varchar { max_length } => {
437 if let Some(len) = max_length {
438 format!("VARCHAR({})", len)
439 } else {
440 "VARCHAR".to_string()
441 }
442 }
443 DataType::Character { length } => format!("CHAR({})", length),
444 DataType::Boolean => "BOOLEAN".to_string(),
445 DataType::Date => "DATE".to_string(),
446 DataType::Time { .. } => "TIME".to_string(),
447 DataType::Timestamp { with_timezone } => {
448 if *with_timezone {
449 "TIMESTAMP WITH TIME ZONE".to_string()
450 } else {
451 "TIMESTAMP".to_string()
452 }
453 }
454 DataType::Interval { start_field, end_field: _ } => {
455 format!("INTERVAL {:?}", start_field)
457 }
458 DataType::Numeric { precision, scale } => {
459 format!("NUMERIC({}, {})", precision, scale)
460 }
461 DataType::Decimal { precision, scale } => {
462 format!("DECIMAL({}, {})", precision, scale)
463 }
464 DataType::CharacterLargeObject => "CLOB".to_string(),
465 DataType::Name => "VARCHAR(128)".to_string(),
466 DataType::BinaryLargeObject => "BLOB".to_string(),
467 DataType::Bit { length } => {
468 if let Some(len) = length {
469 format!("BIT({})", len)
470 } else {
471 "BIT".to_string()
472 }
473 }
474 DataType::Vector { dimensions } => format!("VECTOR({})", dimensions),
475 DataType::UserDefined { type_name } => type_name.clone(),
476 DataType::Null => "NULL".to_string(),
477 }
478}
479
480pub(super) fn sql_value_to_literal(value: &vibesql_types::SqlValue) -> String {
482 use vibesql_types::SqlValue;
483
484 match value {
485 SqlValue::Null => "NULL".to_string(),
486 SqlValue::Integer(n) => n.to_string(),
487 SqlValue::Smallint(n) => n.to_string(),
488 SqlValue::Bigint(n) => n.to_string(),
489 SqlValue::Unsigned(n) => n.to_string(),
490 SqlValue::Numeric(f) => {
491 if f.is_nan() {
493 "'NaN'".to_string()
494 } else if f.is_infinite() {
495 if f.is_sign_positive() {
496 "'Infinity'".to_string()
497 } else {
498 "'-Infinity'".to_string()
499 }
500 } else {
501 format_f64_for_sql(*f)
502 }
503 }
504 SqlValue::Float(f) => {
505 if f.is_nan() {
506 "'NaN'".to_string()
507 } else if f.is_infinite() {
508 if f.is_sign_positive() {
509 "'Infinity'".to_string()
510 } else {
511 "'-Infinity'".to_string()
512 }
513 } else {
514 format_f32_for_sql(*f)
515 }
516 }
517 SqlValue::Real(f) => {
518 if f.is_nan() {
520 "'NaN'".to_string()
521 } else if f.is_infinite() {
522 if f.is_sign_positive() {
523 "'Infinity'".to_string()
524 } else {
525 "'-Infinity'".to_string()
526 }
527 } else {
528 format_f64_for_sql(*f)
529 }
530 }
531 SqlValue::Double(f) => {
532 if f.is_nan() {
533 "'NaN'".to_string()
534 } else if f.is_infinite() {
535 if f.is_sign_positive() {
536 "'Infinity'".to_string()
537 } else {
538 "'-Infinity'".to_string()
539 }
540 } else {
541 format_f64_for_sql(*f)
542 }
543 }
544 SqlValue::Character(s) | SqlValue::Varchar(s) => format!("'{}'", s.replace('\'', "''")),
545 SqlValue::Boolean(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
546 SqlValue::Date(d) => format!("DATE '{}'", d),
547 SqlValue::Time(t) => format!("TIME '{}'", t),
548 SqlValue::Timestamp(ts) => format!("TIMESTAMP '{}'", ts),
549 SqlValue::Interval(i) => format!("INTERVAL '{}'", i),
550 SqlValue::Vector(v) => {
551 let formatted: Vec<String> = v.iter().map(|f| f.to_string()).collect();
553 format!("'{}'", formatted.join(","))
554 }
555 SqlValue::Blob(b) => {
556 let hex: String = b.iter().map(|byte| format!("{:02X}", byte)).collect();
558 format!("x'{}'", hex)
559 }
560 }
561}
562
563fn format_f64_for_sql(n: f64) -> String {
567 let mut buffer = ryu::Buffer::new();
569 let s = buffer.format(n);
570 s.to_string()
571}
572
573fn format_f32_for_sql(n: f32) -> String {
577 let mut buffer = ryu::Buffer::new();
579 let s = buffer.format(n);
580 s.to_string()
581}