错误处理

PostgreSQL 9.4.4 中文手册
上一页 | 上一级 | 章 33. ECPG - 在C中嵌入SQL | 下一页

# 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`错误代码；因此通过在所有应用程序中 使用该错误代码方案实现高度一致性。 更多信息参阅附录 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)

上一页 | 起始页 | 下一页
使用描述符范围 | 上一级 | 预处理器指令