背景
面向过程编程和面向对象编程,是两种编程的思维方式。在数据库中编程,大多都是用的存储过程,但是Oracle也支持面向对象的编程方式,即在自定义type中,包含constructor function、member function的声明及定义。这种方式,能够较为轻松地使用其他面向对象编程的语言进行相互移植,虽然语法上有所区别,但是重要的是主体逻辑基本不用变,甚至oracle也提供了其他开发语言对这种type对象直接调用的支持。
《ORACLE对象关系开发人员指南》
所以,实际上也存在很多在oracle中使用面向对象编程的代码。
在openGauss3.0中,是不支持这种功能的,所以本文来讨论,如何在openGauss3.0中进行改写以支持类似的功能。
改写的例子
例一:
CREATE OR REPLACE TYPE MESSAGE_RET_T AS OBJECT (
RET_CODE VARCHAR2 (6 CHAR),
RET_MESSAGE VARCHAR2 (512 CHAR),
CONSTRUCTOR FUNCTION MESSAGE_RET_T RETURN SELF AS RESULT
)
/
CREATE OR REPLACE TYPE BODY MESSAGE_RET_T
AS
CONSTRUCTOR FUNCTION MESSAGE_RET_T RETURN SELF AS RESULT
IS
BEGIN
RETURN;
END;
END;
/
--使用(指定参数位置)
declare
a MESSAGE_RET_T:=MESSAGE_RET_T(RET_CODE=>'a1',RET_MESSAGE=>'b1');
begin
dbms_output.put_line(a.RET_CODE||'-'||a.RET_MESSAGE);
end;
在MESSAGE_RET_T这个type中,有一个构建函数返回了自己,也就是说,通过这一段
MESSAGE_RET_T(RET_CODE=>‘a1’,RET_MESSAGE=>‘b1’) 生成了一个对象,对象的类型为MESSAGE_RET_T。
如果在openGauss3.0中也要支持这样的功能,那么MESSAGE_RET_T就只能是个函数,因为openGauss中的type不能指定参数位置传参。
这样就变成了,既要有MESSAGE_RET_T这个函数,也得有MESSAGE_RET_T这个TYPE,很明显不行。而且如果一个type内的函数很多,不同type间有重名函数,这样必然会导致混乱和冲突。
所以这个时候可以引入package的概念,于是乎,我尝试在openGauss中创建一个type,并用这个type创建同名的package,package中必须包含一个self函数,用于返回同名package对象,比如
CREATE TYPE MESSAGE_RET_T AS (
RET_CODE VARCHAR2 (6 ), -- 交易返回代码
RET_MESSAGE VARCHAR2 (512 ) -- 交易返回信息
);
create or REPLACE package MESSAGE_RET_T is
function SELF(RET_CODE varchar2,RET_MESSAGE VARCHAR2) return MESSAGE_RET_T;
end MESSAGE_RET_T;
create or replace package body MESSAGE_RET_T is
function SELF(RET_CODE varchar2,RET_MESSAGE VARCHAR2) return MESSAGE_RET_T is
val MESSAGE_RET_T;
begin
val.RET_CODE:=RET_CODE;
val.RET_MESSAGE:=RET_MESSAGE;
return val;
end;
end MESSAGE_RET_T;
--使用(指定参数位置)
declare
a MESSAGE_RET_T:=MESSAGE_RET_T.self(RET_CODE=>'a1',RET_MESSAGE=>'b1');
begin
dbms_output.put_line(a.RET_CODE||'-'||a.RET_MESSAGE);
end;
例二
如果type中存在返回self以外的其他函数,那么改写时,需要对其他函数都增加一个参数,用于把self生成的对象传入,比如,在oracle中
CREATE OR REPLACE TYPE MESSAGE_LOCAL_HEADER_T
AS OBJECT(
RET MESSAGE_RET_A, -- 交易结果
CONSTRUCTOR FUNCTION MESSAGE_LOCAL_HEADER_T RETURN SELF AS RESULT,
MEMBER FUNCTION getRet RETURN MESSAGE_RET_A
);
CREATE OR REPLACE TYPE BODY MESSAGE_LOCAL_HEADER_T
AS
CONSTRUCTOR FUNCTION MESSAGE_LOCAL_HEADER_T RETURN SELF AS RESULT
IS
BEGIN
RETURN;
END;
MEMBER FUNCTION getRet RETURN MESSAGE_RET_A
IS
BEGIN
RETURN RET;
END;
END;
/
--使用
declare
a MESSAGE_LOCAL_HEADER_T;
b MESSAGE_RET_A:= MESSAGE_RET_A();
c MESSAGE_RET_T;
r1 MESSAGE_RET_A:= MESSAGE_RET_A();
r2 varchar2(20);
begin
c := MESSAGE_RET_T(RET_CODE => 'a1', RET_MESSAGE => 'b1');
b.extend;
b(1) := c;
a := MESSAGE_LOCAL_HEADER_T(ret => b);
r1 := a.getRet;
r2:=r1(1).RET_CODE;
dbms_output.put_line(r2);
end;
改写后
CREATE TYPE MESSAGE_LOCAL_HEADER_T
AS (
RET MESSAGE_RET_A
);
create or replace package MESSAGE_LOCAL_HEADER_T is
function self(RET MESSAGE_RET_A ) return MESSAGE_LOCAL_HEADER_T;
function getRet(self MESSAGE_LOCAL_HEADER_T) return MESSAGE_RET_A;
end MESSAGE_LOCAL_HEADER_T;
create or replace package body MESSAGE_LOCAL_HEADER_T is
function self(RET MESSAGE_RET_A ) return MESSAGE_LOCAL_HEADER_T is
val MESSAGE_LOCAL_HEADER_T;
begin
val:=MESSAGE_LOCAL_HEADER_T(RET);
return val;
end;
function getRet(self MESSAGE_LOCAL_HEADER_T) return MESSAGE_RET_A is
begin
return self.RET;
end;
end MESSAGE_LOCAL_HEADER_T;
--使用
declare
a MESSAGE_LOCAL_HEADER_T;
b MESSAGE_RET_A;
c MESSAGE_RET_T;
r1 MESSAGE_RET_A;
r2 varchar2(20);
begin
c := MESSAGE_RET_T.self(RET_CODE => 'a1', RET_MESSAGE => 'b1');
b.extend;
b(1) := c;
a := MESSAGE_LOCAL_HEADER_T.self(ret => b );
r1:= MESSAGE_LOCAL_HEADER_T.getRet(a);
r2:= r1(1).RET_CODE;
dbms_output.put_line(r2);
end;
其实这个改写的语法结构,很像python中的class,因为它函数的第一个参数也是self,只是不需要再传自己进来。这种改写方式,除了创建命令有所区别外,调用方式也只有一点点区别
例三
如果例一中,不需要指定参数位置,那么其实也不需要创建package,比如,在oracle中
CREATE OR REPLACE TYPE MESSAGE_RET_T AS OBJECT (
RET_CODE VARCHAR2 (6 CHAR), -- 交易返回代码
RET_MESSAGE VARCHAR2 (512 CHAR), -- 交易返回信息
CONSTRUCTOR FUNCTION MESSAGE_RET_T RETURN SELF AS RESULT
);
CREATE OR REPLACE TYPE BODY MESSAGE_RET_T
AS
CONSTRUCTOR FUNCTION MESSAGE_RET_T RETURN SELF AS RESULT
IS
BEGIN
RETURN;
END;
END;
/
--使用(不指定参数位置)
declare
a MESSAGE_RET_T:=MESSAGE_RET_T('a1','b1');
begin
dbms_output.put_line(a.RET_CODE||'-'||a.RET_MESSAGE);
end;
改写后
CREATE TYPE MESSAGE_RET_T AS (
RET_CODE VARCHAR2 (6 ),
RET_MESSAGE VARCHAR2 (512 )
);
--使用(不指定参数位置)
declare
a MESSAGE_RET_T:=MESSAGE_RET_T('a1','b1');
begin
dbms_output.put_line(a.RET_CODE||'-'||a.RET_MESSAGE);
end;
可以发现,使用上是一模一样的.
总结
- 创建type和同名package
- package中放一个self函数用来构造同名type对象
- 从原有type中将其他函数都放在package中,并且在函数的参数的第一个位置增加一个参数,指定类型为改type
- 使用时,返回自己使用 包名.self 函数
- 调用成员函数时,改写成调用 包名.函数的方式,并把原type对象作为第一个参数,其他参数往后排
理论上,这可以算一个通用改写规则了,硬生生用面向过程的处理方式,实现了面向对象的改写,但在编程思想上,仍保留有面向对象的痕迹。