Postgresql 中文操作指南

36.8. Error Handling #

本部分介绍如何在嵌入式 SQL 程序中处理异常条件和警告。为此,有两个互不排斥的工具。

36.8.1. Setting Callbacks #

捕捉错误和警告的一个简单方法是在发生特定条件时设置要执行的特定操作。一般而言:

EXEC SQL WHENEVER condition action;

condition 可能属于以下情形之一:

  • SQLERROR #

    • 每当在执行 SQL 语句期间发生错误时,都会调用指定的操作。

  • SQLWARNING #

    • 每当在执行 SQL 语句期间发生警告时,都会调用指定的操作。

  • NOT FOUND #

    • 每当 SQL 语句检索或影响 0 行时,都会调用指定的操作。(此情况并非错误,但您可能希望专门处理它。)

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

36.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 表示无害情况,如上次查询返回 0 行。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,那么当值存储在主机变量中时,它会被截断。如果将任何其他元素设置为 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
===============

36.8.3. SQLSTATE vs. SQLCODE #

字段 sqlca.sqlstatesqlca.sqlcode 是提供错误代码的两种不同方案。两者都派生自 SQL 标准,但 SQLCODE 已在标准的 SQL-92 版本中被标记为已弃用,并且在后续版本中已被移除。因此,强烈建议新的应用程序使用 SQLSTATE

SQLSTATE 是一个五字符数组。这五个字符包含表示各种错误和警告条件代码的数字或大写字母。SQLSTATE 具有分层方案:前两个字符指示条件的一般类型,后三个字符指示一般条件的子类型。代码 00000 表示成功状态。SQLSTATE 代码在 SQL 标准中大部分定义。PostgreSQL 服务器本机支持 SQLSTATE 错误代码;因此,在所有应用程序中使用此错误代码方案可以实现高度一致性。有关详细信息,请参见 Appendix A

SQLCODE,已弃用的错误代码方案,是简单的整数。值为 0 表示成功,正值表示带附加信息的成功,负值表示错误。SQL 标准仅定义正值 +100,这表示上一个命令返回或影响了零行,并且没有具体负值。因此,此方案只能实现较差的可移植性,并且没有分层的代码分配。从历史上看,PostgreSQL 的嵌入式 SQL 处理器为其使用分配了一些特定的 SQLCODE 值,这些值在下文列出了其数字值和符号名称。请记住,这些值不可移植到其他 SQL 实现中。为了简化应用程序向 SQLSTATE 方案的移植,相应的 SQLSTATE 也在列表中。但是,这两个方案之间不能进行一对一或一对多的映射(实际上是多对多),因此你应在每种情况下查阅 Appendix 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)