crtx-store 0.1.0

SQLite persistence: migrations, repositories, transactions.
Documentation
//! Trace repository operations.

use chrono::{DateTime, Utc};
use cortex_core::{EventId, Trace, TraceId, TraceStatus};
use rusqlite::params;

use crate::{Pool, StoreError, StoreResult};

/// Repository for trace lifecycle and event membership rows.
#[derive(Debug)]
pub struct TraceRepo<'a> {
    pool: &'a Pool,
}

impl<'a> TraceRepo<'a> {
    /// Creates a trace repository over an open SQLite connection.
    #[must_use]
    pub const fn new(pool: &'a Pool) -> Self {
        Self { pool }
    }

    /// Opens a trace row.
    pub fn open(&self, trace: &Trace) -> StoreResult<()> {
        self.pool.execute(
            "INSERT INTO traces (
                id, schema_version, opened_at, closed_at, trace_type, status
             ) VALUES (?1, ?2, ?3, ?4, ?5, ?6);",
            params![
                trace.id.to_string(),
                i64::from(trace.schema_version),
                trace.opened_at.to_rfc3339(),
                trace.closed_at.map(|at| at.to_rfc3339()),
                trace.trace_type,
                trace_status_wire(trace.status),
            ],
        )?;

        Ok(())
    }

    /// Attaches an event to a trace at a zero-based ordinal.
    pub fn attach(&self, trace_id: &TraceId, event_id: &EventId, ordinal: i64) -> StoreResult<()> {
        self.pool.execute(
            "INSERT INTO trace_events (trace_id, event_id, ordinal)
             VALUES (?1, ?2, ?3);",
            params![trace_id.to_string(), event_id.to_string(), ordinal],
        )?;

        Ok(())
    }

    /// Closes or quarantines a trace.
    pub fn close(
        &self,
        trace_id: &TraceId,
        closed_at: DateTime<Utc>,
        status: TraceStatus,
    ) -> StoreResult<()> {
        if status == TraceStatus::Open {
            return Err(StoreError::Validation(
                "close requires a non-open trace status".into(),
            ));
        }

        let changed = self.pool.execute(
            "UPDATE traces
             SET closed_at = ?2, status = ?3
             WHERE id = ?1;",
            params![
                trace_id.to_string(),
                closed_at.to_rfc3339(),
                trace_status_wire(status)
            ],
        )?;

        if changed == 0 {
            return Err(StoreError::Validation(format!(
                "trace {trace_id} not found"
            )));
        }

        Ok(())
    }
}

fn trace_status_wire(status: TraceStatus) -> &'static str {
    match status {
        TraceStatus::Open => "open",
        TraceStatus::Closed => "closed",
        TraceStatus::Quarantined => "quarantined",
    }
}