• Postgresql源码(77)plpgsql中参数传递和赋值


    相关
    《Postgresql源码(77)plpgsql中参数传递和赋值(pl参数)》
    《Postgresql源码(78)plpgsql中调用call proc()时的参数传递和赋值(pl参数)》
    《Postgresql源码(79)plpgsql中多层调用时参数传递关键点分析(pl参数)》

    0 速查

    • call procedure(...);的入口函数ExecuteCallStmt:入参为CallStmt,函数中将CallStmt中的参数列表(可能是值、可能是表达式)赋值给fcinfo,然后通过PG函数框架进入plpgsql堆栈中,给对应入参的Dataums赋值。(下一篇介绍CallStmt中的args在哪里构造)

    • 入参有两组args,一组是语法解析直接生成的funccall;一组是经过优化器的funcexpr:

    • CallStmt->funccall->args:【有位置信息】【初始值】【与入参对应】

      • 灵活节点类型,数组中:A_Const、NamedArgExpr都有。
      • 严格与输入保持一致,不考虑out、默认值等情况。
      • tp12(1,2,3,e=>500)为例:
        • 共四个值。
        • 三个A_Const记录入参1,2,3。
        • 一个NamedArgExpr记录指向参数500。
        • 尽管2、3都是out不需要赋值,这里也做记录不转换。
    • CallStmt->funcexpr->args:【无位置信息】【加工值】【只有in、inout参数有值】【会填充默认值】

      • 全部是值Const类型。
      • 入参经过转换的值,会填充默认值。
      • tp12(1,2,3,e=>500)为例,函数定义见下面用例:
        • 共三个值。
        • 三个Const、记录1,300,500。300来自默认值,500来自e的定向赋值。
        • 2、3都是out类型参数不需要赋值,直接舍弃。
    • fcinfo->args直接按funcexpr->args填充(如果是表达式就执行后在填充)

      • 从0位置开始紧凑填充。

    1 用例

    CREATE or replace PROCEDURE tp12(
      a in integer, 
      b out integer,
      c out integer,
      d inout integer default 300,
      e in integer default 400)
    LANGUAGE plpgsql
    AS $$
    BEGIN
      raise notice 'a: %', a;
      raise notice 'b: %', b;
      raise notice 'c: %', c;
      raise notice 'd: %', d;
      raise notice 'e: %', e;
    END;
    $$;
    
    ==============================================
    call tp12(1,2,3,4,5);
    NOTICE:  a: 1
    NOTICE:  b: 
    NOTICE:  c: 
    NOTICE:  d: 4
    NOTICE:  e: 5
     b | c 
    ---+---
       |  
    ==============================================
    call tp12(1,2,3,4);
    NOTICE:  a: 1
    NOTICE:  b: 
    NOTICE:  c: 
    NOTICE:  d: 4
    NOTICE:  e: 400
     b | c 
    ---+---
       |  
    ==============================================
    call tp12(1,2,3);
    NOTICE:  a: 1
    NOTICE:  b: 
    NOTICE:  c: 
    NOTICE:  d: 300
    NOTICE:  e: 400
     b | c 
    ---+---
       |  
    ==============================================
    call tp12(1,2);
    ERROR:  procedure tp12(integer, integer) does not exist
    ==============================================
    call tp12(1,2,3,e=>500);
    NOTICE:  a: 1
    NOTICE:  b: 
    NOTICE:  c: 
    NOTICE:  d: 300
    NOTICE:  e: 500
     b | c 
    ---+---
       |  
    ==============================================
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    按执行顺序分析

    1 第一阶段:do_compile(编译不解析参数默认值)

    编译总结:

    • 从编译结果看,编译不处理default默认值。
    • 编译生成的datums数组中不区分in、out,没有参数信息。
    • 参数信息在functions->fn_argvarnos、functions->out_param_varno中记录。
    CREATE or replace PROCEDURE tp12(
      a in integer,                ---> function->fn_argvarnos
      b out integer,               ---> function->out_param_varno
      c out integer,               ---> function->out_param_varno
      d inout integer default 300, ---> function->fn_argvarnos   --->function->out_param_varno
      e in integer default 400)    ---> function->fn_argvarnos
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • (复习)PLpgSQL_row用于保存多变量结果:
      • select into后面的变量记录在row中。
      • for loop targetlist记录在row中。
      • 游标的参数列表记录在row中。

    编译完成变量:

    plpgsql_nDatums = 7
    
    p *((PLpgSQL_var*)plpgsql_Datums[0])
    {
      dtype = PLPGSQL_DTYPE_VAR, dno = 0, refname = 0x29d8260 "a", lineno = 0, 
      isconst = false, notnull = false, default_val = 0x0, datatype = 0x29d81e8,
      cursor_explicit_expr = 0x0, cursor_explicit_argrow = 0, cursor_options = 0, 
      value = 0, isnull = true, freeval = false, promise = PLPGSQL_PROMISE_NONE
    }
    
    p *((PLpgSQL_var*)plpgsql_Datums[1])
    {
      dtype = PLPGSQL_DTYPE_VAR, dno = 1, refname = 0x29d8368 "b", lineno = 0, 
      isconst = false, notnull = false, default_val = 0x0, datatype = 0x29d82f0,
      cursor_explicit_expr = 0x0, cursor_explicit_argrow = 0, cursor_options = 0, 
      value = 0, isnull = true, freeval = false, promise = PLPGSQL_PROMISE_NONE
    }
    
    p *((PLpgSQL_var*)plpgsql_Datums[2])
    {
      dtype = PLPGSQL_DTYPE_VAR, dno = 2, refname = 0x29d8508 "c", lineno = 0, 
      isconst = false, notnull = false, default_val = 0x0, datatype = 0x29d83f8,
      cursor_explicit_expr = 0x0, cursor_explicit_argrow = 0, cursor_options = 0, 
      value = 0, isnull = true, freeval = false, promise = PLPGSQL_PROMISE_NONE
    }
    
    p *((PLpgSQL_var*)plpgsql_Datums[3])
    {
      dtype = PLPGSQL_DTYPE_VAR, dno = 3, refname = 0x29d86a8 "d", lineno = 0, 
      isconst = false, notnull = false, default_val = 0x0, datatype = 0x29d8598,
      cursor_explicit_expr = 0x0, cursor_explicit_argrow = 0, cursor_options = 0, 
      value = 0, isnull = true, freeval = false, promise = PLPGSQL_PROMISE_NONE
    }
    
    p *((PLpgSQL_var*)plpgsql_Datums[4])
    {
      dtype = PLPGSQL_DTYPE_VAR, dno = 4, refname = 0x29d8848 "e", lineno = 0, 
      isconst = false, notnull = false, default_val = 0x0, datatype = 0x29d8738,
      cursor_explicit_expr = 0x0, cursor_explicit_argrow = 0, cursor_options = 0, 
      value = 0, isnull = true, freeval = false, promise = PLPGSQL_PROMISE_NONE
    }
    
    p *((PLpgSQL_row*)plpgsql_Datums[5])
    {
      dtype = PLPGSQL_DTYPE_ROW, dno = 5, refname = 0x7f02d092bea8 "(unnamed row)", 
      lineno = -1, isconst = false, notnull = false, default_val = 0x0,
      rowtupdesc = 0x29d7830, nfields = 3, fieldnames = 0x29d8930, varnos = 0x29d7c90
    }
    
    p *((PLpgSQL_var*)plpgsql_Datums[6])
    {
      dtype = PLPGSQL_DTYPE_VAR, dno = 6, refname = 0x29d8a78 "found", lineno = 0, 
      isconst = false, notnull = false, default_val = 0x0, datatype = 0x29d8968, 
      cursor_explicit_expr = 0x0, cursor_explicit_argrow = 0, cursor_options = 0, 
      value = 0, isnull = true, freeval = false,promise = PLPGSQL_PROMISE_NONE
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    编译完成和参数相关的三个关键变量:

    • In参数分别记录在Datums数组中
    • Out参数不管有几个,拼装到一个row中用function->out_param_varno指向dno(变量数组位置)
    本地临时变量             | --->     保存到编译结果function中
    -----------------------|------------------------------
    procStruct->pronargs   | --->     function->fn_nargs        = 3
    in_arg_varnos          | --->     function->fn_argvarnos    = {0 ,3, 4}    整形数组:存所有in参数变量的dno
    out_arg_variables      | --->     function->out_param_varno = 5            整形:只存一个dno指向row,row保存所有out参数
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1 第二阶段:执行call tp12(1,2,3,4);

    总结:函数入参赋值是遍历datums中需要值的变量,然后按顺序拿fcinfo->args数组的值。

    比如下面case:

    for i(次数 = 编译完了需要入参的数量 = 3)
      编译完Datums中0、3、4行需要入参
      循环三次用0、1、2从fcinfo->args[i]数组中拿三个赋值,数组中一定有值(没传的话外面会把默认值填进来)
    
    • 1
    • 2
    • 3

    进入plpgsql_exec_function

    plpgsql_exec_function
      for (i = 0; i < func->fn_nargs; i++)   // 循环3个入参
        int n = func->fn_argvarnos[i];       // 拿dno
        switch (estate.datums[n]->dtype)
          case PLPGSQL_DTYPE_VAR:
            xxx
          case PLPGSQL_DTYPE_REC:
            xxx
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    上述三轮循环发生了什么

    给a、d、e赋值:

    n = 0     a in integer                 从fcinfo->args[0].value拿到1通过assign_simple_var赋值给var
              b out integer
              c out integer
    n = 3     d inout integer default 300  从fcinfo->args[1].value拿到4通过assign_simple_var赋值给var
    n = 4     e in integer default 400     从fcinfo->args[2].value拿到400通过assign_simple_var赋值给var
    
    • 1
    • 2
    • 3
    • 4
    • 5

    赋值后e有400这个值了

    p *((PLpgSQL_var*)estate->datums[4])
    {
      dtype = PLPGSQL_DTYPE_VAR, dno = 4, refname = 0x29dc868 "e", lineno = 0, 
      isconst = false, notnull = false, default_val = 0x0, datatype = 0x29dc758,
      cursor_explicit_expr = 0x0, cursor_explicit_argrow = 0, cursor_options = 0, 
      value = 400, isnull = false, freeval = false, promise = PLPGSQL_PROMISE_NONE
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    400是从哪来的?

    到这里发现函数的默认值400是从fcinfo->args[2].value拿出来的,那么后面继续追溯下这个值是哪里添加的:

    2 追溯:fcinfo->args来源?

    函数调用堆栈:fcinfo的值是在ExecuteCallStmt中构造的:

    standard_ProcessUtility
      ExecuteCallStmt
        LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS) <----------入参默认的400在这里就填好了,存在fcinfo里
        ...
        /* 在这里把传入的参数值赋值 */
        foreach(lc, fexpr->args)
          exprstate = ExecPrepareExpr(lfirst(lc), estate);
          val = ExecEvalExprSwitchContext(exprstate, econtext, &isnull);
          fcinfo->args[i].value = val;
          fcinfo->args[i].isnull = isnull;
        /* 结束 */
        ...
        FunctionCallInvoke
          plpgsql_call_handler
            SPI_connect_ext
            plpgsql_compile
            PG_TRY
              plpgsql_exec_function
            PG_FINALLY
            PG_END_TRY
            SPI_finish
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2.1| call tp12(1,2,3,4) | ExecuteCallStmt输入值情况

    在这里插入图片描述

    2.2 | call tp12(1,2,3,e=>500) | ExecuteCallStmt输入值情况

    CallStmt->funccall->args

      {type = T_A_Const, val = {type = T_Integer, val = {ival = 1}}, location = 10}
      {type = T_A_Const, val = {type = T_Integer, val = {ival = 2}}, location = 12}
      {type = T_A_Const, val = {type = T_Integer, val = {ival = 3}}, location = 14}
      {xpr = {type = T_NamedArgExpr}, arg = 0x28dd4d0, name = 0x28dd0a8 "e", argnumber = 4, location = 16}
        NamedArgExpr->arg: {xpr = {type = T_Const}, consttype = 23, consttypmod = -1, constcollid = 0, constlen = 4, constvalue = 500, constisnull = false, constbyval = true, location = 19}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    CallStmt->funcexpr->args

    {xpr = {type = T_Const}, consttype = 23, consttypmod = -1, constcollid = 0, constlen = 4, constvalue = 1, constisnull = false, constbyval = true, location = 10}
    {xpr = {type = T_Const}, consttype = 23, consttypmod = -1, constcollid = 0, constlen = 4, constvalue = 300, constisnull = false, constbyval = true, location = -1}
    {xpr = {type = T_Const}, consttype = 23, consttypmod = -1, constcollid = 0, constlen = 4, constvalue = 500, constisnull = false, constbyval = true, location = 19}
    
    • 1
    • 2
    • 3

    2.3 ExecuteCallStmt输入值总结

    • CallStmt->funccall->args

      • 灵活节点类型,数组中:A_Const、NamedArgExpr都有。
      • 严格与输入保持一致,不考虑out、默认值等情况。
      • tp12(1,2,3,e=>500)为例:三个A_Const记录入参1,2,3、一个NamedArgExpr记录指向参数500。尽管2、3都是out不需要赋值,这里也做记录不转换。
    • CallStmt->funcexpr->args

      • 全部是值Const类型
      • 入参经过转换的值,会填充默认值
      • tp12(1,2,3,e=>500)为例:三个Const、记录1,300,500;因为2、3都是out不需要赋值。

    2.4 使用CallStmt->funcexpr构造fcinfo->args

    fcinfo->args完整继承CallStmt->funcexpr的值,从0开始紧凑填充。

    ExecuteCallStmtCallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver *dest)
     	...
    	fexpr = stmt->funcexpr;
      	...
      	i = 0;
    	foreach(lc, fexpr->args)
    	{
    		ExprState  *exprstate;
    		Datum		val;
    		bool		isnull;
    
    		exprstate = ExecPrepareExpr(lfirst(lc), estate);
    
    		val = ExecEvalExprSwitchContext(exprstate, econtext, &isnull);
    
    		fcinfo->args[i].value = val;
    		fcinfo->args[i].isnull = isnull;
    
    		i++;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2 实验:去除默认值位置限制

    PG要求默认值参数后面的参数,必须也有默认值,去除这个限制会有一些问题。

    例如:

    CREATE or replace PROCEDURE tp13(
      a in integer, 
      b out integer,
      c out integer,
      d inout integer default 300,
      e in integer)
    LANGUAGE plpgsql
    AS $$
    BEGIN
      raise notice 'a: %', a;
      raise notice 'b: %', b;
      raise notice 'c: %', c;
      raise notice 'd: %', d;
      raise notice 'e: %', e;
    END;
    $$;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    结果错误:call tp13(1,2,3,4);

    默认值错误的给e了,这条本应该报错,因为e无值。

    postgres=# call tp13(1,2,3,4);
    NOTICE:  a: 1
    NOTICE:  b: 
    NOTICE:  c: 
    NOTICE:  d: 4
    NOTICE:  e: 300
     b | c | d 
    ---+---+---
       |   | 4
    (1 row)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    报错:call tp13(1,2,3, e=>5);

    直接报错了,这条应该正常执行,d给默认值300,e给传入值5。

    postgres=# call tp13(1,2,3, e=>5);
    
    ERROR:  procedure tp13(integer, integer, integer, e => integer) does not exist
    LINE 1: call tp13(1,2,3, e=>5);
                 ^
    HINT:  No procedure matches the given name and argument types. You might need to add explicit type casts.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这里的问题发生在ParseFuncOrColumn中:

    #0  errfinish (filename=0xc50838 "parse_func.c", lineno=620, funcname=0xc51e80 <__func__.22568> "ParseFuncOrColumn") at elog.c:515
    #1  0x000000000064470f in ParseFuncOrColumn (pstate=0x1660268, funcname=0x165ff60, fargs=0x16603d8, last_srf=0x0, fn=0x1660148, proc_call=true, location=5)
        at parse_func.c:620
    #2  0x00000000005fb84b in transformCallStmt (pstate=0x1660268, stmt=0x16601a0) at analyze.c:2971
    #3  0x00000000005f5c17 in transformStmt (pstate=0x1660268, parseTree=0x16601a0) at analyze.c:357
    #4  0x00000000005f5a5f in transformOptionalSelectInto (pstate=0x1660268, parseTree=0x16601a0) at analyze.c:268
    #5  0x00000000005f591a in transformTopLevelStmt (pstate=0x1660268, parseTree=0x16601d8) at analyze.c:218
    #6  0x00000000005f56f4 in parse_analyze (parseTree=0x16601d8, sourceText=0x165f4e0 "call tp13(1,2,3, e=>5);", paramTypes=0x0, numParams=0, queryEnv=0x0)
        at analyze.c:127
    #7  0x0000000000975d70 in pg_analyze_and_rewrite (parsetree=0x16601d8, query_string=0x165f4e0 "call tp13(1,2,3, e=>5);", paramTypes=0x0, numParams=0, 
        queryEnv=0x0) at postgres.c:656
    #8  0x00000000009764a1 in exec_simple_query (query_string=0x165f4e0 "call tp13(1,2,3, e=>5);") at postgres.c:1129
    #9  0x000000000097ab59 in PostgresMain (argc=1, argv=0x7ffd124d16f0, dbname=0x1688cb0 "postgres", username=0x1688c88 "mingjiegao") at postgres.c:4494
    #10 0x00000000008b6d4e in BackendRun (port=0x1680870) at postmaster.c:4530
    #11 0x00000000008b66cd in BackendStartup (port=0x1680870) at postmaster.c:4252
    #12 0x00000000008b2b45 in ServerLoop () at postmaster.c:1745
    #13 0x00000000008b2417 in PostmasterMain (argc=1, argv=0x16590d0) at postmaster.c:1417
    #14 0x00000000007b4c93 in main (argc=1, argv=0x16590d0) at main.c:209
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    下面一篇展开分析这个问题。

  • 相关阅读:
    【云原生之kubernetes实战】在k8s环境下部署Heimdall导航页
    宏集案例 | Panarama SCADA平台在风电场测量的应用,实现风电场的高效管理!
    RocketMq5 消息消费及相关源码浅阅
    二、OSPF协议基础
    JVM之垃圾回收
    读高性能MySQL(第4版)笔记04_操作系统和硬件优化
    Chrome开发者工具课程
    ChinaSoft 论坛巡礼 | 开源软件生态健康度量论坛
    持续集成部署-k8s-部署利器-Helm
    记一个 Nvidia Control Panel 打不开的问题
  • 原文地址:https://blog.csdn.net/jackgo73/article/details/126674870