Something went wrong. Try again.
atproto Thingiverse but good
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889//! SQLite pool initialization and projection-cursor persistence.//!//! Two durable stores back the server://! 1. Hydrant's fjall store (`HYDRANT_DATABASE_PATH`) for raw ATProto records.//! 2. This SQLite projection database (`DATABASE_URL`) for app-specific derived//! views. The pool here owns the second.
use std::str::FromStr;
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions};use sqlx::{Sqlite, SqlitePool, Transaction};
use super::config::ServerConfig;
/// Open the SQLite pool, enable WAL mode, run migrations, and return the pool.////// WAL mode is set on the connection options (not in a migration) because SQLite/// silently ignores `journal_mode` changes inside the transaction sqlx uses to/// run migrations.pub async fn init_db(cfg: &ServerConfig) -> anyhow::Result<SqlitePool> { ensure_data_dir(&cfg.database_url)?;
let options = SqliteConnectOptions::from_str(&cfg.database_url)? .create_if_missing(true) .journal_mode(SqliteJournalMode::Wal);
let pool = SqlitePoolOptions::new() .max_connections(5) .connect_with(options) .await?;
sqlx::migrate!("./migrations").run(&pool).await?; tracing::info!("sqlite migrations applied"); Ok(pool)}
fn ensure_data_dir(db_url: &str) -> anyhow::Result<()> { if let Some(path) = db_url.strip_prefix("sqlite:") && let Some(parent) = std::path::Path::new(path).parent() { std::fs::create_dir_all(parent)?; } Ok(())}
struct CursorRow { value: String,}
/// Load the last persisted projection cursor (the Hydrant event id), if any.pub async fn load_cursor(db: &SqlitePool) -> anyhow::Result<Option<u64>> { let row: Option<CursorRow> = sqlx::query_as!( CursorRow, "SELECT value FROM projection_state WHERE key = ?", "cursor" ) .fetch_optional(db) .await?; Ok(row.and_then(|r| r.value.parse().ok()))}
/// Persist the projection cursor outside a projection transaction.////// Used for events that are acknowledged but not projected (e.g. account status/// events), so the durable cursor advances past them and a reconnect/resume does/// not replay them indefinitely.pub async fn advance_cursor(db: &SqlitePool, cursor: u64) -> anyhow::Result<()> { let mut tx = db.begin().await?; save_cursor(&mut tx, cursor).await?; tx.commit().await?; Ok(())}
/// Persist the projection cursor inside an in-flight projection transaction.pub async fn save_cursor(tx: &mut Transaction<'_, Sqlite>, cursor: u64) -> anyhow::Result<()> { let now = chrono::Utc::now() .timestamp_nanos_opt() .expect("current timestamp must fit in i64 nanoseconds"); sqlx::query!( "INSERT INTO projection_state (key, value, updated_at) VALUES ('cursor', ?, ?) \ ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at", cursor.to_string(), now ) .execute(&mut **tx) .await?; Ok(())}