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