use std::borrow::Borrow;
use std::ops::{Bound, Deref};
use std::path::Path;
use fjall::Keyspace;
use super::grep_bloom::{GrepEntry, Needle};
use super::{
IMPORT_PROBE_VER, IndexDb, IndexError, META_BM25_DOC_COUNT, META_BM25_TOTAL_LEN, META_IMPORT_PROBE_VER, keys,
name_dict, probe_candidates,
};
use crate::extract::SymbolKind;
use crate::path::RelPath;
#[derive(Clone)]
pub struct IndexBytes(Repr);
#[derive(Clone)]
enum Repr {
Fjall(fjall::Slice),
}
impl IndexBytes {
fn from_fjall(slice: fjall::Slice) -> Self {
Self(Repr::Fjall(slice))
}
}
impl Deref for IndexBytes {
type Target = [u8];
fn deref(&self) -> &[u8] {
match &self.0 {
Repr::Fjall(slice) => slice,
}
}
}
impl AsRef<[u8]> for IndexBytes {
fn as_ref(&self) -> &[u8] {
self
}
}
impl PartialEq for IndexBytes {
fn eq(&self, other: &Self) -> bool {
**self == **other
}
}
impl Eq for IndexBytes {}
impl std::fmt::Debug for IndexBytes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
(**self).fmt(f)
}
}
pub type KvResult = Result<(IndexBytes, IndexBytes), IndexError>;
pub type KvIter<'a> = Box<dyn Iterator<Item = KvResult> + 'a>;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Part {
Meta,
SymbolsByPath,
SymbolsByName,
CallsByPath,
CallsByCallee,
ImportsByModule,
ImportsByPath,
ImplementationsByTrait,
ImplementationsByPath,
RefsByDef,
RefsByPath,
ImportTargets,
ImportersByProbe,
CodeBm25Postings,
CodeBm25ByPath,
GrepBloom,
}
impl Part {
#[cfg(test)]
pub(super) const ALL: [Part; 16] = [
Part::Meta,
Part::SymbolsByPath,
Part::SymbolsByName,
Part::CallsByPath,
Part::CallsByCallee,
Part::ImportsByModule,
Part::ImportsByPath,
Part::ImplementationsByTrait,
Part::ImplementationsByPath,
Part::RefsByDef,
Part::RefsByPath,
Part::ImportTargets,
Part::ImportersByProbe,
Part::CodeBm25Postings,
Part::CodeBm25ByPath,
Part::GrepBloom,
];
#[cfg(test)]
pub(super) const fn name(self) -> &'static str {
match self {
Part::Meta => "meta",
Part::SymbolsByPath => "symbols_by_path",
Part::SymbolsByName => "symbols_by_name",
Part::CallsByPath => "calls_by_path",
Part::CallsByCallee => "calls_by_callee",
Part::ImportsByModule => "imports_by_module",
Part::ImportsByPath => "imports_by_path",
Part::ImplementationsByTrait => "implementations_by_trait",
Part::ImplementationsByPath => "implementations_by_path",
Part::RefsByDef => "refs_by_def",
Part::RefsByPath => "refs_by_path",
Part::ImportTargets => "import_targets",
Part::ImportersByProbe => "importers_by_probe",
Part::CodeBm25Postings => "code_bm25_postings",
Part::CodeBm25ByPath => "code_bm25_by_path",
Part::GrepBloom => "grep_bloom",
}
}
}
impl IndexDb {
fn keyspace(&self, part: Part) -> &Keyspace {
match part {
Part::Meta => &self.meta,
Part::SymbolsByPath => &self.symbols_by_path,
Part::SymbolsByName => &self.symbols_by_name,
Part::CallsByPath => &self.calls_by_path,
Part::CallsByCallee => &self.calls_by_callee,
Part::ImportsByModule => &self.imports_by_module,
Part::ImportsByPath => &self.imports_by_path,
Part::ImplementationsByTrait => &self.implementations_by_trait,
Part::ImplementationsByPath => &self.implementations_by_path,
Part::RefsByDef => &self.refs_by_def,
Part::RefsByPath => &self.refs_by_path,
Part::ImportTargets => &self.import_targets,
Part::ImportersByProbe => &self.importers_by_probe,
Part::CodeBm25Postings => &self.code_bm25_postings,
Part::CodeBm25ByPath => &self.code_bm25_by_path,
Part::GrepBloom => &self.grep_bloom,
}
}
#[cfg(test)]
pub(crate) fn keyspace_for_test(&self, part: Part) -> &Keyspace {
self.keyspace(part)
}
#[cfg(test)]
pub(crate) fn callee_dict(&self) -> &std::sync::Arc<name_dict::NameDict> {
&self.callee_names
}
#[cfg(test)]
pub(crate) fn trait_dict(&self) -> &std::sync::Arc<name_dict::NameDict> {
&self.trait_names
}
pub fn view(&self) -> StandaloneView<&IndexDb> {
StandaloneView(self)
}
}
pub(crate) fn as_dyn<V: IndexView>(view: &Option<V>) -> Option<&dyn IndexView> {
view.as_ref().map(|v| v as &dyn IndexView)
}
pub(super) fn kv(guard: fjall::Guard) -> KvResult {
let (key, value) = guard.into_inner().map_err(IndexError::from)?;
Ok((IndexBytes::from_fjall(key), IndexBytes::from_fjall(value)))
}
pub trait IndexView: Send + Sync {
fn get(&self, part: Part, key: &[u8]) -> Result<Option<IndexBytes>, IndexError>;
fn prefix<'a>(&'a self, part: Part, prefix: &[u8]) -> KvIter<'a>;
fn range<'a>(&'a self, part: Part, lower: Bound<&[u8]>, upper: Bound<&[u8]>) -> KvIter<'a>;
fn iter<'a>(&'a self, part: Part) -> KvIter<'a>;
fn callee_keys_matching<'a>(
&'a self,
needle: &str,
cursor_after: Option<&[u8]>,
lower: Bound<Vec<u8>>,
) -> KvIter<'a>;
fn trait_keys_matching<'a>(
&'a self,
needle: &str,
cursor_after: Option<&[u8]>,
lower: Bound<Vec<u8>>,
) -> KvIter<'a>;
fn symbols_index_is_empty(&self) -> bool {
self.iter(Part::SymbolsByPath).next().is_none()
}
fn references_to(&self, def_path: &RelPath, def_start: u32) -> Vec<(RelPath, u32)> {
let prefix = keys::refs_by_def_prefix(def_path, def_start);
self.prefix(Part::RefsByDef, &prefix)
.filter_map(|row| row.ok())
.filter_map(|(k, _)| keys::parse_ref_by_def(&k))
.map(|(_, _, use_path, use_start)| (use_path, use_start))
.collect()
}
fn definition_of(&self, use_path: &RelPath, use_start: u32) -> Option<(RelPath, u32)> {
let prefix = keys::refs_by_use_prefix(use_path, use_start);
let mut local = None;
for row in self.prefix(Part::RefsByPath, &prefix) {
if let Ok((k, _)) = row
&& let Some((_use_path, _use_start, def_path, def_start)) = keys::parse_ref_by_path(&k)
{
if def_path != *use_path {
return Some((def_path, def_start));
}
local = Some((def_path, def_start));
}
}
local
}
fn import_targets_of(&self, importer: &RelPath) -> Vec<(u32, RelPath, u32)> {
self.prefix(Part::ImportTargets, &keys::import_targets_prefix(importer))
.filter_map(|row| row.ok())
.filter_map(|(k, v)| keys::parse_import_target(&k, &v))
.map(|(_, local_start, def_path, def_start)| (local_start, def_path, def_start))
.collect()
}
fn importers_probing(&self, path: &str) -> Vec<RelPath> {
probe_candidates(path)
.flat_map(|candidate| {
let prefix = keys::importers_by_probe_prefix(&keys::probe_hash(candidate));
self.prefix(Part::ImportersByProbe, &prefix)
.filter_map(|row| row.ok())
.filter_map(|(k, _)| keys::parse_importer_by_probe(&k))
.collect::<Vec<_>>()
})
.collect()
}
fn is_probed(&self, path: &str) -> bool {
probe_candidates(path).any(|candidate| {
let prefix = keys::importers_by_probe_prefix(&keys::probe_hash(candidate));
self.prefix(Part::ImportersByProbe, &prefix).next().is_some()
})
}
fn import_probes_complete(&self) -> bool {
self.get(Part::Meta, META_IMPORT_PROBE_VER)
.ok()
.flatten()
.is_some_and(|v| v.first() == Some(&IMPORT_PROBE_VER))
}
fn symbols_by_name_lookup(&self, name: &str, cap: usize) -> Vec<(String, SymbolKind, RelPath, u32)> {
let prefix = keys::symbols_by_name_prefix(name);
let mut out = Vec::new();
for row in self.prefix(Part::SymbolsByName, &prefix) {
if out.len() >= cap {
break;
}
if let Ok((k, _)) = row
&& let Some(symbol) = keys::parse_symbol_by_name(&k)
{
out.push(symbol);
}
}
out
}
fn bm25_stats(&self) -> Option<(u64, u64)> {
let stat = |key: &[u8]| {
self.get(Part::Meta, key)
.ok()
.flatten()
.and_then(|b| <[u8; 8]>::try_from(&b[..]).ok())
.map(u64::from_be_bytes)
};
let n = stat(META_BM25_DOC_COUNT)?;
Some((n, stat(META_BM25_TOTAL_LEN).unwrap_or(0)))
}
fn grep_bloom_row(&self, hash_hex: &str) -> Option<IndexBytes> {
self.get(Part::GrepBloom, hash_hex.as_bytes()).ok().flatten()
}
fn grep_skip_verdicts(&self, root: &Path, entries: &[GrepEntry], needle: &Needle) -> Vec<bool> {
super::grep_bloom::skip_verdicts(|hash| self.grep_bloom_row(hash), root, entries, needle)
}
}
pub struct StandaloneView<T = IndexDb>(pub(crate) T);
impl<T: Borrow<IndexDb>> StandaloneView<T> {
fn db(&self) -> &IndexDb {
self.0.borrow()
}
}
impl<T: Borrow<IndexDb> + Send + Sync> IndexView for StandaloneView<T> {
fn get(&self, part: Part, key: &[u8]) -> Result<Option<IndexBytes>, IndexError> {
Ok(self.db().keyspace(part).get(key)?.map(IndexBytes::from_fjall))
}
fn prefix<'a>(&'a self, part: Part, prefix: &[u8]) -> KvIter<'a> {
Box::new(self.db().keyspace(part).prefix(prefix).map(kv))
}
fn range<'a>(&'a self, part: Part, lower: Bound<&[u8]>, upper: Bound<&[u8]>) -> KvIter<'a> {
Box::new(self.db().keyspace(part).range::<&[u8], _>((lower, upper)).map(kv))
}
fn iter<'a>(&'a self, part: Part) -> KvIter<'a> {
Box::new(self.db().keyspace(part).iter().map(kv))
}
fn callee_keys_matching<'a>(
&'a self,
needle: &str,
cursor_after: Option<&[u8]>,
lower: Bound<Vec<u8>>,
) -> KvIter<'a> {
let db = self.db();
name_dict::name_ordered_keys(&db.calls_by_callee, &db.callee_names, needle, cursor_after, lower)
}
fn trait_keys_matching<'a>(
&'a self,
needle: &str,
cursor_after: Option<&[u8]>,
lower: Bound<Vec<u8>>,
) -> KvIter<'a> {
let db = self.db();
name_dict::name_ordered_keys(
&db.implementations_by_trait,
&db.trait_names,
needle,
cursor_after,
lower,
)
}
}
#[cfg(test)]
pub(crate) use recording::{ReadLog, RecordingView};
#[cfg(test)]
mod recording {
use std::sync::{Arc, Mutex};
use super::*;
#[derive(Clone, Default)]
pub(crate) struct ReadLog(Arc<Mutex<Vec<String>>>);
impl ReadLog {
fn record(&self, op: &str, part: Option<Part>) {
let entry = part.map_or_else(|| op.to_owned(), |p| format!("{op}:{}", p.name()));
self.0.lock().expect("read log").push(entry);
}
pub(crate) fn entries(&self) -> Vec<String> {
self.0.lock().expect("read log").clone()
}
pub(crate) fn clear(&self) {
self.0.lock().expect("read log").clear();
}
}
pub(crate) struct RecordingView {
inner: StandaloneView<IndexDb>,
log: ReadLog,
}
impl RecordingView {
pub(crate) fn new(db: IndexDb) -> (Self, ReadLog) {
let log = ReadLog::default();
(
Self {
inner: StandaloneView(db),
log: log.clone(),
},
log,
)
}
}
impl IndexView for RecordingView {
fn get(&self, part: Part, key: &[u8]) -> Result<Option<IndexBytes>, IndexError> {
self.log.record("get", Some(part));
self.inner.get(part, key)
}
fn prefix<'a>(&'a self, part: Part, prefix: &[u8]) -> KvIter<'a> {
self.log.record("prefix", Some(part));
self.inner.prefix(part, prefix)
}
fn range<'a>(&'a self, part: Part, lower: Bound<&[u8]>, upper: Bound<&[u8]>) -> KvIter<'a> {
self.log.record("range", Some(part));
self.inner.range(part, lower, upper)
}
fn iter<'a>(&'a self, part: Part) -> KvIter<'a> {
self.log.record("iter", Some(part));
self.inner.iter(part)
}
fn callee_keys_matching<'a>(
&'a self,
needle: &str,
cursor_after: Option<&[u8]>,
lower: Bound<Vec<u8>>,
) -> KvIter<'a> {
self.log.record("callee_keys_matching", None);
self.inner.callee_keys_matching(needle, cursor_after, lower)
}
fn trait_keys_matching<'a>(
&'a self,
needle: &str,
cursor_after: Option<&[u8]>,
lower: Bound<Vec<u8>>,
) -> KvIter<'a> {
self.log.record("trait_keys_matching", None);
self.inner.trait_keys_matching(needle, cursor_after, lower)
}
}
}