Skip to main content

chronon_backend_postgres/
lib.rs

1//! `PostgreSQL` [`SchedulerStore`](chronon_core::store::SchedulerStore) for Chronon.
2//!
3//! **Audience:** backend engineers deploying shared durable storage for production
4//! coordinator–worker clusters.
5//!
6//! ## Stack position
7//!
8//! ```text
9//! chronon (facade, `postgres` feature) → chronon-backend-postgres → chronon-backend-sql-common → chronon-core
10//! ```
11//!
12//! ## Entry points
13//!
14//! - [`PostgresSchedulerStore::connect`] — open a pool and bootstrap schema
15//! - [`PostgresSchedulerStore::connect_isolated`] — isolated schema for parallel tests
16//! - [`postgres_test_url`] — resolve test URL from `CHRONON_POSTGRES_URL` / `CHRONON_TEST_POSTGRES_URL`
17//!
18//! ## Example
19//!
20//! ```rust,no_run
21//! use chronon_backend_postgres::PostgresSchedulerStore;
22//!
23//! # async fn example() -> chronon_core::Result<()> {
24//! let store = PostgresSchedulerStore::connect(
25//!     "postgres://user:pass@localhost/chronon",
26//! )
27//! .await?;
28//! # Ok(())
29//! # }
30//! ```
31
32mod bootstrap;
33
34use chronon_backend_sql_common::SqlSchedulerStore;
35use chronon_core::Result;
36use sqlx::PgPool;
37
38pub use bootstrap::{postgres_store_from_env, postgres_test_url};
39
40/// PostgreSQL-backed scheduler store.
41pub struct PostgresSchedulerStore {
42    inner: SqlSchedulerStore,
43}
44
45impl PostgresSchedulerStore {
46    /// Connect using a `PostgreSQL` connection URL.
47    ///
48    /// # Examples
49    ///
50    /// ```rust,no_run
51    /// use chronon_backend_postgres::PostgresSchedulerStore;
52    ///
53    /// # async fn example() -> chronon_core::Result<()> {
54    /// let store = PostgresSchedulerStore::connect(
55    ///     "postgres://user:pass@localhost/chronon",
56    /// )
57    /// .await?;
58    /// # Ok(())
59    /// # }
60    /// ```
61    ///
62    /// # Errors
63    ///
64    /// Returns a storage error when the pool cannot connect or schema bootstrap fails.
65    pub async fn connect(url: &str) -> Result<Self> {
66        let inner = SqlSchedulerStore::connect_postgres(url).await?;
67        Ok(Self { inner })
68    }
69
70    /// Connect with an isolated schema (for parallel tests).
71    ///
72    /// # Examples
73    ///
74    /// ```rust,no_run
75    /// use chronon_backend_postgres::PostgresSchedulerStore;
76    ///
77    /// # async fn example() -> chronon_core::Result<()> {
78    /// let store = PostgresSchedulerStore::connect_isolated(
79    ///     "postgres://user:pass@localhost/chronon",
80    ///     "chronon_test_schema",
81    /// )
82    /// .await?;
83    /// # Ok(())
84    /// # }
85    /// ```
86    ///
87    /// # Errors
88    ///
89    /// Returns a storage error when schema creation, pool connect, or bootstrap fails.
90    pub async fn connect_isolated(url: &str, schema: &str) -> Result<Self> {
91        let inner = SqlSchedulerStore::connect_postgres_isolated(url, schema).await?;
92        Ok(Self { inner })
93    }
94
95    /// Attach to an existing isolated schema (no DDL bootstrap; for multi-process workers).
96    ///
97    /// # Errors
98    ///
99    /// Returns a storage error when the pool cannot be opened.
100    pub async fn attach_isolated(url: &str, schema: &str) -> Result<Self> {
101        let inner = SqlSchedulerStore::attach_postgres_isolated(url, schema).await?;
102        Ok(Self { inner })
103    }
104
105    /// Drop an isolated bench/test schema (multibench cell reset).
106    ///
107    /// # Errors
108    ///
109    /// Returns a storage error when the admin connection or DDL fails.
110    pub async fn drop_isolated_schema(url: &str, schema: &str) -> Result<()> {
111        SqlSchedulerStore::drop_postgres_schema(url, schema).await
112    }
113
114    /// Wrap an existing pool (schema bootstrap runs).
115    ///
116    /// # Examples
117    ///
118    /// ```rust,no_run
119    /// use chronon_backend_postgres::PostgresSchedulerStore;
120    /// use sqlx::postgres::PgPoolOptions;
121    ///
122    /// # async fn example() -> chronon_core::Result<()> {
123    /// let pool = PgPoolOptions::new()
124    ///     .connect("postgres://localhost/chronon")
125    ///     .await
126    ///     .expect("pool");
127    /// let store = PostgresSchedulerStore::from_pool(pool).await?;
128    /// # Ok(())
129    /// # }
130    /// ```
131    ///
132    /// # Errors
133    ///
134    /// Returns a storage error when schema bootstrap fails.
135    pub async fn from_pool(pool: PgPool) -> Result<Self> {
136        let inner = SqlSchedulerStore::from_postgres_pool(pool).await?;
137        Ok(Self { inner })
138    }
139
140    /// Underlying connection pool.
141    ///
142    /// # Panics
143    ///
144    /// Panics if the inner pool is not `PostgreSQL` (internal invariant violation).
145    #[must_use]
146    pub fn pool(&self) -> &PgPool {
147        match self.inner.pool() {
148            chronon_backend_sql_common::SqlPool::Postgres(pool) => pool,
149            chronon_backend_sql_common::SqlPool::Sqlite(_) => {
150                panic!("postgres backend has non-postgres pool")
151            }
152        }
153    }
154}
155
156impl std::fmt::Debug for PostgresSchedulerStore {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        f.debug_struct("PostgresSchedulerStore")
159            .finish_non_exhaustive()
160    }
161}
162
163chronon_backend_sql_common::delegate_scheduler_store!(PostgresSchedulerStore, inner);