Chapter 25. Object-Oriented Aspects of PL/SQL

PL/SQL has always been a language that supports traditional procedural programming styles such as structured design and functional decomposition. Using PL/SQL packages, it is also possible to take an object-based approach, applying principles such as abstraction and encapsulation to the business of manipulating relational tables. Later versions of Oracle have introduced direct support for object-oriented programming (OOP), providing a rich and complex type system, complete with support for type hierarchies and “substitutability.”

In the interest of summarizing this book-sized topic into a modest number of pages, this chapter presents a few choice code samples to demonstrate the most significant aspects of object programming with PL/SQL. These cover the following areas:

  • Creating and using object types

  • Inheritance and substitutability

  • Type evolution

  • Pointer (REF)-based retrieval

  • Object views

Among the things you will not find in this chapter are:

  • Comprehensive syntax diagrams for SQL statements dealing with object types

  • Database administration topics such as importing and exporting object data

  • Low-level considerations such as physical data storage on disk

I’d like to introduce the topic with a brief history.

Introduction to Oracle’s Object Features

First released in 1997 as an add-on to the Oracle8 Database (the so-called “object-relational database”), the Objects Option allowed developers to extend Oracle’s built-in datatypes to include abstract datatypes . The introduction of programmer-defined collections (described in Chapter 12) in that release also proved useful, not only because application developers had been looking for ways to store and retrieve arrays in the database, but also because PL/SQL provided a new way to query collections as if they were tables. While there were other interesting aspects of the new Oracle object model such as pointer-based navigation, there was no notion of inheritance or dynamic polymorphism, making the object-relational features of the Oracle8 Database an option that drew few converts from (or into) the camp of true OOP believers. The complexity of the object features, plus a perceived performance hit, also limited uptake in the relational camp.

The Oracle8i Database introduced support for Java Stored Procedures, which not only provided the ability to program the server using a less proprietary language than PL/SQL, but also made it easier for the OOP community to consider using stored procedures. Oracle provided a way to translate object type definitions from the server into Java classes, making it possible to share objects across the Java/database boundary. Oracle released the Oracle8i Database during a peak of market interest in Java, so hardly anyone really noticed that Oracle’s core object features were not much enhanced, except that Oracle quietly began bundling the object features with the core database server. Around this time, I asked an Oracle representative about the future of object programming in PL/SQL, and the response was, “If you want real object-oriented programming in the database, use Java.”

Nevertheless, with the Oracle9i Database release, Oracle significantly extended the depth of its native object support, becoming a more serious consideration for OO purists. Inheritance and polymorphism have become available in the database, and PL/SQL has gained new object features. Does it finally make sense to extend the object model of our system into the structure of the database itself? Should we repartition existing middleware or client applications to take advantage of “free stuff” in the database server? As Table 25-1 shows, Oracle has made great strides, and the move may be tempting. The table also shows that a few desirable features still aren’t available.[*]

Tip

Oracle Database 10g, although introducing several useful enhancements to collections (see Chapter 12), includes only one new feature unique to object types: it is described in the sidebar "The OBJECT_VALUE Pseudo Column" later in this chapter.

Table 25-1. Significant object programming features in the Oracle database

    

Oracle8 Database release

Feature

8.0

8.1

9.1

9.2, 10.1, 10.2

Abstract datatypes as first-class database entity

Abstract datatypes as PL/SQL parameter

Collection-typed attributes

REF-typed attributes for intra-database object navigation

Implementing method logic in PL/SQL or C

Programmer-defined object comparison semantics

Views of relational data as object-typed data

Compile-time or static polymorphism (method overloading)

Ability to “evolve” type by modifying existing method logic (but not signature), or by adding methods

Implementing method logic in Java

 

“Static” methods (executes without having object instance)

 

Relational primary key can serve as persistent object identifier, allowing declarative integrity of REFs

 

Inheritance of attributes and methods from a user-defined type

  

Dynamic method dispatch

  

Noninstantiable supertypes, similar to Java-style “abstract classes”

  

Ability to evolve type by removing methods (and adding to change signature)

  

Ability to evolve type by adding and removing attributes, automatically propagating changes to associated physical database structures

  

“Anonymous” types: ANYTYPE, ANYDATA, ANYDATASET

  

Downcast operator (TREAT) and type detection operator (IS OF) available in SQL

  

TREAT and IS OF available in PL/SQL

   

User-defined constructor functions

   

“Private” attributes, variables, constants, and methods

    

Inheritance from multiple supertypes

    

Sharing of object types or instances across distributed databases without resorting to object views

    

Unless you’re already a practicing object-oriented programmer, many of the terms in this table probably don’t mean much to you. However, the remainder of this chapter should shed some light on these terms and give some clues about the larger architectural decisions you may need to make.

An Extended Example

I’d like to point out that this example—indeed, most of this chapter—relies heavily on features introduced in Oracle9i Database. In a number of areas, this section focuses on features available only in Oracle9i Release 2 and Oracle Database 10g. If you really want server-side object orientation, you probably won’t want to use any version earlier than Oracle9i Database anyway, and you’ll probably agree that ignoring the OO-challenged earlier versions is no great sacrifice.

A Tree of Types

In keeping with the sample general application area we explored in our introductory book, Learning Oracle PL/SQL Programming (O’Reilly), I’d like to build an Oracle system that will use an object-oriented approach to modeling a trivial library catalog. The catalog can hold books, serials (such as magazines, proceedings, or newspapers), and, eventually, other artifacts.

A graphic portrayal of the top-level types appears in Figure 25-1. Later on, we might want to add to the type hierarchy, as the dotted-line boxes imply.

Type hierarchy for a trivial library catalog

Figure 25-1. Type hierarchy for a trivial library catalog

Creating a base type

The “root” or top of the hierarchy represents the common characteristics of all the subtypes. For now, let’s assume that the only things that books and serials have in common are a library-assigned identification number and some kind of filing title. We can create an object type for catalog items using the following SQL statement from SQL*Plus:

    CREATE OR REPLACE TYPE catalog_item_t AS OBJECT (
       id INTEGER,
       title VARCHAR2(4000),

       NOT INSTANTIABLE MEMBER FUNCTION ck_digit_okay
          RETURN BOOLEAN,
       MEMBER FUNCTION print
          RETURN VARCHAR2
    ) NOT INSTANTIABLE NOT FINAL;

This statement creates an object type, which is similar to a Java or C++ class. In relational terms, an object type is akin to a record type bundled with related functions and procedures. These subprograms are known collectively as methods .

The NOT FINAL keyword at the end flags the datatype as being able to serve as the base type or supertype from which you can derive other types. I needed to include NOT FINAL because I want to create subtypes for books and serials; if this keyword is omitted, Oracle defaults to FINAL, that is, no subtypes allowed.

Notice also that I’ve marked this type specification NOT INSTANTIABLE. Although PL/SQL will let me declare a variable of type catalog_item_t, I won’t be able to give it a value—not directly, anyway. Similar to a Java abstract class, this kind of type exists only to serve as a base type from which to create subtypes, and objects of the subtype will, presumably, be instantiable.

For demonstration and debugging purposes, I’ve included a print method (“print” is not a reserved word, by the way) as a way to describe the object in a single string. When I create a subtype, it can (and probably should) override this method—in other words, the subtype will include a method with the same name, but will also print the subtype’s attributes. Notice that instead of making print a procedure, which would have hardcoded a decision to use something like DBMS_OUTPUT.PUT_LINE, I decided to make it a function whose output can be redirected later. This decision isn’t particularly object-oriented, just good design.

I’ve also defined a ck_digit_okay method that will return TRUE or FALSE depending on whether the “check digit " is OK. The assumption here (which is a bad one, I admit) is that all subtypes of catalog_item_t will be known by some identifier other than their library-assigned id, and these other identifiers include some concept of a check digit.[*] I’m only going to be dealing with books and serials, normally identified with an ISBN or ISSN, so the check digit concept applies to all the subtypes.

Here are a few further comments before moving on to the next part of the example:

  • The CREATE TYPE statement above creates only an object type specification. The corresponding body, which implements the methods, will be created separately using CREATE TYPE BODY.

  • Object types live in the same namespace as tables and top-level PL/SQL programs. This is one of the reasons I use the “_t” naming convention with types.

  • Object types are owned by the Oracle user (schema) that created them, and this user may grant EXECUTE privilege to other users.

  • You can attempt to create synonyms on object types, but unless you’re using Oracle9i Database Release 2 or later, the synonyms won’t work.

  • As with conventional PL/SQL programs, you can create an object type using either definer rights (the default) or invoker rights (described in Chapter 23).

  • Unlike some languages’ object models, Oracle’s model doesn’t define a master root-level class from which all programmer-defined classes derive. Instead, you can create any number of standalone root-level datatypes such as catalog_item_t.

  • If you see the compiler error PLS-00103: Encountered the symbol “;” when expecting one of the following ... , you have probably made the common mistake of terminating the methods with a semicolon. The correct token in the type specification is a comma.

Creating a subtype

I made catalog_item_t impossible to instantiate, so now would be a good time to show how to create a subtype for book objects. In the real world, a book is a type of catalog item; this is also true in my example, in which all instances of this book_t subtype will have four attributes:

id

Inherited from the base catalog_item_t type

title

Also inherited from the base type

isbn

Corresponds to the book’s assigned ISBN, if any

pages

An integer giving the number of pages in the book

In code, I can make the equivalent statement as follows:

 1   CREATE OR REPLACE TYPE book_t UNDER catalog_item_t (
 2      isbn VARCHAR2(13),
 3      pages INTEGER,
 4
 5      CONSTRUCTOR FUNCTION book_t (id IN INTEGER DEFAULT NULL,
 6         title IN VARCHAR2 DEFAULT NULL,
 7         isbn IN VARCHAR2 DEFAULT NULL,
 8         pages IN INTEGER DEFAULT NULL)
 9         RETURN SELF AS RESULT,
10
11       OVERRIDING MEMBER FUNCTION ck_digit_okay
12          RETURN BOOLEAN,
13
14       OVERRIDING MEMBER FUNCTION print
15          RETURN VARCHAR2
16    );

The interesting portions of this code are as follows:

Line 1

You can see that the syntax for indicating a subtype is the keyword UNDER in line 1, which makes a certain amount of intuitive sense. Oracle doesn’t use the phrase AS OBJECT here because it would be redundant; the only thing that can exist “under” an object type, is another object type.

Lines 2-3

I need to list only those attributes that are unique to the subtype; those in the parent type are implicitly included. Oracle orders the attributes with the base type first, then the subtype, in the same order as defined in the specification.

Lines 5-15

Here are the method declarations. We’ll look at these methods more closely in the next section.

Methods

I’ve used two kinds of methods in the previous type definition:

Constructor method

A function that accepts values for each attribute and assembles them into a typed object. Declared in lines 5-9 of the example.

Member method

A function or procedure that executes in the context of an object instance—that is, it has access to the current values of each of the attributes. Declared in lines 11-12, as well as in lines 14-15 of the example.

My example shows a user-defined constructor, a feature that was introduced in Oracle9i Database Release 2. Earlier versions provided only a system-defined constructor. Creating your own constructor for each type gives you precise control over what happens at instantiation. That control can be very useful for doing extra tasks like validation and introducing controlled side effects. In addition, you can use several overloaded versions of a user-defined constructor , allowing it to adapt to a variety of calling circumstances.

To see some types and methods in action, take a look at this anonymous block:

 1   DECLARE
 2      generic_item catalog_item_t;
 3      abook book_t;
 4   BEGIN
 5      abook := NEW book_t(title => 'Out of the Silent Planet',
 6         isbn => '0-6848-238-02'),
 7      generic_item := abook;
 8      DBMS_OUTPUT.PUT_LINE('BOOK: ' || abook.print());
 9      DBMS_OUTPUT.PUT_LINE('ITEM: ' || generic_item.print());
10    END;

Interestingly, the objects’ print invocations (lines 8 and 9) yield identical results for both abook and generic_item. :

    BOOK: id=; title=Out of the Silent Planet; isbn=0-6848-238-02; pages=
    ITEM: id=; title=Out of the Silent Planet; isbn=0-6848-238-02; pages=

Let’s walk through the code:

Lines 5-6

The constructor assembles a new object and puts it into a book. My example takes advantage of PL/SQL’s named notation. It supplied values for only two of the four attributes, but the constructor creates the object anyway, which is what I asked it to do.

The syntax to use any constructor follows the pattern:

    [ NEW ]typename ( arg1, arg2, ... );

The NEW keyword, introduced in the Oracle9i Database Release 2, is optional, but is nevertheless useful as a visual cue that the statement will create a new object.

Line 7

Even though a catalog item is not instantiable, I can assign to it an instance of a subtype, and it will even hold all the attributes that are unique to the subtype. This demonstrates one nifty aspect of “substitutability” that Oracle supports in PL/SQL, which is that by default, an object variable may hold an instance of any of its subtypes. Note to programmers of other languages: The assignment in line 7 is not simply creating a second reference to one object; instead, it’s making a complete copy.

In English, it certainly makes sense to regard a book as a catalog item. In computerese, it’s a case of widening or upcasting the generic item by adding attributes from a more specific subtype. The converse operation, narrowing, is trickier but nevertheless possible, as you’ll see later.

Lines 8-9

Notice that the calls to print() use the graceful object-style invocation:

object.methodname( arg1, arg2, ...)

because it is a member method executing on an already declared and instantiated object. Which version of the print method executes for objects of different types? The one in the most specific subtype associated with the currently instantiated object. The selection of the method gets deferred until runtime, in a feature known as dynamic method dispatch . This can be very handy, although it may incur a performance cost.

Let’s turn now to the body of the book_t method, so we can better understand the result we’ve just seen. The implementation holds two important new concepts, which I’ll describe afterwards.

 1   CREATE OR REPLACE TYPE BODY book_t
 2   AS
 3      CONSTRUCTOR FUNCTION book_t (id IN INTEGER,
 4         title IN VARCHAR2,
 5         isbn IN VARCHAR2,
 6         pages IN INTEGER)
 7         RETURN SELF AS RESULT
 8      IS
 9      BEGIN
10          SELF.id := id;
11          SELF.title := title;
12          SELF.isbn := isbn;
13          SELF.pages := pages;
14          IF isbn IS NULL OR SELF.ck_digit_okay
15          THEN
16             RETURN;
17          ELSE
18             RAISE_APPLICATION_ERROR(-20000, 'ISBN ' || isbn
19                || ' has bad check digit'),
20          END IF;
21       END;
22
23       OVERRIDING MEMBER FUNCTION ck_digit_okay
24          RETURN BOOLEAN
25       IS
26          subtotal PLS_INTEGER := 0;
27          isbn_digits VARCHAR2(10);
28       BEGIN
29          /* remove dashes and spaces */
30          isbn_digits := REPLACE(REPLACE(SELF.isbn, '-'), ' '),
31          IF LENGTH(isbn_digits) != 10
32          THEN
33             RETURN FALSE;
34          END IF;
35
36          FOR nth_digit IN 1..9
37          LOOP
38             subtotal := subtotal +
39                (11 - nth_digit) * TO_NUMBER(SUBSTR(isbn_digits, nth_digit, 1));
40          END LOOP;
41
42          /* check digit can be 'X' which has value of 10 */
43          IF UPPER(SUBSTR(isbn_digits, 10, 1)) = 'X'
44          THEN
45             subtotal := subtotal + 10;
46          ELSE
47             subtotal := subtotal + TO_NUMBER(SUBSTR(isbn_digits, 10, 1));
48          END IF;
49
50          RETURN MOD(subtotal, 11) = 0;
51
52       EXCEPTION
53          WHEN OTHERS
54          THEN
55             RETURN FALSE;
56       END;
57
58       OVERRIDING MEMBER FUNCTION print
59          RETURN VARCHAR2
60       IS
61       BEGIN
62         RETURN 'id=' || id || '; title=' || title
63             || '; isbn=' || isbn || '; pages=' || pages;
64       END;
65    END;
Lines 3-21

A user-defined constructor has several rules to follow:

  1. It must be declared with keywords CONSTRUCTOR FUNCTION (line 3).

  2. The return clause must be RETURN SELF AS RESULT (line 7).

  3. It assigns values to any of the current object’s attributes (lines 10-13).

  4. It ends with a bare RETURN statement or an exception (line 16; lines 18-19).

A constructor would typically assign values to as many of the attributes as it knows about. As you can see from line 14, my constructor tests the check digit before completing the construction. You will notice, if you skip ahead to line 30, that object attributes (such as SELF.isbn) are accessible even before validation is complete, an interesting and useful feature.

Lines 16-17 are merely a placeholder; you should definitely take a more comprehensive approach to application-specific exceptions, as discussed in Chapter 6 in “Use Standardized Error Management Programs.”

Next I’d like to discuss the use of the SELF keywords that appears throughout the type body, which is akin to Java’s this keyword. Translation for non-Java programmers: SELF is merely a way to refer to the invoking (current) object when writing implementations of member methods. You can use SELF by itself when referring to the entire object, or you can use dot notation to refer to an attribute or a method:

    IF SELF.id ...

    IF SELF.ck_digit_okay() ...

The SELF keyword is not always required inside a member method, as you can see in lines 62-63, because the current object’s attribute identifiers are always in scope. Using SELF can provide attribute visibility (as in lines 10-13, where the PL/SQL compiler interprets those unqualified identifiers as the formal parameters) and help to make your code SELF-documenting. (Ugh, sorry about that.)

There are a few more rules I’d like to mention about this keyword:

  • SELF isn’t available inside static method bodies because static methods have no “current object.” (I’ll define static methods later in this section.)

  • By default, SELF is an IN variable in functions and an IN OUT variable in procedures and constructor functions.

  • You can change the default mode by including SELF as the first formal parameter.

Lines 23-56 of the previous example show the computing of the check digit, which is kind of fun, but my algorithm doesn’t really exploit any new object-oriented features. I will digress to mention that the exception handler is quite important here; it responds to a multitude of problems such as the TO_NUMBER function encountering a character instead of a digit.

Next, on to creating a subtype for serials:

    CREATE OR REPLACE TYPE serial_t UNDER catalog_item_t (
       issn VARCHAR2(10),
       open_or_closed VARCHAR2(1),

       CONSTRUCTOR FUNCTION serial_t (id IN INTEGER DEFAULT NULL,
          title IN VARCHAR2 DEFAULT NULL,
          issn IN VARCHAR2 DEFAULT NULL,
          open_or_closed IN VARCHAR2 DEFAULT NULL)
          RETURN SELF AS RESULT,

       OVERRIDING MEMBER FUNCTION ck_digit_okay
          RETURN BOOLEAN,

       OVERRIDING MEMBER FUNCTION print
          RETURN VARCHAR2
    ) NOT FINAL;

Again, no new features appear in this type specification, but it does give another example of subtyping. A serial item in this model will have its own constructor, its own version of validating the check digit, and its own way to print itself.[*]

In addition to constructor and member methods, Oracle supports two other categories of methods:

Static method

A function or procedure invoked independently of any instantiated objects. Static methods behave a lot like conventional PL/SQL procedures or functions. See the upcoming sidebar, "Static Method as Pseudo-Constructor,” for an example.

Comparison method

That is, a map or order method. These are special member methods that let you program what Oracle should do when it needs to compare two objects of this datatype—for example, in an equality test in PL/SQL or when sorting objects in SQL.

One final point before moving on. Objects follow PL/SQL’s general convention that uninitialized variables are null;[*] the precise term for objects is atomically null.

As with collections, when an object is null, you cannot simply assign values to its attributes. Take a look at this short example:

    DECLARE
       mybook book_t;      -- declared, but not initialized
    BEGIN
       IF mybook IS NULL   -- this will be TRUE; it is atomically null
       THEN
          mybook.title := 'Learning Oracle PL/SQL'; -- this line raises...
       END IF;
    EXCEPTION
       WHEN ACCESS_INTO_NULL    -- ...this predefined exception
       THEN
          ...
    END;

Before assigning values to the attributes, you must initialize (instantiate) the entire object in one of three ways: by using a constructor method, via direct assignment from another object, or via a fetch from the database, as the next section discusses.

Storing, Retrieving, and Using Persistent Objects

Thus far, I’ve only been discussing the definition of the datatypes and the instantiation of objects in the memory of running programs. Fortunately, that’s not even half the story! Oracle wouldn’t be Oracle if there were no way to store an object in the database.

There are at least two main ways that I could physically store the library catalog as modeled thus far: either as one big table of catalog objects or as a series of smaller tables, one for each subtype. I’ll show the former arrangement, which could begin as follows:

    CREATE TABLE catalog_items OF catalog_item_t
       (CONSTRAINT catalog_items_pk PRIMARY KEY (id));

This statement tells Oracle to build an object table called catalog_items, each row of which will be a row object of type catalog_item_t. An object table generally has one column per attribute:

    SQL >DESC catalog_items
     Name                                 Null?    Type
     ------------------------------------ -------- -------------------------
     ID                                   NOT NULL NUMBER(38)
     TITLE                                         VARCHAR2(4000)

Remember, though, that catalog_item_t isn’t instantiable, and each row in the table will actually be of a subtype such as a book or serial item. So where do the extra attributes go? Consider that these are legal statements:[*]

    INSERT INTO catalog_items
       VALUES (NEW book_t(10003, 'Perelandra', '0-684-82382-9', 222));
    INSERT INTO catalog_items
       VALUES (NEW serial_t(10004, 'Time', '0040-781X', 'O'));

In fact, Oracle put the ISBN, ISSN, etc., into hidden columns on the catalog_items table. From an object programming point of view, that’s pretty cool because it helps

preserve the abstraction of the catalog item, yet provides a way to expose the additional subtype information when needed.

One more thing about the catalog_items table: the CONSTRAINT clause above designates the id column as the primary key. Yes, object tables can have primary keys too. And, if you exclude such a CONSTRAINT clause, Oracle will instead create a system-generated object identifier (OID), as described next.

Object identity

If you’re a relational database programmer, you know that conventional tables have a unique identifier for every row. If you’re an object-oriented programmer, you know that OOP environments generally assign unique arbitrary identifiers that serve as object handles. If you’re a programmer using object-relational features of Oracle, you have a mix of both approaches. The following table summarizes where you will find object identifiers:

What and where

Has object identifier?

Row object in object table

Yes

Column object in any table (or fetched into PL/SQL program)

No; use row’s primary key instead

Transient object created in PL/SQL program

No; use entire object instead

Row object fetched from object table into PL/SQL program

Yes, but available in program only if you explicitly fetch the “REF” ( See the later section "Using REFs“)

Here is an example of a table that can hold column objects:

    CREATE TABLE my_writing_projects (
       project_id INTEGER NOT NULL PRIMARY KEY,
       start_date DATE,
       working_title VARCHAR2(4000),catalog_item catalog_item_t  -- this is a "column object"
    );

Oracle Corporation takes the view that a column object is dependent on the row’s primary key, and should not be independently identified.[*]

For any object table, Oracle can base its object identifier on one of two things:

The primary key value

To use this feature, use the clause OBJECT IDENTIFIER IS PRIMARY KEY at the end of the CREATE TABLE statement.

A system-generated value

If you omit the PRIMARY KEY clause, Oracle adds a hidden column named SYS_NC_OID$ to the table and populates it with a unique 16-byte RAW value for each row.

Which kind of OID should you use? Primary-key-based OIDs typically use less storage than system-generated OIDs , provide a means of enforcing referential integrity, and allow for much more convenient loading of objects. System-generated OIDs have the advantage that REFs to them cause “SCOPED” or limited to values from only one table. For a more complete discussion of the pros and cons of these two approaches, check out Oracle’s document Application Developer’s Guide—Object-Relational Features. For now, you should know that a system-generated OID is:

Opaque

Although your programs can use the OID indirectly, you don’t typically see its value.

Potentially globally unique across databases

The OID space makes provisions for up to 2128 objects (definitely “many” by the reckoning of the Hottentots).[*] In theory, these OIDs could allow object navigation across distributed databases without embedding explicit database links.

Immutable

Immutable in this context means incapable of update. Even after export and import, the OID remains the same, unlike a ROWID. To “change” an OID, you would have to delete and recreate the object.

The VALUE function

To retrieve an object from the database, Oracle provides the VALUE function in SQL. VALUE accepts a single argument, which must be a table alias in the current FROM clause, and returns an object of the type on which the table is defined. It looks like this in a SELECT statement:

    SELECT VALUE(c)
      FROM catalog_items c;

I like short abbreviations as table aliases, which explains the c. The VALUE function returns an opaque series of bits to the calling program rather than a record of column values. SQL*Plus, however, has built-in features to interpret these bits, returning the following result from that query:

    VALUE(C)(ID, TITLE)
    -------------------------------------------------
    BOOK_T(10003, 'Perelandra', '0-684-82382-9', 222)
    SERIAL_T(10004, 'Time', '0040-781X', 'O')

PL/SQL also has features to deal with fetching objects. Start with a properly typed local variable named catalog_item:

    DECLARE
       catalog_item catalog_item_t;
       CURSOR ccur IS
          SELECT VALUE(c)
            FROM catalog_items c;
    BEGIN
       OPEN ccur;
       FETCH ccur INTO catalog_item;
       DBMS_OUTPUT.PUT_LINE('I fetched item #' || catalog_item.id);
       CLOSE ccur;
    END;

The argument to PUT_LINE uses variable.attribute notation to yield the attribute value, resulting in the output:

    I fetched item #10003

The fetch assigns the object to the local variable catalog_item, which is of the base type; this makes sense because I don’t know in advance which subtype I’ll be retrieving. My fetch simply assigns the object into the variable.

In addition to substitutability, the example also illustrates (by displaying catalog_item.id) that I have direct access to the base type’s attributes.

In case you’re wondering, normal cursor attribute tricks work too; the previous anonymous block is equivalent to:

    DECLARE
       CURSOR ccur IS
          SELECT VALUE(c) obj
            FROM catalog_items c;
       arec ccur%ROWTYPE;
    BEGIN
       OPEN ccur;
       FETCH ccur INTO arec;
       DBMS_OUTPUT.PUT_LINE('I fetched item #' || arec.obj.id);
       CLOSE ccur;
    END;

If I just wanted to print out all of the object’s attributes, I could, of course, use the print method I’ve already defined. It’s legal to use this because it has been defined at the root type level and implemented in the subtypes; at runtime, Oracle will find the appropriate overriding implementations in each subtype. Ah, the beauty of dynamic method dispatch.

As a matter of fact, the VALUE function supports dot notation, which provides access to attributes and methods—but only those specified on the base type. For example, the following:

    SELECT VALUE(c).id, VALUE(c).print()
      FROM catalog_items c;

yields:

    VALUE(C).ID VALUE(C).PRINT()
    ----------- ----------------------------------------------------------
          10003 id=10003; title=Perelandra; isbn=0-684-82382-9; pages=222
          10004 id=10004; title=Time; issn=0040-781X; open_or_closed=Open

If I happen to be working in a client environment that doesn’t understand Oracle objects, I might want to take advantage of such features.

But what if I want to read only the attribute(s) unique to a particular subtype? I might first try something like this:

    SELECT VALUE(c).issn   /* Error; subtype attributes are inaccessible */
      FROM catalog_items c;

This gives me ORA-00904: invalid column name. Oracle is telling me that an object of the parent type provides no direct access to subtype attributes. I might try declaring book of book_t and assigning the subtyped object to it, hoping that it will expose the “hidden” attributes:

    book := catalog_item;  /* Error; Oracle won't do implied downcasts */

This time I get PLS-00382: expression is of wrong type. What’s going on? The non-intuitive answer to that mystery appears in the next section.

Before we move on, here are a few final notes about performing DML on object relational tables:

  • For object tables built on object types that lack subtypes, it is possible to select, insert, update, and delete all column values using conventional SQL statements. In this way, some object-oriented and relational programs can share the same underlying data.

  • You cannot perform conventional relational DML on hidden columns that exist as a result of subtype-dependent attributes. You must use an “object DML” approach.

  • To update an entire persistent object from a PL/SQL program, you can use an object DML statement such as:

        UPDATE catalog_items c SET c =object_variable WHERE ...

    This updates all the attributes (columns), including those unique to a subtype.

  • The only good way I have found to update a specific column that is unique to a subtype is to update the entire object. For example, to change the page count to 1,000 for the book with id 10007:

        UPDATE catalog_items c
           SET c = NEW book_t(c.id, c.title, c.publication_date, c.subject_refs,
                             (SELECT TREAT 
    (VALUE(y) AS book_t).isbn
                                FROM catalog_items y
                               WHERE id = 10007),
                          1000)
         WHERE id = 10007;

Now let’s go back and take a look at that last problem I mentioned.

The TREAT function

If I’m dealing with a PL/SQL variable typed as a supertype, and it’s populated with a value of one of its subtypes, how can I gain access to the subtype-specific attributes and methods? In my case, I want to treat a generic catalog item as the more narrowly defined book. This operation is called narrowing or downcasting, and is something the compiler can’t, or won’t, do automatically. What I need to use is the special Oracle function called TREAT:

    DECLARE
       book book_t;
       catalog_item catalog_item_t := NEW book_t();
    BEGIN
       book := TREAT(catalog_item AS book_t);   /* Using 9i R2 or later */
    END;
    /

or, in SQL (Oracle9i Database Release 1 PL/SQL doesn’t directly support TREAT):

    DECLARE
       book book_t;
       catalog_item catalog_item_t := book_t(NULL, NULL, NULL, NULL);
    BEGIN
       SELECTTREAT (catalog_item AS book_t)
         INTO book
         FROM DUAL;
    END;

The general syntax of the TREAT function is:

    TREAT (object_instance AS subtype) [ . { attribute | method( args...) } ]

where object_instance is any object with subtype as the name of one of its subtypes. Calls to TREAT won’t compile if you attempt to treat one type as another from a different type hierarchy. One notable feature of TREAT is that if you have supplied an object from the correct type hierarchy, it will return either the downcasted object or NULL—but not an error.

As with VALUE, you can use dot notation with TREAT to specify an attribute or method of the TREATed object. For example:

    DBMS_OUTPUT.PUT_LINE(TREAT (VALUE(c) AS serial_t).issn);

If I want to iterate over all the objects in the table in a type-aware fashion, I can do something like this:

    DECLARE
       CURSOR ccur IS
          SELECT VALUE(c) item FROM catalog_items c;
       arec ccur%ROWTYPE;
    BEGIN
       FOR arec IN ccur
       LOOP
          CASE
             WHEN arec.item IS OF (book_t)
             THEN
                DBMS_OUTPUT.PUT_LINE('Found a book with ISBN '
                   || TREAT(arec.item AS book_t).isbn);
             WHEN arec.item IS OF (serial_t)
             THEN
                DBMS_OUTPUT.PUT_LINE('Found a serial with ISSN '
                   || TREAT(arec.item AS serial_t).issn);
             ELSE
                DBMS_OUTPUT.PUT_LINE('Found unknown catalog item'),
          END CASE;
       END LOOP;
    END;

This block introduces the IS OF predicate to test an object’s type. Although the syntax is somewhat exciting:

object IS OF ( [ ONLY ] typename )

the IS OF operator is much more limited than one would hope: it works only on object types, not on any of Oracle’s core datatypes like NUMBER or DATE. Also, it will return an error if the object is not in the same type hierarchy as typename.

Notice the ONLY keyword . The default behavior—without ONLY—is to return TRUE if the object is of the given type or any of its subtypes. If you use ONLY, the expression won’t check the subtypes and returns TRUE only if the type is an exact match.

Warning

Syntactically, you must always use the output from any TREAT expression as a function, even if you just want to call TREAT to invoke a member procedure. For example, you’d expect that if there were a set_isbn member procedure in the book_t, you could do this:

    TREAT(item AS book_t).set_isbn('0140714154'), --wrong

But that gives the curious compiler error PLS-00363: expression ‘SYS_TREAT’ cannot be used as an assignment target.

Instead, you need to store the item in a temporary variable, and then invoke the member procedure:

    book := TREAT(item AS book_t);
    book.set_isbn('0140714154'),

The IS OF predicate, like TREAT itself, became available in Oracle9i Database Release 1 SQL, although direct support for it in PL/SQL didn’t appear until Oracle9i Database Release 2. As a Release 1 workaround, I could define one or more additional methods in the type tree, taking advantage of dynamic method dispatch to perform the desired operation at the correct level in the hierarchy. The “correct” solution to the narrowing problem depends not just on the version number, though, but also on what my application is supposed to accomplish.

For the moment, I’d like to move on to another interesting area: exploring the features Oracle offers when (not if!) you have to deal with changes in application design.

Evolution and Creation

The Oracle9i Database and Oracle Database 10g are light years beyond the Oracle8i Database in the area known as type evolution . That is, the later version lets you make a variety of changes to object types, even if you have created tables full of objects that depend on the type. Yippee!

Earlier in this chapter, I did a quick-and-dirty job of defining catalog_item_t. As almost any friendly librarian would point out, it might also be nice to carry publication date information[*] about all the holdings in the library. So I just hack out the following (no doubt while my DBA cringes):

    ALTER TYPE catalog_item_t
       ADD ATTRIBUTE publication_date VARCHAR2(400)
       CASCADE INCLUDING TABLE DATA;

Et voilà! Oracle propagates this change to perform the needed physical alterations in the corresponding table(s). It appends the attribute to the bottom of the supertype’s attributes and adds a column after the last column of the supertype in the corresponding object table. A DESCRIBE of the type now looks like this:

    SQL>DESC catalog_item_t
     catalog_item_t is NOT FINAL
     catalog_item_t is NOT INSTANTIABLE
     Name                                      Null?    Type
     ----------------------------------------- -------- ----------------------------
     ID                                                 NUMBER(38)
     TITLE                                              VARCHAR2(4000)
     PUBLICATION_DATE                                   VARCHAR2(400)

    METHOD
    ------
     MEMBER FUNCTION CK_DIGIT_OKAY RETURNS BOOLEAN
     CK_DIGIT_OKAY IS NOT INSTANTIABLE

    METHOD
    ------
     MEMBER FUNCTION PRINT RETURNS VARCHAR2

And a DESCRIBE of the table now looks like this:

    SQL>DESC catalog_items
     Name                                      Null?    Type
     ----------------------------------------- -------- ----------------------------
     ID                                        NOT NULL NUMBER(38)
     TITLE                                              VARCHAR2(4000)
     PUBLICATION_DATE                                   VARCHAR2(400)

In fact, the ALTER TYPE statement fixes nearly everything—though alas, it isn’t smart enough to rewrite my methods. My constructors are a particular issue because I need to alter their signature. Hey, no problem! I can change a method signature by dropping and then recreating the method.

Tip

When evolving object types , you may encounter the message ORA-22337: the type of accessed object has been evolved. This condition may prevent you from doing a DESCRIBE on the type. You might think that recompiling it will fix the problem, but it won’t. Moreover, if you have hard dependencies on the type, Oracle won’t let you recompile the object type specification. To get rid of this error, disconnect and then reconnect your Oracle session. This clears various buffers and enables DESCRIBE to see the new version.

To drop the method from the book type specification, specify:

    ALTER TYPE book_t
       DROP CONSTRUCTOR FUNCTION book_t (id INTEGER DEFAULT NULL,
          title VARCHAR2 DEFAULT NULL,
          isbn VARCHAR2 DEFAULT NULL,
          pages INTEGER DEFAULT NULL)
          RETURN SELF AS RESULT
       CASCADE;

Notice that I supply the full function specification. That will guarantee that I’m dropping the correct method because multiple overloaded versions of it might exist. (Strictly speaking, though, the DEFAULTs are not required, but I left them in because I’m usually just cutting and pasting this stuff.)

The corresponding add-method operation is easy:

    ALTER TYPE book_t
       ADD CONSTRUCTOR FUNCTION book_t (id INTEGER DEFAULT NULL,
          title VARCHAR2 DEFAULT NULL,
          publication_date VARCHAR2 DEFAULT NULL,
          isbn VARCHAR2 DEFAULT NULL,
          pages INTEGER DEFAULT NULL)
          RETURN SELF AS RESULT
       CASCADE;

Easy for me, anyway; Oracle is doing a lot more behind the scenes than I will probably ever know.

The next steps (not illustrated in this chapter) would be to alter the serial_t type in a similar fashion and then rebuild the two corresponding object type bodies with the CREATE OR REPLACE TYPE BODY statement . I would also want to inspect all the methods to see whether any changes would make sense elsewhere (for example, it would be a good idea to include the publication date in the print method).

By the way, you can drop a type using the statement:

    DROP TYPEtypename [ FORCE ];

Use the FORCE option with care because it cannot be undone. Any object types or object tables that depend on a force-dropped type will be rendered permanently useless. If there are any columns defined on a force-dropped type, Oracle marks them as UNUSED and makes them inaccessible. If your type is a subtype, and you have used the supertype in any table definitions, you might benefit from this form of the statement:

    DROP TYPEsubtypename VALIDATE;

VALIDATE causes Oracle to look through the table and drop the type as long as there are no instances of the subtype, avoiding the disastrous consequences of the FORCE option.

Now let’s visit the strange and fascinating world of object referencing .

Back to Pointers?

The object-relational features in Oracle include the ability to store an object reference or REF value. A REF is a logical pointer to a particular row in an object table. Oracle stores inside each reference the following information:

  • The target row’s primary key or system-generated object identifier

  • A unique identifier to designate the table

  • At the programmer’s option, a hint on the row’s physical whereabouts on disk, in the form of its ROWID

The literal contents of a REF are not terribly useful unless you happen to like looking at long hex strings:

    SQL>SELECT REF(c) FROM catalog_items c WHERE ROWNUM = 1;

    REF(C)
    --------------------------------------------------------------------------------------
    00002802099FC431FBE5F20599E0340003BA0F1F139FC431FBE5F10599E0340003BA0F1F130240000C000
    0

However, your queries and programs can use a REF to retrieve a row object without having to name the table where the object resides. Huh? Queries without table names? A pointer in a relational database? Let’s take a look at how this feature might work in our library catalog.

Using REFs

Libraries classify their holdings within a strictly controlled set of subjects. For example, the Library of Congress might classify the book you’re reading now in the following three subjects:

  • Oracle (Computer file)

  • PL/SQL (Computer program language)

  • Relational databases

The Library of Congress uses a hierarchical subject tree: “Computer file” is the broader subject or parent of “Oracle,” and “Computer program language” is the broader subject for “PL/SQL.”

When classifying things, any number of subjects may apply to a particular catalog item in a many-to-many (M:M) relationship between subjects and holdings. In my simple library catalog, I will make one long list (table) of all available subjects. While a relational approach to the problem would then establish an “intersection entity” to resolve the M:M relationship, I have other options out here in object-relational land.

I will start with an object type for each subject:

    CREATE TYPE subject_t AS OBJECT (
       name VARCHAR2(2000),
       broader_term_ref REF subject_t
    );

Each subject has a name and a broader term. However, I’m not going to store the term itself as a second attribute, but instead a reference to it. The third line of this type definition shows that I’ve typed the broader_term_ref attribute as a REF to a same-typed object. It’s kind of like Oracle’s old EMP table, with a MGR column whose value identifies the manager’s record in the same table.

I now create a table of subjects:

    CREATE TABLE subjects OF subject_t
      (CONSTRAINT subject_pk PRIMARY KEY (name),
       CONSTRAINT subject_self_ref FOREIGN KEY (broader_term_ref)
          REFERENCES subjects);

The foreign key begs a bit of explanation. Even though it references a table with a relational primary key, because the foreign key datatype is a REF, Oracle knows to use the table’s object identifier instead. This support for the REF-based foreign key constraint is a good example of Oracle’s bridge between the object and relational worlds.

Here are a few unsurprising inserts into this table (just using the default constructor):

    INSERT INTO subjects VALUES (subject_t('Computer file', NULL));
    INSERT INTO subjects VALUES (subject_t('Computer program language', NULL));
    INSERT INTO subjects VALUES (subject_t('Relational databases', NULL));
    INSERT INTO subjects VALUES (subject_t('Oracle',
       (SELECT REF(s) FROM subjects s WHERE name = 'Computer file')));
    INSERT INTO subjects VALUES (subject_t('PL/SQL',
       (SELECT REF(s) FROM subjects s WHERE name = 'Computer program language')));

For what it’s worth, you can list the contents of the subjects table, as shown here:

    SQL>SELECT VALUE(s) FROM subjects s;

    VALUE(S)(NAME, BROADER_TERM_REF)
    ---------------------------------------------------------------------------------
    SUBJECT_T('Computer file', NULL)
    SUBJECT_T('Computer program language', NULL)
    SUBJECT_T('Oracle', 00002202089FC431FBE6FB0599E0340003BA0F1F139FC431FBE6690599E03
    40003BA0F1F13)

    SUBJECT_T('PL/SQL', 00002202089FC431FBE6FC0599E0340003BA0F1F139FC431FBE6690599E03
    40003BA0F1F13)

    SUBJECT_T('Relational databases', NULL)

Even if that’s interesting, it’s not terribly useful. However, what’s both interesting and useful is that I can easily have Oracle automatically “resolve” or follow those pointers. For example, I can use the DEREF function to navigate those ugly REFs back to their target row in the table:

    SELECT s.name, DEREF(s.broader_term_ref).name bt
      FROM subjects s;

Dereferencing is like an automatic join, although it’s more of an outer join than an equi-join. In other words, if the reference is null or invalid, the driving row will still appear, but the target object (and column) will be null.

Oracle introduced a dereferencing shortcut that is really quite elegant. You only need to use dot notation to indicate what attribute you wish to retrieve from the target object:

    SELECT s.name, s.broader_term_ref.name bt FROM subjects s;

Both queries produce the following output:

    NAME                           BT
    ------------------------------ ------------------------------
    Computer file
    Computer program language
    Oracle                         Computer file
    PL/SQL                         Computer program language
    Relational databases

As a point of syntax, notice that both forms require a table alias, as in the following:

    SELECT
                        table_alias
                     
                     .ref_column_name.column_name
       FROM tablename 
                     
                        table_alias
                     

You can also use REF-based navigation in the WHERE clause. To show all the subjects whose broader term is “Computer program language,” specify:

    SELECT VALUE(s).name FROM subjects s
     WHERE s.broader_term_ref.name = 'Computer program language';

Although my example table uses a reference to itself, in reality a reference can point to an object in any object table in the same database. To see this in action, let’s return to the definition of the base type catalog_item_t. I can now add an attribute that will hold a collection of REFs, so that each cataloged item can be associated with any number of subjects. First, I’ll create a collection of subject references:

    CREATE TYPE subject_refs_t AS TABLE OF REF subject_t;

Now I’ll allow every item in the catalog to be associated with any number of subjects:

    ALTER TYPE catalog_item_t
       ADD ATTRIBUTE subject_refs subject_refs_t
       CASCADE INCLUDING TABLE DATA;

And now (skipping gleefully over the boring parts about modifying any affected methods in the dependent types), I might insert a catalog record using the following exotic SQL statement:

    INSERT INTO catalog_items
    VALUES (NEW book_t(10007,
       'Oracle PL/SQL Programming',
       'Sept 1997',
        CAST(MULTISET(SELECT REF(s)
                        FROM subjects s
                       WHERE name IN ('Oracle', 'PL/SQL', 'Relational databases'))
          AS subject_refs_t),
       '1-56592-335-9',
       987));

The CAST/MULTISET clause performs an on-the-fly conversion of the subject REFs into a collection, as explained in “Working with Collections” in Chapter 12.

Here is a slightly more understandable PL/SQL equivalent:

    DECLARE
       subrefs subject_refs_t;
    BEGIN
       SELECT REF(s)
         BULK COLLECT INTO subrefs
         FROM subjects s
        WHERE name IN ('Oracle', 'PL/SQL', 'Relational databases'),

       INSERT INTO catalog_items VALUES (NEW book_t(10007,
          'Oracle PL/SQL Programming', 'Sept 1997', subrefs, '1-56592-335-9', 987));
    END;

In English, that code says “grab the REFs to three particular subjects, and store them with this particular book.”

REF-based navigation is so cool that I’ll show another example using some more of that long-haired SQL:

    SELECT VALUE(s).name
      || ' (' || VALUE(s).broader_term_ref.name || ')' plsql_subjects
      FROM TABLE(SELECT subject_refs
                   FROM catalog_items
                  WHERE id=10007) s;

This example retrieves values from the subjects table, including the name of each broader subject term, without ever mentioning the subjects table by name. (The TABLE function converts a collection into a virtual table.) Here are the results:

    PLSQL_SUBJECTS
    ------------------------------------
    Relational databases ()
    PL/SQL (Computer program language)
    Oracle (Computer file)

Other than automatic navigation from SQL, what else does all this effort offer the PL/SQL programmer? Er, well, not a whole lot. References have a slight edge, at least because as theory goes, they are strongly typed—that is, a REF-typed column can point only to an object that is defined on the same object type as the REF. Contrast this behavior with conventional foreign keys, which can point to any old thing as long as the target is constrained to be a primary key or has a unique index on it.

The UTL_REF package

The UTL_REF built-in package performs the dereferencing operation without an explicit SQL call, allowing your application to perform a programmatic lock, select, update, or delete of an object given only its REF. As a short example, I can add a method such as the following to the subject_t type:

    MEMBER FUNCTION print_bt (str IN VARCHAR2)
       RETURN VARCHAR2
    IS
       bt subject_t;
    BEGIN
       IF SELF.broader_term_ref IS NULL
       THEN
          RETURN str;
      ELSE
          UTL_REF.SELECT_OBJECT(SELF.broader_term_ref, bt);
          RETURN bt.print_bt(NVL(str,SELF.name)) || ' (' || bt.name || ')';
       END IF;
    END;

This recursive procedure walks the hierarchy from the current subject to the “topmost” broader subject.

When using the procedures in UTL_REF, the REF argument you supply must be typed to match your object argument. The complete list of subprograms in UTL_REF follows:

UTL_REF.SELECT_OBJECT ( obj_ref IN, object_variable OUT);

Finds the object to which obj_ref points and retrieves a copy in object_variable.

UTL_REF.SELECT_OBJECT_WITH_CR ( obj_ref IN, object_variable OUT);

Like SELECT_OBJECT, but makes a copy (“snapshot”) of the object. This version exists to avoid a mutating table error (ORA-4091), which can occur if you are updating an object table and setting the value to a function, but the function uses UTL_REF to dereference an object from the same table you’re updating.

UTL_REF.LOCK_OBJECT ( obj_ref IN);

Locks the object to which obj_ref points but does not fetch it yet.

UTL_REF.LOCK_OBJECT ( obj_ref IN, object_variable OUT);

Locks the object to which obj_ref points and retrieves a copy in object_variable.

UTL_REF.UPDATE_OBJECT ( obj_ref IN, object_variable IN);

Replaces the object to which obj_ref points with the value supplied in object_variable. This operation updates all of the columns in the corresponding object table.

UTL_REF.DELETE_OBJECT ( obj_ref IN);

Deletes the object to which obj_ref points.

REFs and type hierarchies

All of the UTL_REF subprograms are procedures, not functions,[*] and the parameters have the unique characteristic of being semiweakly typed. In other words, Oracle doesn’t need to know at compile time what the precise datatypes are, as long as the REF matches the object variable.

I’d like to mention a few more technical points about REFs when dealing with type hierarchies. Assume the following program declarations:

    DECLARE
       book book_t;
       item catalog_item_t;
       itemref REF catalog_item_t;
       bookref REF book_t;

As you have seen, assigning a REF to an “exactly typed” variable works fine:

    SELECT REF(c) INTO itemref
      FROM catalog_items c WHERE id = 10007;

Similarly, you can dereference an object into the exact type, using:

    UTL_REF.select_object(itemref, item);

or:

    SELECT DEREF(itemref) INTO item FROM DUAL;

However, you cannot directly narrow a REF:

    SELECT REF(c)
      INTO bookref    /* Error */
      FROM catalog_items c WHERE id = 10007;

One way to narrow a REF would be to use TREAT, which understands how to narrow references:

    SELECT TREAT(REF(c) AS REF book_t)
      INTO bookref
      FROM catalog_items c WHERE id = 10007;

You can always widen or upcast while dereferencing, whether you are using:

    UTL_REF.select_object(TREAT(bookref AS ref catalog_item_t), item);

(notice the explicit upcast) or:

    SELECT DEREF(bookref) INTO item FROM DUAL;

And, although you cannot narrow or downcast while dereferencing with DEREF, as shown here:

    SELECT DEREF(itemref)
      INTO book   /* Error */
      FROM DUAL;

TREAT can again come to the rescue:

    SELECT DEREF(TREAT(itemref AS REF book_t))
      INTO book
      FROM catalog_items c WHERE id = 10007;

Or, amazingly enough, you can also perform an implicit downcast with UTL_REF:

    UTL_REF.select_object(itemref, book);

Got all that?

Dangling REFs

Here are a few final comments about object references:

  • A REF may point to nothing, in which case it’s known as a dangling REF. This can happen when you store a reference to an object and then delete the object. Oracle permits such nonsense if you fail to define a foreign key constraint that would prevent it.

  • To locate references that point to nothing, use the IS DANGLING operator :

        SELECT VALUE(s) FROM subjects s
        WHERE broader_term_ref IS DANGLING;

Now let’s move on and take a look at some Oracle features for dealing with data whose type is either unknown or varying.

Generic Data: The ANY Types

As discussed in Chapter 13, Oracle provides the ANYDATA type , which can hold data in any other built-in or user-defined type. With ANYDATA, a PL/SQL program could, for instance, store, retrieve, and operate on a data item declared on any SQL type in the database—without having to create dozens of overloaded versions. Sounds pretty good, right? This feature was tailor-made for advanced queuing, where an application needs to put a “thing” in the queue, and you don’t want the queue to have to know what the datatype of each item is.

The built-in packages and types in this family are:

ANYDATA type

Encapsulation of any SQL-datatyped item in a self-descriptive data structure.

ANYTYPE type

When used with ANYDATA, reads the description of the data structure. Can be used separately to create transient object types.

DBMS_TYPES package

A package consisting only of constants that help interpret which datatype is being used in the ANYDATA object.

ANYDATASET type

Similar to an ANYDATA, but the contents are one or more instances of a datatype (like a collection).

Preview: What ANYDATA is not

If I wanted to write a function that would print anything (that is, convert it to a string), I might start with this spec:

    FUNCTION printany (whatever IN ANYDATA) RETURN VARCHAR2;

and hope to invoke the function like this:

    DBMS_OUTPUT.PUT_LINE(printany(SYSDATE));         -- nope
    DBMS_OUTPUT.PUT_LINE(printany(NEW book_t(111));  -- nada
    DBMS_OUTPUT.PUT_LINE(printany('Hello world'));   -- nyet

Unfortunately, those calls won’t work. ANYDATA is actually an encapsulation of other types, and you must first convert the data into the ANYDATA type using one of its built-in static methods:

    DBMS_OUTPUT.PUT_LINE(printany(ANYDATA.ConvertDate(SYSDATE));
    DBMS_OUTPUT.PUT_LINE(printany(ANYDATA.ConvertObject(NEW book_t(12345)));
    DBMS_OUTPUT.PUT_LINE(printany(ANYDATA.ConvertVarchar2('Hello world'));

Don’t think of ANYDATA as an exact replacement for overloading.

Dealing with an ANYDATA

Let’s take a look at an implementation of the printany program and see how it figures out how to deal with data of different types. This code is not comprehensive; it deals only with numbers, strings, dates, objects, and REFs, but you could extend it to almost any other datatype.

        /* File on web: printany.fun */
 1   CREATE OR REPLACE FUNCTION printany (adata IN ANYDATA)
 2      RETURN VARCHAR2
 3   AS
 4      aType ANYTYPE;
 5      retval VARCHAR2(32767);
 6      result_code PLS_INTEGER;
 7   BEGIN
 8      CASE adata.GetType(aType)
 9      WHEN DBMS_TYPES.TYPECODE_NUMBER THEN
10          RETURN 'NUMBER: ' || TO_CHAR(adata.AccessNumber);
11       WHEN DBMS_TYPES.TYPECODE_VARCHAR2 THEN
12          RETURN 'VARCHAR2: ' || adata.AccessVarchar2;
13       WHEN DBMS_TYPES.TYPECODE_CHAR THEN
14          RETURN 'CHAR: ' || RTRIM(adata.AccessChar);
15       WHEN DBMS_TYPES.TYPECODE_DATE THEN
16          RETURN 'DATE: ' || TO_CHAR(adata.AccessDate, 'YYYY-MM-DD hh24:mi:ss'),
17       WHEN DBMS_TYPES.TYPECODE_OBJECT THEN
18          EXECUTE IMMEDIATE 'DECLARE ' ||
19                            '   myobj ' || adata.GetTypeName || '; ' ||
20                            '   myad anydata := :ad; ' ||
21                            'BEGIN ' ||
22                            '   :res := myad.GetObject(myobj); ' ||
23                            '   :ret := myobj.print(); ' ||
24                            'END;'
25                            USING IN adata, OUT result_code, OUT retval;
26          retval := adata.GetTypeName || ': ' || retval;
27       WHEN DBMS_TYPES.TYPECODE_REF THEN
28          EXECUTE IMMEDIATE 'DECLARE ' ||
29                            '   myref ' || adata.GetTypeName || '; ' ||
30                            '   myobj ' || SUBSTR(adata.GetTypeName,
31                                           INSTR(adata.GetTypeName, ' ')) || '; ' ||
32                            '   myad anydata := :ad; ' ||
33                            'BEGIN ' ||
34                            '   :res := myad.GetREF(myref); ' ||
35                            '   UTL_REF.SELECT_OBJECT(myref, myobj);' ||
36                            '   :ret := myobj.print(); ' ||
37                           'END;'
38                            USING IN adata, OUT result_code, OUT retval;
39          retval := adata.GetTypeName || ': ' || retval;
40       ELSE
41          retval := '<data of type ' || adata.GetTypeName ||'>';
42       END CASE;
43
44       RETURN retval;
45
46    EXCEPTION
47       WHEN OTHERS
48       THEN
49          IF INSTR(SQLERRM, 'component ''PRINT'' must be declared') > 0
50          THEN
51             RETURN adata.GetTypeName || ': <no print() function>';
52          ELSE
53             RETURN 'Error: ' || SQLERRM;
54          END IF;
55    END;

Here are just a few highlights:

Line 5

In cases where I need a temporary variable to hold the result, I assume that 32K will be big enough. Remember that PL/SQL dynamically allocates memory for large VARCHAR2s, so it won’t be a memory pig unless required.

Line 6

The value of result_code (see lines 25 and 38) is irrelevant for the operations in this example, but is required by the ANYDATA API.

Line 8

The ANYDATA type includes a method called GetType that returns a code corresponding to the datatype. Here is its specification:

    MEMBER FUNCTION ANYDATA.GetType (OUT NOCOPY ANYTYPE) RETURNtypecode_integer;

To use this method, though, you have to declare an ANYTYPE variable into which Oracle will store detailed information about the type that you’ve encapsulated.

Lines 9, 11, 13, 15, 17, 27

These expressions rely on the constants that Oracle provides in the built-in package DBMS_TYPES.

Lines 10, 12, 14, 16

These statements use the ANYDATA.ConvertNNN member functions introduced in the Oracle9i Database Release 2. In Release 1, you can use the GetNNN member procedures for a similar result, although they require the use of a temporary local variable.

Lines 18-25

To get an object to print itself without doing a lot of data dictionary contortions, this little dynamic anonymous block will construct an object of the correct type and invoke its print() member method. You did give it a print(), didn’t you?

Lines 28-38

The point of this is to dereference the pointer and return the referenced object’s content. Well, it will work if there’s a print().

Lines 49-51

In the event that I’m trying to print an object with no print member method, the compiler will return an error at runtime that I can detect in this fashion. In this case the code will just punt and return a generic message.

Running my earlier invocations:

    DBMS_OUTPUT.PUT_LINE(printany(ANYDATA.ConvertDate(SYSDATE));
    DBMS_OUTPUT.PUT_LINE(printany(ANYDATA.ConvertObject(NEW book_t(12345)));
    DBMS_OUTPUT.PUT_LINE(printany(ANYDATA.ConvertVarchar2('Hello world'));

yields:

    DATE: 2005-03-10 16:00:25
    SCOTT.BOOK_T: id=12345; title=; publication_date=; isbn=; pages=
    VARCHAR2: Hello world

As you can see, using ANYDATA isn’t as convenient as true inheritance hierarchies because ANYDATA requires explicit conversions. On the other hand, it does make possible the creation of a table column or object attribute that will hold any type of data.[*]

Creating a transient type

Although PL/SQL still does not support defining new object types inside a program’s declaration section, it is possible to use these ANY built-ins to create this kind of “transient” type—that is, one that exists only at runtime. Wrapped up as an ANYTYPE, you can even pass such a type as a parameter and create an instance of it as an ANYDATA. Here is an example:

    /* Create (anonymous) transient type with two attributes: number, date */
    CREATE OR REPLACE FUNCTION create_a_type
       RETURN ANYTYPE
    AS
       mytype ANYTYPE;
    BEGIN
       ANYTYPE.BeginCreate(typecode => DBMS_TYPES.TYPECODE_OBJECT,
                           atype => mytype);

       mytype.AddAttr(typecode => DBMS_TYPES.TYPECODE_NUMBER,
                      aname => 'just_a_number',
                      prec => 38,
                      scale => 0,
                      len => NULL,
                      csid => NULL,
                      csfrm => NULL);

       mytype.AddAttr(typecode => DBMS_TYPES.TYPECODE_DATE,
                      aname => 'just_a_date',
                      prec => 5,
                      scale => 5,
                      len => NULL,
                      csid => NULL,
                      csfrm => NULL);

       mytype.EndCreate;
       RETURN mytype;
    END;
    /

As you can see, there are three main steps:

  1. Begin the creation by calling the static procedure BeginCreate. This returns an initialized ANYTYPE.

  2. One at a time, add the desired attributes using the AddAttr member procedure.

  3. Call the member procedure EndCreate.

Similarly, when you wish to use the type, you will need to assign attribute values in a piecewise manner:

    DECLARE
       ltype ANYTYPE := create_a_type;
       l_any ANYDATA;
    BEGIN
       ANYDATA.BeginCreate(dtype => ltype, adata => l_any);
       l_any.SetNumber(num => 12345);
       l_any.SetDate(dat => SYSDATE);
       l_any.EndCreate;
    END;

If you don’t know the structure of the datatype in advance, it is possible to discover it using ANYTYPE methods (such as GetAttrElemInfo) in combination with a piecewise application of the ANYDATA.Get methods. (See the anyObject.sql script on the book’s web site for an example.)

I Can Do It Myself

In object-oriented design, there is a school of thought that wants each object type to have the intelligence necessary to be self-sufficient. If the object needs to be stored persistently in a database, it would know how to save itself; similarly, it would include methods for update, delete, and retrieval. If I subscribed to this philosophy, here is one of the methods I would want to add to my type:

    ALTER TYPE catalog_item_t
       ADD MEMBER PROCEDURE remove
       CASCADE;

    CREATE OR REPLACE TYPE BODY catalog_item_t
    AS
       ...
       MEMBER PROCEDURE remove
       IS
       BEGIN
          DELETE catalog_items
           WHERE id = SELF.id;
          SELF := NULL;
       END;
    END;

(Oracle does not offer a destructor method, by the way.) By defining this method at the supertype level, all my subtypes are taken care of too. This design assumes that corresponding objects will live in a single table; some applications might need some additional logic to locate the object. (Also, a real version of this method might include logic to perform ancillary functions like removing dependent objects and/or archiving the data before removing the object permanently.)

Assuming that my applications would always modify a transient object in memory before writing it to disk, I could combine insert and update into a single method I’ll call “save”:

    ALTER TYPE catalog_item_t
       ADD MEMBER PROCEDURE save,
       CASCADE;

    CREATE OR REPLACE TYPE BODY catalog_item_t
    AS
       ...
       MEMBER PROCEDURE save
       IS
       BEGIN
          UPDATE catalog_items c
             SET c = SELF
           WHERE id = SELF.id;
           IF SQL%ROWCOUNT = 0
           THEN
              INSERT INTO catalog_items VALUES (SELF);
           END IF;
       END;

You may correctly point out that this will replace all of the column values in the table even if they are unchanged, which could cause triggers to fire that shouldn’t, and results in needless I/O. Alas, this is one of the unfortunate by-products of an object approach. It is true that with careful programming, you could avoid modifying columns from the supertype that haven’t changed, but columns from any subtype are not individually accessible from any variation on the UPDATE statement that Oracle currently offers.

Retrieval is the most difficult operation to encapsulate because of the many WHERE-clause permutations and the multiset nature of the result. The specification of the query criteria can be a real rat’s nest, as anyone who has ever built a custom query screen will attest. Considering only the result side, the options for what to return include:

  • A collection of objects

  • A collection of REFs

  • A pipelined result set

  • A cursor variable (strongly or weakly typed)

The requirements of the application and its programming environment will have the largest influence on how to choose from these options. Here’s a stripped-down example that uses the fourth approach, a cursor variable:

    ALTER TYPE catalog_item_t
       ADD STATIC FUNCTION cursor_for_query (typename IN VARCHAR2 DEFAULT NULL,
          title IN VARCHAR2 DEFAULT NULL,
          att1 IN VARCHAR2 DEFAULT NULL,
          val1 IN VARCHAR2 DEFAULT NULL)
          RETURN SYS_REFCURSOR
       CASCADE;

I use a static method that returns the built-in SYS_REFCURSOR type, which is a weak cursor type that Oracle provides (just something of a convenience feature), allowing the client program to iterate over the results. The “att1” and “val1” parameters provide a means of querying subtype-specific attribute/value pairs; a real version of this program would be better off accepting a collection of such attribute/value pairs to allow queries on multiple attributes of a given subtype.

Jumping ahead to how you might execute a query, let’s look at this example:

    DECLARE
       catalog_item catalog_item_t;
       l_refcur SYS_REFCURSOR;
    BEGIN
       l_refcur := catalog_item_t.cursor_for_query(
          typename => 'book_t',
          title => 'Oracle PL/SQL Programming'),
       LOOP
          FETCH l_refcur INTO catalog_item;
          EXIT WHEN l_refcur%NOTFOUND;
          DBMS_OUTPUT.PUT_LINE('Matching item:' || catalog_item.print);
       END LOOP;
       CLOSE l_refcur;
    END;

which yields:

    Matching item:id=10007; title=Oracle PL/SQL Programming; publication_date=Sept 1997;
    isbn=1-56592-335-9; pages=987

The implementation:

 1       MEMBER PROCEDURE save
 2       IS
 3       BEGIN
 4          UPDATE catalog_items c
 5             SET c = SELF
 6           WHERE id = SELF.id;
 7           IF SQL%ROWCOUNT = 0
 8           THEN
 9              INSERT INTO catalog_items VALUES (SELF);
10            END IF;
11        END;
12
13       STATIC FUNCTION cursor_for_query (typename IN VARCHAR2 DEFAULT NULL,
14           title IN VARCHAR2 DEFAULT NULL,
15           att1 IN VARCHAR2 DEFAULT NULL,
16           val1 IN VARCHAR2 DEFAULT NULL)
17           RETURN SYS_REFCURSOR
18        IS
19           l_sqlstr VARCHAR2(1024);
20           l_refcur SYS_REFCURSOR;
21        BEGIN
22           l_sqlstr := 'SELECT VALUE(c) FROM catalog_items c WHERE 1=1 ';
23           IF title IS NOT NULL
24           THEN
25             l_sqlstr := l_sqlstr || 'AND title = :t ';
26           END IF;
27
28           IF typename IS NOT NULL
29           THEN
30              IF att1 IS NOT NULL
31              THEN
32                 l_sqlstr := l_sqlstr
33                    || 'AND TREAT(SELF AS ' || typename || ').' || att1 || ' ';
34                 IF val1 IS NULL
35                 THEN
36                    l_sqlstr := l_sqlstr || 'IS NULL ';
37                 ELSE
38                    l_sqlstr := l_sqlstr || '=:v1 ';
39                 END IF;
40              END IF;
41              l_sqlstr := l_sqlstr || 'AND VALUE(c) IS OF (' || typename ||') ';
42           END IF;
43
44           l_sqlstr := 'BEGIN OPEN :lcur FOR ' || l_sqlstr || '; END;';
45
46           IF title IS NULL AND att1 IS NULL
47           THEN
48              EXECUTE IMMEDIATE l_sqlstr USING IN OUT l_refcur;
49           ELSIF title IS NOT NULL AND att1 IS NULL
50           THEN
51              EXECUTE IMMEDIATE l_sqlstr USING IN OUT l_refcur, IN title;
52           ELSIF title IS NOT NULL AND att1 IS NOT NULL
53           THEN
54              EXECUTE IMMEDIATE l_sqlstr USING IN OUT l_refcur, IN title, IN att1;
55           END IF;
56
57           RETURN l_refcur;
58        END;

Because dynamic SQL is a little tricky to follow, here is what the function would have generated internally with the previous query:

    BEGIN
       OPEN :lcur FOR
          SELECT VALUE(c)
            FROM catalog_items c
           WHERE 1=1
             AND title = :t
             AND VALUE(c) IS OF (book_t);
    END;

One nice thing about this approach is that you don’t have to modify the query code every time you add a subtype to the inheritance tree.

Comparing Objects

So far, my examples have used object tables—tables in which each row constitutes an object built with the CREATE TABLE ... OF type statement. As I’ve illustrated, such an arrangement enjoys some special features, such as REF-based navigation and the treatment of entire objects (rather than individual column values) as the unit of I/O.

You can also use an object type as the datatype for individual columns in a table (the relevant nomenclature is column objects , as mentioned earlier). For example, imagine that I want to create an historical record of changes in the catalog_items table, capturing all inserts, updates, and deletes.

    CREATE TABLE catalog_history (
       id INTEGER NOT NULL PRIMARY KEY,
       action CHAR(1) NOT NULL,
       action_time TIMESTAMP DEFAULT (SYSTIMESTAMP) NOT NULL,
       old_item catalog_item_t,
       new_item catalog_item_t)
       NESTED TABLE old_item.subject_refs STORE AS catalog_history_old_subrefs
       NESTED TABLE new_item.subject_refs STORE AS catalog_history_new_subrefs;

As soon as you start populating a table with column objects, though, you raise some questions about how Oracle should behave when you ask it to do things like sort or index on one of those catalog_item_t columns. There are four ways you can compare objects; some are more useful than others:

Attribute-level comparison

Include the relevant attribute(s) when sorting, creating indexes, or comparing.

Default SQL

Oracle’s SQL knows how to do a simple equality test. In this case, two objects are considered equal if they are defined on exactly the same type and every corresponding attribute is equal. This will work if the objects have only scalar attributes (no collections or LOBs) and if you haven’t already defined a MAP or ORDER member method on the object type.

MAP member method

You can create a special function method that returns a “mapping” of the object value onto a datatype that Oracle already knows how to compare, such as a number or a date. This will work only if no ORDER method exists.

ORDER member method

This is another special function that compares two objects and returns a flag value that indicates their relative ordering. This will work only if no MAP method exists.

Default SQL comparison is not terribly useful, so I won’t say any more about it. The following sections describe the other, more useful ways to compare objects.

Attribute-level comparison

Attribute-level comparison may not be precisely what you want, but it is fairly easy in PL/SQL, or even in SQL if you remember to use a table alias in the SQL statement. Oracle lets you expose attributes via dot notation:

    SELECT * FROM catalog_history c
     WHERE c.old_item.id > 10000
     ORDER BY NVL(TREAT(c.old_item as book_t).isbn, TREAT(c.old_item AS serial_t).issn)

Attribute-level index creation is equally easy:

    CREATE INDEX catalog_history_old_id_idx ON catalog_history c (c.old_item.id);

The MAP method

Both the MAP and the ORDER methods make it possible to perform statements such as the following:

    SELECT * FROM catalog_history
     ORDER BY old_item;

    IF old_item > new_item
    THEN ...

First let’s look at MAP. I can add a trivial MAP method to catalog_item_t as follows:

    ALTER TYPE catalog_item_t
       ADD MAP MEMBER FUNCTION mapit RETURN NUMBER
       CASCADE;

    CREATE OR REPLACE TYPE BODY catalog_item_t
    AS ...
       MAP MEMBER FUNCTION mapit RETURN NUMBER
       IS
       BEGIN
          RETURN id;
       END;
       ...
    END;

Assuming, of course, that ordering by id makes sense, now I can order and compare catalog items to my heart’s content, and Oracle will call this method automatically whenever necessary. The function needn’t be so simple; for example, it could return a scalar value computed from all the object attributes, melded together in some way that actually might be of some value to librarians.

Creating a MAP method like this has a side effect, though: the equality comparison gets defined in a way you might not like. “Equality” now becomes a matter of the mapped value’s being equal for the objects you’re comparing. If you want an easy way to compare two objects for attribute-by-attribute equality, you will want to either create your own (non-MAP) method and invoke it by name when needed, or use an ORDER method.

The ORDER method

The alternative to MAP is an ORDER member function, which compares two methods: SELF, and another object of the same type that you supply as an argument. You want to program the function to return an integer that is positive, zero, or negative, indicating the ordering relationship of the second object to SELF. Table 25-2 illustrates the behavior you need to incorporate.

Table 25-2. Desired behavior of ORDER member functions

For these desired semantics...

Your ORDER member function must return

SELF < argumentObject

Any negative number (typically -1)

SELF = argumentObject

0

SELF > argumentObject

Any positive number (typically +1)

Undefined comparison

NULL

Let’s take a look at a nontrivial example of an ORDER method:

 1   ALTER TYPE catalog_item_t
 2      DROP MAP MEMBER FUNCTION mapit RETURN NUMBER
 3      CASCADE;
 4
 5   ALTER TYPE catalog_item_t
 6      ADD ORDER MEMBER FUNCTION orderit (obj2 IN catalog_item_t)
 7         RETURN INTEGER
 8      CASCADE;
 9
10    CREATE OR REPLACE TYPE BODY catalog_item_t
11    AS ...
12       ORDER MEMBER FUNCTION orderit (obj2 IN catalog_item_t)
13          RETURN INTEGER
14       IS
15          self_gt_o2 CONSTANT PLS_INTEGER := 1;
16          eq CONSTANT PLS_INTEGER := 0;
17          o2_gt_self CONSTANT PLS_INTEGER := -1;
18          l_matching_count NUMBER;
19       BEGIN
20          CASE
21             WHEN obj2 IS OF (book_t) AND SELF IS OF (serial_t) THEN
22                RETURN o2_gt_self;
23             WHEN obj2 IS OF (serial_t) AND SELF IS OF (book_t) THEN
24                RETURN self_gt_o2;
25             ELSE
26                IF obj2.title = SELF.title
27                   AND obj2.publication_date = SELF.publication_date
28                THEN
29                   IF obj2.subject_refs IS NOT NULL
30                      AND SELF.subject_refs IS NOT NULL
31                      AND obj2.subject_refs.COUNT = SELF.subject_refs.COUNT
32                   THEN
33                      SELECT COUNT(*) INTO l_matching_count FROM
34                         (SELECT *
35                           FROM TABLE(SELECT CAST(SELF.subject_refs AS subject_refs_t)
36                                       FROM dual)
37                         INTERSECT
38                         SELECT *
39                           FROM TABLE(SELECT CAST(obj2.subject_refs AS subject_refs_t)
40                                        FROM dual));
41                      IF l_matching_count = SELF.subject_refs.COUNT
42                      THEN
43                         RETURN eq;
44                      END IF;
45                   END IF;
46                END IF;
47                RETURN NULL;
48          END CASE;
49       END;
50       ...
51    END;

Here are the important things to note:

Lines 21-24

This means that “books sort higher than serials.”

Lines 26-46

This is an equality test that uses a very cool feature. Because Oracle doesn’t know how to compare collections, this code uses Oracle’s ability to select from a collection as if it were a table. By checking to make sure that the relational intersection of these two collections has the expected number of elements, I can determine whether every element in the first collection has an equal counterpart in the second (which is my definition of “equality”).

Overall, however, my ORDER method is still inadequate because it fails to treat the subtype-specific attributes, but anything longer would just be too unwieldy for this book.

Additional comparison recommendations

To close out this discussion, here are a few additional rules and recommendations for comparison methods:

  • MAP and ORDER cannot coexist in the same object type; use one or the other.

  • Oracle recommends MAP when you have a large number of objects to sort or compare, as in a SQL statement. This is because of an internal optimization that reduces the number of function calls. With ORDER, the function must run once for every comparison.

  • Oracle ignores the method names; you can call them anything you want.

  • Subtypes can include MAP methods, but only if the supertype also has one.

  • Subtypes cannot have ORDER methods; you’ll have to put all the comparison “smarts” into the supertype.

Object Views

Although Oracle’s object extensions offer PL/SQL programmers rich possibilities for the design of new systems, it’s unlikely that you will want to completely reengineer your existing systems to use objects. In part to allow established applications to take advantage of the new object features over time, Oracle provides object views . This feature offers several unique advantages:

“Object-ification” of remote data

It’s not yet possible, even in Oracle Database 10g, to use the object tables and physical REFs across a distributed database, but you can create object views and virtual REFs that cast remote relational data as objects.

Virtual denormalization

In a relational database or even an object-relational database, you will usually find relationships modeled in only one direction. For example, a book has some number of subjects. With an object view, it’s easy to associate a column that provides the inverse mapping; for example, a subject object could include a collection of REFs that point to all of the books in that subject.

Efficiency of object access

In Oracle Call Interface (OCI) applications, object programming constructs provide for the convenient retrieval, caching, and updating of object data. By reducing trips between application and database server, these programming facilities may provide performance improvements, with the added benefit that application code can be more succinct.

Greater flexibility to change the object model

Although newer versions of Oracle have tremendous abilities in the area of type evolution, adding and removing object attributes still cause table bits to move around on the disk, which administrators may be loath to do. Recompiling object views suffers no such consequences.

On the other hand, there are some disadvantages to using object views:

View performance

Object views are still views, and some Oracle shops are generally leery of the performance of any view.

No virtual REFs

You cannot store virtual REFs in the database; instead, they get constructed on the fly. This may present some challenges if you someday want to convert those object views into object tables.

Other features of Oracle can improve the expressiveness of any types of views, not just object views. Two such features that are not strictly limited to object views are collections and INSTEAD OF triggers.

Collections

Consider two relational tables with a simple master-detail relationship. You can create a view portraying the detail records as a single nonscalar attribute (collection) of the master.

INSTEAD OF triggers

In addition, by using INSTEAD OF triggers, you can tell Oracle exactly how to perform inserts, updates, and deletes on the view.

From an object perspective, there is one slight disadvantage of object views when compared to comprehensive reengineering: object views cannot retrofit any benefits of encapsulation. Insofar as any applications apply INSERT, UPDATE, and DELETE statements directly to the underlying relational data, they may subvert the benefits of encapsulation normally provided by an object approach. Object-oriented designs typically prevent free-form access directly to data. However, because Oracle supports neither private attributes nor private methods, the incremental sacrifice here is small.

If you do choose to layer object views on top of an existing system, it may be possible for new applications to enjoy incremental benefit, and your legacy systems are no worse off than they were before. Figure 25-2 illustrates this use of object views.

Object views allow you to bind an object type definition to (existing) relational tables

Figure 25-2. Object views allow you to bind an object type definition to (existing) relational tables

The following sections discuss aspects of using object views (including differences between object tables and object views) that PL/SQL programmers should find particularly useful and interesting.

A Sample Relational System

For this chapter’s second major example, let’s look at how object views might be used in a database application that supports a graphic design firm. Their relational application includes information about images (GIF, JPEG, etc.) that appear on web sites they design. These images are stored in files, but data about them is stored in relational tables. To help the graphic artists locate the right image, each image has one or more associated keywords stored in a straightforward master-detail relationship.

The legacy system has a table of suppliers:

    CREATE TABLE suppliers (
       id INTEGER NOT NULL PRIMARY KEY,
       name VARCHAR2(400) NOT NULL
    );

Here is the table for image metadata:

    CREATE TABLE images (
       image_id INTEGER NOT NULL PRIMARY KEY,
       file_name VARCHAR2(512) NOT NULL,
       file_type VARCHAR2(12) NOT NULL,
       supplier_id INTEGER REFERENCES suppliers (id),
       supplier_rights_descriptor VARCHAR2(256),
       bytes INTEGER
    );

Not all images originate from suppliers; if the supplier id is null, then the image was created in-house.

Finally, there is one table for the keywords associated with the images:

    CREATE TABLE keywords (
       image_id INTEGER NOT NULL REFERENCES images (image_id),
       keyword VARCHAR2(45) NOT NULL,
       CONSTRAINT keywords_pk PRIMARY KEY (image_id, keyword)
    );

Let’s assume that the following data exists in the underlying tables:

    INSERT INTO suppliers VALUES (101, 'Joe''s Graphics'),
    INSERT INTO suppliers VALUES (102, 'Image Bar and Grill'),
    INSERT INTO images VALUES (100001, '/files/web/60s/smiley_face.png', 'image/png',
       101, 'fair use', 813);
    INSERT INTO images VALUES (100002, '/files/web/60s/peace_symbol.gif', 'image/gif',
       101, 'fair use', 972);
    INSERT INTO images VALUES (100003, '/files/web/00s/towers.jpg', 'image/jpeg', NULL,
       NULL, 2104);
    INSERT INTO KEYWORDS VALUES (100001, 'SIXTIES'),
    INSERT INTO KEYWORDS VALUES (100001, 'HAPPY FACE'),
    INSERT INTO KEYWORDS VALUES (100002, 'SIXTIES'),
    INSERT INTO KEYWORDS VALUES (100002, 'PEACE SYMBOL'),
    INSERT INTO KEYWORDS VALUES (100002, 'JERRY RUBIN'),

In the next few sections, you’ll see several object views defined on this data:

  • The first view is defined on an image type that includes the keywords as a collection attribute.

  • The second view is a “subview”—that is, defined on a subtype in an object type hierarchy. It will include characteristics for images that originate from suppliers.

  • The final view includes keywords and their inverse references back to the relevant images.

Object View with a Collection Attribute

Before creating an underlying type for the first view, we need a collection type to hold the keywords. Use of a nested table makes sense here, because keyword ordering is unimportant and because there is no logical maximum number of keywords.[*]

    CREATE TYPE keyword_tab_t AS TABLE OF VARCHAR2(45);

At this point, it’s a simple matter to define the image object type:

    CREATE TYPE image_t AS OBJECT (
        image_id INTEGER,
        image_file BFILE,
        file_type VARCHAR2(12),
        bytes INTEGER,
        keywords keyword_tab_t
    );

Assuming that the image files and the database server are on the same machine, I can use an Oracle BFILE datatype rather than the filename. I’ll need to create a “directory,” that is, an alias by which Oracle will know the directory that contains the images. In this case, I use the root directory (on the target Unix system, this is represented by a single forward slash), because I happen to know that the file_name column includes full pathnames.

    CREATE DIRECTORY rootdir AS  '/';

So far, I have not defined a connection between the relational tables and the object type. They are independent organisms. It is in building the object view that we overlay the object definition onto the tables, as the next statement illustrates:

    CREATE VIEW images_v
       OF image_t
       WITH OBJECT IDENTIFIER (image_id)
    AS
       SELECT i.image_id, BFILENAME('ROOTDIR', i.file_name),
          i.file_type, i.bytes,
          CAST (MULTISET (SELECT keyword
                            FROM keywords k
                           WHERE k.image_id = i.image_id)
            AS keyword_tab_t)
         FROM images i;

There are two components of this statement that are unique to object views:

OF image_t

This means that the view will return objects of type image_t.

WITH OBJECT IDENTIFIER (image_id)

To behave like a “real” object instance, data returned by the view will need some kind of object identifier. By designating the primary key as the basis of a virtual OID, we can enjoy the benefits of REF-based navigation to objects in the view.

In addition, the select list of an object view must correspond in number, position, and datatype with the attributes in the associated object type.

OK, now that we’ve created an object view, what can we do with it? Most significantly, we can retrieve data from it just as if it were an object table. So, from SQL*Plus, a query like the following:

    SQL>SELECT image_id, keywords FROM images_v;

yields:

      IMAGE_ID KEYWORDS
    ---------- -------------------------------------------------------
        100003 KEYWORD_TAB_T()
        100001 KEYWORD_TAB_T('HAPPY FACE', 'SIXTIES')
        100002 KEYWORD_TAB_T('JERRY RUBIN', 'PEACE SYMBOL', 'SIXTIES')

In the interest of deepening the object appearance, I could also add methods to the type definition. Here, for example, is a print() method:

    ALTER TYPE image_t
       ADD MEMBER FUNCTION print RETURN VARCHAR2
       CASCADE;

    CREATE OR REPLACE TYPE BODY image_t
    AS
       MEMBER FUNCTION print
          RETURN VARCHAR2
       IS
          filename images.file_name%TYPE;
          dirname VARCHAR2(30);
          keyword_list VARCHAR2(32767);
       BEGIN
          DBMS_LOB.FILEGETNAME(SELF.image_file, dirname, filename);
          IF SELF.keywords IS NOT NULL
          THEN
             FOR key_elt IN 1..SELF.keywords.COUNT
             LOOP
                keyword_list := keyword_list || ', ' || SELF.keywords(key_elt);
             END LOOP;
          END IF;
          RETURN 'Id=' || SELF.image_id || '; File=' || filename
             || '; keywords=' || SUBSTR(keyword_list, 3);
       END;
    END;

This example illustrates a way to “flatten” the keyword list by iterating over the virtual collection of keywords.

Other things you can do with object views include the following:

Use virtual REFs

These are pointers to virtual objects. They are discussed in detail in the later section, "Differences Between Object Views and Object Tables.”

Write INSTEAD OF triggers

These will allow direct manipulation of the view’s contents. You can read more about this topic in the later section, "INSTEAD OF Triggers.”

Object Subview

In the case where I want to treat certain images differently from others, I might want to create a subtype. In my example, I’m going to create a subtype for those images that originate from suppliers. I’d like the subtype to include a REF to a supplier object, which is defined by:

    CREATE TYPE supplier_t AS OBJECT (
       id INTEGER,
       name VARCHAR2(400)
    );

and by a simple object view:

    CREATE VIEW suppliers_v
       OF supplier_t
       WITH OBJECT IDENTIFIER (id)
    AS
       SELECT id, name
         FROM suppliers;

I will need to alter or recreate the base type to be NOT FINAL:

    ALTER TYPE image_t NOT FINAL CASCADE;

so that I can create the subtype under it:

    CREATE TYPE supplied_images_t UNDER image_t (
       supplier_ref REF supplier_t,
       supplier_rights_descriptor VARCHAR2(256)
    );

After all this preparation, I make the subview of this subtype and declare it to be UNDER the images_v view using the following syntax:

    CREATE VIEW supplied_images_v
       OF supplied_images_t
       UNDER images_v
    AS
       SELECT i.image_id, BFILENAME('ROOTDIR', i.file_name),
              i.file_type, i.bytes,
              CAST (MULTISET (SELECT keyword
                                FROM keywords k
                               WHERE k.image_id = i.image_id)
                AS keyword_tab_t),
    
              MAKE_REF(suppliers_v, supplier_id),
              supplier_rights_descriptor
         FROM images i
        WHERE supplier_id IS NOT NULL;

Oracle won’t let a subview query through the superview, so this view queries the base table, adding the WHERE clause to restrict the records retrieved. Also notice that subviews don’t use the WITH OBJECT IDENTIFIER clause because they inherit the same OID as their superview.

I have introduced the MAKE_REF function in this query, which Oracle provides as a way to compute a REF to a virtual object. Here, the virtual object is the supplier, as conveyed through suppliers_v. The specification of MAKE_REF is:

    FUNCTION MAKE_REF (view, value_list) RETURN ref;

where:

view

Is the object view to which you want ref to point.

value_list

Is a comma-separated list of column values whose datatypes must match one for one with the OID attributes of view.

You should realize that MAKE_REF does not actually select through the view; it merely applies an internal Oracle algorithm to derive a REF. And, as with “real” REFs, virtual REFs may not point to actual objects.

Now I come to a surprising result. Although it seems that I have not changed the superview, images from suppliers now appear twice in the superview—that is, as duplicates:

    SQL>SELECT COUNT(*), image_id FROM images_v GROUP BY image_id;

      COUNT(*)   IMAGE_ID
    ---------- ----------
             2     100001
             2     100002
             1     100003

Oracle is returning a logical UNION ALL of the query in the superview and that of the subview. This does sort of make sense; an image from a supplier is still an image. To eliminate the duplicates, add a WHERE clause on the parent that excludes records returned in the subview:

    CREATE OR REPLACE VIEW images_v AS
       ...
       WHERE supplier_id IS NULL;

Object View with Inverse Relationship

To demonstrate virtual denormalization , I can create a keyword type for a view that links keywords back to the images they describe:

    CREATE TYPE image_refs_t AS TABLE OF REF image_t;

    CREATE TYPE keyword_t AS OBJECT (
       keyword VARCHAR2(45),
       image_refs image_refs_t);

And here is a keywords view definition:

    CREATE OR REPLACE VIEW keywords_v
       OF keyword_t
       WITH OBJECT IDENTIFIER (keyword)
    AS
       SELECT keyword, CAST(MULTISET(SELECT MAKE_REF(images_v, image_id)
                                       FROM keywords
                                      WHERE keyword = main.keyword)
                         AS image_refs_t)
         FROM (SELECT DISTINCT keyword FROM keywords) main;

Now, I don’t promise that queries on this view will run fast; the query is compensating for the fact that the database lacks a reference table of keywords by doing a SELECT DISTINCT operation. Even if I weren’t using any object features, that would be an expensive query.

You may correctly point out that using MAKE_REF is not mandatory here; I could have retrieved a REF by making the inner query on images_v rather than on the keywords table. In general, MAKE_REF should be faster than a lookup through an object view; on occasion, you may not have the luxury of being able to perform that lookup.

Anyway, at this point I can run such pithy queries as this one:

    SQL>SELECT DEREF(VALUE(i)).print()
      2    FROM keywords_v v, TABLE(v.image_refs) i
      3   WHERE keyword = 'SIXTIES';

    DEREF(VALUE(I)).PRINT()
    ------------------------------------------------------------------------------------
    Id=100001; File=/files/web/60s/smiley_face.gif; keywords=HAPPY FACE, SIXTIES
    Id=100002; File=/files/web/60s/peace_symbol.gif; keywords=JERRY RUBIN, PEACE SYMBOL,
    SIXTIES

That is, I can show a list of all the images tagged with the keyword SIXTIES, along with their other keywords and attributes. I admit that I’m not sure how groovy that really is!

INSTEAD OF Triggers

Because Chapter 19 covered the syntax and use of INSTEAD OF triggers , I’m not going to discuss their mechanics here. Instead, I will explore whether they are a good fit for the problem of updating object views . If your goal is to migrate toward an object approach, you may ask whether INSTEAD OF triggers are just a relational throwback that facilitates a free-for-all in which any application can perform DML.

Well, they are and they aren’t.

Let’s examine the arguments for both sides, and come up with some considerations so you can decide what’s best for your application.

The case against

On the one hand, you could use PL/SQL programs such as packages and object methods to provide a more comprehensive technique than triggers for encapsulating DML. It is nearly trivial to take the logic from our INSTEAD OF trigger and put it into an alternate PL/SQL construct that has more universal application. In other words, if you’ve already standardized on some combination of packages and methods as the means of performing DML, you could keep your environment consistent without using view triggers. You might conclude that view triggers are just added complexity in an increasingly confusing equation.

Moreover, even Oracle cautions against the “excessive use” of triggers because they can cause “complex interdependencies.” Imagine if your INSTEAD OF triggers performed DML on tables that had other triggers, which performed DML on still other tables with triggers ... it’s easy to see how this could get impossible to debug.

The case for

On the other hand, you can put much of the necessary logic that you would normally put into a package or method body into an INSTEAD OF trigger instead. Doing this in combination with a proper set of privilege restrictions could protect your data just as well as, or even better than, methods or packages.

If you happen to use a client tool such as Oracle Forms, INSTEAD OF triggers allow you to use much more of the product’s default functionality when you create a Forms “block” against a view rather than a table.

Finally, if you use OCI, INSTEAD OF triggers are required if the object view is not inherently modifiable, and you want to be able to easily “flush” cached object view data back to the server.

The bigger question

The bigger question is this: what’s the best place for the SQL statements that insert, update, and delete data, especially when using object views? Assuming that you want to localize these operations on the server side, you have at least three choices: PL/SQL packages, object methods, and INSTEAD OF triggers.

Table 25-3 summarizes some of the major considerations of the three techniques. Note that this table is not meant to compare these approaches for general-purpose use, but only as they apply to localizing DML on object views .

Table 25-3. Assessment of techniques for encapsulating DML on object views

Consideration

PL/SQL package

Object method

INSTEAD OF trigger

Consistency with object-oriented approach

Potentially very good

Excellent

Potentially very good

Ability to modify when underlying schema changes

Excellent; can be easily altered and recompiled independently

Excellent in Oracle9i Database and Oracle Database 10g

Excellent

Risk of unexpected interactions

Low

Low

High; triggers may have unpredictable interactions with each other

Ease of use with client tool default functionality (specifically Oracle Developer)

Acceptable; programmer must add code for all client-side transactional triggers

Acceptable; programmer must add code for all client-side transactional triggers

Excellent for top-level types (however, there is no INSTEAD OF LOCK server-side trigger)

Can be turned on and off at will

No

No

Yes (by disabling and enabling the trigger)

As you can see, there is no clear “winner.” Each technique has benefits that may be of more or less importance to your application.

One important point about using INSTEAD OF triggers in view hierarchies is that you will need a separate trigger for each level of the hierarchy. When you perform DML through a subview, the subview’s trigger will fire; when you perform DML through the superview, the superview’s trigger will fire.

And of course, you may decide that INSTEAD OF triggers make sense in combination with PL/SQL packages and/or object methods to provide layers of encapsulation. For example:

    CREATE OR REPLACE TRIGGER images_v_insert
    INSTEAD OF INSERT ON images_v
    FOR EACH ROW
    BEGIN
       /* Call a packaged procedure to perform the insert. */
       manage_image.create_one(:NEW.image_id, :NEW.file_type,
          :NEW.file_name, :NEW.bytes, :NEW.keywords);
    END;

In an ideal world, developers would select an overall architecture and design approach before hurling every Oracle feature at their application. Use a feature only if it make sense for your design. I agree with Oracle’s advice that if you do use triggers, you should use them in moderation.

Differences Between Object Views and Object Tables

In addition to the obvious difference between an object view and an object table, PL/SQL programmers should be aware of the more subtle differences. Areas of difference include the following:

  • OID uniqueness

  • “Storeability” of physical versus virtual REFs

  • REFs to nonunique OIDs

Let’s look at each difference in turn.

OID uniqueness

An object table will always have a unique object identifier, either system-generated or derived from the primary key. It is technically possible—though poor practice—to create an object table with duplicate rows, but the instances will still be unique in their object identifier. This can happen in two different ways:

Duplicate OIDs in a single view

An object view can easily contain multiple object instances (rows) for a given OID. We’ve already seen a case where the superview can accidentally contain duplicates.

Duplicate OIDs across multiple views

If your object view is defined on an underlying object table or view and if you use the DEFAULT keyword to specify the OID, the view contains OIDs that match the OIDs of the underlying structure.

It seems more likely that this second possibility of duplication would be legitimate in your application because separate views are just separate stored queries.

“Storeability” of physical versus virtual REFs

If you’ve built an application with physical object tables, you can store REFs to those objects persistently in other tables. A REF is a binary value that Oracle can use as a pointer to an object.

However, Oracle returns an error if you attempt to store a virtual REF—that is, a REF to a row of an object view—in an actual table. Because the reference depends on some column value(s), you will need to save the underlying column value(s) instead of the virtual reference. From one perspective, this is an irritant rather than a major obstacle. Still, it’s a bit unpleasant that we cannot intermingle object tables with object views, nor can we perform a simple transformation from an object view into an object table. I would like to be able to create an object table:

    CREATE TABLE images2 OF image_t
       NESTED TABLE keywords STORE AS keyword_tab;

and then populate it from the view:

    INSERT INTO images2       /* invalid because images_v includes a REF */
     SELECT VALUE(i) FROM images_v i;

But alas, Oracle tells me ORA-22979: cannot INSERT object view REF or user-defined REF. Life goes on, however.

REFs to nonunique OIDs

I don’t believe that it is possible to have a REF to a non-unique OID when dealing with object tables. You may want to consider what will happen if you create a REF to an object in an object view, but the view has multiple object instances for the OID in question. Granted, this is a pretty weird case; you shouldn’t be creating object views with ambiguous OIDs.

In my testing, DEREFing this type of virtual REF did indeed return an object—apparently, the first one Oracle found that matched. In previous versions of Oracle, this operation returns a NULL. I don’t think I would count on either behavior in an application.

Maintaining Object Types and Object Views

If you work much with object types, you will learn a number of ways to get information about the types and views that you have created. Once you reach the limits of the SQL*Plus DESCRIBE command, this could involve direct queries from the Oracle data dictionary.

The dictionary term for user-defined types (objects and collections) is simply TYPE. Object type definitions and object type bodies are both found in the USER_SOURCE view (or DBA_SOURCE, or ALL_SOURCE), just as package specifications and bodies are. Table 25-4 lists a number of helpful queries you can use.

Table 25-4. Data dictionary entries for object types

To answer the question...

Use a query such as

What object and collection types have I created?

SELECT * FROM user_types;
SELECT * FROM user_objects
  WHERE object_type = 'TYPE';

What do my object type hierarchies look like?

SELECT RPAD(' ', 3*(LEVEL-1)) || type_name
  FROM user_types
WHERE typecode = 'OBJECT'
  CONNECT BY PRIOR type_name = supertype_name;

What are the attributes of type foo?

SELECT * FROM user_type_attrs
  WHERE type_name = 'FOO';

What are the methods of type foo?

SELECT * FROM user_type_methods
  WHERE type_name = 'FOO';

What are the parameters of foo’s methods?

SELECT * FROM user_method_params
  WHERE type_name = 'FOO';

What datatype is returned by foo’s method called bar?

SELECT * FROM user_method_results
  WHERE type_name = 'FOO' AND method_name = 'BAR';

What is the source code for foo, including all ALTER statements?

SELECT text FROM user_source
  WHERE name = 'FOO'
    AND type = 'TYPE' /* or 'TYPE BODY' */
  ORDER BY line;

What are the object tables that implement foo?

SELECT table_name FROM user_object_tables
  WHERE table_type = 'FOO';

What are all the columns in an object table foo_tab, including the hidden ones?

SELECT column_name, data_type, hidden_column,
    virtual_column
  FROM user_tab_cols
  WHERE table_name = 'FOO_TAB';

What columns implement foo?

SELECT table_name, column_name
  FROM user_tab_columns
 WHERE data_type = 'FOO';

What database objects depend on foo?

SELECT name, type FROM user_dependencies
 WHERE referenced_name = 'FOO';

What object views have I created, using what OIDs?

SELECT view_name, view_type, oid_text
  FROM user_views
 WHERE type_text IS NOT NULL;

What does my view hierarchy look like? (Requires a temporary table in Oracle versions that can’t use a subquery with CONNECT BY)

CREATE TABLE uvtemp AS
     SELECT v.view_name, v.view_type,
       v.superview_name, v1.view_type superview_type
       FROM user_views v, user_views v1
      WHERE v.superview_name = v1.view_name (+);
SELECT RPAD(' ', 3*(LEVEL-1)) || view_name
      || ' (' || view_type || ') '
  FROM uvtemp
  CONNECT BY PRIOR view_type = superview_type;
DROP TABLE uvtemp;

What is the query on which I defined the foo_v view?

SET LONG 1000 -- or greater
SELECT text FROM user_views
  WHERE view_name = 'FOO_V';

What columns are in view foo_v?

SELECT column_name, data_type_mod, data_type
  FROM user_tab_columns
 WHERE table_name = 'FOO_V';

One potentially confusing thing Oracle has done in the data dictionary is to make object tables invisible from the USER_TABLES view. Instead, a list of object tables appears in USER_OBJECT_TABLES (as well as in USER_ALL_TABLES).

Privileges

There are a handful of system-level privileges associated with object types , summarized here:

CREATE [ ANY ] TYPE

Create, alter, and drop object types and type bodies. ANY means in any schema.

CREATE [ ANY ] VIEW

Create and drop views, including object views. ANY means in any schema.

ALTER ANY TYPE

Use ALTER TYPE facilities on types in any schema.

EXECUTE ANY TYPE

Use an object type from any schema for purposes including instantiating, executing methods, referencing, and dereferencing.

UNDER ANY TYPE

Create a subtype in one schema under a type in any other schema.

UNDER ANY VIEW

Create a subview in one schema under a view in any other schema.

There are three kinds of object-level privileges on object types : EXECUTE, UNDER, and DEBUG. It is also important to understand how the conventional DML privileges apply to object tables and views.

The EXECUTE privilege

If you want your associate Joe to use one of your types in his own PL/SQL programs or tables, you can grant the EXECUTE privilege to him:

    GRANT EXECUTE on catalog_item_t TO joe;

If Joe has the privilege needed to create synonyms and is running Oracle9i Database Release 2 or later, he will be able to create a synonym:

    CREATE SYNONYM catalog_item_t FOR scott.catalog_item_t;

and use it as follows:

    CREATE TABLE catalog_items OF catalog_item_t;

and/or:

    DECLARE
      an_item catalog_item_t;

Joe can also use a qualified reference to the type scott.catalog_item_t.

If you refer to an object type in a stored program and grant EXECUTE privilege on that program to a user or role, having EXECUTE on the type is not required, even if the program is defined using invoker rights (described in Chapter 23). Similarly, if a user has a DML privilege on a view that has an INSTEAD OF trigger for that DML operation, that user doesn’t need explicit EXECUTE privileges if the trigger refers to the object type because triggers run under the definer rights model. However, the EXECUTE privilege is required by users who need to run anonymous blocks that use the object type.

The UNDER privilege

The UNDER privilege gives the grantee the right to create a subtype. You can grant it as follows:

    GRANT UNDER ON image_t TO scott;

For a schema to be able to create a subtype, the supertype must be defined using invoker rights (AUTHID CURRENT_USER).

This privilege can also grant the recipient the right to create a subview:

    GRANT UNDER ON images_v TO scott;

The DEBUG privilege

If one of your associates is using a PL/SQL debugger to analyze code that uses a type you have created, you may want to grant him the DEBUG privilege:

    GRANT DEBUG ON image_t TO joe;

Doing so will enable the grantee to look “under the covers” to examine the variables used in the type and to set breakpoints inside methods.

The DEBUG privilege also applies to object views, providing a way to debug the PL/SQL source code of INSTEAD OF triggers.

The DML privileges

For object tables, the traditional SELECT, INSERT, UPDATE, and DELETE privileges still have some meaning. A user with only SELECT privilege on the object table may retrieve any relational columns in the base type on which the table is defined, but cannot retrieve the object-as-object. That is, VALUE, TREAT, REF, and DEREF are not available. Similarly, the other DML privileges, INSERT, UPDATE, and DELETE, also apply only to the relational interpretation of the table.

In the same fashion, the grantee will not have permission to use the constructor or other object methods unless the object type owner has granted the user EXECUTE privilege on the object type. Any columns defined on subtypes will be invisible.

Pontifications

I have to confess that I started programming before object approaches made any kind of inroads into business application development. I think I’m still waiting for that to happen.

Over the years, I’ve seen no compelling evidence that any particular programming style has a monopoly on the fundamental things we care about—fidelity to requirements, performance efficiency, developer effectiveness, and system reliability. I have seen a lot of fads, bandwagons, hand-waving, and unsupported assumptions (OK, I’m probably not entirely innocent myself), and object-oriented programming seems to attract quite a lot of it. That isn’t to say that OOP fails to help you solve problems; it’s just that OOP is not the magic bullet that many would have you believe.

Take, for example, the principle of object-based decomposition , particularly as it tends to generate inheritance hierarchies. By accurately modeling objects as they exist in the real world, software artifacts should be easier to comprehend, faster to assemble, and more amenable to large-scale system development. Sounds fabulous, doesn’t it? Well, there are a lot of different ways to decompose something drawn from the real world.[*] It is a rare taxonomy that can exist in a simple hierarchy. My library catalog hierarchy could have been decomposed according to, say, media (print versus audio tape versus digital format ... ). And, although Oracle provides wonderful tools for type evolution, it may still be so painful to make sweeping changes in a type hierarchy that it will never happen. This isn’t really the tool’s fault; reality has a way of circumventing even the best-laid plans.

Nor is it even clear that co-locating the programming logic (methods) with the data (attributes) in an abstract datatype yields any measurable benefits. It looks reasonable and makes for some great sound bites, but how exactly will coupling data and behavior be better than keeping data structures (logical and physical table design) separate from processes (procedures, functions, packages)? Many development methods acknowledge that an organization’s business data structures have a much slower rate of change than do the algorithms that manipulate them. It is a design truism (even for OOP) that the more volatile elements of a system should be kept separate from the more stable elements.

There is considerable inconsistency on this last point. Rich and famous object evangelists, while emphasizing the value of bundling data with behaviors, simultaneously promote a model-view-controller approach that “separates business logic from data.” Are these emperors wearing clothes, or not?

Many OOP proponents have argued for years that its greatest benefit is the reuse of software. It has been said so many times that it must be true! Unfortunately, few observers have hard evidence for this,[] in part because there is no consensus on what constitutes “reuse.” Even object apologists began promoting higher-level “components” (whatever those may be) as a preferred unit of reuse precisely because objects proved very difficult to fit into situations beyond those for which they were designed. My sense is that OOP results in no more code reuse than well-designed subroutines.

It is certainly possible to use object-oriented approaches with PL/SQL and achieve reuse of software. Fellow author Don Bales, an accomplished object-oriented programmer, has been using PL/SQL packages as “types” for about a decade, and he says that he has been able to take entire packages (and any accompanying tables) and drop them into new software development projects without modification. He believes that the missing ingredient in most object approaches is an accurate model of the person who is actually executing the software—the user—whom Don models as an object with behaviors implemented in the actual program that is run.

Regardless of development method, some of the critical ingredients of software success are having prior expertise with similar problems, being able to employ seasoned project leaders, and incorporating a conscious software design phase. Introducing object methods or any other approach is likely to produce more positive results than an unplanned, organically grown system.

A few final thoughts on when to best use Oracle’s object features:

  • If you use the Oracle Call Interface (OCI), it’s possible that the client-side cache and complex object retrieval would tip the scales in favor of heavy use of Oracle’s object features. I’m not an OCI programmer, though, so I can’t speak from experience in this regard.

  • If your organization already uses object programming across the board, Oracle’s object features will probably make it easier and more graceful to introduce database technology into your systems.

  • Don’t throw the collections baby out with the objects bathwater. Remember that you don’t need to use object types or object views to take advantage of collections.

  • If you’ve never used OOP before, these object features may seem quite complicated. I would encourage quite a bit of playing around before committing to an object approach. In particular, try out object views in conjunction with an existing system.

  • I would caution against rejecting object types and object views on a vague performance argument. Oracle has made continuous progress in reducing overhead. If you perform some actual measurements, you might find OOP within acceptable bounds for your application.

  • It turns out that Oracle delivers some of its built-in functionality (in features such as Advanced Queuing, Oracle Spatial, and Rules Manager) using object types. Using these features will require you to understand something about the object features even if you don’t want to create your own custom types.



[*] Perhaps I should say arguably desirable features. The missing features are unlikely to be showstoppers.

[*] A check digit is a number incorporated into an identifier that is mathematically derived from the identifier’s other digits. Its accuracy yields a small amount of confidence that the overall identifier has been correctly transcribed. The ISBN (International Standard Book Number) and ISSN (International Standard Serial Number)—identifiers assigned by external authorities—both contain check digits. So do most credit card numbers.

[*] In case you’re curious, the open_or_closed attribute will be either (O)pen, meaning that the library can continue to modify the catalog entry (perhaps they do not own all the issues); (C)losed, meaning that the catalog entry is complete; or NULL, meaning we just don’t know at the moment.

[*] Associative arrays are a significant exception; they are non-null but empty when first declared.

[*] I would prefer to use named notation in these static function calls, but Oracle does not support named notation when calling any kind of PL/SQL function from SQL (as of the Oracle Database10g Release 1).

[*] A contrary view is held by relational industry experts who assert that OIDs should not be used for row identification and that only column objects should have OIDs. See Hugh Darwen and C. J. Date, “The Third Manifesto,” SIGMOD Record, Volume 24 Number 1, March 1995.

[*] The Hottentots had a four-valued counting system: 1, 2, 3, and “many.”

[*] I can’t make this attribute an Oracle DATE type, though, because sometimes it’s just a year, sometimes a month or a quarter, and occasionally something completely offbeat. I might get really clever and make this a nifty object type ... well, maybe in the movie version.

[*] I’m somewhat mystified by this; it would be a lot handier if at least SELECT_OBJECT were a function.

[*] As of this writing, it is impossible to store in a table an ANYDATA encapsulating an object that has evolved or that is part of a type hierarchy.

[*] If ordering were important or if there were a (small) logical maximum number of keywords per image, a VARRAY collection would be a better choice.

[*] See “Object Oriented Programming Oversold!” by B. Jacobs, http://www.geocities.com/tablizer/oopbad.htm.

[] See “So Much OO, So Little Reuse,” by Lawrence Finch, Dr. Dobb’s Journal, May 7, 1998.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.19.27.178