Skip to content

Commit 173a296

Browse files
committed
feat: add types to show API shape and design
- Once we have consensus on the API and types, I'll move on to implementation details and tests - Have to allow dead code and unused right now until we fully implement and test the API - Have to ignore doc tests temporarily for the same reason
1 parent 726b975 commit 173a296

8 files changed

Lines changed: 228 additions & 7 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/sqlx-sqlite-conn-mgr/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ rust-version = "1.89"
88

99
[dependencies]
1010
sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite"] }
11+
thiserror = "2.0.17"
1112
tokio = { version = "1.48.0", features = ["full"] }

crates/sqlx-sqlite-conn-mgr/README.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,20 @@ management.
88

99
## Features
1010

11-
* **Single connection pool per database**: Prevents violation of access policies
12-
and/or a glut of open file handles and (mostly) idle threads
11+
* **Maintains one read connection pool and one write connection per database**:
12+
Prevents violation of access policies and/or a glut of open file handles and
13+
(mostly) idle threads
1314
* **Connection pooling**:
1415
* Read-only pool for concurrent reads (up to 6 connections)
15-
* **Lazy write pool**: Single write connection pool (max=1) initialized on
16+
* **Lazy write pool**: Single write connection pool (max_connections=1) initialized on
1617
first use
1718
* **Exclusive write access**: WriteGuard ensures serialized writes
18-
(enforced by max_connections=1)
1919
* **WAL mode**: Automatically enabled on first `acquire_writer()` call
2020
(idempotent)
21+
* See [WAL documentation](https://www.sqlite.org/wal.html) for details
2122
* **30-second idle timeout**: Both read and write connections close after
2223
30 seconds of inactivity
23-
* **No perpetual caching**: Zero minimum connections (min_connections=0) to
24+
* **No perpetual connection caching**: Zero minimum connections (min_connections=0) to
2425
avoid idle thread overhead
2526

2627
## Design Philosophy
@@ -110,9 +111,11 @@ queries.
110111
is released via `WriteGuard` drop.
111112

112113
5. **Connection Management**:
113-
* Read pool: max 6 concurrent connections, 0 cached
114+
* Read pool: 6 concurrent connections by default, 0 cached
115+
* Can be configured via `SqliteDatabaseConfig`
114116
* Write pool: max 1 connection, 0 cached
115117
* Idle timeout: 30 seconds for both pools
118+
* Can be configured via `SqliteDatabaseConfig`
116119
* No perpetual caching to minimize idle thread overhead
117120

118121
## Error Handling
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//! Configuration for SQLite database connection pools
2+
3+
use std::time::Duration;
4+
5+
/// Configuration for SqliteDatabase connection pools
6+
///
7+
/// # Examples
8+
///
9+
/// ```
10+
/// use sqlx_sqlite_conn_mgr::SqliteDatabaseConfig;
11+
/// use std::time::Duration;
12+
///
13+
/// // Use defaults
14+
/// let config = SqliteDatabaseConfig::default();
15+
///
16+
/// // Customize specific fields
17+
/// let config = SqliteDatabaseConfig {
18+
/// max_read_connections: 3,
19+
/// idle_timeout: Duration::from_secs(60),
20+
/// };
21+
///
22+
/// // Override just one field
23+
/// let config = SqliteDatabaseConfig {
24+
/// max_read_connections: 3,
25+
/// ..Default::default()
26+
/// };
27+
/// ```
28+
#[derive(Debug, Clone)]
29+
pub struct SqliteDatabaseConfig {
30+
/// Maximum number of concurrent read connections
31+
///
32+
/// This controls the size of the read-only connection pool.
33+
/// Higher values allow more concurrent read queries but consume more resources.
34+
///
35+
/// Default: 6
36+
pub max_read_connections: u32,
37+
38+
/// Idle timeout for both read and write connections
39+
///
40+
/// Connections that remain idle for this duration will be closed automatically.
41+
/// This helps prevent resource exhaustion from idle threads.
42+
///
43+
/// Default: 30 seconds
44+
pub idle_timeout: Duration,
45+
}
46+
47+
impl Default for SqliteDatabaseConfig {
48+
fn default() -> Self {
49+
Self {
50+
max_read_connections: 6,
51+
idle_timeout: Duration::from_secs(30),
52+
}
53+
}
54+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//! SQLite database with connection pooling and optional write access
2+
3+
use sqlx::{Pool, Sqlite};
4+
use std::path::PathBuf;
5+
use std::sync::atomic::AtomicBool;
6+
7+
/// SQLite database with connection pooling for concurrent reads and optional exclusive writes.
8+
///
9+
/// ## Architecture
10+
///
11+
/// The database maintains two connection pools:
12+
/// - **`read_pool`**: Pool of read-only connections for concurrent reads
13+
/// - **`write_conn`**: Single-connection pool for exclusive write access (enforced by max_connections=1)
14+
///
15+
/// ## State Management
16+
///
17+
/// - **`wal_initialized`**: Tracks whether WAL journal mode has been enabled (lazy initialization)
18+
/// - **`closed`**: Prevents use after the database has been closed
19+
/// - **`path`**: Database file path for cleanup operations
20+
///
21+
/// ## Usage Pattern
22+
///
23+
/// ```text
24+
/// 1. Connect to database (creates/reuses connection pools)
25+
/// 2. Read operations: Access read_pool for concurrent reads
26+
/// 3. Write operations: Acquire writer (lazily enables WAL on first call)
27+
/// 4. Close database when done
28+
/// ```
29+
#[derive(Debug)]
30+
pub struct SqliteDatabase {
31+
/// Pool of read-only connections (defaults to max_connections=6) for concurrent reads
32+
read_pool: Pool<Sqlite>,
33+
34+
/// Single read-write connection pool (max_connections=1) for serialized writes
35+
write_conn: Pool<Sqlite>,
36+
37+
/// Tracks if WAL mode has been initialized (set on first write)
38+
wal_initialized: AtomicBool,
39+
40+
/// Marks database as closed to prevent further operations
41+
closed: AtomicBool,
42+
43+
/// Path to database file (used for cleanup and registry lookups)
44+
path: PathBuf,
45+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//! Error types for sqlx-sqlite-conn-mgr
2+
3+
use thiserror::Error;
4+
5+
/// Errors that may occur when working with sqlx-sqlite-conn-mgr
6+
#[derive(Error, Debug)]
7+
pub enum Error {
8+
/// IO error when accessing database files. Standard library IO errors
9+
/// are converted to this variant.
10+
#[error("IO error: {0}")]
11+
Io(#[from] std::io::Error),
12+
13+
/// Error from the sqlx library. Standard sqlx errors are converted to this variant
14+
#[error("Sqlx error: {0}")]
15+
Sqlx(#[from] sqlx::Error),
16+
17+
/// Database has been closed and cannot be used
18+
#[error("Database has been closed")]
19+
DatabaseClosed,
20+
}
21+
22+
/// A type alias for Results with our Error type
23+
pub type Result<T> = std::result::Result<T, Error>;
Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,33 @@
1-
//! # SQLx Connection Pool Manager
1+
//! # sqlx-sqlite-conn-mgr
2+
//!
3+
//! A minimal wrapper around SQLx that enforces pragmatic SQLite connection policies
4+
//! for mobile and desktop applications.
5+
//!
6+
//! ## Core Types
7+
//!
8+
//! - **[`SqliteDatabase`]**: Main database type with separate read and write connection pools
9+
//! - **[`SqliteDatabaseConfig`]**: Configuration for connection pool settings
10+
//! - **[`WriteGuard`]**: RAII guard ensuring exclusive write access
11+
//! - **[`Error`]**: Error type for database operations
12+
//!
13+
//! ## Architecture
14+
//!
15+
//! - **Dual pools**: Separate read-only pool (max 6 connections) and write pool (max 1 connection)
16+
//! - **Lazy WAL mode**: Write-Ahead Logging enabled automatically on first write
17+
//! - **Exclusive writes**: Single-connection write pool enforces serialized write access
18+
//! - **Concurrent reads**: Multiple readers can query simultaneously via the read pool
19+
20+
// TODO: Remove these allows once implementation is complete
21+
#![allow(dead_code)]
22+
#![allow(unused)]
23+
24+
mod config;
25+
mod database;
26+
mod error;
27+
mod write_guard;
28+
29+
// Re-export public types
30+
pub use config::SqliteDatabaseConfig;
31+
pub use database::SqliteDatabase;
32+
pub use error::{Error, Result};
33+
pub use write_guard::WriteGuard;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//! WriteGuard for exclusive write access to the database
2+
3+
use sqlx::Sqlite;
4+
use sqlx::pool::PoolConnection;
5+
use sqlx::sqlite::SqliteConnection;
6+
use std::ops::{Deref, DerefMut};
7+
8+
/// RAII guard for exclusive write access to a database connection
9+
///
10+
/// This guard wraps a pool connection and returns it to the pool on drop.
11+
/// Only one `WriteGuard` can exist at a time (enforced by max_connections=1),
12+
/// ensuring serialized write access.
13+
///
14+
/// The guard derefs to `SqliteConnection` allowing direct use with sqlx queries.
15+
///
16+
/// # Example
17+
/// TODO: Remove ignore once implementation is complete
18+
/// ```ignore
19+
/// use sqlx_sqlite_conn_mgr::SqliteDatabase;
20+
/// use sqlx::query;
21+
///
22+
/// # async fn example() -> Result<(), sqlx_sqlite_conn_mgr::Error> {
23+
/// let db = SqliteDatabase::connect("test.db").await?;
24+
/// let mut writer = db.acquire_writer().await?;
25+
/// // Use &mut *writer for write queries (e.g. INSERT/UPDATE/DELETE)
26+
/// query("INSERT INTO users (name) VALUES (?)")
27+
/// .bind("Alice")
28+
/// .execute(&mut *writer)
29+
/// .await?;
30+
/// // Writer is automatically returned when dropped
31+
/// # Ok(())
32+
/// # }
33+
/// ```
34+
#[derive(Debug)]
35+
pub struct WriteGuard {
36+
conn: PoolConnection<Sqlite>,
37+
}
38+
39+
impl WriteGuard {
40+
/// Create a new WriteGuard by taking ownership of a pool connection
41+
pub(crate) fn new(conn: PoolConnection<Sqlite>) -> Self {
42+
Self { conn }
43+
}
44+
}
45+
46+
impl Deref for WriteGuard {
47+
type Target = SqliteConnection;
48+
49+
fn deref(&self) -> &Self::Target {
50+
&*self.conn
51+
}
52+
}
53+
54+
impl DerefMut for WriteGuard {
55+
fn deref_mut(&mut self) -> &mut Self::Target {
56+
&mut *self.conn
57+
}
58+
}
59+
60+
// Drop is automatically implemented - PoolConnection returns itself to the pool
61+
62+
// WriteGuard is automatically Send because PoolConnection<Sqlite> is Send

0 commit comments

Comments
 (0)