kaiyun官方注册
您所在的位置: 首页> 嵌入式技术> 业界动态> GKD-Base PL/SQL存储函数实现的关键技术研究

GKD-Base PL/SQL存储函数实现的关键技术研究

2008-10-24
作者:高朝瑞 熊伟 陈宏盛  翟玉人

摘 要:介绍具有自主知识产权的某安全数据库管理系统" title="管理系统">管理系统GKD-Base" title="GKD-Base">GKD-Base的PL/SQL引擎,基于该引擎研究GKD-Base存储函数机制实现的关键技术。设计了函数管理器" title="管理器">管理器和执行状态堆栈,通过语法树表示存储函数编译后生成的中间代码,并解决了中间代码的执行问题。最后实现了IN、OUT、INOUT三种参数模式函数的参数传递机制。
关键词:存储函数 过程式SQL语言 PL/SQL 函数管理器 执行状态堆栈


  GKD-Base是一个具有自主知识产权的安全数据库管理系统。经过十几年的发展和试点应用,已经证明该系统是稳定和安全可靠的。但是,随着应用需求向深度和广度的发展,GKD-Base在很多方面暴露出不足,如不支持过程式SQL语言、不支持存储过程和函数、缺少触发器功能,这都为GKD-Base数据库管理系统的进一步推广应用带来了不小的障碍。由于目前数据库管理系统产品大都支持过程式的SQL语言,如Oracle的PL/SQL、SQL Server的T-SQL、PostgreSQL的PL/pgSQL等,其中又以Oracle的PL/SQL使用最为广泛。因此,为使GKD-Base适应应用需求的发展,满足数据库管理和开发人员的需求,笔者开发了GKD-Base PL/SQL引擎,进而在引擎的基础上实现了GKD-Base存储函数机制。GKD-Base PL/SQL引擎兼容了Oracle PL/SQL V2.3语言规范,可以使用变量、类型、表达式、条件和循环、过程和函数等过程式语言中的通用机制进行程序设计,同时也可以使用SQL 语句进行数据操作。
  存储函数是独立存在于表之外的数据库对象,是由用户按照存储函数语言规范编写的经过数据库分析和编译的PL/SQL程序,它有输入/输出参数和返回值,可以被其它应用程序调用。存储函数可以避免重复编码,具有更高的可靠性和效率,是数据库管理系统的重要功能扩充。本文在介绍GKD-Base PL/SQL引擎设计框架的基础上,重点探讨GKD-Base存储函数实现的关键技术。
1 GKD-Base PL/SQL引擎的体系结构
  GKD-Base采用客户服务器结构,以多线程机制支持客户访问,登录到数据库的用户实际上是运行数据库管理系统映像的一个线程。GKD-Base支持TCP/IP协议,客户可以在任何TCP/IP网络和服务器建立连接访问数据库。GKD-Base API提供了用户应用、实用程序和第三方开发工具三种接入数据库的方式,并提供了两个级别的接口,即GKD-Base内部接口GKD-API和GKD-Base ODBC Driver。GKD-API是用户访问数据库的高效调用方式,用户可利用GKD-API实现实时性要求较高的数据库应用,起到比较理想的效果。GKD-Base ODBC Driver是GKD-Base开放连接的手段,符合ODBC扩展一级。在GKD-Base的ODBC驱动程序支持下,用户可利用各种第三方开发工具,方便地开发GKD-Base数据库应用程序。
  GKD-Base PL/SQL引擎应用模式如图1。PL/SQL语句块通过PL/SQL编译器编译成语法树形式表示的中间代码,中间代码信息可以保存在DBMS的中间代码库中。客户应用程序中的请求由GKD-Base预处理器分离成为过程语句部分和SQL命令部分。过程语句解释执行器" title="执行器">执行器解释执行过程语句的中间代码,SQL命令则直接调用GKD-API执行。


  根据PL/SQL语言兼有过程式语句和SQL语句的特点,GKD-Base PL/SQL引擎把过程式语句和SQL语句分开处理。为了降低引擎实现的复杂程度,尽可能地从功能性的角度将整个引擎划分为不同的处理模块,每个处理模块尽可能地降低相互间的耦合程度。其中最主要的划分是将整个引擎划分为前端编译器和后端解释执行器。
  前端编译器包括语言预处理、SQL语句分析、过程语句分析以及中间代码生成等功能。PL/SQL语句块输入后,由语言预处理功能分离成SQL语句和过程语句。对于SQL语句,由GKD-Base SQL引擎解析后,建立SQL语句结点,进行相应的变量绑定和语法检查,检查无误后产生语法树形式的中间代码。对于过程语句,将语句成分进行语法分析,在分析中把声明的变量和数据类型加入到符号表中,同时也产生语法树形式的中间代码。
  后端解释执行器的功能就是对前端编译器生成的中间代码进行解释执行。因此在结构上,解释执行器与编译器对应,也有过程语句执行模块和SQL语句执行模块。过程语句解释执行器解释执行过程语句的中间代码,SQL命令则直接由SQL引擎执行。另外,解释器还包括例外处理模块,负责错误检查和报告。
  符号表是一个包含程序中的变量、自定义类型和函数信息的数据库,它是GKD-Base PL/SQL引擎的核心数据结构。它以一个关键字域(通常是一个符号的名字)为索引,一个关键字域的值对应于库中的一条记录。每条记录,即数据库的一个项目,都对应着一个符号的信息,如变量的类型或函数的返回值等。
2 GKD-Base PL/SQL过程与函数语言成分设计
子程序" title="子程序">子程序把功能独立并需要反复用到的代码加以参数化处理,从而整合为一个命名模块,在要使用这个模块时就传入具体参数值(如果需要)进行调用。与第三代语言中的子程序一样,GKD-Base PL/SQL过程与函数是特定功能的逻辑抽象,由子程序定义和引用组成,可以分为过程和函数两种类别。PL/SQL子程序可以分开编译并存储到数据库中,用CREATE 语句建立,并成为模式对象的一部分。存储过程/函数可以位于包结构中,包分为包声明和包体实现部分。本地过程/函数在DECLARE 节中的最后部分进行,而作为存储过程和存储函数将存储在数据库中,它们是命名的PL/SQL块。这样它们在结构上也就没有特别之处了。下面主要讨论形参和实参的绑定设计。
  PL/SQL 函数中的参数说明带有输入和输出描述,需要在编译时刻进行处理,并对调用时的参数类型是否相容进行检查。但在PL/SQL过程和函数的声明中,限制CHAR、VARCHAR2参数的长度以及限制NUMBER的精度都是非法的。这是由于在调用一个函数时,实际参数的取值被传递进去,在函数内部通过使用形式参数来引用这些实际参数,不仅实际数值被传递进去,而且作为参数传递机制的一部分,对于这些变量的限制也被传递进去。例如:
  创建函数:
  CREATE OR REPLACE FUNCTION ParameterLength (p_Var1 IN OUT VARCHAR2)
  RETURN VARCHAR2 AS
  BEGIN
    p_Var1:=′This is an example′;
    RETURN p_Var1; 
  END ParameterLength;
  调用函数:
  DECLARE
  v_Variable1 VARCHAR (30);
  v_Variable2 VARCHAR (30);
  BEGIN
    v_Variable2:= ParameterLength (v_Variable1);
  END;
  上述调用将使p_Var1变量的最大长度为30(来自实参)。而如果将v_Variable1的长度定义成10,将产生错误。这个错误的产生不是过程设计不好,而是因为调用该过程的代码发生了问题。值得注意的是:在过程定义中如果使用%TYPE作为形参类型的限制,而基准类型又是受限的,那么该限制将作用于形式参数而不是实际参数。
  在缺省参数值的处理方式上,即当调用时,没有指明实际值时,就用缺省值。对使用缺省值的形式参数,通常需要把它们放到参数表的末尾,这样无论使用位置标识法还是带名标识法都可以。
3 GKD-Base存储函数实现的关键技术
3.1函数管理器

  GKD-Base PL/SQL引擎对PL/SQL程序的解释执行都通过一个内部统一的入口,解释器把语法树载入到入口内部的一个默认的主控函数,由这个函数驱动对其解释执行。这样处理的主要目的是为了兼容子程序的语法分析和保持解释器运转机制的一致性。因为子程序就是PL/SQL程序中命名的语句块,用户调用子程序时,PL/SQL引擎可以再次调用内部主控函数对其进行处理,并提交解释器执行。
  PL/SQL子程序包括局部声明子程序和存储子程序。为了管理和调用局部声明子程序,设计了函数管理器,如图2。函数管理器内部定义了一系列数据结构来描述局部声明子程序调用的各种属性,提供各种宏调用来获取局部声明子程序调用的参数和返回值,并提供必要的支持函数。从某种角度看,函数管理器也是用来存放各种局部声明函数和过程信息的“符号表”,因为在管理器中还包括对声明子程序声明和调用信息的存储、查找等功能。函数管理器提供一种结构化的处理手段,以一种统一的方式实现对局部声明子程序的调用。这种“统一”表现在:每个局部声明子程序都在函数管理器中获得一个识别号(Oid),这个识别号唯一标识该局部声明子程序;函数管理器维持一份记录表格,其中存放所有局部声明子程序的识别号、子程序名、子程序地址、参数个数和返回值类型等信息,同时接口还提供相应的查找功能,源程序中的局部声明子程序调用会被转换成为子程序的识别号并通过该识别号查找得到子程序的详细信息。


  所有局部声明子程序都在函数管理器中进行注册,存储子程序并不在函数管理器中注册,而是保存在数据库中。对于一个子程序调用,首先根据子程序名到函数管理器中查找;如果没有在函数管理器中找到再去查找数据库,这样就兼容了Oracle PL/SQL V2.3局部声明子程序的定义优先于存储子程序的规则。
3.2 执行状态堆栈
  在GKD-Base PL/SQL引擎的解释执行器中,设计了一个 “执行状态”的数据结构,其中记载了存储函数的执行状态信息,如函数中的声明变量、函数的参数个数、函数的参数类型、函数每个参数的值、函数的返回值等。解释器中所有的解释函数都拥有一个指针参数指向这个结构,需要在这些函数间传递的信息(包括全局变量信息)将被拷贝到这个结构。考虑到存储函数是个单独的PL/SQL语句块层次,因此设计了执行状态堆栈。在调用存储函数前,对当前的现场进行保存,也就是将当前的执行状态压栈;在存储函数调用结束之后恢复保存的现场,弹出当前执行状态,回到上一层执行状态继续执行,如图3。


3.3 存储函数的创建和调用
  用户创建的存储函数经过编译得到中间代码并保存在GKD-Base的中间代码库中。函数调用时,根据函数名到GKD-Base的中间代码库中查找相应存储函数的中间代码对其解释执行,最后得到返回值。GKD-Base存储函数的创建和调用分别对应中间代码的产生和执行过程。
  PL/SQL引擎对存储函数的创建的具体处理过程如下:
  ①编译器得到用户创建的存储函数;
  ②对用户创建的存储过程进行编译,如果编译通过则生成该存储函数的语法树;否则向用户报告错误;
  ③将存储函数中间代码保存在中间代码库中;存储函数的源代码保存在GKD-Base的系统表中,如表1;
  ④向用户返回消息。
  根据Oracle PL/SQL V2.3 语言规范,存储函数调用本身不是一个语句,它只能作为其它语句的一部分。PL/SQL引擎对存储函数的调用的具体过程如图4:
  ①解释器中语句链的解释函数调用存储函数的解释函数;
  ②解释器在执行存储函数之前,对当前的现场进行保存,即将当前的执行状态压栈;
  ③到数据库的中间代码库中找到存储函数的语法树,把存储函数的语法树挂在整个PL/SQL语句块语法树的过程调用节点上,并提交解释器;
  ④解释器执行存储函数中间代码;
  ⑤解释器在存储函数调用结束后,将返回值返回给调用它的语句,并恢复保存的现场,弹出当前执行状态,回到上一层执行状态继续执行。


3.4 存储函数的参数传递机制
  上述存储函数调用过程中的一个主要难点就是实际参数和形式参数之间值的传递。与其它第三代语言一样,用户可以创建带参数的PL/SQL存储函数。这些参数可以通过值进行传递,也可以通过引用进行传递。
  GKD-Base PL/SQL引擎对存储函数的形式参数的处理与对变量的处理是一致的,每个形式参数的属性信息对应着符号表中的一条记录,形式参数的名字信息保存在相应的名字堆栈层次内。如本文第三部分所述,存储函数的语法树也是由解释器载入到一个内部默认的主控函数,由这个函数驱动对其解释执行。解释器内部的主控函数驱动存储函数中所有变量的初始化,也驱动实际参数和形式参数之间值的传递。编译器解析过程调用语句得到实际参数信息,首先判断实际参数和形式参数的数据类型是否匹配,经过类型检查后,如果实际参数是常量或普通变量,直接将实际参数的值拷贝给形式参数;如果实际参数是表达式,计算表达式的值传给形式参数;如果存储函数的形式参数个数大于解析得到的实际参数个数,二者之差为n,那么前面的形式参数取实际参数的值,后n个形式参数取语法树中对应的缺省值。
  在Oracle PL/SQL V2.3中,存储函数的形式参数有三种模式:IN、OUT或INOUT。当调用函数时,对IN参数,实际参数的值传递给过程,函数结束后,实际参数没有被改变;对OUT参数,实际参数的任何值都被忽略,形式参数有一个NULL值,函数结束后,形式参数的内容赋给实际参数;对INOUT参数,实际参数的值传递给过程,函数结束后,形式参数的内容赋给实际参数。同时在存储函数内部,对IN参数赋值和把OUT参数赋给局部变量都是非法操作。
  在GKD-Base PL/SQL引擎中实现存储函数三种形式参数模式的关键是执行前后实际参数和形式参数的值的相互正确传递。在执行存储函数中间代码前,判断每个形式参数的模式,如果参数模式是IN或INOUT,直接把实际参数的值传递给形式参数;如果模式是OUT,直接赋给形式参数一个初始的NULL值。对函数执行体内的赋值语句进行判断检查,如果赋值目标是IN参数或把OUT参数赋给其它变量,都报告相应的执行错误。函数执行结束后,解释器弹出当前执行状态,回到上一层执行状态继续执行。对IN参数,实际参数直接取其本身的值,即实际参数没有改变;对OUT参数和INOUT参数,根据执行前形式参数和实际参数的对应关系,用执行完毕后的形式参数的值替代原来实际参数的值,即执行完后形式参数的内容赋给实际参数。
  PL/SQL 集成了一般过程式语言和说明性SQL 语言的特点,简洁、高效,而其内容却十分丰富。这也从一个方面说明了作为过程式数据库编程语言,在考虑到底层数据库功能时所面临的各种选择的艰难。PL/SQL 语言的某些功能将随着ORACLE 数据库的发展而继续增强,但是该语言的结构是基本稳定的。本文参照Oracle PL/SQL V2.3 语言规范,在某安全数据库管理系统GKD-Base上开发了PL/SQL引擎作为该系统的重要扩充,并在该引擎基础上实现了GKD-Base存储函数机制,提供给用户一种高效率的编程手段,增强了GKD-Base的功能。希望本文实现的PL/SQL引擎能对国产数据库的开发起到借鉴作用。当然PL/SQL 语言内容十分丰富,本文不可能涉及到每一个设计细节,PL/SQL 还在进一步发展,值得继续关注。
  为了进一步扩展GKD-Base在安全保密领域的应用前景,笔者将在对GKD-Base本身进一步完善的同时,继续对GKD-Base PL/SQL引擎进行完善和改进。一方面在功能上实现封装函数和过程的包;另一方面,在性能上对原有GKD-Base PL/SQL引擎进行优化以提高编译和执行效率。
参考文献
1 Ken Henderson. The Guru′s Guide to Transact-SQL. AddisonWesley PuB Co., 2000
2 PostgreSQL 7.4 Documentation.The PostgreSQL GloBal Development Group, 2003
3 Kenneth C.Louden著,冯博琴译.编译原理及实践.北京:机械工业出版社,2000
4 Hector Garcia-Molina,Jeffrey D. Ullman,Jennifer Widom著.杨东青,唐世渭,徐其钧译. 数据库系统实现. 北京:机械工业出版社,2001
5 Levine,J.R.,lex与yacc(第二版).北京:机械工业出版社,2003

本站内容除特别声明的原创文章之外,转载内容只为传递更多信息,并不代表本网站赞同其观点。转载的所有的文章、图片、音/视频文件等资料的版权归版权所有权人所有。本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如涉及作品内容、版权和其它问题,请及时通过电子邮件或电话通知我们,以便迅速采取适当措施,避免给双方造成不必要的经济损失。联系电话:010-82306118;邮箱:aet@chinaaet.com。
Baidu
map