Author: timbo
Date: Thu Apr 26 16:28:15 2007
New Revision: 9461
Modified:
dbi/trunk/Changes
dbi/trunk/DBI.xs
dbi/trunk/DBIXS.h
dbi/trunk/t/10examp.t
dbi/trunk/t/70callbacks.t
Log:
Added more functionality to the (undocumented) Callback mechanism.
Callbacks can now elect to provide a value to be returned, in which case
the method won't be called. A callback for "*" is applied to all methods
that don't have their own callback.
Callbacks are now called slightly later in the dispatch processing.
That means you can't have a callback for 'can' or 'func'.
Change all remaining SvPV(sv,lna) into SvPV_nolen(sv).
Modified: dbi/trunk/Changes
==============================================================================
--- dbi/trunk/Changes (original)
+++ dbi/trunk/Changes Thu Apr 26 16:28:15 2007
@@ -61,6 +61,10 @@
Changed File::Spec prerequisite to not require a minimum version.
Changed tests to work with other DBMs thanks to ZMAN.
+ Added more functionality to the (undocumented) Callback mechanism.
+ Callbacks can now elect to provide a value to be returned, in which case
+ the method won't be called. A callback for "*" is applied to all methods
+ that don't have their own callback.
Added support for DBI Profile Path to contain refs to scalars
which will be de-ref'd for each profile sample.
Added dbilogstrip utility to edit DBI logs for diff'ing (gets installed)
Modified: dbi/trunk/DBI.xs
==============================================================================
--- dbi/trunk/DBI.xs (original)
+++ dbi/trunk/DBI.xs Thu Apr 26 16:28:15 2007
@@ -124,7 +124,7 @@
#define DBIc_STATE_adjust(imp_xxh, state) \
(SvOK(state) /* SQLSTATE is implemented by driver */ \
- ? (strEQ(SvPV(state,lna),"00000") ? &sv_no : sv_mortalcopy(state))\
+ ? (strEQ(SvPV_nolen(state),"00000") ? &sv_no : sv_mortalcopy(state))\
: (SvTRUE(DBIc_ERR(imp_xxh)) \
? sv_2mortal(newSVpv("S1000",5)) /* General error */ \
: &sv_no) /* Success ("00000") */ \
@@ -557,7 +557,6 @@
static char *
mkvname(pTHX_ HV *stash, const char *item, int uplevel) /* construct a
variable name */
{
- STRLEN lna;
SV *sv = sv_newmortal();
sv_setpv(sv, HvNAME(stash));
if(uplevel) {
@@ -568,7 +567,7 @@
}
sv_catpv(sv, "::");
sv_catpv(sv, item);
- return SvPV(sv, lna);
+ return SvPV_nolen(sv);
}
@@ -639,7 +638,6 @@
{
dTHX;
dPERINTERP;
- STRLEN lna;
const char *filename;
PerlIO *fp = Nullfp;
IO *io;
@@ -659,7 +657,7 @@
DBIS->logfp_ref = io;
}
else {
- filename = (SvOK(file)) ? SvPV(file, lna) : Nullch;
+ filename = (SvOK(file)) ? SvPV_nolen(file) : Nullch;
/* undef arg == reset back to stderr */
if (!filename || strEQ(filename,"STDERR")) {
close_trace_file(aTHX);
@@ -1187,7 +1185,6 @@
dPERINTERP;
SV *flags = sv_2mortal(newSVpv("",0));
SV *inner;
- STRLEN lna;
static const char pad[] = " ";
if (!msg)
msg = "dbih_dumpcom";
@@ -1215,7 +1212,7 @@
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 FLAGS 0x%lx: %s\n", pad,
(long)DBIc_FLAGS(imp_xxh), SvPV_nolen(flags));
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)))
@@ -1645,8 +1642,7 @@
TAINT_NOT; /* the require is presumed innocent till proven guilty */
perl_require_pv("DBI/Profile.pm");
if (SvTRUE(ERRSV)) {
- STRLEN lna;
- warn("Can't load %s: %s", profile_class, SvPV(ERRSV,lna));
+ warn("Can't load %s: %s", profile_class, SvPV_nolen(ERRSV));
valuesv = &sv_undef;
}
else {
@@ -1780,7 +1776,6 @@
dPERINTERP;
dTHR;
D_imp_xxh(h);
- STRLEN lna;
STRLEN keylen;
char *key = SvPV(keysv, keylen);
int htype = DBIc_TYPE(imp_xxh);
@@ -1846,7 +1841,7 @@
assert(i == AvFILL(name_av)+1);
while (--i >= 0) {
sv = newSVsv(AvARRAY(name_av)[i]);
- name = SvPV(sv,lna);
+ name = SvPV_nolen(sv);
if (key[5] != 'h') { /* "NAME_hash" */
for (p = name; p && *p; ++p) {
#ifdef toUPPER_LC
@@ -2621,9 +2616,9 @@
SV *st2 = ST(2); /* used in debugging */
SV *orig_h = h;
SV *err_sv;
+ SV **tmp_svp;
SV **hook_svp = 0;
MAGIC *mg;
- STRLEN lna;
int gimme = GIMME;
I32 trace_flags = DBIS->debug; /* local copy may change during
dispatch */
I32 trace_level = (trace_flags & DBIc_TRACE_LEVEL_MASK);
@@ -2717,7 +2712,7 @@
imp_xxh = dbih_getcom2(aTHX_ h, 0); /* get common Internal Handle
Attributes */
if (!imp_xxh) {
if (strEQ(meth_name, "can")) { /* ref($h)->can("foo") */
- const char *can_meth = SvPV(st1,lna);
+ const char *can_meth = SvPV_nolen(st1);
SV *rv = &PL_sv_undef;
GV *gv = gv_fetchmethod_autoload(gv_stashsv(orig_h,FALSE),
can_meth, FALSE);
if (gv && isGV(gv))
@@ -2750,39 +2745,6 @@
| 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)
- ) {
- SV *code = SvRV(*hook_svp);
- I32 count;
- if (trace_level)
- PerlIO_printf(DBILOGFP, "%c {{ %s callback %s being invoked\n",
- (dirty?'!':' '), meth_name, neatsvpv(*hook_svp,0));
- ENTER;
- SAVETMPS;
- EXTEND(SP, items+1);
- PUSHMARK(SP);
- PUSHs(h); /* push inner handle, then others
params */
- for (i=1; i < items; ++i) { /* start at 1 to skip handle */
- PUSHs( ST(i) );
- }
- PUTBACK;
- SAVE_DEFSV; /* local($_) = $method_name */
- DEFSV = sv_2mortal(newSVpv(meth_name,0));
- count = call_sv(code, G_ARRAY);
- if (count != 0)
- die("Callback for %s returned %d values but must not return any
(temporary restriction in current version)", meth_name, (int)count);
- SPAGAIN;
- FREETMPS;
- LEAVE;
- 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) ;
@@ -2813,9 +2775,9 @@
if (ima_flags & IMA_STUB) {
if (*meth_name == 'c' && strEQ(meth_name,"can")) {
- const char *can_meth = SvPV(st1,lna);
+ const char *can_meth = SvPV_nolen(st1);
SV *dbi_msv = Nullsv;
- SV *imp_msv; /* handle implementors method (GV or CV) */
+ /* find handle implementors method (GV or CV) */
if ( (imp_msv =
(SV*)gv_fetchmethod_autoload(DBIc_IMP_STASH(imp_xxh), can_meth, FALSE)) ) {
/* return DBI's CV, not the implementors CV (else we'd
bypass dispatch) */
/* and anyway, we may have hit a private method not
part of the DBI */
@@ -2841,7 +2803,7 @@
if (!SvPOK(meth_name_sv) || SvNIOK(meth_name_sv))
croak("%s->%s() invalid redirect method name %s",
neatsvpv(h,0), meth_name, neatsvpv(meth_name_sv,0));
- meth_name = SvPV(meth_name_sv, lna);
+ meth_name = SvPV_nolen(meth_name_sv);
}
if (ima_flags & IMA_EXECUTE) {
imp_xxh_t *parent = DBIc_PARENT_COM(imp_xxh);
@@ -2894,7 +2856,7 @@
if (SvTAINTED(ST(i))) {
char buf[100];
sprintf(buf,"parameter %d of %s->%s method call",
- i, SvPV(h,lna), meth_name);
+ i, SvPV_nolen(h), meth_name);
tainted = 1; /* needed for TAINT_PROPER to work */
TAINT_PROPER(buf); /* die's */
}
@@ -2947,8 +2909,74 @@
}
DBIh_CLEAR_ERROR(imp_xxh);
}
- else /* we check for change in ErrCount during call */
+ else { /* we check for change in ErrCount during call */
ErrCount = DBIc_ErrCount(imp_xxh);
+ }
+
+ if (DBIc_has(imp_xxh,DBIcf_Callbacks)
+ && (tmp_svp = hv_fetch((HV*)SvRV(h), "Callbacks", 9, 0))
+ && ( (hook_svp = hv_fetch((HV*)SvRV(*tmp_svp), meth_name,
strlen(meth_name), 0))
+ || (hook_svp = hv_fetch((HV*)SvRV(*tmp_svp), "*", 1, 0)) ) /* all
methods */
+ && SvROK(*hook_svp)
+ ) {
+ SV *orig_defsv;
+ SV *code = SvRV(*hook_svp);
+ I32 skip_dispatch = 0;
+ if (trace_level)
+ PerlIO_printf(DBILOGFP, "%c {{ %s callback %s being invoked (mark
%d)\n",
+ (dirty?'!':' '), meth_name, neatsvpv(*hook_svp,0), mark);
+
+ /* we don't use ENTER,SAVETMPS & FREETMPS,LEAVE because we may need
mortal
+ * results to live long enough to be returned to our caller
+ */
+ /* we want to localize $_ for the callback but can't just to that alone
+ * because we're not using SAVETMPS & FREETMPS, so we have to get
sneaky.
+ * We still localize, so we're safe from the callback dieing,
+ * but after the callback we manually restore the original $_.
+ */
+ orig_defsv = DEFSV; /* remember the current $_ */
+ SAVE_DEFSV; /* local($_) = $method_name */
+ DEFSV = sv_2mortal(newSVpv(meth_name,0));
+
+ EXTEND(SP, items+1);
+ PUSHMARK(SP);
+ PUSHs(h); /* push inner handle, then others
params */
+ for (i=1; i < items; ++i) { /* start at 1 to skip handle */
+ PUSHs( ST(i) );
+ }
+ PUTBACK;
+ outitems = call_sv(code, G_ARRAY); /* call the callback code */
+ SPAGAIN;
+
+ /* The callback code can undef $_ to indicate to skip dispatch */
+ skip_dispatch = !SvOK(DEFSV);
+ /* put $_ back now, but with an incremented ref count to compensate
+ * for the ref count decrement that will happen when we exit the scope.
+ */
+ DEFSV = SvREFCNT_inc(orig_defsv);
+
+ if (trace_level)
+ PerlIO_printf(DBILOGFP, "%c }} %s callback %s returned%s (mark
%d)\n",
+ (dirty?'!':' '), meth_name, neatsvpv(*hook_svp,0),
+ skip_dispatch ? ", actual method will not be called" : "", mark
+ );
+ if (skip_dispatch) { /* XXX experimental */
+ int ix = outitems;
+ /* copy the new items down to the destination list */
+ while (ix-- > 0) {
+ if(0)warn("\tcopy down %d: %s overwriting %s\n", ix,
SvPV_nolen(TOPs), SvPV_nolen(ST(ix)) );
+ ST(ix) = POPs;
+ }
+ imp_msv = *hook_svp; /* for trace and profile */
+ goto post_dispatch;
+ }
+ else {
+ if (outitems != 0)
+ die("Callback for %s returned %d values but must not return
any (temporary restriction in current version)",
+ meth_name, (int)outitems);
+ /* POP's and PUTBACK? to clear stack */
+ }
+ }
/* The "quick_FETCH" logic... */
/* Shortcut for fetching attributes to bypass method call overheads */
@@ -3018,7 +3046,7 @@
if (imp_msv && isGV(imp_msv) && GvSTASH(imp_msv) != imp_stash)
PerlIO_printf(logfp, "in %s ", HvNAME(GvSTASH(imp_msv)));
PerlIO_printf(logfp, "for %s (%s", HvNAME(imp_stash),
- SvPV(orig_h,lna));
+ SvPV_nolen(orig_h));
if (h != orig_h) /* show inner handle to aid tracing */
PerlIO_printf(logfp, "~0x%lx", (long)SvRV(h));
else PerlIO_printf(logfp, "~INNER");
@@ -3050,8 +3078,8 @@
PUSHMARK(mark); /* mark arguments again so we can pass them on */
/* Note: the handle on the stack is still an object blessed into a
- * DBI::* class and *not* the DBD::*::* class whose method is being
- * invoked. This *is* correct and should be largely transparent.
+ * DBI::* class and not the DBD::*::* class whose method is being
+ * invoked. This is correct and should be largely transparent.
*/
/* SHORT-CUT ALERT! */
@@ -3088,8 +3116,8 @@
SPAGAIN;
/* XXX restore local vars so ST(n) works below */
- sp -= outitems;
- ax = (sp - stack_base) + 1;
+ SP -= outitems;
+ ax = (SP - stack_base) + 1;
#ifdef DBI_save_hv_fetch_ent
if (is_FETCH)
@@ -3196,6 +3224,7 @@
}
if (ima_flags & IMA_END_WORK) { /* commit() or rollback() */
+ /* XXX does not consider if the method call actually worked or not */
DBIc_off(imp_xxh, DBIcf_Executed);
if (DBIc_has(imp_xxh, DBIcf_BegunWork)) {
@@ -3259,6 +3288,9 @@
}
}
+ /* if method returned a new handle, and that handle has an error on it
+ * then copy the error up into the parent handle
+ */
if (ima_flags & IMA_IS_FACTORY && SvROK(ST(0))) {
SV *h_new = ST(0);
D_impdata(imp_xxh_new, imp_xxh_t, h_new);
@@ -3287,7 +3319,7 @@
if (*meth_name=='s' && strEQ(meth_name,"set_err")) {
SV **sem_svp = hv_fetch((HV*)SvRV(h), "dbi_set_err_method", 18,
GV_ADDWARN);
if (SvOK(*sem_svp))
- err_meth_name = SvPV(*sem_svp,lna);
+ err_meth_name = SvPV_nolen(*sem_svp);
}
/* XXX change to vsprintf into sv directly */
@@ -3344,6 +3376,7 @@
}
}
+ hook_svp = NULL;
if ( SvTRUE(err_sv)
&& DBIc_has(imp_xxh, DBIcf_HandleError)
&& (hook_svp = hv_fetch((HV*)SvRV(h),"HandleError",11,0))
@@ -3397,13 +3430,13 @@
}
if (is_warning) {
if (DBIc_has(imp_xxh, DBIcf_PrintWarn))
- warn("%s", SvPV(msg,lna));
+ warn("%s", SvPV_nolen(msg));
}
else if (!hook_svp && SvTRUE(err_sv)) {
if (DBIc_has(imp_xxh, DBIcf_PrintError))
- warn("%s", SvPV(msg,lna));
+ warn("%s", SvPV_nolen(msg));
if (DBIc_has(imp_xxh, DBIcf_RaiseError))
- croak("%s", SvPV(msg,lna));
+ croak("%s", SvPV_nolen(msg));
}
}
else if (profile_t1) { /* see also dbi_profile() call a few lines above */
@@ -3411,7 +3444,6 @@
dbi_profile(h, imp_xxh, statement_sv, imp_msv ? imp_msv : (SV*)cv,
profile_t1, dbi_time());
}
-
XSRETURN(outitems);
}
@@ -4010,12 +4042,11 @@
if (ima->hidearg) sv_catpvf(trace_msg, ", H %d",
(unsigned)ima->hidearg);
}
if ( (svp=DBD_ATTRIB_GET_SVP(attribs, "U",1)) != NULL) {
- STRLEN lna;
AV *av = (AV*)SvRV(*svp);
ima->minargs = (U8)SvIV(*av_fetch(av, 0, 1));
ima->maxargs = (U8)SvIV(*av_fetch(av, 1, 1));
svp = av_fetch(av, 2, 0);
- ima->usage_msg = (svp) ? savepv_using_sv(SvPV(*svp, lna)) : "";
+ ima->usage_msg = (svp) ? savepv_using_sv(SvPV_nolen(*svp)) : "";
ima->flags |= IMA_HAS_USAGE;
if (trace_msg && DBIS_TRACE_LEVEL >= 11)
sv_catpvf(trace_msg, ",\n usage: min %d, max %d, '%s'",
@@ -4154,8 +4185,7 @@
CODE:
dPERINTERP;
/* Note that we do not come through the dispatcher to get here. */
- STRLEN lna;
- char *meth = SvPV(SvRV(sv),lna); /* what should this tie do ? */
+ char *meth = SvPV_nolen(SvRV(sv)); /* what should this tie do ? */
char type = *meth++; /* is this a $ or & style */
imp_xxh_t *imp_xxh = (DBI_LAST_HANDLE_OK) ? DBIh_COM(DBI_LAST_HANDLE) :
NULL;
int trace = 0;
@@ -4685,7 +4715,6 @@
SV * h
CODE:
D_imp_xxh(h);
- STRLEN lna;
SV *state = DBIc_STATE(imp_xxh);
(void)cv;
ST(0) = DBIc_STATE_adjust(imp_xxh, state);
Modified: dbi/trunk/DBIXS.h
==============================================================================
--- dbi/trunk/DBIXS.h (original)
+++ dbi/trunk/DBIXS.h Thu Apr 26 16:28:15 2007
@@ -481,10 +481,9 @@
/* attribs value. One day we may add some extra magic in here. */
#define DBD_ATTRIBS_CHECK(func, h, attribs) \
if ((attribs) && SvOK(attribs)) { \
- STRLEN lna1=0, lna2=0; \
if (!SvROK(attribs) || SvTYPE(SvRV(attribs))!=SVt_PVHV) \
croak("%s->%s(...): attribute parameter '%s' is not a hash ref",
\
- SvPV(h,lna1), func, SvPV(attribs,lna2)); \
+ SvPV_nolen(h), func, SvPV_nolen(attribs)); \
} else (attribs) = Nullsv
#define DBD_ATTRIB_GET_SVP(attribs, key,klen) \
Modified: dbi/trunk/t/10examp.t
==============================================================================
--- dbi/trunk/t/10examp.t (original)
+++ dbi/trunk/t/10examp.t Thu Apr 26 16:28:15 2007
@@ -12,7 +12,7 @@
my $haveFileSpec = eval { require File::Spec };
require VMS::Filespec if $^O eq 'VMS';
-use Test::More tests => 204;
+use Test::More tests => 209;
# "globals"
my ($r, $dbh);
Modified: dbi/trunk/t/70callbacks.t
==============================================================================
--- dbi/trunk/t/70callbacks.t (original)
+++ dbi/trunk/t/70callbacks.t Thu Apr 26 16:28:15 2007
@@ -9,7 +9,7 @@
BEGIN {
plan skip_all => '$h->{Callbacks} attribute not supported for
DBI::PurePerl'
if $DBI::PurePerl && $DBI::PurePerl; # doubled to avoid typo
warning
- plan tests => 35;
+ plan tests => 49;
}
$| = 1;
@@ -29,6 +29,8 @@
ok $dbh->{Callbacks} = {
ping => sub {
is $_, 'ping', '$_ holds method name';
+ is @_, 1, '@_ holds 1 values';
+ is ref $_[0], 'DBI::db', 'first is $dbh';
$called{$_}++;
return;
},
@@ -40,12 +42,18 @@
is $_[1], 'bar';
is $_[2], undef;
$_[2] = { baz => 1 };
- is $_, 'quote_identifier', '$_ holds method name';
$called{$_}++;
- return (1,2,3); # return something
+ return (1,2,3); # return something - which is not allowed
},
+ disconnect => sub { # test die from within a callback
+ die "You can't disconnect that easily!\n";
+ },
+ "*" => sub {
+ $called{$_}++;
+ return;
+ }
};
-is keys %{ $dbh->{Callbacks} }, 2;
+is keys %{ $dbh->{Callbacks} }, 4;
is ref $dbh->{Callbacks}->{ping}, 'CODE';
@@ -57,15 +65,67 @@
ok $dbh->ping;
is $called{ping}, 2;
+ok $dbh->type_info_all;
+is $called{type_info_all}, 1, 'fallback callback';
+
my $attr;
eval { $dbh->quote_identifier('foo','bar', $attr) };
is $called{quote_identifier}, 1;
ok $@, 'quote_identifier callback caused fatal error';
is ref $attr, 'HASH', 'param modified by callback - not recommended!';
+ok !eval { $dbh->disconnect };
+ok $@, "You can't disconnect that easily!\n";
+
$dbh->{Callbacks} = undef;
ok $dbh->ping;
-is $called{ping}, 2;
+is $called{ping}, 2; # no change
+
+
+# --- test skipping dispatch and fallback callbacks
+
+$dbh->{Callbacks} = {
+ ping => sub {
+ undef $_; # tell dispatch to not call the method
+ return "42 bells";
+ },
+ data_sources => sub {
+ my ($h, $values_to_return) = @_;
+ undef $_; # tell dispatch to not call the method
+ my @ret = 11..10+($values_to_return||0);
+ return @ret;
+ },
+ commit => sub { # test using set_err within a callback
+ my $h = shift;
+ undef $_; # tell dispatch to not call the method
+ return $h->set_err(42, "faked commit failure");
+ },
+};
+
+# these tests are slightly convoluted because messing with the stack is bad for
+# your mental health
+my $rv = $dbh->ping;
+is $rv, "42 bells";
+my @rv = $dbh->ping;
+is scalar @rv, 1, 'should return a single value in list context';
+is "@rv", "42 bells";
+# test returning lists with different number of args to test
+# the stack handling in the dispatch code
+is join(":", $dbh->data_sources()), "";
+is join(":", $dbh->data_sources(0)), "";
+is join(":", $dbh->data_sources(1)), "11";
+is join(":", $dbh->data_sources(2)), "11:12";
+
+{
+local $dbh->{RaiseError} = 1;
+local $dbh->{PrintError} = 0;
+is eval { $dbh->commit }, undef, 'intercepted commit should return undef';
+like $@, '/DBD::ExampleP::db commit failed: faked commit failure/';
+is $DBI::err, 42;
+is $DBI::errstr, "faked commit failure";
+}
+
+# --- test connect_cached.*
=for comment XXX