#![doc = include_str!("../README.md")] use std::{collections::BTreeMap, sync::OnceLock}; use brk_computer::Computer; use brk_error::{Error, Result}; use brk_indexer::Indexer; use brk_parser::Parser; use brk_structs::Height; use nucleo_matcher::{ Config, Matcher, pattern::{AtomKind, CaseMatching, Normalization, Pattern}, }; use quick_cache::sync::Cache; use vecdb::{AnyCollectableVec, AnyStoredVec}; mod deser; mod format; mod index; mod metrics; mod output; mod pagination; mod params; mod vecs; pub use format::Format; pub use index::Index; pub use output::{Output, Value}; pub use pagination::{PaginatedIndexParam, PaginationParam}; pub use params::{Params, ParamsDeprec, ParamsOpt}; use vecs::Vecs; use crate::vecs::{IndexToVec, MetricToVec}; pub fn cached_errors() -> &'static Cache { static CACHE: OnceLock> = OnceLock::new(); CACHE.get_or_init(|| Cache::new(1000)) } #[allow(dead_code)] pub struct Interface<'a> { vecs: Vecs<'a>, parser: &'a Parser, indexer: &'a Indexer, computer: &'a Computer, } impl<'a> Interface<'a> { pub fn build(parser: &Parser, indexer: &Indexer, computer: &Computer) -> Self { let parser = parser.static_clone(); let indexer = indexer.static_clone(); let computer = computer.static_clone(); let vecs = Vecs::build(indexer, computer); Self { vecs, parser, indexer, computer, } } pub fn get_height(&self) -> Height { Height::from(self.indexer.vecs.height_to_blockhash.stamp()) } pub fn search(&self, params: &Params) -> Result> { let metrics = ¶ms.metrics; let index = params.index; let ids_to_vec = self .vecs .index_to_metric_to_vec .get(&index) .ok_or(Error::String(format!( "Index \"{}\" isn't a valid index", index )))?; metrics.iter() .map(|metric| { let vec = ids_to_vec.get(metric.as_str()).ok_or_else(|| { let cached_errors = cached_errors(); if let Some(message) = cached_errors.get(metric) { return Error::String(message) } let mut message = format!( "No vec named \"{}\" indexed by \"{}\" found.\n", metric, index ); let mut matcher = Matcher::new(Config::DEFAULT); let matches = Pattern::new( metric.as_str(), CaseMatching::Ignore, Normalization::Smart, AtomKind::Fuzzy, ) .match_list(ids_to_vec.keys(), &mut matcher) .into_iter() .take(10) .map(|(s, _)| s) .collect::>(); if !matches.is_empty() { message += &format!("\nMaybe you meant one of the following: {matches:#?} ?\n"); } if let Some(index_to_vec) = self.metric_to_index_to_vec().get(metric.as_str()) { message += &format!("\nBut there is a vec named {metric} which supports the following indexes: {:#?}\n", index_to_vec.keys()); } cached_errors.insert(metric.clone(), message.clone()); Error::String(message) }); vec.map(|vec| (metric.clone(), vec)) }) .collect::>>() } pub fn format( &self, metrics: Vec<(String, &&dyn AnyCollectableVec)>, params: &ParamsOpt, ) -> Result { let from = params.from().map(|from| { metrics .iter() .map(|(_, v)| v.i64_to_usize(from)) .min() .unwrap_or_default() }); let to = params.to().map(|to| { metrics .iter() .map(|(_, v)| v.i64_to_usize(to)) .min() .unwrap_or_default() }); let format = params.format(); Ok(match format { Format::CSV => { let headers = metrics .iter() .map(|(id, _)| id.as_str()) .collect::>(); let mut values = metrics .iter() .map(|(_, vec)| Ok(vec.collect_range_string(from, to)?)) .collect::>>()?; if values.is_empty() { return Ok(Output::CSV(headers.join(","))); } let first_len = values[0].len(); let estimated_size = (headers.len() + values.len() * first_len) * 15; let mut csv = String::with_capacity(estimated_size); csv.push_str(&headers.join(",")); csv.push('\n'); for col_index in 0..first_len { let mut first = true; for vec in &mut values { if col_index < vec.len() { if !first { csv.push(','); } first = false; let field = std::mem::take(&mut vec[col_index]); if field.contains(',') { csv.push('"'); csv.push_str(&field); csv.push('"'); } else { csv.push_str(&field); } } } csv.push('\n'); } Output::CSV(csv) } Format::JSON => { let mut values = metrics .iter() .map(|(_, vec)| -> Result> { Ok(vec.collect_range_json_bytes(from, to)?) }) .collect::>>()?; if values.is_empty() { return Ok(Output::default(format)); } if values.len() == 1 { Output::Json(Value::List(values.pop().unwrap())) } else { Output::Json(Value::Matrix(values)) } } }) } pub fn search_and_format(&self, params: Params) -> Result { self.format(self.search(¶ms)?, ¶ms.rest) } pub fn metric_to_index_to_vec(&self) -> &BTreeMap<&str, IndexToVec<'_>> { &self.vecs.metric_to_index_to_vec } pub fn index_to_metric_to_vec(&self) -> &BTreeMap> { &self.vecs.index_to_metric_to_vec } pub fn distinct_metric_count(&self) -> usize { self.vecs.metric_count } pub fn total_metric_count(&self) -> usize { self.vecs.vec_count } pub fn index_count(&self) -> usize { self.vecs.index_count } pub fn get_indexes(&self) -> &[&'static str] { &self.vecs.indexes } pub fn get_accepted_indexes(&self) -> &BTreeMap<&'static str, &'static [&'static str]> { &self.vecs.accepted_indexes } pub fn get_metrics(&self, pagination: PaginationParam) -> &[&str] { self.vecs.metrics(pagination) } pub fn get_index_to_vecids(&self, paginated_index: PaginatedIndexParam) -> Vec<&str> { self.vecs.index_to_ids(paginated_index) } pub fn metric_to_indexes(&self, metric: String) -> Option<&Vec<&'static str>> { self.vecs.metric_to_indexes(metric) } pub fn parser(&self) -> &Parser { self.parser } pub fn indexer(&self) -> &Indexer { self.indexer } pub fn computer(&self) -> &Computer { self.computer } }