Skip Headers
Oracle® Database PL/SQL Language Reference
11g Release 1 (11.1)

Part Number B28370-01
Go to Documentation Home
Home
Go to Book List
Book List
Go to Table of Contents
Contents
Go to Index
Index
Go to Master Index
Master Index
Go to Feedback page
Contact Us

Go to previous page
Previous
Go to next page
Next
View PDF

8 Using PL/SQL Subprograms

This chapter explains how to turn sets of statements into reusable subprograms. Subprograms are the building blocks of modular, maintainable applications.

Topics:

What Are PL/SQL Subprograms?

A PL/SQL subprogram is a named PL/SQL block that can be invoked with a set of parameters. You can declare and define a subprogram within either a PL/SQL block or another subprogram.

Topics:

Subprogram Parts

A subprogram consists of a specification ("spec") and a body. To declare a subprogram, you must provide the spec, which includes descriptions of any parameters. To define a subprogram, you must provide both the spec and the body. You can either declare a subprogram first and define it later in the same block or subprogram, or you can declare and define it at the same time.

A subprogram body has the parts described in Table 8-1.

Table 8-1 Subprogram Body Parts

Part Description

PRAGMA AUTONOMOUS_TRANSACTION (optional)

Makes the subprogam autonomous (independent). For more information, see "Doing Independent Units of Work with Autonomous Transactions".

Declarative part (optional)

Contains declarations of local types, cursors, constants, variables, exceptions, and nested subprograms (these items cease to exist when the subprogram ends). Does not begin with the keyword DECLARE, as the declarative part of a block does.

Executable part

Contains statements that assign values, control execution, and manipulate data.

Exception-handling part (optional)

Contains code that handles run-time errors.


Subprogram Types

PL/SQL has two types of subprograms, procedures and functions. Typically, you use a procedure to perform an action and a function to compute and return a value.

A procedure and a function have the same structure, except that only a function has the following items:

Item Description
RETURN clause Specifies the datatype of the return value (required).
RETURN statement Specifies the return value (required).
DETERMINISTIC option Helps the optimizer avoid redundant function calls.
PARALLEL_ENABLED option Allows the function to be used safely in slave sessions of parallel DML evaluations.
PIPELINED option Returns the results of a table function iteratively.
RESULT_CACHE option Stores function results in the cross-session function result cache.
RESULT_CACHE clause Specifies the data sources on which the results of a function.

For the syntax of subprogram declarations and definitions, including descriptions of the items in the preceding table, see "Procedure Declaration and Definition" and "Function Declaration and Definition". For additional information, see "Using the RETURN Statement" and "Using the Cross-Session PL/SQL Function Result Cache".

Note:

PL/SQL procedures and functions are different from SQL procedures and functions created with the SQL statements CREATE PROCEDURE and CREATE FUNCTION.

See Also:

Subprogram Calls

A subprogram call has this form:

subprogram_name [ (parameter [, parameter]... ) 

A procedure call is a PL/SQL statement. For example:

raise_salary(emp_id, amount);

A function call is part of an expression. For example:

IF sal_ok(new_sal, new_title) THEN ...

For information about subprogram parameters, see "Passing Parameters to PL/SQL Subprograms".

Subprogram Examples

In Example 8-1, a block declares, defines, and invokes a string-manipulation procedure, double, which accepts input and output parameters and handles potential errors. The procedure double includes the optional exception-handling part.

Example 8-1 Simple PL/SQL Procedure

-- Declarative part of block begins
DECLARE
  in_string  VARCHAR2(100) := 'This is my test string.';
  out_string VARCHAR2(200);
  -- Procedure spec begins
  PROCEDURE double (original IN VARCHAR2,
                    new_string OUT VARCHAR2) AS
  -- Procedure spec ends
  -- Procedure body begins
    -- Executable part begins
    BEGIN
      new_string := original || ' + ' || original;
    -- Executable part ends
    -- Exception-handling part begins
      EXCEPTION
      WHEN VALUE_ERROR THEN
        DBMS_OUTPUT.PUT_LINE('Output buffer not long enough.');
    END;
  -- Exception-handling part ends
  -- Procedure body ends
-- Declarative part of block ends
-- Executable part of block begins
BEGIN
  double(in_string, out_string);
  DBMS_OUTPUT.PUT_LINE(in_string || ' - ' || out_string);
END;
-- Executable part of block ends
/

In Example 8-2, a block declares, defines, and invokes a numeric function, square, which declares a local variable to hold temporary results and returns a value when finished. The function square omits the optional exception-handling part.

Example 8-2 Simple PL/SQL Function

-- Declarative part of block begins
DECLARE
  -- Function spec begins
  FUNCTION square (original NUMBER)
    RETURN NUMBER
  AS
    original_squared NUMBER;
  -- Function spec ends
  -- Function body begins
    -- Executable part begins
    BEGIN
      original_squared := original * original;
      RETURN original_squared;
    END;
    -- Executable part ends
  -- Function body ends
-- Declarative part of block ends
-- Executable part of block begins
BEGIN
  DBMS_OUTPUT.PUT_LINE(square(100));
END;
-- Executable part of block ends
/

Why Use PL/SQL Subprograms?

Using the RETURN Statement

The RETURN statement immediately ends the execution of a subprogram and returns control to the caller. Execution continues with the statement following the subprogram call. (Do not confuse the RETURN statement with the RETURN clause in a function spec, which specifies the datatype of the return value.)

A subprogram can contain several RETURN statements. The subprogram does not have to conclude with a RETURN statement. Executing any RETURN statement completes the subprogram immediately.

In procedures, a RETURN statement does not return a value and so cannot contain an expression. The statement returns control to the caller before the end of the procedure.

In functions, a RETURN statement must contain an expression, which is evaluated when the RETURN statement is executed. The resulting value is assigned to the function identifier, which acts like a variable of the type specified in the RETURN clause. See the use of the RETURN statement in Example 8-2.

The expression in a function RETURN statement can be arbitrarily complex:

CREATE OR REPLACE FUNCTION half_of_square(original NUMBER)
  RETURN NUMBER IS
BEGIN
   RETURN (original * original)/2 + (original * 4);
END half_of_square;
/

In a function, there must be at least one execution path that leads to a RETURN statement. Otherwise, you get a function returned without value error at run time.

Declaring Nested PL/SQL Subprograms

You can declare subprograms in any PL/SQL block, subprogram, or package. The subprograms must go at the end of the declarative section, after all other items.

You must declare a subprogram before invoking it. This requirement can make it difficult to declare several nested subprograms that invoke each other.

You can declare interrelated nested subprograms using a forward declaration: a subprogram spec terminated by a semicolon, with no body.

Although the formal parameter list appears in the forward declaration, it must also appear in the subprogram body. You can place the subprogram body anywhere after the forward declaration, but they must appear in the same program unit.

Example 8-3 Forward Declaration for a Nested Subprogram

DECLARE
   PROCEDURE proc1(number1 NUMBER);  -- forward declaration
   PROCEDURE proc2(number2 NUMBER) IS
      BEGIN
        proc1(number2);  -- invokes proc1
      END; 
   PROCEDURE proc1(number1 NUMBER) IS 
     BEGIN
      proc2 (number1);  -- invokes proc2
     END;
BEGIN
  NULL;
END;
/

Passing Parameters to PL/SQL Subprograms

This section explains how to pass parameters to PL/SQL subprograms.

Topics:

Formal and Actual Subprogram Parameters

Formal parameters are the variables declared in the subprogram specification and referenced in the subprogram body. Actual parameters are the variables or expressions that you pass to the subprogram when you invoke it. Corresponding formal and actual parameters must have compatible datatypes.

A good programming practice is to use different names for actual and formal parameters.

In Example 8-4, emp_id and amount are formal parameters and emp_num and bonus are the corresponding actual parameters.

Example 8-4 Formal Parameters and Actual Parameters

DECLARE
  emp_num NUMBER(6) := 120;
  bonus   NUMBER(6) := 100;
  merit   NUMBER(4) := 50;
  PROCEDURE raise_salary (emp_id NUMBER,
                          amount NUMBER) IS
    BEGIN
      UPDATE employees SET salary = 
        salary + amount WHERE employee_id = emp_id;
  END raise_salary;
BEGIN
  -- Procedure call specifies actual parameters
  raise_salary(emp_num, bonus);
  -- Expressions can be used as parameters
  raise_salary(emp_num, merit + bonus);
END;
/

When you invoke a subprogram, PL/SQL evaluates each actual parameter and assigns its value to the corresponding formal parameter. If necessary, PL/SQL implicitly converts the datatype of the actual parameter to the datatype of the corresponding formal parameter before the assignment (this is why corresponding formal and actual parameters must have compatible datatypes). For information about implicit conversion, see "Implicit Conversion".

A good programming practice is to avoid implicit conversion, either by using explicit conversion (explained in "Explicit Conversion") or by declaring the variables that you intend to use as actual parameters with the same datatypes as their corresponding formal parameters. For example, suppose that pkg has this specification:

PACKAGE pkg IS
  PROCEDURE s (n IN PLS_INTEGER);
END pkg;

The following invocation of pkg.s avoids implicit conversion:

DECLARE
  y PLS_INTEGER :=1;
BEGIN
  pkg.s(y);
END;

The following invocation of pkg.s causes implicit conversion:

DECLARE
  y INTEGER :=1;
BEGIN
  pkg.s(y);
END;

Note:

The specifications of many of Oracle's supplied packages and types declare formal parameters with the following notation:
i1 IN VARCHAR2 CHARACTER SET ANY_CS
i2 IN VARCHAR2 CHARACTER SET i1%CHARSET

Do not use this notation when declaring your own formal or actual parameters. It is reserved for Oracle's implementation of the supplied packages types.

Using Positional, Named, or Mixed Notation for PL/SQL Subprogram Parameters

When invoking a subprogram, you can specify the actual parameters using either positional, named, or mixed notation. Table 8-2 compares these notations.

Table 8-2 PL/SQL Subprogram Parameter Notations

Notation Description Usage Notes

Positional

Specify the same parameters in the same order as the procedure declares them.

Compact and readable, but has these disadvantages:

  • If you specify the parameters (especially literals) in the wrong order, the bug can be hard to detect.

  • If the procedure's parameter list changes, you must change your code.

Named

Specify the name and value of each parameter, using the association operator, =>. Order of parameters is insignificant.

More verbose than positional notation, but easier to read and maintain. You can sometimes avoid changing your code if the procedure's parameter list changes (for example, if parameters are reordered or a new optional parameter is added). Safer than positional notation when you invoke an API that you did not define, or define an API for others to use.

Mixed

Start with positional notation, then use named notation for the remaining parameters.

Recommended when you invoke procedures that have required parameters followed by optional parameters, and you need to specify only a few of the optional parameters.


Example 8-5 shows equivalent subprogram calls using positional, named, and mixed notation.

Example 8-5 Subprogram Calls Using Positional, Named, and Mixed Notation

DECLARE
  emp_num NUMBER(6) := 120;
  bonus   NUMBER(6) := 50;
  PROCEDURE raise_salary (emp_id NUMBER, amount NUMBER) IS
    BEGIN
      UPDATE employees SET salary =
        salary + amount WHERE employee_id = emp_id;
  END raise_salary;
BEGIN
  -- Positional notation:
  raise_salary(emp_num, bonus);
  -- Named notation (parameter order is insignificant):
  raise_salary(amount => bonus, emp_id => emp_num);
  raise_salary(emp_id => emp_num, amount => bonus);
  -- Mixed notation:
  raise_salary(emp_num, amount => bonus);
END;
/

CREATE OR REPLACE FUNCTION compute_bonus (emp_id NUMBER, bonus NUMBER)
  RETURN NUMBER
IS
  emp_sal NUMBER;
BEGIN
  SELECT salary INTO emp_sal
    FROM employees
      WHERE employee_id = emp_id;
  RETURN emp_sal + bonus;
END compute_bonus;
/

The following equivalent SELECT statements invoke the PL/SQL subprogram in Example 8-5 using positional, named, and mixed notation:

SELECT compute_bonus(120, 50) FROM DUAL;                   -- positional
SELECT compute_bonus(bonus => 50, emp_id => 120) FROM DUAL; -- named
SELECT compute_bonus(120, bonus => 50) FROM DUAL;           -- mixed

Specifying Subprogram Parameter Modes

Parameter modes define the action of formal parameters. The three parameter modes are IN (the default), OUT, and IN OUT.

Any parameter mode can be used with any subprogram. Avoid using the OUT and IN OUT modes with functions. To have a function return multiple values is poor programming practice. Also, make functions free from side effects, which change the values of variables not local to the subprogram.

Topics:

Using IN Mode

An IN parameter lets you pass a value to the subprogram being invoked. Inside the subprogram, an IN parameter acts like a constant. It cannot be assigned a value.

You can pass a constant, literal, initialized variable, or expression as an IN parameter.

An IN parameter can be initialized to a default value, which is used if that parameter is omitted from the subprogram call. For more information, see "Using Default Values for Subprogram Parameters".

Using OUT Mode

An OUT parameter returns a value to the caller of a subprogram. Inside the subprogram, an OUT parameter acts like a variable. You can change its value and reference the value after assigning it (see Example 8-6).

Example 8-6 Using OUT Mode

DECLARE
  emp_num       NUMBER(6) := 120;
  bonus         NUMBER(6) := 50;
  emp_last_name VARCHAR2(25);
  PROCEDURE raise_salary (emp_id IN NUMBER, amount IN NUMBER, 
                          emp_name OUT VARCHAR2) IS
    BEGIN
      UPDATE employees SET salary =
        salary + amount WHERE employee_id = emp_id;
      SELECT last_name INTO emp_name
        FROM employees
       WHERE employee_id = emp_id;
  END raise_salary;
BEGIN
  raise_salary(emp_num, bonus, emp_last_name);
  DBMS_OUTPUT.PUT_LINE
    ('Salary was updated for: ' || emp_last_name);
END;
/

You must pass a variable, not a constant or an expression, to an OUT parameter. Its previous value is lost unless you specify the NOCOPY keyword or the subprogram exits with an unhandled exception. See "Using Default Values for Subprogram Parameters".

The initial value of an OUT parameter is NULL; therefore, the datatype of an OUT parameter cannot be a subtype defined as NOT NULL, such as the built-in subtype NATURALN or POSITIVEN. Otherwise, when you invoke the subprogram, PL/SQL raises VALUE_ERROR.

Before exiting a subprogram, assign values to all OUT formal parameters. Otherwise, the corresponding actual parameters will be null. If you exit successfully, PL/SQL assigns values to the actual parameters. If you exit with an unhandled exception, PL/SQL does not assign values to the actual parameters.

Using IN OUT Mode

An IN OUT parameter passes an initial value to a subprogram and returns an updated value to the caller. It can be assigned a value and its value can be read. Typically, an IN OUT parameter is a string buffer or numeric accumulator, that is read inside the subprogram and then updated.

The actual parameter that corresponds to an IN OUT formal parameter must be a variable, not a constant or an expression.

If you exit a subprogram successfully, PL/SQL assigns values to the actual parameters. If you exit with an unhandled exception, PL/SQL does not assign values to the actual parameters.

Summary of Subprogram Parameter Modes

Table 8-3 summarizes the characteristics of parameter modes.

Table 8-3 Parameter Modes

IN OUT IN OUT

The default

Must be specified

Must be specified

Passes a value to the subprogram

Returns a value to the caller

Passes an initial value to the subprogram and returns an updated value to the caller

Formal parameter acts like a constant

Formal parameter acts like an uninitialized variable

Formal parameter acts like an initialized variable

Formal parameter cannot be assigned a value

Formal parameter must be assigned a value

Formal parameter should be assigned a value

Actual parameter can be a constant, initialized variable, literal, or expression

Actual parameter must be a variable

Actual parameter must be a variable

Actual parameter is passed by reference (the caller passes the subprogram a pointer to the value)

Actual parameter is passed by value (the subprogram passes the caller a copy of the value) unless NOCOPY is specified

Actual parameter is passed by value (the caller passes the subprogram a copy of the value and the subprogram passes the caller a copy of the value) unless NOCOPY is specified


Using Default Values for Subprogram Parameters

By initializing formal IN parameters to default values, you can pass different numbers of actual parameters to a subprogram, accepting the default values for omitted actual parameters. You can also add new formal parameters without having to change every call to the subprogram.

If an actual parameter is omitted, the default value of its corresponding formal parameter is used.

You cannot skip a formal parameter by omitting its actual parameter. To omit the first parameter and specify the second, use named notation (see "Using Positional, Named, or Mixed Notation for PL/SQL Subprogram Parameters" on ).

You cannot assign NULL to an uninitialized formal parameter by omitting its actual parameter. You must either assign NULL as a default value or pass NULL explicitly.

Example 8-7 illustrates the use of default values for subprogram parameters.

Example 8-7 Procedure with Default Parameter Values

DECLARE
  emp_num NUMBER(6) := 120;
  bonus   NUMBER(6);
  merit   NUMBER(4);
  PROCEDURE raise_salary (emp_id IN NUMBER,
    amount IN NUMBER DEFAULT 100,
                          extra IN NUMBER DEFAULT 50) IS
    BEGIN
      UPDATE employees SET salary = salary + amount + extra 
        WHERE employee_id = emp_id;
  END raise_salary;
BEGIN
  -- Same as raise_salary(120, 100, 50)
  raise_salary(120);
  -- Same as raise_salary(120, 100, 25)
  raise_salary(emp_num, extra => 25);
END;
/

If the default value of a formal parameter is an expression, and you provide a corresponding actual parameter when you invoke the subprogram, the expression is not evaluated (see Example 8-8).

Example 8-8 Formal Parameter with Expression as Default Value

DECLARE
  cnt pls_integer := 0;
  FUNCTION dflt RETURN pls_integer IS
  BEGIN
    cnt := cnt + 1;
    RETURN 42;
  END dflt;
  -- Default is expression
  PROCEDURE p(i IN pls_integer DEFAULT dflt()) IS
     BEGIN
       DBMS_Output.Put_Line(i);
  END p;
BEGIN
  FOR j IN 1..5 LOOP
    p(j);  -- Actual parameter is provided
  END loop;
  DBMS_Output.Put_Line('cnt: '||cnt);
  p();  -- Actual parameter is not provided
  DBMS_Output.Put_Line('cnt: '||cnt);
END;

The output of Example 8-8 is:

1
2
3
4
5
Cnt: 0
42
Cnt: 1

Overloading PL/SQL Subprogram Names

PL/SQL lets you overload local subprograms, packaged subprograms, and type methods. You can use the same name for several different subprograms as long as their formal parameters differ in number, order, or datatype family.

Example 8-9 defines two subprograms with the same name, initialize. The procedures initialize different types of collections. Because the processing in these two procedures is the same, it is logical to give them the same name.

You can place the two initialize procedures in the same block, subprogram, package, or object type. PL/SQL determines which procedure to invoke by checking their formal parameters. The version of initialize that PL/SQL uses depends on whether you invoke the procedure with a date_tab_typ or num_tab_typ parameter.

Example 8-9 Overloading a Subprogram Name

DECLARE
  TYPE date_tab_typ IS TABLE OF DATE   INDEX BY PLS_INTEGER;
  TYPE num_tab_typ  IS TABLE OF NUMBER INDEX BY PLS_INTEGER;

  hiredate_tab  date_tab_typ;
  sal_tab       num_tab_typ;

  PROCEDURE initialize (tab OUT date_tab_typ, n INTEGER) IS
  BEGIN
    FOR i IN 1..n LOOP
      tab(i) := SYSDATE;
    END LOOP;
  END initialize;

  PROCEDURE initialize (tab OUT num_tab_typ, n INTEGER) IS
  BEGIN
    FOR i IN 1..n LOOP
      tab(i) := 0.0;
    END LOOP;
  END initialize;

BEGIN
  initialize(hiredate_tab, 50);  -- Invokes first (date_tab_typ) version
  initialize(sal_tab, 100);      -- Invokes second (num_tab_typ) version
END;
/

For an example of an overloaded procedure in a package, see Example 10-3.

Topics:

Guidelines for Overloading with Numeric Types

You can overload subprograms if their formal parameters differ only in numeric datatype. This technique is useful in writing mathematical application programming interfaces (APIs), because several versions of a function can use the same name, and each can accept a different numeric type. For example, a function that accepts BINARY_FLOAT might be faster, while a function that accepts BINARY_DOUBLE might provide more precision.

To avoid problems or unexpected results passing parameters to such overloaded subprograms:

  • Ensure that the expected version of a subprogram is invoked for each set of expected parameters. For example, if you have overloaded functions that accept BINARY_FLOAT and BINARY_DOUBLE, which is invoked if you pass a VARCHAR2 literal such as '5.0'?

  • Qualify numeric literals and use conversion functions to make clear what the intended parameter types are. For example, use literals such as 5.0f (for BINARY_FLOAT), 5.0d (for BINARY_DOUBLE), or conversion functions such as TO_BINARY_FLOAT, TO_BINARY_DOUBLE, and TO_NUMBER.

PL/SQL looks for matching numeric parameters in this order:

  1. PLS_INTEGER (or BINARY_INTEGER, an identical datatype)

  2. NUMBER

  3. BINARY_FLOAT

  4. BINARY_DOUBLE

A VARCHAR2 value can match a NUMBER, BINARY_FLOAT, or BINARY_DOUBLE parameter.

PL/SQL uses the first overloaded subprogram that matches the supplied parameters. For example, the SQRT function takes a single parameter. There are overloaded versions that accept a NUMBER, a BINARY_FLOAT, or a BINARY_DOUBLE parameter. If you pass a PLS_INTEGER parameter, the first matching overload is the one with a NUMBER parameter.

The SQRT function that takes a NUMBER parameter is likely to be slowest. To use a faster version, use the TO_BINARY_FLOAT or TO_BINARY_DOUBLE function to convert the parameter to another datatype before passing it to the SQRT function.

If PL/SQL must convert a parameter to another datatype, it first tries to convert it to a higher datatype. For example:

  • The ATAN2 function takes two parameters of the same type. If you pass parameters of different types—for example, one PLS_INTEGER and one BINARY_FLOAT—PL/SQL tries to find a match where both parameters use the higher type. In this case, that is the version of ATAN2 that takes two BINARY_FLOAT parameters; the PLS_INTEGER parameter is converted upwards.

  • A function takes two parameters of different types. One overloaded version takes a PLS_INTEGER and a BINARY_FLOAT parameter. Another overloaded version takes a NUMBER and a BINARY_DOUBLE parameter. If you invoke this function and pass two NUMBER parameters, PL/SQL first finds the overloaded version where the second parameter is BINARY_FLOAT. Because this parameter is a closer match than the BINARY_DOUBLE parameter in the other overload, PL/SQL then looks downward and converts the first NUMBER parameter to PLS_INTEGER.

Restrictions on Overloading

You cannot overload the following subprograms:

  • Standalone subprograms

  • Subprograms whose formal parameters differ only in mode; for example:

    PACKAGE pkg IS
      PROCEDURE s (p IN  VARCHAR2);
      PROCEDURE s (p OUT VARCHAR2);
    END pkg;
    
  • Subprograms whose formal parameters differ only in subtype; for example:

    PACKAGE pkg IS
      PROCEDURE s (p INTEGER);
      PROCEDURE s (p REAL);
    END pkg;
    

    INTEGER and REAL are subtypes of NUMBER, so they belong to the same datatype family.

  • Functions that differ only in return value datatype, even if the datatypes are in different families; for example:

    PACKAGE pkg IS
      FUNCTION f (p INTEGER) RETURN BOOLEAN;
      FUNCTION f (p INTEGER) RETURN INTEGER;
    END pkg;
    

When Compiler Catches Overloading Errors

The PL/SQL compiler catches overloading errors as soon as it can determine that it will be unable to tell which subprogram was invoked. When subprograms have identical headings, the compiler catches the overloading error when you try to compile the subprograms themselves (if they are local) or when you try to compile the package specification that declares them (if they are packaged); otherwise, it catches the error when you try to compile an ambiguous invocation of a subprogram.

When you try to compile the package specification in Example 8-10, which declares subprograms with identical headings, you get compile-time error PLS-00305.

Example 8-10 Package Specification with Overloading Violation that Causes Compile-Time Error

PACKAGE pkg1 IS
  PROCEDURE s (p VARCHAR2);
  PROCEDURE s (p VARCHAR2);
END pkg1;

Although the package specification in Example 8-11 violates the rule that you cannot overload subprograms whose formal parameters differ only in subtype, you can compile it without error.

Example 8-11 Package Specification with Overloading Violation that Compiles Without Error

PACKAGE pkg2 IS
  SUBTYPE t1 IS VARCHAR2(10);
  SUBTYPE t2 IS VARCHAR2(10);
  PROCEDURE s (p t1);
  PROCEDURE s (p t2);
END pkg2;

However, when you try to compile an invocation of pkg2.s, such as the one in Example 8-12, you get compile-time error PLS-00307.

Example 8-12 Invocation of Improperly Overloaded Subprogram

PROCEDURE p IS
  a pkg.t1 := 'a';
BEGIN
  pkg.s(a)  -- Causes compile-time error PLS-00307;
END p;

Suppose that you correct the overloading violation in Example 8-11 by giving the formal parameters of the overloaded subprograms different names, as follows:

PACKAGE pkg2 IS
  SUBTYPE t1 IS VARCHAR2(10);
  SUBTYPE t2 IS VARCHAR2(10);
  PROCEDURE s (p1 t1);
  PROCEDURE s (p2 t2);
END pkg2;

Now you can compile an invocation of pkg2.s without error if you specify the actual parameter with named notation. For example:

PROCEDURE p IS
  a pkg.t1 := 'a';
BEGIN
  pkg.s(p1=>a);  -- Compiles without error
END p;

If you specify the actual parameter with positional notation, as in Example 8-12, you still get compile-time error PLS-00307.

The package specification in Example 8-13 violates no overloading rules and compiles without error. However, you can still get compile-time error PLS-00307 when invoking its overloaded procedure, as in the second invocation in Example 8-14.

Example 8-13 Package Specification Without Overloading Violations

PACKAGE pkg3 IS
  PROCEDURE s (p1 VARCHAR2);
  PROCEDURE s (p1 VARCHAR2, p2 VARCHAR2 := 'p2');
END pkg3;

Example 8-14 Improper Invocation of Properly Overloaded Subprogram

PROCEDURE p IS
  a1 VARCHAR2(10) := 'a1';
  a2 VARCHAR2(10) := 'a2';
BEGIN
  pkg.s(p1=>a1, p2=>a2);  -- Compiles without error
  pkg.s(p1=>a1);          -- Causes compile-time error PLS-00307
END p;

How PL/SQL Subprogram Calls Are Resolved

Figure 8-1 shows how the PL/SQL compiler resolves subprogram calls. When the compiler encounters a subprogram call, it tries to find a declaration that matches the call. The compiler searches first in the current scope and then, if necessary, in successive enclosing scopes. The compiler looks more closely when it finds one or more subprogram declarations in which the subprogram name matches the name of the called subprogram.

To resolve a call among possibly like-named subprograms at the same level of scope, the compiler must find an exact match between the actual and formal parameters. They must match in number, order, and datatype (unless some formal parameters were assigned default values). If no match is found or if multiple matches are found, the compiler generates a semantic error.

Figure 8-1 How the PL/SQL Compiler Resolves Calls

Description of Figure 8-1 follows
Description of "Figure 8-1 How the PL/SQL Compiler Resolves Calls"

Example 8-15 invokes the enclosing procedure swap from the function balance, generating an error because neither declaration of swap within the current scope matches the procedure call.

Example 8-15 Resolving PL/SQL Procedure Names

DECLARE
  PROCEDURE swap (n1 NUMBER, n2 NUMBER) IS
    num1 NUMBER;
    num2 NUMBER;
    FUNCTION balance (bal NUMBER) RETURN NUMBER IS
      x NUMBER := 10;
      PROCEDURE swap (d1 DATE, d2 DATE) IS BEGIN NULL; END;
      PROCEDURE swap (b1 BOOLEAN, b2 BOOLEAN) IS BEGIN NULL; END;
    BEGIN
      DBMS_OUTPUT.PUT_LINE('The following raises an error');
--      swap(num1, num2);
--      wrong number or types of arguments in call to 'SWAP'
      RETURN x;
    END balance;
  BEGIN NULL;END swap;
BEGIN
  NULL;
END;
/

Using Invoker's Rights or Definer's Rights (AUTHID Clause)

By default, a stored procedure or SQL method executes with the privileges of its owner, not its current user. Such definer's rights (DR) subprograms are bound to the schema in which they reside, allowing you to refer to objects in the same schema without qualifying their names. For example, if schemas HR and OE both have a table called departments, a subprogram owned by HR can refer to departments rather than HR.departments. If user OE invokes HR's subprogram, the subprogram still accesses the departments table owned by HR.

If you compile the same subprogram in both schemas, you can define the schema name as a variable in SQL*Plus and refer to the table like &schema..departments. The code is portable, but if you change it, you must recompile it in each schema.

A more maintainable way is to create the subprogram with the AUTHID CURRENT_USER clause, which makes it execute with the privileges and schema context of the calling user. You can create one instance of the subprogram, and many users can invoke it to access their own data.

Such invoker's-rights (IR) subprograms are not bound to a particular schema. In Example 8-16, the procedure create_dept executes with the privileges of the calling user and inserts rows into that user's departments table:

Example 8-16 Specifying Invoker's Rights with a Procedure

CREATE OR REPLACE PROCEDURE create_dept (
   v_deptno NUMBER,
   v_dname  VARCHAR2,
   v_mgr    NUMBER,
   v_loc    NUMBER) 
AUTHID CURRENT_USER AS
BEGIN
   INSERT INTO departments
     VALUES (v_deptno, v_dname, v_mgr, v_loc);
END;
/
CALL create_dept(44, 'Information Technology', 200, 1700);

Topics:

Advantages of Invoker's Rights

Invoker's-rights (IR) subprograms let you reuse code and centralize application logic. They are especially useful in applications that store data using identical tables in different schemas. All the schemas in one instance can invoke procedures owned by a central schema. You can even have schemas in different instances invoke centralized procedures using a database link.

Consider a company that uses a stored procedure to analyze sales. If the company has several schemas, each with a similar SALES table, it usually also needs several copies of the stored procedure, one in each schema.

To solve the problem, the company installs an IR version of the stored procedure in a central schema. Now, all the other schemas can invoke the same procedure, which queries the appropriate to SALES table in each case.

You can restrict access to sensitive data by invoking, from an IR subprogram, a DR subprogram that queries or updates the table containing the sensitive data. Although multiple users can invoke the IR subprogram, they do not have direct access to the sensitive data.

Specifying Subprogram Privileges (AUTHID Clause)

To implement invoker's rights, create the subprogram with the AUTHID CURRENT_USER clause, which specifies that the subprogram executes with the privileges of its current user. (The default is AUTHID DEFINER, which specifies that the subprogram executes with the privileges of its owner.) The AUTHID clause also specifies whether external references (that is, references to objects outside the subprogram) are resolved in the schema of the owner or the current user.

The AUTHID clause is allowed only in the header of a standalone subprogram, a package spec, or an object type spec. In the CREATE FUNCTION, CREATE PROCEDURE, CREATE PACKAGE, or CREATE TYPE statement, you can include either AUTHID CURRENT_USER or AUTHID DEFINER immediately before the IS or AS keyword that begins the declaration section.

In a package or object type, the AUTHID clause applies to all subprograms.

Most supplied PL/SQL packages (such as DBMS_LOB, DBMS_PIPE, DBMS_ROWID, DBMS_SQL, and UTL_REF) are IR packages.

Who Is the Current User During Subprogram Execution?

Initially, the current user is the session user. When an IR subprogram is invoked, the current user does not change. When a DR subprogram is invoked, the owner of that subprogram becomes the current user. Note that when an IR subprogram is invoked from a DR subprogram, the current user is the owner of the DR subprogram, not the session user.

To verify who the current user is at any time, you can check the static data dictionary view USER_USERS. Inside an IR subprogram, the value from this view might be different from the value of the USER built-in function, which always returns the name of the session user. In addition, the current user setting can be checked by invoking the built-in function SYS_CONTEXT('USERENV','CURRENT_USER').

How External References Are Resolved in IR Subprograms

External references in an IR subprogram are resolved as follows:

  • In the following statements, external references are resolved in the schema of the current user, whose privileges are checked at run time:

    • Data manipulation statements SELECT, INSERT, UPDATE, and DELETE

    • Transaction control statement LOCK TABLE

    • Cursor-control statements OPEN and OPEN-FOR

    • Dynamic SQL statements EXECUTE IMMEDIATE and OPEN-FOR-USING

    • SQL statements parsed with DBMS_SQL.PARSE procedure

  • In all other statements, external references are resolved in the schema of the owner, whose privileges are checked at compile time.

  • External references to constants follow the preceding rules only if the constants are not inlined.

    One optimization that the PL/SQL compiler can perform is constant inlining. Constant inlining replaces a reference to a constant with its value. The value of an inlined constant becomes part of the calling program, and is therefore available to it. Outside the calling program, however, the caller cannot access the constant.

In Example 8-17, the IR procedure above_salary includes two external references to the function num_above_salary in the package emp_actions in Example 1-14. The external reference in the SELECT statement is resolved in the schema of the current user. The external reference in the assignment statement is resolved in the schema of the owner of the procedure above_salary.

Example 8-17 Resolving External References in IR Subprogram

CREATE PROCEDURE above_salary
  (emp_id IN NUMBER)
  AUTHID CURRENT_USER
AS
  emps NUMBER;
BEGIN
  -- External reference is resolved in schema of current user:
  SELECT * FROM employees
    WHERE employee_id = emp_actions.num_above_salary(emp_id);

  -- External reference is resolved in schema of owner of above_salary:
  emps := emp_actions.num_above_salary(emp_id);
  DBMS_OUTPUT.PUT_LINE
    ('Number of employees with higher salary: ' || TO_CHAR(emps));
END;
/
CALL above_salary(120);
/

In Example 8-18, the IR procedure test is owned by schema_a and has an external reference to the constant c. When compiling test, the PL/SQL compiler inlines the external reference to c, replacing it with the value of c (which is one). The value of c (one) becomes part of the procedure test. Therefore, when schema_b invokes test, the external reference to c within test succeeds. However, schema_b cannot access c directly.

Example 8-18 Inlined External Reference to Constant in IR Subprogram

-- In schema_a:
CREATE OR REPLACE PACKAGE pkg IS
  c CONSTANT NUMBER :=  1;
END;
/
CREATE OR REPLACE PACKAGE c_test AUTHID CURRENT_USER IS
  PROCEDURE test; -- IR procedure owned by schema_a
END;
/
CREATE OR REPLACE PACKAGE BODY c_test IS
  PROCEDURE test IS
    n NUMBER;
  BEGIN
  -- External reference to constant c in package pkg:
    SELECT schema_a.pkg.c INTO n FROM DUAL;
  -- If external reference succeeds:
    DBMS_OUTPUT.PUT_LINE ('c_test: referenced schema_a.pkg.c');
  -- If external reference fails:
    EXCEPTION WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE
        ('c_test: caught exception referencing schema_a.pkg.c: '
         || SQLCODE || ': ' || SQLERRM);
  END;
END;
/
-- Allow schema_b to execute package c_test:
GRANT EXECUTE ON schema_a.c_test TO schema_b;

-- In schema_b, invoke procedure test in package c_test:
SQL> BEGIN schema_a.c_test.test; END;
  2  /
-- External reference to constant c from inside procedure test succeeds:
c_test: referenced schema_a.pkg.c

PL/SQL procedure successfully completed.

-- External reference to constant c from outside procedure test fails:
SQL> DECLARE n NUMBER; BEGIN n := schema_a.pkg.c; END;
  2  /
DECLARE n NUMBER; BEGIN n := schema_a.pkg.c; END;
                             *
ERROR at line 1:
ORA-06550: line 1, column 30:
PLS-00201: identifier 'schema_a.pkg' must be declared
ORA-06550: line 1, column 25:
PL/SQL: Statement ignored

Need for Template Objects in IR Subprograms

The PL/SQL compiler must resolve all references to tables and other objects at compile time. The owner of an IR subprogram must have objects in the same schema with the right names and columns, even if they do not contain any data. At run time, the corresponding objects in the invoker's schema must have matching definitions. Otherwise, you get an error or unexpected results, such as ignoring table columns that exist in the invoker's schema but not in the schema that contains the subprogram.

Overriding Default Name Resolution in IR Subprograms

If you want an unqualified name to refer to a particular schema, rather than to the schema of the invoker, create a public synonym for the object; for example:

CREATE PUBLIC SYNONYM emp FOR hr.employees;

When the IR subprogram refers to emp, it matches the public synonym, which resolves to hr.employees.

Note:

If the invoking schema has an object or private synonym named emp, fully qualify the reference in the IR subprogram.

Granting Privileges to IR Subprograms

To invoke a subprogram directly, users must have the EXECUTE privilege on that subprogram. By granting the privilege, you allow a user to:

  • Invoke the subprogram directly

  • Compile functions and procedures that invoke the subprogram

For external references resolved in the current user's schema (such as those in DML statements), the current user must have the privileges needed to access schema objects referenced by the subprogram. For all other external references (such as function calls), the owner's privileges are checked at compile time, and no run-time check is done.

A DR subprogram operates under the security domain of its owner, no matter who is executing it. The owner must have the privileges needed to access schema objects referenced by the subprogram.

You can write a program consisting of multiple subprograms, some with definer's rights and others with invoker's rights. Then, you can use the EXECUTE privilege to restrict program entry points. That way, users of an entry-point subprogram can execute the other subprograms indirectly but not directly.

Example: Granting Privileges on an IR Subprogram

Suppose user UTIL grants the EXECUTE privilege on subprogram FFT to user APP:

GRANT EXECUTE ON util.fft TO app;

Now, user APP can compile functions and procedures that invoke subprogram FFT. At run time, no privilege checks on the calls are done. As Figure 8-2 shows, user UTIL need not grant the EXECUTE privilege to every user who might invoke FFT indirectly.

Since subprogram util.fft is invoked directly only from IR subprogram app.entry, user util must grant the EXECUTE privilege only to user APP. When util.fft is executed, its current user can be APP, SCOTT, or BLAKE even though SCOTT and BLAKE were not granted the EXECUTE privilege.

Figure 8-2 Indirect Calls to an IR Subprogram

Description of Figure 8-2 follows
Description of "Figure 8-2 Indirect Calls to an IR Subprogram"

Using Views and Database Triggers with IR Subprograms

For IR subprograms executed within a view expression, the schema that created the view, not the schema that is querying the view, is considered to be the current user. This rule also applies to database triggers.

Using Database Links with IR Subprograms

You can create a database link to use invoker's rights:

CREATE DATABASE LINK link_name CONNECT TO CURRENT_USER
  USING connect_string;

A current-user link lets you connect to a remote database as another user, with that user's privileges. To connect, Oracle uses the username of the current user (who must be a global user). Suppose an IR subprogram owned by user OE references the following database link. If global user HR invokes the subprogram, it connects to the Dallas database as user HR, who is the current user.

CREATE DATABASE LINK dallas CONNECT TO CURRENT_USER USING ...

If it were a definer's rights subprogram, the current user would be OE, and the subprogram would connect to the Dallas database as global user OE.

Using Object Types with IR Subprograms

To define object types for use in any schema, specify the AUTHID CURRENT_USER clause. For information on object types, see Oracle Database Object-Relational Developer's Guide.

Suppose user HR creates the following object type:

Example 8-19 Creating an Object Type with AUTHID CURRENT USER

CREATE TYPE person_typ AUTHID CURRENT_USER AS OBJECT (
  person_id   NUMBER,
  person_name VARCHAR2(30),
  person_job  VARCHAR2(10),
  STATIC PROCEDURE new_person_typ (
    person_id NUMBER, person_name VARCHAR2, person_job VARCHAR2,
    schema_name VARCHAR2, table_name VARCHAR2),
  MEMBER PROCEDURE change_job (SELF IN OUT NOCOPY person_typ,
                               new_job VARCHAR2) 
  );
/
CREATE TYPE BODY person_typ AS
  STATIC PROCEDURE new_person_typ (
    person_id NUMBER, person_name VARCHAR2, person_job VARCHAR2,
    schema_name VARCHAR2, table_name VARCHAR2) IS
    sql_stmt VARCHAR2(200);
  BEGIN 
    sql_stmt := 'INSERT INTO ' || schema_name || '.'
       || table_name || ' VALUES (HR.person_typ(:1, :2, :3))';
    EXECUTE IMMEDIATE sql_stmt
      USING person_id, person_name, person_job;
  END;
  MEMBER PROCEDURE change_job (SELF IN OUT NOCOPY person_typ,
                               new_job VARCHAR2) IS
  BEGIN
    person_job := new_job;
  END;
END;
/

Then, user HR grants the EXECUTE privilege on object type person_typ to user OE:

GRANT EXECUTE ON person_typ TO OE;

Finally, user OE creates an object table to store objects of type person_typ, then invokes procedure new_person_typ to populate the table:

CONNECT OE/password;
CREATE TABLE person_tab OF hr.person_typ;

BEGIN
  hr.person_typ.new_person_typ(1001,
                               'Jane Smith',
                               'CLERK',
                               'oe',
                               'person_tab');
  hr.person_typ.new_person_typ(1002,
                               'Joe Perkins',
                               'SALES','oe',
                               'person_tab');
  hr.person_typ.new_person_typ(1003,
                               'Robert Lange',
                               'DEV',
                               'oe', 'person_tab');
                               'oe', 'person_tab');
END;
/

The calls succeed because the procedure executes with the privileges of its current user (OE), not its owner (HR).

For subtypes in an object type hierarchy, the following rules apply:

  • If a subtype does not explicitly specify an AUTHID clause, it inherits the AUTHID of its supertype.

  • If a subtype does specify an AUTHID clause, its AUTHID must match the AUTHID of its supertype. Also, if the AUTHID is DEFINER, both the supertype and subtype must have been created in the same schema.

Invoking IR Instance Methods

An IR instance method executes with the privileges of the invoker, not the creator of the instance. Suppose that person_typ is an IR object type as created in Example 8-19, and that user HR creates p1, an object of type person_typ. If user OE invokes instance method change_job to operate on object p1, the current user of the method is OE, not HR, as shown in Example 8-20.

Example 8-20 Invoking an IR Instance Methods

-- oe creates a procedure that invokes change_job
CREATE PROCEDURE reassign
  (p IN OUT NOCOPY hr.person_typ, new_job VARCHAR2) AS
BEGIN
   p.change_job(new_job); -- executes with the privileges of oe
END;
/
-- OE grants EXECUTE to HR on procedure reassign
GRANT EXECUTE ON reassign to HR;
CONNECT HR/password

-- user hr passes a person_typ object to the procedure reassign
DECLARE
   p1 person_typ;
BEGIN
   p1 := person_typ(1004,  'June Washburn', 'SALES');
   oe.reassign(p1, 'CLERK'); -- current user is oe, not hr
END;
/

Using Recursive PL/SQL Subprograms

A recursive subprogram is one that invokes itself. Each recursive call creates a new instance of any items declared in the subprogram, including parameters, variables, cursors, and exceptions. Likewise, new instances of SQL statements are created at each level in the recursive descent.

Be careful where you place a recursive call. If you place it inside a cursor FOR loop or between OPEN and CLOSE statements, another cursor is opened at each call, which might exceed the limit set by the Oracle initialization parameter OPEN_CURSORS.

There must be at least two paths through a recursive subprogram: one that leads to the recursive call and one that does not. At least one path must lead to a terminating condition. Otherwise, the recursion continues until PL/SQL runs out of memory and raises the predefined exception STORAGE_ERROR.

Recursion is a powerful technique for simplifying the design of algorithms. Basically, recursion means self-reference. In a recursive mathematical sequence, each term is derived by applying a formula to preceding terms. The Fibonacci sequence (0, 1, 1, 2, 3, 5, 8, 13, 21, ...), is an example. Each term in the sequence (after the second) is the sum of the two terms that immediately precede it.

In a recursive definition, something is defined as simpler versions of itself. Consider the definition of n factorial (n!), the product of all integers from 1 to n:

n! = n * (n - 1)!

Invoking External Subprograms

Although PL/SQL is a powerful, flexible language, some tasks are more easily done in another language. Low-level languages such as C are very fast. Widely used languages such as Java have reusable libraries for common design patterns.

You can use PL/SQL call specifications to invoke external subprograms written in other languages, making their capabilities and libraries available from PL/SQL. For example, you can invoke Java stored procedures from any PL/SQL block, subprogram, or package. For more information about Java stored procedures, see Oracle Database Java Developer's Guide.

If the following Java class is stored in the database, it can be invoked as shown in Example 8-21.

import java.sql.*;
import oracle.jdbc.driver.*;
public class Adjuster {
  public static void raiseSalary (int empNo, float percent)
  throws SQLException {
    Connection conn = new OracleDriver().defaultConnection();
    String sql = "UPDATE employees SET salary = salary * ?
                    WHERE employee_id = ?";
    try {
      PreparedStatement pstmt = conn.prepareStatement(sql);
      pstmt.setFloat(1, (1 + percent / 100));
      pstmt.setInt(2, empNo);
      pstmt.executeUpdate();
      pstmt.close();
    } catch (SQLException e)
          {System.err.println(e.getMessage());}
    }
}

The class Adjuster has one method, which raises the salary of an employee by a given percentage. Because raiseSalary is a void method, you publish it as a procedure using the call specification shown inExample 8-21 and then can invoke the procedure raise_salary from an anonymous PL/SQL block.

Example 8-21 Invoking an External Procedure from PL/SQL

CREATE OR REPLACE PROCEDURE raise_salary (empid NUMBER, pct NUMBER)
AS LANGUAGE JAVA
NAME 'Adjuster.raiseSalary(int, float)';
/

DECLARE
   emp_id  NUMBER := 120;
   percent NUMBER := 10;
BEGIN
   -- get values for emp_id and percent
   raise_salary(emp_id, percent);  -- invoke external subprogram
END;
/

Java call specifications cannot be declared as nested procedures, but can be specified in object type specifications, object type bodies, PL/SQL package specifications, PL/SQL package bodies, and as top level PL/SQL procedures and functions.

Example 8-22 shows a call to a Java function from a PL/SQL procedure.

Example 8-22 Invoking a Java Function from PL/SQL

-- the following invalid nested Java call spec throws PLS-00999
--   CREATE PROCEDURE sleep (milli_seconds in number) IS
--     PROCEDURE java_sleep (milli_seconds IN NUMBER) AS ...

-- Create Java call spec, then call from PL/SQL procedure
CREATE PROCEDURE java_sleep (milli_seconds IN NUMBER)
  AS LANGUAGE JAVA NAME 'java.lang.Thread.sleep(long)';
/
CREATE PROCEDURE sleep (milli_seconds in number) IS
-- the following nested PROCEDURE spec is not legal
--  PROCEDURE java_sleep (milli_seconds IN NUMBER)
--    AS LANGUAGE JAVA NAME 'java.lang.Thread.sleep(long)';
BEGIN
  DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.get_time());
  java_sleep (milli_seconds);
  DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.get_time());
END;
/ 

External C subprograms are used to interface with embedded systems, solve engineering problems, analyze data, or control real-time devices and processes. External C subprograms extend the functionality of the database server, and move computation-bound programs from client to server, where they execute faster. For more information about external C subprograms, see Oracle Database Advanced Application Developer's Guide.

Controlling Side Effects of PL/SQL Subprograms

The fewer side effects a function has, the better it can be optimized within a query, particularly when the PARALLEL_ENABLE or DETERMINISTIC hints are used.

To be callable from SQL statements, a stored function (and any subprograms that it invokes) must obey the following purity rules, which are meant to control side effects:

If any SQL statement inside the function body violates a rule, you get an error at run time (when the statement is parsed).

To check for purity rule violations at compile time, use the RESTRICT_REFERENCES pragma to assert that a function does not read or write database tables or package variables (for syntax, see "RESTRICT_REFERENCES Pragma").

In Example 8-23, the RESTRICT_REFERENCES pragma asserts that packaged function credit_ok writes no database state (WNDS) and reads no package state (RNPS).

Example 8-23 RESTRICT_REFERENCES Pragma

CREATE PACKAGE loans AS
   FUNCTION credit_ok RETURN BOOLEAN;
   PRAGMA RESTRICT_REFERENCES (credit_ok, WNDS, RNPS);
END loans;
/

A static INSERT, UPDATE, or DELETE statement always violates WNDS, and if it reads columns, it also violates RNDS (reads no database state). A dynamic INSERT, UPDATE, or DELETE statement always violates both WNDS and RNDS.

Understanding PL/SQL Subprogram Parameter Aliasing

To optimize a subprogram call, the PL/SQL compiler can choose between two methods of parameter passing. with the BY VALUE method, the value of an actual parameter is passed to the subprogram. With the BY REFERENCE method, only a pointer to the value is passed; the actual and formal parameters reference the same item.

The NOCOPY compiler hint increases the possibility of aliasing (that is, having two different names refer to the same memory location). This can occur when a global variable appears as an actual parameter in a subprogram call and then is referenced within the subprogram. The result is indeterminate because it depends on the method of parameter passing chosen by the compiler.

In Example 8-24, procedure ADD_ENTRY refers to varray LEXICON both as a parameter and as a global variable. When ADD_ENTRY is invoked, the identifiers WORD_LIST and LEXICON point to the same varray.

Example 8-24 Aliasing from Passing Global Variable with NOCOPY Hint

DECLARE
  TYPE Definition IS RECORD (
    word    VARCHAR2(20),
    meaning VARCHAR2(200));
  TYPE Dictionary IS VARRAY(2000) OF Definition;
  lexicon Dictionary := Dictionary();
  PROCEDURE add_entry (word_list IN OUT NOCOPY Dictionary) IS
    BEGIN
      word_list(1).word := 'aardvark';
      lexicon(1).word := 'aardwolf';
    END;
BEGIN
  lexicon.EXTEND;
  add_entry(lexicon);
  DBMS_OUTPUT.PUT_LINE(lexicon(1).word);
END;
/

The program prints aardwolf if the compiler obeys the NOCOPY hint. The assignment to WORD_LIST is done immediately through a pointer, then is overwritten by the assignment to LEXICON.

The program prints aardvark if the NOCOPY hint is omitted, or if the compiler does not obey the hint. The assignment to WORD_LIST uses an internal copy of the varray, which is copied back to the actual parameter (overwriting the contents of LEXICON) when the procedure ends.

Aliasing can also occur when the same actual parameter appears more than once in a subprogram call. In Example 8-25, n2 is an IN OUT parameter, so the value of the actual parameter is not updated until the procedure exits. That is why the first PUT_LINE prints 10 (the initial value of n) and the third PUT_LINE prints 20. However, n3 is a NOCOPY parameter, so the value of the actual parameter is updated immediately. That is why the second PUT_LINE prints 30.

Example 8-25 Aliasing Passing Same Parameter Multiple Times

DECLARE
   n NUMBER := 10;
   PROCEDURE do_something (
      n1 IN NUMBER,
      n2 IN OUT NUMBER,
      n3 IN OUT NOCOPY NUMBER) IS
   BEGIN
      n2 := 20;
      DBMS_OUTPUT.put_line(n1);  -- prints 10
      n3 := 30;
      DBMS_OUTPUT.put_line(n1);  -- prints 30
   END;
BEGIN
   do_something(n, n, n);
   DBMS_OUTPUT.put_line(n);  -- prints 20
END;
/

Because they are pointers, cursor variables also increase the possibility of aliasing. In Example 8-26, after the assignment, emp_cv2 is an alias of emp_cv1; both point to the same query work area. The first fetch from emp_cv2 fetches the third row, not the first, because the first two rows were already fetched from emp_cv1. The second fetch from emp_cv2 fails because emp_cv1 is closed.

Example 8-26 Aliasing from Assigning Cursor Variables to Same Work Area

DECLARE
  TYPE EmpCurTyp IS REF CURSOR;
  c1 EmpCurTyp;
  c2 EmpCurTyp;
  PROCEDURE get_emp_data (emp_cv1 IN OUT EmpCurTyp,
                          emp_cv2 IN OUT EmpCurTyp) IS
    emp_rec employees%ROWTYPE;
  BEGIN
    OPEN emp_cv1 FOR SELECT * FROM employees;
    emp_cv2 := emp_cv1;
    FETCH emp_cv1 INTO emp_rec;  -- fetches first row
    FETCH emp_cv1 INTO emp_rec;  -- fetches second row
    FETCH emp_cv2 INTO emp_rec;  -- fetches third row
    CLOSE emp_cv1;
    DBMS_OUTPUT.put_line('The following raises an invalid cursor');
--  FETCH emp_cv2 INTO emp_rec;
--  raises invalid cursor when get_emp_data is invoked
  END;
BEGIN
  get_emp_data(c1, c2);
END;
/

Using the Cross-Session PL/SQL Function Result Cache

The cross-session PL/SQL function result caching mechanism provides a language-supported and system-managed means for caching the results of PL/SQL functions in a shared global area (SGA), which is available to every session that runs your application. The caching mechanism is both efficient and easy to use, and it relieves you of the burden of designing and developing your own caches and cache-managment policies.

To enable result-caching for a function, use the RESULT_CACHE clause. When a result-cached function is invoked, the system checks the cache. If the cache contains the result from a previous call to the function with the same parameter values, the system returns the cached result to the invoker and does not re-execute the function body. If the cache does not contain the result, the system executes the function body and adds the result (for these parameter values) to the cache before returning control to the invoker.

Note:

If function execution results in an unhandled exception, the exception result is not stored in the cache.

The cache can accumulate very many results—one result for every unique combination of parameter values with which each result-cached function was invoked. If the system needs more memory, it ages out (deletes) one or more cached results.

You can specify the database objects that are used to compute a cached result, so that if any of them are updated, the cached result becomes invalid and must be recomputed. The best candidates for result-caching are functions that are invoked frequently but depend on information that changes infrequently or never.

Topics:

Enabling Result-Caching for a Function

To make a function result-cached, do the following:

  • In the function declaration, include the option RESULT_CACHE.

  • In the function definition:

    • Include the RESULT_CACHE clause.

    • In the optional RELIES_ON clause, specify any tables or views on which the function results depend.

For the syntax of the RESULT_CACHE and RELIES_ON clauses, see "Function Declaration and Definition".

In Example 8-27, the package PKG declares and then defines a result-cached function, get_dept_info, which returns the average salary and number of employees in a given department. get_dept_info depends on the database table EMPLOYEES.

Example 8-27 Declaration and Definition of Result-Cached Function

-- Package specification
CREATE OR REPLACE PACKAGE department_pks IS
  TYPE dept_info_record IS RECORD (average_salary      NUMBER,
                                   number_of_employees NUMBER);
  -- Function declaration
  FUNCTION get_dept_info (dept_id NUMBER) RETURN dept_info_record
  RESULT_CACHE;
  ...
END department_pks;
/
CREATE OR REPLACE PACKAGE BODY department_pks AS
  -- Function definition
  FUNCTION get_dept_info (dept_id NUMBER) RETURN dept_info_record
RESULT_CACHE RELIES_ON (EMPLOYEES)
  IS
rec dept_info_record;
  BEGIN
    SELECT AVG(SALARY), COUNT(*) INTO result
      FROM EMPLOYEES
        WHERE DEPARTMENT_ID = dept_id;
RETURN rec;
  END get_dept_info;
  ...
END department_pks;
/
DECLARE
  dept_id   NUMBER := 50;
  avg_sal   NUMBER;
  no_of_emp NUMBER;
BEGIN
  avg_sal   := department_pks.get_dept_info(50).average_salary;
  no_of_emp :=department_pks.get_dept_info(50).number_of_employees;
  DBMS_OUTPUT.PUT_LINE('dept_id:=' ||dept_id );
  DBMS_OUTPUT.PUT_LINE('  average_salary= '|| avg_sal);
  DBMS_OUTPUT.PUT_LINE('   Number_of_employees' ||no_of_emp );
END;
/

You invoke the function get_dept_info as you invoke any function. For example, the following call returns the average salary and the number of employees in department number 10:

PKG.get_dept_info(10);

The following call returns only the average salary in department number 10:

PKG.get_dept_info(10).average_salary;

If the result for get_dept_info(10) is already in the result cache, the result is returned from the cache; otherwise, the result is computed and added to the cache. Because the RELIES_ON clause specifies EMPLOYEES, any update to EMPLOYEES invalidates all cached results for PKG.get_dept_info, relieving you of programming cache invalidation logic everywhere that EMPLOYEES might change.

Developing Applications with Result-Cached Functions

When developing an application that uses a result-cached function, make no assumptions about the number of times the body of the function will execute for a given set of parameter values.

Some situations in which the body of a result-cached function executes are:

  • The first time a session on this database instance invokes the function with these parameter values

  • When the cached result for these parameter values is invalid

    A cached result becomes invalid when any database object specified in the RELIES_ON clause of the function definition changes.

  • When the cached results for these parameter values have aged out

    If the system needs memory, it might discard the oldest cached values.

  • When the function bypasses the cache (see "Bypassing the Result Cache")

Restrictions on Result-Cached Functions

To be result-cached, a function must meet all of the following criteria:

  • It is not defined in a module that has invoker's rights or in an anonymous block.

  • It is not a pipelined table function.

  • It has no OUT or IN OUT parameters.

  • No IN parameter has one of the following types:

    • BLOB

    • CLOB

    • NCLOB

    • REF CURSOR

    • Collection

    • Object

    • Record

  • The return type is none of the following:

    • BLOB

    • CLOB

    • NCLOB

    • REF CURSOR

    • Object

    • Record or PL/SQL collection that contains one of the preceding unsupported return types

It is recommended that a result-cached function also meet the following criteria:

Examples of Result-Cached Functions

The best candidates for result-caching are functions that are invoked frequently but depend on information that changes infrequently (as might be the case in the first example). Result-caching avoids redundant computations in recursive functions.

Examples:

Result-Cached Application Configuration Parameters

Consider an application that has configuration parameters that can be set at either the global level, the application level, or the role level. The application stores the configuration information in the following tables:

-- Global Configuration Settings
CREATE TABLE global_config_params
   ( name  VARCHAR2(20), -- parameter NAME
     value VARCHAR2(20), -- parameter VALUE
     PRIMARY KEY (name)
   )
/

-- Application-Level Configuration Settings
CREATE TABLE app_level_config_params
   ( app_id  VARCHAR2(20), -- application ID
     name    VARCHAR2(20), -- parameter NAME
     value   VARCHAR2(20), -- parameter VALUE
     PRIMARY KEY (app_id, name)
   )
/

-- Role-Level Configuration Settings
CREATE TABLE role_level_config_params
   ( role_id  VARCHAR2(20), -- application (role) ID
     name     VARCHAR2(20),  -- parameter NAME
     value    VARCHAR2(20),  -- parameter VALUE
     PRIMARY KEY (role_id, name)
   )
/

For each configuration parameter, the role-level setting overrides the application-level setting, which overrides the global setting. To determine which setting applies to a parameter, the application defines the PL/SQL function get_value. Given a parameter name, application ID, and role ID, get_value returns the setting that applies to the parameter.

The function get_value is a good candidate for result-caching if it is invoked frequently and if the configuration information changes infrequently. To ensure that a committed change to global_config_params, app_level_config_params, or role_level_config_params invalidates the cached results of get_value, include their names in the RELIES_ON clause.

Example 8-28 shows a possible definition for get_value.

Example 8-28 Result-Cached Function that Returns Configuration Parameter Setting

FUNCTION get_value (p_param VARCHAR2,
                    p_app_id  NUMBER,
                    p_role_id NUMBER) RETURN VARCHAR2
                    RESULT_CACHE RELIES_ON (role_level_config_params,
                                            app_level_config_params,
                                            global_config_params)
IS
  answer VARCHAR2(20);
BEGIN
  -- Is parameter set at role level?
  BEGIN
    SELECT value INTO answer
      FROM role_level_config_params
        WHERE role_id = p_role_id
          AND name = p_param;
    RETURN answer;  -- Found
    EXCEPTION
      WHEN no_data_found THEN
        NULL;  -- Fall through to following code
  END;
  -- Is parameter set at application level?
  BEGIN
    SELECT value INTO answer
      FROM app_level_config_params
        WHERE app_id = p_app_id
          AND name = p_param;
    RETURN answer;  -- Found
    EXCEPTION
      WHEN no_data_found THEN
        NULL;  -- Fall through to following code
  END;
  -- Is parameter set at global level?
    SELECT value INTO answer
     FROM global_config_params
      WHERE name = p_param;
    RETURN answer;
END;

Result-Cached Recursive Function

A recursive function for finding the nth term of a Fibonacci series that mirrors the mathematical definition of the series might do many redundant computations. For example, to evaluate fibonacci(7), the function must compute fibonacci(6) and fibonacci(5). To compute fibonacci(6), the function must compute fibonacci(5) and fibonacci(4). Therefore, fibonacci(5) and several other terms are computed redundantly. Result-caching avoids these redundant computations. A RELIES_ON clause is unnecessary.

FUNCTION fibonacci (n NUMBER) RETURN NUMBER RESULT_CACHE IS
BEGIN
  IF (n =0) OR (n =1) THEN
    RETURN 1;
  ELSE
    RETURN fibonacci(n - 1) + fibonacci(n - 2);
  END IF;
END;

Advanced Result-Cached Function Topics

Topics:

Rules for a Cache Hit

Each time a result-cached function is invoked with different parameter values, those parameters and their result are stored in the cache. Subsequently, when the same function is invoked with the same parameter values (that is, when there is a cache hit), the result is retrieved from the cache, instead of being recomputed.

The rules for parameter comparison for a cache hit differ from the rules for the PL/SQL "equal to" (=) operator, as follows:

Cache Hit Rules "Equal To" Operator Rules
NULL is the same as NULL NULL = NULL evaluates to NULL.
Non-null scalars are the same if and only if their values are identical; that is, if and only if their values have identical bit patterns on the given platform. For example, CHAR values 'AA' and 'AA ' are not the same. (This rule is stricter than the rule for the "equal to" operator.) Non-null scalars can be equal even if their values do not have identical bit patterns on the given platform; for example, CHAR values 'AA' and 'AA ' are equal.

Bypassing the Result Cache

In some situations, the cache is bypassed. When the cache is bypassed:

  • The function computes the result instead of retrieving it from the cache.

  • The result that the function computes is not added to the cache.

Some examples of situations in which the cache is bypassed are:

  • The cache is unavailable to all sessions.

    For example, the database administrator has disabled the use of the result cache during application patching (as in "Hot-Patching PL/SQL Program Units on Which Result-Cached Functions Depend").

  • A session is performing a DML statement on a table or view that was specified in the RELIES_ON clause of a result-cached function. The session bypasses the result cache for that function until the DML statement is completed (either committed or rolled back), and then resumes using the cache for that function.

    Cache bypass ensures the following:

    • The user of each session sees his or her own uncommitted changes.

    • The cross-session cache has only committed changes that are visible to all sessions, so that uncommitted changes in one session are not visible to other sessions.

Making Result-Cached Functions Handle Session-Specific Settings

If a function depends on settings that might vary from session to session (such as NLS_DATE_FORMAT and TIME ZONE), make the function result-cached only if you can modify it to handle the various settings.

Consider the following function:

FUNCTION get_hire_date (emp_id NUMBER) RETURN VARCHAR
 RESULT_CACHE RELIES_ON (HR.EMPLOYEES) IS
  date_hired DATE;
BEGIN
  SELECT hire_date INTO date_hired
   FROM HR.EMPLOYEES
    WHERE EMPLOYEE_ID = emp_id;
  RETURN TO_CHAR(date_hired);
END;

The preceding function, get_hire_date, uses the TO_CHAR function to convert a DATE item to a VARCHAR item. The function get_hire_date does not specify a format mask, so the format mask defaults to the one that NLS_DATE_FORMAT specifies. If sessions that call get_hire_date have different NLS_DATE_FORMAT settings, cached results can have different formats. If a cached result computed by one session ages out, and another session recomputes it, the format might vary even for the same parameter value. If a session gets a cached result whose format differs from its own format, that result will probably be incorrect.

Some possible solutions to this problem are:

  • Change the return type of get_hire_date to DATE and have each session invoke the TO_CHAR function.

  • If a common format is acceptable to all sessions, specify a format mask, removing the dependency on NLS_DATE_FORMAT. For example:

    TO_CHAR(date_hired, 'mm/dd/yy');
    
  • Add a format mask parameter to get_hire_date. For example:

    FUNCTION get_hire_date (emp_id NUMBER, fmt VARCHAR) RETURN VARCHAR
     RESULT_CACHE RELIES_ON (HR.EMPLOYEES) IS
      date_hired DATE;
    BEGIN
      SELECT hire_date INTO date_hired
       FROM HR.EMPLOYEES
        WHERE EMPLOYEE_ID = emp_id;
      RETURN TO_CHAR(date_hired, fmt);
    END;
    

Making Result-Cached Functions Handle Session-Specific Application Contexts

An application context, which can be either global or session-specific, is a set of attributes and their values. A PL/SQL function depends on session-specific application contexts if it does at least one of the following:

  • Directly invokes the built-in function SYS_CONTEXT, which returns the value of a specified attribute in a specified context

  • Indirectly invokes SYS_CONTEXT by using Virtual Private Database (VPD) mechanisms for fine-grained security

    (For information about VPD, see Oracle Database Security Guide.)

The PL/SQL function result-caching feature does not automatically handle dependence on session-specific application contexts. If you must cache the results of a function that depends on session-specific application contexts, you must pass the application context to the function as a parameter. You can give the parameter a default value, so that not every user must specify it.

In Example 8-29, assume that config_tab has a VPD policy that translates this query:

SELECT value FROM config_tab
   WHERE name = param_name;

To this query:

SELECT value FROM config_tab
   WHERE name = param_name
     AND app_id = SYS_CONTEXT('Config', 'App_ID');

Example 8-29 Result-Cached Function that Depends on Session-Specific Application Context

FUNCTION get_param_value
  ( param_name VARCHAR,
    appctx     VARCHAR
    DEFAULT SYS_CONTEXT('Config', 'App_ID')
  )
  RETURN VARCHAR
  RESULT_CACHE RELIES_ON (config_tab)
IS
  result VARCHAR(2000);
BEGIN
  SELECT value INTO result
    FROM config_tab
      WHERE Name = param_name;
END;

Choosing Result-Caching Granularity

PL/SQL provides the function result cache, but you choose the caching granularity. To understand the concept of granularity, consider the Product_Descriptions table in the Order Entry (OE) sample schema:

NAME                     NULL?      TYPE
----------------------   --------   ---------------
PRODUCT_ID               NOT NULL   NUMBER(6)
LANGUAGE_ID              NOT NULL   VARCHAR2(3)
TRANSLATED_NAME          NOT NULL   NVARCHAR2(50)
TRANSLATED_DESCRIPTION   NOT NULL   NVARCHAR2(2000)

The table has the name and description of each product in several languages. The unique key for each row is PRODUCT_ID,LANGUAGE_ID.

Suppose that you want to define a function that takes a PRODUCT_ID and a LANGUAGE_ID and returns the associated TRANSLATED_NAME. You also want to cache the translated names. Some of the granularity choices for caching the names are:

  • One name at a time (finer granularity)

  • One language at a time (coarser granularity)

Table 8-4 Comparison of Finer and Coarser Caching Granularity

Finer Granularity Coarser Granularity

Each function result corresponds to one logical result.

Each function result contains many logical subresults.

Stores only data that is needed at least once.

Might store data that is never used.

Each data item ages out individually.

One aged-out data item ages out the whole set.

Does not allow bulk loading optimizations.

Allows bulk loading optimizations.


In each of the following four examples, the function productName takes a PRODUCT_ID and a LANGUAGE_ID and returns the associated TRANSLATED_NAME. Each version of productName caches translated names, but at a different granularity.

In Example 8-30, get_product_name is a result-cached function. Whenever get_product_name is invoked with a different PRODUCT_ID and LANGUAGE_ID, it caches the associated TRANSLATED_NAME. Each call to get_product_name adds at most one TRANSLATED_NAME to the cache.

Example 8-30 Caching One Name at a Time (Finer Granularity)

FUNCTION get_product_name (prod_id NUMBER, lang_id VARCHAR2)
  RETURN NVARCHAR2
  RESULT_CACHE RELIES_ON (Product_Descriptions)
IS
  result VARCHAR2(50);
BEGIN
  SELECT translated_name INTO result
    FROM product_descriptions
      WHERE PRODUCT_ID = prod_id
        AND LANGUAGE_ID = lang_id;
  RETURN result;
END;

In Example 8-31, get_product_name defines a result-cached function, all_product_names. Whenever get_product_name invokes all_product_names with a different LANGUAGE_ID, all_product_names caches every TRANSLATED_NAME associated with that LANGUAGE_ID. Each call to all_product_names adds every TRANSLATED_NAME of at most one LANGUAGE_ID to the cache.

Example 8-31 Caching Translated Names One Language at a Time (Coarser Granularity)

FUNCTION get_product_name (prod_id NUMBER, lang_id VARCHAR2)
  RETURN NVARCHAR2
IS
  TYPE product_names IS TABLE OF NVARCHAR2(50) INDEX BY PLS_INTEGER;

  FUNCTION all_product_names (lang_id NUMBER) RETURN product_names
    RESULT_CACHE RELIES_ON (product_descriptions)
  IS
    all_names product_names;
  BEGIN
    FOR c IN (SELECT * FROM product_descriptions
      WHERE LANGUAGE_ID = lang_id) LOOP
        all_names(c.PRODUCT_ID) = c.TRANSLATED_NAME;
    END LOOP;
  END;
  RETURN all_names;
BEGIN
  RETURN all_product_names(lang_id)(prod_id);
END;

Result Caches in Oracle RAC Environment

Cached results are stored in the system global area (SGA). In an Oracle RAC environment, each database instance has a private function result cache, available only to sessions on that instance.

The access pattern and work load of an instance determine the set of results in its private cache; therefore, the private caches of different instances can have different sets of results.

If a required result is missing from the private cache of the local instance, the body of the function executes to compute the result, which is then added to the local cache. The result is not retrieved from the private cache of another instance.

Although each database instance might have its own set of cached results, the mechanisms for handling invalid results are Oracle RAC environment-wide. If results were invalidated only in the local instance's result cache, other instances might use invalid results. For example, consider a result cache of item prices that are computed from data in database tables. If any of these database tables is updated in a way that affects the price of an item, the cached price of that item must be invalidated in every database instance in the Oracle RAC environment.

Managing the Result Cache

The PL/SQL function result cache shares its administrative and manageability infrastructure with the Result Cache, which is described in Oracle Database Performance Tuning Guide.

The database administrator can use the following to manage the Result Cache:

Hot-Patching PL/SQL Program Units on Which Result-Cached Functions Depend

When you hot-patch a PL/SQL program unit on which a result-cached function depends (directly or indirectly), the cached results associated with the result-cached function might not be automatically flushed in all cases.

For example, suppose that the result-cached function P1.foo() depends on the packaged subprogram P2.bar(). If a new version of the body of package P2 is loaded, the cached results associated with P1.foo() are not automatically flushed.

Therefore, this is the recommended procedure for hot-patching a PL/SQL program unit:

  1. Put the result cache in bypass mode and flush existing results:

    BEGIN
      DBMS_RESULT_CACHE.Bypass(TRUE);
      DBMS_RESULT_CACHE.Flush;
    END;
    /
    

    In an Oracle RAC environment, perform this step for each database instance.

  2. Patch the PL/SQL code.

  3. Resume using the result cache:

    BEGIN
      DBMS_RESULT_CACHE.Bypass(FALSE);
    END;
    /
    

    In an Oracle RAC environment, perform this step for each database instance.