From 73692bcfdb19bc5cb8545548950b9c8c9a6702be Mon Sep 17 00:00:00 2001 From: dualfroz Date: Sat, 3 Oct 2026 02:47:01 +0200 Subject: [PATCH] fix(core): Back up and restore FST buckets again Since 27a1b2b ("chore(core): Flatten FST store code"), the bucket loop in `FstStorePool::dump_action` checked the type and name of the collection entry instead of the bucket entry. As a collection is a directory, every bucket was skipped: `TRIGGER backup` answered `OK` but wrote no FST, and `TRIGGER restore` restored none. Add a test which backs up a consolidated FST and restores it. --- core/src/store/fst/backup.rs | 4 ++-- core/tests/data_backup.rs | 46 ++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 core/tests/data_backup.rs diff --git a/core/src/store/fst/backup.rs b/core/src/store/fst/backup.rs index f733aa6..aca144e 100644 --- a/core/src/store/fst/backup.rs +++ b/core/src/store/fst/backup.rs @@ -88,7 +88,7 @@ impl FstStorePool { let bucket_entry = bucket_entry?; // Actual bucket found? - let file_type = collection_entry.file_type()?; + let file_type = bucket_entry.file_type()?; if !file_type.is_file() { tracing::trace!( ?file_type, @@ -97,7 +97,7 @@ impl FstStorePool { continue 'buckets; } - let file_name = collection_entry.file_name(); + let file_name = bucket_entry.file_name(); let Some(bucket_file_name) = file_name.to_str() else { tracing::warn!( file_name_bytes = ?file_name.as_encoded_bytes(), diff --git a/core/tests/data_backup.rs b/core/tests/data_backup.rs new file mode 100644 index 0000000..b3c21ae --- /dev/null +++ b/core/tests/data_backup.rs @@ -0,0 +1,46 @@ +// Sonic +// +// Fast, lightweight and schema-less search backend +// Copyright: 2026, DualFroz +// License: Mozilla Public License v2.0 (MPL v2.0) + +//! Feature: Data backup + +mod common; + +use crate::common::util::unique_hex; +use crate::common::*; + +/// Consolidated FST stores are backed up, and can be restored. +#[test] +fn test_fst_backup_and_restore() { + init_logging(); + let executor = make_test_executor(|_| {}); + + exec!(executor -> PUSH "messages" "user:1" "chat:1" "Hello world"); + exec!(executor -> TRIGGER consolidate); + + let backup_path = std::path::Path::new(env!("CARGO_TARGET_TMPDIR")) + .join(unique_hex().unwrap()) + .join("fst-backup"); + + executor.fst_pool.backup(&backup_path).unwrap(); + + let backup_files = std::fs::read_dir(&backup_path) + .unwrap() + .flat_map(|collection| std::fs::read_dir(collection.unwrap().path()).unwrap()) + .map(|bucket| bucket.unwrap().path()) + .collect::>(); + assert_eq!(backup_files.len(), 1, "got: {backup_files:?}"); + assert!( + backup_files[0].to_string_lossy().ends_with(".fst.bck"), + "got: {backup_files:?}" + ); + + let restored_executor = make_test_executor(|_| {}); + + restored_executor.fst_pool.restore(&backup_path).unwrap(); + + let response = exec!(restored_executor -> LIST "messages" "user:1"); + assert_contains!(response, ["hello", "world"]); +}