Add database transaction support with atomic operations
- Create transaction.py with @transactional decorator, atomic() context manager - Add TransactionPropagation modes: REQUIRED, REQUIRES_NEW, NESTED - Add savepoint support for nested transactions with partial rollback - Update connection.py with TransactionManager, get_transactional_session - Update service.py with bulk operations (bulk_mark_downloaded, bulk_delete) - Wrap QueueRepository.save_item() and clear_all() in atomic transactions - Add comprehensive tests (66 transaction tests, 90% coverage) - All 1090 tests passing
This commit is contained in:
117
docs/DATABASE.md
117
docs/DATABASE.md
@@ -197,14 +197,97 @@ Source: [src/server/models/download.py](../src/server/models/download.py#L63-L11
|
||||
|
||||
---
|
||||
|
||||
## 6. Repository Pattern
|
||||
## 6. Transaction Support
|
||||
|
||||
### 6.1 Overview
|
||||
|
||||
The database layer provides comprehensive transaction support to ensure data consistency across compound operations. All write operations can be wrapped in explicit transactions.
|
||||
|
||||
Source: [src/server/database/transaction.py](../src/server/database/transaction.py)
|
||||
|
||||
### 6.2 Transaction Utilities
|
||||
|
||||
| Component | Type | Description |
|
||||
| ------------------------- | ----------------- | ---------------------------------------- |
|
||||
| `@transactional` | Decorator | Wraps function in transaction boundary |
|
||||
| `atomic()` | Async context mgr | Provides atomic operation block |
|
||||
| `atomic_sync()` | Sync context mgr | Sync version of atomic() |
|
||||
| `TransactionContext` | Class | Explicit sync transaction control |
|
||||
| `AsyncTransactionContext` | Class | Explicit async transaction control |
|
||||
| `TransactionManager` | Class | Helper for manual transaction management |
|
||||
|
||||
### 6.3 Transaction Propagation Modes
|
||||
|
||||
| Mode | Behavior |
|
||||
| -------------- | ------------------------------------------------ |
|
||||
| `REQUIRED` | Use existing transaction or create new (default) |
|
||||
| `REQUIRES_NEW` | Always create new transaction |
|
||||
| `NESTED` | Create savepoint within existing transaction |
|
||||
|
||||
### 6.4 Usage Examples
|
||||
|
||||
**Using @transactional decorator:**
|
||||
|
||||
```python
|
||||
from src.server.database.transaction import transactional
|
||||
|
||||
@transactional()
|
||||
async def compound_operation(db: AsyncSession, data: dict):
|
||||
# All operations commit together or rollback on error
|
||||
series = await AnimeSeriesService.create(db, ...)
|
||||
episode = await EpisodeService.create(db, series_id=series.id, ...)
|
||||
return series, episode
|
||||
```
|
||||
|
||||
**Using atomic() context manager:**
|
||||
|
||||
```python
|
||||
from src.server.database.transaction import atomic
|
||||
|
||||
async def some_function(db: AsyncSession):
|
||||
async with atomic(db) as tx:
|
||||
await operation1(db)
|
||||
await operation2(db)
|
||||
# Auto-commits on success, rolls back on exception
|
||||
```
|
||||
|
||||
**Using savepoints for partial rollback:**
|
||||
|
||||
```python
|
||||
async with atomic(db) as tx:
|
||||
await outer_operation(db)
|
||||
|
||||
async with tx.savepoint() as sp:
|
||||
await risky_operation(db)
|
||||
if error_condition:
|
||||
await sp.rollback() # Only rollback nested ops
|
||||
|
||||
await final_operation(db) # Still executes
|
||||
```
|
||||
|
||||
Source: [src/server/database/transaction.py](../src/server/database/transaction.py)
|
||||
|
||||
### 6.5 Connection Module Additions
|
||||
|
||||
| Function | Description |
|
||||
| ------------------------------- | -------------------------------------------- |
|
||||
| `get_transactional_session` | Session without auto-commit for transactions |
|
||||
| `TransactionManager` | Helper class for manual transaction control |
|
||||
| `is_session_in_transaction` | Check if session is in active transaction |
|
||||
| `get_session_transaction_depth` | Get nesting depth of transactions |
|
||||
|
||||
Source: [src/server/database/connection.py](../src/server/database/connection.py)
|
||||
|
||||
---
|
||||
|
||||
## 7. Repository Pattern
|
||||
|
||||
The `QueueRepository` class provides data access abstraction.
|
||||
|
||||
```python
|
||||
class QueueRepository:
|
||||
async def save_item(self, item: DownloadItem) -> None:
|
||||
"""Save or update a download item."""
|
||||
"""Save or update a download item (atomic operation)."""
|
||||
|
||||
async def get_all_items(self) -> List[DownloadItem]:
|
||||
"""Get all items from database."""
|
||||
@@ -212,17 +295,17 @@ class QueueRepository:
|
||||
async def delete_item(self, item_id: str) -> bool:
|
||||
"""Delete item by ID."""
|
||||
|
||||
async def get_items_by_status(
|
||||
self, status: DownloadStatus
|
||||
) -> List[DownloadItem]:
|
||||
"""Get items filtered by status."""
|
||||
async def clear_all(self) -> int:
|
||||
"""Clear all items (atomic operation)."""
|
||||
```
|
||||
|
||||
Note: Compound operations (`save_item`, `clear_all`) are wrapped in `atomic()` transactions.
|
||||
|
||||
Source: [src/server/services/queue_repository.py](../src/server/services/queue_repository.py)
|
||||
|
||||
---
|
||||
|
||||
## 7. Database Service
|
||||
## 8. Database Service
|
||||
|
||||
The `AnimeSeriesService` provides async CRUD operations.
|
||||
|
||||
@@ -246,11 +329,23 @@ class AnimeSeriesService:
|
||||
"""Get series by primary key identifier."""
|
||||
```
|
||||
|
||||
### Bulk Operations
|
||||
|
||||
Services provide bulk operations for transaction-safe batch processing:
|
||||
|
||||
| Service | Method | Description |
|
||||
| ---------------------- | ---------------------- | ------------------------------ |
|
||||
| `EpisodeService` | `bulk_mark_downloaded` | Mark multiple episodes at once |
|
||||
| `DownloadQueueService` | `bulk_delete` | Delete multiple queue items |
|
||||
| `DownloadQueueService` | `clear_all` | Clear entire queue |
|
||||
| `UserSessionService` | `rotate_session` | Revoke old + create new atomic |
|
||||
| `UserSessionService` | `cleanup_expired` | Bulk delete expired sessions |
|
||||
|
||||
Source: [src/server/database/service.py](../src/server/database/service.py)
|
||||
|
||||
---
|
||||
|
||||
## 8. Data Integrity Rules
|
||||
## 9. Data Integrity Rules
|
||||
|
||||
### Validation Constraints
|
||||
|
||||
@@ -269,7 +364,7 @@ Source: [src/server/database/models.py](../src/server/database/models.py#L89-L11
|
||||
|
||||
---
|
||||
|
||||
## 9. Migration Strategy
|
||||
## 10. Migration Strategy
|
||||
|
||||
Currently, SQLAlchemy's `create_all()` is used for schema creation.
|
||||
|
||||
@@ -286,7 +381,7 @@ Source: [src/server/database/connection.py](../src/server/database/connection.py
|
||||
|
||||
---
|
||||
|
||||
## 10. Common Query Patterns
|
||||
## 11. Common Query Patterns
|
||||
|
||||
### Get all series with missing episodes
|
||||
|
||||
@@ -317,7 +412,7 @@ items = await db.execute(
|
||||
|
||||
---
|
||||
|
||||
## 11. Database Location
|
||||
## 12. Database Location
|
||||
|
||||
| Environment | Default Location |
|
||||
| ----------- | ------------------------------------------------- |
|
||||
|
||||
Reference in New Issue
Block a user