/***************************************************************
 * Run Time Access
 * Copyright (C) 2003 Robert W Smith (bsmith@linuxtoys.org)
 *
 *  This program is distributed under the terms of the GNU LGPL.
 *  See the file COPYING file.
 **************************************************************/

/***************************************************************
 * do_sql.c:  The subroutines in this file execute the SQL
 * commands received from the UIs.  
 **************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>             /* for va_arg */
#include <string.h>
#if 0
#include <syslog.h>
#endif
#include "log.h"
#include "do_sql.h"
#include "list.h"
#include "hash.h"

struct Sql_Cmd cmd;

extern TBLDEF *Tbl[];
extern int Ntbl;
extern COLDEF *Col[];
extern int Ncol;
extern struct EpgStat rtastat;
extern struct EpgDbg rtadbg;

/***************************************************************
 * How this stuff works:
 *   The main program accepts TCP connections from Postgres
 * clients.  The main program collects the SQL command from
 * the clients and calls dbcommand() to handle the protocol
 * between the DB client and our embedded DB server.  The
 * routine dbcommand() handles the actual SQL by setting up
 * a lex buffer and calling yyparse() to invoke the yacc/lex
 * scanner for our micro-SQL.  The scanner parses the command
 * and places the relevant information into the "sql_cmd"
 * structure.  If the parse of the command is successful, the
 * routine do_sql() is called to actually execute the command.
 **************************************************************/

/***************************************************************
 * do_sql(): - Execute the SQL select or update command in the
 * sql_cmd structure.
 *
 * Input:        A buffer to store the output
 *               The number of free bytes in the buffer
 * Output:       The number of free bytes in the buffer
 * Effects:      Lots.  This is where the read and write
 *               callbacks are executed.
 ***************************************************************/
void
do_sql(char *buf, int *nbuf)
{
  switch (cmd.command)
  {
    case RTA_SELECT:
      verify_table_name(buf, nbuf);
      if (cmd.err)
        return;
      verify_select_list(buf, nbuf);
      if (cmd.err)
        return;
      verify_where_list(buf, nbuf);
      if (cmd.err)
        return;

      /* The command looks good.  Send the Row Desc. */
      buf += send_row_description(buf, nbuf);
      if (cmd.err)
        return;
      do_select(buf, nbuf);
      rtastat.nselect++;
      break;

    case RTA_UPDATE:
      verify_table_name(buf, nbuf);
      if (cmd.err)
        return;
      verify_update_list(buf, nbuf);
      if (cmd.err)
        return;
      verify_where_list(buf, nbuf);
      if (cmd.err)
        return;

      /* The command looks good. Update and do callbacks */
      sqldo_update(buf, nbuf);
      rtastat.nupdate++;
      break;

    case RTA_CALL:
      do_call(buf, nbuf);
      break;

    case RTA_BEGIN:
      do_begin(buf, nbuf);
      break;

    case RTA_COMMIT:
      do_commit(buf, nbuf);
      break;

    default:
      nlog(LOG_CORE, LOG_NOTICE, "SQLSRC error: no SQL cmd\n");
      break;
  }
}

/***************************************************************
 * verify_table_name(): - Verify that the table referenced in
 * the command exists.   We want to make sure everything is
 * correct before we start any of the actual command since we 
 * we don't want side effects left over from a failed command.
 * On error, we output the error message and set the err flag.
 *
 * Input:        A buffer to store the output
 *               The number of free bytes in the buffer
 * Output:       The number of free bytes in the buffer
 * Effects:      The err flag, and the output buffer
 ***************************************************************/
void
verify_table_name(char *buf, int *nbuf)
{
  int      i;          /* temp integers */

  /* if its for the rta_tables then calc the size of each list or hash */
  if (!strncmp(cmd.tbl, Tbl[0]->name, MXTBLNAME))
  {
    for (i = 0; i < Ntbl; i++)
    {
      if (Tbl[i]->tabletype == TBL_LIST)
      {
        Tbl[i]->nrows = list_count((list_t *) Tbl[i]->address);
      }
      else if (Tbl[i]->tabletype == TBL_HASH)
      {
        Tbl[i]->nrows = hash_count((hash_t *) Tbl[i]->address);
      }
    }
  }

  /* Verify that the table exists */
  for (i = 0; i < Ntbl; i++)
  {
    if ((Tbl[i] != (TBLDEF *) 0) &&
      (!strncmp(cmd.tbl, Tbl[i]->name, MXTBLNAME)))
    {
      break;
    }
  }
  if ((Tbl[i] == (TBLDEF *) 0) || (i == Ntbl))
  {
    send_error(LOC, E_NOTABLE, cmd.tbl);
    return;
  }

  /* Save the pointer to the table in TBLDEFS for later use */
  cmd.itbl = i;
  cmd.ptbl = Tbl[i];
  if (Tbl[i]->tabletype == TBL_LIST)
  {
    Tbl[i]->nrows = list_count((list_t *) Tbl[i]->address);
  }
  else if (Tbl[i]->tabletype == TBL_HASH)
  {
    Tbl[i]->nrows = hash_count((hash_t *) Tbl[i]->address);
  }
}

/***************************************************************
 * verify_select_list(): - Verify the list of column to display
 * in a select statement.  We want to make sure everything is
 * correct before we start any of the actual select since we 
 * we don't want side effects left over from a failed select.
 * This is where we compute the length of each output line so
 * we can test buffer space later.
 * On error, we output the error message and set the err flag.
 *
 * Input:        A buffer to store the output
 *               The number of free bytes in the buffer
 * Output:       The number of free bytes in the buffer
 * Effects:      The err flag, and the output buffer
 ***************************************************************/
void
verify_select_list(char *buf, int *nbuf)
{
  COLDEF  *coldefs;    /* the table of columns */
  int      ncols;      /* the number of columns */
  int      i, j;       /* temp integers */

  /* Verify that each column exists in the right table */
  coldefs = cmd.ptbl->cols;
  ncols = cmd.ptbl->ncol;

  /* Handle the special case of a SELECT * FROM .... We look for the
     '*', then for each column in the table, free any memory in
     cmd.cols, get the size of the column name, allocate memeory, copy
     the column name, and put the pointer into cmd.cols.  */
  if (cmd.cols[0][0] == '*')
  {
    /* they are asking for the full column list */
    for (i = 0; i < ncols; i++)
    {
      if (cmd.cols[i])
        free(cmd.cols[i]);
      cmd.cols[i] = malloc(strlen(coldefs[i].name) + 1);
      if (cmd.cols[i] == (void *) 0)
      {
        rtastat.nsyserr++;
        if (rtadbg.syserr)
          rtalog(LOC, Er_No_Mem);
        cmd.err = 1;
        return;
      }
      strcpy(cmd.cols[i], coldefs[i].name);

      /* Save pointer to the column in COLDEFS */
      cmd.pcol[i] = &(coldefs[i]);
    }

    /* Give the command the correct # cols to display */
    cmd.ncols = ncols;
  }

  /* OK, scan the columns definitions to verify that the columns in the 
     command are really columns in the table. Scan is ...for each col
     in select list ... */
  for (j = 0; j < NCMDCOLS; j++)
  {
    if (cmd.cols[j] == (char *) 0)
    {
      return;                   /* select list is OK */
    }

    /* Scan is ...for each column definition ... */
    for (i = 0; i < ncols; i++)
    {
      /* Is this column in the table of interest? */
      if (!strncmp(cmd.cols[j], coldefs[i].name, MXCOLNAME))
      {
        /* Save pointer to the column in COLDEFS */
        cmd.pcol[j] = &(coldefs[i]);

        /* Compute length of output line */
        switch (coldefs[i].type)
        {
          case RTA_STR:
          case RTA_PSTR:
            cmd.nlineout += coldefs[i].length;
            break;
          case RTA_PTR:
          case RTA_INT:
          case RTA_PINT:
            cmd.nlineout += MX_INT_STRING;
            break;
          case RTA_LONG:
          case RTA_PLONG:
            cmd.nlineout += MX_LONG_STRING;
            break;
          case RTA_FLOAT:
          case RTA_PFLOAT:
            cmd.nlineout += MX_FLOT_STRING;
            break;
        }
        cmd.nlineout += 4;      /* 4 byte length in reply */
        break;
      }
    }

    /* We scanned column defs.  Error if not found */
    if (i == ncols)
    {
      send_error(LOC, E_NOCOLUMN, cmd.cols[j]);
      return;
    }
  }

  /* The select column list is OK */
  return;
}

/***************************************************************
 * verify_where_list(): - Verify the relations in a WHERE
 * clause.  We want to make sure everything is correct before
 * we start any of the actual select/update since we we don't
 * want side effects left over from a failed command.
 * On error, we output the error message and set the err flag.
 * We check that each column is in the specified table, and that
 * the type of the data in each phrase (eg,  x = 12) matches the
 * data type for that column.
 *
 * Input:        A buffer to store the output
 *               The number of free bytes in the buffer
 * Output:       The number of free bytes in the buffer
 * Effects:      The err flag and the output buffer on error
 ***************************************************************/
void
verify_where_list(char *buf, int *nbuf)
{
  COLDEF  *coldefs;    /* the table of columns */
  int      ncols;      /* the number of columns */
  int      i, j;       /* Loop index */

  /* Verify that each column exists in the right table */
  coldefs = cmd.ptbl->cols;
  ncols = cmd.ptbl->ncol;

  /* scan the columns definitions to verify that the columns in the
     command are really columns in the table. Scan is ...for each col
     in where list ... */
  for (j = 0; j < NCMDCOLS; j++)
  {
    if (cmd.whrcols[j] == (char *) 0)
    {
      return;
    }

    /* Scan is ...for each column definition ... */
    for (i = 0; i < ncols; i++)
    {
      /* we are on a column def for the right table */
      if (!strncmp(cmd.whrcols[j], coldefs[i].name, MXCOLNAME))
      {
        /* column is valid, now check data type.  Must be string or
           num, if num, need val */
        if ((coldefs[i].type == RTA_STR) ||
          (coldefs[i].type == RTA_PSTR) ||
          ((coldefs[i].type == RTA_INT)
            && (sscanf(cmd.whrvals[j], "%d", &(cmd.whrints[j])) == 1))
          || ((coldefs[i].type == RTA_PINT)
            && (sscanf(cmd.whrvals[j], "%d", &(cmd.whrints[j])) == 1))
          || ((coldefs[i].type == RTA_PTR)
            && (sscanf(cmd.whrvals[j], "%d", &(cmd.whrints[j])) == 1))
          || ((coldefs[i].type == RTA_LONG)
            && (sscanf(cmd.whrvals[j], "%lld", &(cmd.whrlngs[j])) == 1))
          || ((coldefs[i].type == RTA_PLONG)
            && (sscanf(cmd.whrvals[j], "%lld", &(cmd.whrlngs[j])) == 1))
          || ((coldefs[i].type == RTA_FLOAT)
            && (sscanf(cmd.whrvals[j], "%f", &(cmd.whrflot[j])) == 1))
          || ((coldefs[i].type == RTA_PFLOAT)
            && (sscanf(cmd.whrvals[j], "%f", &(cmd.whrflot[j])) == 1)))
        {
          /* Save WHERE column pointer for later use */
          cmd.pwhr[j] = &(coldefs[i]);
          break;
        }

        /* bogus where phrase */
        send_error(LOC, E_BADPARSE);
        return;
      }
    }

    /* We scanned column defs.  Error if not found */
    if (i == ncols)
    {
      send_error(LOC, E_NOCOLUMN, cmd.whrcols[j]);
      return;
    }
  }

  /* The where column list is OK */
  return;
}

/***************************************************************
 * verify_update_list(): - Verify the list of column to update
 * in an update statement.  We want to make sure everything is
 * correct before we start any of the actual select since we 
 * we don't want side effects left over from a failed select.
 * On error, we output the error message and set the err flag.
 * We verify that the columns are indeed part of the specified
 * table and that the data type of the column matches the data
 * type of the right-hand-side.
 *
 * Input:        A buffer to store the output
 *               The number of free bytes in the buffer
 * Output:       The number of free bytes in the buffer
 * Effects:      The err flag and the output buffer on error
 ***************************************************************/
void
verify_update_list(char *buf, int *nbuf)
{
  COLDEF  *coldefs;    /* the table of columns */
  int      ncols;      /* the number of columns */
  int      i, j;       /* Loop index */

  /* Verify that each column exists in the right table */
  coldefs = cmd.ptbl->cols;
  ncols = cmd.ptbl->ncol;

  /* scan the columns definitions to verify that the columns in the
     command are really columns in the table. Scan is ...for each col
     in update list ... */
  for (j = 0; j < NCMDCOLS; j++)
  {
    if (cmd.cols[j] == (char *) 0)
    {
      return;
    }

    /* Scan is ...for each column definition ... */
    for (i = 0; i < ncols; i++)
    {
      if (strncmp(cmd.cols[j], coldefs[i].name, MXCOLNAME))
        continue;

      /* Save pointer to the column in COLDEFS */
      cmd.pcol[j] = &(coldefs[i]);

      /* Verify that column is not read-only */
      if (coldefs[i].flags & RTA_READONLY)
      {
        send_error(LOC, E_NOWRITE, coldefs[i].name);
        return;
      }

      /* Column exists and is not read-only. * Now check data type.  If 
         a string, check the * string length */
      if ((coldefs[i].type == RTA_STR) || (coldefs[i].type == RTA_PSTR))
      {
        if (strlen(cmd.updvals[j]) <= coldefs[i].length)
        {
          break;
        }
        send_error(LOC, E_BIGSTR, coldefs[i].name);
        return;
      }

      /* Verify conversion of int/long */
      if (((coldefs[i].type == RTA_INT)
          && (sscanf(cmd.updvals[j], "%d", &(cmd.updints[j])) == 1))
        || ((coldefs[i].type == RTA_PINT)
          && (sscanf(cmd.updvals[j], "%d", &(cmd.updints[j])) == 1))
        || ((coldefs[i].type == RTA_PTR)
          && (sscanf(cmd.updvals[j], "%d", &(cmd.updints[j])) == 1))
        || ((coldefs[i].type == RTA_LONG)
          && (sscanf(cmd.updvals[j], "%lld", &(cmd.updlngs[j])) == 1))
        || ((coldefs[i].type == RTA_PLONG)
          && (sscanf(cmd.updvals[j], "%lld", &(cmd.updlngs[j])) == 1))
        || ((coldefs[i].type == RTA_FLOAT)
          && (sscanf(cmd.updvals[j], "%f", &(cmd.updflot[j])) == 1))
        || ((coldefs[i].type == RTA_PFLOAT)
          && (sscanf(cmd.updvals[j], "%f", &(cmd.updflot[j])) == 1)))
      {
        break;
      }

      /* bogus update list */
      send_error(LOC, E_BADPARSE);
      return;
    }

    /* We scanned column defs.  Error if not found */
    if (i == ncols)
    {
      send_error(LOC, E_NOCOLUMN, cmd.cols[j]);
      return;
    }
  }

  /* The update column list is OK */
  return;
}

/***************************************************************
 * do_select(): - Execute a SELECT statement against the DB.
 *
 * Input:        A buffer to store the output
 *               The number of free bytes in the buffer
 * Output:       The number of free bytes in the buffer
 * Effects:      Maybe lots.  This is where the read callbacks
 *               are executed.
 ***************************************************************/
void
do_select(char *buf, int *nbuf)
{
  int      nr;         /* the Number of Rows in the table */
  int      sr;         /* the Size of each Row in the table */
  int      rx;         /* Row indeX in for() loop */
  int      wx;         /* Where clause indeX in for loop */
  void    *pd;         /* Pointer to the Data in the table/column */
  long long cmp;       /* has actual relation of col and val */
  int      dor;        /* DO Row == 1 if we should print row */
  int      npr = 0;    /* Number of output rows */
  char     nprstr[30]; /* string to hold ASCII of npr */
  char    *startbuf;   /* used to compute response length */
  int      cx;         /* Column index while building Data pkt */
  int      n;          /* number of chars printed in sprintf() */
  lnode_t *node;       /* the current node */
  hnode_t *hnode;
  hscan_t  hs;

  startbuf = buf;

  /* We loop through all rows in the table in question applying the
     WHERE condition.  If a row matches we perform read callbacks on
     the selected columns and build a reply with the requested data */

  nr = cmd.ptbl->nrows;
  sr = cmd.ptbl->rowlen;
  node = NULL;
  hnode = NULL;
  if (cmd.ptbl->tabletype == TBL_LIST)
  {
    node = list_first((list_t *) cmd.ptbl->address);
  }
  else if (cmd.ptbl->tabletype == TBL_HASH)
  {
    hash_scan_begin(&hs, (hash_t *) cmd.ptbl->address);
    hnode = hash_scan_next(&hs);
  }
  for (rx = 0; rx < nr; rx++)
  {
    dor = 1;
    for (wx = 0; wx < cmd.nwhrcols; wx++)
    {
      /* execute read callback (if defined) on row */
      /* the call back is expected to fill in the data */
      if ((cmd.pwhr[wx]->readcb) && ((cmd.ptbl->tabletype != TBL_LIST)
          && (cmd.ptbl->tabletype != TBL_HASH)))
      {
        (cmd.pwhr[wx]->readcb) (cmd.ptbl, cmd.whrcols[wx], cmd.sqlcmd,
          (int *) rx);
      }
      /* compute pointer to actual data */
      if (cmd.ptbl->tabletype == TBL_LIST)
      {
        if (cmd.pwhr[wx]->readcb)
        {
          pd =
            (cmd.pwhr[wx]->readcb) (cmd.ptbl, cmd.whrcols[wx],
            cmd.sqlcmd, lnode_get(node));
        }
        else
        {
          pd = lnode_get(node) + cmd.pwhr[wx]->offset;
        }
      }
      else if (cmd.ptbl->tabletype == TBL_HASH)
      {
        if (cmd.pwhr[wx]->readcb)
        {
          pd =
            (cmd.pwhr[wx]->readcb) (cmd.ptbl, cmd.whrcols[wx],
            cmd.sqlcmd, hnode_get(hnode));
        }
        else
        {
          pd = hnode_get(hnode) + cmd.pwhr[wx]->offset;
        }
      }
      else
      {
        if (cmd.itbl > RTA_COLUMNS)
          pd = cmd.ptbl->address + (rx * sr) + cmd.pwhr[wx]->offset;
        else if (cmd.itbl == RTA_TABLES)
          pd = (void *) Tbl[rx] + cmd.pwhr[wx]->offset;
        else
          pd = (void *) Col[rx] + cmd.pwhr[wx]->offset;
      }
      /* do comparison based on column data type */
      switch (cmd.pwhr[wx]->type)
      {
        case RTA_STR:
          cmp = strncmp((char *) pd, cmd.whrvals[wx],
            cmd.pwhr[wx]->length);
          break;
        case RTA_PSTR:
          cmp = strncmp(*(char **) pd, cmd.whrvals[wx],
            cmd.pwhr[wx]->length);
          break;
        case RTA_INT:
          cmp = *((int *) pd) - cmd.whrints[wx];
          break;
        case RTA_PINT:
          cmp = **((int **) pd) - cmd.whrints[wx];
          break;
        case RTA_LONG:
          cmp = *((long long *) pd) - cmd.whrlngs[wx];
          break;
        case RTA_PLONG:
          cmp = **((long long **) pd) - cmd.whrlngs[wx];
          break;
        case RTA_PTR:
          cmp = *((int *) pd) - cmd.whrints[wx];
          break;
        case RTA_FLOAT:
          cmp = *((float *) pd) - cmd.whrflot[wx];
          break;
        case RTA_PFLOAT:
          cmp = **((float **) pd) - cmd.whrflot[wx];
          break;
        default:
          cmp = 1;              /* assume no match */
          break;
      }
      if (!(((cmp == 0) && (cmd.whrrel[wx] == RTA_EQ ||
              cmd.whrrel[wx] == RTA_GE ||
              cmd.whrrel[wx] == RTA_LE)) ||
          ((cmp != 0) && (cmd.whrrel[wx] == RTA_NE)) ||
          ((cmp < 0) && (cmd.whrrel[wx] == RTA_LE ||
              cmd.whrrel[wx] == RTA_LT)) ||
          ((cmp > 0) && (cmd.whrrel[wx] == RTA_GE ||
              cmd.whrrel[wx] == RTA_GT))))
      {
        dor = 0;
        break;
      }
    }
    if (dor)
    {
      /* if we get here, we've passed the WHERE clause. Check for LIMIT 
         and OFFSET filtering */
      if (cmd.offset)
      {
        cmd.offset--;
        goto increment;
      }
      if (npr >= cmd.limit)
      {
        break;
      }

      /* Verify that the buffer has enough room for this * row. */
      if (*nbuf - ((int) (buf - startbuf)) - cmd.nlineout < 100)
      {                         /* 100 for CSELECT and margin */
        send_error(LOC, E_FULLBUF);
        return;
      }

      /* At this point we have a row which passed the * WHERE clause,
         is greater than OFFSET and less * than LIMIT. So send it! */
      *buf++ = 'D';             /* Data packet */
      for (cx = (cmd.ncols - 1) / 8; cx >= 0; cx--)
      {
        /* No NULL responses in bit field for cols */
        *buf++ = 0xFF;          /* Bit field, 1=non-NULL */
      }
      for (cx = 0; cx < cmd.ncols; cx++)
      {
        /* execute column read callback (if defined). callback will
           fill in the data if needed */
        if ((cmd.pcol[cx]->readcb) && ((cmd.ptbl->tabletype != TBL_LIST)
            && (cmd.ptbl->tabletype != TBL_HASH)))
        {
          (cmd.pcol[cx]->readcb) (cmd.ptbl, cmd.pcol[cx]->name,
            cmd.sqlcmd, (void *) rx);
        }

        /* compute pointer to actual data */
        if (cmd.ptbl->tabletype == TBL_LIST)
        {
          if (cmd.pcol[cx]->readcb)
          {
            pd =
              (cmd.pcol[cx]->readcb) (cmd.ptbl, cmd.pcol[cx]->name,
              cmd.sqlcmd, lnode_get(node));
          }
          else
          {
            pd = lnode_get(node) + cmd.pcol[cx]->offset;
          }
        }
        else if (cmd.ptbl->tabletype == TBL_HASH)
        {
          if (cmd.pcol[cx]->readcb)
          {
            pd =
              (cmd.pcol[cx]->readcb) (cmd.ptbl, cmd.pcol[cx]->name,
              cmd.sqlcmd, hnode_get(hnode));
          }
          else
          {
            pd = hnode_get(hnode) + cmd.pcol[cx]->offset;
          }
        }
        else
        {
          if (cmd.itbl > RTA_COLUMNS)
            pd = cmd.ptbl->address + (rx * sr) + cmd.pcol[cx]->offset;
          else if (cmd.itbl == RTA_TABLES)
            pd = (void *) Tbl[rx] + cmd.pcol[cx]->offset;
          else
            pd = (void *) Col[rx] + cmd.pcol[cx]->offset;
        }
        switch ((cmd.pcol[cx])->type)
        {
          case RTA_STR:
            /* send 4 byte length.  Include the lenght */
            ad_int4(&buf, 4 + strlen(pd));
            ad_str(&buf, pd);   /* send the response */
            break;
          case RTA_PSTR:
            ad_int4(&buf, 4 + strlen(*(char **) pd));
            /* send the response */
            ad_str(&buf, *(char **) pd);
            break;
          case RTA_INT:
            n = sprintf((buf + 4), "%d", *((int *) pd));
            ad_int4(&buf, 4 + n); /* send length */
            buf += n;
            break;
          case RTA_PINT:
            n = sprintf((buf + 4), "%d", **((int **) pd));
            ad_int4(&buf, 4 + n); /* send length */
            buf += n;
            break;
          case RTA_LONG:
            n = sprintf((buf + 4), "%lld", *((long long *) pd));
            ad_int4(&buf, 4 + n);
            buf += n;
            break;
          case RTA_PLONG:
            n = sprintf((buf + 4), "%lld", **((long long **) pd));
            ad_int4(&buf, 4 + n);
            buf += n;
            break;
          case RTA_PTR:
            n = sprintf((buf + 4), "%d", *((int *) pd));
            ad_int4(&buf, 4 + n); /* send length */
            buf += n;
            break;
          case RTA_FLOAT:
            n = sprintf((buf + 4), "%20.10f", *((float *) pd));
            ad_int4(&buf, 4 + n);
            buf += n;
            break;
          case RTA_PFLOAT:
            n = sprintf((buf + 4), "%20.10f", **((float **) pd));
            ad_int4(&buf, 4 + n);
            buf += n;
            break;
        }
      }
      npr++;
    }
increment:
    if (cmd.ptbl->tabletype == TBL_LIST)
    {
      node = list_next((list_t *) cmd.ptbl->address, node);
    }
    else if (cmd.ptbl->tabletype == TBL_HASH)
    {
      hnode = hash_scan_next(&hs);
    }

  }
  ad_str(&buf, "CSELECT");
  *buf++ = 0x00;
  *nbuf -= (int) (buf - startbuf);

  /* Log SQL if trace is on */
  (void) sprintf(nprstr, "%d", npr);
  if (rtadbg.trace)
    rtalog(LOC, Er_Trace_SQL, cmd.sqlcmd, nprstr);
}

/***************************************************************
 * send_row_description(): - We have analyzed the select command
 * and it seems OK.  We start the reply by sending the row
 * description first.
 *
 * Input:        A buffer to store the output
 *               The number of free bytes in the buffer
 * Output:       Returns the number of bytes written to buffer
 * Effects:      Lots.  This is where the write callbacks 
 *               are executed.
 ***************************************************************/
int
send_row_description(char *buf, int *nbuf)
{
  char    *startbuf;   /* used to compute response length */
  int      i;          /* loop index */
  int      size;       /* extimated size of response */

  startbuf = buf;

  /* Verify that the buffer has enough room for this reply */
  size = 7;                     /* sizeof "Pblank\0" */
  size += 3;                    /* sizeof 'T' and int2 */
  size += cmd.ncols * (MXCOLNAME + 1 + 4 + 2 + 4);
  if (*nbuf - size < 100)
  {                             /* 100 just for safety */
    send_error(LOC, E_FULLBUF);
    return ((int) (cmd.out - startbuf)); /* ignored */
  }

  /* Send the blank cursor response */
  ad_str(&buf, "Pblank");
  *buf++ = (char) 0;

  /* Send the row description header */
  *buf++ = 'T';                 /* row description */
  ad_int2(&buf, cmd.ncols);     /* num fields */

  for (i = 0; i < cmd.ncols; i++)
  {
    ad_str(&buf, cmd.cols[i]);  /* column name */
    *buf++ = (char) 0;          /* send the NULL */

    /* OIDs are tbl index times max col + col index */
    ad_int4(&buf, (cmd.itbl * NCMDCOLS) + i);

    /* set size/modifier based on type */
    switch ((cmd.pcol[i])->type)
    {
      case RTA_STR:
      case RTA_PSTR:
        ad_int2(&buf, -1);      /* length */
        ad_int4(&buf, 29);      /* type modifier */
        break;
      case RTA_INT:
      case RTA_PINT:
        ad_int2(&buf, sizeof(int)); /* length */
        ad_int4(&buf, -1);      /* type modifier */
        break;
      case RTA_LONG:
      case RTA_PLONG:
        ad_int2(&buf, sizeof(long long)); /* length */
        ad_int4(&buf, -1);      /* type modifier */
        break;
      case RTA_FLOAT:
      case RTA_PFLOAT:
        ad_int2(&buf, sizeof(float)); /* length */
        ad_int4(&buf, -1);      /* type modifier */
        break;
      case RTA_PTR:
        ad_int2(&buf, sizeof(void *)); /* length */
        ad_int4(&buf, -1);      /* type modifier */
        break;
    }
  }
  *nbuf -= (int) (buf - startbuf);
  return ((int) (buf - startbuf));
}

/***************************************************************
 * send_error(): - Send an error message back to the requesting
 * program or process.  
 * The error output is of the form...
 * EERROR:  (some string to describe problem)(NULL)Z
 *
 * Input:        The file name and line number where the error
 *               was detected, and the format and optional
 *               argument of the error message.
 * Output:       void
 * Effects:      Puts data in the command response buffer.
 ***************************************************************/
void
send_error(char *filename, int lineno, char *fmt, char *arg)
{
  int      cnt;        /* a byte count of printed chars */

  /* We want to overwrite any output so far.  We do this by */
  /* pointing the output buffer back to its original value. */
  cmd.out = cmd.errout;         /* Reset any output so far */
  *(cmd.nout) = cmd.nerrout;

  /* -2 below to allow room for null and 'Z' */
  cnt = snprintf(cmd.out, (*(cmd.nout) - 2), fmt, arg);

  /* Check for a truncated output. */
  cnt = (cnt < *(cmd.nout)) ? cnt : *(cmd.nout);
  *(cmd.nout) -= cnt;
  cmd.out += cnt;
  cmd.out++;                    /* to include the NULL */
  *cmd.out = 'Z';
  cmd.out++;
  *(cmd.nout) -= 2;
  cmd.err = 1;
  rtastat.nsqlerr++;
  if (rtadbg.sqlerr)
    rtalog(filename, lineno, Er_Bad_SQL, cmd.sqlcmd);
  return;
}

/***************************************************************
 * sqldo_update(): - Execute the SQL update command in the
 * sql_cmd structure.
 *
 * Input:        A buffer to store the output
 *               The number of free bytes in the buffer
 * Output:       The number of free bytes in the buffer
 * Effects:      Lots.  This is where the write callbacks 
 *               are executed.
 ***************************************************************/
void
sqldo_update(char *buf, int *nbuf)
{
  int      nr;         /* the Number of Rows in the table */
  int      sr;         /* the Size of each Row in the table */
  int      rx;         /* Row indeX in for() loop */
  int      wx;         /* Where clause indeX in for loop */
  void    *pd;         /* Pointer to the Data in the table/column */
  long long cmp;       /* has actual relation of col and val */
  int      dor;        /* DO Row == 1 if we should update row */
  char    *startbuf;   /* used to compute response length */
  int      cx;         /* Column index while building Data pkt */
  int      n;          /* number of chars printed in sprintf() */
  int      nru = 0;    /* =# rows updated */
  int      svt = 0;    /* Save table if == 1 */
  char    *tmark;      /* Address of U in "CUPDATE" if success */

  startbuf = buf;

  /* We loop through all rows in the table in question applying the
     WHERE condition.  If a row matches we update the appropriate
     columns and call any write callbacks */
  nr = cmd.ptbl->nrows;
  sr = cmd.ptbl->rowlen;
  for (rx = 0; rx < nr; rx++)
  {
    dor = 1;
    for (wx = 0; wx < cmd.nwhrcols; wx++)
    {
      /* The WHERE clause ...... execute read callback (if defined) on
         row * the call back is expected to fill in the data */
      if (cmd.pwhr[wx]->readcb)
      {
        (cmd.pwhr[wx]->readcb) (cmd.tbl, cmd.whrcols[wx],
          cmd.sqlcmd, (void *) rx);
      }

      /* compute pointer to actual data */
      if (cmd.itbl > RTA_COLUMNS)
        pd = cmd.ptbl->address + (rx * sr) + cmd.pwhr[wx]->offset;
      else if (cmd.itbl == RTA_TABLES)
        pd = (void *) Tbl[rx] + cmd.pwhr[wx]->offset;
      else
        pd = (void *) Col[rx] + cmd.pwhr[wx]->offset;

      /* do comparison based on column data type */
      switch (cmd.pwhr[wx]->type)
      {
        case RTA_STR:
          cmp = strncmp((char *) pd, cmd.whrvals[wx],
            cmd.pwhr[wx]->length);
          break;
        case RTA_PSTR:
          cmp = strcmp(*(char **) pd, cmd.whrvals[wx]);
          break;
        case RTA_INT:
          cmp = *((int *) pd) - cmd.whrints[wx];
          break;
        case RTA_PINT:
          cmp = **((int **) pd) - cmd.whrints[wx];
          break;
        case RTA_LONG:
          cmp = *((long long *) pd) - cmd.whrlngs[wx];
          break;
        case RTA_PLONG:
          cmp = **((long long **) pd) - cmd.whrlngs[wx];
          break;
        case RTA_FLOAT:
          cmp = *((float *) pd) - cmd.whrflot[wx];
          break;
        case RTA_PFLOAT:
          cmp = **((float **) pd) - cmd.whrflot[wx];
          break;
        case RTA_PTR:
          cmp = *((int *) pd) - cmd.whrints[wx];
          break;
        default:
          cmp = 1;              /* assume no match */
          break;
      }
      if (!(((cmp == 0) && (cmd.whrrel[wx] == RTA_EQ ||
              cmd.whrrel[wx] == RTA_GE ||
              cmd.whrrel[wx] == RTA_LE)) ||
          ((cmp != 0) && (cmd.whrrel[wx] == RTA_NE)) ||
          ((cmp < 0) && (cmd.whrrel[wx] == RTA_LE ||
              cmd.whrrel[wx] == RTA_LT)) ||
          ((cmp > 0) && (cmd.whrrel[wx] == RTA_GE ||
              cmd.whrrel[wx] == RTA_GT))))
      {
        dor = 0;
        break;
      }
    }
    if (dor)
    {                           /* DO Row */
      /* if we get here, we've passed the WHERE clause Check for LIMIT
         and OFFSET filtering */
      if (cmd.offset)
      {
        cmd.offset--;
        continue;
      }
      if (cmd.limit <= 0)
      {
        break;
      }

      /* At this point we have a row which passed the * WHERE clause,
         is greater than OFFSET and less * than LIMIT. So update it! */
      for (cx = 0; cx < cmd.ncols; cx++)
      {
        /* compute pointer to actual data */
        if (cmd.itbl > RTA_COLUMNS)
          pd = cmd.ptbl->address + (rx * sr) + cmd.pcol[cx]->offset;
        else if (cmd.itbl == RTA_TABLES)
          pd = (void *) Tbl[rx] + cmd.pcol[cx]->offset;
        else
          pd = (void *) Col[rx] + cmd.pcol[cx]->offset;
        switch ((cmd.pcol[cx])->type)
        {
          case RTA_STR:
            strncpy((char *) pd, cmd.updvals[cx], cmd.pcol[cx]->length);
            break;
          case RTA_PSTR:
            strncpy(*(char **) pd, cmd.updvals[cx],
              cmd.pcol[cx]->length);
            break;
          case RTA_INT:
            *((int *) pd) = cmd.updints[cx];
            break;
          case RTA_PINT:
            **((int **) pd) = cmd.updints[cx];
            break;
          case RTA_LONG:
            *((long long *) pd) = cmd.updlngs[cx];
            break;
          case RTA_PLONG:
            **((long long **) pd) = cmd.updlngs[cx];
            break;
          case RTA_PTR:
            /* works only if INT and PTR are same size */
            *((int *) pd) = cmd.updints[cx];
            break;
          case RTA_FLOAT:
            *((float *) pd) = cmd.updflot[cx];
            break;
          case RTA_PFLOAT:
            **((float **) pd) = cmd.updflot[cx];
            break;
        }
        if (cmd.pcol[cx]->flags & RTA_DISKSAVE)
          svt = 1;
      }

      /* We call the write callbacks after all of the * columns have
         been updated.  */
      for (cx = 0; cx < cmd.ncols; cx++)
      {
        /* execute write callback (if defined) on row. callback will
           fill in the data if needed */
        if (cmd.pcol[cx]->writecb)
        {
          (cmd.pcol[cx]->writecb) (cmd.tbl,
            cmd.whrcols[wx], cmd.sqlcmd, rx);
        }
      }
      cmd.limit--;
      nru++;
    }
  }

  /* Save the table to disk if needed */
  if (svt && cmd.ptbl->savefile && strlen(cmd.ptbl->savefile))
    rta_save(cmd.ptbl, cmd.ptbl->savefile);

  /* Send the blank cursor response */
  ad_str(&buf, "Pblank");
  *buf++ = (char) 0;

  /* Send the row description header */
  ad_str(&buf, "CUPDATE");
  n = sprintf(buf, " %d", nru); /* # rows affected */
  tmark = buf + 1;              /* Save status for trace */
  buf += n;
  *buf++ = 0x00;
  *nbuf -= (int) (buf - startbuf);

  /* Log SQL if trace is on */
  if (rtadbg.trace)
    rtalog(LOC, Er_Trace_SQL, cmd.sqlcmd, tmark);
}

/***************************************************************
 * do_call(): - Execute the SQL function call in the sql_cmd
 * structure.
 *
 * Input:        A buffer to store the output
 *               The number of free bytes in the buffer
 * Output:       The number of free bytes in the buffer
 * Effects:      Maybe lots, depending on the function
 * Notes:        The function implementation probably belongs
 *               as part of the select statement.  A possible
 *               rewrite of this code might put it there.
 ***************************************************************/
void
do_call(char *buf, int *nbuf)
{
  char    *startbuf;   /* used to compute response length */

  if (!strcmp(cmd.tbl, "getdatabaseencoding"))
  {
    if (*nbuf < 50)
    {
      rtastat.nrtaerr++;
      if (rtadbg.rtaerr)
        rtalog(LOC, "%s %d: not enough buffer space\n");
      return;
    }
    startbuf = buf;

    /* Send the blank cursor response */
    ad_str(&buf, "Pblank");
    *buf++ = (char) 0;

    /* Send the query response */
    *buf++ = 'T';               /* row description */
    ad_int2(&buf, 1);           /* num fields */
    ad_str(&buf, "getdatabaseencoding");
    *buf++ = (char) 0;
    ad_int4(&buf, 19);          /* OID of field */
    ad_int2(&buf, 32);          /* type size */
    ad_int4(&buf, -1);          /* type modifier */

    /* Send the ASCII row */
    *buf++ = 'D';               /* ASCII row */
    *buf++ = (char) (1 << 7);   /* bit mask for rows present */
    ad_int4(&buf, 13);          /* sizeof(int4)+strlen(field) */
    ad_str(&buf, "SQL_ASCII");  /* 13=4+len("SQL_ASCII") */

    /* Tell of completed response */
    ad_str(&buf, "CSELECT");
    *buf++ = (char) 0;
    *nbuf -= (int) (buf - startbuf);
  }
  else
  {
    /* not recognized.  Return an error */
    send_error(LOC, E_BADPARSE);
    return;
  }
}

/***************************************************************
 * do_begin(): - Execute the SQL begin command
 *
 * Input:        A buffer to store the output
 *               The number of free bytes in the buffer
 * Output:       The number of free bytes in the buffer
 * Effects:      Nothing.  This command is ignored.
 ***************************************************************/
void
do_begin(char *buf, int *nbuf)
{
  char    *startbuf;   /* used to compute response length */

  if (*nbuf < 50)
  {
    rtastat.nrtaerr++;
    if (rtadbg.rtaerr)
      rtalog(LOC, "%s %d: not enough buffer space\n");
    return;
  }
  startbuf = buf;

  /* Tell of completed response */
  ad_str(&buf, "CBEGIN");
  *buf++ = (char) 0;
  *nbuf -= (int) (buf - startbuf);
}

/***************************************************************
 * do_commit(): - Execute the SQL commit command
 *
 * Input:        A buffer to store the output
 *               The number of free bytes in the buffer
 * Output:       The number of free bytes in the buffer
 * Effects:      Nothing.  This command is ignored.
 ***************************************************************/
void
do_commit(char *buf, int *nbuf)
{
  char    *startbuf;   /* used to compute response length */

  if (*nbuf < 50)
  {
    rtastat.nrtaerr++;
    if (rtadbg.rtaerr)
      rtalog(LOC, "%s %d: not enough buffer space\n");
    return;
  }
  startbuf = buf;

  /* Tell of completed response */
  ad_str(&buf, "CCOMMIT");
  *buf++ = (char) 0;
  *nbuf -= (int) (buf - startbuf);
}

/***************************************************************
 * ad_str(): - Add a string to the output buffer.  Includes a
 *             NULL to terminate the string.
 *
 * Input:        A **char to the buffer and the string
 * Output:       void
 * Effects:      Increments the **char to point to the next
 *               available space in the buffer.
 ***************************************************************/
void
ad_str(char **pbuf, char *instr)
{
  strcpy(*pbuf, instr);
  *pbuf += strlen(instr);
  **pbuf = (char) 0;
}

/***************************************************************
 * ad_int2(): - Add a 2 byte integer to the output buffer
 *
 * Input:        A **char to the buffer and the integer
 * Output:       void
 * Effects:      Increments the **char to point to the next
 *               available space in the buffer.
 ***************************************************************/
void
ad_int2(char **pbuf, int inint)
{
  **pbuf = (char) ((inint >> 8) & 0x00FF);
  (*pbuf)++;
  **pbuf = (char) (inint & 0x00FF);
  (*pbuf)++;
}

/***************************************************************
 * ad_int4(): - Add a 4 byte integer to the output buffer
 *
 * Input:        A **char to the buffer and the integer
 * Output:       void
 * Effects:      Increments the **char to point to the next
 *               available space in the buffer.
 ***************************************************************/
void
ad_int4(char **pbuf, int inint)
{
  **pbuf = (char) ((inint >> 24) & 0x00FF);
  (*pbuf)++;
  **pbuf = (char) ((inint >> 16) & 0x00FF);
  (*pbuf)++;
  **pbuf = (char) ((inint >> 8) & 0x00FF);
  (*pbuf)++;
  **pbuf = (char) (inint & 0x00FF);
  (*pbuf)++;
}

/***************************************************************
 * rtalog(): - Sends debug log messages to syslog() and stderr.
 *
 * Output:       void
 * Effects:      Increments the **char to point to the next
 *               available space in the buffer.
 ***************************************************************/
void
rtalog(char *fname,    /* error detected in file... */
  int linen,           /* error detected at line # */
  char *format, ...)   /* printf format string */
{
  va_list  ap;
  char    *s1;         /* first optional argument */
  char    *s2;         /* second optional argument */
  char    *sptr;       /* used to look for %s */
  char 	  final[1024];

  s1 = (char *) 0;
  s2 = (char *) 0;

  /* Get the optional parameters if any */
  va_start(ap, format);
  sptr = strstr(format, "%s");
  if (sptr)
  {
    s1 = va_arg(ap, char *);

    sptr++;
    if (sptr)
      s2 = va_arg(ap, char *);
  }
  va_end(ap);
  snprintf(final, 1024, format, fname, linen, s1, s2);
  RTA_Conf.loggingfunc(final);
}
