前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql中plpgsql数组的赋值与取值分析

Postgresql中plpgsql数组的赋值与取值分析

作者头像
mingjie
发布2022-10-28 15:13:46
1.6K0
发布2022-10-28 15:13:46
举报
文章被收录于专栏:Postgresql源码分析

总结

数组赋值

  • 如果是默认值数组
    • 编译时只组装expr出来记录ARRAY1,2,3,4,5
    • 在执行赋默认值时走执行器把expr变成value记录到datum中
      • 执行时语义解析阶段就会把只拼出来:transformArrayExpr将ARRAY1,2,3,4,5转换为ArrayExpr
      • 执行时ExecEvalExpr阶段只当做Const把Value赋值给Datum即可。
  • 如果是传入数组,直接当做常量赋值即可。

数组取值

  • 取值阶段即调用SPI执行"x = arr3;"语句的过程,注意赋值逻辑全部交给PG执行器,PL只给回调用于查询变量。
  • 执行器会回调pl函数找到x和arr,然后走ExecEvalExpr把数组指定的值赋给x。

用例

代码语言:javascript
复制
DO $$
DECLARE
  arr int[] := ARRAY[1,2,3,4,5];
  x int;
BEGIN
  x = arr[3];
  raise notice '%', x;
END;
$$;



-- 编译后、执行前:变量状态
$6 = {dtype = PLPGSQL_DTYPE_VAR, dno = 0, refname = 0x2b1e3c0 "found", 
  lineno = 0, isconst = false, notnull = false, default_val = 0x0, datatype = 0x2b1e2c8, 
  cursor_explicit_expr = 0x0, cursor_explicit_argrow = 0, cursor_options = 0, 
  value = 0, isnull = true, freeval = false, promise = PLPGSQL_PROMISE_NONE}

$7 = {dtype = PLPGSQL_DTYPE_VAR, dno = 1, refname = 0x2b1f1c0 "arr", 
  lineno = 3, isconst = false, notnull = false, default_val = 0x2b1f070, datatype = 0x2b1eff0, 
  cursor_explicit_expr = 0x0, cursor_explicit_argrow = 0, cursor_options = 0, 
  value = 0, isnull = true, freeval = false, promise = PLPGSQL_PROMISE_NONE}

$8 = {dtype = PLPGSQL_DTYPE_VAR, dno = 2, refname = 0x2b1fa40 "x", 
  lineno = 4, isconst = false, notnull = false, default_val = 0x0, datatype = 0x2b1f948, 
  cursor_explicit_expr = 0x0, cursor_explicit_argrow = 0, cursor_options = 0, 
  value = 0, isnull = true, freeval = false, promise = PLPGSQL_PROMISE_NONE}

【如何赋值】DECLARE BLOCK初始值赋值(exec_stmt_block)

arr int[] := ARRAY[1,2,3,4,5];

(为什么进入BLOCK每次都要走一遍赋初值流程?哪里决定的哪些变量需要赋值?《Postgresql源码(79)plpgsql中多层调用时参数传递关键点分析(pl参数)》:问题五)

同样是走assign_simple_var统一赋值入口给arr赋值

代码语言:javascript
复制
exec_stmt_block
  // 两个值都是VAR类型,所以都需要初始化赋默认值
  for (i = 0; i < block->n_initvars; i++)
    case PLPGSQL_DTYPE_VAR:
      // 先给0
      assign_simple_var(estate, var, (Datum) 0, true, false);
      // 如果有默认值,在给默认值
      exec_assign_expr(estate, (PLpgSQL_datum *) var, var->default_val);

arr有默认值进入exec_assign_expr

代码语言:javascript
复制
exec_assign_expr
  exec_prepare_plan
    SPI_prepare_extended
      _SPI_prepare_plan      // 完成 语法解析、语义解析
        raw_parser           // src=0x2b1bd30 "ARRAY[1,2,3,4,5]"
// 语法树:SelectStmt={targetList = 0x2b1d640}
//   targetList   =[{ResTarget}]
//   ResTarget    ={val = A_ArrayExpr}
//   A_ArrayExpr  ={type = T_A_ArrayExpr, elements = 0x2b1d4b0, location = 0}
//   elements     =[{A_Const},{A_Const},{A_Const},{A_Const},{A_Const}]
        pg_analyze_and_rewrite_withcb
          parse_analyze_withcb
            plpgsql_parser_setup      // 挂上从PL拿变量的钩子
            transformTopLevelStmt
              transformOptionalSelectInto
                transformStmt
                  【case T_SelectStmt】
                    transformSelectStmt
                      transformTargetList
                        transformTargetEntry
                          transformExpr
                            transformExprRecurse   // 传入:expr=A_ArrayExpr
                              【case T_A_ArrayExpr】
                                transformArrayExpr
                                  // 根据A_ArrayExpr拼接一个ArrayExpr结构返回
// ArrayExpr= {xpr = {type = T_ArrayExpr}, array_typeid = 1007, array_collid = 0, element_typeid = 23, elements = 0x2b1dc70, multidims = false, location = 0}
// elements = [{Const},{Const},{Const},{Const},{Const}]
                                
        pg_analyze_and_rewrite_withcb:完成
      _SPI_prepare_plan:完成
    SPI_prepare_extended:完成
  
返回plan中plancache_list记录了转换后的ArrayExpr
// {magic = 569278163, saved = false, oneshot = false, plancache_list = 0x2b283d8, plancxt = 0x2b28270, parse_mode = RAW_PARSE_PLPGSQL_EXPR, cursor_options = 0, nargs = 0, argtypes = 0x0, parserSetup = 0x7fd83131cfec <plpgsql_parser_setup>, parserSetupArg = 0x2b1bca0}

  exec_eval_expr
    // 进入时:PLpgSQL_expr = {query = 0x2b1dd40 "ARRAY[1,2,3,4,5]", parseMode = RAW_PARSE_PLPGSQL_EXPR...}
    exec_eval_simple_expr
      ExecEvalExpr
        state->evalfunc
          ExecInterpExprStillValid
            ExecJustConst

ARRAY在编译阶段已经做好了放在default_val中,这里只是一个简单的常量赋值,没有发现数组拼接构造的逻辑。

代码语言:javascript
复制
$7 = {dtype = PLPGSQL_DTYPE_VAR, dno = 1, refname = 0x2b1f1c0 "arr", 
  lineno = 3, isconst = false, notnull = false, default_val = 0x2b1f070, datatype = 0x2b1eff0, 
  cursor_explicit_expr = 0x0, cursor_explicit_argrow = 0, cursor_options = 0, 
  value = 0, isnull = true, freeval = false, promise = PLPGSQL_PROMISE_NONE}

编译生成default_val

编译阶段是怎么处理生成default_val的:plpgsql_compile_inline编译 arr int[] := ARRAY[1,2,3,4,5];

第一步:处理类型int[]:decl_datatype识别int[]返回PLpgSQL_type
代码语言:javascript
复制
arr int[] := ARRAY[1,2,3,4,5]
    T_WORD

decl_datatype	:
					{
						/*
						 * If there's a lookahead token, read_datatype
						 * should consume it.
						 */
						$$ = read_datatype(yychar);
						yyclearin;
					}
				;


read_datatype
  char *dtname = yylval.word.ident; // dtname = "int"
  // while (tok != ';')
  tok = yylex();                    // 91 -> [
  tok = yylex();                    // 93 -> ]
  tok = yylex();                    // 270 -> COLON_EQUALS
  // 取出完整typename = int[]
  result = parse_datatype(type_name, startlocation)
  return result;
第二步:读取默认值
代码语言:javascript
复制
decl_defval		: ';'
					{ $$ = NULL; }
				| decl_defkey
					{
						$$ = read_sql_expression(';', ";");
					}
				;

decl_defkey		: assign_operator
				| K_DEFAULT 
				;
			
			
read_sql_expression
  read_sql_construct(until=59 ";", until2=0, until3=0, expected=0x7fd83134458c ";",
                     parsemode=RAW_PARSE_PLPGSQL_EXPR, isexpression=true, 
                     valid_sql=true, trim=true, startloc=0x0, endtoken=0x0)
    // 全部工作就是
    // 【1】拼一个PLpgSQL_expr
    // 【2】check_sql_expr检查正确性(过一遍raw_parser即可)
    返回:
    {query = 0x2b1bd30 "ARRAY[1,2,3,4,5]", parseMode = RAW_PARSE_PLPGSQL_EXPR, plan = 0x0, 
     paramnos = 0x0, func = 0x0, ns = 0x2b1b038, expr_simple_expr = 0x0, expr_simple_type = 0, 
     expr_simple_typmod = 0, expr_simple_mutable = false, target_param = -1, expr_rw_param = 0x0,
     expr_simple_plansource = 0x0, expr_simple_plan = 0x0, expr_simple_plan_lxid = 0, 
     expr_simple_state = 0x0, expr_simple_in_use = false, expr_simple_lxid = 0}

【如何取值】数组取值给普通变量

x = arr[3];

代码语言:javascript
复制
exec_stmt_assign
  exec_assign_expr
    exec_assign_expr
      阶段一:exec_prepare_plan
      阶段二:exec_eval_expr

阶段一:exec_prepare_plan

正常执行到exec_stmt_assign开始赋值,还是在语义分析的时候回调取值

代码语言:javascript
复制
exec_stmt_assign
  exec_assign_expr
    exec_assign_expr
      exec_prepare_plan
        SPI_prepare_extended
          _SPI_prepare_plan
            pg_analyze_and_rewrite_withcb
              parse_analyze_withcb
                transformTopLevelStmt
                  transformOptionalSelectInto
                    transformStmt
                      transformPLAssignStmt
                        transformTargetList
                          transformTargetEntry
                            transformExpr
                              transformExprRecurse
                                transformIndirection
                                  transformExprRecurse
                                    transformColumnRef
                                      plpgsql_post_column_ref
                                        resolve_column_ref
                                          make_datum_param                    // 拼一个Param返回
plpgsql_post_column_ref回调函数都做了什么?
第一步:make_datum_param

由datum:

代码语言:javascript
复制
PLpgSQL_var = {
 dtype = PLPGSQL_DTYPE_VAR, dno = 1, refname = 0x2b13db0 "arr", lineno = 3, 
 isconst = false, notnull = false, default_val = 0x2b13c60, datatype = 0x2b13be0, 
 cursor_explicit_expr = 0x0, cursor_explicit_argrow = 0, cursor_options = 0, 
 value = 44332064, isnull = false, freeval = true, promise = PLPGSQL_PROMISE_NONE}

生成Param,注意paramid=dno+1

代码语言:javascript
复制
Param = {
  xpr = {type = T_Param}, paramkind = PARAM_EXTERN, paramid = 2, 
  paramtype = 1007, paramtypmod = -1, paramcollid = 0, location = 4}

返回的Param只记录了paramid=2表示dno=1,和类型信息。

阶段二:exec_eval_expr

代码语言:javascript
复制
exec_stmt_assign
  exec_assign_expr
    exec_eval_expr
      exec_eval_simple_expr
        ExecInitExprWithParams
          ExecInitExprRec
            ExecInitSubscriptingRef
              ExecInitExprRec
                plpgsql_param_compile
                  // 给执行器配一个合适的取值函数
                  scratch.d.cparam.paramfunc = plpgsql_param_eval_var_ro;
        ExecEvalExpr
          ExecInterpExprStillValid
            ExecInterpExpr
              plpgsql_param_eval_var_ro
                int dno = op->d.cparam.paramid - 1;
                var = (PLpgSQL_var *) estate->datums[dno];    // 根据paramid找到var
                *op->resvalue = MakeExpandedObjectReadOnly    // 拿到返回整个var
              
              结果保存在(op+2)->d.constval.value = 3

TODO:ExecInterpExpr后面单独分析一篇。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-10-11,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 总结
  • 用例
  • 【如何赋值】DECLARE BLOCK初始值赋值(exec_stmt_block)
    • 编译生成default_val
      • 第一步:处理类型int[]:decl_datatype识别int[]返回PLpgSQL_type
      • 第二步:读取默认值
  • 【如何取值】数组取值给普通变量
    • 阶段一:exec_prepare_plan
      • plpgsql_post_column_ref回调函数都做了什么?
    • 阶段二:exec_eval_expr
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档