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

本文深入探讨了PostgreSQL中plpgsql语言的异常处理机制,通过实例展示了如何使用RAISE EXCEPTION捕获并处理异常,包括sqlstate和sqlerrm的设置与使用。同时,详细解析了plpgsql的编译和执行过程,特别是异常处理部分的数据结构和流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

相关
《Postgresql中plpgsql异常处理方法与实例(RAISE EXCEPTION)》
《Postgresql源码(80)plpgsql中异常处理编译与执行流程分析(sqlstate)》

0 总结

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
  • 执行阶段:

    • 进入异常分支:exec_stmt_block --> PG_CATCH();
    • 遍历异常处理块:foreach(e, block->exceptions->exc_list)拿到一个exception
    • 对每一个块匹配异常关键字exception_matches_conditions(edata, exception->conditions))
    • 匹配成功则开始sqlstatesqlerrm赋值,然后继续执行exec_stmts(estate, exception->action)

1 案例

主动抛出异常

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 编译

编译代码

do $g$
BEGIN
  RAISE division_by_zero;
EXCEPTION
    WHEN division_by_zero THEN
        raise notice 'sqlstate: %', sqlstate;
        raise notice 'sqlerrm: %', sqlerrm;
END;
$g$;

语法树代码

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

位置

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

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

(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数组中的匹配元素:

PLpgSQL_condition
{
  sqlerrstate = 33816706, 
  condname = 0x104fdd0 "division_by_zero", 
  next = 0x0
}

2.4 语法树匹配:总装1:proc_exception

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

PLpgSQL_exception_block

{
 sqlstate_varno = 1, 
 sqlerrm_varno = 2, 
 exc_list = 0x10506d8 // 链表 --> 2.4组装的PLpgSQL_exception
 }

3 执行

exec_stmts时PLpgSQL_stmt_block数据结构

[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

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}
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高铭杰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值