relay-knowledge 1.1.17

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

use crate::{
    domain::GraphVersion,
    storage::{IndexRefreshCompletion, IndexRefreshTask, IndexRefreshTaskState, StorageError},
};

use super::record::{inactive_lease_error, input_fingerprint, require_task};

pub(crate) fn complete_index_refresh_task(
    connection: &mut Connection,
    request: IndexRefreshCompletion,
) -> Result<IndexRefreshTask, StorageError> {
    let transaction = connection.transaction()?;
    let task = require_task(&transaction, &request.task_id)?;
    let superseded = task.target_graph_version > request.indexed_graph_version
        || has_matching_mutation_after(&transaction, &task, request.indexed_graph_version)?;
    let metadata = super::super::cursor_metadata::cursor_backend_metadata(
        &transaction,
        super::super::cursor_metadata::CursorBackendMetadataRequest {
            kind: task.kind,
            scope: &task.source_scope,
            modality: task.modality,
            cursor_before: task.cursor_before,
            graph_version: request.indexed_graph_version,
            model_name: request.model_name.as_deref(),
            model_dimension: request.model_dimension,
        },
    )?;
    let next_target = if superseded {
        super::super::current_graph_version(&transaction)?.max(task.target_graph_version)
    } else {
        task.target_graph_version
    };
    let next_state = if superseded {
        IndexRefreshTaskState::Queued
    } else {
        IndexRefreshTaskState::Succeeded
    };
    let next_cursor_before = if superseded {
        request.indexed_graph_version
    } else {
        task.cursor_before
    };
    let next_cursor_after = if superseded {
        None
    } else {
        Some(request.indexed_graph_version.get())
    };
    let next_attempt_count = if superseded { 0 } else { task.attempt_count };
    let next_fingerprint =
        input_fingerprint(task.kind, &task.source_scope, task.modality, next_target);
    let updated = transaction.execute(
        "
        UPDATE index_refresh_tasks
        SET state = ?5,
            target_graph_version = ?6,
            lease_owner = NULL,
            lease_expires_at_ms = NULL,
            next_retry_at_ms = ?7,
            input_fingerprint = ?8,
            cursor_before = ?9,
            cursor_after = ?10,
            attempt_count = ?11,
            last_error_kind = NULL,
            last_error_message = NULL,
            updated_at_ms = ?12
        WHERE task_id = ?1
          AND state = 'running'
          AND lease_owner = ?2
          AND attempt_count = ?3
          AND lease_expires_at_ms > ?4
        ",
        params![
            &request.task_id,
            &request.lease_owner,
            request.attempt_count,
            request.now_ms,
            next_state.as_str(),
            next_target.get(),
            request.now_ms,
            next_fingerprint,
            next_cursor_before.get(),
            next_cursor_after,
            next_attempt_count,
            request.now_ms
        ],
    )?;
    if updated != 1 {
        return Err(inactive_lease_error(&request.task_id));
    }
    if superseded {
        super::super::mark_cursor_stale_at(
            &transaction,
            task.kind,
            &task.source_scope,
            task.modality,
            request.indexed_graph_version,
            None,
            &metadata,
        )?;
    } else {
        super::super::mark_cursor_complete(
            &transaction,
            task.kind,
            &task.source_scope,
            task.modality,
            request.indexed_graph_version,
            None,
            &metadata,
        )?;
    }
    super::super::recompute_aggregate_status(&transaction, task.kind, GraphVersion::ZERO)?;
    transaction.commit()?;

    require_task(connection, &task.task_id)
}

fn has_matching_mutation_after(
    connection: &Connection,
    task: &IndexRefreshTask,
    graph_version: GraphVersion,
) -> Result<bool, StorageError> {
    let mut statement = connection.prepare(
        "
        SELECT affected_scopes_json
        FROM graph_mutations
        WHERE graph_version > ?1
        ORDER BY graph_version ASC
        ",
    )?;
    let mut rows = statement.query(params![graph_version.get()])?;

    while let Some(row) = rows.next()? {
        if task.source_scope == super::super::DEFAULT_SCOPE {
            return Ok(true);
        }
        let scopes_json = row.get::<_, String>(0)?;
        let scopes = super::super::parse_json_array(scopes_json)?;
        if scopes.iter().any(|scope| scope == &task.source_scope) {
            return Ok(true);
        }
    }

    Ok(false)
}

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