ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 33.8\. 错误处理 本节描述了如何处理异常情况以及嵌入SQL程序的警告。有两个非排他性功能可以解决。 * 配置回调用来处理警告以及使用`WHENEVER`命令处理错误条件。 * 关于错误或者警告的详细信息可以从`sqlca`变量中获得。 ## 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标准仅仅提供`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程序片段都不会产生期望效果: ``` /* * 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` 表示无害条件,比如最后查询返回零行。 `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`, `sqlcabc`, `sqlerrp`,以及 `sqlerrd`和 `sqlwarn`的剩余元素 目前没有任何有用信息。 在SQL标准中没有定义结构`sqlca`, 但是在其他几个SQL数据库系统中实现了。定义核心是相似的,但是如果你想要 编写可移植应用程序,那么你应该仔细调查不同的实现。 这是一个结合`WHENEVER`和`sqlca`的使用的例子, 当发生错误时,输出`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.sqlstate`和 `sqlca.sqlcode`是提供错误码的两个不同模式。 两者来自SQL标准,但是`SQLCODE`在标准SQL-92 版本中已经过时,并且在后期版本中已经废除。因此, 强烈建议新应用使用`SQLSTATE`。 `SQLSTATE`是五字符数组。 五字符包含数字或者表示不同错误和警告条件代码的大写字母。 `SQLSTATE`有一个分层模式: 前两个字符表示条件的一般类,最后三个字符表示一般条件的子类。 通过代码`00000`表示成功状态。 `SQLSTATE`代码是SQL标准中定义最多部分。 PostgreSQL服务器本地支持 `SQLSTATE`错误代码;因此通过在所有应用程序中 使用该错误代码方案实现高度一致性。 更多信息参阅[Appendix A](#calibre_link-120)。 `SQLCODE`,已废弃的错误编码方案,是一个简单的integer。 0值表示成功,正值表示额外信息成功,负值表示错误。 SQL标准仅仅定义正值+100,这表示返回最后命令或者影响零行,并且 没有明确负值。因此,该方案实现差的移植性,而且没有分层编码安排。 从历史角度,PostgreSQL嵌入的SQL预处理器 为它的使用分配了一些指定`SQLCODE`。 使用数值和符号名称将它列在下面。记住这些是不能移植到其他SQL实现的。 为了简化应用程序移植到`SQLSTATE`方案,相应的 `SQLSTATE`也被列出来。然而, 在两个方案(实际上是多对多)之间没有一对一或者一对多映射, 因此在每种情况下你应该咨询列在[Appendix A](#calibre_link-120)中的全球`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)