Exception → PostgreSQL ErrorResponse classification for the pgwire layer. Owns three things:
:foo/bar, {:db/id 47, …}).getServerErrorMessage() /
Diagnostics.constraint_name.Throw sites within datahike.pg.* describe errors structurally:
(throw (ex-info "<short internal description>" {:error :undefined-column :table "employee" :column "dept_id"}))
The wire boundary (classify-exception) then derives:
error-categories:format fn (falls back to the
throw site's own message when nil)extract-error-fieldsPG's own backend works the same way: throw sites call
ereport(ERROR, errcode(ERRCODE_UNDEFINED_COLUMN), errmsg(...))
with structured args; the wire layer (pqcomm.c) emits the protocol
message from the resulting ErrorData struct. There is no
report_undefined_column() helper at every throw site. That's the
pattern this namespace implements.
:sqlstate in ex-data (override; bypasses formatter).:error key in error-categories →
:format fn (or fallback):datahike/canceled in ex-data → "57014" (cancelled).classify-message) — a
last-resort safety net for unstructured errors thrown by
Datahike core that we don't own. New pgwire throws should NOT
rely on this.Pgwire-side throw categories live in error-categories. Each entry
has a :sqlstate and an optional :format fn. Datahike-internal
:error keys map directly to SQLSTATEs (no formatter — Datahike's
own message text comes through, with the SQLAlchemy-class
missing-attribute case rewritten to PG vocabulary).
Canonical PG code list: postgres/src/backend/utils/errcodes.txt.
Exception → PostgreSQL ErrorResponse classification for the pgwire
layer. Owns three things:
1. Mapping a Throwable → SQLSTATE code (the wire ABI clients
branch on).
2. Producing a PG-shaped user-facing message (don't leak
Datahike vocabulary like `:foo/bar`, `{:db/id 47, …}`).
3. Populating the ErrorResponse fields (n / t / c / d / D / H)
that ORMs read via `getServerErrorMessage()` /
`Diagnostics.constraint_name`.
Throw sites within `datahike.pg.*` describe errors structurally:
(throw (ex-info "<short internal description>"
{:error :undefined-column
:table "employee"
:column "dept_id"}))
The wire boundary (`classify-exception`) then derives:
- SQLSTATE via `error-categories`
- message via the category's `:format` fn (falls back to the
throw site's own message when nil)
- fields via `extract-error-fields`
## Why centralised formatting
PG's own backend works the same way: throw sites call
`ereport(ERROR, errcode(ERRCODE_UNDEFINED_COLUMN), errmsg(...))`
with structured args; the wire layer (`pqcomm.c`) emits the protocol
message from the resulting `ErrorData` struct. There is no
`report_undefined_column()` helper at every throw site. That's the
pattern this namespace implements.
## Lookup order in classify-exception
1. Explicit `:sqlstate` in ex-data (override; bypasses formatter).
2. `:error` key in `error-categories` →
- SQLSTATE from the registry
- message from the entry's `:format` fn (or fallback)
3. `:datahike/canceled` in ex-data → "57014" (cancelled).
4. Datahike-emitted message regex (`classify-message`) — a
last-resort safety net for unstructured errors thrown by
Datahike core that we don't own. New pgwire throws should NOT
rely on this.
5. Fallback: "XX000" (internal_error).
## Categories
Pgwire-side throw categories live in `error-categories`. Each entry
has a `:sqlstate` and an optional `:format` fn. Datahike-internal
`:error` keys map directly to SQLSTATEs (no formatter — Datahike's
own message text comes through, with the SQLAlchemy-class
missing-attribute case rewritten to PG vocabulary).
Canonical PG code list: postgres/src/backend/utils/errcodes.txt.(classify-exception e)Map a Throwable to [sqlstate message fields] for emission via the
pgwire ErrorResponse. Falls back to ["XX000" <some-msg> nil]
when no rule matches.
Resolution order (first match wins):
:sqlstate in ex-data — full override; uses the
throw site's own message.:error key matched against error-categories — derives
SQLSTATE; runs the category's :format fn over ex-data; if
the formatter returns a string, that's the user-facing
message, otherwise the throw site's message stands.:datahike/canceled flag → 57014.:error key matched against dh-error->sqlstate (Datahike
internal). Tries rewrite-datahike-message to upgrade the
message + extract extra fields.rewrite-datahike-message on a bare message (no error key).classify-message regex (last-resort SQLSTATE only).XX000.fields is a Java Map<String,String> of optional ErrorResponse
field codes (n, t, c, d, D, H), or nil.
Map a Throwable to `[sqlstate message fields]` for emission via the
pgwire ErrorResponse. Falls back to `["XX000" <some-msg> nil]`
when no rule matches.
Resolution order (first match wins):
1. Explicit `:sqlstate` in ex-data — full override; uses the
throw site's own message.
2. `:error` key matched against `error-categories` — derives
SQLSTATE; runs the category's `:format` fn over ex-data; if
the formatter returns a string, that's the user-facing
message, otherwise the throw site's message stands.
3. `:datahike/canceled` flag → 57014.
4. `:error` key matched against `dh-error->sqlstate` (Datahike
internal). Tries `rewrite-datahike-message` to upgrade the
message + extract extra fields.
5. `rewrite-datahike-message` on a bare message (no error key).
6. `classify-message` regex (last-resort SQLSTATE only).
7. Fallback `XX000`.
`fields` is a Java Map<String,String> of optional ErrorResponse
field codes (n, t, c, d, D, H), or nil.(classify-message msg)Last-resort SQLSTATE-only classifier for Datahike messages we can't
structurally rewrite. Returns the SQLSTATE code or nil. Used only
when ex-data has no :error / :sqlstate and
rewrite-datahike-message doesn't match.
Do not add new reliance on this from pgwire code — set :error or
:sqlstate at the throw site instead.
Last-resort SQLSTATE-only classifier for Datahike messages we can't structurally rewrite. Returns the SQLSTATE code or nil. Used only when ex-data has no `:error` / `:sqlstate` and `rewrite-datahike-message` doesn't match. Do not add new reliance on this from pgwire code — set `:error` or `:sqlstate` at the throw site instead.
Map Datahike's :error keyword (set by datahike core when it raises
ex-info) to a PG SQLSTATE. Covers errors the wire layer doesn't
throw itself but receives via the cause chain.
Datahike's messages are unstructured; the wire layer uses them
verbatim unless rewrite-datahike-message recognises a known
pattern and produces a PG-shaped substitute.
Map Datahike's `:error` keyword (set by datahike core when it raises ex-info) to a PG SQLSTATE. Covers errors the wire layer doesn't throw itself but receives via the cause chain. Datahike's messages are unstructured; the wire layer uses them verbatim unless `rewrite-datahike-message` recognises a known pattern and produces a PG-shaped substitute.
Pgwire-side error categories. Throw sites set :error in ex-data to
one of these keys.
The :format fn receives the full ex-data map and returns the
PG-shaped user-facing message, or nil to fall through to the
throw site's own message string.
Pgwire-side error categories. Throw sites set `:error` in ex-data to one of these keys. The `:format` fn receives the full ex-data map and returns the PG-shaped user-facing message, or nil to fall through to the throw site's own message string.
(pg-error category data)Build an ex-info whose message is the PG-shaped formatted form for
the category + data, suitable to pass to throw. Sets :error so
classify-exception produces the right SQLSTATE + fields when the
exception flows through the wire boundary's catch.
Throw sites should prefer this over raw ex-info because:
.getMessage(e) returns the PG-shaped string directly, so paths
that bypass classify-exception (e.g. QueryHandler.parse() going
straight to the Java wire layer) still produce a correct
user-facing message.Build an ex-info whose message is the PG-shaped formatted form for the category + data, suitable to pass to `throw`. Sets `:error` so `classify-exception` produces the right SQLSTATE + fields when the exception flows through the wire boundary's catch. Throw sites should prefer this over raw `ex-info` because: - `.getMessage(e)` returns the PG-shaped string directly, so paths that bypass `classify-exception` (e.g. QueryHandler.parse() going straight to the Java wire layer) still produce a correct user-facing message. - The full ex-data is preserved so downstream classifier passes extract structured fields (table, column, constraint, …).
(pg-error-message category data)Return the PG-shaped formatted message for category given data,
or nil when the category has no formatter or it doesn't produce
a message for the given data.
Return the PG-shaped formatted message for `category` given `data`, or nil when the category has no formatter or it doesn't produce a message for the given data.
(rewrite-datahike-message msg)Translate a Datahike-emitted error message into PG vocabulary.
Returns [code message extra-fields] if the message matches a
known pattern, nil otherwise.
Datahike's :transact/schema for an unknown attribute reads:
Bad entity attribute :employee/dept_id at {:db/id 47, …}, not defined in current schema
PG would say:
column "dept_id" of relation "employee" does not exist
with SQLSTATE 42703 (UndefinedColumn). Without this rewrite,
SQL clients see SQLSTATE 22P02 (InvalidTextRepresentation) and
:db/id 47 in the user-facing string.
Translate a Datahike-emitted error message into PG vocabulary.
Returns `[code message extra-fields]` if the message matches a
known pattern, nil otherwise.
Datahike's `:transact/schema` for an unknown attribute reads:
`Bad entity attribute :employee/dept_id at {:db/id 47, …},
not defined in current schema`
PG would say:
`column "dept_id" of relation "employee" does not exist`
with SQLSTATE 42703 (UndefinedColumn). Without this rewrite,
SQL clients see SQLSTATE 22P02 (InvalidTextRepresentation) and
`:db/id 47` in the user-facing string.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |