File Coverage

blib/lib/DBIx/MySQLSequence.pm
Criterion Covered Total %
statement 31 145 21.3
branch 0 108 0.0
condition 0 17 0.0
subroutine 11 36 30.5
pod 11 11 100.0
total 53 317 16.7


line stmt bran cond sub pod time code
1             package DBIx::MySQLSequence;
2              
3             =pod
4              
5             =head1 NAME
6              
7             DBIx::MySQLSequence - Proper and correct (emulated) sequence support for MySQL
8              
9             =head1 SYNOPSIS
10            
11             # Get a handle to a new or existing sequence
12             $dbh = DBI->connect( 'dbi:mysql:db:host', 'user', 'pass' );
13             $sequence = DBIx::MySQLSequence->new( $dbh, 'sequence_name' );
14            
15             # Does the sequence already exist?
16             if ( $sequence->exists ) {
17             die "Sequence already exists";
18             }
19            
20             # Create the sequence
21             unless ( $sequence->create ) {
22             die "Failed to create sequence";
23             }
24            
25             # Get the next value off the sequence
26             $id = $sequence->nextval;
27            
28             # Drop the sequence
29             unless ( $sequence->drop ) {
30             die "Failed to drop sequence";
31             }
32            
33             # Remove sequence emulation support entirely
34             DBIx::MySQLSequence->remove_sequence_support( $dbh );
35              
36             =head1 STATUS
37              
38             C is complete and has been used to real application, but
39             does not have paranoidly thorough unit testing (yet).
40              
41             Please report any issues you encounter.
42              
43             =head1 DESCRIPTION
44              
45             The C package implements an emulation layer that
46             provides "real" sequences on MySQL. The module works by creating a
47             "sequence table", a single table where each record represents a single
48             sequence, and performing some "magic" MySQL specific SQL to ensure the
49             sequences will work correctly.
50              
51             =head2 What is a sequence?
52              
53             A sequence is a source of guarenteed unique numbers within a particular
54             context. These may or may not be in order, and in fact in typical database
55             systems they are rarely perfectly incremental. It is much more preferrable
56             that they are strictly unique than that they are perfectly in order. In any
57             case, DBIx::MySQLSequence does actually return sequence values in order,
58             but this will probably change once caching is implemented.
59              
60             In short, this is AUTO_INCREMENT done right. Oracle, PostgreSQL and
61             practically all other major database support sequences. MySQL does not.
62              
63             =head2 Why do I need sequences? Isn't AUTO_INCREMENT enough?
64              
65             MySQL provides its own AUTO_INCREMENT extention to SQL92 to
66             implement incrementing values for primary keys.
67              
68             However, this is not a very nice way to do them. I won't get into
69             the reasoning in depth here, but primarily there are huge advantages
70             to be had by knowing the value you are going to use BEFORE you
71             insert the record into the database. Additionally, if records with
72             the highest value for the AUTO_INCREMENT are deleted, their values
73             will (in some versions of MySQL) be re-used for the next record.
74             This is B.
75              
76             =head2 DBIx::MySQLSequence Feature Summary
77              
78             - Sequence names are case insensitive.
79             - Sequence names can be any string 1 to 32 chars in length.
80             - Sequence names can include spaces and other control characters.
81             - Sequence values use BIGINT fields, so the start, increment
82             and current values can be any integer between
83             -9223372036854775808 and 9223372036854775807.
84             - The module is safe for multiple database users or connections.
85             - The module is not transaction friendly. ( See below )
86             - The module is probably NOT thread safe.
87              
88             =head2 Transaction Safety
89              
90             Because the sequences are emulated through tables, they will have
91             problems with transactions, if used inside the same database connection
92             as your normal code. This is not normally a problem, since MySQL
93             databases are not historically used for transaction based database
94             work.
95              
96             If you are using transactions in MySQL, you can and should ensure
97             have a seperate connection open to do additional statements outside
98             the scope of the task the transaction is being used for.
99              
100             You should use that connection to get the sequence values.
101              
102             Any C methods called on a handle that isn't
103             in an autocommit state will cause a fatal error.
104              
105             It is highly recommended that if you need to do transactions, you
106             should consider looking at something ore robust that supports suequences
107             properly. Most people running up against the limits and idiosyncracies
108             of MySQL tend to be much more relaxed once they discover PostgreSQL.
109              
110             =head2 MySQL Permissions
111              
112             At the time the first sequence is created, you will need C
113             permissions in the database. After this, you will need C,
114             C and C on the sequence table. Should you want to remove
115             sequence support completely, the C permission will also be needed.
116              
117             The default name for the sequence table is contained in the variable
118             C<$DBIx::MySQLSequence::MYSQL_SEQUENCE_TABLE>.
119              
120             =head1 INTERFACE
121              
122             The interface for C is very flexible, and largely
123             inspired by the interface to C. It is somewhat
124             simpler though, as we don't need or aren't capable of everything Oracle
125             does.
126              
127             To quickly summarise the main methods.
128              
129             exists - Does a sequence exist
130             create - Create a sequence
131             drop - Drop a sequence
132             reset - Resets the current value to the start value
133             currval - Get the current value
134             nextval - Get the next value
135             errstr - Retrieve an error message should one occur
136             remove_sequence_support - Removes the sequence table completely
137              
138             =head2 Hybrid Interface
139              
140             Most of the methods in C will act in a hybrid manner,
141             allowing you to interact with an object or directly with the class
142             (statically).
143              
144             For example, the following two code fragments are equivalent.
145              
146             # Instantiation and Object Method
147             $sequence = DBIx::MySQLSequence->new( $dbh, 'sequence_name' );
148             $sequence->create( $start_value );
149            
150             # Static Method
151             DBIx::MySQLSequence->create( $dbh, 'sequence_name', $start_value );
152              
153             As demonstated here, when calling a method statically, you should prepend
154             a L database handle and sequence name to the method's arguments.
155              
156             Note: C can ONLY be called as a static method.
157              
158             =head1 METHODS
159              
160             =cut
161              
162 1     1   1422 use 5.005;
  1         4  
  1         40  
163 1     1   5 use strict;
  1         2  
  1         40  
164 1     1   1106 use Params::Util '_ARRAY0', '_INSTANCE';
  1         3486  
  1         82  
165 1     1   2434 use DBI ();
  1         21824  
  1         55  
166              
167 1     1   13 use vars qw{$VERSION $errstr $MYSQL_SEQUENCE_TABLE};
  1         2  
  1         101  
168             BEGIN {
169 1     1   3 $VERSION = '1.04';
170              
171             # Class-level error string
172 1         2 $errstr = '';
173              
174 1         1225 $MYSQL_SEQUENCE_TABLE = "_sequences";
175             }
176              
177              
178              
179              
180              
181             #####################################################################
182             # Constructor and Accessors
183              
184             =pod
185              
186             =head2 new $dbh, $name
187              
188             The C constructor creates a handle to a new or existing sequence. It is
189             passed as arguments a valid autocommit state MySQL DBI handle, and the name
190             of the sequence. Returns a new DBIx::MySQLSequence object, or undef on error.
191              
192             =cut
193              
194             sub new {
195 0     0 1   my $class = shift;
196 0 0         my $dbh = shift or return $class->_error( "Missing database handle argument" );
197 0 0         my $name = shift or return $class->_error( "Missing sequence name argument" );
198              
199             # Check that it is a mysql database handle
200 0 0         unless ( _INSTANCE($dbh, 'DBI::db') ) {
201 0           return $class->_error( "The database handle argument is not a DBI database handle" );
202             }
203 0 0         unless ( $dbh->{Driver}->{Name} eq 'mysql' ) {
204 0           return $class->_error( "The database handle argument is not a MySQL database" );
205             }
206              
207             # Create the object
208 0           my $self = bless {
209             dbh => $dbh,
210             name => $name,
211             }, $class;
212              
213             # Check that the database is in auto-commit mode.
214             # See notes in POD below on why this module doesn't work with transactions.
215 0 0         $class->_autocommit_error unless $self->dbh->{AutoCommit};
216              
217 0           $self;
218             }
219              
220             =pod
221              
222             =head2 dbh
223              
224             The C object method returns the L handle of the database the object
225             is using.
226              
227             =cut
228              
229 0     0 1   sub dbh { $_[0]->{dbh} }
230              
231             =pod
232              
233             =head2 name
234              
235             The C object method returns the sequence name for the handle
236              
237             =cut
238              
239 0     0 1   sub name { $_[0]->{name} }
240              
241              
242              
243              
244              
245             #####################################################################
246             # DBIx::MySQLSequence Methods
247              
248             =pod
249              
250             =head2 exists
251              
252             Static Syntax: Cexists( $dbh, $name );>
253              
254             Examines the database to determine if a sequence exists in the database.
255             Returns true if the sequence exists. Returns false if the sequence does
256             not exists, or sequence support has not been created in the database.
257              
258             =cut
259              
260             sub exists {
261 0 0   0 1   my $self = ref $_[0] ? shift : $_[0]->new( @_ ) or return undef;
    0          
262              
263             # Does the sequence table exist?
264 0           my $rv = $self->_sequence_table_exists;
265 0 0         return $rv unless $rv;
266              
267             # Is the sequence entry in the table
268 0           $self->_sequence_exists;
269             }
270              
271             =pod
272              
273             =head2 create [ $start ][, $increment ]
274              
275             Static Syntax: Ccreate( $dbh, $name [, $start ][, $increment ] );>
276              
277             Creates a sequence in the database. The create method takes optional arguments of the
278             value you want to sequence to start at, and the amount you want the value to increment
279             ( or decrement ) by.
280              
281             For example
282              
283             C<$sequence->create( 10, 5 )>
284              
285             The above would create a new sequence whose value starts at 10, and increments by 5 each
286             time a value is returned. If not passed, the default is a starting value of 1, and an
287             increment of 1. These are the defaults typically used by databases internally.
288              
289             If called as an object method, returns a true if the sequence is created, or undef if an
290             error occurs, or the sequence already exists.
291              
292             If called as a static method, it will return a new handle to the created sequence, or undef
293             if an error occurs, or the sequence already exists. You can use this as a sort of alternate
294             constructor.
295              
296             Ccreate( $dbh, $name, 5 );>
297              
298             DBIx::MySQLSequence will work quite happily without the sequence table existing. It will be
299             automatically created for you the first time that you create a sequence. Please note that
300             this will mean that you need CREATE and INSERT permissions when you create the first sequence.
301              
302             Once the first sequence is created, you will only need INSERT permissions.
303              
304             DBIx::MySQLSequence will not check for permissions for you, as the MySQL process for checking
305             permissions is a bit too involved, so you will most likely only find out about this when
306             the SQL statement fails. You should check that you have CREATE permissions before you start
307             using the database.
308              
309             =cut
310            
311             sub create {
312 0 0   0 1   my $self = ref $_[0] ? shift : $_[0]->new( @_ ) or return undef;
    0          
313              
314             # Does the sequence table exist?
315 0           my $rv = $self->_sequence_table_exists;
316 0 0         return undef unless defined $rv;
317 0 0         unless ( $rv ) {
318             # Create the sequence table
319 0 0         $rv = $self->_create_sequence_table or return undef;
320             }
321              
322             # Add the sequence to the table
323 0           $rv = $self->_create_sequence( $_[3], $_[4] );
324 0 0         $rv ? ref $self ? 1 : $self : undef;
    0          
325             }
326              
327             =pod
328              
329             =head2 drop
330              
331             Static Syntax: Cdrop( $dbh, $name );>
332              
333             The C method will drop a sequence from the database. It returns true on success, or undef
334             on error.
335              
336             Please note that when the last sequence is removed, the module will NOT remove the sequence
337             table. This is done in case you are operating on a database, and do not have CREATE permissions.
338             In this situation, the module would not be able to re-create the sequence table should it need to.
339              
340             To remove the sequence table completely, see the C method.
341              
342             =cut
343            
344             sub drop {
345 0 0   0 1   my $self = ref $_[0] ? shift : $_[0]->new( @_ ) or return undef;
    0          
346              
347             # Does the sequence table exist?
348 0 0         my $rv = $self->_sequence_table_exists or return undef;
349              
350             # Remove the sequence from the table
351 0           $self->_drop_sequence;
352             }
353              
354             =pod
355              
356             =head2 reset
357              
358             Static Syntax: Creset( $dbh, $name );>
359              
360             The C method will return the sequence to the state it was in when it was originally created.
361             Unlike Oracle, we do not need to drop and re-create the sequence in order to do this. Returns true
362             on success, or undef on error.
363              
364             =cut
365              
366             sub reset {
367 0 0   0 1   my $self = ref $_[0] ? shift : $_[0]->new( @_ ) or return undef;
    0          
368              
369             # Does the sequence exist?
370 0           my $rv = $self->_sequence_exists;
371 0 0         return undef unless defined $rv;
372 0 0         return $self->_error( "Sequence '$self->{self}' does not exist" ) unless $rv;
373              
374             # Set its value to the start value
375 0           $self->_db_void( qq{update $MYSQL_SEQUENCE_TABLE
376             set sequence_value = sequence_start - sequence_increment
377             where sequence_name = ?}, [ $self->{name} ] );
378             }
379              
380             =pod
381              
382             =head2 currval
383              
384             Static Syntax: Ccurrval( $dbh, $name );>
385              
386             The C method retrieves the current value of a sequence from the database.
387             The value that this returns is currently unreliable, but SHOULD match the last
388             value returned from the sequence. Returns the sequence value, or undef on error.
389              
390             =cut
391              
392             sub currval {
393 0 0   0 1   my $self = ref $_[0] ? shift : $_[0]->new( @_ ) or return undef;
    0          
394              
395             # Assume the sequence table exists, as we will return an error
396             # if the table doesn't exist OR if the record does not exist.
397 0           my $rv = $self->_db_value( qq{select sequence_value
398             from $MYSQL_SEQUENCE_TABLE
399             where sequence_name = ?}, [ lc $self->{name} ] );
400 0 0         $rv ? $$rv : undef;
401             }
402              
403             =pod
404              
405             =head2 nextval
406              
407             Static Syntax: Cnextval( $dbh, $name );>
408              
409             The C method retrieves the next value of a sequence from the database.
410             Returns the next value, or undef on error.
411              
412             =cut
413              
414             sub nextval {
415 0 0   0 1   my $self = ref $_[0] ? shift : $_[0]->new( @_ ) or return undef;
    0          
416              
417             # Assume the sequence table exists, as we will return an error
418             # if the table doesn't exist OR if the record does not exist.
419              
420             # Increment the sequence
421 0 0         my $rv = $self->_db_void( qq{update $MYSQL_SEQUENCE_TABLE
422             set sequence_value = last_insert_id(sequence_value + sequence_increment)
423             where sequence_name = ?}, [ lc $self->{name} ] ) or return undef;
424              
425             # Get the next value
426 0           my $value = $self->_db_value( "select last_insert_id()" );
427 0 0         $value ? $$value : undef;
428             }
429              
430             =pod
431              
432             =head2 remove_sequence_support
433              
434             The C method is a static only method that is used to remove
435             sequence support completely from a database, should you no longer need it.
436             Effectively, this just deletes the sequence table. Once you have removed sequence
437             support, any existing sequence object will most likely throw errors should you
438             try to use them.
439              
440             =cut
441              
442             sub remove_sequence_support {
443 0     0 1   my $class = shift;
444              
445             # Make sure we are called as a static method
446 0 0         if ( ref $class ) {
447 0           return $class->_error( "remove_sequence_support cannot be called as an object method" );
448             }
449 0 0         my $dbh = shift or return $class->_error( "Missing database handle argument" );
450              
451             # Cheat a little to actually become an object, so the handle
452             # provisioning in _execute works
453 0           my $self = bless \{ dbh => $dbh, name => undef }, $class;
454 0           $self->_drop_sequence_table;
455             }
456              
457             BEGIN {
458 1     1   616 *removeSequenceSupport = *remove_sequence_support;
459             }
460              
461              
462              
463              
464              
465             #####################################################################
466             # Support Methods
467              
468             # Does the sequence table exist
469             sub _sequence_table_exists {
470 0     0     my $self = shift;
471              
472             # Get the list of tables
473 0           my $tables = $self->_db_list( 'show tables' );
474 0 0         return undef unless defined $tables;
475 0 0         return 0 unless $tables;
476 0           foreach ( @$tables ) {
477             # Found the table
478 0 0         return 1 if $_ eq $MYSQL_SEQUENCE_TABLE;
479             }
480              
481 0           0;
482             }
483              
484             # Does a single sequence exist within the sequence table
485             sub _sequence_exists {
486 0     0     my $self = shift;
487              
488             # Try to find the record
489 0           my $rv = $self->_db_value( qq{select count(*) from $MYSQL_SEQUENCE_TABLE
490             where sequence_name = ?}, [ lc $self->{name} ] );
491 0 0         return undef unless defined $rv;
492 0 0 0       (ref $rv && $$rv) ? 1 : 0;
493             }
494            
495             # Create the sequence table
496             sub _create_sequence_table {
497 0     0     my $self = shift;
498 0           $self->_db_void( qq{create table $MYSQL_SEQUENCE_TABLE (
499             sequence_name char(32) not null primary key,
500             sequence_start bigint not null default 1,
501             sequence_increment bigint not null default 1,
502             sequence_value bigint not null default 1
503             )} );
504             }
505              
506             # Drop the sequence table
507             sub _drop_sequence_table {
508 0     0     my $self = shift;
509 0           $self->_db_void( qq{drop table $MYSQL_SEQUENCE_TABLE} );
510             }
511              
512             # Add a single sequence to the table
513             sub _create_sequence {
514 0     0     my $self = shift;
515 0 0 0       my $start = defined $_[0] && $_[0] =~ /^-?\d+$/ ? shift : 1;
516 0 0 0       my $increment = defined $_[0] && $_[0] =~ /^-?\d+$/ ? shift : 1;
517            
518             # Assume the sequence table exists
519 0           $self->_db_void( qq{insert into $MYSQL_SEQUENCE_TABLE
520             ( sequence_name, sequence_start, sequence_increment, sequence_value )
521             values ( ?, $start, $increment, $start - $increment )}, [ lc $self->{name} ] );
522             }
523              
524             # Remove a single sequence from the table
525             sub _drop_sequence {
526 0     0     my $self = shift;
527              
528             # Assume the sequence table exists
529 0           $self->_db_void( qq{delete from $MYSQL_SEQUENCE_TABLE
530             where sequence_name = ?}, [ lc $self->{name} ] );
531             }
532              
533             # Get the entire record hash for a sequence
534             sub _get_sequence_details {
535 0     0     my $self = shift;
536              
537             # Pull the entire record
538 0           my $record = $self->_db_record( qq{select * FROM $MYSQL_SEQUENCE_TABLE
539             where sequence_name = ?}, [ lc $self->{name} ] );
540 0 0         return undef unless defined $record;
541 0 0         $record or $self->_error( "Sequence '$self->{name}' does not exist" );
542             }
543              
544              
545              
546              
547              
548             #####################################################################
549             # Database Methods
550              
551 1     1   7 use constant FORMAT_VOID => 0;
  1         2  
  1         72  
552 1     1   5 use constant FORMAT_VALUE => 1;
  1         2  
  1         42  
553 1     1   4 use constant FORMAT_LIST => 2;
  1         2  
  1         72  
554 1     1   6 use constant FORMAT_RECORD => 3;
  1         2  
  1         703  
555              
556             sub _db_void {
557 0     0     my ($self, $sql, $arguments) = @_;
558 0   0       $self->_execute( $sql, $arguments || [], FORMAT_VOID );
559             }
560              
561             sub _db_value {
562 0     0     my ($self, $sql, $arguments) = @_;
563 0   0       $self->_execute( $sql, $arguments || [], FORMAT_VALUE );
564             }
565              
566             sub _db_list {
567 0     0     my ($self, $sql, $arguments) = @_;
568 0   0       $self->_execute( $sql, $arguments || [], FORMAT_LIST );
569             }
570              
571             sub _db_record {
572 0     0     my ($self, $sql, $arguments) = @_;
573 0   0       $self->_execute( $sql, $arguments || [], FORMAT_RECORD );
574             }
575              
576             sub _execute {
577 0     0     my $self = shift;
578 0           my $sql = shift;
579 0           my $arguments = shift;
580 0           my $rformat = shift;
581 0 0         unless ( _ARRAY0($arguments) ) {
582 0           return $self->_error( "Arguments list is not an array reference" );
583             }
584            
585             # Make sure we have a connection,
586             # and arn't in a transaction.
587 0 0         return $self->_error( "Database connection missing" ) unless $self->{dbh};
588 0 0         $self->_autocommit_error unless $self->{dbh}->{AutoCommit};
589              
590             # Create the statement handle using the sql
591 0           my $sth = $self->{dbh}->prepare( $sql );
592 0 0         return $self->_error( "SQL error during prepare: " . $self->{dbh}->errstr ) unless $sth;
593            
594             # Looks good. Execute the statement
595 0           my $result = $sth->execute( @$arguments);
596 0 0         unless ( $result ) {
597 0           $self->_error( "SQL error during execute: " . $sth->errstr );
598 0           $sth->finish;
599 0           return undef;
600             }
601            
602             # Format the response data
603 0           my $data;
604 0 0         if ( $rformat == FORMAT_VOID ) {
    0          
    0          
    0          
605             # It worked, return true
606 0           $data = 1;
607              
608             } elsif ( $rformat == FORMAT_VALUE ) {
609             # Get a single value
610 0           my $rv = $sth->fetch;
611 0 0         $data = $rv ? \$rv->[ 0 ] : 0;
612            
613             } elsif ( $rformat == FORMAT_LIST ) {
614             # Get a list
615 0           my ($rv, @list) = ();
616 0           push @list, $rv->[ 0 ] while $rv = $sth->fetch;
617 0 0         $data = scalar @list ? \@list : 0;
618            
619             } elsif ( $rformat == FORMAT_RECORD ) {
620             # Get a single hash reference
621 0           my $rv = $sth->fetchrow_hashref( 'NAME_lc' );
622 0 0         $data = $rv ? $rv : 0;
623            
624             } else {
625 0           $sth->finish;
626 0           $self->_error( "Statement executed successfully, but return format is invalid" );
627             }
628              
629             # Finish and return
630 0           $sth->finish;
631 0           $data;
632             }
633              
634              
635              
636              
637              
638             #####################################################################
639             # Error handling
640              
641             # Set an error string and return
642             sub _error {
643 0     0     my $either = shift;
644 0 0         if ( ref $either ) {
645 0           $either->{_errstr} = shift;
646             } else {
647 0           $errstr = shift;
648             }
649 0           undef;
650             }
651              
652             # This module will not work inside a transaction.
653             # This is a fatal error.
654             sub _autocommit_error {
655 0     0     die "You cannot use DBIx::MySQLSequence inside a transaction. See the documentation for details.";
656             }
657              
658             =pod
659              
660             =head2 errstr
661              
662             Static Syntax: Cerrstr;>
663              
664             When an error occurs ( usually indicated by a method return value of C ),
665             the C method is used to retrieve any error message that may be available.
666             Any error message specific to a object method will be available from that object
667             using.
668              
669             C<$sequence->errstr;>
670              
671             If you use a static method, or one of the above object method in its static form,
672             you should retrieve the error message from the class statically, using
673              
674             Cerrstr;>
675              
676             =cut
677              
678             sub errstr {
679 0     0 1   my $either = shift;
680 0 0         ref $either ? $either->{_errstr} : $errstr;
681             }
682              
683             1;
684              
685             =pod
686              
687             =head1 TO DO
688              
689             - More testing, but then there's ALWAYS more testing to do
690              
691             In Oracle, sequence values are cached server side. We can emulate this by
692             creating a DBIx::MySQLSequence::Cache object to do caching client side, for
693             when people want to get a lot of sequence values without having to go back
694             to the server all the time.
695              
696             This would be a good thing. It would make things MUCH faster.
697              
698             =head1 AUTHORS
699              
700             Adam Kennedy Eadamk@cpan.orgE
701              
702             Patches are welcome
703              
704             =head1 SEE ALSO
705              
706             DBIx::OracleSequence
707              
708             =head1 COPYRIGHT
709              
710             Copyright 2002, 2007 Adam Kennedy.
711              
712             This program is free software; you can redistribute
713             it and/or modify it under the same terms as Perl itself.
714              
715             The full text of the license can be found in the
716             LICENSE file included with this module.
717              
718             =cut