Cover V05, I09
Article
Listing 1
Table 1

sep96.tar


Accessing Oracle from CGI Scripts Using Perl

Danny Lawrence and Scott Beimer

How do you communicate with an Oracle database through the World Wide Web? You can use Common Gateway Interface (CGI) scripts to communicate with the Web, but there's no direct way for a CGI script to access an Oracle database. Oracle provides a library of C functions known as the Oracle Call Interface (OCI), which allows C programs to communicate with the database, but these calls are cumbersome and require too much setup time for simple applications. We needed something easier to use than OCI calls - something that could be used for CGI code as well as for reports and scripts.

So, we decided to write an extension to Perl. Perl is already widely used for reports and CGI scripts, so it makes a natural link between CGI and Oracle. Although we have since discovered others doing similar work, we still like our extension. It is simple and does what we want. We can run it against V6 and V7 Oracle databases, and we can execute any select, DML, or PL/SQL block. We run it with Perl 5.001 on ISC 486 and Sun Solaris, and our scripts can run unmodified on either platform.

Perl

Perl is popular for its flexibility and for the ease with which the language can be extended with C subroutines. You can link in your own external libraries, and then call these libraries from within a Perl script. Often, just a few wrapper functions are needed to call the library and to convert datatypes back and forth between C and Perl. These wrapper functions are written in the XS language, which is mostly C interspersed with directives to the preprocessor xsubpp. Through the use of XS directives, datatype conversions and stack manipulations are mostly hidden. The Perl source distribution includes very good documentation.

The Oracle Call Interface

C programs can directly save and retrieve data in Oracle via a set of functions known as OCI functions. These are very powerful. You only need a few to accomplish everything related to connecting to a database, converting datatypes, buffering, and error checking. Unfortunately, these functions are too complex to be used directly in simple, slap-together programs. By the time you've declared and initialized various private Oracle structs, parsed the SQL statement, set up input and output buffers, and checked for errors, you may wonder if you will ever get out alive. And, you still haven't retrieved one row of data. These OCI calls are fully explained in the Oracle documentation, and examples of usage are in the XS code included in this article. But, you don't need to know anything about OCI to use this Perl extension.

Oracle7 introduced several new functions for OCI programs. The V6 interfaces are still available, but run considerably slower on the V7 platform. Our OCI package was originally written for V6, so it doesn't take advantage of any of the new functions yet.

The OCI Perl Extension

Our Oci package is a set of routines that glue the Oracle OCI calls to Perl, but we didn't just make the function usage a carryover from the low-level oracle OCI syntax. We tried to hide as many details as possible. Our Oci routines handle cursor allocation and freeing, variable binding, and input buffering almost invisibly. In fact, the Perl usage was designed to be very similar to PL/SQL, while still being Perlish. Table 1 shows a summary of the Perl functions in the Oci package. (See Listing 1 for the entire program.)

Perl wrapper subroutines that check the return status and print error messages are included in ociwrap.pl. These subroutines reduce the clutter in the main script and explain errors as much as possible. For example, the ckcursor() subroutine displays the actual text of the SQL statement when there is an error, which is a big help.

Usage

The function usage is fairly straightforward, but a couple of hints and examples might be useful. The descriptions below are given in the order in which you would use the functions in your scripts.

Connecting

oraconnect() must be called before any other Oci functions. The connect string is formatted the same as in all other Oracle products. If the connect string is coded directly into the script, use single quotes to protect login names such as ops$scott from being altered by Perl. If SQL*Net is up and running, connect strings like scott/tiger@T:host:DB work fine. Remember to call oradisconnect() before your script exits.

Cursors

You need a cursor for every SQL statement. Every time you use oracursor() to parse a statement, a cursor is returned. Oracle uses this cursor to remember things about the statement, such as the text of the statement, the current status, and buffer locations. In this Perl package, cursors can be viewed as similar to C FILE handles. The value returned by fopen() is used in other C file I/O operations. Similarly, the value returned by oracursor() is used in other Oracle calls.

The perl here-document syntax provides an elegant mechanism for declaring multiline SQL code:

($retval, $c) = oracursor(<<EOS);
select 'X'
from dual
EOS

or

($retval, $c) = oracursor(<<EOS);
begin
:X := :Y;
end;
EOS

Cursor variables (the $c above) can be local to subroutines. When the variable goes out of scope, Oracle and Perl resources allocated to that cursor are automatically released. You can use the same cursor variable for different statements at different times. Perl knows when to free resources. Note also that PL/SQL blocks require the trailing semicolon, but straight SQL blocks do not.

Binding

There are two ways to bind Perl data to SQL substitution variables - by value and by reference. If a variable is bound by value, it cannot be changed by a PL/SQL block. If a variable is bound by reference, then variable assignments within the PL/SQL block will be visible outside the block. Furthermore, the contents of a variable bound by value are set at the time of the orabind() call, while referenced binds are bound at either oraopen() or oraexec() time. Referenced binding is more flexible, but sometimes binding by value is more useful.

For instance, if you want to ensure the Perl variable is read-only, bind by value. Even assignments to the corresponding substitution variable within the PL/SQL block will not be seen by Perl. Binding by value is also useful within Perl subroutines. If the subroutine is called with parameters that are to be bound to substitution variables, then binding by reference won't work. The variable's locations are different for every call. In this case, it is better to re-bind by value on every call to the subroutine.

A bind by value can be as simple as using a string:

$retval = orabind($c, ":X", "value");

In this case, the SQL can use the value of :X, but cannot assign values to it. In the bind below, however, values can be assigned to :Y because a reference to $y is passed. If the PL/SQL block corresponding to $c assigns a value to :Y, the value of $y will be set accordingly.

$y = "";
$retval = orabind($c, ":Y",$y, 25);

Note the use of a backslash before the $y. This is Perl's syntax for a reference to $y. When references are used, the referenced variable must already have been defined. Assigning a null string, as with $y above, is sufficient.

The fourth parameter, length, is used to ensure $y has enough space even though, as in the example above, it might still be too short for what you expect to retrieve. One neat thing about referenced binds is that the value of the referenced variable can change between orabind() and oraopen() or oraexec(). The last value assigned is the one that will be bound, unlike binds by value.

Opening, Closing, and Execing

Depending on the type of statement used in the oracursor() call, you will want to use either the pair oraopen()/oraclose(), or the single function oraexec(), but not both. oraopen() and oraclose() are equivalent to PL/SQL's open and close for select cursors. All other types of cursors should use oraexec().

Fetching

orafetch() tries to be Perlish by returning the selected rows as an array of scalars. In this way, it is a mirror image of PL/SQL's fetch. When PL/SQL says:

fetch foo_cur into var1, var2;

the equivalent Perl is:

($retval, $var1, $var2) = orafetch($foo_cur);

The variables are retrieved in the same order as the items in the SQL select list. You must make sure you use the right number of Perl variables in the right order. When there are no more rows to fetch, $oerr is set to $ORA_NO_DATA_FOUND.

Examples

Examples of how to use these functions can be in tabhtml.cgi and comment.cgi. tabhtml.cgi prompts for an Oracle connect string and then displays a description of all tables owned by the login user. The html code returned is generated dynamically based on data in the system views.

comment.cgi is a script that simplifies documenting your tables and fields in the database. You enter the table or field name and a comment, and the form inserts it. The next time tabhtml.cgi is executed, the comments will be displayed.

Installation

Installation specifics for Oci vary by platform because of the different locations of Oracle headers and libraries. First, download the sources and put them in their own directory. You'll see a file called Makefile.PL. This file must be edited, and the LIBS, DEFINE, and INC parameters must be set for your system. On our Sparc 20 running Solaris 2.4 and Oracle 7.2, we use the following Makefile.PL:

use ExtUtils::MakeMaker;
# See lib/ExtUtils/MakeMaker.pm for details of how to
# influence the contents of the Makefile that is written.
WriteMakefile(
'NAME' => 'Oci',
'VERSION' => '0.1',
'LIBS' => ['-L/disk00/app/oracle/product/7.2.2/ \
lib -locic -L. -lsqlnet -lora -lsqlnet -lora \
-lnlsrtl3 -lc3v6 -lcore3 -lnlsrtl3 -lcore3 \
-losntab -lsqlnet -lora -lc3v6'],
'DEFINE' => '-DORACLE7',
'INC' => '-I${ORACLE_HOME}/sqllib/public
-I${ORACLE_HOME}/rdbms/demo',
);

The -losntab is our own invention. Oracle provides osntab.o, but because of this MakeMaker syntax, only .a archives are allowed. So, we built an archive libosntab.a containing only osntab.o. Also note that we spelled out the library path in the LIBS section instead of using a variable such as ${ORACLE_HOME}. There is probably an easier way to do this, but, alas, we're not Perl-building experts.

Once you've edited Makefile.PL, type:

perl Makefile.PL
make perl

If you got the parameters right, a new Perl executable will be sitting in the directory ready to be used. If you are a mere mortal, though, you will discover that the LIBS parameter takes a lot of twiddling. The Oracle demo makefiles for your system should provide enough information for you to get started.

There are other parameters in Makefile.PL that you might want to set. LINKTYPE lets you specify static or dynamic linking on platforms that support it. The Perl XS Tutorial document in the Perl 5.002 release explains all about Makefile.PL and building Perl.

Last Bit

Since we built this extension, we've had all sorts of fun with it. CGI scripts are almost too easy now, and we've discovered that even Oracle reporting is easier. We don't even need the Oracle reports module. We use SQL*Forms to collect report parameters, then have SQL*Forms spawn a Perl script in the background. The report runs, and the user gets to use his/her terminal right away, instead of waiting for the report to finish.

Our OCI extension is very simple, so if you need a more industrial-strength extension, check out the DBI/DBD extensions on the Hermetica web site: http://www.hermetica.com/technologia/DBI/index.html.

About the Authors

Danny's business card says he is Computer Systems Manager at the Grants Pass Daily Courier in Oregon. Scott's says he is Computer Services (we don't know exactly what that means). They have a combined 872 years experience in writing in the third person. They can be reached at danny@magick.net or scottb@blarg.net.