Author: timbo
Date: Tue Mar 22 16:20:17 2005
New Revision: 933

Added:
   dbi/trunk/t/70callbacks.t
Modified:
   dbi/trunk/Changes
   dbi/trunk/DBI.pm
   dbi/trunk/DBI.xs
   dbi/trunk/DBIXS.h
   dbi/trunk/MANIFEST
   dbi/trunk/lib/DBI/PurePerl.pm
Log:
Added $h->{Callbacks} attribute to enable code hooks to be invoked
    when certain methods are called.


Modified: dbi/trunk/Changes
==============================================================================
--- dbi/trunk/Changes   (original)
+++ dbi/trunk/Changes   Tue Mar 22 16:20:17 2005
@@ -4,6 +4,13 @@
 
 =cut
 
+=head2 Changes in DBI 1.49 (svn rev XXX),    2005
+
+  Added $h->{Callbacks} attribute to enable code hooks to be invoked
+    when certain methods are called. For example:
+    $dbh->{Callbacks}->{prepare} = sub { ... };
+    With thanks to David Wheeler for the kick start.
+
 =head2 Changes in DBI 1.48 (svn rev 928),    14th March 2005
 
   Fixed DBI::DBD::Metadata generation of type_info_all thanks to Steffen 
Goeldner

Modified: dbi/trunk/DBI.pm
==============================================================================
--- dbi/trunk/DBI.pm    (original)
+++ dbi/trunk/DBI.pm    Tue Mar 22 16:20:17 2005
@@ -9,7 +9,7 @@
 require 5.006_00;
 
 BEGIN {
-$DBI::VERSION = "1.48"; # ==> ALSO update the version in the pod text below!
+$DBI::VERSION = "1.49"; # ==> ALSO update the version in the pod text below!
 }
 
 =head1 NAME
@@ -115,7 +115,7 @@
 
 =head2 NOTES
 
-This is the DBI specification that corresponds to the DBI version 1.48.
+This is the DBI specification that corresponds to the DBI version 1.49.
 
 The DBI is evolving at a steady pace, so it's good to check that
 you have the latest copy.
@@ -652,10 +652,10 @@
        # if we've been subclassed then let the subclass know that we're 
connected
        $dbh->connected($dsn, $user, $pass, $attr) if ref $dbh ne 'DBI::db';
 
-       # if the caller has provided a callback then call it
-       # especially useful with connect_cached() XXX not 
enabled/tested/documented
-       if (0 and $dbh and my $oc = $dbh->{OnConnect}) { # XXX
-           $oc->($dbh, $dsn, $user, $pass, $attr) if ref $oc eq 'CODE';
+       # If the caller has provided a callback then call it
+       if (my $cb = $dbh->{Callbacks}) { # take care not to autovivify
+           $cb = $cb->{connect};
+           $cb->($dbh, $dsn, $user, $pass, $attr) if $cb;
        }
 
        DBI->trace_msg("    <- connect= $dbh\n") if $DBI::dbi_debug;
@@ -1411,9 +1411,11 @@
        };
        my $dbh = $cache->{$key};
        if ($dbh && $dbh->FETCH('Active') && eval { $dbh->ping }) {
-           # XXX warn if BegunWork?
-           # XXX warn if $dbh->FETCH('AutoCommit') != $attr->{AutoCommit} ?
-           # but that's just one (bad) case of a more general issue.
+            # If the caller has provided a callback then call it
+            if (my $cb = $dbh->{Callbacks}) { # take care not to autovivify
+                $cb = $cb->{"connect_cached.reused"};
+                $cb->($dbh, $dsn, $user, $auth, $attr) if $cb;
+            }
            return $dbh;
        }
        $dbh = $drh->connect(@_);

Modified: dbi/trunk/DBI.xs
==============================================================================
--- dbi/trunk/DBI.xs    (original)
+++ dbi/trunk/DBI.xs    Tue Mar 22 16:20:17 2005
@@ -1089,6 +1089,7 @@
     if (DBIc_is(imp_xxh, DBIcf_TaintIn))       sv_catpv(flags,"TaintIn ");
     if (DBIc_is(imp_xxh, DBIcf_TaintOut))      sv_catpv(flags,"TaintOut ");
     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));
     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,
@@ -1510,6 +1511,12 @@
            DBIc_CACHED_KIDS(imp_dbh) = (HV*)SvREFCNT_inc(SvRV(valuesv));
        }
     }
+    else if (keylen==9 && strEQ(key, "Callbacks")) {
+       if ( on && (!SvROK(valuesv) || (SvTYPE(SvRV(valuesv)) != SVt_PVHV)) )
+           croak("Can't set Callbacks to '%s'",neatsvpv(valuesv,0));
+       DBIc_set(imp_xxh, DBIcf_Callbacks, on);
+       cacheit = 1;
+    }
     else if (htype<=DBIt_DB && keylen==10 && strEQ(key, "AutoCommit")) {
        /* driver should have intercepted this and either handled it    */
        /* or set valuesv to either the 'magic' on or off value.        */
@@ -1861,6 +1868,7 @@
                ||      (*key=='P' && strEQ(key, "ParamValues"))
                ||      (*key=='P' && strEQ(key, "Profile"))
                ||      (*key=='C' && strEQ(key, "CursorName"))
+               ||      (*key=='C' && strEQ(key, "Callbacks"))
                ||      !isUPPER(*key)  /* dbd_*, private_* etc */
        ))
            warn("Can't get %s->{%s}: unrecognised 
attribute",neatsvpv(h,0),key);
@@ -2385,6 +2393,38 @@
        profile_t1 = dbi_time(); /* just get start time here */
     }
 
+    if ((i = DBIc_DEBUGIV(imp_xxh))) { /* merge handle into global */
+       I32 h_trace_level = (i & DBIc_TRACE_LEVEL_MASK);
+       if ( h_trace_level > trace_level )
+           trace_level = h_trace_level;
+       trace_flags = (trace_flags & ~DBIc_TRACE_LEVEL_MASK)
+                   | (          i & ~DBIc_TRACE_LEVEL_MASK)
+                   | trace_level;
+    }
+
+    if (DBIc_has(imp_xxh,DBIcf_Callbacks)
+       && (hook_svp = hv_fetch((HV*)SvRV(h), "Callbacks", 9, 0))
+       && HvKEYS((HV*)SvRV(*hook_svp))
+       && (hook_svp = hv_fetch((HV*)SvRV(*hook_svp), meth_name, 
strlen(meth_name), 0))
+       && SvROK(*hook_svp)
+    ) {
+       dSP;
+       SV *code = SvRV(*hook_svp);
+       if (trace_level)
+           PerlIO_printf(DBILOGFP, "%c   {{ %s callback %s being invoked\n",
+               (dirty?'!':' '), meth_name, neatsvpv(*hook_svp,0));
+       PUSHMARK(SP);
+       XPUSHs(h);
+       /* XXX add more params */
+       PUTBACK;
+       if (call_sv(code, G_ARRAY))
+           die("Callback for %s must not return any values (temporary 
restriction in current version)");
+       SPAGAIN;
+       if (trace_level)
+           PerlIO_printf(DBILOGFP, "%c   }} %s callback %s returned\n",
+               (dirty?'!':' '), meth_name, neatsvpv(*hook_svp,0));
+    }
+
 #ifdef DBI_USE_THREADS
 {
     PerlInterpreter * h_perl = DBIc_THR_USER(imp_xxh) ;
@@ -2494,15 +2534,6 @@
        }
     }
 
-    if ((i = DBIc_DEBUGIV(imp_xxh))) { /* merge handle into global */
-       I32 h_trace_level = (i & DBIc_TRACE_LEVEL_MASK);
-       if ( h_trace_level > trace_level )
-           trace_level = h_trace_level;
-       trace_flags = (trace_flags & ~DBIc_TRACE_LEVEL_MASK)
-                   | (          i & ~DBIc_TRACE_LEVEL_MASK)
-                   | trace_level;
-    }
-
     /* record this inner handle for use by DBI::var::FETCH     */
     if (is_DESTROY) {
 

Modified: dbi/trunk/DBIXS.h
==============================================================================
--- dbi/trunk/DBIXS.h   (original)
+++ dbi/trunk/DBIXS.h   Tue Mar 22 16:20:17 2005
@@ -264,11 +264,12 @@
 #define DBIcf_TaintOut    0x040000     /* taint outgoing data */
 #define DBIcf_Executed    0x080000     /* do/execute called since commit/rollb 
*/
 #define DBIcf_PrintWarn   0x100000     /* warn() on warning (err="0")          
*/
+#define DBIcf_Callbacks   0x200000     /* has Callbacks attribute hash         
*/
 /* NOTE: new flags may require clone() to be updated */
 
 #define DBIcf_INHERITMASK              /* what NOT to pass on to children */   
\
   (U32)( DBIcf_COMSET | DBIcf_IMPSET | DBIcf_ACTIVE | DBIcf_IADESTROY          
\
-  | DBIcf_AutoCommit | DBIcf_BegunWork | DBIcf_Executed )
+  | DBIcf_AutoCommit | DBIcf_BegunWork | DBIcf_Executed | DBIcf_Callbacks )
 
 /* general purpose bit setting and testing macros                      */
 #define DBIbf_is( bitset,flag)         ((bitset) &   (flag))

Modified: dbi/trunk/MANIFEST
==============================================================================
--- dbi/trunk/MANIFEST  (original)
+++ dbi/trunk/MANIFEST  Tue Mar 22 16:20:17 2005
@@ -61,6 +61,7 @@
 t/42prof_data.t
 t/50dbm.t
 t/60preparse.t
+t/70callbacks.t
 t/80proxy.t
 t/pod.t
 test.pl                                A very simple test harness using 
ExampleP.pm

Modified: dbi/trunk/lib/DBI/PurePerl.pm
==============================================================================
--- dbi/trunk/lib/DBI/PurePerl.pm       (original)
+++ dbi/trunk/lib/DBI/PurePerl.pm       Tue Mar 22 16:20:17 2005
@@ -143,6 +143,7 @@
        Attribution
        BegunWork
        CachedKids
+        Callbacks
        CursorName
        Database
        DebugDispatch

Added: dbi/trunk/t/70callbacks.t
==============================================================================
--- (empty file)
+++ dbi/trunk/t/70callbacks.t   Tue Mar 22 16:20:17 2005
@@ -0,0 +1,75 @@
+#!perl -w
+
+use strict;
+
+use Test::More;
+use DBI;
+
+BEGIN {
+        plan skip_all => '$h->{Callbacks} attribute not supported for 
DBI::PurePerl'
+                if $DBI::PurePerl && $DBI::PurePerl; # doubled to avoid typo 
warning
+        plan tests => 24;
+}
+
+$| = 1;
+my $dsn = "dbi:ExampleP:";
+my %called;
+
+ok my $dbh = DBI->connect($dsn, '', ''), "Create dbh";
+
+is $dbh->{Callbacks}, undef, "Callbacks initially undef";
+ok $dbh->{Callbacks} = my $cb = { };
+is ref $dbh->{Callbacks}, 'HASH', "Callbacks can be set to a hash ref";
+is $dbh->{Callbacks}, $cb, "Callbacks set to same hash ref";
+
+$dbh->{Callbacks} = undef;
+is $dbh->{Callbacks}, undef, "Callbacks set to undef again";
+
+ok $dbh->{Callbacks} = { ping => sub { $called{ping}++; return; } };
+is keys %{ $dbh->{Callbacks} }, 1;
+is ref $dbh->{Callbacks}->{ping}, 'CODE';
+ok $dbh->ping;
+is $called{ping}, 1;
+ok $dbh->ping;
+is $called{ping}, 2;
+$dbh->{Callbacks} = undef;
+ok $dbh->ping;
+is $called{ping}, 2;
+
+=for comment XXX
+
+The big problem here is that conceptually the Callbacks attribute
+is # applied to the $dbh _during_ the $drh->connect() call, so you can't
+set a callback on "connect" on the $dbh because connect isn't called
+on the dbh, but on the $drh.
+
+So a "connect" callback would have to be defined on the $drh, but that's
+cumbersom for the user and then it would apply to all future connects
+using that driver.
+
+The best thing to do is probably to special-case "connect", "connect_cached"
+and (the already special-case) "connect_cached.reused".
+
+=cut
+
+my @args = (
+    $dsn, '', '', {
+        Callbacks => {
+            connect                 => sub { $called{connect}++; return; },
+            "connect_cached.reused" => sub { $called{cached}++; return; },
+        }
+    }
+);
+
+ok $dbh = DBI->connect(@args), "Create handle with callbacks";
+is $called{connect}, 1, "Connect callback called once.";
+is $called{cached}, undef, "Cached not yet called";
+
+ok $dbh = DBI->connect_cached(@args), "Create handle with callbacks";
+is $called{connect}, 2, "Connect callback called by connect_cached.";
+is $called{cached}, undef, "Cached still not yet called";
+
+ok $dbh = DBI->connect_cached(@args), "Create handle with callbacks";
+is $called{connect}, 3, "Connect callback called by second connect_cached.";
+is $called{cached}, 1, "Cached called";
+

Reply via email to