relay-knowledge 1.1.17

Graph-database-based knowledge graph project.
Documentation
use std::collections::BTreeSet;

use rusqlite::Connection;

use crate::{
    domain::{
        GraphVersion, SoftwareComponent, SoftwareDependencyUsage, SoftwareDependencyUsageInput,
    },
    storage::StorageError,
};

mod matching;
mod persistence;
mod python;
mod schema;

use matching::{
    DependencyMatchIndex, component_alias_keys, import_match_candidates_with_python_locals,
};
use persistence::import_evidence;
pub(super) use persistence::{delete_scope, insert_usages, usages_for_scope};
pub(super) use schema::initialize_schema;

const MAX_COMPONENT_ALIAS_EVIDENCE_PER_SCOPE: usize = 65_536;
const MAX_IMPORT_EVIDENCE_PER_SCOPE: usize = 131_072;
const MAX_PYTHON_FILES_PER_SCOPE: usize = 131_072;
const MAX_PYTHON_LOCAL_MODULES_PER_SCOPE: usize = 262_144;
const MAX_DEPENDENCY_USAGES_PER_SCOPE: usize = 131_072;
const MAX_MATCH_CANDIDATES_PER_IMPORT: usize = 128;

pub(super) fn derive_dependency_usages(
    connection: &Connection,
    source_scope: &str,
    graph_version: GraphVersion,
    components: &[SoftwareComponent],
) -> Result<Vec<SoftwareDependencyUsage>, StorageError> {
    let alias_keys = component_alias_keys(
        connection,
        source_scope,
        MAX_COMPONENT_ALIAS_EVIDENCE_PER_SCOPE,
    )?;
    let index = DependencyMatchIndex::new(components, &alias_keys);
    if index.is_empty() {
        return Ok(Vec::new());
    }

    let imports = import_evidence(connection, source_scope, MAX_IMPORT_EVIDENCE_PER_SCOPE)?;
    let python_local_modules = python::local_modules(
        connection,
        source_scope,
        MAX_PYTHON_FILES_PER_SCOPE,
        MAX_PYTHON_LOCAL_MODULES_PER_SCOPE,
    )?;
    let mut seen_usage_ids = BTreeSet::new();
    let mut usages = Vec::new();
    for import in imports {
        let candidates = import_match_candidates_with_python_locals(
            &import.language_id,
            &import.module,
            import.target_hint.as_deref(),
            &import.resolution_state,
            Some(&python_local_modules),
            MAX_MATCH_CANDIDATES_PER_IMPORT,
        )?;
        for candidate in candidates {
            let matches = index.matching_components(
                &import.language_id,
                &candidate.value,
                &import.evidence_path,
            );
            for component_match in matches {
                let confidence = import
                    .confidence_basis_points
                    .min(candidate.confidence_basis_points)
                    .min(component_match.confidence_basis_points);
                let component = component_match.component;
                let usage = SoftwareDependencyUsage::new(SoftwareDependencyUsageInput {
                    component_id: component.component_id.clone(),
                    repository_id: import.repository_id.clone(),
                    source_scope: import.source_scope.clone(),
                    ecosystem: component.ecosystem.clone(),
                    package_name: component.name.clone(),
                    language_id: import.language_id.clone(),
                    module: import.module.clone(),
                    target_hint: import.target_hint.clone(),
                    resolution_state: import.resolution_state.clone(),
                    evidence_path: import.evidence_path.clone(),
                    evidence_line_range: import.evidence_line_range.clone(),
                    confidence_basis_points: confidence,
                    created_graph_version: graph_version,
                })
                .map_err(|error| StorageError::InvalidInput(error.to_string()))?;
                insert_bounded_usage(
                    &mut usages,
                    &mut seen_usage_ids,
                    usage,
                    MAX_DEPENDENCY_USAGES_PER_SCOPE,
                )?;
            }
        }
    }

    usages.sort_by(|left, right| {
        left.ecosystem
            .cmp(&right.ecosystem)
            .then_with(|| left.package_name.cmp(&right.package_name))
            .then_with(|| left.evidence_path.cmp(&right.evidence_path))
            .then_with(|| {
                left.evidence_line_range
                    .start
                    .cmp(&right.evidence_line_range.start)
            })
    });
    Ok(usages)
}

fn insert_bounded_usage(
    usages: &mut Vec<SoftwareDependencyUsage>,
    seen_usage_ids: &mut BTreeSet<String>,
    usage: SoftwareDependencyUsage,
    limit: usize,
) -> Result<(), StorageError> {
    if !seen_usage_ids.insert(usage.usage_id.clone()) {
        return Ok(());
    }
    if usages.len() >= limit {
        return Err(StorageError::CapacityExceeded(format!(
            "software dependency usages exceed the bounded limit {limit}"
        )));
    }
    usages.push(usage);
    Ok(())
}

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