|
| 1 | +:sectnums: |
| 2 | +:sectnumlevels: 5 |
| 3 | + |
| 4 | +:imagesdir: ./_images |
| 5 | + |
| 6 | += 用户自定义EXCEPTION |
| 7 | + |
| 8 | +== 目的 |
| 9 | + |
| 10 | +IvorySQL提供了兼容Oracle的用户自定义EXCEPTION功能,支持在PL/iSQL存储过程与包中声明用户自定义EXCEPTION。 |
| 11 | + |
| 12 | +本文档旨在为使用人员介绍此新增功能。 |
| 13 | + |
| 14 | +== 功能说明 |
| 15 | + |
| 16 | +IvorySQL提供的兼容Oracle的用户自定义EXCEPTION功能,包括如下内容。 |
| 17 | + |
| 18 | +=== 在包中声明自定义exception |
| 19 | + |
| 20 | +可以在包声明部分或包体声明部分使用 `异常名 EXCEPTION;` 声明用户自定义异常。声明在包声明部分的异常可以在对应包体中使用;只在包体中声明的异常用于包体内部的过程和函数。 |
| 21 | + |
| 22 | +语法如下: |
| 23 | + |
| 24 | +```sql |
| 25 | +exception_name EXCEPTION; |
| 26 | +``` |
| 27 | + |
| 28 | +自定义异常是PL/iSQL的异常对象,不是普通SQL数据类型,不能被赋值、作为表达式求值或作为过程返回值。异常名称遵循PL/iSQL的作用域规则,同一声明作用域内不能与其他变量或异常重名。 |
| 29 | + |
| 30 | +使用 `RAISE` 按名称抛出异常,并在 `EXCEPTION` 区域通过同一名称捕获: |
| 31 | + |
| 32 | +```sql |
| 33 | +RAISE exception_name; |
| 34 | + |
| 35 | +EXCEPTION |
| 36 | + WHEN exception_name THEN |
| 37 | + handler_statement; |
| 38 | +``` |
| 39 | + |
| 40 | +包级异常可以由包内的一个子程序抛出,再由包内调用它的另一个子程序捕获。异常向外传播时,当前语句块中的修改会按照PL/iSQL原有的异常子事务机制进行回滚,然后执行匹配的异常处理器。 |
| 41 | + |
| 42 | + |
| 43 | +=== 在存储过程中声明exception |
| 44 | + |
| 45 | +可以在独立存储过程的声明区定义局部异常。局部异常只在声明它的过程及其嵌套作用域内可见,过程外不能直接引用该异常名称。 |
| 46 | + |
| 47 | +```sql |
| 48 | +CREATE OR REPLACE PROCEDURE example_proc IS |
| 49 | + local_exception EXCEPTION; |
| 50 | +BEGIN |
| 51 | + RAISE local_exception; |
| 52 | +EXCEPTION |
| 53 | + WHEN local_exception THEN |
| 54 | + NULL; |
| 55 | +END; |
| 56 | +/ |
| 57 | +``` |
| 58 | + |
| 59 | +执行 `RAISE local_exception` 时,IvorySQL创建错误并进入异常匹配流程。`WHEN local_exception` 命中后,可以在处理器中访问 `SQLERRM`。未通过 `PRAGMA EXCEPTION_INIT` 绑定错误码的用户异常使用内部SQLSTATE `P0001`;如果没有另外指定消息,`SQLERRM` 的默认内容是 `User-Defined Exception`。 |
| 60 | + |
| 61 | +异常处理器中使用不带参数的 `RAISE;`,可以继续向外层重新抛出当前异常。 |
| 62 | + |
| 63 | + |
| 64 | +=== 把一个用户自定义异常名称与特定数据库错误码绑定 |
| 65 | + |
| 66 | +`PRAGMA EXCEPTION_INIT` 是编译期指令,用于把已经声明的用户自定义异常与指定错误码关联。它本身不是运行期语句,必须写在声明区,并且位于对应的 `EXCEPTION` 声明之后。 |
| 67 | + |
| 68 | +语法如下: |
| 69 | + |
| 70 | +```sql |
| 71 | +exception_name EXCEPTION; |
| 72 | +PRAGMA EXCEPTION_INIT(exception_name, error_code); |
| 73 | +``` |
| 74 | + |
| 75 | +绑定后,执行 `RAISE exception_name` 时使用关联的错误码;`WHEN exception_name` 也根据该错误码进行匹配。这样可以用具有业务含义的名称代替数字错误码。 |
| 76 | + |
| 77 | +IvorySQL接受的错误码范围如下: |
| 78 | + |
| 79 | +* 正数只允许 `100`,用于ANSI `NO_DATA_FOUND`; |
| 80 | +* 允许 `-1000000` 到 `-1` 之间的负整数,但不允许 `-1403`; |
| 81 | +* 不允许 `0`、除 `100` 外的正整数以及小于 `-1000000` 的整数。 |
| 82 | + |
| 83 | +非法错误码会在编译存储过程或包时报告 `illegal ORACLE error number ... for PRAGMA EXCEPTION_INIT`。异常名称不存在,或者名称对应的对象不是异常时,也会在编译期报错。 |
| 84 | + |
| 85 | +错误码和错误消息相互独立。例如,`PRAGMA EXCEPTION_INIT(my_exception, -20001)` 只把 `-20001` 绑定到异常,并不会把 `SQLERRM` 自动转换成 `ORA-20001`。使用普通 `RAISE my_exception` 且未指定消息时,`SQLERRM` 仍为 `User-Defined Exception`。如需自定义消息,可以使用: |
| 86 | + |
| 87 | +```sql |
| 88 | +RAISE my_exception USING MESSAGE = 'application error'; |
| 89 | +``` |
| 90 | + |
| 91 | +=== 使用注意事项 |
| 92 | + |
| 93 | +IvorySQL当前通过错误码匹配 `WHEN` 处理器,而不是通过异常对象的声明身份匹配。所有未使用 `PRAGMA EXCEPTION_INIT` 的用户异常默认使用同一个内部SQLSTATE `P0001`。如果同一异常处理区域需要区分多个用户异常,应使用 `PRAGMA EXCEPTION_INIT` 为它们绑定不同的合法错误码;绑定相同错误码的异常也无法在运行期相互区分。 |
| 94 | + |
| 95 | + |
| 96 | +== 测试用例 |
| 97 | + |
| 98 | +=== 在包中声明自定义exception |
| 99 | + |
| 100 | +下面的用例实际抛出并捕获包级异常。只有匹配的异常处理器得到执行,才会向结果表写入记录。 |
| 101 | + |
| 102 | +``` |
| 103 | +CREATE TABLE plisql_exception_results |
| 104 | +( |
| 105 | + test_no NUMBER, |
| 106 | + test_name VARCHAR2(40), |
| 107 | + caught_by VARCHAR2(40), |
| 108 | + detail VARCHAR2(100) |
| 109 | +); |
| 110 | + |
| 111 | +CREATE OR REPLACE PACKAGE test_exc_pkg1 IS |
| 112 | + PROCEDURE test_proc; |
| 113 | +END test_exc_pkg1; |
| 114 | +/ |
| 115 | + |
| 116 | +CREATE OR REPLACE PACKAGE BODY test_exc_pkg1 IS |
| 117 | + bad_interval EXCEPTION; |
| 118 | + |
| 119 | + PROCEDURE test_proc IS |
| 120 | + BEGIN |
| 121 | + RAISE bad_interval; |
| 122 | + EXCEPTION |
| 123 | + WHEN bad_interval THEN |
| 124 | + INSERT INTO plisql_exception_results |
| 125 | + VALUES (1, 'package_basic', 'bad_interval', SQLERRM); |
| 126 | + END test_proc; |
| 127 | +END test_exc_pkg1; |
| 128 | +/ |
| 129 | + |
| 130 | +BEGIN |
| 131 | + test_exc_pkg1.test_proc(); |
| 132 | +END; |
| 133 | +/ |
| 134 | +``` |
| 135 | +=== 在存储过程中声明exception |
| 136 | + |
| 137 | +``` |
| 138 | +CREATE OR REPLACE PROCEDURE test_standalone_exc IS |
| 139 | + my_exception EXCEPTION; |
| 140 | +BEGIN |
| 141 | + RAISE my_exception; |
| 142 | +EXCEPTION |
| 143 | + WHEN my_exception THEN |
| 144 | + INSERT INTO plisql_exception_results |
| 145 | + VALUES (3, 'standalone', 'my_exception', SQLERRM); |
| 146 | +END; |
| 147 | +/ |
| 148 | + |
| 149 | +BEGIN |
| 150 | + test_standalone_exc(); |
| 151 | +END; |
| 152 | +/ |
| 153 | +``` |
| 154 | + |
| 155 | +=== 把一个用户自定义异常名称与特定数据库错误码绑定 |
| 156 | + |
| 157 | +``` |
| 158 | +CREATE OR REPLACE PACKAGE test_pragma_init IS |
| 159 | + PROCEDURE test_basic_pragma; |
| 160 | +END test_pragma_init; |
| 161 | +/ |
| 162 | + |
| 163 | +CREATE OR REPLACE PACKAGE BODY test_pragma_init IS |
| 164 | + my_exception EXCEPTION; |
| 165 | + PRAGMA EXCEPTION_INIT(my_exception, -20001); |
| 166 | + |
| 167 | + PROCEDURE test_basic_pragma IS |
| 168 | + BEGIN |
| 169 | + RAISE my_exception; |
| 170 | + EXCEPTION |
| 171 | + WHEN my_exception THEN |
| 172 | + INSERT INTO plisql_exception_results |
| 173 | + VALUES (6, 'pragma_basic', 'my_exception', SQLERRM); |
| 174 | + END test_basic_pragma; |
| 175 | +END test_pragma_init; |
| 176 | +/ |
| 177 | + |
| 178 | +BEGIN |
| 179 | + test_pragma_init.test_basic_pragma(); |
| 180 | +END; |
| 181 | +/ |
| 182 | +``` |
| 183 | + |
| 184 | +执行以上三个用例后,可以查询实际进入的异常处理器及 `SQLERRM`: |
| 185 | + |
| 186 | +```sql |
| 187 | +SELECT test_name, caught_by, detail |
| 188 | +FROM plisql_exception_results |
| 189 | +ORDER BY test_no; |
| 190 | +``` |
| 191 | + |
| 192 | +预期结果为: |
| 193 | + |
| 194 | +``` |
| 195 | + test_name | caught_by | detail |
| 196 | +-----------------+---------------+------------------------ |
| 197 | + package_basic | bad_interval | User-Defined Exception |
| 198 | + standalone | my_exception | User-Defined Exception |
| 199 | + pragma_basic | my_exception | User-Defined Exception |
| 200 | +``` |
| 201 | +
|
| 202 | +测试结束后清理对象: |
| 203 | +
|
| 204 | +```sql |
| 205 | +DROP PACKAGE test_exc_pkg1; |
| 206 | +DROP PROCEDURE test_standalone_exc; |
| 207 | +DROP PACKAGE test_pragma_init; |
| 208 | +DROP TABLE plisql_exception_results; |
| 209 | +``` |
0 commit comments