selene-db-gql 1.3.0

ISO/IEC 39075:2024 GQL parser, planner, optimizer, and executor for selene-db.
Documentation
use std::time::Instant;

use selene_core::metrics;

use crate::{
    PlannedCall, ProcedureError, ProcedureMutability, ProcedureTier,
    runtime::{
        ExecutorError, GraphContext, MaintenanceContext, MutationContext, ProcedureContext,
        TxContext,
    },
};

pub(super) fn validate_call_tier(call: &PlannedCall) -> Result<(), ExecutorError> {
    let expected = tier_for_mutability(call.mutability);
    if call.tier != expected {
        return Err(procedure_error(
            ProcedureError::TierMismatch {
                expected,
                actual: call.tier,
            },
            call.span,
            None,
        ));
    }
    Ok(())
}

pub(super) fn build<'borrow, 'ctx, 'g>(
    call: &PlannedCall,
    ctx: &'borrow mut TxContext<'ctx, 'g>,
) -> Result<ProcedureContext<'borrow, 'g>, ExecutorError>
where
    'ctx: 'borrow,
{
    match call.tier {
        ProcedureTier::Graph => Ok(ProcedureContext::Graph(GraphContext::new(
            ctx.snapshot(),
            ctx.impl_defined_caps(),
            ctx.providers(),
            ctx.cancellation_checker(),
            ctx.binding_table_registry(),
        ))),
        ProcedureTier::Mutation => {
            let caps = ctx.impl_defined_caps();
            let cancellation = ctx.cancellation_checker();
            let binding_tables = ctx.binding_table_registry();
            let mutator = ctx.mutator_with_span(
                "mutation-tier procedure requires a write transaction",
                call.span,
            )?;
            Ok(ProcedureContext::Mutation(MutationContext::new(
                mutator,
                caps,
                cancellation,
                binding_tables,
            )))
        }
        ProcedureTier::Maintenance => {
            let graph = ctx.maintenance_graph_with_span(
                "maintenance-tier procedure requires a maintenance statement context",
                call.span,
            )?;
            Ok(ProcedureContext::Maintenance(MaintenanceContext::new(
                graph,
                ctx.impl_defined_caps(),
                ctx.cancellation_checker(),
                ctx.binding_table_registry(),
            )))
        }
    }
}

pub(super) fn build_read_only<'borrow, 'ctx, 'g>(
    call: &PlannedCall,
    ctx: &'borrow TxContext<'ctx, 'g>,
) -> Result<ProcedureContext<'borrow, 'g>, ExecutorError>
where
    'ctx: 'borrow,
{
    match call.tier {
        ProcedureTier::Graph => Ok(ProcedureContext::Graph(GraphContext::new(
            ctx.snapshot(),
            ctx.impl_defined_caps(),
            ctx.providers(),
            ctx.cancellation_checker(),
            ctx.binding_table_registry(),
        ))),
        ProcedureTier::Mutation => Err(ExecutorError::InvalidTransactionState {
            detail: "mutation-tier procedure requires a write transaction",
            span: call.span,
        }),
        ProcedureTier::Maintenance => {
            let graph = ctx.maintenance_graph_with_span(
                "maintenance-tier procedure requires a maintenance statement context",
                call.span,
            )?;
            Ok(ProcedureContext::Maintenance(MaintenanceContext::new(
                graph,
                ctx.impl_defined_caps(),
                ctx.cancellation_checker(),
                ctx.binding_table_registry(),
            )))
        }
    }
}

pub(super) const fn tier_for_mutability(mutability: ProcedureMutability) -> ProcedureTier {
    match mutability {
        ProcedureMutability::Read => ProcedureTier::Graph,
        ProcedureMutability::SchemaWrite => ProcedureTier::Mutation,
        ProcedureMutability::MaintenanceWrite => ProcedureTier::Maintenance,
    }
}

pub(super) fn procedure_error(
    source: ProcedureError,
    span: crate::SourceSpan,
    deadline: Option<Instant>,
) -> ExecutorError {
    match source {
        ProcedureError::Cancelled => {
            metrics::counter_inc(metrics::CANCELLATIONS_TOTAL);
            ExecutorError::Cancelled { span }
        }
        ProcedureError::Timeout { elapsed } => {
            metrics::counter_inc(metrics::CANCELLATIONS_TOTAL);
            ExecutorError::Timeout {
                deadline: deadline.unwrap_or_else(Instant::now),
                elapsed,
                span,
            }
        }
        ProcedureError::NodeScanBudgetExceeded { .. } => ExecutorError::ProgramLimitExceeded {
            detail: "node scan budget exceeded",
            span,
        },
        source => ExecutorError::Procedure { source, span },
    }
}