2024 年 9 月 26 日: PostgreSQL 17 发布!
支持版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不支持版本: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

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 语法中 CALLDO 的含义不同。)

SQL 标准只提供了 CONTINUEGOTO(以及 GO TO)操作。

以下是一个您可能想在简单程序中使用的示例。它在发生警告时打印一条简单消息,并在发生错误时中止程序

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

语句 EXEC SQL WHENEVER 是 SQL 预处理器的指令,而不是 C 语句。它设置的错误或警告操作适用于出现在设置处理程序的位置之后的嵌入式 SQL 语句,除非在第一个 EXEC SQL WHENEVER 和导致该条件的 SQL 语句之间针对同一条件设置了不同的操作,而不管 C 程序中的控制流如何。因此,以下两个 C 程序段中的任何一个都没有预期的效果

/*
 * WRONG
 */
int main(int argc, char *argv[])
{
    ...
    if (verbose) {
        EXEC SQL WHENEVER SQLWARNING SQLPRINT;
    }
    ...
    EXEC SQL SELECT ...;
    ...
}
/*
 * WRONG
 */
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 表示无害的条件,例如最后一次查询返回了零行。 sqlcodesqlstate 是两种不同的错误代码方案;详细信息见下文。

如果最后一个 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

字段 sqlcaidsqlabcsqlerrp 以及 sqlerrdsqlwarn 的其余元素目前不包含任何有用的信息。

结构 sqlca 未在 SQL 标准中定义,但在其他几个 SQL 数据库系统中实现。这些定义在核心上是类似的,但如果您想要编写可移植的应用程序,则应该仔细调查不同的实现。

以下是一个结合了 WHENEVERsqlca 使用的示例,在发生错误时打印出 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. SQLSTATESQLCODE #

字段 sqlca.sqlstatesqlca.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)

提交更正

如果您在文档中发现任何不正确的内容,与您对特定功能的体验不符,或者需要进一步说明,请使用 此表格 报告文档问题。