I am working on some old PL/SQL code and I want to display the call hierarchy like I would have for Eclipse and Java code.
For instance, if I have the following code:
create or replace package body pkgA as
procedure foobar is begin
lambda(1);
end;
procedure lambda(a NUMBER) is begin
pkgB.test();
end;
end pkgA;
/
create or replace package body pkgB as
procedure test is begin
select 1 from dual;
end;
end pkgB;
/
I'd like to have this tree:
pkgB.test
pkgA.lambda
pkgA.foobar
Note: I am using Toad 9, but I did not see such feature (well, unless I need to look for to something like the referential tree for foreign keys).
Beside, I'm looking more for a static analysis than a dynamic, or something implying that I execute the code.
I think the better package procedure to call for this is: dbms_utility.format_error_backtrace
So you'd use DBMS_OUTPUT.PUT_LINE(dbms_utility.format_error_backtrace);
I put those in my exception handler for testing all the time (to the point where I have a code template that drops it in). I also output the instruction:
So it returns results like this:
EXCEPTION IN aeo_misc_tools.cursor_to_listV2 - -900: ORA-00900: invalid SQL statement
EXCEPTION IN my test script - -900: ORA-00900: invalid SQL statement
Error stack at top level:
ORA-06512: at "AEO.AEO_MISC_TOOLS", line 805
ORA-06512: at line 8
It won't format the error in a tree format, as you specified, but it gets the job done so you can find the error on the correct line.
You can use the called hierarchy of Visual-Expert, this tools analyse the PL code and show you the call function
It might not be the best solution, but I have figured out by using PL/Scope recursively. Hope it can help anyone in need. :)
create or replace procedure getAllChildCall ( definitionName in char, packageName in char, callLevel in number )
as
begin
for rec in (
with package_tree as (
select * from all_identifiers
where object_name = packageName and object_type = 'PACKAGE BODY'
)
select distinct
name, signature, type, line,
usage, usage_id, usage_context_id
from package_tree
start with (name = definitionName and usage = 'DEFINITION')
connect by prior usage_id = usage_context_id
) loop
if rec.usage = 'CALL' and rec.name not in (
'DEBUG', 'SQLERRM', 'COMMITRECORD', 'ISNOTZERO', 'NVL', 'TRUNC', 'ROUND') then
dbms_output.put_line(LPAD(' ', 2*callLevel, ' ') || rec.name);
getallchildcall(rec.name, packageName, callLevel+1);
end if;
end loop;
end;
/
set serveroutput on
declare
callLevel number;
begin
callLevel := 1;
dbms_output.put_line('start traverse the package child call graph...');
getAllChildCall(upper('YOUR_PROCEDURE_NAME'), upper('YOUR_PACKAGE_NAME'), callLevel);
end;
Related
I am writing this below stored procedure but i am also getting the exception while compiling the procedure in oracle, below is the procedure
CREATE OR REPLACE PACKAGE BODY TEST_TABLE AS
PROCEDURE TEST_TABLE
--This procedure will delete partitions for the following tables:
--TEST_TABLE
BEGIN
FOR cc IN
(
SELECT partition_name, high_value
FROM user_tab_partitions
WHERE table_name = 'TEST_TABLE'
)
LOOP
EXECUTE IMMEDIATE 'BEGIN
IF sysdate >= ADD_MONTHS(' || cc.high_value || ', 3) THEN
EXECUTE IMMEDIATE
''ALTER TABLE TEST_TABLE DROP PARTITION ' || cc.partition_name || '
'';
END IF;
dbms_output.put_line('drop partition completed');
END;';
END LOOP;
exception
when others then
dbms_output.put_line(SQLERRM);
END;
END;
/
and the exception that I am getting it while compiling is Please advise how to overcome from this.
Error(7,1): PLS-00103: Encountered the symbol "BEGIN" when expecting one of the following: ( ; is with authid as cluster order using external deterministic parallel_enable pipelined result_cache The symbol ";" was substituted for "BEGIN" to continue.
Error(22,25): PLS-00103: Encountered the symbol "DROP" when expecting one of the following: * & = - + ; < / > at in is mod remainder not rem return returning <an exponent (**)> <> or != or ~= >= <= <> and or like like2 like4 likec between into using || bulk member submultiset
You need to make correct quotation as below( and one more is keyword just after PROCEDURE TEST_TABLE "thanks to Alex who make me awaken" ) :
CREATE OR REPLACE PACKAGE BODY PKG_TEST_TABLE IS
PROCEDURE TEST_TABLE IS
--This procedure will delete partitions for the following tables:
--TEST_TABLE
BEGIN
FOR cc IN
(
SELECT partition_name, high_value
FROM user_tab_partitions
WHERE table_name = 'TEST_TABLE'
)
LOOP
BEGIN
IF sysdate >= ADD_MONTHS(cc.high_value, 3) THEN
EXECUTE IMMEDIATE
'ALTER TABLE TEST_TABLE DROP PARTITION ' || cc.partition_name;
Dbms_Output.Put_Line('Dropping partition is completed.');
END IF;
END;
END LOOP;
EXCEPTION WHEN Others THEN Dbms_Output.Put_Line( SQLERRM );
END TEST_TABLE;
END PKG_TEST_TABLE;
/
As a little suggestion use a different name for package than procedure such as PKG_TEST_TABLE against confusion.
Edit : of course you need to create a specification part for a package before the body part of the package :
CREATE OR REPLACE PACKAGE PKG_TEST_TABLE IS
PROCEDURE TEST_TABLE;
END PKG_TEST_TABLE;
/
The first error message tells you something is missing before BEGIN, and even mentions the two possible options in the 'when expecting' list. You need to change it to:
PROCEDURE TEST_TABLE IS
-- ^^
Or you can use AS instead of IS if you prefer...
The second error is because you have a string literal embedded in your dynamic SQL and you haven't escaped the single quotes, though you have elsewhere:
...
dbms_output.put_line(''drop partition completed'');
-- ^ ^
END;';
You could use the alternative quoting mechanism instead.
I'm not sure why you're doing two levels of dynamic SQL; you can do the dbms_output() and evaluate cc.high_value statically, and decide whether to make the alter call, with only that part dynamic (as #BarbarosĂ–zhan has shown, so I wont repeat that!). Or do the high-value check within the cursor query.
I am still getting exception Error(1,14): PLS-00304: cannot compile body of 'TEST_TABLE' without its specification
If you want a package then you have to create its specification before you try to create its body:
CREATE OR REPLACE PACKAGE TEST_TABLE AS
PROCEDURE TEST_TABLE;
END TEST_TABLE;
/
CREATE OR REPLACE PACKAGE BODY TEST_TABLE AS
PROCEDURE TEST_TABLE IS
BEGIN
FOR cc IN
...
LOOP
...
END LOOP;
END TEST_TABLE; -- end of procedure
END TEST_TABLE; -- end of package
/
Having the package name the same as a procedure within it is a bit odd and confusing.
But maybe you didn't actually want a package at all, and were trying to create a standalone procedure, in which case just remove the package-body part:
CREATE OR REPLACE PROCEDURE TEST_TABLE AS
BEGIN
FOR cc IN
...
LOOP
...
END LOOP;
END TEST_TABLE; -- end of procedure
/
Read more.
I would strongly suggest you get rid of the exception handler, and I've left that out of those outlines - you should let any exception flow back to the caller. You don't know that whoever calls this will even have output enabled, so might well not even see the message you're printing instead. Only ever catch exceptions you can handle and need to handle at that point.
I have a couple of local functions/procedures defined in a DECLARE..BEGIN..END; block:
DECLARE
PROCEDURE a IS
BEGIN
...
END;
PROCEDURE b IS
BEGIN
...
END;
BEGIN
END;
How can I get a list of all defined functions/procedures?
(In this example: ['a', 'b'])
As mentioned in comments, you are much, much, MUCH better off putting your procedures and functions into a package, and then in your anonymous block, you call those packaged subprograms.
This step will make it much easier to find, fix and maintain your code over time.
Plus, as a stored database object, you can then take advantage of data dictionary views and other DB features to analyze your code. For example, if you want to know the names of all subprograms in a package, or even nested/local subprograms in a single procedure or function, you can use PL/Scope.
ALTER SESSION SET plscope_settings='identifiers:all'
/
CREATE OR REPLACE PACKAGE my_pkg
AUTHID DEFINER
IS
FUNCTION my_function1
RETURN NUMBER;
FUNCTION my_function2
RETURN VARCHAR2;
END;
/
WITH one_obj_name AS (SELECT 'MY_PKG' object_name FROM DUAL)
SELECT LPAD (' ', 2 * (LEVEL - 1)) || usage || ' ' || name usages
FROM (SELECT ai.object_name,
ai.usage usage,
ai.usage_id,
ai.usage_context_id,
ai.TYPE || ' ' || ai.name name,
ai.line,
ai.col
FROM all_identifiers ai, one_obj_name
WHERE ai.object_name = one_obj_name.object_name)
START WITH usage_context_id = 0
CONNECT BY PRIOR usage_id = usage_context_id
/
USAGES
-----------------------------------------
DECLARATION PACKAGE MY_PKG
DECLARATION FUNCTION MY_FUNCTION1
REFERENCE NUMBER DATATYPE NUMBER
DECLARATION FUNCTION MY_FUNCTION2
REFERENCE CHARACTER DATATYPE VARCHAR2
PL/Scope is an incredibly powerful utility, built right into PL/SQL. Check out these resources for more information:
Philippe Salvisberg's PL/Scope Utilities
My blog posts on PL/Scope
You can use PL/Scope, if the anonymous PL/SQL block is part of a stored object and you are using an Oracle Database version >= 11.1.
ALTER SESSION SET plscope_settings='IDENTIFIERS:ALL';
CREATE OR REPLACE PROCEDURE p IS
BEGIN
DECLARE
PROCEDURE a IS
BEGIN
NULL;
END;
PROCEDURE b IS
BEGIN
NULL;
END;
BEGIN
NULL;
END;
END p;
/
SELECT name
FROM user_identifiers
WHERE object_name = 'P'
AND usage = 'DEFINITION'
AND type IN ('PROCEDURE','FUNCTION')
AND line > 1;
NAME
------------------------------
A
B
You need some kind of extraction logic and a PL/SQL parser, if the anonymous PL/SQL block is stored somewhere else.
I'm stuck with some simple procedure and I can't figure out why.
This is my code, which I'm running in sqlplus:
CREATE OR REPLACE PROCEDURE NormalizeName(fullname IN NVARCHAR2)
IS
BEGIN
SELECT TRIM(fullname) INTO fullname FROM DUAL;
DBMS_OUTPUT.PUT_LINE(fullname);
END NormalizeName;
/
BEGIN
NormalizeName('Alice Wonderland ');
END;
/
When I run it, I get the error:
Warning: Procedure created with compilation errors.
NormalizeName('Alice Wonderland ');
*
ERROR at line 2:
ORA-06550: line 2, column 2:
PLS-00905: object SYSTEM.NORMALIZENAME is invalid
ORA-06550: line 2, column 2:
PL/SQL: Statement ignored
What's wrong?
1) Never create objects in the SYS or SYSTEM schema. Those are reserved for Oracle. If you want to create objects, create a new schema first.
2) When you see that a procedure has been created with compilation errors in SQL*Plus, type show errors to see the errors.
3) The error appears to be that your SELECT statement is trying to write to the fullname parameter. But that parameter is defined as an IN parameter, not IN OUT, so it is read-only. If you define the parameter as IN OUT, though, you could not pass a string constant to the procedure, you'd need to define a local variable in your calling block. It doesn't make a lot of sense to have a procedure that doesn't do anything other than call dbms_output since there is no guarantee that anyone will see the data written to that buffer. My guess is that you really want a function that returns a normalized name. Something like
CREATE OR REPLACE FUNCTION NormalizeName( p_full_name IN VARCHAR2 )
RETURN VARCHAR2
IS
BEGIN
RETURN TRIM( p_full_name );
END;
which you can then call
DECLARE
l_normalized_name VARCHAR2(100);
BEGIN
l_normalized_name := NormalizeName( 'Alice Wonderland ' );
dbms_output.put_line( l_normalized_name );
END;
If you really need a procedure because this is a homework assignment
CREATE OR REPLACE PROCEDURE NormalizeName( p_fullname IN VARCHAR2 )
AS
BEGIN
dbms_output.put_line( TRIM( p_fullname ));
END;
In the real world, you should only be using procedures when you want to manipulate the state of the database (i.e. you're doing INSERT, UPDATE, DELETE, MERGE, etc.). You use functions when you want to perform calculations without changing the state of the database or when you want to manipulate data passed in parameters.
I'm trying to call an Oracle stored proc using SQL Developer. The proc outputs results using a sys_refcursor. I right click in the proc window which brings up the Run PL/SQL window. When I choose the proc I want it creates all the input params etc for me. Below is the code I'm using to try and loop through the sys_refcursor and output the results, but I'm getting an error on the 'v_rec v_Return%rowtype;' line :
ORA-06550: line 6 column 9:
PLS-00320: the declaration of the type of this expression is incomplete or malformed.
ORA-06550: line 6 column 9:
PL/SQL: Item ignored
vendor code 6550
I found the looping code on a couple of other websites and it seems to be the way to do it but it's not working for me no matter what I try. Another question - on the DBMS_OUTPUT.PUT_LINE('name = ' || v_rec.ADM) am I referencing the v_rec correctly i.e. is v_rec."column_name" the correct way??
I'm not that used to Oracle and have never used SQL plus. Any suggestions appreciated.
DECLARE
P_CAE_SEC_ID_N NUMBER;
P_PAGE_INDEX NUMBER;
P_PAGE_SIZE NUMBER;
v_Return sys_refcursor;
v_rec v_Return%rowtype;
BEGIN
P_CAE_SEC_ID_N := NULL;
P_PAGE_INDEX := 0;
P_PAGE_SIZE := 25;
CAE_FOF_SECURITY_PKG.GET_LIST_FOF_SECURITY(
P_CAE_SEC_ID_N => P_CAE_SEC_ID_N,
P_PAGE_INDEX => P_PAGE_INDEX,
P_PAGE_SIZE => P_PAGE_SIZE,
P_FOF_SEC_REFCUR => v_Return
);
-- Modify the code to output the variable
-- DBMS_OUTPUT.PUT_LINE('P_FOF_SEC_REFCUR = ');
loop
fetch v_Return into v_rec;
exit when v_Return%notfound;
DBMS_OUTPUT.PUT_LINE('name = ' || v_rec.ADM);
end loop;
END;
Your problem is here:
v_Return sys_refcursor;
v_rec v_Return%rowtype;
v_Return is a cursor variable and has no specific structure (list of columns), so v_Return%rowtype is not a valid record structure to declare v_rec. It is even possible for different calls to the procedure to return cursors with different structures.
You know what you are expecting the structure of the returned cursor to be (but Oracle doesn't) so you need to explicitly define the appropriate record structure e.g.
type t_row is record (empno number, ename varchar2(30));
v_rec t_row;
You need a strongly typed ref cursor to be able to define it as a %ROWTYPE.
Example here
#Tony Andrews thanks for this it gave me a better idea where I was going wrong. Still having problems though - here's a shortened version of my proc. It's a bit complex in that it's selecting all fields from a subquery and 2 other values:
open p_fof_sec_refcur for
SELECT *
FROM(
SELECT securities.*, rownum rnum, v_total_count
FROM
(
SELECT
CFS.CAE_SEC_ID,
CFS.FM_SEC_CODE,
...
FROM
CAEDBO.CAE_FOF_SECURITY CFS
INNER JOIN caedbo.CAE_DATA_SET_ELEMENT CDSE_STAT
ON (CDSE_STAT.DATA_SET_ELEMENT_ID = CFS.APPR_STATUS)
...
WHERE APPR_STATUS = NVL(p_appr_status, APPR_STATUS)
...
)securities
)
WHERE rnum between v_pgStart and v_pgEnd;
I explicitly defined the output structure as below to match the return fields from the proc but I'm still getting an error:
v_Return sys_refcursor;
type t_row is record (CAE_SEC_ID NUMBER,FM_SEC_CODE VARCHAR2(7),...rnum number, v_total_count number);
v_rec t_row;
The error I get is
ORA-06504: PL/SQL: Return types of Result Set variables or query do not match
ORA-06512: at line 45
I'm just wondering is the "rownum rnum, v_total_count" part tripping me up. I'm pretty sure I have all the other fields in the output structure correct as I copied them directly from the proc.
I have many cursors that all return rows with the same fields: a numeric ID field and an XMLType field. Every time I access one of these cursors (each cursor has now got its own function for access), I go through the same pattern:
--query behind cursor is designed to no more than one row.
for rec in c_someCursor(in_searchKey => local_search_key_value) loop
v_id := rec.ID
v_someXMLVar := rec.XMLDataField
end loop;
if v_someXMLVar is null then
/* A bunch of mostly-standard error handling and logging goes here */
end if;
exception
/* all cursor access functions have the same error-handling */
end;
As the pattern became more obvious, it made sense to centralize it in a single function:
function fn_standardCursorAccess(in_cursor in t_xmlCursorType, in_alt in XMLType) return XMLType is
v_XMLData XMLType;
begin
dbms_application_info.set_module(module_name => $$PLSQL_UNIT, action_name => 'fn_standardCursorAccess');
loop
fetch in_cursor
into v_XMLData;
exit when in_cursor%notfound;
end loop;
/*some additional standard processing goes here*/
return v_XML;
exception
/*standard exception handling happens here*/
end;
The problem I've run into is in calling this function. I now have to call it like this:
open v_curs for select /*blah blah blah*/ where key_field = x and /*...*/;
v_data := fn_standardCursorAccess(v_curs,alt);
close v_curs;
What I'd like to do is call it like this:
open v_curs for c_getSomeData(x);
v_data := fn_standardCursorAccess(v_curs,alt);
close v_curs;
...reason being to minimize the amount of changes to my code (I don't want to have to cut/paste all these cursors to the functions that depend on them, and in the case where multiple functions depend on the same cursor, I'll have to wrap that in a new function).
Unfortunately, this does not work, Oracle returns an error saying
Error: PLS-00222: no function with name 'C_GETSOMEDATA' exists in this scope
Is what I'm trying to do even possible?
(Oracle version is 10.2)
EDIT:
I think a better way to describe what I'm doing is pass a reference to an explicit cursor to a function that will perform some common routines on the data returned by the cursor.
It appears that I cannot use an open-for statement with an explcit cursor, is there any other way to get a reference to an explicit cursor so I can pass that reference to a function? Maybe there is some other way I could approach this problem?
EDIT:
Copying and pasting from my earlier reply to R Van Rijn's reply:
I tried declaring the cursor in the package specification, and referencing it with the package name: open v_curs for PKG.c_getSomeData(x);... This gives me a new error, saying that PKG.c_getSomeData must be a function or array to be used that way.
UPDATE:
I spoke to our DBA here, he says it is not possible to have a ref cursor point to an explicit cursor. It looks like I can't do this after all. Bummer. :(
concerning the Error PLS-00222:
An identifier being referenced as a function 'c_getSomeData' was not declared or actually represents another object (for example, it might have been declared as a procedure).
Check the spelling and declaration of the identifier. Also confirm that the declaration is placed correctly in the block structure
This means you must create a function that actually returns some value(s).
Does this test script and output represent what you are trying to do? Instead of open v_curs for c_getSomeData(x); I'm setting the cursor variable = to the output from the function.
Our Test Data:
set serveroutput on
--create demo table
drop table company;
create table company
(
id number not null,
name varchar2(40)
);
insert into company (id, name) values (1, 'Test 1 Company');
insert into company (id, name) values (2, 'Test 2 Company');
insert into company (id, name) values (3, 'Test 3 Company');
commit;
Create Packages
create or replace package test_pkg as
type cursor_type is ref cursor;
function c_getSomeData(v_companyID number) return cursor_type;
end test_pkg;
/
create or replace package body test_pkg as
function c_getSomeData(v_companyID number) return cursor_type
is
v_cursor cursor_type;
begin
open v_cursor for
select id,
name
from company
where id = v_companyID;
return v_cursor;
end c_getSomeData;
end test_pkg;
/
Run Our Procedure
declare
c test_pkg.cursor_type;
v_id company.id%type;
v_name company.name%type;
begin
c := test_pkg.c_getSomeData(1);
loop
fetch c
into v_id, v_name;
exit when c%notfound;
dbms_output.put_line(v_id || ' | ' || v_name);
end loop;
close c;
end;
/
1 | Test 1 Company
PL/SQL procedure successfully completed.
I confess to finding your requirements a trifle hard to divine. You have posted a lot of code, but as I suggested in my comment, not the parts which would illuminate the problem. So possibly the following is way off-beam. But it is an interesting issue.
The following code shows how we can define a common, geneneric REF CURSOR, populate it with specific data from different queries, and then process them in a standardised fashion. Again, I apologise if this does not fit your business logic; if such is the case, please edit your question to explain where I have made a bloomer..
Here is the generic ref cursor. ...
create or replace package type_def is
type xml_rec is record (id number, payload xmltype);
type xml_cur is ref cursor return xml_rec;
end type_def;
/
and here is the standatd processor
create or replace procedure print_xml_cur
( p_cur in type_def.xml_cur )
is
lrec type_def.xml_rec;
begin
loop
fetch p_cur into lrec;
exit when p_cur%notfound;
dbms_output.put_line('ID='||lrec.id);
dbms_output.put_line('xml='||lrec.payload.getClobVal());
end loop;
close p_cur;
end print_xml_cur;
/
Two procedures which return the standard cursor with different data....
create or replace function get_emp_xml
( p_id in emp.deptno%type )
return type_def.xml_cur
is
return_value type_def.xml_cur;
begin
open return_value for
select deptno
, sys_xmlagg(sys_xmlgen(ename))
from emp
where deptno = p_id
group by deptno;
return return_value;
end get_emp_xml;
/
create or replace function get_dept_xml
( p_id in dept.deptno%type )
return type_def.xml_cur
is
return_value type_def.xml_cur;
begin
open return_value for
select deptno
, sys_xmlagg(sys_xmlgen(dname))
from dept
where deptno = p_id
group by deptno;
return return_value;
end get_dept_xml;
/
Now let's put it all together ....
SQL> set serveroutput on size unlimited
SQL>
SQL> exec print_xml_cur(get_emp_xml(40))
ID=40
xml=<?xml
version="1.0"?>
<ROWSET>
<ENAME>GADGET</ENAME>
<ENAME>KISHORE</ENAME>
</ROWSET>
PL/SQL procedure successfully completed.
SQL> exec print_xml_cur(get_dept_xml(20))
ID=20
xml=<?xml version="1.0"?>
<ROWSET>
<DNAME>RESEARCH</DNAME>
</ROWSET>
PL/SQL procedure successfully completed.
SQL>
OK, so the short answer from Oracle is: "can't be done!"
The short answer from me is: "Yeah - like Oracle is gonna stop me! So yes you can....but you need to be sneaky ... oh yes, and there is a 'but' or two....in fact...ugh!"
So, how can you pass your explicit cursor by reference? By nesting it into another cursor using the CURSOR() construct!
e.g.)
CREATE OR REPLACE package CFSDBA_APP.test_Cursor
as
function get_cursor(ed_id number) return sys_refcursor;
end;
/
CREATE OR REPLACE package body CFSDBA_APP.test_Cursor
as
function get_cursor(ed_id number) return sys_refcursor
is
test_Cur sys_refcursor;
cursor gettest is
select CURSOR( -pass our actual query back as a nested CURSOR type
select ELCTRL_EVNT_ELCTRL_DISTRCT_ID,
ELECTORAL_DISTRICT_ID,
ELECTORAL_EVENT_ID
from ELCTRL_EVNT_ELCTRL_DISTRCT
where electoral_District_id = ed_id)
from dual;
begin
open gettest;
fetch gettest into test_Cur;
return test_Cur;
end;
end;
/
So what is the problem with this solution? It has a leak! The outer gettest cursor is never closed, because we don't close it and the client will only close the reference to the nested cursor that was selected for them - not the main cursor. And we can't close it automatically because closign the parent would force closing the nested cursor that you have returned by reference - and it is entirely likely that the client has not done using it.
So we have to leave a cursor open in order to return the nested cursor.
And if the user tried calling get_Cursor again with a new value of ed_id, they would discover that the session persistence in the package meant that the cursor handle is still in use and an error would be raised.
Now, we could fix that by first checking and closing the explicit cursor:
if gettest%isopen then
close gettest;
end if;
open gettest;
fetch gettest into test_Cur;
return test_Cur;
But still - what if the user never calls this again? How long 'til Oracle garbage-collects the cursor? And how many users running how many sessions calling how many functions that use this construct will be leaving cursors open after they are done with them? Better count on a huuuuuge overhead to leave all those open cursors layin' about!
No, you would need to have the users do a callback to explicitely close it or you would be clogging the database. But doing this would require changing the scope of the explicit cursor so that both functions can access it: So we need to make it at package scope, not function scope
CREATE OR REPLACE package CFSDBA_APP.test_Cursor
as
function get_cursor(ed_id number) return sys_refcursor;
function close_cursor return sys_refcursor;
end;
/
CREATE OR REPLACE package body CFSDBA_APP.test_Cursor
as
cursor l_gettest(p_ed_id in number) is
select CURSOR(
select ELCTRL_EVNT_ELCTRL_DISTRCT_ID, ELECTORAL_DISTRICT_ID, ELECTORAL_EVENT_ID
from ELCTRL_EVNT_ELCTRL_DISTRCT
where electoral_District_id = p_ed_id)
from dual;
function get_cursor(ed_id number) return sys_refcursor
is
l_get_Cursor sys_refcursor;
begin
open l_gettest (ed_id);
fetch l_gettest into l_get_Cursor;
return l_get_cursor;
end;
function close_cursor return sys_refcursor
is
begin
if l_gettest%isopen then
close l_gettest;
end if;
return pkg_common.generic_success_cursor;
end;
end;
/
OK, plugged the leak. Except it cost us a network round trip instead of the hard parse,...oh wait - and also except embedding a bind variable into an explicit cursor declared at this level is probably going to cause scoping issues of its own which was the reason we wanted to do this in the first place!
Oh, and in a session-pooling environment can two users step on each other's cursors? IF they aren't very carefull about doing an open-fetch-close before returning the session to the pool - we could wind up with some really interesting (and impossible to debug) results!
And how much do you trust the maintainers of the client code to be extra-diligent on this? YEah - me too.
So the short answer is: Yes, with a bit of sneakiness it could be done despite Oracle saying it can't.
The better answer is: But please don't! The extra round trip and potential for memory leaks and client code errors causing data problems makes this a very scary proposition.
It appears that what I wanted to do (have an open-for statement reference an existing explicit cursor) is simply not allowed in Oracle. :(