Persist backend per database id in admin metadata so restarts and lazy opens always use the correct engine (Sled/Redb)
This commit is contained in:
@@ -69,9 +69,38 @@ pub fn open_admin_storage(
|
|||||||
if let Some(st) = w.get(base_dir) {
|
if let Some(st) = w.get(base_dir) {
|
||||||
return Ok(st.clone());
|
return Ok(st.clone());
|
||||||
}
|
}
|
||||||
let st = init_admin_storage(base_dir, backend, admin_secret)?;
|
|
||||||
|
// Detect existing 0.db backend by filesystem, if present.
|
||||||
|
let admin_path = PathBuf::from(base_dir).join("0.db");
|
||||||
|
let detected = if admin_path.exists() {
|
||||||
|
if admin_path.is_file() {
|
||||||
|
Some(options::BackendType::Redb)
|
||||||
|
} else if admin_path.is_dir() {
|
||||||
|
Some(options::BackendType::Sled)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let effective_backend = match detected {
|
||||||
|
Some(d) if d != backend => {
|
||||||
|
eprintln!(
|
||||||
|
"warning: Admin DB 0 at {} appears to be {:?}, but process default is {:?}. Using detected backend.",
|
||||||
|
admin_path.display(),
|
||||||
|
d,
|
||||||
|
backend
|
||||||
|
);
|
||||||
|
d
|
||||||
|
}
|
||||||
|
Some(d) => d,
|
||||||
|
None => backend, // First boot: use requested backend to initialize 0.db
|
||||||
|
};
|
||||||
|
|
||||||
|
let st = init_admin_storage(base_dir, effective_backend, admin_secret)?;
|
||||||
w.insert(base_dir.to_string(), st.clone());
|
w.insert(base_dir.to_string(), st.clone());
|
||||||
return Ok(st);
|
Ok(st)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +151,39 @@ pub fn open_data_storage(
|
|||||||
return Ok(st.clone());
|
return Ok(st.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine per-db encryption
|
// Resolve effective backend for this db id:
|
||||||
|
// 1) Try admin meta "backend" field
|
||||||
|
// 2) If missing, sniff filesystem (file => Redb, dir => Sled), then persist into admin meta
|
||||||
|
// 3) Fallback to requested 'backend' (startup default) if nothing else is known
|
||||||
|
let meta_backend = get_database_backend(base_dir, backend.clone(), admin_secret, id).ok().flatten();
|
||||||
|
let db_path = PathBuf::from(base_dir).join(format!("{}.db", id));
|
||||||
|
let sniffed_backend = if db_path.exists() {
|
||||||
|
if db_path.is_file() {
|
||||||
|
Some(options::BackendType::Redb)
|
||||||
|
} else if db_path.is_dir() {
|
||||||
|
Some(options::BackendType::Sled)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let effective_backend = meta_backend.clone().or(sniffed_backend).unwrap_or(backend.clone());
|
||||||
|
|
||||||
|
// If we had to sniff (i.e., meta missing), persist it for future robustness
|
||||||
|
if meta_backend.is_none() {
|
||||||
|
let _ = set_database_backend(base_dir, backend.clone(), admin_secret, id, effective_backend.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn if caller-provided backend differs from effective
|
||||||
|
if effective_backend != backend {
|
||||||
|
eprintln!(
|
||||||
|
"notice: Database {} backend resolved to {:?} (caller requested {:?}). Using resolved backend.",
|
||||||
|
id, effective_backend, backend
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine per-db encryption (from admin meta)
|
||||||
let enc = get_enc_key(base_dir, backend.clone(), admin_secret, id)?;
|
let enc = get_enc_key(base_dir, backend.clone(), admin_secret, id)?;
|
||||||
let should_encrypt = enc.is_some();
|
let should_encrypt = enc.is_some();
|
||||||
|
|
||||||
@@ -134,8 +195,8 @@ pub fn open_data_storage(
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open storage
|
// Open storage using the effective backend
|
||||||
let storage: Arc<dyn StorageBackend> = match backend {
|
let storage: Arc<dyn StorageBackend> = match effective_backend {
|
||||||
options::BackendType::Redb => Arc::new(Storage::new(&db_file, should_encrypt, enc.as_deref())?),
|
options::BackendType::Redb => Arc::new(Storage::new(&db_file, should_encrypt, enc.as_deref())?),
|
||||||
options::BackendType::Sled => Arc::new(SledStorage::new(&db_file, should_encrypt, enc.as_deref())?),
|
options::BackendType::Sled => Arc::new(SledStorage::new(&db_file, should_encrypt, enc.as_deref())?),
|
||||||
};
|
};
|
||||||
@@ -217,6 +278,39 @@ pub fn set_database_public(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Persist per-db backend type in admin metadata (module-scope)
|
||||||
|
pub fn set_database_backend(
|
||||||
|
base_dir: &str,
|
||||||
|
backend: options::BackendType,
|
||||||
|
admin_secret: &str,
|
||||||
|
id: u64,
|
||||||
|
db_backend: options::BackendType,
|
||||||
|
) -> Result<(), DBError> {
|
||||||
|
let admin = open_admin_storage(base_dir, backend, admin_secret)?;
|
||||||
|
let mk = k_meta_db(id);
|
||||||
|
let val = match db_backend {
|
||||||
|
options::BackendType::Redb => "Redb",
|
||||||
|
options::BackendType::Sled => "Sled",
|
||||||
|
};
|
||||||
|
let _ = admin.hset(&mk, vec![("backend".to_string(), val.to_string())])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_database_backend(
|
||||||
|
base_dir: &str,
|
||||||
|
backend: options::BackendType,
|
||||||
|
admin_secret: &str,
|
||||||
|
id: u64,
|
||||||
|
) -> Result<Option<options::BackendType>, DBError> {
|
||||||
|
let admin = open_admin_storage(base_dir, backend, admin_secret)?;
|
||||||
|
let mk = k_meta_db(id);
|
||||||
|
match admin.hget(&mk, "backend")? {
|
||||||
|
Some(s) if s == "Redb" => Ok(Some(options::BackendType::Redb)),
|
||||||
|
Some(s) if s == "Sled" => Ok(Some(options::BackendType::Sled)),
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set database name
|
// Set database name
|
||||||
pub fn set_database_name(
|
pub fn set_database_name(
|
||||||
base_dir: &str,
|
base_dir: &str,
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum BackendType {
|
pub enum BackendType {
|
||||||
Redb,
|
Redb,
|
||||||
Sled,
|
Sled,
|
||||||
|
47
src/rpc.rs
47
src/rpc.rs
@@ -158,14 +158,42 @@ impl RpcServerImpl {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create server instance with default options
|
// Resolve effective backend for this db from admin meta or filesystem; fallback to default
|
||||||
|
let meta_backend = admin_meta::get_database_backend(&self.base_dir, self.backend.clone(), &self.admin_secret, db_id)
|
||||||
|
.ok()
|
||||||
|
.flatten();
|
||||||
|
let db_path = std::path::PathBuf::from(&self.base_dir).join(format!("{}.db", db_id));
|
||||||
|
let sniffed_backend = if db_path.exists() {
|
||||||
|
if db_path.is_file() {
|
||||||
|
Some(crate::options::BackendType::Redb)
|
||||||
|
} else if db_path.is_dir() {
|
||||||
|
Some(crate::options::BackendType::Sled)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let effective_backend = meta_backend.clone().or(sniffed_backend).unwrap_or(self.backend.clone());
|
||||||
|
if effective_backend != self.backend {
|
||||||
|
eprintln!(
|
||||||
|
"notice: get_or_create_server: db {} backend resolved to {:?} (server default {:?})",
|
||||||
|
db_id, effective_backend, self.backend
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// If we had to sniff (no meta), persist the resolved backend
|
||||||
|
if meta_backend.is_none() {
|
||||||
|
let _ = admin_meta::set_database_backend(&self.base_dir, self.backend.clone(), &self.admin_secret, db_id, effective_backend.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create server instance with resolved backend
|
||||||
let db_option = DBOption {
|
let db_option = DBOption {
|
||||||
dir: self.base_dir.clone(),
|
dir: self.base_dir.clone(),
|
||||||
port: 0, // Not used for RPC-managed databases
|
port: 0, // Not used for RPC-managed databases
|
||||||
debug: false,
|
debug: false,
|
||||||
encryption_key: None,
|
encryption_key: None,
|
||||||
encrypt: false,
|
encrypt: false,
|
||||||
backend: self.backend.clone(),
|
backend: effective_backend,
|
||||||
admin_secret: self.admin_secret.clone(),
|
admin_secret: self.admin_secret.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -308,17 +336,22 @@ impl RpcServer for RpcServerImpl {
|
|||||||
return Err(jsonrpsee::types::ErrorObjectOwned::owned(-32000, format!("Failed to ensure base dir: {}", e), None::<()>));
|
return Err(jsonrpsee::types::ErrorObjectOwned::owned(-32000, format!("Failed to ensure base dir: {}", e), None::<()>));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create server instance using base_dir and admin secret
|
// Map RPC backend to options backend and persist it in admin meta for this db id
|
||||||
|
let opt_backend = match backend {
|
||||||
|
BackendType::Redb => crate::options::BackendType::Redb,
|
||||||
|
BackendType::Sled => crate::options::BackendType::Sled,
|
||||||
|
};
|
||||||
|
admin_meta::set_database_backend(&self.base_dir, self.backend.clone(), &self.admin_secret, db_id, opt_backend.clone())
|
||||||
|
.map_err(|e| jsonrpsee::types::ErrorObjectOwned::owned(-32000, e.0, None::<()>))?;
|
||||||
|
|
||||||
|
// Create server instance using base_dir, chosen backend and admin secret
|
||||||
let option = DBOption {
|
let option = DBOption {
|
||||||
dir: self.base_dir.clone(),
|
dir: self.base_dir.clone(),
|
||||||
port: 0, // Not used for RPC-managed databases
|
port: 0, // Not used for RPC-managed databases
|
||||||
debug: false,
|
debug: false,
|
||||||
encryption_key: None, // per-db key is stored in admin DB 0
|
encryption_key: None, // per-db key is stored in admin DB 0
|
||||||
encrypt: false, // encryption decided per-db at open time
|
encrypt: false, // encryption decided per-db at open time
|
||||||
backend: match backend {
|
backend: opt_backend,
|
||||||
BackendType::Redb => crate::options::BackendType::Redb,
|
|
||||||
BackendType::Sled => crate::options::BackendType::Sled,
|
|
||||||
},
|
|
||||||
admin_secret: self.admin_secret.clone(),
|
admin_secret: self.admin_secret.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user