相关 《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参数有值】【会填充默认值】
代码语言:txt复制- 全部是值Const类型。
- 入参经过转换的值,会填充默认值。
- tp12(1,2,3,e=>500)为例,函数定义见下面用例:
- 共三个值。
- 三个Const、记录1,300,500。300来自默认值,500来自e的定向赋值。
- 2、3都是out类型参数不需要赋值,直接舍弃。fcinfo->args直接按funcexpr->args填充(如果是表达式就执行后在填充)
代码语言:txt复制- 从0位置开始紧凑填充。
1 用例
代码语言:javascript复制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中。
编译完成变量:
代码语言:javascript复制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:
代码语言:javascript复制for i(次数 = 编译完了需要入参的数量 = 3)
编译完Datums中0、3、4行需要入参
循环三次用0、1、2从fcinfo->args[i]数组中拿三个赋值,数组中一定有值(没传的话外面会把默认值填进来)
进入plpgsql_exec_function
代码语言:javascript复制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赋值:
代码语言:javascript复制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这个值了
代码语言:javascript复制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中构造的:
代码语言:javascript复制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
代码语言:javascript复制 {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
代码语言:javascript复制{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
代码语言:txt复制- 全部是值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开始紧凑填充。
代码语言:javascript复制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要求默认值参数后面的参数,必须也有默认值,去除这个限制会有一些问题。
例如:
代码语言:javascript复制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无值。
代码语言:javascript复制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。
代码语言:javascript复制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中:
代码语言:javascript复制#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
下面一篇展开分析这个问题。