# P0004 — assert_failure

> PostgreSQL SQLSTATE P0004 的源码与诊断参考。
---

# P0004

## 速览 {#at-a-glance}

P0004 是 PL/pgSQL `assert_failure`。固定 `ASSERT` 执行路径确认它以 ERROR 报告，并按是否提供 message 选择错误文本；运行案例已在 PostgreSQL 18.6 和 10.21 上通过。

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

| 字段 | 值 |
| --- | --- |
| SQLSTATE | `P0004` |
| 条件名 | `assert_failure` |
| 状态 | `有效` |
| 已知存在于 | `9.5.0` |
| 锁定快照 | `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` |
| 宏 | `ERRCODE_ASSERT_FAILURE` |
| 别名 | `—` |

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

共享的 `assertion_failure_recovery` 案例已在 PostgreSQL 18.6 和 PostgreSQL 10.21 上通过。启用 `plpgsql.check_asserts` 后，失败的 ASSERT 返回 `P0004` 和 `value must be positive`，显式事务进入 `INERROR`；`ROLLBACK` 恢复为 `IDLE`，有效输入执行成功，关闭断言的对照调用返回零。

<!-- 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}

ASSERT 是 PL/pgSQL 中执行的不变量检查。PostgreSQL 计算其 Boolean 条件；结果为 **false 或 NULL** 时进入断言失败路径。固定执行器随后以 ERROR 和 SQLSTATE `P0004` 报告：只在进入该路径后计算 message 表达式，非 NULL 结果成为主报文，NULL 或省略 message 时使用 `assertion failed`。关闭检查时，条件和 message 表达式都会跳过。

`plpgsql.check_asserts` 是按会话生效的设置，用来控制是否检查 ASSERT。启用时，失败断言是真正的 ERROR，显式事务会进入 `INERROR`；关闭时 ASSERT 会被跳过，同一个 false 输入不能证明不变量成立，也不能替代生产环境的输入校验。

选定运行覆盖了带非 NULL message 的 false 条件，以及关闭检查时的 false 条件。`NULL` 条件和无 message 的回退文本是源码确认的语义，不是本批额外的自然运行观察。

## 诊断 {#diagnosis}

先把 ErrorResponse 字段与服务器日志中的同一条记录对照。固定 18.6 路径的 `message_primary` 是计算后的 message 或 `assertion failed`；运行案例还记录了 `ERROR`、`P0004`、`exec_stmt_assert`，以及指向 PL/pgSQL 函数和 `line 1 at ASSERT` 的 context。要在出错的同一个会话执行 `SHOW plpgsql.check_asserts`，其他连接的设置不会影响它。

如果主报文是 `assertion failed`，检查 ASSERT 是否没有 message，或 message 表达式是否计算为 NULL。如果完全没有 P0004，先检查 `SHOW plpgsql.check_asserts`：关闭时会在计算条件前跳过检查。比较调用时要同时保留函数源码和会话设置；连接池中的另一个连接可能有不同设置，即使它们调用的是同一个函数。

区分程序不变量和预期业务输入。`value > 0` 这类 ASSERT 适合检查经过验证后本应永远成立的假设；预期的负数等业务输入应使用普通校验、约束或明确的应用错误。若错误发生在显式事务中，先检查事务状态再发送下一条命令：运行案例中事务在 `ROLLBACK` 前是 `INERROR`，并不是连接已经失效。

## 处置 {#response}

显式事务中先执行 `ROLLBACK`，再以修正后的不变量或输入重现。共享案例验证了 `ROLLBACK → IDLE`、有效调用返回 `1`，以及把 `plpgsql.check_asserts` 设为 `off` 后同一个 false 输入不再报告断言。

如果 PL/pgSQL block 有意处理这个命名条件，可以使用 `WHEN ASSERT_FAILURE`。`WHEN OTHERS` 不会捕获 `ASSERT_FAILURE`，因此不能靠宽泛 handler 隐藏断言失败。异常块可以按文档规定的子事务边界恢复，但吞掉错误前必须检查不变量；关闭断言只是诊断对照，不是修复。

## 版本 {#versions}

锁定目录从 9.5.0 记录 P0004，并在列出的正式快照及 19beta3 中出现。固定执行器源码是 PostgreSQL 18.6 的 [`pl_exec.c#L3965-L3968`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/pl/plpgsql/src/pl_exec.c#L3965-L3968)。官方 [PL/pgSQL 错误和消息文档](https://www.postgresql.org/docs/18/plpgsql-errors-and-messages.html)说明 ASSERT 和命名条件；[控制结构中的错误捕获文档](https://www.postgresql.org/docs/18/plpgsql-control-structures.html#PLPGSQL-ERROR-TRAPPING)定义 `EXCEPTION` 子事务及 handler 匹配边界。最新版本与 PG10 的运行案例确认了共享函数中的 P0004 和事务行为。

## 相关条件 {#related}

[`P0002`](../p0002/) 是 PL/pgSQL 无数据条件， [`P0003`](../p0003/) 是严格多行条件， [`P0000`](../p0000/) 是 PL/pgSQL 错误类别。应以响应中的实际 SQLSTATE 为准，不要把所有 PL/pgSQL 失败都归为 P0004。

## 来源 {#sources}

固定实现见 [`pl_exec.c#L3965-L3968`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/pl/plpgsql/src/pl_exec.c#L3965-L3968)。官方 [PL/pgSQL 错误和消息参考](https://www.postgresql.org/docs/18/plpgsql-errors-and-messages.html)说明 ASSERT 语义；[控制结构中的错误捕获参考](https://www.postgresql.org/docs/18/plpgsql-control-structures.html#PLPGSQL-ERROR-TRAPPING)说明 `WHEN OTHERS` 排除项和子事务边界。结构化的[证据记录](../../data/evidence/p0004.json)固定了源码 SHA、运行摘要、原始结果和共享片段注册表。
