Author: timbo
Date: Thu Feb 1 15:38:56 2007
New Revision: 8782
Modified:
dbi/trunk/DBI.pm
dbi/trunk/DBI.xs
dbi/trunk/lib/DBD/Gofer.pm
dbi/trunk/lib/DBD/Gofer/Transport/pipeone.pm
dbi/trunk/lib/DBD/Gofer/Transport/pipestream.pm
dbi/trunk/lib/DBI/Gofer/Execute.pm
dbi/trunk/lib/DBI/Gofer/Response.pm
dbi/trunk/lib/DBI/Gofer/Transport/pipestream.pm
dbi/trunk/lib/DBI/PurePerl.pm
dbi/trunk/t/09trace.t
Log:
Factory methods (ie connect & prepare) copy error/warn/info to their parent
handle (drh,dbh)
Matured pipwone and pipestream transports significantly.
Added some DBD::Gofer docs.
Warnings ($SIG{__WARN__}) are now caught by DBI::Gofer::Execute, included in
the response, and repeated by DBD::Gofer.
Modified: dbi/trunk/DBI.pm
==============================================================================
--- dbi/trunk/DBI.pm (original)
+++ dbi/trunk/DBI.pm Thu Feb 1 15:38:56 2007
@@ -391,8 +391,8 @@
swap_inner_handle => { U =>[2,3,'$h [, $allow_reparent ]'] },
},
dr => { # Database Driver Interface
- 'connect' => { U =>[1,5,'[$db [,$user [,$passwd [,\%attr]]]]'], H=>3
},
- 'connect_cached'=>{U=>[1,5,'[$db [,$user [,$passwd [,\%attr]]]]'], H=>3
},
+ 'connect' => { U =>[1,5,'[$db [,$user [,$passwd [,\%attr]]]]'],
H=>3, O=>0x8000 },
+ 'connect_cached'=>{U=>[1,5,'[$db [,$user [,$passwd [,\%attr]]]]'],
H=>3, O=>0x8000 },
'disconnect_all'=>{ U =>[1,1], O=>0x0800 },
data_sources => { U =>[1,2,'[\%attr]' ], O=>0x0800 },
default_user => { U =>[3,4,'$user, $pass [, \%attr]' ] },
@@ -401,15 +401,15 @@
data_sources => { U =>[1,2,'[\%attr]' ], O=>0x0200 },
take_imp_data => { U =>[1,1], },
clone => { U =>[1,2,'[\%attr]'] },
- connected => undef,
+ connected => { U =>[1,0], O => 0x0004 },
begin_work => { U =>[1,2,'[ \%attr ]'], O=>0x0400 },
commit => { U =>[1,1], O=>0x0480|0x0800 },
rollback => { U =>[1,1], O=>0x0480|0x0800 },
'do' => { U =>[2,0,'$statement [, \%attr [, @bind_params ]
]'], O=>0x3200 },
last_insert_id => { U =>[5,6,'$catalog, $schema, $table_name,
$field_name [, \%attr ]'], O=>0x2800 },
preparse => { }, # XXX
- prepare => { U =>[2,3,'$statement [, \%attr]'],
O=>0x2200 },
- prepare_cached => { U =>[2,4,'$statement [, \%attr [, $if_active ]
]'], O=>0x2200 },
+ prepare => { U =>[2,3,'$statement [, \%attr]'],
O=>0xA200 },
+ prepare_cached => { U =>[2,4,'$statement [, \%attr [, $if_active ]
]'], O=>0xA200 },
selectrow_array => { U =>[2,0,'$statement [, \%attr [, @bind_params ]
]'], O=>0x2000 },
selectrow_arrayref=>{U =>[2,0,'$statement [, \%attr [, @bind_params ]
]'], O=>0x2000 },
selectrow_hashref=>{ U =>[2,0,'$statement [, \%attr [, @bind_params ]
]'], O=>0x2000 },
Modified: dbi/trunk/DBI.xs
==============================================================================
--- dbi/trunk/DBI.xs (original)
+++ dbi/trunk/DBI.xs Thu Feb 1 15:38:56 2007
@@ -80,7 +80,7 @@
static int quote_type _((int sql_type, int p, int s, int *base_type, void
*v));
static int dbi_hash _((const char *string, long i));
static void dbih_dumphandle _((SV *h, const char *msg, int level));
-static void dbih_dumpcom _((imp_xxh_t *imp_xxh, const char *msg, int
level));
+static int dbih_dumpcom _((imp_xxh_t *imp_xxh, const char *msg, int
level));
char *neatsvpv _((SV *sv, STRLEN maxlen));
SV * preparse(SV *dbh, const char *statement, IV ps_return, IV ps_accept, void
*foo);
@@ -119,6 +119,7 @@
#define IMA_EXECUTE 0x1000 /* do/execute: DBIcf_Executed */
#define IMA_SHOW_ERR_STMT 0x2000 /* dbh meth relates to Statement*/
#define IMA_HIDE_ERR_PARAMVALUES 0x4000 /* ParamValues are not relevant
*/
+#define IMA_IS_FACTORY 0x8000 /* new h ie connect and prepare */
#define DBIc_STATE_adjust(imp_xxh, state) \
(SvOK(state) /* SQLSTATE is implemented by driver */ \
@@ -207,6 +208,10 @@
return buf;
}
+/* handy for embedding into condition expression for debugging */
+static int warn1(char *s) { warn(s); return 1; }
+static int dump1(SV *sv) { dTHX; sv_dump(sv); return 1; }
+
/* --- */
@@ -1183,7 +1188,7 @@
dbih_dumpcom(imp_xxh, msg, level);
}
-static void
+static int
dbih_dumpcom(imp_xxh_t *imp_xxh, const char *msg, int level)
{
dTHX;
@@ -1218,10 +1223,15 @@
if (DBIc_is(imp_xxh, DBIcf_Profile)) sv_catpv(flags,"Profile ");
if (DBIc_is(imp_xxh, DBIcf_Callbacks)) sv_catpv(flags,"Callbacks ");
PerlIO_printf(DBILOGFP,"%s FLAGS 0x%lx: %s\n", pad,
(long)DBIc_FLAGS(imp_xxh), SvPV(flags,lna));
+ if (SvOK(DBIc_ERR(imp_xxh)))
+ PerlIO_printf(DBILOGFP,"%s ERR %s\n", pad,
neatsvpv((SV*)DBIc_ERR(imp_xxh),0));
+ if (SvOK(DBIc_ERR(imp_xxh)))
+ PerlIO_printf(DBILOGFP,"%s ERRSTR %s\n", pad,
neatsvpv((SV*)DBIc_ERRSTR(imp_xxh),0));
PerlIO_printf(DBILOGFP,"%s PARENT %s\n", pad,
neatsvpv((SV*)DBIc_PARENT_H(imp_xxh),0));
PerlIO_printf(DBILOGFP,"%s KIDS %ld (%ld Active)\n", pad,
(long)DBIc_KIDS(imp_xxh), (long)DBIc_ACTIVE_KIDS(imp_xxh));
- PerlIO_printf(DBILOGFP,"%s IMP_DATA %s\n", pad,
neatsvpv(DBIc_IMP_DATA(imp_xxh),0));
+ if (DBIc_IMP_DATA(imp_xxh) && SvOK(DBIc_IMP_DATA(imp_xxh)))
+ PerlIO_printf(DBILOGFP,"%s IMP_DATA %s\n", pad,
neatsvpv(DBIc_IMP_DATA(imp_xxh),0));
if (DBIc_LongReadLen(imp_xxh) != DBIc_LongReadLen_init)
PerlIO_printf(DBILOGFP,"%s LongReadLen %ld\n", pad,
(long)DBIc_LongReadLen(imp_xxh));
@@ -1246,6 +1256,7 @@
PerlIO_printf(DBILOGFP,"%s '%s' => %s\n", pad, key,
neatsvpv(value,0));
}
}
+ return 1;
}
@@ -2912,7 +2923,7 @@
if (!keep_error && !(*meth_name=='s' && strEQ(meth_name,"set_err"))) {
SV *err_sv;
- if (trace_level >= 4 && SvOK(err_sv=DBIc_ERR(imp_xxh))) {
+ if (trace_level && SvOK(err_sv=DBIc_ERR(imp_xxh))) {
PerlIO *logfp = DBILOGFP;
PerlIO_printf(logfp, " !! %s: %s CLEARED by call to %s method\n",
SvTRUE(err_sv) ? "ERROR" : strlen(SvPV_nolen(err_sv)) ? "warn"
: "info",
@@ -3060,9 +3071,9 @@
}
SPAGAIN;
- if (trace_level) { /* XXX restore local vars so ST(n) works below
*/
- sp -= outitems; ax = (sp - stack_base) + 1;
- }
+ /* XXX restore local vars so ST(n) works below */
+ sp -= outitems;
+ ax = (sp - stack_base) + 1;
#ifdef DBI_save_hv_fetch_ent
if (is_FETCH)
@@ -3232,6 +3243,14 @@
}
}
+ if (ima_flags & IMA_IS_FACTORY && SvROK(ST(0))) {
+ SV *h_new = ST(0);
+ D_impdata(imp_xxh_new, imp_xxh_t, h_new);
+ if (SvOK(DBIc_ERR(imp_xxh_new))) {
+ set_err_sv(h, imp_xxh, DBIc_ERR(imp_xxh_new),
DBIc_ERRSTR(imp_xxh_new), DBIc_STATE(imp_xxh_new), &sv_no);
+ }
+ }
+
if ( !keep_error /* is a new err/warn/info
*/
&& call_depth <= 1 /* skip nested (internal) calls
*/
&& (
Modified: dbi/trunk/lib/DBD/Gofer.pm
==============================================================================
--- dbi/trunk/lib/DBD/Gofer.pm (original)
+++ dbi/trunk/lib/DBD/Gofer.pm Thu Feb 1 15:38:56 2007
@@ -69,10 +69,20 @@
$drh;
}
+
sub CLONE {
undef $drh;
}
+
+ sub set_err_from_response {
+ my ($h, $response) = @_;
+ # set error/warn/info
+ my $warnings = $response->warnings || [];
+ warn $_ for @$warnings;
+ return $h->set_err($response->err, $response->errstr,
$response->state);
+ }
+
}
@@ -119,7 +129,7 @@
or return $drh->set_err(1, "No transport= argument in
'$orig_dsn'");
$transport_class = "DBD::Gofer::Transport::$dsn_attr{go_transport}"
unless $transport_class =~ /::/;
- eval "require $transport_class" # XXX fix unsafe string eval
+ _load_class($transport_class)
or return $drh->set_err(1, "Error loading $transport_class: $@");
my $go_trans = eval { $transport_class->new(\%dsn_attr) }
or return $drh->set_err(1, "Error instanciating $transport_class:
$@");
@@ -158,6 +168,17 @@
}
sub DESTROY { undef }
+
+
+ sub _load_class { # return true or false+$@
+ my $class = shift;
+ (my $pm = $class) =~ s{::}{/}g;
+ $pm .= ".pm";
+ return 1 if eval { require $pm };
+ delete $INC{$pm}; # shouldn't be needed (perl bug?) and assigning
undef isn't enough
+ undef; # error in $@
+ }
+
}
@@ -198,7 +219,7 @@
$rv = [ $sth ];
}
- $dbh->set_err($response->err, $response->errstr, $response->state);
+ DBD::Gofer::set_err_from_response($dbh, $response);
return (wantarray) ? @$rv : $rv->[0];
}
@@ -362,12 +383,11 @@
$sth->{go_rows} = $response->rv;
}
# set error/warn/info (after more_results as that'll clear err)
- $sth->set_err($response->err, $response->errstr, $response->state);
+ DBD::Gofer::set_err_from_response($sth, $response);
return $response->rv;
}
-
# sth methods that should always fail, at least for now
for my $method (qw(
bind_param_inout bind_param_array bind_param_inout_array execute_array
execute_for_fetch
@@ -479,15 +499,21 @@
use DBI;
- $dbh = DBI->connect("dbi:Gofer:transport=$transport;...;dsn=$dsn",
+ $original_dsn = "dbi:..."; # your original DBI Data Source Name
+
+ $dbh = DBI->connect("dbi:Gofer:transport=$transport;...;dsn=$original_dsn",
$user, $passwd, \%attributes);
+ ... use $dbh as if it was connected to $original_dsn ...
+
+
The C<transport=$transport> part specifies the name of the module to use to
transport the requests to the remote DBI. If $transport doesn't contain any
double colons then it's prefixed with C<DBD::Gofer::Transport::>.
-The C<dsn=$dsn> part I<must> be the last element of the dsn because everything
-after C<dsn=> is assumed to be the DSN that the remote DBI should use.
+The C<dsn=$original_dsn> part I<must be the last element> of the DSN because
+everything after C<dsn=> is assumed to be the DSN that the remote DBI should
+use.
The C<...> represents attributes that influence the operation of the Gofer
driver or transport. These are described below or in the documentation of the
@@ -495,13 +521,15 @@
=head1 DESCRIPTION
-DBD::Gofer is a DBI database driver that forwards requests to another DBI
driver,
-usually in a seperate process, often on a separate machine.
-
-It is very similar to DBD::Proxy. The major difference is that DBD::Gofer
-assumes no state is maintained on the remote end. That means every request
-contains all the information needed to create the required state. (So, for
-example, every request includes the DSN to connect to.) Each request can be
+DBD::Gofer is a DBI database driver that forwards requests to another DBI
+driver, usually in a seperate process, often on a separate machine. It tries to
+be as transparent as possible so it appears that you are using the remote
+driver directly.
+
+DBD::Gofer is very similar to DBD::Proxy. The major difference is that with
+DBD::Gofer no state is maintained on the remote end. That means every
+request contains all the information needed to create the required state. (So,
+for example, every request includes the DSN to connect to.) Each request can be
sent to any available server. The server executes the request and returns a
single response that includes all the data.
@@ -549,8 +577,7 @@
=head3 Thin Clients / Unsupported Platforms
-You no longer need drivers for your database on every system.
-DBD::Gofer is pure perl
+You no longer need drivers for your database on every system. DBD::Gofer is
pure perl.
=head1 CONSTRAINTS
@@ -564,42 +591,128 @@
This is because it's critical that when a request is complete the database
handle is left in the same state it was when first connected.
-=head2 AutoCommit only
+=head2 You can't use transactions.
-Transactions aren't supported.
+AutoCommit only. Transactions aren't supported.
-=head1 CAVEATS
+=head2 You need to use func() to call driver-private dbh methods
-A few things to keep in mind when using DBD::Gofer:
+So instead of the new-style:
-=head2 Driver-private Methods
+ $dbh->foo_method_name(...)
-These can be called via the func() method on the dbh
-but not the sth.
+you need to use the old-style:
-=head2 Driver-private Statement Handle Attributes
+ $dbh->func(..., 'foo_method_name');
-Driver-private sth attributes can be set in the prepare() call. XXX
+This constraint might be removed in future.
-Driver-private sth attributes can't be read, currently. In future it will be
-possible to indicate which sth attributes you'd like to be able to read.
+=head2 You can't call driver-private sth methods
-=head1 Array Methods
+But few people need to do that.
+
+=head2 Array Methods are not supported
The array methods (bind_param_inout bind_param_array bind_param_inout_array
execute_array execute_for_fetch)
are not currently supported. Patches welcome, of course.
+=head1 CAVEATS
+
+A few things to keep in mind when using DBD::Gofer:
+
+=head2 Driver-private Database Handle Attributes
+
+Some driver-private dbh attributes may not be available, currently.
+In future it will be possible to indicate which attributes you'd like to be
+able to read.
+
+=head2 Driver-private Statement Handle Attributes
+
+Driver-private sth attributes can be set in the prepare() call. TODO
+
+Some driver-private sth attributes may not be available, currently.
+In future it will be possible to indicate which attributes you'd like to be
+able to read.
+
=head1 Multiple Resultsets
Multiple resultsets are supported if the driver supports the more_results()
method.
+=head1 TRANSPORTS
+
+DBD::Gofer doesn't concern itself with transporting requests and responses to
and fro.
+For that it uses special Gofer transport modules.
+
+Gofer transport modules usually come in pairs: one for the 'client' DBD::Gofer
+driver to use and one for the remote 'server' end. They have very similar
names:
+
+ DBD::Gofer::Transport::<foo>
+ DBI::Gofer::Transport::<foo>
+
+Several transport modules are provided with DBD::Gofer:
+
+=head2 null
+
+The null transport is the simplest of them all. It doesn't actually transport
the request anywhere.
+It just serializes (freezes) the request into a string, then thaws it back into
+a data structure before passing it to DBI::Gofer::Execute to execute. The same
+freeze and thaw is applied to the results.
+
+The null transport is the best way to test if your application will work with
Gofer.
+Just set the DBI_AUTOPROXY environment variable to
"C<dbi:Gofer:transport=null>"
+(see L</DBI_AUTOPROXY> below) and run your application, or ideally its test
suite, as usual.
+
+It doesn't take any parameters.
+
+=head2 pipeone
+
+The pipeone transport launches a subprocess for each request. It passes in the
+request and reads the response. The fact that a new subprocess is started for
+each request proves that the server side is truely stateless. It also makes
+this transport very slow. It's useful, however, both as a proof of concept and
+as a base class for the stream driver.
+
+It doesn't take any parameters.
+
+=head2 stream
+
+The stream driver also launches a subprocess and writes requests and reads
+responses, like the pipeone transport. In this case, however, the subprocess
+is expected to handle more that one request. (Though it will be restarted if
it exits.)
+
+This is the first transport that is truly useful because it can launch the
+subprocess using ssh. This means you can now use DBD::Gofer to easily access
+any databases that's accessible from any system you can login to.
+
+XXX ssh
+
+=head2 http
+
+The http driver uses the http protocol to send Gofer requests and receive
replies.
+
+XXX not yet implemented
+
=head1 CONNECTING
+Simply prefix your existing DSN with "C<dbi:Gofer:transport=$transport;dsn=>"
+where $transport is the name of the Gofer transport you want to use (see
L</TRANSPORTS>).
+The C<transport> and C<dsn> attributes must be specified and the C<dsn>
attributes must be last.
+
+Other attributes can be specified in the DSN to configure DBD::Gofer and/or
the transport being used.
+
XXX
=head2 Using DBI_AUTOPROXY
-XXX
+The simplest way to try out DBD::Gofer is to set the DBI_AUTOPROXY environment
variable.
+In this case you don't include the C<dsn=> part.
+
+ export DBI_AUTOPROXY=dbi:Gofer:transport=null
+
+or
+
+ export DBI_AUTOPROXY=dbi:Gofer:transport=stream;[EMAIL PROTECTED]
+
=head1 CONFIGURING VIA POLICY
@@ -607,11 +720,11 @@
=head1 AUTHOR AND COPYRIGHT
-The DBI module is Copyright (c) 2007 Tim Bunce. Ireland.
-All rights reserved.
-
-You may distribute under the terms of either the GNU General Public
-License or the Artistic License, as specified in the Perl README file.
+The DBD::Gofer, DBD::Gofer::* and DBI::Gofer::* modules are
+Copyright (c) 2007 Tim Bunce. Ireland. All rights reserved.
+
+You may distribute under the terms of either the GNU General Public License or
+the Artistic License, as specified in the Perl README file.
=head1 SEE ALSO
@@ -622,7 +735,7 @@
=head1 TODO
-dbh STORE doesn't record set attributes
+Test existing compiled drivers (ie DBD::mysql) for binary compatibility
Driver-private sth attributes - set via prepare() - change DBI spec
Auto-configure based on driver name.
@@ -634,8 +747,6 @@
Driver-private sth methods via func? Can't be sure of state?
-Sybase specific features.
-
XXX track installed_methods and install proxies on client side after connect?
XXX add hooks into transport base class for checking & updating a cache
@@ -652,6 +763,10 @@
check clone tests
-add early test that connected dbh is active
+Add policy mechanism
+
+Add mecahism for transports to list config params
+and for Gofer to apply any that match
+(and warn if any left over?)
=cut
Modified: dbi/trunk/lib/DBD/Gofer/Transport/pipeone.pm
==============================================================================
--- dbi/trunk/lib/DBD/Gofer/Transport/pipeone.pm (original)
+++ dbi/trunk/lib/DBD/Gofer/Transport/pipeone.pm Thu Feb 1 15:38:56 2007
@@ -25,6 +25,7 @@
sub start_pipe_command {
my ($self, $cmd) = @_;
+ $cmd = [ $cmd ] unless ref $cmd eq 'ARRAY';
# ensure subprocess will use the same modules as us
local $ENV{PERL5LIB} = join ":", @INC;
@@ -35,7 +36,7 @@
local $ENV{DBI_PROFILE};
my ($wfh, $rfh, $efh) = (gensym, gensym, gensym);
- my $pid = open3($wfh, $rfh, $efh, $cmd)
+ my $pid = open3($wfh, $rfh, $efh, @$cmd)
or die "error starting $cmd: $!\n";
warn "Started pid $pid: $cmd\n" if $self->trace;
@@ -47,13 +48,22 @@
}
+sub cmd_as_string {
+ my $self = shift;
+ # XXX meant to return a prroperly shell-escaped string suitable for system
+ # but its only for debugging so that can wait
+ my $connection_info = $self->connection_info;
+ return join " ", @{$connection_info->{cmd}};
+}
+
+
sub transmit_request {
my ($self, $request) = @_;
my $info = eval {
my $frozen_request = $self->freeze_data($request);
- my $cmd = "perl -MDBI::Gofer::Transport::pipeone -e run_one_stdio";
+ my $cmd = [qw(perl -MDBI::Gofer::Transport::pipeone -e run_one_stdio)];
my $info = $self->start_pipe_command($cmd);
my $wfh = delete $info->{wfh};
Modified: dbi/trunk/lib/DBD/Gofer/Transport/pipestream.pm
==============================================================================
--- dbi/trunk/lib/DBD/Gofer/Transport/pipestream.pm (original)
+++ dbi/trunk/lib/DBD/Gofer/Transport/pipestream.pm Thu Feb 1 15:38:56 2007
@@ -17,6 +17,10 @@
our $VERSION = sprintf("0.%06d", q$Revision: 8748 $ =~ /(\d+)/o);
+__PACKAGE__->mk_accessors(qw(
+ ssh
+));
+
sub nonblock;
@@ -27,8 +31,15 @@
my $connection = $self->connection_info;
if (not $connection || ($connection->{pid} && not kill 0,
$connection->{pid})) {
- my $cmd = "perl -MDBI::Gofer::Transport::pipestream -e
run_stdio_hex";
- #$cmd = "DBI_TRACE=2=/tmp/pipestream.log $cmd";
+ my $cmd = [qw(perl -MDBI::Gofer::Transport::pipestream -e
run_stdio_hex)];
+ #push @$cmd, "DBI_TRACE=2=/tmp/pipestream.log", "sh", "-c";
+ if (0) {
+ my $ssh = '[EMAIL PROTECTED]';
+ unshift @$cmd, 'ssh', '-q', split(' ', $ssh);
+ }
+ # XXX add a handshake - some message from
DBI::Gofer::Transport::pipestream that's
+ # sent as soon as it starts that we can wait for to report success
- and soak up
+ # and useful warnings etc from shh before we get it.
$connection = $self->start_pipe_command($cmd);
nonblock($connection->{efh});
$self->connection_info($connection);
@@ -65,25 +76,35 @@
my $connection = $self->connection_info || die;
my ($pid, $rfh, $efh) = @{$connection}{qw(pid rfh efh)};
+ # blocks till a newline has been read
my $frozen_response = <$rfh>; # always one line
- my $stderr_msg = do { local $/; <$efh> }; # nonblocking
-
- chomp $stderr_msg if $stderr_msg;
+ my $frozen_response_errno = $!;
- if (not $frozen_response) { # no output on stdout at all
- warn "STDERR err message: $stderr_msg" if $stderr_msg; # XXX do something
more useful
- return DBI::Gofer::Response->new({
- err => 1,
- errstr => "Error reading from $connection->{cmd}: $stderr_msg",
- });
+ # must read any stderr output _afterwards_
+ # warnings during execution are caught and returned as part
+ # of the response object. So stderr should be silent.
+ my $stderr_msg = do { local $/; <$efh> }; # nonblocking
+
+ # if we got no output on stdout at all then the command has
+ # proably exited, possibly with an error to stderr.
+ # Turn this situation into a reasonably useful DBI error.
+ if (not $frozen_response or !chomp $frozen_response) {
+ chomp $stderr_msg if $stderr_msg;
+ my $msg = sprintf("Error reading from %s (pid %d%s): ",
+ $self->cmd_as_string, $pid, (kill 0, $pid) ? "" : ", exited");
+ $msg .= $stderr_msg || $frozen_response_errno;
+ return DBI::Gofer::Response->new({ err => 1, errstr => $msg });
}
- chomp $frozen_response if $frozen_response;
- warn "STDERR additional message: $stderr_msg" if $stderr_msg; # XXX do
something more useful
#warn DBI::neat($frozen_response);
+ warn "pipestream stderr message: $stderr_msg\n" if $stderr_msg &&
$self->trace;
# XXX need to be able to detect and deal with corruption
$response = $self->thaw_data(pack("H*",$frozen_response));
+ # add any stderr messages as a warning (ie PrintWarn)
+ $response->add_err(0, $stderr_msg, undef, $self->trace)
+ if $stderr_msg;
+
return $response;
}
Modified: dbi/trunk/lib/DBI/Gofer/Execute.pm
==============================================================================
--- dbi/trunk/lib/DBI/Gofer/Execute.pm (original)
+++ dbi/trunk/lib/DBI/Gofer/Execute.pm Thu Feb 1 15:38:56 2007
@@ -155,6 +155,8 @@
my $request = shift;
local $recurse = $recurse + 1;
warn "Gofer request level $recurse\n" if $trace;
+ my @warnings;
+ local $SIG{__WARN__} = sub { push @warnings, @_ };
# guaranteed not to throw an exception
my $response = eval {
($request->is_sth_request)
@@ -162,13 +164,13 @@
: execute_dbh_request($request);
};
if ($@) {
- warn $@; # XXX
chomp $@;
$response = DBI::Gofer::Response->new({
err => 1, errstr => $@, state => '',
});
}
#warn "Gofer response level $recurse: ".$response->rv."\n" if $trace;
+ $response->warnings([EMAIL PROTECTED]) if @warnings;
return $response;
}
Modified: dbi/trunk/lib/DBI/Gofer/Response.pm
==============================================================================
--- dbi/trunk/lib/DBI/Gofer/Response.pm (original)
+++ dbi/trunk/lib/DBI/Gofer/Response.pm Thu Feb 1 15:38:56 2007
@@ -18,6 +18,50 @@
state
last_insert_id
sth_resultsets
+ warnings
));
+
+sub add_err {
+ my ($self, $err, $errstr, $state, $trace) = @_;
+ chomp $errstr if $errstr;
+ $state ||= '';
+ warn "add_err($err, $errstr, $state)" if $trace and $errstr || $err;
+
+ # acts like the DBI's set_err method.
+ # this code copied from DBI::PurePerl's set_err method.
+
+ my ($r_err, $r_errstr, $r_state) = ($self->{err}, $self->{errstr},
$self->{state});
+
+ if ($r_errstr) {
+ $r_errstr .= sprintf " [err was %s now %s]", $r_err, $err
+ if $r_err && $err;
+ $r_errstr .= sprintf " [state was %s now %s]", $r_state, $state
+ if $r_state and $r_state ne "S1000" && $state;
+ $r_errstr .= "\n$errstr";
+ }
+ else {
+ $r_errstr = $errstr;
+ }
+
+ # assign if higher priority: err > "0" > "" > undef
+ my $err_changed;
+ if ($err # new error: so assign
+ or !defined $r_err # no existing warn/info: so assign
+ # new warn ("0" len 1) > info ("" len 0): so assign
+ or defined $err && length($err) > length($r_err)
+ ) {
+ $r_err = $err;
+ ++$err_changed;
+ }
+
+ $r_state = ($state eq "00000") ? "" : $state
+ if $state && $err_changed;
+
+ ($self->{err}, $self->{errstr}, $self->{state}) = ($r_err, $r_errstr,
$r_state);
+
+ return undef;
+}
+
+
1;
Modified: dbi/trunk/lib/DBI/Gofer/Transport/pipestream.pm
==============================================================================
--- dbi/trunk/lib/DBI/Gofer/Transport/pipestream.pm (original)
+++ dbi/trunk/lib/DBI/Gofer/Transport/pipestream.pm Thu Feb 1 15:38:56 2007
@@ -24,6 +24,8 @@
my $self = DBI::Gofer::Transport::pipestream->new();
local $| = 1;
+ #warn "STARTED $$";
+
while ( my $frozen_request = <STDIN> ) {
my $request = $self->thaw_data( pack "H*", $frozen_request );
Modified: dbi/trunk/lib/DBI/PurePerl.pm
==============================================================================
--- dbi/trunk/lib/DBI/PurePerl.pm (original)
+++ dbi/trunk/lib/DBI/PurePerl.pm Thu Feb 1 15:38:56 2007
@@ -130,6 +130,7 @@
use constant IMA_EXECUTE => 0x1000; #/* do/execute: DBIcf_Executed */
use constant IMA_SHOW_ERR_STMT => 0x2000; #/* dbh meth relates to Statement*/
use constant IMA_HIDE_ERR_PARAMVALUES => 0x4000; #/* ParamValues are not
relevant */
+use constant IMA_IS_FACTORY => 0x8000; #/* new h ie connect & prepare */
my %is_flag_attribute = map {$_ =>1 } qw(
Active
@@ -320,6 +321,13 @@
} if IMA_END_WORK & $bitmask;
push @post_call_frag, q{
+ if ( ref $ret[0] and defined( (my $h_new = $ret[0])->{err} ) ) {
+ # copy up info/warn to drh so PrintWarn on connect is triggered
+ $h->set_err($h_new->{err}, $h_new->{errstr}, $h_new->{state})
+ }
+ } if IMA_IS_FACTORY & $bitmask;
+
+ push @post_call_frag, q{
$keep_error = 0 if $keep_error && $h->{ErrCount} > $ErrCount;
$DBI::err = $h->{err};
Modified: dbi/trunk/t/09trace.t
==============================================================================
--- dbi/trunk/t/09trace.t (original)
+++ dbi/trunk/t/09trace.t Thu Feb 1 15:38:56 2007
@@ -98,7 +98,7 @@
print "test unknown parse_trace_flag\n";
my $warn = 0;
local $SIG{__WARN__} = sub {
- if ($_[0] =~ /unknown/i) { ++$warn; print "warn: ",@_ }else{ warn @_ }
+ if ($_[0] =~ /unknown/i) { ++$warn; print "caught warn: ",@_ }else{
warn @_ }
};
is $dbh->parse_trace_flag("nonesuch"), undef;
is $warn, 0;