Skip to content

Commit 8593a41

Browse files
authored
Merge pull request silvermine#17 from pmorris-dev/support_migrations
Support migrations
2 parents ca3407d + 5dd12b5 commit 8593a41

14 files changed

Lines changed: 818 additions & 23 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.

README.md

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ SQLite database interface for Tauri applications using
1919
> "_SQLite ... will only allow one writer at any instant in time._"
2020
* **WAL Mode**: Enabled automatically on first write operation
2121
* **Type Safety**: Full TypeScript bindings
22-
* **Migration Support**: SQLx's migration framework (coming soon)
23-
* **Resource Management**: Proper cleanup on application exit (coming soon)
22+
* **Migration Support**: SQLx's migration framework
23+
* **Resource Management**: Proper cleanup on application exit
2424

2525
## Architecture
2626

@@ -96,6 +96,74 @@ fn main() {
9696
}
9797
```
9898

99+
### Migrations
100+
101+
This plugin uses [SQLx's migration system][sqlx-migrate]. Create numbered `.sql`
102+
files in a migrations directory:
103+
104+
[sqlx-migrate]: https://docs.rs/sqlx/latest/sqlx/macro.migrate.html
105+
106+
```text
107+
src-tauri/migrations/
108+
├── 0001_create_users.sql
109+
├── 0002_add_email_column.sql
110+
└── 0003_create_posts.sql
111+
```
112+
113+
Register migrations using SQLx's `migrate!()` macro, which embeds them at compile time:
114+
115+
```rust
116+
use tauri_plugin_sqlite::Builder;
117+
118+
fn main() {
119+
tauri::Builder::default()
120+
.plugin(
121+
Builder::new()
122+
.add_migrations("main.db", sqlx::migrate!("./migrations"))
123+
.build()
124+
)
125+
.run(tauri::generate_context!())
126+
.expect("error while running tauri application");
127+
}
128+
```
129+
130+
**Timing:** Migrations start automatically at plugin setup (non-blocking). When
131+
TypeScript calls `Database.load()`, it waits for migrations to complete before
132+
returning. If migrations fail, `load()` returns an error. Applied migrations are
133+
tracked in `_sqlx_migrations` — re-running is safe and idempotent.
134+
135+
#### Retrieving Migration Events
136+
137+
Use `getMigrationEvents()` to retrieve cached events:
138+
139+
```typescript
140+
import Database from '@silvermine/tauri-plugin-sqlite'
141+
142+
const db = await Database.load('mydb.db')
143+
144+
// Get all migration events (including ones emitted before listener could be registered)
145+
const events = await db.getMigrationEvents()
146+
for (const event of events) {
147+
console.log(`${event.status}: ${event.dbPath}`)
148+
if (event.status === 'failed') {
149+
console.error(`Migration error: ${event.error}`)
150+
}
151+
}
152+
```
153+
154+
**Optional:** Listen for real-time events, globally. May miss early events due the Rust
155+
layer completing some or all migrations before the frontend subscription initializes.
156+
157+
```typescript
158+
import { listen } from '@tauri-apps/api/event'
159+
import type { MigrationEvent } from '@silvermine/tauri-plugin-sqlite'
160+
161+
await listen<MigrationEvent>('sqlite:migration', (event) => {
162+
const { dbPath, status, migrationCount, error } = event.payload
163+
// status: 'running' | 'completed' | 'failed'
164+
})
165+
```
166+
99167
### Connecting
100168

101169
```typescript

api-iife.js

Lines changed: 1 addition & 1 deletion
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: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ edition = "2024"
77
rust-version = "1.89"
88

99
[dependencies]
10-
sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite"] }
10+
sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite", "migrate"] }
1111
thiserror = "2.0.17"
1212
tokio = { version = "1.48.0", features = ["full"] }
1313
tracing = { version = "0.1.41", default-features = false, features = ["std", "release_max_level_off"] }
1414
serde = { version = "1.0.228", features = ["derive"] }
15+
16+
[dev-dependencies]
17+
tempfile = "3.23.0"

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,32 @@ let config = SqliteDatabaseConfig {
6666
let db = SqliteDatabase::connect("example.db", Some(config)).await?;
6767
```
6868

69+
### Migrations
70+
71+
Run [SQLx migrations][sqlx-migrate] directly:
72+
73+
[sqlx-migrate]: https://docs.rs/sqlx/latest/sqlx/macro.migrate.html
74+
75+
```rust
76+
use sqlx_sqlite_conn_mgr::SqliteDatabase;
77+
78+
// Embed migrations at compile time (reads ./migrations/*.sql)
79+
static MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!("./migrations");
80+
81+
async fn run() -> Result<(), sqlx_sqlite_conn_mgr::Error> {
82+
let db = SqliteDatabase::connect("example.db", None).await?;
83+
db.run_migrations(&MIGRATOR).await?;
84+
Ok(())
85+
}
86+
```
87+
88+
Migrations are tracked in `_sqlx_migrations` — calling `run_migrations()` multiple
89+
times is safe (already-applied migrations are skipped).
90+
91+
> **Note:** When using the Tauri plugin, migrations are handled automatically via
92+
> `Builder::add_migrations()`. The plugin starts migrations at setup and waits for
93+
> completion when `load()` is called.
94+
6995
## API Reference
7096

7197
### `SqliteDatabase`
@@ -75,8 +101,9 @@ let db = SqliteDatabase::connect("example.db", Some(config)).await?;
75101
| `connect(path, config)` | Connect/create database, returns cached `Arc` if already open |
76102
| `read_pool()` | Get read-only pool reference |
77103
| `acquire_writer()` | Acquire exclusive `WriteGuard` (enables WAL on first call) |
104+
| `run_migrations(migrator)` | Run pending migrations from a `Migrator` |
78105
| `close()` | Close and remove from cache |
79-
| `close_and_remove()` | Close and delete database files (.db, .db-wal, .db-shm) |
106+
| `remove()` | Close and delete database files (.db, .db-wal, .db-shm) |
80107

81108
### `WriteGuard`
82109

crates/sqlx-sqlite-conn-mgr/src/database.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,44 @@ impl SqliteDatabase {
268268
Ok(WriteGuard::new(conn))
269269
}
270270

271+
/// Run database migrations using the provided migrator
272+
///
273+
/// This method runs all pending migrations from the provided `Migrator`.
274+
/// Migrations are executed using the write connection to ensure exclusive access.
275+
/// WAL mode is enabled automatically before running migrations.
276+
///
277+
/// SQLx tracks applied migrations in a `_sqlx_migrations` table, so calling
278+
/// this method multiple times is safe - already-applied migrations are skipped.
279+
///
280+
/// # Arguments
281+
///
282+
/// * `migrator` - A reference to a `Migrator` containing the migrations to run.
283+
/// Typically created using `sqlx::migrate!()` macro.
284+
///
285+
/// # Example
286+
///
287+
/// ```ignore
288+
/// use sqlx_sqlite_conn_mgr::SqliteDatabase;
289+
///
290+
/// // sqlx::migrate! is evaluated at compile time
291+
/// static MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!("./migrations");
292+
///
293+
/// let db = SqliteDatabase::connect("test.db", None).await?;
294+
/// db.run_migrations(&MIGRATOR).await?;
295+
/// ```
296+
pub async fn run_migrations(&self, migrator: &sqlx::migrate::Migrator) -> Result<()> {
297+
// Ensure WAL mode is initialized via acquire_writer
298+
// (WriteGuard dropped immediately, returning connection to pool)
299+
{
300+
let _writer = self.acquire_writer().await?;
301+
}
302+
303+
// Migrator acquires its own connection from the write pool
304+
migrator.run(&self.write_conn).await?;
305+
306+
Ok(())
307+
}
308+
271309
/// Close the database and clean up resources
272310
///
273311
/// This closes all connections in the pool and removes the database from the cache.

crates/sqlx-sqlite-conn-mgr/src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ pub enum Error {
1414
#[error("Sqlx error: {0}")]
1515
Sqlx(#[from] sqlx::Error),
1616

17+
/// Migration error from the sqlx migrate framework
18+
#[error("Migration error: {0}")]
19+
Migration(#[from] sqlx::migrate::MigrateError),
20+
1721
/// Database has been closed and cannot be used
1822
#[error("Database has been closed")]
1923
DatabaseClosed,

crates/sqlx-sqlite-conn-mgr/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
//! - **[`SqliteDatabase`]**: Main database type with separate read and write connection pools
99
//! - **[`SqliteDatabaseConfig`]**: Configuration for connection pool settings
1010
//! - **[`WriteGuard`]**: RAII guard ensuring exclusive write access
11+
//! - **[`Migrator`]**: Re-exported from sqlx for running database migrations
1112
//! - **[`Error`]**: Error type for database operations
1213
//!
1314
//! ## Architecture
@@ -71,5 +72,8 @@ pub use database::SqliteDatabase;
7172
pub use error::Error;
7273
pub use write_guard::WriteGuard;
7374

75+
// Re-export sqlx migrate types for convenience
76+
pub use sqlx::migrate::Migrator;
77+
7478
/// A type alias for Results with our custom Error type
7579
pub type Result<T> = std::result::Result<T, Error>;

0 commit comments

Comments
 (0)