DBIO
view release on metacpan or search on metacpan
t/resultset/async_parity_helpers.t view on Meta::CPAN
use strict;
use warnings;
use Test::More;
use Test::Exception;
use DBIO::Test;
use DBIO::Future::Immediate;
use DBIO::Storage::Async;
use DBIO::Storage::DBI;
# ADR 0031's own Consequences section named Row-level update_async/delete_async
# as "not added (no caller); only Row::insert_async was needed (by
# create_async). Same pattern when wanted." DBIO-Valiant's validated async
# writes are that caller now. This file gives every Row/ResultSet operation
# ADR 0031 left out a real, backend-routed async counterpart: the row-level
# primitives (update_async/delete_async), the cheap wins that fall out of
# find_async (get_from_storage_async/discard_changes_async/
# update_or_insert_async/count_literal_async), the find_async-composed
# composites (find_or_new_async/find_or_create_async/update_or_create_async/
# update_or_new_async), and results_exist_async.
#
# All of this runs on the mock storage -- no real DB, no event loop.
# --- A mock async backend that delegates to the sync storage ------------------
# Same shape as t/resultset/async_backend.t's My::Mock::RowBackend, extended
# with update_async/delete_async (which that file's mock never needed).
{
package My::Mock::ParityBackend;
use base 'DBIO::Storage::Async';
sub new { my ($c, $s) = @_; bless { schema => $s, calls => [] }, $c }
sub future_class { 'DBIO::Future::Immediate' }
sub connect_info { my $s = shift; $s->{connect_info} = shift if @_; $s->{connect_info} }
sub disconnect { 1 }
sub _sync { $_[0]{schema}->storage }
sub select_async {
my ($self, @args) = @_;
push @{$self->{calls}}, 'select';
my @rows = $self->_sync->select(@args)->all;
return DBIO::Future::Immediate->done(@rows);
}
sub select_single_async {
my ($self, @args) = @_;
push @{$self->{calls}}, 'select_single';
my @row = $self->_sync->select_single(@args);
return DBIO::Future::Immediate->done( @row ? [ @row ] : undef );
}
sub insert_async {
my ($self, @args) = @_;
push @{$self->{calls}}, 'insert';
my $cols = $self->_sync->insert(@args);
return DBIO::Future::Immediate->done($cols);
}
sub update_async {
my ($self, @args) = @_;
push @{$self->{calls}}, 'update';
my $rows = $self->_sync->update(@args);
return DBIO::Future::Immediate->done($rows);
}
sub delete_async {
my ($self, @args) = @_;
push @{$self->{calls}}, 'delete';
my $rows = $self->_sync->delete(@args);
return DBIO::Future::Immediate->done($rows);
}
# The transport seam for an already-rendered statement (ADR 0037 section 2:
# results_exist executes the _exists_value_sql hook's complete statement,
# which has no ident/select/where to hand to the CRUD entry points). Routed
# through the sync fake storage so the statement lands in the same captured
# query log the sync path uses -- that is what makes SQL parity assertable.
sub _query_async {
my ($self, $sql, $bind) = @_;
push @{$self->{calls}}, 'query';
my (undef, $sth) = $self->_sync->_execute_rendered($sql, $bind);
return DBIO::Future::Immediate->done(@{ $sth->fetchall_arrayref || [] });
}
}
DBIO::Storage::DBI->register_async_mode( parityasyncmock => 'My::Mock::ParityBackend' );
# Set the chosen mode on a mock storage exactly as connect would, clearing the
# resolved-backend cache. Mirrors the helper in t/resultset/async_backend.t.
sub set_mode {
my ($storage, $mode) = @_;
$storage->_async_mode($mode);
delete $storage->{_async_storage_obj};
return $storage;
}
# --- A local schema carrying an OnColumnChange hook -------------------------
# Isolated from DBIO::Test::Schema (which registers no hooks) so registering
# before/after callbacks here can't leak into other tests. Mirrors the
# TestOCC::Schema fixture in t/helpers/row_helpers_storage.t.
{
package TestUAOCC::Changes;
our @LOG;
sub reset { @LOG = () }
sub all { @LOG }
}
{
package TestUAOCC::Schema;
use base 'DBIO::Schema';
}
{
package TestUAOCC::Schema::Result::Account;
use base 'DBIO::Core';
__PACKAGE__->table('account');
__PACKAGE__->add_columns(
id => {
( run in 0.945 second using v1.01-cache-2.11-cpan-145b269f2f3 )