# P0004 — assert_failure

> Source-backed reference for PostgreSQL SQLSTATE P0004.
---

# P0004

## At a glance {#at-a-glance}

P0004 is PL/pgSQL `assert_failure`. The fixed ASSERT path reports ERROR with either the evaluated message or `assertion failed`.

<!-- BEGIN SQLSTATE FACTS: generated by scripts/generate.py; do not edit -->

| Field | Value |
| --- | --- |
| SQLSTATE | `P0004` |
| Condition | `assert_failure` |
| Status | `active` |
| Known present by | `9.5.0` |
| Locked snapshots | `9.5.25, 9.6.24, 10.23, 11.22, 12.22, 13.23, 14.24, 15.19, 16.15, 17.11, 18.6, 19beta3` |
| Macros | `ERRCODE_ASSERT_FAILURE` |
| Aliases | `—` |

<!-- source facts: data/errcodes/P0004.json -->
<!-- END SQLSTATE FACTS -->

The shared `assertion_failure_recovery` case passed on PostgreSQL 18.6 and PostgreSQL 10.21. With `plpgsql.check_asserts` enabled, the false ASSERT returned `P0004` with `value must be positive` and left the explicit transaction `INERROR`; `ROLLBACK` restored `IDLE`, a valid input succeeded, and the disabled-assert control returned zero.

<!-- BEGIN SQLSTATE SNIPPET: assertion_failure_recovery -->
```sql
CREATE OR REPLACE FUNCTION p0004_assert(value integer) RETURNS integer LANGUAGE plpgsql AS $$ BEGIN ASSERT value > 0, 'value must be positive'; RETURN value; END $$;
SET plpgsql.check_asserts = on;
SHOW plpgsql.check_asserts;
BEGIN;
SELECT p0004_assert(0);
ROLLBACK;
SELECT p0004_assert(1);
SET plpgsql.check_asserts = off;
SHOW plpgsql.check_asserts;
SELECT p0004_assert(0);
SET plpgsql.check_asserts = on;
SHOW plpgsql.check_asserts;
SELECT p0004_assert(1);
```
<!-- END SQLSTATE SNIPPET -->

## Meaning {#meaning}

An ASSERT is an executable invariant check inside PL/pgSQL. PostgreSQL evaluates its Boolean condition; a false **or NULL** result takes the assertion-failure path. The fixed executor then reports `ERROR` with SQLSTATE `P0004`: it evaluates the message expression only on that path, uses a non-NULL result as the primary text, and uses `assertion failed` for a NULL or omitted message. With checks disabled, it skips both the condition and message expression.

`plpgsql.check_asserts` is a session setting that controls whether ASSERT statements are checked. With it enabled, a failed assertion is a real ERROR and an explicit transaction enters `INERROR`. With it disabled, ASSERT statements are skipped; the same false input does not prove the invariant and should not be used as a production validation substitute.

The selected runtime exercises a false condition with a non-NULL message and a false condition with checks disabled. The `NULL` condition and the no-message fallback are source-confirmed semantics, not additional natural observations in this batch.

## Diagnosis {#diagnosis}

Start with the ErrorResponse fields and the server log entry together. In the fixed 18.6 path, `message_primary` is either the evaluated message or `assertion failed`; the runtime case also recorded `ERROR`, `P0004`, `exec_stmt_assert`, and context identifying the PL/pgSQL function and `line 1 at ASSERT`. Check `SHOW plpgsql.check_asserts` on the affected session, because a setting change in another connection does not change this one.

If the primary is `assertion failed`, check whether the ASSERT had no message or its message expression evaluated to NULL. If there is no P0004 at all, inspect `SHOW plpgsql.check_asserts` first: a disabled setting skips the check before evaluating its condition. Keep the function source and the session setting together when comparing two calls; a pooled connection can have a different setting from the one that created the function.

Separate a broken program invariant from expected business input. An assertion such as `value > 0` is useful for an assumption that should always hold after validation; an expected negative user value belongs in ordinary validation, a constraint, or an explicit application error with a deliberate SQLSTATE. If the error occurred inside an explicit transaction, inspect the transaction state before issuing another command: the observed case was `INERROR` until `ROLLBACK`, not a connection failure.

## Response {#response}

For an explicit transaction, issue `ROLLBACK` before unrelated work, then reproduce with the corrected invariant or input. The shared case verified `ROLLBACK → IDLE`, a valid call returning `1`, and the same assertion remaining silent when `plpgsql.check_asserts` was set to `off`.

Use `WHEN ASSERT_FAILURE` when a PL/pgSQL block deliberately wants to handle this named condition. `WHEN OTHERS` does not catch `ASSERT_FAILURE`, so a broad handler cannot be used as a hidden assertion switch. An exception block can recover through its documented subtransaction boundary, but swallowing the failure without checking the invariant leaves the program assumption unverified. Keep assertions enabled while diagnosing; disabling them is a diagnostic control, not a repair.

## Versions {#versions}

The locked catalogue records P0004 from 9.5.0 through the listed formal snapshots and 19beta3. The fixed executor source is PostgreSQL 18.6 [`pl_exec.c#L3965-L3968`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/pl/plpgsql/src/pl_exec.c#L3965-L3968). The official [PL/pgSQL error and message documentation](https://www.postgresql.org/docs/18/plpgsql-errors-and-messages.html) covers ASSERT and named conditions; the [control-structures error-trapping documentation](https://www.postgresql.org/docs/18/plpgsql-control-structures.html#PLPGSQL-ERROR-TRAPPING) defines the `EXCEPTION` subtransaction and handler matching boundary. The latest/PG10 runtime case confirms the described P0004 and transaction behavior for the shared function.

## Related {#related}

[`P0002`](../p0002/) for PL/pgSQL no-data handling, [`P0003`](../p0003/) for strict multi-row handling, and [`P0000`](../p0000/) for the PL/pgSQL error category. Use the actual SQLSTATE from the response rather than treating every PL/pgSQL failure as P0004.

## Sources {#sources}

The fixed implementation is [`pl_exec.c#L3965-L3968`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/pl/plpgsql/src/pl_exec.c#L3965-L3968). The official [PL/pgSQL error and message reference](https://www.postgresql.org/docs/18/plpgsql-errors-and-messages.html) documents ASSERT semantics, while the [control-structures error-trapping reference](https://www.postgresql.org/docs/18/plpgsql-control-structures.html#PLPGSQL-ERROR-TRAPPING) documents the `WHEN OTHERS` exclusion and subtransaction boundary. The structured [evidence record](../data/evidence/p0004.json) pins the source SHA, runtime summaries, raw results, and shared snippet registry.
