use std::collections::{BTreeMap, HashMap};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{OnceLock, RwLock};
use crate::{Error, Result};
#[derive(Debug, Clone)]
pub struct CachedTableInfo {
pub storage_location: Option<String>,
pub format: String,
}
static TABLE_INFO_CACHE_EPOCH: AtomicU64 = AtomicU64::new(0);
#[derive(Clone)]
struct CacheEntry {
info: CachedTableInfo,
epoch: u64,
}
fn table_info_cache() -> &'static RwLock<HashMap<String, CacheEntry>> {
static CACHE: OnceLock<RwLock<HashMap<String, CacheEntry>>> = OnceLock::new();
CACHE.get_or_init(|| RwLock::new(HashMap::new()))
}
fn invalidate_cache() {
TABLE_INFO_CACHE_EPOCH.fetch_add(1, Ordering::Relaxed);
}
#[derive(Clone, Debug)]
pub struct CatalogInfo {
pub name: String,
pub comment: Option<String>,
pub storage_root: Option<String>,
}
#[derive(Clone, Debug)]
pub struct NamespaceInfo {
pub name: String,
pub catalog_name: String,
pub comment: Option<String>,
pub storage_root: Option<String>,
}
#[derive(Clone, Debug)]
pub struct ColumnInfo {
pub name: String,
pub type_name: String,
pub position: usize,
pub nullable: bool,
pub comment: Option<String>,
}
#[derive(Clone, Debug)]
pub struct TableInfo {
pub name: String,
pub catalog_name: String,
pub namespace_name: String,
pub storage_location: Option<String>,
pub data_source_format: Option<String>,
pub columns: Vec<ColumnInfo>,
}
#[derive(Debug, Default)]
struct CatalogStore {
catalogs: BTreeMap<String, CatalogEntry>,
}
#[derive(Debug)]
struct CatalogEntry {
info: CatalogInfo,
namespaces: BTreeMap<String, NamespaceEntry>,
}
#[derive(Debug)]
struct NamespaceEntry {
info: NamespaceInfo,
tables: BTreeMap<String, TableInfo>,
}
fn catalog_store() -> &'static RwLock<CatalogStore> {
static STORE: OnceLock<RwLock<CatalogStore>> = OnceLock::new();
STORE.get_or_init(|| RwLock::new(CatalogStore::default()))
}
pub struct Catalog;
impl Catalog {
pub fn list_catalogs() -> Result<Vec<CatalogInfo>> {
let guard = catalog_store()
.read()
.map_err(|_| Error::CatalogLockPoisoned)?;
Ok(guard
.catalogs
.values()
.map(|entry| entry.info.clone())
.collect())
}
pub fn list_namespaces(catalog_name: &str) -> Result<Vec<NamespaceInfo>> {
let guard = catalog_store()
.read()
.map_err(|_| Error::CatalogLockPoisoned)?;
let catalog = guard
.catalogs
.get(catalog_name)
.ok_or_else(|| Error::CatalogNotFound(catalog_name.to_string()))?;
Ok(catalog
.namespaces
.values()
.map(|entry| entry.info.clone())
.collect())
}
pub fn list_tables(catalog_name: &str, namespace_name: &str) -> Result<Vec<TableInfo>> {
let guard = catalog_store()
.read()
.map_err(|_| Error::CatalogLockPoisoned)?;
let catalog = guard
.catalogs
.get(catalog_name)
.ok_or_else(|| Error::CatalogNotFound(catalog_name.to_string()))?;
let namespace = catalog.namespaces.get(namespace_name).ok_or_else(|| {
Error::NamespaceNotFound(catalog_name.to_string(), namespace_name.to_string())
})?;
Ok(namespace.tables.values().cloned().collect::<Vec<_>>())
}
pub fn get_table_info(
catalog_name: &str,
namespace_name: &str,
table_name: &str,
) -> Result<TableInfo> {
let guard = catalog_store()
.read()
.map_err(|_| Error::CatalogLockPoisoned)?;
let catalog = guard
.catalogs
.get(catalog_name)
.ok_or_else(|| Error::CatalogNotFound(catalog_name.to_string()))?;
let namespace = catalog.namespaces.get(namespace_name).ok_or_else(|| {
Error::NamespaceNotFound(catalog_name.to_string(), namespace_name.to_string())
})?;
namespace.tables.get(table_name).cloned().ok_or_else(|| {
Error::TableNotFound(format!(
"{}.{}.{}",
catalog_name, namespace_name, table_name
))
})
}
pub fn get_table_info_cached(
catalog_name: &str,
namespace_name: &str,
table_name: &str,
) -> Result<CachedTableInfo> {
let cache_key = format!("{}.{}.{}", catalog_name, namespace_name, table_name);
let current_epoch = TABLE_INFO_CACHE_EPOCH.load(Ordering::Relaxed);
if let Ok(cache) = table_info_cache().read() {
if let Some(entry) = cache.get(&cache_key) {
if entry.epoch == current_epoch {
return Ok(entry.info.clone());
}
}
}
let info = Self::get_table_info(catalog_name, namespace_name, table_name)?;
let cached = CachedTableInfo {
storage_location: info.storage_location.clone(),
format: info
.data_source_format
.as_deref()
.unwrap_or("PARQUET")
.to_ascii_uppercase(),
};
if let Ok(mut cache) = table_info_cache().write() {
cache.insert(
cache_key,
CacheEntry {
info: cached.clone(),
epoch: current_epoch,
},
);
}
Ok(cached)
}
pub fn create_catalog(name: &str) -> Result<()> {
invalidate_cache();
let mut guard = catalog_store()
.write()
.map_err(|_| Error::CatalogLockPoisoned)?;
if guard.catalogs.contains_key(name) {
return Err(Error::CatalogAlreadyExists(name.to_string()));
}
guard.catalogs.insert(
name.to_string(),
CatalogEntry {
info: CatalogInfo {
name: name.to_string(),
comment: None,
storage_root: None,
},
namespaces: BTreeMap::new(),
},
);
Ok(())
}
pub fn delete_catalog(name: &str) -> Result<()> {
invalidate_cache();
let mut guard = catalog_store()
.write()
.map_err(|_| Error::CatalogLockPoisoned)?;
if guard.catalogs.remove(name).is_none() {
return Err(Error::CatalogNotFound(name.to_string()));
}
Ok(())
}
pub fn create_namespace(catalog_name: &str, namespace_name: &str) -> Result<()> {
invalidate_cache();
let mut guard = catalog_store()
.write()
.map_err(|_| Error::CatalogLockPoisoned)?;
let catalog = guard
.catalogs
.get_mut(catalog_name)
.ok_or_else(|| Error::CatalogNotFound(catalog_name.to_string()))?;
if catalog.namespaces.contains_key(namespace_name) {
return Err(Error::NamespaceAlreadyExists(
catalog_name.to_string(),
namespace_name.to_string(),
));
}
catalog.namespaces.insert(
namespace_name.to_string(),
NamespaceEntry {
info: NamespaceInfo {
name: namespace_name.to_string(),
catalog_name: catalog_name.to_string(),
comment: None,
storage_root: None,
},
tables: BTreeMap::new(),
},
);
Ok(())
}
pub fn delete_namespace(catalog_name: &str, namespace_name: &str) -> Result<()> {
invalidate_cache();
let mut guard = catalog_store()
.write()
.map_err(|_| Error::CatalogLockPoisoned)?;
let catalog = guard
.catalogs
.get_mut(catalog_name)
.ok_or_else(|| Error::CatalogNotFound(catalog_name.to_string()))?;
if catalog.namespaces.remove(namespace_name).is_none() {
return Err(Error::NamespaceNotFound(
catalog_name.to_string(),
namespace_name.to_string(),
));
}
Ok(())
}
pub fn create_table(
catalog_name: &str,
namespace_name: &str,
table_name: &str,
columns: Vec<ColumnInfo>,
storage_location: Option<String>,
data_source_format: Option<String>,
) -> Result<()> {
invalidate_cache();
if let Some(format) = data_source_format.as_deref() {
if format != "parquet" {
return Err(Error::UnsupportedDataSourceFormat(format.to_string()));
}
}
let mut guard = catalog_store()
.write()
.map_err(|_| Error::CatalogLockPoisoned)?;
let catalog = guard
.catalogs
.get_mut(catalog_name)
.ok_or_else(|| Error::CatalogNotFound(catalog_name.to_string()))?;
let namespace = catalog.namespaces.get_mut(namespace_name).ok_or_else(|| {
Error::NamespaceNotFound(catalog_name.to_string(), namespace_name.to_string())
})?;
if namespace.tables.contains_key(table_name) {
return Err(Error::TableAlreadyExists(format!(
"{}.{}.{}",
catalog_name, namespace_name, table_name
)));
}
namespace.tables.insert(
table_name.to_string(),
TableInfo {
name: table_name.to_string(),
catalog_name: catalog_name.to_string(),
namespace_name: namespace_name.to_string(),
storage_location,
data_source_format,
columns,
},
);
Ok(())
}
pub fn delete_table(catalog_name: &str, namespace_name: &str, table_name: &str) -> Result<()> {
invalidate_cache();
let mut guard = catalog_store()
.write()
.map_err(|_| Error::CatalogLockPoisoned)?;
let catalog = guard
.catalogs
.get_mut(catalog_name)
.ok_or_else(|| Error::CatalogNotFound(catalog_name.to_string()))?;
let namespace = catalog.namespaces.get_mut(namespace_name).ok_or_else(|| {
Error::NamespaceNotFound(catalog_name.to_string(), namespace_name.to_string())
})?;
if namespace.tables.remove(table_name).is_none() {
return Err(Error::TableNotFound(format!(
"{}.{}.{}",
catalog_name, namespace_name, table_name
)));
}
Ok(())
}
}