Skip to main content

powdb_backup/
full.rs

1use crate::manifest::{
2    active_durable_file_names, current_sync_snapshot_metadata, BackupManifest, FileEntry,
3};
4use powdb_storage::catalog::Catalog;
5use std::io;
6use std::path::Path;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9/// Take a consistent full snapshot of `catalog`'s data dir into `dest`.
10///
11/// Consistency model: checkpoint flushes every dirty heap page + index and
12/// truncates the WAL, producing a clean-shutdown image. If sync identity exists,
13/// the checkpoint first archives retained WAL records into `powdb-sync`
14/// segments. We then copy the durable files. The brief write-quiesce is the
15/// duration of the checkpoint, held by the caller's `&mut` borrow.
16pub fn full_backup(catalog: &mut Catalog, dest: &Path) -> io::Result<BackupManifest> {
17    powdb_sync::checkpoint_preserving_retained_segments_if_enabled(catalog)?;
18    let source_lsn = catalog.max_lsn();
19    let catalog_version = catalog.active_catalog_version();
20    let src = catalog.data_dir().to_path_buf();
21    let sync = current_sync_snapshot_metadata(&src, source_lsn, catalog_version)?;
22    crate::secure::create_dir_secure(dest)?;
23
24    let mut files = Vec::new();
25    for name in active_durable_file_names(catalog) {
26        let source_path = src.join(&name);
27        if !source_path.exists() {
28            // `catalog.lsn` is absent in pristine databases with no durable
29            // statement boundary yet. Every metadata-referenced heap/index is
30            // required and a missing one must fail closed.
31            if name == powdb_storage::catalog::CATALOG_LSN_FILE {
32                continue;
33            }
34            return Err(io::Error::new(
35                io::ErrorKind::NotFound,
36                format!("catalog references missing durable file {name}"),
37            ));
38        }
39        if !source_path.is_file() {
40            return Err(io::Error::new(
41                io::ErrorKind::InvalidData,
42                format!("catalog durable path is not a file: {name}"),
43            ));
44        }
45        let bytes = std::fs::read(source_path)?;
46        let hash = blake3::hash(&bytes).to_hex().to_string();
47        crate::secure::write_file_secure(&dest.join(&name), &bytes)?;
48        files.push(FileEntry {
49            name,
50            len: bytes.len() as u64,
51            blake3_hex: hash,
52        });
53    }
54    files.sort_by(|a, b| a.name.cmp(&b.name));
55
56    let manifest = BackupManifest {
57        format_version: BackupManifest::FORMAT_VERSION,
58        created_unix_secs: SystemTime::now()
59            .duration_since(UNIX_EPOCH)
60            .map(|d| d.as_secs())
61            .unwrap_or(0),
62        source_lsn,
63        catalog_version,
64        sync,
65        files,
66    };
67    manifest.write(dest)?;
68    Ok(manifest)
69}