ochna 0.3.1

A structural code graph indexing and analysis CLI using Tree-sitter and SQLite
//! Raw (unresolved) call-site storage and lookups used by edge resolution.

use super::{map_row_to_raw_call, RawCall};
use rusqlite::Connection;

/// Insert a raw call into the raw_calls table.
pub fn insert_raw_call(conn: &Connection, r: &RawCall) -> rusqlite::Result<()> {
    let mut stmt = conn.prepare_cached(
        "INSERT INTO raw_calls (
            caller_nid, callee_name, callee_simple, callee_scope,
            call_kind, receiver_expr, receiver_type, package_or_namespace, import_hint,
            relationship_kind, reverse_edge, target_qualified_hint, resolution_hint, line, column
         ) SELECT nid, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15 FROM nodes WHERE id = ?1",
    )?;
    stmt.execute((
        &r.caller_id,
        &r.callee_name,
        &r.callee_simple,
        &r.callee_scope,
        &r.call_kind,
        &r.receiver_expr,
        &r.receiver_type,
        &r.package_or_namespace,
        &r.import_hint,
        &r.relationship_kind,
        i64::from(r.reverse_edge),
        &r.target_qualified_hint,
        r.resolution_hint,
        r.line,
        r.column,
    ))?;
    Ok(())
}

pub fn get_raw_call_source_ids_by_callee_simple(
    conn: &Connection,
    callee_simple: &str,
) -> rusqlite::Result<Vec<String>> {
    let mut stmt = conn.prepare(
        "SELECT DISTINCT n.id FROM raw_calls r
         JOIN nodes n ON n.nid = r.caller_nid
         WHERE r.callee_simple = ?",
    )?;
    let rows = stmt.query_map([callee_simple], |row| row.get(0))?;
    rows.collect()
}

pub fn get_raw_calls_for_source_id(
    conn: &Connection,
    source_id: &str,
) -> rusqlite::Result<Vec<RawCall>> {
    let mut stmt = conn.prepare(
        "SELECT n.id, r.callee_name, r.callee_simple, r.callee_scope,
                r.call_kind, r.receiver_expr, r.receiver_type, r.package_or_namespace, r.import_hint,
                r.relationship_kind, r.reverse_edge, r.target_qualified_hint, r.resolution_hint,
                r.line, r.column
         FROM raw_calls r
         JOIN nodes n ON n.nid = r.caller_nid
         WHERE n.id = ?",
    )?;
    let rows = stmt.query_map([source_id], map_row_to_raw_call)?;
    rows.collect()
}

/// Retrieve all raw calls from the database.
pub fn get_all_raw_calls(conn: &Connection) -> rusqlite::Result<Vec<RawCall>> {
    let mut stmt = conn.prepare(
        "SELECT n.id, r.callee_name, r.callee_simple, r.callee_scope,
                r.call_kind, r.receiver_expr, r.receiver_type, r.package_or_namespace, r.import_hint,
                r.relationship_kind, r.reverse_edge, r.target_qualified_hint, r.resolution_hint,
                r.line, r.column
         FROM raw_calls r
         JOIN nodes n ON n.nid = r.caller_nid",
    )?;
    let rows = stmt.query_map([], map_row_to_raw_call)?;
    let mut calls = Vec::new();
    for r in rows {
        calls.push(r?);
    }
    Ok(calls)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::db::{init_schema, upsert_node, Node};

    fn node(id: &str, name: &str) -> Node {
        Node {
            id: id.to_string(),
            name: name.to_string(),
            kind: "class".to_string(),
            qualified_name: Some(name.to_string()),
            file_path: "App.java".to_string(),
            start_line: 1,
            end_line: 1,
            start_column: 0,
            end_column: 1,
            signature: None,
            doc_comment: None,
            is_test: false,
            resolution_kind: None,
            confidence: None,
        }
    }

    #[test]
    fn relationship_metadata_survives_raw_call_round_trip() {
        let conn = Connection::open_in_memory().unwrap();
        init_schema(&conn).unwrap();
        upsert_node(&conn, &node("App.java::Consumer", "Consumer")).unwrap();
        let mut relation = RawCall::new(
            "App.java::Consumer".to_string(),
            "Dependency".to_string(),
            4,
            2,
        );
        relation.relationship_kind = "injected_into".to_string();
        relation.reverse_edge = true;
        relation.target_qualified_hint = Some("pkg::Dependency".to_string());
        relation.resolution_hint = Some(6);
        insert_raw_call(&conn, &relation).unwrap();
        assert_eq!(get_all_raw_calls(&conn).unwrap(), vec![relation]);
    }
}