0
# Transaction Management
1
2
Transaction handling with context managers, retry logic, isolation level control, and automatic rollback on errors.
3
4
## Capabilities
5
6
### Transaction Context Managers
7
8
Create and manage database transactions using Python context managers for automatic commit/rollback handling.
9
10
```python { .api }
11
def transaction(
12
*,
13
options: Optional[TransactionOptions] = None,
14
retry_options: Optional[RetryOptions] = None
15
) -> ContextManager:
16
"""
17
Create a transaction context manager for synchronous operations.
18
19
Parameters:
20
- options: Transaction configuration options
21
- retry_options: Retry policy for transaction conflicts
22
23
Returns:
24
Context manager that automatically commits on success or rolls back on error
25
26
Usage:
27
with client.transaction() as tx:
28
tx.execute("INSERT User { name := 'Alice' }")
29
tx.execute("INSERT User { name := 'Bob' }")
30
# Automatically commits if no exceptions
31
"""
32
33
def transaction(
34
*,
35
options: Optional[TransactionOptions] = None,
36
retry_options: Optional[RetryOptions] = None
37
) -> AsyncContextManager:
38
"""
39
Create an async transaction context manager.
40
41
Parameters: Same as synchronous version
42
43
Returns:
44
Async context manager for transaction handling
45
46
Usage:
47
async with client.transaction() as tx:
48
await tx.execute("INSERT User { name := 'Alice' }")
49
await tx.execute("INSERT User { name := 'Bob' }")
50
# Automatically commits if no exceptions
51
"""
52
```
53
54
### Transaction Options
55
56
Configuration options for controlling transaction behavior.
57
58
```python { .api }
59
class TransactionOptions:
60
"""
61
Transaction configuration options.
62
63
Controls isolation level, read-only mode, and deferrable transactions.
64
"""
65
66
def __init__(
67
self,
68
isolation: IsolationLevel = IsolationLevel.Serializable,
69
readonly: bool = False,
70
deferrable: bool = False
71
):
72
"""
73
Create transaction options.
74
75
Parameters:
76
- isolation: Transaction isolation level
77
- readonly: Whether transaction is read-only
78
- deferrable: Whether transaction can be deferred
79
"""
80
81
@classmethod
82
def defaults(cls) -> TransactionOptions:
83
"""Get default transaction options."""
84
85
def start_transaction_query(self) -> str:
86
"""Generate START TRANSACTION query with options."""
87
88
class IsolationLevel:
89
"""
90
Transaction isolation levels.
91
92
EdgeDB currently supports only Serializable isolation.
93
"""
94
Serializable = "SERIALIZABLE"
95
```
96
97
### Retry Configuration
98
99
Configuration for automatic retry of transactions on conflicts.
100
101
```python { .api }
102
class RetryOptions:
103
"""
104
Configuration for transaction retry logic.
105
106
Defines retry attempts and backoff strategy for handling
107
transaction conflicts and temporary failures.
108
"""
109
110
def __init__(
111
self,
112
attempts: int,
113
backoff: Callable[[int], float]
114
):
115
"""
116
Create retry options.
117
118
Parameters:
119
- attempts: Maximum number of retry attempts
120
- backoff: Function that takes attempt number and returns delay in seconds
121
"""
122
123
@classmethod
124
def defaults(cls) -> RetryOptions:
125
"""Get default retry options (3 attempts with exponential backoff)."""
126
127
def with_rule(
128
self,
129
condition: RetryCondition,
130
attempts: Optional[int] = None,
131
backoff: Optional[Callable[[int], float]] = None
132
) -> RetryOptions:
133
"""
134
Create retry options with specific rule.
135
136
Parameters:
137
- condition: Condition that triggers retry
138
- attempts: Number of retry attempts for this condition
139
- backoff: Backoff function for this condition
140
141
Returns:
142
New RetryOptions with added rule
143
"""
144
145
def get_rule_for_exception(self, exc: Exception) -> Optional[Any]:
146
"""Get retry rule for specific exception."""
147
148
class RetryCondition:
149
"""
150
Conditions that trigger transaction retry.
151
"""
152
TransactionConflict = "transaction_conflict"
153
NetworkError = "network_error"
154
155
def default_backoff(attempt: int) -> float:
156
"""
157
Default exponential backoff algorithm.
158
159
Parameters:
160
- attempt: Attempt number (0-based)
161
162
Returns:
163
Delay in seconds before next retry
164
"""
165
```
166
167
### Transaction State
168
169
Transaction lifecycle state management.
170
171
```python { .api }
172
class TransactionState(Enum):
173
"""
174
Transaction lifecycle states.
175
"""
176
NEW = "new"
177
STARTED = "started"
178
COMMITTED = "committed"
179
ROLLEDBACK = "rolledback"
180
FAILED = "failed"
181
182
class BaseTransaction:
183
"""
184
Base transaction implementation.
185
186
Provides transaction lifecycle management and state tracking.
187
"""
188
189
def start(self) -> None:
190
"""Start the transaction."""
191
192
def commit(self) -> None:
193
"""Commit the transaction."""
194
195
def rollback(self) -> None:
196
"""Roll back the transaction."""
197
198
@property
199
def state(self) -> TransactionState:
200
"""Current transaction state."""
201
```
202
203
## Usage Examples
204
205
### Basic Transactions
206
207
```python
208
import edgedb
209
210
client = edgedb.create_client()
211
212
# Synchronous transaction
213
with client.transaction() as tx:
214
# All operations in this block are part of the transaction
215
tx.execute("INSERT User { name := 'Alice', email := 'alice@example.com' }")
216
tx.execute("INSERT User { name := 'Bob', email := 'bob@example.com' }")
217
218
# Query within transaction sees uncommitted changes
219
users = tx.query("SELECT User { name }")
220
print(f"Users in transaction: {len(users)}")
221
222
# Transaction automatically commits when exiting the block
223
224
print("Transaction committed successfully")
225
```
226
227
### Async Transactions
228
229
```python
230
import asyncio
231
import edgedb
232
233
async def main():
234
client = edgedb.create_async_client()
235
236
async with client.transaction() as tx:
237
await tx.execute("INSERT User { name := 'Alice', email := 'alice@example.com' }")
238
await tx.execute("INSERT User { name := 'Bob', email := 'bob@example.com' }")
239
240
users = await tx.query("SELECT User { name }")
241
print(f"Users in transaction: {len(users)}")
242
243
print("Async transaction committed successfully")
244
await client.aclose()
245
246
asyncio.run(main())
247
```
248
249
### Transaction Options
250
251
```python
252
import edgedb
253
from edgedb import TransactionOptions, IsolationLevel
254
255
client = edgedb.create_client()
256
257
# Read-only transaction
258
options = TransactionOptions(readonly=True)
259
with client.transaction(options=options) as tx:
260
# Can only perform read operations
261
users = tx.query("SELECT User { name, email }")
262
# tx.execute("INSERT User { name := 'Charlie' }") # Would raise error
263
264
# Deferrable transaction
265
options = TransactionOptions(deferrable=True)
266
with client.transaction(options=options) as tx:
267
# Transaction can be deferred by the database
268
report = tx.query("SELECT generate_monthly_report()")
269
```
270
271
### Retry Configuration
272
273
```python
274
import edgedb
275
from edgedb import RetryOptions, RetryCondition, default_backoff
276
277
client = edgedb.create_client()
278
279
# Custom retry options
280
retry_options = RetryOptions(
281
attempts=5,
282
backoff=default_backoff
283
)
284
285
with client.transaction(retry_options=retry_options) as tx:
286
# This transaction will be retried up to 5 times on conflicts
287
tx.execute("""
288
UPDATE User
289
FILTER .email = 'alice@example.com'
290
SET { login_count := .login_count + 1 }
291
""")
292
293
# Retry with specific conditions
294
retry_options = (
295
RetryOptions.defaults()
296
.with_rule(RetryCondition.TransactionConflict, attempts=10)
297
.with_rule(RetryCondition.NetworkError, attempts=3)
298
)
299
300
with client.transaction(retry_options=retry_options) as tx:
301
# Different retry strategies for different error types
302
tx.execute("INSERT User { name := 'Dave', email := 'dave@example.com' }")
303
```
304
305
### Error Handling
306
307
```python
308
import edgedb
309
310
client = edgedb.create_client()
311
312
try:
313
with client.transaction() as tx:
314
tx.execute("INSERT User { name := 'Alice', email := 'alice@example.com' }")
315
tx.execute("INSERT User { name := 'Alice', email := 'alice@example.com' }") # Duplicate
316
# Transaction is automatically rolled back on error
317
except edgedb.ConstraintViolationError:
318
print("Constraint violation - transaction rolled back")
319
except edgedb.TransactionError as e:
320
print(f"Transaction error: {e}")
321
322
print("Continuing after transaction failure")
323
```
324
325
### Manual Transaction Control
326
327
```python
328
import edgedb
329
330
client = edgedb.create_client()
331
332
# Get transaction object without context manager
333
tx = client.transaction()
334
335
try:
336
tx.start()
337
tx.execute("INSERT User { name := 'Alice' }")
338
tx.execute("INSERT User { name := 'Bob' }")
339
tx.commit()
340
print("Transaction committed manually")
341
except Exception as e:
342
tx.rollback()
343
print(f"Transaction rolled back: {e}")
344
raise
345
```
346
347
### Nested Operations
348
349
```python
350
import edgedb
351
352
client = edgedb.create_client()
353
354
def create_user_with_profile(tx, name, email, bio):
355
"""Function that takes a transaction and performs multiple operations."""
356
user_id = tx.query_single(
357
"INSERT User { name := $name, email := $email } RETURNING .id",
358
name=name, email=email
359
)
360
361
tx.execute(
362
"INSERT Profile { user := $user_id, bio := $bio }",
363
user_id=user_id, bio=bio
364
)
365
366
return user_id
367
368
# Use function within transaction
369
with client.transaction() as tx:
370
user1_id = create_user_with_profile(
371
tx, "Alice", "alice@example.com", "Software developer"
372
)
373
user2_id = create_user_with_profile(
374
tx, "Bob", "bob@example.com", "Data scientist"
375
)
376
377
# Both users and profiles created atomically
378
print(f"Created users: {user1_id}, {user2_id}")
379
```
380
381
### Transaction State Monitoring
382
383
```python
384
import edgedb
385
386
client = edgedb.create_client()
387
388
# Access transaction state
389
tx = client.transaction()
390
print(f"Initial state: {tx.state}") # TransactionState.NEW
391
392
tx.start()
393
print(f"After start: {tx.state}") # TransactionState.STARTED
394
395
try:
396
tx.execute("INSERT User { name := 'Alice' }")
397
tx.commit()
398
print(f"After commit: {tx.state}") # TransactionState.COMMITTED
399
except Exception:
400
tx.rollback()
401
print(f"After rollback: {tx.state}") # TransactionState.ROLLEDBACK
402
```
403
404
### Complex Transaction Patterns
405
406
```python
407
import edgedb
408
from edgedb import RetryOptions, TransactionOptions
409
410
client = edgedb.create_client()
411
412
def transfer_credits(from_user_id, to_user_id, amount):
413
"""Transfer credits between users with retry logic."""
414
415
retry_options = RetryOptions(
416
attempts=10,
417
backoff=lambda attempt: min(2 ** attempt * 0.1, 1.0)
418
)
419
420
with client.transaction(retry_options=retry_options) as tx:
421
# Check sender balance
422
sender = tx.query_required_single(
423
"SELECT User { credits } FILTER .id = $id",
424
id=from_user_id
425
)
426
427
if sender.credits < amount:
428
raise ValueError("Insufficient credits")
429
430
# Perform transfer
431
tx.execute(
432
"UPDATE User FILTER .id = $id SET { credits := .credits - $amount }",
433
id=from_user_id, amount=amount
434
)
435
436
tx.execute(
437
"UPDATE User FILTER .id = $id SET { credits := .credits + $amount }",
438
id=to_user_id, amount=amount
439
)
440
441
# Log transaction
442
tx.execute("""
443
INSERT Transaction {
444
from_user := $from_user,
445
to_user := $to_user,
446
amount := $amount,
447
timestamp := datetime_current()
448
}
449
""", from_user=from_user_id, to_user=to_user_id, amount=amount)
450
451
# Use the transfer function
452
try:
453
transfer_credits(
454
"123e4567-e89b-12d3-a456-426614174000",
455
"987fcdeb-51d2-43a8-b456-426614174000",
456
100
457
)
458
print("Transfer completed successfully")
459
except ValueError as e:
460
print(f"Transfer failed: {e}")
461
except edgedb.TransactionConflictError:
462
print("Transfer failed due to conflict, but was retried automatically")
463
```