relay-knowledge 1.1.17

Graph-database-based knowledge graph project.
Documentation
//! Retrieval-hit construction, filtering, deduplication, and degradation metadata.

use std::collections::BTreeMap;

use rusqlite::Connection;

use crate::{
    domain::{
        CodeRepositorySelector, CodeRepositoryStatus, CodeRetrievalHit, CodeRetrievalLayer,
        CodeRetrievalRequest, RepositoryCodeRange, StalenessHint,
    },
    storage::StorageError,
};

use super::super::{
    code_query_scope::{
        language_filter_allows_path, path_filter_allows, selector_filters_fit_indexed_scope,
    },
    lifecycle::status::{repository_scope_status, repository_status},
};

pub(in crate::storage::sqlite::code) fn required_repository(
    connection: &mut Connection,
    selector: &CodeRepositorySelector,
) -> Result<CodeRepositoryStatus, StorageError> {
    let status = repository_status(connection, &selector.repository)?.ok_or_else(|| {
        StorageError::InvalidInput(format!(
            "code repository '{}' is not registered",
            selector.repository
        ))
    })?;
    let path_filters = merged_filters(&status.path_filters, &selector.path_filters);
    let language_filters = merged_filters(&status.language_filters, &selector.language_filters);
    let scoped_status = match repository_scope_status(
        connection,
        &selector.repository,
        &selector.ref_selector,
        &path_filters,
        &language_filters,
    )? {
        Some(status) => Some(status),
        None if (!selector.path_filters.is_empty() || !selector.language_filters.is_empty())
            && selector_filters_fit_indexed_scope(
                &status.path_filters,
                &status.language_filters,
                &selector.path_filters,
                &selector.language_filters,
            ) =>
        {
            repository_scope_status(
                connection,
                &selector.repository,
                &selector.ref_selector,
                &status.path_filters,
                &status.language_filters,
            )?
        }
        None => None,
    }
    .ok_or_else(|| {
        StorageError::InvalidInput(format!(
            "code repository '{}' has no index for ref {} and requested filters",
            selector.repository, selector.ref_selector
        ))
    })?;

    if (status.stale || status.state != "fresh")
        && scoped_status.last_indexed_scope_id != status.last_indexed_scope_id
    {
        return Err(StorageError::InvalidInput(format!(
            "code repository '{}' has no published index for ref {} and requested filters",
            selector.repository, selector.ref_selector
        )));
    }

    Ok(scoped_status)
}

fn merged_filters(left: &[String], right: &[String]) -> Vec<String> {
    let mut merged = Vec::new();
    for value in left.iter().chain(right.iter()) {
        if !merged.contains(value) {
            merged.push(value.clone());
        }
    }

    merged
}

pub(in crate::storage::sqlite::code) fn selected_row(
    path: &str,
    language_id: &str,
    is_generated: bool,
    status: &CodeRepositoryStatus,
    request: &CodeRetrievalRequest,
) -> bool {
    if request.exclude_generated && is_generated {
        return false;
    }
    path_filter_allows(path, &status.path_filters)
        && path_filter_allows(path, &request.repository.path_filters)
        && language_filter_allows_path(path, language_id, &status.language_filters)
        && language_filter_allows_path(path, language_id, &request.repository.language_filters)
        && language_filter_allows_path(path, language_id, &request.query_language_filters)
}

pub(in crate::storage::sqlite::code) fn chunk_layers(
    parse_status: &str,
) -> Vec<CodeRetrievalLayer> {
    let mut layers = vec![CodeRetrievalLayer::Lexical];
    if parse_status != "parsed" {
        layers.push(CodeRetrievalLayer::TextFallback);
    }

    layers
}

pub(in crate::storage::sqlite::code) struct HitParts {
    pub(in crate::storage::sqlite::code) path: String,
    pub(in crate::storage::sqlite::code) language_id: String,
    pub(in crate::storage::sqlite::code) byte_range: RepositoryCodeRange,
    pub(in crate::storage::sqlite::code) line_range: RepositoryCodeRange,
    pub(in crate::storage::sqlite::code) symbol_snapshot_id: Option<String>,
    pub(in crate::storage::sqlite::code) canonical_symbol_id: Option<String>,
    pub(in crate::storage::sqlite::code) file_id: Option<String>,
    pub(in crate::storage::sqlite::code) retrieval_layers: Vec<CodeRetrievalLayer>,
    pub(in crate::storage::sqlite::code) score: f64,
    pub(in crate::storage::sqlite::code) excerpt: String,
    pub(in crate::storage::sqlite::code) is_generated: bool,
    pub(in crate::storage::sqlite::code) degraded_reason: Option<String>,
    pub(in crate::storage::sqlite::code) edge_kind: Option<String>,
    pub(in crate::storage::sqlite::code) edge_resolution_state: Option<String>,
    pub(in crate::storage::sqlite::code) edge_target_hint: Option<String>,
    pub(in crate::storage::sqlite::code) edge_confidence_basis_points: Option<u16>,
    pub(in crate::storage::sqlite::code) edge_confidence_tier: Option<String>,
}

pub(in crate::storage::sqlite::code) fn hit_from_parts(
    status: &CodeRepositoryStatus,
    parts: HitParts,
) -> CodeRetrievalHit {
    CodeRetrievalHit {
        repository_id: status.repository_id.clone(),
        scope_id: status.last_indexed_scope_id.clone().unwrap_or_default(),
        resolved_commit_sha: status.last_indexed_commit.clone().unwrap_or_default(),
        tree_hash: status.tree_hash.clone().unwrap_or_default(),
        path: parts.path,
        language_id: parts.language_id,
        byte_range: parts.byte_range,
        line_range: parts.line_range,
        symbol_snapshot_id: parts.symbol_snapshot_id,
        canonical_symbol_id: parts.canonical_symbol_id,
        file_id: parts.file_id,
        retrieval_layers: parts.retrieval_layers,
        index_versions: vec![format!(
            "code:{}:{}",
            status
                .last_indexed_scope_id
                .as_deref()
                .unwrap_or("unscoped"),
            status.tree_hash.as_deref().unwrap_or("unindexed")
        )],
        stale: status.stale,
        staleness_hint: Some(if status.stale {
            StalenessHint::Stale {}
        } else {
            StalenessHint::Fresh
        }),
        degraded_reason: parts.degraded_reason,
        edge_kind: parts.edge_kind,
        edge_resolution_state: parts.edge_resolution_state,
        edge_target_hint: parts.edge_target_hint,
        edge_confidence_basis_points: parts.edge_confidence_basis_points,
        edge_confidence_tier: parts.edge_confidence_tier,
        score: generated_adjusted_score(parts.score, parts.is_generated),
        excerpt: parts.excerpt,
    }
}

fn generated_adjusted_score(score: f64, is_generated: bool) -> f64 {
    const GENERATED_FILE_SCORE_MULTIPLIER: f64 = 0.35;

    if is_generated {
        score * GENERATED_FILE_SCORE_MULTIPLIER
    } else {
        score
    }
}

pub(in crate::storage::sqlite::code) fn required_scope(
    status: &CodeRepositoryStatus,
) -> Result<&str, StorageError> {
    status.last_indexed_scope_id.as_deref().ok_or_else(|| {
        StorageError::InvalidInput(format!(
            "code repository '{}' does not have an indexed source scope",
            status.alias
        ))
    })
}

pub(in crate::storage::sqlite::code) fn dedupe_sort_truncate(
    hits: &mut Vec<CodeRetrievalHit>,
    limit: usize,
) {
    let mut best = BTreeMap::<(String, u32, String), CodeRetrievalHit>::new();
    for hit in hits.drain(..) {
        let key = (hit.path.clone(), hit.line_range.start, hit.excerpt.clone());
        match best.get(&key) {
            Some(existing) if existing.score >= hit.score => {
                let existing = best.get_mut(&key).expect("checked entry should exist");
                merge_hit_provenance(existing, &hit);
            }
            Some(_) => {
                let mut hit = hit;
                if let Some(existing) = best.get(&key) {
                    merge_hit_provenance(&mut hit, existing);
                }
                best.insert(key, hit);
            }
            _ => {
                best.insert(key, hit);
            }
        }
    }
    hits.extend(best.into_values());
    hits.sort_by(|left, right| {
        right
            .score
            .total_cmp(&left.score)
            .then_with(|| left.path.cmp(&right.path))
            .then_with(|| left.line_range.start.cmp(&right.line_range.start))
    });
    hits.truncate(limit);
}

pub(in crate::storage::sqlite::code) fn filter_dedupe_sort_truncate(
    hits: &mut Vec<CodeRetrievalHit>,
    request: &CodeRetrievalRequest,
) {
    hits.retain(|hit| query_field_filters_allow_hit(hit, request));
    dedupe_sort_truncate(hits, request.limit);
}

pub(in crate::storage::sqlite::code) fn filtered_hits_for_gate(
    hits: &[CodeRetrievalHit],
    request: &CodeRetrievalRequest,
) -> Vec<CodeRetrievalHit> {
    let mut filtered = hits.to_vec();
    filter_dedupe_sort_truncate(&mut filtered, request);
    filtered
}

pub(in crate::storage::sqlite::code) fn query_field_filtered_hits_for_gate(
    hits: &[CodeRetrievalHit],
    request: &CodeRetrievalRequest,
) -> Vec<CodeRetrievalHit> {
    let mut filtered = hits.to_vec();
    filtered.retain(|hit| query_field_filters_allow_hit(hit, request));
    dedupe_sort_truncate(&mut filtered, usize::MAX);
    filtered
}

pub(in crate::storage::sqlite::code) fn has_query_field_hit_filters(
    request: &CodeRetrievalRequest,
) -> bool {
    !request.query_kind_filters.is_empty()
        || !request.query_path_substrings.is_empty()
        || !request.query_name_substrings.is_empty()
}

fn query_field_filters_allow_hit(hit: &CodeRetrievalHit, request: &CodeRetrievalRequest) -> bool {
    kind_filters_allow_hit(hit, &request.query_kind_filters)
        && path_substrings_allow_hit(&hit.path, &request.query_path_substrings)
        && name_substrings_allow_hit(hit, &request.query_name_substrings)
}

fn kind_filters_allow_hit(hit: &CodeRetrievalHit, filters: &[String]) -> bool {
    filters.is_empty()
        || (hit.retrieval_layers.contains(&CodeRetrievalLayer::Symbol)
            && hit.symbol_snapshot_id.is_some()
            && hit.canonical_symbol_id.is_some()
            && hit.edge_kind.is_none())
}

fn path_substrings_allow_hit(path: &str, filters: &[String]) -> bool {
    filters.is_empty()
        || filters
            .iter()
            .any(|filter| contains_ignore_ascii_case(path, filter))
}

fn name_substrings_allow_hit(hit: &CodeRetrievalHit, filters: &[String]) -> bool {
    filters.is_empty()
        || filters.iter().any(|filter| {
            hit.canonical_symbol_id
                .as_deref()
                .is_some_and(|symbol_id| contains_ignore_ascii_case(symbol_id, filter))
                || sbom_dependency_name(hit)
                    .is_some_and(|package_name| contains_ignore_ascii_case(package_name, filter))
        })
}

fn sbom_dependency_name(hit: &CodeRetrievalHit) -> Option<&str> {
    if hit.retrieval_layers.contains(&CodeRetrievalLayer::Sbom)
        && hit.edge_kind.as_deref() == Some("dependency")
    {
        hit.edge_target_hint.as_deref()
    } else {
        None
    }
}

fn contains_ignore_ascii_case(haystack: &str, needle: &str) -> bool {
    haystack
        .to_ascii_lowercase()
        .contains(&needle.to_ascii_lowercase())
}

pub(in crate::storage::sqlite::code) fn mark_hits_degraded(
    hits: &mut [CodeRetrievalHit],
    reason: &str,
) {
    for hit in hits {
        if hit.degraded_reason.is_none() {
            hit.degraded_reason = Some(reason.to_owned());
        }
    }
}

fn merge_hit_provenance(target: &mut CodeRetrievalHit, source: &CodeRetrievalHit) {
    target.stale |= source.stale
        || source
            .staleness_hint
            .as_ref()
            .is_some_and(StalenessHint::requires_source_verification);
    for layer in &source.retrieval_layers {
        if !target.retrieval_layers.contains(layer) {
            target.retrieval_layers.push(*layer);
        }
    }
    for version in &source.index_versions {
        if !target.index_versions.contains(version) {
            target.index_versions.push(version.clone());
        }
    }
    if target.degraded_reason.is_none() {
        target.degraded_reason = source.degraded_reason.clone();
    }
    if target.symbol_snapshot_id.is_none() {
        target.symbol_snapshot_id = source.symbol_snapshot_id.clone();
    }
    if target.canonical_symbol_id.is_none() {
        target.canonical_symbol_id = source.canonical_symbol_id.clone();
    }
    if target.file_id.is_none() {
        target.file_id = source.file_id.clone();
    }
    if target.edge_kind.is_none() {
        target.edge_kind = source.edge_kind.clone();
        target.edge_resolution_state = source.edge_resolution_state.clone();
        target.edge_target_hint = source.edge_target_hint.clone();
        target.edge_confidence_basis_points = source.edge_confidence_basis_points;
        target.edge_confidence_tier = source.edge_confidence_tier.clone();
    }
    if let Some(source_hint) = &source.staleness_hint {
        if source_hint.should_replace(target.staleness_hint.as_ref()) {
            target.staleness_hint = source.staleness_hint.clone();
        }
    }
}

#[cfg(test)]
#[path = "mod_tests.rs"]
mod tests;