use anyhow::{Context, Result};
use rusqlite::{Connection, OptionalExtension};
use std::path::Path;
use crate::types::FileCoupling;
const SQLITE_MAX_BIND_VARS: usize = 30_000;
pub struct MetadataStore {
conn: Connection,
}
impl MetadataStore {
pub fn open(path: &Path) -> Result<Self> {
let conn = Connection::open(path)
.with_context(|| format!("Failed to open database: {}", path.display()))?;
conn.execute("PRAGMA foreign_keys = ON", [])
.context("Failed to enable foreign keys")?;
let store = Self { conn };
store.init_schema()?;
Ok(store)
}
pub fn snapshot(&self, destination: &Path) -> Result<()> {
self.conn
.execute("VACUUM INTO ?1", [destination.to_string_lossy().as_ref()])
.with_context(|| format!("Failed to snapshot database to {}", destination.display()))?;
Ok(())
}
pub fn get_coupling(&self, file_path: &str, limit: usize) -> Result<Vec<FileCoupling>> {
let mut stmt = self.conn.prepare(
r#"SELECT file_a, file_b, score, co_changes, last_co_change
FROM coupling
WHERE file_a = ?1 OR file_b = ?1
ORDER BY score DESC
LIMIT ?2"#,
)?;
let results = stmt
.query_map([file_path, &limit.to_string()], |row| {
Ok(FileCoupling {
file_a: row.get(0)?,
file_b: row.get(1)?,
score: row.get(2)?,
co_changes: row.get(3)?,
last_co_change: row.get(4)?,
})
})?
.collect::<Result<Vec<_>, _>>()?;
Ok(results)
}
pub fn upsert_coupling(&self, coupling: &FileCoupling) -> Result<()> {
self.conn.execute(
r#"INSERT INTO coupling (file_a, file_b, score, co_changes, last_co_change)
VALUES (?1, ?2, ?3, ?4, ?5)
ON CONFLICT(file_a, file_b) DO UPDATE SET
score = excluded.score,
co_changes = excluded.co_changes,
last_co_change = excluded.last_co_change"#,
(
&coupling.file_a,
&coupling.file_b,
coupling.score,
coupling.co_changes,
coupling.last_co_change,
),
)?;
Ok(())
}
pub fn begin_transaction(&self) -> Result<()> {
self.conn.execute("BEGIN TRANSACTION", [])?;
Ok(())
}
pub fn commit(&self) -> Result<()> {
self.conn.execute("COMMIT", [])?;
Ok(())
}
pub fn rollback(&self) -> Result<()> {
self.conn.execute("ROLLBACK", [])?;
Ok(())
}
pub fn get_meta(&self, key: &str) -> Result<Option<String>> {
let mut stmt = self.conn.prepare("SELECT value FROM meta WHERE key = ?1")?;
let result = stmt.query_row([key], |row| row.get(0)).optional()?;
Ok(result)
}
pub fn set_meta(&self, key: &str, value: &str) -> Result<()> {
self.conn.execute(
"INSERT OR REPLACE INTO meta (key, value) VALUES (?1, ?2)",
[key, value],
)?;
Ok(())
}
pub fn delete_meta(&self, key: &str) -> Result<usize> {
let n = self
.conn
.execute("DELETE FROM meta WHERE key = ?1", [key])?;
Ok(n)
}
pub fn get_meta_by_prefix(&self, prefix: &str) -> Result<Vec<(String, String)>> {
let mut stmt = self
.conn
.prepare("SELECT key, value FROM meta WHERE key LIKE ?1")?;
let pattern = format!("{}%", prefix);
let rows = stmt.query_map([&pattern], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
})?;
let mut results = Vec::new();
for row in rows {
results.push(row?);
}
Ok(results)
}
}
mod coupling;
mod files;
mod lineage;
mod schema;
#[cfg(test)]
mod tests;
pub use lineage::{
BeadLineageRecord, BugCausalityRecord, NewBeadLineage, NewBugCausality, PriorTouch,
TouchedSymbol,
};