use candid::CandidType;
use ic_stable_structures::memory_manager::MemoryId;
use ic_stable_structures::Storable;
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::HashMap;
use super::nosql_db::Database;
use super::types::{CompositeKey, CompositeKeys, Document, QueryResponse};
use crate::memory::stable_memory::create_stable_btree_map;
pub struct DatabaseManager {
registered_models: RefCell<HashMap<String, ModelInfo>>,
next_memory_id: RefCell<u8>,
}
#[derive(Clone, Debug)]
struct ModelInfo {
_model_name: String,
primary_memory_id: MemoryId,
secondary_memory_id: Option<MemoryId>,
}
impl DatabaseManager {
pub fn new() -> Self {
Self {
registered_models: RefCell::new(HashMap::new()),
next_memory_id: RefCell::new(0),
}
}
pub fn register_model(
&self,
model_name: &str,
primary_memory_id: Option<u8>,
secondary_memory_id: Option<u8>,
) -> Result<(), String> {
let mut models = self.registered_models.borrow_mut();
if models.contains_key(model_name) {
return Err(format!("Model '{}' is already registered", model_name));
}
let primary_id = match primary_memory_id {
Some(id) => {
let memory_id = MemoryId::new(id);
for (_, info) in models.iter() {
if info.primary_memory_id == memory_id {
return Err(format!("Memory ID {} is already in use", id));
}
if let Some(secondary_id) = info.secondary_memory_id {
if secondary_id == memory_id {
return Err(format!("Memory ID {} is already in use", id));
}
}
}
MemoryId::new(id)
}
None => {
let mut next_id = self.next_memory_id.borrow_mut();
let id = MemoryId::new(*next_id);
*next_id += 1;
id
}
};
let secondary_id = secondary_memory_id
.map(|id| {
let memory_id = MemoryId::new(id);
for (_, info) in models.iter() {
if info.primary_memory_id == memory_id {
return Err(format!("Memory ID {} is already in use", id));
}
if let Some(secondary_id) = info.secondary_memory_id {
if secondary_id == memory_id {
return Err(format!("Memory ID {} is already in use", id));
}
}
}
Ok(memory_id)
})
.transpose()?;
let model_info = ModelInfo {
_model_name: model_name.to_string(),
primary_memory_id: primary_id,
secondary_memory_id: secondary_id,
};
models.insert(model_name.to_string(), model_info);
Ok(())
}
pub fn get_database<T, SecondaryKey>(
&self,
model_name: &str,
get_secondary_key: Option<Box<dyn Fn(&T) -> Option<SecondaryKey>>>,
) -> Result<Database<T, SecondaryKey>, String>
where
T: CandidType + Serialize + for<'de> Deserialize<'de> + Clone,
SecondaryKey: Storable + Ord + Clone,
{
let models = self.registered_models.borrow();
let model_info = models
.get(model_name)
.ok_or_else(|| format!("Model '{}' is not registered", model_name))?;
let primary_map = RefCell::new(create_stable_btree_map::<CompositeKey, Document<T>>(
model_info.primary_memory_id,
));
let secondary_index = model_info.secondary_memory_id.map(|memory_id| {
RefCell::new(create_stable_btree_map::<SecondaryKey, CompositeKeys>(
memory_id,
))
});
Ok(Database::new(
primary_map,
secondary_index,
get_secondary_key,
))
}
pub fn get_simple_database<T>(&self, model_name: &str) -> Result<Database<T>, String>
where
T: CandidType + Serialize + for<'de> Deserialize<'de> + Clone,
{
self.get_database::<T, ()>(model_name, None)
}
pub fn insert<T>(&self, model_name: &str, key: &str, data: &T) -> Result<Document<T>, String>
where
T: CandidType + Serialize + for<'de> Deserialize<'de> + Clone,
{
let db = self.get_simple_database::<T>(model_name)?;
db.insert(model_name.to_string(), Some(key.to_string()), data.clone())
}
pub fn get<T>(&self, model_name: &str, key: &str) -> Result<T, String>
where
T: CandidType + Serialize + for<'de> Deserialize<'de> + Clone,
{
let db = self.get_simple_database::<T>(model_name)?;
let document = db.get(model_name, Some(key.to_string()))?;
Ok(document.data)
}
pub fn query<T>(
&self,
model_name: &str,
page_size: usize,
page_number: usize,
) -> Result<QueryResponse<T>, String>
where
T: CandidType + Serialize + for<'de> Deserialize<'de> + Clone,
{
let db = self.get_simple_database::<T>(model_name)?;
db.query(Some(model_name), None, page_size, page_number)
}
pub fn list_models(&self) -> Vec<String> {
self.registered_models.borrow().keys().cloned().collect()
}
pub fn is_model_registered(&self, model_name: &str) -> bool {
self.registered_models.borrow().contains_key(model_name)
}
pub fn get_model_info(&self, model_name: &str) -> Option<(MemoryId, Option<MemoryId>)> {
self.registered_models
.borrow()
.get(model_name)
.map(|info| (info.primary_memory_id, info.secondary_memory_id))
}
pub fn reserve_memory_range(
&self,
start: u8,
end: u8,
description: &str,
) -> Result<(), String> {
let mut models = self.registered_models.borrow_mut();
for id in start..=end {
let memory_id = MemoryId::new(id);
for (_, info) in models.iter() {
if info.primary_memory_id == memory_id {
return Err(format!("Memory ID {} is already in use", id));
}
if let Some(secondary_id) = info.secondary_memory_id {
if secondary_id == memory_id {
return Err(format!("Memory ID {} is already in use", id));
}
}
}
}
models.insert(
format!("__reserved_{}_{}_{}", start, end, description),
ModelInfo {
_model_name: description.to_string(),
primary_memory_id: MemoryId::new(start),
secondary_memory_id: Some(MemoryId::new(end)),
},
);
let mut next_id = self.next_memory_id.borrow_mut();
if *next_id <= end {
*next_id = end + 1;
}
Ok(())
}
}
impl Default for DatabaseManager {
fn default() -> Self {
Self::new()
}
}