Question

In our Oracle(11g)-based application, we are making heavy use of pipelined functions. Now it turns out that error reporting from such functions is difficult, as shown in the (cut-down) example below. The procedure exists to be called from Java, and it shall receive errors occurring anywhere during the PL/SQL execution.

ORACLE part:

set serveroutput on

create table dummy (id NUMBER);

create or replace package mytest
as
        type t_rec is record (id integer);
        type t_tab is table of t_rec;
        type t_ref_cur IS REF CURSOR RETURN t_rec;

        function foo
        return t_tab pipelined;

        procedure bar( p_ref_cur out t_ref_cur);
end mytest;
/
show errors

create or replace package body mytest
as
        function foo
        return t_tab pipelined
        is
                v_cur SYS_REFCURSOR;
                v_sql varchar2(2000);
                v_rec t_rec;
        begin
                v_sql := 'select wrong_column from DUMMY';
                open v_cur for v_sql;
                loop
                        fetch v_cur into v_rec;
                        exit when v_cur%notfound;

                        pipe row (v_rec);
                end loop;
        exception
                when no_data_needed then
                        null;
                when others then
                        dbms_output.put_line(SQLCODE||' '||sqlerrm );
                        raise no_data_found;
        end foo;

        procedure bar( p_ref_cur out t_ref_cur)
        is
        begin
                open p_ref_cur for select * from table(foo);
        end bar;
end mytest;
/
show errors

-- call procedure bar() from pl/sql
set serveroutput on
declare v_ref_cur mytest.t_ref_cur;
        v_rec mytest.t_rec;
begin
        mytest.bar(v_ref_cur);
        loop
              fetch v_ref_cur into v_rec;
              exit when v_ref_cur%notfound;
              dbms_output.put_line(v_rec.id);
      end loop;
end;
/
show errors

Running the above function results in the exception being shown:

ORA-00904: "WRONG_COLUMN": invalid column name

Java part:

package test1;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import oracle.jdbc.OracleTypes;

public class start {

    public static void main(String[] args) 
    {
        try 
        {
            Connection con = DriverManager.getConnection("jdbc:oracle:thin:@...", "…", "…");
            CallableStatement stmt = con.prepareCall("BEGIN mytest.bar(?); END;");
            stmt.registerOutParameter(1, OracleTypes.CURSOR);
            stmt.executeQuery();
            ResultSet rs = (ResultSet)stmt.getObject(1);
            while (rs.next())
            {
                System.out.println(rs.getInt(1));
            }
            stmt.close();
            con.close();
        } catch (Exception e) 
        {
            e.printStackTrace();
        }
    }
}

Exception will be not catched. ResultSet is empty.

Is this the expected behavior? Do we do something wrong? Does a workaround exist?

Was it helpful?

Solution

The problem is you're raising no_data_found, which is not treated as an exception by the cursor in bar - it's valid for a select (which the cursor you open is, effectively) to return no rows. When you run your anonymous block you are only seeing the dbms_output call, not a raised exception - the block completes successfully, and if you set serveroutput off you wouldn't see anything. (Edit: this old AskTom thread talks about this behaviour too).

It isn't clear why you're trying to change the exception type after you've caught it. The only reason I can see to do that would be to get the behaviour you have, where any error gives you an empty result set rather than an exception you have to handle. If that isn't what you want to happen then you shouldn't do that, to state the obvious a bit. If it is then you'd need to look for the dbms_output message from the Java side.

But I don't see why you would want to do that in preference to just reraising the original exception:

            when others then
                    dbms_output.put_line(SQLCODE||' '||sqlerrm );
                    raise;

... or since the dbms_output isn't always going to be visible to the client, just not catching others at all. It seems a bit pointless and counterintuitive to catch and squash an exception when you want the (Java) client to see it.

The exception on the Java side would then be something like (from e.getMessage()):

ORA-00904: "WRONG_COLUMN": invalid identifier
ORA-06512: at "SCHEMA.MYTEST", line 23

Perhaps you want to hide the PL/SQL stack trace and just show a single line of the first error encountered, but it seems like it would be rather more useful to keep that information so you can track down the error.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top