Skip to content

feat: typed constraint errors (unique, not-null, fk, check) - #6154

Open
adsqx wants to merge 2 commits into
drizzle-team:mainfrom
adsqx:feat/typed-constraint-errors
Open

feat: typed constraint errors (unique, not-null, fk, check)#6154
adsqx wants to merge 2 commits into
drizzle-team:mainfrom
adsqx:feat/typed-constraint-errors

Conversation

@adsqx

@adsqx adsqx commented Aug 20, 2026

Copy link
Copy Markdown

/claim #376
Fixes #376

feat: typed constraint errors across pg, mysql, sqlite, and singlestore

This builds on the typed-error hierarchy explored in #5383 and keeps its public class names, while completing SingleStore wiring, repo-standard is() support, delete-side MySQL FK errors, and safe handling of non-Error throws.

Catching a unique-violation today means parsing driver-specific code / errno / message strings. This wraps those into one typed instanceof / is() chain that still keeps the original driver error on .cause.

try {
  await db.insert(users).values({ email });
} catch (e) {
  if (e instanceof UniqueConstraintViolationError) {
    e.constraintName; // 'users_email_key'
    e.table;          // 'users'
    e.cause;          // original pg/mysql2/libsql error
  }
}

DrizzleQueryError keeps the same behavior for everything that is not a constraint failure (syntax, connection, missing relation, …). Additive — no existing catch of DrizzleQueryError breaks.

Mapping

unique not null foreign key check
Postgres 23505 23502 23503 23514
MySQL / SingleStore 1062 1048 1451/1452/1216/1217 3819
SQLite SQLITE_CONSTRAINT_UNIQUE / PRIMARYKEY + libsql message NOTNULL FOREIGNKEY CHECK

Wired in the one place every driver already goes through: queryWithCache in pg-core, mysql-core, sqlite-core, and singlestore-core.

Classes are exported from drizzle-orm (export * from './errors.ts' already).

Related open work

PR Difference
#3996 WIP since May 2025; covers broader debugging/logging and currently only postgres.js
#5383 Typed-error foundation used here; this PR adds SingleStore, Drizzle is(), complete MySQL FK extraction/delete codes, and non-Error safety
#6064 Postgres-only; its synchronous try/catch around a thenable does not cover async rejections
#5877 Also adds an onError hook and changes 68 files; broader, separate scope
#5412 Adds an onError callback rather than typed constraint classes

No onError hook in this PR. Constraint classes first; a hook can sit on top later.

Tests

drizzle-orm/tests/errors.test.ts — 22 cases, no live database: SQLSTATE, mysql errno (including FK table/column/constraint extraction), sqlite codes + libsql message fallback, unknown codes stay DrizzleQueryError, instanceof / is() behavior, and non-Error throws.

adsqx and others added 2 commits August 21, 2026 00:20
Map Postgres SQLSTATE, MySQL/SingleStore errno and SQLite
SQLITE_CONSTRAINT_* (plus libsql message fallback) onto
Unique/NotNull/ForeignKey/Check subclasses of DrizzleQueryError.

Wire wrapQueryError in every dialect queryWithCache so gel and
singlestore get the same mapping as pg/mysql/sqlite. Unknown
errors still become DrizzleQueryError.
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.

Add proper error handling and wrapping

1 participant