166 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
			
		
		
	
	
			166 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
package App::Netdisco::DB::ExplicitLocking;
 | 
						|
 | 
						|
use strict;
 | 
						|
use warnings;
 | 
						|
 | 
						|
our %lock_modes;
 | 
						|
 | 
						|
BEGIN {
 | 
						|
  %lock_modes = (
 | 
						|
    ACCESS_SHARE => 'ACCESS SHARE',
 | 
						|
    ROW_SHARE => 'ROW SHARE',
 | 
						|
    ROW_EXCLUSIVE => 'ROW EXCLUSIVE',
 | 
						|
    SHARE_UPDATE_EXCLUSIVE => 'SHARE UPDATE EXCLUSIVE',
 | 
						|
    SHARE => 'SHARE',
 | 
						|
    SHARE_ROW_EXCLUSIVE => 'SHARE ROW EXCLUSIVE',
 | 
						|
    EXCLUSIVE => 'EXCLUSIVE',
 | 
						|
    ACCESS_EXCLUSIVE => 'ACCESS EXCLUSIVE',
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
use constant \%lock_modes;
 | 
						|
 | 
						|
use base 'Exporter';
 | 
						|
our @EXPORT = ();
 | 
						|
our @EXPORT_OK = (keys %lock_modes);
 | 
						|
our %EXPORT_TAGS = (modes => \@EXPORT_OK);
 | 
						|
 | 
						|
sub txn_do_locked {
 | 
						|
  my ($self, $table, $mode, $sub) = @_;
 | 
						|
  my $sql_fmt = q{LOCK TABLE %s IN %%s MODE};
 | 
						|
  my $schema = $self;
 | 
						|
 | 
						|
  if ($self->can('result_source')) {
 | 
						|
      # ResultSet component
 | 
						|
      $sub = $mode;
 | 
						|
      $mode = $table;
 | 
						|
      $table = $self->result_source->from;
 | 
						|
      $schema = $self->result_source->schema;
 | 
						|
  }
 | 
						|
 | 
						|
  $schema->throw_exception('missing Table name to txn_do_locked()')
 | 
						|
    unless $table;
 | 
						|
 | 
						|
  $table = [$table] if ref '' eq ref $table;
 | 
						|
  my $table_fmt = join ', ', ('%s' x scalar @$table);
 | 
						|
  my $sql = sprintf $sql_fmt, $table_fmt;
 | 
						|
 | 
						|
  if (ref '' eq ref $mode and $mode) {
 | 
						|
      scalar grep {$_ eq $mode} values %lock_modes
 | 
						|
        or $schema->throw_exception('bad LOCK_MODE to txn_do_locked()');
 | 
						|
  }
 | 
						|
  else {
 | 
						|
      $sub = $mode;
 | 
						|
      $mode = 'ACCESS EXCLUSIVE';
 | 
						|
  }
 | 
						|
 | 
						|
  $schema->txn_do(sub {
 | 
						|
      my @params = map {$schema->storage->dbh->quote_identifier($_)} @$table;
 | 
						|
      $schema->storage->dbh->do(sprintf $sql, @params, $mode);
 | 
						|
      $sub->();
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
=head1 NAME
 | 
						|
 | 
						|
App::Netdisco::DB::ExplicitLocking - Support for PostgreSQL Lock Modes
 | 
						|
 | 
						|
=head1 SYNOPSIS
 | 
						|
 | 
						|
In your L<DBIx::Class> schema:
 | 
						|
 | 
						|
 package My::Schema;
 | 
						|
 __PACKAGE__->load_components('+App::Netdisco::DB::ExplicitLocking');
 | 
						|
 | 
						|
Then, in your application code:
 | 
						|
 | 
						|
 use App::Netdisco::DB::ExplicitLocking ':modes';
 | 
						|
 $schema->txn_do_locked($table, MODE_NAME, sub { ... });
 | 
						|
 | 
						|
This also works for the ResultSet:
 | 
						|
 | 
						|
 package My::Schema::ResultSet::TableName;
 | 
						|
 __PACKAGE__->load_components('+App::Netdisco::DB::ExplicitLocking');
 | 
						|
 | 
						|
Then, in your application code:
 | 
						|
 | 
						|
 use App::Netdisco::DB::ExplicitLocking ':modes';
 | 
						|
 $schema->resultset('TableName')->txn_do_locked(MODE_NAME, sub { ... });
 | 
						|
 | 
						|
=head1 DESCRIPTION
 | 
						|
 | 
						|
This L<DBIx::Class> component provides an easy way to execute PostgreSQL table
 | 
						|
locks before a transaction block.
 | 
						|
 | 
						|
You can load the component in either the Schema class or ResultSet class (or
 | 
						|
both) and then use an interface very similar to C<DBIx::Class>'s C<txn_do()>.
 | 
						|
 | 
						|
The package also exports constants for each of the table lock modes supported
 | 
						|
by PostgreSQL, which must be used if specifying the mode (default mode is
 | 
						|
C<ACCESS EXCLUSIVE>).
 | 
						|
 | 
						|
=head1 EXPORTS
 | 
						|
 | 
						|
With the C<:modes> tag (as in SYNOPSIS above) the following constants are
 | 
						|
exported and must be used if specifying the lock mode:
 | 
						|
 | 
						|
=over 4
 | 
						|
 | 
						|
=item * C<ACCESS_SHARE>
 | 
						|
 | 
						|
=item * C<ROW_SHARE>
 | 
						|
 | 
						|
=item * C<ROW_EXCLUSIVE>
 | 
						|
 | 
						|
=item * C<SHARE_UPDATE_EXCLUSIVE>
 | 
						|
 | 
						|
=item * C<SHARE>
 | 
						|
 | 
						|
=item * C<SHARE_ROW_EXCLUSIVE>
 | 
						|
 | 
						|
=item * C<EXCLUSIVE>
 | 
						|
 | 
						|
=item * C<ACCESS_EXCLUSIVE>
 | 
						|
 | 
						|
=back
 | 
						|
 | 
						|
=head1 METHODS
 | 
						|
 | 
						|
=head2 C<< $schema->txn_do_locked($table|\@tables, MODE_NAME?, $subref) >>
 | 
						|
 | 
						|
This is the method signature used when the component is loaded into your
 | 
						|
Schema class. The reason you might want to use this over the ResultSet version
 | 
						|
(below) is to specify multiple tables to be locked before the transaction.
 | 
						|
 | 
						|
The first argument is one or more tables, and is required. Note that these are
 | 
						|
the real table names in PostgreSQL, and not C<DBIx::Class> ResultSet aliases
 | 
						|
or anything like that.
 | 
						|
 | 
						|
The mode name is optional, and defaults to C<ACCESS EXCLUSIVE>. You must use
 | 
						|
one of the exported constants in this parameter.
 | 
						|
 | 
						|
Finally pass a subroutine reference, just as you would to the normal
 | 
						|
C<DBIx::Class> C<txn_do()> method. Note that additional arguments are not
 | 
						|
supported.
 | 
						|
 | 
						|
=head2 C<< $resultset->txn_do_locked(MODE_NAME?, $subref) >>
 | 
						|
 | 
						|
This is the method signature used when the component is loaded into your
 | 
						|
ResultSet class. If you don't yet have a ResultSet class (which is the default
 | 
						|
- normally only Result classes are created) then you can create a stub which
 | 
						|
simply loads this component (and inherits from C<DBIx::Class::ResultSet>).
 | 
						|
 | 
						|
This is the simplest way to use this module if you only want to lock one table
 | 
						|
before your transaction block.
 | 
						|
 | 
						|
The first argument is the optional mode name, which defaults to C<ACCESS
 | 
						|
EXCLUSIVE>. You must use one of the exported constants in this parameter.
 | 
						|
 | 
						|
The second argument is a subroutine reference, just as you would pass to the
 | 
						|
normal C<DBIx::Class> C<txn_do()> method. Note that additional arguments are
 | 
						|
not supported.
 | 
						|
 | 
						|
=cut
 | 
						|
 | 
						|
1;
 |