Getting Serious
with the Perl DBI
Making simple things easy and difficult things possible
Tim Bunce

Why Perl for Databases?
Ideally suited to data processing:
parsing, cleansing / filtering / merging and formatting
Applications
Data Migration
Data acquisition and reporting
Web Page creation (static and dynamic)
Perl lets you build sharper tools faster

What Is the Perl DBI?
The standard Database Interface for Perl
“A perl module and specification that defines a consistent database interface, independent of the actual database being used”

What drives the design of the DBI?
Goals of the DBI
Be simple to use for simple applications
Have sufficient flexibility to accommodate unusual functionality and non-SQL databases
Conform to applicable standards (ODBC etc.)
Enable the creation of database-independent Perl scripts without being limited to the lowest functionality
Be free with open source code
A ‘higher-level’ interface than ODBC/JDBC

Where does the DBI fit?

A Simple Example
use DBI;
$dbh = DBI->connect('dbi:ODBC:PRICE', 'user', 'password',
{ RaiseError => 1, AutoCommit => 0 });
$upd = $dbh->prepare('UPDATE prices SET price=? WHERE prod=?');
$ins = $dbh->prepare('INSERT INTO prices(prod,price) VALUES (?,?)');
while ($line = <COST>) {
    chop $line;
    ($prod, $price) = split /,/, $line;
    $rows = $upd->execute($price, $prod);
    $ins->execute($prod,$price) if $rows == 0;
}
$db->commit;

So why use the Perl DBI?
Because...
It delivers what it promises
It’s simple and quick to use
It’s fast, flexible and well proven
It’s here, there and everywhere
It’s free, with source
Drivers available for many databases
Commercial support is available
It has a large user base and a strong future

Comparing DBI and Alternatives
(Attempting the Impossible!)

Under the Hood
DBI defines and implements an interface
Driver modules do much of the real work
DBI provides default methods, functions, tools etc  for drivers
Not limited to the lowest common denominator -  mechanism provided for driver specific extensions
Designed and built for speed
Valuable detailed call tracing/debugging built-in

Architecture of the DBI Classes #1

Architecture of the DBI Classes #2

Anatomy of a DBI Handle

Method call walk-through
Consider a simple prepare call:
  $dbh->prepare(...)
$dbh is reference to a DBI::db object (regardless of driver)
DBI::db::prepare is an alias for DBI dispatch method
Dispatch calls driver’s prepare method something like this:
   my $inner_hash_ref = …     # from tie magic
   my $implementor_class = …  # from DBI magic data
   $inner_hash_ref->$implementor_class::prepare(…)
Since driver code gets inner hash it has fast access to the hash contents

Give me SPEED!
DBI designed for speed from day one.
DBI dispatch written in hand-crafted XS/C.
Dispatch to XS driver methods optimized.
Cached attributes returned by dispatch.
Placeholders give maximum server performance.
Binding columns and/or fetchrow_arrayref give maximum client-side fetch performance.
DBI overhead is generally insignificant.

What's the difference?
while(@row = $sth->fetchrow_array) {  }
one field:   3100 fetches per cpu second
ten fields:  1000 fetches per cpu second
while($row = $sth->fetchrow_arrayref) {  }
one field:   5300 fetches per cpu second
ten fields:  4000 fetches per cpu second
Notes:
Timings made on an old Sparc 10 using DBD::Oracle.
Timings assume instant record fetch within driver.
Fields all one char. @row would be even slower for more/bigger fields.

Give me SAFETY!
A recipe for safety
Use transactions (don't use AutoCommit)
Use RaiseError
Use eval { … } to catch errors and rollback

Safety by example
use DBI;
use Text::Wrap;
$dbh = DBI->connect(…, …, …, {
    AutoCommit => 0, RaiseError => 1
});
eval {
    $sth = $dbh->prepare('insert into table values (?)');
    foreach (@array_of_long_strings) {
        $text = wrap($_);
        $sth->execute($test);
    }
};
$@ ? $dbh->rollback : $dbh->commit;

Give me (a vague impression of ) SAFETY!
Safety on the cheap (without transactions)
push change information onto an array
undo in reverse order, in place of rollback
consider signals etc.

Wheels within wheels
Watching the DBI in action
DBI has detailed call tracing built-in
The trace be very helpful in understanding application behavior and for debugging
Shows parameters and results
Trace information can be written to a file
Not used often enough!

Our program for today...
 1: #!/usr/bin/perl -w
 2:
 3: use DBI;
 4:
 5: $dbh = DBI->connect('user', 'passwd', { RaiseError => 1 });
 6:
 7: $upd = $dbh->prepare("UPDATE prices SET price=? WHERE prod=?");
 8: $ins = $dbh->prepare("INSERT INTO prices (prod,price) VALUES(?,?)");
 9: $rows = $upd->execute(42, "Widgets");
10: $ins->execute("Widgets", 42) if $rows == 0;
11:
12: $dbh->disconnect;

Trace Level 1
Trace Level 1 shows results only:
    <- connect= DBI::db=HASH(0xe0abc) at DBI.pm line 356.
    <- STORE('PrintError', 1)= 1 at DBI.pm line 382.
    <- STORE('AutoCommit', 1)= 1 at DBI.pm line 382.
    <- STORE('RaiseError', 1)= 1 at DBI.pm line 382.
    <- prepare= DBI::st=HASH(0xe1238) at test.pl line 7.
    <- prepare= DBI::st=HASH(0xe1504) at test.pl line 8.
    <- execute= '0E0' at test.pl line 9.
    <- execute= 1 at test.pl line 10.
    <- disconnect= 1 at test.pl line 11.
    <- DESTROY= undef
    <- DESTROY= undef
    <- DESTROY= undef

Trace Level 2
Trace Level 2 shows calls with parameters and more:
    -> connect for DBD::ODBC::dr (DBI::dr=HASH(0x13dfec)~0xe14a4
                                  'demo' 'user' 'passwd' HASH(0xe0a10))
    <- connect= DBI::db=HASH(0xe0ab0) at DBI.pm line 356.
    -> STORE for DBD::ODBC::db (DBI::db=HASH(0xe0abc)~INNER 'PrintError' 1)
    <- STORE= 1 at DBI.pm line 382.
    -> prepare for DBD::ODBC::db (DBI::db=HASH(0xe0ab0)~0xe0abc
                                  'UPDATE prices SET price=? WHERE prod=?')
    dbd_preparse scanned 2 distinct placeholders
    <- prepare= DBI::st=HASH(0xe1274) at test.pl line 7.

Trace Level 2 (continued)
    -> execute for DBD::ODBC::st (DBI::st=HASH(0xe1274)~0xe110c 42 'Widgets')
    bind 1 <== 42 (size 2/3/0, ptype 5, otype 1)
    bind 2 <== 'Widgets' (size 7/8/0, ptype 4, otype 1)
    dbd_st_execute (for sql f1499360 after)...
    dbd_describe sql 1499360: num_fields=0
    dbd_describe skipped (no result cols) (sql f1499360)
    <- execute= '0E0' at test.pl line 9.
    -> execute for DBD::ODBC::st (DBI::st=HASH(0xe10d0)~0x142e6c 'Widgets' 42)
    bind 1 <== 'Widgets' (size 7/8/0, ptype 4, otype 1)
    bind 2 <== 42 (size 2/3/0, ptype 5, otype 1)
    dbd_st_execute (for sql f1511752 after)...
    dbd_describe sql 1511752: num_fields=0
    dbd_describe skipped (no result cols) (sql f1511752)
    <- execute= 1 at test.pl line 10.

Trace Level 2 (concluded)
    -> disconnect for DBD::ODBC::db (DBI::db=HASH(0xe0ab0)~0xe0abc)
    <- disconnect= 1 at test.pl line 11.
       (outer handle DESTROY ignored)
       (outer handle DESTROY ignored)
    -> DESTROY for DBD::ODBC::st (DBI::st=HASH(0xe110c)~INNER)
    <- DESTROY= undef
       (outer handle DESTROY ignored)
    -> DESTROY for DBD::ODBC::st (DBI::st=HASH(0x142e6c)~INNER)
    <- DESTROY= undef
    -> DESTROY for DBD::ODBC::db (DBI::db=HASH(0xe0abc)~INNER)
    <- DESTROY= undef

State of the Nation
DBI builds and runs on many systems
Unix, Windows, VMS etc.
DBI has many available drivers
ODBC, Oracle, Informix, Ingres, mSQL, mysql, DB2, Adabas, Illustra, Solid, Sybase, Xbase, Postgress, Quickbase, Empress, Fulcrum,  Altera, ...
Merging the ODBC/JDBC ‘inheritance’
data types, meta-data etc.
Specifying the unspecified
Documenting the undocumented… at last!

What's New in the DBI?
Methods
$sth->prepare_cached($statement)
$dbh->table_info and $dbh->tables
$dbh->type_info
Attributes
$sth->{TYPE}     (Standards based type values)
$sth->{SCALE}
$sth->{PRECISION}
And finally… Version 1.00!

The Interactive DBI::Shell
Starting the shell
The long way:
     perl -MDBI::shell -e shell dsn user passwd
     perl -MDBI::shell -e shell
The short way:
    dbish ...
Interactive DSN selection
Command and Result History (uses ReadLine)
Output Redirection for all commands

DBD::Proxy & DBI::ProxyServer
Networking for Non-networked databases
DBD::Proxy driver forwards calls over network to remote DBI::ProxyServer
Proxy can be made transparent by setting DBI_AUTOPROXY environment variable
DBI::ProxyServer doesn't work on Win32 yet (work in progress)

DBD::Proxy & DBI::ProxyServer

What   Next?
SQL for Non-SQL data sources
Networking for Non-Networked data sources
Template driver for command line interfaces
Data types, especially date/time/interval
Non-blocking (method calls return at once)
Multi-threading
Binding In/Out Variables
Binding Arrays

So What's Next Now?
Extend the Shell (batch mode, plug-ins etc)
Driver catch-up to DBI version 1.00
Common test suite for all drivers
Multi-threading (at least thread safety)
Binding In/Out Variables (for DBD::ODBC)
Binding Arrays (for DBD::Oracle)
Non-blocking (method calls return at once)
fetch_scroll (next, previous, first, last etc)
SQL Parser (ODBC) for greater script portability

What's new in DBD::Oracle?
Some, but not all, of DBI 1.00 is supported.
Should catch up in the next release or so.
Now bundles Alan Burlison's explain tool.

Slide 33

The end.
Till next year...