前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql源码(80)plpgsql中异常处理编译与执行流程分析(sqlstate)

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

作者头像
mingjie
发布2022-09-26 21:49:03
3950
发布2022-09-26 21:49:03
举报

相关 《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}
本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-09-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0 总结
  • 1 案例
    • 主动抛出异常
    • 2 编译
      • 2.1 语法树匹配:K_RAISE
        • 2.2 语法树匹配:K_EXCEPTION
          • 2.3 语法树匹配:proc_condition
            • 2.4 语法树匹配:总装1:proc_exception
              • 2.5 语法树匹配:总装2:proc_exception
              • 3 执行
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
              http://www.vxiaotou.com