Transactions

July 10, 2026 ยท View on GitHub

MongrelDB commits every write through a single atomic transaction endpoint (POST /kit/txn). This guide covers the two ways to use it - a one-shot single op, and a staged batch - plus idempotency keys for safe retries and constraint-violation handling.

The engine enforces UNIQUE, foreign-key, check, and trigger constraints at commit time. A violation aborts the entire batch: no op in the batch becomes visible.


Single puts vs. batch transactions

Single op: Add-MongrelDBRow

Add-MongrelDBRow is a convenience wrapper that sends a one-op transaction. Use it when a write is independent and you do not need atomicity across multiple rows.

try {
    Add-MongrelDBRow -Table 'orders' -Cells @{ 1 = 1; 2 = 'Alice'; 3 = 99.5 }
} catch {
    Write-Host "put failed: $($_.Exception.Message)"
}

Set-MongrelDBRow (upsert) and Remove-MongrelDBRow are the same shape: single-op transactions.

Batch: Invoke-MongrelDBTransaction

When several writes must succeed or fail together, stage them in an ops array and commit once. All ops go to the server in a single HTTP request and commit atomically.

$ops = @(
    @{ put = @{ table = 'orders'; cells = @(1, 10, 2, 'Dave'); returning = $false } },
    @{ put = @{ table = 'orders'; cells = @(1, 11, 2, 'Eve'); returning = $false } },
    @{ delete_by_pk = @{ table = 'orders'; pk = 2 } }
)
Invoke-MongrelDBTransaction -Ops $ops

An upsert op takes an additional update_cells array applied on a primary-key conflict. Omitting it means "do nothing on conflict".

Idempotency keys for safe retries

Networks drop requests and daemons crash after committing but before replying. An idempotency key makes a commit safe to retry: the daemon remembers the key and replays the original result on a duplicate commit, even across restarts.

Pass the key via -IdempotencyKey:

$ops = @( @{ put = @{ table = 'charges'; cells = @(1, $orderId, 2, 199.0); returning = $false } } )
# On a retry with the same key the daemon returns the first commit's result
# instead of inserting a second row.
Invoke-MongrelDBTransaction -Ops $ops -IdempotencyKey 'charge-order-123'

Rules for keys:

  • Any non-empty string works. Prefer content-derived, globally-unique values.
  • Omitting the key disables idempotency - a retry will commit again.
  • The key scopes the entire batch, not individual ops. Reuse the exact same ops and key together when retrying.

Handling constraint violations

Constraint violations arrive as HTTP 409, mapped to the Conflict category. The exception message carries the daemon's structured message:

try {
    Invoke-MongrelDBTransaction -Ops $ops
} catch {
    if ($_.Exception.Category -eq 'Conflict') {
        Write-Host "constraint violated: $($_.Exception.Message)"
        # The engine already rolled back the whole batch. Nothing to undo.
    } elseif ($_.Exception.Category -eq 'Auth') {
        Write-Host "not authorized: $($_.Exception.Message)"
    }
}

Structured codes you will commonly see in the message:

codeMeaning
UNIQUE_VIOLATIONA unique/PK constraint rejected the commit
FK_VIOLATIONA foreign-key reference was missing
CHECK_VIOLATIONA check constraint or trigger rejected the commit
NOT_FOUNDA named resource (table, schema) does not exist

Rollback

There are two notions of "rollback":

  1. Server-side. When Invoke-MongrelDBTransaction fails with Conflict, the engine has already discarded the entire batch. Nothing was written.
  2. Client-side. Because ops are staged in your own array, discarding them is just a matter of not calling Invoke-MongrelDBTransaction. There is no transaction handle to roll back - the batch only exists once you send it.

Summary

GoalUse
One independent writeAdd-MongrelDBRow / Set-MongrelDBRow / Remove-MongrelDBRow
Several writes that must commit togetherInvoke-MongrelDBTransaction with an ops array
Retry safely after a network blipInvoke-MongrelDBTransaction with a stable idempotency key
Distinguish constraint classesCheck the Conflict category and read the message
Abort before sendingDon't call Invoke-MongrelDBTransaction - the batch is local

See errors.md for the full error category set and queries.md for read patterns.