1pub mod migrations;
2pub mod runtime;
3pub mod types;
4
5use anyhow::Error;
6use runtime::RUNTIME;
7use sqlx::sqlite::{SqliteConnectOptions, SqlitePool, SqlitePoolOptions};
8use std::path::Path;
9use std::str::FromStr;
10
11#[derive(Clone)]
15pub struct Database {
16 pool: SqlitePool,
17}
18
19impl std::fmt::Debug for Database {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 f.debug_struct("Database").finish()
22 }
23}
24
25impl Database {
26 #[cfg_attr(feature = "otel", tracing::instrument(fields(db_path = %path.as_ref().display())))]
37 pub fn new<P: AsRef<Path> + std::fmt::Debug>(path: P) -> Result<Self, Error> {
38 let path_str = path.as_ref().display().to_string();
39
40 tracing::info!(db_path = %path_str, "connecting to database");
41
42 RUNTIME.block_on(async {
43 let options = SqliteConnectOptions::from_str(&format!("sqlite://{}", path_str))?
44 .create_if_missing(true)
45 .foreign_keys(true);
46
47 let pool = SqlitePoolOptions::new()
48 .max_connections(5)
49 .connect_with(options)
50 .await?;
51
52 tracing::info!(db_path = %path_str, "database connected");
53 Ok(Database { pool })
54 })
55 }
56
57 pub fn close(&self) {
63 tracing::info!("closing database connection pool");
64 RUNTIME.block_on(async {
65 self.pool.close().await;
66 });
67 tracing::info!("database connection pool closed");
68 }
69
70 pub fn pool(&self) -> &SqlitePool {
72 &self.pool
73 }
74
75 pub fn migration_runner(&self) -> migrations::MigrationRunner {
80 migrations::MigrationRunner::new(self.pool.clone())
81 }
82
83 #[cfg_attr(feature = "otel", tracing::instrument(skip(self)))]
88 pub fn migrate(&self) -> Result<(), Error> {
89 RUNTIME.block_on(async {
90 tracing::info!("running schema migrations");
91 #[cfg(feature = "otel")]
92 let span = tracing::info_span!("sqlx_migrations").entered();
93 sqlx::migrate!("./migrations").run(&self.pool).await?;
94 #[cfg(feature = "otel")]
95 span.exit();
96
97 tracing::info!("running runtime migrations");
98 self.migration_runner().run_all().await
99 })
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn test_database_creation() {
109 let db = Database::new(":memory:").expect("failed to create in-memory database");
110 db.migrate().expect("failed to run migrations");
111
112 RUNTIME.block_on(async {
113 let result: (i64,) = sqlx::query_as(
114 "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='books'",
115 )
116 .fetch_one(&db.pool)
117 .await
118 .expect("failed to query sqlite_master");
119
120 assert_eq!(result.0, 1, "books table should exist after migrations");
121 });
122 }
123}