Author: timbo
Date: Fri Feb 16 07:15:34 2007
New Revision: 9116

Added:
   dbi/trunk/lib/DBD/Gofer/Policy/rush.pm
      - copied, changed from r9112, /dbi/trunk/lib/DBD/Gofer/Policy/classic.pm
Modified:
   dbi/trunk/Changes
   dbi/trunk/DBI.pm
   dbi/trunk/MANIFEST
   dbi/trunk/lib/DBD/Gofer.pm
   dbi/trunk/lib/DBD/Gofer/Policy/Base.pm
   dbi/trunk/lib/DBI/Gofer/Execute.pm
   dbi/trunk/lib/DBI/Gofer/Request.pm
   dbi/trunk/lib/DBI/Gofer/Response.pm
   dbi/trunk/t/85gofer.t

Log:
Added mechanism to fetch back all dbh attributes on first (or every or never) 
request.
Added 'rush' policy.
Rewrote t/85gofer.t to cover more transports and policies
also added relative performance benchmarking.
(Currently null transport adds 1ms and http transport adds 5ms)
Added $h->private_attribute_info method to dbi so drivers can report what 
private attributes they use.


Modified: dbi/trunk/Changes
==============================================================================
--- dbi/trunk/Changes   (original)
+++ dbi/trunk/Changes   Fri Feb 16 07:15:34 2007
@@ -6,10 +6,9 @@
 
 =cut
 
-Implement more policies
-Add $dbh->private_attribute_info method
 Add attr-passthru to prepare()?
 Test policies.
+Guard against version skew.
 
 
 =head2 Changes in DBI 1.54 (svn rev 8791),  2nd February 2007
@@ -52,6 +51,7 @@
   Added ability for drivers to implement func() method
     so proxy drivers can proxy the func method itself.
   Added SQL_BIGINT type code (resolved to the ODBC/JDBC value (-5))
+  Added $h->private_attribute_info method.
 
 =head2 Changes in DBI 1.53 (svn rev 7995),   31st October 2006
 

Modified: dbi/trunk/DBI.pm
==============================================================================
--- dbi/trunk/DBI.pm    (original)
+++ dbi/trunk/DBI.pm    Fri Feb 16 07:15:34 2007
@@ -389,6 +389,7 @@
        trace           => { U =>[1,3,'[$trace_level, [$filename]]'],   
O=>0x0004 },
        trace_msg       => { U =>[2,3,'$message_text [, $min_level ]' ],        
O=>0x0004, T=>8 },
        swap_inner_handle => { U =>[2,3,'$h [, $allow_reparent ]'] },
+        private_attribute_info => { },
     },
     dr => {            # Database Driver Interface
        'connect'  =>   { U =>[1,5,'[$db [,$user [,$passwd [,\%attr]]]]'], 
H=>3, O=>0x8000 },
@@ -1395,6 +1396,10 @@
        return;
     }
 
+    sub private_attribute_info {
+        return undef;
+    }
+
 }
 
 
@@ -3250,6 +3255,14 @@
 
 The parse_trace_flag() method was added in DBI 1.42.
 
+=item C<private_attribute_info>
+
+  $array_ref = $h->private_attribute_info();
+
+Returns a reference to an array containing the names of driver-private
+attributes available for that kind of handle (driver, database, statement),
+or else undef.
+
 =item C<swap_inner_handle>
 
   $rc = $h1->swap_inner_handle( $h2 );

Modified: dbi/trunk/MANIFEST
==============================================================================
--- dbi/trunk/MANIFEST  (original)
+++ dbi/trunk/MANIFEST  Fri Feb 16 07:15:34 2007
@@ -26,8 +26,9 @@
 lib/DBD/File.pm                        A driver base class for simple drivers
 lib/DBD/Gofer.pm                DBD::Gofer 'stateless proxy' driver
 lib/DBD/Gofer/Policy/Base.pm
-lib/DBD/Gofer/Policy/pedantic.pm
-lib/DBD/Gofer/Policy/classic.pm
+lib/DBD/Gofer/Policy/pedantic.pm    Safest and most transparent, but also 
slowest
+lib/DBD/Gofer/Policy/classic.pm     Reasonable policy for typical usage
+lib/DBD/Gofer/Policy/rush.pm        Raw speed, fewest round trips, least 
transparent
 lib/DBD/Gofer/Transport/Base.pm Base class for DBD::Gofer driver transport 
classes
 lib/DBD/Gofer/Transport/http.pm
 lib/DBD/Gofer/Transport/null.pm DBD::Gofer transport that executes in same 
process (for testing)

Modified: dbi/trunk/lib/DBD/Gofer.pm
==============================================================================
--- dbi/trunk/lib/DBD/Gofer.pm  (original)
+++ dbi/trunk/lib/DBD/Gofer.pm  Fri Feb 16 07:15:34 2007
@@ -103,7 +103,7 @@
         my $orig_dsn = $dsn;
 
         # first remove dsn= and everything after it
-        my $remote_dsn = ($dsn =~ s/\bdsn=(.*)$// && $1)
+        my $remote_dsn = ($dsn =~ s/;?\bdsn=(.*)$// && $1)
             or return $drh->set_err(1, "No dsn= argument in '$orig_dsn'");
 
         if ($attr->{go_bypass}) { # don't use DBD::Gofer for this connection
@@ -220,6 +220,13 @@
 
         my $request = $dbh->{go_request};
         $request->init_request([EMAIL PROTECTED], wantarray);
+        ++$dbh->{go_request_count};
+
+        my $go_policy = $dbh->{go_policy};
+        my $dbh_attribute_update = $go_policy->dbh_attribute_update();
+        $request->dbh_attributes( $go_policy->dbh_attribute_list() )
+            if $dbh_attribute_update eq 'every'
+            or $dbh_attribute_update eq 'first' && $dbh->{go_request_count}==1;
 
         my $transport = $dbh->{go_trans}
             or return $dbh->set_err(1, "Not connected (no transport)");
@@ -230,10 +237,15 @@
             or return $dbh->set_err(1, "transmit_request failed: $@");
 
         my $response = $transport->receive_response;
-        my $rv = $response->rv;
-
         $dbh->{go_response} = $response;
 
+        if (my $dbh_attributes = $response->dbh_attributes) {
+            # XXX we don't STORE here, we just stuff the value into the 
attribute cache
+            $dbh->{$_} = $dbh_attributes->{$_}
+                for keys %$dbh_attributes;
+        }
+
+        my $rv = $response->rv;
         if (my $resultset_list = $response->sth_resultsets) {
             # dbh method call returned one or more resultsets
             # (was probably a metadata method like table_info)
@@ -393,15 +405,24 @@
             }
         }
 
+        my $dbh = $sth->{Database} or die 42; # XXX
+        ++$dbh->{go_request_count};
+
         my $request = $sth->{go_request};
         $request->init_request($sth->{go_prepare_call}, undef);
         $request->sth_method_calls($sth->{go_method_calls})
             if $sth->{go_method_calls};
-        $request->sth_result_attr({}); # (currently) indicates this is an sth 
request
+        $request->sth_result_attr({}); # (currently) also indicates this is an 
sth request
 
         $request->last_insert_id_args($sth->{go_last_insert_id_args})
             if $sth->{go_last_insert_id_args};
 
+        my $go_policy = $sth->{go_policy};
+        my $dbh_attribute_update = $go_policy->dbh_attribute_update();
+        $request->dbh_attributes( $go_policy->dbh_attribute_list() )
+            if $dbh_attribute_update eq 'every'
+            or $dbh_attribute_update eq 'first' && $dbh->{go_request_count}==1;
+
         my $transport = $sth->{go_trans}
             or return $sth->set_err(1, "Not connected (no transport)");
         my $TraceLevel = $sth->FETCH('TraceLevel');
@@ -409,12 +430,20 @@
         eval { $transport->transmit_request($request) }
             or return $sth->set_err(1, "transmit_request failed: $@");
 
+        delete $sth->{go_method_calls};
+
         my $response = $transport->receive_response;
         $sth->{go_response} = $response;
-        delete $sth->{go_method_calls};
 
+        if (my $dbh_attributes = $response->dbh_attributes) {
+            # XXX we don't STORE here, we just stuff the value into the 
attribute cache
+            $dbh->{$_} = $dbh_attributes->{$_}
+                for keys %$dbh_attributes;
+        }
+
+        my $rv = $response->rv;
         if ($response->sth_resultsets) {
-            # setup first resultset - including atributes
+            # setup first resultset - including sth attributes
             $sth->more_results;
         }
         else {

Modified: dbi/trunk/lib/DBD/Gofer/Policy/Base.pm
==============================================================================
--- dbi/trunk/lib/DBD/Gofer/Policy/Base.pm      (original)
+++ dbi/trunk/lib/DBD/Gofer/Policy/Base.pm      Fri Feb 16 07:15:34 2007
@@ -18,6 +18,8 @@
     skip_connect_check => 0,
     skip_prepare_check => 0,
     skip_ping => 0,
+    dbh_attribute_update => 'every',
+    dbh_attribute_list => ['*'],
 );
 
 my $base_policy_file = $INC{"DBD/Gofer/Policy/Base.pm"};

Copied: dbi/trunk/lib/DBD/Gofer/Policy/rush.pm (from r9112, 
/dbi/trunk/lib/DBD/Gofer/Policy/classic.pm)
==============================================================================
--- /dbi/trunk/lib/DBD/Gofer/Policy/classic.pm  (original)
+++ dbi/trunk/lib/DBD/Gofer/Policy/rush.pm      Fri Feb 16 07:15:34 2007
@@ -1,4 +1,4 @@
-package DBD::Gofer::Policy::classic;
+package DBD::Gofer::Policy::rush;
 
 #   $Id$
 #
@@ -19,7 +19,7 @@
     # don't skip the connect check since that also sets dbh attributes
     # although this makes connect more expensive, that's partly offset
     # by skip_ping=>1 below, which makes connect_cached very fast.
-    skip_connect_check => 0,
+    skip_connect_check => 1,
 
     # most code doesn't rely on sth attributes being set after prepare
     skip_prepare_check => 1,
@@ -27,6 +27,10 @@
     # ping is almost meaningless for DBD::Gofer and most transports anyway
     skip_ping => 1,
 
+    # don't update dbh attributes at all
+    dbh_attribute_update => 'none',
+    dbh_attribute_list => undef,
+
 });
 
 

Modified: dbi/trunk/lib/DBI/Gofer/Execute.pm
==============================================================================
--- dbi/trunk/lib/DBI/Gofer/Execute.pm  (original)
+++ dbi/trunk/lib/DBI/Gofer/Execute.pm  Fri Feb 16 07:15:34 2007
@@ -208,6 +208,7 @@
 
 sub execute_dbh_request {
     my ($self, $request) = @_;
+
     my $dbh;
     my $rv_ref = eval {
         $dbh = $self->_connect($request);
@@ -219,13 +220,31 @@
         [EMAIL PROTECTED];
     };
     my $response = $self->new_response_with_err($rv_ref, $@);
-    if ($dbh) {
-        if ($rv_ref and my $lid_args = $request->dbh_last_insert_id_args) {
-            my $id = $dbh->last_insert_id( @$lid_args );
-            $response->last_insert_id( $id );
+
+    return $response if not $dbh;
+
+    # does this request also want any dbh attributes returned?
+    if (my $dbh_attributes = $request->dbh_attributes) {
+        my @req_attr_names = @$dbh_attributes;
+        if ($req_attr_names[0] eq '*') { # auto include std + private
+            shift @req_attr_names;
+            # add ChopBlanks LongReadLen LongTruncOk because drivers may have 
different defaults
+            # plus Name so the client gets the real Name of the connection
+            push @req_attr_names, qw(ChopBlanks LongReadLen LongTruncOk Name);
+            my $pai = $dbh->private_attribute_info
+                   || $extra_attr{ $dbh->{Driver}{Name} }{dbh} || [];
+            push @req_attr_names, @$pai;
         }
-        $self->reset_dbh($dbh);
+        my %dbh_attr_values;
+        $dbh_attr_values{$_} = $dbh->FETCH($_) for @req_attr_names;
+        $response->dbh_attributes(\%dbh_attr_values);
+    }
+
+    if ($rv_ref and my $lid_args = $request->dbh_last_insert_id_args) {
+        my $id = $dbh->last_insert_id( @$lid_args );
+        $response->last_insert_id( $id );
     }
+
     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
@@ -233,6 +252,10 @@
         $response->sth_resultsets( $self->gather_sth_resultsets($rv, $request) 
);
         $response->rv("(sth)"); # don't try to return actual sth
     }
+
+    # we're finished with this dbh for this request
+    $self->reset_dbh($dbh);
+
     return $response;
 }
 

Modified: dbi/trunk/lib/DBI/Gofer/Request.pm
==============================================================================
--- dbi/trunk/lib/DBI/Gofer/Request.pm  (original)
+++ dbi/trunk/lib/DBI/Gofer/Request.pm  Fri Feb 16 07:15:34 2007
@@ -16,6 +16,7 @@
     connect_args
     dbh_method_call
     dbh_wantarray
+    dbh_attributes
     dbh_last_insert_id_args
     sth_method_calls
     sth_result_attr

Modified: dbi/trunk/lib/DBI/Gofer/Response.pm
==============================================================================
--- dbi/trunk/lib/DBI/Gofer/Response.pm (original)
+++ dbi/trunk/lib/DBI/Gofer/Response.pm Fri Feb 16 07:15:34 2007
@@ -17,6 +17,7 @@
     errstr
     state
     last_insert_id
+    dbh_attributes
     sth_resultsets
     warnings
 ));
@@ -24,21 +25,22 @@
 
 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.
 
+    chomp $errstr if $errstr;
+    $state ||= '';
+    warn "add_err($err, $errstr, $state)" if $trace and $errstr || $err;
+
     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;
+                if $r_err && $err && $r_err ne $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";
+                if $r_state and $r_state ne "S1000" && $state && $r_state ne 
$state;
+        $r_errstr .= "\n$errstr" if $r_errstr ne $errstr;
     }   
     else { 
         $r_errstr = $errstr;

Modified: dbi/trunk/t/85gofer.t
==============================================================================
--- dbi/trunk/t/85gofer.t       (original)
+++ dbi/trunk/t/85gofer.t       Fri Feb 16 07:15:34 2007
@@ -5,67 +5,149 @@
 use strict;
 use warnings;
 
+use Time::HiRes qw(time);
+use Data::Dumper;
 use Test::More 'no_plan';
 
 use DBI;
 
+# 0=SQL::Statement if avail, 1=DBI::SQL::Nano
+# next line forces use of Nano rather than default behaviour
+$ENV{DBI_SQL_NANO}=1;
+
+my $perf_count = (@ARGV && $ARGV[0] =~ s/^-c=//) ? shift : (-t STDOUT) ? 100 : 
0;
+my %durations;
+
 # so users can try others from the command line
 my $dbm = $ARGV[0] || "SDBM_File";
+my $remote_driver_dsn = "dbm_type=$dbm;lockfile=0";
+my $remote_dsn = "dbi:DBM:$remote_driver_dsn";
 
 # use DBD::Gofer directly.
 # when combined with DBI_AUTOPROXY this means we have DBD::Gofer => DBD::Gofer 
=> DBD::DBM!
-#
-my $dsn = "dbi:Gofer:transport=null;dsn=dbi:DBM:dbm_type=$dbm;lockfile=0";
-my $dbh = DBI->connect($dsn);
-ok $dbh, 'should connect';
-
-
-    # 0=SQL::Statement if avail, 1=DBI::SQL::Nano
-    # next line forces use of Nano rather than default behaviour
-    $ENV{DBI_SQL_NANO}=1;
-
-#my $dir = './test_output';
-#rmtree $dir;
-#mkpath $dir;
-
-my @sql = split /\s*;\n/, join '',<DATA>;
-
-for my $sql ( @sql ) {
-    $sql =~ s/;$//;  # in case no final \n on last line of __DATA__
-    my $null = '';
-    my $expected_results = {
-        1 => 'oranges',
-        2 => 'apples',
-        3 => $null,
-    };
-    if ($sql !~ /SELECT/) {
-        print " do $sql\n";
-        $dbh->do($sql) or die $dbh->errstr;
-        next;
+
+my $username = getpwuid($>);
+my %transports = (
+    null => {},
+    pipeone => {},
+#   stream => { url => "ssh:[EMAIL PROTECTED]" },
+    http => { url => "http://localhost:8001/gofer"; },
+);
+# delete stream test for everyone else because it's to dependent
+# on local configuration issues unrelated to the DBI
+delete $transports{stream} unless $username eq 'timbo' && -d '.svn';
+delete $transports{http} unless $username eq 'timbo' && -d '.svn';
+
+for my $transport (keys %transports) {
+    my $trans_attr = $transports{$transport};
+
+    for my $policy_name (qw(pedantic classic rush)) {
+
+        eval { run_tests($transport, $trans_attr, $policy_name) };
+        ($@) ? fail($@) : pass();
+
+    }
+}
+
+# to get baseline for comparisons if doing performance testing
+run_tests('no', {}, 'pedantic') if $perf_count;
+
+while ( my ($activity, $stats_hash) = each %durations ) {
+    print "\n";
+    $stats_hash->{baseline} = delete $stats_hash->{"no+pedantic"};
+    for my $perf_tag (sort keys %$stats_hash) {
+        my $dur = $stats_hash->{$perf_tag};
+        printf "  %6s %-13s: %.6fsec (%5d/sec)",
+            $activity, $perf_tag, $dur/$perf_count, $perf_count/$dur;
+        my $baseline_dur = $stats_hash->{baseline};
+        printf " %+dms", (($dur-$baseline_dur)/$perf_count)*1000
+            unless $perf_tag eq 'baseline';
+        print "\n";
     }
-    print " run $sql\n";
-    my $sth = $dbh->prepare($sql) or die $dbh->errstr;
-    $sth->execute;
-    die $sth->errstr if $sth->err and $sql !~ /DROP/;
-    # Note that we can't rely on the order here, it's not portable,
-    # different DBMs (or versions) will return different orders.
-    while (my ($key, $value) = $sth->fetchrow_array) {
-        ok exists $expected_results->{$key};
-        is $value, $expected_results->{$key};
+}
+
+
+sub run_tests {
+    my ($transport, $trans_attr, $policy_name) = @_;
+
+    my $policy = get_policy($policy_name);
+
+    my $test_run_tag = "Testing $transport transport with $policy_name policy";
+    print "\n$test_run_tag\n";
+
+    my $driver_dsn = "transport=$transport;policy=$policy_name";
+    $driver_dsn .= join ";", '', map { "$_=$trans_attr->{$_}" } keys 
%$trans_attr
+        if %$trans_attr;
+
+    my $dsn = "dbi:Gofer:$driver_dsn;dsn=$remote_dsn";
+    $dsn = $remote_dsn if $transport eq 'no';
+    print " $dsn\n";
+
+    my $dbh = DBI->connect($dsn, undef, undef, { HandleError => sub { print 
$_[0]; 1 } } );
+    ok $dbh, 'should connect';
+    die "$test_run_tag aborted\n" unless $dbh;
+
+    is $dbh->{Name}, ($policy->skip_connect_check or 
$policy->dbh_attribute_update eq 'none')
+        ? $driver_dsn
+        : $remote_driver_dsn;
+
+    ok $dbh->do("DROP TABLE IF EXISTS fruit");
+    ok $dbh->do("CREATE TABLE fruit (dKey INT, dVal VARCHAR(10))");
+    die "$test_run_tag aborted\n" if $DBI::err;
+
+    my $sth;
+    $sth = $dbh->prepare("complete non-sql gibberish");
+    ($policy->skip_prepare_check)
+        ? isa_ok $sth, 'DBI::st'
+        : is $sth, undef, 'should detect prepare failure';
+
+    ok my $ins_sth = $dbh->prepare("INSERT INTO fruit VALUES (?,?)");
+    ok $ins_sth->execute(1, 'oranges');
+    ok $ins_sth->execute(2, 'oranges');
+
+    my $rowset;
+    ok $rowset = $dbh->selectall_arrayref("SELECT dKey, dVal FROM fruit");
+    is_deeply($rowset, [ [ '1', 'oranges' ], [ '2', 'oranges' ] ]);
+
+    ok $dbh->do("UPDATE fruit SET dVal='apples' WHERE dVal='oranges'");
+
+    ok $sth = $dbh->prepare("SELECT dKey, dVal FROM fruit");
+    ok $sth->execute;
+    ok $rowset = $sth->fetchall_hashref('dKey');
+    is_deeply($rowset, { '1' => { dKey=>1, dVal=>'apples' }, 2 => { dKey=>2, 
dVal=>'apples' } });
+
+    if ($perf_count and $transport ne 'pipeone') {
+        my $start = time();
+        $dbh->selectall_arrayref("SELECT dKey, dVal FROM fruit")
+            for (1000..1000+$perf_count);
+        $durations{select}{"$transport+$policy_name"} = time() - $start;
+
+        # some rows in to get a (*very* rough) idea of overheads
+        $start = time();
+        $ins_sth->execute($_, 'speed')
+            for (1000..1000+$perf_count);
+        $durations{insert}{"$transport+$policy_name"} = time() - $start;
     }
-    is $DBI::rows, keys %$expected_results;
+
+    ok $dbh->do("DROP TABLE fruit");
+    ok $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();
 }
-$dbh->disconnect;
+
+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;
-__DATA__
-DROP TABLE IF EXISTS fruit;
-CREATE TABLE fruit (dKey INT, dVal VARCHAR(10));
-INSERT INTO  fruit VALUES (1,'oranges'   );
-INSERT INTO  fruit VALUES (2,'to_change' );
-INSERT INTO  fruit VALUES (3, NULL       );
-INSERT INTO  fruit VALUES (4,'to delete' );
-UPDATE fruit SET dVal='apples' WHERE dKey=2;
-DELETE FROM  fruit WHERE dVal='to delete';
-SELECT * FROM fruit;
-DROP TABLE fruit;

Reply via email to