33.8. 错误处理

本节描述了如何处理异常情况以及嵌入SQL程序的警告。有两个非排他性功能可以解决。

33.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 语句中使用。

CALL name (args)
DO name (args)

调用具有指定参数的指定C函数。

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

33.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.sqlerrmcstrlen()的结果,C程序员不感兴趣)中的错误消息。 注意一些消息太长而不适合固定大小的sqlerrmc数组; 它们将被截断。

在一个警告的情况下,sqlca.sqlwarn[2]设置为 W。(在所有其他情况下,它被设置为不同于W 的东西。)如果sqlca.sqlwarn[1]被设置为 W,那么一个值被存储在宿主变量的时候,截断它。 如果任何其他元素设置为显示一个警告,则sqlca.sqlwarn[0] 设置为W

字段sqlcaid, sqlcabc, sqlerrp,以及 sqlerrdsqlwarn的剩余元素 目前没有任何有用信息。

在SQL标准中没有定义结构sqlca, 但是在其他几个SQL数据库系统中实现了。定义核心是相似的,但是如果你想要 编写可移植应用程序,那么你应该仔细调查不同的实现。

这是一个结合WHENEVERsqlca的使用的例子, 当发生错误时,输出sqlca的内容。 在安装更多"user-friendly"错误处理程序之前, 这可能用于调试或者原型应用。

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
===============

33.8.3. SQLSTATE vs. SQLCODE

字段sqlca.sqlstatesqlca.sqlcode是提供错误码的两个不同模式。 两者来自SQL标准,但是SQLCODE在标准SQL-92 版本中已经过时,并且在后期版本中已经废除。因此, 强烈建议新应用使用SQLSTATE

SQLSTATE是五字符数组。 五字符包含数字或者表示不同错误和警告条件代码的大写字母。 SQLSTATE有一个分层模式: 前两个字符表示条件的一般类,最后三个字符表示一般条件的子类。 通过代码00000表示成功状态。 SQLSTATE代码是SQL标准中定义最多部分。 PostgreSQL服务器本地支持 SQLSTATE错误代码;因此通过在所有应用程序中 使用该错误代码方案实现高度一致性。 更多信息参阅附录 A

SQLCODE,已废弃的错误编码方案,是一个简单的integer。 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)

宿主变量是类型无符号int,并且数据库中数据是不同类型,而且 包含不能解释为无符号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)

-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)