Skip to content

Commit dabf445

Browse files
authored
Merge pull request silvermine#24 from pmorris-dev/attached_databases_conn_mgr
First-class support for attached databases in the connection manager
2 parents 9e47b40 + 5c6302a commit dabf445

8 files changed

Lines changed: 955 additions & 12 deletions

File tree

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ Or specify individual permissions:
7171
"sqlite:allow-load",
7272
"sqlite:allow-fetch-one",
7373
"sqlite:allow-fetch-all",
74-
// etc.
7574
]
7675
}
7776
```
@@ -187,14 +186,14 @@ All query methods use `$1`, `$2`, etc. syntax with `SqlValue` types:
187186
type SqlValue = string | number | boolean | null | Uint8Array
188187
```
189188
190-
| SQLite Type | TypeScript Type | Notes |
191-
| ----------- | --------------- | ----- |
192-
| TEXT | `string` | Also for DATE, TIME, DATETIME |
193-
| INTEGER | `number` | Integers preserved up to i64 range |
194-
| REAL | `number` | Floating point |
195-
| BOOLEAN | `boolean` | |
196-
| NULL | `null` | |
197-
| BLOB | `Uint8Array` | Binary data |
189+
| SQLite Type | TypeScript Type | Notes |
190+
| ----------- | --------------- | ----------------------------------- |
191+
| TEXT | `string` | Also for DATE, TIME, DATETIME |
192+
| INTEGER | `number` | Integers preserved up to i64 range |
193+
| REAL | `number` | Floating point |
194+
| BOOLEAN | `boolean` | |
195+
| NULL | `null` | |
196+
| BLOB | `Uint8Array` | Binary data |
198197
199198
> **Note:** JavaScript safely represents integers up to ±2^53 - 1. The plugin binds
200199
> integers as SQLite's INTEGER type (i64), maintaining full precision within that range.
@@ -255,7 +254,7 @@ generated ID or other computed values, then using that data in subsequent writes
255254

256255
```typescript
257256
// Begin transaction with initial insert
258-
const tx = await db.executeInterruptibleTransaction([
257+
let tx = await db.executeInterruptibleTransaction([
259258
['INSERT INTO orders (user_id, total) VALUES ($1, $2)', [userId, 0]]
260259
])
261260

@@ -267,13 +266,13 @@ const orders = await tx.read<Array<{ id: number }>>(
267266
const orderId = orders[0].id
268267

269268
// Continue transaction with the order ID
270-
const tx2 = await tx.continue([
269+
tx = await tx.continue([
271270
['INSERT INTO order_items (order_id, product_id) VALUES ($1, $2)', [orderId, productId]],
272271
['UPDATE orders SET total = $1 WHERE id = $2', [itemTotal, orderId]]
273272
])
274273

275274
// Commit the transaction
276-
await tx2.commit()
275+
await tx.commit()
277276
```
278277

279278
**Important:**
@@ -390,6 +389,7 @@ fn init_tracing() {
390389
use tracing_subscriber::{fmt, EnvFilter};
391390
let filter = EnvFilter::try_from_default_env()
392391
.unwrap_or_else(|_| EnvFilter::new("trace"));
392+
393393
fmt().with_env_filter(filter).compact().init();
394394
}
395395

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,67 @@ times is safe (already-applied migrations are skipped).
9292
> `Builder::add_migrations()`. The plugin starts migrations at setup and waits for
9393
> completion when `load()` is called.
9494
95+
### Attached Databases
96+
97+
Attach other SQLite databases to enable cross-database queries. Attachments are
98+
connection-scoped and automatically detached when the guard is dropped.
99+
100+
```rust
101+
use sqlx_sqlite_conn_mgr::{SqliteDatabase, AttachedSpec, AttachedMode, acquire_reader_with_attached};
102+
use sqlx::query;
103+
use std::sync::Arc;
104+
105+
async fn example() -> Result<(), sqlx_sqlite_conn_mgr::Error> {
106+
// You must first connect to the main database and any database(s) you intend to
107+
// attach.
108+
let main_db = SqliteDatabase::connect("main.db", None).await?;
109+
let orders_db = SqliteDatabase::connect("orders.db", None).await?;
110+
111+
// Attach orders database for read-only access
112+
let specs = vec![AttachedSpec {
113+
database: orders_db,
114+
schema_name: "orders".to_string(),
115+
mode: AttachedMode::ReadOnly,
116+
}];
117+
118+
let mut conn = acquire_reader_with_attached(&main_db, specs).await?;
119+
120+
// Cross-database query
121+
let rows = query(
122+
"SELECT u.name, o.total
123+
FROM main.users u
124+
JOIN orders.orders o ON u.id = o.user_id"
125+
)
126+
.fetch_all(&mut *conn)
127+
.await?;
128+
129+
// Attached database automatically detached when conn is dropped
130+
Ok(())
131+
}
132+
```
133+
134+
#### Attached Modes
135+
136+
* **`AttachedMode::ReadOnly`**: Attach for read access only. Can be used with
137+
both reader and writer connections.
138+
* **`AttachedMode::ReadWrite`**: Attach for write access. Can only be used with
139+
writer connections. Acquires the attached database's writer lock to ensure
140+
exclusive access.
141+
142+
#### Safety Guarantees
143+
144+
1. **Lock ordering**: Multiple attachments are acquired in alphabetical order by
145+
schema name to prevent deadlocks
146+
2. **Mode validation**: Read-only connections cannot attach databases in
147+
read-write mode (returns `CannotAttachReadWriteToReader` error)
148+
3. **Automatic cleanup**: SQLite automatically detaches databases when connections
149+
close; no manual cleanup required
150+
151+
> **Caution:** Do not bypass this API by executing raw
152+
> `ATTACH DATABASE '/path/to/db.db' AS alias` SQL commands directly. Doing so
153+
> circumvents the connection manager's policies and will result in
154+
> unpredictable behavior, including potential deadlocks.
155+
95156
## API Reference
96157

97158
### `SqliteDatabase`
@@ -110,6 +171,16 @@ times is safe (already-applied migrations are skipped).
110171
RAII guard for exclusive write access. Derefs to `SqliteConnection`. Connection
111172
returned to pool on drop.
112173

174+
### Attached Database Functions
175+
176+
| Function | Description |
177+
| -------- | ----------- |
178+
| `acquire_reader_with_attached(db, specs)` | Acquire read connection with attached databases |
179+
| `acquire_writer_with_attached(db, specs)` | Acquire writer connection with attached databases |
180+
181+
Returns `AttachedConnection` or `AttachedWriteGuard` respectively. Both guards
182+
deref to `SqliteConnection` and automatically detach databases on drop.
183+
113184
## Design Details
114185

115186
### Read-Only Pool

0 commit comments

Comments
 (0)