PLSQL涉及对象类型能力域的一次代码改造案例

文章概述

本文通过某项目一次针对对象类型中一些不支持的功能项进行代码改造为契机,重新回顾和熟悉了对象类型继承,子父对象转换,函数重载等概念和应用,包括集合类型的一些编码应用场景。

通过这个案例可以快速帮助我们熟悉和深刻对PSLQL对象类型和集合类型能力域的掌握。

一,问题背景

相对于Oracle的PLSQL,早期KES的PLSQL在对象类型功能上暂时还缺失以下功能:
1,对象类型的继承:UNDER,NOT FINAL
2,子对象变量赋值给父对象变量实例,父对象变量获得相应的属性
3,treat函数进行对象类型的转换
4,order函数的支持,声明成员函数为order函数,进行实例的排序与比较
5,重载 OVERRIDING ,NOT INSTANTIABLE,抽象类型

此业务涉及到上述五个不支持功能,当时的项目情况来看由于开发兼容涉及较长时间周期,我们对齐进行了分析和改造,下面将进行详细描述。

本文测试的版本为

ORACLE测试版本:
"Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production Version 21.3.0.0.0"

KES测试版本:
KingbaseES V008R006C006B0021 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 
4.1.2 20080704 (Red Hat 4.1.2-46), 64-bit

二,客户代码的模拟缩小案例:

某项目问题的模拟简化例子,子对象和父对象的赋值转换

将要改造的代码的核心逻辑是
1,子对象tabletop嵌套在嵌套表tabletop_table中,初始化后获得值后赋值给基于父对象rectangle 的嵌套表rectangle_table
2,使用函数fp_MergeOrder,对基于父对象的嵌套表rectangle_table进行大小比较,获得从大到小的下标数组(嵌套表),并返回
3,根据下标数组,将子对象的嵌套表tabletop_table重新排序,最终为了能获得有顺序的子对象的嵌套表tabletop_table,

-- 在继承关系下,子对象可以赋值给父对象,父对象将获取到属于自己的属性值
--(后续又可以通过treat函数转换成子类型)
-- 下列案例可直接在oracle执行,运行正确,(KES不支持,需要改造一下)

drop type rectangle force;
drop type tabletop force;

-- 创建基类型
CREATE OR REPLACE TYPE rectangle FORCE AS OBJECT
(
    length number,
    width number,
    NOT FINAL member procedure display,
    --声明为NOT INSTANTIABLE的方法也必须声明为not final,同时该对象类型也必须是NOT INSTANTIABLE类型的
    NOT INSTANTIABLE NOT FINAL member function measure_son( ilbase rectangle ) return number,   
    order member function measure( ilbase rectangle ) return number
) NOT INSTANTIABLE NOT FINAL;
/

-- 创建基类型的主体
CREATE OR REPLACE TYPE BODY rectangle AS
    NOT FINAL MEMBER PROCEDURE display IS
    BEGIN
        dbms_output.put_line('Length: '|| length);
        dbms_output.put_line('Width: '|| width);
    END display;

    order member function measure( ilbase rectangle ) return number is
    begin
        return measure_son( ilbase );
    end;
END;
/

-- 创建子对象类型,继承基对象rectangle
CREATE OR REPLACE TYPE tabletop force UNDER rectangle
(
    perimeter  number(10),
    material varchar2(20),
    overriding member procedure display, --重载:重新编写基类的函数
    overriding member function measure_son( ilbase rectangle ) return number
)
/

-- 为子对象tabletop创建类型主体
CREATE OR REPLACE TYPE BODY tabletop AS
    OVERRIDING MEMBER PROCEDURE display IS
    BEGIN
        dbms_output.put_line('Length: '|| length);
        dbms_output.put_line('Width: '|| width);
        dbms_output.put_line('perimeter: '|| perimeter);
        dbms_output.put_line('material: '|| material);
    END display;

    overriding member function measure_son( ilbase rectangle ) return number is
        vl_Row tabletop; 
        vl_rectangle_self number(10);
        vl_rectangle_in number(10);
    begin
        vl_Row := treat(ilbase as tabletop);
        vl_rectangle_in := vl_Row.perimeter;
        vl_rectangle_self := self.perimeter;

        if vl_rectangle_self > vl_rectangle_in then
            return 1;
        elsif vl_rectangle_self < vl_rectangle_in then
            return -1;
        end if;
        return -1;
    end;
END;
/

--对嵌套表进行排序
create or replace function fp_MergeOrder( il_tab_rectangle_Base in rectangle_table ) return Tab_Ordered is
      recOrdered1 Tab_Ordered := Tab_Ordered();
      recOrdered2 Tab_Ordered := Tab_Ordered();
      vl_objCount number(8);
      vl_step     number(8);
      vl_i        number(8);
      vl_j        number(8);
      vl_k        number(8);
      vl_end1     number(8);
      vl_end2     number(8);
begin
  vl_step := 1;                     --分割步进,从1开始,每次乘2,层层递进
  vl_objCount := il_tab_rectangle_Base.Count;    --元素总个数
  recOrdered1.extend(vl_objCount);  --recOrdered1存储上一次排序结果
  recOrdered2.extend(vl_objCount);  --recOrdered2存储本次排序结果

  for i in 1..vl_objCount loop      --recOrdered1初始为元素下标自然序
        recOrdered1(i) := i;
  end loop;

  while vl_step < vl_objCount loop  --step最开始是1,分割到1个元素,每往上一层乘2,直到折半时就是最后一遍合并了
    vl_i := 1;
    vl_j := vl_i + vl_step;
    vl_k := vl_i;

    if vl_i - 1 + vl_step < vl_objCount then vl_end1 := vl_i - 1 + vl_step; else vl_end1 := vl_objCount; end if;  --vl_end1为第一段的最后一个元素位置
    if vl_j - 1 + vl_step < vl_objCount then vl_end2 := vl_j - 1 + vl_step; else vl_end2 := vl_objCount; end if;  --vl_end2为第二段的最后一个元素位置

    while vl_i <= vl_objCount loop  --第一段起始位最远就是最后一个元素

        while vl_i <= vl_end1 and vl_j <= vl_end2 loop  --第一段与第二段逐个元素比对,小的放前面,大的放后面
        if il_tab_rectangle_Base(recOrdered1(vl_i)) < il_tab_rectangle_Base(recOrdered1(vl_j)) then
          recOrdered2(vl_k) := recOrdered1(vl_i);
          vl_i := vl_i + 1;

        else
          recOrdered2(vl_k) := recOrdered1(vl_j);
          vl_j := vl_j + 1;
        end if;
        vl_k := vl_k + 1;
      end loop;                                       --本循环退出后,第一段与第二段只会有一个剩余有元素,将剩余元素入队

      while vl_i <= vl_end1 loop                      --第一段剩余元素入队
            recOrdered2(vl_k) := recOrdered1(vl_i);
            vl_i := vl_i + 1;
            vl_k := vl_k + 1;
      end loop;

      while vl_j <= vl_end2 loop                      --第二段剩余元素入队
            recOrdered2(vl_k) := recOrdered1(vl_j);
            vl_j := vl_j + 1;
            vl_k := vl_k + 1;
      end loop;

      vl_i := vl_end2 + 1;     --上一波完后,将第一段指针移到上一次第二段之后
      vl_j := vl_i + vl_step;  --将第二段指针移到(第一段+步进)
      vl_k := vl_i;            --本波有序元组的指针也偏移

      if vl_i - 1 + vl_step < vl_objCount then vl_end1 := vl_i - 1 + vl_step; else vl_end1 := vl_objCount; end if;  --vl_end1为第一段的最后一个元素位置
      if vl_j - 1 + vl_step < vl_objCount then vl_end2 := vl_j - 1 + vl_step; else vl_end2 := vl_objCount; end if;  --vl_end2为第二段的最后一个元素位置
    end loop;
    vl_step := vl_step*2;      --一层完结,上一层步进要翻倍
    recOrdered1 := recOrdered2;--recOrdered2为本次排完序的,覆盖recOrdered1上次排完序的,上一层再开始
  end loop;

  return recOrdered1;
end;

--嵌套集合使用
CREATE OR REPLACE TYPE tabletop_table is table of tabletop;
CREATE OR REPLACE TYPE rectangle_table is table of rectangle;
CREATE OR REPLACE TYPE Tab_Ordered is table of int;

-- 模拟测试代码
DECLARE
    t1 tabletop;
    t2 tabletop;
    t3 tabletop;
    t4 tabletop;

    ttab tabletop_table := tabletop_table();
    rtab rectangle_table := rectangle_table();

    vtab_orderd Tab_Ordered;
BEGIN
    t1 := tabletop(10,20,60,'wood');
    t2 := tabletop(30,40,140,'plastic');
    t3 := tabletop(25,33,116,'soil');
    t4 := tabletop(15,15,125,'water');

    ttab.extend(4);
    rtab.extend(4);
    ttab(1) := t1;
    ttab(2) := t2;
    ttab(3) := t3;
    ttab(4) := t4;

    for i in 1..4 loop
        rtab(i) := ttab(i); --子对象实例赋值给基对象实例,基对象获取到自己的属性(子类的属性获取不到)
        dbms_output.put_line( rtab(i).length );
    end loop;

    --dbms_output.put_line( rtab(1).material0 ); --rtab不存在material0变量,oracle会报错

    ttab.delete;

    vtab_orderd := fp_MergeOrder(rtab);    --进行排序,获取vtab_orderd为顺序的下标

    for i in vtab_orderd.First..vtab_orderd.Last loop   
        ttab.extend();
        ttab(i) := treat( rtab(vtab_orderd(i) ) as tabletop);    ---基类对象实例转化为子对象,赋值给新的子对象
        dbms_output.put_line( ttab(i).perimeter );  --打印有顺序的结果
    end loop;
END;

oracle执行结果(KES暂不支持上述部分核心功能):

10           --仅打印长度
30
25
15
60           --打印有顺序的结果
116
125
140

PL/SQL 过程已成功完成。

三,KES中的改造

上述使用到对象类型的代码案例中,我们观察到客户一般只使用子对象,父对象仅仅用来被继承。这种情况下改造时就可以考虑将子对象和父对象独立(核心思路)。

为了获得有顺序的ttab表,代码改造方案如下:

drop type rectangle force;
drop type tabletop force;

-- 创建基类型
-- 改写:去掉不支持的NOT INSTANTIABLE和NOT FINAL,将基类完全独立创建,不再走继承
-- 将独立出去的measure_son和measure函数删除

create or replace type rectangle force as object
(
    length number,
    width number,
    member procedure display
);
/

-- 创建基类型的主体
create or replace type body rectangle as
    member procedure display is
    begin
        dbms_output.put_line('length: '|| length);
        dbms_output.put_line('width: '|| width);
    end;
end;
/

--将measure函数独立
create or replace function measure( ilbase rectangle ) return number is
begin
    return measure_son( ilbase );
end;

-- 创建子对象类型
-- 将原父对象的属性移到子对象中,就如同继承一样的效果
-- 去掉不支持的OVERRIDING关键字
-- 将独立出去的measure_son函数删除
-- 删除继承under
create or replace type tabletop force as object
(
    length number,   --这是原父类的属性
    width number,
    perimeter  number(10),
    material varchar2(20),
    member procedure display --重载:重新编写基类的函数
)
/

-- 将原order函数独立出来,
-- 因为原本就是用的是子类的属性进行比较和判断,所以不再使用treat函数来进行类型转换,直接传入子类对象即可
-- 改写原order函数,注意形参新增加了一个
create or replace function measure_son( ilbase_self tabletop,ilbase_in tabletop ) return number is
    --vl_Row tabletop;    --删除,不再做转换
    vl_rectangle_self number(10);
    vl_rectangle_in number(10);
begin
    --vl_Row := treat(ilbase as tabletop);   --删除,不再做转换
    vl_rectangle_in := ilbase_in.perimeter;
    vl_rectangle_self := ilbase_self.perimeter;

    if vl_rectangle_self > vl_rectangle_in then
        return 1;
    elsif vl_rectangle_self < vl_rectangle_in then
        return -1;
    end if;
    return -1;
end;


-- 为子对象tabletop创建类型主体
CREATE OR REPLACE TYPE BODY tabletop AS
    MEMBER PROCEDURE display IS
    BEGIN
        dbms_output.put_line('Length: '|| length);
        dbms_output.put_line('Width: '|| width);
        dbms_output.put_line('perimeter: '|| perimeter);
        dbms_output.put_line('material: '|| material);
    END;
END;
/


--嵌套集合使用
CREATE OR REPLACE TYPE tabletop_table is table of tabletop;
CREATE OR REPLACE TYPE rectangle_table is table of rectangle;
CREATE OR REPLACE TYPE Tab_Ordered is table of int;

-- 对嵌套表进行排序
-- 由于实际的order比较使用的是原子类的属性进行比较,这里不再传递原父类,直接使用现定义的
create or replace function fp_MergeOrder( il_tab_tabletop_Base in tabletop_table ) return Tab_Ordered is
      recOrdered1 Tab_Ordered := Tab_Ordered();
      recOrdered2 Tab_Ordered := Tab_Ordered();
      vl_objCount number(8);
      vl_step     number(8);
      vl_i        number(8);
      vl_j        number(8);
      vl_k        number(8);
      vl_end1     number(8);
      vl_end2     number(8);
      vresult     int;
begin
      vl_step := 1;                     --分割步进,从1开始,每次乘2,层层递进
      vl_objCount := il_tab_tabletop_Base.Count;    --元素总个数
      recOrdered1.extend(vl_objCount);  --recOrdered1存储上一次排序结果
      recOrdered2.extend(vl_objCount);  --recOrdered2存储本次排序结果

      for i in 1..vl_objCount loop      --recOrdered1初始为元素下标自然序
            recOrdered1(i) := i;
      end loop;

      while vl_step < vl_objCount loop  --step最开始是1,分割到1个元素,每往上一层乘2,直到折半时就是最后一遍合并了
        vl_i := 1;
        vl_j := vl_i + vl_step;
        vl_k := vl_i;

        if vl_i - 1 + vl_step < vl_objCount then vl_end1 := vl_i - 1 + vl_step; else vl_end1 := vl_objCount; end if;  --vl_end1为第一段的最后一个元素位置
        if vl_j - 1 + vl_step < vl_objCount then vl_end2 := vl_j - 1 + vl_step; else vl_end2 := vl_objCount; end if;  --vl_end2为第二段的最后一个元素位置

        while vl_i <= vl_objCount loop  --第一段起始位最远就是最后一个元素

            while vl_i <= vl_end1 and vl_j <= vl_end2 loop  --第一段与第二段逐个元素比对,小的放前面,大的放后面
            ----不再使用order函数,即不再使用对象进行进行比较
            --使用原order函数独立出来的函数进行比较
            vresult := measure_son(il_tab_tabletop_Base(recOrdered1(vl_i)),il_tab_tabletop_Base(recOrdered1(vl_j)));
            if vresult = -1 then
              recOrdered2(vl_k) := recOrdered1(vl_i);
              vl_i := vl_i + 1;

            elsif vresult = 1 then
              recOrdered2(vl_k) := recOrdered1(vl_j);
              vl_j := vl_j + 1;
            end if;
            vl_k := vl_k + 1;
          end loop;                                       --本循环退出后,第一段与第二段只会有一个剩余有元素,将剩余元素入队

          while vl_i <= vl_end1 loop                      --第一段剩余元素入队
                recOrdered2(vl_k) := recOrdered1(vl_i);
                vl_i := vl_i + 1;
                vl_k := vl_k + 1;
          end loop;

          while vl_j <= vl_end2 loop                      --第二段剩余元素入队
                recOrdered2(vl_k) := recOrdered1(vl_j);
                vl_j := vl_j + 1;
                vl_k := vl_k + 1;
          end loop;

          vl_i := vl_end2 + 1;     --上一波完后,将第一段指针移到上一次第二段之后
          vl_j := vl_i + vl_step;  --将第二段指针移到(第一段+步进)
          vl_k := vl_i;            --本波有序元组的指针也偏移

          if vl_i - 1 + vl_step < vl_objCount then vl_end1 := vl_i - 1 + vl_step; else vl_end1 := vl_objCount; end if;  --vl_end1为第一段的最后一个元素位置
          if vl_j - 1 + vl_step < vl_objCount then vl_end2 := vl_j - 1 + vl_step; else vl_end2 := vl_objCount; end if;  --vl_end2为第二段的最后一个元素位置
        end loop;
        vl_step := vl_step*2;      --一层完结,上一层步进要翻倍
        recOrdered1 := recOrdered2;--recOrdered2为本次排完序的,覆盖recOrdered1上次排完序的,上一层再开始
      end loop;

      return recOrdered1;
end;

模拟测试代码

DECLARE
    t1 tabletop;
    t2 tabletop;
    t3 tabletop;
    t4 tabletop;

    ttab tabletop_table := tabletop_table();
    ttab_tmp tabletop_table := tabletop_table();

    vtab_orderd Tab_Ordered;
BEGIN
    --模拟初始化
    t1 := tabletop(10,20,60,'wood');
    t2 := tabletop(30,40,140,'plastic');
    t3 := tabletop(25,33,116,'soil');
    t4 := tabletop(15,15,125,'water');

    ttab.extend(4);
    ttab_tmp.extend(4);
    ttab(1) := t1;
    ttab(2) := t2;
    ttab(3) := t3;
    ttab(4) := t4;

    /* 
    --不再需要这段代码
    for i in 1..4 loop
        rtab(i) := ttab(i); --子对象实例赋值给基对象实例,基对象获取到自己的属性(子类的属性获取不到)
        dbms_output.put_line( rtab(i).length );
    end loop; 
    */
    for i in 1..4 loop
        ttab_tmp(i) := ttab(i); --子对象实例赋值给基对象实例,基对象获取到自己的属性(子类的属性获取不到)
        raise notice '--->%', ttab_tmp(i).length;
    end loop; 
    --dbms_output.put_line( rtab(1).material0 ); --rtab不存在material0变量,oracle会报错

    ttab.delete;

    vtab_orderd := fp_MergeOrder(ttab_tmp);    --进行排序,获取vtab_orderd为顺序的下标

    for i in vtab_orderd.First..vtab_orderd.Last loop   
        ttab.extend();
        --ttab(i) := treat( rtab(vtab_orderd(i) ) as tabletop);    ---基类对象实例转化为子对象,赋值给新的子对象
        ttab(i) := ttab_tmp(vtab_orderd(i));
        raise notice '--->%', ttab(i).perimeter;  --打印有顺序的结果
    end loop;
END;

KES结果(运行结果和oracle一致):

NOTICE:  --->10
NOTICE:  --->30
NOTICE:  --->25
NOTICE:  --->15
NOTICE:  --->60
NOTICE:  --->116
NOTICE:  --->125
NOTICE:  --->140
ANONYMOUS BLOCK

自此改造完成。当然随着,KES的PSLQL中对象类型能力域完善,不久的版本会完全兼容上面的功能,不再需要变动客户代码。

四,其他

  1. treat函数例子

如果对treat函数不够理解,可以看看在对象类型中的应用举例:

treat函数的理解,treat函数转换对象类型的案例,进行父到子类型的转化

create or replace type ma_product is object (
  product_id number,
  product_name varchar2(100),
  product_price number,
  not instantiable member procedure show_discount
) 
not instantiable not final;


create or replace type ma_book under ma_product
(
  book_author varchar2(100),
  book_pages  number,
  constructor function ma_book(product_id    number,
                               product_name  varchar2,
                               product_price number,
                               book_author   varchar2,
                               book_pages    number) return self as result,
  overriding member procedure show_discount
)
instantiable not final;

create or replace type body ma_book is
  constructor function ma_book(product_id    number,
                               product_name  varchar2,
                               product_price number,
                               book_author   varchar2,
                               book_pages    number) return self as result is
  begin
    self.product_id    := product_id;
    self.product_name  := product_name;
    self.product_price := product_price;
    self.book_author   := book_author;
    self.book_pages    := book_pages;
    return;
  end ma_book;

  overriding member procedure show_discount is
  begin
    dbms_output.put_line(self.product_name || ' 作者是' || self.book_author ||
                         ',共' || self.book_pages || '页');
  end show_discount;
end;


DECLARE
  V_BOOK    ma_book;  --子类对象
  V_PRODUCT ma_product;  --基类对象
BEGIN
  V_PRODUCT := ma_book(1, '书籍XXX', 25, 'test', 55);  --子类对象赋值给基类对象
  V_PRODUCT.show_discount();     --调的实际就是子类的函数,因为是在子类中实现的
  v_book := treat(v_product as ma_book);  --将基对象v_product转换为ma_book类型,然后返回数据
  --  v_book := V_PRODUCT;  --错误用法,基类对象无法赋值给子类
  v_book.show_discount();
END;
/

oracle测试结果:

书籍XXX 作者是test,共55页
书籍XXX 作者是test,共55页

PL/SQL 过程已成功完成。

2,子父对象转换例子

如果对子对象赋值转化为父对象实例不够理解,可以看看在下面这个应用举例:

最小案例3:子对象转换为父对象的赋值转换例子

-- 编写最小案例解释如下:
-- oracle执行正确,(KES不支持)

-- 创建基类型
CREATE OR REPLACE TYPE rectangle FORCE AS OBJECT
(
    length number,
    width number,
    member function enlarge(inc number) return rectangle,
    NOT FINAL member procedure display
) NOT FINAL;
/

-- 创建基类型的主体
CREATE OR REPLACE TYPE BODY rectangle AS
    MEMBER FUNCTION enlarge(inc number) return rectangle IS
    BEGIN
        return rectangle(self.length + inc, self.width + inc);
    END enlarge;

    MEMBER PROCEDURE display IS
    BEGIN
        dbms_output.put_line('Length: '|| length);
        dbms_output.put_line('Width: '|| width);
    END display;
END;
/

-- 创建子对象类型,继承基对象rectangle
CREATE OR REPLACE TYPE tabletop force UNDER rectangle
(
    material0 varchar2(20),
    material1 varchar2(20),
    material2 varchar2(20),
    OVERRIDING member procedure display --重载:重新编写基类的函数
)
/

-- 为子对象tabletop创建类型主体
CREATE OR REPLACE TYPE BODY tabletop AS
OVERRIDING MEMBER PROCEDURE display IS
BEGIN
dbms_output.put_line('Length: '|| length);
dbms_output.put_line('Width: '|| width);
dbms_output.put_line('id1: '|| material0);
END display;
END;
/
--嵌套集合使用
CREATE OR REPLACE TYPE tabletop_table is table of tabletop;
CREATE OR REPLACE TYPE rectangle_table is table of rectangle;

-- 测试代码
DECLARE
    t1 tabletop;
    t2 tabletop;

    ttab tabletop_table := tabletop_table();
    rtab rectangle_table := rectangle_table();
BEGIN
    t1 := tabletop(10,20,‘aaaa’,‘bbbb’,‘cccc’);
    t2 := tabletop(30,40,‘dddd’,‘eeee’,‘ffff’);

    ttab.extend(10);
    rtab.extend(10);
    ttab(1) := t1;
    ttab(2) := t2;
    for i in 1…2 loop
    rtab(i) := ttab(i); --基类和父类嵌套在集合时,相应的集合变量(实际是对象实例)的可以转化赋值(子对象赋值给基对象实例)
    end loop;
    dbms_output.put_line( rtab(1).length );
    dbms_output.put_line( rtab(1).width );
    --–dbms_output.put_line( rtab(1).material0 ); --rtab不存在material0变量,oracle会报错
END;
posted @ 2024-04-03 17:02  KINGBASE研究院  阅读(18)  评论(0编辑  收藏  举报