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