NAME¶
DBIx::Sequence - A simple SQL92 ID generator
SYNOPSIS¶
use DBIx::Sequence;
my $sequence = new DBIx::Sequence({ dbh => $dbh });
my $next_id = $sequence->Next('dataset');
DESCRIPTION¶
This module is intended to give easier portability to Perl database application
by providing a database independant unique ID generator. This way, an
application developer is not bound to use his database's SEQUENCE or
auto_increment thus making his application portable on multiple database
environnements.
This module implements a simple Spin Locker mechanism and is garanteed to return
a unique value every time it is called, even with concurrent processes. It
uses your database for its state storage with ANSI SQL92 compliant SQL. All
SQL queries inside DBIx::Sequence are pre cached and very efficient especially
under mod_perl.
INSTALLATION¶
perl Makefile.PL
make
make test
make install
Note:
If you decide to run extended tests for the module, you will have to provide the
make test with a DSN (connect string) to your database
(dbi:Driver:db;host=hostname) and a valid username/password combination for a
privileged user.
DBIx::Sequence uses 2 tables for its operation, namely the dbix_sequence_state
and the dbix_sequence_release tables. Those tables will be created if you run
extended tests, if not you will need to create them yourself.
dbix_sequence_state:
| dataset | varchar(50) |
| state_id | int(11) |
dbix_sequence_release:
| dataset | varchar(50) |
| released_id | int(11) |
Those table names are overloadable at your convenience, see the OVERLOADING
section for details.
BASIC USAGE¶
The basic usage of this module is to generate a unique ID to replace the use of
your database's SEQUENCE of auto_increment field.
INIT¶
First, you need to create the sequence object:
use DBIx::Sequence;
my $sequence = new DBIx::Sequence({
db_user => 'scott',
db_pw => 'tiger',
db_dsn => 'dbi:mysql:scottdb',
allow_id_reuse => 1,
});
DBIx::Sequence can be used to manage multiple sets of ID's (perhaps you could
have one dataset per table, or one and only one dataset). This permits you to
handle multiple applications with the same sequence class. The dataset is
normally simply a token string that represents your ID set. If the dataset
does not exists, DBIx::Sequence will create automagically for you. No special
steps are involved in the creation of a dataset.
The arguments contains the database informations, db_user, db_pw and db_dsn and
are stored in a hash reference.
At this point, the object has pre cached all of the SQL that will be used to
generate the spin locker race. It is normally a good idea to have a shared
sequence object (especially) under mod_perl to save the prepare overhead. The
'allow_id_reuse' argument can be passed to the constructor to either allow the
use of the
Release() or deny it. (True value makes it allowed)
GETTING THE NEXT ID¶
To get the next id, you simpy have to use the
Next() method of your
sequence while specifying the dataset you are getting the next id for.
my $next_id = $sequence->Next($dataset);
RELEASING ID'S.¶
Generated ID's can be _explicitly_ released in your application. When an ID is
released, the sequence will be able to give this id back to you throught the
Next() method.
This is how it is done:
$sequence->Release($dataset, $id);
Note:
You must use release only when you are _CERTAIN_ that your ID is not used
anymore and that you want it to be recycled. The Spin Locking mechanism will
also take place on released id's to ensure that no two processes can get the
same ID.
PERMANENTLY REMOVING A DATASET¶
To make DBIx::Sequence forget about an existing dataset, you need to use the
Delete_Dataset() method.
$sequence->Delete_Dataset($dataset);
This will clear all state and existence for this dataset and will also clear
it's released id's. Note that if your application still uses this dataset, it
will be automatically recreated blank.
BOOTSTRAPPING A DATASET FROM EXISTING DATA¶
It is possible to sync the state of a DBIx::Sequence dataset by using the
Bootstrap() method.
$sequence->Bootstrap('my_dataset','my_bootstrap_table','my_primary_field');
Bootstrap() takes 3 arguments.
- •
- The dataset to bootstrap
- •
- The table from wich you will bootstrap
- •
- The field in the bootstrap table that will be used to bootstrap the
dataset.
Bootstrap will then sync up the DBIx::Sequence's state with the maximum id of
the 'my_primary_field' in 'my_bootstrap_table'. The bootstrap field must be a
numeric field as you can suspect. The SQL function
MAX() will be called
on it during the bootstrap process.
Note: The bootstrap method _can_ be used at runtime since it will initiate a
race for updating the value thus following the same algorithm. It is
recommended though that you use
Bootstrap() when no other concurrent
processes are requesting id's.
OVERLOADING¶
It is possible to create an overloaded class of DBIx::Sequence. This permits you
to create a DBIx::Sequence that has different properties than the orignal one.
The only thing you really have to overload to modify the behaviour of
DBIx::Sequence are some constants:
- •
- STATE_TABLE : Defines the table used by DBIx::Sequence to store dataset's
states.
- •
- RELEASE_TABLE : Defines the table used by DBIx::Sequence to store released
id's.
- •
- COLUMN_PREFIX : A string to be prepended to every column in the internal
SQL statements.
- •
- DEFAULT_INIT_VALUE : Value used to initialize a dataset when it is first
created.
- •
- DEFAULT_ALLOW_ID_REUSE : When set to true, will allow the use of
Release() if not specified in the constructor.
(allow_id_reuse)
- •
- DEBUG_LEVEL : When set to true, will enable debugging to STDERR.
So it is very easy to specify the behaviour of DBIx::Sequence that you wish to
use by creating an overloaded class.
Also, a very important method to overload is the
getDbh() method. This is
the function that returns the database handle to the DBIx::Sequence. Your
overloaded class should redefine the getDbh method.
Overloading getDbh will make your sequence class integrate more cleanly with
your application.
i.e.
package MySequence;
use DBI;
use DBIx::Sequence;
use vars qw(@ISA);
@ISA = qw(DBIx::Sequence);
use constant STATE_TABLE => 'my_state_table';
use constant RELEASE_TABLE => 'my_release_table';
use constant COLUMN_PREFIX => '';
use constant DEFAULT_INIT_VALUE => '100';
use constant DEFAULT_ALLOW_ID_REUSE => 1;
use constant DEBUG_LEVEL => 0;
sub getDbh
{
my $self = shift;
return MyApplication::MyDBModule::getDbh();
}
1;
Then, your code can use this class for its sequencing. Notice that since we
overloaded
getDbh(), we don't need to pass a second parameter to
new().
use MySequence;
my $sequence = new MySequence();
my $next_id = $sequence->Next($dataset);
SPECIAL NOTE ON DATABASE HANDLE OPTIONS¶
DBIx::Sequence requires that the dbh object you passe to it has the AutoCommit
flag set to 1. The main reason for this is that if AutoCommit is off,
DBIx::Sequence will have to do an implicit
commit() call, wich in most
cases is a bad idea, especially when the dbh passed to the sequence object
already has transactions prelogged in it.
CVS AND BLEEDING VERSIONS¶
For the latest development information, CVS access and Changelog, please visit:
http://labs.turbulent.ca
If you use this module in a project, please let me know!
Your comments and rants are more than welcomed!
Commercial support for this module is available, please contact me for info!
TODO¶
- •
- Implement multiple locking mechanism (semaphore, spin, db locker)
- •
- Implement pluggable locking module support
AUTHOR¶
Benoit Beausejour, <bbeausej@pobox.com>
NOTES¶
This code was made possible by the help of individuals:
Philippe "Gozer" M. Chiasson <gozer@cpan.org>
Thanks to Uri Guttman for documentation checks ;)
CONTRIBUTORS¶
Here are the people who submitted patches and changes to the module, they have
my thanks for their contributions:
Trevor Shellhorn <trevor.schellhorn-perl@marketingtips.com>
Dan Kubb <dkubb@cpan.org>
SEE ALSO¶
perl(1).
COPYRIGHT¶
Copyright (c) 2000 Benoit Beausejour <bbeausej@pobox.com> All rights
reserved. This program is free software, you can redistribute it and/or modify
it under the same terms as Perl itself.