1package App::Netdisco::DB::ExplicitLocking; 2 3use strict; 4use warnings; 5 6our %lock_modes; 7 8BEGIN { 9 %lock_modes = ( 10 ACCESS_SHARE => 'ACCESS SHARE', 11 ROW_SHARE => 'ROW SHARE', 12 ROW_EXCLUSIVE => 'ROW EXCLUSIVE', 13 SHARE_UPDATE_EXCLUSIVE => 'SHARE UPDATE EXCLUSIVE', 14 SHARE => 'SHARE', 15 SHARE_ROW_EXCLUSIVE => 'SHARE ROW EXCLUSIVE', 16 EXCLUSIVE => 'EXCLUSIVE', 17 ACCESS_EXCLUSIVE => 'ACCESS EXCLUSIVE', 18 ); 19} 20 21use constant \%lock_modes; 22 23use base 'Exporter'; 24our @EXPORT = (); 25our @EXPORT_OK = (keys %lock_modes); 26our %EXPORT_TAGS = (modes => \@EXPORT_OK); 27 28sub txn_do_locked { 29 my ($self, $table, $mode, $sub) = @_; 30 my $sql_fmt = q{LOCK TABLE %s IN %%s MODE}; 31 my $schema = $self; 32 33 if ($self->can('result_source')) { 34 # ResultSet component 35 $sub = $mode; 36 $mode = $table; 37 $table = $self->result_source->from; 38 $schema = $self->result_source->schema; 39 } 40 41 $schema->throw_exception('missing Table name to txn_do_locked()') 42 unless $table; 43 44 $table = [$table] if ref '' eq ref $table; 45 my $table_fmt = join ', ', ('%s' x scalar @$table); 46 my $sql = sprintf $sql_fmt, $table_fmt; 47 48 if (ref '' eq ref $mode and $mode) { 49 scalar grep {$_ eq $mode} values %lock_modes 50 or $schema->throw_exception('bad LOCK_MODE to txn_do_locked()'); 51 } 52 else { 53 $sub = $mode; 54 $mode = 'ACCESS EXCLUSIVE'; 55 } 56 57 $schema->txn_do(sub { 58 my @params = map {$schema->storage->dbh->quote_identifier($_)} @$table; 59 $schema->storage->dbh->do(sprintf $sql, @params, $mode); 60 $sub->(); 61 }); 62} 63 64=head1 NAME 65 66App::Netdisco::DB::ExplicitLocking - Support for PostgreSQL Lock Modes 67 68=head1 SYNOPSIS 69 70In your L<DBIx::Class> schema: 71 72 package My::Schema; 73 __PACKAGE__->load_components('+App::Netdisco::DB::ExplicitLocking'); 74 75Then, in your application code: 76 77 use App::Netdisco::DB::ExplicitLocking ':modes'; 78 $schema->txn_do_locked($table, MODE_NAME, sub { ... }); 79 80This also works for the ResultSet: 81 82 package My::Schema::ResultSet::TableName; 83 __PACKAGE__->load_components('+App::Netdisco::DB::ExplicitLocking'); 84 85Then, in your application code: 86 87 use App::Netdisco::DB::ExplicitLocking ':modes'; 88 $schema->resultset('TableName')->txn_do_locked(MODE_NAME, sub { ... }); 89 90=head1 DESCRIPTION 91 92This L<DBIx::Class> component provides an easy way to execute PostgreSQL table 93locks before a transaction block. 94 95You can load the component in either the Schema class or ResultSet class (or 96both) and then use an interface very similar to C<DBIx::Class>'s C<txn_do()>. 97 98The package also exports constants for each of the table lock modes supported 99by PostgreSQL, which must be used if specifying the mode (default mode is 100C<ACCESS EXCLUSIVE>). 101 102=head1 EXPORTS 103 104With the C<:modes> tag (as in SYNOPSIS above) the following constants are 105exported and must be used if specifying the lock mode: 106 107=over 4 108 109=item * C<ACCESS_SHARE> 110 111=item * C<ROW_SHARE> 112 113=item * C<ROW_EXCLUSIVE> 114 115=item * C<SHARE_UPDATE_EXCLUSIVE> 116 117=item * C<SHARE> 118 119=item * C<SHARE_ROW_EXCLUSIVE> 120 121=item * C<EXCLUSIVE> 122 123=item * C<ACCESS_EXCLUSIVE> 124 125=back 126 127=head1 METHODS 128 129=head2 C<< $schema->txn_do_locked($table|\@tables, MODE_NAME?, $subref) >> 130 131This is the method signature used when the component is loaded into your 132Schema class. The reason you might want to use this over the ResultSet version 133(below) is to specify multiple tables to be locked before the transaction. 134 135The first argument is one or more tables, and is required. Note that these are 136the real table names in PostgreSQL, and not C<DBIx::Class> ResultSet aliases 137or anything like that. 138 139The mode name is optional, and defaults to C<ACCESS EXCLUSIVE>. You must use 140one of the exported constants in this parameter. 141 142Finally pass a subroutine reference, just as you would to the normal 143C<DBIx::Class> C<txn_do()> method. Note that additional arguments are not 144supported. 145 146=head2 C<< $resultset->txn_do_locked(MODE_NAME?, $subref) >> 147 148This is the method signature used when the component is loaded into your 149ResultSet class. If you don't yet have a ResultSet class (which is the default 150- normally only Result classes are created) then you can create a stub which 151simply loads this component (and inherits from C<DBIx::Class::ResultSet>). 152 153This is the simplest way to use this module if you only want to lock one table 154before your transaction block. 155 156The first argument is the optional mode name, which defaults to C<ACCESS 157EXCLUSIVE>. You must use one of the exported constants in this parameter. 158 159The second argument is a subroutine reference, just as you would pass to the 160normal C<DBIx::Class> C<txn_do()> method. Note that additional arguments are 161not supported. 162 163=cut 164 1651; 166