Can we declare and use a cursor in a package specification without a body? - oracle

It's said in database PL/SQL language reference in topic 10.1 What is a package? that:
If the public items include cursors or subprograms, then the package must also have a body. The body must define queries for public cursors and code for public subprograms.
I've tested cursor package spec without body and it worked fine (in DB version 19c):
create or replace package some_pak is
cursor c is select * from employees where employee_id <102;
end;
begin
for i in some_pak.c
loop
dbms_output.put_line(i.employee_id|| ' '||i.first_name||' '|| i.salary);
end loop;
end;
Result:
100 Steven 24000
101 Neena 17000
What am I doing or understanding wrong?
I am preparing for 1z0-149 exam and want to know accurate information.

It is a bit misleading, but if you take the previous paragraph from the documentation into account:
A package always has a specification, which declares the public items that can be referenced from outside the package.
... and stress the declares in that, then it sort of makes sense; if you only declare a cursor, rather than declaring and defining as your example does, then you do need a body too. For example, your specification could do:
create or replace package some_pak is
cursor c return dual%rowtype;
end;
/
But if you tried to reference that cursor you'd get "ORA-04067: not executed, package body ... does not exist".
You would then need to define the cursor in the package body, e.g.:
create or replace package body some_pak is
cursor c return dual%rowtype is
select * from dual;
end;
/
Notice that you need to declare the return type, in both the specification and body. In your original version that isn't necessary, but here the declaration still has to tell callers the structure of the data the cursor will return. That is the API contract, if you like.
Declaring and defining the cursor separately means you can redefine the cursor by recompiling just the package body (as long as the projection remains the same, of course), avoiding invalidating anything that refers to it - as would happen if you recompiled the specification.
db<>fiddle
Splitting the declaration and definition would also allow you to hide (albeit not very securely) the actual cursor query by wrapping the package body, without wrapping the specification. Whether that would ever be useful is another matter, but thought it might be worth mentioning anyway.

Related

Oracle out parameter not being NULL'd

My understanding is OUT formal parameters should always be defaulted to NULL when the procedure is called.
create or replace package parameter_tests as
procedure callerproc;
end parameter_tests;
/
create or replace package body parameter_tests as
procedure getstring(p_str out varchar) is
begin
if p_str is null then
dbms_output.put_line('parameter null');
else
dbms_output.put_line('parameter NOT null');
end if;
p_str :='zz';
end getstring;
procedure getcursor(p_out out sys_refcursor) is
begin
if p_out%isopen then
dbms_output.put_line('cursor open');
else
dbms_output.put_line('cursor closed');
end if;
open p_out for
select *
from dual;
end getcursor;
procedure callerproc is
lv_cursor sys_refcursor;
lv_string varchar2(2) := null;
begin
for i in 1..2 loop
getstring(lv_string);
getcursor(lv_cursor);
end loop;
end callerproc;
end parameter_tests;
/
set serveroutput on
begin
parameter_tests.CALLERPROC;
end;
/
parameter_tests.getstring would expect to output "Parameter null" both times around the callerproc loop. And when you run the code that's exactly what happens.
However the output from parameter_tests.getcursor indicates the reference cursor is still open the second time around the loop.
If the formal parameter p_out was being nulled when getcursor was called, we'd expect it to close the reference cursor. Instead it passes the open reference cursor and it's actually the OPEN FOR that tidies up the open reference cursor (and prevents us from hitting the maximum open cursors if you loop hundreds of times).
If I manually null the reference cursor between calls it does behave like we would expect.
Can anyone tell me why reference cursors are being handled as a special case when they are OUT parameters? And also what other types are handled differently?
The database version is 11.2.0.2.0.
According to the documentation:
When declaring a cursor variable as the formal parameter of a subprogram:
If the subprogram opens or assigns a value to the cursor variable, then the parameter mode must be IN OUT.
If the subprogram only fetches from, or closes, the cursor variable, then the parameter mode can be either IN or IN OUT.
The cursor parameter seems to be treated as if it had been declared as IN OUT, even though you only actually declare it as OUT. The behaviour is what you would expect to see from IN OUT; you can even fetch the cursor in the second call, and see the dummy value from dual.
It sort of makes sense given the nature of the ref cursor as a pointer, but you would think that rule would be enforced by the compiler erroring if only OUT was specified (and it isn't even reported as a warning). The example in the docs works fine with just OUT too, incidentally.
So really this looks like a compiler bug since it doesn't report the incorrect parameter direction; but also (more tenuously!) a bug in your code because it isn't declared as IN OUT. And, possibly, a further bug because you don't explicitly close the cursor - which does appear to 'fix' the issue too, sort of:
procedure callerproc is
lv_cursor sys_refcursor;
lv_string varchar2(20) := null;
begin
for i in 1..2 loop
getstring(lv_string);
getcursor(lv_cursor);
close lv_cursor;
end loop;
end callerproc;
which is probably more correct than assigning null to the cursor variable, which you mentioned also works.

PL/SQL - Call new instance of package procedure

I have package that uses global variables.
From one of the procedures i need to call "new instance" od another procedure (of the same package). I need this second procedure to use it's own version of global variables and when it's done i need first procedure to use variables as they were before.
I did a quick and dirty fix, at the beginning of second procedure i create backup variables and put global variables values in it and then setting global variables to NULL and at the end i put backup values back to global variables.
But i hope there is better solution than this one, a way to start new instance of that procedure/package
EDIT
Detailed scenario is like this:
Package has multiple procedures and functions that use variables defined at package level. I call firs procedure of the package and that procedure calls everything that is needed for current action. In some cases from second procedure i need to start new task (action) that has to be executed. In that case i would like to start again first procedure in package, but for it to use its own global variables (not to mess with original ones) because original procedure is not yet finished and still needs it's variables
Some of package global variables are custom type (table of custom_type to be exact) so it's creating additional problem for me.
Hope it's clear now what problem i have and what i'm trying to do
So it's something like this
Package 1
global_var 1
global_var 2
global_var 3
Procedure 1
some code
call to function 1
some code
call to procedure 2
and so on...
Procedure 2
some code
call function 3
some code
call to procedure 1 (start new process)
some code
call procedure 3
and so on....
Procedure 3
Procedure 4
Function 1
Function 2
Function 3
Function 4
And variables are like this
type type1 is record (x NUMBER, y VARCHAR2(1024), z NUMBER);
type type2 is table of type1;
global_var_1 NUMBER;
global_var_2 type2;
...
After reading your entire post (with the additional description), I came to the conclusion that your problem starts with a wrongly conceived view of the required process. In fact (and lending words from other programming languages) you need your package to spawn new threads with their own data context.
I see two possible solutions (in Oracle):
Write your package in JAVA (here I'm assuming that this option does allow the creation of threads, but never checked it),
Do not use GLOBAL VARIABLES.
For the first option I have no much to say except that it may be worth for you to investigate further.
As for the second option, you can (quite easily in fact):
Define a type a package level that will contain all the variables that you are currently using at global level and:
Add to all your functions in the package a parameter of the new type (I would put it as the last one) with default value NULL. Wherever needed, the definition of the new parameter can be set as IN OUT (for the case that one or more of the fields within the structure is updated within a function and the update needs to "bubble-up" to the invoking code).
Now, let's analyze the two scenarios (first invocation of FIRST procedure and second invocation from within the package):
FIRST INVOCATION (from outside the package): The new parameter will be empty (NULL) and the procedure will assign whatever values are needed to cascade throughout additional invocations of other functions/procedures;
SECOND INVOCATION (from within the package): can include a non-NULL value for the new parameter (depending on your needs); the code will then be able to prepare a new set of values (for a new variable of the type described above) which will then be cascaded through the additional invocations of other functions and procedures.
This method achieves exactly what you need while (as far as I can imagine) the required code changes would be minimal.
Hope this addresses the question you posted and the proposed approach serves you well.

Overloading oracle procedure with DATE and VARCHAR2

I have the following code that doesn't work. It compiles, but when called with sysdate as the parm_value parameter it throws PLS-00307: too many declarations of 'P_UPSERT_SDE_DATA' match this call If I comment out the varchar2 entry, the overload works as expected with just date and number datatypes.
What is the best way to go about what I'm trying to do, which is accept parameters differing only in parm_values datatype(specifically date and varchar2)?
PROCEDURE P_Upsert_SDE_Data(parm_table_name GORSDAV.GORSDAV_TABLE_NAME%TYPE,
parm_attr_name GORSDAV.GORSDAV_ATTR_NAME%TYPE,
parm_key GORSDAV.GORSDAV_PK_PARENTTAB%TYPE,
parm_user_id GORSDAV.GORSDAV_USER_ID%TYPE,
parm_value VARCHAR2);
--
PROCEDURE P_Upsert_SDE_Data(parm_table_name GORSDAV.GORSDAV_TABLE_NAME%TYPE,
parm_attr_name GORSDAV.GORSDAV_ATTR_NAME%TYPE,
parm_key GORSDAV.GORSDAV_PK_PARENTTAB%TYPE,
parm_user_id GORSDAV.GORSDAV_USER_ID%TYPE,
parm_value NUMBER);
PROCEDURE P_Upsert_SDE_Data(parm_table_name GORSDAV.GORSDAV_TABLE_NAME%TYPE,
parm_attr_name GORSDAV.GORSDAV_ATTR_NAME%TYPE,
parm_key GORSDAV.GORSDAV_PK_PARENTTAB%TYPE,
parm_user_id GORSDAV.GORSDAV_USER_ID%TYPE,
parm_value DATE);
You can see this if one of the others arguments you pass is being implicitly converted; from the call you posted I suspect var_FRB‌​GRNT_CODE is a different type and is being converted; e.g. that variable is a number and GORSDAV.GORSDAV_PK_PARENTTAB is a string.
From the documentation:
When trying to determine which subprogram was invoked, if the PL/SQL compiler implicitly converts one parameter to a matching type, then the compiler looks for other parameters that it can implicitly convert to matching types. If there is more than one match, then compile-time error PLS-00307 occurs, as in Example 8-34.
Implicit conversion of one of the other arguments makes it look for potential conversions of the others; it's only then that it sees the date and varchar2 versions, which can be implicitly converted to each other. If all of the arguments are of the same type as the table columns used for the formal parameter declarations then it won't look for implicit conversions, and won't be confused by the date/varchar2 versions.
Whenever I get this error, it seems to be that there's a mismatch between the spec declaration and the body declaration somewhere. Make sure each of your spec declarations match your body declarations exactly.

Oracle PL/SQL package compilation with interdependent procedure

If I have procedure Proc1 and another Procedure Proc2 where proc1 depends on proc2 and proc2 depends on proc1 . I need to compile package pkg1 with both these procedures .
Oracile 9i.
How can I achieve it? any particular keyword is required ?
I presume these are both private procedures, that is, neither appears in the package specification.
If both procedures appeared in the spec there would be no problem, because the body compiles against the public declaration.
Likewise if one procedure appeared in the spec then again there would be no problem: simply write the private procedure in the body before the public procedure.
But if both procedures are private, then you need to use forward declaration. This simply means declaring the procedures' signatures at the top of the package. Exactly like putting them in the spec, only private. Here is a spec ...
create or replace package pkg as
procedure main (n0 in out number);
end;
/
... and body ...
create or replace package body pkg as
-- forward declarations
procedure p1 (n1 in out number);
procedure p2 (n2 in out number);
-- actual declarations
procedure p1 (n1 in out number)
is
begin
dbms_output.put_line('P1='||n1);
if n1 < 3 then
p2(n1);
end if;
end p1;
procedure p2 (n2 in out number)
is
begin
dbms_output.put_line('P2='||n2);
n2 := n2+1;
if n2 < 3 then
p1(n2);
end if;
end p2;
procedure main (n0 in out number)
is
begin
if n0 < 0 then
p1(n0);
else
p2(n0);
end if;
end main;
end pkg;
/
"I need to have the interdependent procedures"
You really should try to figure out a way to avoid cyclic dependencies. It is a very bad programming practice. It is hard to understand how the procedures interrelate, and we have to manage the recursion and ensure that both procedures will definitely exit eventually without calling the other. Remember, this is not just a problem you have: it's a legacy you will pass one to future maintainers of your code.
You can break the dependency chain by using packages and compiling all your package specs prior to compiling package bodies. The specs will all compile without regard to dependencies and then when the bodies are compiled they can do so in any order as they will reference the already compiled specifications. Typically, you'll have package specs stored with a *.pks suffix and bodies with *.pkb suffix and some sort of build script with compiles them. Like so
/* Master Package Build Script */
-- Specs
#package_a.pks
#package_b.pks
#package_c.pks
-- Bodies
#package_a.pkb
#package_b.pkb
#package_c.pkb
If both procedures are in the specification, simply compiling the package should work:
alter package your_package compile;
This assumes that your package is valid, of course.
If either (or both) procedure is not in the specification, the best thing to do is list it as a forward declaration at the beginning of the package body.

Nested PIPELINED function is skipped by Oracle

I'll post a trivial example, which actually works, just to get an approximate picture of what I'm trying to achieve:
Here is the 'inner' function which takes the data from some table, called test_tab:
create or replace function test_inner RETURN num_typ PIPELINED
IS
BEGIN
FOR cur in (
SELECT x FROM test_tab
)
LOOP
PIPE ROW(cur.x);
END LOOP;
END;
/
Here is the 'outer' function which uses the result of inner function and transforms them appropriately:
create or replace function test_outer RETURN num_typ PIPELINED
IS
BEGIN
FOR x IN (
SELECT * FROM table(test_inner())
)
LOOP
PIPE ROW(x.column_value * 2);
END LOOP;
END;
/
And here is how I use it:
begin
execute immediate 'insert into test_tab(x) values(1)';
execute immediate 'insert into test_tab(x) values(2)';
execute immediate 'insert into test_tab(x) values(3)';
FOR x IN (
select * from table(test_outer())
) LOOP
DBMS_OUTPUT.put_line(x.column_value);
END LOOP;
end;
/
The problem is that test_inner function seems to be ignored by Oracle. When it is called separately, it 'sees' the data inserted prior to its execution. But when it's called as a part of test_outer it doesn't return any data, or maybe doesn't get called at all.
Like I said, the above example will work. But my case is a little bit more complex, so I can't post it entirely.
The test case you have posted does not reproduce the problem you claim to be seeing. So it is not a test case.
#AlexPoole has provided you with some other ideas. Basically there is nothing further we can do unless you can post a better test case, given that you cannot post the entire things (and we don't want to go through hundreds of lines of somebody else's shonky code - I for one get enough of that debugging my own stuff).
Producing a poor test case has not been a waste of time. At least you can rule out certain things: it's nothing to do with one pipelined function calling another. So it's clearly something in your specific implementation. Perhaps not in the internal function logic, but how they are called, the data they work with or some other part of the infrastructure.
All you can do is analyse the individual parts. Start with the core component, and build out, adding other components until you find the bit which breaks. Yes this is a tedious chore. But from your comments it sounds like you're quite a way through this process already.
I'm marking this CW as it isn't an answer to the question, just an extended comment.
Your both pipelined functions in fact are syntactic wrong: you are missing RETURN
See docs

Resources