relay-knowledge 1.1.17

Graph-database-based knowledge graph project.
Documentation
//! Bounded SQLite chunk, caller, and importer evidence retrieval for code impact.

use std::collections::BTreeSet;

use rusqlite::{Connection, params_from_iter, types::Value};

use crate::{
    domain::{
        CodeImpactRequest, CodeRepositoryStatus, CodeRetrievalHit, CodeRetrievalLayer,
        RepositoryCodeRange,
    },
    storage::StorageError,
};

use super::{
    super::query::{HitParts, chunk_layers, hit_from_parts, required_scope},
    path_selection::impact_row_allowed,
    seed::module_import_matches,
};

const SQLITE_EXPRESSION_BATCH_SIZE: usize = 250;

pub(super) fn chunks_for_paths(
    connection: &Connection,
    status: &CodeRepositoryStatus,
    paths: &BTreeSet<String>,
    request: &CodeImpactRequest,
) -> Result<Vec<CodeRetrievalHit>, StorageError> {
    if paths.is_empty() {
        return Ok(Vec::new());
    }
    let path_values = paths.iter().cloned().collect::<Vec<_>>();
    let path_clause = placeholders(path_values.len());
    let sql = format!(
        "
        SELECT c.file_id, c.path, c.language_id, c.content, c.byte_start, c.byte_end,
               c.line_start, c.line_end, c.symbol_snapshot_id,
               symbol.canonical_symbol_id, f.parse_status, f.degraded_reason, f.is_generated
        FROM code_repository_chunks c
        INNER JOIN code_repository_files f
            ON f.source_scope = c.source_scope AND f.path = c.path
        LEFT JOIN code_repository_symbols symbol
            ON symbol.source_scope = c.source_scope
           AND symbol.symbol_snapshot_id = c.symbol_snapshot_id
        WHERE c.source_scope = ?1
          AND c.path IN ({path_clause})
        ORDER BY c.path ASC, c.line_start ASC
        ",
    );
    let mut values = vec![Value::Text(required_scope(status)?.to_owned())];
    values.extend(path_values.into_iter().map(Value::Text));
    let mut statement = connection.prepare(&sql)?;
    let rows = statement.query_map(params_from_iter(values), |row| {
        Ok(ImpactChunkRow {
            file_id: row.get(0)?,
            path: row.get(1)?,
            language_id: row.get(2)?,
            content: row.get(3)?,
            byte_range: RepositoryCodeRange {
                start: row.get(4)?,
                end: row.get(5)?,
            },
            line_range: RepositoryCodeRange {
                start: row.get(6)?,
                end: row.get(7)?,
            },
            symbol_snapshot_id: row.get(8)?,
            canonical_symbol_id: row.get(9)?,
            parse_status: row.get(10)?,
            degraded_reason: row.get(11)?,
            is_generated: row.get::<_, i64>(12)? != 0,
        })
    })?;
    let rows = rows
        .collect::<Result<Vec<_>, _>>()
        .map_err(StorageError::from)?;

    Ok(rows
        .into_iter()
        .filter(|row| impact_row_allowed(&row.path, &row.language_id, status, request))
        .map(|row| {
            hit_from_parts(
                status,
                HitParts {
                    path: row.path,
                    language_id: row.language_id,
                    byte_range: row.byte_range,
                    line_range: row.line_range,
                    symbol_snapshot_id: row.symbol_snapshot_id,
                    canonical_symbol_id: row.canonical_symbol_id,
                    file_id: Some(row.file_id),
                    retrieval_layers: chunk_layers(&row.parse_status),
                    score: 4.0,
                    excerpt: row.content,
                    is_generated: row.is_generated,
                    degraded_reason: row.degraded_reason,
                    edge_kind: None,
                    edge_resolution_state: None,
                    edge_target_hint: None,
                    edge_confidence_basis_points: None,
                    edge_confidence_tier: None,
                },
            )
        })
        .collect())
}

pub(super) fn callers_for_symbols(
    connection: &Connection,
    status: &CodeRepositoryStatus,
    symbol_ids: &[String],
    deleted_symbol_names: &[String],
    request: &CodeImpactRequest,
) -> Result<Vec<CodeRetrievalHit>, StorageError> {
    if symbol_ids.is_empty() && deleted_symbol_names.is_empty() {
        return Ok(Vec::new());
    }
    let mut values = vec![Value::Text(required_scope(status)?.to_owned())];
    let mut filters = Vec::new();
    if !symbol_ids.is_empty() {
        filters.push(format!(
            "c.callee_symbol_snapshot_id IN ({})",
            placeholders(symbol_ids.len())
        ));
        values.extend(symbol_ids.iter().cloned().map(Value::Text));
    }
    if !deleted_symbol_names.is_empty() {
        filters.push(format!(
            "(c.callee_symbol_snapshot_id IS NULL AND c.callee_name IN ({}))",
            placeholders(deleted_symbol_names.len())
        ));
        values.extend(deleted_symbol_names.iter().cloned().map(Value::Text));
    }
    let sql = format!(
        "
        SELECT c.file_id, c.path, f.language_id, c.caller_symbol_snapshot_id,
               c.caller_name, c.callee_symbol_snapshot_id, c.callee_name,
               c.line_start, c.line_end, c.target_hint, c.resolution_state,
               c.confidence_basis_points, c.confidence_tier, caller.canonical_symbol_id,
               f.is_generated
        FROM code_repository_calls c
        INNER JOIN code_repository_files f
            ON f.source_scope = c.source_scope AND f.path = c.path
        LEFT JOIN code_repository_symbols caller
            ON caller.source_scope = c.source_scope
           AND caller.symbol_snapshot_id = c.caller_symbol_snapshot_id
        WHERE c.source_scope = ?1
          AND ({})
        ORDER BY c.path ASC, c.line_start ASC
        ",
        filters.join(" OR ")
    );
    let mut statement = connection.prepare(&sql)?;
    let rows = statement.query_map(params_from_iter(values), |row| {
        Ok(ImpactCallRow {
            file_id: row.get(0)?,
            path: row.get(1)?,
            language_id: row.get(2)?,
            caller_symbol_snapshot_id: row.get(3)?,
            caller_name: row.get(4)?,
            callee_symbol_snapshot_id: row.get(5)?,
            callee_name: row.get(6)?,
            line_range: RepositoryCodeRange {
                start: row.get(7)?,
                end: row.get(8)?,
            },
            target_hint: row.get(9)?,
            resolution_state: row.get(10)?,
            confidence_basis_points: row.get(11)?,
            confidence_tier: row.get(12)?,
            caller_canonical_symbol_id: row.get(13)?,
            is_generated: row.get::<_, i64>(14)? != 0,
        })
    })?;
    let symbol_set = symbol_ids.iter().collect::<BTreeSet<_>>();
    let deleted_name_set = deleted_symbol_names.iter().collect::<BTreeSet<_>>();
    let rows = rows
        .collect::<Result<Vec<_>, _>>()
        .map_err(StorageError::from)?;

    Ok(rows
        .into_iter()
        .filter(|row| impact_row_allowed(&row.path, &row.language_id, status, request))
        .filter(|row| {
            row.callee_symbol_snapshot_id
                .as_ref()
                .is_some_and(|symbol_id| symbol_set.contains(symbol_id))
                || (row.callee_symbol_snapshot_id.is_none()
                    && deleted_name_set.contains(&row.callee_name))
        })
        .map(|row| {
            let caller = row.caller_name.unwrap_or_else(|| "<module>".to_owned());
            hit_from_parts(
                status,
                HitParts {
                    path: row.path,
                    language_id: row.language_id,
                    byte_range: RepositoryCodeRange { start: 0, end: 0 },
                    line_range: row.line_range,
                    symbol_snapshot_id: row.caller_symbol_snapshot_id,
                    canonical_symbol_id: row.caller_canonical_symbol_id,
                    file_id: Some(row.file_id),
                    retrieval_layers: vec![CodeRetrievalLayer::CallGraph],
                    score: 2.5,
                    excerpt: format!("{caller} calls {}", row.callee_name),
                    is_generated: row.is_generated,
                    degraded_reason: None,
                    edge_kind: Some("call".to_owned()),
                    edge_resolution_state: Some(row.resolution_state),
                    edge_target_hint: row.target_hint,
                    edge_confidence_basis_points: Some(row.confidence_basis_points),
                    edge_confidence_tier: Some(row.confidence_tier),
                },
            )
        })
        .collect())
}

pub(super) fn importers_for_modules(
    connection: &Connection,
    status: &CodeRepositoryStatus,
    modules: &[String],
    request: &CodeImpactRequest,
) -> Result<Vec<CodeRetrievalHit>, StorageError> {
    if modules.is_empty() {
        return Ok(Vec::new());
    }
    let module_patterns = modules
        .iter()
        .map(|module| format!("%{module}%"))
        .collect::<Vec<_>>();
    let mut rows = Vec::new();
    for patterns in module_patterns.chunks(SQLITE_EXPRESSION_BATCH_SIZE) {
        rows.extend(import_rows_for_patterns(connection, status, patterns)?);
    }

    Ok(rows
        .into_iter()
        .filter(|row| impact_row_allowed(&row.path, &row.language_id, status, request))
        .filter(|row| {
            modules
                .iter()
                .any(|module| module_import_matches(&row.module, module))
        })
        .map(|row| {
            hit_from_parts(
                status,
                HitParts {
                    path: row.path,
                    language_id: row.language_id,
                    byte_range: RepositoryCodeRange { start: 0, end: 0 },
                    line_range: row.line_range,
                    symbol_snapshot_id: None,
                    canonical_symbol_id: None,
                    file_id: Some(row.file_id),
                    retrieval_layers: vec![CodeRetrievalLayer::ImportGraph],
                    score: 2.0,
                    excerpt: row.module,
                    is_generated: row.is_generated,
                    degraded_reason: None,
                    edge_kind: Some("import".to_owned()),
                    edge_resolution_state: Some(row.resolution_state),
                    edge_target_hint: row.target_hint,
                    edge_confidence_basis_points: Some(row.confidence_basis_points),
                    edge_confidence_tier: Some(row.confidence_tier),
                },
            )
        })
        .collect())
}

fn import_rows_for_patterns(
    connection: &Connection,
    status: &CodeRepositoryStatus,
    module_patterns: &[String],
) -> Result<Vec<ImpactImportRow>, StorageError> {
    let module_clause = module_patterns
        .iter()
        .map(|_| "i.module LIKE ?")
        .collect::<Vec<_>>()
        .join(" OR ");
    let sql = format!(
        "
        SELECT i.file_id, i.path, f.language_id, i.module, i.line_start, i.line_end,
               i.target_hint, i.resolution_state, i.confidence_basis_points, i.confidence_tier,
               f.is_generated
        FROM code_repository_imports i
        INNER JOIN code_repository_files f
            ON f.source_scope = i.source_scope AND f.path = i.path
        WHERE i.source_scope = ?1
          AND ({module_clause})
        ORDER BY i.path ASC, i.line_start ASC
        ",
    );
    let mut values = vec![Value::Text(required_scope(status)?.to_owned())];
    values.extend(module_patterns.iter().cloned().map(Value::Text));
    let mut statement = connection.prepare(&sql)?;
    let rows = statement.query_map(params_from_iter(values), |row| {
        Ok(ImpactImportRow {
            file_id: row.get(0)?,
            path: row.get(1)?,
            language_id: row.get(2)?,
            module: row.get(3)?,
            line_range: RepositoryCodeRange {
                start: row.get(4)?,
                end: row.get(5)?,
            },
            target_hint: row.get(6)?,
            resolution_state: row.get(7)?,
            confidence_basis_points: row.get(8)?,
            confidence_tier: row.get(9)?,
            is_generated: row.get::<_, i64>(10)? != 0,
        })
    })?;
    rows.collect::<Result<Vec<_>, _>>()
        .map_err(StorageError::from)
}

fn placeholders(count: usize) -> String {
    std::iter::repeat_n("?", count)
        .collect::<Vec<_>>()
        .join(", ")
}

struct ImpactChunkRow {
    file_id: String,
    path: String,
    language_id: String,
    content: String,
    byte_range: RepositoryCodeRange,
    line_range: RepositoryCodeRange,
    symbol_snapshot_id: Option<String>,
    canonical_symbol_id: Option<String>,
    parse_status: String,
    degraded_reason: Option<String>,
    is_generated: bool,
}

struct ImpactCallRow {
    file_id: String,
    path: String,
    language_id: String,
    caller_symbol_snapshot_id: Option<String>,
    caller_name: Option<String>,
    callee_symbol_snapshot_id: Option<String>,
    callee_name: String,
    line_range: RepositoryCodeRange,
    target_hint: Option<String>,
    resolution_state: String,
    confidence_basis_points: u16,
    confidence_tier: String,
    caller_canonical_symbol_id: Option<String>,
    is_generated: bool,
}

struct ImpactImportRow {
    file_id: String,
    path: String,
    language_id: String,
    module: String,
    line_range: RepositoryCodeRange,
    target_hint: Option<String>,
    resolution_state: String,
    confidence_basis_points: u16,
    confidence_tier: String,
    is_generated: bool,
}