MacLochlainns Weblog

Michael McLaughlin's Technical Blog

Site Admin

Type & Body Basics

with 3 comments

Object Types and Bodies Basics

Oracle Database 10g gave us a new way to write PL/SQL – object types. Object types are different from standard PL/SQL functions, procedures, and packages. While you can pin packages in memory, object types go one step further. You can instantiate them, which means you can start them, assign values to their variables, and put them into your PGA’s memory. Object types provide you with new challenges writing programs in the Oracle database.

Oracle Database 12c makes using object types simpler. That’s because Oracle Database 12c supports type evolution. Type evolution lets you change an object type when it has dependents. An object type’s dependents can be a table, another object type, function, procedure, or package. Oracle Database 12c also lets you white list the callers of an object type.

You define object types with variables and methods, like you define packages. Object type methods are either functions or procedures. You can implement object type functions and procedures as instance or static methods. An instance method works on the object type’s variable, whereas, static methods work like ordinary functions and procedures. That means static methods can’t access object type variables.
You learn how to define and implement basic object types and bodies in this article. This article shows you how to use and deploy objects and shows you how to implement the specialized CONSTRUCTOR functions.

The following declares a basic people_obj object type:

SQL> CREATE OR REPLACE
  2    TYPE people_obj IS OBJECT
  3    ( people_id    NUMBER
  4    , first_name   VARCHAR2(20)
  5    , middle_name  VARCHAR2(20)
  6    , last_name    VARCHAR2(20));
  7    /

The CREATE OR REPLACE is SQL syntax creates an object type, like you would create a PL/SQL function, procedure, or package. Lines 2 through 6 declare a four element people_obj object type, and the semicolon on line 6 acts as a statement terminator. The forward slash on line 7 executes the CREATE TYPE statement.

To most developers the foregoing syntax appears to declare a record data structure. There’s more to it than that. The CREATE TYPE syntax also creates an implicit constructor function. You can call the people_obj constructor with a list of parameter that matches both the list of element names and their data types. The call syntax supports both named and positional notation.
You can test the people_obj object type with the following anonymous block:

SQL> DECLARE
  2    people  PEOPLE_OBJ := people_obj(1,'John','Paul','Jones');
  3  BEGIN
  4    dbms_output.put_line( people.first_name  || ' '
  5                       ||people.middle_name || ' '
  6                       ||people.last_name);
  7  END;
  8  /

Line 2 declares a variable of the object type with positional notation, and then it assigns an instance of the people_obj object type. On the right side of the assignment operator, a call to the constructor function creates an instance of the people_obj object type. Object construction has the highest order of precedence, which means it always creates the people_obj instance first.

Lines 4 through 6 print the values of the first, middle, and last name elements. These values are the instance values held by the peoplevariable. It prints:

John Paul Jones

The following example shows you how to call the default people_obj constructor with named notation:

SQL> DECLARE
  2    people  PEOPLE_OBJ := people_obj( first_name => 'John'
  3                                    , middle_name => 'Paul'
  4                                    , last_name => 'Jones'
  5                                    , people_id => 2);
  6  BEGIN
  ...
 10  END;
 11  /

The named notation on lines 2 through 5 let us vary the order of the object attributes. Oracle raises the following exception if you pare the list of call parameters by removing one of them.

PLS-00306: wrong number or types of arguments in call to 'PEOPLE_OBJ'

You can add one or more override constructor functions to the people_obj object type. The first override constructor example has two call parameters, and they are the first_name and last_name parameters.

SQL> CREATE OR REPLACE
  2    TYPE people_obj IS OBJECT
  3    ( people_id    NUMBER
  4    , first_name   VARCHAR2(20)
  5    , middle_name  VARCHAR2(20)
  6    , last_name    VARCHAR2(20)
  7    , CONSTRUCTOR FUNCTION people_obj
  8      ( first_name  VARCHAR2
  9      , last_name   VARCHAR2 ) RETURN SELF AS RESULT)
 10  INSTANTIABLE NOT FINAL;
 11  /

Lines 7 through 9 declare the override constructor function. This override constructor function doesn’t provide a value for the people_id attribute. The concept of an object having a unique identifier, or ID, is part of good object-oriented design practices.

An Oracle sequence can help us guarantee the unique ID. You can create a people_obj_s sequence for the people_obj with the following syntax:

SQL> CREATE SEQUENCE people_obj_s;

You can use the people_obj_s sequence in the override constructor to generate the unique ID. The following code implements the modified people_obj object type:

SQL> CREATE OR REPLACE
  2    TYPE BODY people_obj IS
  3    CONSTRUCTOR FUNCTION people_obj
  4    ( first_name     VARCHAR2
  5    , last_name      VARCHAR2 ) RETURN SELF AS RESULT IS
  6
  7     /* Set a counter variable using a sequence. */
  8     lv_people_obj_s  NUMBER := people_obj_s.NEXTVAL;
  9
 10    BEGIN
 11     /* Create the instance with the default constructor. */
 12     self := people_obj( people_id => lv_people_obj_s
 13                       , first_name => first_name
 14                       , middle_name => NULL
 15                       , last_name => last_name );
 16     /* Return the current instance. */
 17     RETURN;
 18    END people_obj;
 19  END;
 20  /

Line 8 declares a local lv_people_obj_s variable, and it assigns the next value from the people_obj_s sequence. The local variable is necessary because you can’t put a call to the .NEXTVAL pseudo column inside a call to an object type constructor function.

The self key word on line 12 represents the instance of an object. You call the default constructor on lines 12 through 15. The default constructor takes a local variable, two parameter values, and a null value.

You can test the new people_obj with the following anonymous block:

SQL> DECLARE
  2    people  PEOPLE_OBJ := people_obj( first_name => 'John'
  3                                    , last_name => 'Jones');
  4  BEGIN
  5    dbms_output.put_line( '['|| people.people_id   ||'] '
  6                        ||'['|| people.first_name  ||'] '
  7                        ||'['|| people.middle_name ||'] '
  8                        ||'['|| people.last_name   ||']');
  9  END;
 10  /

It prints

[1] [John] [] [Jones]

Clearly, the handling of the middle_name attribute is suboptimal. Actually, it’s more or less a joke. However, it does give us an opportunity to show how to handle optional parameters in a constructor function.

You would change the people_obj object type by adding a parameter to the override constructor function, like

SQL> CREATE OR REPLACE
  2    TYPE people_obj IS OBJECT
  3    ( people_id      NUMBER
  4    , first_name     VARCHAR2(20)
  5    , middle_name  VARCHAR2(20)
  6    , last_name      VARCHAR2(20)
  7    , CONSTRUCTOR FUNCTION people_obj
  8      ( first_name     VARCHAR2
  9      , middle_name    VARCHAR2 DEFAULT NULL
 10      , last_name      VARCHAR2 ) RETURN SELF AS RESULT)
 11  INSTANTIABLE NOT FINAL;
 12  /

There are only two changes to the implementation of the people_obj object body. One changes the list of parameters in the constructor function. The other replaces the null assignment with a parameter value from the overriding constructor function.

Here’s the implementation of the new people_obj object body:

SQL> CREATE OR REPLACE
  2    TYPE BODY people_obj IS
  3    CONSTRUCTOR FUNCTION people_obj
  4    ( first_name   VARCHAR2
  5    , middle_name  VARCHAR2 DEFAULT NULL
  6    , last_name    VARCHAR2 ) RETURN SELF AS RESULT IS
  7 
  8   /* Set a counter variable using a sequence. */
  9   lv_people_obj_s  NUMBER := people_obj_s.NEXTVAL;
 10 
 11    BEGIN
 12     /* Create the instance with the default constructor. */
 13     self := people_obj( people_id => lv_people_obj_s
 14                       , first_name => first_name
 15                        , middle_name => middle_name
 16                       , last_name => last_name );
 17     /* Return the current instance. */
 18     RETURN;
 19    END people_obj;
 20  END;
 21  /

Line 5 specifies the middle_name parameter as an optional parameter. The optional parameter in the middle of the list can present a problem when you make call to it with positional notation. A call with named notation on the other hand works without a hitch. Line 15 replaces the null value with the middle_name parameter from the constructor function.

You can test the modified people_obj with the following anonymous block:

SQL> DECLARE
  2    people  PEOPLE_OBJ := people_obj( first_name => 'John'
  3                                    , last_name => 'Jones');
  4
  5  BEGIN
  6    dbms_output.put_line( '['|| people.people_id   ||'] '
  7                       ||'['|| people.first_name  ||'] '
  8                       ||'['|| people.middle_name ||'] '
  9                       ||'['|| people.last_name   ||']');
 10  END;
 11  /

It prints

[1] [John] [] [Jones]

If you modify the constructor call on lines 2 through 4, as follows:

  2    people  PEOPLE_OBJ := people_obj( first_name => 'James'
  3                                    , middle_name => 'Wilson'
  4                                    , last_name => 'Jones');

It prints

[1] [John] [Wilson] [Jones]

There are still several problems with the current people_obj object type. The largest shortfall is that there’s no traditional default constructor. In many object-oriented language, a default constructor is a null argument constructor. A null argument constructor let’s you position logic that all other constructors can leverage.

A sequence value is an example of logic that you can share across constructor functions. The following version of the people_obj object type declares a standard no argument constructor function:

SQL> CREATE OR REPLACE
  2    TYPE people_obj IS OBJECT
  3    ( people_id    NUMBER
  4    , first_name   VARCHAR2(20)
  5    , middle_name  VARCHAR2(20)
  6    , last_name    VARCHAR2(20)
  7    , CONSTRUCTOR FUNCTION people_obj RETURN SELF AS RESULT
  8    , CONSTRUCTOR FUNCTION people_obj
  9      ( first_name   VARCHAR2
 10      , middle_name  VARCHAR2 DEFAULT NULL
 11      , last_name    VARCHAR2 ) RETURN SELF AS RESULT)
 12  INSTANTIABLE NOT FINAL;
 13  /

Line 7 holds the declaration of a no argument constructor. The following people_obj object type implements a no argument constructor. The object body also makes access to the sequence a feature available to all overriding constructors.

SQL> CREATE OR REPLACE
  2    TYPE BODY people_obj IS
  3 
  4    /* Default constructor. */
  5    CONSTRUCTOR FUNCTION people_obj RETURN SELF AS RESULT IS
  6 
  7     /* Set a counter variable using a sequence. */
  8     lv_people_obj_s  NUMBER := people_obj_s.NEXTVAL;
  9 
 10    BEGIN
 11     /* Assign a sequence value to the instance. */
 12     self.people_id := lv_people_obj_s;
 13 
 14     /* Return a constructed instance. */
 15     RETURN;
 16    END;
 17 
 18    /* Override constructor. */
 19    CONSTRUCTOR FUNCTION people_obj
 20    ( first_name   VARCHAR2
 21    , middle_name  VARCHAR2 DEFAULT NULL
 22    , last_name    VARCHAR2 ) RETURN SELF AS RESULT IS
 23 
 24     /* Create a empty default instance. */
 25     people  PEOPLE_OBJ := people_obj();
 26 
 27    BEGIN
 28     /* Create the instance with the default constructor. */
 29     people.first_name := first_name;
 30     people.middle_name := middle_name;
 31     people.last_name := last_name;
 32 
 33     /* Assign a local instance this instance. */
 34     self := people;
 35 
 36     /* Return the current instance. */
 37     RETURN;
 38    END people_obj;
 39  END;
 40  /

The implementation of the no argument constructor is on lines 5 through 16. It uses the .NEXTVAL pseudo column to secure the next sequence value as a unique ID. Then, the constructor function returns a uniquely identified but otherwise empty object instance.

Line 25 creates a people_obj instance inside the declaration block of the overriding constructor. Inside the execution block, the overriding parameters are assigned to the attributes of the local instance. Ultimately, the local instance is assigned to the current instance and returned to any caller of the overriding constructor.

You call the modified overriding function with the following anonymous block:

SQL> DECLARE
  2    people  PEOPLE_OBJ := people_obj( first_name => 'Samuel'
  3                                    , middle_name => 'Langhorne'
  4                                    , last_name => 'Clemens');
  5  BEGIN
  6    dbms_output.put_line( '['|| people.people_id   ||'] '
  7                        ||'['|| people.first_name  ||'] '
  8                        ||'['|| people.middle_name ||'] '
  9                        ||'['|| people.last_name   ||']');
 10  END;
 11  /

It prints

[3] [Samuel] [Langhorne] [Clemens]

This article has shown you how to define and implement basic object types and bodies. It also has shown you how to work with default, no argument, and overriding constructor functions.

Written by maclochlainn

November 23rd, 2018 at 10:17 pm