/*-------------------------------------------------------------------------
 *
 * psql.c--
 *    a monitor workalike to test the libtdb library
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    /usr/local/devel/pglite/cvs/src/bin/psql/psql.c,v 1.21 1995/08/30 23:05:59 jolly Exp
 *
 *-------------------------------------------------------------------------
 */
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <pwd.h>
#include "libpq-fe.h"
#include "stringutils.h"

#include "psqlHelp.h"

#ifdef NOREADLINE
extern char *readline(char *);	/* in rlstubs.c */
#else
/* from the GNU readline library */
#include "readline.h"
#include "history.h"
#endif

#define MAX_QUERY_BUFFER 20000

/* declarations for functions in this file */
static void usage(char* progname);
static void slash_usage();
static void handle_copy_out(PGresult *res, int quiet);
static void handle_copy_in(PGresult *res, int quiet);
static int tableList(PGconn* conn);
static int tableDesc(PGconn* conn, char* table);

char* gets_noreadline(char* prompt, FILE* source);
char* gets_readline(char* prompt, FILE* source);
char* gets_fromFile(char* prompt, FILE* source);
void SendQuery(PGconn* db, char* query, int echo_query, int quiet,
	       int single_step);
int HandleSlashCmds(PGconn** db_ptr, char* line, char** prompt_ptr,
		    int echo_query, int quiet,
		    int single_step);
void MainLoop(PGconn** db_ptr, int single_line_mode, FILE* source,
	      int quiet, int use_readline, int echo_query,
	      int single_step);
void PQpagerPrintTuples(PGresult *res);


/*
 * usage 
 *   print out usage for command line arguments 
 */

static void  
usage(char* progname)
{
  fprintf(stderr,"Usage: %s [options] [dbname]\n",progname);
  fprintf(stderr,"\t -c query                run single query\n");
  fprintf(stderr,"\t -d dbName               specify database name\n");
  fprintf(stderr,"\t -e                      echo the query sent to the backend\n");
  fprintf(stderr,"\t -f filename             use file as a source of queries\n");
  fprintf(stderr,"\t -h                      help information\n");
  fprintf(stderr,"\t -H host                 set database server host\n");
  fprintf(stderr,"\t -l                      list available databases\n");
  fprintf(stderr,"\t -n                      don't use readline library\n");
  fprintf(stderr,"\t -s                      single step mode (prompts for each query)\n");
  fprintf(stderr,"\t -S                      single line mode (i.e. query terminated by newline)\n");
  fprintf(stderr,"\t -p port                 set port number\n");
  fprintf(stderr,"\t -q                      run quietly (no messages, no prompts)\n");
  exit(1);
}

/*
 * slash_usage
 *    print out usage for the backslash commands 
 */

static void
slash_usage()
{
  fprintf(stderr,"\t \\c <dbname>  -- establish new connection to <dbname>\n");
  fprintf(stderr,"\t \\d [<table>] -- list tables in database or columns in <table>\n");
  fprintf(stderr,"\t \\h <command> -- help on syntax of sql commands\n");
  fprintf(stderr,"\t \\g           -- send query to backend\n");
  fprintf(stderr,"\t \\i <fname>   -- read queries from filename\n");
  fprintf(stderr,"\t \\l           -- list all databases\n");
  fprintf(stderr,"\t \\q           -- quit\n");
  fprintf(stderr,"\t \\s <fname>   -- save history to filename\n");
  fprintf(stderr,"\t \\?           -- help\n");
}

/*
 * listAllDbs   (PGconn* db)
 *
 * list all the databases in the system
 *     returns 0 if all went well
 *    
 *
 */
int 
listAllDbs(PGconn* db)
{
  PGresult *results;
  char* query = "select * from pg_database;";

  results = PQexec(db, query);
  if (results == NULL) {
    fprintf(stderr,"%s", PQerrorMessage(db));
    return 1;
  }

  if (PQresultStatus(results) != PGRES_TUPLES_OK)
    {
      fprintf(stderr,"Unexpected error from executing: %s\n", query);
      return 2;
    }
  else
    {
      PQprintTuples(results,
		    stdout,
		    1, /* print attribute names */
		    0, /* don't use terse output */
		    12 /* use fixed width of 15 */
		    );
      PQclear(results);
      return 0;
    }
}

/*
 * tableList (PGconn* conn)
 *
 * List The Database Tables
 *     returns 0 if all went well
 *    
 *
 */
int
tableList (PGconn* conn)
{
  char listbuf[256];
  int nColumns;
  int i;
  char* rk;

  PGresult* res;


  /*
  ** Get the list of tables
  */

  /* start a transaction block */
  res = PQexec(conn,"BEGIN"); 
  if (PQresultStatus(res) != PGRES_COMMAND_OK) {
    fprintf(stderr,"BEGIN command failed\n");
    PQclear(res);
  }
  /* should PQclear PGresult whenever it is no longer needed to avoid
     memory leaks */
  PQclear(res); 

  /* Build the query */

  listbuf[0] = '\0';
  strcat(listbuf,"DECLARE myportal CURSOR FOR ");
  strcat(listbuf,"SELECT relname, relkind");
  strcat(listbuf,"  FROM pg_class ");
  strcat(listbuf,"WHERE ( relkind = 'r' OR relkind = 'i') ");
  strcat(listbuf,"  and relname !~ '^pg_'");
  strcat(listbuf,"  and relname !~ '^Inv'");
  strcat(listbuf,"  ORDER BY relname ");
  res = PQexec(conn,listbuf);
  if (PQresultStatus(res) != PGRES_COMMAND_OK) {
    fprintf(stderr,"DECLARE CURSOR command failed\n");
    PQclear(res);
  }
  PQclear(res);

  res = PQexec(conn,"FETCH ALL in myportal");
  if (PQresultStatus(res) != PGRES_TUPLES_OK) {
    fprintf(stderr,"FETCH ALL command didn't return tuples properly\n");
    PQclear(res);
  }
 
  /* first, print out the attribute names */
  nColumns = PQntuples(res);
  if (nColumns > 0)
  {
    /*
    ** Display the information
    */

    printf ("\nDatabase    = %s\n", PQdb(conn));
    printf (" +-----------------+----------+\n");
    printf (" |    Relation     |   Type   |\n");
    printf (" +-----------------+----------+\n");

    /* next, print out the instances */
    for (i=0; i < PQntuples(res); i++) {
      printf (" | %-15.15s | ", PQgetvalue(res,i,0));
      rk =  PQgetvalue(res,i,1);
      if (strcmp(rk, "r") == 0)
        printf ("%-8.8s |", "table" );
      else
        printf ("%-8.8s |", "index");
      printf("\n");
    }
    printf (" +-----------------+----------+");
    printf ("\n");

    PQclear(res);
    /* close the portal */
    res = PQexec(conn, "CLOSE myportal");
    PQclear(res);

    /* end the transaction */
    res = PQexec(conn, "END");
    PQclear(res);
    return (0);
  
  } else {
    fprintf (stderr, "Couldn't find any tables!\n");
    /* close the portal */
    res = PQexec(conn, "CLOSE myportal");
    PQclear(res);

    /* end the transaction */
    res = PQexec(conn, "END");
    PQclear(res);
    return (-1);
  }
}

/*
 * Describe a table   (PGconn* conn, char* table)
 *
 * Describe the columns in a database table.
 *     returns 0 if all went well
 *    
 *
 */
int
tableDesc (PGconn* conn, char* table)
{
  char descbuf[256];
  int nColumns;
  int i;

  PGresult* res;


  /*
  ** Get the list of attributes
  */

  /* start a transaction block */
  res = PQexec(conn,"BEGIN"); 
  if (PQresultStatus(res) != PGRES_COMMAND_OK) {
    fprintf(stderr,"BEGIN command failed\n");
    PQclear(res);
  }
  /* should PQclear PGresult whenever it is no longer needed to avoid
     memory leaks */
  PQclear(res); 

  /* Build the query */

  descbuf[0] = '\0';
  strcat(descbuf,"DECLARE myportal CURSOR FOR ");
  strcat(descbuf,"SELECT a.attname, t.typname, a.attlen");
  strcat(descbuf,"  FROM pg_class c, pg_attribute a, pg_type t ");
  strcat(descbuf,"    WHERE c.relname = '");
  strcat(descbuf,table);
  strcat(descbuf,"'");
  strcat(descbuf,"    and a.attnum > 0 ");
  strcat(descbuf,"    and a.attrelid = c.oid ");
  strcat(descbuf,"    and a.atttypid = t.oid ");
  strcat(descbuf,"  ORDER BY attname ");
  res = PQexec(conn,descbuf);
  if (PQresultStatus(res) != PGRES_COMMAND_OK) {
    fprintf(stderr,"DECLARE CURSOR command failed\n");
    PQclear(res);
  }
  PQclear(res);

  res = PQexec(conn,"FETCH ALL in myportal");
  if (PQresultStatus(res) != PGRES_TUPLES_OK) {
    fprintf(stderr,"FETCH ALL command didn't return tuples properly\n");
    PQclear(res);
  }
 
  /* first, print out the attribute names */
  nColumns = PQntuples(res);
  if (nColumns > 0)
  {
    /*
    ** Display the information
    */

    printf ("\nTable    = %s\n", table);
    printf (" +-----------------+----------+--------+\n");
    printf (" |     Field       |   Type   | Length |\n");
    printf (" +-----------------+----------+--------+\n");

    /* next, print out the instances */
    for (i=0; i < PQntuples(res); i++) {
      printf (" | %-15.15s | ", PQgetvalue(res,i,0));
      printf ("%-8.8s |", PQgetvalue(res,i,1));
      if (strcmp(PQgetvalue(res,i,2), "-1"))
        printf (" %-6s |",  PQgetvalue(res,i,2));
      else
        printf (" %-6s |",  "var" );
      printf("\n");
    }
    printf (" +-----------------+----------+--------+");
    printf ("\n");

    PQclear(res);
    /* close the portal */
    res = PQexec(conn, "CLOSE myportal");
    PQclear(res);

    /* end the transaction */
    res = PQexec(conn, "END");
    PQclear(res);
    return (0);
  
  } else {
    fprintf (stderr, "Couldn't find table %s!\n", table);
    /* close the portal */
    res = PQexec(conn, "CLOSE myportal");
    PQclear(res);

    /* end the transaction */
    res = PQexec(conn, "END");
    PQclear(res);
    return (-1);
  }
}

typedef char* (*READ_ROUTINE)(char* prompt, FILE* source);

/* gets_noreadline  prompt source
      gets a line of input without calling readline, the source is ignored
*/
char* 
gets_noreadline(char* prompt, FILE* source)
{
    fputs(prompt, stdout);
    fflush(stdin);
    return(gets_fromFile(prompt,stdin));
}

/*
 * gets_readline  prompt source
 *   the routine to get input from GNU readline(), the source is ignored 
 * the prompt argument is used as the prompting string
 */
char* 
gets_readline(char* prompt, FILE* source)
{
  return (readline(prompt));
}


/*
 * gets_fromFile  prompt source
 *    
 * the routine to read from a file, the prompt argument is ignored
 * the source argument is a FILE* 
 */
char* 
gets_fromFile(char* prompt, FILE* source)
{
  char* line;
  int len;

  line = malloc(MAX_QUERY_BUFFER+1);

  /* read up to MAX_QUERY_BUFFER - 1 characters */
  if (fgets(line, MAX_QUERY_BUFFER, source) == NULL)
    return NULL;

  line[MAX_QUERY_BUFFER-1] = '\0';
  len = strlen(line);
  if (len == MAX_QUERY_BUFFER)
    {
      fprintf(stderr, "line read exceeds maximum length.  Truncating at %d\n", MAX_QUERY_BUFFER);
    }
  
  return line;
}

/*
 * SendQuery:
     SendQuery: send the query string to the backend 
 *
 */
void 
SendQuery(PGconn* db, char* query, int echo_query, int quiet, 
	  int single_step)
{
  PGresult* results;
  PGnotify* notify;

  if (single_step)
	fprintf(stdout, "\n*******************************************************************************\n");

  if (echo_query || single_step) {
      fprintf(stderr,"QUERY: %s\n",query);
      fflush(stderr);
  }

  if (single_step) {
	fprintf(stdout, "\n*******************************************************************************\n");
	fflush(stdout);
	printf("\npress return to continue ..\n");
	gets_fromFile("",stdin);
  }

  results = PQexec(db, query);
  if (results == NULL) {
    fprintf(stderr,"%s",PQerrorMessage(db));
    return;
  }

  switch (PQresultStatus(results)) {
  case PGRES_TUPLES_OK:
    PQpagerPrintTuples(results);
    PQclear(results);
    break;
  case PGRES_EMPTY_QUERY:
    /* do nothing */
    break;
  case PGRES_COMMAND_OK:
    if (!quiet)
      fprintf(stdout,"%s\n",PQcmdStatus(results));
    break;
  case PGRES_COPY_OUT:
    handle_copy_out(results, quiet);
    break;
  case PGRES_COPY_IN:
    handle_copy_in(results, quiet);
    break;
  case PGRES_NONFATAL_ERROR:
  case PGRES_FATAL_ERROR:
  case PGRES_BAD_RESPONSE:
    fprintf(stderr,"%s",PQerrorMessage(db));
    break;

  } 

  /* check for asynchronous returns */
  notify = PQnotifies(db);
  if (notify) {
      fprintf(stderr,"ASYNC NOTIFY of '%s' from backend pid '%d' received\n",
	      notify->relname, notify->be_pid);
      free(notify);
  }

}

/*
  HandleSlashCmds:

  Handles all the different commands that start with \ 
     db_ptr is a pointer to the TgDb* structure
     line is the current input line
     prompt_ptr is a pointer to the prompt string,
                  a pointer is used because the prompt can be used with 
		  a connection to a new database
  returns a status:
       0 - send currently constructed query to backend (i.e. we got a \g)
       1 - skip processing of this line, continue building up query
       2 - terminate processing of this query entirely
*/
int
HandleSlashCmds(PGconn** db_ptr, char* line, char** prompt_ptr, 
		int echo_query, int quiet, int single_step)
{
  int status = 0;
  PGconn* db = *db_ptr;
  char* dbname = PQdb(db);

  switch (line[1])
    {
    case 'g':  /* \g means send query */
      status = 0;     
      break;
    case 'c':  /* \c means connect to new database */
      {
	char* newdbname;
	
	if (strlen(line) < 3) {
	  fprintf(stderr,"\\c must be followed by a database name\n");
	  status = 1;
	  break;
	}
	/* left and rightTrim take out whitespaces, including \n's
	   from gets_fromFile */
	newdbname = leftTrim(line+2);
	if (strcmp(newdbname, dbname) == 0)
	  {
	    fprintf(stderr,"already connected to %s\n", dbname);
	    status = 1;
	    break;
	  }
	else
	  {
	    PGconn *olddb;

	    printf("closing connection to database:%s\n", dbname);
	    olddb = db;
	    db = PQsetdb(PQhost(olddb), PQport(olddb), NULL, NULL, newdbname);
	    *db_ptr = db;
	    printf("connecting to new database: %s\n", newdbname);
	    if (PQstatus(db) == CONNECTION_BAD)
	      {
		fprintf(stderr,"%s\n", PQerrorMessage(db));
		printf("reconnecting to %s\n", dbname);
		db = PQsetdb(PQhost(olddb), PQport(olddb), NULL, NULL, dbname);
		*db_ptr = db;
		if (PQstatus(db) == CONNECTION_BAD)
		  {
		    fprintf(stderr, "could not reconnect to %s.  exiting\n", dbname);
		    exit(2);
		  }
		status = 1;
		break;
	      }
	    PQfinish(olddb);
	    free(*prompt_ptr);
	    *prompt_ptr = malloc(strlen(newdbname) + 10);
	    sprintf(*prompt_ptr,"%s=> ", newdbname);
	    status = 1;
	    break;
	  }
      }
      break;
    case 'd':     /* \d describe tables or columns in a table */
      {
        char* tabname;
	if (strlen(line) < 3) {
          tableList(db);
	  status = 1;
	  break;
	}
	tabname = leftTrim(line+2);
        tableDesc(db,tabname);
        status = 1;
        break;
      }
    case 'i':     /* \i is include file */
      {
	char* fname;
	FILE* fd;

	if (strlen(line) < 3) {
	  fprintf(stderr,"\\i must be followed by a file name\n");
	  status = 1;
	  break;
	}

	fname = leftTrim(line+2);
	if ( (fd = fopen(fname, "r")) == NULL)
	  {
	    fprintf(stderr,"file named %s could not be opened\n",fname);
	    status = 1;
	    break;
	  }
	MainLoop(&db, 0, fd, quiet, 0, echo_query, single_step);
	fclose(fd);
	status = 1;
	break;
      }
    case 'h':
      {
	char* cmd;
	int i, numCmds;

	if (strlen(line) < 3)
	  {
	    printf("type \\h <cmd> where <cmd> is one of the following:\n");
	    i = 0;
	    while (QL_HELP[i].cmd != NULL)
	      {
		printf("\t%s\n", QL_HELP[i].cmd);
		i++;
	      }
	  }
	else
	  {
	  cmd = leftTrim(line+2);

	  numCmds = 0;
	  while (QL_HELP[numCmds++].cmd != NULL);

	  numCmds = numCmds - 1;
	  
	  for (i=0; i<numCmds;i++)  {
	      if (strcmp(QL_HELP[i].cmd, cmd) == 0)    {
		printf("Description: %s\n", QL_HELP[i].help);
		printf("Syntax:\n");
		printf("%s\n", QL_HELP[i].syntax);
		break;
	      }
	    }
	  if (i == numCmds)
	    printf("command not found,  try \\h with no arguments to see available help\n");
	}
	status = 1;
	break;
      }
    case 'l':     /* \l is list database */
      listAllDbs(db);
      status = 1;
      break;
    case 'q': /* \q is quit */
      status = 2;
      break;
    case 's': /* \s is save history to a file */
      {
	char* fname;

	if (strlen(line) < 3) {
	  fprintf(stderr,"\\s must be followed by a file name\n");
	  status = 1;
	  break;
	}

	fname = leftTrim(line+2);
	if (write_history(fname) != 0)
	  {
	    fprintf(stderr,"cannot write history to %s\n",fname);
	  }
	status = 1;
	break;
      }
    default:
    case '?':     /* \? is help */
      slash_usage();
      status = 1;
      break;
    }
  return status;
}

/* 
 MainLoop: main processing loop for reading lines of input
 and sending them to the backend

 this loop is re-entrant.  May be called by \i command
 which reads input from a file

 *db_ptr must be initialized and set
 A pointer to a TgDb* is passed in, because the TgDb* itself
 can be changed by a \c command 
*/
void 
MainLoop(PGconn** db_ptr, int single_line_mode, FILE* source,
	 int quiet, int use_readline, int echo_query,
	 int single_step)
{
  char* prompt;                 /* readline prompt */
  char* line;                   /* line of input*/
  int len;                      /* length of the line */
  char query[MAX_QUERY_BUFFER]; /* multi-line query storage */
  PGconn* db = *db_ptr;
  char* dbname = PQdb(db);
  int slash_cmd_status = 0;
  /* slash_cmd_status can be:
       0 - send currently constructed query to backend (i.e. we got a \g)
       1 - skip processing of this line, continue building up query
       2 - terminate processing of this query entirely
  */

  int send_query = 0;
  int interactive;
  READ_ROUTINE GetNextLine;

  interactive = (source == stdin);

  if (interactive) {
    prompt = malloc(strlen(dbname) + 10);
    if (quiet)
      prompt[0] = '\0';
    else
      sprintf(prompt,"%s=> ", dbname);
    if (use_readline) {
	using_history();
	GetNextLine = gets_readline;
    } else
	GetNextLine = gets_noreadline;

  }
  else
    GetNextLine = gets_fromFile;

  query[0] = '\0';
  
  /* main loop for getting queries and executing them */
  while ((line = GetNextLine(prompt, source)) != NULL)
    {
      line = rightTrim(line); /* remove whitespaces on the right, incl. \n's */

      /* filter out comment lines that begin with --,
         this could be incorrect if -- is part of a quoted string.
         But we won't go through the trouble of detecting that.  If you have
	 -- in your quoted string, be careful and don't start a line with it*/
      if (line[0] == '-' && line[1] == '-') {
	  if (single_step) /* in single step mode, show comments too */
	      fprintf(stdout,"%s\n",line);
	  free(line);
	  continue;
      }

      if (line[0] == '\0') {
	  free(line);
	  continue;
      }

      len = strlen(line);

      if (interactive && use_readline)
	  add_history(line);      /* save non-empty lines in history */
      
      /* do the query immediately if we are doing single line queries 
       or if the last character is a semicolon */
      send_query = single_line_mode || (line[len-1] == ';') ;

      /* normally, \ commands have to be start the line,
	 but for backwards compatibility with monitor,
	 check for \g at the end of line */
      if (len > 2 && !send_query) 
	{
	  if (line[len-1]=='g' && line[len-2]=='\\')
	    {
	    send_query = 1;
	    line[len-2]='\0';
	  }
	}
      
      /* / commands have to be on their own line */
      /* slash commands handling here */
      if (line[0] == '\\') {
	slash_cmd_status = HandleSlashCmds(db_ptr, line, &prompt, 
					   echo_query, quiet, single_step);
	db = *db_ptr; /* in case \c changed the database */
	if (slash_cmd_status == 1)
	  continue;
	if (slash_cmd_status == 2)
	  break;
	if (slash_cmd_status == 0)
	  send_query = 1;
      }
      else
	if (strlen(query) + len > MAX_QUERY_BUFFER)
	  {
	    fprintf(stderr,"query buffer max length of %d exceeded\n",MAX_QUERY_BUFFER);
	    fprintf(stderr,"query line ignored\n");
	  }
      else
	if (query[0]!='\0')
	  sprintf(query,"%s\n%s",query,line);
      else
	strcpy(query,line);
      
      if (send_query && query[0] != '\0')
	{
	    /* echo the line read from the file,
	     unless we are in single_step mode, because single_step mode
	     will echo anyway */
	  if (!interactive && !single_step) 
	    fprintf(stderr,"%s\n",query);

	  SendQuery(db, query, echo_query, quiet, single_step);
	  query[0] = '\0';
	}
      
       free(line); /* free storage malloc'd by GetNextLine */
    } /* while */
} 

int
main(int argc, char** argv)
{
  extern char* optarg;
  extern int optind, opterr;
  
  PGconn *db;
  char* dbname = NULL;
  char* host = NULL;
  char* port = NULL;
  char* qfilename = NULL;
  
  int c;
  int list_databases = 0 ;
  int single_line_mode = 0;
  char* single_query = NULL;
  int quiet = 0;
#ifdef NOREADLINE
  int use_readline = 0;
#else
  int use_readline = 1;
#endif
  int echo_query = 0;
  int single_step_mode = 0;
 
  while ((c = getopt(argc, argv, "c:d:eintff:lhH:nsp:qS")) != EOF) {
    switch (c) {
    case 'c':
      single_query = optarg;
      break;
    case 'd':
      dbname = optarg;
      break;
    case 'e':
      echo_query = 1;
      break;
    case 'f':
      qfilename = optarg;
      break;
    case 'l':
      list_databases = 1;
      break;
    case 'H':
      host = optarg;
      break;
    case 'S':
      single_line_mode = 1;
      break;
    case 'n':
      use_readline = 0;
      break;
    case 's':
      single_step_mode = 1;
      break;
    case 'p':
      port = optarg;
      break;
    case 'q':
      quiet = 1;
      break;
    case 'h':
    default:
      usage(argv[0]);
      break;
    }
  }
  /* if we still have an argument, use it as the database name */
  if (argc - optind == 1)
    dbname = argv[optind];

  if (list_databases)
    dbname = "template1";
  
  if (dbname == NULL)
    {
      char *name;
      struct passwd *pw = getpwuid(getuid());
      name = pw->pw_name;
      fprintf(stderr, "No database name supplied, using user name: %s\n", name);
      dbname = name;
    }
  
  db = PQsetdb(host, port, NULL, NULL, dbname);

  if (PQstatus(db) == CONNECTION_BAD) {
    fprintf(stderr,"Connection to database '%s' failed.\n", dbname);
    fprintf(stderr,"%s",PQerrorMessage(db));
    exit(1);
  }
  if (list_databases) {
      exit(listAllDbs(db));
    }

  if (!quiet && !single_query && !qfilename) {
    printf("Welcome to the POSTGRES95 interactive sql monitor:\n");
    printf("  Please read the file COPYRIGHT for copyright terms of POSTGRES95\n\n");
    printf("   type \\? for help on slash commands\n");
    printf("   type \\q to quit\n");
    printf("   type \\g or terminate with semicolon to execute query\n");
    printf(" You are currently connected to the database: %s\n\n", dbname);
     }

  if (qfilename) {
      /* read in a file full of queries instead of reading in queries
	 interactively */
      char *line;
      char prompt[100];

      line = malloc(strlen(qfilename) + 5);
      sprintf(line,"\\i %s", qfilename);
      HandleSlashCmds(&db, line, (char**)prompt, echo_query, quiet,
		      single_step_mode);
  } else
      if (single_query)
	  SendQuery(db, single_query, echo_query, quiet, single_step_mode);
      else
	  MainLoop(&db, single_line_mode, stdin, quiet, 
		   use_readline, echo_query, single_step_mode);

  PQfinish(db);

  return 0;
}


#define COPYBUFSIZ	8192

static void
handle_copy_out(PGresult *res, int quiet)
{
    bool copydone = false;
    char copybuf[COPYBUFSIZ];
    int ret;

    if (!quiet)
	fprintf(stdout, "Copy command returns...\n");
    
    while (!copydone) {
	ret = PQgetline(res->conn, 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(res->conn);
}


static void
handle_copy_in(PGresult *res, int quiet)
{
    bool copydone = false;
    bool firstload;
    bool linedone;
    char copybuf[COPYBUFSIZ];
    char *s;
    int buflen;
    int c;
    
    if (!quiet) {
	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 ... */
	if (!quiet) {
	    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(res->conn, ".");
		copydone = true;
		break;
	    }
	    *s = '\0';
	    PQputline(res->conn, copybuf);
	    if (firstload) {
		if (!strcmp(copybuf, ".")) {
		    copydone = true;
		}
		firstload = false;
	    }
	}
	PQputline(res->conn, "\n");
    }
    PQendcopy(res->conn);
}

/*
 * PQpagerPrintTuples -
 *    print tuples through a pager (eg. more/less). Thanks to
 *    Sven Goldt <goldt@pegasus.in-berlin.de> for this idea and the patch.
 */
void
PQpagerPrintTuples(PGresult *res)
{
    FILE *fp = NULL;
    char *pager;

    pager=getenv("PAGER");
    if (pager!=NULL) fp = popen(pager, "w");
    if (fp==NULL) fp=stdout;
    signal(SIGPIPE, SIG_IGN);
    PQprintTuples(res,
		  fp,
		  1, /* print attribute names */
		  0, /* don't use terse output */
		  12 /* use fixed width of 15 */
		  );
    fflush(fp);
    if (fp!=stdout) pclose(fp);
    signal(SIGPIPE, SIG_DFL);
}
