转载来自:http://www.codeweblog.com/postgresql%E7%9A%84%E7%B3%BB%E7%BB%9F%E5%87%BD%E6%95
PostgreSQL数据库中有许多内部函数,这次对系统表pg_proc以及函数代码进行分析记录(这里是针对9.3进行介绍的)。
一、数据库系统表pg_proc
数据库中所有内部函数信息都存储在系统表pg_proc. 内部函数都是在编译之前写好并存储在pg_proc.h文件中。 下面来看一下pg_proc的表结构,首先是看源码中的结构体:
CATALOG(pg_proc,1255) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81) BKI_SCHEMA_MACRO
37 {
38 NameData proname;
39 Oid pronamespace;
40 Oid proowner;
41 Oid prolang;
42 float4 procost;
43 float4 prorows;
44 Oid provariadic;
45 regproc protransform;
46 bool proisagg;
47 bool proiswindow;
48 bool prosecdef;
49 bool proleakproof;
50 bool proisstrict;
51 bool proretset;
52 char provolatile;
53 int16 pronargs;
54 int16 pronargdefaults;
55 Oid prorettype;
56
57
61 oidvector proargtypes;
62
63 #ifdef CATALOG_VARLEN
64 Oid proallargtypes[1];
65 char proargmodes[1];
66 text proargnames[/* parameter names (NULL if no names) */
67 pg_node_tree proargdefaults;
69 text prosrc;
70 text probin;
71 text proconfig[1];
72 aclitem proacl[/* access permissions */
73 #endif
74 } FormData_pg_proc;
下面来简单介绍pg_type的各个字段含义: proname、pronamespace、proowner分别是函数名(sql调用的名字)、存在的模式(oid)、所属用户(oid),这里就不多说了。 prolang:实现语言或该函数的调用接口,目前在系统中定义的为(internal,12),(c、13),(sql,14),数据库中主要用的是internal和sql。 procost:估计执行成本,这里和执行计划相关联。 prorows:结果行估计数。 provariadic:可变数组参数类型,这是9.1之后加入的,这是能够然函数定义不再受限于参数个数。这个类型可以参照一下函数concat和concat_ws这两个函数。这个地方在这里对concat说明,在函数concat这个参数是这样写的2276,这 个函数是拼接字符串,而2276正是any,在这里填写后,表示这个函数可以接收多个any类型的参数,而不用像以前那样每多一个参数就得写一个定义。 protransform:可以替代被调用的简化函数。可以参看varbit函数。这里写的是varbit_transform,而通过查看代码,可以知道varbit_transform只有一个参数,也就是说当只有一个参数的时候调用varbit实际上执行的是varbit_transform。 proisagg:这是不是一个聚集函数。 proiswindow:是否为窗口函数。窗口函数(RANK,SUM等) 可以对一组相关的记录进行操作。 prosecdef:函数是一个安全定义器(也就是一个"setuid"函数)。 proleakproof:有无其他影响。 proisstrict:遇到NULL值是否直接返回NULL,这里要说明的是,数据库中有一个数组专门来存储这个值,当为true时,数据库对参数为NULL的qi。 proretset:函数返回一个集合(也就是说,指定数据类型的多个数值)。 provolatile:告诉该函数的结果是否只倚赖于它的输入参数,或者还会被外接因素影响。对于"不可变的"(immutable)函数它是 i ,这样的函数对于相同的输入总是产生相同的结果。对于"稳定的"(stable)函数它是 s ,(对于固定输入)其结果在一次扫描里不变。对于"易变"(volatile)函数它是 v ,其结果可能在任何时候变化。v 也用于那些有副作用的函数,因此调用它们无法得到优化。 pronargs:参数个数。 pronargdefaults:默认参数的个数。 prorettype:返回参数类型的oid。 proargtypes:一个存放函数参数的数据类型的数组。 proargmodes:一个保存函数参数模式的数组,编码如下:i 表示 IN 参数, o 表示 OUT 参数, b 表示 INOUT 参数。如果所有参数都是 IN 参数,那么这个字段为空。请注意,下标对应的是 proallargtypes 的位置,而不是 proargtypes。 proargnames:一个保存函数参数的名字的数组。没有名字的参数在数组里设置为空字符串。如果没有一个参数有名字,这个字段将是空。请注意,此数组的下标对应 proallargtypes 而不是 proargtypes。 proargdefaults:表达式树(以nodeToString()形式表示)的默认值。这是pronargdefaults元素的列表,对应的最后N个输入参数(即最后N proargtypes位置)。如果没有的参数有默认值,这个领域将是空的。 prosrc:这个字段告诉函数处理器如何调用该函数。它实际上对于解释语言来说就是函数的源程序,或者一个链接符号,一个文件名,或者是任何其它的东西,具体取决于语言/调用习惯的实现。 probin:关于如何调用该函数的附加信息。同样,其含义也是和语言相关的。 proconfig:在运行时配置变量函数的局部设置。 proacl:访问权限。
以上就是对系统表pg_proc的介绍,下面对如何阅读和编写内部函数作一下介绍。
二、函数基础
1、函数的使用:
在数据库中函数的使用是非常简单的。 用法为: select FunctionName(args); select FunctionName(columnname) from tablename; …… (具体可以去查找文档,这里不做一一介绍了)
2、使用的函数名
这里的函数名(Functionname)就是系统表pg_proc中的proname了。
3、函数的定义
一般能看到的定义有两种。
第一种:
CREATE OR REPLACE FUNCTION date_part(text,time with time zone)
RETURNS double precision AS
'timetz_part'
LANGUAGE internal IMMUTABLE STRICT
COST 1;
data_part就是我们调用的函数的名称。 (text,time with time zone)即我们输入参数的类型。 double precision是我们返回的数据类型。 'timetz_part'是我们源码中命名的函数名,调用date_part其实是调用函数timetz_part。 internal是我们规定的函数语言。 1是我们估计的时间成本。
第二种:
'select pg_catalog.date_part($1,cast($2 as timestamp with time zone))'
LANGUAGE sql STABLE STRICT
COST 1;
ALTER TO highgo;
COMMENT ON FUNCTION date_part(text,abstime) IS 'extract field from abstime';
这里基本和第一种相同。不同之处在于: 这里没有写源码中命名的函数,而是用一条SQL语句替代了,在这里执行的时候又在执行的上边的date_part,然后再去调用的 timetz_part。
这里的函数语言是SQL。
第三种:
FUNCTION concat(VARIADIC "any")
RETURNS text 'text_concat'
LANGUAGE internal STABLE
COST concat("any")
OWNER TO postgres;
COMMENT ON FUNCTION concat("any") IS 'concatenate values';
这里不同的就是在参数上添加了VARIADIC,这是说明这个类型是一个可变数组。其他的都类似,就不说明了。
第四种:
FUNCTION varbit(bit varying,integer,95)">boolean)
varying 'varbit'
这里是看起来和第一种是一样的,这里拿过来主要是说明一下,pg_proc中的 protransform字段,应该不能通过SQL定义的方式填写。这个函数在proc中protransform的定义有varbit_transform。这段定义是admin反向出来的。
4、定义自己的函数(主要指的用SQL定义)
这个可以去看文档。
5、函数的源码
如果要进行学习函数的源码学习,那么必须首先要阅读src/include/fmgr.h,这里对函数的制定了一揽子的宏定义。 首先呢,要说明的是,能够直接用SQL语句调用的函数(prosrc),他的参数必须是PG_FUNCTION_ARGS。 下面是对PG_FUNCTION_ARGS的定义:
define PG_FUNCTION_ARGS FunctionCallInfo fcinfo
typedef struct FunctionCallInfoData *FunctionCallInfo;
typedef Datum (*PGFunction) (FunctionCallInfo fcinfo);
struct Node *fmNodePtr; uintptr_t Datum;
struct Node
{
NodeTag type;
} Node;
struct FmgrInfo
{
PGFunction fn_addr;
Oid fn_oid;
short fn_nargs;
bool fn_strict;
bool fn_retset;
unsigned char fn_stats;
void *fn_extra;
MemoryContext fn_mcxt;
fmNodePtr fn_expr;
} FmgrInfo;
struct FunctionCallInfoData
{
FmgrInfo *flinfo;
fmNodePtr context;
fmNodePtr resultinfo;
Oid fncollation;
bool isnull;
short nargs;
Datum arg[FUNC_MAX_ARGS];
bool argnull[FUNC_MAX_ARGS];
} FunctionCallInfoData;
上面是很复杂的一个结构体,这就是调用函数生成的结构体。
三、函数在数据库中的历程
现在我以一个函数使用的SQL语句去解读一下函数。
1、执行函数
首先,在命令行下输入一条SQL语句,在此主要介绍函数,主要对函数运行进行介绍(其他的内存上下文、执行计划之类的,这里就不做介绍了,在下才疏学浅,有待进一步的学习后会做相应介绍),所以直接输入参数作为介绍,为了更好地说明,这里用concat作为函数例子进行介绍。进入客户端,调用函数。
postgres=# select 'su','re');
2、进入到服务端
数据库客户端会根据前后端协议将用户查询将信息发送到服务端,进入函数PostgresMain,然后进入exec_simple_query,exec_simple_query函数主要分为两部分,第一部分是查询分析,第二部分是查询执行,下面以下图进行说明查询分析:
(1)首先exec_simple_query函数会将得到的SQL语句通过调用pg_parse_query进入词法和语法分析的主题处理过程,然后函数pg_parse_query调用词法和语法分析的入口函数raw_parse生成分析树。 raw_parse函数通过分词与语法引擎进行对SQL语句的识别,其中执行函数时会调用makeFuncCall,初始化FuncCall。这是执行函数所必须调用的。 raw_parse函数通过分词与语法引擎进行对SQL语句的识别,其中执行函数时会调用makeFuncCall,初始化FuncCall。这是执行函数所必须调用的。
struct FuncCall
{
NodeTag type;
List *funcname;
List *args;
List *agg_order;
Node *agg_filter;
bool agg_star;
bool agg_distinct;
bool func_variadic;
struct WindowDef *over; location;
} FuncCall;
(2)函数pg_parse_query返回分析树给外部函数。 (3)exec_simple_query接着调用函数pg_analyze_and_rewrite进行语义分析和查询重写。首先调用parse_analyze进行语义分析并生成查询树,其中parse_analyze会调用transformTopLevelStmt等(见下图) 进行一系列的转化。 之 后 会 将 查 询 树 传 递 给 函 数pg_rewrite_querye对查询进行重写,对执行计划进行优化。
上面这一系列函数都是对函数pg_parse_query返回的分析树,进行一系列的转化,通过判定和选择对应函数,最终通过对系统表pg_proc进行查找、判定最优函数,并执行函数ParseFuncOrColumn来确认并找到函数,添加到执行计划中。否则返回错误,告知用户并无此函数(这里吐槽一下pg,函数的定义的非常死板,不够灵活,常常发生有对应函数,却找不到的情况,问题在于,数据库查找用户执行的函数时,会对参数类型进行确认,然后去寻找,当然这里主要是数据类型无法隐式转化的原因,当参数类型无法转化时,数据库就会报错,无法找到函数)。这里的transformTopLevelStmt、transformStmt、transformSelectStmt、transformTargetList、transformTargetList、transformExpr、transformExprRecurse、transformFuncCall都是进行转化的,而ParseFuncOrColumn函数的功能是详细寻找函数,而make_const是对参数进行处理的。 以下图来详细说明ParseFuncOrColumn的工作原理:
(1)ParseFuncOrColumn调用函数func_get_detail来确认函数是否存在,存在则返回函数oid号,否则返回错误。 (a)func_get_detail函数调用FuncnameGetCandidates通过函数名、参数个数在系统表pg_proc中得到候选函数列表。没有则返回错误。 (b)func_get_detail函数调用func_match_argtypes对参数类型进行匹配,其中会调用can_coerce_type来判定当前参数类型能否进行隐式转换。进而缩小范围。 (c)func_get_detail函数调用func_select_candidate最终确认函数参数类型(可转换的),返回类型、函数oid。 (2)ParseFuncOrColumn会调用coerce_type对参数表达式进行转换。 (3)ParseFuncOrColumn调用函数make_fn_arguments对参数进行转化,变为函数能够使用的参数。
上述过程是创建并优化执行计划,这里仅仅是计划,真正执行的地方是查询执行。下面以图简单说明一下:
这里有一个很重要的结构体Portal:
struct PortalData *Portal;
struct PortalData
{
const char *name;
char *prepStmtName;
MemoryContext heap;
ResourceOwner resowner;
void (*cleanup) (Portal portal);
SubTransactionId createSubid;
char *sourceText;
char *commandTag;
List *stmts;
CachedPlan *cplan;
ParamListInfo portalParams;
PortalStrategy strategy;
int cursorOptions;
PortalStatus status; portalPinned;
QueryDesc *queryDesc;
TupleDesc tupDesc;
int16 *formats;
Tuplestorestate *holdStore;
MemoryContext holdContext;
bool atStart;
bool atEnd;
bool posOverflow;
long portalPos;
TimestampTz creation_time;
bool visible;
} PortalData;
这是查询执行中所必需的Portal ,存储的信息为查询计划树链表以及最后选中的执行策略等信息。上图中大部分都是在进行策略的选择。
调用CreatePortal创建空白的Portal,调用PortalStart进行初始化,调用函数PortalRun执行Portal,清理Portal。 其中PortalRun是真正执行用户需要的函数。他的大体步骤以下图为例:
这样,一个简单函数的调用结束了。最主要的两步为查询分析与查询执行。 (编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|