P0004 — assert_failure
P0004 is PL/pgSQL assert_failure. The fixed ASSERT path reports ERROR with either the evaluated message or assertion failed.
English manual coverage begins with PostgreSQL 8.1. Source history and runtime observations have their own recorded version boundaries.
Reading PG 18·Select a recorded version
- aliases
- Unknown
- class name
- PL/pgSQL Error
- condition name
- assert_failure
- lang
- en
- sqlstate
- P0004
Definition PG 18
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.
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 {#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. 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.
Related {#related}
P0002 for PL/pgSQL no-data handling, P0003 for strict multi-row handling, and 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. 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.
Compare versions
SQLSTATE evidence and recorded runtime versions
cases · assertion_failure_recovery
{
"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."
],
"case_id": "assertion_failure_recovery",
"cleanup": "Drop the case schema with an owner connection.",
"has_snippet": true,
"position": 0,
"preconditions": [
"A disposable runner target has PL/pgSQL available.",
"plpgsql.check_asserts is explicitly enabled before the trigger."
],
"repair": "Rollback the failed transaction, use a valid input, and keep assertions enabled for production behavior.",
"trigger": "Call a PL/pgSQL function whose ASSERT condition is false inside an explicit transaction.",
"versions": [
"10",
"18"
]
}claims · identity
{
"claim_id": "identity",
"limits": "",
"method": "Read the fixed definition row.",
"position": 0,
"runtime": [],
"sources": [
"src.errcodes.18.6"
],
"statement": "P0004 is assert_failure in PostgreSQL-specific Class P0."
}claims · assert-path
{
"claim_id": "assert-path",
"limits": "",
"method": "Read the fixed `ASSERT` execution path and both errmsg branches.",
"position": 1,
"runtime": [],
"sources": [
"src.path.plpgsql"
],
"statement": "PL/pgSQL assertion execution reports P0004 at ERROR level, using the evaluated message when present and `assertion failed` when no message is supplied."
}claims · runtime.assertion-case
{
"claim_id": "runtime.assertion-case",
"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.",
"method": "Read both target summaries and the fixed snippet registry.",
"position": 2,
"runtime": [],
"sources": [
"runtime.P0004-same-session-latest-final.latest",
"runtime.P0004-same-session-pg10-final.pg10",
"snippet-registry.P0004"
],
"statement": "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."
}claims · runtime.diagnostics
{
"claim_id": "runtime.diagnostics",
"limits": "",
"method": "Compare structured diagnostics in both target summaries.",
"position": 3,
"runtime": [],
"sources": [
"runtime.P0004-same-session-latest-final.latest",
"runtime.P0004-same-session-pg10-final.pg10"
],
"statement": "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."
}claims · assert-semantics
{
"claim_id": "assert-semantics",
"limits": "",
"method": "Read the fixed executor branch and the official PL/pgSQL ASSERT documentation.",
"position": 4,
"runtime": [],
"sources": [
"src.path.plpgsql",
"doc.plpgsql.18"
],
"statement": "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."
}claims · settings-and-handler-boundary
{
"claim_id": "settings-and-handler-boundary",
"limits": "The setting is session-scoped; this claim does not make a connection-level setting change global.",
"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.",
"position": 5,
"runtime": [],
"sources": [
"doc.plpgsql-control.18",
"src.path.plpgsql"
],
"statement": "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."
}claims · transaction-recovery
{
"claim_id": "transaction-recovery",
"limits": "",
"method": "Read both passed target summaries and the ordered shared registry.",
"position": 6,
"runtime": [],
"sources": [
"runtime.P0004-same-session-latest-final.latest",
"runtime.P0004-same-session-pg10-final.pg10",
"snippet-registry.P0004"
],
"statement": "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."
}messages · message.assert
{
"limits": "",
"message_id": "message.assert",
"path": "",
"position": 0,
"raw": {
"alternate_template": "%s",
"id": "message.assert",
"primary_template": "assertion failed",
"severity_source": "ERROR",
"sources": [
"src.path.plpgsql"
],
"sqlstate": "P0004"
},
"severity": "ERROR",
"sources": [
"src.path.plpgsql"
],
"templates": [
{
"kind": "primary",
"literal": "assertion failed",
"position": 0,
"role": "",
"template": "assertion failed"
},
{
"kind": "primary",
"literal": "",
"position": 1,
"role": "alternate",
"template": "%s"
}
]
}runtimes · runtime.P0004-same-session-latest-final.latest · 18.6 (Homebrew) · passed
{
"cases": [
"assertion_failure_recovery"
],
"limits": "",
"position": 0,
"raw": {
"cases": [
"assertion_failure_recovery"
],
"id": "runtime.P0004-same-session-latest-final.latest",
"observed": {
"after_error": "INERROR",
"after_rollback": "IDLE",
"disabled_control_result": 0,
"message_primary": "value must be positive",
"repair_result": 1,
"severity": "ERROR",
"sqlstate": "P0004"
},
"raw": "verify/results/P0004-same-session-latest-final/latest/raw.jsonl",
"raw_sha256": "2b03b5469ea34a0e49ad4a71e93cef4b19df0d01d86fc1156e478d88bc93168c",
"run_id": "P0004-same-session-latest-final",
"server_version": "18.6 (Homebrew)",
"server_version_num": 180006,
"status": "passed",
"summary": "verify/results/P0004-same-session-latest-final/latest/summary.json",
"summary_sha256": "5246ae67ee8dd29e769c3a03db7bfb6cde2f201c9f45993201fb269cc53988ad",
"target": "latest"
},
"run_id": "P0004-same-session-latest-final",
"runtime_id": "runtime.P0004-same-session-latest-final.latest",
"server_version": "18.6 (Homebrew)",
"status": "passed",
"target": "latest"
}runtimes · runtime.P0004-same-session-pg10-final.pg10 · 10.21 (Debian 10.21-1.pgdg90+1) · passed
{
"cases": [
"assertion_failure_recovery"
],
"limits": "",
"position": 1,
"raw": {
"cases": [
"assertion_failure_recovery"
],
"id": "runtime.P0004-same-session-pg10-final.pg10",
"observed": {
"after_error": "INERROR",
"after_rollback": "IDLE",
"disabled_control_result": 0,
"message_primary": "value must be positive",
"repair_result": 1,
"severity": "ERROR",
"sqlstate": "P0004"
},
"raw": "verify/results/P0004-same-session-pg10-final/pg10/raw.jsonl",
"raw_sha256": "2369b62eb7c22d99122c9929134a878f2f3c0fe828887ae657f87b7b95e07ac8",
"run_id": "P0004-same-session-pg10-final",
"server_version": "10.21 (Debian 10.21-1.pgdg90+1)",
"server_version_num": 100021,
"status": "passed",
"summary": "verify/results/P0004-same-session-pg10-final/pg10/summary.json",
"summary_sha256": "50819f9620584f17766ffb62e2bb1c14e5aaa9162f91ec6cd26795cced3e6d30",
"target": "pg10"
},
"run_id": "P0004-same-session-pg10-final",
"runtime_id": "runtime.P0004-same-session-pg10-final.pg10",
"server_version": "10.21 (Debian 10.21-1.pgdg90+1)",
"status": "passed",
"target": "pg10"
}sources · src.errcodes.18.6
{
"commit": "724edf9bde9d356724ad384a2e196edc3c9f80f7",
"docs_url": "",
"kind": "upstream_source",
"location": "",
"path": "src/backend/utils/errcodes.txt",
"position": 0,
"sha256": "6e8de346643ba84aa3c9c6a73360acfc7b2dfb89162c06c08ce9bf5bcd5bbcba",
"source_id": "src.errcodes.18.6",
"tag": "REL_18_6",
"url": "https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/errcodes.txt#L494"
}sources · src.calls.REL_18_6
{
"commit": "",
"docs_url": "",
"kind": "local_artifact",
"location": "",
"path": "raw/calls/REL_18_6.jsonl",
"position": 1,
"sha256": "9ee8a0e81d8f0825c5c1ae45583439859a26e602bdd4ce2f2a62aa278867ccbf",
"source_id": "src.calls.REL_18_6",
"tag": "",
"url": ""
}sources · src.path.plpgsql
{
"commit": "724edf9bde9d356724ad384a2e196edc3c9f80f7",
"docs_url": "",
"kind": "upstream_source",
"location": "",
"path": "src/pl/plpgsql/src/pl_exec.c",
"position": 2,
"sha256": "0df3c70a6bccf6dddf443fb99e152a16a67009827c44a820597e98e585cb8b20",
"source_id": "src.path.plpgsql",
"tag": "REL_18_6",
"url": "https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/pl/plpgsql/src/pl_exec.c#L3965-L3968"
}sources · runtime.P0004-same-session-latest-final.latest
{
"commit": "",
"docs_url": "",
"kind": "local_artifact",
"location": "",
"path": "verify/results/P0004-same-session-latest-final/latest/summary.json",
"position": 3,
"sha256": "5246ae67ee8dd29e769c3a03db7bfb6cde2f201c9f45993201fb269cc53988ad",
"source_id": "runtime.P0004-same-session-latest-final.latest",
"tag": "",
"url": ""
}sources · runtime.P0004-same-session-latest-final.latest.raw
{
"commit": "",
"docs_url": "",
"kind": "local_artifact",
"location": "",
"path": "verify/results/P0004-same-session-latest-final/latest/raw.jsonl",
"position": 4,
"sha256": "2b03b5469ea34a0e49ad4a71e93cef4b19df0d01d86fc1156e478d88bc93168c",
"source_id": "runtime.P0004-same-session-latest-final.latest.raw",
"tag": "",
"url": ""
}sources · runtime.P0004-same-session-pg10-final.pg10
{
"commit": "",
"docs_url": "",
"kind": "local_artifact",
"location": "",
"path": "verify/results/P0004-same-session-pg10-final/pg10/summary.json",
"position": 5,
"sha256": "50819f9620584f17766ffb62e2bb1c14e5aaa9162f91ec6cd26795cced3e6d30",
"source_id": "runtime.P0004-same-session-pg10-final.pg10",
"tag": "",
"url": ""
}sources · runtime.P0004-same-session-pg10-final.pg10.raw
{
"commit": "",
"docs_url": "",
"kind": "local_artifact",
"location": "",
"path": "verify/results/P0004-same-session-pg10-final/pg10/raw.jsonl",
"position": 6,
"sha256": "2369b62eb7c22d99122c9929134a878f2f3c0fe828887ae657f87b7b95e07ac8",
"source_id": "runtime.P0004-same-session-pg10-final.pg10.raw",
"tag": "",
"url": ""
}sources · snippet-registry.P0004
{
"commit": "",
"docs_url": "",
"kind": "local_artifact",
"location": "",
"path": "verify/cases/P0004/snippets.json",
"position": 7,
"sha256": "ef124f75ec61be666abc87ff431ebfb496dfa3352f78a3bee4432430dd68c229",
"source_id": "snippet-registry.P0004",
"tag": "",
"url": ""
}sources · doc.plpgsql.18
{
"commit": "724edf9bde9d356724ad384a2e196edc3c9f80f7",
"docs_url": "",
"kind": "upstream_source",
"location": "",
"path": "doc/src/sgml/plpgsql.sgml",
"position": 8,
"sha256": "43d705adbded343336341fd3e34ea8940432e8a8211f737d36ba3041ad0a4a51",
"source_id": "doc.plpgsql.18",
"tag": "PG18-docs",
"url": "https://www.postgresql.org/docs/18/plpgsql-errors-and-messages.html"
}sources · doc.plpgsql-control.18
{
"commit": "724edf9bde9d356724ad384a2e196edc3c9f80f7",
"docs_url": "",
"kind": "upstream_source",
"location": "",
"path": "doc/src/sgml/plpgsql.sgml",
"position": 9,
"sha256": "43d705adbded343336341fd3e34ea8940432e8a8211f737d36ba3041ad0a4a51",
"source_id": "doc.plpgsql-control.18",
"tag": "PG18-docs",
"url": "https://www.postgresql.org/docs/18/plpgsql-control-structures.html#PLPGSQL-ERROR-TRAPPING"
}Source references
Full definition and evidence JSON
Definition sources
center · PostgreSQL 18 · english-manuals:7b52e2fa72c2635d8c6f026e04c4234c170292416ad23065c2234b43e0c4d270
Text language: en · english-manuals:7b52e2fa72c2635d8c6f026e04c4234c170292416ad23065c2234b43e0c4d270