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 2.125 seconds using v1.01-cache-2.11-cpan-14f38c9f855 )