#![allow(dead_code)]
use log::{debug, info};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::path::PathBuf;
use crate::StorageError;
use crate::StorageResult;
use crate::traits::backend::StorageBackend;
use crate::traits::metadata::Metadata;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum CollectionKind {
VectorSpace,
Graph,
Table,
}
impl CollectionKind {
pub const fn as_str(&self) -> &'static str {
match self {
CollectionKind::VectorSpace => "vector-space",
CollectionKind::Graph => "graph",
CollectionKind::Table => "table",
}
}
pub fn parse(s: &str) -> StorageResult<Self> {
match s {
"vector-space" => Ok(CollectionKind::VectorSpace),
"graph" => Ok(CollectionKind::Graph),
"table" => Ok(CollectionKind::Table),
other => Err(StorageError::Invalid(format!(
"unknown collection kind '{other}' (expected vector-space, graph or table)"
))),
}
}
pub fn for_filetype(filetype: &str) -> Option<Self> {
match filetype {
"dense" | "vectors" | "vector" => Some(CollectionKind::VectorSpace),
"sparse" | "graph" => Some(CollectionKind::Graph),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileInfo {
pub filename: String,
pub filetype: String,
pub storage_format: String,
pub rows: usize,
pub cols: usize,
pub nnz: Option<usize>,
pub size_bytes: Option<u64>,
#[serde(default)]
pub kind: Option<CollectionKind>,
#[serde(default)]
pub properties: BTreeMap<String, String>,
}
impl FileInfo {
pub fn new(
filename: String,
filetype: &str,
data_shape: (usize, usize),
nnz: Option<usize>,
size_bytes: Option<u64>,
) -> StorageResult<Self> {
debug!(
"FileInfo::new: filename={}, filetype={}, shape={}x{}, nnz={:?}",
filename, filetype, data_shape.0, data_shape.1, nnz
);
Ok(Self {
filename,
filetype: filetype.into(),
storage_format: Self::which_format(filetype)?,
rows: data_shape.0,
cols: data_shape.1,
nnz,
size_bytes,
kind: CollectionKind::for_filetype(filetype),
properties: BTreeMap::new(),
})
}
pub fn which_format(filetype: &str) -> StorageResult<String> {
match filetype {
"dense" | "vectors" => Ok(String::from("lance fixed-row")),
"sparse" => Ok(String::from("lance row-major")),
"vector" => Ok(String::from("lance row-major")),
"graph" => Ok(String::from("lance edge-list")),
other => Err(StorageError::UnsupportedFormat(other.to_string())),
}
}
pub fn which_filetype(filetype: &str) -> StorageResult<String> {
match filetype {
"rawinput" | "sub_centroids" | "dense" => Ok(String::from("dense")),
"adjacency" | "laplacian" | "signals" | "sparse" => Ok(String::from("sparse")),
"lambdas" | "item_norms" | "norms" | "vector" => Ok(String::from("vector")),
"graph" | "vectors" => Ok(filetype.into()),
other => Err(StorageError::UnsupportedFiletype(other.to_string())),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeneMetadata {
pub name_id: String,
pub nrows: usize,
pub ncols: usize,
pub base: String,
pub files: HashMap<String, FileInfo>,
pub created_at: String,
}
impl GeneMetadata {
pub async fn read(path: PathBuf) -> Result<Self, StorageError> {
info!("Reading metadata from {:?}", path);
let s = tokio::fs::read_to_string(path)
.await
.map_err(|e| StorageError::Io(e.to_string()))?;
let md: GeneMetadata = serde_json::from_str(&s).map_err(StorageError::Serde)?;
info!("Metadata read successfully");
Ok(md)
}
}
impl GeneMetadata {
pub fn new(name_id: &str) -> Self {
<Self as Metadata>::new(name_id)
}
pub fn with_base(self, base_path: PathBuf) -> Self {
<Self as Metadata>::with_base(self, base_path)
}
pub fn with_dimensions(self, rows: usize, cols: usize) -> Self {
<Self as Metadata>::with_dimensions(self, rows, cols)
}
pub fn add_file(self, key: &str, info: FileInfo) -> Self {
<Self as Metadata>::add_file(self, key, info)
}
pub fn new_fileinfo(
&self,
key: &str,
filetype: &str,
data_shape: (usize, usize),
nnz: Option<usize>,
size_bytes: Option<u64>,
) -> StorageResult<FileInfo> {
<Self as Metadata>::new_fileinfo(self, key, filetype, data_shape, nnz, size_bytes)
}
}
impl Metadata for GeneMetadata {
fn new(name_id: &str) -> Self {
info!("GeneMetadata::new: creating metadata for '{}'", name_id);
Self {
name_id: name_id.to_string(),
nrows: 0,
ncols: 0,
base: String::from(""),
files: HashMap::new(),
created_at: chrono::Utc::now().to_rfc3339(),
}
}
fn new_fileinfo(
&self,
key: &str,
filetype: &str,
data_shape: (usize, usize),
nnz: Option<usize>,
size_bytes: Option<u64>,
) -> StorageResult<FileInfo> {
FileInfo::new(
format!("{}_{}.lance", self.name_id, key),
filetype,
(data_shape.0, data_shape.1),
nnz,
size_bytes,
)
}
async fn seed_metadata<B: StorageBackend>(
name_id: &str,
nitems: usize,
nfeatures: usize,
storage: &B,
) -> Result<GeneMetadata, StorageError> {
info!(
"GeneMetadata::seed_metadata: seeding metadata for '{}' with nitems={}, nfeatures={}",
name_id, nitems, nfeatures
);
let md = Self::new(name_id)
.with_base(storage.base_path())
.with_dimensions(nitems, nfeatures);
debug!("GeneMetadata::seed_metadata: saving metadata to storage");
storage.save_metadata(&md).await?;
info!(
"GeneMetadata::seed_metadata: metadata seeded successfully for '{}'",
name_id
);
Ok(md)
}
fn with_base(mut self, base_path: PathBuf) -> Self {
self.base = base_path.to_string_lossy().to_string();
self
}
fn with_dimensions(mut self, rows: usize, cols: usize) -> Self {
debug!(
"GeneMetadata::with_dimensions: setting dimensions to {}x{}",
rows, cols
);
self.nrows = rows;
self.ncols = cols;
self
}
fn add_file(mut self, key: &str, info: FileInfo) -> Self {
debug!(
"GeneMetadata::add_file: adding file '{}' ({})",
key, info.filename
);
self.files.insert(key.to_string(), info);
self
}
}