use super::block_storage::BLOCK_SIZE;
use super::export::validate_sqlite_file;
use crate::types::DatabaseError;
pub async fn clear_database_storage(db_name: &str) -> Result<(), DatabaseError> {
use super::vfs_sync::{
with_global_allocation_map, with_global_commit_marker, with_global_storage,
};
log::info!("Clearing storage for database: {}", db_name);
#[cfg(target_arch = "wasm32")]
{
crate::vfs::indexeddb_vfs::remove_storage_from_registry(db_name);
log::debug!("Removed {} from STORAGE_REGISTRY", db_name);
}
#[cfg(target_arch = "wasm32")]
{
let pool_key = db_name.trim_end_matches(".db");
crate::connection_pool::force_close_connection(pool_key);
log::debug!("Force closed connection pool for {}", pool_key);
}
with_global_storage(|gs| {
let mut storage = gs.borrow_mut();
if let Some(blocks) = storage.get_mut(db_name) {
let count = blocks.len();
blocks.clear();
log::debug!(
"Cleared {} blocks from GLOBAL_STORAGE for {}",
count,
db_name
);
}
storage.remove(db_name);
});
#[cfg(target_arch = "wasm32")]
{
use super::vfs_sync::with_global_metadata;
with_global_metadata(|gm| {
let mut metadata = gm.borrow_mut();
if let Some(meta) = metadata.get_mut(db_name) {
let count = meta.len();
meta.clear();
log::debug!("Cleared {} metadata entries for {} (WASM)", count, db_name);
}
metadata.remove(db_name);
});
}
#[cfg(all(
not(target_arch = "wasm32"),
any(test, debug_assertions),
not(feature = "fs_persist")
))]
{
use super::block_storage::GLOBAL_METADATA_TEST;
GLOBAL_METADATA_TEST.with(|gm| {
#[cfg(target_arch = "wasm32")]
let metadata = gm;
#[cfg(not(target_arch = "wasm32"))]
let mut metadata = gm.lock();
if let Some(meta) = metadata.get_mut(db_name) {
let count = meta.len();
meta.clear();
log::debug!(
"Cleared {} metadata entries from GLOBAL_METADATA_TEST for {} (native test)",
count,
db_name
);
}
metadata.remove(db_name);
});
}
with_global_commit_marker(|gcm| {
let mut markers = gcm.borrow_mut();
if markers.contains_key(db_name) {
markers.insert(db_name.to_string(), 0);
log::debug!("Reset commit marker for {}", db_name);
}
markers.remove(db_name);
});
with_global_allocation_map(|gam| {
let mut alloc = gam.borrow_mut();
if let Some(ids) = alloc.get_mut(db_name) {
let count = ids.len();
ids.clear();
log::debug!("Cleared {} allocation IDs for {}", count, db_name);
}
alloc.remove(db_name);
});
#[cfg(target_arch = "wasm32")]
{
log::debug!(
"WASM: In-memory storage cleared for {}. IndexedDB clearing requires VFS interaction.",
db_name
);
}
log::info!("Storage cleared successfully for: {}", db_name);
Ok(())
}
pub async fn import_database_from_bytes(db_name: &str, data: Vec<u8>) -> Result<(), DatabaseError> {
use super::vfs_sync::{with_global_allocation_map, with_global_storage};
use std::collections::{HashMap, HashSet};
log::info!(
"Starting database import for: {} ({} bytes)",
db_name,
data.len()
);
validate_sqlite_file(&data)?;
log::debug!("SQLite file validation passed");
clear_database_storage(db_name).await?;
log::debug!("Existing storage cleared from memory");
#[cfg(target_arch = "wasm32")]
{
log::debug!(
"Deleting all existing blocks from IndexedDB for: {}",
db_name
);
super::wasm_indexeddb::delete_all_database_blocks_from_indexeddb(db_name).await?;
log::debug!("All old blocks deleted from IndexedDB");
}
let total_blocks = data.len().div_ceil(BLOCK_SIZE);
log::debug!(
"Splitting {} bytes into {} blocks of {} bytes",
data.len(),
total_blocks,
BLOCK_SIZE
);
let mut blocks = HashMap::new();
let mut allocated_ids = HashSet::new();
for block_id in 0..total_blocks {
let start = block_id * BLOCK_SIZE;
let end = std::cmp::min(start + BLOCK_SIZE, data.len());
let mut block_data = Vec::with_capacity(BLOCK_SIZE);
block_data.extend_from_slice(&data[start..end]);
if block_data.len() < BLOCK_SIZE {
let padding = BLOCK_SIZE - block_data.len();
block_data.resize(BLOCK_SIZE, 0);
log::debug!(
"Block {} padded with {} zero bytes ({} -> {} bytes)",
block_id,
padding,
end - start,
BLOCK_SIZE
);
}
blocks.insert(block_id as u64, block_data);
allocated_ids.insert(block_id as u64);
}
log::debug!("Created {} blocks for import", blocks.len());
with_global_storage(|gs| {
gs.borrow_mut().insert(db_name.to_string(), blocks.clone());
});
log::debug!("Blocks written to GLOBAL_STORAGE");
with_global_allocation_map(|gam| {
gam.borrow_mut()
.insert(db_name.to_string(), allocated_ids.clone());
});
log::debug!("Allocation map updated");
#[cfg(target_arch = "wasm32")]
{
use super::metadata::{BlockMetadataPersist, ChecksumAlgorithm, ChecksumManager};
use super::vfs_sync::with_global_metadata;
with_global_metadata(|gm| {
let mut db_metadata = std::collections::HashMap::new();
for block_id in allocated_ids.iter() {
let checksum = if let Some(block_data) = blocks.get(block_id) {
ChecksumManager::compute_checksum_with(block_data, ChecksumAlgorithm::CRC32)
} else {
0
};
db_metadata.insert(
*block_id,
BlockMetadataPersist {
version: 1, checksum,
last_modified_ms: 0,
algo: ChecksumAlgorithm::CRC32,
},
);
}
gm.borrow_mut().insert(db_name.to_string(), db_metadata);
});
log::debug!(
"Metadata created for {} blocks in global storage (WASM)",
allocated_ids.len()
);
}
#[cfg(all(
not(target_arch = "wasm32"),
any(test, debug_assertions),
not(feature = "fs_persist")
))]
{
use super::block_storage::GLOBAL_METADATA_TEST;
use super::metadata::{BlockMetadataPersist, ChecksumAlgorithm, ChecksumManager};
GLOBAL_METADATA_TEST.with(|gm| {
#[cfg(target_arch = "wasm32")]
let metadata = gm;
#[cfg(not(target_arch = "wasm32"))]
let mut metadata = gm.lock();
let mut db_metadata = std::collections::HashMap::new();
for block_id in allocated_ids.iter() {
let checksum = if let Some(block_data) = blocks.get(block_id) {
ChecksumManager::compute_checksum_with(block_data, ChecksumAlgorithm::CRC32)
} else {
0
};
db_metadata.insert(
*block_id,
BlockMetadataPersist {
version: 1, checksum,
last_modified_ms: 0,
algo: ChecksumAlgorithm::CRC32,
},
);
}
metadata.insert(db_name.to_string(), db_metadata);
});
log::debug!(
"Metadata created for {} blocks in GLOBAL_METADATA_TEST (native test)",
allocated_ids.len()
);
}
#[cfg(all(not(target_arch = "wasm32"), feature = "fs_persist"))]
{
use super::metadata::{ChecksumAlgorithm, ChecksumManager};
use std::path::PathBuf;
let base_path =
std::env::var("ABSURDERSQL_FS_BASE").unwrap_or_else(|_| "./test_storage".to_string());
let mut meta_path = PathBuf::from(&base_path);
meta_path.push(db_name);
if let Err(e) = std::fs::create_dir_all(&meta_path) {
log::warn!("Failed to create metadata directory during import: {}", e);
}
meta_path.push("metadata.json");
let mut meta_entries = Vec::new();
for block_id in allocated_ids.iter() {
let checksum = if let Some(block_data) = blocks.get(block_id) {
ChecksumManager::compute_checksum_with(block_data, ChecksumAlgorithm::CRC32)
} else {
0
};
meta_entries.push((
*block_id,
super::metadata::BlockMetadataPersist {
version: 1,
checksum,
last_modified_ms: 0,
algo: ChecksumAlgorithm::CRC32,
},
));
}
let meta_json = serde_json::json!({
"entries": meta_entries,
});
if let Err(e) = std::fs::write(
&meta_path,
serde_json::to_string_pretty(&meta_json).unwrap(),
) {
log::warn!("Failed to write metadata during import: {}", e);
} else {
log::debug!(
"Metadata written to filesystem for {} blocks",
allocated_ids.len()
);
}
let mut blocks_dir = PathBuf::from(&base_path);
blocks_dir.push(db_name);
blocks_dir.push("blocks");
if let Err(e) = std::fs::create_dir_all(&blocks_dir) {
log::warn!("Failed to create blocks directory during import: {}", e);
}
for (block_id, block_data) in blocks.iter() {
let mut block_path = blocks_dir.clone();
block_path.push(format!("block_{}.bin", block_id));
if let Err(e) = std::fs::write(&block_path, block_data) {
log::warn!("Failed to write block {} during import: {}", block_id, e);
}
}
log::debug!("Wrote {} block files to filesystem", blocks.len());
let mut alloc_path = PathBuf::from(&base_path);
alloc_path.push(db_name);
alloc_path.push("allocations.json");
let alloc_json = serde_json::json!({
"allocated": allocated_ids.iter().copied().collect::<Vec<_>>(),
});
if let Err(e) = std::fs::write(
&alloc_path,
serde_json::to_string_pretty(&alloc_json).unwrap(),
) {
log::warn!("Failed to write allocations during import: {}", e);
} else {
log::debug!("Allocations written to filesystem");
}
}
use super::vfs_sync::with_global_commit_marker;
with_global_commit_marker(|gcm| {
gcm.borrow_mut().insert(db_name.to_string(), 1);
});
log::debug!("Commit marker set to 1 for immediate visibility");
#[cfg(target_arch = "wasm32")]
{
log::debug!("Syncing imported data to IndexedDB for {}", db_name);
let next_commit = with_global_commit_marker(|cm| {
let current = cm.borrow().get(db_name).copied().unwrap_or(0);
let new_marker = current + 1;
cm.borrow_mut().insert(db_name.to_string(), new_marker);
new_marker
});
let (blocks_to_persist, metadata_to_persist) = {
use super::vfs_sync::with_global_metadata;
with_global_storage(|storage| {
let blocks = if let Some(db_storage) = storage.borrow().get(db_name) {
db_storage
.iter()
.map(|(&id, data)| (id, data.clone()))
.collect::<Vec<_>>()
} else {
Vec::new()
};
let metadata = with_global_metadata(|meta| {
if let Some(db_meta) = meta.borrow().get(db_name) {
db_meta
.iter()
.map(|(&id, metadata)| (id, metadata.checksum))
.collect::<Vec<_>>()
} else {
Vec::new()
}
});
(blocks, metadata)
})
};
if !blocks_to_persist.is_empty() {
log::debug!(
"Persisting {} blocks to IndexedDB with commit marker {}",
blocks_to_persist.len(),
next_commit
);
super::wasm_indexeddb::persist_to_indexeddb_event_based(
db_name,
blocks_to_persist,
metadata_to_persist,
next_commit,
#[cfg(feature = "telemetry")]
None,
#[cfg(feature = "telemetry")]
None,
)
.await
.map_err(|e| {
log::error!("Failed to persist imported data to IndexedDB: {}", e);
DatabaseError::new(
"IMPORT_SYNC_FAILED",
&format!("Failed to persist imported data: {}", e),
)
})?;
log::debug!("Import sync to IndexedDB complete for {}", db_name);
} else {
log::warn!("No blocks to persist to IndexedDB for {}", db_name);
}
}
log::info!(
"Database import complete: {} ({} blocks, {} bytes)",
db_name,
total_blocks,
data.len()
);
#[cfg(target_arch = "wasm32")]
{
use super::vfs_sync::with_global_storage;
with_global_storage(|storage_map| {
if let Some(db_storage) = storage_map.borrow().get(db_name) {
web_sys::console::log_1(
&format!(
"[IMPORT] GLOBAL_STORAGE now has {} blocks for {}",
db_storage.len(),
db_name
)
.into(),
);
for (block_id, data) in db_storage.iter().take(5) {
web_sys::console::log_1(
&format!(
"[IMPORT] Block {} has {} bytes, first 16: {:02x?}",
block_id,
data.len(),
&data[..16.min(data.len())]
)
.into(),
);
}
}
});
}
Ok(())
}
pub fn invalidate_block_storage_caches(db_name: &str) {
#[cfg(target_arch = "wasm32")]
{
crate::vfs::indexeddb_vfs::remove_storage_from_registry(db_name);
log::info!("Removed BlockStorage from registry for: {}", db_name);
}
#[cfg(not(target_arch = "wasm32"))]
{
log::info!(
"Cache invalidation for native not yet implemented for: {}",
db_name
);
}
}