diff --git a/src/admin_meta.rs b/src/admin_meta.rs index 1112605..9039402 100644 --- a/src/admin_meta.rs +++ b/src/admin_meta.rs @@ -69,9 +69,38 @@ pub fn open_admin_storage( if let Some(st) = w.get(base_dir) { 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()); - return Ok(st); + Ok(st) } } @@ -122,7 +151,39 @@ pub fn open_data_storage( 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 should_encrypt = enc.is_some(); @@ -134,8 +195,8 @@ pub fn open_data_storage( })?; } - // Open storage - let storage: Arc = match backend { + // Open storage using the effective backend + let storage: Arc = match effective_backend { 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())?), }; @@ -217,6 +278,39 @@ pub fn set_database_public( 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, 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 pub fn set_database_name( base_dir: &str, diff --git a/src/options.rs b/src/options.rs index 2ee44c2..c819ca0 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,4 +1,4 @@ -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum BackendType { Redb, Sled, diff --git a/src/rpc.rs b/src/rpc.rs index bf0eb2d..207f45f 100644 --- a/src/rpc.rs +++ b/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 { dir: self.base_dir.clone(), port: 0, // Not used for RPC-managed databases debug: false, encryption_key: None, encrypt: false, - backend: self.backend.clone(), + backend: effective_backend, 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::<()>)); } - // 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 { dir: self.base_dir.clone(), port: 0, // Not used for RPC-managed databases debug: false, encryption_key: None, // per-db key is stored in admin DB 0 encrypt: false, // encryption decided per-db at open time - backend: match backend { - BackendType::Redb => crate::options::BackendType::Redb, - BackendType::Sled => crate::options::BackendType::Sled, - }, + backend: opt_backend, admin_secret: self.admin_secret.clone(), };