Skip to content

fix(data-bbs): map 23505 UniqueConstraintViolation to JSON-RPC 409 (#508) - #511

Open
soberm wants to merge 1 commit into
mainfrom
fix/issue-508-unique-violation-409
Open

fix(data-bbs): map 23505 UniqueConstraintViolation to JSON-RPC 409 (#508)#511
soberm wants to merge 1 commit into
mainfrom
fix/issue-508-unique-violation-409

Conversation

@soberm

@soberm soberm commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Problem

Follow-up to #500. Duplicate-key / unique-constraint violations (SQLSTATE 23505, UniqueConstraintViolation) were thrown as plain named Errors, so the JSON-RPC serializer (packages/core/src/rpc.ts) mapped them to code 500 — the same "5xx for a 4xx cause" smell #500 fixed for OCC/40001. A duplicate key is a client-caused conflict, so it should be 409 (Conflict).

Issue #, if available: #508

Changes

Mirrors the OCC→409 pattern from #500.

  • @aws-blocks/bb-data — new uniqueConstraintConflict(cause) helper (parallel to serializationConflict) returning ApiError('The item violates a unique constraint', 409, { name: DatabaseErrors.UniqueConstraintViolation, cause }). Routed through it at every 23505 site: pg-error-translator.ts, pglite-engine.ts, and data-api-engine.ts (both the SQLState-parsed code === '23505' path and the message-matched duplicate-key path).
  • @aws-blocks/bb-distributed-datatranslateDsqlError's 23505 branch now throws the equivalent 409 ApiError (mirrors its existing 40001 branch).

error.name is preserved end-to-end so isBlocksError(e, DatabaseErrors.UniqueConstraintViolation) keeps matching on both server and client. The raw driver error is kept only as the server-side cause (non-enumerable via ApiError; the serializer never emits it), and the client-visible message is a fixed string (no column/driver text leak). retriable is false — a blind retry of a duplicate insert fails identically.

Out of scope / unchanged: ConnectionFailed, QueryFailed and other genuine infra errors correctly stay 500; the 40001 serialization/OCC path (already 409 from #500) is untouched.

Validation

  • TDD: unit tests updated from the old plain-Error/name-only assertions to assert ApiError + status === 409 + preserved name (pg-error-translator.test.ts, pglite-engine.test.ts, data-api-engine.test.ts ×3, bb-distributed-data/errors.test.ts) — confirmed red before the fix, green after.
  • Over-the-wire e2e (per AGENTS.md §11 — serialization/behavior change): test-apps/comprehensive/test/database.test.ts and dsql.test.ts provoke a real duplicate-key insert and assert e instanceof ApiError && e.status === 409 && isBlocksError(e, …UniqueConstraintViolation). No type casts. The PGlite/DSQL mocks enforce the primary key, so the path is genuinely exercised locally.
  • Local: npm run build, lint, lint:deps, changed-package unit tests (bb-data 381/0, bb-distributed-data 164/0), and both changeset-guard checks (verify-coverage, verify-umbrella) pass.
  • Changeset: .changeset/unique-violation-409.md@aws-blocks/bb-data + @aws-blocks/bb-distributed-data minor (pre-1.0 breaking = minor); @aws-blocks/blocks patch (umbrella).

Checklist

  • PR description included
  • Tests are changed or added
  • Relevant documentation is changed or added (and PR referenced)

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

)

Follow-up to PR #500. Duplicate-key / unique-constraint violations
(SQLSTATE 23505) were thrown as plain named Errors, which the JSON-RPC
serializer maps to code 500 — the same "5xx for a 4xx cause" smell PR #500
fixed for OCC/40001. A duplicate key is a client-caused conflict, so it now
returns 409 (Conflict).

- bb-data: new uniqueConstraintConflict(cause) helper (ApiError, 409,
  name DatabaseErrors.UniqueConstraintViolation, fixed message, raw error
  kept only as server-side cause, not retriable). Routed through it in
  pg-error-translator, pglite-engine, and data-api-engine (both the
  SQLState-parsed and message-matched 23505 paths).
- bb-distributed-data: translateDsqlError's 23505 branch now throws the
  equivalent 409 ApiError (mirrors its existing 40001 branch).

error.name is preserved end-to-end so isBlocksError() keeps matching.
ConnectionFailed / QueryFailed and other genuine infra errors correctly
stay 500; the 40001 serialization path is unchanged.

Behavior change: these paths now return 409 instead of 500.

Refs #508.
@changeset-bot

changeset-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1e199f0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@aws-blocks/bb-distributed-data Minor
@aws-blocks/bb-data Minor
@aws-blocks/blocks Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@soberm
soberm marked this pull request as ready for review September 8, 2026 12:35
@soberm
soberm requested a review from a team as a code owner September 8, 2026 12:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant