rushdb_storage/
tenant_dir.rs1use std::collections::HashMap;
12
13use rushdb_types::ids::{TenantId, TenantScope};
14use rushdb_types::keys;
15
16use crate::{StorageError, Store};
17
18const CACHE_ENTRIES: usize = 65_536;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum TenantState {
25 Active,
27 Purged,
29}
30
31impl TenantState {
32 fn to_byte(self) -> u8 {
33 match self {
34 Self::Active => 0,
35 Self::Purged => 1,
36 }
37 }
38
39 fn from_byte(byte: u8) -> Result<Self, StorageError> {
40 match byte {
41 0 => Ok(Self::Active),
42 1 => Ok(Self::Purged),
43 _ => Err(StorageError::CorruptLog),
44 }
45 }
46}
47
48#[must_use]
50pub fn encode_by_scope(tenant: TenantId, state: TenantState) -> Vec<u8> {
51 let mut out = tenant.to_be_bytes().to_vec();
52 out.push(state.to_byte());
53 out
54}
55
56pub fn decode_by_scope(raw: &[u8]) -> Result<(TenantId, TenantState), StorageError> {
61 let (ordinal, state) = raw.split_at_checked(4).ok_or(StorageError::CorruptLog)?;
62 let ordinal: [u8; 4] = ordinal.try_into().map_err(|_| StorageError::CorruptLog)?;
63 let [state] = state else {
64 return Err(StorageError::CorruptLog);
65 };
66 Ok((
67 TenantId(u32::from_be_bytes(ordinal)),
68 TenantState::from_byte(*state)?,
69 ))
70}
71
72#[must_use]
74pub fn encode_by_ord(scope: &TenantScope, state: TenantState) -> Vec<u8> {
75 let mut out = scope.0.to_vec();
76 out.push(state.to_byte());
77 out
78}
79
80pub fn decode_by_ord(raw: &[u8]) -> Result<(TenantScope, TenantState), StorageError> {
85 let (digest, state) = raw.split_at_checked(32).ok_or(StorageError::CorruptLog)?;
86 let digest: [u8; 32] = digest.try_into().map_err(|_| StorageError::CorruptLog)?;
87 let [state] = state else {
88 return Err(StorageError::CorruptLog);
89 };
90 Ok((TenantScope(digest), TenantState::from_byte(*state)?))
91}
92
93pub fn decode_seq(raw: Option<&[u8]>) -> Result<u32, StorageError> {
98 raw.map_or(Ok(TenantId::FIRST_ASSIGNED), |raw| {
99 raw.try_into()
100 .map(u32::from_be_bytes)
101 .map_err(|_| StorageError::CorruptLog)
102 })
103}
104
105#[derive(Default)]
107pub(crate) struct TenantCache {
108 entries: HashMap<TenantScope, TenantId>,
109}
110
111impl Store {
112 pub fn resolve_tenant(&self, scope: &TenantScope) -> Result<Option<TenantId>, StorageError> {
119 if let Some(tenant) = self
120 .health
121 .guard(self.tenant_cache.lock())?
122 .entries
123 .get(scope)
124 {
125 return Ok(Some(*tenant));
126 }
127 let Some(raw) = self.try_get(&keys::tenant_by_scope_key(scope))? else {
128 return Ok(None);
129 };
130 let (tenant, state) = decode_by_scope(&raw)?;
131 if state != TenantState::Active {
132 return Ok(None);
133 }
134 let mut cache = self.health.guard(self.tenant_cache.lock())?;
135 if cache.entries.len() >= CACHE_ENTRIES {
136 cache.entries.clear();
137 }
138 cache.entries.insert(*scope, tenant);
139 Ok(Some(tenant))
140 }
141
142 pub fn tenant_scope(
148 &self,
149 tenant: TenantId,
150 ) -> Result<Option<(TenantScope, TenantState)>, StorageError> {
151 self.try_get(&keys::tenant_by_ord_key(tenant))?
152 .map(|raw| decode_by_ord(&raw))
153 .transpose()
154 }
155
156 pub fn active_tenants(&self) -> Result<Vec<(TenantId, TenantScope)>, StorageError> {
162 let prefix = keys::tenant_by_ord_prefix();
163 let mut out = Vec::new();
164 for (key, raw) in self.try_range_prefix(&prefix)? {
165 let ordinal: [u8; 4] = key
166 .get(prefix.len()..)
167 .and_then(|bytes| bytes.try_into().ok())
168 .ok_or(StorageError::CorruptLog)?;
169 let (scope, state) = decode_by_ord(&raw)?;
170 if state == TenantState::Active {
171 out.push((TenantId(u32::from_be_bytes(ordinal)), scope));
172 }
173 }
174 Ok(out)
175 }
176
177 pub fn forget_tenant(&self, scope: &TenantScope) {
181 self.health
182 .cleanup(self.tenant_cache.lock())
183 .entries
184 .remove(scope);
185 }
186}