PostgreSQL函数管理接口

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
简介: 学习PostgreSQL服务端开发必须要对函数管理接口有比较深入的了解

V1版函数管理接口(The V1 Function-Manager Interface)

fmgr v1设计的核心是用于表示函数查找结果和表示传递给特定函数调用的参数的数据结构。(我们希望将函数查找与函数调用分开,因为系统的许多部分反复应用相同的函数;查找开销应该为每个查询一次,而不是每个元组一次。)

pg_proc中查找函数时,结果表示为:

typedefstruct{
PGFunctionfn_addr;    /* pointer to function or handler to be called */Oidfn_oid;     /* OID of function (NOT of handler, if any) */shortfn_nargs;   /* number of input args (0..FUNC_MAX_ARGS) */boolfn_strict;  /* function is "strict" (NULL in => NULL out) */boolfn_retset;  /* function returns a set (over multiple calls) */unsignedcharfn_stats; /* collect stats if track_functions > this */void*fn_extra;   /* extra space for use by handler */MemoryContextfn_mcxt;  /* memory context to store fn_extra in */Node*fn_expr;    /* expression parse tree for call, or NULL */} FmgrInfo;

对于一个普通的内置函数,fn_addr只是实现该函数的C例程的地址。否则,它是包含目标函数的函数类的处理程序的地址。处理程序可以使用函数OID,也许还可以使用fn_extra槽来查找要执行的特定代码。(fn_oid=InvalidOid可用于表示尚未初始化的FmgrInfo结构。当函数查找代码首次填充FmgrInformation时,fn_extra将始终为NULL,但函数处理程序可以将其设置以避免在查询过程中重复使用相同的Fmgr信息时进行重复查找。)fn_nargs是函数所需的参数数,fn_strict是它的严格性标志,fn_retset显示它是否返回一个集合;所有这些值都来自函数的pgproc条目。还设置了fn_stats来控制是否跟踪调用此函数的运行时统计信息。

如果函数作为SQL表达式的一部分被调用,fn_expr将指向函数调用的表达式解析树;这可以用于提取关于实际参数的解析时间知识。请注意,该字段实际上是关于参数的信息,而不是关于函数的信息,但事实证明,将其保存在FmgrInfo中比保存在FunctionCallInfoBaseData中更方便,因为后者可能更符合逻辑。 

在调用函数期间,将创建以下数据结构并将其传递给函数:

typedefstruct{
FmgrInfo*flinfo;         /* ptr to lookup info used for this call */Node*context;        /* pass info about context of call */Node*resultinfo;     /* pass or return extra info about result */Oidfncollation;    /* collation for function to use */boolisnull;         /* function must set true if result is NULL */shortnargs;          /* # arguments actually passed */NullableDatumargs[];       /* Arguments passed to function */} FunctionCallInfoBaseData;
typedefFunctionCallInfoBaseData*FunctionCallInfo;

flinfo指向用于进行调用的查找信息。普通函数可能会忽略此字段,但函数类处理程序将需要它来找出被调用的特定函数的OID 

对于“普通”函数调用,上下文为NULL,但在某些上下文中调用函数时,上下文可能会指向其他信息。(例如,触发器管理器将在此处传递有关当前触发器事件的信息。)更多详细信息显示在下面的“函数调用上下文”中。 

当调用任何需要简单Datum结果的函数时,resultinfoNULL。如果函数返回多个Datum,则它可能指向节点的某些子类型。(例如,resultinfo在调用返回集合的函数时使用,如下所述。)与上下文字段一样,resultinfo是用于扩展的挂钩;fmgr本身并不限制字段的使用。 

fncollation是由解析器派生的输入排序规则,或者在没有可收集类型的输入或它们不共享公共排序规则时为InvalidOid。这实际上是一个隐藏的附加参数,排序规则敏感的函数可以使用它来确定它们的行为。 

nargsargs[]保存传递给函数的参数。请注意,传递给函数的所有参数(及其结果值)现在都统一为Datum类型。如下面所讨论的,调用者和被调用者应该将标准Datum应用于任何宏,并从中转换为特定函数的实际参数类型。当args[i].isnulltrue时,args[i].value中的值未指定。

调用者通常负责确保传递的参数数量与被调用者期望的数量匹配;除了接受可变数量的参数的被调用方之外,被调用方通常会忽略nargs字段,而只是从args[]中获取值。 

在调用之前,isnull字段将被初始化为“false”。从函数返回时,isnull是函数结果的null标志:如果为true,则函数的结果为null,而不管实际的函数返回值如何。请注意,简单的“strict”函数可以忽略isnullargs[i].isnull,因为当args[].isnull中有任何TRUE值时,它们甚至不会被调用。

被调用方,无论是单个函数还是函数处理程序,都应始终具有此签名:

Datumfunction (FunctionCallInfofcinfo);
//通过typedef定义成函数指针typedefDatum (*PGFunction) (FunctionCallInfofcinfo);

该函数负责适当地设置fcinfo->isnull,并返回表示为Datum的结果。请注意,由于所有被调用方现在都将具有完全相同的签名,并通过使用该签名声明的函数指针来调用,因此我们应该没有可移植或优化问题。

函数编码约定

下面是建议的宏和编码约定:

fmgr可调用函数的定义总是类似于

Datumfunction_name(PG_FUNCTION_ARGS)
{
    ...
}

PG_FUNCTION_ARGS”仅扩展为“FunctionCallInfo fcinfo”。使用该宏的主要原因是使脚本能够轻松地定位函数定义。然而,如果我们决定再次更改调用约定,那么将这个宏放在适当的位置可能会很方便。 

非严格函数(nonstrict)负责检查每个单独的参数是否为null,这可以使用PG_ARGISNULLn)来完成(它只是“fcinfo->args[n].isnull”)。它应该避免尝试获取任何为null的参数的值。

strict和nonstrict函数可以返回NULL,如果需要,使用PG_RETURN_NULL()宏

它会被展开成

        { fcinfo->isnull = true; return (Datum) 0; }

函数参数可使用类似如下的代码取得:

int32name=PG_GETARG_INT32(number);

      对于float4float8int8PG_GETARG宏将隐藏类型是按值传递还是按引用传递。例如,如果float8通过引用传递,则PG_GETARG_float8扩展为:

(* (float8*) DatumGetPointer(fcinfo->args[number].value))

典型的调用方式如下:

float8arg=PG_GETARG_FLOAT8(0);

由于历史原因,与浮点相关的typedef和宏以字节(48)表示类型宽度,而我们更喜欢以位标记整数类型的宽度。

非NULL的值通过PG_RETURN_XXX宏返回适当的类型。比如, PG_RETURN_INT32被展开成:

returnInt32GetDatum(x)

       PG_RETURN_FLOAT4PG_RETRN_FLOAT8PG_RETURN_INT64通过在需要时执行palloc来隐藏其数据类型是按值传递还是按引用传递。 

fmgr.h将为所有基本数据类型提供PG_GETARGPG_RETURN宏。定义专用SQL数据类型(例如,时间戳)的模块或头文件应该为这些类型定义适当的宏,以便可以用标准样式编码操作类型的函数。 

对于非原语数据类型(特别是可变长度类型),隐藏数据类型的按引用传递性质是不太实际的,因此这些类型的PG_GETARGPG_RETURN宏只会做DatumGetPointer/PointerGetDatum加上适当的类型转换(但请参阅下面的TOAST讨论)。返回此类类型的函数将需要显式地palloc()其结果空间。我建议将这种类型的GETARGRETURN宏命名为以“_P”结尾,以提醒它们生成或获取指针。例如,PG_GETARG_TEXT_P生成“TEXT*”。 

TOAST-Able数据类型支持

对于TOAST-able的数据类型,PG_GETARG宏将传递de-TOAST数据值。在某些情况下,可能需要still-toasted的值,但绝大多数情况下需要de-toasted的结果,因此这将是默认值。要获取参数值而不导致de-toasting,使用PG_GETARG_RAW_VARLENA_P(n)。

某些函数需要其输入值的可修改副本。在这些情况下,如果我们要复制数据以对其进行传播,执行额外的复制步骤是不可取的。因此,每个toast-able的数据类型有一个额外的提取宏,例如PG_GETARG_TEXT_P_copy(n),它提供有保证的新副本,如果可能,将其与detoasting步骤相结合。

还有一个PG_FREE_IF_COPY(ptr,n)宏,当且仅当它与第n个参数的原始值不同时,pfree给定的指针。这可以用于释放第n个参数的de-toasted值,如果它实际上是de-toased。目前,对于大多数函数来说,这样做是不必要的,因为核心后端代码会定期释放临时空间,因此在函数执行中泄漏的内存不是一个大问题。然而,从7.1开始,索引搜索调用的函数中的内存泄漏直到事务结束才会被清除。因此,在pg_amop或pg_amproc中列出的函数应注意不要泄漏detoasted的副本,因此这些函数确实需要将PG_FREE_IF_COPY()用于toastable的输入。

函数永远不要尝试去re-TOAST其结果值;它应该只提供一个在当前内存上下文中已经被palloc的untoasted的结果。当且如果该值实际存储到tuple中时,tuple toaster将决定是否需要toasting。

函数调用上下文

如果调用者在fcinfo->context中传递一个非NULL指针,它应该指向Node的某个子类型;特定类型的上下文由节点类型字段指示。(被调用方应始终通过IsA()检查节点类型,然后假设它知道正在传递的是哪种类型的上下文。)fmgr本身对该字段的使用没有其他限制。

本约定目前的用途包括:

*触发器函数被传递给structTriggerData的实例,其中包含有关触发器上下文的信息。(触发器函数不接收任何正常参数。)有关触发器函数常用的详细信息和宏,请参阅命令/trigger.h。

*聚合函数(或更准确地说,它们的转换和最终函数)被传递给结构AggState的实例,即调用Agg计划节点的执行器状态节点;或者,如果它们被调用为窗口函数,则它们将接收struct WindowAggState的实例。建议仅通过AggCheckCallContext()和同级函数使用这些指针,它们在fmgr.h中声明,但仅在src/backend/executor/nodeAg.c.中与它们的源代码一起记录。通常,当转换和最终函数希望基于知道它们正在聚合中使用而不是作为独立的SQL函数来优化执行时,才对这些上下文节点感兴趣。

*真窗口函数接收结构WindowObject的实例。(与触发器函数一样,它们不接收任何普通参数。)有关详细信息,请参阅windowapi.h。

*过程被传递给struct CallContext的实例,其中包含有关CALL语句上下文的信息,特别是它是否在“原子”执行上下文中。

*数据类型输入函数的一些调用方(以及将来可能的其他函数类)传递ErrorSaveContext的实例。这表示调用者希望在不引发事务终止异常的情况下处理“软”错误:相反,被调用者应在ErrorSaveContext结构中存储有关错误原因的信息,并返回虚拟结果值。更多详细信息请参阅下面的“处理软错误”。

处理软错误

Postgres报告错误的标准机制(ereport()或elog())用于各种错误条件。这意味着通过ereport(ERROR)引发异常需要昂贵的事务或子事务中止和清理,因为异常捕捉器不敢对发生的错误做出许多假设。在某些情况下,我们宁愿使用更轻的机制来处理已知可以安全恢复的错误,而不需要进行完整的事务清理。SQL可调用函数可以使用ErrorSaveContext上下文机制支持这一需求。

如果传递的“context”为NULL或不是ErrorSaveContext节点,则errsave的行为与ereportERROR)完全相同:异常通过longjmp引发,因此控件不会返回。如果“context”是ErrorSaveContext节点,则errsave的子报告调用中包含的错误信息存储到上下文节点中,并且控件通常从errsave返回。然后,该函数应将虚拟值返回给其调用方。(建议将SQL NULL作为伪值;但任何操作都可以,因为一旦调用方看到ErrorSaveContext节点中报告了错误,它就会忽略函数的返回值。)

如果在调用errsave()后除了return之外没有其他事情可做,则可以通过编写ereturn(fcinfo->context,dummy_value,…)来执行errsave(),然后“return dummy_ value”来保存一到两行。

“软”报告的错误必须是安全的,从这个意义上讲,我们继续正常处理事务的能力是毫无疑问的。不应以这种方式处理的错误情况包括内存不足、意外的内部错误或以后无法轻松清除的任何错误。像过去一样,这种情况仍然会使用ereport抛出。

以数据类型输入函数为例,典型的“软”错误条件包括输入语法错误和超出范围的值。输入函数通常使用简单的if测试来检测这种情况,并且可以轻松地将随后的ereport调用更改为errsave或ereturn。由于这种限制,通常不需要将ErrorSaveContext指针向下传递很远,因为低级函数报告的错误通常是合理的,可以考虑内部错误。(另一种确定区别的方法是,输入函数应该柔和地报告所有无效的输入条件,但内部问题是硬错误。)

由于不会发生事务清理,因此在errsave()返回后退出的函数将负责资源清理。没有必要担心palloc内存的小泄漏,因为调用方应该在短期内存上下文中运行函数。然而,诸如锁、打开的文件或缓冲区管脚之类的资源必须干净地关闭,因为它们将位于非错误代码路径中。

使用ErrorSaveContext机制捕获错误的调用者的约定在nodes/miscnodes.h中与该结构的声明一起讨论。

函数接受或返回集合类型

如果函数在pg_proc中标记为返回结果集,则使用指向ReturnSetInfo类型的节点的fcinfo->resultinfo调用该函数。如果resultinfoNULL或不指向ReturnSetInfo节点,则希望返回集合的函数应引发错误“在不接受集合结果的上下文中调用”。 

当前有两种模式,函数可以返回设置的结果:每次调用的值或物化。在每次调用值模式下,函数每次调用时返回一个值,并在没有更多要返回的值时最终报告“完成”。在物化模式下,函数的输出集在Tuplestore对象中实例化;在一次调用中返回所有值。将来可能会添加其他模式。 

ReturnSetInfo包含一个字段“allowedModes”,该字段(由调用者)设置为位掩码,该位掩码是调用者可以支持的模式的OR。函数使用的实际模式在另一个字段“returnMode”中返回。出于向后兼容性的原因,returnMode被初始化为每次调用的值,并且仅当函数希望使用不同的模式时才需要更改。如果函数不能使用调用方愿意支持的任何模式,则应该使用ereport()。 

每次调用的值模式的工作方式如下:ReturnSetInfo包含字段“isDone”,应将其设置为以下值之一: 

ExprSingleResult/* expression does not return a set */ExprMultipleResult/* this result is an element of a set */ExprEndResult/* there are no more elements in the set */

(调用方将其初始化为ExprSingleResult)。如果函数仅返回Datum而不触摸ReturnSetInfo,则调用结束,并返回单个项目集。要返回集合,函数必须将每个集合元素的isDone设置为ExprMultipleResult。返回所有元素后,下一个调用应将isDone设置为ExprEndResult,并返回空结果。(请注意,通过在第一次调用时执行此操作,可以返回空集。) 

每次调用的值函数不得假设它们将运行到完成;执行者可以简单地停止调用它们,例如,因为LIMIT。因此,在最终调用中尝试执行任何资源清理是不安全的。无论如何,通常不需要清理内存。如果需要清理其他类型的资源(如文件描述符),可以在ReturnSetInfo节点指向的ExprContext中注册关闭回调函数。(但请注意,文件描述符是一种有限的资源,因此在调用之间保留这些打开的资源通常是不明智的;最好编写需要文件访问的SRF,以使用Materialize模式在单个调用中完成这项工作。) 

物化模式的工作原理如下:函数创建一个保存结果集(可能为空)的Tuplestore,并返回它。没有多个调用。该函数还必须返回指示元组结构的TupleDescTuplestoreTupleDesc应该在上下文econtext->ecxt_per_query_memory中创建(注意,这将*不是*调用函数的上下文)。该函数将指向TuplestoreTupleDesc的指针存储到ReturnSetInfo中,将returnMode设置为指示物化模式,并返回nullisDone未使用,应保留在ExprSingleResult 

如果SFRM_Materialize_Random设置为允许的模式,则必须使用randomAccess=true创建Tuplestore,但如果不是,则可以(并且最好应该)使用randomAccess=false创建它。可以支持ValuePerCallMaterialize模式的调用者将设置SFRM_Materialize_Prefered,或不设置,具体取决于他们喜欢的模式。 

如果可用,则在ReturnSetInfo中传递预期的元组描述符;在其他上下文中,expectedDesc字段将为NULL。函数不需要注意expectedDesc,但它在特殊情况下可能有用。 

InitMaterializedSRF()是一个帮助函数,能够为单个调用设置函数的ReturnSetInfo,用Materialize模式的正确配置填充TuplestoreTupleDesc 

函数不支持接受集合类型的参数,替代的方法是每次用集合的一个成员为参数多次调用该函数。

有关函数处理程序的注意事项

在这种设计中,函数类的处理程序应该使用起来更容易、更干净。被调用函数的OID可以从传递的参数直接访问;我们不再需要全局变量fmgr_pl_finfo。此外,通过修改fcinfo->flinfo->fn_extra,处理程序可以缓存查找信息,以避免在多次调用同一函数时重复查找。(fn_extra只能用作提示,因为调用方不需要重新使用FmgrInfo结构。但在性能关键路径中,它们通常会这样做。)

如果处理程序希望分配内存来保存fn_extra数据,则不应在CurrentMemoryContext中这样做,因为当前上下文的生命周期很可能比FmgrInfo所在的上下文短得多。相反,请在上下文flinfo->fn_mcxt或长生存期缓存上下文中分配内存。fn_mcxt通常指向创建FmgrInfo结构时为CurrentMemoryContext的上下文;在任何情况下,它都需要是一个上下文,至少与FmgrInfo本身的生命周期一样长。



相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
3月前
|
存储 关系型数据库 Java
polardb有没有搞过pg 全量及增量备份管理的
【1月更文挑战第3天】【1月更文挑战第11篇】 polardb有没有搞过pg 全量及增量备份管理的
34 1
|
4月前
|
存储 Oracle 关系型数据库
postgresql数据库|wal日志的开启以及如何管理
postgresql数据库|wal日志的开启以及如何管理
290 0
|
5月前
|
消息中间件 存储 关系型数据库
PostgreSQL技术大讲堂 - 第33讲:并行查询管理
PostgreSQL从小白到专家,技术大讲堂 - 第33讲:并行查询管理
289 1
|
1月前
|
关系型数据库 PostgreSQL
postgresql日程排程函数的编写实例
postgresql日程排程函数的编写实例
|
2月前
|
SQL 关系型数据库 分布式数据库
在PolarDB for PostgreSQL中,你可以使用LIKE运算符来实现类似的查询功能,而不是使用IF函数
在PolarDB for PostgreSQL中,你可以使用LIKE运算符来实现类似的查询功能,而不是使用IF函数
43 7
|
4月前
|
SQL 关系型数据库 C语言
PostgreSQL【应用 03】Docker部署的PostgreSQL扩展SQL之C语言函数(编写、编译、载入)计算向量余弦距离实例分享
PostgreSQL【应用 03】Docker部署的PostgreSQL扩展SQL之C语言函数(编写、编译、载入)计算向量余弦距离实例分享
45 0
|
4月前
|
SQL 关系型数据库 数据库
PostgreSQL【应用 02】扩展SQL之C语言函数(编写、编译、载入)实例分享
PostgreSQL【应用 02】扩展SQL之C语言函数(编写、编译、载入)实例分享
49 0
|
4月前
|
SQL 关系型数据库 PostgreSQL
PostgreSQL【SQL 01】根据条件更新字段值或追加信息STRPOS(string, substring)函数使用及LIKE函数对比
PostgreSQL【SQL 01】根据条件更新字段值或追加信息STRPOS(string, substring)函数使用及LIKE函数对比
56 0
|
4月前
|
缓存 关系型数据库 MySQL
postgresql|数据库|序列Sequence的创建和管理
postgresql|数据库|序列Sequence的创建和管理
50 0
|
4月前
|
SQL 关系型数据库 编译器
PostgreSQL SQL扩展 ---- C语言函数(二)
可以用C(或者与C兼容,比如C++)语言编写用户自定义函数(User-defined functions)。这些函数被编译到动态可加载目标文件(也称为共享库)中并被守护进程加载到服务中。“C语言函数”与“内部函数”的区别就在于动态加载这个特性,二者的实际编码约定本质上是相同的(因此,标准的内部函数库为用户自定义C语言函数提供了丰富的示例代码)
67 0