DBIO
view release on metacpan or search on metacpan
lib/DBIO/Storage/Async.pm view on Meta::CPAN
}
# Extract a blocking-do-capable DBI handle from a pool connection value. The two
# shapes the DBD-based Model-B backends use are a bare $dbh and the future_io
# { dbh => $dbh } wrapper (DBIO::PostgreSQL::Storage::Async::_create_pool_connection
# and friends). Anything else is a native backend that must override
# _run_pool_connect_statement.
sub _pool_connect_dbh {
my ($self, $conn) = @_;
return $conn
if blessed($conn) && $conn->can('do');
return $conn->{dbh}
if ref $conn eq 'HASH' && blessed($conn->{dbh}) && $conn->{dbh}->can('do');
croak 'Cannot run pool connect action: connection is neither a do-capable '
. 'DBI handle nor a { dbh => $dbh } wrapper -- override '
. '_run_pool_connect_statement for this backend';
}
sub sql_maker {
my $self = shift;
$self->{sql_maker} ||= do {
my $class = $self->sql_maker_class;
$class->new($self->_sql_maker_args);
};
}
sub sql_maker_class {
croak 'Subclass must override sql_maker_class';
}
sub _sql_maker_args { () }
sub transport_capabilities { () }
sub select_async {
my $self = shift;
return $self->_run_crud('select', $self->_pool_runner, @_);
}
sub select_single_async {
my $self = shift;
return $self->_run_crud('select_single', $self->_pool_runner, @_);
}
sub insert_async {
my $self = shift;
return $self->_run_crud('insert', $self->_pool_runner, @_);
}
sub update_async {
my $self = shift;
return $self->_run_crud('update', $self->_pool_runner, @_);
}
sub delete_async {
my $self = shift;
return $self->_run_crud('delete', $self->_pool_runner, @_);
}
# Build SQL for a CRUD op and execute it through $runner, a coderef that
# takes ($sql, \@bind) and returns a Future of result rows. The DB-specific
# INSERT-column retrieval (_post_insert_sql) and first-row post-processing for
# select_single live here exactly once, so the pooled path (_pool_runner) and
# the pinned txn path (_pinned_runner, used by the TransactionContext) share
# identical behaviour. Placeholder-dialect shaping (?->$N) is NOT applied here:
# the query seams receive raw sql_maker '?' SQL and shape it internally (karr
# #70 / ADR 0032), so the runner is handed the raw $sql.
sub _run_crud {
my ($self, $op, $runner, @args) = @_;
my $sm = $self->sql_maker;
if ($op eq 'select' || $op eq 'select_single') {
my ($sql, @bind) = $sm->select(@args);
my $f = $runner->($sql, \@bind);
return $f unless $op eq 'select_single';
return $f->then(sub {
my @rows = @_;
return @rows ? $rows[0] : undef;
});
}
elsif ($op eq 'insert') {
# The CRUD contract is insert_async($source, \%rowdata) (ADR 0031 §3),
# where $source is a result source. SQLMaker wants a plain table name,
# so unwrap a blessed source to its ->name; a bare string passes through.
my ($source, @rest) = @args;
my $to_insert = $rest[0];
my $table = (blessed($source) && $source->can('name'))
? $source->name
: $source;
my ($sql, @bind) = $sm->insert($table, @rest);
my $post = $self->_post_insert_sql;
$sql .= $post if length $post && $sql !~ /RETURNING/i;
# ADR 0031 §3: insert_async must resolve the returned-columns HASHREF --
# the supplied insert data overlaid with the DB-populated columns (autoinc
# PK + retrieve-on-insert), exactly what sync $storage->insert returns --
# so create_async / Row::insert_async can fold it back via
# _store_inserted_columns. The runner yields the raw RETURNING row(s); map
# the first onto the insert data. (select_async / select_single_async keep
# resolving raw arrayrefs, matching the sync cursor shape.)
return $runner->($sql, \@bind)->then(sub {
my @rows = @_;
return $self->_insert_returned_columns($source, $to_insert, $rows[0]);
});
}
elsif ($op eq 'update') {
my ($sql, @bind) = $sm->update(@args);
return $runner->($sql, \@bind);
lib/DBIO/Storage/Async.pm view on Meta::CPAN
}
sub _use_transactional_ddl {
my ($self) = @_;
my $owner = $self->_owner_storage or return 0;
return 0 unless $owner->can('_use_transactional_ddl');
return $owner->_use_transactional_ddl ? 1 : 0;
}
sub _execute_ddl_async {
my ($self, $conn, $ddl) = @_;
my @stmts = grep { !/^\s*--/ } DBIO::SQL::Util::_split_statements($ddl);
my $fc = $self->future_class;
my $recur;
$recur = sub {
my $stmt = shift @stmts;
return $fc->done unless defined $stmt;
return $self->_query_async_pinned($conn, $stmt, [])->then($recur);
};
return $recur->();
}
sub _drop_statements_for {
my ($self, $schema) = @_;
my @out;
for my $name ($schema->sources) {
my $source = $schema->source($name);
next if $source->isa('DBIO::ResultSource::View');
my $table = $source->name;
next if ref $table;
next if $table =~ /\s|\(/;
push @out, sprintf 'DROP TABLE IF EXISTS %s CASCADE;', DBIO::SQL::Util::_quote_ident($table);
}
return join("\n\n", @out);
}
# ADR 0026: one-shot informational warning when async DDL runs on a
# non-transactional engine, mirroring the sync DBIO::Deploy::Base helper. Kept
# as a local one-time closure (not DBIO::Carp) so nothing leaks into the
# package's method namespace (t/55namespaces_cleaned.t). Keyed on the full
# message so a different class gets its own one-shot.
my %_WARNED_NON_TXN_DDL;
sub _warn_non_txn_ddl_once {
my ($self, $msg) = @_;
return if $_WARNED_NON_TXN_DDL{$msg}++;
warn "non-transactional DDL on $msg\n";
}
# --- Sync fallbacks ---
# Let sync methods work by blocking on the async result via ->get.
# Useful for scripts/migrations.
sub select { my $self = shift; return $self->select_async(@_)->get }
sub select_single { my $self = shift; return $self->select_single_async(@_)->get }
sub insert { my $self = shift; return $self->insert_async(@_)->get }
sub update { my $self = shift; return $self->update_async(@_)->get }
sub delete { my $self = shift; return $self->delete_async(@_)->get }
sub txn_do { my $self = shift; return $self->txn_do_async(@_)->get }
sub deploy { my $self = shift; return $self->deploy_async(@_)->get }
# --- Schema integration ---
sub schema { $_[0]->{schema} }
sub debug { $_[0]->{debug} }
sub in_txn { 0 }
sub connected { defined $_[0]->{pool} && $_[0]->pool->available > 0 }
sub disconnect {
my $self = shift;
if ($self->{pool}) {
$self->{pool}->shutdown;
$self->{pool} = undef;
}
}
sub DESTROY {
my $self = shift;
$self->disconnect if $self->{pool};
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
DBIO::Storage::Async - Base class for async storage implementations
=head1 VERSION
version 0.900001
=head1 SYNOPSIS
# Async is chosen per connection, not by naming a storage class:
my $schema = MyApp::Schema->connect(
$dsn, $user, $pass,
{ async => 'future_io' },
);
# Async queries return Futures
$schema->resultset('Artist')->all_async->then(sub {
my @artists = @_;
say $_->name for @artists;
});
See F<t/test/09_async.t> for a runnable example.
=head1 DESCRIPTION
( run in 0.493 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )