use crate::{Result, StorageError};
use dashmap::DashMap;
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum IndexType {
Column,
Vector,
Text,
Spatial,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexMetadata {
pub name: String,
pub table_name: String,
pub column_name: String,
pub index_type: IndexType,
pub created_at: u64,
#[serde(default)]
pub stale: bool,
}
impl IndexMetadata {
pub fn new(name: String, table_name: String, column_name: String, index_type: IndexType) -> Self {
let created_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
Self {
name,
table_name,
column_name,
index_type,
created_at,
stale: false,
}
}
}
pub struct IndexRegistry {
indexes: Arc<DashMap<String, IndexMetadata>>,
metadata_path: std::path::PathBuf,
}
impl IndexRegistry {
pub fn new(db_path: &Path) -> Self {
let metadata_path = db_path.join("index_metadata.bin");
Self {
indexes: Arc::new(DashMap::new()),
metadata_path,
}
}
pub fn load(&self) -> Result<()> {
if !self.metadata_path.exists() {
return Ok(());
}
let data = std::fs::read(&self.metadata_path)
.map_err(StorageError::Io)?;
let metadata_list: Vec<IndexMetadata> = bincode::deserialize(&data)
.map_err(|e| StorageError::Serialization(e.to_string()))?;
for metadata in metadata_list {
self.indexes.insert(metadata.name.clone(), metadata);
}
Ok(())
}
pub fn save(&self) -> Result<()> {
let metadata_list: Vec<IndexMetadata> = self.indexes.iter()
.map(|entry| entry.value().clone())
.collect();
let data = bincode::serialize(&metadata_list)
.map_err(|e| StorageError::Serialization(e.to_string()))?;
let tmp_path = self.metadata_path.with_extension("bin.tmp");
{
let mut f = std::fs::File::create(&tmp_path)
.map_err(StorageError::Io)?;
std::io::Write::write_all(&mut f, &data)
.map_err(StorageError::Io)?;
f.sync_all()
.map_err(StorageError::Io)?;
}
std::fs::rename(&tmp_path, &self.metadata_path)
.map_err(StorageError::Io)?;
Ok(())
}
pub fn register(&self, metadata: IndexMetadata) -> Result<()> {
if self.indexes.contains_key(&metadata.name) {
return Err(StorageError::Index(format!(
"Index '{}' already exists",
metadata.name
)));
}
self.indexes.insert(metadata.name.clone(), metadata);
self.save()?;
Ok(())
}
pub fn remove(&self, index_name: &str) -> Result<()> {
self.indexes.remove(index_name);
self.save()?;
Ok(())
}
pub fn get(&self, index_name: &str) -> Option<IndexMetadata> {
self.indexes.get(index_name).map(|entry| entry.value().clone())
}
pub fn list_table_indexes(&self, table_name: &str) -> Vec<IndexMetadata> {
self.indexes.iter()
.filter(|entry| entry.value().table_name == table_name)
.map(|entry| entry.value().clone())
.collect()
}
pub fn find_by_column(&self, table_name: &str, column_name: &str, index_type: IndexType) -> Option<String> {
self.indexes.iter()
.find(|entry| {
let meta = entry.value();
meta.table_name == table_name
&& meta.column_name == column_name
&& meta.index_type == index_type
})
.map(|entry| entry.key().clone())
}
pub fn generate_name(table_name: &str, column_name: &str, index_type: &IndexType) -> String {
match index_type {
IndexType::Column => format!("{}.{}", table_name, column_name),
IndexType::Vector | IndexType::Text | IndexType::Spatial => {
format!("{}_{}", table_name, column_name)
}
}
}
pub fn mark_stale(&self, index_name: &str) {
if let Some(mut entry) = self.indexes.get_mut(index_name) {
entry.stale = true;
}
let _ = self.save();
}
pub fn is_stale(&self, index_name: &str) -> bool {
self.indexes.get(index_name)
.map(|e| e.stale)
.unwrap_or(false)
}
pub fn clear_stale(&self, index_name: &str) {
if let Some(mut entry) = self.indexes.get_mut(index_name) {
entry.stale = false;
}
let _ = self.save();
}
pub fn list_stale_indexes(&self, table_name: &str) -> Vec<IndexMetadata> {
self.indexes.iter()
.filter(|entry| {
let meta = entry.value();
meta.table_name == table_name && meta.stale
})
.map(|entry| entry.value().clone())
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_index_metadata_registry() {
let dir = tempdir().unwrap();
let registry = IndexRegistry::new(dir.path());
let metadata = IndexMetadata::new(
"idx_users_age".to_string(),
"users".to_string(),
"age".to_string(),
IndexType::Column,
);
registry.register(metadata.clone()).unwrap();
let retrieved = registry.get("idx_users_age").unwrap();
assert_eq!(retrieved.name, "idx_users_age");
assert_eq!(retrieved.table_name, "users");
assert_eq!(retrieved.column_name, "age");
let indexes = registry.list_table_indexes("users");
assert_eq!(indexes.len(), 1);
let found = registry.find_by_column("users", "age", IndexType::Column);
assert_eq!(found, Some("idx_users_age".to_string()));
registry.remove("idx_users_age").unwrap();
assert!(registry.get("idx_users_age").is_none());
}
}