pub mod keys;
pub mod writer;
use std::path::{Path, PathBuf};
use fjall::{Database, Keyspace, KeyspaceCreateOptions};
use thiserror::Error;
const INDEX_PARTITION_REVISION: u32 = 4;
pub const INDEX_SCHEMA_VER: u32 = crate::version::RELEASE_MINOR as u32 + INDEX_PARTITION_REVISION;
const META_SCHEMA_VER: &[u8] = b"schema_ver";
const META_BM25_DOC_COUNT: &[u8] = b"code_bm25_n";
const META_BM25_TOTAL_LEN: &[u8] = b"code_bm25_total_len";
const INDEX_DIR: &str = "index.fjall";
#[derive(Debug, Error)]
pub enum IndexError {
#[error("fjall error: {0}")]
Fjall(#[from] fjall::Error),
#[error("io error on {path}: {source}")]
Io {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("msgpack encode error: {0}")]
Encode(#[from] rmp_serde::encode::Error),
#[error("msgpack decode error: {0}")]
Decode(#[from] rmp_serde::decode::Error),
}
#[derive(Clone)]
pub struct IndexDb {
pub(crate) db: Database,
#[allow(dead_code)]
pub(crate) meta: Keyspace,
pub(crate) symbols_by_path: Keyspace,
pub(crate) symbols_by_name: Keyspace,
pub(crate) calls_by_path: Keyspace,
pub(crate) calls_by_callee: Keyspace,
pub(crate) imports_by_module: Keyspace,
pub(crate) imports_by_path: Keyspace,
pub(crate) implementations_by_trait: Keyspace,
pub(crate) implementations_by_path: Keyspace,
pub(crate) refs_by_def: Keyspace,
pub(crate) refs_by_path: Keyspace,
#[allow(dead_code)]
pub(crate) code_bm25_postings: Keyspace,
pub(crate) code_bm25_by_path: Keyspace,
#[allow(dead_code)] pub(crate) embeddings: Keyspace,
#[allow(dead_code)]
pub(crate) memory_by_key: Keyspace,
#[allow(dead_code)]
pub(crate) memory_archive: Keyspace,
#[allow(dead_code)]
pub(crate) proposals: Keyspace,
}
impl IndexDb {
pub fn open(view_dir: &Path) -> Result<Self, IndexError> {
let dir = view_dir.join(INDEX_DIR);
std::fs::create_dir_all(&dir).map_err(|source| IndexError::Io {
path: dir.clone(),
source,
})?;
let mut db = Database::builder(&dir).open()?;
let mut meta = db.keyspace("meta", KeyspaceCreateOptions::default)?;
let on_disk_ver = meta
.get(META_SCHEMA_VER)?
.and_then(|bytes| <[u8; 4]>::try_from(&bytes[..]).ok())
.map(u32::from_be_bytes);
if matches!(on_disk_ver, Some(ver) if ver != INDEX_SCHEMA_VER) {
drop(meta);
drop(db);
std::fs::remove_dir_all(&dir).map_err(|source| IndexError::Io {
path: dir.clone(),
source,
})?;
std::fs::create_dir_all(&dir).map_err(|source| IndexError::Io {
path: dir.clone(),
source,
})?;
db = Database::builder(&dir).open()?;
meta = db.keyspace("meta", KeyspaceCreateOptions::default)?;
}
let symbols_by_path = db.keyspace("symbols_by_path", KeyspaceCreateOptions::default)?;
let symbols_by_name = db.keyspace("symbols_by_name", KeyspaceCreateOptions::default)?;
let calls_by_path = db.keyspace("calls_by_path", KeyspaceCreateOptions::default)?;
let calls_by_callee = db.keyspace("calls_by_callee", KeyspaceCreateOptions::default)?;
let imports_by_module = db.keyspace("imports_by_module", KeyspaceCreateOptions::default)?;
let imports_by_path = db.keyspace("imports_by_path", KeyspaceCreateOptions::default)?;
let implementations_by_trait = db.keyspace("implementations_by_trait", KeyspaceCreateOptions::default)?;
let implementations_by_path = db.keyspace("implementations_by_path", KeyspaceCreateOptions::default)?;
let refs_by_def = db.keyspace("refs_by_def", KeyspaceCreateOptions::default)?;
let refs_by_path = db.keyspace("refs_by_path", KeyspaceCreateOptions::default)?;
let code_bm25_postings = db.keyspace("code_bm25_postings", KeyspaceCreateOptions::default)?;
let code_bm25_by_path = db.keyspace("code_bm25_by_path", KeyspaceCreateOptions::default)?;
let embeddings = db.keyspace("embeddings", KeyspaceCreateOptions::default)?;
let memory_by_key = db.keyspace("memory_by_key", KeyspaceCreateOptions::default)?;
let memory_archive = db.keyspace("memory_archive", KeyspaceCreateOptions::default)?;
let proposals = db.keyspace("proposals", KeyspaceCreateOptions::default)?;
meta.insert(META_SCHEMA_VER, INDEX_SCHEMA_VER.to_be_bytes())?;
Ok(Self {
db,
meta,
symbols_by_path,
symbols_by_name,
calls_by_path,
calls_by_callee,
imports_by_module,
imports_by_path,
implementations_by_trait,
implementations_by_path,
refs_by_def,
refs_by_path,
code_bm25_postings,
code_bm25_by_path,
embeddings,
memory_by_key,
memory_archive,
proposals,
})
}
pub fn writer(&self) -> writer::IndexWriter {
writer::IndexWriter::new(self.clone())
}
pub fn symbols_index_is_empty(&self) -> bool {
self.symbols_by_path.iter().next().is_none()
}
pub fn references_to(&self, def_path: &crate::path::RelPath, def_start: u32) -> Vec<(crate::path::RelPath, u32)> {
let prefix = keys::refs_by_def_prefix(def_path, def_start);
let mut out = Vec::new();
for guard in self.refs_by_def.prefix(prefix) {
if let Ok((k, _)) = guard.into_inner()
&& let Some((_def_path, _def_start, use_path, use_start)) = keys::parse_ref_by_def(&k)
{
out.push((use_path, use_start));
}
}
out
}
pub fn definition_of(
&self,
use_path: &crate::path::RelPath,
use_start: u32,
) -> Option<(crate::path::RelPath, u32)> {
let prefix = keys::refs_by_use_prefix(use_path, use_start);
for guard in self.refs_by_path.prefix(prefix) {
if let Ok((k, _)) = guard.into_inner()
&& let Some((_use_path, _use_start, def_path, def_start)) = keys::parse_ref_by_path(&k)
{
return Some((def_path, def_start));
}
}
None
}
pub fn symbols_by_name_lookup(
&self,
name: &str,
cap: usize,
) -> Vec<(String, crate::extract::SymbolKind, crate::path::RelPath, u32)> {
let prefix = keys::symbols_by_name_prefix(name);
let mut out = Vec::new();
for guard in self.symbols_by_name.prefix(prefix) {
if out.len() >= cap {
break;
}
if let Ok((k, _)) = guard.into_inner()
&& let Some((matched, kind, rel, start_byte)) = keys::parse_symbol_by_name(&k)
{
out.push((matched, kind, rel, start_byte));
}
}
out
}
pub fn bm25_stats(&self) -> Option<(u64, u64)> {
let n = self
.meta
.get(META_BM25_DOC_COUNT)
.ok()
.flatten()
.and_then(|b| <[u8; 8]>::try_from(&b[..]).ok())
.map(u64::from_be_bytes)?;
let total_len = self
.meta
.get(META_BM25_TOTAL_LEN)
.ok()
.flatten()
.and_then(|b| <[u8; 8]>::try_from(&b[..]).ok())
.map(u64::from_be_bytes)
.unwrap_or(0);
Some((n, total_len))
}
pub fn recompute_bm25_stats(&self) -> Result<(), IndexError> {
let mut n: u64 = 0;
let mut total_len: u64 = 0;
for guard in self.code_bm25_by_path.iter() {
let (_k, v) = guard.into_inner()?;
if v.len() >= 4 {
total_len += u64::from(u32::from_be_bytes([v[0], v[1], v[2], v[3]]));
}
n += 1;
}
self.meta.insert(META_BM25_DOC_COUNT, n.to_be_bytes())?;
self.meta.insert(META_BM25_TOTAL_LEN, total_len.to_be_bytes())?;
Ok(())
}
}