33.8. 错误处理

33.8.1. 设置回调
33.8.2. sqlca
33.8.3. SQLSTATESQLCODE

这一节描述在一个嵌入式 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语句中。

DO CONTINUE

执行C语句continue。这应该只被用在循环语句中。如果被执行,将导致控制流返回到循环的顶层。

CALL name (args)
DO name (args)

用指定参数调用指定的C函数(这种用法不同于正常LightDB语法中CALLDO的含义)。

SQL 标准只提供动作CONTINUEGOTO(以及GO TO)。

这里有一个可能会用在简单程序中的例子。当一个警告发生时它打印一个简单消息,而发生一个错误时它会中止程序:

EXEC SQL WHENEVER SQLWARNING SQLPRINT;
EXEC SQL WHENEVER SQLERROR STOP;

语句EXEC SQL WHENEVER是 SQL 预处理器的一个指令,而不是一个 C 语句。不管 C 程序的控制流程如何,该语句设置的错误或警告动作适用于所有位于处理程序设置点之后的嵌入式 SQL 语句,除非在第一个EXEC SQL WHENEVER和导致情况的 SQL 语句之间为同一个情况设置了不同的动作。因此下面的两个 C 程序都不会得到预期的效果:

/*
 * 错误
 */
int main(int argc, char *argv[])
{
    ...
    if (verbose) {
        EXEC SQL WHENEVER SQLWARNING SQLPRINT;
    }
    ...
    EXEC SQL SELECT ...;
    ...
}

/*
 * 错误
 */
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]包含被处理或被返回的行数(如果适用于该命令)。 在 Oracle 模式下, 对游标进行 fetch 时, sqlca.sqlerrd[2] 记录的是 fetch 影响的总行数, 而不是当前 fetch 一次影响的行数。

在发生一个错误或警告的情况下,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

sqlcaidsqlabc, sqlerrp以及 sqlerrd的剩余元素还有 sqlwarn当前不包含有用的信息。

SQL 标准中没有定义sqlca结构,但是在一些其他的 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
===============

33.8.3. SQLSTATESQLCODE

sqlca.sqlstate以及sqlca.sqlcode是提供错误代码的两种不同模式。两种都源自于 SQL 标准,但是在标准的 SQL-92 版本中SQLCODE已经被标记为弃用并且在后面的版本中被删除。因此,强烈建议新应用使用SQLSTATE

SQLSTATE是一个五字符数组。这五个字符包含数字或大写字母,它表示多种错误或警告情况的代码。SQLSTATE具有一种层次模式:前两个字符表示情况的总体分类,后三个字符表示总体情况的子类。代码00000表示一种成功的状态。SQL 标准中的大部分都有对应的SQLSTATE代码。LightDB服务器本地支持SQLSTATE错误代码,因此通过在所有应用中自始至终使用这种错误代码模式可以实现高度的一致性。进一步的信息请见Appendix A

被弃用的错误代码模式SQLCODE是一个简单的整数。值为 0 表示成功,一个正值表示带附加信息的成功,一个负值表示一个错误。SQL 标准只定义了正值 +100,它表示上一个命令返回或者影响了零行,并且没有特定的负值。因此,这种模式只能实现很可怜的可移植性并且不具有层次性的代码分配。历史上,LightDB的嵌入式 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)

主变量是类型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)

发送给LightDB服务器的语句是空的(通常在一个嵌入式 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)

在执行一个动态语句期间,数据库返回了一个numeric值而主变量不是numeric的(SQLSTATE 07006)。

-244 (ECPG_VAR_NOT_CHAR)

在执行一个动态语句期间,数据库返回了一个非numeric值而主变量是numeric的(SQLSTATE 07006)。

-284 (ECPG_INFORMIX_SUBSELECT_NOT_ONE)

子查询的结果不是单一行(Informix 兼容模式)(SQLSTATE 21000)。

-400 (ECPG_PGSQL)

LightDB服务器导致了某个错误。该消息包含来自LightDB服务器的错误消息。

-401 (ECPG_TRANS)

LightDB服务器通知我们不能启动、提交或回滚事务(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)。

除上述外,LightDB 在兼容 Oracle SQLCODE 时,新增以下四个值:

1403 (LT_ECPG_NOT_FOUND)

这是一种无害情况,它表示上一个命令检索或者处理了零行,或者你到达了游标的末尾(SQLSTATE 02000)。

在一个循环中处理一个游标时,你可以使用这个代码作为一种方法来检测何时中止该循环,像这样:

while (1)
{
    EXEC SQL FETCH ... ;
    if (sqlca.sqlcode == LT_ECPG_NOT_FOUND)
        break;
}

但是WHENEVER NOT FOUND DO BREAK实际上会在内部这样做,因此显式地把它写出来通常没有什么好处。

-1 (LT_ECPG_DUPLICATE_KEY)

重复键错误,违背唯一约束(SQLSTATE 23505)。

-942 (LT_ECPG_OBJECT_NOT_EXISTS)

表或者视图不存在(SQLSTATE 42P01)。

-1017 (LT_ECPG_CONNECT)

到数据库的连接尝试没有成功(SQLSTATE 08001)。