relay-knowledge 1.1.17

Graph-database-based knowledge graph project.
Documentation
use rusqlite::Connection;

use super::*;

const PATH_TABLES: &[&str] = &[
    "code_repository_file_diagnostics",
    "code_repository_chunks",
    "code_repository_calls",
    "code_repository_routes",
    "code_repository_feature_flags",
    "code_repository_framework_edges",
    "code_repository_framework_nodes",
    "code_repository_dependencies",
    "code_repository_imports",
    "code_repository_reference_search_groups",
    "code_repository_references",
    "code_repository_symbols",
    "code_repository_files",
];

const SCOPE_TABLES: &[&str] = &[
    "code_repository_path_tombstones",
    "code_repository_file_diagnostics",
    "code_repository_chunks",
    "code_repository_calls",
    "code_repository_routes",
    "code_repository_feature_flags",
    "code_repository_framework_edges",
    "code_repository_framework_nodes",
    "code_repository_dependencies",
    "code_repository_imports",
    "code_repository_reference_search_groups",
    "code_repository_reference_search_manifests",
    "code_repository_references",
    "code_repository_symbols",
    "code_repository_files",
    "software_components",
    "software_dependency_usages",
    "software_sdk_usages",
    "software_files",
    "software_topics",
    "software_relationships",
    "software_global_status",
    "software_build_targets",
    "software_iac_resources",
    "software_design_elements",
    "software_entities",
    "software_statements",
    "software_ontology_diagnostics",
    "business_domains",
    "business_terms",
    "business_term_aliases",
    "business_mappings",
    "business_knowledge_status",
];

#[test]
fn delete_scope_index_removes_software_projection_tables() {
    let mut connection = Connection::open_in_memory().expect("connection should open");
    for table in SCOPE_TABLES {
        connection
            .execute(
                &format!("CREATE TABLE {table} (source_scope TEXT NOT NULL)"),
                [],
            )
            .expect("table should create");
        connection
            .execute(
                &format!("INSERT INTO {table} (source_scope) VALUES ('scope'), ('other')"),
                [],
            )
            .expect("rows should insert");
    }
    connection
        .execute(
            "
                CREATE VIRTUAL TABLE code_repository_search USING fts5(
                    source_scope UNINDEXED,
                    document_kind UNINDEXED,
                    record_id UNINDEXED,
                    path UNINDEXED,
                    language_id UNINDEXED,
                    content
                )
                ",
            [],
        )
        .expect("search table should create");
    create_search_metadata_table(&connection);
    connection
        .execute(
            "
                INSERT INTO code_repository_search (
                    source_scope, document_kind, record_id, path, language_id, content
                )
                VALUES ('scope', 'symbol', 'a', 'src/a.rs', 'rust', 'target'),
                       ('other', 'symbol', 'b', 'src/b.rs', 'rust', 'target')
                ",
            [],
        )
        .expect("search rows should insert");
    backfill_search_metadata(&connection);

    let transaction = connection.transaction().expect("transaction should open");
    delete_scope_index(&transaction, "scope").expect("scope should delete");
    transaction.commit().expect("transaction should commit");

    for table in SCOPE_TABLES
        .iter()
        .copied()
        .chain(["code_repository_search"])
    {
        let deleted_remaining = connection
            .query_row(
                &format!("SELECT COUNT(*) FROM {table} WHERE source_scope = 'scope'"),
                [],
                |row| row.get::<_, usize>(0),
            )
            .expect("deleted row count should load");
        let retained_remaining = connection
            .query_row(
                &format!("SELECT COUNT(*) FROM {table} WHERE source_scope = 'other'"),
                [],
                |row| row.get::<_, usize>(0),
            )
            .expect("retained row count should load");
        assert_eq!(deleted_remaining, 0, "{table} should delete pruned scope");
        assert_eq!(retained_remaining, 1, "{table} should keep other scope");
    }
}

#[test]
fn delete_path_indexes_removes_multiple_paths_from_all_path_tables() {
    let mut connection = Connection::open_in_memory().expect("connection should open");
    for table in PATH_TABLES {
        connection
            .execute(
                &format!("CREATE TABLE {table} (source_scope TEXT NOT NULL, path TEXT NOT NULL)"),
                [],
            )
            .expect("table should create");
    }
    connection
        .execute(
            "
                CREATE VIRTUAL TABLE code_repository_search USING fts5(
                    source_scope UNINDEXED,
                    document_kind UNINDEXED,
                    record_id UNINDEXED,
                    path UNINDEXED,
                    language_id UNINDEXED,
                    content
                )
                ",
            [],
        )
        .expect("search table should create");
    create_search_metadata_table(&connection);
    create_reference_search_manifest_table(&connection);

    for path in ["src/a.rs", "src/b.rs", "src/c.rs"] {
        for table in PATH_TABLES {
            connection
                .execute(
                    &format!("INSERT INTO {table} (source_scope, path) VALUES (?1, ?2)"),
                    rusqlite::params!["scope", path],
                )
                .expect("path row should insert");
        }
        connection
            .execute(
                "
                    INSERT INTO code_repository_search (
                        source_scope, document_kind, record_id, path, language_id, content
                    )
                    VALUES (?1, 'symbol', ?2, ?2, 'rust', 'target')
                    ",
                rusqlite::params!["scope", path],
            )
            .expect("search row should insert");
    }
    backfill_search_metadata(&connection);
    connection
        .execute(
            "INSERT INTO code_repository_reference_search_manifests (
                 source_scope, reference_count, group_count
             ) VALUES ('scope', 3, 3)",
            [],
        )
        .expect("reference-search manifest should insert");

    let transaction = connection.transaction().expect("transaction should open");
    assert!(
        path_indexes_exist(&transaction, "scope", ["src/a.rs", "src/b.rs"])
            .expect("path existence should load")
    );
    assert!(
        !path_indexes_exist(&transaction, "scope", ["src/missing.rs"])
            .expect("missing path existence should load")
    );
    delete_path_indexes(&transaction, "scope", ["src/a.rs", "src/b.rs", "src/a.rs"])
        .expect("paths should delete");
    transaction.commit().expect("transaction should commit");

    for table in PATH_TABLES
        .iter()
        .copied()
        .chain(["code_repository_search"])
    {
        let remaining = connection
            .query_row(
                &format!("SELECT COUNT(*) FROM {table} WHERE source_scope = 'scope'"),
                [],
                |row| row.get::<_, usize>(0),
            )
            .expect("remaining row count should load");
        assert_eq!(remaining, 1, "{table} should keep only the unmatched path");
    }
    assert_eq!(
        connection
            .query_row(
                "SELECT reference_count, group_count
                 FROM code_repository_reference_search_manifests
                 WHERE source_scope = 'scope'",
                [],
                |row| Ok((row.get::<_, usize>(0)?, row.get::<_, usize>(1)?)),
            )
            .expect("reference-search manifest should load"),
        (1, 1)
    );
}

fn create_search_metadata_table(connection: &Connection) {
    connection
        .execute(
            "
                CREATE TABLE code_repository_search_metadata (
                    source_scope TEXT NOT NULL,
                    document_kind TEXT NOT NULL,
                    record_id TEXT NOT NULL,
                    path TEXT NOT NULL,
                    search_rowid INTEGER NOT NULL UNIQUE,
                    PRIMARY KEY (source_scope, document_kind, record_id)
                )
                ",
            [],
        )
        .expect("search metadata table should create");
}

fn create_reference_search_manifest_table(connection: &Connection) {
    connection
        .execute(
            "CREATE TABLE code_repository_reference_search_manifests (
                 source_scope TEXT PRIMARY KEY,
                 reference_count INTEGER NOT NULL,
                 group_count INTEGER NOT NULL
             )",
            [],
        )
        .expect("reference-search manifest table should create");
}

fn backfill_search_metadata(connection: &Connection) {
    connection
        .execute(
            "
                INSERT INTO code_repository_search_metadata (
                    source_scope, document_kind, record_id, path, search_rowid
                )
                SELECT source_scope, document_kind, record_id, path, rowid
                FROM code_repository_search
                ",
            [],
        )
        .expect("search metadata rows should insert");
}