Skip to content

Commit 1cb7b43

Browse files
authored
Merge pull request #29 from pmorris-dev/use_transactions_api_from_rust
Support the transaction API from Rust
2 parents ebd3e52 + 55711be commit 1cb7b43

11 files changed

Lines changed: 1161 additions & 247 deletions

File tree

README.md

Lines changed: 202 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ const db = await Database.load('mydb.db')
140140
// Get all migration events (including ones emitted before listener could be registered)
141141
const events = await db.getMigrationEvents()
142142
for (const event of events) {
143-
console.log(`${event.status}: ${event.dbPath}`)
143+
console.info(`${event.status}: ${event.dbPath}`)
144144
if (event.status === 'failed') {
145145
console.error(`Migration error: ${event.error}`)
146146
}
@@ -156,7 +156,7 @@ import type { MigrationEvent } from '@silvermine/tauri-plugin-sqlite'
156156

157157
await listen<MigrationEvent>('sqlite:migration', (event) => {
158158
const { dbPath, status, migrationCount, error } = event.payload
159-
// status: 'running' | 'completed' | 'failed'
159+
console.info(`Migration ${status} for ${dbPath}: ${migrationCount} migrations`, error)
160160
})
161161
```
162162

@@ -166,16 +166,16 @@ await listen<MigrationEvent>('sqlite:migration', (event) => {
166166
import Database from '@silvermine/tauri-plugin-sqlite'
167167

168168
// Path is relative to app config directory (no sqlite: prefix needed)
169-
const db = await Database.load('mydb.db')
169+
let db = await Database.load('mydb.db')
170170

171171
// With custom configuration
172-
const db = await Database.load('mydb.db', {
172+
db = await Database.load('mydb.db', {
173173
maxReadConnections: 10, // default: 6
174174
idleTimeoutSecs: 60 // default: 30
175175
})
176176

177177
// Lazy initialization (connects on first query)
178-
const db = Database.get('mydb.db')
178+
db = Database.get('mydb.db')
179179
```
180180

181181
### Parameter Binding
@@ -211,7 +211,7 @@ const result = await db.execute(
211211
'INSERT INTO users (name, email) VALUES ($1, $2)',
212212
['Alice', 'alice@example.com']
213213
)
214-
console.log(result.rowsAffected, result.lastInsertId)
214+
console.info(`Inserted ${result.rowsAffected} row(s), ID: ${result.lastInsertId}`)
215215
```
216216

217217
### Read Operations
@@ -224,12 +224,16 @@ const users = await db.fetchAll<User[]>(
224224
'SELECT * FROM users WHERE email LIKE $1',
225225
['%@example.com']
226226
)
227+
console.info(`Found ${users.length} users`)
227228

228229
// Single row (returns undefined if not found, throws if multiple rows)
229230
const user = await db.fetchOne<User>(
230231
'SELECT * FROM users WHERE id = $1',
231232
[42]
232233
)
234+
if (user) {
235+
console.info(`Found user: ${user.name}`)
236+
}
233237
```
234238

235239
### Transactions
@@ -242,6 +246,7 @@ const results = await db.executeTransaction([
242246
['UPDATE accounts SET balance = balance + $1 WHERE id = $2', [100, 2]],
243247
['INSERT INTO transfers (from_id, to_id, amount) VALUES ($1, $2, $3)', [1, 2, 100]]
244248
])
249+
console.info(`Transaction completed: ${results.length} statements executed`)
245250
```
246251

247252
Transactions use `BEGIN IMMEDIATE`, commit on success, and rollback on any failure.
@@ -253,8 +258,13 @@ decide how to proceed.** For example, inserting a record, reading back its
253258
generated ID or other computed values, then using that data in subsequent writes.
254259

255260
```typescript
261+
// Assuming userId, productId, itemTotal are defined in your application context
262+
const userId = 123
263+
const productId = 456
264+
const itemTotal = 99.99
265+
256266
// Begin transaction with initial insert
257-
let tx = await db.executeInterruptibleTransaction([
267+
let tx = await db.beginInterruptibleTransaction([
258268
['INSERT INTO orders (user_id, total) VALUES ($1, $2)', [userId, 0]]
259269
])
260270

@@ -266,7 +276,7 @@ const orders = await tx.read<Array<{ id: number }>>(
266276
const orderId = orders[0].id
267277

268278
// Continue transaction with the order ID
269-
tx = await tx.continue([
279+
tx = await tx.continueWith([
270280
['INSERT INTO order_items (order_id, product_id) VALUES ($1, $2)', [orderId, productId]],
271281
['UPDATE orders SET total = $1 WHERE id = $2', [itemTotal, orderId]]
272282
])
@@ -311,6 +321,7 @@ const results = await db.fetchAll(
311321
mode: 'readOnly'
312322
}
313323
])
324+
console.info(`Found ${results.length} results from cross-database query`)
314325

315326
// Update main database using data from attached database
316327
await db.execute(
@@ -325,6 +336,10 @@ await db.execute(
325336
])
326337

327338
// Atomic writes across multiple databases
339+
// Assuming userId and total are defined in your application context
340+
const userId = 123
341+
const total = 99.99
342+
328343
await db.executeTransaction([
329344
['INSERT INTO main.orders (user_id, total) VALUES ($1, $2)', [userId, total]],
330345
['UPDATE stats.order_count SET count = count + 1', []]
@@ -397,7 +412,7 @@ await db.remove() // Close and DELETE database file(s) - irreversible!
397412
| ------ | ----------- |
398413
| `execute(query, values?)` | Execute write query, returns `{ rowsAffected, lastInsertId }` |
399414
| `executeTransaction(statements)` | Execute statements atomically (use for batch writes) |
400-
| `executeInterruptibleTransaction(statements)` | Begin interruptible transaction, returns `InterruptibleTransaction` |
415+
| `beginInterruptibleTransaction(statements)` | Begin interruptible transaction, returns `InterruptibleTransaction` |
401416
| `fetchAll<T>(query, values?)` | Execute SELECT, return all rows |
402417
| `fetchOne<T>(query, values?)` | Execute SELECT, return single row or `undefined` |
403418
| `close()` | Close connection, returns `true` if was loaded |
@@ -418,7 +433,7 @@ return builders that are directly awaitable and support method chaining:
418433
| Method | Description |
419434
| ------ | ----------- |
420435
| `read<T>(query, values?)` | Read uncommitted data within this transaction |
421-
| `continue(statements)` | Execute additional statements, returns new `InterruptibleTransaction` |
436+
| `continueWith(statements)` | Execute additional statements, returns new `InterruptibleTransaction` |
422437
| `commit()` | Commit transaction and release write lock |
423438
| `rollback()` | Rollback transaction and release write lock |
424439

@@ -447,6 +462,182 @@ interface SqliteError {
447462
}
448463
```
449464

465+
## Rust-Only API
466+
467+
For Rust code that needs direct database access without going through Tauri commands,
468+
use `DatabaseWrapper`.
469+
470+
### Setup (Rust)
471+
472+
```rust
473+
use tauri_plugin_sqlite::DatabaseWrapper;
474+
use std::path::PathBuf;
475+
476+
// Load a database
477+
let mut db = DatabaseWrapper::load(PathBuf::from("/path/to/mydb.db"), None).await?;
478+
479+
// With custom configuration
480+
use tauri_plugin_sqlite::CustomConfig;
481+
let config = CustomConfig {
482+
max_read_connections: Some(10),
483+
idle_timeout_secs: Some(60),
484+
};
485+
db = DatabaseWrapper::load(PathBuf::from("/path/to/mydb.db"), Some(config)).await?;
486+
```
487+
488+
### Basic Operations
489+
490+
```rust
491+
use serde_json::json;
492+
493+
// Write operations
494+
let result = db.execute(
495+
"INSERT INTO users (name, email) VALUES (?, ?)".into(),
496+
vec![json!("Alice"), json!("alice@example.com")]
497+
).await?;
498+
499+
println!("Inserted row {}", result.last_insert_id);
500+
501+
// Read multiple rows
502+
let users = db.fetch_all(
503+
"SELECT * FROM users WHERE active = ?".into(),
504+
vec![json!(true)]
505+
).await?;
506+
507+
println!("Found {} users", users.len());
508+
509+
// Read single row
510+
let user = db.fetch_one(
511+
"SELECT * FROM users WHERE id = ?".into(),
512+
vec![json!(42)]
513+
).await?;
514+
515+
if let Some(user_data) = user {
516+
println!("Found user: {:?}", user_data);
517+
}
518+
```
519+
520+
### Simple Transactions
521+
522+
Use `execute_transaction()` for atomic execution of multiple statements:
523+
524+
```rust
525+
let results = db.execute_transaction(vec![
526+
("UPDATE accounts SET balance = balance - ? WHERE id = ?", vec![json!(100), json!(1)]),
527+
("UPDATE accounts SET balance = balance + ? WHERE id = ?", vec![json!(100), json!(2)]),
528+
("INSERT INTO transfers (from_id, to_id, amount) VALUES (?, ?, ?)", vec![json!(1), json!(2), json!(100)]),
529+
]).await?;
530+
531+
println!("Transaction completed: {} statements executed", results.len());
532+
533+
// Returns Vec<WriteQueryResult> on success, rolls back on any failure
534+
```
535+
536+
### Interruptible Transactions (Rust)
537+
538+
For transactions that need to read data mid-transaction:
539+
540+
```rust
541+
// Assuming user_id, product_id, item_total are defined in your application context
542+
let user_id = 123;
543+
let product_id = 456;
544+
let item_total = 99.99;
545+
546+
// Begin transaction with initial statements
547+
let mut tx = db.begin_interruptible_transaction()
548+
.execute(vec![
549+
("INSERT INTO orders (user_id, total) VALUES (?, ?)", vec![json!(user_id), json!(0)]),
550+
])
551+
.await?;
552+
553+
// Read uncommitted data
554+
let orders = tx.read(
555+
"SELECT id FROM orders WHERE user_id = ? ORDER BY id DESC LIMIT 1".into(),
556+
vec![json!(user_id)]
557+
).await?;
558+
559+
let order_id = orders[0].get("id").unwrap().as_i64().unwrap();
560+
561+
// Continue with more statements
562+
tx.continue_with(vec![
563+
("INSERT INTO order_items (order_id, product_id) VALUES (?, ?)", vec![json!(order_id), json!(product_id)]),
564+
("UPDATE orders SET total = ? WHERE id = ?", vec![json!(item_total), json!(order_id)]),
565+
]).await?;
566+
567+
// Commit (or rollback)
568+
tx.commit().await?;
569+
// tx.rollback().await?; // Alternative: rollback changes
570+
```
571+
572+
### Cross-Database Operations
573+
574+
Attach other databases for cross-database queries:
575+
576+
```rust
577+
use tauri_plugin_sqlite::AttachedDatabaseSpec;
578+
579+
// Simple transaction with attached database
580+
let results = db.execute_transaction(vec![
581+
("INSERT INTO main.orders (user_id) VALUES (?)", vec![json!(1)]),
582+
("UPDATE stats.order_count SET count = count + 1", vec![]),
583+
])
584+
.attach(vec![AttachedDatabaseSpec {
585+
database_path: "stats.db".into(),
586+
schema_name: "stats".into(),
587+
mode: tauri_plugin_sqlite::AttachedDatabaseMode::ReadWrite,
588+
}])
589+
.await?;
590+
591+
println!("Cross-database transaction completed: {} statements", results.len());
592+
593+
// Interruptible transaction with attached database
594+
// Assuming product_id is defined in your application context
595+
let product_id = 789;
596+
597+
let _tx = db.begin_interruptible_transaction()
598+
.attach(vec![AttachedDatabaseSpec {
599+
database_path: "inventory.db".into(),
600+
schema_name: "inv".into(),
601+
mode: tauri_plugin_sqlite::AttachedDatabaseMode::ReadWrite,
602+
}])
603+
.execute(vec![
604+
("UPDATE inv.stock SET quantity = quantity - ? WHERE product_id = ?", vec![json!(1), json!(product_id)]),
605+
])
606+
.await?;
607+
// Continue with transaction operations...
608+
```
609+
610+
### Cleanup
611+
612+
```rust
613+
db.close().await?; // Close connection
614+
db.remove().await?; // Close and DELETE database file(s)
615+
```
616+
617+
### Rust API Reference
618+
619+
#### DatabaseWrapper Methods
620+
621+
| Method | Description |
622+
| ------ | ----------- |
623+
| `load(path, config?)` | Load database, returns `DatabaseWrapper` |
624+
| `execute(query, values)` | Execute write query |
625+
| `execute_transaction(statements)` | Execute statements atomically (builder) |
626+
| `begin_interruptible_transaction()` | Begin interruptible transaction (builder) |
627+
| `fetch_all(query, values)` | Fetch all rows |
628+
| `fetch_one(query, values)` | Fetch single row |
629+
| `close()` | Close connection |
630+
| `remove()` | Close and delete database file(s) |
631+
632+
#### InterruptibleTransaction Methods (Rust)
633+
634+
| Method | Description |
635+
| ------ | ----------- |
636+
| `read(query, values)` | Read uncommitted data within transaction |
637+
| `continue_with(statements)` | Execute additional statements |
638+
| `commit()` | Commit and release write lock |
639+
| `rollback()` | Rollback and release write lock |
640+
450641
## Tracing and Logging
451642

452643
The plugin uses [`tracing`](https://crates.io/crates/tracing) with
@@ -476,7 +667,7 @@ fn init_tracing() {}
476667
fn main() {
477668
init_tracing();
478669
tauri::Builder::default()
479-
.plugin(tauri_plugin_sqlite::init())
670+
.plugin(tauri_plugin_sqlite::Builder::new().build())
480671
.run(tauri::generate_context!())
481672
.expect("error while running tauri application");
482673
}

0 commit comments

Comments
 (0)