use rusqlite::{Connection, OptionalExtension}; use tracing::info; use super::error::Result; use super::time::now_ms; pub fn extract_refs_from_state_events(conn: &Connection, project_id: i64) -> Result { let changes = conn.execute( "INSERT OR IGNORE INTO entity_references ( project_id, source_entity_type, source_entity_id, target_entity_type, target_entity_id, reference_type, source_method, created_at ) SELECT rse.project_id, 'merge_request', mr.id, 'issue', rse.issue_id, 'closes', 'api', rse.created_at FROM resource_state_events rse JOIN merge_requests mr ON mr.project_id = rse.project_id AND mr.iid = rse.source_merge_request_iid WHERE rse.source_merge_request_iid IS NOT NULL AND rse.issue_id IS NOT NULL AND rse.project_id = ?1", rusqlite::params![project_id], )?; if changes > 0 { info!( project_id, references_inserted = changes, "Extracted cross-references from state events" ); } Ok(changes) } #[derive(Debug, Clone)] pub struct EntityReference<'a> { pub project_id: i64, pub source_entity_type: &'a str, pub source_entity_id: i64, pub target_entity_type: &'a str, pub target_entity_id: Option, pub target_project_path: Option<&'a str>, pub target_entity_iid: Option, pub reference_type: &'a str, pub source_method: &'a str, } pub fn insert_entity_reference(conn: &Connection, ref_: &EntityReference<'_>) -> Result { let now = now_ms(); let changes = conn.execute( "INSERT OR IGNORE INTO entity_references \ (project_id, source_entity_type, source_entity_id, \ target_entity_type, target_entity_id, target_project_path, target_entity_iid, \ reference_type, source_method, created_at) \ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)", rusqlite::params![ ref_.project_id, ref_.source_entity_type, ref_.source_entity_id, ref_.target_entity_type, ref_.target_entity_id, ref_.target_project_path, ref_.target_entity_iid, ref_.reference_type, ref_.source_method, now, ], )?; Ok(changes > 0) } pub fn resolve_issue_local_id( conn: &Connection, project_id: i64, issue_iid: i64, ) -> Result> { let mut stmt = conn.prepare_cached("SELECT id FROM issues WHERE project_id = ?1 AND iid = ?2")?; let result = stmt .query_row(rusqlite::params![project_id, issue_iid], |row| row.get(0)) .optional()?; Ok(result) } pub fn resolve_project_path(conn: &Connection, gitlab_project_id: i64) -> Result> { let mut stmt = conn .prepare_cached("SELECT path_with_namespace FROM projects WHERE gitlab_project_id = ?1")?; let result = stmt .query_row(rusqlite::params![gitlab_project_id], |row| row.get(0)) .optional()?; Ok(result) } pub fn count_references_for_source( conn: &Connection, source_entity_type: &str, source_entity_id: i64, ) -> Result { let count: i64 = conn.query_row( "SELECT COUNT(*) FROM entity_references \ WHERE source_entity_type = ?1 AND source_entity_id = ?2", rusqlite::params![source_entity_type, source_entity_id], |row| row.get(0), )?; Ok(count as usize) } #[cfg(test)] #[path = "references_tests.rs"] mod tests;