use crate::context_efficiency::{
ManifestMetadataCacheKey, ManifestMetadataSummary, cached_manifest_metadata_summary,
store_manifest_metadata_summary,
};
use rusqlite::Transaction;
use super::*;
impl Storage {
pub fn upsert_repository(
&self,
repository_id: &str,
root_path: &std::path::Path,
display_name: &str,
) -> FriggResult<()> {
let conn = self.open_current_schema_connection()?;
upsert_repository_on_connection(&conn, repository_id, root_path, display_name)
}
pub fn upsert_manifest(
&self,
repository_id: &str,
snapshot_id: &str,
entries: &[ManifestEntry],
) -> FriggResult<()> {
let mut conn = self.open_current_schema_connection()?;
upsert_manifest_on_connection(&mut conn, repository_id, snapshot_id, entries)
}
pub fn load_manifest_for_snapshot(&self, snapshot_id: &str) -> FriggResult<Vec<ManifestEntry>> {
let conn = self.open_current_schema_connection()?;
load_manifest_entries_for_snapshot(&conn, snapshot_id)
}
pub fn load_latest_manifest_for_repository(
&self,
repository_id: &str,
) -> FriggResult<Option<RepositoryManifestSnapshot>> {
let conn = self.open_current_schema_connection()?;
load_latest_manifest_snapshot_for_repository(&conn, repository_id)
}
pub fn load_latest_manifest_metadata_for_repository(
&self,
repository_id: &str,
) -> FriggResult<Option<RepositoryManifestMetadataSnapshot>> {
let conn = self.open_current_schema_connection()?;
load_latest_manifest_metadata_snapshot_for_repository(&conn, repository_id)
}
#[allow(dead_code)]
pub(crate) fn load_latest_context_efficiency_manifest_summary_for_repository(
&self,
repository_id: &str,
) -> FriggResult<Option<ManifestMetadataSummary>> {
let conn = self.open_current_schema_connection()?;
let Some(snapshot_id) =
load_latest_manifest_snapshot_id_for_repository(&conn, repository_id)?
else {
return Ok(None);
};
let cache_key = ManifestMetadataCacheKey::from_storage_path(
self.db_path(),
repository_id,
&snapshot_id,
);
if let Some(summary) = cached_manifest_metadata_summary(&cache_key) {
return Ok(Some(summary));
}
let entries = load_manifest_metadata_entries_for_snapshot(&conn, &snapshot_id)?;
let snapshot = RepositoryManifestMetadataSnapshot {
repository_id: repository_id.to_owned(),
snapshot_id,
entries,
};
let summary = ManifestMetadataSummary::from_snapshot(&snapshot);
store_manifest_metadata_summary(cache_key, summary.clone());
Ok(Some(summary))
}
pub fn delete_snapshot(&self, snapshot_id: &str) -> FriggResult<()> {
let snapshot_id = snapshot_id.trim();
if snapshot_id.is_empty() {
return Err(FriggError::InvalidInput(
"snapshot_id must not be empty".to_owned(),
));
}
let mut conn = self.open_current_schema_connection()?;
let tx = conn.transaction().map_err(|err| {
FriggError::Internal(format!(
"failed to start snapshot delete transaction for '{snapshot_id}': {err}"
))
})?;
delete_snapshot_rows_in_transaction(&tx, snapshot_id)?;
tx.commit().map_err(|err| {
FriggError::Internal(format!(
"failed to commit snapshot delete for '{snapshot_id}': {err}"
))
})?;
Ok(())
}
}
impl StorageSession {
pub(crate) fn upsert_repository(
&self,
repository_id: &str,
root_path: &std::path::Path,
display_name: &str,
) -> FriggResult<()> {
upsert_repository_on_connection(&self.conn, repository_id, root_path, display_name)
}
pub(crate) fn upsert_manifest(
&mut self,
repository_id: &str,
snapshot_id: &str,
entries: &[ManifestEntry],
) -> FriggResult<()> {
upsert_manifest_on_connection(&mut self.conn, repository_id, snapshot_id, entries)
}
pub(crate) fn load_manifest_for_snapshot(
&self,
snapshot_id: &str,
) -> FriggResult<Vec<ManifestEntry>> {
load_manifest_entries_for_snapshot(&self.conn, snapshot_id)
}
pub(crate) fn load_latest_manifest_for_repository(
&self,
repository_id: &str,
) -> FriggResult<Option<RepositoryManifestSnapshot>> {
load_latest_manifest_snapshot_for_repository(&self.conn, repository_id)
}
pub(crate) fn delete_snapshot(&mut self, snapshot_id: &str) -> FriggResult<()> {
let snapshot_id = snapshot_id.trim();
if snapshot_id.is_empty() {
return Err(FriggError::InvalidInput(
"snapshot_id must not be empty".to_owned(),
));
}
let tx = self.conn.transaction().map_err(|err| {
FriggError::Internal(format!(
"failed to start snapshot delete transaction for '{snapshot_id}': {err}"
))
})?;
delete_snapshot_rows_in_transaction(&tx, snapshot_id)?;
tx.commit().map_err(|err| {
FriggError::Internal(format!(
"failed to commit snapshot delete for '{snapshot_id}': {err}"
))
})?;
Ok(())
}
}
fn upsert_repository_on_connection(
conn: &Connection,
repository_id: &str,
root_path: &std::path::Path,
display_name: &str,
) -> FriggResult<()> {
let repository_id = repository_id.trim();
if repository_id.is_empty() {
return Err(FriggError::InvalidInput(
"repository_id must not be empty".to_owned(),
));
}
let display_name = display_name.trim();
if display_name.is_empty() {
return Err(FriggError::InvalidInput(
"display_name must not be empty".to_owned(),
));
}
conn.execute(
r#"
INSERT INTO repository (repository_id, root_path, display_name, created_at)
VALUES (?1, ?2, ?3, STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'now'))
ON CONFLICT(repository_id) DO UPDATE SET
root_path = excluded.root_path,
display_name = excluded.display_name
"#,
(
repository_id,
root_path.to_string_lossy().into_owned(),
display_name,
),
)
.map_err(|err| {
FriggError::Internal(format!(
"failed to upsert repository metadata for '{repository_id}': {err}"
))
})?;
Ok(())
}
fn upsert_manifest_on_connection(
conn: &mut Connection,
repository_id: &str,
snapshot_id: &str,
entries: &[ManifestEntry],
) -> FriggResult<()> {
let tx = conn.transaction().map_err(|err| {
FriggError::Internal(format!(
"failed to start manifest upsert transaction for snapshot '{snapshot_id}': {err}"
))
})?;
tx.execute(
r#"
INSERT OR IGNORE INTO repository (repository_id, root_path, display_name, created_at)
VALUES (?1, '/legacy-import', ?1, STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'now'))
"#,
[repository_id],
)
.map_err(|err| {
FriggError::Internal(format!(
"failed to seed repository stub row for snapshot '{snapshot_id}' in repository '{repository_id}': {err}"
))
})?;
tx.execute(
r#"
INSERT INTO snapshot (snapshot_id, repository_id, kind, revision, created_at)
VALUES (?1, ?2, 'manifest', NULL, STRFTIME('%Y-%m-%dT%H:%M:%fZ', 'now'))
ON CONFLICT(snapshot_id) DO UPDATE SET
repository_id = excluded.repository_id
"#,
[snapshot_id, repository_id],
)
.map_err(|err| {
FriggError::Internal(format!(
"failed to upsert snapshot metadata for '{snapshot_id}': {err}"
))
})?;
tx.execute(
"DELETE FROM file_manifest WHERE snapshot_id = ?1",
[snapshot_id],
)
.map_err(|err| {
FriggError::Internal(format!(
"failed to clear existing manifest rows for snapshot '{snapshot_id}': {err}"
))
})?;
let mut ordered_entries = entries.to_vec();
ordered_entries.sort_by(|left, right| left.path.cmp(&right.path));
let mut insert_stmt = tx
.prepare(
r#"
INSERT INTO file_manifest (snapshot_id, path, sha256, size_bytes, mtime_ns)
VALUES (?1, ?2, ?3, ?4, ?5)
"#,
)
.map_err(|err| {
FriggError::Internal(format!(
"failed to prepare manifest insert statement for snapshot '{snapshot_id}': {err}"
))
})?;
for entry in ordered_entries {
insert_stmt
.execute((
snapshot_id,
entry.path,
entry.sha256,
u64_to_i64(entry.size_bytes, "size_bytes")?,
option_u64_to_option_i64(entry.mtime_ns, "mtime_ns")?,
))
.map_err(|err| {
FriggError::Internal(format!(
"failed to insert manifest row for snapshot '{snapshot_id}': {err}"
))
})?;
}
drop(insert_stmt);
tx.commit().map_err(|err| {
FriggError::Internal(format!(
"failed to commit manifest upsert for snapshot '{snapshot_id}': {err}"
))
})?;
Ok(())
}
pub(super) fn delete_snapshot_rows_in_transaction(
tx: &Transaction<'_>,
snapshot_id: &str,
) -> FriggResult<()> {
let active_semantic_heads: i64 = tx
.query_row(
"SELECT COUNT(*) FROM semantic_head WHERE covered_snapshot_id = ?1",
[snapshot_id],
|row| row.get(0),
)
.map_err(|err| {
FriggError::Internal(format!(
"failed to query semantic head coverage for snapshot '{snapshot_id}': {err}"
))
})?;
if active_semantic_heads > 0 {
return Err(FriggError::InvalidInput(format!(
"cannot delete snapshot '{snapshot_id}' because it is still covered by the active live semantic corpus; refresh semantics to a newer snapshot or clear the semantic head first"
)));
}
for (table, description) in [
("path_witness_projection", "path witness projection rows"),
("test_subject_projection", "test subject projection rows"),
(
"entrypoint_surface_projection",
"entrypoint surface projection rows",
),
(
"retrieval_projection_head",
"retrieval projection head rows",
),
("path_relation_projection", "path relation projection rows"),
(
"subtree_coverage_projection",
"subtree coverage projection rows",
),
(
"path_surface_term_projection",
"path surface-term projection rows",
),
(
"path_anchor_sketch_projection",
"path anchor sketch projection rows",
),
("file_manifest", "manifest rows"),
] {
tx.execute(
&format!("DELETE FROM {table} WHERE snapshot_id = ?1"),
[snapshot_id],
)
.map_err(|err| {
FriggError::Internal(format!(
"failed to delete {description} for snapshot '{snapshot_id}': {err}"
))
})?;
}
tx.execute("DELETE FROM snapshot WHERE snapshot_id = ?1", [snapshot_id])
.map_err(|err| {
FriggError::Internal(format!(
"failed to delete snapshot metadata for '{snapshot_id}': {err}"
))
})?;
Ok(())
}