On Mon, Nov 09, 2009 at 05:05:11PM +0000, Martin Evans wrote: > Martin Evans wrote: > >> > >> +/* Convert a simple string representation of a value into a more specific > >> + * perl type based on an sql_type value. > >> + * The semantics of SQL standard TYPE values are interpreted _very_ > >> loosely > >> + * on the basis of "be liberal in what you accept and let's throw in some > >> + * extra semantics while we're here" :) > >> + * Returns: > >> + * -1: sv is undef or doesn't > >> + * 0: sv couldn't be converted to requested (strict) type > >> + * 1: sv was handled without a problem > >> + */ > >> +int > >> +post_fetch_sv(pTHX_ SV *h, imp_xxh_t *imp_xxh, SV *sv, int sql_type, U32 > >> flags, void *v) > >> +{ > >> + int discard_pv = 0; > >> + > >> + /* do nothing for undef (NULL) or non-string values */ > >> + if (!sv || !SvPOK(sv)) > >> + return -1; > >> + > >> + switch(sql_type) { > >> + > >> + /* caller would like IV (but may get UV or NV) */ > >> + /* will warn if not numeric. return 0 on overflow */ > >> + case SQL_SMALLINT: > >> + discard_pv = 1; > >> + case SQL_INTEGER: > >> + sv_2iv(sv); /* is liberal, may return SvIV, SvUV, or SvNV */ > >> + if (SvNOK(sv)) { /* suspicious */ > >> + NV nv = SvNV(sv); > >> + /* ignore NV set just to preserve digits after the decimal > >> place */ > >> + /* just complain if the value won't fit in an IV or NV */ > >> + if (nv > UV_MAX || nv < IV_MIN) > >> + return 0; > >> + } > >> + break; > >> + > >> + /* caller would like SvNOK/SvIOK true if the value is a number */ > >> + /* will warn if not numeric */ > >> + case SQL_FLOAT: > >> + discard_pv = 1; > >> + case SQL_DOUBLE: > >> + sv_2nv(sv); > >> + break; > >> + > >> + /* caller would like IV else UV else NV */ > >> + /* else no error and sv is untouched */ > >> + case SQL_NUMERIC: > >> + discard_pv = 1; > >> + case SQL_DECIMAL: { > >> + UV uv; > >> + /* based on the code in perl's toke.c */ > >> + int flags = grok_number(SvPVX(sv), SvCUR(sv), &uv); > >> + > >> + if (flags == IS_NUMBER_IN_UV) { /* +ve int */ > >> + if (uv <= IV_MAX) /* prefer IV over UV */ > >> + sv_2iv(sv); > >> + else sv_2uv(sv); > >> + } > >> + else if (flags == (IS_NUMBER_IN_UV | IS_NUMBER_NEG) > >> + && uv <= IV_MAX > >> + ) { > >> + sv_2iv(sv); > >> + } > >> + else if (flags) /* is numeric */ > >> + sv_2nv(sv); > >> + } > >> + break; > >> + > >> +#if 0 /* XXX future possibilities */ > >> + case SQL_BIGINT: /* use Math::BigInt if too large for IV/UV */ > >> +#endif > >> + default: > >> + return 0; /* value unchanged */ > >> + } > >> + > >> + if (discard_pv /* caller wants string buffer discarded */ > >> + && SvNIOK(sv) /* we set a numeric value */ > >> + && SvPVX(sv) && SvLEN(sv) /* we have a buffer to discard */ > >> + ) { > >> + Safefree(SvPVX(sv)); > >> + SvPVX(sv) = NULL; > >> + SvPOK_off(sv); > >> + } > >> + return 1; > >> +}
> There was an omission in my addition to Tim's example as I forgot to > change DBISTATE_VERSION. Thanks. Though that's less important than it was now there's also DBIXS_REVISION (in dbixs_rev.h) that automatically tracks the svn revsion number. > I've implemented this as it stands in DBD::Oracle and it seems to work > out ok and certainly where I was wanting to go (and further). Ok. > My own feeling is that if someone asks for something to be bound as an > SQL_INTEGER and it cannot due to over/under flow this should be an error > and that is how I've implemented it. The return value of post_fetch_sv() is meant to allow drivers to report an error. I thought about making post_fetch_sv() itself call DBIh_SET_ERR_* to report an error but opted to avoid that because, to generate a good error more info would need to be passed, like the column number. On the other hand, if post_fetch_sv() doesn't do it then there's a greater risk of inconsistency between the drivers. > Perhaps it could have been one of those informationals as the sv is > unchanged and still usable but it is not in the requested format so > I'd class that an error. Perhaps we should have $sth->bind_col(..., { LooselyTyped => 1 }); to allow for those who don't want an error if the type doesn't fit. That certainly feels better than overloading SQL_INTEGER vs SQL_NUMERIC to achieve the same effect! > However, I have > a very small concern for people who might have been binding columns with > a type but no destination SV but their DBD did nothing about it (which I > believe is all DBDs up to now). For me, I didn't leave that code in and > just documented it as: > > # I was hoping the following would work (according to DBI, it > # might) to ensure the a, b and c > # columns are returned as integers instead of strings saving > # us from having to add 0 to them below. It does not with > # DBD::Oracle. > # NOTE: you don't have to pass a var into bind_col to receive > # the column data as it works on the underlying column and not > # just a particular bound variable. > #$cursor->bind_col(4, undef, { TYPE => SQL_INTEGER }); > #$cursor->bind_col(5, undef, { TYPE => SQL_INTEGER }); > #$cursor->bind_col(10, undef, { TYPE => SQL_INTEGER }); > > but if those last 3 lines were left uncommented they would have ended up > a noop before but not now. However, I'd be surprised if anyone was > really doing that as it did nothing. Does anyone know of any drivers that pay any attention to the type param of bind_column? We could make it default to issuing a warning on overflow, and have attributes to specify either an error or ignore. > I think a MinMemory attribute would be ok but I'd use it as in most of > my cases I am retrieving the whole result-set in one go and it can be > very large. How would post_fetch_sv know this attribute? Via the flags argument. > What was the intention of "void *v" argument at the end of post_fetch_sv? Planning for an uncertain future. After mulling it over some more, and looking at ODBC's SQLBindCol (which takes a C type, not an SQL type) I've decided to err on the simple side. I've appended a patch for review. Tim. Index: DBI.xs =================================================================== --- DBI.xs (revision 13478) +++ DBI.xs (working copy) @@ -78,6 +78,7 @@ static int set_err_char _((SV *h, imp_xxh_t *imp_xxh, const char *err_c, IV err_i, const char *errstr, const char *state, const char *method)); static int set_err_sv _((SV *h, imp_xxh_t *imp_xxh, SV *err, SV *errstr, SV *state, SV *method)); static int quote_type _((int sql_type, int p, int s, int *base_type, void *v)); +static int sql_type_cast_svpv _((pTHX_ SV *h, imp_xxh_t *imp_xxh, SV *sv, int sql_type, U32 flags, void *v)); static I32 dbi_hash _((const char *string, long i)); static void dbih_dumphandle _((pTHX_ SV *h, const char *msg, int level)); static int dbih_dumpcom _((pTHX_ imp_xxh_t *imp_xxh, const char *msg, int level)); @@ -434,11 +435,12 @@ DBIS->get_fbav = dbih_get_fbav; DBIS->make_fdsv = dbih_make_fdsv; DBIS->neat_svpv = neatsvpv; - DBIS->bind_as_num = quote_type; + DBIS->bind_as_num = quote_type; /* XXX deprecated */ DBIS->hash = dbi_hash; DBIS->set_err_sv = set_err_sv; DBIS->set_err_char= set_err_char; DBIS->bind_col = dbih_sth_bind_col; + DBIS->sql_type_cast_svpv = sql_type_cast_svpv; /* Remember the last handle used. BEWARE! Sneaky stuff here! */ @@ -1696,6 +1698,8 @@ (void)s; (void)t; (void)v; + /* looks like it's never been used, and doesn't make much sense anyway */ + warn("Use of DBI internal bind_as_num/quote_type function is deprecated"); switch(sql_type) { case SQL_INTEGER: case SQL_SMALLINT: @@ -1714,6 +1718,95 @@ } +/* Convert a simple string representation of a value into a more specific + * perl type based on an sql_type value. + * The semantics of SQL standard TYPE values are interpreted _very_ loosely + * on the basis of "be liberal in what you accept and let's throw in some + * extra semantics while we're here" :) + * Returns: + * -1: sv is undef, unchanged + * -2: sql_type isn't handled, value unchanged + * 0: sv couldn't be converted to requested (strict) type + * 1: sv was handled without a problem + */ +#define DBIstcf_DISCARD_PV 0x0001 +#define DBIstcf_STRICT 0x0002 + +int +sql_type_cast_svpv(pTHX_ SV *h, imp_xxh_t *imp_xxh, SV *sv, int sql_type, U32 flags, void *v) +{ + + /* do nothing for undef (NULL) or non-string values */ + if (!sv || !SvOK(sv)) + return -1; + + switch(sql_type) { + + case SQL_INTEGER: + /* sv_2iv is liberal, may return SvIV, SvUV, or SvNV */ + sv_2iv(sv); + /* if strict, complain if SvNOK set because value is out of range + * for IV/UV, or if SvIOK is not set because it's not numeric (in which + * case perl would have warn'd already if -w or warnings are in effect) + */ + if (flags & DBIstcf_STRICT && (SvNOK(sv) || !SvIOK(sv))) { + return 0; + } + break; + + case SQL_DOUBLE: + sv_2nv(sv); + /* if strict, complain if !SvNOK because value is not numeric + * (perl would have warn'd already if -w or warnings are in effect) + */ + if (flags & DBIstcf_STRICT && !SvNOK(sv)) { + return 0; + } + break; + + /* caller would like IV else UV else NV */ + /* else no error and sv is untouched */ + case SQL_NUMERIC: { + UV uv; + /* based on the code in perl's toke.c */ + int flags = grok_number(SvPVX(sv), SvCUR(sv), &uv); + if (flags == IS_NUMBER_IN_UV) { /* +ve int */ + if (uv <= IV_MAX) /* prefer IV over UV */ + sv_2iv(sv); + else sv_2uv(sv); + } + else if (flags == (IS_NUMBER_IN_UV | IS_NUMBER_NEG) + && uv <= IV_MAX + ) { + sv_2iv(sv); + } + else if (flags) /* is numeric */ + sv_2nv(sv); + } + else if (flags & DBIstcf_STRICT) + return 0; /* not numeric */ + break; + +#if 0 /* XXX future possibilities */ + case SQL_BIGINT: /* use Math::BigInt if too large for IV/UV */ +#endif + default: + return -2; /* not a recognised SQL TYPE, value unchanged */ + } + + if (flags & DBIstcf_DISCARD_PV /* caller wants string buffer discarded */ + && SvNIOK(sv) /* we set a numeric value */ + && SvPVX(sv) && SvLEN(sv) /* we have a buffer to discard */ + ) { + Safefree(SvPVX(sv)); + SvPVX(sv) = NULL; + SvPOK_off(sv); + } + return 1; +} + + + /* --- Generic Handle Attributes (for all handle types) --- */ static int Index: DBIXS.h =================================================================== --- DBIXS.h (revision 13478) +++ DBIXS.h (working copy) @@ -392,7 +392,7 @@ struct dbistate_st { -#define DBISTATE_VERSION 94 /* Must change whenever dbistate_t does */ +#define DBISTATE_VERSION 95 /* Must change whenever dbistate_t does */ /* this must be the first member in structure */ void (*check_version) _((const char *name, @@ -417,7 +417,7 @@ SV * (*get_attr_k) _((SV *h, SV *keysv, int dbikey)); AV * (*get_fbav) _((imp_sth_t *imp_sth)); SV * (*make_fdsv) _((SV *sth, const char *imp_class, STRLEN imp_size, const char *col_name)); - int (*bind_as_num) _((int sql_type, int p, int s, int *t, void *v)); + int (*bind_as_num) _((int sql_type, int p, int s, int *t, void *v)); /* XXX deprecated */ I32 (*hash) _((const char *string, long i)); SV * (*preparse) _((SV *sth, char *statement, IV ps_return, IV ps_accept, void *foo)); @@ -432,9 +432,10 @@ IO *logfp_ref; /* DAA keep ptr to filehandle for refcounting */ + int (*cast_svpv) _((SV *h, imp_xxh_t *imp_xxh, SV *sv, int sql_type, U32 flags, void *v)); /* WARNING: Only add new structure members here, and reduce pad2 to keep */ /* the memory footprint exactly the same */ - void *pad2[4]; + void *pad2[3]; }; /* macros for backwards compatibility */ Index: dbixs_rev.h =================================================================== --- dbixs_rev.h (revision 13478) +++ dbixs_rev.h (working copy) @@ -1,4 +1,4 @@ -/* Mon Nov 2 22:44:58 2009 */ -/* Mixed revision working copy (13455M:13465) */ +/* Fri Nov 6 23:01:13 2009 */ +/* Mixed revision working copy (13455M:13466) */ /* Code modified since last checkin */ #define DBIXS_REVISION 13455