Tuesday, October 27, 2009

Section 13.6.  Predefined Object Types









13.6. Predefined Object Types


Starting with Oracle9i Database Release 1, Oracle provides a collection of useful, predefined

object types:



XMLType


Use this to store and manipulate XML data.


Various URI types


Use these to store uniform resource identifiers (such as HTML addresses).


Various Any types


Use these to define a PL/SQL variable that can hold any type of data.


The following subsections discuss these predefined object types in more detail.



13.6.1. The XMLType Type






Oracle9i Database Release 1 introduced a native object type called XMLType. You can use XMLType to define database columns and PL/SQL variables containing XML documents. Methods defined on XMLType enable you to instantiate new XMLType values, to extract portions of an XML document, and to otherwise manipulate the contents of an XML document in various ways.


In Oracle9i Database Release 1, you needed to use the "SYS." prefix when referencing the XMLType object type. Staring with Oracle9i Database Release 2, Oracle allows synonyms to point to object types, and the database creation script ($ORACLE_HOME/rdbms/admin/dbmsxmlt.sql) that creates XMLType now also creates the public synonym XMLTYPE, which points to the SYS.XMLType predefined object type.




XML in PL/SQL



XML is a huge subject that we can't hope to cover in any detail in this book. However, if you're working with XML from PL/SQL, there are at least two things you need to know about:



XMLType


A built-in object type, introduced in this section, which enables you to store XML documents in a database column or in a PL/SQL variable. XMLType was introduced in Oracle9i Database Release 1.


XQuery



A query language to use in retrieving and constructing XML documents. XQuery is new in Oracle Database 10g Release 2.


If you begin with these two things and drill down, you'll be led to many other XMLish topics you need to know about: XPath for referring to portions of an XML document, XML Schema for describing document structure, and so forth.




Using XMLType, you can easily create a table to hold XML data:



CREATE TABLE falls (
fall_id NUMBER,
fall XMLType
);



The fall column in this table is of XMLType
and can hold XML
data. To store XML data into this column, you must invoke the static CreateXML method, passing it your XML data. CreateXML accepts XML data as input and instantiates a new XMLType object to hold that data. The new object is then returned as the method's result, and it is that object that you must store in the column. CreateXML is overloaded to accept both VARCHAR2 strings and CLOBs as input.


Use the following INSERT statements to create three XML documents in the falls table:



INSERT INTO falls VALUES (1, XMLType.CreateXML(
'<?xml version="1.0"?>
<fall>
<name>Munising Falls</name>
<county>Alger</county>
<state>MI</state>
<url>
http://michiganwaterfalls.com/munising_falls/munising_falls.html
</url>
</fall>'));

INSERT INTO falls VALUES (2, XMLType.CreateXML(
'<?xml version="1.0"?>
<fall>
<name>Au Train Falls</name>
<county>Alger</county>
<state>MI</state>
<url>
http://michiganwaterfalls.com/autrain_falls/autrain_falls.html
</url>
</fall>'));

INSERT INTO falls VALUES (3, XMLType.CreateXML(
'<?xml version="1.0"?>
<fall>
<name>Laughing Whitefish Falls</name>
<county>Alger</county>
<state>MI</state>
</fall>'));



You can query XML data in the table using various XMLType methods. The existsNode method
used in the following example allows you to test for the existence of a specific XML node in an XML document. The built-in SQL EXISTSNODE function, also in the example, performs the same test. Whether you use the method or the built-in function, you identify the node of interest using an XPath expression.[*]

[*] XPath is a syntax that describes parts of an XML document. Among other things, you can use XPath to specify a particular node or attribute value in an XML document.


Both of the following statements produce the same output:



SQL> SELECT fall_id
2 FROM falls f
3 WHERE f.fall.existsNode('/fall/url') > 0;

SQL> SELECT fall_id
2 FROM falls
3 WHERE EXISTSNODE(fall,'/fall/url') > 0;


FALL_ID
----------
1
2



You can, of course, also work with XML data from within PL/SQL. In the following example, we retrieve the fall column for Munising Falls into a PL/SQL variable that is also of XMLType. Thus, we retrieve the entire XML document into our PL/SQL program, where we can work further with it. After retrieving the document, we extract and print the text from the /fall/url node.



<<demo_block>>
DECLARE
fall XMLType;
url VARCHAR2(100);
BEGIN
--Retrieve XML for Munising Falls
SELECT fall INTO demo_block.fall
FROM falls f
WHERE f.fall_id = 1;

--Extract and display the URL for Munising Falls
url := fall.extract('/fall/url/text( )').getStringVal;
DBMS_OUTPUT.PUT_LINE(url);
END;



We'd like to call your attention to the following two lines:



SELECT fall INTO demo_block.fall


Our variable name, fall, matches the name of the column in the database table. In our SQL query, therefore, we qualify our variable name with the name of our PL/SQL block.


url := fall.extract('/fall/url/text( )').getStringVal;


To get the text of the URL, we invoke two of XMLType's methods:



extract


Returns an XML document, of XMLType, containing only the specified fragment of the original XML document. Use XPath notation to specify the fragment you want returned.


getStringVal


Returns the text of an XML document.


In our example, we apply the getStringVal method to the XML document returned by the extract method, thus retrieving the text for the Munising Fall's URL. The extract method returns the contents of the <url> node as a XMLType object, and getStringVal then returns that content as a text string that we can display.


You can even index XMLType columns to allow for efficient retrieval of XML documents based on their content. You do this by creating a function-based index, for which you need the QUERY REWRITE privilege. The following example creates a function-based index on the first 80 characters of each falls name:



CREATE INDEX falls_by_name
ON falls f (
SUBSTR(
XMLType.getStringVal(
XMLType.extract(f.fall,'/fall/name/text( )')
),1,80
)
);



We had to use the SUBSTR function in the creation of this index. The getStringVal method returns a string that is too long to index, resulting in an ORA-01450: maximum key length (3166) exceeded error. Thus, when creating an index like this, you need to use SUBSTR to restrict the results to some reasonable length.


If you decide to use XMLType in any of your applications, be sure to consult Oracle's documentation for more complete and current information. The XML DB Developer's Guide for Oracle Database 10g Release 2 is an important, if not critical, reference for developers working with XML. The SQL Reference also has some useful information on XMLType and on the built-in SQL functions that support XML.




13.6.2. The URI Types



The URI types

introduced in Oracle9i Database Release 1 consist of a supertype and a collection of subtypes that provide support for storing URIs in PL/SQL variables and in database columns. UriType is the supertype, and a UriType variable can hold any instance of one of the subtypes:



HttpUriType



A subtype of UriType that is specific to HTTP URLs, which usually point to web pages.


DBUriType



A subtype of UriType that supports URLs that are XPath expressions.


XDBUriType



A subtype of UriType that supports URLs that reference Oracle XML DB objects. XML DB is Oracle's name for a set of XML technologies built into the database.


To facilitate your work with URIs, Oracle also provides a UriFactory
package that automatically generates the appropriate URI type for whatever URI you pass to it.


The URI types are created by the script named dbmsuri.sql $ORACLE_HOME/rdbms/admin. All the types and subtypes are owned by the user SYS. In Oracle9i Database Release 1, you must use the "SYS." prefix to reference the URI types. From Oracle9i Database Release 2 onwards, you do not need to use the "SYS." prefix.


The following code example demonstrates the use of HttpUriType:



DECLARE
WebPageURL HttpUriType;
WebPage CLOB;
BEGIN
--Create an instance of the type pointing
--to a message from Jonathan Gennick
WebPageURL := HttpUriType.createUri(
'http://gennick.com/message.plsql');

--Retrieve the message via HTTP
WebPage := WebPageURL.getclob( );

--Display the message
DBMS_OUTPUT.PUT_LINE((SUBSTR(WebPage,1,60)));
END;



The output from this code example will be:



Brighten the corner where you are.



For more information on the use of the UriType family, see Chapter 9, Accessing Data Through URIs, of the XML DB Developer's Guide for Oracle Database 10g Release 2.




13.6.3. The Any Types






















Beginning with Oracle9i Database Release 1, a family of types known as the Any types
enables you to write programs to manipulate data when you don't know the type of that data until runtime. Member functions support introspection, allowing you to determine the type of a value at runtime and to access that value.[*]

[*] An introspection function is one that you can use in a program to examine and learn about variables declared by your program. In essence, your program learns about itselfhence the term introspection.


The following predefined types belong to this family:



AnyData


Can hold a single value of any type, whether it's a built-in scalar datatype, a user-defined object type, a nested table, a large object, a varying array (VARRAY), or any other type not listed here.


AnyDataSet


Can hold a set of values of any type, as long as all values are of the same type.


AnyType


Can hold a description of a type. Think of this as an AnyData without the data.


The Any types are created by a script named dbmsany.sql found in $ORACLE_HOME/rdbms/admin, and are owned by the user SYS. As with the URI types, in Oracle9i Database Release 1 you must use a "SYS." prefix to reference the Any types, but this is no longer necessary beginning in Oracle 9i Database Release 2).


In addition to creating the Any types, the dbmsany.sql script also creates a package named DBMS_TYPES
that defines the constants in the following list. You can use these constants in conjunction with introspection functions
such as GETTYPE in order to determine the type of data held by a given AnyData or AnyDataSet variable. The specific numeric values assigned to the constants are not important; rely on the constants, not on their underlying values.



TYPECODE_DATE
TYPECODE_NUMBER
TYPECODE_RAW
TYPECODE_CHAR
TYPECODE_VARCHAR2
TYPECODE_VARCHAR
TYPECODE_MLSLABEL
TYPECODE_BLOB
TYPECODE_BFILE
TYPECODE_CLOB
TYPECODE_CFILE
TYPECODE_TIMESTAMP
TYPECODE_TIMESTAMP_TZ
TYPECODE_TIMESTAMP_LTZ
TYPECODE_INTERVAL_YM
TYPECODE_INTERVAL_DS
TYPECODE_REF
TYPECODE_OBJECT
TYPECODE_VARRAY
TYPECODE_TABLE
TYPECODE_NAMEDCOLLECTION
TYPECODE_OPAQUE



The following example creates two user-defined types representing two kinds of geographic features. The subsequent PL/SQL block then uses SYS.AnyType to define a heterogeneous array of features (i.e., each array element can be of a different datatype).


First, you'll need to create the following two types:



CREATE OR REPLACE TYPE waterfall AS OBJECT (
name VARCHAR2(30),
height NUMBER
);

CREATE OR REPLACE TYPE river AS OBJECT (
name VARCHAR2(30),
length NUMBER
);



Next, execute the following PL/SQL code block:



DECLARE
TYPE feature_array IS VARRAY(2) OF SYS.AnyData;
features feature_array;
wf waterfall;
rv river;
ret_val NUMBER;
BEGIN
--Create an array where each element is of
--a different object type
features := feature_array(
AnyData.ConvertObject(
waterfall('Grand Sable Falls',30)),
AnyData.ConvertObject(
river('Manistique River', 85.40))
);

--Display the feature data
FOR x IN 1..features.COUNT LOOP
--Execute code pertaining to whatever object type
--we are currently looking at. NOTE! Replace GENNICK
--with whatever schema you are using.
CASE features(x).GetTypeName
WHEN 'GENNICK.WATERFALL' THEN
ret_val := features(x).GetObject(wf);
DBMS_OUTPUT.PUT_LINE('Waterfall: '
|| wf.name || ', Height = ' || wf.height || ' feet.');
WHEN 'GENNICK.RIVER' THEN
ret_val := features(x).GetObject(rv);
DBMS_OUTPUT.PUT_LINE('River: '
|| rv.name || ', Length = ' || rv.length || ' miles.');
END CASE;
END LOOP;
END;



Finally, your output should appear as follows:



Waterfall: Grand Sable Falls, Height = 30 feet.
River: Manistique River, Length = 85.4 miles.



Let's look at this code one piece at a time. The features are stored in a VARRAY, which is initialized as follows:



features := feature_array(
AnyData.ConvertObject(
waterfall('Grand Sable Falls',30)),
AnyData.ConvertObject(
river('Manistique River, 85.40))
);



Working from the inside out and focusing on Grand Sable Falls, you can interpret this code as follows:



waterfall('Grand Sable Falls',30)


Invokes the constructor for the waterfall type to create an object of that type.


AnyData.ConvertObject(


Converts the waterfall object into an instance of SYS.AnyData, allowing it to be stored in our array of SYS.AnyData objects.


feature_array(


Invokes the constructor for the array. Each argument to feature_array is of type AnyData. The array is built from the two arguments we pass.


VARRAYs were discussed in Chapter 12, and you can read about object types in more detail in Chapter 25.


The next significant part of the code is the FOR loop in which each object in the features array is examined. A call to:



features(x).GetTypeName



returns the fully qualified type name of the current features object. For user-defined objects, the type name is prefixed with the schema name of the user who created the object. We had to include this schema name in our WHEN clauses; for example:



WHEN 'GENNICK.WATERFALL' THEN



If you're running this example on your own system, be sure to replace the schema we used (GENNICK) with the one that is valid for you.


For built-in types such as NUMBER, DATE, and VARCHAR2, GetTypeName will return just the type name. Schema names apply only to user-defined types (i.e., those created using CREATE TYPE).



Once we determined which datatype we were dealing with, we retrieved the specific object using the following call:



ret_val := features(x).GetObject(wf);



In our example, we ignored the return code. There are two possible return code values:



DBMS_TYPES.SUCCESS


The value (or object, in our case) was successfully returned.


DBMS_TYPES.NO_DATA


No data was ever stored in the AnyData variable in question, so no data can be returned.


Once we had the object in a variable, it was an easy enough task to write a DBMS_OUTPUT statement specific to that object type. For example, to print information about waterfalls, we used:



DBMS_OUTPUT.PUT_LINE('Waterfall: '
|| wf.name || ', Height = ' || wf.height || ' feet.');



For more information on the "Any" family of types:


  • Visit Chapter 25, which examines the Any datatypes from an object-oriented perspective.

  • Check out Oracle's PL/SQL Packages and Types Reference and the SQL Reference.

  • Try out the anynums.pkg and anynums.tst scripts on the book's web site.


From an object-oriented design standpoint, there are better ways to deal with multiple feature types than the method we used in this section's example. In the real world, however, not everything is ideal, and our example does serve the purpose of demonstrating the utility of the SYS.AnyData predefined object type.











    No comments:

    Post a Comment