genegraph_storage/
catalog.rs1use std::collections::BTreeMap;
16use std::path::PathBuf;
17
18use crate::StorageError;
19use crate::StorageResult;
20use crate::metadata::{FileInfo, GeneMetadata};
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct TableDescriptor {
25 pub name: String,
27 pub format: String,
29 pub base_location: PathBuf,
31 pub properties: BTreeMap<String, String>,
33}
34
35pub trait Catalog {
37 fn list_tables(&self) -> StorageResult<Vec<TableDescriptor>>;
39 fn table_exists(&self, name: &str) -> StorageResult<bool>;
41 fn describe_table(&self, name: &str) -> StorageResult<TableDescriptor>;
43 fn register_table(&mut self, table: TableDescriptor) -> StorageResult<()>;
46 fn deregister_table(&mut self, name: &str) -> StorageResult<()>;
48}
49
50#[derive(Debug, Clone)]
57pub struct LocalRegistry {
58 metadata: GeneMetadata,
59 base: PathBuf,
60}
61
62impl LocalRegistry {
63 pub fn new(metadata: GeneMetadata, base: PathBuf) -> Self {
65 Self { metadata, base }
66 }
67
68 pub fn metadata(&self) -> &GeneMetadata {
70 &self.metadata
71 }
72
73 pub fn into_metadata(self) -> GeneMetadata {
75 self.metadata
76 }
77
78 fn descriptor(&self, key: &str, info: &FileInfo) -> TableDescriptor {
79 let mut properties = BTreeMap::new();
80 properties.insert("filetype".to_string(), info.filetype.clone());
81 properties.insert("storage_format".to_string(), info.storage_format.clone());
82 properties.insert("rows".to_string(), info.rows.to_string());
83 properties.insert("cols".to_string(), info.cols.to_string());
84 if let Some(nnz) = info.nnz {
85 properties.insert("nnz".to_string(), nnz.to_string());
86 }
87 if let Some(size) = info.size_bytes {
88 properties.insert("size_bytes".to_string(), size.to_string());
89 }
90 TableDescriptor {
91 name: key.to_string(),
92 format: "lance".to_string(),
93 base_location: self.base.join(&info.filename),
94 properties,
95 }
96 }
97}
98
99impl Catalog for LocalRegistry {
100 fn list_tables(&self) -> StorageResult<Vec<TableDescriptor>> {
101 let mut tables: Vec<TableDescriptor> = self
102 .metadata
103 .files
104 .iter()
105 .map(|(key, info)| self.descriptor(key, info))
106 .collect();
107 tables.sort_by(|a, b| a.name.cmp(&b.name));
108 Ok(tables)
109 }
110
111 fn table_exists(&self, name: &str) -> StorageResult<bool> {
112 Ok(self.metadata.files.contains_key(name))
113 }
114
115 fn describe_table(&self, name: &str) -> StorageResult<TableDescriptor> {
116 let info =
117 self.metadata.files.get(name).ok_or_else(|| {
118 StorageError::Invalid(format!("table '{name}' is not registered"))
119 })?;
120 Ok(self.descriptor(name, info))
121 }
122
123 fn register_table(&mut self, table: TableDescriptor) -> StorageResult<()> {
124 if table.format != "lance" {
125 return Err(StorageError::UnsupportedFormat(format!(
126 "unsupported table format '{}'",
127 table.format
128 )));
129 }
130 let prop = |key: &str| table.properties.get(key).cloned();
131 let parse = |key: &str| -> StorageResult<Option<usize>> {
132 prop(key)
133 .map(|v| {
134 v.parse::<usize>().map_err(|_| {
135 StorageError::Invalid(format!(
136 "property '{key}' = '{v}' is not a valid integer"
137 ))
138 })
139 })
140 .transpose()
141 };
142 let info = FileInfo::new(
143 table
144 .base_location
145 .file_name()
146 .map(|n| n.to_string_lossy().to_string())
147 .unwrap_or_else(|| format!("{}_{}.lance", self.metadata.name_id, table.name)),
148 prop("filetype").as_deref().unwrap_or("vector"),
149 (parse("rows")?.unwrap_or(0), parse("cols")?.unwrap_or(0)),
150 parse("nnz")?,
151 None,
152 )?;
153 self.metadata.files.insert(table.name, info);
154 Ok(())
155 }
156
157 fn deregister_table(&mut self, name: &str) -> StorageResult<()> {
158 if self.metadata.files.remove(name).is_none() {
159 return Err(StorageError::Invalid(format!(
160 "table '{name}' is not registered"
161 )));
162 }
163 Ok(())
164 }
165}