genegraph_storage/
catalog.rs1use std::collections::BTreeMap;
16use std::path::PathBuf;
17
18use crate::StorageError;
19use crate::StorageResult;
20pub use crate::metadata::CollectionKind;
21use crate::metadata::{FileInfo, GeneMetadata};
22
23pub(crate) const RESERVED_PROPERTIES: [&str; 6] = [
27 "filetype",
28 "storage_format",
29 "rows",
30 "cols",
31 "nnz",
32 "size_bytes",
33];
34
35#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct TableDescriptor {
38 pub name: String,
40 pub format: String,
42 pub base_location: PathBuf,
44 pub kind: CollectionKind,
47 pub properties: BTreeMap<String, String>,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct VectorSpaceDescriptor {
59 pub vectors: TableDescriptor,
61 pub graph: Option<TableDescriptor>,
63}
64
65pub trait Catalog {
67 fn list_tables(&self) -> StorageResult<Vec<TableDescriptor>>;
69 fn table_exists(&self, name: &str) -> StorageResult<bool>;
71 fn describe_table(&self, name: &str) -> StorageResult<TableDescriptor>;
73 fn register_table(&mut self, table: TableDescriptor) -> StorageResult<()>;
76 fn deregister_table(&mut self, name: &str) -> StorageResult<()>;
78
79 fn describe_vector_space(&self, name: &str) -> StorageResult<VectorSpaceDescriptor> {
84 let vectors = self.describe_table(name)?;
85 if vectors.kind != CollectionKind::VectorSpace {
86 return Err(StorageError::Invalid(format!(
87 "collection '{name}' has kind '{}', not a vector space",
88 vectors.kind.as_str()
89 )));
90 }
91 let graph = match vectors.properties.get("graph") {
92 Some(graph_name) => Some(self.describe_table(graph_name)?),
93 None => None,
94 };
95 Ok(VectorSpaceDescriptor { vectors, graph })
96 }
97}
98
99#[derive(Debug, Clone)]
106pub struct LocalRegistry {
107 metadata: GeneMetadata,
108 base: PathBuf,
109}
110
111impl LocalRegistry {
112 pub fn new(metadata: GeneMetadata, base: PathBuf) -> Self {
114 Self { metadata, base }
115 }
116
117 pub fn metadata(&self) -> &GeneMetadata {
119 &self.metadata
120 }
121
122 pub fn into_metadata(self) -> GeneMetadata {
124 self.metadata
125 }
126
127 fn descriptor(&self, key: &str, info: &FileInfo) -> TableDescriptor {
128 let kind = info
129 .kind
130 .or_else(|| CollectionKind::for_filetype(&info.filetype))
131 .unwrap_or(CollectionKind::Table);
132 let mut properties = BTreeMap::new();
133 properties.insert("filetype".to_string(), info.filetype.clone());
134 properties.insert("storage_format".to_string(), info.storage_format.clone());
135 properties.insert("rows".to_string(), info.rows.to_string());
136 properties.insert("cols".to_string(), info.cols.to_string());
137 if let Some(nnz) = info.nnz {
138 properties.insert("nnz".to_string(), nnz.to_string());
139 }
140 if let Some(size) = info.size_bytes {
141 properties.insert("size_bytes".to_string(), size.to_string());
142 }
143 for (k, v) in &info.properties {
146 properties.insert(k.clone(), v.clone());
147 }
148 properties.insert("kind".to_string(), kind.as_str().to_string());
149 TableDescriptor {
150 name: key.to_string(),
151 format: "lance".to_string(),
152 base_location: self.base.join(&info.filename),
153 kind,
154 properties,
155 }
156 }
157}
158
159impl Catalog for LocalRegistry {
160 fn list_tables(&self) -> StorageResult<Vec<TableDescriptor>> {
161 let mut tables: Vec<TableDescriptor> = self
162 .metadata
163 .files
164 .iter()
165 .map(|(key, info)| self.descriptor(key, info))
166 .collect();
167 tables.sort_by(|a, b| a.name.cmp(&b.name));
168 Ok(tables)
169 }
170
171 fn table_exists(&self, name: &str) -> StorageResult<bool> {
172 Ok(self.metadata.files.contains_key(name))
173 }
174
175 fn describe_table(&self, name: &str) -> StorageResult<TableDescriptor> {
176 let info =
177 self.metadata.files.get(name).ok_or_else(|| {
178 StorageError::Invalid(format!("table '{name}' is not registered"))
179 })?;
180 Ok(self.descriptor(name, info))
181 }
182
183 fn register_table(&mut self, table: TableDescriptor) -> StorageResult<()> {
184 if table.format != "lance" {
185 return Err(StorageError::UnsupportedFormat(format!(
186 "unsupported table format '{}'",
187 table.format
188 )));
189 }
190 let prop = |key: &str| table.properties.get(key).cloned();
191 let parse = |key: &str| -> StorageResult<Option<usize>> {
192 prop(key)
193 .map(|v| {
194 v.parse::<usize>().map_err(|_| {
195 StorageError::Invalid(format!(
196 "property '{key}' = '{v}' is not a valid integer"
197 ))
198 })
199 })
200 .transpose()
201 };
202 let kind = match prop("kind") {
207 Some(k) => {
208 let prop_kind = CollectionKind::parse(&k)?;
209 if prop_kind != table.kind {
210 return Err(StorageError::Invalid(format!(
211 "kind mismatch for table '{}': typed field is '{}', \
212 'kind' property is '{}'",
213 table.name,
214 table.kind.as_str(),
215 prop_kind.as_str()
216 )));
217 }
218 Some(prop_kind)
219 }
220 None => Some(table.kind),
221 };
222 let user_properties: BTreeMap<String, String> = table
226 .properties
227 .iter()
228 .filter(|(k, _)| !RESERVED_PROPERTIES.contains(&k.as_str()) && k.as_str() != "kind")
229 .map(|(k, v)| (k.clone(), v.clone()))
230 .collect();
231 let mut info = FileInfo::new(
232 table
233 .base_location
234 .file_name()
235 .map(|n| n.to_string_lossy().to_string())
236 .unwrap_or_else(|| {
237 crate::generations::artifact_file_name(&self.metadata.name_id, &table.name)
238 }),
239 prop("filetype").as_deref().unwrap_or("vector"),
240 (parse("rows")?.unwrap_or(0), parse("cols")?.unwrap_or(0)),
241 parse("nnz")?,
242 None,
243 )?;
244 info.kind = kind;
245 info.properties = user_properties;
246 self.metadata.files.insert(table.name, info);
247 Ok(())
248 }
249
250 fn deregister_table(&mut self, name: &str) -> StorageResult<()> {
251 if self.metadata.files.remove(name).is_none() {
252 return Err(StorageError::Invalid(format!(
253 "table '{name}' is not registered"
254 )));
255 }
256 Ok(())
257 }
258}