#PL异常处理
在PL程序开发中,需要使用异常处理机制,对各类异常状况进行终止程序或错误规避的逻辑处理,而不是直接抛出错误信息和中止程序。
YashanDB提供如下三类异常:
- 系统预定义的异常,这类异常通常是一些触发了程序内部逻辑的错误,由系统抛出,过程体中可对其进行捕获,例如NO_DATA_FOUND未获得查询结果,LOGIN_DENIED鉴权不通过等。
- 用户自定义的异常,这类异常通常是开发人员依据业务逻辑规则所制定的错误,且须在过程体中自行抛出和捕获,例如身份证号码的合法性判断等。
- 不可预知的其他异常,开发人员无法预知到程序运行时可能会产生的错误,但可以通过OTHERS将其捕获,并进行友好型提示等逻辑处理。
无论上述何种异常,必须在过程体中进行主动捕获,才能接着对其进行逻辑处理,否则,系统将依据错误机制进行相应处理并抛出错误码和错误消息。
# 系统预定义的异常
YashanDB预定义了如下可以在过程体中被识别的异常名称:
内置异常名称 | 说明 |
---|---|
COLLECTION_IS_NULL | 访问的数据集未初始化。 |
CURSOR_ALREADY_OPEN | 重复打开游标。 |
DUP_VAL_ON_INDEX | 发生索引冲突。 |
INVALID_CURSOR | 使用无效的游标。 |
INVALID_NUMBER | 使用非法数字。 |
LOGIN_DENIED | 被拒绝登录。 |
NO_DATA_FOUND | 在存储过程中查询数据为空。 |
ROWTYPE_MISMATCH | 数据列类型不匹配。 |
STORAGE_ERROR | 存储错误。 |
SYS_INVALID_ROWID | 使用非法ROWID。 |
TIMEOUT_ON_RESOURCE | 访问资源超时。 |
TOO_MANY_ROWS | 在PL中指定select查询的数据返回了多行。 |
VALUE_ERROR | 发生数值异常。 |
ZERO_DIVIDE | 发生除零异常。 |
系统预定义的异常由系统抛出,过程体中只需要进行捕获处理,具体操作可参考EXCEPTION Statement。
# 用户自定义的异常
用户自定义的异常需由用户在过程体中自行生成异常和抛出异常。分为如下两种方式:
1.直接生成异常
此方式通过RAISE_APPLICATION_ERROR在需要的地方直接指定错误码和错误消息生成异常,并抛出该错误码和错误消息,无需指定异常名称,并且不对异常进行捕获。
2.异常定义->异常初始化->异常抛出->异常接收
此方式需首先进行异常的变量声明(异常定义),之后可对该异常进行错误码绑定(异常初始化),异常抛出和异常捕获(异常接收)等操作。
- 异常定义:声明自定义异常变量,具体操作可参考自定义异常文档说明。
- 异常初始化:对异常绑定错误码,不执行此操作则由系统自动生成错误码。
- 异常抛出:可执行RAISE(捕获后向外层抛出),RAISE exception_name(抛出供本层捕获)两种不同的抛出。
- 异常接收:在异常句柄中捕获该异常并执行逻辑处理,具体操作可参考EXCEPTION Statement。
# OTHERS异常
对于开发人员无法预知的异常,或者过程体中未进行主动捕获的异常,都可以全部归集到OTHERS里进行处理,具体操作可参考EXCEPTION Statement。
# 错误码
YashanDB制定了一套错误码,用于对用户需要感知的错误进行抛出,此时用户只能接收到错误信息且程序被中止。异常处理机制则对指定的错误给予用户对其进行捕获处理的机会,每个异常需要对应相应的错误码。如下为YashanDB的错误码框架:
类别 | 错误号区间范围 | 对应方式 |
---|---|---|
系统预定义的错误 | (0 , 20000] | -- |
系统预定义的异常 | [20000 , 21000) | 系统生成 |
用户自定义的异常 | [21000 , 30000) | EXCEPTION变量声明 |
用户自定义的异常 | [30000 , 99999] | RAISE_APPLICATION_ERROR |
用户自定义的异常 | (0 , 99999] | EXCEPTION_INIT |
其中,系统预定义的错误见参考手册错误码文档说明。
# RAISE_APPLICATION_ERROR
RAISE_APPLICATION_ERROR为系统提供的异常处理函数,其语法格式为:
RAISE_APPLICATION_ERROR(errCode, errMsg);
errCode
错误号,需指定为上表范围内的值,否则在执行程序时系统将抛出YAS-04426错误。
errMsg
错误消息。
示例
CREATE OR REPLACE PROCEDURE ya_proc(vno CHAR) IS
no VARCHAR(2);
name VARCHAR(20);
str1 VARCHAR(100) := 'select area_no,area_name from area where area_no=:a';
BEGIN
IF vno>'10' THEN
RAISE_APPLICATION_ERROR(40000,'no data found for '||vno);
END IF;
EXECUTE IMMEDIATE str1 INTO no,name USING vno;
DBMS_OUTPUT.PUT_LINE('num:'||no||'name:'||name);
END;
/
exec ya_proc('99');
--result
YAS-40000 no data found for 99
# DBMS_AUDIT_MGMT 自定义异常
调用DBMS_AUDIT_MGMT高级包进行处理,若参数设置不正确会抛出异常。
错误码 | 错误内容 | 解释 |
---|---|---|
YAS-30000 | invalid value of argument AUDIT_TRAIL_TYPE | 审计清理类型的设置非法 |
YAS-30001 | invalid value of argument AUDIT_TRAIL_STATUS_VALUE | 审计清理job状态的设置非法 |
YAS-30002 | invalid value of argument LAST_ARCHIVE_TIME | 审计清理时间点的设置非法 |
YAS-30003 | invalid value of argument AUDIT_TRAIL_PURGE_INTERVAL | 下次执行时间的设置非法 |
YAS-30004 | Cleanup job already existed for the given audit trail type | 给定类型的审计清理job已存在 |
# DBMS_DESCRIBE 自定义异常
错误码 | 错误内容 | 解释 |
---|---|---|
YAS-30007 | cannot describe a non-existent procedure | 指定的过程不存在,无法描述 |
YAS-30008 | cannot describe an invalid procedure | 指定的过程无效,无法描述 |
# DBMS_OUTPUT 自定义异常
错误码 | 错误内容 | 解释 |
---|---|---|
YAS-30005 | line length overflow, limit of 32000 bytes per line | 缓冲区写入当前行导致溢出,每行上限为32000bytes |
YAS-30006 | buffer overflow, limit of %d bytes | 缓冲区写入导致溢出,缓冲区总长度上限为%d bytes |
# EXCEPTION_INIT
EXCEPTION_INIT为系统内置程序块,使用该程序块的语法格式为:
PRAGMA EXCEPTION_INIT(exception_name,errCode);
对用户声明的EXCEPTION变量,使用EXCEPTION_INIT可以对其绑定一个错误码,否则系统将按上表范围自动生成一个错误码。
exception_name
已被声明的变量名称。
errCode
绑定的错误码,需指定为上表范围内的值,否则编译报错。
Caution:
禁止定义2017错误码:
2017错误码表示DC失效,依据YashanDB内部处理逻辑,在DC失效时会进行reparse后再执行。如手动raise这个错误码且无异常句柄接收,将导致系统陷入不断reparse和执行的死循环。
示例
CREATE OR REPLACE PROCEDURE ya_proc(vno CHAR) IS
no VARCHAR(2);
name VARCHAR(20);
str1 VARCHAR(100) := 'select area_no,area_name from area where area_no=:a';
err EXCEPTION;
PRAGMA EXCEPTION_INIT(err, 45000);
BEGIN
IF vno>'10' THEN
RAISE err;
END IF;
EXECUTE IMMEDIATE str1 INTO no,name USING vno;
DBMS_OUTPUT.PUT_LINE('num:'||no||'name:'||name);
EXCEPTION
WHEN err THEN
DBMS_OUTPUT.PUT_LINE('no data found for '||vno);
END;
/
exec ya_proc('99');
--result
no data found for 99
# SQLCODE&SQLERRM
SQLCODE和SQLERRM为系统提供的异常处理函数,用于获取异常对应的错误号和错误消息,其语法格式为:
SQLCODE[()]
SQLERRM[(errCode)]
SQLCODE和SQLERRM只能用于过程性语句中,不能用于SQL语句中。当被用于非异常处理的语句中时,SQLCODE函数将返回0值,SQLERRM函数将返回"YAS-00000 normal, successful completion"。
当被用于异常处理语句中时,SQLCODE函数将返回当前捕获到的异常错误号,SQLERRM函数返回规则如下:
- 不指定errCode参数时,返回当前错误对应的错误消息。
- 指定errCode参数时,如errCode超出上表指定范围,返回"%d non-yas exception",否则查找errCode对应的错误消息并返回。
- 当前错误号或errCode未定义错误消息时,返回”YAS-%05d message %05d not found“。
示例
CREATE OR REPLACE PROCEDURE ya_proc(vno CHAR) IS
no VARCHAR(2);
name VARCHAR(20);
str1 VARCHAR(100) := 'select area_no,area_name from area where area_no=:a';
err EXCEPTION;
PRAGMA EXCEPTION_INIT(err, 45000);
BEGIN
DBMS_OUTPUT.PUT_LINE(SQLCODE||':'||SQLERRM);
IF vno>'10' THEN
RAISE err;
END IF;
EXECUTE IMMEDIATE str1 INTO no,name USING vno;
DBMS_OUTPUT.PUT_LINE('num:'||no||'name:'||name);
EXCEPTION
WHEN err THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE||':'||SQLERRM);
END;
/
exec ya_proc('99');
--result
0:YAS-00000 normal, successful completion
45000:YAS-45000 message 45000 not found
# STANDARD
STANDARD用于表明其后面的exception_name为系统预定义的异常名称,当用户自定义的异常可能与系统预定义的异常重名时,使用STANDARD可以准确识别出系统预定义的异常。语法格式为:
STANDARD.exception_name
exception_name必须为系统预定义异常的名称,否则将出现编译错误。
示例
CREATE OR REPLACE PROCEDURE ya_proc(vno CHAR) IS
no VARCHAR(2);
name VARCHAR(20);
str1 VARCHAR(100) := 'select area_no,area_name from area where area_no=:a';
no_data_found EXCEPTION;
PRAGMA EXCEPTION_INIT(no_data_found, 45000);
BEGIN
IF vno>'10' THEN
RAISE no_data_found;
ELSE
EXECUTE IMMEDIATE str1 INTO no,name USING vno;
DBMS_OUTPUT.PUT_LINE('num:'||no||'name:'||name);
END IF;
EXCEPTION
WHEN STANDARD.NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Standard exception.');
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE||':'||SQLERRM);
END;
/
exec ya_proc('99');
--result
45000:YAS-45000 message 45000 not found
exec ya_proc('09');
--result
Standard exception.
# RAISE
RAISE语句用于显式地抛出异常,其语法格式为:
RAISE [exception_name];
当不指定exception_name时,该语句必须位于异常句柄(EXCEPTION Statment)中,表示将当前的异常继续向外层抛出。
exception_name可以为用户自定义的异常,也可以是系统预定义的异常。
当本层的异常句柄并没有捕获RAISE后的exception_name时,该异常将向外层抛出,对与用户自定义的异常同名的系统预定义的异常,则需要使用STANDARD捕获。
示例
CREATE OR REPLACE PROCEDURE ya_proc(vno CHAR) IS
no VARCHAR(2);
name VARCHAR(20);
str1 VARCHAR(100) := 'select area_no,area_name from area where area_no=:a';
no_data_found EXCEPTION;
BEGIN
IF vno>'10' THEN
BEGIN
DBMS_OUTPUT.PUT_LINE('First:');
RAISE no_data_found;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE;
END;
ELSE
DBMS_OUTPUT.PUT_LINE('Second:');
EXECUTE IMMEDIATE str1 INTO no,name USING vno;
DBMS_OUTPUT.PUT_LINE('num:'||no||'name:'||name);
END IF;
EXCEPTION
WHEN STANDARD.NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE||':'||SQLERRM);
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE||':'||SQLERRM);
END;
/
exec ya_proc('99');
--result
First:
21000:YAS-21000 message 21000 not found
exec ya_proc('09');
--result
Second:
5206:YAS-05206 no data found