codlet_sqlx/migration.rs
1//! Migration runner for codlet SQLite tables (RFC-011 §10.4).
2//!
3//! The SQL is embedded at compile time via `include_str!`. Host applications
4//! own the migration *application order* — this function is idempotent and
5//! safe to call on startup, but the host decides when and how to run it
6//! relative to its own migrations (RFC-011 §10.4).
7
8use sqlx::SqlitePool;
9
10/// Run codlet's embedded SQLite migrations against `pool`.
11///
12/// Uses `IF NOT EXISTS` semantics; safe to call on every startup.
13///
14/// # Errors
15/// Returns a [`sqlx::Error`] if the SQL execution fails.
16pub async fn run_migrations(pool: &SqlitePool) -> Result<(), sqlx::Error> {
17 // WAL mode gives better concurrent read/write performance and is
18 // recommended for codlet's workload.
19 sqlx::query("PRAGMA journal_mode = WAL")
20 .execute(pool)
21 .await?;
22 // Enforce foreign key constraints if the host schema uses them.
23 sqlx::query("PRAGMA foreign_keys = ON")
24 .execute(pool)
25 .await?;
26
27 let migration_sql = include_str!("../migrations/0001_initial.sql");
28
29 // Split on statement boundaries and execute each statement separately,
30 // since SQLx's `execute` does not support multiple statements in one call.
31 for stmt in migration_sql.split(';') {
32 // Strip leading comment lines and whitespace from each segment, then
33 // execute only non-empty segments. A segment that is entirely comments
34 // (e.g. the preamble before the first real statement) is silently
35 // skipped; a segment that starts with comments but contains SQL is
36 // executed with the comments stripped.
37 let code_lines: String = stmt
38 .lines()
39 .filter(|l| !l.trim_start().starts_with("--"))
40 .collect::<Vec<_>>()
41 .join("\n");
42 let trimmed = code_lines.trim();
43 if trimmed.is_empty() {
44 continue;
45 }
46 sqlx::query(trimmed).execute(pool).await?;
47 }
48
49 Ok(())
50}