consolidated skills
This commit is contained in:
parent
6ed2401be1
commit
2ea8a63ae4
|
|
@ -6,12 +6,14 @@ Guide to flexible connection management.
|
|||
- "connection provider", "conn provider"
|
||||
- "borrow connection", "pool connection"
|
||||
- "lazy connection", "connection management"
|
||||
- "transaction provider", "use transaction"
|
||||
|
||||
## Overview
|
||||
|
||||
`ConnProvider` enables flexible connection handling:
|
||||
- **Borrowed**: Use an existing connection reference
|
||||
- **Owned**: Lazily acquire from pool on first use
|
||||
- **Transaction**: Use a transaction reference (all operations participate in the transaction)
|
||||
|
||||
## Enum Variants
|
||||
|
||||
|
|
@ -26,6 +28,10 @@ pub enum ConnProvider<'a> {
|
|||
pool: Pool,
|
||||
conn: Option<PoolConnection<DB>>,
|
||||
},
|
||||
/// Reference to a transaction
|
||||
Transaction {
|
||||
tx: &'a mut Transaction<'static, DB>,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -45,15 +51,29 @@ let mut provider = ConnProvider::from_pool(pool.clone());
|
|||
// Connection acquired on first get_conn() call
|
||||
```
|
||||
|
||||
### from_tx
|
||||
Use a transaction (all operations participate in the transaction):
|
||||
```rust
|
||||
let mut tx = pool.begin().await?;
|
||||
let mut provider = ConnProvider::from_tx(&mut tx);
|
||||
|
||||
// All operations through provider use the transaction
|
||||
do_work(&mut provider).await?;
|
||||
|
||||
// You must commit/rollback the transaction yourself
|
||||
tx.commit().await?;
|
||||
```
|
||||
|
||||
## Getting the Connection
|
||||
|
||||
```rust
|
||||
let conn = provider.get_conn().await?;
|
||||
// Returns &mut PoolConnection<DB>
|
||||
// Returns &mut <DB>Connection (e.g., &mut MySqlConnection)
|
||||
```
|
||||
|
||||
- **Borrowed**: Returns reference immediately
|
||||
- **Borrowed**: Returns underlying connection immediately
|
||||
- **Owned**: Acquires on first call, returns same connection on subsequent calls
|
||||
- **Transaction**: Returns transaction's underlying connection
|
||||
|
||||
## Use Cases
|
||||
|
||||
|
|
@ -105,15 +125,37 @@ let mut conn = pool.acquire().await?;
|
|||
do_database_work(&mut ConnProvider::from_ref(&mut conn)).await?;
|
||||
|
||||
// Call with pool
|
||||
do_database_work(&mut ConnProvider::from_pool(pool)).await?;
|
||||
do_database_work(&mut ConnProvider::from_pool(pool.clone())).await?;
|
||||
|
||||
// Call with transaction
|
||||
let mut tx = pool.begin().await?;
|
||||
do_database_work(&mut ConnProvider::from_tx(&mut tx)).await?;
|
||||
tx.commit().await?;
|
||||
```
|
||||
|
||||
### Transaction-like Patterns
|
||||
### Using Transactions
|
||||
```rust
|
||||
async fn transactional_operation(pool: MySqlPool) -> Result<()> {
|
||||
let mut tx = pool.begin().await?;
|
||||
let mut provider = ConnProvider::from_tx(&mut tx);
|
||||
|
||||
// All operations participate in the transaction
|
||||
step_1(&mut provider).await?;
|
||||
step_2(&mut provider).await?;
|
||||
step_3(&mut provider).await?;
|
||||
|
||||
// Commit (or rollback on error)
|
||||
tx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Same Connection Pattern
|
||||
```rust
|
||||
async fn multi_step_operation(pool: MySqlPool) -> Result<()> {
|
||||
let mut provider = ConnProvider::from_pool(pool);
|
||||
|
||||
// All operations use same connection
|
||||
// All operations use same connection (but no transaction)
|
||||
step_1(&mut provider).await?;
|
||||
step_2(&mut provider).await?;
|
||||
step_3(&mut provider).await?;
|
||||
|
|
@ -127,11 +169,13 @@ async fn multi_step_operation(pool: MySqlPool) -> Result<()> {
|
|||
|
||||
The concrete types depend on the enabled feature:
|
||||
|
||||
| Feature | Pool Type | Connection Type |
|
||||
|---------|-----------|-----------------|
|
||||
| `mysql` | `MySqlPool` | `PoolConnection<MySql>` |
|
||||
| `postgres` | `PgPool` | `PoolConnection<Postgres>` |
|
||||
| `sqlite` | `SqlitePool` | `PoolConnection<Sqlite>` |
|
||||
| Feature | Pool Type | Connection Type | Transaction Type |
|
||||
|---------|-----------|-----------------|------------------|
|
||||
| `mysql` | `MySqlPool` | `MySqlConnection` | `Transaction<'static, MySql>` |
|
||||
| `postgres` | `PgPool` | `PgConnection` | `Transaction<'static, Postgres>` |
|
||||
| `sqlite` | `SqlitePool` | `SqliteConnection` | `Transaction<'static, Sqlite>` |
|
||||
|
||||
Note: `get_conn()` returns `&mut <DB>Connection` (the underlying connection type).
|
||||
|
||||
## Example: Service Layer
|
||||
|
||||
|
|
@ -175,28 +219,28 @@ let user_id = UserService::create_with_profile(&mut provider, "Alice", "Hello!")
|
|||
## Connection Lifecycle
|
||||
|
||||
```
|
||||
from_pool(pool) from_ref(&mut conn)
|
||||
│ │
|
||||
▼ ▼
|
||||
Owned { Borrowed {
|
||||
pool, conn: &mut PoolConnection
|
||||
conn: None }
|
||||
} │
|
||||
│ │
|
||||
│ get_conn() │ get_conn()
|
||||
▼ ▼
|
||||
pool.acquire() return conn
|
||||
│ │
|
||||
▼ │
|
||||
Owned { │
|
||||
pool, │
|
||||
conn: Some(acquired) │
|
||||
} │
|
||||
│ │
|
||||
│ get_conn() (subsequent) │
|
||||
▼ │
|
||||
return &mut acquired │
|
||||
│ │
|
||||
▼ ▼
|
||||
Drop: conn returned Drop: nothing (borrowed)
|
||||
from_pool(pool) from_ref(&mut conn) from_tx(&mut tx)
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
Owned { Borrowed { Transaction {
|
||||
pool, conn: &mut tx: &mut
|
||||
conn: None PoolConnection Transaction
|
||||
} } }
|
||||
│ │ │
|
||||
│ get_conn() │ get_conn() │ get_conn()
|
||||
▼ ▼ ▼
|
||||
pool.acquire() deref conn deref tx
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
Owned { return &mut return &mut
|
||||
pool, Connection Connection
|
||||
conn: Some(acquired) │ │
|
||||
} │ │
|
||||
│ │ │
|
||||
│ get_conn() (subsequent) │ │
|
||||
▼ ▼ ▼
|
||||
return &mut conn Drop: nothing Drop: nothing
|
||||
│ (borrowed) (tx managed
|
||||
▼ externally)
|
||||
Drop: conn returned
|
||||
```
|
||||
|
|
@ -59,11 +59,13 @@ large_count: i64,
|
|||
### #[soft_delete]
|
||||
```rust
|
||||
#[soft_delete]
|
||||
is_deleted: bool,
|
||||
is_active: bool,
|
||||
```
|
||||
- Enables soft delete functionality
|
||||
- Generates `delete()`, `restore()`, `hard_delete()` methods
|
||||
- Generates `soft_delete()`, `soft_delete_by_{pk}()`, `restore()`, `restore_by_{pk}()` methods
|
||||
- Field must be `bool` type
|
||||
- Convention: `is_active` fields are auto-detected (FALSE = deleted)
|
||||
- `#[soft_delete]` attribute means field is FALSE when entity is deleted
|
||||
|
||||
### #[created_at]
|
||||
```rust
|
||||
|
|
@ -194,17 +196,20 @@ pub async fn get_version(executor, pk: &PkType) -> Result<Option<VersionType>, E
|
|||
pub async fn get_versions(executor, pks: &[PkType]) -> Result<HashMap<PkType, VersionType>, Error>
|
||||
```
|
||||
|
||||
### Soft Delete Methods (if #[soft_delete] exists)
|
||||
### Hard Delete (always generated)
|
||||
```rust
|
||||
// Soft delete - sets field to true
|
||||
pub async fn delete(&self, executor) -> Result<(), Error>
|
||||
pub async fn delete_by_id(executor, id: &Uuid) -> Result<(), Error>
|
||||
|
||||
// Hard delete - permanently removes row
|
||||
// Permanently removes row from database
|
||||
pub async fn hard_delete(&self, executor) -> Result<(), Error>
|
||||
pub async fn hard_delete_by_id(executor, id: &Uuid) -> Result<(), Error>
|
||||
```
|
||||
|
||||
// Restore - sets field to false
|
||||
### Soft Delete Methods (if `is_active` field or `#[soft_delete]` exists)
|
||||
```rust
|
||||
// Soft delete - marks as deleted (is_active = FALSE)
|
||||
pub async fn soft_delete(&self, executor) -> Result<(), Error>
|
||||
pub async fn soft_delete_by_id(executor, id: &Uuid) -> Result<(), Error>
|
||||
|
||||
// Restore - marks as active (is_active = TRUE)
|
||||
pub async fn restore(&self, executor) -> Result<(), Error>
|
||||
pub async fn restore_by_id(executor, id: &Uuid) -> Result<(), Error>
|
||||
|
||||
|
|
@ -122,28 +122,30 @@ sqlx-record = { version = "0.3", features = ["mysql", "derive"] }
|
|||
# Optional: "derive", "static-validation"
|
||||
```
|
||||
|
||||
## Soft Delete, Timestamps, Batch Operations
|
||||
## Delete, Soft Delete, Timestamps, Batch Operations
|
||||
|
||||
```rust
|
||||
#[derive(Entity, FromRow)]
|
||||
struct User {
|
||||
#[primary_key] id: Uuid,
|
||||
name: String,
|
||||
is_active: bool, // Auto-detected for soft delete (is_active = FALSE when deleted)
|
||||
|
||||
#[soft_delete] // Enables delete/restore/hard_delete
|
||||
is_deleted: bool,
|
||||
|
||||
#[created_at] // Auto-set on insert
|
||||
#[created_at] // Auto-set on insert
|
||||
created_at: i64,
|
||||
|
||||
#[updated_at] // Auto-set on update
|
||||
#[updated_at] // Auto-set on update
|
||||
updated_at: i64,
|
||||
}
|
||||
|
||||
// Soft delete
|
||||
user.delete(&pool).await?; // is_deleted = true
|
||||
user.restore(&pool).await?; // is_deleted = false
|
||||
user.hard_delete(&pool).await?; // DELETE FROM
|
||||
// Hard delete (always available on all entities)
|
||||
user.hard_delete(&pool).await?; // DELETE FROM
|
||||
User::hard_delete_by_id(&pool, &id).await?;
|
||||
|
||||
// Soft delete (when is_active or #[soft_delete] field exists)
|
||||
user.soft_delete(&pool).await?; // is_active = false
|
||||
User::soft_delete_by_id(&pool, &id).await?;
|
||||
user.restore(&pool).await?; // is_active = true
|
||||
|
||||
// Batch insert
|
||||
User::insert_many(&pool, &users).await?;
|
||||
|
|
@ -206,11 +208,23 @@ User::update_by_id(&pool, &id,
|
|||
## ConnProvider (Flexible Connections)
|
||||
|
||||
```rust
|
||||
use sqlx_record::ConnProvider;
|
||||
use sqlx_record::prelude::ConnProvider;
|
||||
|
||||
// Borrowed or owned pool connections
|
||||
let conn = ConnProvider::Borrowed(&pool);
|
||||
let users = User::find(&*conn, filters![], None).await?;
|
||||
// From borrowed connection
|
||||
let mut conn = pool.acquire().await?;
|
||||
let mut provider = ConnProvider::from_ref(&mut conn);
|
||||
|
||||
// From pool (lazy acquisition)
|
||||
let mut provider = ConnProvider::from_pool(pool.clone());
|
||||
|
||||
// From transaction (operations participate in the transaction)
|
||||
let mut tx = pool.begin().await?;
|
||||
let mut provider = ConnProvider::from_tx(&mut tx);
|
||||
// ... use provider ...
|
||||
tx.commit().await?;
|
||||
|
||||
// Get underlying connection
|
||||
let conn = provider.get_conn().await?;
|
||||
```
|
||||
|
||||
## Database Differences
|
||||
|
|
@ -1,61 +1,17 @@
|
|||
# sqlx-record Soft Delete Skill
|
||||
# sqlx-record Delete & Soft Delete Skill
|
||||
|
||||
Guide to soft delete functionality with #[soft_delete] attribute.
|
||||
Guide to hard delete and soft delete functionality.
|
||||
|
||||
## Triggers
|
||||
- "soft delete", "soft-delete"
|
||||
- "is_deleted", "deleted"
|
||||
- "restore", "undelete"
|
||||
- "hard delete", "permanent delete"
|
||||
- "is_active", "is_deleted", "deleted"
|
||||
- "restore", "undelete"
|
||||
- "delete_by_id", "hard_delete_by_id"
|
||||
|
||||
## Overview
|
||||
## Hard Delete (Always Generated)
|
||||
|
||||
Soft delete allows marking records as deleted without removing them from the database. This enables:
|
||||
- Recovery of accidentally deleted data
|
||||
- Audit trails of deletions
|
||||
- Referential integrity preservation
|
||||
|
||||
## Enabling Soft Delete
|
||||
|
||||
Add `#[soft_delete]` to a boolean field:
|
||||
|
||||
```rust
|
||||
use sqlx_record::prelude::*;
|
||||
|
||||
#[derive(Entity, FromRow)]
|
||||
#[table_name = "users"]
|
||||
struct User {
|
||||
#[primary_key]
|
||||
id: Uuid,
|
||||
name: String,
|
||||
|
||||
#[soft_delete]
|
||||
is_deleted: bool, // Must be bool type
|
||||
}
|
||||
```
|
||||
|
||||
Auto-detection: Fields named `is_deleted` or `deleted` with `bool` type are automatically treated as soft delete fields even without the attribute.
|
||||
|
||||
## Generated Methods
|
||||
|
||||
### delete() / delete_by_{pk}()
|
||||
Sets the soft delete field to `true`:
|
||||
|
||||
```rust
|
||||
// Instance method
|
||||
user.delete(&pool).await?;
|
||||
|
||||
// Static method by primary key
|
||||
User::delete_by_id(&pool, &user_id).await?;
|
||||
```
|
||||
|
||||
**SQL generated:**
|
||||
```sql
|
||||
UPDATE users SET is_deleted = TRUE WHERE id = ?
|
||||
```
|
||||
|
||||
### hard_delete() / hard_delete_by_{pk}()
|
||||
Permanently removes the row:
|
||||
Every Entity gets `hard_delete()` and `hard_delete_by_{pk}()` methods. No configuration needed.
|
||||
|
||||
```rust
|
||||
// Instance method
|
||||
|
|
@ -70,8 +26,73 @@ User::hard_delete_by_id(&pool, &user_id).await?;
|
|||
DELETE FROM users WHERE id = ?
|
||||
```
|
||||
|
||||
## Soft Delete
|
||||
|
||||
Marks records as deleted without removing them from the database. This enables:
|
||||
- Recovery of accidentally deleted data
|
||||
- Audit trails of deletions
|
||||
- Referential integrity preservation
|
||||
|
||||
### Enabling Soft Delete
|
||||
|
||||
**Preferred: `is_active` convention** (auto-detected, no attribute needed):
|
||||
|
||||
```rust
|
||||
use sqlx_record::prelude::*;
|
||||
|
||||
#[derive(Entity, FromRow)]
|
||||
#[table_name = "users"]
|
||||
struct User {
|
||||
#[primary_key]
|
||||
id: Uuid,
|
||||
name: String,
|
||||
is_active: bool, // Auto-detected: FALSE = deleted, TRUE = active
|
||||
}
|
||||
```
|
||||
|
||||
**Alternative: `#[soft_delete]` attribute** on any bool field:
|
||||
|
||||
```rust
|
||||
#[derive(Entity, FromRow)]
|
||||
#[table_name = "users"]
|
||||
struct User {
|
||||
#[primary_key]
|
||||
id: Uuid,
|
||||
name: String,
|
||||
|
||||
#[soft_delete] // Field will be FALSE when deleted
|
||||
is_active: bool,
|
||||
}
|
||||
```
|
||||
|
||||
**Legacy: `is_deleted`/`deleted` fields** are also auto-detected (TRUE = deleted).
|
||||
|
||||
### Detection Priority
|
||||
|
||||
1. Field with `#[soft_delete]` attribute (FALSE = deleted)
|
||||
2. Field named `is_active` with bool type (FALSE = deleted)
|
||||
3. Field named `is_deleted` or `deleted` with bool type (TRUE = deleted)
|
||||
|
||||
## Generated Methods
|
||||
|
||||
### soft_delete() / soft_delete_by_{pk}()
|
||||
Marks the record as deleted:
|
||||
|
||||
```rust
|
||||
// Instance method
|
||||
user.soft_delete(&pool).await?;
|
||||
|
||||
// Static method by primary key
|
||||
User::soft_delete_by_id(&pool, &user_id).await?;
|
||||
```
|
||||
|
||||
**SQL generated (is_active convention):**
|
||||
```sql
|
||||
UPDATE users SET is_active = FALSE WHERE id = ?
|
||||
```
|
||||
|
||||
### restore() / restore_by_{pk}()
|
||||
Sets the soft delete field to `false`:
|
||||
Restores a soft-deleted record:
|
||||
|
||||
```rust
|
||||
// Instance method
|
||||
|
|
@ -81,16 +102,16 @@ user.restore(&pool).await?;
|
|||
User::restore_by_id(&pool, &user_id).await?;
|
||||
```
|
||||
|
||||
**SQL generated:**
|
||||
**SQL generated (is_active convention):**
|
||||
```sql
|
||||
UPDATE users SET is_deleted = FALSE WHERE id = ?
|
||||
UPDATE users SET is_active = TRUE WHERE id = ?
|
||||
```
|
||||
|
||||
### soft_delete_field()
|
||||
Returns the field name:
|
||||
|
||||
```rust
|
||||
let field = User::soft_delete_field(); // "is_deleted"
|
||||
let field = User::soft_delete_field(); // "is_active"
|
||||
```
|
||||
|
||||
## Filtering Deleted Records
|
||||
|
|
@ -98,11 +119,11 @@ let field = User::soft_delete_field(); // "is_deleted"
|
|||
Soft delete does **NOT** automatically filter `find()` queries. You must add the filter manually:
|
||||
|
||||
```rust
|
||||
// Include only non-deleted
|
||||
let users = User::find(&pool, filters![("is_deleted", false)], None).await?;
|
||||
// Include only active (non-deleted)
|
||||
let users = User::find(&pool, filters![("is_active", true)], None).await?;
|
||||
|
||||
// Include only deleted (trash view)
|
||||
let deleted = User::find(&pool, filters![("is_deleted", true)], None).await?;
|
||||
let deleted = User::find(&pool, filters![("is_active", false)], None).await?;
|
||||
|
||||
// Include all records
|
||||
let all = User::find(&pool, filters![], None).await?;
|
||||
|
|
@ -119,7 +140,7 @@ impl User {
|
|||
mut filters: Vec<Filter<'_>>,
|
||||
index: Option<&str>
|
||||
) -> Result<Vec<Self>, sqlx::Error> {
|
||||
filters.push(Filter::Equal("is_deleted", false.into()));
|
||||
filters.push(Filter::Equal("is_active", true.into()));
|
||||
Self::find(pool, filters, index).await
|
||||
}
|
||||
}
|
||||
|
|
@ -130,28 +151,28 @@ let users = User::find_active(&pool, filters![("role", "admin")], None).await?;
|
|||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Soft Delete Flow
|
||||
### Basic Flow
|
||||
|
||||
```rust
|
||||
// Create user
|
||||
let user = User {
|
||||
id: new_uuid(),
|
||||
name: "Alice".into(),
|
||||
is_deleted: false,
|
||||
is_active: true,
|
||||
};
|
||||
user.insert(&pool).await?;
|
||||
|
||||
// Soft delete
|
||||
user.delete(&pool).await?;
|
||||
// user still exists in DB with is_deleted = true
|
||||
user.soft_delete(&pool).await?;
|
||||
// user still exists in DB with is_active = false
|
||||
|
||||
// Find won't return deleted users (with proper filter)
|
||||
let users = User::find(&pool, filters![("is_deleted", false)], None).await?;
|
||||
let users = User::find(&pool, filters![("is_active", true)], None).await?;
|
||||
// Alice not in results
|
||||
|
||||
// Restore
|
||||
User::restore_by_id(&pool, &user.id).await?;
|
||||
// user.is_deleted = false again
|
||||
// user.is_active = true again
|
||||
|
||||
// Hard delete (permanent)
|
||||
User::hard_delete_by_id(&pool, &user.id).await?;
|
||||
|
|
@ -170,13 +191,13 @@ async fn soft_delete_with_audit(
|
|||
) -> Result<(), sqlx::Error> {
|
||||
transaction!(&pool, |tx| {
|
||||
// Soft delete the user
|
||||
User::delete_by_id(&mut *tx, user_id).await?;
|
||||
User::soft_delete_by_id(&mut *tx, user_id).await?;
|
||||
|
||||
// Record the deletion
|
||||
let change = EntityChange {
|
||||
id: new_uuid(),
|
||||
entity_id: *user_id,
|
||||
action: "delete".into(),
|
||||
action: "soft_delete".into(),
|
||||
changed_at: chrono::Utc::now().timestamp_millis(),
|
||||
actor_id: *actor_id,
|
||||
session_id: Uuid::nil(),
|
||||
|
|
@ -198,11 +219,11 @@ async fn delete_user_cascade(pool: &Pool, user_id: &Uuid) -> Result<(), sqlx::Er
|
|||
// Soft delete user's orders
|
||||
let orders = Order::find(&mut *tx, filters![("user_id", user_id)], None).await?;
|
||||
for order in orders {
|
||||
order.delete(&mut *tx).await?;
|
||||
order.soft_delete(&mut *tx).await?;
|
||||
}
|
||||
|
||||
// Soft delete user
|
||||
User::delete_by_id(&mut *tx, user_id).await?;
|
||||
User::soft_delete_by_id(&mut *tx, user_id).await?;
|
||||
|
||||
Ok::<_, sqlx::Error>(())
|
||||
}).await
|
||||
|
|
@ -215,27 +236,28 @@ Recommended column definition:
|
|||
|
||||
```sql
|
||||
-- MySQL
|
||||
is_deleted BOOLEAN NOT NULL DEFAULT FALSE
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE
|
||||
|
||||
-- PostgreSQL
|
||||
is_deleted BOOLEAN NOT NULL DEFAULT FALSE
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE
|
||||
|
||||
-- SQLite
|
||||
is_deleted INTEGER NOT NULL DEFAULT 0 -- 0=false, 1=true
|
||||
is_active INTEGER NOT NULL DEFAULT 1 -- 1=true, 0=false
|
||||
```
|
||||
|
||||
Add an index for efficient filtering:
|
||||
|
||||
```sql
|
||||
CREATE INDEX idx_users_is_deleted ON users (is_deleted);
|
||||
CREATE INDEX idx_users_is_active ON users (is_active);
|
||||
|
||||
-- Or composite index for common queries
|
||||
CREATE INDEX idx_users_active_name ON users (is_deleted, name);
|
||||
CREATE INDEX idx_users_active_name ON users (is_active, name);
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Soft delete field must be `bool` type
|
||||
- The field is included in UpdateForm (can be manually toggled)
|
||||
- `hard_delete()` / `hard_delete_by_{pk}()` are always available, even on entities with soft delete
|
||||
- Consider adding `deleted_at: Option<i64>` for deletion timestamps
|
||||
- For complex filtering, consider database views
|
||||
23
CLAUDE.md
23
CLAUDE.md
|
|
@ -31,13 +31,14 @@ sqlx-record/
|
|||
│ └── src/main.rs
|
||||
├── mcp/ # MCP server for documentation/code generation
|
||||
│ └── src/main.rs # sqlx-record-expert executable
|
||||
├── .claude/skills/ # Claude Code skills documentation
|
||||
│ ├── sqlx-record.md # Overview and quick reference
|
||||
│ ├── sqlx-entity.md # #[derive(Entity)] detailed guide
|
||||
│ ├── sqlx-filters.md # Filter system guide
|
||||
│ ├── sqlx-audit.md # Audit trail guide
|
||||
│ ├── sqlx-lookup.md # Lookup tables guide
|
||||
│ └── sqlx-values.md # Value types guide
|
||||
├── .claude/skills/sqlx-record/ # Claude Code skills documentation
|
||||
│ ├── sqlx-record.md # Overview and quick reference
|
||||
│ ├── sqlx-entity.md # #[derive(Entity)] detailed guide
|
||||
│ ├── sqlx-filters.md # Filter system guide
|
||||
│ ├── sqlx-audit.md # Audit trail guide
|
||||
│ ├── sqlx-lookup.md # Lookup tables guide
|
||||
│ ├── sqlx-values.md # Value types guide
|
||||
│ └── sqlx-conn-provider.md # Connection provider guide
|
||||
└── Cargo.toml # Workspace root
|
||||
```
|
||||
|
||||
|
|
@ -122,7 +123,7 @@ let id = new_uuid(); // Timestamp prefix (8 bytes) + random (8 bytes)
|
|||
|
||||
## Connection Provider
|
||||
|
||||
Flexible connection management - borrow existing or lazily acquire from pool:
|
||||
Flexible connection management - borrow existing connection, lazily acquire from pool, or use a transaction:
|
||||
|
||||
```rust
|
||||
use sqlx_record::prelude::ConnProvider;
|
||||
|
|
@ -133,6 +134,12 @@ let mut provider = ConnProvider::from_ref(&mut conn);
|
|||
// From pool (lazy acquisition)
|
||||
let mut provider = ConnProvider::from_pool(pool.clone());
|
||||
|
||||
// From transaction (operations participate in the transaction)
|
||||
let mut tx = pool.begin().await?;
|
||||
let mut provider = ConnProvider::from_tx(&mut tx);
|
||||
// ... use provider ...
|
||||
tx.commit().await?;
|
||||
|
||||
// Get connection (acquires on first call for Owned variant)
|
||||
let conn = provider.get_conn().await?;
|
||||
```
|
||||
|
|
|
|||
|
|
@ -51,7 +51,8 @@ A Rust library providing derive macros for automatic CRUD operations and compreh
|
|||
- **Audit Trails**: Track who changed what, when, and why
|
||||
- **Type-Safe Filters**: Composable query building with `Filter` enum
|
||||
- **UpdateExpr**: Advanced updates with arithmetic, CASE/WHEN, conditionals
|
||||
- **Soft Deletes**: `#[soft_delete]` with delete/restore/hard_delete methods
|
||||
- **Hard Delete**: `hard_delete_by_{pk}()` always generated for all entities
|
||||
- **Soft Deletes**: `#[soft_delete]` or `is_active` convention with soft_delete/restore methods
|
||||
- **Auto Timestamps**: `#[created_at]`, `#[updated_at]` auto-populated
|
||||
- **Batch Operations**: `insert_many()`, `upsert()` for efficient bulk operations
|
||||
- **Pagination**: `Page<T>` with `paginate()` method
|
||||
|
|
@ -166,6 +167,21 @@ pub async fn update_by_ids(executor, ids: &[Uuid], form: UpdateForm) -> Result<(
|
|||
pub fn update_form() -> UpdateForm
|
||||
```
|
||||
|
||||
### Delete (always generated)
|
||||
```rust
|
||||
pub async fn hard_delete(&self, executor) -> Result<(), Error>
|
||||
pub async fn hard_delete_by_id(executor, id: &Uuid) -> Result<(), Error>
|
||||
```
|
||||
|
||||
### Soft Delete (if `is_active` field or `#[soft_delete]` exists)
|
||||
```rust
|
||||
pub async fn soft_delete(&self, executor) -> Result<(), Error>
|
||||
pub async fn soft_delete_by_id(executor, id: &Uuid) -> Result<(), Error>
|
||||
pub async fn restore(&self, executor) -> Result<(), Error>
|
||||
pub async fn restore_by_id(executor, id: &Uuid) -> Result<(), Error>
|
||||
pub const fn soft_delete_field() -> &'static str
|
||||
```
|
||||
|
||||
### Diff (Change Detection)
|
||||
```rust
|
||||
pub fn model_diff(form: &UpdateForm, model: &Self) -> serde_json::Value
|
||||
|
|
@ -1100,11 +1116,43 @@ if page.has_next() {
|
|||
```
|
||||
"#;
|
||||
|
||||
const SOFT_DELETE: &str = r#"# Soft Delete
|
||||
const SOFT_DELETE: &str = r#"# Delete Methods
|
||||
|
||||
## Hard Delete (always generated)
|
||||
|
||||
Every Entity gets `hard_delete` and `hard_delete_by_{pk}` methods:
|
||||
|
||||
```rust
|
||||
// Instance method
|
||||
user.hard_delete(&pool).await?;
|
||||
|
||||
// Static method by primary key
|
||||
User::hard_delete_by_id(&pool, &user_id).await?;
|
||||
```
|
||||
|
||||
**SQL generated:**
|
||||
```sql
|
||||
DELETE FROM users WHERE id = ?
|
||||
```
|
||||
|
||||
## Soft Delete
|
||||
|
||||
Mark records as deleted without removing from database.
|
||||
|
||||
## Enable
|
||||
### Enable
|
||||
|
||||
Convention: an `is_active` bool field is auto-detected (preferred):
|
||||
|
||||
```rust
|
||||
#[derive(Entity, FromRow)]
|
||||
struct User {
|
||||
#[primary_key]
|
||||
id: Uuid,
|
||||
is_active: bool, // Auto-detected: FALSE = deleted
|
||||
}
|
||||
```
|
||||
|
||||
Or use `#[soft_delete]` on any bool field:
|
||||
|
||||
```rust
|
||||
#[derive(Entity, FromRow)]
|
||||
|
|
@ -1112,42 +1160,38 @@ struct User {
|
|||
#[primary_key]
|
||||
id: Uuid,
|
||||
|
||||
#[soft_delete] // Must be bool
|
||||
is_deleted: bool,
|
||||
#[soft_delete] // Field will be FALSE when deleted
|
||||
is_active: bool,
|
||||
}
|
||||
```
|
||||
|
||||
Auto-detection: Fields named `is_deleted` or `deleted` with `bool` type work without attribute.
|
||||
Auto-detection also works for `is_deleted` or `deleted` bool fields (TRUE = deleted).
|
||||
|
||||
## Generated Methods
|
||||
### Generated Methods
|
||||
|
||||
```rust
|
||||
// Soft delete (set to true)
|
||||
user.delete(&pool).await?;
|
||||
User::delete_by_id(&pool, &id).await?;
|
||||
// Soft delete (set is_active = FALSE)
|
||||
user.soft_delete(&pool).await?;
|
||||
User::soft_delete_by_id(&pool, &id).await?;
|
||||
|
||||
// Hard delete (permanent)
|
||||
user.hard_delete(&pool).await?;
|
||||
User::hard_delete_by_id(&pool, &id).await?;
|
||||
|
||||
// Restore (set to false)
|
||||
// Restore (set is_active = TRUE)
|
||||
user.restore(&pool).await?;
|
||||
User::restore_by_id(&pool, &id).await?;
|
||||
|
||||
// Field name
|
||||
User::soft_delete_field() // "is_deleted"
|
||||
User::soft_delete_field() // "is_active"
|
||||
```
|
||||
|
||||
## Filtering
|
||||
### Filtering
|
||||
|
||||
Soft delete does NOT auto-filter. Add filter manually:
|
||||
|
||||
```rust
|
||||
// Only non-deleted
|
||||
let users = User::find(&pool, filters![("is_deleted", false)], None).await?;
|
||||
// Only active (non-deleted)
|
||||
let users = User::find(&pool, filters![("is_active", true)], None).await?;
|
||||
|
||||
// Only deleted (trash)
|
||||
let deleted = User::find(&pool, filters![("is_deleted", true)], None).await?;
|
||||
// Only deleted
|
||||
let deleted = User::find(&pool, filters![("is_active", false)], None).await?;
|
||||
|
||||
// All records
|
||||
let all = User::find(&pool, filters![], None).await?;
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ fn db_type() -> TokenStream2 {
|
|||
}
|
||||
#[cfg(feature = "sqlite")]
|
||||
{
|
||||
quote! { sqlx::Sqlite }
|
||||
return quote! { sqlx::Sqlite };
|
||||
}
|
||||
#[cfg(feature = "mysql")]
|
||||
{
|
||||
|
|
@ -82,7 +82,7 @@ fn db_arguments() -> TokenStream2 {
|
|||
}
|
||||
#[cfg(feature = "sqlite")]
|
||||
{
|
||||
quote! { sqlx::sqlite::SqliteArguments<'static> }
|
||||
return quote! { sqlx::sqlite::SqliteArguments<'q> };
|
||||
}
|
||||
#[cfg(feature = "mysql")]
|
||||
{
|
||||
|
|
@ -99,7 +99,7 @@ fn table_quote() -> &'static str {
|
|||
#[cfg(feature = "postgres")]
|
||||
{ "\"" }
|
||||
#[cfg(feature = "sqlite")]
|
||||
{ "\"" }
|
||||
{ return "\""; }
|
||||
#[cfg(feature = "mysql")]
|
||||
{ "`" }
|
||||
#[cfg(not(any(feature = "mysql", feature = "postgres", feature = "sqlite")))]
|
||||
|
|
@ -139,10 +139,11 @@ fn derive_entity_internal(input: TokenStream) -> TokenStream {
|
|||
.or_else(|| fields.iter().find(|&f| is_version_field(f)));
|
||||
|
||||
// Find soft delete field (by attribute or by name convention)
|
||||
// Convention: `is_active` (FALSE = deleted), `is_deleted`/`deleted` (TRUE = deleted)
|
||||
let soft_delete_field = fields.iter()
|
||||
.find(|f| f.is_soft_delete)
|
||||
.or_else(|| fields.iter().find(|f| {
|
||||
(f.ident == "is_deleted" || f.ident == "deleted") &&
|
||||
(f.ident == "is_active" || f.ident == "is_deleted" || f.ident == "deleted") &&
|
||||
matches!(&f.ty, Type::Path(p) if p.path.is_ident("bool"))
|
||||
}));
|
||||
|
||||
|
|
@ -151,6 +152,7 @@ fn derive_entity_internal(input: TokenStream) -> TokenStream {
|
|||
let get_impl = generate_get_impl(&name, &table_name, primary_key, version_field, soft_delete_field, &fields, &impl_generics, &ty_generics, &where_clause);
|
||||
let update_impl = generate_update_impl(&name, &update_form_name, &table_name, &fields, primary_key, version_field, has_updated_at, &impl_generics, &ty_generics, &where_clause);
|
||||
let diff_impl = generate_diff_impl(&name, &update_form_name, &fields, primary_key, version_field, &impl_generics, &ty_generics, &where_clause);
|
||||
let delete_impl = generate_delete_impl(&name, &table_name, primary_key, &impl_generics, &ty_generics, &where_clause);
|
||||
let soft_delete_impl = generate_soft_delete_impl(&name, &table_name, primary_key, soft_delete_field, &impl_generics, &ty_generics, &where_clause);
|
||||
|
||||
let pk_type = &primary_key.ty;
|
||||
|
|
@ -161,6 +163,7 @@ fn derive_entity_internal(input: TokenStream) -> TokenStream {
|
|||
#get_impl
|
||||
#update_impl
|
||||
#diff_impl
|
||||
#delete_impl
|
||||
#soft_delete_impl
|
||||
|
||||
impl #impl_generics #name #ty_generics #where_clause {
|
||||
|
|
@ -1446,6 +1449,51 @@ fn generate_diff_impl(
|
|||
}
|
||||
}
|
||||
|
||||
// Generate delete implementation - always generated for ALL entities
|
||||
fn generate_delete_impl(
|
||||
name: &Ident,
|
||||
table_name: &str,
|
||||
primary_key: &EntityField,
|
||||
impl_generics: &ImplGenerics,
|
||||
ty_generics: &TypeGenerics,
|
||||
where_clause: &Option<&WhereClause>,
|
||||
) -> TokenStream2 {
|
||||
let pk_field = &primary_key.ident;
|
||||
let pk_type = &primary_key.ty;
|
||||
let pk_db_name = &primary_key.db_name;
|
||||
let db = db_type();
|
||||
let tq = table_quote();
|
||||
|
||||
let pk_field_name = primary_key.ident.to_string();
|
||||
let hard_delete_by_func = format_ident!("hard_delete_by_{}", pk_field_name);
|
||||
|
||||
quote! {
|
||||
impl #impl_generics #name #ty_generics #where_clause {
|
||||
/// Hard delete - permanently removes the row from database
|
||||
pub async fn hard_delete<'a, E>(&self, executor: E) -> Result<(), sqlx::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = #db>,
|
||||
{
|
||||
Self::#hard_delete_by_func(executor, &self.#pk_field).await
|
||||
}
|
||||
|
||||
/// Hard delete by primary key - permanently removes the row from database
|
||||
pub async fn #hard_delete_by_func<'a, E>(executor: E, #pk_field: &#pk_type) -> Result<(), sqlx::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = #db>,
|
||||
{
|
||||
let query = format!(
|
||||
"DELETE FROM {}{}{} WHERE {} = {}",
|
||||
#tq, #table_name, #tq,
|
||||
#pk_db_name, ::sqlx_record::prelude::placeholder(1)
|
||||
);
|
||||
sqlx::query(&query).bind(#pk_field).execute(executor).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate soft delete implementation
|
||||
fn generate_soft_delete_impl(
|
||||
name: &Ident,
|
||||
|
|
@ -1468,51 +1516,41 @@ fn generate_soft_delete_impl(
|
|||
let tq = table_quote();
|
||||
|
||||
let pk_field_name = primary_key.ident.to_string();
|
||||
let delete_by_func = format_ident!("delete_by_{}", pk_field_name);
|
||||
let hard_delete_by_func = format_ident!("hard_delete_by_{}", pk_field_name);
|
||||
let soft_delete_by_func = format_ident!("soft_delete_by_{}", pk_field_name);
|
||||
let restore_by_func = format_ident!("restore_by_{}", pk_field_name);
|
||||
|
||||
// Determine semantics based on field name and attribute:
|
||||
// - #[soft_delete] attribute: field should be FALSE when deleted (user convention)
|
||||
// - `is_active` by name: FALSE when deleted, TRUE when active
|
||||
// - `is_deleted`/`deleted` by name: TRUE when deleted, FALSE when active
|
||||
let sd_field_name = sd_field.ident.to_string();
|
||||
let is_inverted = sd_field.is_soft_delete || sd_field_name == "is_active";
|
||||
|
||||
let (delete_value, restore_value) = if is_inverted {
|
||||
("FALSE", "TRUE")
|
||||
} else {
|
||||
("TRUE", "FALSE")
|
||||
};
|
||||
|
||||
quote! {
|
||||
impl #impl_generics #name #ty_generics #where_clause {
|
||||
/// Soft delete - sets the soft_delete field to true
|
||||
pub async fn delete<'a, E>(&self, executor: E) -> Result<(), sqlx::Error>
|
||||
/// Soft delete - marks record as deleted without removing from database
|
||||
pub async fn soft_delete<'a, E>(&self, executor: E) -> Result<(), sqlx::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = #db>,
|
||||
{
|
||||
Self::#delete_by_func(executor, &self.#pk_field).await
|
||||
Self::#soft_delete_by_func(executor, &self.#pk_field).await
|
||||
}
|
||||
|
||||
/// Soft delete by primary key
|
||||
pub async fn #delete_by_func<'a, E>(executor: E, #pk_field: &#pk_type) -> Result<(), sqlx::Error>
|
||||
pub async fn #soft_delete_by_func<'a, E>(executor: E, #pk_field: &#pk_type) -> Result<(), sqlx::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = #db>,
|
||||
{
|
||||
let query = format!(
|
||||
"UPDATE {}{}{} SET {} = TRUE WHERE {} = {}",
|
||||
#tq, #table_name, #tq,
|
||||
#sd_db_name,
|
||||
#pk_db_name, ::sqlx_record::prelude::placeholder(1)
|
||||
);
|
||||
sqlx::query(&query).bind(#pk_field).execute(executor).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Hard delete - permanently removes the row from database
|
||||
pub async fn hard_delete<'a, E>(&self, executor: E) -> Result<(), sqlx::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = #db>,
|
||||
{
|
||||
Self::#hard_delete_by_func(executor, &self.#pk_field).await
|
||||
}
|
||||
|
||||
/// Hard delete by primary key
|
||||
pub async fn #hard_delete_by_func<'a, E>(executor: E, #pk_field: &#pk_type) -> Result<(), sqlx::Error>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = #db>,
|
||||
{
|
||||
let query = format!(
|
||||
"DELETE FROM {}{}{} WHERE {} = {}",
|
||||
"UPDATE {}{}{} SET {} = {} WHERE {} = {}",
|
||||
#tq, #table_name, #tq,
|
||||
#sd_db_name, #delete_value,
|
||||
#pk_db_name, ::sqlx_record::prelude::placeholder(1)
|
||||
);
|
||||
sqlx::query(&query).bind(#pk_field).execute(executor).await?;
|
||||
|
|
@ -1533,9 +1571,9 @@ fn generate_soft_delete_impl(
|
|||
E: sqlx::Executor<'a, Database = #db>,
|
||||
{
|
||||
let query = format!(
|
||||
"UPDATE {}{}{} SET {} = FALSE WHERE {} = {}",
|
||||
"UPDATE {}{}{} SET {} = {} WHERE {} = {}",
|
||||
#tq, #table_name, #tq,
|
||||
#sd_db_name,
|
||||
#sd_db_name, #restore_value,
|
||||
#pk_db_name, ::sqlx_record::prelude::placeholder(1)
|
||||
);
|
||||
sqlx::query(&query).bind(#pk_field).execute(executor).await?;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use sqlx::pool::PoolConnection;
|
||||
|
||||
#[cfg(feature = "mysql")]
|
||||
use sqlx::{MySql, MySqlPool};
|
||||
use sqlx::{MySql, MySqlConnection, MySqlPool, Transaction};
|
||||
|
||||
#[cfg(feature = "postgres")]
|
||||
use sqlx::{Postgres, PgPool};
|
||||
use sqlx::{Postgres, PgConnection, PgPool, Transaction};
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
use sqlx::{Sqlite, SqlitePool};
|
||||
use sqlx::{Sqlite, SqliteConnection, SqlitePool, Transaction};
|
||||
|
||||
// ============================================================================
|
||||
// MySQL Implementation
|
||||
|
|
@ -24,6 +24,10 @@ pub enum ConnProvider<'a> {
|
|||
pool: MySqlPool,
|
||||
conn: Option<PoolConnection<MySql>>,
|
||||
},
|
||||
/// Stores a reference to a transaction
|
||||
Transaction {
|
||||
tx: &'a mut Transaction<'static, MySql>,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(feature = "mysql")]
|
||||
|
|
@ -38,18 +42,25 @@ impl<'a> ConnProvider<'a> {
|
|||
ConnProvider::Owned { pool, conn: None }
|
||||
}
|
||||
|
||||
/// Create a ConnProvider from a borrowed transaction reference
|
||||
pub fn from_tx(tx: &'a mut Transaction<'static, MySql>) -> Self {
|
||||
ConnProvider::Transaction { tx }
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the underlying connection.
|
||||
/// For borrowed connections, returns the reference directly.
|
||||
/// For owned connections, lazily acquires from pool on first call.
|
||||
pub async fn get_conn(&mut self) -> Result<&mut PoolConnection<MySql>, sqlx::Error> {
|
||||
/// For transactions, returns the transaction's underlying connection.
|
||||
pub async fn get_conn(&mut self) -> Result<&mut MySqlConnection, sqlx::Error> {
|
||||
match self {
|
||||
ConnProvider::Borrowed { conn } => Ok(conn),
|
||||
ConnProvider::Borrowed { conn } => Ok(&mut **conn),
|
||||
ConnProvider::Owned { pool, conn } => {
|
||||
if conn.is_none() {
|
||||
*conn = Some(pool.acquire().await?);
|
||||
}
|
||||
Ok(conn.as_mut().unwrap())
|
||||
Ok(&mut **conn.as_mut().unwrap())
|
||||
}
|
||||
ConnProvider::Transaction { tx } => Ok(&mut **tx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -69,6 +80,10 @@ pub enum ConnProvider<'a> {
|
|||
pool: PgPool,
|
||||
conn: Option<PoolConnection<Postgres>>,
|
||||
},
|
||||
/// Stores a reference to a transaction
|
||||
Transaction {
|
||||
tx: &'a mut Transaction<'static, Postgres>,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(feature = "postgres")]
|
||||
|
|
@ -83,18 +98,25 @@ impl<'a> ConnProvider<'a> {
|
|||
ConnProvider::Owned { pool, conn: None }
|
||||
}
|
||||
|
||||
/// Create a ConnProvider from a borrowed transaction reference
|
||||
pub fn from_tx(tx: &'a mut Transaction<'static, Postgres>) -> Self {
|
||||
ConnProvider::Transaction { tx }
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the underlying connection.
|
||||
/// For borrowed connections, returns the reference directly.
|
||||
/// For owned connections, lazily acquires from pool on first call.
|
||||
pub async fn get_conn(&mut self) -> Result<&mut PoolConnection<Postgres>, sqlx::Error> {
|
||||
/// For transactions, returns the transaction's underlying connection.
|
||||
pub async fn get_conn(&mut self) -> Result<&mut PgConnection, sqlx::Error> {
|
||||
match self {
|
||||
ConnProvider::Borrowed { conn } => Ok(conn),
|
||||
ConnProvider::Borrowed { conn } => Ok(&mut **conn),
|
||||
ConnProvider::Owned { pool, conn } => {
|
||||
if conn.is_none() {
|
||||
*conn = Some(pool.acquire().await?);
|
||||
}
|
||||
Ok(conn.as_mut().unwrap())
|
||||
Ok(&mut **conn.as_mut().unwrap())
|
||||
}
|
||||
ConnProvider::Transaction { tx } => Ok(&mut **tx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -114,6 +136,10 @@ pub enum ConnProvider<'a> {
|
|||
pool: SqlitePool,
|
||||
conn: Option<PoolConnection<Sqlite>>,
|
||||
},
|
||||
/// Stores a reference to a transaction
|
||||
Transaction {
|
||||
tx: &'a mut Transaction<'static, Sqlite>,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
|
|
@ -128,18 +154,25 @@ impl<'a> ConnProvider<'a> {
|
|||
ConnProvider::Owned { pool, conn: None }
|
||||
}
|
||||
|
||||
/// Create a ConnProvider from a borrowed transaction reference
|
||||
pub fn from_tx(tx: &'a mut Transaction<'static, Sqlite>) -> Self {
|
||||
ConnProvider::Transaction { tx }
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the underlying connection.
|
||||
/// For borrowed connections, returns the reference directly.
|
||||
/// For owned connections, lazily acquires from pool on first call.
|
||||
pub async fn get_conn(&mut self) -> Result<&mut PoolConnection<Sqlite>, sqlx::Error> {
|
||||
/// For transactions, returns the transaction's underlying connection.
|
||||
pub async fn get_conn(&mut self) -> Result<&mut SqliteConnection, sqlx::Error> {
|
||||
match self {
|
||||
ConnProvider::Borrowed { conn } => Ok(conn),
|
||||
ConnProvider::Borrowed { conn } => Ok(&mut **conn),
|
||||
ConnProvider::Owned { pool, conn } => {
|
||||
if conn.is_none() {
|
||||
*conn = Some(pool.acquire().await?);
|
||||
}
|
||||
Ok(conn.as_mut().unwrap())
|
||||
Ok(&mut **conn.as_mut().unwrap())
|
||||
}
|
||||
ConnProvider::Transaction { tx } => Ok(&mut **tx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -181,6 +181,7 @@ pub mod prelude {
|
|||
pub use crate::values;
|
||||
pub use crate::{new_uuid, lookup_table, lookup_options, transaction};
|
||||
pub use crate::pagination::{Page, PageRequest};
|
||||
pub use crate::conn_provider::*;
|
||||
|
||||
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
|
||||
pub use crate::conn_provider::ConnProvider;
|
||||
|
|
|
|||
Loading…
Reference in New Issue