use super::FactsDb;
use crate::{CodeLoreError, Result};
pub fn materialize_path_lineage(db: &FactsDb) -> Result<()> {
use duckdb::params;
let sql = "CREATE OR REPLACE TEMPORARY TABLE path_lineage AS
WITH RECURSIVE lineage(orig, current, current_date, current_rowid, depth) AS (
SELECT DISTINCT c.rename_from, c.path, co.date, co.rowid, 1
FROM changes c
INNER JOIN commits co ON co.rev = c.rev
WHERE c.rename_from IS NOT NULL
UNION ALL
SELECT l.orig, c.path, co.date, co.rowid, l.depth + 1
FROM lineage l
INNER JOIN changes c ON c.rename_from = l.current
INNER JOIN commits co ON co.rev = c.rev
WHERE l.depth < 50
AND (co.date > l.current_date
OR (co.date = l.current_date AND co.rowid < l.current_rowid))
)
SELECT orig AS old_path, current AS canonical_path
FROM (
SELECT orig, current, depth,
ROW_NUMBER() OVER (
PARTITION BY orig
-- Secondary order on `current` so ties at the same
-- depth (possible when a non-linear rename graph
-- reaches the same intermediate via multiple paths)
-- break deterministically and run-to-run output stays
-- byte-equal.
ORDER BY depth DESC, current ASC
) AS rn
FROM lineage
)
WHERE rn = 1 AND orig != current";
db.conn()
.execute(sql, params![])
.map_err(|e| CodeLoreError::Analysis(format!("materialize path_lineage: {e}")))?;
Ok(())
}
pub fn materialize_changes_lineage(db: &FactsDb) -> Result<()> {
use duckdb::params;
if db.is_changes_lineage_built() {
return Ok(());
}
materialize_path_lineage(db)?;
let sql = "CREATE OR REPLACE TEMPORARY TABLE changes_lineage AS
SELECT
c.rev,
COALESCE(pl.canonical_path, c.path) AS path,
c.change_type,
c.rename_from,
c.loc_added,
c.loc_deleted
FROM changes c
LEFT JOIN path_lineage pl ON pl.old_path = c.path";
db.conn()
.execute(sql, params![])
.map_err(|e| CodeLoreError::Analysis(format!("materialize changes_lineage: {e}")))?;
for stmt in [
"CREATE INDEX IF NOT EXISTS idx_changes_lineage_path ON changes_lineage(path)",
"CREATE INDEX IF NOT EXISTS idx_changes_lineage_rev ON changes_lineage(rev)",
] {
db.conn()
.execute(stmt, params![])
.map_err(|e| CodeLoreError::Analysis(format!("index changes_lineage: {e}")))?;
}
db.mark_changes_lineage_built();
tracing::info!("materialized changes_lineage with canonical rename paths");
Ok(())
}