Author: timbo
Date: Thu Feb 15 08:08:20 2007
New Revision: 9112

Added:
   dbi/trunk/lib/DBD/Gofer/Policy/
   dbi/trunk/lib/DBD/Gofer/Policy/Base.pm   (contents, props changed)
   dbi/trunk/lib/DBD/Gofer/Policy/classic.pm   (contents, props changed)
   dbi/trunk/lib/DBD/Gofer/Policy/pedantic.pm   (contents, props changed)
Modified:
   dbi/trunk/Changes
   dbi/trunk/MANIFEST
   dbi/trunk/lib/DBD/Gofer.pm
   dbi/trunk/lib/DBI/Gofer/Execute.pm
   dbi/trunk/lib/DBI/Gofer/Transport/Base.pm
   dbi/trunk/t/08keeperr.t

Log:
Implement 'policy' config mechanism for DBD::Gofer
Fixup last_insert_id handling
(both need testing)


Modified: dbi/trunk/Changes
==============================================================================
--- dbi/trunk/Changes   (original)
+++ dbi/trunk/Changes   Thu Feb 15 08:08:20 2007
@@ -6,9 +6,10 @@
 
 =cut
 
-Implement policies
-Add attr-passthru to prepare()
+Implement more policies
 Add $dbh->private_attribute_info method
+Add attr-passthru to prepare()?
+Test policies.
 
 
 =head2 Changes in DBI 1.54 (svn rev 8791),  2nd February 2007
@@ -18,7 +19,7 @@
 
   WARNING: This version has some subtle changes in DBI internals.
   It's possible, though doubtful, that some may affect your code.
-  I recommend some extra texting before using this release.
+  I recommend some extra testing before using this release.
   Or perhaps I'm just being over cautious...
 
   Fixed type_info when called for multiple dbh thanks to Cosimo Streppone.

Modified: dbi/trunk/MANIFEST
==============================================================================
--- dbi/trunk/MANIFEST  (original)
+++ dbi/trunk/MANIFEST  Thu Feb 15 08:08:20 2007
@@ -25,6 +25,9 @@
 lib/DBD/ExampleP.pm            A very simple example Driver module
 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/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  Thu Feb 15 08:08:20 2007
@@ -92,6 +92,7 @@
         go_dsn => undef,
         go_url => undef,
         go_transport => undef,
+        go_policy => undef,
     );
 
     $imp_data_size = 0;
@@ -125,6 +126,20 @@
             return $drh->set_err(1, "Unknown attributes: @{[ keys %dsn_attr 
]}");
         }
 
+        if (not ref $dsn_attr{go_policy}) { # if not a policy object already
+            my $policy_class = $dsn_attr{go_policy} || 'pedantic';
+            $policy_class = "DBD::Gofer::Policy::$policy_class"
+                unless $policy_class =~ /::/;
+            _load_class($policy_class)
+                or return $drh->set_err(1, "Error loading $policy_class: $@");
+            # replace policy name in %dsn_attr with policy object
+            $dsn_attr{go_policy} = eval { $policy_class->new(\%dsn_attr) }
+                or return $drh->set_err(1, "Error instanciating $policy_class: 
$@");
+        }
+        # policy object is left in $dsn_attr{go_policy} so transport can see it
+        my $go_policy = $dsn_attr{go_policy};
+
+        my $request_class = "DBI::Gofer::Request";
         my $transport_class = delete $dsn_attr{go_transport}
             or return $drh->set_err(1, "No transport= argument in 
'$orig_dsn'");
         $transport_class = "DBD::Gofer::Transport::$transport_class"
@@ -134,7 +149,6 @@
         my $go_trans = eval { $transport_class->new(\%dsn_attr) }
             or return $drh->set_err(1, "Error instanciating $transport_class: 
$@");
 
-        my $request_class = "DBI::Gofer::Request";
         my $go_request = eval {
             my $go_attr = { %$attr };
             # XXX user/pass of fwd server vs db server ? also impact of 
autoproxy
@@ -154,19 +168,21 @@
             'USER' => $user,
             go_trans => $go_trans,
             go_request => $go_request,
-            go_policy => undef, # XXX
+            go_policy => $go_policy,
         });
 
-        $dbh->STORE(Active => 0); # mark as inactive temporarily for STORE
+        # mark as inactive temporarily for STORE. Active not set until 
connected() called.
+        $dbh->STORE(Active => 0);
 
-        # test the connection XXX control via a policy later
-        unless ($dbh->go_dbh_method(undef, 'ping')) {
-            return undef if $dbh->err; # error already recorded, typically
-            return $dbh->set_err(1, "ping failed");
+        # should we ping to check the connection
+        # and fetch dbh attributes
+        my $skip_connect_check = $go_policy->skip_connect_check($attr, $dbh);
+        if (not $skip_connect_check) {
+            if (not $dbh->go_dbh_method(undef, 'ping')) {
+                return undef if $dbh->err; # error already recorded, typically
+                return $dbh->set_err(1, "ping failed");
+            }
         }
-            # unless $policy->skip_connect_ping($attr, $dsn, $user, $auth, 
$attr);
-
-        # Active not set until connected() called.
 
         return $dbh;
     }
@@ -219,12 +235,16 @@
         $dbh->{go_response} = $response;
 
         if (my $resultset_list = $response->sth_resultsets) {
+            # dbh method call returned one or more resultsets
+            # (was probably a metadata method like table_info)
+            #
             # setup an sth but don't execute/forward it
-            my $sth = $dbh->prepare(undef, { go_skip_early_prepare => 1 }); # 
XXX
+            my $sth = $dbh->prepare(undef, { go_skip_prepare_check => 1 });
             # set the sth response to our dbh response
             (tied %$sth)->{go_response} = $response;
-            # setup the set with the results in our response
+            # setup the sth with the results in our response
             $sth->more_results;
+            # and return that new sth as if it came from original request
             $rv = [ $sth ];
         }
 
@@ -268,14 +288,13 @@
         my $dbh = shift;
         return $dbh->set_err(0, "can't ping while not connected") # warning
             unless $dbh->SUPER::FETCH('Active');
-        # XXX local or remote - add policy attribute
-        return $dbh->go_dbh_method(undef, 'ping', @_);
+        my $skip_ping = $dbh->{go_policy}->skip_ping();
+        return ($skip_ping) ? 1 : $dbh->go_dbh_method(undef, 'ping', @_);
     }
 
     sub last_insert_id {
         my $dbh = shift;
         my $response = $dbh->{go_response} or return undef;
-        # will be undef unless last_insert_id was explicitly requested
         return $response->last_insert_id;
     }
 
@@ -339,17 +358,14 @@
        my ($sth, $sth_inner) = DBI::_new_sth($dbh, {
            Statement => $statement,
             go_prepare_call => [ 'prepare', $statement, $attr ],
-            go_method_calls => [],
+            # go_method_calls => [], # autovivs if needed
             go_request => $dbh->{go_request},
             go_trans => $dbh->{go_trans},
             go_policy => $policy,
         });
 
-        #my $skip_early_prepare = $policy->skip_early_prepare($attr, $dbh, 
$statement, $attr, $sth);
-        my $skip_early_prepare = 0;
-
-        $skip_early_prepare = 1 if not defined $statement; # XXX hack, see 
go_dbh_method
-        if (not $skip_early_prepare) {
+        my $skip_prepare_check = $policy->skip_prepare_check($attr, $dbh, 
$statement, $attr, $sth);
+        if (not $skip_prepare_check) {
             $sth->go_sth_method() or return undef;
         }
 
@@ -379,13 +395,17 @@
 
         my $request = $sth->{go_request};
         $request->init_request($sth->{go_prepare_call}, undef);
-        $request->sth_method_calls($sth->{go_method_calls});
-        $request->sth_result_attr({});
+        $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->last_insert_id_args($sth->{go_last_insert_id_args})
+            if $sth->{go_last_insert_id_args};
 
         my $transport = $sth->{go_trans}
             or return $sth->set_err(1, "Not connected (no transport)");
         my $TraceLevel = $sth->FETCH('TraceLevel');
-        $transport->trace( $TraceLevel - 4 ) if $TraceLevel > 4;
+        $transport->trace( (($TraceLevel-4) > 0) ? $TraceLevel-4 : 0 );
         eval { $transport->transmit_request($request) }
             or return $sth->set_err(1, "transmit_request failed: $@");
 
@@ -493,18 +513,26 @@
 
     sub STORE {
        my ($sth, $attrib, $value) = @_;
+
        return $sth->SUPER::STORE($attrib => $value)
-            if $sth_local_store_attrib{$attrib}  # handle locally
-            or $attrib =~ m/^[a-z]/;             # driver-private XXX
+            if $sth_local_store_attrib{$attrib}; # handle locally
 
-        # could perhaps do
-        #   push @{ $sth->{go_method_calls} }, [ 'STORE', $attrib, $value ];
-        # if not $sth->FETCH('Executed')
+        # otherwise warn but do it anyway
+        # this will probably need refining later
+        my $msg = "Altering \$sth->{$attrib} won't affect proxied handle";
+        Carp::carp($msg) if $sth->FETCH('Warn');
+
+        # XXX could perhaps do
+        #   push @{ $sth->{go_method_calls} }, [ 'STORE', $attrib, $value ]
+        #       if not $sth->FETCH('Executed');
         # but how to handle repeat executions? How to we know when an
         # attribute is being set to affect the current resultset or the
-        # next execution? Could just always use go_method_calls I guess.
-        Carp::carp("Can't alter \$sth->{$attrib}") if $sth->FETCH('Warn');
-        return $sth->set_err(1, "Can't alter \$sth->{$attrib}");
+        # next execution?
+        # Could just always use go_method_calls I guess.
+
+       $sth->SUPER::STORE($attrib => $value);
+
+        return $sth->set_err(1, $msg);
     }
 
 }
@@ -660,6 +688,25 @@
 
 Multiple resultsets are supported if the driver supports the more_results() 
method.
 
+=head1 Use of last_insert_id requires a minor code change
+
+To enable use of last_insert_id you need to indicate to DBD::Gofer that you'd
+like to use it.  You do that my adding a C<go_last_insert_id_args> attribute to
+the do() or prepare() method calls. For example:
+
+    $dbh->do($sql, { go_last_insert_id_args => [...] });
+
+or
+
+    $sth = $dbh->prepare($sql, { go_last_insert_id_args => [...] });
+
+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?
+
 =head1 TRANSPORTS
 
 DBD::Gofer doesn't concern itself with transporting requests and responses to 
and fro.
@@ -713,7 +760,8 @@
 
 The http driver uses the http protocol to send Gofer requests and receive 
replies.
 
-XXX not yet implemented
+The DBI::Gofer::Transport::mod_perl module implements the corresponding 
server-side
+transport.
 
 =head1 CONNECTING
 
@@ -721,18 +769,18 @@
 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
+Other attributes can be specified in the DSN to configure DBD::Gofer and/or the
+Gofer transport module being used. The two main attributes after C<transport>,
+are C<url> and C<policy>. These are described below.
 
 =head2 Using DBI_AUTOPROXY
 
 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.
+In this case you don't include the C<dsn=> part. For example:
 
     export DBI_AUTOPROXY=dbi:Gofer:transport=null
 
-or
+or, for a more useful example, try:
 
     export DBI_AUTOPROXY=dbi:Gofer:transport=stream;url=ssh:[EMAIL PROTECTED]
 
@@ -749,11 +797,18 @@
 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 ACKNOWLEDGEMENTS
+
+The development of DBD::Gofer and related modules was sponsored by
+Shopzilla.com (L<http://Shopzilla.com>), where I currently work.
+
 =head1 SEE ALSO
 
-L<DBD::Gofer::Request>, L<DBD::Gofer::Response>, 
L<DBD::Gofer::Transport::Base>,
+L<DBI::Gofer::Request>, L<DBI::Gofer::Response>, L<DBI::Gofer::Execute>.
+
+L<DBI::Gofer::Transport::Base>
 
-L<DBI>, L<DBI::Gofer::Execute>.
+L<DBI>
 
 
 =head1 TODO

Added: dbi/trunk/lib/DBD/Gofer/Policy/Base.pm
==============================================================================
--- (empty file)
+++ dbi/trunk/lib/DBD/Gofer/Policy/Base.pm      Thu Feb 15 08:08:20 2007
@@ -0,0 +1,58 @@
+package DBD::Gofer::Policy::Base;
+
+#   $Id$
+#
+#   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;
+
+our $VERSION = sprintf("0.%06d", q$Revision$ =~ /(\d+)/o);
+our $AUTOLOAD;
+
+my %policy_defaults = (
+    skip_connect_check => 0,
+    skip_prepare_check => 0,
+    skip_ping => 0,
+);
+
+my $base_policy_file = $INC{"DBD/Gofer/Policy/Base.pm"};
+
+__PACKAGE__->create_default_policy_subs(\%policy_defaults);
+
+sub create_default_policy_subs {
+    my ($class, $policy_defaults) = @_;
+
+    while ( my ($policy_name, $policy_default) = each %$policy_defaults) { 
+        my $policy_attr_name = "go_$policy_name";
+        my $sub = sub {
+            # $policy->foo($attr, ...)
+            #carp "$policy_name($_[1],...)";
+            # return the policy default value unless an attribute overrides it
+            return ($_[1] && exists $_[1]->{$policy_attr_name})
+                ? $_[1]->{$policy_attr_name}
+                : $policy_default;
+        };
+        no strict 'refs';
+        *{$class . '::' . $policy_name} = $sub;
+    }
+}
+
+sub AUTOLOAD {
+    carp "Unknown policy name $AUTOLOAD used";
+    return undef;
+}
+
+sub new {
+    my ($class, $args) = @_;
+    my $policy = {};
+    bless $policy, $class;
+}
+
+sub DESTROY { };
+
+1;

Added: dbi/trunk/lib/DBD/Gofer/Policy/classic.pm
==============================================================================
--- (empty file)
+++ dbi/trunk/lib/DBD/Gofer/Policy/classic.pm   Thu Feb 15 08:08:20 2007
@@ -0,0 +1,33 @@
+package DBD::Gofer::Policy::classic;
+
+#   $Id$
+#
+#   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;
+
+our $VERSION = sprintf("0.%06d", q$Revision$ =~ /(\d+)/o);
+
+use base qw(DBD::Gofer::Policy::Base);
+
+__PACKAGE__->create_default_policy_subs({
+
+    # 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,
+
+    # most code doesn't rely on sth attributes being set after prepare
+    skip_prepare_check => 1,
+
+    # ping is almost meaningless for DBD::Gofer and most transports anyway
+    skip_ping => 1,
+
+});
+
+
+1;

Added: dbi/trunk/lib/DBD/Gofer/Policy/pedantic.pm
==============================================================================
--- (empty file)
+++ dbi/trunk/lib/DBD/Gofer/Policy/pedantic.pm  Thu Feb 15 08:08:20 2007
@@ -0,0 +1,19 @@
+package DBD::Gofer::Policy::pedantic;
+
+#   $Id$
+#
+#   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;
+
+our $VERSION = sprintf("0.%06d", q$Revision$ =~ /(\d+)/o);
+
+use base qw(DBD::Gofer::Policy::Base);
+
+# the 'pedantic' policy is the same as the Base policy
+
+1;

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 15 08:08:20 2007
@@ -220,8 +220,10 @@
     };
     my $response = $self->new_response_with_err($rv_ref, $@);
     if ($dbh) {
-        $response->last_insert_id = $dbh->last_insert_id( @{ 
$request->dbh_last_insert_id_args })
-            if $rv_ref && $request->dbh_last_insert_id_args;
+        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 );
+        }
         $self->reset_dbh($dbh);
     }
     if ($rv_ref and UNIVERSAL::isa($rv_ref->[0],'DBI::st')) {
@@ -239,6 +241,7 @@
     my ($self, $request) = @_;
     my $dbh;
     my $sth;
+    my $last_insert_id;
 
     my $rv = eval {
         $dbh = $self->_connect($request);
@@ -249,14 +252,23 @@
         my $last = '(sth)'; # a true value (don't try to return actual sth)
 
         # execute methods on the sth, e.g., bind_param & execute
-        for my $meth_call (@{ $request->sth_method_calls }) {
-            my $method = shift @$meth_call;
-            $last = $sth->$method(@$meth_call);
+        if (my $calls = $request->sth_method_calls) {
+            for my $meth_call (@$calls) {
+                my $method = shift @$meth_call;
+                $last = $sth->$method(@$meth_call);
+            }
+        }
+
+        if (my $lid_args = $request->dbh_last_insert_id_args) {
+            $last_insert_id = $dbh->last_insert_id( @$lid_args );
         }
+
         $last;
     };
     my $response = $self->new_response_with_err($rv, $@);
 
+    $response->last_insert_id( $last_insert_id ) if defined $last_insert_id;
+
     # even if the eval failed we still want to try to gather attribute values
     $response->sth_resultsets( $self->gather_sth_resultsets($sth, $request) ) 
if $sth;
 

Modified: dbi/trunk/lib/DBI/Gofer/Transport/Base.pm
==============================================================================
--- dbi/trunk/lib/DBI/Gofer/Transport/Base.pm   (original)
+++ dbi/trunk/lib/DBI/Gofer/Transport/Base.pm   Thu Feb 15 08:08:20 2007
@@ -19,6 +19,7 @@
 
 __PACKAGE__->mk_accessors(qw(
     trace
+    go_policy
 ));
 
 

Modified: dbi/trunk/t/08keeperr.t
==============================================================================
--- dbi/trunk/t/08keeperr.t     (original)
+++ dbi/trunk/t/08keeperr.t     Thu Feb 15 08:08:20 2007
@@ -36,7 +36,7 @@
     my $sth = shift;
     # we localize an attribute here to check that the correpoding STORE
     # at scope exit doesn't clear any recorded error
-    local $sth->{dbd_dummy} = 0;
+    local $sth->{Warn} = 0;
     my $rv = $sth->SUPER::execute(@_);
     return $rv;
 }

Reply via email to