#自定义高级包

自定义高级包是数据库里的一种PL对象,简称UDP。

分布式部署中不能创建和使用UDP。

# 创建UDP

UDP的创建分为两部分:

  • 定义PACKAGE HEAD:PACKAGE HEAD用于声明PUBLIC属性的变量、类型、游标和子过程体对象(存储过程、自定义函数),在UDP被成功创建后,这些变量、类型、游标和子过程体对象将可以作为UDP的成员被其他外部程序所引用。
  • 定义PACKAGE BODY:PACKAGE BODY用于定义过程体,PACKAGE BODY除可以直接使用PACKAGE HEAD中声明的变量外,也可以自行声明新的变量,但这些变量为该UDP私有(PRIVATE),即只能在PACKAGE BODY中使用,而不可以被外部其他过程体所引用。

PACKAGE HEAD和PACKAGE BODY通过不同语法分别定义,且YashanDB并不严格要求HEAD和BODY的创建顺序,如在HEAD还未创建时先定义BODY,系统将仍然创建该UDP,但会因为HEAD声明不存在而抛出编译错误,该UDP也无法被执行和调用,直到HEAD被创建。

系统对PACKAGE HEAD和PACKAGE BODY还存在如下约束要求:

  • HEAD和BODY内部不允许出现同名的变量或者子过程体对象声明;
  • HEAD中的子过程体对象声明,在BODY中必须有对应的过程体定义,在HEAD中声明的参数缺省值在BODY中可以省略;
  • 对于子过程体对象,HEAD中只能对其进行声明,BODY中只能对其进行过程体定义。

使用CREATE PACKAGE语句可以分别创建PACKAGE HEAD或者PACKAGE BODY,语法定义如下:

create package::=

syntax
CREATE OR REPLACE EDITIONABLE NONEDITIONABLE PACKAGE package_head_clause package_body_clause END package_name ;

package_head_clause::=

syntax
schema . package_name invoker_rights_clause IS AS pragma_clause package_item_clause

invoker_rights_clause::=

syntax
AUTHID CURRENT_USER DEFINER

pragma_clause::=

syntax
PRAGMA SERIALLY_REUSABLE ;

package_item_clause::=

syntax
variable_declare pl_declare variable_declare pl_declare

variable_declare

pl_declare::=

syntax
PROCEDURE FUNCTION pl_name ( argument_define , ) RETURN return_datatype

package_body_clause::=

syntax
BODY schema . package_name IS AS package_item_clause package_subprogram_clause

package_subprogram_clause::=

syntax
IS AS variable_declare BEGIN plsql_statements END pl_name

# or replace

当要创建的UDP已经存在时,将其进行重建。

# editionable | noneditionable

用于语法兼容,无实际含义。

# package_name

UDP的名称,不可省略,且需符合YashanDB的对象命名规范

# package_head_clause

创建一个UDP的HEAD。

# invoker_rights_clause

PL对象中invoker_rights_clause部分描述。

# pragma_clause

可选项,表示UDP的变量每次被引用时,是否将其重置为变量声明时定义的缺省值,默认不重置。

在YashanDB里,在UDP中声明的变量均为SESSION级别的全局变量,这意味着在同一个会话中,变量赋值相互影响,而不同会话中的变量则相互独立。通过设置SERIALLY_REUSABLE可以在每次引用变量(客户端到服务端的一次调用)时让其恢复初始值。

示例(单机、共享集群部署)

DROP PACKAGE IF EXISTS calc_fee;

CREATE OR REPLACE PACKAGE calc_fee
AS
c NUMBER := 100;
PROCEDURE branch_quantity(date_from date);
END;
/

CREATE OR REPLACE PACKAGE BODY calc_fee AS
PROCEDURE branch_quantity(date_from DATE) IS
BEGIN
 DBMS_OUTPUT.PUT_LINE(c);
 c := c+1;
 DBMS_OUTPUT.PUT_LINE(c);
END;
END calc_fee;
/

--第一次执行
exec calc_fee.branch_quantity(sysdate);
100
101
--第二次执行
exec calc_fee.branch_quantity(sysdate);
101
102

--设置pragma_clause
CREATE OR REPLACE PACKAGE calc_fee
AS
PRAGMA SERIALLY_REUSABLE;
c NUMBER := 100;
PROCEDURE branch_quantity(date_from date);
END;
/

CREATE OR REPLACE PACKAGE BODY calc_fee AS
PRAGMA SERIALLY_REUSABLE;
PROCEDURE branch_quantity(date_from date) IS
BEGIN
DBMS_OUTPUT.PUT_LINE(c);
c := c+1;
DBMS_OUTPUT.PUT_LINE(c);
END;
END calc_fee;
/

--第一次执行
exec calc_fee.branch_quantity(sysdate);
100
101
--第二次执行
exec calc_fee.branch_quantity(sysdate);
100
101

# package_item_clause

UDP的成员声明,包括变量声明和子过程体对象声明,一个UDP(HEAD或BODY)里可声明的成员数量最多为1024个。

  • 变量声明:支持在声明章节中列出的各类变量的声明,但对于游标,允许显式定义游标,不允许显式声明游标。可交叉声明多个不同类变量且顺序无限制。
  • 子过程体对象声明:支持声明存储过程和自定义函数。允许交叉声明多个存储过程和自定义函数,且顺序无限制。

变量声明必须位于子过程体对象声明的前面,且定义的成员先后顺序决定了后一个成员是否可见前面成员。

# package_body_clause

创建一个UDP的BODY。

# package_subprogram_clause

UDP里的过程体定义,包括PROCEDURE或FUNCTION的定义,定义语法同存储过程自定义函数章节里的过程体语法描述。

# 加载udp的变量

UDP的变量无法在外部SQL语句中直接访问,而是通过在过程体(内部或外部)中加载使用,加载格式为:

[schema.][package_name.]variable_name;

在被外部过程体调用时,package_name不可省略。

在YashanDB中,UDP的变量与UDP进行绑定管理,如果UDP的HEAD或者BODY发生变更,该UDP的所有变量将被重置。此规则与Oracle数据库存在差异。

# 运行udp的子过程体对象

SQL语句中调用子函数

通过SQL语句调用UDP的子函数,允许出现在SQL语句的列项、条件项或查询选项(如ORDER BY/LIMIT/OFFSET)等各个位置。以出现在查询列为例的调用格式为:

SELECT package_name.subpl_name[(arguments)] FROM table_name;

CALL/EXEC语句中调用子存储过程

通过CALL/EXEC语句可调用UDP的子存储过程,调用格式为:

(CALL|EXEC) package_name.subpl_name[(arguments)];

过程体中调用子存储过程

通过在过程体(内部或外部)中调用子存储过程,调用格式为:

[package_name.]procedure_name[(arguments)];

在被外部过程体调用时,package_name不可省略。

示例(单机、共享集群部署)

--创建无HEAD的UDP
DROP PACKAGE IF EXISTS calc_fee;

--创建UDP的BODY,由于未声明相关变量会返回错误,实际上该BODY已创建成功
CREATE OR REPLACE PACKAGE BODY calc_fee AS
PROCEDURE calc_rev(prefix CHAR) IS
 str1 VARCHAR(100) := 'select sum(revenue_total) from finance_info where substr(branch,0,2)=:a';
BEGIN
 EXECUTE IMMEDIATE str1 INTO c USING prefix;
 DBMS_OUTPUT.PUT_LINE(c);
END;
END calc_fee;
/
--result
YAS-05278 cannot compile body without its specification

--创建calc_fee的HEAD(此步骤后无需再次创建BODY)
CREATE OR REPLACE PACKAGE calc_fee
AS
c NUMBER := 100;
PROCEDURE calc_rev(prefix CHAR);
END calc_fee;
/

--调用UDP
DECLARE
 c CHAR(2);
BEGIN
 c := '01';
 DBMS_OUTPUT.PUT_LINE(c);
 DBMS_OUTPUT.PUT_LINE(calc_fee.c);
 calc_fee.calc_rev(c);
END;
/
--result
01
100
76666

# 删除自定义高级包

使用DROP PACKAGE语句删除自定义高级包。

示例(单机、共享集群部署)

--创建UDF,调用上例中的UDP子过程体对象
CREATE OR REPLACE FUNCTION calc(c CHAR) RETURN NUMBER IS
BEGIN
  calc_fee.calc_rev(c);
  RETURN calc_fee.c;
END;
/
SELECT calc('01') FROM dual;
 CALC('01') 
----------- 
      76666

--删除UDP
DROP PACKAGE calc_fee;
SELECT calc('01') FROM dual;
[1:8]YAS-04253 PL/SQL compiling errors:
[3:3] YAS-04243 invalid identifier "CALC_FEE"."CALC_REV"
[4:10] YAS-04243 invalid identifier "CALC_FEE"."C"

# 修改自定义高级包

使用ALTER PACKAGE语句修改自定义高级包。