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);