Beginners guide

From ClassDBI

Jump to: navigation, search

Contents

SUMMARY

In summary, Class::DBI maps an instance of an object to a row in a relational database table. When you operate upon the object, you affect the underlying database. Class::DBI takes care of generating all the select, inserts, updates, deletes for you. It makes working with a relational database almost trivial...

INTRODUCTION

If you're anything like me, you started interacting with your Relational Database Management System (RDBMS), such as: Oracle, Postgres, MySQL, Sybase, SQL Server... (in my order of preference :) using Perl via the vendor supplied command line tool. Shelling out in your script, launching the command line tool to execute some SQL, catching the output, parsing it, and using it in your Perl in some meaningful way.

Then I found out about DBI, and the world was beautiful. I could now connect and execute SQL within perl in an concise and intuitive way. But then I got lazy (a good thing) and started writing all kinds of helper modules to do all the things I was doing over and over.

Then I thought, "dummy, I bet someone has already done this", so I searched around the web and CPAN, and found Class::DBI. Since then I've been doing my database interaction exclusively via Class::DBI.

While this beginner's guide overlaps much of the Class::DBI documentation (probably entirely), it is not, I repeat, it is not a substitute for the Class::DBI documentation.

This article attempts to be many things:

  • Pedantic, obviously the seasoned Perl coder can do what I do in 5 lines, in a one-liner, but I felt it was easier to show how CDBI worked by having verbose examples than slick Perl code.
  • Beginners guide showing constistent examples of all Class::DBI methods
  • Show some common patterns in Class::DBI
  • FAQ to things I see on the mailing list

Class::DBI is commonly called CDBI by the lazy. I will be calling it CDBI from now on.

Entity Relationship Diagram (ERD)

This beginner's guide uses the following ERD:

 +------------------+
 |  artistgroup     |
 |------------------|
 |  bandartistid    |
 |  personartistid  |
 +------------------+        +--------------+
     \|/      \|/            | band         |
      |        |             |--------------|
      |        |             | artistid     |
 +------------------+        | creationdate |
 |  artist          |--------| enddate      |
 |------------------|        +--------------+
 |  artistid        |        
 |  name            |        +-----------+
 |  popularity      |--------| person    |
 +------------------+        |-----------|
          |                  | artistid  |
          |                  | gender    |
         /|\                 | haircolor |
 +-----------------+         | birthdate |
 |  cd             |         +-----------+
 |-----------------|
 |  cdid           |
 |  artistid       |
 |  title          |
 |  publishdate    |
 +-----------------+
          |
          |
         /|\
 +-----------------+
 |  track          |
 |-----------------|
 |  cdid           |
 |  songid         |
 |  sequence       |
 +-----------------+
         \|/
          |
          |
    +----------+
    |  song    |
    |----------|
    |  songid  |
    |  name    |
    +----------+

For those unfamiliar with ERDs, ERDs are a graphical representation showing tables, columns within those tables, and the relationships between tables. See entity relationship diagram for more information on ERDs.

The Data Definition Language (DDL) for creating the above tables and relationships is included in the appendix of this document.

SETUP

Base Class

It's usually wise to set up a "top level" class for your entire application to inherit from, rather than have each class inherit directly from Class::DBI. This gives you a convenient point to place system-wide overrides and enhancements to Class::DBI's behavior. For example, it's common to put your database connection into your base class.

package Music::MusicDB;

use strict;
use warnings;

use base 'Class::DBI::Oracle';

my ($dsn, $username, $password) = getConfig();

Music::MusicDB->set_db('Main',
                             $dsn,
                             $username,
                             $password,
                             {AutoCommit=>1},
                            );

sub getConfig {
       return ('dbi:Oracle:o901.mydomain.com','me', 'me');
}

1;


Regular Class

To start off slow let's implement the Artist class. CDBI maps, one to one, a class to a table. The artist class looks like:

package Music::Artist;

use strict;
use base 'Music::MusicDB';

Music::Artist->table('artist');
Music::Artist->columns(All => qw/artistid name popularity/);

1;

You indicate the table via the ->table method. And, you indicate the columns in the table via the ->columns method. When not otherwise specified, CDBI uses the first column "artistid" as the primary key of the table.

CDBI automatically creates methods with the same names as your columns. All the methods are in lower case (by default), regardless of the character case in your database.

So, if I have an Artist object "$artist" and I want to print out the "name" field, I do:

print $artist->name,"\n";

If I want to print a bunch of column values:

@column = qw/name artistid/;
@value = $artist->get(@column);
foreach (@value) {
 print $_,"\n";
}

If I want to change the value of the "name" field:

$artist->name("New Name");

If I want to change a bunch of attributes:

%hash = (name=>"New Name", artistid=>17);
$artist->set(%hash);

CDBI, uses Class::Accessor internally to create the methods. So if you are interested, check out the Class::Accessor docs for how to use it (also a very helpful module).

CRUD (Create, Read, Update, Delete)

Insert (formerly create)

Note that create() is deprecated.

Inserting (creating) data in CDBI, like everything else, is extremely easy. I want to create some artists. I create a script like:

#!/usr/bin/perl

use strict;
use Music::Artist;

my %hash = (artistid=>1, name=>'sublime');
my $obj1 = Music::Artist->insert(\%hash);

%hash = (artistid=>2, name=>'beatles');
my $obj2 = Music::Artist->insert(\%hash);

Simple, I've now created 2 rows in my database. I also have two (2) objects available in my code, which are the instantiation of those two rows, upon which I can perform useful functionality.

Read (aka select)

There are a number of methods with which to instantiate objects in your perl code from the database.

retrieve_all

Like the name indicates this method returns all of the rows in a table.

# Generic form
@obj = Class->retrieve_all;

# In your code
#!/usr/bin/perl

use strict;
use Music::Artist;

my @obj = Music::Artist->retrieve_all;

Which obviously returns an array of objects, one for each row in the database.

retrieve

You use this method to "retrieve" a row by primary key. It returns a single row.

# Generic forms
$obj = Class->retrieve( $id ); 
$obj = Class->retrieve( %hash );

# In your code
my $artist = Music::Artist->retrieve(2);
print $artist->name,"\n";

If the table uses multiple columns to define the primary key, you use a hash to define the key values:

my %key = (CD=>100,songid=>1000);
my $track = Music::Track->retrieve(%key);

print $track->sequence,"\n";

search

Returns an array of objects matching the criteria you supply.

# Generic form
my @objs = Class->search(column1 => $value, column2 => $value ...);

The criteria are exact and additive, meaning that when you say:

my @cds = Music::CD->search(title => "Greatest Hits", year => 1990);

Means "retrieve all the rows where title equals 'Greatest Hits' AND year equals 1990".

Conveniently, if you want to search for any NULL fields:

my @cds = Music::CD->search(title => "Greatest Hits", year => undef);

Returns any albums that haven't yet been released to the public.


search_like

This is a simple search for all objects where the columns specified are "like" the values specified.

# Generic form
@obj = Class->search_like(column1 => $like_pattern, ....);

# In your code
my @cd = Music::CD->search_like(title => 'October%');
my @cd = Music::CD->search_like(title => 'Hits%', artist => 'Various%');

$like_pattern is a pattern given in SQL LIKE predicate syntax. '%' means "any one or more characters", '_' means "any single character". The wildcards can be used in the front, middle, or end of the string.

my @obj = Music::Artist->search_like(name => '_eatles');
my @obj = Music::Artist->search_like(name => '%les');
my @obj = Music::Artist->search_like(name => 'Be%les');

search_where

While not actually a part of the CDBI distribution, it is a nearly indispensable plugin component.

Essentially it lets you create queries that are much more complex than the standard "search" method. Using AbstractSearch you can use "AND"s, "OR"s, <, >, !=, <=, >= and probably more in your search criteria.

To use "search_where" you must "use" Class::DBI::AbstractSearch within your class definition, for example:

package Music::CD
use Class::DBI::AbstractSearch;

# Then in a nearby piece of code

#!/usr/bin/perl
use strict

# select the rows where the (name equals 'Ozzy' or 'Kelly') AND
# (status is not 'outdated')
       my @obj = Music::Artist->search_where(
           name => [ 'Ozzy', 'Kelly' ],
           status => { '!=', 'outdated' },
       );

Class::DBI::AbstractSearch is built upon SQL::Abstract. Look at SQL::Abstract to learn the exactly how to form complicated where clauses.

Iterator

With the inclusion of "iterator" CDBI is CRUDI (hardy har har)

All of the above retrieval methods can also return an "iterator".

#!/usr/bin/perl

use strict;
use Music::Artist;

my $iterator = Music::Artist->retrieve_all;

Notice the above returns a scalar as opposed to an array. This form of return is called an "iterator". CDBI determines which type of return you want based on the target's datatype (i.e. the datatype scalar or array of the field on the left of the equals "=" sign).

You use an iterator like:

my $iterator = Music::Artist->retrieve_all;
       while (my $artist = $iterator->next) {
               print $artist->name,"\n";
       }

You might ask, "what's the big deal". The big deal occurs during execution. The array method returns all the rows and all the columns contained in the "essential" column group (discussed later) from the database, and creates the associated CDBI objects at execution. Whereas, the iterator fetches only the primary keys of all the rows and holds off creating the object until it is asked for, via ->next.

find_or_create

There is another form, that is a combination of search and create. It first looks for the row with the values you specify, if it can't be found, CDBI creates it for you.

my $cd = Music::Artist->find_or_create({name=>'Elvis Presley'});

If CDBI can't find "Elvis" then it will create the King.

Update

The update takes any changes you have made to the data in the object and applies them to the database.

# First get the object
my $obj = Music::Artist->retrieve(1);

# Change the data one column at a time
$obj->name("New Name");
$obj->status("discontinued");
# Or change multiple columns en masse
my $obj2 = Music::Artist->retrieve(2);
$obj2->set(name=>"something", status=>"top 40");

# Store changes in the database
$obj2->update();

Delete

You can probably guess what the delete method does.

#!/usr/bin/perl

use strict;
use Music::Artist;

my @obj = Music::Artist->retrieve_all;
foreach (@obj) {
        $_->delete;
}


RELATIONSHIPS

One of the fundamental points is the "R" of RDBMS, that is "relational". CDBI makes it really easy to navigate from one object to another related object.

has_many

Simply, you define which tables (classes) are children of the table you are working upon. That is, which tables store the primary key of your table, in a column of their table.

This means that for every one of a specific object, there are many directly related objects. When you invoke the "has_many" method, it creates a method in your classes definition which you can then call from your code.

Class->has_many(method_to_create => "Child::Class");

In our model, each artist puts out one or more CDs (what's the point of being an artist if you aren't going to have a CD).

So let's add a bit to the Artist class

Music::Artist->has_many(cds => 'Music::CD');

that lets it relate to the CD class. More specifically, it says an Artist has_many CDs. The entire Artist class looks like:

package Music::Artist;

use strict;
use base 'Music::MusicDB';

Music::Artist->table('artist');
Music::Artist->columns(All => qw/artistid name popularity/);
Music::Artist->has_many(cds => 'Music::CD');

1;

Behind the scenes, it makes a method available called "cds", which you can call on an instance of an Artist.

has_a

Class->has_a(foreign_key_column => 'foreign_class');

Similarly, a CD has to relate to the Artist class. We haven't seen the CD class yet so let's build it now and put in the "has_a" code that relates it to the Artist class.

package Music::CD;

use strict;
use base 'Music::MusicDB';

Music::CD->set_up_table('cd');
Music::CD->has_a(artistid => 'Music::Artist');
Music::CD->has_many(tracks=>Music::Track=>'cdid');

1;

In database speak, you use has_a to obtain data via a foreign key. In other words, your object contains the value of another object's primary key.

The has_a code says "CD" has_a "Artist". It also says, use the artistid in the CD to look up the corresponding value in the primary key of the Artist.

Something you might not have noticed is that the Artist class didn't need to specify how to find the relating CDs. That is because CDBI is smart enough to examine the CD class and learn how it relates to the Artist class.

Now let's load up some sample data (see Appendix 2), so we can do some examples. Whew, that was tough.

Let's read down the hierarchy, via the has_many, from Artist to CD

#!/usr/bin/perl

use strict;
use Music::Artist;

my ($artist) = Music::Artist->search_like(name=>'%Beatles%');

print $artist->name,"\n";

foreach ($artist->cds) {
        print "\t",$_->title,"\n";
}

Here's the output:

The Beatles
        Abbey Road
        Rubber Soul
        Sgt. Peppers Lonely Hearts Club Band
        The Beatles (aka The White Album)
        Revolver

Or if you want to read up the hierarchy, via the has_a, from CD to Artist:

#!/usr/bin/perl

use strict;
use Music::CD;

my ($cd) = Music::CD->search(title=>'Abbey Road');

print "The CD: ",$cd->title," written by the: ",$cd->artistid->name,"\n";

Here's the output:

The CD: Abbey Road written by the: The Beatles

You can chain these together, you can read down one relationship, up another and so on. Here we'll print out all the songs on all the CDs written by the Beatles (at least for the data we have):

#!/usr/bin/perl

use strict; 
use Music::Artist;

my ($artist) = Music::Artist->search_like(name => '%Beatles%');

print $artist->name,"\n";

foreach ($artist->cds) {
        print "\t", $_->title, "\n";
        foreach my $track ($_->tracks) {
                print "\t\t", $track->songid->name,"\n";
        }
}

Here's the output:

The Beatles
        Abbey Road
        Rubber Soul
        Sgt. Peppers Lonely Hearts Club Band
        The Beatles (aka The White Album)
                Back in the U.S.S.R.
                Dear Prudence
                Glass Onion
                Ob-La-Di, Ob-La-Da
                Wild Honey Pie
                Continuing Story of Bungalow Bill
                While My Guitar Gently Weeps
                Happiness Is a Warm Gun
                Martha My Dear
                I'm So Tired
                Blackbird
                Piggies
                Rocky Raccoon
                Don't Pass Me By
                Why Don't We Do It in the Road?
                I Will
                Julia
        Revolver

Many to Many (aka lookups)

Many to many really just means traversing an intersection table (i.e. the Track table). An intersection table is just a table that contains 2 or more has_a definitions, linking to different tables.

In the above example where we went, Artist => CD => Track => Song, the long winded way. CDBI provides a facility for taking some of the work out of it. Really, we're making use of the "mapping" functionality of CDBI, discussed later, but in a very specific context.

Suppose we're really not interested in the sequence of a Track on a CD, we're only concerned (at this time) with the Song.

I'd add to the CD class:

Music::CD->has_many(songs => [ Track => 'songid' ]);

making it look like:

package Music::CD;

use strict;
use base 'Music::MusicDB';

Music::CD->set_up_table('cd');
Music::CD->has_a(artistid => 'Music::Artist');
Music::CD->has_many(tracks=>'Music::Track');
Music::CD->has_many(songs => ['Music::Track' => 'songid']);

1;

then I could use it like:

#!/usr/bin/perl

use strict; 
use Music::CD;
my ($cd) = Music::CD->search_like(title => 'The Bea%');

foreach ($cd->songs) {
	# Note at this point $_ is an instance of a Song
        print $_->name,"\n";
}

might_have

Class->might_have(method_name => Class => (@fields_to_import));

might_have makes using (one-to-one) relationships transparent. A one-to-one relationship is typically used when subtyping a more general class. In our example, there are really 2 types of Artists: Bands, People. Bands and People share some of the same attributes (specifically, name and popularity), but they differ in that a person has: gender, birthdate, haircolor, whereas a band has: creationdate, breakupdate.

Let's change the Artist class (once again) to accomondate the subtypes of Person and Band:

package Music::Artist;
use strict;
use base 'Music::MusicDB';

Music::Artist->table('artist');
Music::Artist->columns(All => qw/artistid name popularity/);
Music::Artist->has_many(cds => 'Music::CD');
# Adding the might_have definitions below
#
Music::Artist->might_have(
        person => 'Music::Person' => qw/gender haircolor birthdate/);
Music::Artist->might_have(
        band=> 'Music::Band' => qw/creationdate breakupdate/);

1;

The cool thing is how CDBI handles the might_have relationship. CDBI lets you access the subtype's attribute "as if" they were in your super-type. CDBI will automagically create a coresponding row in the subtype table, if one doesn't already exist. For example:

#!/usr/bin/perl

use strict;
use Music::Artist;
use Music::Person;

my $artist = Music::Artist->retrieve(name=>'Elvis Presley');
$artist->gender('M');
$artist->haircolor('Brown');
$artist->update;

my $a =  Music::Artist->retrieve(name=>'Elvis Presley');
print $a->haircolor,"\n";
    • NOTE**: You cannot retrieve/search via the attributes in the subtype. That is, you cannot do:
my $b = Music::Artist->retrieve(gender=>'M');

You'll get an error like:

gender is not a column of Music::Artist at ...

Mapping

TRIGGERS

__PACKAGE__->add_trigger(trigger_point_name => \&code_to_execute);

Triggers are bits of code that run at predefined stages during an object or column's retrieval, creation, and deletion. You can create any number of triggers for each point, but you cannot specify the order in which they will be run.

Below are the list of triggers:

before_create  
after_create
before_set_$column
after_set_$column
before_update
after_update
before_delete
after_delete
select

For example, let's set the PUBLISHDATE of a CD if it is not defined.

package Music::CD;

use strict;
use base 'Music::MusicDB';

Music::CD->set_up_table('cd');
Music::CD->has_a(artistid => 'Music::Artist');
Music::CD->has_many(tracks=>'Music::Track');
Music::CD->has_many(songs => ['Music::Track' => 'songid']);

__PACKAGE__->add_trigger(before_create => \&make_publish_date);
sub make_publish_date {
}
1;

HELPER FUNCTIONS (FOR FREE)

Please contribute text.

ORDERING

Please contribute text.

LOWER LEVEL CODING WITH DBI

Please contribute text.

TRAPPING ERRORS

Please contribute text.

CUSTOM/ADVANCED SQL

You can create your own SQL in CDBI too. This invaluable when you want to use database SQL functions or you need to do complex queries like joins and subqueries.

CDBI gives you a couple of ways to make custom sql. If all you need to do is change the "where" clause of your SQL (regardless of how complex) you can use either the "add_constructor" or "retrieve_from_sql" constructs. If your database supports them you can even do subqueries within these.

But, if you need to return columns that are calculated (like: sum, count, min, max...) or you want to perform a join then you leverage the underlying technology of Ima::DBI that is inherited by CDBI. You access this functionality via the "set_sql" construct.

add_constructor

__PACKAGE__->add_constructor(method_name => 'SQL_where_clause');

When used in your class definition, this creates a method, "method_name", which you can call in your executable, which returns a list of objects that match your "SQL_where_clause".

For example, for some reason I need to select the ARTISTs whose name length is greater than 15 characters. So, I could do it by changing the ARTIST class like:

package Music::Artist;

use strict;
use base 'Music::MusicDB';

Music::Artist->table('artist');
Music::Artist->columns(All => qw/artistid name popularity/);
Music::Artist->has_many(cds => 'Music::CD');
Music::Artist->might_have(
        person => 'Music::Person' => qw/gender haircolor birthdate/);
Music::Artist->might_have(band=> 'Music::Band' => 
        qw/creationdate breakupdate/);

#
# Add the add_constructor for looking for name lengths
#
__PACKAGE__->add_constructor(long_names => qq{ length(name) > 15 });

1;


Then in my executable I'd do:

#!/usr/bin/perl

use strict;
use Music::Artist;

foreach (Music::Artist->long_names) {
        print $_->name,"\n";
}

If I want to be tricky, and not so hard coded I can pass in the length of the names I'm looking for. First I need to change the add_constructor to accept a parameter, or placeholder, indicated by the "?". In my case I'm just going to add a new add_constructor:

package Music::Artist;

use strict;
use base 'Music::MusicDB';

Music::Artist->table('artist');
Music::Artist->columns(All => qw/artistid name popularity/);
Music::Artist->has_many(cds => 'Music::CD');
Music::Artist->might_have(
        person => 'Music::Person' => qw/gender haircolor birthdate/);
Music::Artist->might_have(band=> 'Music::Band' => 
        qw/creationdate breakupdate/);

__PACKAGE__->add_constructor(long_names => qq{ length(name) > 15 });
__PACKAGE__->add_constructor(dynamic_long_names => qq{length(name)> ?});

1;

And execute it like:

#!/usr/bin/perl

use strict;
use Music::Artist;

foreach (Music::Artist->dynamic_long_names(20)) {
        print $_->name,"\n";
}

Lastly, if I wanted to do a subquery within my where clause, I can do that too. Lets return only the artist who have more than one CD.

Again, I'll add use "add_constructor" in my Music::Artist class.

package Music::Artist;

use strict;
use base 'Music::MusicDB';

Music::Artist->table('artist');
Music::Artist->columns(All => qw/artistid name popularity/);
Music::Artist->has_many(cds => 'Music::CD');
Music::Artist->might_have(
        person => 'Music::Person' => qw/gender haircolor birthdate/);
Music::Artist->might_have(band=> 'Music::Band' => 
        qw/creationdate breakupdate/);

__PACKAGE__->add_constructor(long_names => qq{ length(name) > 15 });
__PACKAGE__->add_constructor(dynamic_long_names => qq{length(name)> ?});

#
# Here is the new code
#	
__PACKAGE__->add_constructor(prolific => qq{
        artistid in (select artistid
                       from cd
                      having count(*) > 1
                      group by artistid)});
1;

Then I use it in my new code (just like you'd expect):

#!/usr/bin/perl

use strict;
use Music::Artist;

my @artist = Music::Artist->prolific;

foreach (@artist) {
        print $_->name,"\n";
}

You can even do correlated subqueries, I just can't think of an example :)

retrieve_from_sql

This method provides the same functionality as the add_constructor method, except it is setup and performed in your executable as opposed to your class. This can be particularly handy when you don't have permission to alter the classes, or you don't want to build an add_constructor method for ever wacky SQL you might need during your execution.

So, lets do the same example above (in the add_constructor) here with retrieve_from_sql:

#!/usr/bin/perl

use strict;
use Music::Artist;

# retrieve_from_sql
my @artist = Music::Artist->retrieve_from_sql(qq{length(name) > 15});

foreach (@artist) {
        print $_->name,"\n";
}

set_sql

 __PACKAGE__->set_sql("method_name","sql");

Using the inherited functionality of Ima::DBI you can create CDBI objects out of complicated SQL.

For example, let's say you want a method that returns only the Artists that are also Persons. One way to do this is by joining the ARTIST table with the PERSON table. So lets do it.

First you'd add the new functionality to your Artist class:

package Music::Artist;

use strict;
use base 'Music::MusicDB';

Music::Artist->table('artist');
Music::Artist->columns(All => qw/artistid name popularity/);
Music::Artist->has_many(cds => 'Music::CD');
Music::Artist->might_have(
        person => 'Music::Person' => qw/gender haircolor birthdate/);
Music::Artist->might_have(band=> 'Music::Band' => 
	qw/creationdate breakupdate/);

__PACKAGE__->add_constructor(long_names => qq{ length(name) > 15 });
__PACKAGE__->add_constructor(dynamic_long_names => qq{length(name)> ?});
__PACKAGE__->add_constructor(prolific => qq{
        artistid in (select artistid
                       from cd
                      having count(*) > 1
                      group by artistid)});

# 
# Heres the new code
#
__PACKAGE__->set_sql(persons => qq {
       	select artist.id
	from artist, person
	where artist.artistid = person.artistid});
1;

When you do this CDBI will setup a method by the name of "search_persons". CDBI prepends the "search_" to the method name you supply.

Then you'd call it from your code like:

#!/usr/bin/perl

use strict;
use Music::Artist;

my @persons = Music::Artist->search_persons();

foreach (@persons) {
        print $_->name,"\n";
}

Like in most of CDBI you can also use placeholders or parameters in your "set_sql" commands to accect runtime arguments. Like:

__PACKAGE__->set_sql(persons => qq {
	select artist.artistid
	from artist, person
	where artist.artistid = person.artistid
	and person.gender = ?});

Then in your code you'd call it like:

my @persons = Music::Artist->search_persons("F");

NOTE: When using iterators with set_sql command, you must include the tables primary key in the result set. If the primary key is missing the iterators next() method will always return false.

Common Errors

Can't locate object method "xxxxx" via package "Class::DBI::Iterator" (perhaps you forgot to load "Class::DBI::Iterator"?)

This means you retrieved an array of object, but you assigned it to a scalar and then tried to call methods on the scalar. For example:

my $cd = Music::CD->search_like(title => 'The Bea%');

instead of:

my ($cd) = Music::CD->search_like(title => 'The Bea%');

Turning on tracing to see your SQL:

my $dbh = Music::Artist->db_Main; $dbh->trace(2);


APPENDIX

The following DDL is for Oracle, I'm sure you can convert it to your RDBMS of choice fairly simply.

Personal tools