use rusqlite::Connection; use tracing::debug; use crate::core::error::Result; use crate::gitlab::types::GitLabMrDiff; /// Derive the change type from GitLab's boolean flags. fn derive_change_type(diff: &GitLabMrDiff) -> &'static str { if diff.new_file { "added" } else if diff.renamed_file { "renamed" } else if diff.deleted_file { "deleted" } else { "modified" } } /// Replace all file change records for a given MR with the provided diffs. /// Uses DELETE+INSERT (simpler than UPSERT for array replacement). /// /// Does NOT manage its own transaction — the caller is responsible for /// wrapping this in a transaction when atomicity with other operations /// (job completion, watermark update) is needed. pub fn upsert_mr_file_changes( conn: &Connection, mr_local_id: i64, project_id: i64, diffs: &[GitLabMrDiff], ) -> Result { conn.execute( "DELETE FROM mr_file_changes WHERE merge_request_id = ?1", [mr_local_id], )?; let mut stmt = conn.prepare_cached( "INSERT INTO mr_file_changes (merge_request_id, project_id, old_path, new_path, change_type) \ VALUES (?1, ?2, ?3, ?4, ?5)", )?; let mut inserted = 0; for diff in diffs { let old_path = if diff.renamed_file { Some(diff.old_path.as_str()) } else { None }; let change_type = derive_change_type(diff); stmt.execute(rusqlite::params![ mr_local_id, project_id, old_path, diff.new_path, change_type, ])?; inserted += 1; } if inserted > 0 { debug!(inserted, mr_local_id, "Stored MR file changes"); } Ok(inserted) } #[cfg(test)] #[path = "mr_diffs_tests.rs"] mod tests;