relay-knowledge 1.1.17

Graph-database-based knowledge graph project.
Documentation
use rusqlite::{Connection, Row, params};

use crate::{
    domain::{
        CodeExtractionMetadata, CodeReferenceFields, CodeReferenceKind, CodeReferenceRecord,
        CodeResolutionState,
    },
    storage::{CodeReferenceSearchRequest, StorageError},
};

use super::common::{
    RawRange, extraction, invalid_code_metadata, normalize_filter, parse_scope, validate_limit,
};

pub(in crate::storage::sqlite) fn search_references(
    connection: &mut Connection,
    request: CodeReferenceSearchRequest,
) -> Result<Vec<CodeReferenceRecord>, StorageError> {
    validate_limit("code reference search limit", request.limit)?;
    let scope = normalize_filter("source_scope", request.source_scope)?;
    let path = normalize_filter("code_path", request.path)?;
    let symbol_text = normalize_filter("symbol_text", request.symbol_text)?;
    let target_symbol_id = normalize_filter("target_symbol_id", request.target_symbol_id)?;
    let mut statement = connection.prepare(
        "
        SELECT source_scope, path, reference_id, symbol_text, kind, start_byte,
               end_byte, start_line, end_line, resolution_state, target_symbol_id,
               grammar_version, query_name, query_version, node_kind, capture_kind
        FROM code_references
        WHERE (?1 IS NULL OR source_scope = ?1)
          AND (?2 IS NULL OR path = ?2)
          AND (?3 IS NULL OR lower(symbol_text) LIKE '%' || lower(?3) || '%')
          AND (?4 IS NULL OR target_symbol_id = ?4)
          AND created_graph_version <= ?5
        ORDER BY created_graph_version DESC, source_scope ASC, path ASC,
                 start_line ASC, reference_id ASC
        LIMIT ?6
        ",
    )?;
    let rows = statement.query_map(
        params![
            scope.as_deref(),
            path.as_deref(),
            symbol_text.as_deref(),
            target_symbol_id.as_deref(),
            request.graph_version.get(),
            request.limit
        ],
        row_to_reference,
    )?;

    rows.collect::<Result<Vec<_>, _>>()
        .map_err(StorageError::from)?
        .into_iter()
        .map(RawReference::into_record)
        .collect()
}

struct RawReference {
    source_scope: String,
    path: String,
    reference_id: String,
    symbol_text: String,
    kind: String,
    range: RawRange,
    resolution_state: String,
    target_symbol_id: Option<String>,
    extraction: CodeExtractionMetadata,
}

impl RawReference {
    fn into_record(self) -> Result<CodeReferenceRecord, StorageError> {
        CodeReferenceRecord::new(CodeReferenceFields {
            reference_id: self.reference_id,
            source_scope: parse_scope(self.source_scope)?,
            path: self.path,
            symbol_text: self.symbol_text,
            kind: parse_reference_kind(&self.kind)?,
            range: self.range.into_range()?,
            resolution_state: parse_resolution_state(&self.resolution_state)?,
            target_symbol_id: self.target_symbol_id,
            extraction: self.extraction,
        })
        .map_err(|error| StorageError::InvalidInput(error.to_string()))
    }
}

fn row_to_reference(row: &Row<'_>) -> rusqlite::Result<RawReference> {
    Ok(RawReference {
        source_scope: row.get(0)?,
        path: row.get(1)?,
        reference_id: row.get(2)?,
        symbol_text: row.get(3)?,
        kind: row.get(4)?,
        range: RawRange::from_row(row, 5)?,
        resolution_state: row.get(9)?,
        target_symbol_id: row.get(10)?,
        extraction: extraction(
            row.get(11)?,
            row.get(12)?,
            row.get(13)?,
            row.get(14)?,
            row.get(15)?,
        ),
    })
}

fn parse_reference_kind(value: &str) -> Result<CodeReferenceKind, StorageError> {
    match value {
        "call" => Ok(CodeReferenceKind::Call),
        "type" => Ok(CodeReferenceKind::Type),
        "import" => Ok(CodeReferenceKind::Import),
        "implementation" => Ok(CodeReferenceKind::Implementation),
        _ => Err(invalid_code_metadata(format!(
            "unknown code reference kind '{value}'"
        ))),
    }
}

fn parse_resolution_state(value: &str) -> Result<CodeResolutionState, StorageError> {
    match value {
        "unresolved" => Ok(CodeResolutionState::Unresolved),
        "ambiguous" => Ok(CodeResolutionState::Ambiguous),
        "resolved" => Ok(CodeResolutionState::Resolved),
        _ => Err(invalid_code_metadata(format!(
            "unknown code resolution state '{value}'"
        ))),
    }
}

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