Postgresql源码(80)plpgsql中异常处理编译与执行流程分析(sqlstate)

2022-09-26 21:49:03 浏览数 (1)

相关 《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

编译后的数据结构

  • 编译阶段:
代码语言:txt复制
- 根结构还是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)
代码语言:javascript复制
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:

代码语言:javascript复制
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}

0 人点赞