Search code examples
oracleplsqloracle10gref-cursor

Column names in an empty Oracle REF CURSOR


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?


Solution

  • 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