Search code examples
oracleplsql

Why does Oracle think that a strong reference cursor is not correctly declared?


I’m trying to create some stored procedures, one of which accepts a strong reference cursor as a parameter. Although I think I’ve correctly specify the strong reference cursor I encounter an error when I attempt to create a unit test for this stored procedure.

The following commands were executed by SYSTEM:


    CREATE USER HR IDENTIFIED BY HR;
    grant CREATE session to hr;
    grant create any procedure to HR;
    the following is how I connect to Oracle via sqlplus:
    sqlplus hr/HR@connectionstring:7152 

contents of create_emp_table.sql:
DROP TABLE "HR"."EMP";
CREATE TABLE "HR"."EMP"
(
  EMPNO            NUMBER(4)                             PRIMARY KEY,
  ENAME            VARCHAR2(10),                                  
  JOB              VARCHAR2(9),                                   
  MGR              NUMBER(4),                                     
  HIREDATE         DATE,                                          
  SAL              NUMBER(7,2),                                   
  COMM             NUMBER(7,2),                                   
  DEPTNO           NUMBER(2)                                     
);
END;

SQL> @create_emp_table.sql

Table dropped.
Table created.


SQL>  @create_pkgbdy_hr.plb

Package body created.

SQL> @create_pkgbdy_hr-child.plb
Sequence dropped.
Sequence created.
Package body created.
Contents of create_pkgbdy_hr.plb:
CREATE sequence  employees_seq INCREMENT BY 1 START WITH 1;
CREATE OR REPLACE
PACKAGE BODY hr_child
IS

-- Read all employee details
   PROCEDURE get_all_emp_details(
                                   p_details IN OUT hr_child.empl_details_refcur_t
                                )   
IS    BEGIN

  --
  OPEN p_details FOR
     SELECT *
     FROM   emp;
  --
END;

--

various procedures redacted
END;
/

Contents of create_pkg_hr-child.pls follows.
Note that  empl_details_refcur_t is a strong reference cursor based on the emp table
CREATE OR REPLACE
PACKAGE hr_child
IS
   --
   TYPE empl_details_refcur_t IS REF CURSOR RETURN emp%ROWTYPE;   
   TYPE dept_details_refcur_t IS REF CURSOR RETURN dept%ROWTYPE;
   --===========================================================
various procedures redacted
END;
/

SQL> @create_pkgbdy_hr.plb

Package body created.
    --
    -- contents of create_pkgbdy_hr.plb follows:
    --
    CREATE OR REPLACE
    PACKAGE BODY hr
    IS
       --
    various procedures redacted   --
       -----------------------------------------------------------------------------------
       --
       -- obtain details for all employees
       --
       PROCEDURE employees_r(
                               p_details IN OUT hr_child.empl_details_refcur_t
                            )
       IS
       BEGIN
          --
          OPEN p_details FOR
             SELECT *
             FROM   emp;
          --
          --
       END;
    various procedure redacted   --
    END;
    /

SQL> @create_pkg_hr.pls

Package created. Contents of create_pkg_hr.pls follows:

    --
    -- hr.pls
    
    -- The "CRUD indicator" is a letter following the last underscore in
    -- the procedure's name.  In this package the various procedures
    -- that affect the employee table are:
    --
    --    employees_c()  -- create an employee
    --    employees_r()  -- read employee details
    --    employees_u()  -- update  employee details
    --    employees_d()  -- destroy an employee
    --
    
    CREATE OR REPLACE
    PACKAGE hr
    IS
       --
       --===========================================================
       --
       -- emp
       --
       -----------------------------------------------------------------------------------
    various procedures redacted
       -----------------------------------------------------------------------------------
       --
       -- Read all emp' details
       --
       PROCEDURE employees_r(
                                 p_details    IN OUT  hr_child.empl_details_refcur_t
                              );
       --
    various procedures redacted    --
    END;
    /

OK the emp table is created and the packages built without error. Now create a unit test. ut_hr.sql follows:

    -- unittest
    --

    SET serverout ON
    SET linesize 300
    COLUMN name FORMAT A30
    CREATE OR REPLACE
    PROCEDURE read_employee
        IS
    
    BEGIN
        DECLARE
            io          hr_child.empl_details_refcur_t;  
            v_retcode   NUMBER := NULL;
    
        BEGIN
            hr.employees_r(io);
        END;
        DBMS_OUTPUT.PUT_LINE('v_employee_id,   '  || '(' || TO_CHAR( io.EMPNO)    || ')' );
    END;
    /

    SQL> @ut_hr.sql
    LINE/COL ERROR
    -------- -----------------------------------------------------------------
    12/5     PL/SQL: Statement ignored
    12/66    PLS-00201: identifier 'IO.EMPNO' must be declared

so it appears that empno is not part of hr_child.empl_details_refcur_t; because hr_child.empl_details_refcur_t is a strong ref cursor it should match the columns in the emp table


    SQL> desc emp
     Name                                      Null?    Type
     ----------------------------------------- -------- -------
     EMPNO                                     NOT NULL NUMBER(4)
     ENAME                                              VARCHAR2(10)
     JOB                                                VARCHAR2(9)
     MGR                                                NUMBER(4)
     HIREDATE                                           DATE
     SAL                                                NUMBER(7,2)
     COMM                                               NUMBER(7,2)
     DEPTNO                                             NUMBER(2)
    
    SQL>

empno is a column in the emp table. So why must 'IO.EMPNO' must be declared?


Solution

  • The scope where the IO variable was declared has ended and, in the outer scope of the procedure there is no IO variable.

    CREATE OR REPLACE PROCEDURE read_employee
    IS 
          -- Declare section for outer scope
    BEGIN -- Begin outer scope
      DECLARE -- Declare section for inner scope
        io          hr_child.empl_details_refcur_t;  
        v_retcode   NUMBER := NULL;
      BEGIN   -- Begin inner scope
        hr.employees_r(io);
      END;    -- End inner scope
          -- Continue outer scope
      DBMS_OUTPUT.PUT_LINE('v_employee_id,   '  || '(' || TO_CHAR( io.EMPNO)    || ')' );
    END;
    /
    

    To fix it, remove the inner PL/SQL block and move everything to the outer block. Once you do that you also need to fix that IO is a cursor and is not a row from the table and a cursor does not have column attributes so IO.EMPNO does not exist - to get the row you need to loop through the cursor and fetch the row and then row.EMPNO will exist:

    CREATE OR REPLACE PROCEDURE read_employee
    IS 
      io hr_child.empl_details_refcur_t;  
      rw EMP%ROWTYPE;
    BEGIN
      hr.employees_r(io);
      LOOP
        FETCH io INTO rw;
        EXIT WHEN io%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE('v_employee_id, (' || rw.EMPNO || ')' );
      END LOOP;
      CLOSE io;
    EXCEPTION
      WHEN OTHERS THEN
        IF io%ISOPEN THEN
          CLOSE io;
        END IF;
        RAISE;
    END;
    /
    

    fiddle