Files
ResolveSpec/pkg/common/TRANSACTIONS.md
T

2.6 KiB

Request Transactions (cheatsheet)

Every DB statement and every DB-touching hook of one request runs on one transaction. Hooks never get the pool.

Rules

  • hookCtx.Tx is always the open transaction (never h.db), except BeforeHandle, which runs before any tx and must not touch the DB.
  • OnTxBegin fires once, first, in every tx the handler opens (incl. the second short tx).
  • OnTxBegin error or abort: rollback, client gets a generic error, nothing leaked.
  • Begin or commit failure: generic error (transaction_error / "Transaction failed" in websocketspec, mqttspec, funcspec).
  • Transaction-local state (set_config(..., true), RLS GUCs) is only visible on that tx. Set it in OnTxBegin.

Transactions per operation

Operation Tx 1 Tx 2 (short, after commit)
read BeforeRead, count, scan, AfterRead* restheadspec: AfterRead
create Before*, insert re-fetch, BeforeScan, AfterCreate
update Before*, select, update, AfterUpdate† re-fetch (+ AfterUpdate where noted)
delete (single/batch) BeforeDelete, select, delete, AfterDelete none
funcspec query BeforeQuery*, BeforeSQLExec, SQL, After* BeforeResponse

* resolvespec, websocketspec, mqttspec, resolvemcp. restheadspec runs AfterRead in tx 2. † restheadspec, websocketspec, mqttspec run AfterUpdate in tx 2. resolvespec, resolvemcp run it in tx 1.

Tx 2 exists so the re-fetch sees trigger changes from the committed write.

Per spec

Spec OnTxBegin Helper
resolvespec, restheadspec, websocketspec, resolvemcp, funcspec own HookType = common.TxHookName Handler.runInTx
mqttspec re-exports websocketspec.OnTxBegin Handler.runInTx
  • common.RunRequestTx(ctx, db, TxContext, onBegin, body): open tx, SetTx, onBegin, body.
  • common.TxContext: SetTx(tx); implemented by each spec's HookContext.

RLS / transaction settings (pkg/security)

  • SecurityList.SetTxSettings(fn): fn(SecurityContext) (map[string]string, error); nil disables.
  • Every spec's RegisterSecurityHooks registers OnTxBegin → security.StampTxSettings. fn is read per call, so set order does not matter.
  • Stamps via set_config(name, value, true) in name order, before any other SQL.
  • Fail closed: fn error, invalid name, or non-Postgres driver with a non-empty map aborts the tx.
  • Name: dotted identifier (ns.name). Value is hex-encoded in SQL, never inlined.
  • Low level: security.ApplyTxSettings(secCtx, tx, map).

Test notes

  • sqlmock + SetMaxOpenConns(1): any pool use inside an open tx blocks and fails.
  • restheadspec model-based updates/reads need the bun adapter.