本篇内容介绍了“分析PostgreSQL CreateFunction中的ProcedureCreate函数”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
一、数据结构
Form_pg_language
plpgsql语言定义结构体
CATALOG(pg_language,2612,LanguageRelationId)
{
Oid oid;
NameData lanname;
Oid lanowner BKI_DEFAULT(PGUID);
bool lanispl BKI_DEFAULT(f);
bool lanpltrusted BKI_DEFAULT(f);
Oid lanplcallfoid BKI_DEFAULT(0) BKI_LOOKUP(pg_proc);
Oid laninline BKI_DEFAULT(0) BKI_LOOKUP(pg_proc);
Oid lanvalidator BKI_DEFAULT(0) BKI_LOOKUP(pg_proc);
#ifdef CATALOG_VARLEN
aclitem lanacl[1] BKI_DEFAULT(_null_);
#endif
} FormData_pg_language;
typedef FormData_pg_language *Form_pg_language;
ArrayType
typedef struct
{
//可变的header
int32 vl_len_;
//维度
int ndim;
//指向数据的偏移量,如为0则表示没有位图
int32 dataoffset;
//元素类型的OID
Oid elemtype;
} ArrayType;
DefElem
typedef struct DefElem
{
NodeTag type;
char *defnamespace;
char *defname;
Node *arg;
DefElemAction defaction;
int location;
} DefElem;
FunctionParameter
typedef enum FunctionParameterMode
{
FUNC_PARAM_IN = 'i',
FUNC_PARAM_OUT = 'o',
FUNC_PARAM_INOUT = 'b',
FUNC_PARAM_VARIADIC = 'v',
FUNC_PARAM_TABLE = 't'
} FunctionParameterMode;
typedef struct FunctionParameter
{
NodeTag type;
char *name;
TypeName *argType;
FunctionParameterMode mode;
Node *defexpr;
} FunctionParameter;
二、源码解读
ObjectAddress
ProcedureCreate(const char *procedureName,
Oid procNamespace,
bool replace,
bool returnsSet,
Oid returnType,
Oid proowner,
Oid languageObjectId,
Oid languageValidator,
const char *prosrc,
const char *probin,
char prokind,
bool security_definer,
bool isLeakProof,
bool isStrict,
char volatility,
char parallel,
oidvector *parameterTypes,
Datum allParameterTypes,
Datum parameterModes,
Datum parameterNames,
List *parameterDefaults,
Datum trftypes,
Datum proconfig,
Oid prosupport,
float4 procost,
float4 prorows)
{
Oid retval;//返回值类型
int parameterCount;//输入参数
int allParamCount;//所有参数,如无输出参数,则为0
Oid *allParams;//所有参数类型,如无输出参数,则为NULL
char *paramModes = NULL;//参数类型
bool genericInParam = false;
bool genericOutParam = false;
bool anyrangeInParam = false;
bool anyrangeOutParam = false;
bool internalInParam = false;
bool internalOutParam = false;
Oid variadicType = InvalidOid;
Acl *proacl = NULL;//ACL结构体
Relation rel;//关系
HeapTuple tup;//tuple
HeapTuple oldtup;//原pg_proc tuple
bool nulls[Natts_pg_proc];//是否为null
Datum values[Natts_pg_proc];//值
bool replaces[Natts_pg_proc];//是否替换
NameData procname;//名称
TupleDesc tupDesc;//tuple描述符
bool is_update;//是否替换?
ObjectAddress myself,
referenced;
int i;//临时变量
Oid trfid;
Assert(PointerIsValid(prosrc));
//参数个数
parameterCount = parameterTypes->dim1;
//#define FUNC_MAX_ARGS 100
if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
errmsg_plural("functions cannot have more than %d argument",
"functions cannot have more than %d arguments",
FUNC_MAX_ARGS,
FUNC_MAX_ARGS)));
//重构数组输入:所有参数类型
if (allParameterTypes != PointerGetDatum(NULL))
{
ArrayType *allParamArray = (ArrayType *) DatumGetPointer(allParameterTypes);
//获取数组的维数
//#define ARR_DIMS(a) \
((int *) (((char *) (a)) + sizeof(ArrayType)))
//#define ARR_NDIM(a) ((a)->ndim)
//#define ARR_HASNULL(a) ((a)->dataoffset != 0)
allParamCount = ARR_DIMS(allParamArray)[0];
if (ARR_NDIM(allParamArray) != 1 ||
allParamCount <= 0 ||
ARR_HASNULL(allParamArray) ||
ARR_ELEMTYPE(allParamArray) != OIDOID)
elog(ERROR, "allParameterTypes is not a 1-D Oid array");
//所有参数
allParams = (Oid *) ARR_DATA_PTR(allParamArray);
Assert(allParamCount >= parameterCount);
}
else
{
//均为输入参数,无输出参数
allParamCount = parameterCount;
allParams = parameterTypes->values;
}
if (parameterModes != PointerGetDatum(NULL))
{
//参数模式(输入/输出等)
ArrayType *modesArray = (ArrayType *) DatumGetPointer(parameterModes);
if (ARR_NDIM(modesArray) != 1 ||
ARR_DIMS(modesArray)[0] != allParamCount ||
ARR_HASNULL(modesArray) ||
ARR_ELEMTYPE(modesArray) != CHAROID)
elog(ERROR, "parameterModes is not a 1-D char array");
paramModes = (char *) ARR_DATA_PTR(modesArray);
}
for (i = 0; i < parameterCount; i++)
{
switch (parameterTypes->values[i])
{
case ANYARRAYOID:
case ANYELEMENTOID:
case ANYNONARRAYOID:
case ANYENUMOID:
genericInParam = true;
break;
case ANYRANGEOID:
genericInParam = true;
anyrangeInParam = true;
break;
case INTERNALOID:
internalInParam = true;
break;
}
}
if (allParameterTypes != PointerGetDatum(NULL))
{
for (i = 0; i < allParamCount; i++)
{
if (paramModes == NULL ||
paramModes[i] == PROARGMODE_IN ||
paramModes[i] == PROARGMODE_VARIADIC)
continue;
switch (allParams[i])
{
case ANYARRAYOID:
case ANYELEMENTOID:
case ANYNONARRAYOID:
case ANYENUMOID:
genericOutParam = true;
break;
case ANYRANGEOID:
genericOutParam = true;
anyrangeOutParam = true;
break;
case INTERNALOID:
internalOutParam = true;
break;
}
}
}
if ((IsPolymorphicType(returnType) || genericOutParam)
&& !genericInParam)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("cannot determine result data type"),
errdetail("A function returning a polymorphic type must have at least one polymorphic argument.")));
if ((returnType == ANYRANGEOID || anyrangeOutParam) &&
!anyrangeInParam)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("cannot determine result data type"),
errdetail("A function returning \"anyrange\" must have at least one \"anyrange\" argument.")));
if ((returnType == INTERNALOID || internalOutParam) && !internalInParam)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("unsafe use of pseudo-type \"internal\""),
errdetail("A function returning \"internal\" must have at least one \"internal\" argument.")));
if (paramModes != NULL)
{
for (i = 0; i < allParamCount; i++)
{
switch (paramModes[i])
{
case PROARGMODE_IN:
case PROARGMODE_INOUT:
if (OidIsValid(variadicType))
elog(ERROR, "variadic parameter must be last");
break;
case PROARGMODE_OUT:
case PROARGMODE_TABLE:
break;
case PROARGMODE_VARIADIC:
if (OidIsValid(variadicType))
elog(ERROR, "variadic parameter must be last");
switch (allParams[i])
{
case ANYOID:
variadicType = ANYOID;
break;
case ANYARRAYOID:
variadicType = ANYELEMENTOID;
break;
default:
variadicType = get_element_type(allParams[i]);
if (!OidIsValid(variadicType))
elog(ERROR, "variadic parameter is not an array");
break;
}
break;
default:
elog(ERROR, "invalid parameter mode '%c'", paramModes[i]);
break;
}
}
}
//#define Natts_pg_proc 29
for (i = 0; i < Natts_pg_proc; ++i)
{
nulls[i] = false;
values[i] = (Datum) 0;
replaces[i] = true;
}
//#define Anum_pg_proc_oid 1
//#define Anum_pg_proc_proname 2
//...
//#define Anum_pg_proc_proacl 29
namestrcpy(&procname, procedureName);
values[Anum_pg_proc_proname - 1] = NameGetDatum(&procname);
values[Anum_pg_proc_pronamespace - 1] = ObjectIdGetDatum(procNamespace);
values[Anum_pg_proc_proowner - 1] = ObjectIdGetDatum(proowner);
values[Anum_pg_proc_prolang - 1] = ObjectIdGetDatum(languageObjectId);
values[Anum_pg_proc_procost - 1] = Float4GetDatum(procost);
values[Anum_pg_proc_prorows - 1] = Float4GetDatum(prorows);
values[Anum_pg_proc_provariadic - 1] = ObjectIdGetDatum(variadicType);
values[Anum_pg_proc_prosupport - 1] = ObjectIdGetDatum(prosupport);
values[Anum_pg_proc_prokind - 1] = CharGetDatum(prokind);
values[Anum_pg_proc_prosecdef - 1] = BoolGetDatum(security_definer);
values[Anum_pg_proc_proleakproof - 1] = BoolGetDatum(isLeakProof);
values[Anum_pg_proc_proisstrict - 1] = BoolGetDatum(isStrict);
values[Anum_pg_proc_proretset - 1] = BoolGetDatum(returnsSet);
values[Anum_pg_proc_provolatile - 1] = CharGetDatum(volatility);
values[Anum_pg_proc_proparallel - 1] = CharGetDatum(parallel);
values[Anum_pg_proc_pronargs - 1] = UInt16GetDatum(parameterCount);
values[Anum_pg_proc_pronargdefaults - 1] = UInt16GetDatum(list_length(parameterDefaults));
values[Anum_pg_proc_prorettype - 1] = ObjectIdGetDatum(returnType);
values[Anum_pg_proc_proargtypes - 1] = PointerGetDatum(parameterTypes);
if (allParameterTypes != PointerGetDatum(NULL))
values[Anum_pg_proc_proallargtypes - 1] = allParameterTypes;
else
nulls[Anum_pg_proc_proallargtypes - 1] = true;
if (parameterModes != PointerGetDatum(NULL))
values[Anum_pg_proc_proargmodes - 1] = parameterModes;
else
nulls[Anum_pg_proc_proargmodes - 1] = true;
if (parameterNames != PointerGetDatum(NULL))
values[Anum_pg_proc_proargnames - 1] = parameterNames;
else
nulls[Anum_pg_proc_proargnames - 1] = true;
if (parameterDefaults != NIL)
values[Anum_pg_proc_proargdefaults - 1] = CStringGetTextDatum(nodeToString(parameterDefaults));
else
nulls[Anum_pg_proc_proargdefaults - 1] = true;
if (trftypes != PointerGetDatum(NULL))
values[Anum_pg_proc_protrftypes - 1] = trftypes;
else
nulls[Anum_pg_proc_protrftypes - 1] = true;
values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
if (probin)
values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin);
else
nulls[Anum_pg_proc_probin - 1] = true;
if (proconfig != PointerGetDatum(NULL))
values[Anum_pg_proc_proconfig - 1] = proconfig;
else
nulls[Anum_pg_proc_proconfig - 1] = true;
rel = table_open(ProcedureRelationId, RowExclusiveLock);
tupDesc = RelationGetDescr(rel);
//检查是否已存在
oldtup = SearchSysCache3(PROCNAMEARGSNSP,
PointerGetDatum(procedureName),
PointerGetDatum(parameterTypes),
ObjectIdGetDatum(procNamespace));
if (HeapTupleIsValid(oldtup))
{
//-------- 已存在
//获取原记录(pg_proc记录)
Form_pg_proc oldproc = (Form_pg_proc) GETSTRUCT(oldtup);
Datum proargnames;
bool isnull;
const char *dropcmd;
if (!replace)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_FUNCTION),
errmsg("function \"%s\" already exists with same argument types",
procedureName)));
if (!pg_proc_ownercheck(oldproc->oid, proowner))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION,
procedureName);
//不能改变类型(如原来是proc,那么创建同名func是不行的)
if (oldproc->prokind != prokind)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change routine kind"),
(oldproc->prokind == PROKIND_AGGREGATE ?
errdetail("\"%s\" is an aggregate function.", procedureName) :
oldproc->prokind == PROKIND_FUNCTION ?
errdetail("\"%s\" is a function.", procedureName) :
oldproc->prokind == PROKIND_PROCEDURE ?
errdetail("\"%s\" is a procedure.", procedureName) :
oldproc->prokind == PROKIND_WINDOW ?
errdetail("\"%s\" is a window function.", procedureName) :
0)));
dropcmd = (prokind == PROKIND_PROCEDURE ? "DROP PROCEDURE" :
prokind == PROKIND_AGGREGATE ? "DROP AGGREGATE" :
"DROP FUNCTION");
if (returnType != oldproc->prorettype ||
returnsSet != oldproc->proretset)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
prokind == PROKIND_PROCEDURE
? errmsg("cannot change whether a procedure has output parameters")
: errmsg("cannot change return type of existing function"),
errhint("Use %s %s first.",
dropcmd,
format_procedure(oldproc->oid))));
if (returnType == RECORDOID)
{
//返回RECORD类型
TupleDesc olddesc;
TupleDesc newdesc;
olddesc = build_function_result_tupdesc_t(oldtup);
newdesc = build_function_result_tupdesc_d(prokind,
allParameterTypes,
parameterModes,
parameterNames);
if (olddesc == NULL && newdesc == NULL)
;
else if (olddesc == NULL || newdesc == NULL ||
!equalTupleDescs(olddesc, newdesc))
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("cannot change return type of existing function"),
errdetail("Row type defined by OUT parameters is different."),
errhint("Use %s %s first.",
dropcmd,
format_procedure(oldproc->oid))));
}
proargnames = SysCacheGetAttr(PROCNAMEARGSNSP, oldtup,
Anum_pg_proc_proargnames,
&isnull);
if (!isnull)
{
Datum proargmodes;
char **old_arg_names;
char **new_arg_names;
int n_old_arg_names;
int n_new_arg_names;
int j;
proargmodes = SysCacheGetAttr(PROCNAMEARGSNSP, oldtup,
Anum_pg_proc_proargmodes,
&isnull);
if (isnull)
proargmodes = PointerGetDatum(NULL);
n_old_arg_names = get_func_input_arg_names(proargnames,
proargmodes,
&old_arg_names);
n_new_arg_names = get_func_input_arg_names(parameterNames,
parameterModes,
&new_arg_names);
for (j = 0; j < n_old_arg_names; j++)
{
if (old_arg_names[j] == NULL)
continue;
if (j >= n_new_arg_names || new_arg_names[j] == NULL ||
strcmp(old_arg_names[j], new_arg_names[j]) != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("cannot change name of input parameter \"%s\"",
old_arg_names[j]),
errhint("Use %s %s first.",
dropcmd,
format_procedure(oldproc->oid))));
}
}
if (oldproc->pronargdefaults != 0)
{
//默认值判断
Datum proargdefaults;
List *oldDefaults;
ListCell *oldlc;
ListCell *newlc;
if (list_length(parameterDefaults) < oldproc->pronargdefaults)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("cannot remove parameter defaults from existing function"),
errhint("Use %s %s first.",
dropcmd,
format_procedure(oldproc->oid))));
proargdefaults = SysCacheGetAttr(PROCNAMEARGSNSP, oldtup,
Anum_pg_proc_proargdefaults,
&isnull);
Assert(!isnull);
oldDefaults = castNode(List, stringToNode(TextDatumGetCString(proargdefaults)));
Assert(list_length(oldDefaults) == oldproc->pronargdefaults);
newlc = list_head(parameterDefaults);
for (i = list_length(parameterDefaults) - oldproc->pronargdefaults;
i > 0;
i--)
newlc = lnext(newlc);
foreach(oldlc, oldDefaults)
{
Node *oldDef = (Node *) lfirst(oldlc);
Node *newDef = (Node *) lfirst(newlc);
if (exprType(oldDef) != exprType(newDef))
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("cannot change data type of existing parameter default value"),
errhint("Use %s %s first.",
dropcmd,
format_procedure(oldproc->oid))));
newlc = lnext(newlc);
}
}
replaces[Anum_pg_proc_oid - 1] = false;
replaces[Anum_pg_proc_proowner - 1] = false;
replaces[Anum_pg_proc_proacl - 1] = false;
tup = heap_modify_tuple(oldtup, tupDesc, values, nulls, replaces);
CatalogTupleUpdate(rel, &tup->t_self, tup);
ReleaseSysCache(oldtup);
is_update = true;
}
else
{
//-------------- 不存在
//创建新的过程
Oid newOid;
//首先:获取默认的权限并设置proacl
proacl = get_user_default_acl(OBJECT_FUNCTION, proowner,
procNamespace);
if (proacl != NULL)
values[Anum_pg_proc_proacl - 1] = PointerGetDatum(proacl);
else
nulls[Anum_pg_proc_proacl - 1] = true;
//获取新的OID
newOid = GetNewOidWithIndex(rel, ProcedureOidIndexId,
Anum_pg_proc_oid);
//设置pg_proc中的OID
values[Anum_pg_proc_oid - 1] = ObjectIdGetDatum(newOid);
//构造tuple
tup = heap_form_tuple(tupDesc, values, nulls);
//执行插入操作
CatalogTupleInsert(rel, tup);
is_update = false;
}
//获取pg_proc结构体
retval = ((Form_pg_proc) GETSTRUCT(tup))->oid;
if (is_update)
//删除依赖
deleteDependencyRecordsFor(ProcedureRelationId, retval, true);
myself.classId = ProcedureRelationId;
myself.objectId = retval;
myself.objectSubId = 0;
//依赖:namespace
referenced.classId = NamespaceRelationId;
referenced.objectId = procNamespace;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
//依赖:语言
referenced.classId = LanguageRelationId;
referenced.objectId = languageObjectId;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
//依赖:返回类型
referenced.classId = TypeRelationId;
referenced.objectId = returnType;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
//依赖:返回类型的转换规则
if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
{
referenced.classId = TransformRelationId;
referenced.objectId = trfid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
//依赖:参数类型
for (i = 0; i < allParamCount; i++)
{
referenced.classId = TypeRelationId;
referenced.objectId = allParams[i];
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
{
referenced.classId = TransformRelationId;
referenced.objectId = trfid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
}
//依赖:默认表达式
if (parameterDefaults)
recordDependencyOnExpr(&myself, (Node *) parameterDefaults,
NIL, DEPENDENCY_NORMAL);
//依赖:支持的函数
if (OidIsValid(prosupport))
{
referenced.classId = ProcedureRelationId;
referenced.objectId = prosupport;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
//依赖:owner
if (!is_update)
recordDependencyOnOwner(ProcedureRelationId, retval, proowner);
//依赖:ACL中标明的角色
if (!is_update)
recordDependencyOnNewAcl(ProcedureRelationId, retval, 0,
proowner, proacl);
//依赖:扩展
recordDependencyOnCurrentExtension(&myself, is_update);
heap_freetuple(tup);
//调用对象创建后的钩子函数
InvokeObjectPostCreateHook(ProcedureRelationId, retval, 0);
//关闭pg_proc
table_close(rel, RowExclusiveLock);
//验证函数body
if (OidIsValid(languageValidator))
{
ArrayType *set_items = NULL;
int save_nestlevel = 0;
//增加命令计数
CommandCounterIncrement();
if (check_function_bodies)
{
//检查函数体
//获取函数设定的参数
set_items = (ArrayType *) DatumGetPointer(proconfig);
if (set_items)
{
save_nestlevel = NewGUCNestLevel();
ProcessGUCArray(set_items,
(superuser() ? PGC_SUSET : PGC_USERSET),
PGC_S_SESSION,
GUC_ACTION_SAVE);
}
}
//调用语言校验器
OidFunctionCall1(languageValidator, ObjectIdGetDatum(retval));
if (set_items)
AtEOXact_GUC(true, save_nestlevel);
}
return myself;
}
三、跟踪分析
测试脚本
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
$$
declare
begin
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 ProcedureCreate
Breakpoint 1 at 0x5bd665: file pg_proc.c, line 99.
(gdb) c
Continuing.
Breakpoint 1, ProcedureCreate (procedureName=0x1173ab0 "func_test", procNamespace=2200,
replace=true, returnsSet=false, returnType=2249, proowner=10, languageObjectId=13581,
languageValidator=13580,
prosrc=0x11745c8 "\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",
probin=0x0, prokind=102 'f', security_definer=false, isLeakProof=false, isStrict=false,
volatility=118 'v', parallel=117 'u', parameterTypes=0x119a3d0,
allParameterTypes=18458616, parameterModes=18457432, parameterNames=18456792,
parameterDefaults=0x0, trftypes=0, proconfig=0, prosupport=0, procost=100, prorows=0)
at pg_proc.c:99
99 char *paramModes = NULL;
(gdb)
输入参数
[local:/data/run/pg12]:5120 pg12@testdb=# \d pg_type
Table "pg_catalog.pg_type"
Column | Type | Collation | Nullable | Default
----------------+--------------+-----------+----------+---------
oid | oid | | not null |
typname | name | | not null |
typnamespace | oid | | not null |
typowner | oid | | not null |
typlen | smallint | | not null |
typbyval | boolean | | not null |
typtype | "char" | | not null |
typcategory | "char" | | not null |
typispreferred | boolean | | not null |
typisdefined | boolean | | not null |
typdelim | "char" | | not null |
typrelid | oid | | not null |
typelem | oid | | not null |
typarray | oid | | not null |
typinput | regproc | | not null |
typoutput | regproc | | not null |
typreceive | regproc | | not null |
[local:/data/run/pg12]:5120 pg12@testdb=# select oid,typname from pg_type where oid=2249;
oid | typname
------+---------
2249 | record
(1 row)
[local:/data/run/pg12]:5120 pg12@testdb=# \d pg_namespace
Table "pg_catalog.pg_namespace"
Column | Type | Collation | Nullable | Default
----------+-----------+-----------+----------+---------
oid | oid | | not null |
nspname | name | | not null |
nspowner | oid | | not null |
nspacl | aclitem[] | | |
Indexes:
"pg_namespace_nspname_index" UNIQUE, btree (nspname)
"pg_namespace_oid_index" UNIQUE, btree (oid)
[local:/data/run/pg12]:5120 pg12@testdb=# select oid,nspname from pg_namespace where oid=2200;
oid | nspname
------+---------
2200 | public
(1 row)
[local:/data/run/pg12]:5120 pg12@testdb=# \d pg_user
View "pg_catalog.pg_user"
Column | Type | Collation | Nullable | Default
--------------+--------------------------+-----------+----------+---------
usename | name | | |
usesysid | oid | | |
usecreatedb | boolean | | |
usesuper | boolean | | |
userepl | boolean | | |
usebypassrls | boolean | | |
passwd | text | | |
valuntil | timestamp with time zone | | |
useconfig | text[] | C | |
[local:/data/run/pg12]:5120 pg12@testdb=# select usename,usesysid from pg_user where usesysid=10;
usename | usesysid
---------+----------
pg12 | 10
(1 row)
[local:/data/run/pg12]:5120 pg12@testdb=# \d pg_language
Table "pg_catalog.pg_language"
Column | Type | Collation | Nullable | Default
---------------+-----------+-----------+----------+---------
oid | oid | | not null |
lanname | name | | not null |
lanowner | oid | | not null |
lanispl | boolean | | not null |
lanpltrusted | boolean | | not null |
lanplcallfoid | oid | | not null |
laninline | oid | | not null |
lanvalidator | oid | | not null |
lanacl | aclitem[] | | |
Indexes:
"pg_language_name_index" UNIQUE, btree (lanname)
"pg_language_oid_index" UNIQUE, btree (oid)
[local:/data/run/pg12]:5120 pg12@testdb=# select oid,lanname,lanowner from pg_language where oid=13581;
oid | lanname | lanowner
-------+---------+----------
13581 | plpgsql | 10
(1 row)
初始化本地临时变量
99 char *paramModes = NULL;
(gdb) n
100 bool genericInParam = false;
(gdb)
101 bool genericOutParam = false;
(gdb)
102 bool anyrangeInParam = false;
(gdb)
103 bool anyrangeOutParam = false;
(gdb)
104 bool internalInParam = false;
(gdb)
105 bool internalOutParam = false;
(gdb)
106 Oid variadicType = InvalidOid;
(gdb)
107 Acl *proacl = NULL;
(gdb)
125 Assert(PointerIsValid(prosrc));
(gdb)
127 parameterCount = parameterTypes->dim1;
(gdb)
获取参数个数(3个输入参数,类型为26-oid,数据类型为int4,varchar,varchar)
(gdb) n
128 if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
(gdb) p parameterCount
$1 = 3
(gdb) p *parameterTypes
$2 = {vl_len_ = 144, ndim = 1, dataoffset = 0, elemtype = 26, dim1 = 3, lbound1 = 0,
values = 0x119a3e8}
(gdb)
(gdb) p *parameterTypes->values
$3 = 23
(gdb) p parameterTypes->values[1]
$4 = 1043
(gdb) p parameterTypes->values[2]
$5 = 1043
(gdb)
###
[local:/data/run/pg12]:5120 pg12@testdb=# select oid,typname from pg_type where oid=26;
oid | typname
-----+---------
26 | oid
(1 row)
[local:/data/run/pg12]:5120 pg12@testdb=# select oid,typname from pg_type where oid in (23,1043);
oid | typname
------+---------
23 | int4
1043 | varchar
(2 rows)
重构数组输入:所有参数类型
(gdb) n
138 if (allParameterTypes != PointerGetDatum(NULL))
(gdb)
145 ArrayType *allParamArray = (ArrayType *) DatumGetPointer(allParameterTypes);
(gdb)
147 allParamCount = ARR_DIMS(allParamArray)[0];
(gdb)
148 if (ARR_NDIM(allParamArray) != 1 ||
(gdb)
150 ARR_HASNULL(allParamArray) ||
(gdb)
149 allParamCount <= 0 ||
(gdb)
151 ARR_ELEMTYPE(allParamArray) != OIDOID)
(gdb)
150 ARR_HASNULL(allParamArray) ||
(gdb)
153 allParams = (Oid *) ARR_DATA_PTR(allParamArray);
(gdb)
154 Assert(allParamCount >= parameterCount);
(gdb) p *allParamArray
$6 = {vl_len_ = 176, ndim = 1, dataoffset = 0, elemtype = 26}
(gdb) p allParamArray[1]
$7 = {vl_len_ = 5, ndim = 1, dataoffset = 23, elemtype = 1043}
(gdb) p allParamCount
$8 = 5
(gdb) p *ARR_DIMS(allParamArray)
$9 = 5
(gdb) p allParamArray[2]
$10 = {vl_len_ = 1043, ndim = 23, dataoffset = 1043, elemtype = 2139062142}
(gdb) p allParamArray[3]
$11 = {vl_len_ = 2139062143, ndim = 2139062143, dataoffset = 2139062143,
elemtype = 2139062143}
(gdb) n
163 if (parameterModes != PointerGetDatum(NULL))
(gdb)
处理参数模式:参数模式(输入/输出等),分别是i,i,b,o,o
(gdb) n
170 ArrayType *modesArray = (ArrayType *) DatumGetPointer(parameterModes);
(gdb)
172 if (ARR_NDIM(modesArray) != 1 ||
(gdb)
173 ARR_DIMS(modesArray)[0] != allParamCount ||
(gdb)
172 if (ARR_NDIM(modesArray) != 1 ||
(gdb)
174 ARR_HASNULL(modesArray) ||
(gdb)
173 ARR_DIMS(modesArray)[0] != allParamCount ||
(gdb)
175 ARR_ELEMTYPE(modesArray) != CHAROID)
(gdb)
174 ARR_HASNULL(modesArray) ||
(gdb)
177 paramModes = (char *) ARR_DATA_PTR(modesArray);
(gdb)
184 for (i = 0; i < parameterCount; i++)
(gdb) p paramModes[0]
$12 = 105 'i'
(gdb) p paramModes[1]
$13 = 105 'i'
(gdb) p paramModes[2]
$14 = 98 'b'
(gdb) p paramModes[3]
$15 = 111 'o'
(gdb) p paramModes[4]
$16 = 111 'o'
(gdb)
检查是否存在多态或者INTERNAL参数.
两趟循环:第一趟检查输入参数,第二趟检查输出参数
(gdb) n
186 switch (parameterTypes->values[i])
(gdb)
184 for (i = 0; i < parameterCount; i++)
(gdb)
186 switch (parameterTypes->values[i])
(gdb)
184 for (i = 0; i < parameterCount; i++)
(gdb)
186 switch (parameterTypes->values[i])
(gdb)
184 for (i = 0; i < parameterCount; i++)
(gdb)
204 if (allParameterTypes != PointerGetDatum(NULL))
(gdb)
206 for (i = 0; i < allParamCount; i++)
(gdb)
208 if (paramModes == NULL ||
(gdb)
209 paramModes[i] == PROARGMODE_IN ||
(gdb)
208 if (paramModes == NULL ||
(gdb)
211 continue;
(gdb)
206 for (i = 0; i < allParamCount; i++)
(gdb)
208 if (paramModes == NULL ||
(gdb)
209 paramModes[i] == PROARGMODE_IN ||
(gdb)
208 if (paramModes == NULL ||
(gdb)
211 continue;
(gdb)
206 for (i = 0; i < allParamCount; i++)
(gdb)
208 if (paramModes == NULL ||
(gdb)
209 paramModes[i] == PROARGMODE_IN ||
(gdb)
208 if (paramModes == NULL ||
(gdb)
210 paramModes[i] == PROARGMODE_VARIADIC)
(gdb)
209 paramModes[i] == PROARGMODE_IN ||
(gdb)
213 switch (allParams[i])
(gdb)
206 for (i = 0; i < allParamCount; i++)
(gdb)
208 if (paramModes == NULL ||
(gdb)
209 paramModes[i] == PROARGMODE_IN ||
(gdb)
208 if (paramModes == NULL ||
(gdb)
210 paramModes[i] == PROARGMODE_VARIADIC)
(gdb)
209 paramModes[i] == PROARGMODE_IN ||
(gdb)
213 switch (allParams[i])
(gdb)
206 for (i = 0; i < allParamCount; i++)
(gdb)
208 if (paramModes == NULL ||
(gdb) p allParamCount
$17 = 5
(gdb) n
209 paramModes[i] == PROARGMODE_IN ||
(gdb)
208 if (paramModes == NULL ||
(gdb)
210 paramModes[i] == PROARGMODE_VARIADIC)
(gdb)
209 paramModes[i] == PROARGMODE_IN ||
(gdb)
213 switch (allParams[i])
(gdb)
206 for (i = 0; i < allParamCount; i++)
(gdb)
239 if ((IsPolymorphicType(returnType) || genericOutParam)
(gdb)
至少存在一个多态输入参数的情况下才允许返回多态类型.
ANYRANGE返回类型更为严格:必须含有一个ANYRANGE输入(因为无法从ANYELEMENT中规约特殊的范围类型.)
同时,除非至少有一个INTERNAL输入参数类型,否则不允许返回INTERNAL类型.
(gdb) n
246 if ((returnType == ANYRANGEOID || anyrangeOutParam) &&
(gdb)
253 if ((returnType == INTERNALOID || internalOutParam) && !internalInParam)
(gdb)
259 if (paramModes != NULL)
(gdb)
只有最后一个输入参数可以是variadic.如是,则存储元素类型.
266 for (i = 0; i < allParamCount; i++)
(gdb) n
268 switch (paramModes[i])
(gdb)
272 if (OidIsValid(variadicType))
(gdb)
274 break;
(gdb)
266 for (i = 0; i < allParamCount; i++)
(gdb)
268 switch (paramModes[i])
(gdb)
272 if (OidIsValid(variadicType))
(gdb)
274 break;
(gdb)
266 for (i = 0; i < allParamCount; i++)
(gdb)
268 switch (paramModes[i])
(gdb)
272 if (OidIsValid(variadicType))
(gdb)
274 break;
(gdb)
266 for (i = 0; i < allParamCount; i++)
(gdb)
268 switch (paramModes[i])
(gdb)
278 break;
(gdb)
266 for (i = 0; i < allParamCount; i++)
(gdb)
268 switch (paramModes[i])
(gdb)
278 break;
(gdb)
266 for (i = 0; i < allParamCount; i++)
(gdb)
308 for (i = 0; i < Natts_pg_proc; ++i)
(gdb)
检查完毕,写入到pg_proc中,初始化values
308 for (i = 0; i < Natts_pg_proc; ++i)
(gdb)
310 nulls[i] = false;
(gdb)
311 values[i] = (Datum) 0;
(gdb)
312 replaces[i] = true;
(gdb)
...
赋值
308 for (i = 0; i < Natts_pg_proc; ++i)
(gdb)
315 namestrcpy(&procname, procedureName);
(gdb)
316 values[Anum_pg_proc_proname - 1] = NameGetDatum(&procname);
(gdb) p procedureName
$20 = 0x1173ab0 "func_test"
(gdb) n
317 values[Anum_pg_proc_pronamespace - 1] = ObjectIdGetDatum(procNamespace);
(gdb)
318 values[Anum_pg_proc_proowner - 1] = ObjectIdGetDatum(proowner);
(gdb)
319 values[Anum_pg_proc_prolang - 1] = ObjectIdGetDatum(languageObjectId);
(gdb)
320 values[Anum_pg_proc_procost - 1] = Float4GetDatum(procost);
(gdb)
321 values[Anum_pg_proc_prorows - 1] = Float4GetDatum(prorows);
(gdb)
322 values[Anum_pg_proc_provariadic - 1] = ObjectIdGetDatum(variadicType);
(gdb)
323 values[Anum_pg_proc_prosupport - 1] = ObjectIdGetDatum(prosupport);
(gdb)
324 values[Anum_pg_proc_prokind - 1] = CharGetDatum(prokind);
(gdb)
325 values[Anum_pg_proc_prosecdef - 1] = BoolGetDatum(security_definer);
(gdb)
326 values[Anum_pg_proc_proleakproof - 1] = BoolGetDatum(isLeakProof);
(gdb)
327 values[Anum_pg_proc_proisstrict - 1] = BoolGetDatum(isStrict);
(gdb)
328 values[Anum_pg_proc_proretset - 1] = BoolGetDatum(returnsSet);
(gdb)
329 values[Anum_pg_proc_provolatile - 1] = CharGetDatum(volatility);
(gdb)
330 values[Anum_pg_proc_proparallel - 1] = CharGetDatum(parallel);
(gdb)
331 values[Anum_pg_proc_pronargs - 1] = UInt16GetDatum(parameterCount);
(gdb)
332 values[Anum_pg_proc_pronargdefaults - 1] = UInt16GetDatum(list_length(parameterDefaults));
(gdb)
333 values[Anum_pg_proc_prorettype - 1] = ObjectIdGetDatum(returnType);
(gdb)
334 values[Anum_pg_proc_proargtypes - 1] = PointerGetDatum(parameterTypes);
(gdb)
335 if (allParameterTypes != PointerGetDatum(NULL))
(gdb)
336 values[Anum_pg_proc_proallargtypes - 1] = allParameterTypes;
(gdb)
339 if (parameterModes != PointerGetDatum(NULL))
(gdb)
340 values[Anum_pg_proc_proargmodes - 1] = parameterModes;
(gdb)
343 if (parameterNames != PointerGetDatum(NULL))
(gdb)
344 values[Anum_pg_proc_proargnames - 1] = parameterNames;
(gdb)
347 if (parameterDefaults != NIL)
(gdb)
350 nulls[Anum_pg_proc_proargdefaults - 1] = true;
(gdb)
351 if (trftypes != PointerGetDatum(NULL))
(gdb)
354 nulls[Anum_pg_proc_protrftypes - 1] = true;
(gdb)
355 values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
(gdb)
356 if (probin)
(gdb)
359 nulls[Anum_pg_proc_probin - 1] = true;
(gdb)
360 if (proconfig != PointerGetDatum(NULL))
(gdb)
363 nulls[Anum_pg_proc_proconfig - 1] = true;
(gdb)
366 rel = table_open(ProcedureRelationId, RowExclusiveLock);
(gdb)
367 tupDesc = RelationGetDescr(rel);
(gdb)
判断是否已存在
(gdb) p values[28]
$21 = 0
(gdb) p values[0]
$22 = 0
(gdb) p values[2]
$23 = 2200
(gdb) n
370 oldtup = SearchSysCache3(PROCNAMEARGSNSP,
(gdb) p *tupDesc
$24 = {natts = 29, tdtypeid = 81, tdtypmod = -1, tdrefcount = 1, constr = 0x7fbeb44493b8,
attrs = 0x7fbeb44483b8}
(gdb) n
375 if (HeapTupleIsValid(oldtup))
(gdb)
378 Form_pg_proc oldproc = (Form_pg_proc) GETSTRUCT(oldtup);
(gdb)
383 if (!replace)
(gdb) p oldproc
$25 = (Form_pg_proc) 0x7fbeb4388bf8
(gdb) p *oldproc
$26 = {oid = 16387, proname = {data = "func_test", '\000' <repeats 54 times>},
pronamespace = 2200, proowner = 10, prolang = 13581, procost = 100, prorows = 0,
provariadic = 0, prosupport = 0, prokind = 102 'f', prosecdef = false,
proleakproof = false, proisstrict = false, proretset = false, provolatile = 118 'v',
proparallel = 117 'u', pronargs = 3, pronargdefaults = 0, prorettype = 2249, proargtypes = {
vl_len_ = 144, ndim = 1, dataoffset = 0, elemtype = 26, dim1 = 3, lbound1 = 0,
values = 0x7fbeb4388c80}}
(gdb)
执行相关判断:如返回类型,参数类型,参数个数等
(gdb) n
388 if (!pg_proc_ownercheck(oldproc->oid, proowner))
(gdb)
393 if (oldproc->prokind != prokind)
(gdb)
407 dropcmd = (prokind == PROKIND_PROCEDURE ? "DROP PROCEDURE" :
(gdb)
419 if (returnType != oldproc->prorettype ||
(gdb)
420 returnsSet != oldproc->proretset)
(gdb)
419 if (returnType != oldproc->prorettype ||
(gdb)
439 if (returnType == RECORDOID)
(gdb)
444 olddesc = build_function_result_tupdesc_t(oldtup);
(gdb)
445 newdesc = build_function_result_tupdesc_d(prokind,
(gdb)
449 if (olddesc == NULL && newdesc == NULL)
(gdb)
451 else if (olddesc == NULL || newdesc == NULL ||
(gdb)
452 !equalTupleDescs(olddesc, newdesc))
(gdb)
451 else if (olddesc == NULL || newdesc == NULL ||
(gdb)
468 proargnames = SysCacheGetAttr(PROCNAMEARGSNSP, oldtup,
(gdb)
471 if (!isnull)
(gdb)
480 proargmodes = SysCacheGetAttr(PROCNAMEARGSNSP, oldtup,
(gdb)
483 if (isnull)
(gdb)
486 n_old_arg_names = get_func_input_arg_names(proargnames,
(gdb)
489 n_new_arg_names = get_func_input_arg_names(parameterNames,
(gdb)
492 for (j = 0; j < n_old_arg_names; j++)
(gdb)
494 if (old_arg_names[j] == NULL)
(gdb)
496 if (j >= n_new_arg_names || new_arg_names[j] == NULL ||
(gdb)
497 strcmp(old_arg_names[j], new_arg_names[j]) != 0)
(gdb)
496 if (j >= n_new_arg_names || new_arg_names[j] == NULL ||
(gdb)
492 for (j = 0; j < n_old_arg_names; j++)
(gdb)
494 if (old_arg_names[j] == NULL)
(gdb)
496 if (j >= n_new_arg_names || new_arg_names[j] == NULL ||
(gdb)
497 strcmp(old_arg_names[j], new_arg_names[j]) != 0)
(gdb)
496 if (j >= n_new_arg_names || new_arg_names[j] == NULL ||
(gdb)
492 for (j = 0; j < n_old_arg_names; j++)
(gdb)
494 if (old_arg_names[j] == NULL)
(gdb)
496 if (j >= n_new_arg_names || new_arg_names[j] == NULL ||
(gdb)
497 strcmp(old_arg_names[j], new_arg_names[j]) != 0)
(gdb)
496 if (j >= n_new_arg_names || new_arg_names[j] == NULL ||
(gdb)
492 for (j = 0; j < n_old_arg_names; j++)
(gdb)
517 if (oldproc->pronargdefaults != 0)
(gdb)
568 replaces[Anum_pg_proc_oid - 1] = false;
(gdb)
569 replaces[Anum_pg_proc_proowner - 1] = false;
(gdb)
570 replaces[Anum_pg_proc_proacl - 1] = false;
(gdb)
573 tup = heap_modify_tuple(oldtup, tupDesc, values, nulls, replaces);
(gdb)
574 CatalogTupleUpdate(rel, &tup->t_self, tup);
(gdb
更新tuple
(gdb) n
576 ReleaseSysCache(oldtup);
(gdb)
577 is_update = true;
(gdb)
601 retval = ((Form_pg_proc) GETSTRUCT(tup))->oid;
(gdb)
处理函数依赖
(gdb) p retval
$27 = 16387
(gdb) n
610 deleteDependencyRecordsFor(ProcedureRelationId, retval, true);
(gdb)
612 myself.classId = ProcedureRelationId;
(gdb)
613 myself.objectId = retval;
(gdb)
614 myself.objectSubId = 0;
(gdb)
617 referenced.classId = NamespaceRelationId;
(gdb)
618 referenced.objectId = procNamespace;
(gdb)
619 referenced.objectSubId = 0;
(gdb)
620 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
(gdb)
623 referenced.classId = LanguageRelationId;
(gdb)
624 referenced.objectId = languageObjectId;
(gdb)
625 referenced.objectSubId = 0;
(gdb)
626 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
(gdb)
629 referenced.classId = TypeRelationId;
(gdb)
630 referenced.objectId = returnType;
(gdb)
631 referenced.objectSubId = 0;
(gdb)
632 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
(gdb)
635 if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
(gdb)
644 for (i = 0; i < allParamCount; i++)
(gdb)
646 referenced.classId = TypeRelationId;
(gdb)
647 referenced.objectId = allParams[i];
(gdb)
648 referenced.objectSubId = 0;
(gdb)
649 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
(gdb)
652 if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
(gdb)
644 for (i = 0; i < allParamCount; i++)
(gdb)
646 referenced.classId = TypeRelationId;
(gdb)
647 referenced.objectId = allParams[i];
(gdb)
648 referenced.objectSubId = 0;
(gdb)
649 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
(gdb)
652 if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
(gdb)
644 for (i = 0; i < allParamCount; i++)
(gdb)
646 referenced.classId = TypeRelationId;
(gdb)
647 referenced.objectId = allParams[i];
(gdb)
648 referenced.objectSubId = 0;
(gdb)
649 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
(gdb)
652 if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
(gdb)
644 for (i = 0; i < allParamCount; i++)
(gdb)
646 referenced.classId = TypeRelationId;
(gdb)
647 referenced.objectId = allParams[i];
(gdb)
648 referenced.objectSubId = 0;
(gdb)
649 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
(gdb)
652 if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
(gdb)
644 for (i = 0; i < allParamCount; i++)
(gdb)
646 referenced.classId = TypeRelationId;
(gdb)
647 referenced.objectId = allParams[i];
(gdb)
648 referenced.objectSubId = 0;
(gdb)
649 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
(gdb)
652 if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
(gdb)
644 for (i = 0; i < allParamCount; i++)
(gdb)
662 if (parameterDefaults)
(gdb)
667 if (OidIsValid(prosupport))
(gdb)
676 if (!is_update)
(gdb)
680 if (!is_update)
(gdb)
685 recordDependencyOnCurrentExtension(&myself, is_update);
(gdb)
687 heap_freetuple(tup);
(gdb)
690 InvokeObjectPostCreateHook(ProcedureRelationId, retval, 0);
(gdb)
692 table_close(rel, RowExclusiveLock);
(gdb)
执行函数body验证
695 if (OidIsValid(languageValidator))
(gdb)
697 ArrayType *set_items = NULL;
(gdb)
698 int save_nestlevel = 0;
(gdb)
701 CommandCounterIncrement();
(gdb)
713 if (check_function_bodies)
(gdb)
(gdb) n
715 set_items = (ArrayType *) DatumGetPointer(proconfig);
(gdb)
716 if (set_items)
(gdb)
726 OidFunctionCall1(languageValidator, ObjectIdGetDatum(retval));
(gdb)
728 if (set_items)
(gdb)
732 return myself;
(gdb)
733 }
(gdb)
完成调用
(gdb)
733 }
(gdb)
CreateFunction (pstate=0x1199c88, stmt=0x11748c8) at functioncmds.c:1176
1176 }
(gdb)
“分析PostgreSQL CreateFunction中的ProcedureCreate函数”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!