In PL/SQL, I can use a trick like this one here to find out name/value pairs for every column in a row from a REF CURSOR
:
TO_CHAR of an Oracle PL/SQL TABLE type
That's an awesome trick. But it doesn't work when the REF CURSOR
is empty, such as this one here (that's just an example. The real cursor doesn't select from DUAL
):
OPEN cursor FOR SELECT 1 FROM DUAL WHERE 1 = 0;
Does an empty REF CURSOR
even have column name/type information?
Yes, I've tried that solution without rows, and you're right. From my limited point of view, I think here we need two different methods to retrieve columns' names and values.
1) Dbms_sql package to retrieve the columns' names.
2) The tbone method to retrieve the data.
Procedure
create or replace procedure demo(sqlText in varchar2) is
refCur sys_refcursor;
curId integer;
cnt number;
ret dbms_sql.desc_tab;
recTab dbms_sql.desc_tab;
FORMAT_STRING constant pls_integer := 20;
procedure printDescTab(desctab in sys.dbms_sql.desc_tab) is
begin
-- do what you want with the columns
for i in 1 .. desctab.count
loop
dbms_output.put(lpad(desctab(i).col_name, FORMAT_STRING));
end loop;
dbms_output.new_line;
end printDescTab;
procedure PrintCur(cv in sys_refcursor) is
begin
for c in ( --select t2.COLUMN_VALUE.getrootelement() name,
select EXTRACTVALUE(t2.COLUMN_VALUE, 'node()') value
from table(XMLSEQUENCE(cv)) t
,table(XMLSEQUENCE(EXTRACT(COLUMN_VALUE, '/ROW/node()'))) t2)
loop
DBMS_OUTPUT.put(lpad(c.VALUE, FORMAT_STRING));
end loop;
dbms_output.new_line;
dbms_output.new_line;
end;
begin
dbms_output.put_line('dynamic sql: ' || sqlText);
curId := dbms_sql.open_cursor();
-- checks for sql injection to do...
dbms_sql.parse(curId, sqlText, dbms_sql.native);
dbms_sql.describe_columns(curId, cnt, recTab);
printDescTab(recTab);
dbms_sql.close_cursor(curId);
open refCur for sqlText;
PrintCur(refCur);
close refCur;
exception
when others then
if dbms_sql.is_open(curId) then
dbms_sql.close_cursor(curId);
end if;
if refCur%isopen then
close RefCur;
end if;
dbms_output.put_line(sqlcode || ' - ' || sqlerrm);
end demo;
Test
declare
sqlText varchar2(2000);
begin
sqlText := 'select 1 as one, 2 as two from dual where 1=0';
demo(sqlText);
sqlText := 'select name, type || chr(13) type' -- chr(13) specific ASCII Carriage return
||' from user_plsql_object_settings'
||' where name not like ''%$%'' and rownum <= 10';
demo(sqlText);
sqlText := 'select 1 as one, 2 as two from dual ';
demo(sqlText);
exception
when others then
dbms_output.put_line(sqlcode || ' - ' || sqlerrm);
end;
Result
dynamic sql: select 1 as one, 2 as two from dual where 1=0
ONE TWO
dynamic sql: select name, type || chr(13) type from user_plsql_object_settings where name not like '%$%' and rownum <= 10
NAME TYPE
ADD_JOB_HISTORY PROCEDURE
AFT_INS_TEST_TRG TRIGGER
BEF_DEL_TEST_TRG TRIGGER
BEF_INS_TEST_TRG TRIGGER
BETWNSTR FUNCTION
BOOL FUNCTION
CACHED_FIBONACCI FUNCTION
DEBUG PACKAGE
DEBUG PACKAGE BODY
DEBUG_TEST PROCEDURE
dynamic sql: select 1 as one, 2 as two from dual
ONE TWO
1 2