relay-knowledge 1.1.17

Graph-database-based knowledge graph project.
Documentation
use std::{thread, time::Duration};

use rusqlite::Connection;

use crate::storage::StorageError;

const GRAPH_RETRIEVAL_SCHEMA_RETRY_DELAYS_MS: [u64; 4] = [10, 30, 90, 270];
pub(super) const GRAPH_BM25_REBUILD_TABLE: &str = "graph_bm25_rebuild";

pub(super) fn execute_retrieval_schema(connection: &Connection) -> Result<(), StorageError> {
    for delay_ms in GRAPH_RETRIEVAL_SCHEMA_RETRY_DELAYS_MS {
        match connection.execute_batch(RETRIEVAL_SCHEMA_SQL) {
            Ok(()) => return Ok(()),
            Err(error) if graph_retrieval_schema_error_is_retryable(&error) => {
                thread::sleep(Duration::from_millis(delay_ms));
            }
            Err(error) => return Err(StorageError::from(error)),
        }
    }

    connection
        .execute_batch(RETRIEVAL_SCHEMA_SQL)
        .map_err(StorageError::from)
}

pub(super) fn prepare_bm25_rebuild_table(connection: &Connection) -> Result<(), StorageError> {
    connection.execute("DROP TABLE IF EXISTS graph_bm25_retired", [])?;
    connection.execute("DROP TABLE IF EXISTS graph_bm25_rebuild", [])?;
    connection.execute_batch(GRAPH_BM25_REBUILD_SCHEMA_SQL)?;
    Ok(())
}

pub(super) fn activate_bm25_rebuild_table(connection: &Connection) -> Result<(), StorageError> {
    connection.execute("ALTER TABLE graph_bm25 RENAME TO graph_bm25_retired", [])?;
    connection.execute("ALTER TABLE graph_bm25_rebuild RENAME TO graph_bm25", [])?;
    Ok(())
}

pub(super) fn drop_retired_bm25_table(connection: &Connection) -> Result<(), StorageError> {
    connection.execute("DROP TABLE IF EXISTS graph_bm25_retired", [])?;
    Ok(())
}

fn graph_retrieval_schema_error_is_retryable(error: &rusqlite::Error) -> bool {
    graph_retrieval_schema_error_message_is_retryable(&error.to_string())
}

fn graph_retrieval_schema_error_message_is_retryable(message: &str) -> bool {
    graph_bm25_transient_error_message(message)
}

pub(in crate::storage::sqlite::retrieval) fn graph_bm25_transient_error_message(
    message: &str,
) -> bool {
    message.contains("vtable constructor failed: graph_bm25")
        || message.contains("database schema is locked")
        || message.contains("database table is locked")
        || message.contains("database is locked")
}

const RETRIEVAL_SCHEMA_SQL: &str = "
    CREATE VIRTUAL TABLE IF NOT EXISTS graph_bm25 USING fts5(
        document_id UNINDEXED,
        document_kind UNINDEXED,
        evidence_id UNINDEXED,
        parent_evidence_id UNINDEXED,
        modality UNINDEXED,
        created_graph_version UNINDEXED,
        routing_key,
        source_scope,
        source_path,
        entity_labels,
        entity_aliases,
        content
    );

    CREATE TABLE IF NOT EXISTS graph_bm25_route_state (
        id INTEGER PRIMARY KEY CHECK (id = 1),
        indexed_graph_version INTEGER NOT NULL,
        document_count INTEGER NOT NULL,
        state TEXT NOT NULL,
        algorithm_version TEXT NOT NULL,
        semantic_generation TEXT NOT NULL,
        vector_generation TEXT NOT NULL,
        rebuild_phase TEXT,
        rebuild_cursor TEXT,
        rebuild_semantic INTEGER,
        rebuild_vector INTEGER,
        rebuild_owner TEXT,
        rebuild_lease_expires_at_ms INTEGER
    );

    INSERT OR IGNORE INTO graph_bm25_route_state (
        id, indexed_graph_version, document_count, state, algorithm_version,
        semantic_generation, vector_generation,
        rebuild_phase, rebuild_cursor, rebuild_semantic, rebuild_vector,
        rebuild_owner, rebuild_lease_expires_at_ms
    ) VALUES (
        1, 0, 0, 'fresh',
        'simhash10-topical4-indexed-scope64-partition-ascii-subset128b-256t-a1-docidlen1-v4',
        'unknown', 'unknown', NULL, NULL, NULL, NULL, NULL, NULL
    );

    CREATE TABLE IF NOT EXISTS graph_bm25_route_documents (
        document_id TEXT PRIMARY KEY,
        fts_rowid INTEGER NOT NULL UNIQUE,
        document_kind TEXT NOT NULL,
        created_graph_version INTEGER NOT NULL,
        source_scope TEXT NOT NULL,
        source_path TEXT,
        label_gram_state TEXT NOT NULL,
        group_token TEXT NOT NULL,
        term_counts_json TEXT NOT NULL
    );

    CREATE INDEX IF NOT EXISTS graph_bm25_route_documents_scope_path
    ON graph_bm25_route_documents(source_scope, source_path, document_id);

    CREATE INDEX IF NOT EXISTS graph_bm25_route_documents_label_state
    ON graph_bm25_route_documents(
        label_gram_state, source_scope, created_graph_version, document_id
    );

    CREATE INDEX IF NOT EXISTS graph_bm25_route_documents_global_label_state
    ON graph_bm25_route_documents(
        label_gram_state, created_graph_version, document_id
    );

    CREATE TABLE IF NOT EXISTS graph_bm25_route_groups (
        source_scope TEXT NOT NULL,
        group_token TEXT NOT NULL,
        document_count INTEGER NOT NULL,
        PRIMARY KEY (source_scope, group_token)
    );

    CREATE TABLE IF NOT EXISTS graph_bm25_route_terms (
        term TEXT NOT NULL,
        source_scope TEXT NOT NULL,
        group_token TEXT NOT NULL,
        collection_frequency INTEGER NOT NULL,
        PRIMARY KEY (term, source_scope, group_token)
    );

    CREATE TABLE IF NOT EXISTS graph_bm25_route_term_totals (
        term TEXT PRIMARY KEY,
        document_frequency INTEGER NOT NULL
    );

    CREATE TABLE IF NOT EXISTS graph_semantic_documents (
        document_id TEXT PRIMARY KEY,
        document_kind TEXT NOT NULL,
        evidence_id TEXT NOT NULL,
        parent_evidence_id TEXT,
        modality TEXT NOT NULL,
        created_graph_version INTEGER NOT NULL,
        source_scope TEXT NOT NULL,
        source_path TEXT,
        entity_labels_json TEXT NOT NULL,
        content TEXT NOT NULL,
        token_signature_json TEXT NOT NULL,
        model TEXT NOT NULL,
        dimension INTEGER NOT NULL,
        source_hash TEXT NOT NULL,
        tokenizer_version TEXT NOT NULL
    );

    CREATE TABLE IF NOT EXISTS graph_vector_documents (
        document_id TEXT PRIMARY KEY,
        document_kind TEXT NOT NULL,
        evidence_id TEXT NOT NULL,
        parent_evidence_id TEXT,
        modality TEXT NOT NULL,
        created_graph_version INTEGER NOT NULL,
        source_scope TEXT NOT NULL,
        source_path TEXT,
        entity_labels_json TEXT NOT NULL,
        content TEXT NOT NULL,
        vector_json TEXT NOT NULL,
        model TEXT NOT NULL,
        dimension INTEGER NOT NULL,
        source_hash TEXT NOT NULL,
        tokenizer_version TEXT NOT NULL
    );

    CREATE INDEX IF NOT EXISTS graph_semantic_documents_scope_version
    ON graph_semantic_documents(source_scope, created_graph_version DESC);
    CREATE INDEX IF NOT EXISTS graph_semantic_documents_version
    ON graph_semantic_documents(created_graph_version DESC, document_id);
    CREATE INDEX IF NOT EXISTS graph_vector_documents_scope_version
    ON graph_vector_documents(source_scope, created_graph_version DESC);
    ";

const GRAPH_BM25_REBUILD_SCHEMA_SQL: &str = "
    CREATE VIRTUAL TABLE graph_bm25_rebuild USING fts5(
        document_id UNINDEXED,
        document_kind UNINDEXED,
        evidence_id UNINDEXED,
        parent_evidence_id UNINDEXED,
        modality UNINDEXED,
        created_graph_version UNINDEXED,
        routing_key,
        source_scope,
        source_path,
        entity_labels,
        entity_aliases,
        content
    );
    ";

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