Skip to main content

runledger_postgres/
lib.rs

1//! PostgreSQL persistence layer for Runledger durable execution.
2//!
3//! This crate owns the SQLx-backed storage and query helpers used by the
4//! runtime and application crates. The main entrypoint is the [`jobs`] module,
5//! which exposes APIs for:
6//! - queueing, claiming, heartbeating, and completing jobs
7//! - listing admin/job log data and runtime configuration
8//! - enqueueing and querying workflow runs and steps
9//! - applying or validating the bundled Runledger schema migrations
10//!
11//! Typical consumers share a [`DbPool`] with `runledger-runtime`, then call the
12//! exported [`jobs`] functions from application setup, admin APIs, or tests.
13//!
14//! For simple embedding, call [`migrate`] during startup:
15//!
16//! ```rust,no_run
17//! # async fn demo() -> Result<(), Box<dyn std::error::Error>> {
18//! let pool = sqlx::PgPool::connect("postgres://localhost/runledger").await?;
19//! runledger_postgres::migrate(&pool).await?;
20//! # Ok(())
21//! # }
22//! ```
23//!
24//! For deployments that manage DDL elsewhere, call
25//! [`ensure_schema_compatible`] instead to fail fast if the schema is missing
26//! or drifted. That check is read-only, but it expects the database to retain
27//! SQLx migration history in `_sqlx_migrations` and, when available,
28//! Runledger-owned migration state in `runledger_migration_history`.
29
30use std::fmt;
31
32mod error;
33pub mod jobs;
34mod migrations;
35
36pub use error::{
37    FrameworkConstraintSpec, QueryError, QueryErrorCategory, classify_framework_constraint,
38    classify_query_error, classify_query_error_with_constraint_classifier,
39    has_framework_constraint_classifier,
40};
41pub use migrations::{MIGRATOR, SchemaCompatibilityError, ensure_schema_compatible, migrate};
42
43pub type DbPool = sqlx::PgPool;
44pub type DbTx<'a> = sqlx::Transaction<'a, sqlx::Postgres>;
45pub type Result<T> = std::result::Result<T, Error>;
46
47#[derive(Debug)]
48pub enum Error {
49    ConfigError(String),
50    ConnectionError(String),
51    MigrationError(String),
52    QueryError(QueryError),
53}
54
55impl fmt::Display for Error {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        match self {
58            Self::ConfigError(message) => write!(f, "{message}"),
59            Self::ConnectionError(message) => write!(f, "{message}"),
60            Self::MigrationError(message) => write!(f, "{message}"),
61            Self::QueryError(query_error) => write!(f, "{query_error}"),
62        }
63    }
64}
65
66impl std::error::Error for Error {
67    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
68        match self {
69            Self::QueryError(query_error) => Some(query_error),
70            Self::ConfigError(_) | Self::ConnectionError(_) | Self::MigrationError(_) => None,
71        }
72    }
73}
74
75impl Error {
76    #[must_use]
77    pub fn from_query_sqlx(error: sqlx::Error) -> Self {
78        Self::QueryError(QueryError::from_sqlx(error, None))
79    }
80
81    #[must_use]
82    pub fn from_query_sqlx_with_context(context: &str, error: sqlx::Error) -> Self {
83        Self::QueryError(QueryError::from_sqlx(error, Some(context)))
84    }
85}