Check constraint name in PgError unique violations

Checking only pgErr.Code == "23505" is insufficient when a
table has multiple unique constraints. Always verify
pgErr.ConstraintName as well to avoid mapping unrelated
violations to the wrong sentinel error.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-05-19 11:03:37 +04:00
parent f81168bc18
commit f1357a7e20
2 changed files with 32 additions and 1 deletions

View File

@@ -149,6 +149,37 @@ var (
Map `pgx.ErrNoRows` to `ErrResourceNotFound`. Check unique constraint violations for `ErrResourceAlreadyExists`, foreign key violations for `ErrResourceInUse`.
**Always check both `pgErr.Code` and `pgErr.ConstraintName`** when mapping PostgreSQL errors to sentinel errors. Checking only the error code (e.g. `"23505"`) is not enough — a table may have multiple unique constraints, and a blind code-only check maps unrelated constraint violations to the wrong sentinel error.
```go
// Good — checks both code and constraint name
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
if pgErr.Code == "23505" && pgErr.ConstraintName == "controls_framework_ref_unique" {
return ErrResourceAlreadyExists
}
}
// Good — multiple constraints on the same table
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
if pgErr.Code == "23505" {
switch pgErr.ConstraintName {
case "document_versions_document_id_major_minor_key",
"document_one_active_version_idx":
return ErrResourceAlreadyExists
}
}
}
// Bad — code-only check; any unique violation silently becomes ErrResourceAlreadyExists
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
if pgErr.Code == "23505" {
return ErrResourceAlreadyExists
}
}
```
The same applies to foreign key violations (`"23503"`) mapped to `ErrResourceInUse` — always verify the constraint name.
## Filters
Filters implement `SQLFragment() string` and `SQLArguments() pgx.NamedArgs`. Use double pointers for three-state filtering: `nil` = no filter, `*nil` = IS NULL, `*val` = equals.

View File

@@ -108,7 +108,7 @@ INSERT INTO common_third_party_domains (
_, err := conn.Exec(ctx, q, args)
if err != nil {
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
if pgErr.Code == "23505" {
if pgErr.Code == "23505" && pgErr.ConstraintName == "common_third_party_domains_party_domain_key" {
return ErrResourceAlreadyExists
}
}