You are on page 1of 16

Oracle

Solutions for High-End


Oracle® DBAs and Developers Professional

Object Types and


Inheritance: From
DBMS_OUTPUT to
Object-Oriented
Stream I/O April 2004
Volume 11, Number 4

Gary Menchen 1 Object Types and Inheritance:


From DBMS_OUTPUT to
Object-Oriented Stream I/O
Gary Menchen
Using DBMS_OUTPUT as a starting point, the inheritance and polymorphism of
Oracle’s object types are used to construct an object type hierarchy patterned 7 Exploring Options: Multiple
after the Java Stream OutputStream classes. Gary Menchen’s examples provide Paths to an Answer
convenient interfaces for debugging, as well as for generating output to CLOBs Steven Feuerstein
and BLOBs. 11 Using the Oracle9i R2
Templates Feature of DBCA

I
N the past two issues of Oracle Professional, I’ve written a pair of articles on Windows 2000
about Oracle’s object types that illustrated how they can be intelligent data Stan Novinsky
structures—that is, data structures bound to procedures and functions. 14 LOG4PLSQL Oracle Database
As a software tool, that was pretty much the limit of their capabilities prior Logging Tools
to Oracle9i. With Oracle9i, however, object types now have an even more Guillaume Moulard
powerful feature: inheritance.
16 April 2004 Downloads
In this article I discuss the construction of a “class hierarchy”: a group of
object types for managing unformatted output. This project began when I first
started thinking about writing an object type wrapper for dbms_output. I
initially had two primary motivations. First, I use dbms_output for debugging
purposes, but I also send the same information to other destinations, Indicates accompanying files are available online at
www.pinnaclepublishing.com.
particularly CLOBs. I thought object types could be used to make the
destination of logging information transparent. Second, I not instantiable not final member procedure
write( pText in varchar2),
wanted to have more control over the state of output than member procedure writeLn( pText in varchar2),
just “on” and “off”—it’s common for me at entry to a not instantiable member procedure
writeRaw( pRaw in raw ),
procedure to want to enable or disable output, and to -- enable and disable push 1 and 0 respectively.
restore the previous state on exit. Using a stack to member procedure push( pInteger in number ),
member procedure enable,
maintain state is cleaner. member procedure disable,
The main components of dbms_output are PUT -- restore does a pop from the state stack,
-- preventing any underflows.
and PUT_LINE (I don’t use ENABLE or DISABLE). member procedure restore,
Obviously these are trivial to implement. However, add a -- isActive returns true if active
member function isActive return boolean,
constructor, flush, and close procedure and you have the -- some subtypes will do buffering, and may
basic structure of the OutputStream classes in Java. Add a -- require a method to flush the buffer
member procedure flush,
couple of methods for maintaining state and you have the member procedure initializeStateStack
framework of an object type that can be implemented for
) NOT INSTANTIABLE NOT FINAL
many different types of output destinations, and for
managing and transforming that output as well. Once this There are a number of important points illustrated in
is realized, dbms_output becomes the least important use this type specification.
for these object types. All of the procedures declared in this specification
I begin with the base object type, which I’ve named will become part of the definition of any new object
OutputChannel, as shown in Listing 1. I use Channel types declared using OutputChannel. This is the basic
instead of Stream because Oracle is already using the term meaning of inheritance: Subtypes begin with all of the
“Stream” for some very different functionality. The root variables, procedures, and functions declared in their
object type is declared as NOT SUBSTANTIABLE NOT parent. They can add new variables, procedures, and
FINAL. This means that I can’t actually create an instance functions, and they can override any procedures or
of an OutputChannel, but that I can define subtypes that functions in their parent—that is, implement them in
inherit from it. OutputChannel provides the template for some different way. For example, in OutputChannel
all of its subtypes. the writeLn procedure is self.write( pText || chr(13)
Even though I can’t create an instance of an ||chr(10)), even though the write procedure itself is
OutputChannel, I can have variables declared as not implemented. For most subtypes this will be
OutputChannel, and I can pass parameters of adequate, but when a different behavior is needed,
OutputChannel type. Any variable that’s of a type writeLn can be overridden, as you’ll see in the following
declared under OutputChannel can be stored in an DbmsOutputChannel type. Only procedures and
OutputChannel variable or parameter, and any operation functions declared as FINAL cannot be overridden.
declared in the OutputChannel class can be performed Secondly, the procedures declared as NOT
on descendent type variables without actually knowing INSTANTIABLE aren’t implemented in the TYPE
their specific type. BODY. They must be implemented in any subtype of
OutputChannel that’s declared as INSTANTIABLE.
Declaring the OutputChannel object type Because their presence is guaranteed in any subtype
I begin by declaring a VARRAY of INTEGER, to base my variable that can be assigned to an OutputChannel
state stack on: variable, PL/SQL allows those procedures to be invoked
regardless of which subtype is involved.
CREATE OR REPLACE TYPE INTEGERLIST
AS VARYING ARRAY (20000) OF INTEGER
Procedure foo( pChannel IN OUT NOCOPY OutputChannel)
is
This is a generic integer VARRAY that I use for a begin
wide variety of purposes. If I were declaring a VARRAY pChannel.writeLn(‘I wonder what my actual type is’);
end;
for use only as a stack for maintaining state in the
OutputChannel type, the size would be reduced by a OutputChannel doesn’t have a constructor. It’s not
factor of 100. instantiable, so it would seem pointless to have a
constructor, but even if I did declare a constructor, I’d still
Listing 1. OutputChannel object type specification. place any actual initialization in a separate procedure
(initializeStateStack in this example) so that it can be
CREATE OR REPLACE TYPE OUTPUTCHANNEL called by descendent types. Constructors aren’t inherited,
AS OBJECT
and I can find no mechanism that will allow a subtype
(
state_stack integerList, -- Varray of integer
constructor to invoke its parent constructor upon itself, so
member procedure close, if significant initialization is done within a constructor it

2 Oracle Professional April 2004 www.pinnaclepublishing.com


must be repeated by each subtype’s constructor, if it’s SQL> set serveroutput on
SQL> begin
relevant to the subtype. Because of this, it seems best to 2 dbms_output.put_line('I am visible');
place the initialization in a separate procedure so that 3 dbms_output.put('I am not');
4 end;
it can be invoked by the constructor and all subtype 5 /
constructors. I am visible
PL/SQL procedure successfully completed.
If you were developing a complex application
environment with many object type hierarchies, it would WriteRaw is implemented as a straight pass-through
be wise to establish a policy to handle this problem— to the write procedure. This will cause the raw parameter
something like “All type declarations must include an to be displayed as a hexadecimal string, which is
Init[type-name] procedure that does all initialization that appropriate behavior for dbms_output.
might be required by its own subtype. If it is itself a Close is implemented as dbms_output.put_line(''), to
subtype, then this object type’s initialization routine must ensure that the buffer used by dbms_output is cleared.
also call its immediate parent’s initialization procedure.” Static procedures are much like the procedures of
This would limit what each subtype needs to know about conventional packages—they’re invoked by using
its ancestry and its parent’s internal workings. the following syntax: type-name.procedure-name. So,
Another point, not directly related to object types: I in our case, we’ll use the following invocation:
use different procedure names to write varchar2 and raw DbmsOutputChannel.test. Having the test routine as
output. I would have preferred to overload the write part of the type makes it very easy to repeat tests and
procedure, rather than use a different procedure name to review just what aspects of the type you’ve tested.
output raw values. However, varchar2 and raw are
insufficiently distinct to allow that without forcing the use An OutputChannel for CLOBs
of named parameters each time they’re called—that is, The declaration for ClobOutputChannel follows the same
pChannel.write( pText =>... or pChannel.write( pRaw=>... pattern and is shown in Listing 3.

Wrapping dbms_output in an
OutputChannel subtype Listing 3. The ClobOutputChannel specification.
Now let’s define the simplest subtype, shown in Listing 2.
CREATE OR REPLACE
The specification for all subtypes will be very similar to TYPE CLOBOUTPUTCHANNEL UNDER OUTPUTCHANNEL
this, because all must implement the procedures declared (
the_clob clob,
as not instantiable in the parent type. constructor function ClobOutputChannel(
pClob in out nocopy clob) return self as result,
overriding member procedure write(pText in varchar2),
Listing 2. DbmsOutputChannel specification. overriding member procedure writeRaw( pRaw in raw),
-- and a function to retrieve the clob if not passed
-- by reference
CREATE OR REPLACE member function getClob return clob,
TYPE DBMSOUTPUTCHANNEL UNDER OUTPUTCHANNEL static procedure test
(
constructor function DbmsOutputChannel ) instantiable not final
return self as result, /
overriding member procedure write(pText in varchar2),
overriding member procedure
writeLn(pText in varchar2), Fill-in-the-blanks programming
overriding member procedure writeRaw(pRaw in raw),
overriding member procedure close, If you check the source code available in the Download,
static procedure test you’ll see how simple this is to implement. One of the
) instantiable not final
/ advantages of working with hierarchies of object types
is that the parent establishes a template. All the
The constructor needs no parameters because there’s subtype must do is fill in those portions of the template
nothing to initialize—its only content is a call to the that apply specifically to it. Think of it as “fill-in-the-
InitializeChannel procedure implemented by Channel. blanks” programming.
The Flush and Close procedures aren’t implemented since The constructor function takes a CLOB as a
with dbms_output they have no function—so the null parameter. Even though the NOCOPY hint is used, I
procedures implemented at the parent level are fine. haven’t found a way of enforcing that a temporary CLOB
WriteLn is implemented, overriding the writeLn be passed by reference, which is why the getClob function
procedure in the OutputChannel type, because has been added. Check the listings associated with this
dbms_output.put(pText ||chr(13)||chr(10)) isn’t the article for an illustration: If you create a temporary CLOB,
same as dbms_output.put_line(pText)—if you end a script pass the locator to the ClobOutputChannel constructor,
in SQL*Plus with a dbms_output.put, instead of put_line, and write to it using one of the ClobOutputChannel
the result will never be displayed, as you can see here: procedures; what you write won’t be visible to the

www.pinnaclepublishing.com Oracle Professional April 2004 3


original LOB locator variable. Chaining types
Because the CLOB is created outside of the object OutputChannel subtypes can be used not only to send
type, I didn’t override the default close procedure (which output to a wide variety of destinations, but also to
does nothing). The decision as to whether or not to use manage the output regardless of the actual destination.
the dbms_lob open and close procedures is left to the user. These sorts of OutputChannel subtypes can be used to
I also didn’t override the writeLn procedure, since in this modify the text as it’s written, passing the modified data
case, the default procedure declared in OutputChannel onto another OutputChannel subtype, or they could
produces the desired result. control the flow of data. Instead of writing to a final
If you’re working with LOBs that are table-based, destination (such as a LOB), these sorts of OutputChannel
remember that the LOB locators don’t span transactions. descendents write to another OutputChannel that would
Once a commit is issued, the LOB locator can no longer normally be specified in their constructors.
be used. If this is an issue, it would probably be best to I think the most useful to implement is buffering. As
create a subtype that receives a primary key into the table I discussed in “A First Look at Object Types” (see the
containing the CLOBs and uses autonomous transactions February 2004 issue of Oracle Professional), it’s a good
when it does writes, reloading the LOB locator after each idea to buffer output to table-based LOBs because Oracle
commit. If you don’t allow for this, you’re likely to see itself does i/o to LOBs in blocks, referred to as chunksize
the following: by Oracle.
Buffering would also make it practical to implement
ORA-22990: LOB locators cannot span transactions a subtype for table-based LOBs of either type that uses
autonomous transactions for its write procedures.
Other OutputChannel types Because LOB locators can’t span transactions, one needed
There are numerous other OutputChannel subtypes for a table-based LOB would need to be reset after
that we could define. I’ve included an implementation each commit.
of a BlobOutputChannel in the Download, and there
are some other obvious variations as well, such as a A BufferOutputChannel object type
PipeOutputChannel. So, given that we have some number In the OutputChannel types, the data being output can be
of different OutputChannel subtypes defined, what can either varchar2 or raw. Since write procedures for both
we do with them? data types are defined in the parent type, it’s possible to
First of all, we can use them in a generic way write a single BufferOutputChannel type that buffers both
any time we need to send stream-oriented output to data types in a single raw buffer, and passes along the
a destination. result using writeRaw.
The procedure log in Listing 4 doesn’t know or The constructor for the BufferOutputChannel type
care whether pChannel is a ClobOutputChannel, a created in Listing 5 takes a parameter of OutputChannel
DbmsOutpoutChannel, or some other type derived from (which can be any OutputChannel subtype), as well as an
the OutputChannel parent. It can perform upon pChannel integer specifying the amount of data to be held in the
any operation that’s defined in the OutputChannel buffer before passing it along to the OutputChannel
specification. This means that you can redirect output in a contained in its child variable.
manner that’s completely transparent to the code that’s
producing the output stream.
Listing 5. BufferOutputChannel specification.

Listing 4. Use of OutputChannel variables as parameters. CREATE OR REPLACE


TYPE BUFFEROUTPUTCHANNEL UNDER OUTPUTCHANNEL
(
1 declare buffer raw(32767),
2 vChannel CloboutputChannel; writeSize integer,
3 vClob clob; child OutputChannel,
4 procedure log(pChannel in out nocopy OutputChannel, constructor function BufferOutputChannel(
5 pText in varchar2) is pChannel in out nocopy OutputChannel,
6 begin pBufferSize in integer default 32767)
7 pChannel.writeLn(pText); return self as result,
8 end; overriding member procedure close,
9 begin overriding member procedure write(
10 dbms_lob.createTemporary(vClob,true); pText in varchar2),
11 vChannel := new ClobOutputChannel( vClob ); overriding member procedure writeRaw( pRaw in raw),
12 log(vChannel,'Hello world'); overriding member procedure flush
13 dbms_output.put_line(vChannel.getClob()); ) instantiable not final
14 dbms_lob.freeTemporary(vClob); /
15* end;
SQL> /
Hello world
For LOBs, the ideal buffer size is some multiple of the
PL/SQL procedure successfully completed. chunksize less than or equal to the maximum size of the

4 Oracle Professional April 2004 www.pinnaclepublishing.com


buffer (see the script in Listing 6 for an example of this and compare the performance with three different
calculation). However, a buffer might be used for variations: writing directly to a CLOB (fastest), writing
purposes other than buffering writes to a LOB, so using ClobOutputChannel, and writing using
BufferOutputChannel makes no assumptions about what BufferOutputChannel attached to a ClobOutputChannel.
an ideal buffer size might be. If no buffer size is specified,
the maximum length of a raw variable is used.
Listing 7. Use and evaluation of write methods.

Listing 6. Buffering output. SQL> declare


2 vClob clob;
3 vClobOutput ClobOutputChannel;
overriding member procedure writeRaw(pRaw in raw) 4 vBuffer BufferOutputChannel;
is 5 vLength integer;
vAdd integer; 6 vString varchar2(256) := rpad('X',256);
vRaw raw(32767); 7 vStartTime integer;
vBufferLen integer; 8 vWriteSize integer;
vRawLen integer; 9 vMax integer := 5000;
begin 10 procedure showTime(pLabel in varchar2) is
if self.isActive then 11 begin
vRaw := pRaw; 12 dbms_output.put_line(pLabel||' Timing: '
vBufferLen := nvl(utl_raw.length(self.buffer),0); 13 || (dbms_utility.get_time - vStartTime)/100
vRawLen := nvl(utl_raw.length(vRaw),0); 14 ||' seconds');
while vBufferLen + vRawLen >= self.writeSize loop 15 end;
-- pad the buffer
16 begin
vAdd := self.writeSize - vBufferLen;
17 dbms_output.put_line('Timing for '
self.buffer :=
18 ||to_char(vMax)|| ' writes');
utl_raw.concat(self.buffer,
utl_raw.substr(vRaw,1,vAdd)); 19 dbms_lob.createTemporary(vClob,true);
self.flush(); 20 -- use dbms_lob.writeAppend
vRawLen := vRawLen - vAdd; 21 vLength := length(vString);
vBufferLen := 0; 22 vStartTime := dbms_utility.get_time;
if vRawLen > 0 then 23 for i in 1 .. vMax loop
vRaw := utl_raw.substr(vRaw,vAdd + 1, 24 dbms_lob.writeAppend(vClob, vLength, vString);
vRawLen); 25 end loop;
else 26 showTime('dbms_lob.writeAppend');
vRaw := utl_raw.cast_to_raw(''); 27 -- Use ClobOutputChannel (without buffer)
end if; 28 dbms_lob.trim(vClob,0);
end loop; 29 vClobOutPut := new ClobOutputChannel(vClob);
-- place whatever remains in the buffer 30 vStartTime := dbms_utility.get_time;
self.buffer := utl_raw.concat(self.buffer,vRaw); 31 for i in 1..vMax loop
end if; -- if isActive 32 vClobOutput.write(vString);
end; 33 end loop;
34 showTime('ClobOutputChannel');
35 -- and buffered
Buffering is done inside the write procedures. 36 dbms_lob.trim(vClob,0);
Write and WriteLn pass through to the writeRaw 37 vClobOutPut := new ClobOutputChannel(vClob);
38 -- Calculate buffersize
procedure defined in Listing 6. In writeRaw the length 39 vWriteSize := trunc(32767
of the pText or pRaw parameter is added to the length 40 / dbms_lob.getChunkSize(vClob))
41 * dbms_lob.getChunkSize(vClob);
of the content already in the buffer. While that total 42 vBuffer := new BufferOutputChannel(vClobOutput,
is greater than or equal to the writeSize variable, a 43 vWritesize);
44 vStartTime := dbms_utility.get_time;
substring of length writeSize is passed along to the 45 for i in 1 .. vMax loop
OutputChannel contained in the child variable using 46 vBuffer.write(vString);
47 end loop;
writeRaw. If or when the total length of whatever 48 vBuffer.flush();
remains in the buffer, and the remaining content of the 49 showTime('Buffered writeLn');
50 end;
parameter, is less than writeSize, the parameter content 51 /
is concatenated to the buffer. Timing for 5000 writes
dbms_lob.writeAppend Timing: .72 seconds
Buffering has other purposes than just writing to ClobOutputChannel Timing: 34.92 seconds
LOBs. For example, suppose you wanted to create an Buffered writeLn Timing: 1.67 seconds

OutputChannel subtype using the dbms_obfuscation PL/SQL procedure successfully completed.


package to encrypt text—the encryption procedures in
that package require blocks of eight characters to operate This script illustrates several important points.
on. Using a buffer that’s sized at some multiple of 8 First of all, the difference in performance between the
would handle that very nicely. Flush would need to do unbuffered and the buffered writes is enormous—a
nothing in this case, while the close procedure would factor of 20 with writes of 256 characters each.
need to handle any portion of the input that’s less than The instance of ClobOutputChannel that’s passed to
eight characters. the constructor for BufferOutputChannel is declared
Let’s look at the script in Listing 7, which displays separately. Because it’s an IN OUT parameter, the
how OutputChannels are chained together and used, constructor for ClobOutputChannel can’t be placed in-line

www.pinnaclepublishing.com Oracle Professional April 2004 5


in the BufferOutputChannel constructor, as you can see in return OutputChannel
) instantiable not final
Listing 8. /

All of the procedures in this subtype simply traverse


Listing 8. Attempting to use an object type constructor in a aList, invoking the same procedure for each element of
parameter list. the VARRAY. The code for the write procedure will serve
as an illustration for all.
SQL> declare
2 vClob clob;
overriding member procedure write( pText in varchar2)
3 vChannel BufferOutputChannel;
is
4 begin begin
5 dbms_lob.createTemporary(vClob,true); if self.isActive then
6 vChannel := new BufferOutputChannel( for i in self.aList.first .. self.aList.last loop
7 new ClobOutputChannel( vClob),1024); self.aList(i).write( pText );
8 end; end loop;
9 / end if;
new ClobOutputChannel( vClob),1024); end write;
*
ERROR at line 7:
The self.isActive function is checked before
ORA-06550: line 7, column 9:
PLS-00363: expression 'CLOBOUTPUTCHANNEL' passing along the input, and will be rechecked by each
cannot be used as an assignment OutputChannel instance contained in aList. This allows
target
ORA-06550: line 6, column 3: you to turn output on or off globally. If you wanted to be
PL/SQL: Statement ignored able to activate or deactivate individual OutputChannels
contained in an OutputChannelCollection, you’d need to
Creating collections of the object types directly access the variables as stored in the collection—
Let’s finish up with one last subtype that illustrates a that is, vCollection.aList[1].disable()—not the original
principle that could be applied whenever you need to outputChannel variable that was passed to the
invoke the same operation upon a group of object type OutputChannelCollection in its constructor or via the
variables that all inherit from a common parent. With addChannel procedure. A better solution would be to
OutputChannels we can imagine, for example, that we change the addChannel procedure to a function that
want to send output to a CLOB and to a pipe at the same returns a number representing the individual element’s
time (I haven’t included a PipeOutputChannel in this offset in the VARRAY, and add overloaded versions of
article—it makes no sense to do so without having a the other procedures that include a handle parameter
corresponding PipeInputChannel). corresponding to that number. In the preceding example I
Start with defining a VARRAY of the hierarchy’s added a getChannel function to retrieve the individual
root type. components, but that should be viewed as nothing more
than a kludge I put into place for use in a test script
CREATE OR REPLACE
TYPE OUTPUTCHANNELCOLLECTION available in the listings.
AS VARYING ARRAY (20) OF CHANNEL.OUTPUTCHANNEL
/
What’s next
In the course of this article I’ve defined half a dozen
Next, create another subtype under OutputChannel
object types, but the possibilities have hardly been
that includes a variable of OutputChannelList type, as in
exhausted. What I haven’t gone into are types that
Listing 9.
actually transform the stream of text being processed.
Encryption using the dbms_obfuscation package, base64
Listing 9. OutputChannelCollection specification. encoding now included in the utl_encode supplied
package, or compression using one of the Java data
CREATE OR REPLACE compression routines are all examples of transformations
TYPE OUTPUTCHANNELCOLLECTION UNDER OUTPUTCHANNEL
( that could be implemented using OutputChannel
aList OutputChannelList, subtypes, making their use almost transparent to the
constructor function OutputChannelCollection(
pChannel in out nocopy OutputChannel ) developer. Subtypes of that nature, of course, would also
return self as result, require a method of un-transforming the contents—an
overriding member procedure close,
overriding member procedure write( InputChannel, in other words. I hope to explore that topic
pText in varchar2), in an upcoming issue of Oracle Professional. ▲
overriding member procedure writeLn(
pText in varchar2),
overriding member procedure writeRaw(pRaw in raw), 404MENCHEN.ZIP at www.pinnaclepublishing.com
overriding member procedure flush ,
member procedure addChannel( pChannel IN OUT
NOCOPY outputChannel), Gary Menchen is a senior programmer analyst at Dartmouth College in
member function getChannel(pChannel in number) Hanover, New Hampshire. Gary.E.Menchen@Dartmouth.edu.

6 Oracle Professional April 2004 www.pinnaclepublishing.com


Oracle
Professional

Exploring Options: Multiple


Paths to an Answer
Steven Feuerstein
As the PL/SQL language grows increasingly rich and nuanced, INSERT INTO tickertable
(ticker, pricedate, pricetype, price
we developers confront both opportunity and dilemma. )
VALUES (rec.ticker, rec.trade_date, 'O'
There can be many different paths to a solution and multiple , rec.open_price
alternative implementations for a particular requirement. );
Various factors collude and sometimes collide in making the INSERT INTO tickertable
best decision, including runtime efficiency and simplicity of (ticker, pricedate, pricetype, price
)
code (and related maintenance costs). In this article, Steven VALUES (rec.ticker, rec.trade_date, 'C'
Feuerstein tackles a specific challenge and explores the , rec.close_price
);
different solutions possible. END LOOP;
END;

I
need to pivot data in a stock table. The structure of
The concept is straightforward, and the code is
this table is:
simple, as well. There is, however, a problem: This isn’t
CREATE TABLE stocktable ( a terribly efficient implementation. I’ll be comparing
ticker VARCHAR2(20), the performance of alternative approaches to solving
trade_date DATE,
open_price NUMBER, the pivot problem in this article. To do so, I’ll fill up the
close_price NUMBER stock table with 100,002 rows, as you can see in Listing
);
2. And with this data in place, it takes 36 seconds to
In other words, each row contains a ticker symbol, pivot the data using the good old cursor FOR loop in
the date of trade, the open price, and the close price on pivot_stock_data1. Given that my application will need
that day. I need to “pivot” this data, which in this case to pivot 10,000,000 rows of data, this performance is
means that I need to take a single row in stocktable and unacceptable. So what are my options?
transform it into two rows in the price table defined here:
Listing 2. Populating the stock table.
CREATE TABLE tickertable (
ticker VARCHAR2(20),
pricedate DATE, BEGIN
pricetype VARCHAR2(1), INSERT INTO stocktable
price NUMBER VALUES ('ORCL', SYSDATE, 13, 13.5);
);
INSERT INTO stocktable
VALUES ('MSFT', SYSDATE, 27, 27.04);
This pivot process shouldn’t conceptually cause any
FOR indx IN 1 .. 100000
reader of Oracle Professional to lie awake in bed at night. LOOP
There is, in fact, a very straightforward approach to INSERT INTO stocktable
VALUES ('STK' || indx, SYSDATE, indx, indx + 15);
performing this transformation, shown in Listing 1. What END LOOP;
could be simpler? Set up a cursor FOR loop to run COMMIT;
through the table and convert one row into two rows in END;
/
the tickertable.
Transformative functions to the rescue?
Listing 1. The traditional, aka “old fashioned,” approach to In the May 2002 issue of Oracle Professional (available on
pivoting the data. the Web site to subscribers), Bryn Llewellyn and I wrote
about the new table function capabilities in Oracle9i. A
PROCEDURE pivot_stock_data1 table function is a function that can be called in the FROM
IS
BEGIN clause of a SELECT statement. It accepts as its parameter a
FOR rec IN (SELECT * result set (rows and columns of data) in the form of a
FROM stocktable)
LOOP cursor variable, and returns a nested table.

www.pinnaclepublishing.com Oracle Professional April 2004 7


Table functions offer a number of potentially useful EXIT WHEN dataset%NOTFOUND;
out_obj.ticker := in_rec.ticker;
features, including: out_obj.pricetype := 'O';
out_obj.price := in_rec.open_price;
• Parallel execution—You can execute the function in out_obj.pricedate := in_rec.trade_date;
parallel within the context of a parallel query, when retval.EXTEND;
retval (retval.LAST) := out_obj;
the function is defined as a PIPELINED program. out_obj.pricetype := 'C';
out_obj.price := in_rec.close_price;
This can greatly improve performance, since in out_obj.pricedate := in_rec.trade_date;
previous releases of Oracle, the use of a PL/SQL retval.EXTEND;
retval (retval.LAST) := out_obj;
function in a query forced serialization. END LOOP;
• Data transformation—Since the function can be called CLOSE dataset;
from within the FROM clause and accepts as its
RETURN retval;
parameter a set of data (logically equivalent to a END;
query), the function can perform arbitrarily complex /

and multiple transformations of data without ever


having to deposit the intermediate results in PL/SQL The name of the table function is stockpivot_nopl, to
program data structures. indicate that it’s not a PIPELINED function. This means
that it can’t execute in parallel; it still may, however, act as
Given these advantages, perhaps I can use a table a transformative function, which I demonstrate in the
function in place of the cursor FOR loop and see some INSERT-SELECT FROM statement here:
improvement.
INSERT INTO tickertable
Listing 3 shows an implementation of a table function SELECT *
FROM TABLE
that pivots the stock data into two tickertable rows. It (stockpivot_nopl
relies on a number of other elements, described here: (CURSOR (SELECT *
FROM stocktable)));
• An object type with the same structure as the
tickertable. To use table functions, I must define the
This single SQL statement replaces all the code shown
RETURN datatype as a nested table of object TYPEs,
in Listing 1! To understand how this INSERT can do all
hence this type:
the work of the cursor FOR loop, you must start from the
CREATE TYPE tickertype AS OBJECT (
“center” of the statement and work outwards. On lines 5
ticker VARCHAR2 (20) and 6, I query all the rows from the stocktable and place
, pricedate DATE
, pricetype VARCHAR2 (1) that query inside the CURSOR operator. CURSOR tells
, price NUMBER Oracle to treat that query as if it were a result set in
);
/ PL/SQL produced by a REF CURSOR or cursor variable.
That data (the contents of stocktable) is then passed
• A nested table TYPE based on the object type: as a parameter to the stockpivot_nopl function (line 4).
This function takes the 100,002 rows from stocktable
CREATE TYPE tickertypeset AS TABLE OF tickertype; and pivots them into 200,004 rows in a collection. The
/
collection is passed back to the outer query and, in line 3,
• A package that defines the REF CURSOR that I’ll use the TABLE operator instructs the SQL engine to treat that
for the IN parameter of the function: collection as if it were a relational table.
From that point, we’re back on familiar ground. The
CREATE OR REPLACE PACKAGE refcur_pkg SELECT * in line 2 retrieves all the data and passes it
IS
TYPE refcur_t IS REF CURSOR
directly into the tickertable. And when I run this code
RETURN stocktable%ROWTYPE; to populate the tickertable, I find that it only takes
END refcur_pkg;
/ approximately 14.4 seconds.
That’s a solid improvement. Can we do even better?
Well, what about using a PIPELINED function that can
Listing 3. Use of a table function to perform a pivot.
run in parallel? Listing 4 shows the changes required in
the stockpivot_nopl to use pipelining. If you haven’t seen
CREATE OR REPLACE FUNCTION stockpivot_nopl (
dataset refcur_pkg.refcur_t) a PIPELINED function before, you’re in for several
RETURN tickertypeset
IS
surprises. First and foremost, look at the bottom of the
out_obj tickertype := tickertype (NULL, NULL function. Notice the RETURN statement—it doesn’t seem
, NULL, NULL);
in_rec dataset%ROWTYPE; to be returning anything at all! In fact, by the time the
retval tickertypeset := tickertypeset (); PL/SQL runtime engine gets to this line, it doesn’t have
BEGIN
retval.DELETE; anything left to return. And that’s because I’ve used the
new PIPE ROW statement within the body of the loop to
LOOP
FETCH dataset INTO in_rec; return the data asynchronously to the termination of the

8 Oracle Professional April 2004 www.pinnaclepublishing.com


function itself. In other words, while the function is IS
curvar sys_refcursor;
executing—and before it’s completed—the PIPE ROW mystock tickertypeset := tickertypeset ();
indx PLS_INTEGER;
statement will send the data back to the calling program. BEGIN
This is very useful when you’re running the function OPEN curvar
FOR
simultaneously in a dozen different processes, but it SELECT *
doesn’t actually do us much good in this situation. FROM stocktable;

You see, I’m not running this function in parallel. I’m mystock := stockpivot_nopl (curvar);
indx := mystock.FIRST;
calling it from my session just once, and I’m not going to
be doing anything with it until the function is completely LOOP
EXIT WHEN indx IS NULL;
finished. I have found, in fact, that in this situation, the
non-pipelined version of the function generally runs just a INSERT INTO tickertable
(ticker
bit more efficiently. The stockpivot_pl program is , pricedate
definitely and dramatically faster than the cursor FOR , pricetype
, price
loop approach, but the PIPELINED syntax doesn’t give )
VALUES (mystock (indx).ticker
you any advantage. , mystock (indx).pricedate
, mystock (indx).pricetype
, mystock (indx).price
);
Listing 4. A pipelined pivot function.
indx := mystock.NEXT (indx);
CREATE OR REPLACE FUNCTION stockpivot_pl ( END LOOP;
dataset refcur_pkg.refcur_t END;
)
RETURN tickertypeset PIPELINED
IS What about bulk processing?
out_obj tickertype := tickertype (NULL, NULL, NULL, NULL);
in_rec dataset%ROWTYPE;
Whenever you’re executing multiple rows of DML in a
BEGIN PL/SQL program (and you’re using Oracle8i 8.1.7 and
LOOP
FETCH dataset INTO in_rec; above), you should always consider FORALL as an option
EXIT WHEN dataset%NOTFOUND; for executing that DML. With FORALL, you can process
-- first row multiple DML statements within the SQL engine, cutting
out_obj.ticker := in_rec.ticker;
out_obj.pricetype := 'O'; down dramatically on “context switches” (between SQL
out_obj.price := in_rec.open_price; and PL/SQL), which can degrade performance.
out_obj.pricedate := in_rec.trade_date;
In fact, I imagine that many readers looked at the
PIPE ROW (out_obj);
code in Listing 5 and said to themselves, “What a cheater,
-- second row Steven is! He could make that program much faster by
out_obj.pricetype := 'C';
out_obj.price := in_rec.close_price; using FORALL.” Let’s see if that’s the case.
out_obj.pricedate := in_rec.trade_date; I’ll now take Listing 5 and change it to use FORALL.
PIPE ROW (out_obj); It’s not as easy as it first might seem. The code is shown in
END LOOP;
Listing 6. I populate the mystock nested table the same
CLOSE dataset; way that I did in Listing 4. Now, you might think I could
RETURN;
END; move immediately to a FORALL statement. That’s not
/ possible, unfortunately, because there’s a restriction in
FORALL: I cannot reference individual attributes or fields
One of the key reasons that the transformative
of a collection of records or objects.
function is so efficient has to do with the avoidance of
In other words, I can’t execute code that looks
relying on intermediate data structures in PL/SQL.
like this:
To drive that point home, consider the implementation
in Listing 5. In this program, I use the efficient FORALL indx IN
stockpivot_nopl to pivot the data, and then dump the l_tickertable.FIRST .. l_tickertable.LAST
INSERT INTO tickertable
data into a local nested table, mystock. I then use a FOR (ticker, price)
VALUES (
loop to perform my inserts. This program takes more l_tickertable (indx).ticker
than 43 seconds. It’s even slower than the cursor FOR , l_tickertable (indx).price);
loop approach!
So I either have to break up my single nested table
into four different nested tables (ugly!) or... I could take
Listing 5. Relying on intermediate data structures in PL/SQL. advantage of another really nice Oracle9i feature: INSERT
a record, as opposed to individual column values.
PROCEDURE intermediate_structure_pivot
/* I’ll go this second route, but in order to do that I’ll
Use a nested table to retrieve the pivoted data
and then transfer to the database table.
need to copy the nested table of objects into an associative
*/ array of records. Once I’ve done this, I can then use the
www.pinnaclepublishing.com Oracle Professional April 2004 9
FORALL statement. The result is a significant boost in FROM stocktable;

performance. I can pivot 100,002 rows in approximately 18 FOR indx IN l_stocktable.FIRST .. l_stocktable.LAST
LOOP
seconds. It’s not as fast as the transformative function, but l_index := l_tickertable.COUNT + 1;
it’s a major improvement over the other implementations l_tickertable (l_index).ticker :=
l_stocktable (indx).ticker;
we’ve explored. l_tickertable (l_index).pricedate :=
l_stocktable (indx).trade_date;
l_tickertable (l_index).pricetype := 'O';
l_tickertable (l_index).price :=
Listing 6. Using FORALL and an intermediate collection. l_stocktable (indx).open_price;
l_index := l_tickertable.COUNT + 1;
l_tickertable (l_index).ticker :=
PROCEDURE intermediate_forall_1 l_stocktable (indx).ticker;
IS l_tickertable (l_index).pricedate :=
curvar sys_refcursor; l_stocktable (indx).trade_date;
mystock tickertypeset := tickertypeset (); l_tickertable (l_index).pricetype := 'C';
l_tickertable (l_index).price :=
TYPE tickertable_tt IS TABLE OF tickertable%ROWTYPE l_stocktable (indx).open_price;
INDEX BY BINARY_INTEGER; END LOOP;

l_tickertable tickertable_tt; FORALL indx IN l_tickertable.FIRST ..


BEGIN l_tickertable.LAST
init; INSERT INTO tickertable
VALUES l_tickertable (indx);
OPEN curvar END;
FOR
SELECT *
FROM stocktable;
Choices, choices, choices... and more choices
Sometimes I wish I was still writing code in the old,
mystock := stockpivot_nopl (curvar);
simple days of Oracle6 and SQL*Forms 2.3. You didn’t
FOR indx IN mystock.FIRST .. mystock.LAST have a whole lot of choices (you didn’t even have
LOOP
l_tickertable (indx).ticker := PL/SQL!). You just did the best you could with the
mystock (indx).ticker; stepwise-logic available in Forms triggers.
l_tickertable (indx).pricedate :=
mystock (indx).pricedate; Okay, so I’m joking. I don’t hanker for those days, no
l_tickertable (indx).pricetype :=
mystock (indx).pricetype;
sir! Yet if we’re to be honest, we must acknowledge some
l_tickertable (indx).price := level of discomfort with the growing complexity of the
mystock (indx).price;
END LOOP; Oracle environment. We have so many more tools and
techniques than ever before, but that doesn’t necessarily
FORALL indx IN l_tickertable.FIRST ..
l_tickertable.LAST make our jobs a whole lot easier.
INSERT INTO tickertable
VALUES l_tickertable (indx); There’s more to learn, more to master. We need
END; more time to play with new features, uncover the gotchas
and the glitches, and figure out what applies best where.
Perhaps there’s a way to improve the performance But how many development managers give us any time
of this FORALL approach. What if we skip the to play?
transformative stockpivot function altogether, and instead Well, we’ll just have to make the best of what we
do it all in PL/SQL? Consider the code in Listing 7. have—which is, when you think about it, rather
I use BULK COLLECT to grab the 100,002 rows from wonderful. Consider this: Our employers actually pay us
stocktable and dump it into a local associative array. I to sit around and think up solutions to problems, and
then use a numeric FOR loop to pivot the data into then type those solutions into computers, all this in
200,004 rows, neatly depositing that doubled data into a reasonably comfortable and safe environments. That’s a
collection of records based on the ticker table. Finally, I pretty good deal, isn’t it?
use FORALL to insert 200,004 records directly into the So it’s time to stop our complaining, rejoice in our
ticker table. many choices, remember to use BULK COLLECT and
This implementation completes in less than 9 seconds! FORALL whenever humanly possible, and pick up,
little by little, the latest tricks of the trade, such as
Listing 7. Using BULK COLLECT and FORALL to pivot data. PIPELINED functions. ▲

PROCEDURE intermediate_forall_2 404FEUER.SQL at www.pinnaclepublishing.com


IS
TYPE stocktable_tt IS TABLE OF stocktable%ROWTYPE
INDEX BY BINARY_INTEGER; Steven Feuerstein is considered one of the world’s leading experts on the
Oracle PL/SQL language, having written nine books on PL/SQL, including
TYPE tickertable_tt IS TABLE OF tickertable%ROWTYPE
INDEX BY BINARY_INTEGER; Oracle PL/SQL Programming and Oracle PL/SQL Best Practices (all from
O’Reilly & Associates). Steven has been developing software since 1980
l_stocktable stocktable_tt;
l_tickertable tickertable_tt; and serves as a Senior Technology Advisor to Quest Software. His current
l_index PLS_INTEGER; projects include Swyg (www.Swyg.com) and the Refuser Solidarity
BEGIN
SELECT * Network (www.refusersolidarity.net), which supports the Israeli military
BULK COLLECT INTO l_stocktable refuser movement. steven@stevenfeuerstein.com.
10 Oracle Professional April 2004 www.pinnaclepublishing.com
Oracle
Professional

Using the Oracle9i R2


Templates Feature of DBCA
on Windows 2000
Stan Novinsky
Templates are a new feature that was introduced in Oracle9i. Cloning your database via database templates
They provide the Database Administrator with an alternative To create a template of a database including the data, the
way to create a new database or to clone an existing DBCA is used.
database. In this article, Stan Novinsky defines some of the To start the DBCA from Windows Explorer, go to
basic nomenclature involved in the process, explains the Program Files | OraHome92 | Configuration and
benefits of using templates, lists the basic steps, and walks Migration Tools. Note that the shortcut for the DBCA on
you through a sample implementation. your machine may reside in a different folder than the one
specified here. Then, follow these steps:

F
IRST, let’s define what a template is. The American 1. Select the Manage Templates option.
Heritage Dictionary defines a template as “a 2. Select either “From an existing database (structure
document or file having a preset format, used as a only)” or “From an existing database (structure as
starting point for a particular application so that the well as data).”
format does not have to be recreated each time it is used: 3. Enter the Source Database that’s to be cloned—this
a loan amortization template for a spreadsheet program.” can be local or remote if Net services are set up.
So, in Oracle9i, the same can be said about a database 4. Enter a name for your template along with
template. It’s basically a file containing a format of a a description.
database that can be used as a starting point to create a 5. Select either “Maintain the file locations” or
new database or clone (duplicate) a database on another “Convert the file locations to use OFA Structure.”
platform. The primary purpose of the 9i database
templates is to remove the complexity for creating This process now jars all of the data files for your
multiple similar databases and transition databases from database and places them in one large file (.dfj). The
one system to another. A database template is created by .dfj and .dbc files are created in the ORACLE_HOME\
using the 9i Database Configuration Assistant (DBCA). assistants\dbca\templates directory. You can now copy
these files to a new machine and have your data go along
Why use database templates? with it. This is great for moving from a test platform DB
Database templates allow DBAs to create new to a production environment, or from production to test
databases using predefined templates, or create new (when you want to test with real production data).
templates based on existing running databases that can
be used to create the databases at another time. This Note: Depending on the size of your database that’s to
serves as a robust new way of creating or duplicating be cloned, the size of the template files and the time
databases with or without the data. Here are some of required to build them may come into play. If the
the advantages: database is in the terabyte range, the .dfj file may
• It saves you time. If you use a template, you don’t become too large for the ORACLE_HOME and may be
have to define the database. difficult to move across a network to a target computer.
• By creating a template containing your database, you
can easily create a cloned database without specifying An example
parameters twice. I was required to create an additional test database for our
• The templates are easy to modify. You can change project on another computer. The target and source PCs
database options from the template settings. were running Oracle9i R2 Enterprise Edition on Windows
• Templates can be shared among several computers. 2000 with SP2.

www.pinnaclepublishing.com Oracle Professional April 2004 11


Prior to Oracle9i, this task of duplicating a production After clicking the Finish button in Step 5, you’ll
or test database on another platform involved using the receive a Summary box (see Figure 6). After clicking on
export/import utility. Using export/import was a valid
method, but somewhat time-consuming and cumbersome.
Now, templates to the rescue!
By using templates, I saved several hours if not a day
or two to clone the test database onto the new PC. I
created a database template via DBCA from our existing
development database (structure as well as data) using
the OFA file structure. The process was as follows:
1. Select the Manage Templates option (see Figure 1).
2. Select the “From an existing database (structure as
well as data)” option (see Figure 2).
3. Select “AVTST” as the Source Database that’s to be
cloned (see Figure 3).
4. Enter “new_dev_db” as the name for the template
along with a description (see Figure 4).
5. Select the “Convert the file locations to use OFA
Structure” option (see Figure 5). Figure 3. Step 3: Select the database used for template creation.

Figure 1. Step 1: Select the Manage Templates option. Figure 4. Step 4: Select a new name for the new template.

Figure 2. Step 2: Select the option to create the template Figure 5. Step 5: Select the OFA option.
with data.

12 Oracle Professional April 2004 www.pinnaclepublishing.com


the OK button in the Summary box, the template creation Java’s limitation with respect to its Zip classes.
process begins (see Figure 7). As an option, if you’re considering using the
The source DB size was 12GB. The template build template feature to clone databases, you may want to
completed in about 45 minutes, and the two required create your primary database with multiple .dbf files
template files (.dfj, .dbc), were created in the ORACLE_ for tablespaces under the 2GB size, prior to building
HOME\assistants\dbca\templates directory. any templates.
I moved the two newly created template files And finally, I encountered another small problem
located in ORACLE_HOME\assistants\dbca\templates with DBCA. When using DBCA to create a template
from the development platform to a second PC that was from a running DB, the database is locked. After the
running the same 9.2 Enterprise Edition software. I template creation process is completed, the DB remains
started the DBCA on the new platform and selected the locked. There’s no notification given prior to or after
option to create the new DB. I selected the template name the process completes. According to Oracle, this appears
that I copied over from the development platform to to be expected behavior. A bug was filed on this, and
create the new DB. After answering the basic questions, development engineers said that it was expected behavior
the cloning process started and was completed in less and wouldn’t be fixed. However, they did suggest that at
than one hour. some point they might put in a confirmation message
before the shutdown of the database occurs. So, the only
Caveats change in the behavior expected to be added is that there
Some of the issues that I encountered are reported to will be a message to let the user know that the database
have been corrected in Oracle10g, although I haven’t yet will be shut down.
experimented with the DBCA in 10g to verify the fixes.
After the cloning process completes, it may be Conclusion
necessary to UNLOCK and RESET users’ passwords. Templates are a real time-saver and one of the finest
You can use OEM to verify and unlock any users. “new” features in Oracle9i that I’ve used. Thanks to
Also, the listener and TNSNAMES file must be Oracle and the new 9i Database Templates feature, I
configured via the Net Configuration Assistant. looked like a genius when I told my manager that the
During an initial attempt at creating a template, I second test DB was ready in less than two hours. ▲
received the following error: Invalid Entry Size (expected
3130007552 but got –1164959744 bytes). The process Stan Novinsky works as an Oracle DBA for the PPSD/VIM group at The
stopped at the UNDOTBS01 file creation, which was Johns Hopkins University Applied Physics Lab. He specializes in Windows
created on the development platform with a size of and Oracle Database Administration. stan.novinsky@jhuapl.edu.
2.91GB.
After checking with Oracle, it turned out to be a bug
(2536772) in 9.2. On MetaLink, note 209516.1 lists a
workaround, which is to reduce the datafile size to less
than 2GB if possible.
According to reports, the bug has been fixed in 10g.
It’s not possible to fix this issue in 9i DBCA because of

Figure 6. The
Summary box.

Figure 7. The DBCA template creation process.

Know a clever shortcut? Have an idea for an article for Oracle Professional?
Visit www.pinnaclepublishing.com and click on “Write For Us” to submit your ideas.

www.pinnaclepublishing.com Oracle Professional April 2004 13


Oracle
Professional

LOG4PLSQL Oracle Database


Logging Tools
Guillaume Moulard
All users of Oracle databases are confronted with the the IsDebugEnabled function, like this:
recurring problem of trace generation in PL/SQL code. In
this article Guillaume Moulard explores LOG4PLSQL, which IF PLOG.isDebugEnabled then
PLOG.debug ('save your perf use isDebugEnabled');
offers the entire functionality necessary for coding, use, and END IF;
application traces in the Oracle database—and it’s free.
Log apart from the transaction

F
IRST, let’s review some of the features that Transactions are very important in a database, but for
LOG4PLSQL offers. It’s easy to implement and use. logging problems, sometimes it’s necessary to log apart
You’ll find adaptation of various logging levels from the SQL transaction. Here are some examples of
depending on user requirements, without modifying the when this is desirable:
application code. It offers the capability to log in to a lot of • When you want to roll back a transaction, but not the
destinations—log file, NTEventLog, Mail, Syslog, JMS, log message at the same time
among others. It also offers the possibility to log apart • When your code is quite long and you want to view
from a transaction. its progress
LOG4PLSQL provides the same features in PL/SQL
that LOG4J does in Java (LOG4J is an Apache Jakarta LOG4PLSQL has two solutions to log apart from your
project—see http://jakarta.apache.org/log4j). transaction. The first one is the autonomous transaction to
After you download and install the software from log messages in a log table. The second one is DBMS_PIPE
http://log4plsql.sourceforge.net, logs are put in levels in to send the log message to the external LOG4J logger with
the PL/SQL code (function, procedure, package, and the log4plsql.backgroundProcess.
trigger). You can organize it in hierarchical sections The autonomous transaction feature is a full PL/SQL
(discussed later). Afterwards, it’s possible to choose the solution. It’s very easy to use the log message, as it’s
trace and the destination according to the need. Every immediately accessible by issuing a SELECT from the
parameter is stored out of the code. Here’s a sample: TLOG table or the VLOG view, as you can see here:

BEGIN SQL> Declare


… 2 pCTX PLOG.LOG_CTX := PLOG.init (
PLOG.error ('mess error'); 3 psection => 'out transaction',
… 4 plevel => PLOG.LINFO,
END; 5 plog4j => FALSE,
6 pout_trans => TRUE);
7 begin
Log levels 8 PLOG.info (pCTX, 'My information');
9 end;
The levels of preset logs by the framework correspond to 10 /
the levels of standard severity:
SQL> rollback;
• The DEBUG level is for fine-grained informational
events that are very useful in debugging an SQL> select * from vlog;

application. LOG
-----------------------------
• The INFO level is for informational messages that [12/06 12:54:01: 89][INFO]
highlight the progress of the application at a coarse- [SCOTT][out transaction][My information]
grained level.
• The WARN level is for potentially harmful situations. To use the second method, you’ll have to start a
• The ERROR level is for error events that might still background process (/cmd/startLog4JbackgroundProcess
allow the application to continue running. .bat or /cmd/startLog4JbackgroundProcess.sh). This
• The FATAL level is for very severe error events that small Java application reads a log message directly from a
might lead the application to abort. PIPE in the database and logs the message with LOG4J
capability. This method has two advantages:
Before logging in your code, it’s best to do a test using • There’s no added I/O in the database.

14 Oracle Professional April 2004 www.pinnaclepublishing.com


• You can choose the LOG4J PropertyConfigurator, PLOG.init('hierarchical', PLOG.LINFO);
begin
Appenders, Layouts, and Level capability. PLOG.SetBeginSection (pCTX, 'mySection');
PLOG.info (pCTX, 'mess 1');
PLOG.SetBeginSection (pCTX, 'mySubSection');
Declare PLOG.info (pCTX, 'mess 2');
pCTX PLOG.LOG_CTX := PLOG.init( PLOG.SetEndSection (pCTX, 'mySubSection');
psection => 'testLog4jFeatures', PLOG.setEndSection(pCTX);
plevel => PLOG.LDEBUG, end;
plog4j => TRUE, /
pout_trans => TRUE);
begin select * from vlog;
PLOG.info (pCTX,
'This message is sent to LOG4J'); LOG
End; ------------------------------------------------------
/ [INFO ][hierarchical.mySection][mess 1]
[INFO ][ULOG][hierarchical.mySection
.mySubSection][mess 2]
In this example, and with these parameters, I now
have a new message in my NT Event Loggers. Installation and settings
At the time of this writing, the version of LOG4PLSQL
Hierarchical log sections available is v2.1.1 using LOG4J 1.2.8. The ZIP distribution
You can organize the log in hierarchical sections. That file contains all files used by LOG4PLSQL. In each
makes it possible to know where the notation is. release, you’ll always find a good version of LOG4J.
For each node, it’s possible to finely set what you A complete release of LOG4J can be found at http://
want to log. And you can also add a message about the jakarta.apache.org/log4j/download.
step or state where you are in a functional process. All scripts in the /cmd folder use the same
By default, all logs use dynamic hierarchical PL/SQL variable parameters: setVariable.bat for Windows
call stacks. Thus it’s possible to follow the sequence of and setVariable.sh for Unix systems. This file is used
package, procedure, function, and trigger calls. For each during the installation. If you want to use all LOG4J
object in the call stack, the owner and the name are stored. capabilities, you must modify the /properties/
When you define a hierarchical section, you use a log4plsql.xml file. If you want to customize the LOG4J
functional part in your PL/SQL application. The steps are: Appender, Layout, and Level, you have to change the
1. Initialize the command. parameters in the LOG4J configuration file. I’ll describe
2. Verify the stack. these files in the next sections.
3. Ask for a delivery. After you complete the /cmd/ install, it’s possible
4. Ask for an invoice. to read the logs under /sql/grantsys.log and /sql/
Declare
install.log. For the /cmd/ uninstall, refer to the /sql/
pCTX PLOG.LOG_CTX := unInstall.log file.

Don’t miss another issue! Subscribe now and save!


Subscribe to Oracle Professional today and receive a special one-year introductory rate:
Just $179* for 12 issues (that’s $20 off the regular rate)

NAME ❑ Check enclosed (payable to Pinnacle Publishing)


❑ Purchase order (in U.S. and Canada only); mail or fax copy
COMPANY
❑ Bill me later
❑ Credit card: __ VISA __MasterCard __American Express
ADDRESS
CARD NUMBER EXP. DATE

CITY STATE/PROVINCE ZIP/POSTAL CODE


SIGNATURE (REQUIRED FOR CARD ORDERS)

COUNTRY IF OTHER THAN U.S.


Detach and return to:
Pinnacle Publishing ▲ 316 N. Michigan Ave. ▲ Chicago, IL 60601
E-MAIL Or fax to 312-960-4106

* Outside the U.S. add $30. Orders payable in


404INS
PHONE (IN CASE WE HAVE A QUESTION ABOUT YOUR ORDER) U.S. funds drawn on a U.S. or Canadian bank.

Pinnacle, A Division of Lawrence Ragan Communications, Inc. ▲ 800-493-4867 x.4209 or 312-960-4100 ▲ Fax 312-960-4106

www.pinnaclepublishing.com Oracle Professional April 2004 15


The setVariable file your Oracle connections.
All parameters in this file are very easy to understand.
Here are descriptions of some of them: The log4j configuration file
• ORACLE_HOME and LOG4PLSQL_HOME are for This file is referenced in log4plsq.xml; the structure and
the path of Oracle and LOG4PLSQL. the entry are LOG4J-dependent. See the “Configuration”
• LOG_SID is for the database alias in tnsnames.ora. section at http://jakarta.apache.org/log4j/docs/
• LOG_USER and LOG_PASSW_USER are for the user manual.html for more information.
in the database. This user should be able to use
DBMS_PIPE, so check your privileges. In scripts Conclusion
there’s never a specification for storage management. In LOG4PLSQL, you should find all the functionality
• SYS_USER and SYS_PASSW_USER are used only you need to manage your logs in your PL/SQL code—
during the installation phase. these features are the result of LOG4J and LOG4PLSQL
• OracleVersion is used to compute a connection projects. It’s easy to implement, and provides great
string in SYS user. Use OracleVersion=9.2 or flexibility regardless of the logging needs of your
OracleVersion=before9.2. application. For more information, new feature requests,
• CLASSPATH is used for the links to log4plsql.jar, and comments, sign up for the log4plsql-all-info@
log4j-X.X.X.jar, xmlparserv2.jar, xmlcomp.jar, lists.sourceforge.net mailing list. ▲
classes12.jar, nls_charset12.jar, and runtime12.jar.
Guillaume Moulard works for France Telecom. He’s a key contributor
The log4plsql.xml file to the LOG4PLSQL project. He teaches XML and SGBD at Paris Sud
In this file, you configure your parameter file for the Orsay University and worked at Oracle France for four years.
LOG4J (Configuration type and file) database for gmoulard@users.sourceforge.net.

April 2004 Downloads


• 404MENCHEN.ZIP—Source code to accompany Gary • 404FEUER.SQL—Source code to accompany Steven
Menchen’s article, “Object Types and Inheritance: From Feuerstein’s article, “Exploring Options: Multiple Paths
DBMS_OUTPUT to Object-Oriented Stream I/O.” to an Answer.”

For access to current and archive content and source code, log in at www.pinnaclepublishing.com.

Editor: Garry Chan (gchan@procaseconsulting.com) Oracle Professional (ISSN 1525-1756)


Contributing Editor: Bryan Boulton is published monthly (12 times per year) by:
CEO & Publisher: Mark Ragan
Pinnacle Publishing
Group Publisher: Michael King A Division of Lawrence Ragan Communications, Inc.
Executive Editor: Farion Grove 316 N. Michigan Ave., Suite 300
Chicago, IL 60601
Questions?
POSTMASTER: Send address changes to Lawrence Ragan Communications, Inc., 316
Customer Service: N. Michigan Ave., Suite 300, Chicago, IL 60601.

Phone: 800-493-4867 x.4209 or 312-960-4100 Copyright © 2004 by Lawrence Ragan Communications, Inc. All rights reserved. No part
Fax: 312-960-4106 of this periodical may be used or reproduced in any fashion whatsoever (except in the
Email: PinPub@Ragan.com case of brief quotations embodied in critical articles and reviews) without the prior
written consent of Lawrence Ragan Communications, Inc. Printed in the United States
of America.
Advertising: RogerS@Ragan.com
Oracle, Oracle 8i, Oracle 9i, PL/SQL, and SQL*Plus are trademarks or registered trademarks of
Editorial: FarionG@Ragan.com Oracle Corporation. Other brand and product names are trademarks or registered trademarks
of their respective holders. Oracle Professional is an independent publication not affiliated
Pinnacle Web Site: www.pinnaclepublishing.com with Oracle Corporation. Oracle Corporation is not responsible in any way for the editorial
policy or other contents of the publication.

Subscription rates This publication is intended as a general guide. It covers a highly technical and complex
subject and should not be used for making decisions concerning specific products or
applications. This publication is sold as is, without warranty of any kind, either express or
United States: One year (12 issues): $199; two years (24 issues): $348 implied, respecting the contents of this publication, including but not limited to implied
Other:* One year: $229; two years: $408 warranties for the publication, performance, quality, merchantability, or fitness for any particular
purpose. Lawrence Ragan Communications, Inc., shall not be liable to the purchaser or any
Single issue rate: other person or entity with respect to any liability, loss, or damage caused or alleged to be
caused directly or indirectly by this publication. Articles published in Oracle Professional
$27.50 ($32.50 outside United States)* reflect the views of their authors; they may or may not reflect the view of Lawrence Ragan
Communications, Inc. Inclusion of advertising inserts does not constitute an endorsement by
* Funds must be in U.S. currency. Lawrence Ragan Communications, Inc., or Oracle Professional.

16 Oracle Professional April 2004 www.pinnaclepublishing.com

You might also like