DBIO

 view release on metacpan or  search on metacpan

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

}

# Extract a blocking-do-capable DBI handle from a pool connection value. The two
# shapes the DBD-based Model-B backends use are a bare $dbh and the future_io
# { dbh => $dbh } wrapper (DBIO::PostgreSQL::Storage::Async::_create_pool_connection
# and friends). Anything else is a native backend that must override
# _run_pool_connect_statement.
sub _pool_connect_dbh {
  my ($self, $conn) = @_;

  return $conn
    if blessed($conn) && $conn->can('do');

  return $conn->{dbh}
    if ref $conn eq 'HASH' && blessed($conn->{dbh}) && $conn->{dbh}->can('do');

  croak 'Cannot run pool connect action: connection is neither a do-capable '
      . 'DBI handle nor a { dbh => $dbh } wrapper -- override '
      . '_run_pool_connect_statement for this backend';
}


sub sql_maker {
  my $self = shift;
  $self->{sql_maker} ||= do {
    my $class = $self->sql_maker_class;
    $class->new($self->_sql_maker_args);
  };
}


sub sql_maker_class {
  croak 'Subclass must override sql_maker_class';
}


sub _sql_maker_args { () }


sub transport_capabilities { () }


sub select_async {
  my $self = shift;
  return $self->_run_crud_pooled('select', @_);
}


sub select_single_async {
  my $self = shift;
  return $self->_run_crud_pooled('select_single', @_);
}


sub insert_async {
  my $self = shift;
  return $self->_run_crud_pooled('insert', @_);
}


sub update_async {
  my $self = shift;
  return $self->_run_crud_pooled('update', @_);
}


sub delete_async {
  my $self = shift;
  return $self->_run_crud_pooled('delete', @_);
}

# Prepare a SELECT through the owning sync storage, preserving the exact
# validation, join pruning and complex-prefetch rewrite its cursor path uses.
# _select_args returns the operation name first; the async SQLMaker consumes
# only the prepared select arguments that follow it.
sub _prepare_select_args_async {
  my ($self, $ident, $select, $where, $attrs) = @_;
  $attrs = {} unless defined $attrs;

  my $owner = _effective_owner_of($self);
  $self->throw_exception('async SELECT requires its owning sync storage')
    unless $owner && $owner->can('_select_args');

  my @prepared = $owner->_select_args($ident, $select, $where, $attrs);
  shift @prepared;
  return @prepared;
}


# The DB-specific INSERT-column retrieval (_post_insert_sql) and first-row
# post-processing for select_single live here exactly once, so the pooled path
# (_pool_runner) and the pinned txn path (_pinned_runner, used by the
# TransactionContext) share identical behaviour.
sub _run_crud {
  my ($self, $op, $runner, @args) = @_;

  my $sm = $self->sql_maker;
  if ($op eq 'select' || $op eq 'select_single') {
    my $ident = $args[0];
    @args = $self->_prepare_select_args_async(@args);
    my $attrs = $args[3] || {};
    my ($sql, @bind) = $sm->select(@args);
    # as in _gen_sql_bind: the values of the bind attribute go first
    @bind = bare_binds(@{ $attrs->{bind} || [] }, @bind);
    my $bind_attrs = $self->_crud_bind_attrs(select => $ident, \@bind, @args);
    my $future = $runner->($sql, \@bind, $bind_attrs);

    if ($op eq 'select' && $attrs->{software_limit}) {
      my $rows   = $attrs->{rows};
      my $offset = $attrs->{offset} || 0;
      my $fc = $self->future_class;
      $future = $future->then(sub {
        my @raw_rows = @_;
        splice @raw_rows, 0, $offset if $offset;
        splice @raw_rows, $rows if $rows && @raw_rows > $rows;
        return $fc->done(@raw_rows);
      });
    }

    return $self->_hold_derived($future) unless $op eq 'select_single';
    return $self->_hold_derived($future->then(sub {

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

    # through without the callback, and nothing after it is sent).
    my $next = $from + 1;
    return $f->then(sub {
      return $self->_run_ddl_stmts_async($conn, $stmts, $next, $pool);
    });
  }

  return $self->future_class->done;
}


sub _ddl_quote_ident {
  my ($self, $name) = @_;
  return DBIO::SQL::Util::_quote_ident($name);
}


sub _drop_statements_for {
  my ($self, $schema) = @_;
  return DBIO::SQL::Util::_drop_statements(
    $schema, sub { $self->_ddl_quote_ident($_[0]) }, { cascade => 1 },
  );
}

# ADR 0026: one-shot informational warning when async DDL runs on a
# non-transactional engine, mirroring the sync DBIO::Deploy::Base helper. Kept
# as a local one-time closure (not DBIO::Carp) so nothing leaks into the
# package's method namespace (t/55namespaces_cleaned.t). Keyed on the full
# message so a different class gets its own one-shot.
my %_WARNED_NON_TXN_DDL;
sub _warn_non_txn_ddl_once {
  my ($self, $msg) = @_;
  return if $_WARNED_NON_TXN_DDL{$msg}++;
  warn "non-transactional DDL on $msg\n";
}

# ADR 0026 addendum (k132), async parity (k199): one-shot notice that a
# transactional engine's DDL batch was split into multiple runs because the
# per-statement hook declared some statement non-transactional, so the batch is
# no longer atomic as a whole. Keyed on the CLASS, not the message -- the text
# carries per-batch statement/run counts that vary, so keying on the message
# would re-arm the one-shot for every new upgrade shape (the same reasoning as
# the sync DBIO::Deploy::Base::_warn_split_ddl_once). Local closure, not
# DBIO::Carp, to keep the package method namespace clean (t/55namespaces_cleaned.t).
my %_WARNED_SPLIT_DDL;
sub _warn_split_ddl_once {
  my ($self, $msg) = @_;
  my $key = ref($self) || $self;
  return if $_WARNED_SPLIT_DDL{$key}++;
  warn "split DDL batch on $msg\n";
}

# --- Sync fallbacks ---
# Let sync methods work by blocking on the async result via ->get.
# Useful for scripts/migrations.


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

# --- Schema integration ---

sub schema { $_[0]->{schema} }


sub debug  { $_[0]->{debug} }


sub in_txn { 0 }


sub connected { defined $_[0]->{pool} && $_[0]->pool->available > 0 }


sub disconnect {
  my $self = shift;
  # Rotate before shutdown so no context from the outgoing pool generation can
  # dispatch while or after that pool is being torn down. This also rotates when
  # the pool was never materialised.
  $self->_rotate_context_owner_token;
  $self->_listen_teardown(undef, 'the storage was disconnected')
    if $self->{_listen};
  if ($self->{pool}) {
    $self->{pool}->shutdown;
    $self->{pool} = undef;
  }
}

sub DESTROY {
  my $self = shift;
  $self->disconnect if $self->{pool} || $self->{_listen};
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

DBIO::Storage::Async - Base class for async storage implementations

=head1 VERSION

version 0.900005

=head1 SYNOPSIS

  # Async is chosen per connection, not by naming a storage class:
  my $schema = MyApp::Schema->connect(
      $dsn, $user, $pass,
      { async => 'future_io' },
  );



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