London Perl Workshop / 26th Nov 2005
Databases and Perl
Databases and Perl
Dave Cross
Databases and Perl
-
Data is important
-
Databases are important
-
A large proportion of Perl programs need to write to and/or read from databases
DBI.pm
-
The basis for all modern Perl database access
-
You should be using DBI (or something based on DBI)
How DBI Works
-
Program uses DBI.pm
-
Create a connection to a particular type of database
-
DBD module gets loaded
-
DBD translates from DBI API to database specific calls
How DBI Works (Diagram)
| DBI |
|---|
| DBD |
| Database API |
| Database |
How DBI Works (Code)
-
use DBI;
my $dbh = DBI->connect("dbi:mysql:$some_stuff", $user, $pass); -
In theory you can port a program to another database
-
Just change the connection line
Selecting Data
-
Prepare the SQL statement
-
my $sth = $dbh->prepare('select name, genre from artist'); -
my $sth = $dbh->prepare("select title, from song where artist = '$id'"); -
Check return values (syntax errors)
Selecting Data
-
Execute the statement
-
$sth->execute
-
Still need to check for errors
Selecting Data
-
Fetch the returned data
-
while (my @row = $sth->fetchrow_array){ print "@row\n"; } -
Fields are returned in the same order as they are defined in the query
Selecting Data
-
Other fetch methods are available:
-
fetchrow_arrayref
-
fetchrow_hashref (keys are column names)
-
fetchall_arrayref
-
fetch (alias for fetchrow_arrayref)
-
-
Many more added each week
Some Caveats
-
If you're using a fetch method that returns an array
-
Never use "select *"
-
For (hopefully) obvious reasons
-
-
If you're using a fetch method that returns a hash
-
Ensure all your columns have (unique) names
-
For (hopefully) obvious reasons
-
Inserting, Updating & Deleting Data
-
Statements that don't return data can be executed the same way
-
my $sql = "update table1 set col1 = '$val' where id_col = $id";my $sth = $dbh->prepare($sql);
$sth->execute;
-
But there's a shortcut
-
$rows = $dbh->do($sql);
Binding Data
-
while (<FILE>) { chomp; my @data = split;my $sql = "insert tab values ($data[0], $data[1], $data[2]");$dbh->do($sql); }
-
Recompiles the SQL every time
-
Very inefficient
Binding Data
-
Prepare statement once, use many times
-
my $sql = "insert tab values (?, ?, ?)";
my $sth = $dbh->prepare($sql);
while (<FILE>) { my @data = split;bind_param(1, $data[0]); bind_param(2, $data[1]); bind_param(3, $data[2]);$sth->execute; }
-
Bonus - binding handles quoting for you
Binding Data
-
Even easier - extra parameters to
execute -
my $sql = "insert tab values (?, ?, ?)";my $sth = $dbh->prepare($sql);
while (<FILE>) { chomp;my @data = split;
$sth->execute(@data); }
Binding by Name
-
Having unnamed placeholders can get confusing
-
my $sql = 'insert into complex_table values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; -
Good chance of getting the variables in the wrong order
-
By the way - there's a basic maintainability error in that SQL
Binding by Name
-
Bind by name instead
-
my $sql = 'insert into complex_table (id, code, name, addr, email, url # etc, etc ) values (:id, :code, :name, :addr, :email, :url, # etc, etc );my $sth = $sql->prepare($sql);
$sth->bind_param(':id', $id); $sth->bind_param(':code', $code); # etc$sth->execute;
Even Easier Binding by Name
-
Store your data in a hash
-
my %complex_data = (id => 42, code => 'H2G2', # etc );# and later... foreach my $col (keys %complex_data) { $sth->bind_param(":$col", $complex_data{$col}; }
Downside of Binding by Name
-
Most DBDs don't support it
-
Which is a bit of a bugger
-
Oracle does
-
So does PostgreSQL (tho' the docs discourage its use)
-
Check your DBD documentation
-
Email your friendly neighbourhood DBD author
Some Tips
-
Make your life as easy as possible
-
Don't hard-code connection data
-
Config file, command line options, environment variables
-
-
Send all of your data access through one function
-
Store SQL queries externally and reference them by name
-
(Use named parameters if you can)
Sample Code
my $dbh;
sub run_sql {
my ($sql_statement, %args) = @_;
my $sql = get_sql($sql_statement);
$dbh = get_dbh() unless $dbh;
my $sth = $dbh->prepare($sql);
foreach my $col (keys %args) {
$sth->bind_param(":$col", $args{$col});
}
return $sth->execute; }
Who Hates SQL?
-
SQL is tedious to write
-
Most SQL isn't hard
-
Most SQL is really predictable
Predictable SQL
-
Select the id and name from this table
-
Select all the details of this row
-
Select something about related tables
-
Update this row with these values
-
Insert a new record with these values
-
Delete this record
Introducing Class::DBI
-
Class::DBI allows you to write code like this
-
use Music::Artist;
foreach my $art (Music::Artist->retrieve_all) { print $art->id, ' ', $art->name, "\n"; } -
Or like this
-
my $art = Music::Artist->retrieve($id); if ($art) { foreach my $song ($art->songs) { print $song->title, "\n"; } }
More Class::DBI Examples
-
Update an object
-
$art->name('Archduke Ferdinand'); $foo->update; -
Insert an object
-
$art = Music::Artist->insert(\%new_art);
-
How about this
-
$art = Music::Artist->find_or_create(\%new_art);
More Class::DBI::Examples
-
Deleting an object
-
$celine_dion->delete;
-
Deleting many objects
-
Music::Artist->search(genre => 'Rap')->delete_all;
The Man Behind the Curtain
-
Class::DBI is an Object Relational Mapper
-
It maps a relational database table onto a class
-
It maps a relational database row onto an object
-
You tell it about your database tables
-
It creates the SQL
-
For the most common cases
-
A Class::DBI Class
package Music::DBI;
use base 'Class::DBI';
# Don't hard-code these!!
Music::DBI->connection('dbi:mysql:musicdb', 'username',
'password');
package Music::Artist;
use base 'Music::DBI';
Music::Artist->table('artist');
Music::Artist->columns(All => qw/id name genre/);
Music::Artist->has_many(songs => 'Music::Song');
package Music::Song;
use base 'Music::DBI';
Music::Song->table('song');
Music::Artist->columns(All => qw/id title artist/);
Music::Artist->has_a(artist => 'Music::Artist');
Other Class::DBI Features
-
Inflate attributes to objects
-
Triggers before/after events
-
Constraints
-
Define your own SQL
-
Class::DBI::FromCGI & Class::DBI::AsForm
Improving on Class::DBI
-
Class::DBI makes life a lot easier
-
You very rarely need to type actual SQL
-
Laziness is a virtue
-
Therefore more laziness is better
-
Let's take another look at that class definition
A Class::DBI Class
package Music::DBI;
use base 'Class::DBI';
# Don't hard-code these!!
Music::DBI->connection('dbi:mysql:musicdb', 'username',
'password');
package Music::Artist;
use base 'Music::DBI';
Music::Artist->table('artist');
Music::Artist->columns(All => qw/id name genre/);
Music::Artist->has_many(songs => 'Music::Song');
package Music::Song;
use base 'Music::DBI';
Music::Song->table('song');
Music::Artist->columns(All => qw/id title artist/);
Music::Artist->has_a(artist => 'Music::Artist');
More Laziness Needed
-
Bear in mind this is a very simple system
-
You'll usually need a lot more tables with a lot more columns
-
We specify the relations twice (once in each direction)
-
We specify all of the column names
-
We specify the table name
-
We have already entered all this information once
Database Metadata
CREATE TABLE artist (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
genre VARCHAR(5)
);
CREATE TABLE song (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(50),
artist INTEGER FOREIGN KEY REFERENCES artist(id)
);
Don't Repeat Yourself
-
CREATE TABLE song ( id INTEGER PRIMARY KEY AUTO_INCREMENT, title VARCHAR(50), artist INTEGER FOREIGN KEY REFERENCES artist(id) ); -
package Music::Song; use base 'Music::DBI'; Music::Song->table('song'); Music::Artist->columns(All => qw/id title artist/); Music::Artist->has_a(artist => 'Music::Artist'); -
Repetition is bad
-
Don't do it (unless you absolutely have to)
Class::DBI::Loader
-
Most of the information we need to build the class is already in the database metadata
-
Let's let the computer do all that tedious work
-
We'll just step in if it needs our help
-
Class::DBI::Loader does this for us
A Class::DBI::Loader Class
-
package Music;
use Class::DBI::Loader;
Class::DBI::Loader->new(dsn => 'dbi:mysql:musicdb', user => 'username', password => 'password', relationships => 1, namespace => 'Music'); -
Scans the database and creates objects as appropriate
-
An object for each table (name based on table name and namespace)
-
Attributes for each column
-
Relationships for each foreign key
Advantages of Class::DBI::Loader
-
Makes it very easy to get simple database systems up quickly
-
No need to repeat database metadata
-
No need to write simple SQL
-
Power to override default behaviour when required
Other ORM Systems Are Available
-
Class::Persist
-
DBIx::Class
-
Tangram
-
SPOPS
-
Alzabo
Further Information
-
Module documentation (CPAN)
-
Programming the Perl DBI - Tim Bunce
-
Advanced Perl Programming - Simon Cozens
Databases and Perl
-
Any Questions?
