36.8. 错误处理#
本节介绍如何在嵌入式 SQL 程序中处理异常情况和警告。有两种非排他性工具可用于此目的。
- 可以使用
WHENEVER命令配置回调来处理警告和错误情况。 - 可以从
sqlca变量获取有关错误或警告的详细信息。
36.8.1. 设置回调#
捕获错误和警告的一种简单方法是在特定情况发生时设置要执行的特定操作。通常
EXEC SQL WHENEVER condition action;*condition*可以是以下之一
SQLERROR#每当在执行 SQL 语句期间发生错误时,都会调用指定的操作。
SQLWARNING#每当在执行 SQL 语句期间发生警告时,都会调用指定的操作。
NOT FOUND#每当 SQL 语句检索或影响零行时,都会调用指定的操作。(此情况并非错误,但你可能希望对其进行特殊处理。)
*action*可以是以下之一
CONTINUE#这实际上意味着忽略该情况。这是默认设置。
GOTOlabelGO TO#label跳转到指定的标签(使用 C
goto语句)。SQLPRINT#将消息打印到标准错误。这对于简单的程序或在原型设计期间非常有用。无法配置消息的详细信息。
STOP#调用
exit(1),该调用将终止程序。DO BREAK#执行 C 语句
break。这只能在循环或switch语句中使用。DO CONTINUE#执行 C 语句
continue。这只能在循环语句中使用。如果执行,将导致控制流返回到循环的顶部。CALLname(args)DO#name(args)使用指定参数调用指定 C 函数。(此用法与正常 PostgreSQL 语法中的
CALL和DO的含义不同。)
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;
}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表示无害条件,例如最后一个查询返回零行。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、sqlabc、sqlerrp以及sqlerrd和sqlwarn的其余元素当前不包含任何有用的信息。
结构sqlca未在 SQL 标准中定义,但在其他一些 SQL 数据库系统中实现。核心定义类似,但如果您想编写可移植应用程序,则应仔细研究不同的实现。
这是一个结合使用WHENEVER和sqlca的示例,在发生错误时打印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.sqlstate和sqlca.sqlcode是提供错误代码的两种不同方案。两者都源自 SQL 标准,但SQLCODE已在标准的 SQL-92 版本中标记为不推荐使用,并在更高版本中已删除。因此,强烈建议新应用程序使用SQLSTATE。
SQLSTATE是一个五字符数组。这五个字符包含数字或大写字母,代表各种错误和警告条件的代码。SQLSTATE具有分层方案:前两个字符表示条件的一般类别,后三个字符表示一般条件的子类。成功状态由代码00000表示。SQLSTATE代码大部分在 SQL 标准中定义。PostgreSQL服务器本机支持SQLSTATE错误代码;因此,通过在所有应用程序中使用此错误代码方案,可以实现高度一致性。有关更多信息,请参阅附录 A。
SQLCODE,弃用的错误代码方案,是一个简单的整数。0 值表示成功,正值表示成功并附带其他信息,负值表示错误。SQL 标准仅定义正值 +100,表示上一个命令返回或影响了 0 行,并且没有特定的负值。因此,此方案只能实现较差的可移植性,并且没有层次代码分配。从历史上看,PostgreSQL的嵌入式 SQL 处理器已经为其使用分配了一些特定的SQLCODE值,这些值在下面列出,其中包括它们的数字值和符号名称。请记住,这些值不能移植到其他 SQL 实现。为了简化应用程序向SQLSTATE方案的移植,还列出了相应的SQLSTATE。然而,这两个方案之间没有一对一或一对多的映射(实际上是多对多),因此在每种情况下,您都应该查阅附录 A中的全局SQLSTATE列表。
以下是已分配的SQLCODE值
- 0 (
ECPG_NO_ERROR) # 表示没有错误。(SQLSTATE 00000)
- 100 (
ECPG_NOT_FOUND) # 这是一个无害的条件,表示上一个命令检索或处理了 0 行,或者您处于游标的末尾。(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)
