use crate::error::{DbxError, DbxResult};
use std::collections::HashMap;
use std::sync::RwLock;
#[derive(Debug, Clone)]
pub struct IndexMeta {
pub name: String,
pub table: String,
pub columns: Vec<String>,
pub index_type: IndexType,
pub version: u64,
pub status: IndexStatus,
}
#[derive(Debug, Clone, PartialEq)]
pub enum IndexType {
Hash,
BTree,
Bitmap,
}
#[derive(Debug, Clone, PartialEq)]
pub enum IndexStatus {
Building,
Ready,
Disabled,
}
pub struct IndexVersionManager {
versions: RwLock<HashMap<String, Vec<IndexMeta>>>,
active: RwLock<HashMap<String, u64>>,
}
impl IndexVersionManager {
pub fn new() -> Self {
Self {
versions: RwLock::new(HashMap::new()),
active: RwLock::new(HashMap::new()),
}
}
pub fn create_index(
&self,
name: &str,
table: &str,
columns: Vec<String>,
index_type: IndexType,
) -> DbxResult<u64> {
let meta = IndexMeta {
name: name.to_string(),
table: table.to_string(),
columns,
index_type,
version: 1,
status: IndexStatus::Ready,
};
let mut versions = self
.versions
.write()
.map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
let mut active = self
.active
.write()
.map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
versions.insert(name.to_string(), vec![meta]);
active.insert(name.to_string(), 1);
Ok(1)
}
pub fn start_reindex(
&self,
name: &str,
columns: Vec<String>,
index_type: IndexType,
) -> DbxResult<u64> {
let mut versions = self
.versions
.write()
.map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
let history = versions
.get_mut(name)
.ok_or_else(|| DbxError::Serialization(format!("Index {name} not found")))?;
let last = history
.last()
.ok_or_else(|| DbxError::Serialization("Empty history".into()))?;
let new_version = last.version + 1;
history.push(IndexMeta {
name: name.to_string(),
table: last.table.clone(),
columns,
index_type,
version: new_version,
status: IndexStatus::Building,
});
Ok(new_version)
}
pub fn complete_reindex(&self, name: &str, version: u64) -> DbxResult<()> {
let mut versions = self
.versions
.write()
.map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
let mut active = self
.active
.write()
.map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
let history = versions
.get_mut(name)
.ok_or_else(|| DbxError::Serialization(format!("Index {name} not found")))?;
for meta in history.iter_mut() {
if meta.version == version {
meta.status = IndexStatus::Ready;
} else if meta.status == IndexStatus::Ready {
meta.status = IndexStatus::Disabled;
}
}
active.insert(name.to_string(), version);
Ok(())
}
pub fn get_active(&self, name: &str) -> DbxResult<IndexMeta> {
let versions = self
.versions
.read()
.map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
let active = self
.active
.read()
.map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
let active_ver = active
.get(name)
.ok_or_else(|| DbxError::Serialization(format!("Index {name} not found")))?;
let history = versions
.get(name)
.ok_or_else(|| DbxError::Serialization(format!("Index {name} not found")))?;
history
.iter()
.find(|m| m.version == *active_ver)
.cloned()
.ok_or_else(|| DbxError::Serialization(format!("Version {active_ver} not found")))
}
pub fn drop_index(&self, name: &str) -> DbxResult<()> {
let mut versions = self
.versions
.write()
.map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
let mut active = self
.active
.write()
.map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
versions.remove(name);
active.remove(name);
Ok(())
}
pub fn list_indexes(&self, table: &str) -> DbxResult<Vec<IndexMeta>> {
let versions = self
.versions
.read()
.map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
let active = self
.active
.read()
.map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
let mut result = Vec::new();
for (name, history) in versions.iter() {
if let Some(&active_ver) = active.get(name)
&& let Some(meta) = history
.iter()
.find(|m| m.version == active_ver && m.table == table)
{
result.push(meta.clone());
}
}
Ok(result)
}
}
impl Default for IndexVersionManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_index() {
let mgr = IndexVersionManager::new();
let ver = mgr
.create_index(
"idx_users_email",
"users",
vec!["email".into()],
IndexType::Hash,
)
.unwrap();
assert_eq!(ver, 1);
let meta = mgr.get_active("idx_users_email").unwrap();
assert_eq!(meta.table, "users");
assert_eq!(meta.status, IndexStatus::Ready);
}
#[test]
fn test_reindex_zero_downtime() {
let mgr = IndexVersionManager::new();
mgr.create_index("idx1", "users", vec!["name".into()], IndexType::Hash)
.unwrap();
let v2 = mgr
.start_reindex(
"idx1",
vec!["name".into(), "email".into()],
IndexType::BTree,
)
.unwrap();
assert_eq!(v2, 2);
let active = mgr.get_active("idx1").unwrap();
assert_eq!(active.version, 1);
mgr.complete_reindex("idx1", 2).unwrap();
let active = mgr.get_active("idx1").unwrap();
assert_eq!(active.version, 2);
assert_eq!(active.columns.len(), 2);
assert_eq!(active.index_type, IndexType::BTree);
}
#[test]
fn test_drop_index() {
let mgr = IndexVersionManager::new();
mgr.create_index("idx1", "users", vec!["name".into()], IndexType::Hash)
.unwrap();
mgr.drop_index("idx1").unwrap();
assert!(mgr.get_active("idx1").is_err());
}
#[test]
fn test_list_indexes() {
let mgr = IndexVersionManager::new();
mgr.create_index("idx1", "users", vec!["name".into()], IndexType::Hash)
.unwrap();
mgr.create_index("idx2", "users", vec!["email".into()], IndexType::BTree)
.unwrap();
mgr.create_index("idx3", "orders", vec!["id".into()], IndexType::Hash)
.unwrap();
let user_indexes = mgr.list_indexes("users").unwrap();
assert_eq!(user_indexes.len(), 2);
let order_indexes = mgr.list_indexes("orders").unwrap();
assert_eq!(order_indexes.len(), 1);
}
}