| Crates.io | sqlx-sqlite-conn-mgr |
| lib.rs | sqlx-sqlite-conn-mgr |
| version | 0.8.6 |
| created_at | 2026-01-16 14:06:50.89058+00 |
| updated_at | 2026-01-16 14:06:50.89058+00 |
| description | Wraps SQLx for SQLite, enforcing pragmatic connection policies for mobile and desktop applications |
| homepage | |
| repository | https://github.com/silvermine/tauri-plugin-sqlite |
| max_upload_size | |
| id | 2048667 |
| size | 154,671 |
A minimal wrapper around SQLx that enforces pragmatic SQLite connection policies for mobile and desktop applications. Not dependent on Tauri — usable in any Rust project needing SQLx connection management.
Single instance per database path: Prevents duplicate pools and idle threads
Read pool: Concurrent read-only connections (default: 6, configurable)
Write connection: Single exclusive writer via WriteGuard
Wait! Why? From SQLite docs: "SQLite ... will only allow one writer at any instant in time."
WAL mode: Enabled on first acquire_writer() call
Idle timeout: Connections close after 30s inactivity (configurable)
No perpetual caching: Zero minimum connections (prevents idle thread sprawl)
Delegates to SQLx's SqlitePoolOptions and SqliteConnectOptions wherever
possible — minimal wrapper logic.
use sqlx_sqlite_conn_mgr::SqliteDatabase;
use sqlx::query;
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<(), sqlx_sqlite_conn_mgr::Error> {
// Connect (creates if missing, returns Arc<SqliteDatabase>)
let db = SqliteDatabase::connect("example.db", None).await?;
// Multiple connects to same path return same instance
let db2 = SqliteDatabase::connect("example.db", None).await?;
assert!(Arc::ptr_eq(&db, &db2));
// Read queries use the pool (concurrent)
let rows = query("SELECT * FROM users")
.fetch_all(db.read_pool()?)
.await?;
// Write queries acquire exclusive access (WAL enabled on first call)
let mut writer = db.acquire_writer().await?;
query("INSERT INTO users (name) VALUES (?)")
.bind("Alice")
.execute(&mut *writer)
.await?;
// Writer released on drop
db.close().await?;
Ok(())
}
use sqlx_sqlite_conn_mgr::{SqliteDatabase, SqliteDatabaseConfig};
use std::time::Duration;
let config = SqliteDatabaseConfig {
max_read_connections: 10, // default: 6
idle_timeout: Duration::from_secs(60), // default: 30s
};
let db = SqliteDatabase::connect("example.db", Some(config)).await?;
Run SQLx migrations directly:
use sqlx_sqlite_conn_mgr::SqliteDatabase;
// Embed migrations at compile time (reads ./migrations/*.sql)
static MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!("./migrations");
async fn run() -> Result<(), sqlx_sqlite_conn_mgr::Error> {
let db = SqliteDatabase::connect("example.db", None).await?;
db.run_migrations(&MIGRATOR).await?;
Ok(())
}
Migrations are tracked in _sqlx_migrations — calling run_migrations() multiple
times is safe (already-applied migrations are skipped).
Note: When using the Tauri plugin, migrations are handled automatically via
Builder::add_migrations(). The plugin starts migrations at setup and waits for completion whenload()is called.
Attach other SQLite databases to enable cross-database queries. Attached databases are connection-scoped and automatically detached when the guard is dropped.
use sqlx_sqlite_conn_mgr::{SqliteDatabase, AttachedSpec, AttachedMode, acquire_reader_with_attached};
use sqlx::query;
use std::sync::Arc;
async fn example() -> Result<(), sqlx_sqlite_conn_mgr::Error> {
// You must first connect to the main database and any database(s) you intend to
// attach.
let main_db = SqliteDatabase::connect("main.db", None).await?;
let orders_db = SqliteDatabase::connect("orders.db", None).await?;
// Attach orders database for read-only access
let specs = vec![AttachedSpec {
database: orders_db,
schema_name: "orders".to_string(),
mode: AttachedMode::ReadOnly,
}];
let mut conn = acquire_reader_with_attached(&main_db, specs).await?;
// Cross-database query
let rows = query(
"SELECT u.name, o.total
FROM main.users u
JOIN orders.orders o ON u.id = o.user_id"
)
.fetch_all(&mut *conn)
.await?;
// Attached database automatically detached when conn is dropped
Ok(())
}
AttachedMode::ReadOnly: Attach for read access only. Can be used with
both reader and writer connections.AttachedMode::ReadWrite: Attach for write access. Can only be used with
writer connections. Acquires the attached database's writer lock to ensure
exclusive access.CannotAttachReadWriteToReader error)Caution: Do not bypass this API by executing raw
ATTACH DATABASE '/path/to/db.db' AS aliasSQL commands directly. Doing so circumvents the connection manager's policies and will result in unpredictable behavior, including potential deadlocks.
SqliteDatabase| Method | Description |
|---|---|
connect(path, config) |
Connect/create database, returns cached Arc if already open |
read_pool() |
Get read-only pool reference |
acquire_writer() |
Acquire exclusive WriteGuard (enables WAL on first call) |
run_migrations(migrator) |
Run pending migrations from a Migrator |
close() |
Close and remove from cache |
remove() |
Close and delete database files (.db, .db-wal, .db-shm) |
WriteGuardRAII guard for exclusive write access. Derefs to SqliteConnection. Connection
returned to pool on drop.
| Function | Description |
|---|---|
acquire_reader_with_attached(db, specs) |
Acquire read connection with attached database(s) |
acquire_writer_with_attached(db, specs) |
Acquire writer connection with attached database(s) |
Returns AttachedConnection or AttachedWriteGuard respectively. Both guards
deref to SqliteConnection and automatically detach databases on drop.
The read pool opens connections with read_only(true), preventing write
operations and ensuring data integrity.
WAL mode is enabled on first acquire_writer() call (idempotent, safe across
sessions). This library sets PRAGMA synchronous = NORMAL instead of FULL:
NORMAL could cause corruption)NORMAL provides the best balance; FULL is
for unreliable storage or power-loss-mid-fsync scenariosSee SQLite WAL Performance Considerations for details.
The write pool has max_connections=1. Callers to acquire_writer() block
asynchronously until the current WriteGuard is dropped.
Uses tracing with release_max_level_off —
all logs compiled out of release builds. Install a tracing-subscriber in your
app to see logs during development.
Follows Silvermine Rust coding standards.
cargo build # Build
cargo test # Test
cargo lint-clippy && cargo lint-fmt # Lint
cargo doc --open # Documentation