diff --git a/Netdisco/lib/App/Netdisco/DB.pm b/Netdisco/lib/App/Netdisco/DB.pm index 7636ad03..3a6ff320 100644 --- a/Netdisco/lib/App/Netdisco/DB.pm +++ b/Netdisco/lib/App/Netdisco/DB.pm @@ -21,6 +21,7 @@ __PACKAGE__->load_components(qw/ Schema::Versioned +App::Netdisco::DB::ExplicitLocking /); + __PACKAGE__->upgrade_directory($schema_versions_dir); 1; diff --git a/Netdisco/lib/App/Netdisco/DB/ExplicitLocking.pm b/Netdisco/lib/App/Netdisco/DB/ExplicitLocking.pm index 875159eb..8f691b32 100644 --- a/Netdisco/lib/App/Netdisco/DB/ExplicitLocking.pm +++ b/Netdisco/lib/App/Netdisco/DB/ExplicitLocking.pm @@ -26,25 +26,140 @@ our @EXPORT_OK = (keys %lock_modes); our %EXPORT_TAGS = (modes => \@EXPORT_OK); sub txn_do_locked { - my ($self, $table, $mode, $sub, @rest) = @_; + 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 length $table; - return 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 (!length $mode) { - unshift @rest, $sub if $sub; + if (length $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'; } - $self->txn_do(sub { - my @params = map {$self->storage->dbh->quote_identifier($_)} @$table; - $self->storage->dbh->do(sprintf $sql, @params, $mode); - $sub->(@rest); + $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 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 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's C. + +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). + +=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 + +=item * C + +=item * C + +=item * C + +=item * C + +=item * C + +=item * C + +=item * C + +=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 ResultSet aliases +or anything like that. + +The mode name is optional, and defaults to C. You must use +one of the exported constants in this parameter. + +Finally pass a subroutine reference, just as you would to the normal +C C 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). + +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. 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 C method. Note that additional arguments are +not supported. + +=cut + 1; diff --git a/Netdisco/lib/App/Netdisco/DB/ResultSet/Node.pm b/Netdisco/lib/App/Netdisco/DB/ResultSet/Node.pm index 483323fa..12ad40a1 100644 --- a/Netdisco/lib/App/Netdisco/DB/ResultSet/Node.pm +++ b/Netdisco/lib/App/Netdisco/DB/ResultSet/Node.pm @@ -4,6 +4,10 @@ use base 'DBIx::Class::ResultSet'; use strict; use warnings FATAL => 'all'; +__PACKAGE__->load_components(qw/ + +App::Netdisco::DB::ExplicitLocking +/); + =head1 search_by_mac( \%cond, \%attrs? ) my $set = $rs->search_by_mac({mac => '00:11:22:33:44:55', active => 1}); diff --git a/Netdisco/lib/App/Netdisco/DB/ResultSet/NodeIp.pm b/Netdisco/lib/App/Netdisco/DB/ResultSet/NodeIp.pm index 4a652e72..2a514c48 100644 --- a/Netdisco/lib/App/Netdisco/DB/ResultSet/NodeIp.pm +++ b/Netdisco/lib/App/Netdisco/DB/ResultSet/NodeIp.pm @@ -4,6 +4,10 @@ use base 'DBIx::Class::ResultSet'; use strict; use warnings FATAL => 'all'; +__PACKAGE__->load_components(qw/ + +App::Netdisco::DB::ExplicitLocking +/); + my $search_attr = { order_by => {'-desc' => 'time_last'}, '+columns' => [ diff --git a/Netdisco/lib/App/Netdisco/DB/ResultSet/NodeWireless.pm b/Netdisco/lib/App/Netdisco/DB/ResultSet/NodeWireless.pm new file mode 100644 index 00000000..c41b74f2 --- /dev/null +++ b/Netdisco/lib/App/Netdisco/DB/ResultSet/NodeWireless.pm @@ -0,0 +1,11 @@ +package App::Netdisco::DB::ResultSet::DeviceModule; +use base 'DBIx::Class::ResultSet'; + +use strict; +use warnings FATAL => 'all'; + +__PACKAGE__->load_components(qw/ + +App::Netdisco::DB::ExplicitLocking +/); + +1; diff --git a/Netdisco/lib/App/Netdisco/DB/ResultSet/Subnets.pm b/Netdisco/lib/App/Netdisco/DB/ResultSet/Subnets.pm new file mode 100644 index 00000000..954c81d3 --- /dev/null +++ b/Netdisco/lib/App/Netdisco/DB/ResultSet/Subnets.pm @@ -0,0 +1,11 @@ +package App::Netdisco::DB::ResultSet::Subnets; +use base 'DBIx::Class::ResultSet'; + +use strict; +use warnings FATAL => 'all'; + +__PACKAGE__->load_components(qw/ + +App::Netdisco::DB::ExplicitLocking +/); + +1;