/*-------------------------------------------------------------------------
 *
 * tdb.c--
 *    providing a common interface for Tioga to access different kind
 *    of database systems.
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    /usr/local/devel/pglite/cvs/src/bin/psql/tdb.c,v 1.13 1995/05/10 23:36:25 andrew Exp
 *
 *-------------------------------------------------------------------------
 */
#include "tdb.h"
#include "stringutils.h"

#define TG_INITIAL_ROWS 32
char tgDbErrMsg[TG_DB_ERR_MSG_LEN];

static void handle_copy_out();
static void handle_copy_in();

/* -- put function interface to POSTGRES database here -- */

#include "libpq-fe.h"
#include "pqutils.h"
extern char* PQhost;
extern char* PQport;
extern char* PQtty;
extern char* PQoption;
extern char* PQdatabase;
extern char PQerrormsg[];

char beginXact[] = "begin";
char endXact[] = "end";
char abortXact[] = "abort";

TgDb* 
TgNewDB(char *name, char *host, char *port, char *pgtty, char *pgoption)
/*
 * TgNewDB -- Initialize a TgDb structure to contain information about the
 *            database that you want to connect to.
 */
{
  TgDb *result;
  char *temp;

  result = (TgDb *) malloc(sizeof(TgDb));

  result->name = (name) ? dupstr(name) : NULL;
  if (host)
    result->host = dupstr(host);
  else {
    if (temp = getenv("PGHOST"))
      result->host = dupstr(temp);
    else
      result->host = dupstr("localhost");
  }
  if (port)
    result->port = dupstr(port);
  else {
    if (temp = getenv("PGPORT"))
      result->port = dupstr(temp);
    else
      result->port = dupstr(POSTPORT);
  }

  if (pgtty)
      result->pgtty = dupstr(pgtty);
  else {
      if (temp = getenv("PGTTY"))
	  result->pgtty = dupstr(temp);
      else
	  result->pgtty = NULL;
  }
  
  if (pgoption)
      result->pgoption = dupstr(pgoption);
  else {
      if (temp = getenv("PGOPTION"))
	  result->pgoption = dupstr(temp);
      else
	  result->pgoption = NULL;
  }
  result->status = 0;
  result->portal = NULL;
  return result;
}

void 
TgFreeDb(TgDb *db)
/*
 * TgFreeDb -- free the db structure.  If a connection is still present when
 *             this routine is called, the connection will be closed.
 *             It would be better for the user to call the macro TG_FREEDB
 *             instead of calling this function directly.
 */
{
  if (db == NULL)
    return;

  if (db->status) {
    TgCloseDB(db);
  }
  TG_FREE(free, db->name);
  TG_FREE(free, db->host);
  TG_FREE(free, db->port);
  TG_FREE(free, db->pgtty);
  TG_FREE(free, db->pgoption);
  TG_FREE(free, db);
}

int 
TgConnectDB(TgDb *db)
/*
 * TgConnectDB -- establish a connection with the database backend.
 *
 *                Returns:
 *                          0  -- connection to server failed
 *                          1  -- connection to server succeed, no error.
 */
{
  char *statusString;

  if (db->status)
    fprintf(stderr,"TgConnectDB: warning, connecting to an already open db\n");

  if ( db->host && (db->host[0] != '\0'))
    PQhost = dupstr(db->host);
  if ( db->port && (db->port[0] != '\0'))
    PQport = dupstr(db->port);
  if ( db->pgtty && (db->pgtty[0] != '\0'))
    PQtty = dupstr(db->pgtty);
  if ( db->pgoption && (db->pgoption[0] != '\0'))
    PQoption = dupstr(db->pgoption);

  if ( db->name && (db->name[0] != '\0'))
    PQsetdb(db->name);
  else
    {
      sprintf(tgDbErrMsg, "TgConnectDB: data base name cannot be null");
      return 0;
    }

  /* check db connection to make sure the database exists and all that */
  statusString = pq_exec(" ", "TgConnectDB");
  if (statusString[0] == 'R' ||
      statusString[0] == 'E') 
    {
	strcpy(tgDbErrMsg,PQerrormsg);
	return 0;
      }
  db->status = 1;

  /* clear the error message string */
  tgDbErrMsg[0] = '\0';
  return 1;
}
  
void 
TgCloseDB(TgDb *db)
/*
 * TgCloseDB -- close the connection with the database server.
 */
{
  if (db->status) {
    PQfinish();
    db->status = 0;
  }
}

TgTupleGroup*
TgRetrieve(TgDb *db, char *query, int binary)
/*
 * TgRetrieve -- Do a retrieve query to the database and returns the result
 *               in a TgTupleGroup.  The result can be in either binary or
 *               ASCII form.
 *
 *               Returns NULL if an error has occurred with the error message
 *               stored in 'tgDbErrMsg'
 *              
 *               the CALLER is responsible for free'ing the TgTupleGroup
 *               returned by using the TG_FREETUPLEGROUP macro
 */
{
  TgTupleGroup *result;
  if (TgDoQuery(db, query, binary) == 2) {
    result = TgGetData(db, binary);
  } else {
    result = NULL;
  }
  while (TgDoQuery(db, NULL, 0) != 1) ;
  return result;
}

TgTupleGroup*
TgGetData(TgDb *db, int binary)
/*
 * TgGetData -- get data from a retrieve query.  Call this function only
 *              immediately after TgDoQuery() returns 2.
 * the CALLER is responsible for free'ing the data returned by calling
 * TgFreeTupleGroup
 */
{
  PortalBuffer* pbuf;
  int           numCols;
  int           numRows;
  TgTupleGroup* result;
  TgTypeBlock*  type;
  TgAttValue    value;
  TgTuple       tuple;
  char*         attval;
  int           i,j;
  int           colLen;

  if (db->status == 0 || db->portal == NULL)
    return NULL;

  result = (TgTupleGroup *) malloc(sizeof(TgTupleGroup));

  pbuf = db->portal;

  /* always assume only 1 group returned */
  numCols = PQnfieldsGroup(pbuf,0);
  if (numCols == 0)
    return NULL;

  result->numAttributes = numCols;
  result->type = binary;
  result->attribute = (TgTypeBlock *) malloc(sizeof(TgTypeBlock) * numCols);

  numRows = PQntuplesGroup(pbuf,0);
  result->tuple = (TgTuple *) malloc(sizeof(TgTuple) * numRows);
  result->numTuples = numRows;

  for (i = 0 ; i < numCols ; i++) {
    /* for determining type name, type id, and type size
       use the first group of the result, and assume that
       the result tuples are not ragged*/
    type = &(result->attribute[i]);
    type->name = dupstr(PQfnameGroup(pbuf,0,i));
    type->adtid = PQftypeGroup(pbuf,0,i);
    type->adtsize = PQfsizeGroup(pbuf,0,i);
  }

 /* fill the result->tuple structure
    with ascii representations of the data */
  for (i=0;i<numRows;i++)
  {
    tuple = (TgTuple) malloc(sizeof(TgAttValue)*numCols);
    result->tuple[i] = tuple;
    for (j=0;j<numCols;j++)
      {
	attval = PQgetvalue(pbuf,i,j);
	if (attval)
	  {
	  value = (TgAttValue) malloc(strlen(attval)+1);
	  strcpy(value,attval);
	}
	else
	  value = NULL;
	tuple[j] = value;
      }
  }    
  return result;
}

int 
TgDoQuery(TgDb *db, char *query, int binary)
/*
 * TgDoQuery -- issue an arbituary query to the database back end.
 *              If a NULL is passed in as the query, this function checks
 *              to make sure that all processing that needs to be done are
 *              done.
 *
 *              Returns:
 *                      0 -- error occurred, error mesg is stored in tgDbErrMsg
 *                      1 -- query successful, no more processing needed
 *                      2 -- query returns tuples, more processing is needed
 */
{
  char* pqres;
  char c = 0;
  int ngroups;

  pqres = &c;
  if (db->status == 0) {
    sprintf(tgDbErrMsg, "No connection with the database");
    return 0;
  }
  if (query != NULL && query[0] != '\0')
    {
      pqres = pq_exec(query,"TgDoQuery");
      if (*pqres == 'E' || *pqres == 'R') {
	sprintf(tgDbErrMsg, "TgDoQuery: error in query: \n\t%s\n",
	       query);
	return 0;
      }
      if (*pqres == 'P')
	{
	  db->portal = PQparray(++pqres);
	  ngroups = PQngroups(db->portal);
	  if (ngroups > 1)
	    {
	      fprintf(stderr,"TgDoQuery: query %s returned %d groups.  expected 0\n", query, ngroups);
            }
	  return 2;
	}
      else
	{
	db->portal = NULL;
      }
  } else
      return 1;

  if (*pqres == 'B')  {  /* copy to stdout */
      handle_copy_out();
      return 1;
  }
  else
      if (*pqres == 'D')  {  /* copy from stdin */
	  handle_copy_in();
	  return 1;
      }

  /* if we're here, we were either sent a blank query 
     or the pqres was not a 'E', 'R', 'P', 'D', or 'B'

     in either case, we want to clear out the libpq buffer
     in case we have multiple status words returned from the backend
     for a single front-end command
  */

  
  pqres = pq_exec(" ","TgDoQuery");
  if (*pqres == 'I')
    return 1;
  else
    {
      /* this cruftiness handles cases where the backend returns multiple
	 commands for a single front-end command, 
	 e.g.  create view results in CAPPEND and CDEFINE
	 */
      int extras = 0;
      while (*pqres != 'I')
	{
	  pqres = pq_exec(" ", "TgDoQuery");
	  extras++;
	}
      PQFlushI(extras);
      return 1;
    }
}

int TgAttNameToNum(TgTupleGroup *tg, char *attName)
/*
 * TgAttNameToNum -- given a tuple group and an attName, returns the attribute
 *                   index.  Returns -1 if attName doesn't exist, or other
 *                   error has occured that prevents the function from
 *                   determine the attribute number.
 */
{
  int i;
  
  if (tg == NULL) {
    return -1;
  }
  for (i = 0 ; i < tg->numAttributes ; i++) {
    if (strcmp(attName, tg->attribute[i].name) == 0) {
      return i;
    }
  }
  return -1;
}

void TgFreeTuplegroup(TgTupleGroup *tg)
/*
 * TgFreeTuplegroup -- free memory associated with a tuple group.  It is
 *                     recommended that you use TG_FREETUPLEGROUP macro
 *                     instead of calling this function directly.
 */
{
  int i , j;
  if (tg == NULL) return;

  for (i = 0 ; i < tg->numTuples ; i++) {
    for (j = 0 ; j < tg->numAttributes ; j++) {
      TG_FREE(free, tg->tuple[i][j]);
    }
    TG_FREE(free, tg->tuple[i]);
  }
  TG_FREE(free, tg->tuple);
  for (i=0;i<tg->numAttributes;i++) {
      TG_FREE(free, tg->attribute[i].name);
  }
  TG_FREE(free, tg->attribute);
  TG_FREE(free, tg);  
}

TgAttValue 
dup_attr(TgTupleGroup* tg, int ti, char* attname)
{
  TgAttValue result;

  result = GET_ATTR(tg, ti, attname);
  if (result)
    return (TgAttValue)(dupstr(result));
  else
    return (TgAttValue)"";
}


/* handle_copy_out and handle_copy_in lifted directly from monitor.c */

#define COPYBUFSIZ	8192
static void
handle_copy_out()
{
    bool copydone = false;
    char copybuf[COPYBUFSIZ];
    int ret;

    while (!copydone) {
	ret = PQgetline(copybuf, COPYBUFSIZ);
	
	if (copybuf[0] == '.' && copybuf[1] == '\0') {
	    copydone = true;	/* don't print this... */
	} else {
	    fputs(copybuf, stdout);
	    switch (ret) {
	    case EOF:
		copydone = true;
		/*FALLTHROUGH*/
	    case 0:
		fputc('\n', stdout);
		break;
	    case 1:
		break;
	    }
	}
    }
    fflush(stdout);
    PQendcopy();
}

static void
handle_copy_in()
{
    bool copydone = false;
    bool firstload;
    bool linedone;
    char copybuf[COPYBUFSIZ];
    char *s;
    int buflen;
    int c;
    
    fputs("Enter info followed by a newline\n", stdout);
    fputs("End with a dot on a line by itself.\n", stdout);
    
    /*
     * eat inevitable newline still in input buffer
     *
     * XXX the 'inevitable newline' is not always present
     *     for example `cat file | monitor -c "copy from stdin"'
     */
    fflush(stdin);
    if ((c = getc(stdin)) != '\n' && c != EOF) {
	(void) ungetc(c, stdin);
    }
    
    while (!copydone) {			/* for each input line ... */
	fputs(">> ", stdout);
	fflush(stdout);

	firstload = true;
	linedone = false;
	while (!linedone) {		/* for each buffer ... */
	    s = copybuf;
	    buflen = COPYBUFSIZ;
	    for (; buflen > 1 &&
		 !(linedone = (c = getc(stdin)) == '\n' || c == EOF);
		 --buflen) {
		*s++ = c;
	    }
	    if (c == EOF) {
		/* reading from stdin, but from a file */
		PQputline(".");
		copydone = true;
		break;
	    }
	    *s = '\0';
	    PQputline(copybuf);
	    if (firstload) {
		if (!strcmp(copybuf, ".")) {
		    copydone = true;
		}
		firstload = false;
	    }
	}
	PQputline("\n");
    }
    PQendcopy();
}

