select open change scope Open full search

PG.CENTER connects PostgreSQL documentation, reference, and ecosystem knowledge. Maintained by Pigsty.

SQLSTATE / CLASS P0 · PL/PGSQL ERROR

assert_failure

SQLSTATE
P0004
Condition name
assert_failure
Class
PL/pgSQL Error
Source macro
ERRCODE_ASSERT_FAILURE
Evidence
Observed at runtime in the source evidence
Analysis & operational context

English SQLSTATE atlas: authored explanations and source/runtime evidence are separate from the manual definitions. View source ↗

<h1>P0004</h1>

At a glance

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

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.

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);

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

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

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

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. The official PL/pgSQL error and message documentation covers ASSERT and named conditions; the control-structures error-trapping documentation defines the EXCEPTION subtransaction and handler matching boundary. The latest/PG10 runtime case confirms the described P0004 and transaction behavior for the shared function.

Sources

The fixed implementation is pl_exec.c#L3965-L3968. The official PL/pgSQL error and message reference documents ASSERT semantics, while the control-structures error-trapping reference documents the WHEN OTHERS exclusion and subtransaction boundary. The structured evidence record pins the source SHA, runtime summaries, raw results, and shared snippet registry.

Source evidence

Evidence belongs to the frozen source and runtime versions listed here. It is not a runtime verification of the selected manual version.

P0004 is assert_failure in PostgreSQL-specific Class P0.

Method: Read the fixed definition row.

src.errcodes.18.6

PL/pgSQL assertion execution reports P0004 at ERROR level, using the evaluated message when present and `assertion failed` when no message is supplied.

Method: Read the fixed `ASSERT` execution path and both errmsg branches.

src.path.plpgsql

The shared assertion_failure_recovery case passed on PostgreSQL 18.6 and PostgreSQL 10.21: the false ASSERT returned P0004 with value must be positive, the explicit transaction was INERROR until ROLLBACK, a valid input succeeded, and the disabled-assert control returned zero.

Method: Read both target summaries and the fixed snippet registry.

Limits: This is a bounded case for the shared function and ASSERT setting; it does not establish behavior for arbitrary assertion messages or non-PL/pgSQL code.

runtime.P0004-same-session-latest-final.latest runtime.P0004-same-session-pg10-final.pg10 snippet-registry.P0004

The latest target recorded ERROR/P0004 from exec_stmt_assert at pl_exec.c:3968 with context at ASSERT; the PG10 target recorded the same SQLSTATE and diagnostic with its version-specific source line.

Method: Compare structured diagnostics in both target summaries.

runtime.P0004-same-session-latest-final.latest runtime.P0004-same-session-pg10-final.pg10

The fixed PL/pgSQL ASSERT path reports P0004 when its Boolean condition evaluates false or NULL; the supplied message expression is used as primary text, otherwise the executor uses assertion failed.

Method: Read the fixed executor branch and the official PL/pgSQL ASSERT documentation.

src.path.plpgsql doc.plpgsql.18

plpgsql.check_asserts controls assertion checking for the session, and the PL/pgSQL handler rules exclude ASSERT_FAILURE from WHEN OTHERS while allowing a named ASSERT_FAILURE condition.

Method: Read the PL/pgSQL control-structures error-trapping section for EXCEPTION subtransactions and condition matching; read the fixed executor for the session setting boundary.

Limits: The setting is session-scoped; this claim does not make a connection-level setting change global.

doc.plpgsql-control.18 src.path.plpgsql

The observed case entered INERROR after P0004, returned to IDLE after ROLLBACK, and then accepted a valid call; disabling check_asserts suppressed the false assertion in the control branch.

Method: Read both passed target summaries and the ordered shared registry.

runtime.P0004-same-session-latest-final.latest runtime.P0004-same-session-pg10-final.pg10 snippet-registry.P0004

  • src/backend/utils/errcodes.txt · REL_18_6 ·
    724edf9bde9d356724ad384a2e196edc3c9f80f7
  • raw/calls/REL_18_6.jsonl · ·
  • src/pl/plpgsql/src/pl_exec.c · REL_18_6 ·
    724edf9bde9d356724ad384a2e196edc3c9f80f7
  • verify/results/P0004-same-session-latest-final/latest/summary.json · ·
  • verify/results/P0004-same-session-latest-final/latest/raw.jsonl · ·
  • verify/results/P0004-same-session-pg10-final/pg10/summary.json · ·
  • verify/results/P0004-same-session-pg10-final/pg10/raw.jsonl · ·
  • verify/cases/P0004/snippets.json · ·
  • doc/src/sgml/plpgsql.sgml · PG18-docs ·
    724edf9bde9d356724ad384a2e196edc3c9f80f7
  • doc/src/sgml/plpgsql.sgml · PG18-docs ·
    724edf9bde9d356724ad384a2e196edc3c9f80f7

Message templates

ERROR · message.assert

Primary

assertion failed

Primary

%s

Reproduction & repair cases

assertion_failure_recovery · PG 10, 18

Preconditions

  • A disposable runner target has PL/pgSQL available.
  • plpgsql.check_asserts is explicitly enabled before the trigger.

Trigger: Call a PL/pgSQL function whose ASSERT condition is false inside an explicit transaction.

Expected assertions

  • The natural ASSERT failure has SQLSTATE P0004 and the expected diagnostic.
  • The failed explicit transaction is INERROR and ROLLBACK returns it to IDLE.
  • A valid call succeeds after rollback, and an explicit check_asserts=off control suppresses the assertion.
  • Re-enabling check_asserts makes the valid call succeed and the runner cleans up.

Repair: Rollback the failed transaction, use a valid input, and keep assertions enabled for production behavior.

Cleanup: Drop the case schema with an owner connection.

Recorded runtime evidence

18.6 (Homebrew) · passed

Run: P0004-same-session-latest-final

{
  "severity": "ERROR",
  "sqlstate": "P0004",
  "after_error": "INERROR",
  "repair_result": 1,
  "after_rollback": "IDLE",
  "message_primary": "value must be positive",
  "disabled_control_result": 0
}
10.21 (Debian 10.21-1.pgdg90+1) · passed

Run: P0004-same-session-pg10-final

{
  "severity": "ERROR",
  "sqlstate": "P0004",
  "after_error": "INERROR",
  "repair_result": 1,
  "after_rollback": "IDLE",
  "message_primary": "value must be positive",
  "disabled_control_result": 0
}
Version history 1
  1. PG 9.4 → 9.5added

Definition snapshot: english-manuals:a6e742b1607d508212de3ff001f… · English manual source