Airlock

 view release on metacpan or  search on metacpan

examples/lib/AirlockExample/StoreDBI.pm  view on Meta::CPAN

package AirlockExample::StoreDBI;

# The four Airlock store subs on plain DBI. Table: examples/schema.sql.
#
#   my $store   = AirlockExample::StoreDBI->new( dbh => $dbh );
#   my $airlock = Airlock->new( store => $store->as_subs, ... );
#
# The handle must have RaiseError set: insert relies on the database refusing
# a duplicate hash or user code.

use Moo;
use Airlock;
use Carp qw( croak );
use namespace::autoclean;

# a DBI handle, or a coderef returning one (for connection pools and forks)
has dbh => ( is => 'ro', required => 1 );

has table => ( is => 'ro', default => 'airlock' );

sub _dbh {
  my ( $self ) = @_;
  return ref $self->dbh eq 'CODE' ? $self->dbh->() : $self->dbh;
}

sub _fields { Airlock->row_fields }

sub insert {
  my ( $self, $row ) = @_;
  my @fields = $self->_fields;
  $self->_dbh->do(
    'INSERT INTO '.$self->table.' ('.join( ', ', @fields ).') VALUES ('.join( ', ', ('?') x @fields ).')',
    undef, @{$row}{@fields}
  );
  return 1;
}

sub find {
  my ( $self, $field, $value ) = @_;
  croak __PACKAGE__.'->find by '.$field.' is not supported' unless $field eq 'hash' || $field eq 'user_code';
  return unless defined $value;
  return $self->_dbh->selectrow_hashref(
    'SELECT '.join( ', ', $self->_fields ).' FROM '.$self->table.' WHERE '.$field.' = ?', undef, $value
  );
}

# The WHERE on the old state is the whole point: of two concurrent redeems
# only one statement changes a row.
sub update {
  my ( $self, $hash, $from_state, $changes ) = @_;
  my %known  = map { $_ => 1 } $self->_fields;
  my @fields = sort keys %$changes;
  croak __PACKAGE__.'->update unknown field' if grep { !$known{$_} } @fields;
  # a reference to a number means "add it", done by the database in one statement
  my @set = map { ref $changes->{$_} eq 'SCALAR' ? $_.' = '.$_.' + ?' : $_.' = ?' } @fields;
  my $changed = $self->_dbh->do(
    'UPDATE '.$self->table.' SET '.join( ', ', @set ).' WHERE hash = ? AND state = ?',
    undef, ( map { ref eq 'SCALAR' ? $$_ : $_ } @{$changes}{@fields} ), $hash, $from_state
  );
  return $changed > 0 ? 1 : 0;
}

sub purge {
  my ( $self, $before ) = @_;
  return $self->_dbh->do( 'DELETE FROM '.$self->table.' WHERE expires < ?', undef, $before ) + 0;
}

sub as_subs {
  my ( $self ) = @_;
  return {
    insert => sub { $self->insert(@_) },
    find   => sub { $self->find(@_) },
    update => sub { $self->update(@_) },
    purge  => sub { $self->purge(@_) }
  };
}

1;



( run in 0.508 second using v1.01-cache-2.11-cpan-02e1dfdd527 )