前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >openGauss数据库Package原理分析FAQ

openGauss数据库Package原理分析FAQ

作者头像
mingjie
发布2023-05-23 17:57:19
5520
发布2023-05-23 17:57:19
举报
文章被收录于专栏:Postgresql源码分析

FAQ的形式分析OpenGauss中package实现基础关键逻辑。

下面四个问题基本将市面上基于postgresql实现package的方法分成了几类。

例如问题一:

  • openGauss使用包所在的namespace作为包函数的namespace。
  • IvorySQL使用包本身的oid作为包函数的namespace。
  • 还有db创建一个新的namespace作为包函数的namespace。

这里对openGauss的实现做一些分析和记录。

问题一:包函数使用谁的namespace?

结论

  1. 包函数的pronamespace直接使用包创建所在的namespace。
  2. 通过在pg_proc中新增列、并调整系统表约束来区分普通函数和包函数。
    • 其中opengaussdb新增了propackageid来指向当前函数所属的包。
    • 其中opengaussdb修改了原有约束,从三元组唯一约束(proname,proargtypes,pronamespace)变成了Oid唯一约束。
  3. 其他开源实现例如Ivorydb的方案也有不同,Ivorydb使用PkgID直接当做函数的namespace的oid使用,在使用时PkgID等价与一个NamespaceID。

列区别

在这里插入图片描述
在这里插入图片描述

约束区别

在这里插入图片描述
在这里插入图片描述

用例

代码语言:javascript
复制
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)

问题二:包函数寻找的逻辑?

形如上述实例:

代码语言:javascript
复制
openGauss=# call dams_ci.emp_bonus13.testpro1();
NOTICE:  testpro1 var5: 42

在调用时使用了call a.b.c的形式,即call schema.pkgname.obj,在实际使用中,也会使用到aa.ba.b.c.d的形式,所有.的解析都集中在DeconstructQualifiedName函数中,总结OpenGaussdb的解析逻辑:

在这里插入图片描述
在这里插入图片描述

code

代码语言:javascript
复制
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变量编译的流程?

用例

代码语言:javascript
复制
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

代码语言:javascript
复制
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的文本。

代码语言:javascript
复制
PACKAGE  
DECLARE  
  var5 int:=42;
  var6 int:=43;
  procedure testpro1();
end

SPEC编译流程 src/common/pl/plpgsql/src/gram.y

代码语言:javascript
复制
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

代码语言:javascript
复制
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中,后面就可以使用了。

代码语言:javascript
复制
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;                                                                 

问题四:依赖包的编译如何实现?

用例

代码语言:javascript
复制
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()分析

代码语言:javascript
复制
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转一圈,出来就有变量和值了。

也是延迟编译的流程,用到谁编谁。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-05-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题一:包函数使用谁的namespace?
  • 问题二:包函数寻找的逻辑?
  • 问题三:包SPEC变量编译的流程?
  • 问题四:依赖包的编译如何实现?
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档