Call now: 252-767-6166  
Oracle Training Oracle Support Development Oracle Apps

 
 Home
 E-mail Us
 Oracle Articles


 Oracle Training
 Oracle Tips

 Oracle Forum
 Class Catalog


 Remote DBA
 Oracle Tuning
 Emergency 911
 RAC Support
 Apps Support
 Analysis
 Design
 Implementation
 Oracle Support


 SQL Tuning
 Security

 Oracle UNIX
 Oracle Linux
 Monitoring
 Remote s
upport
 Remote plans
 Remote
services
 Application Server

 Applications
 Oracle Forms
 Oracle Portal
 App Upgrades
 SQL Server
 Oracle Concepts
 Software Support

 Remote S
upport  
 Development  

 Implementation


 Consulting Staff
 Consulting Prices
 Help Wanted!

 


 Oracle Posters
 Oracle Books

 Oracle Scripts
 Ion
 Excel-DB  

Don Burleson Blog 


 

 

 


 

 

 

 

 

Introduction to using DBI and Oracle

Oracle Tips by Mladen Gogala

Mladen Gogala is the author of "Easy Oracle PHP".

See the whole article: Perl DBI and Oracle

Introduction

DBI is a Perl module, well documented in both online sources and the "Programming the Perl DBI"  book by  Alligator Descartes, Alistair Carty, Tim Bunce and Linda Mui (O'Reilly, ISBN:1565926994) but still, I can see questions about using DBI with Oracle on Oracle USENET groups. That motivated me to write this article.

Oracle has many features and data types, while using DBI with each particular feature is not always entirely trivial. The purpose of this article is to cover many of those uses from  Perl practitioner's point of view. Here is what you can find in this article:

The sole purpose of this article is to serve as an introductory reading for those who have never used the two together. Therefore, some knowledge of both Oracle and Perl is assumed, and although I will not try to show off my obfuscation abilities, this article still assumes that the reader has read “Learning Perl” and has some experience with Oracle RDBMS.

This article will not cover basic features of neither Perl nor Oracle. It will also not cover the installation of Perl, Oracle, DBI or DBD::Oracle. Furthermore, it makes no claims of completeness. This is simply an introduction to using Oracle with Perl DBI.

This article  was created on Red Hat Fedora Core 4, with  Oracle 10.2.0.2 and Perl 5.8.6. The decision not to cover the installation was made because the installation is different for each operating system, while I'd like to concentrate on the common features, the features that  can be used across the whole range of supported systems. Describing installation on Unix or Linux and omitting Windows or VMS would open me for the accusations of being OS biased. As I really am biased toward one type of OS, I wanted to hide that fact and cover just the common parts.

In writing this article I was relying mostly on the experience, scripts and sins of the past and online documentation. The most accurate and up to date online DBI documentation can always be found on CPAN (http://search.cpan.org). In particular, the syntax of all DBI methods can be found on the DBI page. I will occasionally quote DBI documentation, where appropriate and will always warn the reader when I’m doing so. Quotes from the online documentation will also be marked by the different font (smaller and italicized). The online documentation for the DBI and DBD::Oracle can be found at:

http://search.cpan.org/~timb/DBI-1.50/DBI.pm

http://search.cpan.org/~timb/DBD-Oracle-1.16/Oracle.pm

This is, of course, the documentation for the versions used in this article. This documentation is likely change with versions. If your DBI and DBD::Oracle versions are different from what was used to produce this article and  the example scripts within it, you should check the documentation for your favorite version.

So, let’s start with the article. In order to execute the examples from the article the reader will have to have access to an Oracle database and also  access to Perl interpreter with DBI and DBD::Oracle installed. Finding Perl isn’t a problem. Verifying that all necessary modules are installed is also very easy. So, how do we tell that DBI and DBD::Oracle are installed and how do we tell which versions are installed? The answer is  really simple:

$ perl -e 'use DBI; print $DBI::VERSION,"\n";'

1.50

$ perl -e 'use DBD::Oracle; print $DBD::Oracle::VERSION,"\n";'

1.17

Therefore, we have DBI 1.50 and and DBD::Oracle 1.17. At the moment, these are the latest and the greatest versions.  Here is the Oracle version:

$ sqlplus scott/tiger
 
SQL*Plus: Release 10.2.0.2.0 - Production on Sun Apr 16 23:09:35 2006
 
Copyright (c) 1982, 2005, Oracle.  All Rights Reserved.
 
Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.2.0 - Production
With the Partitioning, OLAP and Data Mining options

SQL>
 

This username and password (SCOTT/TIGER) will be used throughout this article, as well as the accompanying EMP and DEPT tables which will be used for almost all of the examples. EMP and DEPT tables do not have LOB columns, so I will have to create my own infrastructure for dealing with LOB data types.


Connecting to Oracle with DBI

DBI uses the following syntax to connect to an Oracle instance:

my $db=DBI->connect("dbi:Oracle:local", "scott", "tiger");

The string on the beginning contains the following elements, separated by colons: the string "dbi", driver type, in this case "Oracle" and the database name, also known as TNS descriptor. In the line above, the database name was "local". The first argument, therefore, defines the database to connect to. The 2nd and 3rd arguments are, of course, username and password. Of course, everyone who has ever worked with databases knows that connection errors happen from time to time, so it is prudent to check for errors:

 my $dbh = DBI->connect( "dbi:Oracle:$db", $username, $passwd ) ||

           die( $DBI::errstr . "\n" );

In case of connection error, database handle $dbh is not created and the error string "errstr" comes from the DBI class itself. If, on the other hand, the connection attempt is successful and the database handle is created, there are several properties that can be very helpful with further programming:

  • AutoCommit: when set to 1, DBI issues a commit after each successful SQL statement. This is very dangerous and on by default. Setting it to 0 is a good idea. This handle property is not Oracle specific, it is available for any database.
     
  • RaiseError: When turned on (it is off by default)  it sends an exception to your script and terminates it. It is a good idea to kill the script if a non-handled Oracle exception happens. This handle property is not Oracle specific, it is available for any database.
     
  • ora_check_sql: Oracle has a performance enhancing trick called "deferred parse". When used, it decreases the number of the needed database calls, by bundling "parse" and "execute" phases of the SQL execution. Unfortunately, this feature is not turned on by default. To turn it on, you have to set ora_check_sql to 0. This is Oracle specific.
     
  • RowCacheSize: this instructs the driver to create a local pre-fetch cache and defines its size.
     
  • Both DBI database and statement handles always have errstr and err member variables which contain error message and code respectively. In case of a failed connection attempt, when database handle is not created, errstr and err can be used in class context, as $DBI::errstr and $DBI::err. That is used in the examples throughout the article.
     
  • This list is by no means complete, I mentioned only the properties most frequently used in the scripts. For the complete list, please consult the online DBI documentation. In further text, I’ll introduce two more handle properties, used to deal with LOB data types.

These properties are keys of an associative array, and here is how they're set:

    $dbh->{AutoCommit}    = 0;

    $dbh->{RaiseError}    = 1;

    $dbh->{ora_check_sql} = 0;

    $dbh->{RowCacheSize}  = 16;

So, this turns off auto-commit,  instructs DBI to terminate the script if SQL error is incurred, instructs Oracle driver to use deferred parse and wait with parsing the SQL until an execution is attempted for the first time. It also creates a local pre-fetch cache with place for 16 rows.

Unfortunately, DBD::Oracle does not support array interface, so the effects of this local cache are not as great as one would expect. As a matter of fact,  this lack of support for Oracle array interface is the feature that severely limits the use of Perl with Oracle and makes it unsuitable for large data loads or massive transactions. Features that are also not supported are TAF (Transparent Application Failover) and direct loads. In other words, Perl with DBI and DBD::Oracle is not an industry strength tool for a production environment.


Executing SQL with DBI

Now that we have a database handle properly created and configured, we can use it to execute SQL commands. Oracle SQL executes in several phases:

Parse phase: SQL statement is checked for syntactical validity and all objects are checked to see whether they exist or not and whether the user has appropriate access privileges or not. It is during this phase that the optimizer is invoked, statistics examined and the optimal access path determined. This parsing operation can be excruciatingly expensive and is best avoided, if at all possible.

Bind phase: placeholders in SQL statements are “connected” to the program variables. During this process the address of program variables is “made known” to oracle, so that it can read or write values from it. The same SQL command can be executed over and over again, without the need for re-parsing for various values of the program variable.

Execute phase: In this phase, the SQL statement in question is executed and the appropriate program areas within the program are updated to reflect that. Oracle can postpone parsing until the statement is actually executed, therefore decreasing the number of calls to oracle and the number of round trips over the network, necessary to reach the database.

Define phase: define phase exists only for queries. During this phase we define variables to receive output. This phase is not really necessary with Perl DBI as DBI calls also create variables to receive data.

Fetch phase: during the fetch phase, data is retrieved from an Oracle cursor and stored into program variables.  Fetch is not bi-directional, it can be read sequentially and closed when no longer needed.

These phases of execution correspond to DBI calls. In particular, “parse” phase corresponds to the prepare DBI call. Now is the right time to demonstrate the fabled “deferred parse:

Example 1.

#!/usr/bin/perl -w

use strict;

use DBI;

my $db = DBI->connect( "dbi:Oracle:Local", "scott", "tiger" )

    || die( $DBI::errstr . "\n" );

$db->{AutoCommit}    = 0;

$db->{RaiseError}    = 1;

$db->{ora_check_sql} = 0;

$db->{RowCacheSize}  = 16;

my $SEL = "invalid SQL statement";

my $sth = $db->prepare($SEL);

print "If you see this, parse phase succeeded without a problem.\n";

$sth->execute();

print "If you see this, execute phase succeeded without a problem.\n";

END {

    $db->disconnect if defined($db);

}

 

The result of this script is the following:

 

bash-3.00$ /tmp/ttt

If you see this, parse phase succeeded without a problem.

DBD::Oracle::st execute failed: ORA-00900: invalid SQL statement (DBD ERROR: OCIStmtExecute) [for Statement "invalid SQL statement"] at /tmp/ttt line 13.

DBD::Oracle::st execute failed: ORA-00900: invalid SQL statement (DBD ERROR: OCIStmtExecute) [for Statement "invalid SQL statement"] at /tmp/ttt line 13.

 

As you can see, parse succeeded without a problem, although the SQL statement in the variable $SEL was clearly invalid. There is one more thing in this script that needs to be explained, namely the END block. The END block gets executed when the script exits, regardless of the way it exits. So , let’s comment it out and see what happens:

 

bash-3.00$ /tmp/ttt

If you see this, parse phase succeeded without a problem.

DBD::Oracle::st execute failed: ORA-00900: invalid SQL statement (DBD ERROR: OCIStmtExecute) [for Statement "invalid SQL statement"] at /tmp/ttt line 13.

DBD::Oracle::st execute failed: ORA-00900: invalid SQL statement (DBD ERROR: OCIStmtExecute) [for Statement "invalid SQL statement"] at /tmp/ttt line 13.

Issuing rollback() for database handle being DESTROY'd without explicit disconnect().

bash-3.00$

The last message was issued by the DBI, because of exiting without disconnect. To avoid that, it is a good practice to include an END block like in the example 1. End block can also be used to issue rollback after a failed transaction. To summarize the example 1, a successful “connect” call established a database handle $db. Database handle has a prepare method, used to create a statement handle, $sth. Statement handle has many methods. Execute, bind and fetch are all methods of a statement handle.

Now, let’s replace the invalid SQL with a valid one, namely “select * from emp”; Our script will need more then cosmetic changes. The script will now look like this:

 

Example 2.

 

#!/usr/bin/perl -w
use strict;
use DBI;
my $db = DBI->connect( "dbi:Oracle:Local", "scott", "tiger" )
    || die( $DBI::errstr . "\n" );
$db->{AutoCommit}    = 0;
$db->{RaiseError}    = 1;
$db->{ora_check_sql} = 0;
$db->{RowCacheSize}  = 16;
my $SEL = "SELECT * FROM EMP";
my $sth = $db->prepare($SEL);
$sth->execute();
 
while ( my @row = $sth->fetchrow_array() ) {
    foreach (@row) {
        $_ = "\t" if !defined($_);
        print "$_\t";
    }
    print "\n";
}
 
END {
    $db->disconnect if defined($db);
}

 

The output looks exactly as expected:

 

bash-3.00$ ./ttt
7369    SMITH   CLERK   7902    17-DEC-80       800                     20
7499    ALLEN   SALESMAN        7698    20-FEB-81       1600    300     30
7521    WARD    SALESMAN        7698    22-FEB-81       1250    500     30
7566    JONES   MANAGER 7839    02-APR-81       2975                    20
7654    MARTIN  SALESMAN        7698    28-SEP-81       1250    1400    30
7698    BLAKE   MANAGER 7839    01-MAY-81       2850                    30
7782    CLARK   MANAGER 7839    09-JUN-81       2450                    10
7788    SCOTT   ANALYST 7566    09-DEC-82       3000                    20
7839    KING    PRESIDENT                       17-NOV-81       5000           10
7844    TURNER  SALESMAN        7698    08-SEP-81       1500    0       30
7876    ADAMS   CLERK   7788    12-JAN-83       1100                    20
7900    JAMES   CLERK   7698    03-DEC-81       950                     30
7902    FORD    ANALYST 7566    03-DEC-81       3000                    20
7934    MILLER  CLERK   7782    23-JAN-82       1300                    10
bash-3.00$

 

So, now we not only have execute, we also have fetch, implemented by fetchrow_array method of the statement handle. The array @row is created on the fly, for each row, by the fetchrow_array method. Therefore, we do not have to do “define” as we would have had, had we been programming an OCI program instead of a Perl script.

 

There several more methods for fetching data from an Oracle cursor:

l       fetchrow_arrayref

l       fetchrow_hashref

l       fetchall_arrayref,

l       fetchall_hashref

 

These methods differ by what do they return (hash or array reference) and how many rows do they return  (one or all). Methods returning hash reference are convenient when we want to pick column value by column name, not its sequence number. For  methods that return reference to hash, retrieval works like this:

 

$row=sth->fetchrow_hashref();

$ename=$row->{ENAME};

 

Methods that fetch all rows are convenient when the underlying query returns relatively few rows that are frequently referenced within a script.

All those methods fetch  relational NULL as “undef” value, which is a problem if we have “use strict” in effect. That is the reason for having the line which reads:

                 $_ = "\t" if !defined($_);

in the script. If that line is commented out, we get an ugly output, laced with warning messages, which looks like this:

 

bash-3.00$ ./ttt

Use of uninitialized value in concatenation (.) or string at line 17

7369    SMITH   CLERK   7902    17-DEC-80       800

7499    ALLEN   SALESMAN        7698    20-FEB-81       1600

7521    WARD    SALESMAN        7698    22-FEB-81       1250

Use of uninitialized value in concatenation (.) or string at line 17

7566    JONES   MANAGER 7839    02-APR-81       2975

7654    MARTIN  SALESMAN        7698    28-SEP-81       1250

Use of uninitialized value in concatenation (.) or string at line 17

7698    BLAKE   MANAGER 7839    01-MAY-81       2850

Use of uninitialized value in concatenation (.) or string at line 17

7782    CLARK   MANAGER 7839    09-JUN-81       2450

Use of uninitialized value in concatenation (.) or string at line 17

7788    SCOTT   ANALYST 7566    09-DEC-82       3000

Use of uninitialized value in concatenation (.) or string at line 17

Use of uninitialized value in concatenation (.) or string at line 17

7839    KING    PRESIDENT               17-NOV-81       5000

7844    TURNER  SALESMAN        7698    08-SEP-81       1500

Use of uninitialized value in concatenation (.) or string at line 17

7876    ADAMS   CLERK   7788    12-JAN-83       1100

Use of uninitialized value in concatenation (.) or string at line 17

7900    JAMES   CLERK   7698    03-DEC-81       950

Use of uninitialized value in concatenation (.) or string at line 17

7902    FORD    ANALYST 7566    03-DEC-81       3000

Use of uninitialized value in concatenation (.) or string at line 17

7934    MILLER  CLERK   7782    23-JAN-82       1300

bash-3.00$

 

The strict checking, enforced by the “use strict” pragma, will flag out any NULL values encountered by our script. That must be handled if we don’t want a warning message after each NULL value.

This was a very simple script, executing and retrieving results of  the simplest possible query from the SCOTT.EMP table. The additional question one can immediately ask is the following:

I don’t know much about the EMP table and SCOTT demo schema. How many columns are returned, what are their names, types and sizes? How many rows were returned by the query? Of course, one solution is to scold the unfortunate wannabe programmer and tell him to go and read the fine manual, but RTFM is not always considered a polite and civilized solution, so I developed a DBI solution, presented in an extended version of the example 2:

 

Example 2a.

 

#!/usr/bin/perl -w
use strict;
use DBI;
my $db = DBI->connect( "dbi:Oracle:Local", "scott", "tiger" )
    || die( $DBI::errstr . "\n" );
$db->{AutoCommit}    = 0;
$db->{RaiseError}    = 1;
$db->{ora_check_sql} = 0;
$db->{RowCacheSize}  = 16;
my $SEL = "SELECT * FROM EMP";
my $sth = $db->prepare($SEL);
$sth->execute();
my $nf = $sth->{NUM_OF_FIELDS};
print "This statement returns $nf fields\n";
 
for ( my $i = 0; $i < $nf; $i++ ) {
    my $name = $sth->{NAME}[$i];
    my $type = $sth->{TYPE}[$i];
    my $prec = $sth->{PRECISION}[$i];
    my $scle = $sth->{SCALE}[$i];
    my $tn=$db->type_info($type)->{TYPE_NAME};
    print
        "Field number $i: name $name of type $tn with precision $prec,$scle\n";
}
while ( my @row = $sth->fetchrow_array() ) {
    foreach (@row) {
        $_ = "\t" if !defined($_);
        print "$_\t";
    }
    print "\n";
}
print "\n\nThis query returned " . $sth->rows . " rows.\n";
 
END {
    $db->disconnect if defined($db);
}

 

Of course, the most interesting thing here is the output of this script, which looks like this:

 

This statement returns 8 fields

Field number 0: name EMPNO of type DECIMAL with precision 4,0

Field number 1: name ENAME of type VARCHAR2 with precision 10,0

Field number 2: name JOB of type VARCHAR2 with precision 9,0

Field number 3: name MGR of type DECIMAL with precision 4,0

Field number 4: name HIREDATE of type DATE with precision 75,0

Field number 5: name SAL of type DECIMAL with precision 7,2

Field number 6: name COMM of type DECIMAL with precision 7,2

Field number 7: name DEPTNO of type DECIMAL with precision 2,0

7369            SMITH       CLERK       7902            17-DEC-80 800                                                      20               
7499            ALLEN       SALESMAN                 7698            20-FEB-81  1600            300              30               
7521            WARD       SALESMAN                 7698            22-FEB-81  1250            500              30               
7566            JONES        MANAGER                   7839            02-APR-81 2975                                                    20               
7654            MARTIN   SALESMAN                 7698            28-SEP-81  1250            1400            30               
7698            BLAKE      MANAGER                   7839            01-MAY-81                   2850                                                    30               
7782            CLARK      MANAGER                   7839            09-JUN-81  2450                                                    10               
7788            SCOTT       ANALYST                    7566            09-DEC-82 3000                                                    20               
7839            KING          PRESIDENT                                                          17-NOV-81 5000                                                    10               
7844            TURNER    SALESMAN                 7698            08-SEP-81  1500            0                  30               
7876            ADAMS    CLERK       7788            12-JAN-83 1100                                                    20               
7900            JAMES      CLERK       7698            03-DEC-81 950                                                      30               
7902            FORD         ANALYST                    7566            03-DEC-81 3000                                                    20               
7934            MILLER     CLERK       7782            23-JAN-82 1300                                                    10               

 

This query returned 14 rows.

So, what is the answer to our question? Column name, column type, column precision and scale, as well as the number of columns returned are all attributes of the statement handle. Those attributes are defined when the statement is parsed. In our script, we bundled the parse phase with the execute phase, which means that those attributes are not defined until we execute the handle, which is a colloquial expression for “invoke the handle’s execute method”. If we have a valid statement handle $sth,  that handle automatically has the following elements:

  • $sth->{NUMBER_OF_FIELDS} is an integer field telling us how many columns does the query return, if the statement is a query.
  • $sth->{NAME} is an array, with  $sth->{NUMBER_OF_FIELDS} elements, each element being the column name of the corresponding returned column.
  • $sth->{TYPE} is also an array which contains column types. Column types are integers, because they're consistent across databases. Supported types depend on the version of the DBI and can be translated into type names by using the type_info() method as shown in the example 2a.
  • $sth->{PRECISION} and $sth->{SCALE} are both arrays containing the information about the precision and the scale of the returned column. Note that the precision for VARCHAR2 column is its size. Its scale, of course is 0. The value of scale traditionally defines the number of decimal places in the number field.
  • Last but not least is the rows method. Invoking $sth->rows() will give you the number of rows fetched so far or the number of affected rows. This method is completely analogous to the PL/SQL ROWCOUNT attribute and is used in exactly the same way.
  • There are, of course, other elements which can be located and studied on the online documentation page.

Generally speaking, statement handles are Oracle cursors, disguised as Perl objects. The whole philosophy of  database handles and statement handles is the same as the philosophy of database connections and cursors: to open a cursor, one has to connect to a database. In the OO world of the modern programming languages, objects have “methods” and “properties” or “members”, so those are the terms that we must use when talking about DB handles  and statement handles.

Now we know how to execute a simple SQL statement and get the basic information about it. For statements like “ALTER SESSION SET NLS_DATE_FORMAT='DD-MON-RR'”, there is also a shortcut:

 

$db=DBI->connect(“dbi:Oracle:Local”,”scott”,”tiger”);

$db->do(“ALTER SESSION SET NLS_DATE_FORMAT='DD-MON-RR'”);

 

No prepare is necessary, no statement handle is available, the “do” method is a database handle method which fuses prepare and execute methods in a single step. That is convenient for the alter session commands or DDL commands. The next thing to learn is how to make a program variable “known” to Oracle.

 


Binding Variables with Oracle DBI

The problem at hand, the one that will be solved by binding variables is the following: we need to find out name, job, department name and salary for an employee, by the employee number. The employee number is read from standard input. The SQL statement that we will use to solve the problem looks like this:

 

SELECT e.ename,e.job,d.dname,e.sal
FROM  emp e, dept d
WHERE e.deptno=d.deptno AND
               e.empno=:EMPNO

 

The expression “:EMPNO” is called a “placeholder” and has to be defined before the statement can be executed.  Connecting a placeholder with a program variable is known as binding . Perl supports several types of binds. Let’s see the most popular and the simplest one first:

 

Example 3

 

#!/usr/bin/perl -w
use strict;
use DBI;
my $db = DBI->connect( "dbi:Oracle:Local", "scott", "tiger" )
                || die( $DBI::errstr . "\n" );
$db->{AutoCommit}    = 0;
$db->{RaiseError}    = 1;
$db->{ora_check_sql} = 0;
$db->{RowCacheSize}  = 16;
my $SEL = "SELECT e.ename,e.job,d.dname,e.sal
                      FROM   emp e, dept d
                      WHERE e.deptno=d.deptno AND
                                      e.empno=?";
my $sth = $db->prepare($SEL);
 
print "Enter EMPNO:";
my $empno = <STDIN>;
chomp($empno);
 
$sth->bind_param( 1, $empno );
$sth->execute();
my ( $ename, $job, $dept, $sal ) = $sth->fetchrow_array();
write if  $sth->rows>0;
 
no strict;
 
format STDOUT_TOP =
Employee Name          Job                            Department               Salary
----------------------------------------------------------------------------------------------------
.
 
format STDOUT =
@<<<<<<<<<<<        @<<<<<<<<<<<<   @<<<<<<<<<             @<<<<<<<<<
$ename,                       $job,                         $dept,                           $sal
.
 
END {
    $db->disconnect if defined($db);
}

 

This script produces the following output:

 

bash-3.00$ ./ttt

Enter EMPNO:7934

Employee Name       Job                   Department             Salary

-------------------------------------------------------------------------------

MILLER                  CLERK           ACCOUNTING      1300

 

In case that a non-existing employee number is entered, no output is produced. So, here we have a script that reads a value from the standard input, and needs that value to complete the SQL execution. The placeholder used here is a simple question mark: “?” :“WHERE empno=?”. That is a DBI specific trick, later emulated in other languages and frameworks, like PEAR::DB. The trick is that DBI uses so called positional bind, in which each question mark is a placeholder, replaced by  its sequence number. It can’t be simpler then that, but there is a problem:

 

If our SQL statement was something like:

 

 WHERE empno = ? and  ?<10000

 

2 different placeholders would be needed. If a condition includes the same placeholder appearing in several places in the same SQL, positional binds can not be used, we have to resort to the named binds. Named binds differ from positional binds in such a way that placeholders are not simple question marks but are named instead. As a matter of fact, our first example was a part of a named bind sequence. The statement below

SELECT e.ename,e.job,d.dname,e.sal
FROM  emp e, dept d
WHERE e.deptno=d.deptno AND
                e.empno=:EMPNO

 

will lead to $sth->bind_param(“:EMPNO”, $empno); Other then that, positional binds are identical to the named ones. I also find named binds easier to follow and less confusing then a forest  of question marks that I have to count by using my fingers in order to really understand the underlying SQL. I have only 10 fingers, which is somewhat limiting when it comes to really complex SQL statements. I also adopted the naming convention which mandates naming placeholders the same as the variables they’re being bound to, only in capital letters, just like in the code snippet above.

So far, Oracle has just been reading script variables and using them in SQL. What happens if Oracle needs to write something into a script variable? In other words, what happens if the placeholder is in place of the input/output  parameter in PL/SQL procedure? It turns out that we need somewhat more complex bind. In order to do that, let’s  take a look at the following  PL/SQL script:

 

CREATE OR REPLACE PROCEDURE DBITEST(emp_no in number, d_name out varchar2)
AS
BEGIN
SELECT  D.DNAME  INTO D_NAME
FROM EMP E, DEPT D
WHERE E.EMPNO=EMP_NO AND
                                            D.DEPTNO=E.DEPTNO;
EXCEPTION
         WHEN NO_DATA_FOUND THEN
         D_NAME:=’NO SUCH EMPLOYEE’;
END;
/

 

In a Perl script, we would have to call it, by using a snippet like this:

 

my $SCR=”begin

                            dbitest(:EMPNO,:DNAME);

                    end;”;

 

Unfortunately, the bind method we’ve studied so far cannot help us here. In other words, using calls like $sth->bind_param(“:ENAME”,$ename) and $sth->bind_param(“:DNAME”, $dname) would produce nothing but a nasty error message. Let’s modify our example 3 and demonstrate:

Example 3a (Doesn’t work)

#!/usr/bin/perl -w
use strict;
my $dname;
use DBI;
my $db = DBI->connect( "dbi:Oracle:Local", "SCOTT", "TIGER" )
    || die( $DBI::errstr . "\n" );
$db->{AutoCommit}    = 0;
$db->{RaiseError}    = 1;
$db->{ora_check_sql} = 0;
$db->{RowCacheSize}  = 16;
my $SEL = "begin
                               dbitest(:EMPNO,:DNAME);
                     end;";
 
my $sth = $db->prepare($SEL);
 
print "Enter EMPNO:";
my $empno = <STDIN>;
chomp($empno);
 
$sth->bind_param( ":EMPNO", $empno );
$sth->bind_param( ":DNAME", $dname);
$sth->execute();
print "Department name: $dname\n";
 
END {
    $db->disconnect if defined($db);
}

 

The execution produces the following:

bash-3.00$ ./ttt
Enter EMPNO:7934
DBD::Oracle::st execute failed: ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "SCOTT.DBITEST", line 4
ORA-06512: at line 2 (DBD ERROR: OCIStmtExecute) [for Statement "begin
                   dbitest(:EMPNO,:DNAME);
           end;" with ParamValues: :empno='7934', :dname=undef] at ./ttt line 23, <STDIN> line 1.
DBD::Oracle::st execute failed: ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "SCOTT.DBITEST", line 4
ORA-06512: at line 2 (DBD ERROR: OCIStmtExecute) [for Statement "begin
                   dbitest(:EMPNO,:DNAME);
           end;" with ParamValues: :empno='7934', :dname=undef] at ./ttt line 23, <STDIN> line 1.
bash-3.00$

 

That is not nice. So, how do we fix that? DBI has another type of bind method, used for PL/SQL procedures, LOB parameters or ref cursors. This method is called bind_param_inout. There are  two important differences between bind_param and bind_param_inout:

  • Bind_param takes a program variable as an argument while bind_param_inout takes a REFERENCE to program variable as an argument. In other words, we’re passing a pointer to variable to Oracle, not the variable itself.
     
  • Specifying the maximum length of the bind variable is mandatory for bind_param_inout.

Bind using the value of the variable, like bind_param are also called binds by value, as the value of the variable is made available to Oracle. Binds using bind_param_inout are also known as binds by reference or binds by address as reference (address) of a Perl variable is made known to Oracle.

So, let’s now modify our example 3a  to use bind by reference:

 

Example 3b

 

#!/usr/bin/perl -w
use strict;
my $dname;
use DBI;
my $db = DBI->connect( "dbi:Oracle:TESTDB01", "mgdba", "qwerty" )
    || die( $DBI::errstr . "\n" );
$db->{AutoCommit}    = 0;
$db->{RaiseError}    = 1;
$db->{ora_check_sql} = 0;
$db->{RowCacheSize}  = 16;
my $SEL = "begin
                               dbitest(:EMPNO,:DNAME);
                     end;";
 
my $sth = $db->prepare($SEL);
 
print "Enter EMPNO:";
my $empno = <STDIN>;
chomp($empno);
 
$sth->bind_param_inout( ":EMPNO", \$empno, 20);
$sth->bind_param_inout( ":DNAME", \$dname, 20 );
$sth->execute();
print "Department name: $dname\n";
 
END {
    $db->disconnect if defined($db);
}

 

The execution now proceeds in much nicer fashion:

 

bash-3.00$ ./ttt

Enter EMPNO:7934

Department name: ACCOUNTING

 

The only difference is the use of bind_param_inout  instead of bind_param in the “b” version. Can the “inout” version of the call be used for simple querying as in the example 3? Yes, it can, but it is simpler to use binds by value then binds by address. Interestingly enough both binds by value and binds by address (binds by reference) support positional binds, with “?”  for placeholders. Binds by reference also have to be used with RETURNING INTO clause as a part of an INSERT statement.

So far, this article has covered connecting to Oracle, simple fetches, binding variables to placeholders and executing PL/SQL scripts. To really spice it up, this article will also cover working with LOB data types and with REF cursors.

 


LOB Types and REF Cursors with Oracle DBI

REF cursors are an extremely handy way of returning sets of records. Before proceeding with Perl scripting, let’s first create PL/SQL code that will help us demonstrate the concept:

CREATE OR REPLACE PACKAGE dbi AS
  type refcsr IS ref CURSOR;
  PROCEDURE test(dno IN NUMBER,   csr OUT refcsr);
END;
/
 
CREATE OR REPLACE PACKAGE BODY dbi AS
  PROCEDURE test(dno IN NUMBER,   csr OUT refcsr) IS
  BEGIN
 
    OPEN csr FOR
    SELECT ename,
      job,
      sal,
      hiredate
    FROM emp
    WHERE deptno = dno;
  END;
END;
/

 

Let’s say we want to execute the following PL/SQL code in our  Perl script:

 

BEGIN

       DBI.TEST(:DNO,:CSR);

END;

                      

The key to success lies in understanding  what exactly is our REF cursor variable. REF Cursor variables are Oracle’s way of returning a statement handle.  I strongly believe that an example is worth a thousand words, so here it is:

Example 4

 

#!/usr/bin/perl -w
use strict;
use DBI;
 
# Note inclusion of DBD::Oracle data types. Without it, we cannot
# work with REF Cursor variables.
 
use DBD::Oracle qw(:ora_types);
 
my $db = DBI->connect( "dbi:Oracle:Local", "scott", "tiger" )
    || die( $DBI::errstr . "\n" );
$db->{AutoCommit}    = 0;
$db->{RaiseError}    = 1;
$db->{ora_check_sql} = 0;
$db->{RowCacheSize}  = 16;
 
my $dno=10;
my $csr;
my ($ename, $job, $sal, $hiredate);
 
my $SEL = "begin
                               dbi.test(:DNO,:CSR);
           end;";
 
my $sth = $db->prepare($SEL);
 
 
$sth->bind_param_inout( ":DNO", \$dno, 20);
# Now comes the important part.....
$sth->bind_param_inout( ":CSR", \$csr, 0, { ora_type => ORA_RSET } );
$sth->execute();
print "\nNow, we have a valid handle....\n\n";
# Observe that here we are fetching from csr, a handle which was
# prepared and executed by the DBI.TEST procedure.
while ( ($ename, $job, $sal, $hiredate)=$csr->fetchrow_array()) {
        write;
}
no strict;
format STDOUT_TOP =
Employee         Job        Hiredate       Salary
----------------------------------------------------
.
 
format STDOUT =
@<<<<<<<<<<      @<<<<<<<   @<<<<<<<<<     @<<<<<<<
$ename,          $job,      $hiredate,     $sal
.
 
END {
    $db->disconnect if defined($db);
}

 

When executed, this code will produce the following output:

 

bash-3.00$ ./ttt

Now, we have a valid handle....

Employee         Job                           Hiredate       Salary

-------------------------------------------------------------------

CLARK            MANAGER    09-JUN-81      2450

KING             PRESIDEN   17-NOV-81      5000

MILLER           CLERK      23-JAN-82      1300

So, what can we see in this example?  First, we had to explicitly include DBD::Oracle, with the type definitions that are not normally exported by the module. Second, we had to do an “inout” bind, with size set to 0 and with a hash reference which tells DBI  the oracle type of the variable being bound. Third, REF Cursor variables are, in essence, statement handles. When a PL/SQL procedure returns REF Cursor variable, it is just the same as if the handle was produced by using $db->prepare.

What is the most frequent use of REF Cursor variables? I’m a database administrator and I’ve never had to use them, I only needed to support them when the real programmers were using them. The most frequent situations involving use of the REF Cursor variables were migration projects where the source database used result sets as a return from a stored procedure. Databases that are known for encouraging such development style are  Sybase and SQL Server. Any migration of a program which runs against SQL Server or Sybase to a Perl script that runs against Oracle database is likely to need REF Cursor variables. Of course, this part of the article would not be complete without explanation of the “real programmers” reference. For those among you who want to become real programmers, here are some definitions:

http://www.toodarkpark.org/textfile.cgi/computers/humor/real-programmers

I can only wish you luck with your further career endeavors.  Real programmers aside, let’s return to DBI and Oracle. REF Cursor is a special column type that warrants inclusion of DBD::Oracle directly, in order to gain access to the definition of types. The same is true for LOB data types. LOB is an abbreviation for “Large Objects”.  LOB types are used when we need to store large documents, images or other types of media files into the database.

Good thing about the LOB data types is that it is mostly handled automatically by the DBI itself. There are two separate cases which need handling: inserting LOB data and selecting LOB data. To demonstrate loading LOB data into the database, we'll need some database infrastructure:

 

SQL> create table dbi_lob (
  2  id number(10,0) constraint dbi_lob_pk primary key,
  3  name varchar2(128) not null,
  4  data blob);
 
Table created.
 
SQL> create sequence dbi_lob_seq start with 1 nocycle nomaxvalue;
 
Sequence created.
 
SQL> create trigger dbi_log_pk_trg
  2  before insert on dbi_lob
  3  for each row
  4  begin
  5  select dbi_lob_seq.nextval into :new.id
  6  from dual;
  7  end;
  8  /
 
Trigger created.

Unfortunately, normal  Oracle demo tables, belonging to SCOTT do not contain LOB columns. No problems, we have just created everything needed for such a demo. The next script will read the file name from the command line argument and read the whole file into a memory buffer. That memory buffer will then be inserted as a BLOB into the database.

Example 5

#!/usr/bin/perl -w
use strict;
use DBI;
use DBD::Oracle qw(:ora_types);
use Getopt::Long;
my ( $user, $passwd, $db ) = ( "scott", "tiger", "local" );
my ( $file, $id );
my $stat = GetOptions( "u|user=s"     => \$user,
                       "p|password=s" => \$passwd,
                       "d|database=s" => \$db,
                       "f|file=s"     => \$file,
                       "h|help|?"     => \&usage
);
if ( !defined($file) or !$stat ) { usage(); }
my $dbh = db_connect( $user, $passwd, $db );
my $INS = "INSERT INTO DBI_LOB(NAME,DATA) VALUES (:FL,:BUFF)
                    RETURNING ID INTO :ID";
my $sth = $dbh->prepare($INS);
$sth->bind_param( ":FL", $file );
$sth->bind_param_inout( ":ID", \$id, 20 );
my $buff = gobble($file);
$sth->bind_param( ":BUFF", $buff, { ora_type => ORA_BLOB } );
 
$sth->execute();
 
print "BLOB ID=$id inserted\n";
$dbh->commit();
 
END {
    $dbh->disconnect() if defined($dbh);
}
 
sub db_connect {
    my ( $username, $passwd, $db ) = ( @_, $ENV{"TWO_TASK"} );
    my $dbh = DBI->connect( "dbi:Oracle:$db", $username, $passwd )
        || die( $DBI::errstr . "\n" );
    $dbh->{AutoCommit}    = 0;
    $dbh->{RaiseError}    = 1;
    $dbh->{ora_check_sql} = 0;
    $dbh->{RowCacheSize}  = 16;
     return ($dbh);
}
 
sub usage {
    print qq(
      USAGE:$0
               -u <username>  -p <password > -d <database>
               -f <file name>
           
      -------
      This script loads file defined by -f argument into database, as a
      BLOBS. This script was written as an educational tool and is free
      to use and modify as needed.
);
    exit(0);
}
 
sub gobble {
    my $file = shift;
    local $/ = undef;
    if ( !defined($file) ) { usage(); }
    open( FL, "<", $file ) or die "Cannot open file $file for reading:$!\n";
    my $buff = <FL>;
    close FL;
    return ($buff);
}
 

The execution of this script produces very unspectacular, if expected results:

 

$ ./load_lobs -f adodb480.tgz

BLOB ID=1 inserted

$ ./load_lobs -f php-5.1.2.tar.bz2

BLOB ID=2 inserted

 

So, lets check our work:

 

$ ls -l adodb480.tgz php-5.1.2.tar.bz2
-rw-r--r--  1 500 6319905 Mar 30 21:04 php-5.1.2.tar.bz2
-rw-r--r--  1 500  452518 Mar 30 21:04 adodb480.tgz
 
SQL> column name format a25
SQL> select name,length(data) as filesize from dbi_lob;
 
NAME                        FILESIZE
------------------------- ----------
adodb480.tgz                  452518
php-5.1.2.tar.bz2            6319905

 

The number of bytes in the database is equal to the number of bytes produced by the “ls -l” command, which means that both files are successfully loaded into my little home database.

This script inserts binary files into our little DBI_LOB table. The important things are the following:

Bind of the large memory buffer, containing file content (“$buff”) was done by value, rather then by reference.  That way, I didn't have to specify length of the data,  just the type was sufficient. BLOB variables aren't much different from VARCHAR2 or NUMBER variables, they're just bigger and the only thing that needs to be done is to specify the type, to warn Oracle that something big is coming its way.

RETURNING INTO clause saves us trip to over the network, but the target needs to be bound  by reference, using “bind_param_inout” method.

I advocated the naming convention about naming placeholders and variables and violated it two scripts later.  Why did I do that? The answer is very simple: placeholder :FILE is reserved and cannot be used.  The truth of the matter is that I've hit the error below:

DBD::Oracle::st execute failed: ORA-01745: invalid host/bind variable name (DBD ERROR: error possibly near <*> indicator at char 48 in 'INSERT INTO DBI_LOB(ID,NAME,DATA) VALUES (NULL,:<*>file,:buff) RETURNING ID INTO :id')

I distinctly remember using the bind variable :FILE earlier in my career, but there was no other recourse here except changing the placeholder name. Things like that depend upon the version of DBI, the version of DBD::Oracle and the version of Oracle RDBMS used. Obviously, there are still things to discover about this particular combination. This error forced me to break my own rule. Such is life.

That concludes the section about inserting LOB variables into database. How do we get them out of the database?  Database is not a black hole, what comes in, has to come out sooner or later.

Few lines earlier, I said that LOB variables can be thought of as big VARCHAR2 variables. That especially applies to selecting them from the database. We can use normal binds, LOB variable is treated as any other variable would be. There are, however, some things  that need careful handling. It is time to learn about some other properties of the database handles. Here they are, taken directly from the DBI online documentation:

 

LongReadLen (unsigned integer, inherited)

The LongReadLen attribute may be used to control the maximum length of 'long' type fields (LONG, BLOB, CLOB, MEMO, etc.) which the driver will read from the database automatically when it fetches each row of data.

The LongReadLen attribute only relates to fetching and reading long values; it is not involved in inserting or updating them.

A value of 0 means not to automatically fetch any long data. Drivers may return undef or an empty string for long fields when LongReadLen is 0.

The default is typically 0 (zero) bytes but may vary between drivers. Applications fetching long fields should set this value to slightly larger than the longest long field value to be fetched.

Some databases return some long types encoded as pairs of hex digits. For these types, LongReadLen relates to the underlying data length and not the doubled-up length of the encoded string.

Changing the value of LongReadLen for a statement handle after it has been prepare'd will typically have no effect, so it's common to set LongReadLen on the $dbh before calling prepare.

For most drivers the value used here has a direct effect on the memory used by the statement handle while it's active, so don't be too generous. If you can't be sure what value to use you could execute an extra select statement to determine the longest value.

LongTruncOk (boolean, inherited)

The LongTruncOk attribute may be used to control the effect of fetching a long field value which has been truncated (typically because it's longer than the value of the LongReadLen attribute).

By default, LongTruncOk is false and so fetching a long value that needs to be truncated will cause the fetch to fail. (Applications should always be sure to check for errors after a fetch loop in case an error, such as a divide by zero or long field truncation, caused the fetch to terminate prematurely.)

If a fetch fails due to a long field truncation when LongTruncOk is false, many drivers will allow you to continue fetching further rows.

These attributes are typically set for the database handle, immediately after the connection is established. This is the reason for having separated connecting to database in a separate subroutine in the last script. The example which will fetch LOB data from the database will use the same skeleton, command line arguments, usage and everything else. It will only modify  subroutine db_connect and  the SQL executed within the script. Cut and paste between scripts works really well with Perl and that is one more reason for loving it.

So, in the example 6,  LongTruncOk will be set to false and LongReadLen will be set to 5 MB The first file (ID=1) is around 400k in length and it will not make any problems. The other file (ID=2) is, however, more then 6 MB long and will help us to demonstrate what happens when the length of the LOB is larger then LongReadLen. Without further ado, here it is:

 

Example 6

#!/usr/bin/perl -w
use strict;
use DBI;
use Getopt::Long;
my ( $user, $passwd, $db ) = ( "scott", "tiger", "local" );
my ( $id, $blob, $file ) = (1);
my $stat = GetOptions( "u|user=s"     => \$user,
                       "p|password=s" => \$passwd,
                       "d|database=s" => \$db,
                       "i|id=s"       => \$id,
                       "h|help|?"     => \&usage
);
if ( !$stat ) { usage(); }
my $dbh = db_connect( $user, $passwd, $db );
my $SEL = "SELECT name,data FROM dbi_lob WHERE id=:ID";
my $sth = $dbh->prepare($SEL);
$sth->bind_param( ":ID", $id );
$sth->execute();
($file,$blob)=$sth->fetchrow_array();
spew($file);
print "File $file written successfully. Len=",length($blob),"\n";
 
END {
    $dbh->disconnect() if defined($dbh);
}
 
sub db_connect {
    my ( $username, $passwd, $db ) = ( @_, $ENV{"TWO_TASK"} );
    my $dbh = DBI->connect( "dbi:Oracle:$db", $username, $passwd )
        || die( $DBI::errstr . "\n" );
    $dbh->{AutoCommit}    = 0;
    $dbh->{RaiseError}    = 1;
    $dbh->{ora_check_sql} = 0;
    $dbh->{RowCacheSize}  = 16;
    $dbh->{LongReadLen}   = 5242880;
    $dbh->{LongTruncOk}   = 0;
    return ($dbh);
}
 
sub usage {
    print qq(
      USAGE:$0
               -u <username>  -p <password > -d <database>
               -i id

           

      -------

      This script unloads blob with id defined by -i parameter from the

      database. This script was written as an educational tool and is

      free to use and modify as needed.

);
    exit(0);
}
 
sub spew {
    my $file = shift;
    if ( !defined($file) ) { usage(); }
    open( FL, ">", $file ) or die "Cannot open file $file for writing:$!\n";
    printf FL ("%s",$blob);
    close FL;
}

 

 

Unloading of adodb480.tgz goes without a problem:

 

$ rm -i adodb480.tgz
rm: remove regular file `adodb480.tgz'? y
 
$ ./unload_lobs  -i 1
File adodb480.tgz written successfully. Len=452518
 
$ tar ztvf adodb480.tgz|head -5
-rw-rw-rw- 0/0           12458 2006-03-10 03:58:55 adodb/adodb-active-record.inc.php
-rw-rw-rw- 0/0            8304 2006-03-10 03:58:54 adodb/adodb-csvlib.inc.php
-rw-rw-rw- 0/0           21041 2006-03-10 03:58:55 adodb/adodb-datadict.inc.php
-rw-rw-rw- 0/0            8541 2006-03-10 03:58:56 adodb/adodb-error.inc.php
-rw-rw-rw- 0/0            2748 2006-03-10 03:58:56 adodb/adodb-errorhandler.inc.php

 

Not only is the file size correct, tar and gunzip utilities also work correctly. Now, let's try with the id=2,  the source code for PHP 5.1.2, which exceeds 5 MB, the value that we've set our LongReadLen to:

 

$ ./unload_lobs -i 2

DBD::Oracle::st fetchrow_array failed: ERROR fetching field 3 of 2. LOB value truncated from 6319905 to 5242880. DBI attribute LongReadLen too small and/or LongTruncOk not set [for Statement "SELECT name,data FROM dbi_lob WHERE id=:ID" with ParamValues: :id='2'] at ./unload_lobs line 19.

DBD::Oracle::st fetchrow_array failed: ERROR fetching field 3 of 2. LOB value truncated from 6319905 to 5242880. DBI attribute LongReadLen too small and/or LongTruncOk not set [for Statement "SELECT name,data FROM dbi_lob WHERE id=:ID" with ParamValues: :id='2'] at ./unload_lobs line 19.

 

DBI killed our script because RaisError was set and LongTruncOk was not set. This is a desired outcome. If LongTruncOk was set, the script would have produced a shortened version of the original file which would be useless. Unfortunately, LongReadLen can not be set to -1 or any other catch-22 value, to prevent the handler from kicking in. If you're unloading LOB columns from database into a Perl script, the script must have  enough memory space available to accommodate the largest of the LOB columns. In all other respects, LOB columns are just like the other VARCHAR2 or NUMBER columns, just larger. In this case, size does matter.

This is not the end of the story. We can work with LOB columns on piece by piece base, using either DBMS_LOB database package or specific abilities for DBD::Oracle, as described in the online documentation. This is, however, an advanced use of BLOB fields, which has no place in an introductory document like this. Advanced LOB handling capabilities of DBD::Oracle are beyond the intended scope of this document.

Question of Life, Universe & Everything

This is the ending part of this, hopefully useful, document in which the author rides into sunset, after few remarks and wisecracks. To achieve the true Hollywood happy end, I must list and comment on the unsupported features and the most appropriate use of  Perl and Oracle combination. Without further ado, here are the unsupported features that I consider important:

l       Array interface

l       Transparent application failover (TAF).

l       Direct load/unload

 


Oracle DBI Array Interface

The absence of array interface is probably the single worst problem of DBD::Oracle. DBI does have an array interface. Here is a code snippet for an array bind:

 

my @values;

my $INS=”INSERT INTO TAB VALUES (:PLACEHOLDER)”;

$sth=$db->prepare($INS);

$sth->bind_param_array(“:PLACEHOLDER”,\@array);

$sth->execute_array( { ArrayTupleStatus => \my @status });

 

It will execute like a charm, without reporting any errors, but it will do so by cheating,

that is by breaking the code above into a loop in which the statement would be executed once for every iteration, not just once per loop. To prove that, one can start SQL trace by activating the event 10046 for that session.

That, of course, takes away all advantages of array interface. The point is precisely in executing once per an array and therefore decreasing the time spent in communicating with database. That means that tools like SQL*Loader will be much, much faster for loading of large data sets then Perl. Of course, one of the intended uses for Perl in tandem with Oracle is precisely as a fancy and a very smart loader.

Perl can parse any file, due to its phenomenal regular expression capabilities, but the other part, the one that would help me in writing Perl scripts to load data into my databases is messed up beyond help and despite numerous promises, array interface has still not been delivered. The same applies to the direct loads:  it doesn't work. The DBD::Oracle driver doesn't have anything that would help you to load data faster.


Oracle DBI and RAC

Transparent failover capabilities are also very important. Assembling RAC database is no longer excruciatingly expensive. As a matter of fact, it can be done by anyone willing to invest in 2 PC boxes with at least 1GB per PC, FireWire or multihost SCSI adapter, corresponding external storage device, two additional network interfaces (preferrably GigaBit ethernet) and a dedicated network switch. That price is well within reach of an ordinary DBA like me, without having to give up food and gas for six months.

As a result, commercial RAC configurations abound these days. That makes TAF more important then ever. What is TAF? Short answer to this question is that it is a mechanism that makes it possible for an application to continue without reconnecting to the database after the instance to which the application was connected fails. TAF capabilities have several levels. The lowest level is that the session content is preserved, while the highest level implies moving the session transparently to another instance, without losing the current transaction. In other words, the user wouldn't even notice that the instance to which he was connected has just failed.

Unfortunately, the term “user” doesn't cover people running Perl scripts. If the instance fails, the script fails too. No failover capabilities whatsoever are built in DBD::Oracle.


When to use the Perl DBI with Oracle

That means that Perl is not necessarily the right tool for commercial applications. The most important area for Perl is to help administrators with patchwork and customization not covered by other, more solid and feature-rich commercial tools. If someone decided to code the company's financial software in Perl, the decision would imply a severe performance disadvantage when compared with other programming languages and no TAF capabilities, which would annoy users to no end during downtimes, whether intentional or accidental.

Such an application would have serious shortcomings when compared with competing application systems. That is the reason why there are very few pieces of commercial software which runs on top of Oracle RDBMS and is written in Perl. An exception to the rule is Hotsos profiler, a tool to analyze Oracle trace files. More information about this excellent tool can be found on http://www.hotsos.com.

In exchange for the tools and features that are so desperately needed by people using Perl and Oracle, DBI offers a whole bunch of metadata extraction, profiling and monitoring capabilities which are, generally speaking, far inferior to the Oracle RDBMS built in capabilities of the same type and completely useless for an Oracle developer or a DBA.

That seems to be a general direction of DBI development. From the recent debate on USENET, I know that DBI V2 is planned and that the author has envisioned a really cool  diagram which would make it possible for DBI to get infected with abbreviations like CORBA, SOAP, EJB and alike. Something as trivial and useless as array interface or direct load capabilities wasn't even mentioned. Cave people were very good at drawing flowcharts and diagrams, but they eventually went extinct. I hope that the same will not happen to DBI which will become huge and useless, color coded monstrosity.

Perl module for Apache is complex, poorly documented, few years late and still in its infancy. CGI applications are on their way out, both for security and performance reasons. That also severely limits the usefulness of Perl. The best proof of my statements is the advance of PHP and increasing number of  PHP pages around. PHP is very similar to Perl, easy to learn and much better suited for web applications.

So, what is Perl good for? Why does it still make sense to learn it?

 

l       Perl is still the best and the most practical scripting language for writing OS level system scripts, with many precious capabilities, like in-situ editing.

l       Wealth of modules on CPAN has solutions for almost any problem. You can parse PDF documents, write Excel spreadsheets, cheerfully write XML files and code/decode emails. You can also do date arithmetics, infinite precision math, create lovely graphics and sequence genes. Fortunately, nobody has come up with an idea to look for WMD on CPAN yet, but I wouldn't bet that they're not there. CPAN, of course, is an abbreviation for “Comprehensive Perl Archive Network”. No other language comes even close to the wealth of the 3rd party application modules which are available on CPAN. CPAN page was mentioned on the beginning of this article, but it is so big that one mention isn't enough. CPAN can be explored at  http://www.cpan.org

l       Perl has a built in report-writing capabilities, which is a rarity after the demise of programming languages like COBOL and RPG. In some of the example scripts above, you will find things like “format” and “write” commands.”. Perl can automatically account for the number of lines per page, formatting, page headers, form feeds and things like that. In their infinite wisdom,  the creators of Perl decided to remove that part from the next version of Perl (Perl6) and make it available as a module. In return, the “switch” command, supported by literally all other programming languages, will find its place in Perl6. To summarize, Perl6 will not have the things that made Perl5 stand out among all other programming languages but will have that what everybody else has, too. Good trade, if there ever was one! I believe that some people from Enron and Andersen Consulting have found new jobs on Perl design committee. They should remember that Perl stands for Practical Extraction and REPORTING Language.

l       Perl is still unsurpassed in the ease and elegance with which it handles regular expressions. Not even the best efforts of the geniuses from the Perl6 design committee were able to take it away. At least not yet.

l       If you do learn Perl now, you'll be able to tell your grandchildren about the experience. Don't leave your home without it. It's free!

Finally, what is the answer to the question of life, universe and everything? 42, of course.

You should have known that!

Mladen Gogala is available to assist you with your toughest Perl DBI problems.  Just call.
For a complete treatment of PHP and Oracle, see Mladen Gogala's great new book "Easy Oracle PHP". 

This unique book focuses on removing the guesswork from creating dynamic web pages with Oracle content, and it's only $19.95.


 

 

  

 
  
 
 
 

 
 
 

Oracle training Excel
 
Oracle performance tuning software 
 

 

Burleson is the American Team

Note: This Oracle documentation was created as a support and Oracle training reference for use by our DBA performance tuning consulting professionals.  Feel free to ask questions on our Oracle forum.

Verify experience! Anyone considering using the services of an Oracle support expert should independently investigate their credentials and experience, and not rely on advertisements and self-proclaimed expertise. All legitimate Oracle experts publish their Oracle qualifications.

Errata?  Oracle technology is changing and we strive to update our BC Oracle support information.  If you find an error or have a suggestion for improving our content, we would appreciate your feedback.  Just  e-mail:  and include the URL for the page.


                    









Burleson Consulting

The Oracle of Database Support

Oracle Performance Tuning

Remote DBA Services


 

Copyright © 1996 -  2014

All rights reserved by Burleson

Oracle © is the registered trademark of Oracle Corporation.