mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-08-24 12:24:01 +02:00
Let yaak-models compile for wasm32-unknown-unknown (#556)
This commit is contained in:
@@ -2,9 +2,8 @@ use crate::error::Result;
|
||||
use crate::util::generate_prefixed_id;
|
||||
use include_dir::{Dir, include_dir};
|
||||
use log::{debug, info};
|
||||
use r2d2::Pool;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use rusqlite::{OptionalExtension, params};
|
||||
use yaak_database::{SqliteConn, SqlitePool};
|
||||
|
||||
static BLOB_MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/blob_migrations");
|
||||
|
||||
@@ -29,11 +28,11 @@ impl BodyChunk {
|
||||
// whole app whenever the pool is exhausted.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BlobManager {
|
||||
pool: Pool<SqliteConnectionManager>,
|
||||
pool: SqlitePool,
|
||||
}
|
||||
|
||||
impl BlobManager {
|
||||
pub fn new(pool: Pool<SqliteConnectionManager>) -> Self {
|
||||
pub fn new(pool: SqlitePool) -> Self {
|
||||
Self { pool }
|
||||
}
|
||||
|
||||
@@ -45,7 +44,7 @@ impl BlobManager {
|
||||
|
||||
/// Context for blob database operations.
|
||||
pub struct BlobContext {
|
||||
conn: r2d2::PooledConnection<SqliteConnectionManager>,
|
||||
conn: SqliteConn,
|
||||
}
|
||||
|
||||
impl BlobContext {
|
||||
@@ -131,7 +130,7 @@ impl BlobContext {
|
||||
}
|
||||
|
||||
/// Run migrations for the blob database.
|
||||
pub fn migrate_blob_db(pool: &Pool<SqliteConnectionManager>) -> Result<()> {
|
||||
pub fn migrate_blob_db(pool: &SqlitePool) -> Result<()> {
|
||||
info!("Running blob database migrations");
|
||||
|
||||
// Create migrations tracking table
|
||||
@@ -198,9 +197,9 @@ pub fn migrate_blob_db(pool: &Pool<SqliteConnectionManager>) -> Result<()> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn create_test_pool() -> Pool<SqliteConnectionManager> {
|
||||
let manager = SqliteConnectionManager::memory();
|
||||
let pool = Pool::builder().max_size(1).build(manager).unwrap();
|
||||
fn create_test_pool() -> SqlitePool {
|
||||
let manager = r2d2_sqlite::SqliteConnectionManager::memory();
|
||||
let pool = r2d2::Pool::builder().max_size(1).build(manager).unwrap();
|
||||
migrate_blob_db(&pool).unwrap();
|
||||
pool
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ pub enum Error {
|
||||
SqlError(#[from] rusqlite::Error),
|
||||
|
||||
#[error("SQL Pool error: {0}")]
|
||||
SqlPoolError(#[from] r2d2::Error),
|
||||
SqlPoolError(#[from] yaak_database::PoolError),
|
||||
|
||||
#[error("Database error: {0}")]
|
||||
Database(String),
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
use crate::blob_manager::{BlobManager, migrate_blob_db};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::error::Result;
|
||||
use crate::migrate::migrate_db;
|
||||
use crate::query_manager::QueryManager;
|
||||
use crate::util::ModelPayload;
|
||||
use log::info;
|
||||
use r2d2::Pool;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use std::fs::create_dir_all;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
use yaak_database::SqlitePool;
|
||||
|
||||
pub mod blob_manager;
|
||||
pub mod client_db;
|
||||
@@ -22,17 +19,78 @@ pub mod query_manager;
|
||||
pub mod render;
|
||||
pub mod util;
|
||||
|
||||
fn sqlite_file_manager(path: impl Into<PathBuf>) -> SqliteConnectionManager {
|
||||
SqliteConnectionManager::file(path.into()).with_init(|conn| {
|
||||
conn.pragma_update(None, "journal_mode", "WAL")?;
|
||||
conn.pragma_update(None, "synchronous", "NORMAL")?;
|
||||
conn.busy_timeout(Duration::from_millis(5000))
|
||||
})
|
||||
/// Per-connection setup, applied by every pool on every connection it opens.
|
||||
fn init_connection(conn: &rusqlite::Connection) -> rusqlite::Result<()> {
|
||||
conn.busy_timeout(std::time::Duration::from_millis(5000))
|
||||
}
|
||||
|
||||
fn sqlite_memory_manager() -> SqliteConnectionManager {
|
||||
SqliteConnectionManager::memory()
|
||||
.with_init(|conn| conn.busy_timeout(Duration::from_millis(5000)))
|
||||
fn init_file_connection(conn: &rusqlite::Connection) -> rusqlite::Result<()> {
|
||||
conn.pragma_update(None, "journal_mode", "WAL")?;
|
||||
conn.pragma_update(None, "synchronous", "NORMAL")?;
|
||||
init_connection(conn)
|
||||
}
|
||||
|
||||
/// The two ways a pool comes to exist, one per target.
|
||||
///
|
||||
/// On the desktop and CLI, an r2d2 pool over a file. In a browser, a single
|
||||
/// connection over whatever VFS the host registered before calling in — the
|
||||
/// path is a name inside that VFS, not a place on disk. Everything downstream
|
||||
/// of `SqlitePool` is target-agnostic; this is the only fork.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod open {
|
||||
use super::*;
|
||||
use crate::error::Error;
|
||||
use r2d2::Pool;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn file_pool(path: impl Into<PathBuf>, max_size: u32, min_idle: u32) -> Result<SqlitePool> {
|
||||
let path: PathBuf = path.into();
|
||||
// Create parent directories if needed
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
let manager = SqliteConnectionManager::file(path).with_init(|c| init_file_connection(c));
|
||||
Pool::builder()
|
||||
.max_size(max_size)
|
||||
.min_idle(Some(min_idle))
|
||||
.connection_timeout(Duration::from_secs(10))
|
||||
.build(manager)
|
||||
.map_err(|e| Error::Database(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn memory_pool() -> Result<SqlitePool> {
|
||||
let manager = SqliteConnectionManager::memory().with_init(|c| init_connection(c));
|
||||
// In-memory DB doesn't support multiple connections
|
||||
Pool::builder().max_size(1).build(manager).map_err(|e| Error::Database(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod open {
|
||||
use super::*;
|
||||
use rusqlite::Connection;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn file_pool(
|
||||
path: impl Into<PathBuf>,
|
||||
_max_size: u32,
|
||||
_min_idle: u32,
|
||||
) -> Result<SqlitePool> {
|
||||
// No WAL: the browser VFSs are single-connection and journal their own
|
||||
// way; the pragma is accepted and ignored on some and rejected on
|
||||
// others, so it is not applied at all here.
|
||||
let conn = Connection::open(path.into())?;
|
||||
init_connection(&conn)?;
|
||||
Ok(SqlitePool::single(conn))
|
||||
}
|
||||
|
||||
pub fn memory_pool() -> Result<SqlitePool> {
|
||||
let conn = Connection::open_in_memory()?;
|
||||
init_connection(&conn)?;
|
||||
Ok(SqlitePool::single(conn))
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the database managers for standalone (non-Tauri) usage.
|
||||
@@ -46,40 +104,16 @@ pub fn init_standalone(
|
||||
let db_path = db_path.as_ref();
|
||||
let blob_path = blob_path.as_ref();
|
||||
|
||||
// Create parent directories if needed
|
||||
if let Some(parent) = db_path.parent() {
|
||||
create_dir_all(parent)?;
|
||||
}
|
||||
if let Some(parent) = blob_path.parent() {
|
||||
create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
// Main database pool. Sized for concurrent in-flight queries, not concurrent app
|
||||
// features — connections are held per-statement, so even heavy fan-out (e.g. many
|
||||
// gRPC streams) only needs a handful at once. Keep max_size modest: WAL connections
|
||||
// hold ~3 file descriptors each, and macOS GUI apps get a 256 fd soft limit.
|
||||
info!("Initializing app database {db_path:?}");
|
||||
let manager = sqlite_file_manager(db_path);
|
||||
let pool = Pool::builder()
|
||||
.max_size(20)
|
||||
.min_idle(Some(2))
|
||||
.connection_timeout(Duration::from_secs(10))
|
||||
.build(manager)
|
||||
.map_err(|e| Error::Database(e.to_string()))?;
|
||||
|
||||
let pool = open::file_pool(db_path, 20, 2)?;
|
||||
migrate_db(&pool)?;
|
||||
|
||||
info!("Initializing blobs database {blob_path:?}");
|
||||
|
||||
// Blob database pool
|
||||
let blob_manager = sqlite_file_manager(blob_path);
|
||||
let blob_pool = Pool::builder()
|
||||
.max_size(10)
|
||||
.min_idle(Some(1))
|
||||
.connection_timeout(Duration::from_secs(10))
|
||||
.build(blob_manager)
|
||||
.map_err(|e| Error::Database(e.to_string()))?;
|
||||
|
||||
let blob_pool = open::file_pool(blob_path, 10, 1)?;
|
||||
migrate_blob_db(&blob_pool)?;
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
@@ -92,22 +126,10 @@ pub fn init_standalone(
|
||||
/// Initialize the database managers with in-memory SQLite databases.
|
||||
/// Useful for testing and CI environments.
|
||||
pub fn init_in_memory() -> Result<(QueryManager, BlobManager, mpsc::Receiver<ModelPayload>)> {
|
||||
// Main database pool
|
||||
let manager = sqlite_memory_manager();
|
||||
let pool = Pool::builder()
|
||||
.max_size(1) // In-memory DB doesn't support multiple connections
|
||||
.build(manager)
|
||||
.map_err(|e| Error::Database(e.to_string()))?;
|
||||
|
||||
let pool = open::memory_pool()?;
|
||||
migrate_db(&pool)?;
|
||||
|
||||
// Blob database pool
|
||||
let blob_manager = sqlite_memory_manager();
|
||||
let blob_pool = Pool::builder()
|
||||
.max_size(1)
|
||||
.build(blob_manager)
|
||||
.map_err(|e| Error::Database(e.to_string()))?;
|
||||
|
||||
let blob_pool = open::memory_pool()?;
|
||||
migrate_blob_db(&blob_pool)?;
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
@@ -2,14 +2,13 @@ use crate::error::Error::MigrationError;
|
||||
use crate::error::Result;
|
||||
use include_dir::{Dir, DirEntry, include_dir};
|
||||
use log::{debug, info};
|
||||
use r2d2::Pool;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use rusqlite::{OptionalExtension, TransactionBehavior, params};
|
||||
use rusqlite::{OptionalExtension, Transaction, TransactionBehavior, params};
|
||||
use sha2::{Digest, Sha384};
|
||||
use yaak_database::SqlitePool;
|
||||
|
||||
static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/migrations");
|
||||
|
||||
pub fn migrate_db(pool: &Pool<SqliteConnectionManager>) -> Result<()> {
|
||||
pub fn migrate_db(pool: &SqlitePool) -> Result<()> {
|
||||
info!("Running database migrations");
|
||||
|
||||
// Ensure the table exists
|
||||
@@ -43,8 +42,10 @@ pub fn migrate_db(pool: &Pool<SqliteConnectionManager>) -> Result<()> {
|
||||
let mut ran_migrations = 0;
|
||||
for entry in entries {
|
||||
num_migrations += 1;
|
||||
let mut conn = pool.get()?;
|
||||
let mut tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
|
||||
let conn = pool.get()?;
|
||||
// `new_unchecked` takes `&Connection`; see yaak_database::pool for why
|
||||
// the pool never hands out `&mut`.
|
||||
let mut tx = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate)?;
|
||||
match run_migration(entry, &mut tx) {
|
||||
Ok(ran) => {
|
||||
if ran {
|
||||
@@ -74,7 +75,7 @@ pub fn migrate_db(pool: &Pool<SqliteConnectionManager>) -> Result<()> {
|
||||
}
|
||||
|
||||
fn run_migration(migration_path: &DirEntry, tx: &mut rusqlite::Transaction) -> Result<bool> {
|
||||
let start = std::time::Instant::now();
|
||||
let start = elapsed_timer();
|
||||
let (version, description) = split_migration_filename(migration_path.path().to_str().unwrap())
|
||||
.expect("Failed to parse migration filename");
|
||||
|
||||
@@ -97,7 +98,7 @@ fn run_migration(migration_path: &DirEntry, tx: &mut rusqlite::Transaction) -> R
|
||||
// Split on `;`? → optional depending on how your SQL is structured
|
||||
tx.execute_batch(&sql)?;
|
||||
|
||||
let execution_time = start.elapsed().as_nanos() as i64;
|
||||
let execution_time = start();
|
||||
let checksum = sha384_hex_prefixed(sql.as_bytes());
|
||||
|
||||
// NOTE: The success column is never used. It's just there for sqlx compatibility.
|
||||
@@ -109,6 +110,21 @@ fn run_migration(migration_path: &DirEntry, tx: &mut rusqlite::Transaction) -> R
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Nanoseconds since the timer was started, for the sqlx-compatible
|
||||
/// `execution_time` column. `Instant` does not exist on `wasm32-unknown-unknown`
|
||||
/// (there is no monotonic clock to ask), and the column is bookkeeping, so
|
||||
/// there it reads as zero rather than taking the migrator down with it.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn elapsed_timer() -> impl Fn() -> i64 {
|
||||
let start = std::time::Instant::now();
|
||||
move || start.elapsed().as_nanos() as i64
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn elapsed_timer() -> impl Fn() -> i64 {
|
||||
|| 0
|
||||
}
|
||||
|
||||
fn split_migration_filename(filename: &str) -> Option<(String, String)> {
|
||||
// Remove the .sql extension
|
||||
let trimmed = filename.strip_suffix(".sql")?;
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::error::Result;
|
||||
use crate::models::{GraphQlIntrospection, GraphQlIntrospectionIden};
|
||||
use crate::util::UpdateSource;
|
||||
use chrono::{Duration, Utc};
|
||||
use sea_query::ExprTrait;
|
||||
use sea_query::{Expr, Query, SqliteQueryBuilder};
|
||||
use sea_query_rusqlite::RusqliteBinder;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::models::{GrpcConnection, GrpcConnectionIden, GrpcConnectionState};
|
||||
use crate::queries::MAX_HISTORY_ITEMS;
|
||||
use crate::util::UpdateSource;
|
||||
use log::debug;
|
||||
use sea_query::ExprTrait;
|
||||
use sea_query::{Expr, Query, SqliteQueryBuilder};
|
||||
use sea_query_rusqlite::RusqliteBinder;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::models::{HttpResponse, HttpResponseIden, HttpResponseState};
|
||||
use crate::queries::MAX_HISTORY_ITEMS;
|
||||
use crate::util::UpdateSource;
|
||||
use log::{debug, error};
|
||||
use sea_query::ExprTrait;
|
||||
use sea_query::{Expr, Query, SqliteQueryBuilder};
|
||||
use sea_query_rusqlite::RusqliteBinder;
|
||||
use std::fs;
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::models::{KeyValue, KeyValueIden, UpsertModelInfo};
|
||||
use crate::util::UpdateSource;
|
||||
use chrono::NaiveDateTime;
|
||||
use log::error;
|
||||
use sea_query::ExprTrait;
|
||||
use sea_query::{Asterisk, Cond, Expr, Query, SqliteQueryBuilder};
|
||||
use sea_query_rusqlite::RusqliteBinder;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::client_db::ClientDb;
|
||||
use crate::error::Result;
|
||||
use crate::models::{PluginKeyValue, PluginKeyValueIden};
|
||||
use sea_query::ExprTrait;
|
||||
use sea_query::Keyword::CurrentTimestamp;
|
||||
use sea_query::{Asterisk, Cond, Expr, OnConflict, Query, SqliteQueryBuilder};
|
||||
use sea_query_rusqlite::RusqliteBinder;
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::client_db::ClientDb;
|
||||
use crate::error::Result;
|
||||
use crate::models::{SyncState, SyncStateIden, UpsertModelInfo};
|
||||
use crate::util::UpdateSource;
|
||||
use sea_query::ExprTrait;
|
||||
use sea_query::{Asterisk, Cond, Expr, Query, SqliteQueryBuilder};
|
||||
use sea_query_rusqlite::RusqliteBinder;
|
||||
use std::path::Path;
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::models::{WebsocketConnection, WebsocketConnectionIden, WebsocketConne
|
||||
use crate::queries::MAX_HISTORY_ITEMS;
|
||||
use crate::util::UpdateSource;
|
||||
use log::debug;
|
||||
use sea_query::ExprTrait;
|
||||
use sea_query::{Expr, Query, SqliteQueryBuilder};
|
||||
use sea_query_rusqlite::RusqliteBinder;
|
||||
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
use crate::client_db::ClientDb;
|
||||
use crate::error::Error::GenericError;
|
||||
use crate::util::ModelPayload;
|
||||
use r2d2::Pool;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use rusqlite::TransactionBehavior;
|
||||
use rusqlite::{Transaction, TransactionBehavior};
|
||||
use std::sync::mpsc;
|
||||
use yaak_database::{ConnectionOrTx, DbContext};
|
||||
use yaak_database::{ConnectionOrTx, DbContext, SqlitePool};
|
||||
|
||||
// Pool is internally synchronized — don't wrap it in a Mutex. A Mutex held across the
|
||||
// blocking `get()` serializes every DB access behind the slowest waiter, freezing the
|
||||
// whole app whenever the pool is exhausted.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct QueryManager {
|
||||
pool: Pool<SqliteConnectionManager>,
|
||||
pool: SqlitePool,
|
||||
events_tx: mpsc::Sender<ModelPayload>,
|
||||
}
|
||||
|
||||
impl QueryManager {
|
||||
pub fn new(pool: Pool<SqliteConnectionManager>, events_tx: mpsc::Sender<ModelPayload>) -> Self {
|
||||
pub fn new(pool: SqlitePool, events_tx: mpsc::Sender<ModelPayload>) -> Self {
|
||||
QueryManager { pool, events_tx }
|
||||
}
|
||||
|
||||
@@ -46,9 +44,10 @@ impl QueryManager {
|
||||
where
|
||||
E: From<crate::error::Error>,
|
||||
{
|
||||
let mut conn = self.pool.get().expect("Failed to get new DB connection from the pool");
|
||||
let tx = conn
|
||||
.transaction_with_behavior(TransactionBehavior::Immediate)
|
||||
let conn = self.pool.get().expect("Failed to get new DB connection from the pool");
|
||||
// `new_unchecked` takes `&Connection`; see yaak_database::pool for why
|
||||
// the pool never hands out `&mut`.
|
||||
let tx = Transaction::new_unchecked(&conn, TransactionBehavior::Immediate)
|
||||
.expect("Failed to start DB transaction");
|
||||
|
||||
let ctx = DbContext::new(ConnectionOrTx::Transaction(&tx));
|
||||
|
||||
Reference in New Issue
Block a user