36.8. 错误处理

本节介绍如何处理嵌入式 SQL 程序中的异常情况和警告。有两个非排他性的功能。

  • 可以使用WHENEVER命令将回调配置为处理警告和错误情况。

  • 可以从sqlca变量获取有关错误或警告的详细信息。

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

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表示无害条件,例如最后一个查询返回零行。 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
===============

36 .8.3. SQLSTATE 与 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。但是,这两种方案之间没有一对一或一对多的 Map(实际上是多对多的),因此在每种情况下,都应查阅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 )

    • 找不到指定的 Descriptors。您要使用的语句尚未准备好。 (SQLSTATE 33000)
  • -241 ( ECPG_INVALID_DESCRIPTOR_INDEX )

    • 指定的 Descriptors 索引超出范围。 (SQLSTATE 07009)
  • -242 ( ECPG_UNKNOWN_DESCRIPTOR_ITEM )

    • 请求了无效的 Descriptors 项。 (这是一个内部错误.)(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 )

    • Transaction 正在进行中。 (SQLSTATE 25001)
  • -604 ( ECPG_WARNING_NO_TRANSACTION )

    • 没有活动的(正在进行中的)事务。 (SQLSTATE 25P01)
  • -605 ( ECPG_WARNING_PORTAL_EXISTS )

    • 指定了现有的游标名称。 (SQLSTATE 42P03)