/* See the copyright notice (COPYRIGHT) in this directory. */

/* 
 * FILE
 * Public:
 *     pg_fetch()              Fetch the data and store it in the dbObj.
 *     pg_get_attr()           Get a string value off the portal.
 *
 * Private:
 *     pg_col_defs()           Create column definitions, add it to the dbObj.
 *     pg_alloc_fetch_buffer() Allocate temp fetch buffer.
 *     pg_free_buffer()        Free buffer allocated by pg_alloc_fetch_buffer.
 *     pg_fill_num_array()     Process numbers (includes array of 1)
 *     pg_fill_str_array()     Process an array of strings (array size > 1).
 *     pg_str_to_num()         Convert a string value to a number (no bounds
 *                             checking yet!).
 *
 * DESCRIPTION
 *     By the time pg_fetch is called, the retrieve has been executed.  These
 *     functions fetch the data returned off the portal and into the dbObj:
 *
 *                                                 GDI Functions Used
 *                                                 -------------------------
 *     1.  Create column definitions               gdi_col_def_create()
 *         and attach them to the dbObj.           gdi_obj_col_def_add()
 *         Fetching the first instance of 
 *         data gives us the info we need. 
 *
 *     2.  Create the tuple container              gdi_obj_container_create()
 *
 *     3.  Process each row
 *          Create a tuple                         gdi_obj_tuple_create()
 *          {
 *          Process each column
 *          fill in column data with               gdi_obj_fill_data()
 *          }
 *          Add the tuple to the container         gdi_obj_tuple_add()
 *
 *     Rows are processed until maxrec is reached or no more data are found
 *     that satisfies the query.
 *
 * NOTES
 *     Data type handling is bogus and needs to be completely redone.
 *     Handling of array types is particularly dreadful. On the first pass,
 *     we copy data too often. I started down the path of the string portal;
 *     I'll look at switching it to binary portals. Here is how it works....
 *
 *     Strings and numbers are handled differently.
 *
 *     Strings
 *     -------
 *     A scalar string is passed straight through to the constructor. 
 *     An array of strings is sent to pg_fill_str_array, which stomps all over
 *     it and points an array of pointers into the Postgres string (the knee 
 *     bone is connected to the thigh bone...).
 *
 *     Numbers
 *     -------
 *     A single row fetch buffer is preallocated from the column definitions.
 *     Numeric data is treated the same whether it is scalar or array. It is 
 *     processed by pg_fill_num_array, which converts it from string to number 
 *     and populates arrays as needed.
 *     
 *     Undimensioned (variable sized) arrays are new. They pass a simple test,
 *     but I have not tried them in FORTRAN yet.
 *
 * BUGS
 *     - Types are hard coded. Adding a new type requires changing pg_types.c.
 *     - Only handles simple types and arrays of simple types.
 *     - Fancy types get passed back as a string.
 *     - Undimensioned numeric arrays are allocated in chunks of PG_ARRAY_SIZE.
 *     - Does not handle multiple result groups.
 *     - At the time we look up attribute definitions, we have no idea what
 *       class they are in. This causes confusion with attributes with the 
 *       same name, but different sizes. The number of elements in the array 
 *       will be set to the first attribute by that name that it finds. 
 *       However, once it hits pg_fill_num_array, it will be realloc'd to the 
 *       right size.
 *
 * AUTHOR
 *     Jean T. Anderson, SAIC Open Systems Division
 */

#ifndef lint
static char SccsId[] = "@(#)pg_fetch.c	16.2 8/7/93 Copyright (c) 1992-1993 Science Applications International Corporation";
#endif

#include "gdi_postgres.h"
#include "limits.h"

#define PG_ARRAY_SIZE 8

extern long atol ();
extern double atof ();
extern char *strtok();

Proto ( static dbColDef *, pg_col_defs,
     (
     dbConn          *d,             /* (i) dbConn */
     PortalBuffer    *p,             /* (i) Postgres portal */
     int             g,              /* (i) Current group number */
     dbObj           *o              /* (i) dbObj */
     ));

Proto ( static int, pg_alloc_fetch_buffer,
       (
        dbConn       *dbconn,        /* (i) dbConn */
        dbObj        *dbobj,         /* (i) dbObj */
        int          ***arrlen,      /* (i/o) tracks array sizes */
        void         ***data         /* (i) pointer to the data  */
       ));

static Proto(int, pg_free_buffer, (dbObj *dbobj, int **arrlen, void **data));

static Proto (int, pg_fill_num_array, 
       (
        dbConn       *dbconn,      /* (i) database connector */
        dbObj        *dbobj,       /* (i) database object */
        int          col_num,      /* (i) column number */
        int          is_null,      /* (i) is this data null? */
        int          **arrlen,     /* (i/o) tracks actual array sizes */
        char         *data_in,     /* (i) string buffer from postgres portal */
        void         ***data_out,  /* (o) output data */
        int          *array_size   /* (i) size of the array */
       ));

static Proto (dbStatus, pg_fill_str_array, 
       (
        dbConn       *dbconn,      /* (i) database connector */
        dbObj        *dbobj,       /* (i) database object */
        int          col_num,      /* (i) column number */
        int          is_null,      /* (i) is this data null? */
        char         *data_in,     /* (i) string buffer from postgres portal */
        char         ***data_out,   /* (o) output data */
        int          *str_length,  /* (o) length of a string */
        int          *array_size   /* (i) size of the array */
       ));

static Proto (void, pg_str_to_num, 
       (
        dbCtype   c_type,       /* (i) output data type   */
        char      *from_ptr,    /* (i) string value */
        short     *tmp_short,
        int       *tmp_int,
        long      *tmp_long,
        float     *tmp_float,
        double    *tmp_double,
        int       is_null       /* (o) is data null? */
       ));

dbStatus
pg_fetch( dbconn, channo, dbobj, maxrec, pname )
dbConn    *dbconn;         /* (i) dbConn */
int       channo;          /* (i) Channel */
dbObj     *dbobj;          /* (i) dbObj will get tuple container down here */
int       maxrec;          /* (i) number of records user wants     */
char      *pname;          /* (i) portal the retrieve got executed on */
{
     char    *routine="pg_fetch";
     char    temp_buff[50];
     int     status;

          /* Portal buffer variables */
     int          i, j, k;
     int          t_tup = 0;     /* Num of tuples in the whole portal */
     int          t_grp = 0;     /* Num of instance groups */
     int          n_tup = 0;     /* Number of tuples in a group */
     int          ncols = 0;     /* Number of attributes */
     PortalBuffer *p = NULL;
     char         *res = NULL;
     char         *att_val = NULL;
     int          att_len;

     dbColDef     *new_col = NULL;  /* Column Definitions */
     void         *tup = NULL;     /* Tuple Container tuple */
     int          fetched_tuples = 0;
     int          user_wants=0;
     
     int          str_length, actual_arrlen, is_null;
     char         **tmp_char;
     int          **arrlen;
     void         **data;
     void         *dptr;
     char         *null_string="\0";

     /* ========  FETCH results ========= */

     switch(maxrec)
     {
          case GDI_FETCH_ALL:
               sprintf(temp_buff, "fetch all in %s", pname);
               break;
          case GDI_FETCH_DEFAULT: 
               /* fetch 1 more so we know if there are more data */
               sprintf(temp_buff, "fetch %d in %s", GDI_FETCH_DEFAULT+1,pname);
               break;
          default:
               sprintf(temp_buff, "fetch %d in %s", maxrec+1, pname);
               break;
     }

     if(dbconn->debug == GDI_DEBUG_VERBOSE)
          fprintf(stderr, "%16s: Query is '%s'\n", routine, temp_buff);

     res = (char *) PQexec (temp_buff);

     if( pg_error(dbconn, GDI_NOT_USED, res, routine) != GDI_SUCCESS )
          return(GDI_FAILURE);

     if (*res != 'P') 
     {
          (void) gdi_error_app(dbconn, GDI_UNIXERR, "pg_fetch: no portal!");
          return(GDI_FAILURE);
     }

     p = PQparray(++res);
     t_tup = PQntuples (p);          /* total tuples in all groups */
     t_grp = PQngroups (p);          /* number of groups */
     
     GDI_OBJ_ROWS_AFFECTED(dbobj) = t_tup;

     if(dbconn->debug == GDI_DEBUG_VERBOSE)
          fprintf(stderr, "%16s: tuples=%d, groups=%d\n", routine,t_tup,t_grp);
     if(t_tup == 0)
          return(GDI_SUCCESS);

     if (t_grp > 1)
     {
          (void) gdi_error_app(dbconn, GDI_BADDATA,
             "pg_fetch: Cannot handle multiple groups yet.");
          return(GDI_FAILURE);
     }

     /* ========= Process results ======= */

     for (k=0; k < t_grp; k++)
     {
          /* Future note: if k > 0 then create another dbObj.*/

          n_tup = PQntuplesGroup(p,k);     /* # tuples in this group */

          if(dbconn->debug == GDI_DEBUG_VERBOSE)
               fprintf(stderr, 
                    "%16s: group %d has %d tuples.\n", routine, k, n_tup);

          /* ===========================
           *  Create dbColDef
           * ===========================
           */
          if((new_col = (pg_col_defs(dbconn, p, k, dbobj))) == (dbColDef *)NULL)
                    return(GDI_FAILURE);

          ncols = GDI_OBJ_NUM_COLUMNS(dbobj);

          /* Now that we have the dbColDef, allocate a fetch buffer */

          if ( pg_alloc_fetch_buffer (dbconn, dbobj, &arrlen, &data) == GDI_FAILURE)
          {    (void) pg_free_buffer(dbobj, arrlen, data);
               return (GDI_FAILURE);
          }

          /* ===========================
           *  Create tuple container
           * ===========================
           */
          if( gdi_obj_container_create (dbobj) == GDI_FAILURE ) 
          {    (void) gdi_error_unix(dbconn, routine);
               (void) pg_free_buffer(dbobj, arrlen, data);
               return(GDI_FAILURE);
          }

          /* ===========================
           *  Get each instance
           * ===========================
           */

          /* How many instances are we really going to get? */
          switch(maxrec)
          {
               case GDI_FETCH_ALL:
                    user_wants = n_tup;     /* user wants everything */
                    break;
               case GDI_FETCH_DEFAULT:
                    if (n_tup < GDI_FETCH_DEFAULT)
                         user_wants = n_tup;
                    else
                         user_wants = GDI_FETCH_DEFAULT;
                    break;
               default:
                    if (n_tup < maxrec)
                         user_wants = n_tup;
                    else
                         user_wants = maxrec;
                    break;
          }

          for (i=0; i < user_wants ; i++, fetched_tuples++)
          {
               if((tup = gdi_obj_tuple_create (dbobj)) == NULL)
               {    (void) gdi_error_app(dbconn, GDI_BADDATA,
                         "pg_fetch: gdi_obj_tuple_create failed: ");
                    (void) pg_free_buffer(dbobj, arrlen, data);
                    return(GDI_FAILURE);
               }

               for (j = 0; j < ncols; j++)
               {
                    att_len = 0;
                    str_length = 0;
                    is_null = GDI_NOT_DBNULL;
                    actual_arrlen = GDI_OBJ_COL_MAX_ARRLEN(dbobj, j);

                    if((status = pg_get_attr (p, i, j, &att_val, &att_len))
                         == GDI_UNIXERR)
                    {
                         (void) gdi_error_unix(dbconn, routine);
                         (void) pg_free_buffer(dbobj, arrlen, data);
                         return(GDI_FAILURE);
                    }

                    if(status == GDI_NODATA)
                         is_null = GDI_DBNULL;

                    if((GDI_OBJ_COL_CTYPE(dbobj, j) == GDI_STRING)
                    || (GDI_OBJ_COL_CTYPE(dbobj, j) == GDI_CHAR)
                    || (GDI_OBJ_COL_CTYPE(dbobj, j) == GDI_UCHAR))
                    {
                          /* pass scalar string straight through */
                          if (actual_arrlen == GDI_SCALAR)
                          {
                               if(is_null == GDI_DBNULL)
                                    dptr = (void *) null_string;
                               else
                               {    str_length = att_len;
                                    dptr = (void *) att_val;
                               }
                          }
                          else
                          {    /* array of strings */
                               if(actual_arrlen == -1)
                                     actual_arrlen = PG_ARRAY_SIZE;

                               if((tmp_char = UCALLOC(char *, actual_arrlen))
                                    == NULL)
                               return(gdi_error_unix (dbconn, "pg_fetch: "));

                               if((pg_fill_str_array
                                   (dbconn, 
                                   dbobj,
                                   j,
                                   is_null,
                                   att_val, 
                                   &tmp_char, 
                                   &str_length,
                                   &actual_arrlen)) != GDI_SUCCESS)
                              {
                                   UFREE(tmp_char);
                                   (void) pg_free_buffer(dbobj, arrlen, data);
                                   return(GDI_FAILURE);
                              }
                              dptr = (void *) tmp_char;
                         }
                    } /* END it's a string */
                    else
                    {
                         /* Convert to a C data type */
                         if((pg_fill_num_array
                                   (dbconn, 
                                   dbobj,
                                   j,
                                   is_null,
                                   arrlen,
                                   att_val, 
                                   &data, 
                                   &actual_arrlen)) != GDI_SUCCESS)
                         {
                              (void) pg_free_buffer(dbobj, arrlen, data);
                              return(GDI_FAILURE);
                         }
                         dptr = (void *) data[j];
                    } /* END it's a number */

                    if(actual_arrlen == 1)
                         actual_arrlen = GDI_SCALAR;

                    if (gdi_obj_fill_data (
                              dbobj,
                              j,
                              tup,
                              dptr,
                              is_null,
                              str_length,
                              actual_arrlen)
                         != GDI_SUCCESS)
                    {
                         gdi_error_app(dbconn, GDI_BADDATA,
                              "pg_fetch: gdi_obj_fill_data failed");
                         (void) pg_free_buffer(dbobj, arrlen, data);
                         return(GDI_FAILURE);
                    }

                    if(((GDI_OBJ_COL_CTYPE(dbobj, j) == GDI_STRING)
                    ||  (GDI_OBJ_COL_CTYPE(dbobj, j) == GDI_CHAR)
                    ||  (GDI_OBJ_COL_CTYPE(dbobj, j) == GDI_UCHAR))
                    &&  (actual_arrlen != GDI_SCALAR) )
                         UFREE(tmp_char);
                    UFREE(att_val);

               } /* END process all columns in an instance */

               if(gdi_obj_tuple_add (dbobj, tup ) == GDI_FAILURE)
               {
                    gdi_error_app(dbconn,
                         GDI_UNIXERR, "pg_submit: gdi_obj_tuple_add failed");
                    (void) pg_free_buffer(dbobj, arrlen, data);
                    return(GDI_FAILURE);
               }
          } /* END process all instances in a result group */

        /* Future Note: if k > 0 then add dbObj to the dbObj linked list .*/

     }  /* END process all result groups */

     /* If the number of tuples fetched is less than available,
      * let the user know.
      */
     if (fetched_tuples < n_tup)
     {
          GDI_OBJ_MORE_ROWS(dbobj) = TRUE;
          GDI_OBJ_NUM_TUPLES(dbobj) = fetched_tuples;
     }
     else
          GDI_OBJ_NUM_TUPLES(dbobj) = n_tup;

     (void) pg_free_buffer(dbobj, arrlen, data);
     return(GDI_SUCCESS);          
}

static
dbColDef *
pg_col_defs (d, p, g, o)
dbConn        *d;     /* (i) dbConn */
PortalBuffer  *p;     /* (i) portal buffer */
int           g;      /* (i) group number */
dbObj         *o;     /* (i) database object */
{
     char     *r = "pg_col_defs";
     int     i, n;

          /* dbColDef Variables
           * ==================
           * Even though Postgres doesn't use allow_null, precision, and 
           * scale, we include them below so folks maintaining multiple
           * db interfaces won't be confused.
           */
     dbColDef     *new_ColDef = NULL;
     char          name[GDI_MAX_NAME+1];
     dbCtype       ctype;
     int           allow_null = GDI_NOT_USED,
                   max_strlen = 0,
                   max_arrlen = GDI_SCALAR,     /* initialize to default */
                   precision = GDI_NOT_USED,
                   scale = GDI_NOT_USED;
     pgType        dbtype;                      /* cast to an int */
     char          dbtype_s[GDI_MAX_TYPE+1];

     n = PQnfieldsGroup(p, g);
     if(d->debug == GDI_DEBUG_VERBOSE)
          fprintf(stderr, "%16s: group %d has %d columns.\n", r, g, n);

     for (i=0; i < n; i++)
     {
          /* column name */
          sprintf (name, "%s",     PQfnameGroup (p, g, i));

          /* PQgetlength() returns the attribute length the 
           * first tuple in a result group.
           * 
           * Correct ColDefs after the fact to reflect the 
           * max length of GDI_STRING columns.
           */

          max_strlen     = PQgetlength (p, g, i);
          dbtype         = PQftype (p, g, i);

               /* Get Database Type info */
          if ( pg_build_dbtype
                    (d,
                    name,
                    &dbtype,
                    dbtype_s,
                    sizeof(dbtype_s),
                    &max_arrlen)
               == GDI_FAILURE )
                    return ( (dbColDef *)NULL );

               /* Decide on an external C type, taking into 
                * account the constructor's coerce option.
                */
          if (pg_derive_ctype
                    (GDI_OBJ_COERCE_OPTION(o),
                    dbtype,
                    &ctype,
                    &max_strlen)
               == GDI_FAILURE)
               return ( (dbColDef *)NULL );

          if ((new_ColDef= gdi_col_def_create ( 
                    name, 
                    ctype, 
                    allow_null, 
                    max_strlen, 
                    max_arrlen, 
                    dbtype, 
                    precision,
                    scale,
                    dbtype_s)) == NULL )
          {
                            (void) gdi_error_app(d, GDI_UNIXERR, 
                    "pg_fetch: gdi_col_def_create failed");
               return ( (dbColDef *)NULL );
          }

          if (gdi_obj_col_def_add(o, new_ColDef) == GDI_FAILURE)
          {
                            (void) gdi_error_app(d, GDI_UNIXERR, 
                    "pg_fetch: gdi_col_def_create failed");
               return ( (dbColDef *)NULL );

          }
     }
     GDI_OBJ_NUM_COLUMNS(o) = n;
     return (new_ColDef);
}


/* =============================  pg_get_attr() ============================
 *
 * Get a string value from an attribute in a Portal buffer.
 *
 * Public
 */

dbStatus
pg_get_attr(portalbuf, tupno, attno, result, length)
PortalBuffer     *portalbuf;
int          tupno;
int          attno;
char          **result;
int          *length;
{
     char     *attval;
     char     *att_ptr;
     int     att_len;

     *length = 0;

     attval = (char *) PQgetvalue(portalbuf, tupno, attno);

     if (attval == NULL)
          return(GDI_NODATA);

     att_len=strlen(attval);

     if ( (att_ptr = UCALLOC (char, att_len + 1)) == (char *)NULL )
          return(GDI_UNIXERR);

     (void) pg_strncpy(att_ptr, attval, att_len, att_len);

     *length = att_len + 1;	/* size including the NULL terminator */
     *result = att_ptr;
     return (GDI_SUCCESS);
}

/* pg_alloc_fetch_buffer()
 *
 * Allocate buffer for fetching results.  We are only fetching one row at
 * a time right now, but are leaving the hooks in for a time when we can
 * figure out how to fetch in larger buffer sizes.
 *
 * Private
 */

static int
pg_alloc_fetch_buffer (dbconn, dbobj, arrlen, data)
dbConn   *dbconn;     /* (i)   database connector */
dbObj    *dbobj;      /* (i/o) database object */
int      ***arrlen;   /* (i/o) tracks actual array sizes */
void     ***data;     /* (o)   array of pointers to arrays for attributes */
{
     int     i;
     int     num_cols;
     int     elength;
     int     arr_length = 1;

     num_cols = gdi_obj_num_columns(dbobj);

     if( ((*data = UCALLOC(void *, num_cols)) == NULL) 
      || ((*arrlen = UCALLOC(int *, num_cols)) == NULL) )
          return (gdi_error_unix (dbconn, "pg_alloc_fetch_buffer: "));

     for( i = 0; i < num_cols; i++ )
         if(( (*arrlen)[i] = UCALLOC(int, 1)) == NULL)
               return( gdi_error_unix (dbconn, "pg_alloc_fetch_buffer: "));
          
     for( i = 0; i < num_cols; i++ )
     {
          arr_length = GDI_OBJ_COL_MAX_ARRLEN(dbobj, i);

          if (arr_length == 0)
               arr_length = 1;
          else if (arr_length == -1)
               arr_length = PG_ARRAY_SIZE;

          *((*arrlen)[i]) = arr_length;

          if (pg_derive_etype(GDI_OBJ_COL_CTYPE(dbobj, i),
               GDI_OBJ_COL_LENGTH(dbobj, i), &elength) == GDI_FAILURE)
          {    gdi_error_app (dbconn, GDI_BADDATA,
                    "pg_alloc_buffer: pg_derive_etype ");
               return (GDI_FAILURE);
          }

          if (elength == 0)
          {     gdi_error_app (dbconn, GDI_BADDATA,
                    "pg_alloc_buffer: pg_derive_etype: 0 elength ");
               return (GDI_FAILURE);
          }

          if ( ( (*data)[i] = (void *) UCALLOC(char , (elength * arr_length)) ) == NULL)
          {     (void) gdi_error_unix (dbconn, 
                    "pg_alloc_fetch_buffer: ");
               return (GDI_FAILURE);
          }
     }
     return(GDI_SUCCESS);
}

/* 
 * pg_free_buffer()
 *
 * Free memory dynamically allocated to move data from portal to final
 * results buffer.
 *
 * Private
 */

static 
int
pg_free_buffer(dbobj, arrlen, data)
dbObj   *dbobj;
int     **arrlen;
void    **data;
{
     int     i;
     int     ncols;

     ncols = gdi_obj_num_columns(dbobj);

     for (i = 0; i < ncols; i++)
     {
          UFREE(data[i]);
          UFREE(arrlen[i]);
     }
     
     UFREE(data);
     UFREE(arrlen);

     return(GDI_SUCCESS);
}

static 
int 
pg_fill_num_array (dbconn, dbobj, col_num, is_null, arrlen, data_in, data_out, 
     array_size)
dbConn       *dbconn;      /* (i) database connector */
dbObj        *dbobj;       /* (i) database object */
int          col_num;      /* (i) column_number */
int          is_null;      /* (i) is this data null? */
int          **arrlen;     /* (i/o) tracks actual arrays sizes */
char         *data_in;     /* (i) string buffer from postgres portal */
void         ***data_out;  /* (o) output data */
int          *array_size;  /* (o) actual size of the array */
{
     char     *routine="pg_fill_num_array";
     char     temp_buff[GDI_ERROR_SIZE];
     char     *ptr;
     void     *rptr;
     int      i, csize, len, cur_size, done = FALSE, first = TRUE;
     dbCtype  output_type;
     dbStatus status = GDI_SUCCESS;

     short    tmp_short;
     int      tmp_int;
     long     tmp_long;
     float    tmp_float;
     double   tmp_double;

     output_type = GDI_OBJ_COL_CTYPE(dbobj, col_num);
     cur_size = *(arrlen[col_num]) ;

     i = 0;
     while(done == FALSE)
     {
          /* Undimensioned arrays look like this:  {1,2,3,4} 
           *                            and this:  {"1.1","2.2","3.3","4.4"} 
           *   Dimensioned arrays look like this:  1 2 3 4
           */
          if(first == TRUE)
          {
               ptr = strtok(data_in, ", \t\n");
               first = FALSE;
          }
          else
               ptr = strtok((char *)NULL, ", \t\n");

          if(ptr)
          {
               /* We still have data; have we run out of pointers? */
               if ( i == cur_size)
               {
                    /* what is the size of this C data type? */
                    if (pg_derive_etype(output_type, 0, &csize) == GDI_FAILURE)
                    {    gdi_error_app (dbconn, GDI_BADDATA,
                              "pg_fill_num_array: pg_derive_etype ");
                         return (GDI_FAILURE);
                    }

                    cur_size += PG_ARRAY_SIZE;

                    if ((rptr = (void *) UREALLOC( (*data_out)[col_num], char ,
                        (csize * cur_size) )) == NULL)
                         return (gdi_error_unix (dbconn, routine));
                    (*data_out)[col_num] = rptr;
               }

               /* Is this puppy a real number or does it have array
                * gobbledegook?
                * It's expensive to check these, but attnelems didn't
                * consistently return -1 on bigfoot for undimensioned arrays.
                */

               if(ptr[0] == '{')
                    ptr++;
               if(ptr[0] == '"')
               {
                    ptr++;
                    len=strlen(ptr);
                    if (ptr[len] == '"')
                         ptr[len] = '\0';
               }
               len=strlen(ptr);
               if (ptr[len] == '}')
                    ptr[len] = '\0';

               pg_str_to_num ( output_type, ptr, &tmp_short, &tmp_int,
                    &tmp_long, &tmp_float, &tmp_double, is_null);

               switch(output_type)
               {
               case GDI_CHAR:
               case GDI_UCHAR:
               case GDI_STRING:
                    sprintf(temp_buff, 
                   "%s: column %d is a string, use pg_fill_str_array",
                        routine, col_num);
                        gdi_error_app(dbconn, GDI_BADDATA, temp_buff);
                    status = GDI_FAILURE;
                    break;
               case GDI_SHORT:
               case GDI_USHORT:
                    ( (short *) (*data_out)[col_num] ) [i]  = tmp_short;
                    break;
               case GDI_INTEGER:
               case GDI_UINTEGER:
                    ( (int *) (*data_out)[col_num] ) [i]  = tmp_int;
                    break;
               case GDI_LONG:
               case GDI_ULONG:
                    ( (long *) (*data_out)[col_num] ) [i]  = tmp_long;
                    break;
               case GDI_FLOAT:
                    ( (float *) (*data_out)[col_num] ) [i]  = tmp_float;
                    break;
               case GDI_DOUBLE:
                    ( (double *) (*data_out)[col_num] ) [i]  = tmp_double;
                    break;
               case GDI_USER_DEFINED:
                    sprintf(temp_buff, 
                   "%s: Cannot handle user-defined datatype in column '%d' yet",
                        routine, col_num);
                   gdi_error_app(dbconn, GDI_BADDATA, temp_buff);
                    status = GDI_FAILURE;
                    break;
               default:
                    sprintf(temp_buff, 
                    "%s: Unknown datatype in column '%d'", routine, col_num);
                    gdi_error_app(dbconn, GDI_BADDATA, temp_buff);
                    status = GDI_FAILURE;
                    break;
               } /* End switch on data type */

               i++;
          }
          else
               done = TRUE;
     } /* End process each element in the array */

     
     *array_size = i;     /* send back the actual size of the array */
     *(arrlen[col_num]) = cur_size;

     return(status);
}

static 
void
pg_str_to_num (c_type, from_ptr, tmp_short, tmp_int, tmp_long, tmp_float, 
     tmp_double, is_null)
dbCtype   c_type;
char      *from_ptr;
short     *tmp_short;
int       *tmp_int;
long      *tmp_long;
float     *tmp_float;
double    *tmp_double;
int       is_null;
{
          switch(c_type)
          {
          case GDI_SHORT:
          case GDI_USHORT:
               if(from_ptr)  *tmp_short = (short) atoi(from_ptr);
               else          *tmp_short = 0;
               break;
          case GDI_INTEGER:
          case GDI_UINTEGER:
               if(from_ptr)  *tmp_int = (int) atoi(from_ptr);
               else          *tmp_int = 0;
               break;
          case GDI_LONG:
          case GDI_ULONG:
               if(from_ptr)  *tmp_long = (long) atol(from_ptr);
               else          *tmp_long = 0.0;
               break;
          case GDI_FLOAT:
               if(from_ptr)  *tmp_float = (float) atof(from_ptr);
               else          *tmp_float = 0.0;
               break;
          case GDI_DOUBLE:
               if(from_ptr)  *tmp_double = (double) atof(from_ptr);
               else          *tmp_double = 0.0;
               break;
          default:
               break;
          }
}

static 
dbStatus
pg_fill_str_array (dbconn, dbobj, col_num, is_null, data_in, data_out, 
     str_length, array_size)
dbConn       *dbconn;      /* (i) database connector */
dbObj        *dbobj;       /* (i) database object */
int          col_num;      /* (i) column_number */
int          is_null;      /* (i) is this data null? */
char         *data_in;     /* (i) string buffer from postgres portal */
char         ***data_out;  /* (o) output data */
int          *array_size;  /* (i/o) actual size of the array */
int          *str_length;  /* (o) size of largest string in the array */
{
     char    *routine="pg_fill_str_array";
     char    temp_buff[GDI_ERROR_SIZE];
     char    *ptr, **rptr;
     int     first = TRUE, done = FALSE, col_length, i;
     dbCtype output_type;
     int     in_field;

     output_type = GDI_OBJ_COL_CTYPE(dbobj, col_num);

     if( (output_type != GDI_CHAR) && (output_type != GDI_UCHAR)
      && (output_type != GDI_STRING) )
     {
          sprintf(temp_buff, 
              "%s: column %d is of type %d, this routine is for strings",
                   routine, col_num, output_type);
                   gdi_error_app(dbconn, GDI_BADDATA, temp_buff);
          return(GDI_FAILURE);
     }

     /* The input data is a string off the portal that looks like this:
      *
      *          {"howard","jean","bonnie","mari"}
      *
      * Set up the pointers that will make it look like an array of strings:
      *
      *          howard
      *          jean
      *          bonnie
      *          mari
      */

      i=0;
      while(done == FALSE)
      {
          if(first == TRUE)
          {
               ptr = data_in;
               first = FALSE;
               in_field = FALSE;
          }

          if(*ptr == '}')
               done = TRUE;
          else if(*ptr == '"')
          {
               if(in_field == FALSE)
               {
                    in_field = TRUE; /* this is the beginning of a field */

                    /* Have we run out of pointers? */
                    if ( i == *array_size )
                    {
                         *array_size = *array_size + PG_ARRAY_SIZE;
                         if ((rptr= UREALLOC((*data_out), char *, *array_size)) 
                              == NULL)
                         return (gdi_error_unix (dbconn, routine));
                         (*data_out) = rptr;
                    }

                    ptr++; /* get past the double quote */

                    (*data_out)[i] = ptr;
               }
               else if(in_field == TRUE) /* end of field */
               {
                    in_field = FALSE;
                    *ptr = '\0';         /* null terminate */

                    col_length = strlen ((*data_out)[i]);

                    if (col_length > *str_length)
                        *str_length = col_length + 1;   /* reset max size */
                    i++;
               }
          }
          else
              ptr++;
       }
      *array_size = i;     /* send back the actual size of the array */

      return(GDI_SUCCESS);
}
