DBIO
view release on metacpan or search on metacpan
t/storage/pool_connect_actions.t view on Meta::CPAN
{
package RecHandle;
sub new { bless { log => $_[1] }, $_[0] }
sub do { my ($self, $sql) = @_; push @{ $self->{log} }, $sql; 1 }
}
{
package RecPool;
use base 'DBIO::Storage::PoolBase';
my $NEXT = 0;
sub _create_connection {
my ($self, $conninfo) = @_;
my @log;
return { id => ++$NEXT, log => \@log, dbh => RecHandle->new(\@log) };
}
# Keep the connection (and its log) intact through shutdown so the test can
# assert on the disconnect actions that ran against it.
sub _shutdown_connection { }
}
# --- The fake async backend -------------------------------------------------
# Concrete DBIO::Storage::Async that overrides only the seams it needs, wiring
# itself as the RecPool's owner so the central pool-spawn hook can reach it. It
# inherits _setup_pool_connection / _teardown_pool_connection / the default
# _run_pool_connect_statement unchanged.
{
package RecBackend;
use base 'DBIO::Storage::Async';
sub future_class { 'Future' }
sub sql_maker_class { 'DBIO::SQLMaker' }
sub _transform_sql { $_[1] }
sub _post_insert_sql { '' }
sub pool {
my $self = shift;
$self->{pool} ||= RecPool->new(
storage => $self,
conninfo => $self->{_conninfo} || 'fake',
size => $self->{_pool_size} || 5,
);
}
}
# --- The owning sync storage ------------------------------------------------
# isa DBIO::Storage::DBI, so it carries the on_connect_* accessors and the sync
# _do_connection_actions dispatch. Defines a connect_call_* / disconnect_call_*
# 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(
on_connect_call => 'test_setup',
on_connect_do => [ 'PRAGMA one', 'PRAGMA two' ],
size => 2,
);
my @expected = ( 'SETUP CALL', 'PRAGMA one', 'PRAGMA two' );
my $c1 = $backend->pool->acquire->get;
is_deeply $c1->{log}, \@expected,
'first pool connection replayed on_connect_call then on_connect_do at spawn';
# The replay happened at spawn, i.e. before acquire even resolved -- so it is
# in place before the first query is ever served on this connection.
ok scalar(@{ $c1->{log} }), 'connect actions present the moment the connection is handed out';
# A second physical connection gets its OWN independent replay.
my $c2 = $backend->pool->acquire->get;
isnt $c1->{id}, $c2->{id}, 'two distinct physical connections spawned (pool size 2)';
is_deeply $c2->{log}, \@expected,
'second pool connection got its own full on_connect replay';
# Idle reuse must NOT re-run the actions (spawn-only, once per physical conn).
$backend->pool->release($c1);
my $reused = $backend->pool->acquire->get;
is $reused->{id}, $c1->{id}, 'released connection is reused, not re-spawned';
is_deeply $reused->{log}, \@expected,
'reused idle connection did not replay the on_connect actions again';
}
# ---------------------------------------------------------------------------
# connect_call dispatch: coderef form and nested-arrayref do_sql form both route
# through _do_query onto the pool connection (mirrors sync resolution order).
# ---------------------------------------------------------------------------
{
my ($backend, $owner) = wired_backend(
on_connect_call => sub { my $s = shift; $s->_do_query('CODEREF SQL') },
on_connect_do => 'PLAIN SQL',
size => 1,
);
my $c = $backend->pool->acquire->get;
is_deeply $c->{log}, [ 'CODEREF SQL', 'PLAIN SQL' ],
'coderef on_connect_call and scalar on_connect_do both ran on the pool connection';
}
{
my ($backend, $owner) = wired_backend(
on_connect_call => [ [ do_sql => 'NESTED DO SQL' ] ],
size => 1,
);
t/storage/pool_connect_actions.t view on Meta::CPAN
my $c = $backend->pool->acquire->get;
is_deeply $c->{log}, [ 'CONNECT SQL' ], 'connect action ran at spawn';
$backend->pool->shutdown;
is_deeply $c->{log},
[ 'CONNECT SQL', 'TEARDOWN CALL', 'DISCONNECT SQL' ],
'shutdown replayed on_disconnect_call then on_disconnect_do on the live connection';
}
# ---------------------------------------------------------------------------
# No configuration + no owner: the seam is a clean no-op (never breaks a plain
# pool spawn).
# ---------------------------------------------------------------------------
{
my ($backend, $owner) = wired_backend(size => 1); # owner wired, but no actions
my $c = $backend->pool->acquire->get;
is_deeply $c->{log}, [], 'no on_connect config -> nothing replayed';
# A backend with NO owner wired at all: spawn must not blow up.
my $bare = RecBackend->new($schema);
$bare->connect_info([ { host => 'h', pool_size => 1 } ]);
my $bc;
lives_ok { $bc = $bare->pool->acquire->get } 'spawn with no owner wired is a no-op, not a crash';
is_deeply $bc->{log}, [], 'no owner -> no connect actions';
}
# ---------------------------------------------------------------------------
# Runner seam: the default _run_pool_connect_statement handles both the
# { dbh => $dbh } wrapper and a bare do-capable handle, and croaks on anything
# else (so a native backend knows to override it).
# ---------------------------------------------------------------------------
{
my $backend = RecBackend->new($schema);
my @log;
my $wrapped = { dbh => RecHandle->new(\@log) };
$backend->_run_pool_connect_statement($wrapped, 'WRAP SQL');
is_deeply \@log, [ 'WRAP SQL' ], 'default runner drives { dbh => $dbh } wrapper handles';
my @bare_log;
my $bare_handle = RecHandle->new(\@bare_log); # blessed, ->can("do")
$backend->_run_pool_connect_statement($bare_handle, 'BARE SQL');
is_deeply \@bare_log, [ 'BARE SQL' ], 'default runner drives a bare do-capable handle';
throws_ok { $backend->_run_pool_connect_statement('not-a-handle', 'X') }
qr/override _run_pool_connect_statement/,
'default runner croaks on an unrecognised connection shape, naming the override';
}
# ---------------------------------------------------------------------------
# _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;
( run in 0.357 second using v1.01-cache-2.11-cpan-14f38c9f855 )