当前位置: 首页 > news >正文

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

0 速查

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

  • 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。
      • 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: <NULL>
NOTICE:  c: <NULL>
NOTICE:  d: 4
NOTICE:  e: 5
 b | c 
---+---
   |  
==============================================
call tp12(1,2,3,4);
NOTICE:  a: 1
NOTICE:  b: <NULL>
NOTICE:  c: <NULL>
NOTICE:  d: 4
NOTICE:  e: 400
 b | c 
---+---
   |  
==============================================
call tp12(1,2,3);
NOTICE:  a: 1
NOTICE:  b: <NULL>
NOTICE:  c: <NULL>
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: <NULL>
NOTICE:  c: <NULL>
NOTICE:  d: 300
NOTICE:  e: 500
 b | c 
---+---
   |  
==============================================

按执行顺序分析

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
  • (复习)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
}

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

  • 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 第二阶段:执行call tp12(1,2,3,4);

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

比如下面case:

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

进入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
    

上述三轮循环发生了什么

给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

赋值后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
}

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

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}

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}

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++;
	}

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;
$$;

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

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

postgres=# call tp13(1,2,3,4);
NOTICE:  a: 1
NOTICE:  b: <NULL>
NOTICE:  c: <NULL>
NOTICE:  d: 4
NOTICE:  e: 300
 b | c | d 
---+---+---
   |   | 4
(1 row)

报错: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.

这里的问题发生在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

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

相关文章:

  • CSAPP datalab
  • Github爆火,阿里最新发布的《高并发核心编程笔记》PDF文档
  • ntohl()、htonl()、ntohs()、htons()函数
  • 基于ssm+vue的师生防疫登记管理系统 elementui
  • trustZone学习
  • USB转多串口设备固定串口号
  • 七牛云 OSS 文件上传demo
  • 我的VS2013版本
  • .NET DataGridView数据绑定说明
  • 商业化广告--体系学习--1
  • apk反编译和重新打包流程
  • Spring Security -- 前后端分离时的安全处理方案
  • 使用HttpServlet和@WebServlet注解
  • Arthas使用指北——命令、原理及案例
  • 简历撰写——Java与.NET(当年毕业生版本)
  • 【Under-the-hood-ReactJS-Part0】React源码解读
  • 10个确保微服务与容器安全的最佳实践
  • gcc介绍及安装
  • java 多线程基础, 我觉得还是有必要看看的
  • Javascript设计模式学习之Observer(观察者)模式
  • PHP 程序员也能做的 Java 开发 30分钟使用 netty 轻松打造一个高性能 websocket 服务...
  • Python利用正则抓取网页内容保存到本地
  • Redis的resp协议
  • Redux 中间件分析
  • Vim Clutch | 面向脚踏板编程……
  • Web设计流程优化:网页效果图设计新思路
  • 关于springcloud Gateway中的限流
  • 前端学习笔记之观察者模式
  • 深入 Nginx 之配置篇
  • 为物联网而生:高性能时间序列数据库HiTSDB商业化首发!
  • 新版博客前端前瞻
  • 学习笔记TF060:图像语音结合,看图说话
  • 用element的upload组件实现多图片上传和压缩
  • 用mpvue开发微信小程序
  • 《码出高效》学习笔记与书中错误记录
  • ​力扣解法汇总1802. 有界数组中指定下标处的最大值
  • ###C语言程序设计-----C语言学习(3)#
  • (1/2)敏捷实践指南 Agile Practice Guide ([美] Project Management institute 著)
  • (DenseNet)Densely Connected Convolutional Networks--Gao Huang
  • (搬运以学习)flask 上下文的实现
  • (附源码)spring boot建达集团公司平台 毕业设计 141538
  • (十八)devops持续集成开发——使用docker安装部署jenkins流水线服务
  • (四)【Jmeter】 JMeter的界面布局与组件概述
  • (四)linux文件内容查看
  • (一)eclipse Dynamic web project 工程目录以及文件路径问题
  • (一)RocketMQ初步认识
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • .equal()和==的区别 怎样判断字符串为空问题: Illegal invoke-super to void nio.file.AccessDeniedException
  • .NET Core 版本不支持的问题
  • .NET 使用配置文件
  • .net 桌面开发 运行一阵子就自动关闭_聊城旋转门家用价格大约是多少,全自动旋转门,期待合作...
  • .NET/C# 使用 SpanT 为字符串处理提升性能
  • .net网站发布-允许更新此预编译站点
  • /bin/rm: 参数列表过长"的解决办法
  • ?.的用法