DBIx::Table - Class used to represent DBI database tables. |
DBIx::Table - Class used to represent DBI database tables.
package SUBCLASS; @ISA = qw(DBIx::Table); sub describe { my($self) = shift; $self->{'table'} = $table_name; $self->{'unique_keys'} = [ [ $column, ... ], ... ]; $self->{'columns'} = { $col_name => { %col_options }, [ ... ] }; [ $self->{'related'} = { $class_name => { %relationship }, [ ... ] }; ] }
$table = load SUBCLASS( db => $dbi_object, [ where => { $column => $value, ... }, ] [ columns => [ $column1, $column2 ], ] [ index => $index, ] [ count => $count, ] [ groupby => $groupby, ] [ orderby => ['+'|'-'] . $column ]); $table = create SUBCLASS( db => $dbi_object);
$new_table = $table->load_related( type => $classname, row => $row, [ %where_arguments ] ); $num_rows = $table->num_rows(); $query_rows = $table->query_rows(); $columns = $table->columns(); $db = $table->db(); $level = $table->debug_level( [ level => $level ] ); $value = $table->get( column => $column, [ row => $row ] ); $retval = $table->set( change => { $column => $value, [ ..., ] }, [ row => $row ] ); $retval = $table->refresh( columns = [ $column1, $column2 ], [ row => $row ] ); $retval = $table->commit( [ row => $row ] ); $retval = $table->remove( [ row => $row ] ); $count = $table->count( [ where => { $column => $value, ... } ]
DBIx::Table is a class designed to act as an abstraction layer around a fairly large subset of SQL queries. It is called 'Table' because it is primarily designed such that a single subclass provides an object-oriented interface to a single database table. The module is flexible enough, though, that it can be used to abstract most any schema in a way that is comfortable to the perl coder who is not familiar with the underlying schema, or even SQL.
As the synopsis above points out, this class is not useful when simply used by itself. Instead, it should be subclassed. The subclass follows a particular syntax for describing the structure of the table, which the Table module uses internally to control its behavior.
The subclass can then be used to access the underlying data, with the Table module taking care of writing the actual SQL. The current version can write SELECT, UPDATE, INSERT and DELETE statements. Depending on the complexity of the definition, it can also do joins across multiple tables and intelligently load related table objects.
The rest of the documentation is split: first, how to create a useful subclass. Second, constructors and access methods on the subclass. Third, some examples. Without further ado...
See the perltoot(1)
and perlobj(1)
manuals if you don't know how to create
a class, subclass, or if you don't understand inheritance or overriding
inherited functions.
Every subclass of DBIx::Table is required to provide a method called ``describe'', which, at a minimum, needs to provide some clues as to the form of the underlying data. This is done by modifying a few key parts of the data stored in the object itself:
commit()
or remove()
methods, you'll need to provide
at least one unique key combination. This parameter takes a reference to an
array of references to arrays of strings. The listed strings in the second
level array are the names of columns which, taken in conjunction, are guaranteed
to be unique in the database. These are tried in order, so put them in order
of preference.
commit()
call will fail if a value for this column is not set()
first, or
no default is supplied. This only applies to local columns.
set()
to this column will be quoted using $self->{'db'}->quote(),
the quote method on the DBI object passed into the constructor.
set()
a value to this column will cause set()
to fail. Immutability
is, for now at least, implicit on all ``foreign'' and ``special'' columns - i.e.
you can't update foreign data!
commit()
to UPDATE or INSERT
data on a column without the null attribute. It will be quoted if the quoted
attribute is set.
All of the public methods use hash-style arguments. I've tried to be consistent and obvious in the naming of arguments.
The only class methods are the constructors, all other methods are strictly object methods (i.e. you can't call SUBCLASS::get(...), you have to call $subclass_object->get(...)).
All methods return undef if they fail.
General issues aside, here are the descriptions of the specific methods:
There are two constructors:
load()
call. The special string '*' is expanded
to all columns which are not 'foreign' or 'special'. If this argument is left
out, a literal '*' will go in the SQL generated, indicating that all local
columns in this table should be loaded.
count()
method. It takes
only one argument, 'db', which is identical to the 'db' argument to load(),
described above.
Two methods for using the data in the object:
get()
above.
Three methods can make additional SQL queries based on the current data:
get()
above.
get()
above.
Please be careful with this method, as it has only been tested for fairly simple cases.
get()
above.
Please be careful with this method, as it has only been tested for fairly simple cases.
Several methods can be used to get meta-data about the object, and the data retrieved, and configure behavior of the object:
num_rows()
if 'index' or 'count' parameters to load()
were used).
And a couple of ``miscellaneous'' utility functions:
count(*)
style SELECT statements. It does
not store the return from the query; instead, it returns it to the caller. It
takes an optional 'where' argument, identical to the one described under load().
All other arguments will be passed on to the load()
constructor for the class
passed in in 'type'. It is not necessary to provide a 'db' argument; it will
simply pass on the one it already has. And finally, the real nicety provided
by load_related is that it will check the 'where' argument (if any) and will
use the information in $self->{'related'} to substitute values. So you can
say where => { 'that_column' => 'this_column' }, and load_related will convert
the literal 'this_column' into the current value of this_column.
These examples are simple but are designed to show you how this module can be used. They progress from table descriptions to complete subclasses to usage, including showing the SQL that is output.
Beginning with two tables (as you would create them with MySQL). First, a simple users table, very straightforward:
CREATE TABLE users ( uid INT UNSIGNED NOT NULL AUTO_INCREMENT, email VARCHAR(70) NOT NULL, password VARCHAR(10) NOT NULL, PRIMARY KEY(uid), UNIQUE (uid), );
Second, a feedback table. This table is complexly related to the users table; each user can both send and receive multiple feedback, so both the to_uid and from_uid columns point back to the users table.
CREATE TABLE feedback ( fid INT UNSIGNED NOT NULL AUTO_INCREMENT, to_uid INT UNSIGNED NOT NULL, from_uid INT UNSIGNED NOT NULL, time DATETIME NOT NULL, type ENUM('good', 'bad', 'neutral') NOT NULL, text VARCHAR(100) NOT NULL, PRIMARY KEY (fid), INDEX (to_uid), INDEX (from_uid), UNIQUE uid_combo (to_uid, from_uid) );
Okay, now we need to create objects to represent them. For users, I want to be able to fetch the counts of the feedback recieved by the user in question for each of the three feedback types, which requires 'special' columns:
package User; use strict; use DBIx::Table; @User::ISA = qw(DBIx::Table);
sub describe { my($self) = shift || return(undef);
$self->{'table'} = 'users'; $self->{'unique_keys'} = [ ['uid'] ]; $self->{'columns'} = { 'uid' => { 'immutable' => 1, 'autoincrement' => 1, 'default' => 'NULL' }, 'email' => { 'quoted' => 1 }, 'password' => { 'quoted' => 1 }, 'good_fb' => { 'special' => { 'select' => 'count(fb_g.type) AS good_fb', 'join' => 'LEFT JOIN feedback AS fb_g ON (fb_g.type = ' . '\'good\' AND fb_g.to_uid = users.uid)', 'groupby' => 'uid' } }, 'bad_fb' => { 'special' => { 'select' => 'count(fb_b.type) AS bad_fb', 'join' => 'LEFT JOIN feedback AS fb_b ON (fb_b.type = ' . '\'bad\' AND fb_b.to_uid = users.uid)', 'groupby' => 'uid' } }, 'neutral_fb' => { 'special' => { 'select' => 'count(fb_n.type) AS neutral_fb', 'join' => 'LEFT JOIN feedback AS fb_n ON (fb_n.type = ' . '\'neutral\' AND fb_n.to_uid = users.uid)', 'groupby' => 'uid' } } }; $self->{'related'} = { 'Feedback' => { 'from_uid' => 'uid', 'to_uid' => 'uid' } }; } 1;
Phew. Now how about the feedback table. In this case, we'd like to be able to fetch the email addresses of both the sender and recipient users in the same query. This can be done with 'foreign' columns. Here's the class:
package Feedback; use strict; use DBIx::Table; @Feedback::ISA = qw(DBIx::Table);
sub describe { my($self) = shift || return(undef);
$self->{'table'} = 'feedback'; $self->{'unique_keys'} = [ ['fid'] ]; $self->{'columns'} = { 'fid' => { 'immutable' => 1, 'autoincrement' => 1, 'default' => 'NULL' }, 'to_uid' => { }, 'from_uid' => { }, 'time' => { 'default' => 'NOW()' }, 'type' => { 'quoted' => 1 }, 'text' => { 'quoted' => 1 }, 'to_email' => { 'foreign' => { 'table' => 'users_to', 'lkey' => 'to_uid', 'rkey' => 'uid', 'actual_table' => 'users', 'actual_column' => 'email' } }, 'from_email' => { 'foreign' => { 'table' => 'users_from', 'lkey' => 'from_uid', 'rkey' => 'uid', 'actual_table' => 'users', 'actual_column' => 'email' } } }; } 1;
Using these is simple enough. The simplest case would be:
$obj = load User( db => $db );
which generates the SQL:
SELECT * from users
Not very intersting. How about:
$obj = load User db => $db, where => { uid => 1 }, columns => [ '*', 'good_fb', 'bad_fb', 'neutral_fb' ];
Which generates the SQL (formatted for viewing ease):
SELECT count(fb_g.type) AS good_fb, count(fb_b.type) AS bad_fb, count(fb_n.type) AS neutral_fb, users.password, users.email, users.uid FROM users LEFT JOIN feedback AS fb_g ON (fb_g.type = 'good' AND fb_g.to_uid = users.uid) LEFT JOIN feedback AS fb_b ON (fb_b.type = 'bad' AND fb_b.to_uid = users.uid) LEFT JOIN feedback AS fb_n ON (fb_n.type = 'neutral' AND fb_n.to_uid = users.uid) WHERE users.uid = 1 GROUP BY uid
Let's load up all feedback with the associated e-mail addresses, arranged by descending time sent:
$obj = load Feedback db => $db, columns => [ '*', 'to_email', 'from_email' ], orderby => '-time' ;
Generates the SQL (again, formatted):
SELECT users_to.email AS to_email, users_from.email AS from_email, feedback.type, feedback.vs_fid, feedback.to_uid, feedback.text, feedback.from_uid, feedback.time FROM feedback JOIN users AS users_to JOIN users AS users_from WHERE feedback.to_uid = 2 AND users_to.uid = feedback.to_uid AND users_from.uid = feedback.from_uid ORDER BY feedback.time DESC
That seems like enough to get started.
J. David Lowe, dlowe@pootpoot.com
perl(1)
DBIx::Table - Class used to represent DBI database tables. |