比如说我有一个Package,它的specification如下,
CREATE OR REPLACE
PACKAGE PACK_TEST AS
-- Return a list of the package name in current schema
function GET_PACKAGE_LIST return T_V30_TABLE PIPELINED;
-- Return the package specifiction (procedure/function) in the given package of current schema
function GET_PACKAGE_SPECIFICATION(V_PACKAGE_IN in varchar2) return T_V4000_TABLE PIPELINED;
-- The following two procedures are to ease calling from outside databae, like in .NET code.
procedure GET_PACKAGE_LIST(V_RESULT_IN_OUT in OUT SYS_REFCURSOR);
procedure GET_PACKAGE_SPECIFICATION(V_PACKAGE_IN in varchar2, V_RESULT_IN_OUT in OUT SYS_REFCURSOR);
-- Help function
-- (It must be pulbic to be used inside the SQL query inside function GET_PACKAGE_SPECIFICATION,
-- otherwise, PLS-00231 will arise!)
function ARG_TABLE_TO_STRING(V_ARG_TABLE in T_V100_TABLE) return varchar2;
END PACK_TEST;
现在我想通过执行一条SQL语句,得到这个Package里面包含哪些Function和Procedure, 类似于SQL*Plus的命令 DESC <package_name>
FUNCTION GET_PACKAGE_LIST () RETURN T_V30_TABLE
FUNCTION ARG_TABLE_TO_STRING (V_ARG_TABLE IN T_V100_TABLE) RETURN VARCHAR2
FUNCTION GET_PACKAGE_SPECIFICATION (V_PACKAGE_IN IN VARCHAR2) RETURN T_V4000_TABLE
PROCEDURE GET_PACKAGE_LIST (V_RESULT_IN_OUT IN/OUT REF CURSOR)
PROCEDURE GET_PACKAGE_SPECIFICATION (V_PACKAGE_IN IN VARCHAR2, V_RESULT_IN_OUT IN/OUT REF CURSOR)
其实思路很简单,只要从数据字典视图里面选择需要的数据,然后进行相应的格式化就OK了。下面就是GET_PACKAGE_SPECIFICATION的代码,
function GET_PACKAGE_SPECIFICATION(V_PACKAGE_IN in varchar2) return T_V4000_TABLE PIPELINED AS
begin
for REC in
(
select
T.SUBPROGRAM_TYPE || ' ' ||
T.SUBPROGRAM_NAME || ' ' ||
ARG_TABLE_TO_STRING(cast(collect(T.ARGUMENT) as T_V100_TABLE)) || ' ' ||
t.return_caluse as subprogram_spec
from
(
select
case FIRST_VALUE(POSITION)over(partition by SUBPROGRAM_ID order by sequence) when 0 then 'FUNCTION ' else 'PROCEDURE 'end as SUBPROGRAM_TYPE,
OBJECT_NAME as SUBPROGRAM_NAME,
case when ARGUMENT_NAME is not null then ARGUMENT_NAME || ' ' || IN_OUT || ' ' || (case DATA_TYPE when 'TABLE' then TYPE_NAME else DATA_TYPE end) end as ARGUMENT,
FIRST_VALUE(case POSITION when 0 then 'RETURN ' || (case DATA_TYPE when 'TABLE' then TYPE_NAME else DATA_TYPE end) end) over(partition by subprogram_id order by sequence) as return_caluse
from USER_ARGUMENTS
where PACKAGE_NAME = V_PACKAGE_IN
) t
group by
T.SUBPROGRAM_TYPE,
T.SUBPROGRAM_NAME,
T.RETURN_CALUSE
)
LOOP
pipe row (rec.subprogram_spec);
end LOOP;
return;
end GET_PACKAGE_SPECIFICATION;
我是从USER_ARGUMENTS里面进行数据查询,然后通过分析函数FIRST_VALUE, 10g新增加的collect聚集函数来进行“格式化”。
这里面遇到的一个问题是通过调用function ARG_TABLE_TO_STRING 来把collect函数返回的collection变成字符串。也就是ARG_TABLE_TO_STRING
function ARG_TABLE_TO_STRING(V_ARG_TABLE in T_V100_TABLE) return varchar2
is
V_RETURN varchar2(4000) := '(';
-- In case the string length is larger than 4000
STRING_BUFFER_TOO_SMALL exception;
pragma EXCEPTION_INIT(STRING_BUFFER_TOO_SMALL, -06502);
begin
for IDX in 1..V_ARG_TABLE.count
LOOP
v_return := v_return || V_ARG_TABLE(IDX) || ', ';
end LOOP;
v_return := RTRIM(v_return, ', ') || ')';
return v_return;
EXCEPTION
when STRING_BUFFER_TOO_SMALL then
V_RETURN := SUBSTR(V_RETURN, 1, 4000 - 5) || ' ...)';
return v_return;
end ARG_TABLE_TO_STRING;
因为这个函数只是在包的内部调用,因此理应该把这个function私有化,但是这样的话在compile的时候会遇到PLS-00231 的问题。关于这个问题,在这里有很好的讨论解释。
造成这个问题的原因是因为我是在SQL语句里面调用这个PL/SQL函数的,因为SQL语句和PL/SQL函数是通过不同的engine(SQL engine 和 PL/SQL engine) 来解析执行的,因此在SQL语句中执行的FUNCTION必须是public的,这样SQL engine才能访问的。 解决这个问题的方法要么就是将function 公有化,即放在package specification中,或者把function的调用改成PL/SQL代码,不是放在SQL语句中。所以要具体情况具体分析,这里我是选择了前者。
调用函数GET_PACKAGE_SPECIFICATION就很简单了,直接用一条SQL语句就OK了,
select * from table(PACK_TEST.GET_PACKAGE_SPECIFICATION('PACK_TEST'));
总结下:
1) USR_ARGUMENTS
2) Table function / Pipelined
3) COLLECT function
4) PLS-00231