Postgresql中plpgsql数组的赋值与取值分析
总结
数组赋值
-
如果是默认值数组
- 编译时只组装expr出来记录ARRAY[1,2,3,4,5]
-
在执行赋默认值时走执行器把expr变成value记录到datum中
- 执行时语义解析阶段就会把只拼出来:transformArrayExpr将ARRAY[1,2,3,4,5]转换为ArrayExpr
- 执行时ExecEvalExpr阶段只当做Const把Value赋值给Datum即可。
- 如果是传入数组,直接当做常量赋值即可。
数组取值
- 取值阶段即调用SPI执行"x = arr[3];"语句的过程,注意赋值逻辑全部交给PG执行器,PL只给回调用于查询变量。
- 执行器会回调pl函数找到x和arr,然后走ExecEvalExpr把数组指定的值赋给x。
用例
DO $$
DECLARE
arr int[] := ARRAY[1,2,3,4,5];
x int;
BEGIN
x = arr[3];
raise notice '%', x;
-- 编译后、执行前:变量状态
$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赋值
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
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中,这里只是一个简单的常量赋值,没有发现数组拼接构造的逻辑。
$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
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;
第二步:读取默认值
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];
exec_stmt_assign
exec_assign_expr
exec_assign_expr
阶段一:exec_prepare_plan
阶段二:exec_eval_expr
阶段一:exec_prepare_plan
正常执行到exec_stmt_assign开始赋值,还是在语义分析的时候回调取值
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:
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
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
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