PostgreSQL data manipulation uses INSERT, UPDATE, and DELETE; RETURNING exposes the rows actually changed, and ON CONFLICT resolves a declared uniqueness conflict atomically. Safe writes define the invariant, target exact rows, check the affected result, run inside an appropriate transaction, and remain correct when requests retry.
Know the foundation constraints. Treat every write as a set transformation: PostgreSQL may change zero, one, or many rows. Your intent must include the expected cardinality.
Update with a tenant boundary and current-state precondition:
$1 and $2 are server-side parameters in application APIs. Zero returned rows could mean not found, wrong tenant, or invalid current state; the application must choose a non-leaking response.
Delete dependents only when the product lifecycle permits it:
Use a declared conflict target:
This is atomic for that unique key. It does not make a whole workflow idempotent, and it can overwrite a newer value unless the update has a version/time predicate.
Create an idempotency table whose unique key includes tenant and operation scope. In one transaction, claim the key, perform the business change, and store the stable response. A concurrent duplicate either sees the committed result or conflicts and retries according to a bounded policy.
Never use ON CONFLICT DO NOTHING as a blanket way to suppress errors. It can turn a missing expected write into apparent success.
Model calls and tool workflows retry after timeouts, network failures, or uncertain responses. Without idempotency, “create ticket,” “send refund,” or “append memory” can happen twice. Persist tool-call identity, parameters hash, approval, state, and outcome under a unique constraint; the model’s conversational memory is not a transaction log.
Inside a rollbackable transaction:
Use GET DIAGNOSTICS inside procedural code or the application driver’s row count when RETURNING is not appropriate.
Add version integer NOT NULL DEFAULT 1 to tickets in a rolled-back experiment. Write an update that succeeds only for an expected version and increments it.
Acceptance criterion: the first update returns version 2; replaying version 1 returns zero rows without overwriting the newer state.