use hashbrown::HashMap;
use std::sync::atomic::{AtomicI64, Ordering};
use noxu_sync::RwLock;
use crate::database_config::DatabaseConfig;
use crate::database_impl::DatabaseImpl;
use crate::{DatabaseId, DbType, DbiError};
pub struct DbTree {
name_to_id: RwLock<HashMap<String, DatabaseId>>,
id_to_db: RwLock<HashMap<DatabaseId, DatabaseImpl>>,
next_id: AtomicI64,
}
impl DbTree {
pub fn new() -> Self {
DbTree {
name_to_id: RwLock::new(HashMap::new()),
id_to_db: RwLock::new(HashMap::new()),
next_id: AtomicI64::new(1),
}
}
pub fn get_next_db_id(&self) -> DatabaseId {
DatabaseId::new(self.next_id.fetch_add(1, Ordering::Relaxed))
}
pub fn create_database(
&self,
name: &str,
config: &DatabaseConfig,
) -> Result<DatabaseId, DbiError> {
if self.name_to_id.read().contains_key(name) {
return Err(DbiError::DatabaseAlreadyExists(name.to_string()));
}
let db_id = self.get_next_db_id();
let db_impl =
DatabaseImpl::new(db_id, name.to_string(), DbType::User, config);
self.id_to_db.write().insert(db_id, db_impl);
self.name_to_id.write().insert(name.to_string(), db_id);
Ok(db_id)
}
pub fn get_database_id(&self, name: &str) -> Option<DatabaseId> {
self.name_to_id.read().get(name).copied()
}
pub fn get_database(&self, id: &DatabaseId) -> Option<DatabaseId> {
if self.id_to_db.read().contains_key(id) { Some(*id) } else { None }
}
pub fn get_database_name(&self, id: &DatabaseId) -> Option<String> {
self.id_to_db.read().get(id).map(|db| db.get_name().to_string())
}
pub fn remove_database(&self, name: &str) -> Result<DatabaseId, DbiError> {
let db_id = self
.name_to_id
.write()
.remove(name)
.ok_or_else(|| DbiError::DatabaseNotFound(name.to_string()))?;
if let Some(mut db) = self.id_to_db.write().remove(&db_id) {
db.start_delete();
db.finish_delete();
}
Ok(db_id)
}
pub fn rename_database(
&self,
old_name: &str,
new_name: &str,
) -> Result<(), DbiError> {
if self.name_to_id.read().contains_key(new_name) {
return Err(DbiError::DatabaseAlreadyExists(new_name.to_string()));
}
let db_id =
self.name_to_id.write().remove(old_name).ok_or_else(|| {
DbiError::DatabaseNotFound(old_name.to_string())
})?;
self.name_to_id.write().insert(new_name.to_string(), db_id);
Ok(())
}
pub fn get_database_names(&self) -> Vec<String> {
self.name_to_id.read().keys().cloned().collect()
}
pub fn n_databases(&self) -> usize {
self.id_to_db.read().len()
}
}
impl Default for DbTree {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_and_lookup_database() {
let tree = DbTree::new();
let config = DatabaseConfig::default();
let db_id = tree.create_database("test_db", &config).unwrap();
assert_eq!(tree.get_database_id("test_db"), Some(db_id));
assert_eq!(tree.get_database(&db_id), Some(db_id));
}
#[test]
fn test_create_duplicate_name_error() {
let tree = DbTree::new();
let config = DatabaseConfig::default();
tree.create_database("test_db", &config).unwrap();
let result = tree.create_database("test_db", &config);
assert!(matches!(result, Err(DbiError::DatabaseAlreadyExists(_))));
}
#[test]
fn test_remove_database() {
let tree = DbTree::new();
let config = DatabaseConfig::default();
let db_id = tree.create_database("test_db", &config).unwrap();
let removed_id = tree.remove_database("test_db").unwrap();
assert_eq!(removed_id, db_id);
assert_eq!(tree.get_database_id("test_db"), None);
assert_eq!(tree.get_database(&db_id), None);
}
#[test]
fn test_remove_nonexistent_database() {
let tree = DbTree::new();
let result = tree.remove_database("nonexistent");
assert!(matches!(result, Err(DbiError::DatabaseNotFound(_))));
}
#[test]
fn test_rename_database() {
let tree = DbTree::new();
let config = DatabaseConfig::default();
let db_id = tree.create_database("old_name", &config).unwrap();
tree.rename_database("old_name", "new_name").unwrap();
assert_eq!(tree.get_database_id("old_name"), None);
assert_eq!(tree.get_database_id("new_name"), Some(db_id));
}
#[test]
fn test_rename_to_existing_name_error() {
let tree = DbTree::new();
let config = DatabaseConfig::default();
tree.create_database("db1", &config).unwrap();
tree.create_database("db2", &config).unwrap();
let result = tree.rename_database("db1", "db2");
assert!(matches!(result, Err(DbiError::DatabaseAlreadyExists(_))));
}
#[test]
fn test_get_database_names() {
let tree = DbTree::new();
let config = DatabaseConfig::default();
tree.create_database("db1", &config).unwrap();
tree.create_database("db2", &config).unwrap();
tree.create_database("db3", &config).unwrap();
let mut names = tree.get_database_names();
names.sort();
assert_eq!(names, vec!["db1", "db2", "db3"]);
}
#[test]
fn test_n_databases() {
let tree = DbTree::new();
let config = DatabaseConfig::default();
assert_eq!(tree.n_databases(), 0);
tree.create_database("db1", &config).unwrap();
assert_eq!(tree.n_databases(), 1);
tree.create_database("db2", &config).unwrap();
assert_eq!(tree.n_databases(), 2);
tree.remove_database("db1").unwrap();
assert_eq!(tree.n_databases(), 1);
}
#[test]
fn test_get_next_db_id_sequential() {
let tree = DbTree::new();
let id1 = tree.get_next_db_id();
let id2 = tree.get_next_db_id();
let id3 = tree.get_next_db_id();
assert_eq!(id1.id(), 1);
assert_eq!(id2.id(), 2);
assert_eq!(id3.id(), 3);
}
#[test]
fn test_get_database_name() {
let tree = DbTree::new();
let config = DatabaseConfig::default();
let db_id = tree.create_database("my_database", &config).unwrap();
assert_eq!(
tree.get_database_name(&db_id),
Some("my_database".to_string())
);
let bad_id = DatabaseId::new(999);
assert_eq!(tree.get_database_name(&bad_id), None);
}
}