Postgresql源码(84)语义分析——函数调用结构CallStmt的构造与函数多态的实现(pl参数)

这篇具有很好参考价值的文章主要介绍了Postgresql源码(84)语义分析——函数调用结构CallStmt的构造与函数多态的实现(pl参数)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

相关
《Postgresql源码(78)plpgsql中调用call proc()时的参数传递和赋值(pl参数)》
《Postgresql源码(79)plpgsql中多层调用时参数传递关键点分析(pl参数)》
《Postgresql源码(84)语义分析——函数调用结构CallStmt的构造与函数多态的实现(pl参数)》

本文涉及模块:语法分析语义分析查询重写

函数调用时在语义分析阶段,transform函数对函数入参进行分析,直观上需要完成几步工作:

  1. 检查是否有函数能匹配上调用输入的参数列表
  2. 如果匹配不上,是参数个数匹配不上,还是参数类型匹配不上?
  • 如果是个数,用默认参数拼接后能否匹配?【默认参数拼接】
  • 如果是类型,经过类型转换后能否匹配?【类型转换】
  1. 如果匹配上了多个,那么需要应该执行哪个函数?【多态】

PG对于上述问题都有了完善的处理逻辑,本篇尝试分析该过程的处理细节。

总结

总入口:transformCallStmt

【1】transformCallStmt

  • 顶层函数transformCallStmt负责组装CallStmt结构({type=T_CallStmt,FuncCall,FuncExpr,List outargs}
  • transformCallStmt组装步骤:
    1. 调用ParseFuncOrColumn生成CallStmt->FuncExpr、生成CallStmt->FuncExpr->args(不包含指向参数和默认参数)
    2. 调用expand_function_arguments补充CallStmt->FuncExpr->args,加入指向参数和默认参数。
    3. 自己拼接List outargs记录输出参数

Postgresql源码(84)语义分析——函数调用结构CallStmt的构造与函数多态的实现(pl参数)

【2】CallStmt是如何使用的
(《Postgresql源码(79)plpgsql中多层调用时参数传递关键点分析(pl参数)》问题四:内层ExecuteCallStmt如何构造fcinfo->args?)

  • 第一步:ExecuteCallStmt时遍历CallStmt->FuncExpr->args,把其中的值直接填入fcinfo->args[i].value使用。
  • 第二步:进入pl后,从fcinfo拿到的是紧凑的参数值数组,pl会使用传入的紧凑数组,把非out值依次赋值。
  • 基于第二步推论:给pl的参数值数组必须每一个in参数都有值,多了少了都会有问题。所以顶层函数必须构造准确的参数值数组CallStmt->FuncExpr->args

【3】对比Oracle

  • 考虑几种情况:
    • 情况一:func(入,出,默,默)
      • 调用失败:call func(值):非默认参数必须全部有值,与Oracle行为一致
      • 调用成功:call func(值,值)
      • 调用成功:call func(值,值,值)
  • 考虑几种PG不可能发生的情况(PG要求默认参数后面必须全部是默认参数)(PG要求OUT不能有默认值)(推论:默认参数后面不能有OUT参数)
  • Oracle行为:
    • 情况一:func(入a,出b,默c,出d)
      • 调用失败:func(值)
      • 调用失败:func(值,变量)
      • 调用成功:func(值,变量,d=>变量)
    • 情况二:func(默a,入b)
      • 调用失败:call func(值)
      • 调用成功:call func(值,值)
      • 调用成功:call func(b=>值)
    • 情况三:func(默a,出b)
      • 调用失败:call func(值)
      • 调用失败:call func(值,值)
      • 调用成功:call func(值,变量)
      • 调用成功:call func(b=>变量)

Oracle的IN OUT类型不能有默认参数,PG可以。
Oracle的OUT参数必须给个变量,否则执行肯定报错。

【4】PG目前的多态逻辑总结

  • 第一步:ParseFuncOrColumn调用func_get_detail调用FuncnameGetCandidates
    • FuncnameGetCandidates用名字找候选者
    • FuncnameGetCandidates对同名候选者做参数个数检查:
      • 如果 (proallargtypes个数) > (传入的全部参数个数):参数不够,需要补默认
        • 如果(传入的全部参数个数+默认参数个数) < (proallargtypes个数):补上默认参数就够用了!
      • 如果:(proallargtypes个数) <= (传入的全部参数个数):参数直接够用
    • FuncnameGetCandidates对指向性参数列表调用MatchNamedCall返回argnumbers数组表示映射关系,数组严格按位置对应入参,值表示函数参数列表中应该指向的位置。在返回候选函数的参数类型数组时,会用映射关系找到正确的类型顺序记录到候选函数参数类型列表中。(没有指向型时不走MatchNamedCall且argnumbers数组为空)
  • 第二步:ParseFuncOrColumn返回func_get_detail
    • 【找到严格匹配候选者】遍历FuncnameGetCandidates返回结果,如果能和argtypes严格匹配,即找到best_candidate,PGPROC中拉出默认参数列表,删除掉没用的,结果放到*argdefaults返回
    • 【没有严格匹配候选者】遍历FuncnameGetCandidates返回结果,没有候选者能和argtypes严格匹配
      • 首先判断这是不是一个强制转换:例如 select int(3.1),如果是的可以当做强制转换返回
      • 如果不是强制转换,这里肯定是参数类型对不上了,这里就开始进行【多态判断】
        • 判断入参类型能不能通过转换 变成 候选者的参数类型:func_match_argtypes
          • 如果只有一个候选者可以匹配, best_candidate = 当前候选者
          • 如果有多个候选者经过转换可以匹配,选择一个:func_select_candidate

1 用例

CREATE or replace PROCEDURE tp13(
  a in integer, 
  b out integer,
  c out integer,
  d inout integer default 400,
  e in integer default 500)
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,5);
call tp13 (1,2,3,e=>5);

2 顶层函数transformCallStmt

transformCallStmt函数负责转换所有函数调用节点,例如:

  • call proc1();
  • select func1();

transformCallStmt函数负责生成CallStmt结构:

typedef struct CallStmt
{
	NodeTag		type;
	FuncCall   *funccall;		/* from the parser */
	FuncExpr   *funcexpr;		/* transformed call, with only input args */
	List	   *outargs;		/* transformed output-argument expressions */
} CallStmt;

CallStmt结构在之前的函数参数分析文章中反复提到过:

  • 其中:FuncCall的args使用A_Const保存全部参数信息(未解析)
  • 其中:FuncExpr的args使用Const只保存IN参数信息(已解析)

截取一部分:Postgresql源码(79)plpgsql中多层调用时参数传递关键点分析(pl参数)


Postgresql源码(84)语义分析——函数调用结构CallStmt的构造与函数多态的实现(pl参数)


transformCallStmt内部有两个关键调用负责生成CallStmt->FuncExpr结构:
Postgresql源码(84)语义分析——函数调用结构CallStmt的构造与函数多态的实现(pl参数)

3 调用ParseFuncOrColumn生成FuncExpr(多态实现)

ParseFuncOrColumn
  func_get_detail            // 从系统表中找到函数,多态实现在这里
    FuncnameGetCandidates    // 第一步:找候选者1】用名字匹配遍历每一个结果
      【2】对于某个结果,拿到PG_PROC参数类型列表proallargtypes
      【3】对于某个结果,检查参数数目够不够?
        【3.1】对于全指向参数或混合型参数输入
          如果 (proallargtypes个数) >  (传入的全部参数个数):参数不够,需要补默认
            如果 (传入的全部参数个数+默认参数个数) < (proallargtypes个数):补上默认参数就够用了!
            如果 (传入的全部参数个数+默认参数个数) >=(proallargtypes个数):补上默认参数也不够,不使用当前函数。
          如果 (proallargtypes个数) <= (传入的全部参数个数):参数够用
          MatchNamedCall判断指向参数列表是否能匹配当前函数
            例如:call tp13 (1,2,3,e=>5);
            tp13(a in integer, b out integer,c out integer,d inout integer default 400,e in integer default 500)
            MatchNamedCall返回argnumbers数组表示映射关系:
            argnumbers = [0,1,2,4,3]
            给的第一个参数对应当前函数的参数列表中的0位置:a
            给的第二个参数对应当前函数的参数列表中的1位置:b
            给的第三个参数对应当前函数的参数列表中的2位置:c
            给的第四个参数对应当前函数的参数列表中的4位置:e
            只给了4个参数进来,第五个位置补充一个需要默认参数的3位置:d
        【3.2】对于全非指向参数输入
           只需要判断参数个数就好了,和上面逻辑类似不在赘述
  
  func_get_detail
    【找到严格匹配候选者】遍历FuncnameGetCandidates返回结果,如果能和argtypes严格匹配,即找到best_candidate
      PGPROC中拉出默认参数列表,删除掉没用的,结果放到*argdefaults返回

    【没有严格匹配候选者】遍历FuncnameGetCandidates返回结果,没有候选者能和argtypes严格匹配
      首先判断这是不是一个强制转换:例如 select int(3.1),如果是的可以当做强制转换返回
      如果不是强制转换,这里肯定是参数类型对不上了,这里就开始进行【多态判断】
        判断入参类型能不能通过转换 变成 候选者的参数类型:func_match_argtypes
          如果只有一个候选者可以匹配, best_candidate = 当前候选者
          如果有多个候选者经过转换可以匹配,选择一个:func_select_candidate
    
    

4 混合参数位置映射关系计算MatchNamedCall

上面给出的结果

            MatchNamedCall返回argnumbers数组表示映射关系:
            argnumbers = [0,1,2,4,3]
            给的第一个参数对应当前函数的参数列表中的0位置:a
            给的第二个参数对应当前函数的参数列表中的1位置:b
            给的第三个参数对应当前函数的参数列表中的2位置:c
            给的第四个参数对应当前函数的参数列表中的4位置:e
            只给了4个参数进来,第五个位置补充一个需要默认参数的3位置:d

涉及代码分析

执行:call tp13 (1,2,3,e=>5);

static bool
MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
			   bool include_out_arguments, int pronargs,
			   int **argnumbers)
{

入参nargs:4(由ParseFuncOrColumn在上层计算参数列表中所有元素)
入参argnames:只记录指向参数List,只有一个元素char:“e”
入参include_out_arguments:有out参数
入参pronargs:5(pg_proc记录函数需要五个参数)

	Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
	int			numposargs = nargs - list_length(argnames);
	int			pronallargs;
	Oid		   *p_argtypes;
	char	  **p_argnames;
	char	   *p_argmodes;
	bool		arggiven[FUNC_MAX_ARGS];
	bool		isnull;
	int			ap;				/* call args position */
	int			pp;				/* proargs position */
	ListCell   *lc;

	/* Ignore this function if its proargnames is null */
	(void) SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_proargnames,
						   &isnull);
	if (isnull)
		return false;

	/* OK, let's extract the argument names and types */
	pronallargs = get_func_arg_info(proctup,
									&p_argtypes, &p_argnames, &p_argmodes);

给输出的映射关系数组申请5个int位置

	/* initialize state for matching */
	*argnumbers = (int *) palloc(pronargs * sizeof(int));
	memset(arggiven, false, pronargs * sizeof(bool));

numposargs=nargs - list_length(argnames);
非指向参数个数 = 3 = 给了4个参数 - 有1个参数是指向型
非指向参数可以会直接记录到argnumbers数组中,同时在arggiven数组中记录已经给了的参数位置

===================================
函数要求: a in integer, b out integer,c out integer,d inout integer default 400,e in integer default 500
调用传入: call tp13 (1,2,3,e=>5);
argnumbers:[0,1,2,x,x]
arggiven:[true,true,true,x,x]
===================================

	/* there are numposargs positional args before the named args */
	for (ap = 0; ap < numposargs; ap++)
	{
		(*argnumbers)[ap] = ap;
		arggiven[ap] = true;
	}

检查指向参数,指向参数的位置由pp偏移

===================================
函数要求: a in integer, b out integer,c out integer,d inout integer default 400,e in integer default 500
调用传入: call tp13 (1,2,3,e=>5);
argnumbers:[0,1,2,4,x]
arggiven:[true,true,true,false,true]
===================================

	/* now examine the named args */
	foreach(lc, argnames)
	{
		char	   *argname = (char *) lfirst(lc);
		bool		found;
		int			i;

		pp = 0;
		found = false;
		for (i = 0; i < pronallargs; i++)
		{
			/* consider only input params, except with include_out_arguments */
			if (!include_out_arguments &&
				p_argmodes &&
				(p_argmodes[i] != FUNC_PARAM_IN &&
				 p_argmodes[i] != FUNC_PARAM_INOUT &&
				 p_argmodes[i] != FUNC_PARAM_VARIADIC))
				continue;
			if (p_argnames[i] && strcmp(p_argnames[i], argname) == 0)
			{

指向参数的位置如果有 非指向参数了,直接弃用返回

				if (arggiven[pp])
					return false;
				arggiven[pp] = true;
				(*argnumbers)[ap] = pp;
				found = true;
				break;
			}
			/* increase pp only for considered parameters */
			pp++;
		}
		/* if name isn't in proargnames, fail */
		if (!found)
			return false;
		ap++;
	}

开始检查默认参数够不够?

第一个需要默认参数的是first_arg_with_default=5-2=3

从numposargs开始遍历(非指向参数个数 = 3 = 给了4个参数 - 有1个参数是指向型

从numposargs开始是合理的,因为pos参数已经给了,不应该再去判断有没有default。

	/* Check for default arguments */
	if (nargs < pronargs)
	{
		int			first_arg_with_default = pronargs - procform->pronargdefaults;

		for (pp = numposargs; pp < pronargs; pp++)
		{
			if (arggiven[pp])
				continue;
			/* fail if arg not given and no default available */

如果pp的位置比第一个默认参数还要小,肯定是缺默认参数了,直接放弃返回。
例如:函数(i,i,d,d,d)调用时(i,i)是可以的,调用时(i)就会报错。

			if (pp < first_arg_with_default)
				return false;
			(*argnumbers)[ap++] = pp;
		}
	}

	Assert(ap == pronargs);		/* processed all function parameters */

	return true;
}

5 调用expand_function_arguments生成FuncExpr->args

expand_function_arguments的逻辑就很简单了,只是把参数解析后拼接到FuncExpr->args中

(其实这件事情上面的函数已经做过了,但是只是用于参数类型匹配检测,并没有真正拼接到FuncExpr->args)文章来源地址https://www.toymoban.com/news/detail-466464.html

expand_function_arguments
	...
  	/* If so, we must apply reorder_function_arguments */
	if (has_named_args)
	{
		args = reorder_function_arguments(args, pronargs, func_tuple);
		/* Recheck argument types and add casts if needed */
		recheck_cast_function_args(args, result_type,
								   proargtypes, pronargs,
								   func_tuple);
	}
	else if (list_length(args) < pronargs)
	{
		/* No named args, but we seem to be short some defaults */
		args = add_function_defaults(args, pronargs, func_tuple);
		/* Recheck argument types and add casts if needed */
		recheck_cast_function_args(args, result_type,
								   proargtypes, pronargs,
								   func_tuple);
	}

到了这里,关于Postgresql源码(84)语义分析——函数调用结构CallStmt的构造与函数多态的实现(pl参数)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • PG-DBA培训07:PostgreSQL体系结构深入与源码解析

    PostgreSQL体系结构深入解析,PostgreSQL数据库源码解析,initdb源码解析 PostgreSQL数据库体系架构 PostgreSQL数据库存储结构 PostgreSQL数据库进程结构 PostgreSQL数据库内存结构 PostgreSQL数据库源码解析 使用gdb跟踪分析PostgreSQL源码 PostgreSQL源码解析之initdb初始化过程 PostgreSQL源码解析之PG启动

    2024年02月15日
    浏览(89)
  • postgresql 内核源码分析 btree索引的增删查代码基本原理流程分析,索引膨胀的原因在这里

    ​ 专栏内容 : postgresql内核源码分析 手写数据库toadb 并发编程 ​ 开源贡献 : toadb开源库 个人主页 :我的主页 管理社区 :开源数据库 座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物. 在postgresql最常用的索引就是btree,它支持范围和等值查询。 本文主要介绍

    2024年02月11日
    浏览(48)
  • 5. 函数调用过程汇编分析

    __cdecl 调用方式 __stdcall 调用方式 __fastcall 调用方式 不同的编译器实现不一样,上述情况只是VC++6.0的编译实现 即便是在同一个编译器,开启优化和关闭优化也不一样 即便是同一个编译器同一种模式,32位和64位下情况也会不一样 参考 GCC GNU 文档属性描述 fastcall On x86-32 target

    2024年01月22日
    浏览(26)
  • postgresql 内核源码分析 btree索引插入分析,索引页面分裂流程,多举措进行并发优化,对异常进行保护处理

    ​ 专栏内容 : postgresql内核源码分析 手写数据库toadb 并发编程 ​ 开源贡献 : toadb开源库 个人主页 :我的主页 管理社区 :开源数据库 座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物. B树索引在PostgreSQL中得到了广泛应用,它是一种自平衡树数据结构,可以维

    2024年02月08日
    浏览(63)
  • ARM64函数调用流程分析

    ARM64 程序调用标准 下图是介绍一个简单函数调用的示例,在该示例中简单介绍了栈的使用。 2.1.1 main的C代码实现 2.1.2 main函数对应汇编及其分析 0000000000000114 main: main函数的入口 114: a9be7bfd stp x29, x30, [sp, #-32]! 将sp = sp - 32,为main函数开一个32Byte的栈空间,然后将x29(FP),X30(LR)寄

    2024年02月11日
    浏览(40)
  • 【Linux 内核源码分析笔记】系统调用

    在Linux内核中,系统调用是用户空间程序与内核之间的接口,它允许用户空间程序请求内核执行特权操作或访问受保护的内核资源。系统调用提供了一种安全可控的方式,使用户程序能够利用内核功能而不直接访问底层硬件。 系统调用: 通过系统调用,用户程序可以请求内核

    2024年02月03日
    浏览(40)
  • X86_64函数调用汇编程序分析

    %rdi, %rsi, %rdx, %rcx, %r8, %r9分别用于函数调用过程中的前6个参数,对于6的参数存放在栈中传递 %rsp用做栈指针寄存器,指向栈顶 %rbp用作栈框寄存器,指向栈底 %rax用做函数返回值的第一个寄存器 2.1.1 main的C代码实现 2.1.2 main函数对应汇编及其分析 这段汇编代码实现了一个简单的

    2024年02月09日
    浏览(41)
  • Day 84:网络结构与参数

    单层数据 多层管理

    2024年02月11日
    浏览(32)
  • Postgresql源码(110)分析dsm动态共享内存分配与共享内存mq实例(dsm/toc接口备忘录)

    相关 《Postgresql源码(90)共享内存申请CreateSharedMemoryAndSemaphores》 《Linux内存映射函数mmap与匿名内存块》 《Linux共享内存与子进程继承》 用dsm框架的流程 评估共享内存大小:多次用shm_toc_estimate_chunk、shm_toc_estimate_keys向estimate中增加数据结构,最后用shm_toc_estimate得出刚才增加

    2024年02月14日
    浏览(58)
  • 《Linux内核源码分析》(2)进程原理及系统调用

    操作系统的作用 :作为硬件的使用层,提供使用硬件资源的能力, 进程的作用 :作为操作系统使用层,提供使用操作系统抽象出的资源层的能力 进程、线程和程序的区别 :进程指计算机中已运行的程序。进程本身不是基本的运行单位,而是线程的容器。 程序本身只是指令

    2024年02月07日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包