DBIO

 view release on metacpan or  search on metacpan

lib/DBIO/Storage/Async/TransactionContext.pm  view on Meta::CPAN

  # Load-order: this context class is loaded standalone by dbio-async's
  # DBIO::Async::TransactionContext (use base ...) WITHOUT pulling
  # DBIO::Storage::Async, and a bare mock backend (above) is not an Async
  # subclass, so Async.pm may not be loaded yet when this runs -- the helper
  # would then be an undefined subroutine (karr core k209). A compile-time
  # `use DBIO::Storage::Async` cannot fix it: Async.pm use-s this class back
  # (its line 13), so that closes a circular use. Break the cycle with a
  # runtime require, which is idempotent and safe here because the helper is
  # only ever needed at call time (dbio-perl-syntax: require to break a genuine
  # circular dependency).
  require DBIO::Storage::Async;
  my $owner = DBIO::Storage::Async::_effective_owner_of($backend);
  DBIO::Exception->throw('async context ownership cannot be proven')
    unless $owner;
  $owner->_assert_async_context_owner($self);
  return $self;
}


sub _query_async {
  my ($self, $sql, $bind) = @_;
  $self->_assert_context_active;
  return $self->_track_statement(
    $self->{storage}->_query_async_pinned($self->{txn_conn}, $sql, $bind)
  );
}

# CRUD inside a transaction MUST run on the pinned connection that
# BEGIN/COMMIT ran on, not a fresh pooled one. Each method routes
# through the storage's shared CRUD builder with the pinned connection,
# so SQL generation (insert post-processing, select_single first-row)
# is not duplicated. Forwarding to $storage->select_async etc. would
# acquire a separate pool connection and run the query OUTSIDE the
# transaction.


sub select_async {
  my $self = shift;
  $self->_assert_context_active;
  return $self->_track_statement(
    $self->{storage}->_run_crud_pinned('select', $self->{txn_conn}, @_)
  );
}

sub select_single_async {
  my $self = shift;
  $self->_assert_context_active;
  return $self->_track_statement(
    $self->{storage}->_run_crud_pinned('select_single', $self->{txn_conn}, @_)
  );
}

sub insert_async {
  my $self = shift;
  $self->_assert_context_active;
  return $self->_track_statement(
    $self->{storage}->_run_crud_pinned('insert', $self->{txn_conn}, @_)
  );
}

sub update_async {
  my $self = shift;
  $self->_assert_context_active;
  return $self->_track_statement(
    $self->{storage}->_run_crud_pinned('update', $self->{txn_conn}, @_)
  );
}

sub delete_async {
  my $self = shift;
  $self->_assert_context_active;
  return $self->_track_statement(
    $self->{storage}->_run_crud_pinned('delete', $self->{txn_conn}, @_)
  );
}

# Sync fallbacks -- block the event loop via ->get


sub select             { my $self = shift; $self->select_async(@_)->get        }
sub select_single      { my $self = shift; $self->select_single_async(@_)->get }
sub insert             { my $self = shift; $self->insert_async(@_)->get        }
sub update             { my $self = shift; $self->update_async(@_)->get        }
sub delete             { my $self = shift; $self->delete_async(@_)->get        }

# Delegations


sub sql_maker    { my $self = shift; $self->{storage}->sql_maker(@_)    }
sub debug        { my $self = shift; $self->{storage}->debug(@_)        }

sub pipeline {
  my ($self, $coderef) = @_;
  $self->_assert_context_active;
  if (my $refusal = $self->{storage}->_pipeline_refusal) {
    return $refusal;
  }
  # No $release: the transaction boundary hands the connection back. A $retire
  # that marks it instead, for that boundary to act on (karr k232).
  my $batch = $self->{storage}->_pipeline_on_conn(
    $self->{txn_conn}, $coderef, undef,
    sub { $self->_mark_txn_conn_unusable(@_) },
  );
  # Kept apart from the statements: the context lets go of those as it ends,
  # and the transaction's ROLLBACK waits for these (karr k331). Weakly -- what
  # holds the Future until it is ready is _track_statement and its retain
  # (karr k292); a context still in hand must not hold it any longer.
  if (Scalar::Util::blessed($batch) && $batch->can('catch')) {
    push @{ $self->{_txn_pipelines} }, $batch;
    Scalar::Util::weaken($self->{_txn_pipelines}[-1]);
  }
  return $self->_track_statement($batch);
}


sub _mark_txn_conn_unusable {
  my ($self, $error) = @_;
  $self->{_txn_conn_unusable} = 1;
  $self->{_txn_conn_unusable_error} = $error
    if defined $error && !defined $self->{_txn_conn_unusable_error};
  return;
}


sub _txn_conn_unusable_error { return $_[0]->{_txn_conn_unusable_error} }

sub _txn_conn_unusable { return $_[0]->{_txn_conn_unusable} ? 1 : 0 }


sub _set_txn_guard { $_[0]->{_txn_guard} = $_[1]; return }

sub _txn_guard { return $_[0]->{_txn_guard} }


sub _track_statement {
  my ($self, $future) = @_;
  push @{ $self->{_txn_statements} }, $future
    if Scalar::Util::blessed($future) && $future->can('catch');
  return $future;
}

sub _txn_statements { return @{ $_[0]->{_txn_statements} || [] } }



( run in 0.619 second using v1.01-cache-2.11-cpan-145b269f2f3 )