Skip to main content

chronon_backend_sqlite/
lib.rs

1//! `SQLite` [`SchedulerStore`](chronon_core::store::SchedulerStore) for Chronon.
2//!
3//! **When to use:** durable single-host **Mode 1**, or same-host **Mode 2** when coordinator and
4//! worker open the **same database file** (SQLite allows one writer at a time — prefer Postgres
5//! ± Redis for multi-worker fleets).
6//!
7//! Getting started:
8//! [Mode 1](https://docs.rs/uf-chronon/latest/chronon/index.html#mode-1--embedded-one-binary) /
9//! [Mode 2](https://docs.rs/uf-chronon/latest/chronon/index.html#mode-2--coordinator--worker-two-binaries).
10//!
11//! ## Stack position
12//!
13//! ```text
14//! chronon (facade, `sqlite` feature) → chronon-backend-sqlite → chronon-backend-sql-common → chronon-core
15//! ```
16//!
17//! ## Entry points
18//!
19//! - [`SqliteSchedulerStore::new`] — open a database file
20//! - [`SqliteSchedulerStore::connect`] — connect via URL (including `:memory:`)
21//!
22//! ## Mode 1 — Embedded
23//!
24//! ```ignore
25//! use std::sync::Arc;
26//! use chronon::prelude::*;
27//! use chronon::SqliteSchedulerStore;
28//!
29//! let store = SqliteSchedulerStore::connect("sqlite:///var/lib/chronon/chronon.db").await?;
30//! let chronon = ChrononBuilder::new()
31//!     .scheduler_store(Arc::new(store))
32//!     .context_factory(Arc::new(JsonScriptContextFactory))
33//!     .embedded()
34//!     .auto_registry()
35//!     .build()?;
36//! ```
37//!
38//! Runnable: `cargo run -p uf-chronon --example sqlite_boot --features sqlite`
39//!
40//! ## Mode 2 — Coordinator binary
41//!
42//! Shared file path with the worker. Tick only — no script execution in this process:
43//!
44//! ```ignore
45//! use std::sync::Arc;
46//! use chronon::prelude::*;
47//! use chronon::SqliteSchedulerStore;
48//!
49//! let path = std::env::var("CHRONON_SQLITE_PATH")
50//!     .unwrap_or_else(|_| "/tmp/chronon-remote.db".into());
51//! let store = SqliteSchedulerStore::new(&path).await?;
52//! let mut chronon = ChrononBuilder::new()
53//!     .scheduler_store(Arc::new(store))
54//!     .context_factory(Arc::new(JsonScriptContextFactory))
55//!     .instance_id("coordinator-0")
56//!     .coordinator_only()
57//!     .build()?;
58//! chronon.scheduler.init_partitions().await;
59//! chronon.run().await?;
60//! ```
61//!
62//! ## Mode 2 — Worker binary
63//!
64//! Same `CHRONON_SQLITE_PATH`, unique `CHRONON_INSTANCE_ID`, scripts linked via `.auto_registry()`:
65//!
66//! ```ignore
67//! use std::sync::Arc;
68//! use chronon::prelude::*;
69//! use chronon::SqliteSchedulerStore;
70//!
71//! let path = std::env::var("CHRONON_SQLITE_PATH")
72//!     .unwrap_or_else(|_| "/tmp/chronon-remote.db".into());
73//! let store = SqliteSchedulerStore::new(&path).await?;
74//! let mut chronon = ChrononBuilder::new()
75//!     .scheduler_store(Arc::new(store))
76//!     .context_factory(Arc::new(JsonScriptContextFactory))
77//!     .instance_id(std::env::var("CHRONON_INSTANCE_ID").unwrap_or_else(|_| "worker-1".into()))
78//!     .auto_registry()
79//!     .worker("general")
80//!     .build()?;
81//! chronon.run().await?;
82//! ```
83//!
84//! Other Mode 2 backends:
85//! [Postgres](../chronon_backend_postgres/index.html#mode-2--coordinator-binary),
86//! [Postgres + Redis](../chronon_backend_redis/index.html#mode-2--coordinator-binary).
87
88use std::path::Path;
89
90use chronon_backend_sql_common::SqlSchedulerStore;
91use chronon_core::Result;
92use sqlx::SqlitePool;
93
94/// SQLite-backed scheduler store.
95///
96/// Durable **single-host** persistence for Mode 1 embedded deployments and CI. SQLite allows
97/// one writer at a time — prefer Postgres (+ Redis) for multi-worker Mode 2 claim throughput.
98/// Same-host Mode 2 is possible when both binaries open the **same path**.
99///
100/// Mode 2 examples: [coordinator](index.html#mode-2--coordinator-binary) /
101/// [worker](index.html#mode-2--worker-binary).
102///
103/// Enable the facade `sqlite` feature. Construct with [`Self::new`] (file path) or
104/// [`Self::connect`] (URL, including `sqlite://:memory:`).
105///
106/// # Examples
107///
108/// ```rust,no_run
109/// use chronon_backend_sqlite::SqliteSchedulerStore;
110///
111/// # async fn example() -> chronon_core::Result<()> {
112/// let store = SqliteSchedulerStore::connect("sqlite://:memory:").await?;
113/// # let _ = store;
114/// # Ok(())
115/// # }
116/// ```
117///
118/// Runnable: `cargo run -p uf-chronon --example sqlite_boot --features sqlite`.
119pub struct SqliteSchedulerStore {
120    inner: SqlSchedulerStore,
121}
122
123impl SqliteSchedulerStore {
124    /// Open a `SQLite` database at `path` (creates the file if missing).
125    ///
126    /// # Examples
127    ///
128    /// ```rust,no_run
129    /// use chronon_backend_sqlite::SqliteSchedulerStore;
130    ///
131    /// # async fn example() -> chronon_core::Result<()> {
132    /// let store = SqliteSchedulerStore::new("/var/lib/chronon/chronon.db").await?;
133    /// # Ok(())
134    /// # }
135    /// ```
136    ///
137    /// # Errors
138    ///
139    /// Returns a storage error when the database cannot be opened or schema bootstrap fails.
140    pub async fn new(path: impl AsRef<Path>) -> Result<Self> {
141        let url = format!("sqlite://{}?mode=rwc", path.as_ref().display());
142        Self::connect(&url).await
143    }
144
145    /// Connect using a `SQLite` connection URL.
146    ///
147    /// # Examples
148    ///
149    /// ```rust,no_run
150    /// use chronon_backend_sqlite::SqliteSchedulerStore;
151    ///
152    /// # async fn example() -> chronon_core::Result<()> {
153    /// let store = SqliteSchedulerStore::connect("sqlite://:memory:").await?;
154    /// # Ok(())
155    /// # }
156    /// ```
157    ///
158    /// # Errors
159    ///
160    /// Returns a storage error when the pool cannot connect or schema bootstrap fails.
161    pub async fn connect(url: &str) -> Result<Self> {
162        let inner = SqlSchedulerStore::connect_sqlite(url).await?;
163        Ok(Self { inner })
164    }
165
166    /// Wrap an existing pool (schema bootstrap runs).
167    ///
168    /// # Examples
169    ///
170    /// ```rust,no_run
171    /// use chronon_backend_sqlite::SqliteSchedulerStore;
172    /// use sqlx::sqlite::SqlitePoolOptions;
173    ///
174    /// # async fn example() -> chronon_core::Result<()> {
175    /// let pool = SqlitePoolOptions::new()
176    ///     .connect("sqlite://:memory:")
177    ///     .await
178    ///     .expect("pool");
179    /// let store = SqliteSchedulerStore::from_pool(pool).await?;
180    /// # Ok(())
181    /// # }
182    /// ```
183    ///
184    /// # Errors
185    ///
186    /// Returns a storage error when schema bootstrap fails.
187    pub async fn from_pool(pool: SqlitePool) -> Result<Self> {
188        let inner = SqlSchedulerStore::from_sqlite_pool(pool).await?;
189        Ok(Self { inner })
190    }
191
192    /// Underlying connection pool.
193    ///
194    /// # Panics
195    ///
196    /// Panics if the inner pool is not `SQLite` (internal invariant violation).
197    #[must_use]
198    pub fn pool(&self) -> &SqlitePool {
199        match self.inner.pool() {
200            chronon_backend_sql_common::SqlPool::Sqlite(pool) => pool,
201            chronon_backend_sql_common::SqlPool::Postgres(_) => {
202                panic!("sqlite backend has non-sqlite pool")
203            }
204        }
205    }
206}
207
208impl std::fmt::Debug for SqliteSchedulerStore {
209    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210        f.debug_struct("SqliteSchedulerStore")
211            .finish_non_exhaustive()
212    }
213}
214
215chronon_backend_sql_common::delegate_scheduler_store!(SqliteSchedulerStore, inner);