本节简单介绍了PostgreSQL创建函数的过程,其实现函数是CreateFunction。

一、数据结构

Form_pg_language
plpgsql语言定义结构体

/* ---------------- * pg_language definition. cpp turns this into * typedef struct FormData_pg_language * ---------------- */CATALOG(pg_language,2612,LanguageRelationId){ Oid oid; /* oid */ /* Language name */ NameData lanname; /* Language's owner */ Oid lanowner BKI_DEFAULT(PGUID); /* Is a procedural language */ bool lanispl BKI_DEFAULT(f); /* PL is trusted */ bool lanpltrusted BKI_DEFAULT(f); /* Call handler, if it's a PL */ Oid lanplcallfoid BKI_DEFAULT(0) BKI_LOOKUP(pg_proc); /* Optional anonymous-block handler function */ Oid laninline BKI_DEFAULT(0) BKI_LOOKUP(pg_proc); /* Optional validation function */ Oid lanvalidator BKI_DEFAULT(0) BKI_LOOKUP(pg_proc);#ifdef CATALOG_VARLEN /* variable-length fields start here */ /* Access privileges */ aclitem lanacl[1] BKI_DEFAULT(_null_);#endif} FormData_pg_language;/* ---------------- * Form_pg_language corresponds to a pointer to a tuple with * the format of pg_language relation. * ---------------- */typedef FormData_pg_language *Form_pg_language;

ArrayType

/* * Arrays are varlena objects, so must meet the varlena convention that * the first int32 of the object contains the total object size in bytes. * Be sure to use VARSIZE() and SET_VARSIZE() to access it, though! * Arrays是可变对象集,必须符合varlena约定,即对象的第一个int32包含对象的总大小(以字节为单位)。 * 但是,一定要确保使用VARSIZE和SET_VARSIZE函数范围该结构体 * * CAUTION: if you change the header for ordinary arrays you will also * need to change the headers for oidvector and int2vector! */typedef struct{ //可变的header int32 vl_len_; /* varlena header (do not touch directly!) */ //维度 int ndim; /* # of dimensions */ //指向数据的偏移量,如为0则表示没有位图 int32 dataoffset; /* offset to data, or 0 if no bitmap */ //元素类型的OID Oid elemtype; /* element type OID */} ArrayType;

DefElem

typedef struct DefElem{ NodeTag type; char *defnamespace; /* NULL if unqualified name */ char *defname; Node *arg; /* a (Value *) or a (TypeName *) */ DefElemAction defaction; /* unspecified action, or SET/ADD/DROP */ int location; /* token location, or -1 if unknown */} DefElem;二、源码解读

/* * Dissect the list of options assembled in gram.y into function * attributes. * 解析集成在gram.y中的选项链表为函数属性 */static voidcompute_function_attributes(ParseState *pstate,//解析状态结构体 bool is_procedure,//是否过程? List *options,//选项链表(stmt->options) List **as,//as语句 char **language,//语言 Node **transform,// bool *windowfunc_p,//是否窗口函数 char *volatility_p,//是否易变函数 bool *strict_p,//是否严格 bool *security_definer,//安全定义 bool *leakproof_p,//是否leakproof ArrayType **proconfig,//过程配置信息 float4 *procost,//过程成本 float4 *prorows,//涉及的行数 Oid *prosupport,// char *parallel_p){ ListCell *option;//临时变量 DefElem *as_item = NULL; DefElem *language_item = NULL; DefElem *transform_item = NULL; DefElem *windowfunc_item = NULL; DefElem *volatility_item = NULL; DefElem *strict_item = NULL; DefElem *security_item = NULL; DefElem *leakproof_item = NULL; List *set_items = NIL; DefElem *cost_item = NULL; DefElem *rows_item = NULL; DefElem *support_item = NULL; DefElem *parallel_item = NULL; foreach(option, options)//循环处理 { //获取定义的元素信息 DefElem *defel = (DefElem *) lfirst(option); if (strcmp(defel->defname, "as") == 0) { //as if (as_item) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"), parser_errposition(pstate, defel->location))); as_item = defel; } else if (strcmp(defel->defname, "language") == 0) { //language if (language_item) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"), parser_errposition(pstate, defel->location))); language_item = defel; } else if (strcmp(defel->defname, "transform") == 0) { //transform if (transform_item) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"), parser_errposition(pstate, defel->location))); transform_item = defel; } else if (strcmp(defel->defname, "window") == 0) { //窗口函数 if (windowfunc_item) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"), parser_errposition(pstate, defel->location))); if (is_procedure) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("invalid attribute in procedure definition"), parser_errposition(pstate, defel->location))); windowfunc_item = defel; } else if (compute_common_attribute(pstate, is_procedure, defel, &volatility_item, &strict_item, &security_item, &leakproof_item, &set_items, &cost_item, &rows_item, &support_item, &parallel_item))//普通属性 { /* recognized common option */ //识别可以同时传递给CREATE FUNCTION和ALTER FUNCTION的选项,并通过out参数返回 continue; } else elog(ERROR, "option \"%s\" not recognized", defel->defname); } /* process required items */ if (as_item) //必选项:函数体 *as = (List *) as_item->arg; else { ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("no function body specified"))); *as = NIL; /* keep compiler quiet */ } if (language_item) //必选项:语言 *language = strVal(language_item->arg); else { ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("no language specified"))); *language = NULL; /* keep compiler quiet */ } /* process optional items */ //可选项 if (transform_item) *transform = transform_item->arg; if (windowfunc_item) *windowfunc_p = intVal(windowfunc_item->arg); if (volatility_item) *volatility_p = interpret_func_volatility(volatility_item); if (strict_item) *strict_p = intVal(strict_item->arg); if (security_item) *security_definer = intVal(security_item->arg); if (leakproof_item) *leakproof_p = intVal(leakproof_item->arg); if (set_items) *proconfig = update_proconfig_value(NULL, set_items); if (cost_item) { *procost = defGetNumeric(cost_item); if (*procost <= 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("COST must be positive"))); } if (rows_item) { *prorows = defGetNumeric(rows_item); if (*prorows <= 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("ROWS must be positive"))); } if (support_item) *prosupport = interpret_func_support(support_item); if (parallel_item) *parallel_p = interpret_func_parallel(parallel_item);}/* * Recognize one of the options that can be passed to both CREATE * FUNCTION and ALTER FUNCTION and return it via one of the out * parameters. Returns true if the passed option was recognized. If * the out parameter we were going to assign to points to non-NULL, * raise a duplicate-clause error. (We don't try to detect duplicate * SET parameters though --- if you're redundant, the last one wins.) * 识别可以同时传递给CREATE FUNCTION和ALTER FUNCTION的选项,并通过out参数返回 */static boolcompute_common_attribute(ParseState *pstate, bool is_procedure, DefElem *defel, DefElem **volatility_item, DefElem **strict_item, DefElem **security_item, DefElem **leakproof_item, List **set_items, DefElem **cost_item, DefElem **rows_item, DefElem **support_item, DefElem **parallel_item){ //----------- 逐个判断赋值 if (strcmp(defel->defname, "volatility") == 0) { if (is_procedure) goto procedure_error; if (*volatility_item) goto duplicate_error; *volatility_item = defel; } else if (strcmp(defel->defname, "strict") == 0) { if (is_procedure) goto procedure_error; if (*strict_item) goto duplicate_error; *strict_item = defel; } else if (strcmp(defel->defname, "security") == 0) { if (*security_item) goto duplicate_error; *security_item = defel; } else if (strcmp(defel->defname, "leakproof") == 0) { if (is_procedure) goto procedure_error; if (*leakproof_item) goto duplicate_error; *leakproof_item = defel; } else if (strcmp(defel->defname, "set") == 0) { *set_items = lappend(*set_items, defel->arg); } else if (strcmp(defel->defname, "cost") == 0) { if (is_procedure) goto procedure_error; if (*cost_item) goto duplicate_error; *cost_item = defel; } else if (strcmp(defel->defname, "rows") == 0) { if (is_procedure) goto procedure_error; if (*rows_item) goto duplicate_error; *rows_item = defel; } else if (strcmp(defel->defname, "support") == 0) { if (is_procedure) goto procedure_error; if (*support_item) goto duplicate_error; *support_item = defel; } else if (strcmp(defel->defname, "parallel") == 0) { if (is_procedure) goto procedure_error; if (*parallel_item) goto duplicate_error; *parallel_item = defel; } else return false; /* Recognized an option */ return true;duplicate_error: ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"), parser_errposition(pstate, defel->location))); return false; /* keep compiler quiet */procedure_error: ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("invalid attribute in procedure definition"), parser_errposition(pstate, defel->location))); return false;}三、跟踪分析

测试脚本

create or replace function func_test(pi_v1 in int,pi_v2 varchar,pio_v3 inout varchar,po_v4 out int,po_v5 out varchar)returns record as$$declarebegin raise notice 'pi_v1 := %,pi_v2 := %,pi_v3 := %',pi_v1,pi_v2,pio_v3; pio_v3 := 'pio_v3 i/o'; po_v4 := 100; po_v5 := 'po_v5 out';end;$$ LANGUAGE plpgsql;

启动GDB跟踪

(gdb) b compute_function_attributesBreakpoint 1 at 0x6702b7: file functioncmds.c, line 711.(gdb) cContinuing.Breakpoint 1, compute_function_attributes (pstate=0x1dd4c88, is_procedure=false, options=0x1daf7e8, as=0x7ffd231851d8, language=0x7ffd23185240, transform=0x7ffd23185238, windowfunc_p=0x7ffd231851ff, volatility_p=0x7ffd231851fb "v", strict_p=0x7ffd231851fe, security_definer=0x7ffd231851fd, leakproof_p=0x7ffd231851fc, proconfig=0x7ffd231851f0, procost=0x7ffd231851ec, prorows=0x7ffd231851e8, prosupport=0x7ffd231851e4, parallel_p=0x7ffd231851d7 "u") at functioncmds.c:711711 DefElem *as_item = NULL;

输入参数

(gdb) p *pstate$1 = {parentParseState = 0x0, p_sourcetext = 0x1daded8 "create or replace function func_test(pi_v1 in int,pi_v2 varchar,pio_v3 inout varchar,po_v4 out int,po_v5 out varchar)\nreturns record \nas\n$$\ndeclare\nbegin\n raise notice 'pi_v1 := %,pi_v2 := %,pi_v3 :="..., p_rtable = 0x0, p_joinexprs = 0x0, p_joinlist = 0x0, p_namespace = 0x0, p_lateral_active = false, p_ctenamespace = 0x0, p_future_ctes = 0x0, p_parent_cte = 0x0, p_target_relation = 0x0, p_target_rangetblentry = 0x0, p_is_insert = false, p_windowdefs = 0x0, p_expr_kind = EXPR_KIND_NONE, p_next_resno = 1, p_multiassign_exprs = 0x0, p_locking_clause = 0x0, p_locked_from_parent = false, p_resolve_unknowns = true, p_queryEnv = 0x0, p_hasAggs = false, p_hasWindowFuncs = false, p_hasTargetSRFs = false, p_hasSubLinks = false, p_hasModifyingCTE = false, p_last_srf = 0x0, p_pre_columnref_hook = 0x0, p_post_columnref_hook = 0x0, p_paramref_hook = 0x0, p_coerce_param_hook = 0x0, p_ref_hook_state = 0x0}(gdb) p is_procedure$2 = false

SQL语句的选项#1(as语句)

(gdb) p *options$3 = {type = T_List, length = 2, head = 0x1daf7c0, tail = 0x1daf8a0}(gdb) p *(Node *)options->head->data.ptr_value$4 = {type = T_DefElem}(gdb) p *(DefElem *)options->head->data.ptr_value$5 = {type = T_DefElem, defnamespace = 0x0, defname = 0xbbf727 "as", arg = 0x1daf730, defaction = DEFELEM_UNSPEC, location = 134}(gdb) set $defelem=(DefElem *)options->head->data.ptr_value(gdb) p $defelem->arg$6 = (Node *) 0x1daf730(gdb) p *(Node *)$defelem->arg$7 = {type = T_List}(gdb) p *(List *)$defelem->arg$8 = {type = T_List, length = 1, head = 0x1daf708, tail = 0x1daf708}(gdb) set $arg=(List *)$defelem->arg(gdb) p *(Node *)$arg->head->data.ptr_value$9 = {type = T_String}(gdb) p *(Value *)$arg->head->data.ptr_value$11 = {type = T_String, val = {ival = 31126984, str = 0x1daf5c8 "\ndeclare\nbegin\n raise notice 'pi_v1 := %,pi_v2 := %,pi_v3 := %',pi_v1,pi_v2,pio_v3;\n pio_v3 := 'pio_v3 i/o';\n po_v4 := 100;\n po_v5 := 'po_v5 out';\nend;\n"}}

SQL语句的选项#2(语言)

(gdb) set $defelem2=(DefElem *)options->head->next->data.ptr_value(gdb) p *$defelem2$13 = {type = T_DefElem, defnamespace = 0x0, defname = 0xbbfcbe "language", arg = 0x1daf820, defaction = DEFELEM_UNSPEC, location = 298}(gdb) p *$defelem2->arg$14 = {type = T_String}(gdb) p *(Value *)$defelem2->arg$15 = {type = T_String, val = {ival = 31126952, str = 0x1daf5a8 "plpgsql"}}(gdb)

提取as_item和language_item

(gdb) n712 DefElem *language_item = NULL;(gdb) n713 DefElem *transform_item = NULL;(gdb) 714 DefElem *windowfunc_item = NULL;(gdb) 715 DefElem *volatility_item = NULL;(gdb) 716 DefElem *strict_item = NULL;(gdb) 717 DefElem *security_item = NULL;(gdb) 718 DefElem *leakproof_item = NULL;(gdb) 719 List *set_items = NIL;(gdb) 720 DefElem *cost_item = NULL;(gdb) 721 DefElem *rows_item = NULL;(gdb) 722 DefElem *support_item = NULL;(gdb) 723 DefElem *parallel_item = NULL;(gdb) 725 foreach(option, options)(gdb) 727 DefElem *defel = (DefElem *) lfirst(option);(gdb) 729 if (strcmp(defel->defname, "as") == 0)(gdb) 731 if (as_item)(gdb) 736 as_item = defel;(gdb) 725 foreach(option, options)(gdb) p *as_item$16 = {type = T_DefElem, defnamespace = 0x0, defname = 0xbbf727 "as", arg = 0x1daf730, defaction = DEFELEM_UNSPEC, location = 134}(gdb) n727 DefElem *defel = (DefElem *) lfirst(option);(gdb) 729 if (strcmp(defel->defname, "as") == 0)(gdb) 738 else if (strcmp(defel->defname, "language") == 0)(gdb) 740 if (language_item)(gdb) 745 language_item = defel;(gdb) 725 foreach(option, options)(gdb) p *language_item$17 = {type = T_DefElem, defnamespace = 0x0, defname = 0xbbfcbe "language", arg = 0x1daf820, defaction = DEFELEM_UNSPEC, location = 298}(gdb) n792 if (as_item)

提取item中的arg

(gdb) n793 *as = (List *) as_item->arg;(gdb) 802 if (language_item)(gdb) p *as$18 = (List *) 0x1daf730(gdb) n803 *language = strVal(language_item->arg);(gdb) 813 if (transform_item)(gdb) p *language$19 = 0x1daf5a8 "plpgsql"(gdb) n815 if (windowfunc_item)(gdb) 817 if (volatility_item)(gdb) 819 if (strict_item)(gdb) 821 if (security_item)(gdb) 823 if (leakproof_item)(gdb) 825 if (set_items)(gdb) 827 if (cost_item)(gdb) 835 if (rows_item)(gdb) 843 if (support_item)(gdb) 845 if (parallel_item)(gdb) 847 }(gdb) CreateFunction (pstate=0x1dd4c88, stmt=0x1daf8c8) at functioncmds.c:989989 languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language));

DONE!

四、参考资料

N/A