Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

sql: `withTransaction` turns a failed COMMIT into a defect, not a SqlError

Aperta
#8,860 0 commenti 1 reazione 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
67/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
postgresql, typescript
Ambito
databases

Direzione di ricerca

Start at packages/effect/src/sql/SqlClient.ts:432 (Effect.orDie(options.commit(conn))) and read the surrounding withTransaction plus the onCommitFailure logic added in #8529. The linked history matters: #8558's tests in packages/pg assert the defect for a ROLLBACK-answered COMMIT and will need updating, and #7236 shows the precedent for turning BEGIN failures into typed SqlError. Done means the reproduction in the issue fails (not dies) with UniqueViolation so catchTag("SqlError") catches it, ROLLBACK/RELEASE SAVEPOINT failures stay defects, and the maintainer answers the two open questions (cleanup-failure cause, patch vs v4/next-minor) before merging.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Summary

When PostgreSQL rejects COMMIT, withTransaction dies instead of failing with a SqlError. PostgreSQL reports deferred constraint violations and serialization failures (40001) at COMMIT. The driver classifies them (UniqueViolation, SerializationError), but catchTag("SqlError") and typed retry policies never see them. withTransaction already returns Effect<A, E | SqlError, R> (packages/effect/src/sql/SqlClient.ts:63).

Is the defect intended? If not, I have a PR ready (2 source lines).

Versions: [email protected] and @effect/[email protected]. Same code on main at 2131d44bd and on the head of #8735. PostgreSQL 18, Node 26.8.2.

Reproduction

import { Cause, Effect, Exit, Redacted } from "effect"
import { PgClient } from "@effect/sql-pg"
import * as Reactivity from "effect/reactivity/Reactivity"

const url = Redacted.make("postgres://postgres:[email protected]:5432/postgres")
const show = (label, exit) =>
  console.log(label, Exit.isSuccess(exit) ? exit.value : {
    hasFails: Cause.hasFails(exit.cause),
    hasDies: Cause.hasDies(exit.cause),
    reason: Cause.squash(exit.cause).reason._tag
  })

const program = Effect.gen(function*() {
  const a = yield* PgClient.make({ url, maxConnections: 1 })
  yield* a`drop table if exists dfr`
  yield* a`create table dfr (id int unique deferrable initially deferred)`
  show("deferred unique", yield* Effect.exit(a.withTransaction(a`insert into dfr values (1), (1)`)))
  show("catchTag", yield* Effect.exit(a.withTransaction(a`insert into dfr values (2), (2)`).pipe(
    Effect.catchTag("SqlError", (e) => Effect.succeed(`caught ${e.reason._tag}`))
  )))
})

await Effect.runPromise(Effect.scoped(program).pipe(Effect.provide(Reactivity.layer)))

Actual:

deferred unique { hasFails: false, hasDies: true, reason: 'UniqueViolation' }
catchTag { hasFails: false, hasDies: true, reason: 'UniqueViolation' }

A write skew under SERIALIZABLE dies the same way with SerializationError. The server log shows each error on STATEMENT: COMMIT.

Expected: each exit fails with the SqlError, and catchTag returns caught UniqueViolation.

Cause

packages/effect/src/sql/SqlClient.ts:432: effect = Effect.orDie(options.commit(conn))

History

  • The orDie came with the sqlfx import (#2104) with no stated reason.
  • #7236 made a failed BEGIN a typed SqlError. It left COMMIT unchanged.
  • #8257 made @effect/sql-sqlite-do fail a rejected native commit with a typed SqlError.
  • #8529 (merged 2026-09-25) added onCommitFailure. Its body says: "Preserve the original COMMIT defect and include cleanup failure in its cause."
  • #8558 (2026-09-27) fails a pg COMMIT that PostgreSQL answers with ROLLBACK. It turns that failure into a defect "the same way it handles any failed COMMIT", and its tests assert the defect.

Proposal

Drop the orDie, so withTransaction fails with the SqlError from commit. The public type does not change. ROLLBACK and RELEASE SAVEPOINT failures stay defects.

One gap: when SQLite onCommitFailure cleanup fails, the cause becomes Fail(SqlError) plus Die(cleanup). catchTag("SqlError") and Effect.catch recover and drop the Die, so the caller never sees the cleanup failure. The connection stays poisoned, so the next query still fails; no data is lost. Should a cleanup failure keep the whole exit a defect?

This changes behavior: Effect.catch now also sees COMMIT failures. Should it go to main as a patch, like #7236, or to v4/next-minor?

Lingua principale
TypeScript
Stelle
16.7k
Fork
808
Merge medio
10h 36m
PR unite (30g)
449

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di Effect-TS/effect

Tutte le issue di Effect-TS/effect

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.