Henrik and dbi-dev list,

I've completed the work on supporting binary data types and also long 
data types in DBD::Ingres.  I've split the development into two parts:
implementing the binary data types (and fixing some memory leaks), which
is 0.36.1; and implementing the long data types, 0.36.2.

Doing it this way should hopefully make it easier to understand how the 
long data type support works as it's not mixed in with the other changes.

Mike Battersby <[EMAIL PROTECTED]>        The University of Melbourne
Fetch my pgp key from: http://ariel.ucs.unimelb.edu.au/~mib/pgpkey.txt

I guess this is a pretty big change in some ways, which I'm sorry about.
I hope you don't think I'm overstepping my bounds.  I've tried to keep
the CHANGES file pretty detailed, so it will hopefully help in  
understanding the changes.

I think the code is reasonably clean (though obviously I'm biased), but 
if you have any suggestions or things you'd like changed so this can
become the 0.37 release as soon as possible, I'd be happy to oblige.

Attached are two unified diffs.  One between the vanilla 0.36 and 0.36.1 
which has the binary support, and one between 0.36.1 and 0.36.2 adding
the long type support.  Unfortunately the first diff contains a bunch of
tab->space conversion changes which aren't really code changes.  :(

Please don't hesitate to ask any questions.  I really would like to see a 
0.37 sometime soon.

These patches are also available as:

    http://mib.personal.unimelb.edu.au/DBD-Ingres-0.36-0.36.1.diff
    http://mib.personal.unimelb.edu.au/DBD-Ingres-0.36.1-0.36.2.diff

Best wishes,

 - Mike
diff -uNr DBD-Ingres-0.36/CHANGES DBD-Ingres-0.36.1/CHANGES
--- DBD-Ingres-0.36/CHANGES     2002-11-22 21:05:25.000000000 +1100
+++ DBD-Ingres-0.36.1/CHANGES   2003-07-02 14:22:49.000000000 +1000
@@ -1,4 +1,27 @@
 $Id: //depot/tilpasninger/dbd-ingres/CHANGES#13 $ $DateTime: 2002/11/22 11:11:32 $ 
$Author: ht000 $
+v0.36.1     2003.07.02
+ - add t/datatypes.t for more thorough testing of support for each
+   datatype in type_info_all.
+ - fix memory leak in attribute fetch by decrementing the ref count on
+   the created SV.
+ - increment the ref count when we put an SV into the dbh cache so we
+   don't access freed memory later (never hit because we don't cache
+   our only attribute, AutoCommit, anyway).
+ - don't allocate 1 extra imp_fbh_t in dbd_describe for no reason.
+ - use perl SVs for buffer management in dbd_describe and remove
+   the var_ptr field in the imp_sth_t.
+ - vastly simplify the memory management in bind_ph by using Renew
+   all the time instead of the home-brew memory manager.
+ - use IISQ_VCH_TYPE for all character types
+ - support BYTE and BYTE VARYING data types now that strings use
+   IISQL_VCH_TYPE and don't rely on nul termination.
+ - actually free the allocated statement data in dbd_st_destroy and
+   stop leaking memory like a sieve.
+ - don't access undef bind values (used for binding nulls), so we
+   protect against invalid memory access and stop perl from warning
+   us about referencing them.
+ - fix up some formatting in the POD documentation, re-work a couple
+   of sections and examples, and add some info about binary data types.
 v0.36       2002.11.22
  -bind_param didn't accept type SQL_DATE. I should have been a stringish
   type. Pointed out by Mark Buckle <[EMAIL PROTECTED]>
diff -uNr DBD-Ingres-0.36/dbdimp.psc DBD-Ingres-0.36.1/dbdimp.psc
--- DBD-Ingres-0.36/dbdimp.psc  2002-11-22 21:07:17.000000000 +1100
+++ DBD-Ingres-0.36.1/dbdimp.psc        2003-07-03 16:09:56.000000000 +1000
@@ -48,9 +48,11 @@
             PerlIO_printf(DBILOGFP, ", var: %g\n",
                 *((double*)(var->sqldata)));
             break;
-        case IISQ_CHA_TYPE:
-            PerlIO_printf(DBILOGFP, ", var: '%s'\n",
-                var->sqldata);
+        case IISQ_VCH_TYPE:
+        case IISQ_VBYTE_TYPE:
+            PerlIO_printf(DBILOGFP, ", var: '");
+            PerlIO_write(DBILOGFP, var->sqldata, var->sqllen);
+            PerlIO_printf(DBILOGFP, "'\n");
             break;
         default:
             PerlIO_printf(DBILOGFP, ", unknown type: %d\n",
@@ -88,7 +90,7 @@
             PerlIO_printf(DBILOGFP, " got errtext: '%s'", errbuf);
         sv_setpv(errstr, (char*)errbuf);
         if (dbis->debug >= 3) PerlIO_printf(DBILOGFP, ", returning\n");
-       DBIh_EVENT2(h, ERROR_event, DBIc_ERR(imp_xxh), errstr);
+        DBIh_EVENT2(h, ERROR_event, DBIc_ERR(imp_xxh), errstr);
         return 0;
     } else return 1;
 }
@@ -515,34 +517,34 @@
       int transaction_state, autocommit_state;
       EXEC SQL END DECLARE SECTION;
       EXEC SQL SELECT INT4(DBMSINFO('AUTOCOMMIT_STATE')),
-       INT4(DBMSINFO('TRANSACTION_STATE'))
-       INTO :autocommit_state, :transaction_state;
+        INT4(DBMSINFO('TRANSACTION_STATE'))
+        INTO :autocommit_state, :transaction_state;
       if (dbis->debug >= 3)
-       PerlIO_printf(DBILOGFP,
-                     "DBD::Ingres::dbd_db_STORE(AUTOCOMMIT=%d, 
TRANSACTION=%d)sqlca=%d\n",
-                     autocommit_state, transaction_state, sqlca.sqlcode);
+        PerlIO_printf(DBILOGFP,
+                      "DBD::Ingres::dbd_db_STORE(AUTOCOMMIT=%d, 
TRANSACTION=%d)sqlca=%d\n",
+                      autocommit_state, transaction_state, sqlca.sqlcode);
       if (transaction_state == 0 || on && transaction_state != 0) {
-       /* commit if
-          a) there was no outstanding transaction (clears the way for
-             SET AUTOCOMMIT ...)
-          b) there was an outstanding transaction and autocommit is set to on
-          (defined in the DBI specs). */
-       EXEC SQL COMMIT;
+        /* commit if
+           a) there was no outstanding transaction (clears the way for
+              SET AUTOCOMMIT ...)
+           b) there was an outstanding transaction and autocommit is set to on
+           (defined in the DBI specs). */
+        EXEC SQL COMMIT;
       }
       if (dbis->debug >= 3)
-       PerlIO_printf(DBILOGFP,"DBD::Ingres::dbd_db_STORE(AUTOCOMMIT=");
+        PerlIO_printf(DBILOGFP,"DBD::Ingres::dbd_db_STORE(AUTOCOMMIT=");
       if (on && autocommit_state == 0) {
-       /* switch from off to on */
-       EXEC SQL SET AUTOCOMMIT ON;
-       if (dbis->debug >= 3)
-         PerlIO_printf(DBILOGFP,"ON), sqlcode=%d\n", sqlca.sqlcode);
-       sql_check(dbh);
+        /* switch from off to on */
+        EXEC SQL SET AUTOCOMMIT ON;
+        if (dbis->debug >= 3)
+          PerlIO_printf(DBILOGFP,"ON), sqlcode=%d\n", sqlca.sqlcode);
+        sql_check(dbh);
       } else if (!on && autocommit_state != 0) {
-       /* switch from on to off */
-       EXEC SQL SET AUTOCOMMIT OFF;
-       if (dbis->debug >= 3)
-         PerlIO_printf(DBILOGFP,"OFF), sqlcode=%d\n", sqlca.sqlcode);
-       sql_check(dbh);
+        /* switch from on to off */
+        EXEC SQL SET AUTOCOMMIT OFF;
+        if (dbis->debug >= 3)
+          PerlIO_printf(DBILOGFP,"OFF), sqlcode=%d\n", sqlca.sqlcode);
+        sql_check(dbh);
       } /* else keep state as it is */
       DBIc_set(imp_dbh, DBIcf_AutoCommit, on);
     } else {
@@ -573,18 +575,18 @@
       EXEC SQL END DECLARE SECTION;
       
       EXEC SQL SELECT INT4(DBMSINFO('AUTOCOMMIT_STATE')),
-       INT4(DBMSINFO('TRANSACTION_STATE'))
-       INTO :autocommit_state, :transaction_state;
+        INT4(DBMSINFO('TRANSACTION_STATE'))
+        INTO :autocommit_state, :transaction_state;
       if (dbis->debug >= 3)
-       PerlIO_printf(DBILOGFP,
-                     "DBD::Ingres::dbd_db_FETCH(AUTOCOMMIT=%d, 
TRANSACTION=%d)sqlca=%d\n",
-                     autocommit_state, transaction_state, sqlca.sqlcode);
+        PerlIO_printf(DBILOGFP,
+                      "DBD::Ingres::dbd_db_FETCH(AUTOCOMMIT=%d, 
TRANSACTION=%d)sqlca=%d\n",
+                      autocommit_state, transaction_state, sqlca.sqlcode);
       DBIc_set(imp_dbh, DBIcf_AutoCommit, autocommit_state);
-      retsv = newSVsv(boolSV(autocommit_state));
+      retsv = sv_2mortal(newSVsv(boolSV(autocommit_state)));
       cacheit = FALSE;   /* Don't cache AutoCommit state - some
-                           /* fool^H^H^H^Huser may change it via SQL */
+                            /* fool^H^H^H^Huser may change it via SQL */
       if (transaction_state == 0) {
-       EXEC SQL COMMIT;
+        EXEC SQL COMMIT;
       }
     }
 
@@ -592,22 +594,14 @@
         return Nullsv;
 
     if (cacheit) { /* cache for next time (via DBI quick_FETCH) */
-        hv_store((HV*)SvRV(dbh), key, kl, retsv, 0);
+        if (hv_store((HV*)SvRV(dbh), key, kl, retsv, 0) != NULL)
+                SvREFCNT_inc(retsv);
     }
     return (retsv);
 }
 
 /* === DBD_ST ======================================================= */
 
-int hash(char *s) {
-    int h = 0;
-    while (*s) {
-        h += (unsigned char)(*s);
-        s++;
-    }
-    return h;
-}
-
 int
 dbd_st_prepare(sth, imp_sth, statement, attribs)
     SV* sth;
@@ -646,10 +640,6 @@
         if (dbis->debug >= 4)
             PerlIO_printf(DBILOGFP, "Statement3 = %s \n", p);
         generate_statement_name(&imp_sth->st_num, name);
-        /*imp_sth->st_num = hash(statement);
-        if (dbis->debug >= 4)
-            PerlIO_printf(DBILOGFP, "Num = %d \n", imp_sth->st_num);
-        sprintf(name, "s%d", imp_sth->st_num);*/
         if (dbis->debug >= 4)
             PerlIO_printf(DBILOGFP, "Name = %s \n", name);
         n = name + strlen(name); /* points at \0 at end */
@@ -719,7 +709,7 @@
           
           for (param_no=0; param_no < param_max; param_no++) {
             var = &imp_sth->ph_sqlda.sqlvar[param_no];
-            var->sqldata = (char *) &sv_undef;
+            var->sqldata = 0;
             var->sqllen  = 0;
           }
         }
@@ -734,7 +724,21 @@
     return 1;
 }
 
-int
+/*
+ * dbd_describe is called when a statement is executed that will fetch
+ * fields, and is responsible for allocating memory in the individual
+ * imp_fbh_t structures and setting up the IISQLVAR stuff so that
+ * Ingres can fetch the data.
+ *
+ * When destroying an sth we need to free all this stuff if
+ * imp_sth->done_desc != 0.
+ *
+ * This uses perl SVs to hold the string data types because it's
+ * convenient to use perl's memory management which keeps track of
+ * lengths etc.
+ *
+ */
+static int
 dbd_describe(sth, imp_sth)
      SV *sth;
      imp_sth_t *imp_sth;
@@ -755,7 +759,7 @@
     imp_sth->done_desc = 1;
 
     /* describe the statement and allocate bufferspace */
-    Newz(42, imp_sth->fbh, DBIc_NUM_FIELDS(imp_sth) + 1, imp_fbh_t);
+    Newz(42, imp_sth->fbh, DBIc_NUM_FIELDS(imp_sth), imp_fbh_t);
     for (i = 0; i < sqlda->sqld; i++)
     {
         imp_fbh_t *fbh = &imp_sth->fbh[i];
@@ -768,40 +772,45 @@
         if (dbis->debug >= 3)
             PerlIO_printf(DBILOGFP, "  field %d, type=%d\n", 1, var->sqltype);
             
+        /* We use perl SVs as buffers, but they are not real SVs and we
+         * don't bother setting all the type flags etc., we just use them
+         * for the memory management.  These SVs should not be used as
+         * SVs and definitely not passed back to perl at any stage. */
+
         switch (var->sqltype) {
         case IISQ_INT_TYPE:
+            var->sqltype = IISQ_INT_TYPE;
             fbh->len = var->sqllen = sizeof(int);
             strcpy(fbh->type, "d");
-            Newz(42, fbh->var_ptr.iv, 1, int);
-            var->sqldata = (char*)fbh->var_ptr.iv;
-            fbh->sv = NULL;
+            fbh->sv = newSV((STRLEN)fbh->len);
+            SvOK_off(fbh->sv);
+            var->sqldata = SvPVX(fbh->sv);
             break;
         case IISQ_MNY_TYPE: /* money - treat as float8 */
         case IISQ_DEC_TYPE: /* decimal */
         case IISQ_FLT_TYPE:
-            fbh->len = var->sqllen = sizeof(double);
             var->sqltype = IISQ_FLT_TYPE;
+            fbh->len = var->sqllen = sizeof(double);
             strcpy(fbh->type, "f");
-            Newz(42, fbh->var_ptr.nv, 1, double);
-            var->sqldata = (char*)fbh->var_ptr.nv;
-            fbh->sv = NULL;
+            fbh->sv = newSV((STRLEN)fbh->len);
+            SvOK_off(fbh->sv);
+            var->sqldata = SvPVX(fbh->sv);
             break;
         case IISQ_DTE_TYPE:
             var->sqllen = IISQ_DTE_LEN;
             /* FALLTHROUGH */
         case IISQ_CHA_TYPE:
-        case IISQ_VCH_TYPE:
+        case IISQ_BYTE_TYPE:
         case IISQ_TXT_TYPE:
-            var->sqltype = IISQ_CHA_TYPE;
+        case IISQ_VCH_TYPE:
+        case IISQ_VBYTE_TYPE:
+            var->sqltype = IISQ_VCH_TYPE;
+            fbh->len = var->sqllen;
             strcpy(fbh->type, "s");
-            /* set up bufferspace */
-            fbh->len = var->sqllen+1;
-            fbh->sv = newSV((STRLEN)fbh->len);
-            (void)SvUPGRADE(fbh->sv, SVt_PV);
-            SvREADONLY_on(fbh->sv);
-            (void)SvPOK_only(fbh->sv);
-            var->sqldata = (char*)SvPVX(fbh->sv);
-            fbh->var_ptr.pv = var->sqldata;
+            /* Need an extra byte for null termination later. */
+            fbh->sv = newSV((STRLEN)fbh->len + 1 + sizeof(short));
+            SvOK_off(fbh->sv);
+            var->sqldata = SvPVX(fbh->sv);
             break;
         default:        /* oh dear! */
             croak("DBD::Ingres: field %d has unsupported type %d\n",
@@ -831,7 +840,10 @@
     return 1;
 }
 
-
+/*
+ * Note that this allocates var->sqldata which needs to be freed.  Also,
+ * we cannot reference a value that isn't SvOK or we get a warning.
+ */
 int
 dbd_bind_ph (sth, imp_sth, param, value, sql_type, attribs, is_inout, maxlen)
     SV *sth;
@@ -846,7 +858,6 @@
     int param_no;
     int type = 0;  /* 1: int, 2: float, 3: string */
     IISQLVAR* var;
-    int* buf;
     
     if (SvNIOK(param) ) {   /* passed as a number   */
         param_no = (int)SvIV(param);
@@ -866,7 +877,7 @@
         imp_sth->ph_sqlda.sqld = param_no;
 
     var = &imp_sth->ph_sqlda.sqlvar[param_no-1];
-    buf = ((int *) var->sqldata)-1;
+
     if (sql_type) {
         switch (sql_type) {
         case SQL_INTEGER:
@@ -879,12 +890,14 @@
         case SQL_DECIMAL:
             type = 2; break;
         case SQL_CHAR:
+        case SQL_BINARY:
         case SQL_VARCHAR:
-       case SQL_DATE:
+        case SQL_VARBINARY:
+        case SQL_DATE:
             type = 3; break;
-       default:
-           croak("DBD::Ingres::bind_param: Unknown TYPE: %d, param_no %d",
-               sql_type, param_no);
+        default:
+            croak("DBD::Ingres::bind_param: Unknown TYPE: %d, param_no %d",
+                sql_type, param_no);
         }
     } else if (!SvOK(value)) { /* NULL */
         croak(
@@ -898,81 +911,60 @@
     }
     if (dbis->debug >= 3)
         PerlIO_printf(DBILOGFP, "  type=%d\n", type);
+
+    var->sqlind  = 0;
     switch (type) {
-      /* Poor mans memory management: We store the actual length of
-         the buffer one int below var->sqldata. */
-    case 1: {/* int */
-        if (var->sqldata == (char *)&sv_undef) {
-            Newz(42, buf, 2, int);
-            *buf = 2;
-            var->sqldata = (char*) (buf+1);
-        } else if (*buf < 2) {
-            Renew(buf, 2, int);
-            *buf = 2;
-            var->sqldata = (char*) (buf+1);
-        }
-        buf[1]       = (int)SvIV(value);
-        var->sqlind  = 0;
-        var->sqllen  = 4;
+        /* This used to have a kind of poor mans memory management, but
+         * it seems using Renew() each time to change the size of
+         * the buffer is just as fast and much easier because you
+         * don't have to worry about the 0 case. */
+    case 1: /* int */
         var->sqltype = IISQ_INT_TYPE;
-        break; }
-    case 2: {/* float */
-        int    need_int = (sizeof(double) + sizeof(int)-1)/sizeof(int) +1;
-        double ptr;
-
-        if (var->sqldata == (char *)&sv_undef) {
-            Newz(42, buf, need_int, int);
-            *buf = need_int;
-            var->sqldata = (char*) (buf+1);
-        } else if (*buf < need_int) {
-            Renew(buf, need_int, int);
-            *buf = need_int;
-            var->sqldata = (char*) (buf+1);
-        }
-        ptr = (double)SvNV(value);
-        /* Double probably not aligned properly: may cause alignment
-           error in Ingres library? Hmm: works for Open Ingres 1.2 and
-           Ingres 6.4. Should we spend 8 bytes for the length tag? */
-        Move(&ptr, buf+1, 1, double); 
-        var->sqlind  = 0;
-        var->sqllen = 8;
+        var->sqllen = sizeof(int);
+        Renew(var->sqldata, var->sqllen, char);
+        if (SvOK(value))
+            *(int *)var->sqldata = (int)SvIV(value);
+        break;
+    case 2: /* float */
         var->sqltype = IISQ_FLT_TYPE;
-        break; }
+        var->sqllen = sizeof(double);
+        Renew(var->sqldata, var->sqllen, char);
+        if (SvOK(value))
+            *(double *)var->sqldata = (double)SvNV(value);
+        break;
     case 3: {/* string */
-        STRLEN strlen;
-        char   *string  = SvPV(value,strlen);
-        int    need_int = ((strlen+1)*sizeof(char) + sizeof(int)-1) /
-                                      sizeof(int) + 1;
-        
-        if (var->sqldata == (char *)&sv_undef) { /* initital allocation */
-            Newz(42, buf, need_int, int);
-            *buf = need_int;
-            var->sqldata = (char *) (buf+1);
+        STRLEN len;
+        char *string;
+
+        var->sqltype = IISQ_VCH_TYPE;
+        if (SvOK(value)) {
+            string = SvPV(value, len);
         } else {
-            buf = ((int *) var->sqldata)-1; 
-            if (*buf < need_int) { /* need to reallocate? */
-                Renew(buf, need_int, int);
-                *buf = need_int;
-                var->sqldata = (char *) (buf+1);
-            }
+            string = 0;
+            len = 0;
         }
-        Move(string, var->sqldata, strlen+1, char);
-        var->sqlind  = 0;
-        var->sqllen  = strlen;
-        var->sqltype = IISQ_CHA_TYPE;
+        var->sqllen = len;
+        Renew(var->sqldata, len + sizeof(short), char);
+        if (SvOK(value)) {
+            *(short *)var->sqldata = (short)len;
+            Copy(string, var->sqldata + sizeof(short), len, char);
+        }
+
+        /* This works around a bug in Ingres where inserting byte fields
+         * as IISQ_VCH_TYPE fails if the strings ends in a \0.  It works
+         * if we use VBYTE_TYPE, but we can't default to that as you
+         * cannot use it to insert dates. */
+        if (sql_type == SQL_BINARY)
+            var->sqltype = IISQ_VBYTE_TYPE;
+
         break; }
     }
     if (!SvOK(value)) {
+        /* !SvOK means binding a NULL */
         if (dbis->debug >= 3) PerlIO_printf(DBILOGFP, "bind(NULL)");
-        if (var->sqldata == (char *)&sv_undef) {
-          Newz(42, buf, 2, int);
-          *buf = 2;
-          var->sqldata = (char*) (buf+1);
-        } else if (*buf < 2) {
-          Renew(buf, 2, int);
-          *buf = 2;
-          var->sqldata = (char*) (buf+1);
-        }
+
+        /* We don't renew here, on the assumption that all of the
+         * data types above allocate space >= sizeof(short). */
         var->sqlind = (short*)var->sqldata; /* cheat a little - use
                                             ** var->sqldata as indicator
                                             ** variable as well - the
@@ -981,7 +973,8 @@
         *var->sqlind = -1;
         var->sqltype = -var->sqltype;
     }
-    if (dbis->debug >= 3) dump_sqlda(&imp_sth->ph_sqlda);
+    if (dbis->debug >= 3)
+        dump_sqlda(&imp_sth->ph_sqlda);
     return 1;
 }
 
@@ -1030,38 +1023,38 @@
         }
         return sql_check(sth) ? sqlca.sqlerrd[2] : -2;
     } else {
-       int is_readonly;
+        int is_readonly;
         /* select statement: open a cursor */
         EXEC SQL DECLARE :name CURSOR FOR :name;
-       /* 0.23 open readonly unless an "FOR UPDATE"- clause is found in */
-       /* select statement. This is done in Ingres.pm in prepare, and */
-       /* is stored in the private variable $sth->{ing_readonly}. */
-       {
-         SV** svp;
-         if ( (svp = hv_fetch((HV*)SvRV(sth), "ing_readonly", 12, 0)) != NULL
-             && SvTRUE(*svp)) is_readonly = 1;
-         else is_readonly = 0;
-       }
+        /* 0.23 open readonly unless an "FOR UPDATE"- clause is found in */
+        /* select statement. This is done in Ingres.pm in prepare, and */
+        /* is stored in the private variable $sth->{ing_readonly}. */
+        {
+          SV** svp;
+          if ( (svp = hv_fetch((HV*)SvRV(sth), "ing_readonly", 12, 0)) != NULL
+              && SvTRUE(*svp)) is_readonly = 1;
+          else is_readonly = 0;
+        }
         if (dbis->debug >= 2)
             PerlIO_printf(DBILOGFP,
                 "DBD::Ingres::dbd_st_execute - cursor %s - param=%d %sreadonly\n",
                 name, imp_sth->ph_sqlda.sqld, is_readonly ? "" : "NOT ");
 
         if (is_readonly) {
-               if (imp_sth->ph_sqlda.sqld > 0) {
-                   EXEC SQL OPEN :name FOR READONLY
-                        USING DESCRIPTOR &imp_sth->ph_sqlda;
-               } else {
-                   EXEC SQL OPEN :name FOR READONLY;
-               }
-       } else {
-               if (imp_sth->ph_sqlda.sqld > 0) {
-                   EXEC SQL OPEN :name
-                        USING DESCRIPTOR &imp_sth->ph_sqlda;
-               } else {
-                   EXEC SQL OPEN :name;
-               }
-       }
+                if (imp_sth->ph_sqlda.sqld > 0) {
+                    EXEC SQL OPEN :name FOR READONLY
+                         USING DESCRIPTOR &imp_sth->ph_sqlda;
+                } else {
+                    EXEC SQL OPEN :name FOR READONLY;
+                }
+        } else {
+                if (imp_sth->ph_sqlda.sqld > 0) {
+                    EXEC SQL OPEN :name
+                         USING DESCRIPTOR &imp_sth->ph_sqlda;
+                } else {
+                    EXEC SQL OPEN :name;
+                }
+        }
         if (!sql_check(sth)) return -2;
         DBIc_ACTIVE_on(imp_sth);
         return -1 /* Unknown number of rows */;
@@ -1126,33 +1119,35 @@
             case 'd':
                 sv_setiv(sv, (IV)*(int*)var->sqldata);
                 if (dbis->debug >= 3)
-                    PerlIO_printf(DBILOGFP, "Int: %ld %d %d\n",
-                          SvIV(sv), fbh->var_ptr.iv, *(int*)var->sqldata);
+                    PerlIO_printf(DBILOGFP, "Int: %ld %d\n",
+                          SvIV(sv), *(int*)var->sqldata);
                 break;
             case 'f':
                 sv_setnv(sv, *(double*)var->sqldata);
                 if (dbis->debug >= 3)
                     PerlIO_printf(DBILOGFP, "Double: %lf\n", SvNV(sv));
                 break;
-            case 's':
-                SvCUR(fbh->sv) = fbh->len;
-                SvPVX(fbh->sv)[fbh->len-1] = 0;
+            case 's': {
+                short len = *(short *)var->sqldata;
+                char *buf = var->sqldata + sizeof(short);
                 /* strip trailing blanks */
                 if ((fbh->origtype == IISQ_DTE_TYPE ||
                      fbh->origtype == IISQ_CHA_TYPE ||
                      fbh->origtype == IISQ_TXT_TYPE)
                  && DBIc_has(imp_sth, DBIcf_ChopBlanks)) {
-                    for (ch = fbh->len - 2;
-                         SvPVX(fbh->sv)[ch] == ' ';
-                         --ch)
-                             SvPVX(fbh->sv)[ch] = 0;
+                    while (len > 0 && buf[len-1] == ' ') {
+                        len--;
+                    }
                 }
-                sv_setsv(sv, fbh->sv);
-                SvCUR(sv) = strlen(SvPVX(sv));
-                if (dbis->debug >= 3)
-                    PerlIO_printf(DBILOGFP, "Text: '%s', Chop: %d\n",
-                        SvPVX(sv), DBIc_has(imp_sth, DBIcf_ChopBlanks));
-                break;
+                buf[len] = '\0';
+                sv_setpvn(sv, buf, len);
+                if (dbis->debug >= 3) {
+                    PerlIO_printf(DBILOGFP, "Text: '");
+                    PerlIO_write(DBILOGFP, buf, len);
+                    PerlIO_printf(DBILOGFP, "', Chop: %d\n",
+                        DBIc_has(imp_sth, DBIcf_ChopBlanks));
+                }
+                break; }
             default:
                 croak("DBD::Ingres: wierd field-type '%s' in field no. %d?\n",
                             fbh->type, i);
@@ -1223,6 +1218,22 @@
 
     /* XXX free contents of imp_sth here */
 
+    for (i=0; i<DBIc_NUM_PARAMS(imp_sth); ++i) {
+        IISQLVAR *var = &imp_sth->ph_sqlda.sqlvar[i];
+        if (var->sqldata != 0) {
+            Safefree(var->sqldata);
+        }
+    }
+
+    if (imp_sth->done_desc) {
+        IISQLDA* sqlda = &imp_sth->sqlda;
+        for (i=0; i<sqlda->sqld; i++) {
+            imp_fbh_t *fbh = &imp_sth->fbh[i];
+            SvREFCNT_dec(fbh->sv);
+        }
+        Safefree(imp_sth->fbh);
+    }
+
     DBIc_IMPSET_off(imp_sth);
 }
 
@@ -1324,6 +1335,12 @@
             case IISQ_VCH_TYPE:
                 type = SQL_VARCHAR;
                 break;
+            case IISQ_BYTE_TYPE:
+                type = SQL_BINARY;
+                break;
+            case IISQ_VBYTE_TYPE:
+                type = SQL_VARBINARY;
+                break;
             default:        /* oh dear! */
                 type = 0;
                 break;
@@ -1376,6 +1393,8 @@
             case IISQ_CHA_TYPE:
             case IISQ_TXT_TYPE:
             case IISQ_VCH_TYPE:
+            case IISQ_BYTE_TYPE:
+            case IISQ_VBYTE_TYPE:
                 len = imp_sth->fbh[i].origlen;
                 break;
             default:        /* oh dear! */
diff -uNr DBD-Ingres-0.36/Ingres.pm DBD-Ingres-0.36.1/Ingres.pm
--- DBD-Ingres-0.36/Ingres.pm   2002-11-22 21:06:09.000000000 +1100
+++ DBD-Ingres-0.36.1/Ingres.pm 2003-07-02 16:25:10.000000000 +1000
@@ -13,16 +13,17 @@
 
 =head1 SYNOPSIS
 
-    $dbh = DBI->connect("DBI:Ingres:$dbname", $user, $options, {AutoCommit=>0})
-    $sth = $dbh->prepare($statement)
-    $sth = $dbh->prepare($statement, {ing_readonly=>1})
-    $sth->execute
-    @row = $sth->fetchrow
-    $sth->finish
-    $dbh->commit
-    $dbh->rollback
-    $dbh->disconnect
-    and many more
+  $dbh = DBI->connect("DBI:Ingres:$dbname", $user, $options, {AutoCommit=>0})
+  $sth = $dbh->prepare($statement)
+  $sth = $dbh->prepare($statement, {ing_readonly=>1})
+  $sth->execute
+  @row = $sth->fetchrow
+  $sth->finish
+  $dbh->commit
+  $dbh->rollback
+  $dbh->disconnect
+
+  ... and many more
 
 =cut
 
@@ -34,7 +35,7 @@
     use DynaLoader ();
     @ISA = qw(DynaLoader);
 
-    $VERSION = '0.36';
+    $VERSION = '0.36.1';
     my $Revision = substr(q$Change: 10694 $, 8)/100;
 
     bootstrap DBD::Ingres $VERSION;
@@ -180,22 +181,36 @@
                 MINIMUM_SCALE   => 13,
                 MAXIMUM_SCALE   => 14,
            },
-           [ 'SHORT',   DBI::SQL_SMALLINT, undef, "","",  undef,
-               1, 0, 2, 0, 0,0,undef,0,0 ],
-           [ 'INTEGER', DBI::SQL_INTEGER, undef, "","",   "size=1,2,4",
-               1, 0, 2, 0, 0,0,undef,0,0 ],
-           [ 'MONEY',   DBI::SQL_DECIMAL, undef, "","",   undef,
-               1, 0, 2, 0, 1,0,undef,0,0 ],
-           [ 'FLOAT',   DBI::SQL_INTEGER, undef, "","",   "size=4,8",
-               1, 0, 2, 0, 0,0,undef,0,0 ],
-           [ 'DATE',    DBI::SQL_DATE,    undef, "'","'", undef,
-               1, 0, 3, 0, 0,0,undef,0,0 ],
-           [ 'DECIMAL', DBI::SQL_DECIMAL, undef, "","",   "precision,scale",
-               1, 0, 2, 0, 0,0,undef,0,0 ],
-           [ 'VARCHAR', DBI::SQL_VARCHAR, undef, "'","'", "max length",
-               1, 1, 3, 0, 0,0,undef,0,0 ],
-           [ 'CHAR',    DBI::SQL_CHAR,    undef, "'","'", "length",
-               1, 1, 3, 0, 0,0,undef,0,0 ],
+           [ 'SMALLINT',     DBI::SQL_SMALLINT,
+             undef, "","",  undef,
+             1, 0, 2, 0, 0, 0, undef, 0, 0 ],
+           [ 'INTEGER',      DBI::SQL_INTEGER,
+             undef, "","",   "size=1,2,4",
+             1, 0, 2, 0, 0 ,0 ,undef ,0 ,0 ],
+           [ 'MONEY',        DBI::SQL_DECIMAL,
+             undef, "","",   undef,
+             1, 0, 2, 0, 1, 0, undef, 0, 0 ],
+           [ 'FLOAT',        DBI::SQL_DOUBLE,
+             undef, "","",   "size=4,8",
+             1, 0, 2, 0, 0, 0, undef, 0, 0 ],
+           [ 'DATE',         DBI::SQL_DATE,   
+             undef, "'","'", undef,
+             1, 0, 3, 0, 0, 0, undef, 0, 0 ],
+           [ 'DECIMAL',      DBI::SQL_DECIMAL,
+             undef, "","",   "precision,scale",
+             1, 0, 2, 0, 0, 0, undef, 0, 0 ],
+           [ 'VARCHAR',      DBI::SQL_VARCHAR,
+             undef, "'","'", "max length",
+             1, 1, 3, 0, 0, 0, undef, 0, 0 ],
+           [ 'BYTE VARYING', DBI::SQL_VARBINARY,
+             undef, "'","'", "max length",
+             1, 1, 3, 0, 0, 0, undef, 0, 0 ],
+           [ 'CHAR',         DBI::SQL_CHAR,   
+             undef, "'","'", "length",
+             1, 1, 3, 0, 0, 0, undef, 0, 0 ],
+           [ 'BYTE',         DBI::SQL_BINARY, 
+             undef, "'","'", "length",
+             1, 1, 3, 0, 0, 0, undef, 0, 0 ],
        ];
        return $ti;
     }
@@ -211,22 +226,73 @@
 
 =head1 DESCRIPTION
 
-DBD::Ingres is an extension to Perl which allows access to Ingres
-databases. It is built on top of the standard DBI extension an
-implements the methods that DBI require.
+DBD::Ingres is a database driver for the perl DBI system that allows
+access to Ingres databases. It is built on top of the standard DBI
+extension and implements the methods that DBI requires.
 
 This document describes the differences between the "generic" DBD and
 DBD::Ingres.
 
-=head2 Extensions/Changes
+=head1 EXTENSIONS/CHANGES
+
+=head2 Connect
+
+  DBI->connect("DBI:Ingres:dbname[;options]");
+  DBI->connect("DBI:Ingres:dbname[;options]", user [, password]);
+  DBI->connect("DBI:Ingres:dbname[;options]", user [, password], \%attr);
+
+To use DBD::Ingres call C<connect> specifying a I<datasource> option beginning
+with I<"DBI:Ingres:">, followed by the database instance name and
+optionally a semi-colon followed by any Ingres connect options.
+
+Options must be given exactly as they would be given in an ESQL-connect
+statement, i.e., separated by blanks.
+
+The connect call will result in a connect statement like:
+
+  CONNECT dbname IDENTIFIED BY user PASSWORD password OPTIONS=options
+
+E.g.,
 
 =over 4
 
-=item returned types
+=item *
+
+local database
+
+  DBI->connect("DBI:Ingres:mydb", "me", "mypassword")
+
+=item *
+
+with options and no password
+
+  DBI->connect("DBI:Ingres:mydb;-Rmyrole/myrolepassword", "me")
+
+=item *
+
+Ingres/Net database
+
+  DBI->connect("DBI:Ingres:thatnode::thisdb;-xw -l", "him", "hispassword")
+
+=back
+
+and so on.
+
+=head2 AutoCommit Defaults to ON
+
+B<Important>: The DBI spec defines that AutoCommit is B<ON> after connect.
+This is the opposite of the normal Ingres default.
+
+It is recommended that the C<connect> call ends with the attributes
+C<{ AutoCommit =E<gt> 0 }>.
+
+=head2 Returned Types
 
 The DBI docs state that:
 
-=over 2
+=over 4
+
+=item *
 
 Most data is returned to the perl script as strings (null values are
 returned as undef).  This allows arbitrary precision numeric data to be
@@ -239,18 +305,24 @@
 
 Data is returned as it would be to an embedded C program:
 
-=over 2
+=over 4
+
+=item *
 
 Integers are returned as integer values (IVs in perl-speak).
 
+=item *
+
 Floats and doubles are returned as numeric values (NVs in perl-speak).
 
+=item *
+
 Dates, moneys, chars, varchars and others are returned as strings
 (PVs in perl-speak).
 
 =back
 
-=item get_dbevent
+=head2 get_dbevent
 
 This non-DBI method calls C<GET DBEVENT> and C<INQUIRE_INGRES> to
 fetch a pending database event. If called without argument a blocking
@@ -271,67 +343,20 @@
     printf "%-20s = '%s'\n", $_, $event_ref->{$_};
   }
 
-=item connect
-
-    connect(dbi:Ingres:dbname[;options] [, user [, password]])
-
-Options to the connection are passed in the datasource
-argument. This argument should contain the database name possibly
-followed by a semicolon and the database options.
-
-Options must be given exactly as they would be given an ESQL-connect
-statement, ie. separated by blanks.
-
-The connect call will result in a connect statement like:
-
-    CONNECT dbname IDENTIFIED BY user PASSWORD password OPTIONS=options
-
-Eg.
+=head2 do
 
-=over 4
-
-=item local database
-
-       connect("mydb", "me", "mypassword")
-
-=item with options and no password
-
-       connect("mydb;-Rmyrole/myrolepassword", "me")
-
-=item Ingres/Net database
-
-       connect("thatnode::thisdb;-xw -l", "him", "hispassword")
-
-=back
-
-and so on.
-
-B<Important>: The DBI spec defines that AutoCommit is B<ON> after connect.
-This is the opposite of the normal Ingres default.
-
-It is recommended that the C<connect> call ends with the attributes
-C<{ AutoCommit => 0 }>.
-
-If you dont want to check for errors after B<every> call use 
-C<{ AutoCommit => 0, RaiseError => 1 }> instead. This will C<die> with
-an error message if any DBI call fails.
-
-=item do
-
-    $dbh->do
-
-This is implemented as a call to 'EXECUTE IMMEDIATE' with all the
+$dbh->do is implemented as a call to 'EXECUTE IMMEDIATE' with all the
 limitations that this implies.
 
-Placeholders and binding is not supported with C<$dbh-E<gt>do>.
+Placeholders and binding are not supported with C<$dbh-E<gt>do>.
 
-=item ChopBlanks and binary data
+=head2 Binary Data
 
-Fetching of binary data is not possible if ChopBlanks is set. ChopBlanks
-uses a \0 character internally to mark the end of the field, so returned
-will be truncated at the first \0 character.
+Fetching binary data from char and varchar fields is not guaranteed
+to work, but probably will most of the time.  Use 'BYTE' or
+'BYTE VARYING' data types in your database for full binary data support.
 
-=item ing_readonly
+=head2 ing_readonly
 
 Normally cursors are declared C<READONLY> 
 to increase speed. READONLY cursors don't create
@@ -352,42 +377,43 @@
 
 =back
 
-Eg.
+E.g.,
 
-   $sth = $dbh->prepare("select ....", {ing_readonly => 0});
+  $sth = $dbh->prepare("select ....", {ing_readonly => 0});
 
 will be opened for update, as will
 
-   $sth = $dbh->prepare("select .... for direct update of ..")
+  $sth = $dbh->prepare("select .... for direct update of ..")
 
 while
 
-   $sth = $dbh->prepare("select .... for direct update of ..",
-                { ing_readonly => 1} );
+  $sth = $dbh->prepare("select .... for direct update of ..",
+                       { ing_readonly => 1} );
 
 will be opened C<FOR READONLY>.
 
 When you wish to actually do the update, where you would normally put the
 cursor name, you put:
 
-    $sth->{CursorName}
+  $sth->{CursorName}
 
 instead,  for example:
 
-    $sth = $dbh->prepare("select a,b,c from t for update of b");
-    $sth->execute;
-    $row = $sth->fetchrow_arrayref;
-    $dbh->do("update t set b='1' where current of $sth->{CursorName}");
+  $sth = $dbh->prepare("select a,b,c from t for update of b");
+  $sth->execute;
+  $row = $sth->fetchrow_arrayref;
+  $dbh->do("update t set b='1' where current of $sth->{CursorName}");
 
 Later you can reexecute the statement without the update-possibility by doing:
 
-    $sth->{ing_readonly} = 1;
-    $sth->execute;
+  $sth->{ing_readonly} = 1;
+  $sth->execute;
 
 and so on. B<Note> that an C<update> will now cause an SQL error.
 
-In fact the "FOR UPDATE" seems to be optional, ie you can update cursors even 
-if their SELECT statements do not contain a C<for update> part.
+In fact the "FOR UPDATE" seems to be optional, i.e., you can update
+cursors even if their SELECT statements do not contain a C<for update>
+part.
 
 If you wish to update such a cursor you B<must> include the C<ing_readonly>
 attribute.
@@ -395,19 +421,16 @@
 B<NOTE> DBD::Ingres version later than 0.19_1 have opened all cursors for
 update. This change breaks that behaviour. Sorry if this breaks your code.
 
-=item ing_statement
-
-    $sth->{ing_statement}             ($)
+=head2 ing_statement
 
-Contains the text of the SQL-statement. Used mainly for debugging.
+This has long been deprecated in favor of C<$sth-E<gt>{Statement}>,
+which is a DBI standard.
 
-This is B<exactly> the same as the new and DBI-supported
-C<$sth-E<gt>{Statement}>
-and B<the use of C<$sth-E<gt>{ing_statement}> is depreceated>.
+$sth->{ing_statement} provides access to the SQL statement text.
 
-=item ing_types
+=head2 ing_types
 
-    $sth->{ing_types}              (\@)
+  $sth->{ing_types}              (\@)
 
 Returns an array of the "perl"-type of the return fields of a select
 statement.
@@ -418,7 +441,7 @@
 
 =item 'i': integer
 
-All integer types, ie. int1, int2 and int4.
+All integer types, i.e., int1, int2 and int4.
 
 These values are returned as integers. This should not cause loss of
 precision as the internal Perl integer is at least 32 bit long.
@@ -434,47 +457,65 @@
 
 =item 's': string
 
-All other supported types, ie. char, varchar, text, date etc.
+All other supported types, i.e., char, varchar, text, date etc.
 
 =back
 
-=item TYPE
+=head2 Ingres Types and their DBI Equivalents
 
-    $sth->TYPE                       (\@)
+  $sth->TYPE                       (\@)
 
-See the DBI-docs for a description.
-
-The ingres translations are:
+See L<DBI> for a description.  The Ingres translations are:
 
 =over 4
 
-=item short -> DBI::SQL_SMALLINT
+=item *
+
+short -> DBI::SQL_SMALLINT
 
-=item int -> DBI::SQL_INTEGER
+=item *
+
+int -> DBI::SQL_INTEGER
+
+=item *
+
+float -> DBI::SQL_DOUBLE
+
+=item *
+
+double -> DBI::SQL_DOUBLE
+
+=item *
+
+char -> DBI::SQL_CHAR
+
+=item *
+
+text -> DBI::SQL_CHAR
 
-=item float -> DBI::SQL_DOUBLE
+=item *
 
-=item double -> DBI::SQL_DOUBLE
+varchar -> DBI::SQL_VARCHAR
 
-=item char -> DBI::SQL_CHAR
+=item *
 
-=item text -> DBI::SQL_CHAR
+date -> DBI::SQL_DATE
 
-=item varchar -> DBI::SQL_VARCHAR
+=item *
 
-=item date -> DBI::SQL_DATE
+money -> DBI::SQL_DECIMAL
 
-=item money -> DBI::SQL_DECIMAL
+=item *
 
-=item decimal -> DBI::SQL_DECIMAL
+decimal -> DBI::SQL_DECIMAL
 
 =back
 
 Have I forgotten any?
 
-=item ing_lengths
+=head2 ing_lengths
 
-    $sth->{ing_lengths}              (\@)
+  $sth->{ing_lengths}              (\@)
 
 Returns an array containing the lengths of the fields in Ingres, eg. an
 int2 will return 2, a varchar(7) 7 and so on.
@@ -482,12 +523,12 @@
 Note that money and date fields will have length returned as 0.
 
 C<$sth-E<gt>{SqlLen}> is the same as C<$sth-E<gt>{ing_lengths}>,
-but the use of it is depreceated.
+but the use of it is deprecated.
 
-See also the C$sth-E<gt>{PRECISION}> field in the DBI docs. This returns
+See also the C<$sth-E<gt>{PRECISION}> field in the DBI docs. This returns
 a 'reasonable' value for all types including money and date-fields.
 
-=item ing_sqltypes
+=head2 ing_sqltypes
 
     $sth->{ing_sqltypes}              (\@)
 
@@ -499,24 +540,19 @@
 
 See also the C$sth-E<gt>{TYPE}> field in the DBI docs.
 
-=back
-
-=head2 Not implemented
-
-=over 4
+=head1 FEATURES NOT IMPLEMENTED
 
-=item state
+=head2 state
 
-    $h->state                (undef)
+  $h->state                (undef)
 
-SQLSTATE is not implemented yet. It is planned for the (not so) near
-future.
+SQLSTATE is not implemented.
 
-=item disconnect_all
+=head2 disconnect_all
 
 Not implemented
 
-=item commit and rollback invalidates open cursors
+=head2 commit and rollback invalidate open cursors
 
 DBD::Ingres should warn when a commit or rollback is isssued on a $dbh
 with open cursors.
@@ -525,7 +561,7 @@
 probably be done in the DBI-layer as other drivers will have the same
 problems).
 
-After a commit or rollback the cursors are all ->finish'ed, ie. they
+After a commit or rollback the cursors are all ->finish'ed, i.e., they
 are closed and the DBI/DBD will warn if an attempt is made to fetch
 from them.
 
@@ -533,11 +569,11 @@
 
 This is needed for
 
-=item Cached statements
+=head2 Cached statements
 
 A new feature in DBI that is not implemented in DBD::Ingres.
 
-=item Procedure calls
+=head2 Procedure calls
 
 It is not possible to call database procedures from DBD::Ingres.
 
@@ -546,7 +582,7 @@
 DB::Ingres-specific function (like L<get_event>) if the need arises and
 someone is willing to do it.
 
-=item OpenIngres new features
+=head2 OpenIngres new features
 
 The new features of OpenIngres are not (yet) supported in DBD::Ingres.
 
@@ -554,15 +590,13 @@
 
 Support will be added when the need arises - if you need it you add it ;-)
 
-=back
-
 =head1 NOTES
 
 I wonder if I have forgotten something?
 
 =head1 SEE ALSO
 
-The DBI documentation in L<DBI>.
+The DBI documentation in L<DBI> and L<DBI::DBD>.
 
 =head1 AUTHORS
 
diff -uNr DBD-Ingres-0.36/Ingres.sh DBD-Ingres-0.36.1/Ingres.sh
--- DBD-Ingres-0.36/Ingres.sh   2003-06-21 23:31:27.000000000 +1000
+++ DBD-Ingres-0.36.1/Ingres.sh 2003-06-30 15:44:49.000000000 +1000
@@ -52,7 +52,7 @@
     int        st_num;      /* statement number */
     int        done_desc;   /* have we described this sth yet ?        */
     IISQLDA    ph_sqlda;    /* descriptor for placeholders */
-    imp_fbh_t *fbh;            /* array of imp_fbh_t structs   */
+    imp_fbh_t *fbh;        /* array of imp_fbh_t structs */
 };
 
 struct imp_fbh_st {        /* field buffer */
@@ -69,11 +69,6 @@
     /* Our storage space for the field data as it's fetched    */
     short       indic;      /* null/trunc indicator variable   */
     SV*         sv;         /* buffer for the data (perl & ingres) */
-    union   {
-        int *   iv;
-        double* nv;
-        char*   pv;
-    } var_ptr;              /* buffer for data */
 };
 
 /* DBD::Ingres extensions */
diff -uNr DBD-Ingres-0.36/Makefile.PL DBD-Ingres-0.36.1/Makefile.PL
--- DBD-Ingres-0.36/Makefile.PL 2000-09-19 17:59:03.000000000 +1100
+++ DBD-Ingres-0.36.1/Makefile.PL       2003-07-02 13:46:43.000000000 +1000
@@ -173,7 +173,7 @@
                  "Ingres\$(OBJ_EXT)" => "Ingres.h",
                 };
 $opts{clean} = { FILES => "dbdimp.c dbdimp.sc Ingres.h Ingres.sc Ingres.xsi" };
-$opts{macro} = { ESQLC => $ESQLC, ESQLCOPT => "-o.h" };
+$opts{macro} = { ESQLC => $ESQLC, ESQLCOPT => "-o.h -w -p" };
 $opts{DEFINE} = '-DOPENINGRES' if $OpenIngres;
 WriteMakefile(%opts);
 
diff -uNr DBD-Ingres-0.36/MANIFEST DBD-Ingres-0.36.1/MANIFEST
--- DBD-Ingres-0.36/MANIFEST    2002-03-06 20:43:56.000000000 +1100
+++ DBD-Ingres-0.36.1/MANIFEST  2003-07-01 17:58:14.000000000 +1000
@@ -7,6 +7,7 @@
 ingperl.pl
 Makefile.PL
 MANIFEST
+t/datatypes.t
 t/dbi.t
 t/event.t
 t/exec.t
diff -uNr DBD-Ingres-0.36/README DBD-Ingres-0.36.1/README
--- DBD-Ingres-0.36/README      2002-11-22 21:05:53.000000000 +1100
+++ DBD-Ingres-0.36.1/README    2003-07-02 16:15:17.000000000 +1000
@@ -59,6 +59,11 @@
 
 New features (See the CHANGES file for a complete list):
 
+v0.36.1     2003.07.02
+ - fix memory leaks on statement handle destruction.
+ - documentation updates/tweaks.
+ - supports binary data: BYTE and BYTE VARYING
+ - remove warnings on binding null (undef) values
 v0.36       2002.11.22
  -bind_param didn't accept type SQL_DATE. I should have been a stringish
   type. Pointed out by Mark Buckle <[EMAIL PROTECTED]>
diff -uNr DBD-Ingres-0.36/t/datatypes.t DBD-Ingres-0.36.1/t/datatypes.t
--- DBD-Ingres-0.36/t/datatypes.t       1970-01-01 10:00:00.000000000 +1000
+++ DBD-Ingres-0.36.1/t/datatypes.t     2003-07-01 17:55:35.000000000 +1000
@@ -0,0 +1,241 @@
+#
+# Test various functions for each data type supported.
+#
+
+use DBI qw(:sql_types);
+use Test::Harness qw($verbose);
+require DBD::Ingres;
+
+my $num_test = 5 + ($#{DBD::Ingres::db->type_info_all()}) * 23;
+
+$verbose = $Test::Harness::verbose;
+my $testtable = "testhththt";
+my $t = 1;
+
+sub ok ($$) {
+    my ($ok, $expl) = @_;
+    print "Testing $expl\n" if $verbose;
+    ($ok) ? print "ok $t\n" : print "not ok $t\n";
+    if (!$ok && $warn) {
+       $warn = $DBI::errstr if $warn eq '1';
+       $warn = "" unless $warn;
+       warn "$expl $warn\n";
+    }
+    ++$t;
+    $ok;
+}
+
+sub get_dbname {
+    # find the name of a database on which test are to be performed
+    # Should ask the user if it can't find a name.
+    my $dbname = $ENV{DBI_DBNAME};
+    if (defined $dbname && $dbname !~ /^dbi:Ingres/) {
+       $dbname = "dbi:Ingres:$dbname";
+    }
+    $dbname;
+}
+
+sub connect_db ($) {
+    # Connects to the database.
+    # If this fails everything else is in vain!
+    my ($dbname) = @_;
+
+    my $dbh = DBI->connect($dbname, "", "",
+                   { AutoCommit => 0, RaiseError => 0, PrintError => 0 })
+       or return undef;
+    $dbh->{ChopBlanks} = 0;
+
+    $dbh;
+}
+
+my $dbname = get_dbname();
+
+if (!defined $dbname) {
+    print "1..1\n";
+    print "ok 1 # skipped\n";
+    warn "\nSkipping tests, DBI_DBNAME environment variable not set.\n";
+    exit 0;
+}
+
+print "1..$num_test\n";
+
+my $dbh;
+
+unless (ok($dbh = connect_db($dbname), "Connecting to database: $dbname")) {
+    while ($t <= $num_test) {
+       print "not ok $t # skipped\n";
+       ++$t;
+    }
+    exit 0;
+}
+       
+#
+# Table creation/destruction.  Can't do much else if this isn't working.
+#
+eval { $dbh->do("DROP TABLE $testtable"); };
+ok($dbh->do("CREATE TABLE $testtable(id INTEGER4 not null, name CHAR(64))"),
+      "Basic create table");
+ok($dbh->do("INSERT INTO $testtable VALUES(1, 'Alligator Descartes')"),
+      "Basic insert(value)");
+ok($dbh->do("DELETE FROM $testtable WHERE id = 1"),
+      "Basic Delete");
+ok($dbh->do( "DROP TABLE $testtable" ),
+      "Basic drop table");
+
+#
+# For each supported data type, we need to test
+#
+#   1. inserting into a table using a bind_param of that type as a value
+#   2. selecting from a table a field with that type
+#   3. selecting from a table using a bind_param of that type as a selector
+#   4. inserting into a table using a null bind_param of that type as a value
+#   5. selecting from a table a null field with that type
+#
+# For extra paranoia we undef the variable after binding but before
+# executing to make sure we aren't core dumping due to referencing things
+# without updating their reference counts.
+#
+
+my %testvals = (
+    'SMALLINT'         => 32511,
+    'INTEGER'          => 1234567,
+    'MONEY'            => 49711.39,
+    'FLOAT'            => 3.1415926,
+    'DATE'             => "1963-03-15 04:55:22",
+    'DECIMAL'          => 98,
+    'VARCHAR'          => "Apricot" x 3,
+    'BYTE VARYING'     => "Ab\0" x 10,
+    'CHAR'             => "AaBb",
+    'BYTE'             => "\3\2\1\0",
+    'LONG VARCHAR'     => "CcDd" x 4096,
+    'LONG BYTE'                => "Ee\0Ff\1Gg\2Hh\0" x 2048,
+);
+
+my $types = $dbh->type_info_all();
+
+for (my $i=1; $i <= $#{$types}; ++$i) {
+    my $name = $types->[$i]->[$types->[0]->{TYPE_NAME}];
+    my $sqltype = $types->[$i]->[$types->[0]->{DATA_TYPE}];
+    my $searchable = $types->[$i]->[$types->[0]->{SEARCHABLE}];
+    my $nullable = $types->[$i]->[$types->[0]->{NULLABLE}];
+    my $params = $types->[$i]->[$types->[0]->{CREATE_PARAMS}];
+    my $val = $testvals{$name};
+
+    unless ($val) {
+       warn "No default value for type $name\n";
+       next;
+    }
+
+    # Update the type based on the create params
+    if ($params && $params =~ /max length/) {
+       $name .= "(2000)";
+    } elsif ($params && $params =~ /length/) {
+       $name .= "(64)";
+       $val = sprintf("%-64s", $val);
+    } elsif ($params && $params =~ /size=/) {
+       $params =~ s/.*size=([0-9,]*).*/$1/;
+       my @sizes = split(/,/, $params);
+       $name .= $sizes[-1];
+    }
+
+    # CREATE TABLE OF APPROPRIATE TYPE
+    ok($dbh->do("CREATE TABLE $testtable (val $name)"),
+         "Create table ($name)");
+
+    # INSERT BOUND VALUE
+    ok($cursor = $dbh->prepare("INSERT INTO $testtable VALUES (?)"),
+         "Insert prepare ($name)");
+    {
+       # By allowing the bind param to go out of scope we make sure the driver
+       # has either copied it or has all its ref counting on it right.
+       my $destroyval = $val;
+       ok($cursor->bind_param(1, $destroyval, { TYPE => $sqltype }),
+             "Insert bind param ($name)");
+    }
+    ok($cursor->execute,
+         "Insert execute ($name)");
+    ok($cursor->finish,
+         "Insert finish ($name)");
+
+    # SELECT VALUE
+    ok($cursor = $dbh->prepare("SELECT val FROM $testtable"),
+         "Select prepare ($name)");
+    ok($cursor->execute,
+         "Select execute ($name)");
+    my $ar = $cursor->fetchrow_arrayref; 
+    ok($ar && $ar->[0] eq $val,
+         "Select fetch ($name)")
+       or print STDERR "Got '$ar->[0]', expected '$val'.\n";
+    ok($cursor->finish,
+         "Select finish ($name)");
+
+    # FETCH BOUND SELECTOR
+    if ($searchable) {
+
+       ok($cursor = $dbh->prepare("SELECT * FROM $testtable WHERE val = ?"),
+             "Select with bound selector prepare ($name)");
+       my $destroyval = $val;
+       ok($cursor->bind_param(1, $destroyval, { TYPE => $sqltype }),
+             "Select with bound selector bind_param ($name)");
+       undef $destroyval;
+       ok($cursor->execute,
+             "Select with bound selector execute ($name)");
+       $ar = $cursor->fetchrow_arrayref; 
+       ok($ar && "$ar->[0]" eq "$val",
+             "Select with bound selector fetch ($name)")
+           or print STDERR "Got '$ar->[0]', expected '$val'.\n";
+       ok($cursor->finish,
+             "Select with bound selector finish ($name)");
+    } else {
+       # These dummies make it easier to set num_tests.  We have to skip
+       # these tests because you can't select on some types.
+       ok(1, "Dummy test.");
+       ok(1, "Dummy test.");
+       ok(1, "Dummy test.");
+       ok(1, "Dummy test.");
+       ok(1, "Dummy test.");
+    }
+
+    # CLEAN UP FOR NULL STUFF
+    $dbh->do("DELETE FROM $testtable");
+
+    # INSERT NULL VALUE
+    if ($nullable) {
+       ok($cursor = $dbh->prepare("INSERT INTO $testtable VALUES (?)"),
+             "Insert null prepare ($name)");
+       ok($cursor->bind_param(1, undef, { TYPE => $sqltype }),
+             "Insert null bind param ($name)");
+       ok($cursor->execute,
+             "Insert null execute ($name)");
+       ok($cursor->finish,
+             "Insert null finish ($name)");
+
+       # SELECT NULL VALUE
+       ok($cursor = $dbh->prepare("SELECT val FROM $testtable"),
+             "Select null prepare ($name)");
+       ok($cursor->execute,
+             "Select null execute ($name)");
+       ok(!defined ($cursor->fetchrow_arrayref->[0]),
+             "Select null fetch ($name)");
+       ok($cursor->finish,
+             "Select null finish ($name)");
+    } else {
+       ok(1, "Dummy test.");
+       ok(1, "Dummy test.");
+       ok(1, "Dummy test.");
+       ok(1, "Dummy test.");
+       ok(1, "Dummy test.");
+       ok(1, "Dummy test.");
+       ok(1, "Dummy test.");
+       ok(1, "Dummy test.");
+    }
+
+    # DROP TABLE AGAIN
+    ok($dbh->do("DROP TABLE $testtable"),
+         "Drop table ($name)");
+}
+
+$dbh and $dbh->commit;
+$dbh and $dbh->disconnect;
+         
+exit(0);
diff -uNr DBD-Ingres-0.36/t/dbi.t DBD-Ingres-0.36.1/t/dbi.t
--- DBD-Ingres-0.36/t/dbi.t     2002-05-21 19:06:47.000000000 +1000
+++ DBD-Ingres-0.36.1/t/dbi.t   2003-06-30 13:41:42.000000000 +1000
@@ -49,6 +49,7 @@
     my $dbh = DBI->connect($dbname, "", "",
        { AutoCommit => 0,
 #          RaiseError => 1,
+          PrintError => 0,
         });
     $dbh->{ChopBlanks} = 1;
     if ($dbh) {
diff -uNr DBD-Ingres-0.36/t/exec.t DBD-Ingres-0.36.1/t/exec.t
--- DBD-Ingres-0.36/t/exec.t    2002-05-14 03:04:06.000000000 +1000
+++ DBD-Ingres-0.36.1/t/exec.t  2003-06-30 13:41:52.000000000 +1000
@@ -3,7 +3,7 @@
 #   col2 char(2)
 # );
 use DBI qw(:sql_types);
-our $num_test;
+my $num_test;
 
 $verbose = 1 unless defined $verbose;
 my $testtable = "testhththt";
@@ -52,7 +52,7 @@
        if $verbose;
     my $dbh = DBI->connect($dbname, "", "",
        { AutoCommit => 0,
-         PrintError => !$ENV{HARNESS_ACTIVE},
+         PrintError => 0
         });
     $dbh->{ChopBlanks} = 1;
     if ($dbh) {
diff -uNr DBD-Ingres-0.36.1/CHANGES DBD-Ingres-0.36.2/CHANGES
--- DBD-Ingres-0.36.1/CHANGES   2003-07-02 14:22:49.000000000 +1000
+++ DBD-Ingres-0.36.2/CHANGES   2003-07-03 16:13:30.000000000 +1000
@@ -1,5 +1,9 @@
 $Id: //depot/tilpasninger/dbd-ingres/CHANGES#13 $ $DateTime: 2002/11/22 11:11:32 $ 
$Author: ht000 $
-v0.36.1     2003.07.02
+v0.36.2     2003.07.03
+ - implement support for LONG VARCHAR and LONG BYTE data types, including
+   dynamically allocating the memory for them.
+ - add tests for long and binary behavior into t/dbi.t.
+v0.36.1     2003.06.30
  - add t/datatypes.t for more thorough testing of support for each
    datatype in type_info_all.
  - fix memory leak in attribute fetch by decrementing the ref count on
diff -uNr DBD-Ingres-0.36.1/dbdimp.psc DBD-Ingres-0.36.2/dbdimp.psc
--- DBD-Ingres-0.36.1/dbdimp.psc        2003-07-03 16:09:56.000000000 +1000
+++ DBD-Ingres-0.36.2/dbdimp.psc        2003-07-03 16:19:47.000000000 +1000
@@ -54,6 +54,9 @@
             PerlIO_write(DBILOGFP, var->sqldata, var->sqllen);
             PerlIO_printf(DBILOGFP, "'\n");
             break;
+        case IISQ_HDLR_TYPE:
+            PerlIO_printf(DBILOGFP, ", var: long type\n");
+            break;
         default:
             PerlIO_printf(DBILOGFP, ", unknown type: %d\n",
                 var->sqltype);
@@ -323,6 +326,10 @@
             if (!sql_check(dbh)) return 0;
         }
     }
+
+    /* Set default value for LongReadLen */
+    DBIc_LongReadLen(imp_dbh) = 2UL * 1024 * 1024 * 1024;
+
     return 1;
 }
 
@@ -709,6 +716,7 @@
           
           for (param_no=0; param_no < param_max; param_no++) {
             var = &imp_sth->ph_sqlda.sqlvar[param_no];
+            var->sqltype = 0;
             var->sqldata = 0;
             var->sqllen  = 0;
           }
@@ -724,6 +732,137 @@
     return 1;
 }
 
+/* Get handler function for getting long types. This is called
+ * by Ingres when a select is done on a long varchar or long byte field. */
+static int
+dbd_get_handler(imp_fbh_t *fbh)
+{
+EXEC SQL BEGIN DECLARE SECTION;
+    int data_end;
+    long size_read;
+    char buf[HANDLER_READ_SIZE];
+    long max_to_read = sizeof(buf);
+EXEC SQL END DECLARE SECTION;
+    STRLEN offset, data_len, trunc_len;
+    char *data;
+    D_imp_sth(fbh->sth);
+
+    /* We allocate memory as needed to hold the data, in chunks of
+     * HANDLER_READ_SIZE length.  We never read more than trunc_len
+     * bytes */
+
+    trunc_len = DBIc_LongReadLen(imp_sth);
+
+    /* This could save us a bit of I/O when the user asks for the
+     * data to be truncated at a length less than HANDLER_READ_SIZE.
+     * We need to read 1 extra so we know when we really have truncated. */
+    if (max_to_read > trunc_len + 1)
+        max_to_read = trunc_len + 1;
+
+    /* We can use indic as the truncated indicator, because Ingres tells
+     * us that the get handler is not called at all when the data is null.
+     * Settings: 0=good, 1=truncated, -1=null */
+    fbh->indic = 0;
+    offset = 0;
+
+    do {
+        /* You must call GET DATA at least once no matter what. */
+        EXEC SQL GET DATA (:buf = segment,
+                           :size_read = segmentlength,
+                           :data_end = dataend)
+            WITH MAXLENGTH = :max_to_read;
+
+        if (!sql_check(fbh->sth)) {
+            SvOK_off(fbh->sv);
+            EXEC SQL ENDDATA;
+            return 0;
+        }
+
+        if (offset + size_read > trunc_len) {
+            /* we've read the maximum, time to truncate. */
+            size_read = trunc_len - offset;
+            fbh->indic = 1;
+            data_end = 1;
+            EXEC SQL ENDDATA;
+        }
+
+#if 0
+        /* This looks tidy, but sv_grow does tricks with PV offsets that
+         * mean we often end up allocating almost twice as much memory.
+         * Obviously for long data types that isn't a good idea. */
+        data = SvGROW(fbh->sv, offset + size_read + 1);
+#else
+        data = SvPV(fbh->sv, data_len);
+        if (data_len < offset + size_read + 1) {
+            data = SvPVX(fbh->sv);
+            data_len = offset + size_read + 1;
+            /* We probably should expose a setting to allow user-tunable
+             * sizes for extending the memory alloc by.  Perhaps a
+             * $dbh->{LongChunkSize} or similar. */
+            Renew(data, data_len, char);
+            SvPV_set(fbh->sv, data);
+            SvLEN_set(fbh->sv, data_len);
+        }
+#endif
+        memcpy(data + offset, buf, size_read);
+        offset += size_read;
+    } while (data_end == 0);
+
+    /* Terminating with an extra null byte doesn't hurt binary data
+     * because the recorded length is still right. */
+    data[offset] = '\0';
+    SvCUR_set(fbh->sv, offset);
+
+    /* if LongReadLen is 0 return undef/null (from DBI spec) */
+    if (trunc_len == 0) {
+        SvOK_off(fbh->sv);
+        fbh->indic = -1;
+    }
+
+    /* The return value from the datahandler is ignored by Ingres. */
+    return 0;
+}
+
+
+/* Put handler function for putting long types. This is called
+ * by Ingres when an insert is done on a long varchar or long byte
+ * field using a type-specified SQL_LONGVARCHAR or SQL_LONGVARBINARY
+ * bind param. */
+static int
+dbd_put_handler(SV *sv)
+{
+EXEC SQL BEGIN DECLARE SECTION;
+    int data_end, chunk;
+    char *cp, *data;
+EXEC SQL END DECLARE SECTION;
+    STRLEN length, offset;
+
+    /* Data to put is in arg->sv. */
+    data = SvPV(sv, length);
+
+    offset = 0;
+    data_end = 0;
+
+    /* This loop is not necessary with the current setup where we
+     * just write the whole buffer in one, but we could reduce
+     * the chunk size later if it turned out to be useful. */
+    do {
+        cp = data + offset;
+        chunk = length - offset;
+        data_end = 1;
+
+        /* You must call PUT DATA at least once.  For 0 bytes you must
+         * call it once with segmentlength = 0. */
+        EXEC SQL PUT DATA (segment = :cp,
+                           segmentlength = :chunk,
+                           dataend = :data_end);
+
+        offset += chunk;
+    } while (offset < length);
+
+    return 1;
+}
+
 /*
  * dbd_describe is called when a statement is executed that will fetch
  * fields, and is responsible for allocating memory in the individual
@@ -764,6 +903,8 @@
     {
         imp_fbh_t *fbh = &imp_sth->fbh[i];
         IISQLVAR *var = fbh->var = &sqlda->sqlvar[i];
+
+        fbh->sth = sth;
         fbh->nullable = var->sqltype < 0;
         fbh->origtype = var->sqltype = abs(var->sqltype);
         fbh->origlen = var->sqllen;
@@ -812,6 +953,31 @@
             SvOK_off(fbh->sv);
             var->sqldata = SvPVX(fbh->sv);
             break;
+        case IISQ_LVCH_TYPE:
+        case IISQ_LBYTE_TYPE: {
+            IISQLHDLR *hdlr;
+
+            var->sqltype = IISQ_HDLR_TYPE;
+            fbh->len = 0;
+            var->sqllen = 0;
+            strcpy(fbh->type, "l");
+
+            /* OK, I lied above.  These ones ARE real SVs.
+             * Memory with long data types is alloced in dbd_get_handler. */
+            fbh->sv = newSV(0);
+            SvUPGRADE(fbh->sv, SVt_PV);
+            SvREADONLY_on(fbh->sv);
+            SvPOK_only(fbh->sv);
+
+            /* Set up dbd_get_handler to read the data, having it passed
+             * fbh so it knows where to store things and can give back
+             * some status info. */
+            Newz(42, hdlr, 1, IISQLHDLR);
+            hdlr->sqlarg = (char *)fbh;
+            hdlr->sqlhdlr = dbd_get_handler;
+
+            var->sqldata = (char *)hdlr;
+            break; }
         default:        /* oh dear! */
             croak("DBD::Ingres: field %d has unsupported type %d\n",
                   i+1, var->sqltype);
@@ -878,6 +1044,13 @@
 
     var = &imp_sth->ph_sqlda.sqlvar[param_no-1];
 
+    /* Need to unref the previous "value" if this var was used with a put
+     * handler before. */
+    if (var->sqltype == IISQ_HDLR_TYPE) {
+        IISQLHDLR *hdlr = (IISQLHDLR *)var->sqldata;
+        SvREFCNT_dec((SV *)hdlr->sqlarg);
+    }
+
     if (sql_type) {
         switch (sql_type) {
         case SQL_INTEGER:
@@ -895,6 +1068,9 @@
         case SQL_VARBINARY:
         case SQL_DATE:
             type = 3; break;
+        case SQL_LONGVARCHAR:
+        case SQL_LONGVARBINARY:
+            type = 4; break;
         default:
             croak("DBD::Ingres::bind_param: Unknown TYPE: %d, param_no %d",
                 sql_type, param_no);
@@ -958,7 +1134,26 @@
             var->sqltype = IISQ_VBYTE_TYPE;
 
         break; }
+    case 4: { /* blob */
+        IISQLHDLR *hdlr;
+
+        var->sqltype = IISQ_HDLR_TYPE;
+        Renew(var->sqldata, sizeof(IISQLHDLR), char);
+        hdlr = (IISQLHDLR *)var->sqldata;
+
+        /* We pass the SV itself as the argument to the put handler.
+         * The value is thus evaluated at execute time, not now
+         * at bind time.
+         *
+         * XXX: make sure the refcount is decremented any time this
+         *      allocation is removed: reuse (above); destroy. */
+        SvREFCNT_inc(value);
+        hdlr->sqlarg = (char *)value;
+        hdlr->sqlhdlr = dbd_put_handler;
+        var->sqllen = 0;
+        break; }
     }
+
     if (!SvOK(value)) {
         /* !SvOK means binding a NULL */
         if (dbis->debug >= 3) PerlIO_printf(DBILOGFP, "bind(NULL)");
@@ -1148,6 +1343,16 @@
                         DBIc_has(imp_sth, DBIcf_ChopBlanks));
                 }
                 break; }
+            case 'l':
+                if (!DBIc_has(imp_sth, DBIcf_LongTruncOk) && fbh->indic == 1) {
+                    (void)SvOK_off(sv);
+                    error(sth, -7, "Data size larger than LongReadLen", 0);
+                    return Nullav;
+                }
+                sv_setsv(sv, fbh->sv);
+                if (dbis->debug >= 3)
+                    PerlIO_printf(DBILOGFP, "Long data (%d)\n", SvCUR(sv));
+                break;
             default:
                 croak("DBD::Ingres: wierd field-type '%s' in field no. %d?\n",
                             fbh->type, i);
@@ -1221,6 +1426,10 @@
     for (i=0; i<DBIc_NUM_PARAMS(imp_sth); ++i) {
         IISQLVAR *var = &imp_sth->ph_sqlda.sqlvar[i];
         if (var->sqldata != 0) {
+            if (var->sqltype == IISQ_HDLR_TYPE) {
+                IISQLHDLR *hdlr = (IISQLHDLR *)var->sqldata;
+                SvREFCNT_dec((SV *)hdlr->sqlarg);
+            }
             Safefree(var->sqldata);
         }
     }
@@ -1230,6 +1439,8 @@
         for (i=0; i<sqlda->sqld; i++) {
             imp_fbh_t *fbh = &imp_sth->fbh[i];
             SvREFCNT_dec(fbh->sv);
+            if (fbh->type[0] == 'l')
+                Safefree(fbh->var->sqldata);
         }
         Safefree(imp_sth->fbh);
     }
diff -uNr DBD-Ingres-0.36.1/Ingres.pm DBD-Ingres-0.36.2/Ingres.pm
--- DBD-Ingres-0.36.1/Ingres.pm 2003-07-02 16:25:10.000000000 +1000
+++ DBD-Ingres-0.36.2/Ingres.pm 2003-07-03 16:20:51.000000000 +1000
@@ -35,7 +35,7 @@
     use DynaLoader ();
     @ISA = qw(DynaLoader);
 
-    $VERSION = '0.36.1';
+    $VERSION = '0.36.2';
     my $Revision = substr(q$Change: 10694 $, 8)/100;
 
     bootstrap DBD::Ingres $VERSION;
@@ -44,7 +44,7 @@
     $errstr = "";      # holds error string for DBI::errstr
     $drh = undef;      # holds driver handle once initialised
 
-    sub driver{
+    sub driver {
         return $drh if $drh;
         my($class, $attr) = @_;
 
@@ -211,9 +211,16 @@
            [ 'BYTE',         DBI::SQL_BINARY, 
              undef, "'","'", "length",
              1, 1, 3, 0, 0, 0, undef, 0, 0 ],
+           [ 'LONG VARCHAR', DBI::SQL_LONGVARCHAR, 
+             undef, undef, undef, undef,
+             1, 1, 0, 0, 0, 0, undef, 0, 0 ],
+           [ 'LONG BYTE', DBI::SQL_LONGVARBINARY, 
+             undef, undef, undef, undef,
+             1, 1, 0, 0, 0, 0, undef, 0, 0 ],
        ];
        return $ti;
     }
+
 }
 
 
@@ -322,6 +329,9 @@
 
 =back
 
+This does not cause loss of precision, because the Ingres API uses
+these types to return the data anyway.
+
 =head2 get_dbevent
 
 This non-DBI method calls C<GET DBEVENT> and C<INQUIRE_INGRES> to
@@ -356,6 +366,39 @@
 to work, but probably will most of the time.  Use 'BYTE' or
 'BYTE VARYING' data types in your database for full binary data support.
 
+=head2 Long Data Types
+
+DBD::Ingres supports the LONG VARCHAR and LONG BYTE data types
+as detailed in L<DBI/"Handling BLOB / LONG / Memo Fields">.
+
+The default value for LongReadLen in DBD::Ingres is 2GB, the maximum
+size of a long data type field.  DBD::Ingres dynamically allocates
+memory for long data types as required, so setting LongReadLen to a
+large value does not waste memory.
+
+In summary:
+
+=over 4
+
+=item *
+
+When inserting blobs, use bind variables with types specified.
+
+=item *
+
+When fetching blobs, set LongReadLen and LongTruncOk in the $dbh.
+
+=item *
+
+Blob fields are returned as undef if LongReadLen is 0.
+
+=back
+
+Due to their size (and hence the impracticality of copying them inside
+the DBD driver), variables bound as blob types are always evaluated at
+execute time rather than bind time. (Similar to bind_param_inout, except
+you don't pass them as references.)
+
 =head2 ing_readonly
 
 Normally cursors are declared C<READONLY> 
@@ -455,6 +498,10 @@
 referred to the data (all Ingres tools fetch these values as
 floating-point numbers)
 
+=item 'l': long / blob
+
+Either of the two long datatypes, long varchar or long byte.
+
 =item 's': string
 
 All other supported types, i.e., char, varchar, text, date etc.
@@ -509,6 +556,14 @@
 
 decimal -> DBI::SQL_DECIMAL
 
+=item *
+
+long varchar -> DBI::SQL_LONGVARCHAR
+
+=item *
+
+long byte -> DBI::SQL_LONGVARBINARY
+
 =back
 
 Have I forgotten any?
@@ -584,9 +639,8 @@
 
 =head2 OpenIngres new features
 
-The new features of OpenIngres are not (yet) supported in DBD::Ingres.
-
-This includes BLOBS and spatial datatypes.
+Some new features of OpenIngres are not (yet) supported in DBD::Ingres,
+including spatial datatypes.
 
 Support will be added when the need arises - if you need it you add it ;-)
 
diff -uNr DBD-Ingres-0.36.1/Ingres.sh DBD-Ingres-0.36.2/Ingres.sh
--- DBD-Ingres-0.36.1/Ingres.sh 2003-06-30 15:44:49.000000000 +1000
+++ DBD-Ingres-0.36.2/Ingres.sh 2003-07-03 15:32:54.000000000 +1000
@@ -24,6 +24,8 @@
 #define IISQ_DEC_TYPE -9999
 #endif
 
+#define HANDLER_READ_SIZE  64 * 1024
+
 typedef struct imp_fbh_st imp_fbh_t;
 
 struct imp_drh_st {
@@ -35,7 +37,6 @@
 struct imp_dbh_st {
     dbih_dbc_t com;         /* MUST be first element in structure   */
     int        session;     /* session id for this connection */
-    imp_sth_t *sth_lst;     /* pointer to first statement in chain */
     int        trans_no;    /* transaction sequence number, is
                             ** incremented by 1 at every commit/
                             ** rollback */
@@ -56,13 +57,13 @@
 };
 
 struct imp_fbh_st {        /* field buffer */
-    imp_sth_t *imp_sth;            /* 'parent' statement */
+    SV*         sth;       /* 'parent' statement */
 
     /* Ingres description of the field */
     IISQLVAR*   var;        /* pointer to Ingres description */
     int         nullable;   /* 1 if field is nullable */
     int         origtype;   /* the ingres type (as given by Ingres originally), this 
type has possibly been modified...*/
-    char        type[2];    /* type "i"=int, "f"=double, "s"=string */
+    char        type[2];    /* type "i"=int, "f"=double, "l"=long, "s"=string */
     int         len;        /* length of field in bytes */
     int         origlen;    /* length of the field in Ingres */
 
diff -uNr DBD-Ingres-0.36.1/t/dbi.t DBD-Ingres-0.36.2/t/dbi.t
--- DBD-Ingres-0.36.1/t/dbi.t   2003-06-30 13:41:42.000000000 +1000
+++ DBD-Ingres-0.36.2/t/dbi.t   2003-07-03 16:13:04.000000000 +1000
@@ -164,6 +164,73 @@
 ###}
 
 ok(0, $dbh->do( "DROP TABLE $testtable" ), "Dropping table", 1);
+ok(0, $dbh->do("CREATE TABLE $testtable(id INTEGER4 not null, name LONG VARCHAR, bin 
BYTE VARYING(64))"), "Create long varchar table", 1);
+ok(0, $dbh->do("INSERT INTO $testtable (id, name) VALUES(1, '')"),
+    "Long varchar zero-length insert", 1);
+ok(0, $dbh->do("DELETE FROM $testtable WHERE id = 1"),
+    "Long varchar delete", 1);
+$cursor = $dbh->prepare("INSERT INTO $testtable (id, name) VALUES (?, ?)");
+$cursor->bind_param(1, 1);
+$cursor->bind_param(2, "AaBb" x 1024, DBI::SQL_LONGVARCHAR);
+ok(0, $cursor->execute, "Long varchar insert of 4096 bytes", 1);
+$cursor->finish;
+$cursor = $dbh->prepare("UPDATE $testtable SET name = ? WHERE ID = 1");
+$cursor->bind_param(1, "CcDd" x 512, DBI::SQL_LONGVARCHAR);
+ok(0, $cursor->execute, "Long varchar update of 2048 bytes", 1);
+$cursor->finish;
+ok(0, $cursor = $dbh->prepare("SELECT name FROM $testtable"),
+     "Long varchar prepare(select)", 1);
+ok(0, $cursor->execute, "Long varchar execute(select)", 1);
+$row = $cursor->fetchrow_arrayref;
+ok(0, ${$row}[0] eq 'CcDd' x 512, "Long varchar fetch", 1);
+ok(0, $cursor->finish, "Long varchar finish", 1);
+
+# Reading a long varchar with LongReadLen = 0 should always return undef.
+$dbh->{LongReadLen} = 0;
+ok(0, $dbh->{LongReadLen} == 0, "Set LongReadLen = 0", 1);
+$cursor = $dbh->prepare("SELECT name FROM $testtable");
+$cursor->execute;
+$row = $cursor->fetchrow_arrayref;
+ok(0, !defined $row->[0], "Long varchar fetch with LongReadLen=0", 1);
+$cursor->finish;
+
+# Reading a long varchar longer than LongReadLen with TruncOk set to 1
+# should return the truncated value.
+$dbh->{LongReadLen} = 5;
+$dbh->{LongTruncOk} = 1;
+$cursor = $dbh->prepare("SELECT name FROM $testtable");
+$cursor->execute;
+$row = $cursor->fetchrow_arrayref;
+ok(0, $row->[0] eq 'CcDdC',
+     "Long varchar fetch with LongReadLen=5 LongTruncOk=1", 1);
+$cursor->finish;
+
+# Reading a long varchar longer than LongReadLen with TrunkOk set to 0
+# should fail with an error.
+$dbh->{LongReadLen} = 5;
+$dbh->{LongTruncOk} = 0;
+$cursor = $dbh->prepare("SELECT name FROM $testtable");
+$cursor->execute;
+eval {
+       $row = $cursor->fetchrow_arrayref;
+};
+ok(0, !defined $row, "Long varchar fetch with LongReadLen=5 LongTruncOk=0", 1);
+$cursor->finish;
+
+# Binary data testing
+$dbh->do("DELETE FROM $testtable");
+$cursor = $dbh->prepare("INSERT INTO $testtable (id, bin) VALUES (?, ?)");
+$cursor->bind_param(1, 1);
+$cursor->bind_param(2, "\0\1\2\3\0\1\2\3\0\1\2\3", DBI::SQL_VARBINARY);
+ok(0, $cursor->execute, "Insert of binary data", 0);
+$cursor->finish;
+$cursor = $dbh->prepare("SELECT bin FROM $testtable WHERE id = 1");
+$cursor->execute;
+$row = $cursor->fetchrow_arrayref;
+ok(0, ${$row}[0] eq "\0\1\2\3\0\1\2\3\0\1\2\3", "Binary data fetch", 1);
+$cursor->finish;
+
+ok(0, $dbh->do( "DROP TABLE $testtable" ), "Dropping table", 1);
 ok(0, $dbh->rollback, "Rolling back", 1);
 #   What else??
 ok(0, !$dbh->{AutoCommit}, "AutoCommit switched off upon connect time", 1);
@@ -185,5 +252,5 @@
 #   test of outerjoin and nullability
 #   what else?
 
-BEGIN { $num_test = 50; }
+BEGIN { $num_test = 66; }
 
Mike Battersby <[EMAIL PROTECTED]>        The University of Melbourne
Fetch my pgp key from: http://ariel.ucs.unimelb.edu.au/~mib/pgpkey.txt

Reply via email to