↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

支持中的版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发中的版本: 19 / 20devel
已结束支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3
开发快照。 PostgreSQL 20devel 尚未正式发布,内容仍可能变化。

34.8. 错误处理 #

这一节描述如何在嵌入式 SQL 程序中处理异常情况和警告。为此提供了两种可以同时使用的机制。

  • 可以使用 WHENEVER 命令配置回调来处理警告和错误情况。
  • 可以从 sqlca 变量中获得错误或警告的详细信息。

34.8.1. 设置回调 #

捕获错误和警告的一种简单方法是指定一个动作,在特定条件发生时执行。一般形式如下:

EXEC SQL WHENEVER condition action;

condition 可以是以下值之一:

SQLERROR #

只要在 SQL 语句执行期间发生一个错误就调用指定的动作。

SQLWARNING #

只要在 SQL 语句执行期间发生一个警告就调用指定的动作。

NOT FOUND #

只要一个 SQL 语句检索或者影响零行就调用指定的动作(这种情况不是一个错误,但是你可能需要特别地处理它)。

action 可以是以下值之一:

CONTINUE #

这实际上表示该情况被忽略。这是默认值。

GOTO label
GO TO label #

跳到指定的标签(使用一个 C goto 语句)。

SQLPRINT #

把一个消息打印到标准错误。这对简单程序或原型开发很有用。消息的细节无法配置。

STOP #

调用 exit(1) 终止程序。

DO BREAK #

执行 C 语句 break。只应被用在循环或 switch 语句中。

DO CONTINUE #

执行 C 语句 continue。它只能用于循环语句,执行后会使控制流返回循环的开头。

CALL name (args)
DO name (args) #

使用指定参数调用指定的 C 函数(这种用法不同于常规 PostgreSQL 语法中 CALL 和 DO 的含义)。

SQL 标准只规定了 CONTINUE 和 GOTO(以及 GO TO)动作。

这里有一个可能会用在简单程序中的例子。当一个警告发生时它打印一个简单消息,而发生一个错误时它会中止程序:

EXEC SQL WHENEVER SQLWARNING SQLPRINT;
EXEC SQL WHENEVER SQLERROR STOP;

语句 EXEC SQL WHENEVER 是 SQL 预处理器的指令,而不是 C 语句。它设置的错误或警告动作适用于源代码中出现在处理器设置位置之后的所有嵌入式 SQL 语句,除非在第一个 EXEC SQL WHENEVER 与触发该条件的 SQL 语句之间,为同一条件设置了其他动作。这与 C 程序的控制流无关。因此,下面两个 C 程序片段都不会产生预期效果:

/*
 * 错误
 */
int main(int argc, char *argv[])
{
    ...
    if (verbose) {
        EXEC SQL WHENEVER SQLWARNING SQLPRINT;
    }
    ...
    EXEC SQL SELECT ...;
    ...
}

/*
 * 错误
 */
int main(int argc, char *argv[])
{
    ...
    set_error_handler();
    ...
    EXEC SQL SELECT ...;
    ...
}

static void set_error_handler(void)
{
    EXEC SQL WHENEVER SQLERROR STOP;
}

34.8.2. sqlca #

为了支持更强大的错误处理,嵌入式 SQL 接口提供了一个名为 sqlca(SQL 通信区域)的全局变量,其结构如下:

struct
{
    char sqlcaid[8];
    long sqlabc;
    long sqlcode;
    struct
    {
        int sqlerrml;
        char sqlerrmc[SQLERRMC_LEN];
    } sqlerrm;
    char sqlerrp[8];
    long sqlerrd[6];
    char sqlwarn[8];
    char sqlstate[5];
} sqlca;

(在一个多线程程序中,每一个线程会自动得到它自己的 sqlca 副本。这和对于标准 C 全局变量 errno 的处理相似。)

sqlca 覆盖了警告和错误。如果执行一个语句时发生了多个警告或错误,那么 sqlca 将只包含关于最后一个的信息。

如果在上一个 SQL 语句中没有产生错误,sqlca.sqlcode 将为 0 并且 sqlca.sqlstate 将为"00000"。如果发生一个警告或错误,则 sqlca.sqlcode 将为负并且 sqlca.sqlstate 将不为"00000"。一个正的 sqlca.sqlcode 表示一种无害的情况,例如上一个查询返回零行。sqlcode 和 sqlstate 是两种不同的错误编码方案,详见下文。

如果上一个 SQL 语句成功,则 sqlca.sqlerrd[1] 包含所处理行的 OID(如果适用),sqlca.sqlerrd[2] 包含处理或返回的行数(如果适用于该命令)。

发生错误或警告时,sqlca.sqlerrm.sqlerrmc 包含描述该错误的字符串。字段 sqlca.sqlerrm.sqlerrml 包含存储在 sqlca.sqlerrm.sqlerrmc 中的错误消息的长度(即 strlen() 的结果,对于 C 程序员通常没有多少用处)。注意,有些消息太长,无法存入定长的 sqlerrmc 数组,因此会被截断。

发生警告时,sqlca.sqlwarn[2] 被设为 W(其他情况下,它被设为非 W 的值)。如果 sqlca.sqlwarn[1] 被设为 W,说明某个值在存入主变量时被截断了。如果其他任一元素被设置为指示警告,sqlca.sqlwarn[0] 就会被设为 W。

字段 sqlcaid、sqlabc、sqlerrp,以及 sqlerrd 和 sqlwarn 的其余元素,目前不包含有用信息。

SQL 标准中没有定义 sqlca 结构体,但是在一些其他的 SQL 数据库系统中都有实现。在核心上这些定义都相似,但是如果你想要编写可移植的应用,那么你应该仔细研究不同的实现。

这里有一个整合使用 WHENEVER 和 sqlca 的例子,当一个错误发生时打印出 sqlca 的内容。在安装一个更“用户友好”的错误处理器之前,这可能对调试或开发原型应用有用。

EXEC SQL WHENEVER SQLERROR CALL print_sqlca();

void
print_sqlca()
{
    fprintf(stderr, "==== sqlca ====\n");
    fprintf(stderr, "sqlcode: %ld\n", sqlca.sqlcode);
    fprintf(stderr, "sqlerrm.sqlerrml: %d\n", sqlca.sqlerrm.sqlerrml);
    fprintf(stderr, "sqlerrm.sqlerrmc: %s\n", sqlca.sqlerrm.sqlerrmc);
    fprintf(stderr, "sqlerrd: %ld %ld %ld %ld %ld %ld\n", sqlca.sqlerrd[0],sqlca.sqlerrd[1],sqlca.sqlerrd[2],
                                                          sqlca.sqlerrd[3],sqlca.sqlerrd[4],sqlca.sqlerrd[5]);
    fprintf(stderr, "sqlwarn: %d %d %d %d %d %d %d %d\n", sqlca.sqlwarn[0], sqlca.sqlwarn[1], sqlca.sqlwarn[2],
                                                          sqlca.sqlwarn[3], sqlca.sqlwarn[4], sqlca.sqlwarn[5],
                                                          sqlca.sqlwarn[6], sqlca.sqlwarn[7]);
    fprintf(stderr, "sqlstate: %5s\n", sqlca.sqlstate);
    fprintf(stderr, "===============\n");
}

结果看起来像(这里的错误是一个拼写错误的表名):

==== sqlca ====
sqlcode: -400
sqlerrm.sqlerrml: 49
sqlerrm.sqlerrmc: relation "pg_databasep" does not exist on line 38
sqlerrd: 0 0 0 0 0 0
sqlwarn: 0 0 0 0 0 0 0 0
sqlstate: 42P01
===============

34.8.3. SQLSTATE 与 SQLCODE #

字段 sqlca.sqlstate 和 sqlca.sqlcode 采用两种不同的错误编码方案。两者都源自 SQL 标准,但 SQLCODE 在 SQL-92 标准中已被标记为弃用,并在后续版本中被删除。因此,强烈建议新应用使用 SQLSTATE。

SQLSTATE 是一个包含五个字符的数组。这五个字符由数字或大写字母组成,表示各种错误和警告条件的代码。SQLSTATE 采用分层编码方案:前两个字符表示条件所属的大类,后三个字符表示该大类中的子类。代码 00000 表示成功状态。大部分 SQLSTATE 代码由 SQL 标准定义。PostgreSQL 服务器原生支持 SQLSTATE 错误码,因此在所有应用中统一使用这种错误编码方案,可以实现较高的一致性。更多信息见附录 A。

已弃用的错误编码方案 SQLCODE 使用一个简单的整数。0 表示成功,正值表示成功并有附加信息,负值表示错误。SQL 标准只定义了正值 +100,表示上一个命令返回或影响了零行,没有定义具体的负值。因此,这种方案的可移植性较差,也不采用分层编码。历史上,PostgreSQL 的嵌入式 SQL 处理器为自身使用分配了一些特定的 SQLCODE 值,下文列出了它们的数值和符号名称。注意,这些值不能移植到其他 SQL 实现。为了便于将应用迁移到 SQLSTATE 方案,还列出了对应的 SQLSTATE。不过,两种方案之间不存在一对一或一对多的映射关系(实际上是多对多),因此每次都应查阅附录 A 中的完整 SQLSTATE 列表。

以下是已分配的 SQLCODE 值:

0 (ECPG_NO_ERROR) #

表示没有错误(SQLSTATE 00000)。

100 (ECPG_NOT_FOUND) #

这是一种无害情况,它表示上一个命令检索或者处理了零行,或者你到达了游标的末尾(SQLSTATE 02000)。

在一个循环中处理一个游标时,你可以使用这个代码作为一种方法来检测何时中止该循环,像这样:

while (1)
{
    EXEC SQL FETCH ... ;
    if (sqlca.sqlcode == ECPG_NOT_FOUND)
        break;
}

但是 WHENEVER NOT FOUND DO BREAK 实际上会在内部这样做,因此显式地把它写出来通常没有什么好处。

-12 (ECPG_OUT_OF_MEMORY) #

表示你的虚拟内存已被耗尽。数字值被定义为 -ENOMEM(SQLSTATE YE001)。

-200 (ECPG_UNSUPPORTED) #

表示预处理器生成了该库无法识别的内容。可能是所用的预处理器与库的版本不兼容(SQLSTATE YE002)。

-201 (ECPG_TOO_MANY_ARGUMENTS) #

这表示命令指定了超过该命令预期数量的主变量(SQLSTATE 07001 或 07002)。

-202 (ECPG_TOO_FEW_ARGUMENTS) #

这表示命令指定的主变量数量低于该命令的预期(SQLSTATE 07001 或 07002)。

-203 (ECPG_TOO_MANY_MATCHES) #

这意味着一个查询已经返回了多个行,但是该语句只准备存储一个结果行(例如,因为指定的变量不是数组)(SQLSTATE 21000)。

-204 (ECPG_INT_FORMAT) #

主变量是类型 int 而数据库中的数据是一种不同的类型并且含有一个不能被解释为 int 的值。该库使用 strtol() 进行这种转换(SQLSTATE 42804)。

-205 (ECPG_UINT_FORMAT) #

主变量是类型 unsigned int 而数据库中的数据是一种不同的类型并且含有一个不能被解释为 unsigned int 的值。该库使用 strtoul() 进行这种转换(SQLSTATE 42804)。

-206 (ECPG_FLOAT_FORMAT) #

主变量是类型 float 而数据库中的数据是另一种类型并且含有一个不能被解释为 float 的值。该库使用 strtod() 进行这种转换(SQLSTATE 42804)。

-207 (ECPG_NUMERIC_FORMAT) #

主变量是类型 numeric 而数据库中的数据是另一种类型并且含有一个不能被解释为 numeric 的值(SQLSTATE 42804)。

-208 (ECPG_INTERVAL_FORMAT) #

主变量是类型 interval 而数据库中的数据是另一种类型并且含有一个不能被解释为 interval 的值(SQLSTATE 42804)。

-209 (ECPG_DATE_FORMAT) #

主变量是类型 date 而数据库中的数据是另一种类型并且含有一个不能被解释为 date 的值(SQLSTATE 42804)。

-210 (ECPG_TIMESTAMP_FORMAT) #

主变量是类型 timestamp 而数据库中的数据是另一种类型并且含有一个不能被解释为 timestamp 的值(SQLSTATE 42804)。

-211 (ECPG_CONVERT_BOOL) #

这表示主变量是类型 bool 而数据库中的数据既不是't' 也不是'f'(SQLSTATE 42804)。

-212 (ECPG_EMPTY) #

发送给 PostgreSQL 服务器的语句是空的(通常在一个嵌入式 SQL 程序中不会发生,因此它可能指向一个内部错误)(SQLSTATE YE002)。

-213 (ECPG_MISSING_INDICATOR) #

返回了一个空值,但未提供空值指示符变量(SQLSTATE 22002)。

-214 (ECPG_NO_ARRAY) #

在要求一个数组的地方使用了一个普通变量(SQLSTATE 42804)。

-215 (ECPG_DATA_NOT_ARRAY) #

在一个要求数组值的地方数据库返回了一个普通变量(SQLSTATE 42804)。

-216 (ECPG_ARRAY_INSERT) #

该值不能被插入到数组(SQLSTATE 42804)。

-220 (ECPG_NO_CONN) #

程序尝试访问一个不存在的连接(SQLSTATE 08003)。

-221 (ECPG_NOT_CONN) #

程序尝试访问一个已存在但未打开的连接(这是一个内部错误)(SQLSTATE YE002)。

-230 (ECPG_INVALID_STMT) #

尝试使用的语句尚未预备(SQLSTATE 26000)。

-239 (ECPG_INFORMIX_DUPLICATE_KEY) #

重复键错误,违背唯一约束(Informix 兼容模式)(SQLSTATE 23505)。

-240 (ECPG_UNKNOWN_DESCRIPTOR) #

未找到指定的描述符。尝试使用的语句尚未预备(SQLSTATE 33000)。

-241 (ECPG_INVALID_DESCRIPTOR_INDEX) #

指定的描述符索引超出范围(SQLSTATE 07009)。

-242 (ECPG_UNKNOWN_DESCRIPTOR_ITEM) #

请求了一个无效的描述符条目(这是一个内部错误)(SQLSTATE YE002)。

-243 (ECPG_VAR_NOT_NUMERIC) #

执行动态语句时,数据库返回了数值,而主变量不是数值类型(SQLSTATE 07006)。

-244 (ECPG_VAR_NOT_CHAR) #

执行动态语句时,数据库返回了非数值的值,而主变量是数值类型(SQLSTATE 07006)。

-284 (ECPG_INFORMIX_SUBSELECT_NOT_ONE) #

子查询的结果不是单一行(Informix 兼容模式)(SQLSTATE 21000)。

-400 (ECPG_PGSQL) #

PostgreSQL 服务器导致了某个错误。该消息包含来自 PostgreSQL 服务器的错误消息。

-401 (ECPG_TRANS) #

PostgreSQL 服务器通知我们不能启动、提交或回滚事务(SQLSTATE 08007)。

-402 (ECPG_CONNECT) #

到数据库的连接尝试没有成功(SQLSTATE 08001)。

-403 (ECPG_DUPLICATE_KEY) #

重复键错误,违背唯一约束(SQLSTATE 23505)。

-404 (ECPG_SUBSELECT_NOT_ONE) #

子查询的结果不是单一行(SQLSTATE 21000)。

-602 (ECPG_WARNING_UNKNOWN_PORTAL) #

指定了一个无效的游标名(SQLSTATE 34000)。

-603 (ECPG_WARNING_IN_TRANSACTION) #

事务正在进行(SQLSTATE 25001)。

-604 (ECPG_WARNING_NO_TRANSACTION) #

没有活动(正在进行)的事务(SQLSTATE 25P01)。

-605 (ECPG_WARNING_PORTAL_EXISTS) #

指定了一个现有的游标名(SQLSTATE 42P03)。

报告文档问题

阅读 上游文档. 反馈更正前请先核对 当前版本手册.