mod affinities; mod articles; mod feeds; mod models; mod reading_events; pub use models::RankedArticleRow; pub(crate) mod schema; use anyhow::Result; use diesel::prelude::*; use diesel::sqlite::SqliteConnection; use diesel_async::pooled_connection::bb8::Pool; use diesel_async::pooled_connection::AsyncDieselConnectionManager; use diesel_async::sync_connection_wrapper::SyncConnectionWrapper; use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations"); /// SQLite has no native async driver, so `diesel-async` wraps a blocking /// `SqliteConnection` and runs it on a blocking thread under the hood. type AsyncSqliteConnection = SyncConnectionWrapper; #[derive(Clone)] pub struct Db { pool: Pool, } impl Db { /// `database_url` is a plain SQLite file path (e.g. "feedsignal.db"), /// created if it doesn't exist. Everything lives in one file, no /// separate database server to run. pub async fn connect(database_url: &str) -> Result { // `MigrationHarness` only works on a synchronous `Connection`, and // this only runs once at boot, so open a throwaway sync connection // just for it rather than pulling migrations through the async pool. // Each pending migration runs in its own transaction (SQLite // supports transactional DDL), and `down.sql` gives each one a // matching revert. let url = database_url.to_string(); tokio::task::spawn_blocking(move || -> Result<()> { let mut conn = SqliteConnection::establish(&url)?; conn.run_pending_migrations(MIGRATIONS) .map_err(|e| anyhow::anyhow!(e))?; Ok(()) }) .await??; let manager = AsyncDieselConnectionManager::::new(database_url); let pool = Pool::builder().max_size(5).build(manager).await?; Ok(Self { pool }) } }