# 39P01 — trigger_protocol_violated

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

# 39P01

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

39P01 是 `trigger_protocol_violated`。固定源码确认它同时出现在核心 trigger manager、contrib/tcn 以及 PL/Perl trigger 返回值检查中，消息和严重级别取决于具体路径。

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

| 字段 | 值 |
| --- | --- |
| SQLSTATE | `39P01` |
| 条件名 | `trigger_protocol_violated` |
| 状态 | `有效` |
| 已知存在于 | `7.4` |
| 锁定快照 | `9.0.23, 9.1.24, 9.2.24, 9.3.25, 9.4.26, 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_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED` |
| 别名 | `—` |

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

## 含义 {#meaning}

这是 trigger 调用契约不成立：核心管理器可能报告 `trigger function %u returned null value` 或 `BEFORE STATEMENT trigger cannot return a value`；tcn 函数在错误的 timing/level/参数条件下被调用，或表没有主键；PL/Perl DELETE trigger 的修改可能被 WARNING 忽略，非法返回值则是 ERROR。

## 诊断 {#diagnosis}

先按 message 区分来源：核心管理器检查 `isnull` 标志和 BEFORE STATEMENT 返回契约；tcn 要核对 AFTER、FOR EACH ROW、参数个数和主键；PL/Perl 要核对返回是否为 `undef`、`SKIP` 或 `MODIFY`。DELETE 修改被忽略的报文是 WARNING，非法返回报文是 ERROR；不要把所有 39P01 都归为同一个 trigger 类型。

## 处置 {#response}

修正 trigger 的 timing、level、事件、参数和返回值契约；对 tcn 还要核对表主键和调用顺序。PL/Perl 的 WARNING 表示 DELETE 行修改被忽略，非法返回值则使调用失败；先确认原操作是否完成和业务是否接受该结果，再决定后续写入。修复契约后才重新执行。

## 版本 {#versions}

锁定目录从 7.4 记录该条件，并在正式快照及 19beta3 中出现；源码路径取 PostgreSQL 18.6。

## 相关条件 {#related}

[`39P02`](../39p02/), [`39P03`](../39p03/), [`2F005`](../2f005/)

## 来源 {#sources}

可直接阅读固定的 [trigger.c](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/commands/trigger.c#L2388-L2394)、[tcn.c](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/contrib/tcn/tcn.c#L77-L110) 和 [plperl.c](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/pl/plperl/plperl.c#L2607-L2618) 路径；完整范围见结构化[证据记录](../../data/evidence/39p01.json)。
