use std::collections::BTreeMap;
use std::path::PathBuf;
use crate::StorageError;
use crate::StorageResult;
pub use crate::metadata::CollectionKind;
use crate::metadata::{FileInfo, GeneMetadata};
pub(crate) const RESERVED_PROPERTIES: [&str; 6] = [
"filetype",
"storage_format",
"rows",
"cols",
"nnz",
"size_bytes",
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TableDescriptor {
pub name: String,
pub format: String,
pub base_location: PathBuf,
pub kind: CollectionKind,
pub properties: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VectorSpaceDescriptor {
pub vectors: TableDescriptor,
pub graph: Option<TableDescriptor>,
}
pub trait Catalog {
fn list_tables(&self) -> StorageResult<Vec<TableDescriptor>>;
fn table_exists(&self, name: &str) -> StorageResult<bool>;
fn describe_table(&self, name: &str) -> StorageResult<TableDescriptor>;
fn register_table(&mut self, table: TableDescriptor) -> StorageResult<()>;
fn deregister_table(&mut self, name: &str) -> StorageResult<()>;
fn describe_vector_space(&self, name: &str) -> StorageResult<VectorSpaceDescriptor> {
let vectors = self.describe_table(name)?;
if vectors.kind != CollectionKind::VectorSpace {
return Err(StorageError::Invalid(format!(
"collection '{name}' has kind '{}', not a vector space",
vectors.kind.as_str()
)));
}
let graph = match vectors.properties.get("graph") {
Some(graph_name) => Some(self.describe_table(graph_name)?),
None => None,
};
Ok(VectorSpaceDescriptor { vectors, graph })
}
}
#[derive(Debug, Clone)]
pub struct LocalRegistry {
metadata: GeneMetadata,
base: PathBuf,
}
impl LocalRegistry {
pub fn new(metadata: GeneMetadata, base: PathBuf) -> Self {
Self { metadata, base }
}
pub fn metadata(&self) -> &GeneMetadata {
&self.metadata
}
pub fn into_metadata(self) -> GeneMetadata {
self.metadata
}
fn descriptor(&self, key: &str, info: &FileInfo) -> TableDescriptor {
let kind = info
.kind
.or_else(|| CollectionKind::for_filetype(&info.filetype))
.unwrap_or(CollectionKind::Table);
let mut properties = BTreeMap::new();
properties.insert("filetype".to_string(), info.filetype.clone());
properties.insert("storage_format".to_string(), info.storage_format.clone());
properties.insert("rows".to_string(), info.rows.to_string());
properties.insert("cols".to_string(), info.cols.to_string());
if let Some(nnz) = info.nnz {
properties.insert("nnz".to_string(), nnz.to_string());
}
if let Some(size) = info.size_bytes {
properties.insert("size_bytes".to_string(), size.to_string());
}
for (k, v) in &info.properties {
properties.insert(k.clone(), v.clone());
}
properties.insert("kind".to_string(), kind.as_str().to_string());
TableDescriptor {
name: key.to_string(),
format: "lance".to_string(),
base_location: self.base.join(&info.filename),
kind,
properties,
}
}
}
impl Catalog for LocalRegistry {
fn list_tables(&self) -> StorageResult<Vec<TableDescriptor>> {
let mut tables: Vec<TableDescriptor> = self
.metadata
.files
.iter()
.map(|(key, info)| self.descriptor(key, info))
.collect();
tables.sort_by(|a, b| a.name.cmp(&b.name));
Ok(tables)
}
fn table_exists(&self, name: &str) -> StorageResult<bool> {
Ok(self.metadata.files.contains_key(name))
}
fn describe_table(&self, name: &str) -> StorageResult<TableDescriptor> {
let info =
self.metadata.files.get(name).ok_or_else(|| {
StorageError::Invalid(format!("table '{name}' is not registered"))
})?;
Ok(self.descriptor(name, info))
}
fn register_table(&mut self, table: TableDescriptor) -> StorageResult<()> {
if table.format != "lance" {
return Err(StorageError::UnsupportedFormat(format!(
"unsupported table format '{}'",
table.format
)));
}
let prop = |key: &str| table.properties.get(key).cloned();
let parse = |key: &str| -> StorageResult<Option<usize>> {
prop(key)
.map(|v| {
v.parse::<usize>().map_err(|_| {
StorageError::Invalid(format!(
"property '{key}' = '{v}' is not a valid integer"
))
})
})
.transpose()
};
let kind = match prop("kind") {
Some(k) => {
let prop_kind = CollectionKind::parse(&k)?;
if prop_kind != table.kind {
return Err(StorageError::Invalid(format!(
"kind mismatch for table '{}': typed field is '{}', \
'kind' property is '{}'",
table.name,
table.kind.as_str(),
prop_kind.as_str()
)));
}
Some(prop_kind)
}
None => Some(table.kind),
};
let user_properties: BTreeMap<String, String> = table
.properties
.iter()
.filter(|(k, _)| !RESERVED_PROPERTIES.contains(&k.as_str()) && k.as_str() != "kind")
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
let mut info = FileInfo::new(
table
.base_location
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| format!("{}_{}.lance", self.metadata.name_id, table.name)),
prop("filetype").as_deref().unwrap_or("vector"),
(parse("rows")?.unwrap_or(0), parse("cols")?.unwrap_or(0)),
parse("nnz")?,
None,
)?;
info.kind = kind;
info.properties = user_properties;
self.metadata.files.insert(table.name, info);
Ok(())
}
fn deregister_table(&mut self, name: &str) -> StorageResult<()> {
if self.metadata.files.remove(name).is_none() {
return Err(StorageError::Invalid(format!(
"table '{name}' is not registered"
)));
}
Ok(())
}
}