<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">package DBD::Gofer::Transport::pipeone;

#   $Id: pipeone.pm 10087 2007-10-16 12:42:37Z Tim $
#
#   Copyright (c) 2007, Tim Bunce, Ireland
#
#   You may distribute under the terms of either the GNU General Public
#   License or the Artistic License, as specified in the Perl README file.

use strict;
use warnings;

use Carp;
use Fcntl;
use IO::Select;
use IPC::Open3 qw(open3);
use Symbol qw(gensym);

use base qw(DBD::Gofer::Transport::Base);

our $VERSION = "0.010088";

__PACKAGE__-&gt;mk_accessors(qw(
    connection_info
    go_perl
));


sub new {
    my ($self, $args) = @_;
    $args-&gt;{go_perl} ||= do {
        ($INC{"blib.pm"}) ? [ $^X, '-Mblib' ] : [ $^X ];
    };
    if (not ref $args-&gt;{go_perl}) {
        # user can override the perl to be used, either with an array ref
        # containing the command name and args to use, or with a string
        # (ie via the DSN) in which case, to enable args to be passed,
        # we split on two or more consecutive spaces (otherwise the path
        # to perl couldn't contain a space itself).
        $args-&gt;{go_perl} = [ split /\s{2,}/, $args-&gt;{go_perl} ];
    }
    return $self-&gt;SUPER::new($args);
}


# nonblock($fh) puts filehandle into nonblocking mode
sub nonblock {
  my $fh = shift;
  my $flags = fcntl($fh, F_GETFL, 0)
        or croak "Can't get flags for filehandle $fh: $!";
  fcntl($fh, F_SETFL, $flags | O_NONBLOCK)
        or croak "Can't make filehandle $fh nonblocking: $!";
}


sub start_pipe_command {
    my ($self, $cmd) = @_;
    $cmd = [ $cmd ] unless ref $cmd eq 'ARRAY';

    # if it's important that the subprocess uses the same
    # (versions of) modules as us then the caller should
    # set PERL5LIB itself.

    # limit various forms of insanity, for now
    local $ENV{DBI_TRACE}; # use DBI_GOFER_TRACE instead
    local $ENV{DBI_AUTOPROXY};
    local $ENV{DBI_PROFILE};

    my ($wfh, $rfh, $efh) = (gensym, gensym, gensym);
    my $pid = open3($wfh, $rfh, $efh, @$cmd)
        or die "error starting @$cmd: $!\n";
    if ($self-&gt;trace) {
        $self-&gt;trace_msg(sprintf("Started pid $pid: @$cmd {fd: w%d r%d e%d, ppid=$$}\n", fileno $wfh, fileno $rfh, fileno $efh),0);
    }
    nonblock($rfh);
    nonblock($efh);
    my $ios = IO::Select-&gt;new($rfh, $efh);

    return {
        cmd=&gt;$cmd,
        pid=&gt;$pid,
        wfh=&gt;$wfh, rfh=&gt;$rfh, efh=&gt;$efh,
        ios=&gt;$ios,
    };
}


sub cmd_as_string {
    my $self = shift;
    # XXX meant to return a properly shell-escaped string suitable for system
    # but its only for debugging so that can wait
    my $connection_info = $self-&gt;connection_info;
    return join " ", map { (m/^[-:\w]*$/) ? $_ : "'$_'" } @{$connection_info-&gt;{cmd}};
}


sub transmit_request_by_transport {
    my ($self, $request) = @_;

    my $frozen_request = $self-&gt;freeze_request($request);

    my $cmd = [ @{$self-&gt;go_perl}, qw(-MDBI::Gofer::Transport::pipeone -e run_one_stdio)];
    my $info = $self-&gt;start_pipe_command($cmd);

    my $wfh = delete $info-&gt;{wfh};
    # send frozen request
    local $\;
    print $wfh $frozen_request
        or warn "error writing to @$cmd: $!\n";
    # indicate that there's no more
    close $wfh
        or die "error closing pipe to @$cmd: $!\n";

    $self-&gt;connection_info( $info );
    return;
}


sub read_response_from_fh {
    my ($self, $fh_actions) = @_;
    my $trace = $self-&gt;trace;

    my $info = $self-&gt;connection_info || die;
    my ($ios) = @{$info}{qw(ios)};
    my $errors = 0;
    my $complete;

    die "No handles to read response from" unless $ios-&gt;count;

    while ($ios-&gt;count) {
        my @readable = $ios-&gt;can_read();
        for my $fh (@readable) {
            local $_;
            my $actions = $fh_actions-&gt;{$fh} || die "panic: no action for $fh";
            my $rv = sysread($fh, $_='', 1024*31);  # to fit in 32KB slab
            unless ($rv) {              # error (undef) or end of file (0)
                my $action;
                unless (defined $rv) {  # was an error
                    $self-&gt;trace_msg("error on handle $fh: $!\n") if $trace &gt;= 4;
                    $action = $actions-&gt;{error} || $actions-&gt;{eof};
                    ++$errors;
                    # XXX an error may be a permenent condition of the handle
                    # if so we'll loop here - not good
                }
                else {
                    $action = $actions-&gt;{eof};
                    $self-&gt;trace_msg("eof on handle $fh\n") if $trace &gt;= 4;
                }
                if ($action-&gt;($fh)) {
                    $self-&gt;trace_msg("removing $fh from handle set\n") if $trace &gt;= 4;
                    $ios-&gt;remove($fh);
                }
                next;
            }
            # action returns true if the response is now complete
            # (we finish all handles
            $actions-&gt;{read}-&gt;($fh) &amp;&amp; ++$complete;
        }
        last if $complete;
    }
    return $errors;
}


sub receive_response_by_transport {
    my $self = shift;

    my $info = $self-&gt;connection_info || die;
    my ($pid, $rfh, $efh, $ios, $cmd) = @{$info}{qw(pid rfh efh ios cmd)};

    my $frozen_response;
    my $stderr_msg;

    $self-&gt;read_response_from_fh( {
        $efh =&gt; {
            error =&gt; sub { warn "error reading response stderr: $!"; 1 },
            eof   =&gt; sub { warn "eof on stderr" if 0; 1 },
            read  =&gt; sub { $stderr_msg .= $_; 0 },
        },
        $rfh =&gt; {
            error =&gt; sub { warn "error reading response: $!"; 1 },
            eof   =&gt; sub { warn "eof on stdout" if 0; 1 },
            read  =&gt; sub { $frozen_response .= $_; 0 },
        },
    });

    waitpid $info-&gt;{pid}, 0
        or warn "waitpid: $!"; # XXX do something more useful?

    die ref($self)." command (@$cmd) failed: $stderr_msg"
        if not $frozen_response; # no output on stdout at all

    # XXX need to be able to detect and deal with corruption
    my $response = $self-&gt;thaw_response($frozen_response);

    if ($stderr_msg) {
        # add stderr messages as warnings (for PrintWarn)
        $response-&gt;add_err(0, $stderr_msg, undef, $self-&gt;trace)
            # but ignore warning from old version of blib
            unless $stderr_msg =~ /^Using .*blib/ &amp;&amp; "@$cmd" =~ /-Mblib/;
    }

    return $response;
}


1;

__END__

=head1 NAME

DBD::Gofer::Transport::pipeone - DBD::Gofer client transport for testing

=head1 SYNOPSIS

  $original_dsn = "...";
  DBI-&gt;connect("dbi:Gofer:transport=pipeone;dsn=$original_dsn",...)

or, enable by setting the DBI_AUTOPROXY environment variable:

  export DBI_AUTOPROXY="dbi:Gofer:transport=pipeone"

=head1 DESCRIPTION

Connect via DBD::Gofer and execute each request by starting executing a subprocess.

This is, as you might imagine, spectacularly inefficient!

It's only intended for testing. Specifically it demonstrates that the server
side is completely stateless.

It also provides a base class for the much more useful L&lt;DBD::Gofer::Transport::stream&gt;
transport.

=head1 AUTHOR

Tim Bunce, L&lt;http://www.tim.bunce.name&gt;

=head1 LICENCE AND COPYRIGHT

Copyright (c) 2007, Tim Bunce, Ireland. All rights reserved.

This module is free software; you can redistribute it and/or
modify it under the same terms as Perl itself. See L&lt;perlartistic&gt;.

=head1 SEE ALSO

L&lt;DBD::Gofer::Transport::Base&gt;

L&lt;DBD::Gofer&gt;

=cut
</pre></body></html>