use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use super::registry::TenantRegistry;
use super::types::{
TenantAuditLog, TenantConfig, TenantError, TenantId, TenantResult, TenantStats,
};
type TripleMap = Arc<RwLock<HashMap<String, Vec<(String, String)>>>>;
#[derive(Debug)]
pub struct TenantStore {
registry: Arc<TenantRegistry>,
data: TripleMap,
}
impl TenantStore {
pub fn new(registry: Arc<TenantRegistry>) -> Self {
Self {
registry,
data: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn insert(
&self,
tenant: &TenantId,
subject: &str,
predicate: &str,
object: &str,
) -> TenantResult<()> {
self.registry.pre_write_check(tenant, predicate, None)?;
let key = format!("{}{}", tenant.namespace_prefix(), subject);
let mut guard = self
.data
.write()
.map_err(|_| TenantError::NotFound("store lock poisoned".to_string()))?;
guard
.entry(key)
.or_default()
.push((predicate.to_string(), object.to_string()));
Ok(())
}
pub fn query(
&self,
tenant: &TenantId,
subject_filter: Option<&str>,
) -> TenantResult<Vec<(String, String, String)>> {
if !self.registry.exists(tenant) {
return Err(TenantError::NotFound(tenant.as_str().to_string()));
}
self.registry.record_read(tenant);
let prefix = tenant.namespace_prefix();
let guard = self
.data
.read()
.map_err(|_| TenantError::NotFound("store lock poisoned".to_string()))?;
let mut results = Vec::new();
for (key, po_list) in guard.iter() {
if let Some(subject) = key.strip_prefix(&prefix) {
if let Some(filter) = subject_filter {
if subject != filter {
continue;
}
}
for (pred, obj) in po_list {
results.push((subject.to_string(), pred.clone(), obj.clone()));
}
}
}
Ok(results)
}
pub fn delete_subject(&self, tenant: &TenantId, subject: &str) -> TenantResult<usize> {
if !self.registry.exists(tenant) {
return Err(TenantError::NotFound(tenant.as_str().to_string()));
}
let key = format!("{}{}", tenant.namespace_prefix(), subject);
let mut guard = self
.data
.write()
.map_err(|_| TenantError::NotFound("store lock poisoned".to_string()))?;
let removed = guard.remove(&key).map(|v| v.len()).unwrap_or(0);
for _ in 0..removed {
self.registry.record_delete(tenant);
}
Ok(removed)
}
pub fn purge(&self, tenant: &TenantId) -> TenantResult<u64> {
if !self.registry.exists(tenant) {
return Err(TenantError::NotFound(tenant.as_str().to_string()));
}
let prefix = tenant.namespace_prefix();
let mut guard = self
.data
.write()
.map_err(|_| TenantError::NotFound("store lock poisoned".to_string()))?;
let keys_to_remove: Vec<String> = guard
.keys()
.filter(|k| k.starts_with(&prefix))
.cloned()
.collect();
let mut total: u64 = 0;
for key in &keys_to_remove {
if let Some(pairs) = guard.remove(key) {
total += pairs.len() as u64;
}
}
Ok(total)
}
pub fn triple_count(&self, tenant: &TenantId) -> TenantResult<u64> {
if !self.registry.exists(tenant) {
return Err(TenantError::NotFound(tenant.as_str().to_string()));
}
let prefix = tenant.namespace_prefix();
let guard = self
.data
.read()
.map_err(|_| TenantError::NotFound("store lock poisoned".to_string()))?;
let count = guard
.iter()
.filter(|(k, _)| k.starts_with(&prefix))
.map(|(_, v)| v.len() as u64)
.sum();
Ok(count)
}
pub fn cross_tenant_access_check(
&self,
accessor: &TenantId,
target: &TenantId,
) -> TenantResult<()> {
if accessor == target {
return Ok(());
}
self.registry
.audit_log()
.record_cross_tenant(accessor.clone(), target.clone(), true);
Err(TenantError::CrossTenantAccess {
accessor: accessor.as_str().to_string(),
target: target.as_str().to_string(),
})
}
}
#[derive(Debug, Clone)]
pub struct TenantHandle {
tenant_id: TenantId,
registry: Arc<TenantRegistry>,
store: Arc<TenantStore>,
}
impl TenantHandle {
pub fn tenant_id(&self) -> &TenantId {
&self.tenant_id
}
pub fn insert_triple(&self, subject: &str, predicate: &str, object: &str) -> TenantResult<()> {
self.store
.insert(&self.tenant_id, subject, predicate, object)
}
pub fn insert_triple_in_graph(
&self,
subject: &str,
predicate: &str,
object: &str,
graph: &str,
) -> TenantResult<()> {
self.registry
.pre_write_check_graph_only(&self.tenant_id, graph)?;
self.store
.insert(&self.tenant_id, subject, predicate, object)
}
pub fn query(
&self,
subject_filter: Option<&str>,
) -> TenantResult<Vec<(String, String, String)>> {
self.store.query(&self.tenant_id, subject_filter)
}
pub fn triple_count(&self) -> u64 {
self.store.triple_count(&self.tenant_id).unwrap_or(0)
}
pub fn graph_count(&self) -> u32 {
self.registry
.stats(&self.tenant_id)
.map(|s| s.graph_count as u32)
.unwrap_or(0)
}
pub fn stats(&self) -> TenantResult<TenantStats> {
self.registry.stats(&self.tenant_id)
}
pub fn delete_subject(&self, subject: &str) -> TenantResult<usize> {
self.store.delete_subject(&self.tenant_id, subject)
}
pub fn purge(&self) -> TenantResult<u64> {
self.store.purge(&self.tenant_id)
}
}
#[derive(Debug)]
pub struct TenantIsolationLayer {
registry: Arc<TenantRegistry>,
store: Arc<TenantStore>,
}
impl TenantIsolationLayer {
pub fn new() -> Self {
let registry = Arc::new(TenantRegistry::new());
let store = Arc::new(TenantStore::new(Arc::clone(®istry)));
Self { registry, store }
}
pub fn create_tenant(&self, id: TenantId, config: TenantConfig) -> TenantResult<TenantHandle> {
self.registry.create_tenant(id.clone(), config)?;
Ok(TenantHandle {
tenant_id: id,
registry: Arc::clone(&self.registry),
store: Arc::clone(&self.store),
})
}
pub fn get_tenant(&self, id: &TenantId) -> Option<TenantHandle> {
if self.registry.exists(id) {
Some(TenantHandle {
tenant_id: id.clone(),
registry: Arc::clone(&self.registry),
store: Arc::clone(&self.store),
})
} else {
None
}
}
pub fn list_tenants(&self) -> Vec<TenantId> {
self.registry.list_tenants()
}
pub fn delete_tenant(&self, id: &TenantId) -> TenantResult<u64> {
let deleted = self.store.purge(id).unwrap_or(0);
self.registry.delete_tenant(id)?;
Ok(deleted)
}
pub fn registry(&self) -> Arc<TenantRegistry> {
Arc::clone(&self.registry)
}
pub fn audit_log(&self) -> Arc<TenantAuditLog> {
self.registry.audit_log()
}
pub fn exists(&self, id: &TenantId) -> bool {
self.registry.exists(id)
}
pub fn graph_iri(id: &TenantId, local_name: &str) -> String {
format!("urn:tenant:{}:{}", id.as_str(), local_name)
}
}
impl Default for TenantIsolationLayer {
fn default() -> Self {
Self::new()
}
}