MacLochlainns Weblog

Michael McLaughlin's Technical Blog

Site Admin

Column Substitutability

without comments

Object Types and Column Substitutability

This article shows you how to use extend parent (or superclass) objects. You extend parent classes when you implement specialized behaviors (or methods) in subtypes. That’s because SQL statements can’t work with specialized methods when a table’s column stores subclasses in a superclass column type.

Substitutability is the process of storing subtypes in a super type column. It is a powerful feature of the Oracle database. The “type evolution” feature of the Oracle Database 12c release makes it more important because it makes it more flexible. The flexibility occurs because Oracle lets you evolve parent classes.

You evolve parent classes when you implement MEMBER functions or procedures, and you want to access them for all substitutable column values. That’s necessary because you need to define the MEMBER function or procedure in the column’s base object type. Prior to Oracle Database 12c, you couldn’t change (evolve) a base type. If you’re new to the idea of object types and subtypes, you may want to check out my earlier “Object Types and Subtypes” article.

Before discussing the complexity of creating and evolving object types to support column substitutability, let’s create a base_t object type. The base_t object type will become our root node object type. A root node object type is our most general object type. A root node is also the topmost node of an inverted tree of object types. All subtypes of the root node become child nodes, and child nodes without their own children are at the bottom of the tree and they’re leaf nodes.

The following creates the base_t object type. It is similar to object types that I use in related articles to keep ideas consistent and simple across the articles. This version of the base_t object doesn’t try to maintain an internal unique identifier because the table maintains it as a surrogate key.

SQL> CREATE OR REPLACE
  2    TYPE base_t IS OBJECT
  3    ( oname VARCHAR2(30)
  4    , CONSTRUCTOR FUNCTION base_t
  5      RETURN SELF AS RESULT
  6    , MEMBER FUNCTION get_oname RETURN VARCHAR2
  7    , MEMBER PROCEDURE set_oname (oname VARCHAR2)
  8    , MEMBER FUNCTION to_string RETURN VARCHAR2)
  9    INSTANTIABLE NOT FINAL;
 10  /

The oname attribute on line two holds the name of the object type. Lines 4 and 5 define the default constructor, which has no formal parameters. Line 6 defines an accessor method, or getter, and line 7 defines a mutator, or setter. Line 8 defines a traditional to_string method that lets you print the contents of the object type.

Next, let’s implement the base_t object type’s body:

SQL> CREATE OR REPLACE
  2    TYPE BODY base_t IS
  3    /* A default constructor w/o formal parameters. */
  4    CONSTRUCTOR FUNCTION base_t
  5    RETURN SELF AS RESULT IS
  6      BEGIN
  7        self.oname := 'BASE_T';
  8        RETURN;
  9      END;
 10    /* An accessor, or getter, method. */
 11    MEMBER FUNCTION get_oname RETURN VARCHAR2 IS
 12      BEGIN
 13        RETURN self.oname;
 14      END get_oname;
 15    /* A mutator, or setter, method. */
 16    MEMBER PROCEDURE set_oname
 17    ( oname VARCHAR2 ) IS
 18      BEGIN
 19        self.oname := oname;
 20      END set_oname;
 21    /* A to_string conversion method. */
 22    MEMBER FUNCTION to_string RETURN VARCHAR2 IS
 23      BEGIN
 24        RETURN '['||self.oname||']';
 25      END to_string;
 26  END;
 27  /

Line 7 assigns a literal value to the oname attribute. Line 24 returns the value of the oname attribute for the instance. The remainder of the object type is generic. You can read about the generic features in my “Object Types and Bodies Basics” and about accessor and mutator methods in my “Object Types with Getters and Setters” articles.

Let’s define and implement a hobbit_t subtype of our base_t object type. The hobbit_t object type is:

SQL> CREATE OR REPLACE TYPE hobbit_t UNDER base_t
  2  ( genus VARCHAR2(20)
  3  , name   VARCHAR2(20)
  4  , CONSTRUCTOR FUNCTION hobbit_t
  5    ( genus VARCHAR2
  6    , name   VARCHAR2) RETURN SELF AS RESULT
  7  , MEMBER FUNCTION get_genus RETURN VARCHAR2
  8  , MEMBER FUNCTION get_name RETURN VARCHAR2
  9  , MEMBER PROCEDURE set_genus (genus VARCHAR2)
 10  , MEMBER PROCEDURE set_name (name VARCHAR2)
 11  , OVERRIDING MEMBER FUNCTION to_string RETURN VARCHAR2)
 12    INSTANTIABLE NOT FINAL;
 13  /

Lines 2 and 3 add two new genus and name attributes to the hobbit_t subtype. The hobbit_t subtype also inherits the oname attribute from its parent base_t type. Lines 7 and 8 define two getters and lines 9 and 10 define two setters, which support the genus and name attributes of the hobbit_t subtype. The hobbit_t object type’s getter and setters are unique to the subtype. They are also a specialization of the base_t object type. As such, these getters and setters are inaccessible to instances of the base_t object type. Line 11 defines an overriding to_string function for the base_t type’s to_string function.

The following implements the hobbit_t object body:

SQL> CREATE OR REPLACE TYPE BODY hobbit_t IS
  2    /* A default constructor with two formal parameters. */
  3    CONSTRUCTOR FUNCTION hobbit_t
  4    ( genus VARCHAR2
  5    , name   VARCHAR2 )
  6    RETURN SELF AS RESULT IS
  7      BEGIN
  8        self.oname := 'HOBBIT_T';
  9        self.name := name;
 10        self.genus := genus;
 11        RETURN;
 12      END;
 13    /* An accessor, or getter, method. */
 14    MEMBER FUNCTION get_genus RETURN VARCHAR2 IS
 15      BEGIN
 16        RETURN self.genus;
 17      END get_genus;
 18    /* An accessor, or getter, method. */
 19    MEMBER FUNCTION get_name RETURN VARCHAR2 IS
 20      BEGIN
 21        RETURN self.name;
 22      END get_name;
 23    /* A mutator, or setter, method. */
 24    MEMBER PROCEDURE set_genus
 25    ( genus VARCHAR2 ) IS
 26      BEGIN
 27        self.genus := genus;
 28      END set_genus;
 29    /* A mutator, or setter, method. */
 30    MEMBER PROCEDURE set_name
 31    ( name VARCHAR2 ) IS
 32      BEGIN
 33        self.name := name;
 34      END set_name;
 35      /* A to_string conversion method. */
 36    OVERRIDING MEMBER FUNCTION to_string RETURN VARCHAR2 IS
 37      BEGIN
 38        /* Uses general invocation on parent to_string
 39           function. */
 40       RETURN (self AS base_t).to_string
 41              || '['||self.genus||']['||self.name||']';
 42    END to_string;
 43  END;
 44  /

Lines 4 and 5 list the parameters for the hobbit_t constructor. Line 8 assigns a literal value to the oname attribute of the base_t object type. Lines 9 and 10 assign the formal parameters to the genus and name attributes of the hobbit_t subtype. Line 40 uses a general invocation statement to call the base_t’s to_string function.

You can now create a table that has a substitutable column that uses the base_t parent object type. The Oracle database assumes object type columns are substitutable at all levels, unless you turn off a column’s substitutability.

The following creates a tolkien table, and it has only two columns. One column has a NUMBER data type and the other has a user-defined object type. The base_t object type column is substitutable at all levels:

SQL> CREATE TABLE tolkien
  2  ( tolkien_id NUMBER
  3  , character   BASE_T );

You create a tolkien_s sequence for the unique tolkien_id column with the following:

SQL> CREATE SEQUENCE tolkien_s START WITH 1001;

You can insert one base_t and two hobbit_t object types with the following INSERT statements:

SQL> INSERT INTO tolkien VALUES
  2  ( tolkien_s.NEXTVAL, base_t() );
SQL> INSERT INTO tolkien VALUES
  2  ( tolkien_s.NEXTVAL, hobbit_t('HOBBIT','Bilbo') );
SQL> INSERT INTO tolkien VALUES
  2  ( tolkien_s.NEXTVAL, hobbit_t('HOBBIT','Frodo') );

The following simple query shows you the unique identifier in the tolkien_id column and collapsed object types in the character column of the tolkien table:

SQL> COLUMN character FORMAT A40
SQL> SELECT   tolkien_id
  2  ,        character
  3  FROM     tolkien;

It should display the following:

TOLKIEN_ID CHARACTER(ONAME)
---------- ----------------------------------------
     1001 BASE_T('BASE_T')
     1002 HOBBIT_T('HOBBIT_T', 'HOBBIT', 'Bilbo')
     1003 HOBBIT_T('HOBBIT_T', 'HOBBIT', 'Frodo')

Oracle always stores object instances as collapsed object instances in tables. You need to use the TREAT function in SQL to read instances of an object type.

The TREAT function lets you place in memory an instance of an object type. The TREAT function requires that you designate the type of object instance. If you want the TREAT function to work with all rows of the table, you designate the column’s object type as the base (or superclass) type. Designating a subtype to work like a parent, grandparent, or any antecedent type is a form of casting. Though casting in this case is actually dynamic dispatch.

Dynamic dispatch lets you pass any subtype as a parent or antecedent type. Dynamic dispatch inspects the object and treats it as a unique object.

The following query uses the TREAT function to read the parent and any subtype of the parent object type:

SQL> COLUMN to_string FORMAT A40
SQL> SELECT tolkien_id
  2  ,      TREAT(character AS BASE_T).to_string() AS to_string
  3  FROM   tolkien;

It prints the oname attribute for base_t instances and the oname, genus, and name attributes for hobbit_t instances, like

TOLKIEN_ID TO_STRING
---------- ---------------------------
     1001 [BASE_T]
     1002 [BASE_T]
     1003 [HOBBIT_T][HOBBIT][Bilbo]
     1004 [HOBBIT_T][HOBBIT][Frodo]

The TREAT function manages dynamic dispatch but requires any specialized method of a subtype to exist in the parent or antecedent type to which it is cast. Any query can cast to the root or an intermediate parent subtype. The TREAT function raises an exception when you don’t have an equivalent method stub (definition) in the parent or antecedent type.

For example, let’s modify the previous query and change the method call on line 2 from the to_string function to the get_name function. The new query is:

SQL> COLUMN to_string FORMAT A40
SQL> SELECT tolkien_id
  2  ,      TREAT(character AS BASE_T).get_name() AS get_name
  3  FROM   tolkien;

It fails with the following error:

,       TREAT(character AS BASE_T).get_name() AS get_name
                                           *
ERROR at line 2:
ORA-00904: "STUDENT"."BASE_T"."GET_NAME": invalid identifier

The reason for the failure is interesting. It occurs because the get_name function is only part of the hobbit_t subtype and can’t be found as an identifier inside the base_t object type. PL/SQL identifiers are: reserved or key words; predefined identifiers; quoted identifiers; user-identifiers; and user-defined variables, subroutine, and data or object type names.

You can access the MEMBER functions or procedures (method) of a subtype when you cast to a parent type provided you meet two conditions. First, you must implement the MEMBER method in the subtype. Second, you must define the same MEMBER method in the parent type.

Accessing a subtype MEMBER method differs from general invocation. General invocation occurs when you call a MEMBER method from a parent or antecedent type from a subtype’s OVERRIDING MEMBER method. Oracle doesn’t explain how you call a subtype’s method from a parent or antecedent type but there is a close corollary – packages.

For example, you can only call a package function or procedure from another PL/SQL block when you’ve defined it in the package specification. This means you need to implement a stub for the get_name function inside the base_t object type because it acts as the specification.

You add a get_name function to the base_t object type in the next example:

SQL> CREATE OR REPLACE
  2    TYPE base_t IS OBJECT
  3    ( oname VARCHAR2(30)
  4    , CONSTRUCTOR FUNCTION base_t
  5      RETURN SELF AS RESULT
  6    , MEMBER FUNCTION get_name RETURN VARCHAR2
  7    , MEMBER FUNCTION get_oname RETURN VARCHAR2
  8    , MEMBER PROCEDURE set_oname (oname VARCHAR2)
  9    , MEMBER FUNCTION to_string RETURN VARCHAR2)
 10    INSTANTIABLE NOT FINAL;
 11  /

Line 6 adds the get_name function to the base_t object type. The following shows you how to implement get_name function stub in the object type body:

SQL> CREATE OR REPLACE
  2    TYPE BODY base_t IS
  3    CONSTRUCTOR FUNCTION base_t
  4    RETURN SELF AS RESULT IS
  5      BEGIN
  6        self.oname := 'BASE_T';
  7      RETURN;
  8    END;
  9    MEMBER FUNCTION get_name RETURN VARCHAR2 IS
 10    BEGIN
 11      RETURN NULL;
 12    END get_name;
 13    MEMBER FUNCTION get_oname RETURN VARCHAR2 IS
 14    BEGIN
 15      RETURN self.oname;
 16    END get_oname;
 17    MEMBER PROCEDURE set_oname
 18    ( oname VARCHAR2 ) IS
 19    BEGIN
 20      self.oname := oname;
 21    END set_oname;
 22    MEMBER FUNCTION to_string RETURN VARCHAR2 IS
 23    BEGIN
 24      RETURN '['||self.oname||']';
 25    END to_string;
 26  END;
 27  /

Lines 9 through 12 implement the get_name function stub. You should note that it returns a null value because the name attribute doesn’t exist in the root node (base_t) object type.

The change to the hobbit_t object type is simpler. All you need to do is add the OVERRIDING keyword before the get_name member function in the hobbit_t object type and body. With that change, you can successfully run the following query:

SQL> COLUMN get_name FORMAT A20
SQL> SELECT tolkien_id
  2  ,      TREAT(character AS BASE_T).get_name() AS get_name
  3  FROM   tolkien;

It now works and prints:

TOLKIEN_ID GET_NAME
---------- --------------------
     1001
     1002 Bilbo
     1003 Frodo

This article showed you how to extend parent object types. It also showed you how to modify parent types to support generalized calls with the TREAT function. Together these principles show you how to leverage substitutability on columns.

Written by maclochlainn

November 24th, 2018 at 1:09 am