Hello,
I am listing the patch (diff file).
The REF CURSOR array-fetch can be turned on using something like
"$dbh->{RefCursorCacheSize}=16;".
Array fetch remains off by default (single row fetch) so that it will not
affect users who dont care about it.
I have added tests to the end of "50cursor.t" that you may execute to view
performance improvements.
I hope you will consider it for the next version.
Thank You.
Biswa

============================== patch file start
==============================
diff -r -u DBD-Oracle-1.19/dbdimp.c DBD-Oracle-1.19.refcursor/dbdimp.c
--- DBD-Oracle-1.19/dbdimp.c    2006-11-03 06:05:46.000000000 -0800
+++ DBD-Oracle-1.19.refcursor/dbdimp.c  2007-09-13 14:02:31.000000000 -0700
@@ -837,6 +837,15 @@
    else if (kl==12 && strEQ(key, "RowCacheSize")) {
               imp_dbh->RowCacheSize = SvIV(valuesv);
    }
+/************************************************************
+* rs_batch: save cache parameter - START
+************************************************************/
+    else if (kl==18 && strEQ(key, "RefCursorCacheSize")) {
+       imp_dbh->RefCursorCacheSize = SvIV(valuesv);
+    }
+/************************************************************
+* rs_batch: save cache parameter - END
+************************************************************/
    else if (kl==22 && strEQ(key, "ora_max_nested_cursors")) {
               imp_dbh->max_nested_cursors = SvIV(valuesv);
    }
@@ -884,6 +893,15 @@
    else if (kl==12 && strEQ(key, "RowCacheSize")) {
       retsv = newSViv(imp_dbh->RowCacheSize);
    }
+/************************************************************
+* rs_batch: get cache parameter - START
+************************************************************/
+    else if (kl==18 && strEQ(key, "RefCursorCacheSize")) {
+       retsv = newSViv(imp_dbh->RefCursorCacheSize);
+    }
+/************************************************************
+* rs_batch: get cache parameter - END
+************************************************************/
    else if (kl==22 && strEQ(key, "ora_max_nested_cursors")) {
       retsv = newSViv(imp_dbh->max_nested_cursors);
    }
@@ -1298,6 +1316,13 @@

       /* set ACTIVE so dbd_describe doesn't do explicit OCI describe */
       DBIc_ACTIVE_on(imp_sth_csr);
+/**********************************************************************
+*  rs_batch: mark as ref cursor - START
+**********************************************************************/
+        imp_sth_csr->rs_batch_ref_cursor=1;
+/**********************************************************************
+*  rs_batch: mark as ref cursor - END
+**********************************************************************/
       if (!dbd_describe(sth_csr, imp_sth_csr)) {
           return 0;
       }
diff -r -u DBD-Oracle-1.19/dbdimp.h DBD-Oracle-1.19.refcursor/dbdimp.h
--- DBD-Oracle-1.19/dbdimp.h    2006-10-27 08:50:52.000000000 -0700
+++ DBD-Oracle-1.19.refcursor/dbdimp.h  2007-09-13 13:51:55.000000000 -0700
@@ -80,6 +80,13 @@
    int proc_handles;           /* If true, srvhp, svchp, and authp handles
                                   are owned by ProC and must not be freed.
*/
    int RowCacheSize;
+/************************************************************
+* rs_batch: cache parameter - START
+************************************************************/
+    int RefCursorCacheSize;     /* ref cursor cache parameter */
+/************************************************************
+* rs_batch: cache parameter - END
+************************************************************/
    int ph_type;               /* default oratype for placeholders */
    ub1 ph_csform;             /* default charset for placeholders */
    int parse_error_offset;    /* position in statement of last error */
@@ -134,6 +141,17 @@

    /* (In/)Out Parameter Details */
    bool  has_inout_params;
+/************************************************************
+* rs_batch: state variables - START
+************************************************************/
+    bool      rs_batch_ref_cursor;   /* indicates ref cursor */
+    int       rs_batch_size;         /* prefetch cache size */
+    int       rs_batch_num_rows;     /* num rows in last fetch */
+    int       rs_batch_idx;          /* index of current row */
+    sword     rs_batch_status;       /* status of last fetch */
+/************************************************************
+* rs_batch: state variables - END
+************************************************************/
 };
 #define IMP_STH_EXECUTING      0x0001

diff -r -u DBD-Oracle-1.19/oci8.c DBD-Oracle-1.19.refcursor/oci8.c
--- DBD-Oracle-1.19/oci8.c      2006-10-13 05:12:20.000000000 -0700
+++ DBD-Oracle-1.19.refcursor/oci8.c    2007-09-13 14:10:17.000000000 -0700
@@ -1572,6 +1572,36 @@
    imp_sth->in_cache  = 0;
    imp_sth->eod_errno = 0;

+/**********************************************************************
+* rs_batch: initialize cache size - START
+**********************************************************************/
+/**********************************************************************
+* rs_batch: initialize cache size - START
+**********************************************************************/
+    if (imp_sth->rs_batch_ref_cursor)
+    {
+      if (imp_dbh->RefCursorCacheSize>64)      /* restrict to 64 max for
now */
+      {
+        imp_dbh->RefCursorCacheSize=64;
+      } else {
+          if (imp_dbh->RefCursorCacheSize>0)
+              imp_sth->rs_batch_size=imp_dbh->RefCursorCacheSize;
+          else
+              imp_sth->rs_batch_size=1;                /* default no array
fetch for now */
+      }
+    }
+    else            /* do not use internal prefetch */
+    {
+      imp_sth->rs_batch_size=1;
+    }
+    imp_sth->rs_batch_num_rows=0;
+    imp_sth->rs_batch_idx=0;
+    imp_sth->rs_batch_status=OCI_SUCCESS;
+    if (DBIS->debug >= 3)
+      PerlIO_printf(DBILOGFP, "    dbd_describe: rs_batch_size set to
%d\n",imp_sth->rs_batch_size);
+/**********************************************************************
+* rs_batch: initialize cache size - END
+**********************************************************************/
    for(i=1; i <= num_fields; ++i) {
       imp_fbh_t *fbh = &imp_sth->fbh[i-1];
       int ftype = fbh->ftype;
@@ -1579,7 +1609,13 @@
       sb4 define_len = (ftype==94||ftype==95) ? fbh->disize+4 :
fbh->disize;
       fb_ary_t  *fb_ary;

-       fbh->fb_ary = fb_ary_alloc(define_len, 1);
+/**********************************************************************
+* rs_batch: allocate field buffer array - START
+**********************************************************************/
+       fbh->fb_ary = fb_ary_alloc(define_len, imp_sth->rs_batch_size);
+/**********************************************************************
+* rs_batch: allocate field buffer array - END
+**********************************************************************/
       fb_ary = fbh->fb_ary;

       if (fbh->ftype == 116) { /* RSET */
@@ -1663,8 +1699,26 @@
    else {
       if (DBIS->debug >= 3)
           PerlIO_printf(DBILOGFP, "    dbd_st_fetch %d fields...\n",
DBIc_NUM_FIELDS(imp_sth));
-       OCIStmtFetch_log_stat(imp_sth->stmhp, imp_sth->errhp,
-               1, (ub2)OCI_FETCH_NEXT, OCI_DEFAULT, status);
+/**********************************************************************
+*  rs_batch: iteration - START
+**********************************************************************/
+    imp_sth->rs_batch_idx++;
+    if (imp_sth->rs_batch_status==OCI_SUCCESS &&
imp_sth->rs_batch_num_rows<=imp_sth->rs_batch_idx)
+    {
+       OCIStmtFetch_log_stat(imp_sth->stmhp, imp_sth->errhp,
+               imp_sth->rs_batch_size, (ub2)OCI_FETCH_NEXT, OCI_DEFAULT,
status);
+       imp_sth->rs_batch_status=status;
+       OCIAttrGet_stmhp_stat(imp_sth, &imp_sth->rs_batch_num_rows,0,
+                OCI_ATTR_ROWS_FETCHED, status);
+       imp_sth->rs_batch_idx=0;
+    }
+    if (imp_sth->rs_batch_num_rows>imp_sth->rs_batch_idx)
+      status=OCI_SUCCESS;
+    else
+      status=imp_sth->rs_batch_status;
+/**********************************************************************
+*  rs_batch: iteration - END
+**********************************************************************/
    }

    if (status != OCI_SUCCESS) {
@@ -1698,7 +1752,15 @@
    for(i=0; i < num_fields; ++i) {
       imp_fbh_t *fbh = &imp_sth->fbh[i];
       fb_ary_t *fb_ary = fbh->fb_ary;
-       int rc = fb_ary->arcode[0];
+/**********************************************************************
+*  rs_batch: offset values - START
+**********************************************************************/
+        int row_idx=imp_sth->rs_batch_idx;
+       int rc = fb_ary->arcode[row_idx];
+        ub1* row_data=&fb_ary->abuf[0]+(fb_ary->bufl*row_idx);
+/**********************************************************************
+*  rs_batch: offset values - END
+**********************************************************************/
       SV *sv = AvARRAY(av)[i]; /* Note: we (re)use the SV in the AV   */

       if (rc == 1406                          /* field was truncated  */
@@ -1724,8 +1786,14 @@
                   ++err;      /* fetch_func already called oci_error */
           }
           else {
-               int datalen = fb_ary->arlen[0];
-               char *p = (char*)&fb_ary->abuf[0];
+/**********************************************************************
+*  rs_batch: use offset data - START
+**********************************************************************/
+               int datalen = fb_ary->arlen[row_idx];
+               char *p = (char*)row_data;
+/**********************************************************************
+*  rs_batch: use offset data - END
+**********************************************************************/
               /* if ChopBlanks check for Oracle CHAR type (blank padded)
   */
               if (ChopBlanks && fbh->dbtype == 96) {
                   while(datalen && p[datalen - 1]==' ')
@@ -1747,8 +1815,14 @@
               if (!fbh->fetch_func) {
                   /* Copy the truncated value anyway, it may be of use,
  */
                   /* but it'll only be accessible via prior bind_column()
  */
-                   sv_setpvn(sv, (char*)&fb_ary->abuf[0],
-                         fb_ary->arlen[0]);
+/**********************************************************************
+*  rs_batch: use offset data/len - START
+**********************************************************************/
+                   sv_setpvn(sv, (char*)row_data,
+                         fb_ary->arlen[row_idx]);
+/**********************************************************************
+*  rs_batch: use offset data/len - END
+**********************************************************************/
                   if (CSFORM_IMPLIES_UTF8(fbh->csform))
                       SvUTF8_on(sv);
               }
diff -r -u DBD-Oracle-1.19/t/50cursor.t DBD-Oracle-1.19.refcursor
/t/50cursor.t
--- DBD-Oracle-1.19/t/50cursor.t        2006-07-07 05:17:29.000000000 -0700
+++ DBD-Oracle-1.19.refcursor/t/50cursor.t      2007-09-13 14:32:
32.000000000 -0700
@@ -96,6 +96,59 @@
       ok(0, $close_cursor->execute);
 }

+##################################################
+# Row fetch timing: START
+##################################################
+use Time::HiRes qw(gettimeofday tv_interval);
+
+sub timed_fetch {
+  my($rs,$caption) = @_;
+  my $row_count=0;
+  my $tm_start=[gettimeofday];
+  while (my $array_ref = $rs->fetchrow_arrayref()) {
+   $row_count++;
+  }
+  my $elapsed=tv_interval($tm_start);
+  print "Fetched $row_count rows ($caption): [$elapsed] secs.\n";
+}
+
+##################################################
+# execute (select)
+##################################################
+my $sth = $dbh->prepare(
+ 'SELECT object_name FROM (SELECT object_name '
+  .'FROM all_objects WHERE ROWNUM<=70),(SELECT 1 FROM all_objects '
+  .'WHERE ROWNUM<=70)'
+);
+$sth->execute();
+timed_fetch($sth,'select');
+
+##################################################
+# execute (refcursor)
+##################################################
+$sth = $dbh->prepare(
+ 'BEGIN OPEN ? FOR '
+  .'SELECT object_name FROM (SELECT object_name '
+  .'FROM all_objects WHERE ROWNUM<=70),(SELECT 1 FROM all_objects '
+  .'WHERE ROWNUM<=70); '
+  .'END;'
+);
+my $rs;
+$sth->bind_param_inout(1,\$rs,0,{ ora_type => ORA_RSET });
+$sth->execute();
+timed_fetch($rs,'ref cursor');
+
+##################################################
+# execute (refcursor array-fetch)
+##################################################
+$dbh->{RefCursorCacheSize}=16;
+$sth->execute();
+timed_fetch($rs,'ref cursor array-fetch');
+
+##################################################
+# Row fetch timing: END
+##################################################
+
 $dbh->disconnect;

 exit 0;
============================== patch file end ==============================


---------- Forwarded message ----------
From: Biswadeep Chowdhury <[EMAIL PROTECTED]>
Date: Sep 14, 2007 2:09 AM
Subject: "speak before you patch": intention to patch DBD-Oracle to allow
faster fetch from REF CURSORs
To: [email protected]

Hello,

As the DBD-Oracle documentation mentions us to "speak before you patch", I
would like to know if there are any objections to patching DBD-Oracle to
improve data fetch from REF CURSORs.

Prefetch does not work for REF CURSORs in OCI. Since this is the underlying
library for DBD-Oracle, fetching a large number of rows from a REF CURSOR is
very slow as a roundtrip to the DB is done for each row.  (See
http://www.cpanforum.com/posts/1567)

I would like to patch DBD-Oracle to allow faster retrieval of records from
REF CURSORs. I intend to do this by calling OCIStmtFetch to fetch more than
one row at a time (array fetch). I believe the improvements are significant.


I had discussed this with Mr. Tim Bunce quite a while ago and am listing the
last email for reference.

Thanks.

Biswa

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

On Fri, Jan 20, 2006 at 10:37:06AM -0500, Biswa Chowdhury wrote:
> -----Original Message-----
> From: Tim Bunce [mailto: [EMAIL PROTECTED]
> Sent: Friday, January 20, 2006 5:56 AM
> To: Biswa Chowdhury
> Cc: Tim Bunce
> Subject: Re: DBD-Oracle: implementing prefetch for REF CURSORs
>
> >But wouldn't any prefetch implemented outside if Oracles own libs will
> >require a round-trip to the server for each row, so you'll see little
> >gain over using $sth->fetchall_arrayref(undef, $max_rows)?
>
> No, if we implement prefetch, we will call to OCIStmtFetch in
> dbd_st_fetch (oci8.c)
> to return (say) 64 rows instead of 1 row.
> That lessens the roundtrips and speeds things up significantly.

Ah. I'd forgotten that OCIStmtFetch can take a count. Thanks.

Tim.

Reply via email to