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。
SQLCODE
,已废弃的错误编码方案,是一个简单的integer。 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
)
宿主变量是类型无符号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)