DBIO

 view release on metacpan or  search on metacpan

docs/adr/0030-async-explicit-per-connection-mode.md  view on Meta::CPAN

   `dbio-postgresql-ev` and `dbio-mysql-ev` each re-implement the same
   loop-agnostic machinery — connect-info normalisation, pool wiring, `_run_crud`
   and its runners, `txn_do_async`, the pipeline scaffold, the TransactionContext.
   The only genuinely different part is the **query transport** (`_query_async`:
   Future::IO vs EV::Pg vs EV::MariaDB) plus a little DB-specific SQL. `dbio-async`
   already generalised that machinery behind seam hooks, but no driver inherits it.

The shaping requirement: async must be a thing the application **declares
explicitly, per connection**, and the same schema class must be runnable in
several async modes at once (one forked instance, one Future::IO instance, one EV
instance — all alive together). And the shared orchestration must live in exactly
one place.

## Decision

### 1. Async is an explicit, per-connection mode

The async backend is chosen **at `connect` time, per instance**, by a named
string and is fixed for the life of that instance:

```perl

lib/DBIO/Manual/Cookbook.pod  view on Meta::CPAN

While you can get more fine-grained control using C<svp_begin>, C<svp_release>
and C<svp_rollback>, it is strongly recommended to use C<txn_do> with coderefs.

=head2 Simple Transactions with DBIO::Storage::TxnScopeGuard

An easy way to use transactions is with
L<DBIO::Storage::TxnScopeGuard>. See L</Automatically creating
related objects> for an example.

Note that unlike txn_do, TxnScopeGuard will only make sure the connection is
alive when issuing the C<BEGIN> statement. It will not (and really can not)
retry if the server goes away mid-operations, unlike C<txn_do>.

=head1 SQL

=head2 Creating Schemas From An Existing Database

L<DBIO::Generate> will connect to a database and create L<DBIO::Schema>
Result class files by examining the database.

The recommended way of achieving this is to use the L<dbiogen> utility or the

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

          $fc->fail($rerr);
        });
      }

      # karr #10: the coderef's Future is almost always the tail of a
      # ->then chain. Real Future holds a downstream sequence Future only
      # WEAKLY, so unless we keep a strong ref it gets GC'd the moment this
      # callback returns -- Future warns "lost a sequence Future",
      # COMMIT/ROLLBACK never fires, and the await loop busy-spins forever.
      # ->retain gives $chain_f a self-reference until it is ready, keeping
      # the whole chain alive. Not every future_class implements ->retain
      # (an immediately-resolved shim has no GC window), so guard the call.
      if (ref $inner && $inner->can('then')) {
        my $chain_f = $inner->then(sub {
          my @result = @_;
          return $self->_query_async_pinned($conn, 'COMMIT', [])->then(sub {
            $self->pool->release($conn);
            $fc->done(@result);
          }, sub {
            my $cerr = shift;
            $self->pool->release($conn);

t/storage/pool_connect_actions.t  view on Meta::CPAN

# method that emits a known SQL via _do_query -- the exact convention real
# drivers use (connect_call_load_age, connect_call_use_foreign_keys, ...).
{
  package RecOwner;
  use base 'DBIO::Storage::DBI';

  sub connect_call_test_setup       { $_[0]->_do_query('SETUP CALL') }
  sub disconnect_call_test_teardown { $_[0]->_do_query('TEARDOWN CALL') }
}

my $schema = DBIO::Test->init_schema;   # kept alive: storages weaken their ref

sub wired_backend {
  my %config = @_;
  my $backend = RecBackend->new($schema);
  my $owner   = RecOwner->new($schema);

  $owner->on_connect_do($config{on_connect_do})           if exists $config{on_connect_do};
  $owner->on_connect_call($config{on_connect_call})       if exists $config{on_connect_call};
  $owner->on_disconnect_do($config{on_disconnect_do})     if exists $config{on_disconnect_do};
  $owner->on_disconnect_call($config{on_disconnect_call}) if exists $config{on_disconnect_call};

  $backend->_owner_storage($owner);
  $backend->connect_info([ { host => 'h', pool_size => $config{size} || 5 } ]);

  # return the owner too so its weak back-ref stays alive for the caller
  return ($backend, $owner);
}

# ---------------------------------------------------------------------------
# on_connect_do + on_connect_call replay on every freshly spawned connection,
# in the sync dispatch order (call before do), BEFORE the connection serves any
# query.
# ---------------------------------------------------------------------------
{
  my ($backend, $owner) = wired_backend(

t/storage/pool_connect_actions.t  view on Meta::CPAN

# _owner_storage back-reference is weak (no cycle: sync storage owns the async
# backend, not the reverse).
# ---------------------------------------------------------------------------
{
  my $backend = RecBackend->new($schema);
  {
    my $owner = RecOwner->new($schema);
    $backend->_owner_storage($owner);
    is $backend->_owner_storage, $owner, '_owner_storage getter returns the wired owner';
  }
  # $owner has gone out of scope; the weak ref must not keep it alive.
  is $backend->_owner_storage, undef, '_owner_storage is a weak reference';
}

done_testing;

t/test/15_async_orchestration.t  view on Meta::CPAN

  sub _pipeline_enter { push @{ $_[0]->{captured} }, { sql => 'PIPELINE_ENTER' }; 1 }
  sub _pipeline_exit  { push @{ $_[0]->{captured} }, { sql => 'PIPELINE_EXIT'  }; 1 }
  sub _pipeline_sync  { push @{ $_[0]->{captured} }, { sql => 'PIPELINE_SYNC'  }; $_[0]->future_class->done }

  # test helper
  sub _last_sql { $_[0]->{captured}[-1]{sql} }
}

sub new_backend {
  my $schema = DBIO::Test->init_schema;
  # keep the schema alive for the caller (backend weakens its ref)
  my $backend = Test::SyncBackend->new($schema);
  return ($backend, $schema);
}

# ---------------------------------------------------------------------------
# connect_info normalization
# ---------------------------------------------------------------------------
{
  my ($backend) = new_backend();

t/test/16_async_future_io_convention.t  view on Meta::CPAN

  use base 'DBIO::Test::Storage';
  use mro 'c3';
}
{
  package AsyncConv::BaseReg::Storage;         # NO ::Async sibling; used with a base-class reg
  use base 'DBIO::Test::Storage';
  use mro 'c3';
}

# Build a driver storage of $class in future_io mode, bound to a live schema.
# Returns ($storage, $schema) -- the caller must keep $schema alive (the async
# backend weakens its schema ref).
sub driver_storage {
  my ($class) = @_;
  my $schema  = DBIO::Test->init_schema;
  my $storage = $class->new($schema);
  $storage->_async_mode('future_io');       # Test::Storage defaults to 'immediate'
  delete $storage->{_async_storage_obj};
  $storage->_connect_info([ { host => 'localhost' } ]);
  return ($storage, $schema);
}



( run in 1.383 second using v1.01-cache-2.11-cpan-14f38c9f855 )