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 :
- http://rubyforge.org/projects/ruby-oci8/
- http://wiki.rubyonrails.org/rails/pages/OracleStoredProceduresAsDataSource
- http://www.oracle.com/technology/pub/articles/haefel-oracle-ruby.html
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?
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 2Warning : 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.
Posts of Interest :