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

Reply via email to