DBIO

 view release on metacpan or  search on metacpan

t/composed/async_mirror.t  view on Meta::CPAN

# ABSTRACT: karr #70 WP2/WP3 -- async mirror composition, capabilities, '?' seam contract
use strict;
use warnings;

use Test::More;

BEGIN { eval { require Future; 1 } or plan skip_all => 'Future not installed' }

use DBIO::Test;
use DBIO::Storage::DBI;
use DBIO::Storage::Async;
use DBIO::Storage::Composed;
use DBIO::SQLMaker;

# ---------------------------------------------------------------------------
# A reusable dummy transport: a concrete DBIO::Storage::Async that records the
# SQL its query seam receives, with an IDENTITY _transform_sql (its wire speaks
# '?' -- so the placeholders must arrive unchanged from _run_crud).
# ---------------------------------------------------------------------------
{
  package T::AM::RecordingTransport;
  use base 'DBIO::Storage::Async';
  use mro 'c3';

  our @RECORDED;
  sub future_class     { 'Future' }
  sub sql_maker_class  { 'DBIO::SQLMaker' }
  sub _transform_sql   { $_[1] }        # identity: wire speaks '?'
  sub _post_insert_sql { '' }
  sub _query_async     { push @RECORDED, $_[1]; return Future->done }
  sub pool             { $_[0]->{pool} ||= bless {}, 'T::AM::NoPool' }
}

# Build a fresh sync storage for one scenario. $mode is the async mode string;
# the transport is registered against it on the sync storage class so
# _async_storage resolves it from the registry (explicit mode -- ADR 0030),
# except the one future_io scenario which resolves it by convention. Returns
# ($storage, $schema); the caller MUST hold $schema in scope -- storage weakens
# its schema back-reference, so a dropped schema would undef $self->schema
# inside _async_storage.
sub build_storage {
  my (%arg) = @_;
  my $schema = DBIO::Test->init_schema;
  $schema->register_storage_layer($_) for @{ $arg{layers} || [] };

  my $storage_class = $arg{storage_class};
  my $storage = $storage_class->new($schema);
  $storage->connect_info([ 'dbi:AMMock:', '', '', { async => $arg{mode} } ]);
  return ($storage, $schema);
}

# ===========================================================================
# Scenario 1 (future_io, convention transport): one layer WITH ::Async and one
# WITHOUT -> the backend is composed with exactly the one async layer.
# ===========================================================================
{
  package T::AM::S1::Storage;         use base 'DBIO::Test::Storage'; use mro 'c3';
  package T::AM::S1::Storage::Async;  use base 'T::AM::RecordingTransport'; use mro 'c3';

  package T::AM::S1::LayerA;          sub layer_a { 'a' }
  package T::AM::S1::LayerA::Async;   sub async_a { 'aa' }   # the async mirror
  package T::AM::S1::LayerB;          sub layer_b { 'b' }    # NO ::Async sibling
}

{
  my ($storage, $schema) = build_storage(
    storage_class => 'T::AM::S1::Storage',
    mode          => 'future_io',
    layers        => [ 'T::AM::S1::LayerA', 'T::AM::S1::LayerB' ],
  );

  my $async = $storage->async;
  isa_ok $async, 'T::AM::S1::Storage::Async',
    'future_io resolved the transport by convention';
  isa_ok $async, 'T::AM::S1::LayerA::Async',
    'the WITH-::Async layer was mirrored and composed in';
  can_ok $async, 'async_a';

  ok !$async->isa('T::AM::S1::LayerB'),
    'the sync-only layer (no ::Async) is not composed into the async backend';
  ok !$async->can('layer_b'),
    'sync-only layer methods are absent from the async backend';

  is_deeply [ DBIO::Storage::Composed->layers_of(ref $async) ],
    [ 'T::AM::S1::LayerA::Async' ],
    'exactly one async layer composed over the transport';

  # --- WP3 '?' seam contract: _run_crud hands the transport raw sql_maker
  # output with '?' placeholders (its _transform_sql is identity) ----------
  @T::AM::RecordingTransport::RECORDED = ();
  $async->select_async('artist', [ '*' ], { id => 5 });
  my $sql = $T::AM::RecordingTransport::RECORDED[-1];
  ok defined $sql, 'the transport recorded a query from _run_crud';
  like $sql, qr/\?/,  'placeholders arrive at the seam as ? (sql_maker dialect)';
  unlike $sql, qr/\$\d/, 'core applied no $N dialect shaping';
}

# ===========================================================================



( run in 1.411 second using v1.01-cache-2.11-cpan-995e09ba956 )