Author: timbo
Date: Thu Mar  1 08:25:21 2007
New Revision: 9185

Added:
   dbi/trunk/goferperf.pl   (contents, props changed)
Modified:
   dbi/trunk/Changes
   dbi/trunk/DBI.pm
   dbi/trunk/MANIFEST
   dbi/trunk/META.yml
   dbi/trunk/lib/DBD/Gofer.pm
   dbi/trunk/lib/DBD/Gofer/Transport/http.pm
   dbi/trunk/lib/DBD/NullP.pm
   dbi/trunk/lib/DBI/Gofer/Execute.pm
   dbi/trunk/t/85gofer.t

Log:
Fix http transport.
Changed DBD::NullP to be vaguely useful for testing.
Assorted Gofer bug fixes, enhancements and docs.
Added goferperf.pl utility (doesn't get installed).
Bump version to 1.55


Modified: dbi/trunk/Changes
==============================================================================
--- dbi/trunk/Changes   (original)
+++ dbi/trunk/Changes   Thu Mar  1 08:25:21 2007
@@ -29,8 +29,11 @@
   Changed DBD::Gofer to work around a DBD::Sybase bind_param bug.
   Changed _set_fbav to not croak when given a wrongly sized array,
     it now warns and adjusts the row buffer to match.
+  Changed DBD::NullP to be vaguely useful for testing.
+  Assorted Gofer bug fixes, enhancements and docs.
 
   Added support for install_methods to DBD::Gofer.
+  Added goferperf.pl utility (doesn't get installed).
 
 =head2 Changes in DBI 1.54 (svn rev 9157),  23rd February 2007
 

Modified: dbi/trunk/DBI.pm
==============================================================================
--- dbi/trunk/DBI.pm    (original)
+++ dbi/trunk/DBI.pm    Thu Mar  1 08:25:21 2007
@@ -9,7 +9,7 @@
 require 5.006_00;
 
 BEGIN {
-$DBI::VERSION = "1.54"; # ==> ALSO update the version in the pod text below!
+$DBI::VERSION = "1.55"; # ==> ALSO update the version in the pod text below!
 }
 
 =head1 NAME
@@ -120,7 +120,7 @@
 
 =head2 NOTES
 
-This is the DBI specification that corresponds to the DBI version 1.54
+This is the DBI specification that corresponds to the DBI version 1.55
 ($Revision$).
 
 The DBI is evolving at a steady pace, so it's good to check that

Modified: dbi/trunk/MANIFEST
==============================================================================
--- dbi/trunk/MANIFEST  (original)
+++ dbi/trunk/MANIFEST  Thu Mar  1 08:25:21 2007
@@ -62,6 +62,7 @@
 lib/DBI/Util/_accessor.pm       A cut-down version of Class::Accessor::Fast
 lib/DBI/W32ODBC.pm             An experimental DBI emulation layer for 
Win32::ODBC
 lib/Win32/DBIODBC.pm           An experimental Win32::ODBC emulation layer for 
DBI
+goferperf.pl                    A performance test utility for DBD::Gofer
 t/01basics.t
 t/02dbidrv.t
 t/03handle.t

Modified: dbi/trunk/META.yml
==============================================================================
--- dbi/trunk/META.yml  (original)
+++ dbi/trunk/META.yml  Thu Mar  1 08:25:21 2007
@@ -1,7 +1,7 @@
 # http://module-build.sourceforge.net/META-spec.html
 #XXXXXXX This is a prototype!!!  It will change in the future!!! XXXXX#
 name:         DBI
-version:      1.54
+version:      1.55
 version_from: DBI.pm
 installdirs:  site
 requires:

Added: dbi/trunk/goferperf.pl
==============================================================================
--- (empty file)
+++ dbi/trunk/goferperf.pl      Thu Mar  1 08:25:21 2007
@@ -0,0 +1,137 @@
+#!perl -w
+# vim:sw=4:ts=8
+$|=1;
+
+use strict;
+use warnings;
+
+use Cwd;
+use Time::HiRes qw(time);
+use Data::Dumper;
+use Getopt::Long;
+
+use DBI;
+
+GetOptions(
+    'c|count=i' => \(my $opt_count = 100),
+    'dsn=s'     => \(my $opt_dsn   = "dbi:NullP:"),
+    'timeout=i' => \(my $opt_timeout = 10),
+    'p|policy=s' => \(my $opt_policy = "pedantic,classic,rush"),
+) or exit 1;
+
+if ($ENV{DBI_AUTOPROXY}) {
+    # this means we have DBD::Gofer => DBD::Gofer => DBD::DBM!
+    # rather than disable it we let it run because we're twisted
+    # and because it helps find more bugs (though debugging can be painful)
+    warn "\n$0 is running with DBI_AUTOPROXY enabled ($ENV{DBI_AUTOPROXY})\n";
+}
+
+# ensure subprocess (for pipeone and stream transport) will use the same 
modules as us, ie ./blib
+local $ENV{PERL5LIB} = join ":", @INC;
+
+my %durations;
+my $username = eval { getpwuid($>) } || ''; # fails on windows
+my $can_ssh = ($username && $username eq 'timbo' && -d '.svn');
+my $perl = "$^X"; # ensure sameperl and our blib (note two spaces)
+   # ensure blib (note two spaces)
+   $perl .= sprintf "  -Mblib=%s/blib", getcwd() if $ENV{PERL5LIB} =~ 
m{/blib/};
+
+my %trials = (
+    null       => {},
+    pipeone    => { perl=>$perl, timeout=>$opt_timeout },
+    stream     => { perl=>$perl, timeout=>$opt_timeout },
+    stream_ssh => ($can_ssh)
+                ? { perl=>$perl, timeout=>$opt_timeout, url => "ssh:[EMAIL 
PROTECTED]" }
+                : undef,
+    http       => { url => "http://localhost:8001/gofer"; },
+);
+
+# to get baseline for comparisons
+run_tests('no', {}, 'no');
+
+for my $trial (@ARGV) {
+    (my $transport = $trial) =~ s/_.*//;
+    my $trans_attr = $trials{$trial} or do {
+        warn "No trial '$trial' defined - skipped";
+        next;
+    };
+
+    for my $policy_name (split /\s*,\s*/, $opt_policy) {
+        eval { run_tests($transport, $trans_attr, $policy_name) };
+        warn $@ if $@;
+    }
+}
+
+while ( my ($activity, $stats_hash) = each %durations ) {
+    print "\n";
+    $stats_hash->{'~baseline~'} = delete $stats_hash->{"no+no"};
+    for my $perf_tag (reverse sort keys %$stats_hash) {
+        my $dur = $stats_hash->{$perf_tag};
+        printf "  %6s %-16s: %.6fsec (%5d/sec)",
+            $activity, $perf_tag, $dur/$opt_count, $opt_count/$dur;
+        my $baseline_dur = $stats_hash->{'~baseline~'};
+        printf " %+5.1fms", (($dur-$baseline_dur)/$opt_count)*1000
+            unless $perf_tag eq '~baseline~';
+        print "\n";
+    }
+}
+
+
+sub run_tests {
+    my ($transport, $trans_attr, $policy_name) = @_;
+
+    my $test_run_tag = "Testing $transport transport with $policy_name policy";
+    print "\n$test_run_tag\n";
+
+    my $dsn = $opt_dsn;
+    if ($policy_name ne 'no') {
+        my $driver_dsn = "transport=$transport;policy=$policy_name";
+        $driver_dsn .= join ";", '', map { "$_=$trans_attr->{$_}" } keys 
%$trans_attr
+            if %$trans_attr;
+        $dsn = "dbi:Gofer:$driver_dsn;dsn=$opt_dsn";
+    }
+    print " $dsn\n";
+
+    my $dbh = DBI->connect($dsn, undef, undef, { RaiseError => 1 } );
+
+    $dbh->do("DROP TABLE IF EXISTS fruit");
+    $dbh->do("CREATE TABLE fruit (dKey INT, dVal VARCHAR(10))");
+    my $ins_sth = $dbh->prepare("INSERT INTO fruit VALUES (?,?)");
+    $ins_sth->execute(1, 'apples');
+    $ins_sth->execute(2, 'oranges');
+    $ins_sth->execute(3, 'lemons');
+    $ins_sth->execute(4, 'limes');
+
+    my $start = time();
+    $dbh->selectall_arrayref("SELECT dKey, dVal FROM fruit")
+        for (1000..1000+$opt_count);
+    $durations{select}{"$transport+$policy_name"} = time() - $start;
+
+    # insert some rows in to get a (*very* rough) idea of overheads
+    $start = time();
+    $ins_sth->execute($_, 'speed')
+        for (1000..1000+$opt_count);
+    $durations{insert}{"$transport+$policy_name"} = time() - $start;
+
+    $dbh->do("DROP TABLE fruit");
+    $dbh->disconnect;
+}
+
+sub get_policy {
+    my ($policy_class) = @_;
+    $policy_class = "DBD::Gofer::Policy::$policy_class" unless $policy_class 
=~ /::/;
+    _load_class($policy_class) or die $@;
+    return $policy_class->new();
+}
+
+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 $@
+}   
+
+
+1;

Modified: dbi/trunk/lib/DBD/Gofer.pm
==============================================================================
--- dbi/trunk/lib/DBD/Gofer.pm  (original)
+++ dbi/trunk/lib/DBD/Gofer.pm  Thu Mar  1 08:25:21 2007
@@ -104,7 +104,7 @@
                 $sub = sub { return shift->go_dbh_method(undef, $method, @_) };
             }
             else {
-                $sub = sub { return shift->set_err(1, "Can't call 
\$${type}h->$method when using DBD::Gofer") };
+                $sub = sub { shift->set_err(1, "Can't call \$${type}h->$method 
when using DBD::Gofer"); return; };
             }
             no strict 'refs';
             *$driver_method = $sub;
@@ -177,7 +177,7 @@
             $request_class->new({
                 connect_args => [ $remote_dsn, $go_attr ]
             })
-        } or return $drh->set_err(1, "Can't instanciate $request_class $@");
+        } or return $drh->set_err(1, "Can't instanciate $request_class: $@");
 
         my ($dbh, $dbh_inner) = DBI::_new_dbh($drh, {
             'Name' => $dsn,
@@ -249,7 +249,8 @@
 
         my $response = $transport->transmit_request($request);
         $response ||= $transport->receive_response;
-        $dbh->{go_response} = $response;
+        $dbh->{go_response} = $response
+            or die "No response object returned by $transport";
 
         if (my $dbh_attributes = $response->dbh_attributes) {
 
@@ -462,7 +463,8 @@
 
         my $response = $transport->transmit_request($request);
         $response ||= $transport->receive_response;
-        $sth->{go_response} = $response;
+        $sth->{go_response} = $response
+            or die "No response object returned by $transport";
         $dbh->{go_response} = $response; # mainly for last_insert_id
 
         delete $sth->{go_method_calls};
@@ -500,7 +502,8 @@
     sub bind_param {
         my ($sth, $param, $value, $attr) = @_;
         $sth->{ParamValues}{$param} = $value;
-        $sth->{ParamAttr}{$param} = $attr;
+        $sth->{ParamAttr}{$param}   = $attr
+            if defined $attr; # attr is sticky if not explicitly set
         return 1;
     }
 
@@ -515,11 +518,17 @@
 
 
     sub more_results {
-       my ($sth) = @_;
+       my $sth = shift;
 
-       $sth->finish if $sth->FETCH('Active');
+       $sth->finish;
+
+       my $response = $sth->{go_response} or do {
+            # e.g., we haven't sent a request yet (ie prepare then 
more_results)
+            $sth->trace_msg("    No response object present", 3);
+            return;
+        };
 
-       my $resultset_list = $sth->{go_response}->sth_resultsets
+       my $resultset_list = $response->sth_resultsets
             or return $sth->set_err(1, "No sth_resultsets");
 
         my $meta = shift @$resultset_list
@@ -565,12 +574,16 @@
 
     sub fetchall_arrayref {
         my ($sth, $slice, $max_rows) = @_;
+       my $resultset = $sth->{go_current_rowset} || do {
+            # should only happen if fetch called after execute failed
+            my $rowset_err = $sth->{go_current_rowset_err}
+                || [ 1, 'no result set (did execute fail)' ];
+            return $sth->set_err( @$rowset_err );
+        };
         my $mode = ref($slice) || 'ARRAY';
         return $sth->SUPER::fetchall_arrayref($slice, $max_rows)
             if ref($slice) or defined $max_rows;
-       my $resultset = $sth->{go_current_rowset}
-            or return $sth->set_err( @{ $sth->{go_current_rowset_err} } );
-       $sth->finish;     # no more data so finish
+       $sth->finish;     # no more data after this so finish
         return $resultset;
     }
 
@@ -700,7 +713,8 @@
 
 =head1 CONSTRAINTS
 
-There are naturally some constraints imposed by DBD::Gofer. But not many:
+There are some natural constraints imposed by the DBD::Gofer 'stateless' 
approach.
+But not too many:
 
 =head2 You can't change database handle attributes after connect()
 
@@ -714,40 +728,38 @@
 
 You can't change statment handle attributes after prepare.
 
-=head2 You can't use transactions.
+=head2 You can't use transactions
 
 AutoCommit only. Transactions aren't supported.
 
-=head2 You can't use temporary tables or other per-connection persistent state
-
-Because the transport or server-side may execute your request via a different
-database connection, you can't rely on any per-connection persistent state,
-such as temporary tables, being available from one request to the next.
-
-=head2 You need to use func() to call driver-private dbh methods
-
-So instead of the new-style:
-
-    $dbh->foo_method_name(...)
-
-you need to use the old-style:
-
-    $dbh->func(..., 'foo_method_name');
-
-This constraint might be removed in future.
-
 =head2 You can't call driver-private sth methods
 
 But that's rarely needed anyway.
 
-=head2 Array Methods are not supported
+=head2 Per-row driver-private sth attributes aren't supported
+
+Some drivers provide sth attributes that relate to the row that was just
+fetched (e.g., Sybase and syb_result_type). These aren't supported.
+
+=head2 Array Methods are currently 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:
+A few important things to keep in mind when using DBD::Gofer:
+
+=head2 You shouldn't use temporary tables, locks, or other per-connection 
persistent state
+
+Because the server-side may execute your requests via a different
+database connections, you can't rely on any per-connection persistent state,
+such as temporary tables, being available from one request to the next.
+
+This is an easy trap to fall into and a difficult one to debug.
+The pipeone transport may help as it forces a new connection for each request.
+(It is very slow though, so I plan to add a way for the stream driver to use
+connect instead of connect cache to achive the same effect much more 
efficiently.)
 
 =head2 Driver-private Database Handle Attributes
 
@@ -763,7 +775,8 @@
 
 =head2 Multiple Resultsets
 
-Multiple resultsets are supported if the driver supports the more_results() 
method.
+Multiple resultsets are supported only if the driver supports the 
more_results() method
+(an exception is made for DBD::Sybase).
 
 =head2 Use of last_insert_id requires a minor code change
 
@@ -780,21 +793,20 @@
 The array reference should contains the args that you want passed to the
 last_insert_id() method.
 
-XXX needs testing
-
 XXX allow $dbh->{go_last_insert_id_args} = [] to enable it by default?
 
 =head2 Statement activity that also updates dbh attributes
 
 Some drivers may update one or more dbh attributes after performing activity on
 a child sth.  For example, DBD::mysql provides $dbh->{mysql_insertid} in 
addition to
-$sth->{mysql_insertid}. Currently this isn't supported, but probably needs to 
be.
+$sth->{mysql_insertid}. Currently mysql_insertid is supported via a hack but a
+more general mechanism is needed for other drivers to use.
 
 =head2 Methods that report an error always return undef
 
 With DBD::Gofer a method that sets an error always return an undef or empty 
list.
 That shouldn't be a problem in practice because the DBI doesn't define any
-methods that do return meaningful values while also reporting an error.
+methods that return meaningful values while also reporting an error.
 
 =head1 TRANSPORTS
 
@@ -931,9 +943,15 @@
 
 L<DBI>
 
+=head1 Caveats for specific drivers
+
+This section aims to record issues to be aware of when using Gofer with 
specific drivers.
+It usually only documents issues that are not natural consequences of the 
limitations
+of the Gofer approach - as documented avove.
+
 =head1 TODO
 
-Random brain dump...
+This is just a random brain dump...
 
 Document policy mechanism
 
@@ -941,16 +959,10 @@
 
 Driver-private sth attributes - set via prepare() - change DBI spec
 
-Timeout for stream and http drivers.
-
 Caching of get_info values
 
 prepare vs prepare_cached
 
-Driver-private sth methods via func? Can't be sure of state?
-
-track installed_methods and install proxies on client side after connect?
-
 add hooks into transport base class for checking & updating a result set cache
    ie via a standard cache interface such as:
    http://search.cpan.org/~robm/Cache-FastMmap/FastMmap.pm
@@ -964,7 +976,7 @@
 
 Neat way for $h->trace to enable transport tracing.
 
-Rework handling of installed_methods.
+Rework handling of installed_methods to not piggback on dbh_attributes?
 
 Perhaps support transactions for transports where it's possible (ie null and 
stream)?
 Would make stream transport (ie ssh) more useful to more people.

Modified: dbi/trunk/lib/DBD/Gofer/Transport/http.pm
==============================================================================
--- dbi/trunk/lib/DBD/Gofer/Transport/http.pm   (original)
+++ dbi/trunk/lib/DBD/Gofer/Transport/http.pm   Thu Mar  1 08:25:21 2007
@@ -58,7 +58,7 @@
         $self->connection_info( $res );
     };
     return DBI::Gofer::Response->new({ err => 1, errstr => $@ }) if $@;
-    return 1;
+    return undef;
 }
 
 

Modified: dbi/trunk/lib/DBD/NullP.pm
==============================================================================
--- dbi/trunk/lib/DBD/NullP.pm  (original)
+++ dbi/trunk/lib/DBD/NullP.pm  Thu Mar  1 08:25:21 2007
@@ -56,13 +56,13 @@
     use Carp qw(croak);
 
     sub prepare {
-       my($dbh, $statement)= @_;
+       my ($dbh, $statement)= @_;
 
-       my($outer, $sth) = DBI::_new_sth($dbh, {
+       my ($outer, $sth) = DBI::_new_sth($dbh, {
            'Statement'     => $statement,
-           }, [ qw'example implementors private data']);
+        });
 
-       $outer;
+       return $outer;
     }
 
     sub FETCH {
@@ -73,7 +73,7 @@
        return 1 if $attrib eq 'AutoCommit';
        # else pass up to DBI to handle
        return $dbh->SUPER::FETCH($attrib);
-       }
+    }
 
     sub STORE {
        my ($dbh, $attrib, $value) = @_;
@@ -99,23 +99,38 @@
     $imp_data_size = 0;
     use strict;
 
+    sub bind_param {
+        my ($sth, $param, $value, $attr) = @_;
+        $sth->{ParamValues}{$param} = $value;
+        $sth->{ParamAttr}{$param}   = $attr
+            if defined $attr; # attr is sticky if not explicitly set
+        return 1;
+    }       
+
     sub execute {
-       my($sth, $data) = @_;
-       $sth->{dbd_nullp_data} = $data if $data;
-       $sth->{NAME} = [ "fieldname" ];
+       my $sth = shift;
+        $sth->bind_param($_, $_[$_-1]) for ([EMAIL PROTECTED]);
+        if ($sth->{Statement} =~ m/^ \s* SELECT \s+/xmsi) {
+            $sth->STORE(NUM_OF_FIELDS => 1); 
+            $sth->{NAME} = [ "fieldname" ];
+            # just for the sake of returning something, we return the params
+            my $params = $sth->{ParamValues} || {};
+            $sth->{dbd_nullp_data} = [ @{$params}{ sort keys %$params } ];
+            $sth->STORE(Active => 1); 
+        }
        1;
     }
 
-    sub fetch {
-       my($sth) = @_;
+    sub fetchrow_arrayref {
+       my $sth = shift;
        my $data = $sth->{dbd_nullp_data};
-        if ($data) {
-           $sth->{dbd_nullp_data} = undef;
-           return [ $data ];
+        if (!$data || [EMAIL PROTECTED]) {
+            $sth->finish;     # no more data so finish
+            return undef;
        }
-       $sth->finish;     # no more data so finish
-       return undef;
+        return $sth->_set_fbav(shift @$data);
     }
+    *fetch = \&fetchrow_arrayref; # alias
 
     sub FETCH {
        my ($sth, $attrib) = @_;

Modified: dbi/trunk/lib/DBI/Gofer/Execute.pm
==============================================================================
--- dbi/trunk/lib/DBI/Gofer/Execute.pm  (original)
+++ dbi/trunk/lib/DBI/Gofer/Execute.pm  Thu Mar  1 08:25:21 2007
@@ -268,8 +268,8 @@
     if ($rv_ref and UNIVERSAL::isa($rv_ref->[0],'DBI::st')) {
         # dbh_method_call was probably a metadata method like table_info
         # that returns a statement handle, so turn the $sth into resultset
-        my $rv = $rv_ref->[0];
-        $response->sth_resultsets( $self->gather_sth_resultsets($rv, $request) 
);
+        my $sth = $rv_ref->[0];
+        $response->sth_resultsets( $self->gather_sth_resultsets($sth, 
$request, $response) );
         $response->rv("(sth)"); # don't try to return actual sth
     }
 
@@ -340,7 +340,7 @@
     # (XXX would be nice to be able to support streaming of results.
     # which would reduce memory usage and latency for large results)
     if ($sth) {
-        $response->sth_resultsets( $self->gather_sth_resultsets($sth, 
$request) );
+        $response->sth_resultsets( $self->gather_sth_resultsets($sth, 
$request, $response) );
         $sth->finish;
     }
 
@@ -357,8 +357,8 @@
 
 
 sub gather_sth_resultsets {
-    my ($self, $sth, $request) = @_;
-    return eval {
+    my ($self, $sth, $request, $response) = @_;
+    my $resultsets = eval {
         my $driver_name = $sth->{Database}{Driver}{Name};
         my $extra_sth_attr = $extra_attr{$driver_name}{sth} || [];
 
@@ -380,6 +380,8 @@
 
         $rs_list;
     };
+    $response->add_err(1, $@) if $@;
+    return $resultsets;
 }
 
 

Modified: dbi/trunk/t/85gofer.t
==============================================================================
--- dbi/trunk/t/85gofer.t       (original)
+++ dbi/trunk/t/85gofer.t       Thu Mar  1 08:25:21 2007
@@ -117,7 +117,7 @@
     $dsn = $remote_dsn if $transport eq 'no';
     print " $dsn\n";
 
-    my $dbh = DBI->connect($dsn, undef, undef, { } );
+    my $dbh = DBI->connect($dsn, undef, undef, { RaiseError => 1, PrintError 
=> 0 } );
     ok $dbh, sprintf "should connect to %s (%s)", $dsn, $DBI::errstr||'';
     die "$test_run_tag aborted\n" unless $dbh;
 
@@ -130,7 +130,7 @@
     die "$test_run_tag aborted\n" if $DBI::err;
 
     my $sth = do {
-        local $dbh->{PrintError} = 0;
+        local $dbh->{RaiseError} = 0;
         $dbh->prepare("complete non-sql gibberish");
     };
     ($policy->skip_prepare_check)

Reply via email to