FAQ的形式分析OpenGauss中package实现基础关键逻辑。
下面四个问题基本将市面上基于postgresql实现package的方法分成了几类。
例如问题一:
- openGauss使用包所在的namespace作为包函数的namespace。
- IvorySQL使用包本身的oid作为包函数的namespace。
- 还有db创建一个新的namespace作为包函数的namespace。
这里对openGauss的实现做一些分析和记录。
问题一:包函数使用谁的namespace?
结论
- 包函数的pronamespace直接使用包创建所在的namespace。
- 通过在pg_proc中新增列、并调整系统表约束来区分普通函数和包函数。
- 其中opengaussdb新增了propackageid来指向当前函数所属的包。
- 其中opengaussdb修改了原有约束,从三元组唯一约束(proname,proargtypes,pronamespace)变成了Oid唯一约束。
- 其他开源实现例如Ivorydb的方案也有不同,Ivorydb使用PkgID直接当做函数的namespace的oid使用,在使用时PkgID等价与一个NamespaceID。
列区别
约束区别
用例
create schema dams_ci;
create or replace package dams_ci.emp_bonus13 is
var5 int:=42;
var6 int:=43;
procedure testpro1();
end emp_bonus13;
/
create or replace package body dams_ci.emp_bonus13 is
var1 int:=46;
var2 int:=47;
procedure testpro1()
is
begin
raise notice 'testpro1 var5: %', var5;
var5 := var5 + 1;
end;
end emp_bonus13;
/
openGauss=# select proname,pronamespace from pg_proc where proname = 'testpro1';
proname | pronamespace
----------+--------------
testpro1 | 16389
(1 row)
openGauss=# select * from pg_namespace where oid = 16389;
nspname | nspowner | nsptimeline | nspacl | in_redistribution | nspblockchain | nspcollation
---------+----------+-------------+--------+-------------------+---------------+--------------
dams_ci | 10 | 0 | | n | f |
(1 row)
问题二:包函数寻找的逻辑?
形如上述实例:
openGauss=# call dams_ci.emp_bonus13.testpro1();
NOTICE: testpro1 var5: 42
在调用时使用了call a.b.c
的形式,即call schema.pkgname.obj
,在实际使用中,也会使用到a
、a.b
、a.b.c.d
的形式,所有.
的解析都集中在DeconstructQualifiedName函数中,总结OpenGaussdb的解析逻辑:
code
void DeconstructQualifiedName(const List* names, char** nspname_p, char** objname_p, char **pkgname_p)
{
char* catalogname = NULL;
char* schemaname = NULL;
char* objname = NULL;
char* pkgname = NULL;
Oid nspoid = InvalidOid;
switch (list_length(names)) {
case 1:
objname = strVal(linitial(names));
break;
// a.b的场景
case 2:
objname = strVal(lsecond(names));
schemaname = strVal(linitial(names));
if (nspname_p != NULL) {
nspoid = get_namespace_oid(schemaname, true);
}
pkgname = strVal(linitial(names));
if (!OidIsValid(PackageNameGetOid(pkgname, nspoid))) {
pkgname = strVal(lsecond(names));
if (OidIsValid(PackageNameGetOid(pkgname, nspoid))) {
schemaname = strVal(linitial(names));
objname = pkgname;
pkgname = NULL;
} else {
pkgname = NULL;
}
} else {
schemaname = NULL;
}
break;
// a.b.c的场景
case 3:
objname = strVal(lthird(names));
pkgname = strVal(lsecond(names));
schemaname = strVal(linitial(names));
if (nspname_p != NULL) {
nspoid = get_namespace_oid(schemaname, true);
}
if (!OidIsValid(PackageNameGetOid(pkgname, nspoid))) {
catalogname = strVal(linitial(names));
schemaname = strVal(lsecond(names));
pkgname = NULL;
break;
}
break;
// a.b.c.d的场景,情况少比较简单
// catalogname是库名,pg特有的叫法
case 4:
catalogname = strVal(linitial(names));
schemaname = strVal(lsecond(names));
pkgname = strVal(lthird(names));
objname = strVal(lfourth(names));
/*
* We check the catalog name and then ignore it.
*/
if (strcmp(catalogname, get_and_check_db_name(u_sess->proc_cxt.MyDatabaseId, true)) != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cross-database references are not implemented: %s", NameListToString(names))));
break;
default:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("improper qualified name (too many dotted names): %s", NameListToString(names))));
break;
}
*nspname_p = schemaname;
*objname_p = objname;
if (pkgname_p != NULL)
*pkgname_p = pkgname;
}
问题三:包SPEC变量编译的流程?
用例
create or replace package dams_ci.emp_bonus14 is
var5 int:=42;
var6 int:=43;
procedure testpro1();
end emp_bonus14;
/
SPEC编译位置
注意pl_gram.y改名了,在这里src/common/pl/plpgsql/src/gram.y
CreatePackageCommand
PackageSpecCreate
PG_TRY
plpgsql_package_validator(pkgOid, true, true)
plpgsql_pkg_compile(packageOid, true, isSpec, isCreate)
PG_TRY
do_pkg_compile
plpgsql_yyparse // 开始编译 src/common/pl/plpgsql/src/gram.y
PG_CATCH
PG_END_TRY
PG_CATCH
PG_END_TRY
SPEC编译文本,注意和用例的差异
这是准本进入gram.y的文本。
PACKAGE
DECLARE
var5 int:=42;
var6 int:=43;
procedure testpro1();
end
SPEC编译流程
src/common/pl/plpgsql/src/gram.y
pl_package_spec : K_PACKAGE { SetErrorState(); } decl_sect K_END
K_DECLARE → decl_start + decl_start → decl_sect
// 编译 var5 int:=42;
T_WORD → decl_varname → decl_varname_list -\
decl_const ---------------------------|
decl_collate ---------------------------|
decl_notnull ---------------------------|
decl_defval ----------------------------|
v
decl_statement
编译完了,结果保存在curr_compile
curr_compile =
{datums_alloc = 0,
datums_pkg_alloc = 256, plpgsql_nDatums = 0,
plpgsql_pkg_nDatums = 2,
datums_last = 2,
datums_pkg_last = 0, plpgsql_error_funcname = 0x0,
plpgsql_error_pkgname = 0x7fb9fc9bbca8 "emp_bonus14",
plpgsql_parse_result = 0x0,
plpgsql_parse_error_result = 0x0,
plpgsql_Datums = 0x7fb9fc401e60,
plpgsql_curr_compile = 0x0,
datum_need_free = 0x7fb9fc9989f0,
plpgsql_DumpExecTree = false,
plpgsql_pkg_DumpExecTree = false,
ns_top = 0x7fb9fbae3e88,
plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL,
yyscanner = 0x0,
core_yy = 0x7fb9fbccfb78,
scanorig = 0x0,
plpgsql_yyleng = 0,
num_pushbacks = 0,
pushback_token = {0, 258, 0 <repeats 98 times>},
cur_line_start = 0x7524070 "INSTANTIATION DECLARE BEGIN NULL; END",
cur_line_end = 0x0,
cur_line_num = 1,
goto_labels = 0x0,
plpgsql_check_syntax = false,
plpgsql_curr_compile_package = 0x7fb9fc138f58, <<<<<<<<<当前编译的包
compile_tmp_cxt = 0x7fb9fbce7818,
compile_cxt = 0x7fb9fbae84b0}
然后把编出来的declare变量记录到pkg中,后面就可以使用了。
do_pkg_compile
pkg->ndatums = curr_compile->plpgsql_pkg_nDatums;
pkg->datums = (PLpgSQL_datum**)palloc(sizeof(PLpgSQL_datum*) * curr_compile->plpgsql_pkg_nDatums);
pkg->datum_need_free = (bool*)palloc(sizeof(bool) * curr_compile->plpgsql_pkg_nDatums);
pkg->datums_alloc = pkg->ndatums;
问题四:依赖包的编译如何实现?
用例
create schema dams_ci;
-- 被依赖包 emp_bonus14
create or replace package dams_ci.emp_bonus14 is
var5 int:=42;
var6 int:=43;
procedure testpro1();
end emp_bonus14;
/
-- 依赖emp_bonus14的变量
create or replace package dams_ci.emp_bonus141231234 is
var5 int:= dams_ci.emp_bonus14.var5;
procedure testpro1();
end emp_bonus141231234;
/
create or replace package body dams_ci.emp_bonus141231234 is
procedure testpro1()
is
begin
raise notice 'testpro1 var5: %', var5;
end;
end emp_bonus141231234;
/
call dams_ci.emp_bonus141231234.testpro1();
上述用例中,emp_bonus141231234包使用的变量var5的值从emp_bonus14包中获取,形成依赖关系。
在执行时编译emp_bonus141231234会处理依赖关系:
call dams_ci.emp_bonus141231234.testpro1()分析
plpgsql_call_handler
PackageInstantiation
plpgsql_package_validator
plpgsql_pkg_compile
do_pkg_compile
plpgsql_yyparse
read_sql_expression
read_sql_construct
read_sql_construct6
plpgsql_yylex
plpgsql_parse_tripword // !!!这里解析 dams_ci.emp_bonus14.var5
plpgsql_pkg_add_unknown_var_to_namespace
GetPackageDatum
compilePackageSpec
plpgsql_package_validator
plpgsql_pkg_compile
do_pkg_compile // !!!这里开始编译依赖包emp_bonus14
*** *** ***
do_pkg_compile这里会把emp_bonus14包拎出来变成字符串:
PACKAGE DECLARE var5 int:=42;\n var6 int:=43;\n procedure testpro1();\nend
进入gram.y转一圈,出来就有变量和值了。
也是延迟编译的流程,用到谁编谁。