PolarDB PostgreSQL版(兼容Oracle)不仅支持原生PostgreSQL的错误处理系统,还兼容Oracle语法中的常见错误处理机制。
主要包括以下几种类型的错误处理机制:
预定义异常:既支持原生PostgreSQL的所有预定义异常,同时也支持兼容Oracle语法中的常见预定义异常。
非预定义异常:兼容Oracle语法中的异常变量,并支持通过
EXCEPTION_INIT为异常变量设置错误码。自定义异常(RAISE_APPLICATION_ERROR):兼容Oracle语法中的
RAISE_APPLICATION_ERROR函数,用于抛出自定义的错误码和错误消息。错误码与错误信息:既支持原生PostgreSQL的
SQLSTATE错误码,也兼容Oracle语法中常见的SQLCODE错误码,此外还支持SQLERRM用于查看详细错误信息。
预定义异常
PolarDB PostgreSQL版(兼容Oracle)既支持原生PostgreSQL的所有预定义异常,如division_by_zero。同时也支持兼容Oracle语法中的常见预定义异常,如zero_divide。在SQL中既可以抛出这些异常,也可以捕获这些异常。
抛出异常
异常的抛出分为两种方式:执行PL/SQL语句抛出以及RAISE语句显式抛出。示例如下:
执行PL/SQL语句抛出异常
DECLARE
    result INT;
BEGIN
    result := 1 / 0;
END;RAISE语句显式抛出异常
-- PostgreSQL 风格
BEGIN
    RAISE division_by_zero;
END;
-- Oracle 风格
BEGIN
    RAISE zero_divide;
END;捕获异常
异常的捕获通过EXCEPTION实现,既可以在WHEN子句中指定异常名以捕获特定异常,也可以通过WHEN OTHERS捕获所有异常。示例如下:
WHEN子句中指定异常名
-- PostgreSQL 风格
DECLARE
    result INT;
BEGIN
    result := 1 / 0;
EXCEPTION
    WHEN division_by_zero THEN
        DBMS_OUTPUT.PUT_LINE('division by zero');
END;
-- Oracle 风格
DECLARE
    result INT;
BEGIN
    result := 1 / 0;
EXCEPTION
    WHEN zero_divide THEN
        DBMS_OUTPUT.PUT_LINE('zero divide');
END;WHEN OTHERS
DECLARE
    result INT;
BEGIN
    result := 1 / 0;
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('division by zero');
END;非预定义异常
在Oracle语法中,存在大量的错误码,其中只有少数错误码被定义为系统预定义异常。例如,错误码100对应于no_data_found异常,可以通过显式抛出RAISE no_data_found来引发该异常,并通过WHEN no_data_found进行异常捕获。
对于大量的非预定义异常,它们仅具备错误码而没有名称,如何抛出和捕获这些异常呢?为了解决这一问题,Oracle引入了异常变量。PolarDB PostgreSQL版(兼容Oracle)同样支持该特性:
首先在
DECLARE中声明异常变量,给定一个变量名,例如my_exception EXCEPTION;。通过
PRAGMA EXCEPTION_INIT语句为该变量绑定一个SQLCODE,例如PRAGMA EXCEPTION_INIT(my_exception, -1476);。重要错误码存在范围限制,需为100(表示
no_data_found异常)或在[-1000000, 0)范围内,且只有[-65535, 0)这个范围能够绑定到Oracle的合法SQLCODE,其他范围的错误码没有实际意义。在
EXCEPTION段中可以用WHEN my_exception或WHEN OTHERS捕获该异常。
示例
DECLARE
    result INT;
    my_exception EXCEPTION;
    PRAGMA EXCEPTION_INIT(my_exception, -66666);
BEGIN
    result := 1 / 0;
EXCEPTION
    WHEN my_exception THEN
        DBMS_OUTPUT.PUT_LINE('zero divide');
END;自定义异常(RAISE_APPLICATION_ERROR)
预定义异常和非预定义异常的错误码都属于Oracle预留的错误码范围,通常为[-65535, 0)范围的负数。除此以外,Oracle还提供了一个自定义异常错误码区间[-20000, -20999],通过RAISE_APPLICATION_ERROR函数即可抛出自定义的错误码和错误消息。
示例
DECLARE
    salary NUMBER := 4000;
BEGIN
    -- 校验业务规则,不符合条件时触发错误
    IF salary < 5000 THEN
        RAISE_APPLICATION_ERROR(-20001, 'Salary is below the allowed minimum.');
    END IF;
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('SQLCODE: ' || SQLCODE);
        DBMS_OUTPUT.PUT_LINE('Custom Error: ' || SQLERRM);
END;错误码与错误信息
在EXCEPTION段中通过WHEN子句捕获到异常以后,如果希望获取异常的各项相关信息,可以通过如下方法:
SQLSTATE:原生PostgreSQL的错误码,由五个字符的字符串组成,前两位表示类别(如语法错误、连接异常等),后三位表示具体错误子类。
SQLCODE:兼容Oracle语法中的错误码,分为三类:
100:表示
no_data_found异常,查询语句无返回结果。[-65535, 0)范围的负数:PolarDB PostgreSQL版(兼容Oracle)集群已兼容Oracle语法中的各类错误场景。-99999:表示非法值,PolarDB PostgreSQL版(兼容Oracle)集群暂不支持当前场景。
SQLERRM:错误信息,用于描述异常的详细情况。
示例
DECLARE
    result INT;
BEGIN
    result := 1 / 0;
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('SQLCODE: ' || SQLCODE);
        DBMS_OUTPUT.PUT_LINE('SQLSTATE: ' || SQLSTATE);
        DBMS_OUTPUT.PUT_LINE('SQLERRM: ' || SQLERRM);
END;输出结果为:
SQLCODE: -1476
SQLSTATE: 22012
SQLERRM: division by zero在PolarDB PostgreSQL版(兼容Oracle)集群中,部分Oracle风格的SQLCODE与PostgreSQL风格的SQLSTATE之间存在映射关系,而另一些则不具备直接映射关系。以下为您列举了已支持的错误场景信息:
SQLCODE  | Oracle异常名  | SQLSTATE  | PostgreSQL异常名  | 错误描述  | 
100  | NO_DATA_FOUND  | P0002  | no_data_found  | 查询语句无返回结果  | 
-1  | DUP_VAL_ON_INDEX  | 23505  | unique_violation  | 插入重复键时违反唯一约束  | 
-54  | 无  | 55P03  | lock_not_available  | 加锁失败  | 
-939  | 无  | 54023  | too_many_arguments  | 函数参数过多  | 
-957  | 无  | 42701  | duplicate_column  | 列名重复  | 
-960  | 无  | 42702  | ambiguous_column  | 列名不明确  | 
-1001  | INVALID_CURSOR  | 34000  | invalid_cursor_name  | 游标未定义或游标名非法  | 
-1031  | 无  | 42501  | insufficient_privilege  | 权限不足  | 
-1422  | TOO_MANY_ROWS  | P0003  | too_many_rows  | 查询语句返回行数过多  | 
-1428  | 无  | 2201E  | invalid_argument_for_logarithm  | log函数参数非法  | 
-1428  | 无  | 2201F  | invalid_argument_for_power_function  | power函数参数非法  | 
-1476  | ZERO_DIVIDE  | 22012  | division_by_zero  | 除数为0  | 
-1578  | 无  | XX001  | data_corrupted  | 数据损坏  | 
-1821  | 无  | 22007  | invalid_datetime_format  | 日期格式错误  | 
-3125  | 无  | 08P01  | protocol_violation  | 通信协议错误  | 
-4020  | 无  | 40P01  | deadlock_detected  | 检测到死锁  | 
-6035  | 无  | 53000  | insufficient_resources  | 资源不足  | 
-6113  | 无  | 53300  | too_many_connections  | 连接过多  | 
-6503  | 无  | 2F005  | function_executed_no_return_statement  | 函数没有RETURN语句  | 
-6511  | CURSOR_ALREADY_OPEN  | 42P03  | duplicate_cursor  | 游标重复定义或重复打开  | 
-6531  | COLLECTION_IS_NULL  | 2203G  | collection_is_null  | 集合为空  | 
-6532  | SUBSCRIPT_OUTSIDE_LIMIT  | 2202E  | array_subscript_error  | 数组下标越界  | 
-6533  | SUBSCRIPT_BEYOND_COUNT  | 2203H  | subscript_beyond_count  | 数组下标越界  | 
-6592  | CASE_NOT_FOUND  | 20000  | case_not_found  | CASE语句没有匹配的WHEN子句  | 
-24381  | 无  | P0005  | forall_dml_error  | FOR ALL语句报错  | 
-27102  | 无  | 53200  | out_of_memory  | 内存不足  | 
-30110  | 无  | 42601  | syntax_error  | 语法错误  | 
-30156  | 无  | 53100  | disk_full  | 磁盘满  | 
-1722  | INVALID_NUMBER  | -  | -  | TO_NUMBER函数参数非法  | 
-1723  | 无  | -  | -  | 列的长度为0  | 
-1724  | 无  | -  | -  | FLOAT类型精度非法  | 
-1727  | 无  | -  | -  | NUMBER类型精度非法  | 
-6502  | VALUE_ERROR  | -  | -  | 数字类型格式非法或超过上限  | 
-6508  | 无  | -  | -  | 包函数未定义  | 
-99999  | 无  | -  | -  | 暂不支持当前场景  |