Showing posts with label pl/sql packages. Show all posts
Showing posts with label pl/sql packages. Show all posts

Thursday, March 20, 2008

Handling ORA-04068: Existing state of packages has been discarded

How to deal with the frustrating ORA-04068: existing state of packages has been discarded error that crops up while in the circular cycle of "code..execute..debug".

ORA-04068: existing state of packages has been discarded
ORA-04061: existing state of package "PKG.PROC" has been invalidated
ORA-04065: not executed, altered or dropped package "PKG.PROC"
ORA-06508: PL/SQL: could not find program unit being called
Previously, the only thing I knew to do was logout and then log back in. This works, but it is kind of a waste of time. I knew there had to be a better way because my PL/SQL Editor (PL/SQL Developer) doesn't require this step. I probably could have asked the guys at AllAround, but I think that I found the answer:
begin
   DBMS_SESSION.RESET_PACKAGE;
   -- execute routine... 
end;
Addendum:
To respond to Abida's comment, you can run into this issue even if all the packages under your ID are valid. Your debug/test session however may not "see" the valid state of these packages. The snippet above fixes the issue with our debug/test session. Thanks, Jason

Tuesday, January 15, 2008

Upsert / Merge within the Same Table (Oracle)

Okay, I'm a huge fan of using Oracle's %ROWTYPE to define records structures. I love writing code like the following:

DECLARE

   rec_contract  CONTRACT%ROWTYPE := Null;

BEGIN

   -- ... retrieval logic and business logic ...

   -- "Set" logic
   UPDATE CONTRACT
   SET ROW = rec_contract
   WHERE CONTRACT_ID = rec_contract.CONTRACT_ID;

   IF (SQL%ROWCOUNT = 0) THEN
      INSERT INTO CONTRACT
      VALUES rec_contract;
   END IF;
END;
Code like the above is really convenient for writing "setters". It can easily be extended to handle DELETE logic too. Now, recently I needed to copy a bunch of data within the same table but with a different primary key. I started to write a bunch of code like the above, but I just needed some of code. To cut to the chase, the code below is an example of how to do an "UPSERT" (like a MERGE) but within the same table [which is impossible with the MERGE command]. Source:
drop table test;
clear;

create table test(
   id number(10),
 name varchar2(50)
);

insert into test(id,name) values (1,'First row');

insert into test
   using select 2,'Row two' from dual;

insert into test
   using select 3,'Row three - unmodified' from dual;

commit;

select * from test;

update test set (id,name) = (select 3,'Row three - modified' from test where id = 3) where id = 3;

commit;

select * from test;
Results:
Table created
 
1 row inserted
 
1 row inserted
 
1 row inserted
 
Commit complete
 
         ID NAME
----------- --------------------------------------------------
          1 First row
          2 Row two
          3 Row three - unmodified
 
1 row updated
 
Commit complete
 
         ID NAME
----------- --------------------------------------------------
          1 First row
          2 Row two
          3 Row three - modified

Tuesday, November 21, 2006

Ruby: Invoking a PL/SQL Package with Array args

I'm fairly new to Ruby (and subsequently to Rails). I would be normally be labeled an "Enterprise Developer"... yada, yada. Anyway, I'm trying to invoke a Oracle PL/SQL Package from Ruby that has IN and OUT arguments that are arrays (TABLE OF VARCHAR2 INDEX BY BINARY-INTEGER, in Oracle PL/SQL terms). We have a fairly extensive library of PL/SQL that I want to reuse. This technique also works with Oracle Types (CREATE OR REPLACE TYPE PROD_TYPES.TYPE_STRING_ARRAY AS TABLE OF VARCHAR2(2000);).

Related Posts :

I've spent several weeks trying to come up with a solution. First, the best solution would be for Oracle to write the OCI driver for Ruby. OCI8 [http://rubyforge.org/projects/ruby-oci8/] (by Kubo Takehiro) is wonderful, but he is still a person. For companies to adopt and support Ruby/Rails, the support needs to be more robust. Upper Management and Operations resist open source. Single person supported software is easier for them to dismiss. I would prefer that Oracle had an OCI expert write and maintain the library (along the lines of their support for PHP). If this were the case, then the driver could support returning arrays natively.

My PL/SQL Packages :

CREATE OR REPLACE PACKAGE common_func IS
TYPE string_table IS TABLE OF VARCHAR2(2000) INDEX BY BINARY_INTEGER;
END common_func;
/
CREATE OR REPLACE PACKAGE BODY common_func
IS
BEGIN
   NULL;
END common_func;
/
create or replace package ruby_test is

function f_ruby(s in number,t out varchar2,st out common_func.string_table)

end ruby_test;
/
create or replace package body ruby_test is

function f_ruby(s in number,t out varchar2,st out common_func.string_table)
return varchar2
is
begin
   t := 'outta here';
   st(1) := 'array 1';
   st(2) := 'array 2';
   return 'Ruby rocks '||TO_CHAR(NVL(s,5))||' times!';
end;

begin
   null;
end ruby_test;
Desired Ruby :
cursor = conn.parse("BEGIN :result := ruby_test.f_ruby(s => :in,t => :out,st => st); END;")

cursor.bind_param(':result', nil, String, 100)
cursor.bind_param(':in', 10)
cursor.bind_param(':out', nil, String, 100)
# cursor.bind_param(':out_array', Array, 100) <= I tried this too!
cursor.bind_param(':out_array', String[], 100)

cursor.exec()

p cursor[':result'] # => 'Ruby rocks 10 times!'
p cursor[':out'] # => 'outta here'
p cursor[':out_array'] # => 'st(1) = array 1, st(2) = array 2' !!! Fails
First Solution : Convert the array to a string, bind to that string, and then split the delimited string apart on the Ruby side. I don't care for this solution for a couple of reasons :
  • Size issue at 32K
  • What delimiter should I use? How do I know that it will be the "right" delimiter?
First Solution (Revised) : I could switch from a string bind argument to a CLOB. OCI8 supports CLOBs, but I couldn't get it to work. The documentation is incomplete, and I'm not fluent enough in Ruby. Hints back to my issue with the library being solely maintained.

Current Solution : Convert the string array to a reference cursor (SYS_REFCURSOR) and use OCI8's bind to OCI::CURSOR support. Type :

CREATE OR REPLACE TYPE PROD_TYPES.TYPE_STRING_ARRAY AS TABLE OF VARCHAR2(2000)
Cursor Package :
CREATE OR REPLACE PACKAGE cursor_func IS
/**
Converts an array into a SYS_REFCURSOR (System Reference Cursor)

@Return
{*} SysRefCursor Success
{*} Exception Error
*/
FUNCTION f_array_to_SYSREFCURSOR(
   st_array_in       IN   common_func.STRING_TABLE )
RETURN SYS_REFCURSOR;
PRAGMA RESTRICT_REFERENCES(f_array_to_sysrefcursor,WNDS,TRUST);

END cursor_func;
/
CREATE OR REPLACE PACKAGE BODY cursor_func IS

/**
Converts an array into a SYS_REFCURSOR (System Reference Cursor)

@Return
{*} SysRefCursor Success
{*} Exception Error
*/
FUNCTION f_array_to_SYSREFCURSOR(
   st_array_in       IN   common_func.STRING_TABLE )
RETURN SYS_REFCURSOR
IS
   lsysrefcursor_array  SYS_REFCURSOR;
   le_error      EXCEPTION;

   lst_prodtypes    prod_types.type_string_array;

BEGIN

   lst_prodtypes := common_func.convert_table(st_array_in);

  OPEN lsysrefcursor_array FOR
     SELECT column_value FROM TABLE(cast(lst_prodtypes AS prod_types.type_string_array));

   RETURN lsysrefcursor_array;

END f_array_to_SYSREFCURSOR;

BEGIN
   Null;
END cursor_func;
Ruby Source :
plsql = conn.parse(
"DECLARE "+
"   st common_func.string_table; " +
"BEGIN "+
"   :result := ruby_test.f_ruby(s => :in,t => :out, st => st); " +
"   :cst := cursor_func.f_array_to_SYSREFCURSOR(st_array_in => st); " +
"END;")

plsql.bind_param(':result', nil, String, 100)
plsql.bind_param(':in', 10)
plsql.bind_param(':out', nil, String, 100)
plsql.bind_param(':cst', OCI8::Cursor)

plsql.exec()

puts "\nResults from returning a SysRefCursor\n"

p plsql[':result'] # => 'Ruby rocks 10 times!'
p plsql[':out'] # => 'outta here'

cursor = plsql[':cst']

plsql.close

x = ''
while r = cursor.fetch()
x = x + r.join(', ') + "\n"
end
cursor.close() # <= Don't forgot this

puts x
And success, finally!
Results from returning a SysRefCursor
"Ruby rocks 10 times"
"outta here"
array 1
array 2
Warning : There are two potential gotchas to this solution :
  • If the developer forgets the cursor.close statement, the transaction could be jeopardized if there are too many cursors opened. I don't remember which ORA-##### this is.
  • A single database session could run into issues with just having too many reference cursors open at any one instant.
Native support for arrays would be a blessing.

Posts of Interest :

Tuesday, October 10, 2006

Oracle VARRAY Example

(Revised 09/20/2010, I've revised this example to be more pertinent.)
VARRAYs provide the interesting ability to store multiple values in a single column. Note, "interesting" doesn't mean I'm necessarily encouraging its use. I expect there are some potentially significant performance penalties using VARRAYs.

set serveroutput on;

column id FORMAT A5;
column url FORMAT A20;
column Tag(s) FORMAT A20;

clear;

-- Create a VARRAY that can hold 10 "objects" [cells] of type VARCHAR2 (i.e. 10 Tags of up to 50 characters)
create or replace type vcarray_tags as VARRAY(10) OF VARCHAR2(50);
/

-- Each URL is stored in a single row regardless of the number of tags
create table site_tags (id number, url varchar2(100), tag_list vcarray_tags);

insert into site_tags values (1, 'www.bing.com', vcarray_tags('search', 'microsoft'));
insert into site_tags values (2, 'www.google.com', vcarray_tags('search', 'google'));
commit;

set echo on;

-- Note, The VARRAY column is of type "object"
select 
   *
from
   site_tags;

-- Display each tag on a separate row 
select 
   site_tags.id, 
   site_tags.url,
   tags.column_value "Tag(s)"
from
   site_tags,
   table(site_tags.tag_list) tags;

-- Show all the URLs "tagged" with 'search'
select 
   site_tags.id, 
   site_tags.url,
   tags.column_value "Tag(s)"
from
   site_tags,
   table(site_tags.tag_list) tags
where
   tags.column_value = 'search';
/
-- PL/SQL Example 
begin
   for c in (select * from site_tags) loop
      dbms_output.put_line(c.id||' : '||c.url);
      for i in c.tag_list.first .. c.tag_list.last loop
         dbms_output.put_line('      Tag: '||c.tag_list(i));
      end loop;
   end loop;
end;
Results:
create or replace type vcarray_tags as VARRAY(10) OF VARCHAR2(50);
/
Type created
create table site_tags (id number, url varchar2(100), tag_list vcarray_tags);
 
Table created
insert into site_tags values (1, 'www.bing.com', vcarray_tags('search', 'microsoft'));
 
1 row inserted
insert into site_tags values (2, 'www.google.com', vcarray_tags('search', 'google'));
 
1 row inserted
commit;
 
Commit complete
set echo on;

select
 *
from
 site_tags;
 
   ID URL                  TAG_LIST
----- -------------------- --------
    1 www.bing.com         <Object>
    2 www.google.com       <Object>

select
 site_tags.id,
 site_tags.url,
 tags.column_value "Tag(s)"
from
 site_tags,
 table(site_tags.tag_list) tags;
 
   ID URL                  Tag(s)
----- -------------------- --------------------
    1 www.bing.com         search
    1 www.bing.com         microsoft
    2 www.google.com       search
    2 www.google.com       google

select
 site_tags.id,
 site_tags.url,
 tags.column_value "Tag(s)"
from
 site_tags,
 table(site_tags.tag_list) tags
where
 tags.column_value = 'search';
 
   ID URL                  Tag(s)
----- -------------------- --------------------
    1 www.bing.com         search
    2 www.google.com       search

begin
 for c in (select * from site_tags) loop
  dbms_output.put_line(c.id||' : '||c.url);
  for i in c.tag_list.first .. c.tag_list.last loop
   dbms_output.put_line('      Tag: '||c.tag_list(i));
  end loop;
 end loop;
end;
/
 
1 : www.bing.com
      Tag: search
      Tag: microsoft
2 : www.google.com
      Tag: search
      Tag: google

Tuesday, September 05, 2006

Oracle ORA-03117: two-task save area overflow

Feel free to skip to the bottom and read the "Update"...

Recently, I made some changes in Development to an Oracle Package. The package compiled cleanly and all the unit tests passed successfully.

However upon testing in one of our client applications, we started getting an "ORA-03117: two-task save area overflow" error. It is similar to an ORA-00600 error, useless to us, and usually results in Oracle saying "upgrade to x" [Oracle 10g, in this case].

What is particularly distressing about this particular case is that the Powerbuilder code errors at the package level. The changes were not even being referenced directly by PowerBuilder application.


Update I've discovered that I could recreate this error solely within SQL-Plus by "desc <complex packagename>". If the "complex" package has a reasonable number of nested records/types, you can get this error.

DESC DBMS_METADATA;
We were using either Oracle 8 or Oracle 9 SQL-Net clients for the communication layer. If you are using the 10.2.x SQL-Net client, you don't encounter an error. Upgrading to a newer version of SQL-Net fixes this problem. It is NOT an application code issue.