相关 《Postgresql中plpgsql异常处理方法与实例(RAISE EXCEPTION)》 《Postgresql源码(80)plpgsql中异常处理编译与执行流程分析(sqlstate)》
0 总结
代码语言:javascript复制do $g$
BEGIN
RAISE division_by_zero;
EXCEPTION
WHEN division_by_zero THEN
raise notice 'sqlstate: %', sqlstate;
raise notice 'sqlerrm: %', sqlerrm;
END;
$g$;
-- NOTICE: sqlstate: 22012
-- NOTICE: sqlerrm: division_by_zero
编译后的数据结构
- 编译阶段:
- 根结构还是block,注意在block的exception部分会挂PLpgSQL_exception结构
- block部分:
- 只记触发的异常关键字:condname=“division_by_zero”
- exception部分:
- PLpgSQL_exception结构记录两个关键变量:
- 触发条件:PLpgSQL_condition:
- 异常关键字转码:sqlerrstate=33816706
- 异常关键字:condname=“division_by_zero”
- 执行什么:
- 正常挂执行block执行阶段:
代码语言:txt复制- 进入异常分支:`exec_stmt_block --> PG_CATCH();`
- 遍历异常处理块:`foreach(e, block->exceptions->exc_list)`拿到一个`exception`
- 对每一个块匹配异常关键字`exception_matches_conditions(edata, exception->conditions))`
- 匹配成功则开始`sqlstate`、`sqlerrm`赋值,然后继续执行`exec_stmts(estate, exception->action)`
1 案例
主动抛出异常
代码语言:javascript复制do $g$
BEGIN
RAISE division_by_zero;
EXCEPTION
WHEN division_by_zero THEN
raise notice 'sqlstate: %', sqlstate;
raise notice 'sqlerrm: %', sqlerrm;
END;
$g$;
-- NOTICE: sqlstate: 22012
-- NOTICE: sqlerrm: division_by_zero
2 编译
编译代码
代码语言:javascript复制do $g$
BEGIN
RAISE division_by_zero;
EXCEPTION
WHEN division_by_zero THEN
raise notice 'sqlstate: %', sqlstate;
raise notice 'sqlerrm: %', sqlerrm;
END;
$g$;
语法树代码
代码语言:javascript复制exception_sect :
{ $$ = NULL; }
| K_EXCEPTION // 下面2.2
{
int lineno = plpgsql_location_to_lineno(@1);
PLpgSQL_exception_block *new = palloc(sizeof(PLpgSQL_exception_block));
PLpgSQL_variable *var;
var = plpgsql_build_variable("sqlstate", lineno,
plpgsql_build_datatype(TEXTOID,
-1,
plpgsql_curr_compile->fn_input_collation,
NULL),
true);
var->isconst = true;
new->sqlstate_varno = var->dno;
var = plpgsql_build_variable("sqlerrm", lineno,
plpgsql_build_datatype(TEXTOID,
-1,
plpgsql_curr_compile->fn_input_collation,
NULL),
true);
var->isconst = true;
new->sqlerrm_varno = var->dno;
$<exception_block>$ = new;
}
proc_exceptions // 下面2.5
{
PLpgSQL_exception_block *new = $<exception_block>2;
new->exc_list = $3;
$$ = new;
}
;
2.1 语法树匹配:K_RAISE
- 第一步:申请PLpgSQL_stmt_raise
PLpgSQL_stmt_raise *new = palloc(sizeof(PLpgSQL_stmt_raise))
- 第二步:读取raise后面的单词division_by_zero
new->condname = yylval.word.ident
- 第三步:识别单词含义,去exception_label_map字符串数组中匹配名字,下面介绍这个数组
plpgsql_recognize_err_condition(new->condname, false)
- 第四步:检查报错信息中的%占位符是否匹配后面参数数量
check_raise_parameters(new)
stmt_raise: K_RAISE
生成数据:
PLpgSQL_stmt_raise
{cmd_type = PLPGSQL_STMT_RAISE,
lineno = 3,
stmtid = 1,
elog_level = 21,
condname = 0x104f9c0 "division_by_zero",
message = 0x0,
params = 0x0,
options = 0x0}
exception_label_map数组
保存{错误名,错误码}
的数组,例如division_by_zero:
typedef struct
{
const char *label;
int sqlerrstate;
} ExceptionLabelMap;
static const ExceptionLabelMap exception_label_map[] = {
#include "plerrcodes.h" /* pgrminclude ignore */
{NULL, 0}
};
plerrcodes.h
...
...
{
"division_by_zero", ERRCODE_DIVISION_BY_ZERO
},
...
...
#define ERRCODE_DIVISION_BY_ZERO MAKE_SQLSTATE('2','2','0','1','2')
2.2 语法树匹配:K_EXCEPTION
位置
代码语言:javascript复制do $g$
BEGIN
RAISE division_by_zero;
EXCEPTION
^
|
|
WHEN division_by_zero THEN
raise notice 'sqlstate: %', sqlstate;
raise notice 'sqlerrm: %', sqlerrm;
END;
$g$;
第一步:构造PLpgSQL_exception_block
代码语言:javascript复制typedef struct PLpgSQL_exception_block
{
int sqlstate_varno;
int sqlerrm_varno;
List *exc_list; /* List of WHEN clauses */
} PLpgSQL_exception_block;
{
sqlstate_varno = 1,
sqlerrm_varno = 2,
exc_list = 0x7f7f7f7f7f7f7f7f
}
第二步:拼两个var到变量数组plpgsql_Datums
代码语言:javascript复制(gdb) p *(PLpgSQL_var*)plpgsql_Datums[1]
$9 = {
dtype = PLPGSQL_DTYPE_VAR, dno = 1, refname = 0x104fc08 "sqlstate", lineno = 4,
isconst = true, notnull = false, default_val = 0x0, datatype = 0x104faf8,
cursor_explicit_expr = 0x0, cursor_explicit_argrow = 0, cursor_options = 0,
value = 0, isnull = true, freeval = false,promise = PLPGSQL_PROMISE_NONE
}
(gdb) p *(PLpgSQL_var*)plpgsql_Datums[2]
$8 = {
dtype = PLPGSQL_DTYPE_VAR, dno = 2, refname = 0x104fd78 "sqlerrm", lineno = 4,
isconst = true, notnull = false, default_val = 0x0, datatype = 0x104fc68,
cursor_explicit_expr = 0x0, cursor_explicit_argrow = 0, cursor_options = 0,
value = 0, isnull = true, freeval = false, promise = PLPGSQL_PROMISE_NONE
}
2.3 语法树匹配:proc_condition
进入plpgsql_parse_err_condition返回exception_label_map数组中的匹配元素:
代码语言:javascript复制PLpgSQL_condition
{
sqlerrstate = 33816706,
condname = 0x104fdd0 "division_by_zero",
next = 0x0
}
2.4 语法树匹配:总装1:proc_exception
代码语言:javascript复制PLpgSQL_exception
{
lineno = 5,
conditions = 0x104fe08, --> {sqlerrstate = 33816706, condname = 0x104fdd0 "division_by_zero", next = 0x0}
action = 0x1050458
--> List2
--> PLpgSQL_stmt_raise: {
cmd_type = PLPGSQL_STMT_RAISE,
lineno = 6, stmtid = 2, elog_level = 18,
condname = 0x0,
message = 0x104fed8 "sqlstate: %",
params = 0x1050400, --> PLpgSQL_expr // {query = 0x10503d8 "sqlstate"}
options = 0x0}
--> PLpgSQL_stmt_raise: {
cmd_type = PLPGSQL_STMT_RAISE,
lineno = 7, stmtid = 3, elog_level = 18,
condname = 0x0,
message = 0x1050548 "sqlerrm: %",
params = 0x1050648, --> PLpgSQL_expr // {query = 0x1050628 "sqlerrm"}
options = 0x0}
}
2.5 语法树匹配:总装2:proc_exception
代码语言:javascript复制PLpgSQL_exception_block
{
sqlstate_varno = 1,
sqlerrm_varno = 2,
exc_list = 0x10506d8 // 链表 --> 2.4组装的PLpgSQL_exception
}
3 执行
exec_stmts时PLpgSQL_stmt_block数据结构
代码语言:javascript复制[PLpgSQL_stmt_block]
{
cmd_type = PLPGSQL_STMT_BLOCK,
lineno = 2, stmtid = 4, label = 0x0,
body = 0x10492a8, [List] --> [PLpgSQL_stmt_raise]
{cmd_type = PLPGSQL_STMT_RAISE, lineno = 3, stmtid = 1,
elog_level = 21, condname = 0x1049218 "division_by_zero",
message = 0x0, params = 0x0, options = 0x0}
n_initvars = 0,
initvarnos = 0x0,
exceptions = 0x1049120 [List] --> [PLpgSQL_exception]
{lineno = 5,
conditions = 0x10495a0, // 给下面sqlstate赋值使用
--> [PLpgSQL_condition] {sqlerrstate = 33816706,
condname = 0x1049568 "division_by_zero",
next = 0x0}
action = 0x1049bc8} // 给下面执行异常处理逻辑使用
--> [List]
}
进入exec_stmt_raise
代码语言:javascript复制exec_stmt_raise
// 从名字返回code:division_by_zero->33816706
err_code = plpgsql_recognize_err_condition(stmt->condname, true)
...
ereport(21, ...)
// 跳转到exec_stmt_block
exec_stmt_block
...
PG_CATCH();
foreach(e, block->exceptions->exc_list)
if (exception_matches_conditions(edata, exception->conditions))
// sqlstate赋值 assign_text_var
assign_text_var (estate=0x7ffd6b492060, var=0x104aec8, str=0xe74338 <buf> "22012")
// sqlerrm赋值 assign_text_var
assign_text_var (estate=0x7ffd6b492060, var=0x104af10, str=0x1052d50 "division_by_zero")
// 异常处理逻辑:
rc = exec_stmts(estate, exception->action)
PLPGSQL_STMT_RAISE
PLpgSQL_stmt_raise {cmd_type = PLPGSQL_STMT_RAISE,
lineno = 6, stmtid = 2,
elog_level = 18,
condname = 0x0,
message = 0x1049418 "sqlstate: %",
params = 0x1049b70,
options = 0x0}
PLPGSQL_STMT_RAISE
PLpgSQL_stmt_raise {cmd_type = PLPGSQL_STMT_RAISE,
lineno = 7, stmtid = 3,
elog_level = 18,
condname = 0x0,
message = 0x1049cb8 "sqlerrm: %",
params = 0x1049db8,
options = 0x0}