/*-------------------------------------------------------------------------
 *
 * psql.c--
 *	  an interactive front-end to postgreSQL
 *
 * Copyright (c) 1996, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/bin/psql/Attic/psql.c,v 1.93 1997/09/08 21:50:14 momjian Exp $
 *
 *-------------------------------------------------------------------------
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/param.h>			/* for MAXPATHLEN */
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include "postgres.h"
#include "libpq-fe.h"
#include "pqsignal.h"
#include "stringutils.h"
#include "psqlHelp.h"
#ifndef HAVE_STRDUP
#include "strdup.h"
#endif
#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif

#ifdef HAVE_LIBREADLINE
#ifdef HAVE_READLINE_H
#include <readline.h>
#if defined(HAVE_HISTORY)
#include <history.h>
#endif
#else
#include <readline/readline.h>
#if defined(HAVE_READLINE_HISTORY_H)
#include <readline/history.h>
#endif
#endif
#endif

/* This prompt string is assumed to have at least 3 characters by code in MainLoop().
 * A character two characters from the end is replaced each time by a mode character.
 */
#define PROMPT "=> "

#define PROMPT_READY	'='
#define PROMPT_CONTINUE '-'
#define PROMPT_COMMENT	'*'
#define PROMPT_QUOTE	'\''

/* Backslash command handling:
 *	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
 *	3 - new query supplied by edit
 */
#define CMD_UNKNOWN		-1
#define CMD_SEND		0
#define CMD_SKIP_LINE	1
#define CMD_TERMINATE	2
#define CMD_NEWEDIT		3

#define MAX_QUERY_BUFFER 20000

#define COPYBUFSIZ	8192

#define DEFAULT_FIELD_SEP "|"
#define DEFAULT_EDITOR	"vi"
#define DEFAULT_SHELL  "/bin/sh"

typedef struct _psqlSettings
{
	PGconn	   *db;				/* connection to backend */
	FILE	   *queryFout;		/* where to send the query results */
	PQprintOpt	opt;			/* options to be passed to PQprint */
	char	   *prompt;			/* prompt to display */
	char	   *gfname;			/* one-shot file output argument for \g */
	bool		notty;			/* input or output is not a tty */
	bool		pipe;			/* queryFout is from a popen() */
	bool		echoQuery;		/* echo the query before sending it */
	bool		quiet;			/* run quietly, no messages, no promt */
	bool		singleStep;		/* prompt before for each query */
	bool		singleLineMode; /* query terminated by newline */
	bool		useReadline;	/* use libreadline routines */
	bool		getPassword;	/* prompt the user for a username and
								 * password */
} PsqlSettings;

/* declarations for functions in this file */
static void usage(char *progname);
static void slashUsage();
static void handleCopyOut(PGresult *res, bool quiet, FILE *copystream);
static void
handleCopyIn(PGresult *res, const bool mustprompt,
			 FILE *copystream);
static int	tableList(PsqlSettings *ps, bool deep_tablelist, char info_type);
static int	tableDesc(PsqlSettings *ps, char *table);
static int	rightsList(PsqlSettings *ps);
static void prompt_for_password(char *username, char *password);
static char *
make_connect_string(char *host, char *port, char *dbname,
					char *username, char *password);

static char *gets_noreadline(char *prompt, FILE *source);
static char *gets_readline(char *prompt, FILE *source);
static char *gets_fromFile(char *prompt, FILE *source);
static int	listAllDbs(PsqlSettings *settings);
static void
SendQuery(bool *success_p, PsqlSettings *settings, const char *query,
		  const bool copy_in, const bool copy_out, FILE *copystream);
static int
HandleSlashCmds(PsqlSettings *settings,
				char *line,
				char *query);
static int	MainLoop(PsqlSettings *settings, FILE *source);

/* probably should move this into libpq */
void
PQprint(FILE *fp,
		PGresult *res,
		PQprintOpt *po
);

static FILE *setFout(PsqlSettings *ps, char *fname);

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

static void
usage(char *progname)
{
	fprintf(stderr, "Usage: %s [options] [dbname]\n", progname);
	fprintf(stderr, "\t -a authsvc              set authentication service\n");
	fprintf(stderr, "\t -A                      turn off alignment when printing out attributes\n");
	fprintf(stderr, "\t -c query                run single query (slash commands too)\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 -F sep                  set the field separator (default is '|')\n");
	fprintf(stderr, "\t -h host                 set database server host\n");
	fprintf(stderr, "\t -H                      turn on html3.0 table output\n");
	fprintf(stderr, "\t -l                      list available databases\n");
	fprintf(stderr, "\t -n                      don't use readline library\n");
	fprintf(stderr, "\t -o filename             send output to filename or (|pipe)\n");
	fprintf(stderr, "\t -p port                 set port number\n");
	fprintf(stderr, "\t -q                      run quietly (no messages, no prompts)\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 -t                      turn off printing of headings and row count\n");
	fprintf(stderr, "\t -T html                 set html3.0 table command options (cf. -H)\n");
	fprintf(stderr, "\t -u                      ask for a username and password for authentication\n");
	fprintf(stderr, "\t -x                      turn on expanded output (field names on left)\n");
	exit(1);
}

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

static char *
on(bool f)
{
	return f ? "on" : "off";
}

static void
slashUsage(PsqlSettings *ps)
{
	int			usePipe = 0;
	char	   *pagerenv;
	FILE	   *fout;

	if (ps->notty == 0 &&
		(pagerenv = getenv("PAGER")) &&
		(pagerenv[0] != '\0') &&
		(fout = popen(pagerenv, "w")))
	{
		usePipe = 1;
		pqsignal(SIGPIPE, SIG_IGN);
	}
	else
		fout = stdout;

	fprintf(fout, " \\?           -- help\n");
	fprintf(fout, " \\a           -- toggle field-alignment (currenty %s)\n", on(ps->opt.align));
	fprintf(fout, " \\C [<captn>] -- set html3 caption (currently '%s')\n", ps->opt.caption ? ps->opt.caption : "");
	fprintf(fout, " \\connect <dbname|-> <user> -- connect to new database (currently '%s')\n", PQdb(ps->db));
	fprintf(fout, " \\copy table {from | to} <fname>\n");
	fprintf(fout, " \\d [<table>] -- list tables and indices in database or columns in <table>, * for all\n");
	fprintf(fout, " \\di          -- list only indices in database\n");
	fprintf(fout, " \\ds          -- list only sequences in database\n");
	fprintf(fout, " \\dt          -- list only tables in database\n");
	fprintf(fout, " \\e [<fname>] -- edit the current query buffer or <fname>, \\E execute too\n");
	fprintf(fout, " \\f [<sep>]   -- change field separater (currently '%s')\n", ps->opt.fieldSep);
	fprintf(fout, " \\g [<fname>] [|<cmd>] -- send query to backend [and results in <fname> or pipe]\n");
	fprintf(fout, " \\h [<cmd>]   -- help on syntax of sql commands, * for all commands\n");
	fprintf(fout, " \\H           -- toggle html3 output (currently %s)\n", on(ps->opt.html3));
	fprintf(fout, " \\i <fname>   -- read and execute queries from filename\n");
	fprintf(fout, " \\l           -- list all databases\n");
	fprintf(fout, " \\m           -- toggle monitor-like table display (currently %s)\n", on(ps->opt.standard));
	fprintf(fout, " \\o [<fname>] [|<cmd>] -- send all query results to stdout, <fname>, or pipe\n");
	fprintf(fout, " \\p           -- print the current query buffer\n");
	fprintf(fout, " \\q           -- quit\n");
	fprintf(fout, " \\r           -- reset(clear) the query buffer\n");
	fprintf(fout, " \\s [<fname>] -- print history or save it in <fname>\n");
	fprintf(fout, " \\t           -- toggle table headings and row count (currently %s)\n", on(ps->opt.header));
	fprintf(fout, " \\T [<html>]  -- set html3.0 <table ...> options (currently '%s')\n", ps->opt.tableOpt ? ps->opt.tableOpt : "");
	fprintf(fout, " \\x           -- toggle expanded output (currently %s)\n", on(ps->opt.expanded));
	fprintf(fout, " \\z           -- list current grant/revoke permissions\n");
	fprintf(fout, " \\! [<cmd>]   -- shell escape or command\n");

	if (usePipe)
	{
		pclose(fout);
		pqsignal(SIGPIPE, SIG_DFL);
	}
}

static PGresult *
PSQLexec(PsqlSettings *ps, char *query)
{
	PGresult   *res;

	res = PQexec(ps->db, query);
	if (!res)
		fputs(PQerrorMessage(ps->db), stderr);
	else
	{
		if (PQresultStatus(res) == PGRES_COMMAND_OK ||
			PQresultStatus(res) == PGRES_TUPLES_OK)
			return res;
		if (!ps->quiet)
			fputs(PQerrorMessage(ps->db), stderr);
		PQclear(res);
	}
	return NULL;
}

/*
 * listAllDbs
 *
 * list all the databases in the system returns 0 if all went well
 *
 *
 */

static int
listAllDbs(PsqlSettings *ps)
{
	PGresult   *results;
	char	   *query = "select * from pg_database;";

	if (!(results = PSQLexec(ps, query)))
		return 1;
	else
	{
		PQprint(ps->queryFout,
				results,
				&ps->opt);
		PQclear(results);
		return 0;
	}
}

/*
 * List The Database Tables returns 0 if all went well
 *
 */
int
tableList(PsqlSettings *ps, bool deep_tablelist, char info_type)
{
	char		listbuf[256];
	int			nColumns;
	int			i;
	char	   *rk;
	char	   *rr;

	PGresult   *res;

	listbuf[0] = '\0';
	strcat(listbuf, "SELECT usename, relname, relkind, relhasrules");
	strcat(listbuf, "  FROM pg_class, pg_user ");
	switch (info_type)
	{
		case 't':
			strcat(listbuf, "WHERE ( relkind = 'r') ");
			break;
		case 'i':
			strcat(listbuf, "WHERE ( relkind = 'i') ");
			break;
		case 'S':
			strcat(listbuf, "WHERE ( relkind = 'S') ");
			break;
		case 'b':
		default:
			strcat(listbuf, "WHERE ( relkind = 'r' OR relkind = 'i' OR relkind = 'S') ");
			break;
	}
	strcat(listbuf, "  and relname !~ '^pg_'");
	strcat(listbuf, "  and relname !~ '^xin[vx][0-9]+'");

	/*
	 * the usesysid = relowner won't work on stock 1.0 dbs, need to add in
	 * the int4oideq function
	 */
	strcat(listbuf, "  and usesysid = relowner");
	strcat(listbuf, "  ORDER BY relname ");
	if (!(res = PSQLexec(ps, listbuf)))
		return -1;
	/* first, print out the attribute names */
	nColumns = PQntuples(res);
	if (nColumns > 0)
	{
		if (deep_tablelist)
		{
			/* describe everything here */
			char	  **table;

			table = (char **) malloc(nColumns * sizeof(char *));
			if (table == NULL)
				perror("malloc");

			/* load table table */
			for (i = 0; i < nColumns; i++)
			{
				table[i] = (char *) malloc(PQgetlength(res, i, 1) * sizeof(char) + 1);
				if (table[i] == NULL)
					perror("malloc");
				strcpy(table[i], PQgetvalue(res, i, 1));
			}

			PQclear(res);		/* PURIFY */
			for (i = 0; i < nColumns; i++)
			{
				tableDesc(ps, table[i]);
			}
			free(table);
		}
		else
		{
			/* Display the information */

			printf("\nDatabase    = %s\n", PQdb(ps->db));
			printf(" +------------------+----------------------------------+----------+\n");
			printf(" |  Owner           |             Relation             |   Type   |\n");
			printf(" +------------------+----------------------------------+----------+\n");

			/* next, print out the instances */
			for (i = 0; i < PQntuples(res); i++)
			{
				printf(" | %-16.16s", PQgetvalue(res, i, 0));
				printf(" | %-32.32s | ", PQgetvalue(res, i, 1));
				rk = PQgetvalue(res, i, 2);
				rr = PQgetvalue(res, i, 3);
				if (strcmp(rk, "r") == 0)
					printf("%-8.8s |", (rr[0] == 't') ? "view?" : "table");
				else if (strcmp(rk, "i") == 0)
					printf("%-8.8s |", "index");
				else
					printf("%-8.8s |", "sequence");
				printf("\n");
			}
			printf(" +------------------+----------------------------------+----------+\n");
			PQclear(res);
		}
		return (0);

	}
	else
	{
		PQclear(res);			/* PURIFY */
		switch (info_type)
		{
			case 't':
				fprintf(stderr, "Couldn't find any tables!\n");
				break;
			case 'i':
				fprintf(stderr, "Couldn't find any indices!\n");
				break;
			case 'S':
				fprintf(stderr, "Couldn't find any sequences!\n");
				break;
			case 'b':
			default:
				fprintf(stderr, "Couldn't find any tables, sequences or indices!\n");
				break;
		}
		return (-1);
	}
}

/*
 * List Tables Grant/Revoke Permissions returns 0 if all went well
 *
 */
int
rightsList(PsqlSettings *ps)
{
	char		listbuf[256];
	int			nColumns;
	int			i;

	PGresult   *res;

	listbuf[0] = '\0';
	strcat(listbuf, "SELECT relname, relacl");
	strcat(listbuf, "  FROM pg_class, pg_user ");
	strcat(listbuf, "WHERE ( relkind = 'r' OR relkind = 'i') ");
	strcat(listbuf, "  and relname !~ '^pg_'");
	strcat(listbuf, "  and relname !~ '^xin[vx][0-9]+'");
	strcat(listbuf, "  and usesysid = relowner");
	strcat(listbuf, "  ORDER BY relname ");
	if (!(res = PSQLexec(ps, listbuf)))
		return -1;

	nColumns = PQntuples(res);
	if (nColumns > 0)
	{
		/* Display the information */

		printf("\nDatabase    = %s\n", PQdb(ps->db));
		printf(" +------------------+----------------------------------------------------+\n");
		printf(" |  Relation        |             Grant/Revoke Permissions               |\n");
		printf(" +------------------+----------------------------------------------------+\n");

		/* next, print out the instances */
		for (i = 0; i < PQntuples(res); i++)
		{
			printf(" | %-16.16s", PQgetvalue(res, i, 0));
			printf(" | %-50.50s | ", PQgetvalue(res, i, 1));
			printf("\n");
		}
		printf(" +------------------+----------------------------------------------------+\n");
		PQclear(res);
		return (0);
	}
	else
	{
		fprintf(stderr, "Couldn't find any tables!\n");
		return (-1);
	}
}

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

	PGresult   *res;

	/* Build the query */

	for (i = strlen(table); i >= 0; i--)
		if (isupper(table[i]))
			table[i] = tolower(table[i]);

	descbuf[0] = '\0';
	strcat(descbuf, "SELECT a.attnum, a.attname, t.typname, a.attlen, a.attnotnull");
	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 attnum ");
	if (!(res = PSQLexec(ps, descbuf)))
		return -1;
	/* 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("| %-32.32s | ", PQgetvalue(res, i, 1));
			rtype = PQgetvalue(res, i, 2);
			rsize = atoi(PQgetvalue(res, i, 3));
			if (strcmp(rtype, "text") == 0)
			{
				printf("%-32.32s |", rtype);
				printf("%6s |", "var");
			}
			else if (strcmp(rtype, "bpchar") == 0)
			{
				printf("%-32.32s |", "(bp)char");
				printf("%6i |", rsize > 0 ? rsize - 4 : 0);
			}
			else if (strcmp(rtype, "varchar") == 0)
			{
				printf("%-32.32s |", rtype);
				printf("%6i |", rsize > 0 ? rsize - 4 : 0);
			}
			else
			{
				/* array types start with an underscore */
				if (rtype[0] != '_')
					printf("%-32.32s |", rtype);
				else
				{
					char	   *newname;

					newname = malloc(strlen(rtype) + 2);
					strcpy(newname, rtype + 1);
					strcat(newname, "[]");
					printf("%-32.32s |", newname);
					free(newname);
				}
				if (rsize > 0)
					printf("%6i |", rsize);
				else
					printf("%6s |", "var");
			}
			printf("\n");
		}
		printf("+----------------------------------+----------------------------------+-------+\n");

		PQclear(res);
		return (0);

	}
	else
	{
		fprintf(stderr, "Couldn't find table %s!\n", table);
		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
 */
static char *
gets_noreadline(char *prompt, FILE *source)
{
	fputs(prompt, stdout);
	fflush(stdout);
	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
 */
static char *
gets_readline(char *prompt, FILE *source)
{
	char	   *s;

#ifdef HAVE_LIBREADLINE
	s = readline(prompt);
#else
	char		buf[500];

	printf("%s", prompt);
	s = fgets(buf, 500, stdin);
#endif
	fputc('\r', stdout);
	fflush(stdout);
	return s;
}

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

	line = malloc(MAX_QUERY_BUFFER + 1);

	/* read up to MAX_QUERY_BUFFER characters */
	if (fgets(line, MAX_QUERY_BUFFER, source) == NULL)
	{
		free(line);
		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: send the query string to the backend return *success_p = 1 if
 * the query executed successfully returns *success_p = 0 otherwise
 */
static void
SendQuery(bool *success_p, PsqlSettings *settings, const char *query,
		  const bool copy_in, const bool copy_out, FILE *copystream)
{

	PGresult   *results;
	PGnotify   *notify;

	if (settings->singleStep)
		fprintf(stdout, "\n**************************************"
				"*****************************************\n");

	if (settings->echoQuery || settings->singleStep)
	{
		fprintf(stderr, "QUERY: %s\n", query);
		fflush(stderr);
	}
	if (settings->singleStep)
	{
		fprintf(stdout, "\n**************************************"
				"*****************************************\n");
		fflush(stdout);
		printf("\npress return to continue ..\n");
		gets_fromFile("", stdin);
	}
	results = PQexec(settings->db, query);
	if (results == NULL)
	{
		fprintf(stderr, "%s", PQerrorMessage(settings->db));
		*success_p = false;
	}
	else
	{
		switch (PQresultStatus(results))
		{
			case PGRES_TUPLES_OK:
				if (settings->gfname)
				{
					PsqlSettings ps = *settings;
					FILE	   *fp;

					ps.queryFout = stdout;
					fp = setFout(&ps, settings->gfname);
					if (!fp || fp == stdout)
					{
						*success_p = false;
						break;
					}
					else
						*success_p = true;
					PQprint(fp,
							results,
							&(settings->opt));
					if (ps.pipe)
						pclose(fp);
					else
						fclose(fp);
					free(settings->gfname);
					settings->gfname = NULL;
					break;
				}
				else
				{
					*success_p = true;
					PQprint(settings->queryFout,
							results,
							&(settings->opt));
					fflush(settings->queryFout);
				}
				break;
			case PGRES_EMPTY_QUERY:
				*success_p = true;
				break;
			case PGRES_COMMAND_OK:
				*success_p = true;
				if (!settings->quiet)
					printf("%s\n", PQcmdStatus(results));
				break;
			case PGRES_COPY_OUT:
				*success_p = true;
				if (copy_out)
				{
					handleCopyOut(results, settings->quiet, copystream);
				}
				else
				{
					if (!settings->quiet)
						printf("Copy command returns...\n");

					handleCopyOut(results, settings->quiet, stdout);
				}
				break;
			case PGRES_COPY_IN:
				*success_p = true;
				if (copy_in)
					handleCopyIn(results, false, copystream);
				else
					handleCopyIn(results, !settings->quiet, stdin);
				break;
			case PGRES_NONFATAL_ERROR:
			case PGRES_FATAL_ERROR:
			case PGRES_BAD_RESPONSE:
				*success_p = false;
				fprintf(stderr, "%s", PQerrorMessage(settings->db));
				break;
		}

		if (PQstatus(settings->db) == CONNECTION_BAD)
		{
			fprintf(stderr,
					"We have lost the connection to the backend, so "
					"further processing is impossible.  "
					"Terminating.\n");
			exit(2);			/* we are out'ta here */
		}
		/* check for asynchronous returns */
		notify = PQnotifies(settings->db);
		if (notify)
		{
			fprintf(stderr,
				 "ASYNC NOTIFY of '%s' from backend pid '%d' received\n",
					notify->relname, notify->be_pid);
			free(notify);
		}
		if (results)
			PQclear(results);
	}
}



static void
editFile(char *fname)
{
	char	   *editorName;
	char	   *sys;

	editorName = getenv("EDITOR");
	if (!editorName)
		editorName = DEFAULT_EDITOR;
	sys = malloc(strlen(editorName) + strlen(fname) + 32 + 1);
	if (!sys)
	{
		perror("malloc");
		exit(1);
	}
	sprintf(sys, "exec '%s' '%s'", editorName, fname);
	system(sys);
	free(sys);
}

static bool
toggle(PsqlSettings *settings, bool *sw, char *msg)
{
	*sw = !*sw;
	if (!settings->quiet)
		printf("turned %s %s\n", on(*sw), msg);
	return *sw;
}



static void
unescape(char *dest, const char *source)
{
	/*-----------------------------------------------------------------------------
	  Return as the string <dest> the value of string <source> with escape
	  sequences turned into the bytes they represent.
	-----------------------------------------------------------------------------*/
	char	   *p;
	bool		esc;			/* Last character we saw was the escape
								 * character (/) */

	esc = false;				/* Haven't seen escape character yet */
	for (p = (char *) source; *p; p++)
	{
		char		c;			/* Our output character */

		if (esc)
		{
			switch (*p)
			{
				case 'n':
					c = '\n';
					break;
				case 'r':
					c = '\r';
					break;
				case 't':
					c = '\t';
					break;
				case 'f':
					c = '\f';
					break;
				case '\\':
					c = '\\';
					break;
				default:
					c = *p;
			}
			esc = false;
		}
		else if (*p == '\\')
		{
			esc = true;
			c = ' ';			/* meaningless, but compiler doesn't know
								 * that */
		}
		else
		{
			c = *p;
			esc = false;
		}
		if (!esc)
			*dest++ = c;
	}
	*dest = '\0';				/* Terminating null character */
}



static void
parse_slash_copy(const char *args, char *table, const int table_len,
				 char *file, const int file_len,
				 bool *from_p, bool *error_p)
{

	char		work_args[200];

	/*
	 * A copy of the \copy command arguments, except that we modify it as
	 * we parse to suit our parsing needs.
	 */
	char	   *table_tok,
			   *fromto_tok;

	strncpy(work_args, args, sizeof(work_args));
	work_args[sizeof(work_args) - 1] = '\0';

	*error_p = false;			/* initial assumption */

	table_tok = strtok(work_args, " ");
	if (table_tok == NULL)
	{
		fprintf(stderr, "\\copy needs arguments.\n");
		*error_p = true;
	}
	else
	{
		strncpy(table, table_tok, table_len);
		file[table_len - 1] = '\0';

		fromto_tok = strtok(NULL, "  ");
		if (fromto_tok == NULL)
		{
			fprintf(stderr, "'FROM' or 'TO' must follow table name.\n");
			*error_p = true;
		}
		else
		{
			if (strcasecmp(fromto_tok, "from") == 0)
				*from_p = true;
			else if (strcasecmp(fromto_tok, "to") == 0)
				*from_p = false;
			else
			{
				fprintf(stderr,
						"Unrecognized token found where "
						"'FROM' or 'TO' expected: '%s'.\n",
						fromto_tok);
				*error_p = true;
			}
			if (!*error_p)
			{
				char	   *file_tok;

				file_tok = strtok(NULL, " ");
				if (file_tok == NULL)
				{
					fprintf(stderr, "A file pathname must follow '%s'.\n",
							fromto_tok);
					*error_p = true;
				}
				else
				{
					strncpy(file, file_tok, file_len);
					file[file_len - 1] = '\0';
					if (strtok(NULL, " ") != NULL)
					{
						fprintf(stderr,
						  "You have extra tokens after the filename.\n");
						*error_p = true;
					}
				}
			}
		}
	}
}



static void
do_copy(const char *args, PsqlSettings *settings)
{
	/*---------------------------------------------------------------------------
	  Execute a \copy command (frontend copy).	We have to open a file, then
	  submit a COPY query to the backend and either feed it data from the
	  file or route its response into the file.

	  We do a text copy with default (tab) column delimiters.  Some day, we
	  should do all the things a backend copy can do.

	----------------------------------------------------------------------------*/
	char		query[200];

	/* The COPY command we send to the back end */
	bool		from;

	/* The direction of the copy is from a file to a table. */
	char		file[MAXPATHLEN + 1];

	/* The pathname of the file from/to which we copy */
	char		table[NAMEDATALEN];

	/* The name of the table from/to which we copy */
	bool		syntax_error;

	/* The \c command has invalid syntax */
	FILE	   *copystream;

	parse_slash_copy(args, table, sizeof(table), file, sizeof(file),
					 &from, &syntax_error);

	if (!syntax_error)
	{
		strcpy(query, "COPY ");
		strcat(query, table);

		if (from)
			strcat(query, " FROM stdin");
		else
			strcat(query, " TO stdout");

		if (from)
		{
			copystream = fopen(file, "r");
		}
		else
		{
			copystream = fopen(file, "w");
		}
		if (copystream == NULL)
			fprintf(stderr,
				"Unable to open file %s which to copy, errno = %s (%d).",
					from ? "from" : "to", strerror(errno), errno);
		else
		{
			bool		success;/* The query succeeded at the backend */

			SendQuery(&success, settings, query, from, !from, copystream);
			fclose(copystream);
			if (!settings->quiet)
			{
				if (success)
					printf("Successfully copied.\n");
				else
					printf("Copy failed.\n");
			}
		}
	}
}


static void
do_connect(const char *new_dbname,
		   const char *new_user,
		   PsqlSettings *settings)
{
	if (!new_dbname)
		fprintf(stderr, "\\connect must be followed by a database name\n");
	else
	{
		PGconn	   *olddb = settings->db;
		static char *userenv = NULL;
		char	   *old_userenv = NULL;
		const char *dbparam;

		if (new_user != NULL)
		{

			/*
			 * PQsetdb() does not allow us to specify the user, so we have
			 * to do it via PGUSER
			 */
			if (userenv != NULL)
				old_userenv = userenv;
			userenv = malloc(strlen("PGUSER=") + strlen(new_user) + 1);
			sprintf(userenv, "PGUSER=%s", new_user);
			/* putenv() may continue to use memory as part of environment */
			putenv(userenv);
			/* can delete old memory if we malloc'ed it */
			if (old_userenv != NULL)
				free(old_userenv);
		}

		if (strcmp(new_dbname, "-") != 0)
			dbparam = new_dbname;
		else
			dbparam = PQdb(olddb);

		settings->db = PQsetdb(PQhost(olddb), PQport(olddb),
							   NULL, NULL, dbparam);
		if (!settings->quiet)
		{
			if (!new_user)
				printf("connecting to new database: %s\n", dbparam);
			else if (dbparam != new_dbname)
				printf("connecting as new user: %s\n", new_user);
			else
				printf("connecting to new database: %s as user: %s\n",
					   dbparam, new_user);
		}

		if (PQstatus(settings->db) == CONNECTION_BAD)
		{
			fprintf(stderr, "%s\n", PQerrorMessage(settings->db));
			fprintf(stderr, "Could not connect to new database. exiting\n");
			exit(2);
		}
		else
		{
			PQfinish(olddb);
			free(settings->prompt);
			settings->prompt = malloc(strlen(PQdb(settings->db)) + 10);
			sprintf(settings->prompt, "%s%s", PQdb(settings->db), PROMPT);
		}
	}
}


static void
do_edit(const char *filename_arg, char *query, int *status_p)
{

	int			fd;
	char		tmp[64];
	char	   *fname;
	int			cc;
	const int	ql = strlen(query);
	bool		error;

	if (filename_arg)
	{
		fname = (char *) filename_arg;
		error = false;
	}
	else
	{
		sprintf(tmp, "/tmp/psql.%ld.%ld", (long) geteuid(), (long) getpid());
		fname = tmp;
		unlink(tmp);
		if (ql > 0)
		{
			if ((fd = open(tmp, O_EXCL | O_CREAT | O_WRONLY, 0600)) == -1)
			{
				perror(tmp);
				error = true;
			}
			else
			{
				if (query[ql - 1] != '\n')
					strcat(query, "\n");
				if (write(fd, query, ql) != ql)
				{
					perror(tmp);
					close(fd);
					unlink(tmp);
					error = true;
				}
				else
					error = false;
				close(fd);
			}
		}
		else
			error = false;
	}

	if (error)
		*status_p = CMD_SKIP_LINE;
	else
	{
		editFile(fname);
		if ((fd = open(fname, O_RDONLY)) == -1)
		{
			perror(fname);
			if (!filename_arg)
				unlink(fname);
			*status_p = CMD_SKIP_LINE;
		}
		else
		{
			if ((cc = read(fd, query, MAX_QUERY_BUFFER)) == -1)
			{
				perror(fname);
				close(fd);
				if (!filename_arg)
					unlink(fname);
				*status_p = CMD_SKIP_LINE;
			}
			else
			{
				query[cc] = '\0';
				close(fd);
				if (!filename_arg)
					unlink(fname);
				rightTrim(query);
				*status_p = CMD_NEWEDIT;
			}
		}
	}
}



static void
do_help(PsqlSettings *ps, const char *topic)
{

	if (!topic)
	{
		char		left_center_right;	/* Which column we're displaying */
		int			i;			/* Index into QL_HELP[] */

		printf("type \\h <cmd> where <cmd> is one of the following:\n");

		left_center_right = 'L';/* Start with left column */
		i = 0;
		while (QL_HELP[i].cmd != NULL)
		{
			switch (left_center_right)
			{
				case 'L':
					printf("    %-25s", QL_HELP[i].cmd);
					left_center_right = 'C';
					break;
				case 'C':
					printf("%-25s", QL_HELP[i].cmd);
					left_center_right = 'R';
					break;
				case 'R':
					printf("%-25s\n", QL_HELP[i].cmd);
					left_center_right = 'L';
					break;
			};
			i++;
		}
		if (left_center_right != 'L')
			puts("\n");
		printf("type \\h * for a complete description of all commands\n");
	}
	else
	{
		int			i;			/* Index into QL_HELP[] */
		bool		help_found; /* We found the help he asked for */

		int			usePipe = 0;
		char	   *pagerenv;
		FILE	   *fout;

		if (strcmp(topic, "*") == 0 &&
			(ps->notty == 0) &&
			(pagerenv = getenv("PAGER")) &&
			(pagerenv[0] != '\0') &&
			(fout = popen(pagerenv, "w")))
		{
			usePipe = 1;
			pqsignal(SIGPIPE, SIG_IGN);
		}
		else
			fout = stdout;

		help_found = false;		/* Haven't found it yet */
		for (i = 0; QL_HELP[i].cmd; i++)
		{
			if (strcmp(QL_HELP[i].cmd, topic) == 0 ||
				strcmp(topic, "*") == 0)
			{
				help_found = true;
				fprintf(fout, "Command: %s\n", QL_HELP[i].cmd);
				fprintf(fout, "Description: %s\n", QL_HELP[i].help);
				fprintf(fout, "Syntax:\n");
				fprintf(fout, "%s\n", QL_HELP[i].syntax);
				fprintf(fout, "\n");
			}
		}

		if (usePipe)
		{
			pclose(fout);
			pqsignal(SIGPIPE, SIG_DFL);
		}

		if (!help_found)
			fprintf(stderr, "command not found, "
					"try \\h with no arguments to see available help\n");
	}
}



static void
do_shell(const char *command)
{

	if (!command)
	{
		char	   *sys;
		char	   *shellName;

		shellName = getenv("SHELL");
		if (shellName == NULL)
			shellName = DEFAULT_SHELL;
		sys = malloc(strlen(shellName) + 16);
		if (!sys)
		{
			perror("malloc");
			exit(1);
		}
		sprintf(sys, "exec %s", shellName);
		system(sys);
		free(sys);
	}
	else
		system(command);
}



/*
 * 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
 *	3 - new query supplied by edit
 */
static int
HandleSlashCmds(PsqlSettings *settings,
				char *line,
				char *query)
{
	int			status = CMD_SKIP_LINE;
	char	   *optarg;

	/*
	 * Pointer inside the <cmd> string to the argument of the slash
	 * command, assuming it is a one-character slash command.  If it's not
	 * a one-character command, this is meaningless.
	 */
	char	   *optarg2;

	/*
	 * Pointer inside the <cmd> string to the argument of the slash
	 * command assuming it's not a one-character command.  If it's a
	 * one-character command, this is meaningless.
	 */
	char	   *cmd;

	/*
	 * String: value of the slash command, less the slash and with escape
	 * sequences decoded.
	 */
	int			blank_loc;

	/* Offset within <cmd> of first blank */

	cmd = malloc(strlen(line)); /* unescaping better not make string grow. */

	unescape(cmd, line + 1);	/* sets cmd string */

	if (strlen(cmd) >= 1 && cmd[strlen(cmd) - 1] == ';')		/* strip trailing ; */
		cmd[strlen(cmd) - 1] = '\0';

	/*
	 * Originally, there were just single character commands.  Now, we
	 * define some longer, friendly commands, but we have to keep the old
	 * single character commands too.  \c used to be what \connect is now.
	 * Complicating matters is the fact that with the single-character
	 * commands, you can start the argument right after the single
	 * character, so "\copy" would mean "connect to database named 'opy'".
	 */

	if (strlen(cmd) > 1)
		optarg = cmd + 1 + strspn(cmd + 1, " \t");
	else
		optarg = NULL;

	blank_loc = strcspn(cmd, " \t");
	if (blank_loc == 0 || !cmd[blank_loc])
		optarg2 = NULL;
	else
		optarg2 = cmd + blank_loc + strspn(cmd + blank_loc, " \t");

	switch (cmd[0])
	{
		case 'a':				/* toggles to align fields on output */
			toggle(settings, &settings->opt.align, "field alignment");
			break;
		case 'C':				/* define new caption */
			if (settings->opt.caption)
			{
				free(settings->opt.caption);
				settings->opt.caption = NULL;
			}
			if (optarg && !(settings->opt.caption = strdup(optarg)))
			{
				perror("malloc");
				exit(CMD_TERMINATE);
			}
			break;
		case 'c':
			{
				if (strncmp(cmd, "copy ", strlen("copy ")) == 0)
					do_copy(optarg2, settings);
				else if (strncmp(cmd, "connect ", strlen("connect ")) == 0 ||
				  strcmp(cmd, "connect") == 0 /* issue error message */ )
				{
					char	   *optarg3 = NULL;
					int			blank_loc2;

					if (optarg2)
					{
						blank_loc2 = strcspn(optarg2, " \t");
						if (blank_loc2 == 0 || *(optarg2 + blank_loc2) == '\0')
							optarg3 = NULL;
						else
						{
							optarg3 = optarg2 + blank_loc2 +
								strspn(optarg2 + blank_loc2, " \t");
							*(optarg2 + blank_loc2) = '\0';
						}
					}
					do_connect(optarg2, optarg3, settings);
				}
				else
				{
					char	   *optarg3 = NULL;
					int			blank_loc2;

					if (optarg)
					{
						blank_loc2 = strcspn(optarg, " \t");
						if (blank_loc2 == 0 || *(optarg + blank_loc2) == '\0')
							optarg3 = NULL;
						else
						{
							optarg3 = optarg + blank_loc2 +
								strspn(optarg + blank_loc2, " \t");
							*(optarg + blank_loc2) = '\0';
						}
					}
					do_connect(optarg, optarg3, settings);
				}
			}
			break;
		case 'd':				/* \d describe tables or columns in a
								 * table */
			if (strncmp(cmd, "dt", 2) == 0)
			{					/* only tables */
				tableList(settings, 0, 't');
			}
			else if (strncmp(cmd, "di", 2) == 0)
			{					/* only indices */
				tableList(settings, 0, 'i');
			}
			else if (strncmp(cmd, "ds", 2) == 0)
			{					/* only sequences */
				tableList(settings, 0, 'S');
			}
			else if (!optarg)
			{					/* show tables, sequences and indices */
				tableList(settings, 0, 'b');
			}
			else if (strcmp(optarg, "*") == 0)
			{					/* show everything */
				if (tableList(settings, 0, 'b') == 0)
					tableList(settings, 1, 'b');
			}
			else
			{					/* describe the specified table */
				tableDesc(settings, optarg);
			}
			break;
		case 'e':				/* edit */
			{
				do_edit(optarg, query, &status);
				break;
			}
		case 'E':
			{
				FILE	   *fd;
				static char *lastfile;
				struct stat st,
							st2;

				if (optarg)
				{
					if (lastfile)
						free(lastfile);
					lastfile = malloc(strlen(optarg + 1));
					if (!lastfile)
					{
						perror("malloc");
						exit(CMD_TERMINATE);
					}
					strcpy(lastfile, optarg);
				}
				else if (!lastfile)
				{
					fprintf(stderr, "\\r must be followed by a file name initially\n");
					break;
				}
				stat(lastfile, &st);
				editFile(lastfile);
				if ((stat(lastfile, &st2) == -1) || ((fd = fopen(lastfile, "r")) == NULL))
				{
					perror(lastfile);
					break;
				}
				if (st2.st_mtime == st.st_mtime)
				{
					if (!settings->quiet)
						fprintf(stderr, "warning: %s not modified. query not executed\n", lastfile);
					fclose(fd);
					break;
				}
				MainLoop(settings, fd);
				fclose(fd);
				break;
			}
		case 'f':
			{
				char	   *fs = DEFAULT_FIELD_SEP;

				if (optarg)
					fs = optarg;
				if (settings->opt.fieldSep)
					free(settings->opt.fieldSep);
				if (!(settings->opt.fieldSep = strdup(fs)))
				{
					perror("malloc");
					exit(CMD_TERMINATE);
				}
				if (!settings->quiet)
					printf("field separator changed to '%s'\n", settings->opt.fieldSep);
				break;
			}
		case 'g':				/* \g means send query */
			if (!optarg)
				settings->gfname = NULL;
			else if (!(settings->gfname = strdup(optarg)))
			{
				perror("malloc");
				exit(CMD_TERMINATE);
			}
			status = CMD_SEND;
			break;
		case 'h':				/* help */
			{
				do_help(settings, optarg);
				break;
			}
		case 'i':				/* \i is include file */
			{
				FILE	   *fd;

				if (!optarg)
				{
					fprintf(stderr, "\\i must be followed by a file name\n");
					break;
				}
				if ((fd = fopen(optarg, "r")) == NULL)
				{
					fprintf(stderr, "file named %s could not be opened\n", optarg);
					break;
				}
				MainLoop(settings, fd);
				fclose(fd);
				break;
			}
		case 'l':				/* \l is list database */
			listAllDbs(settings);
			break;
		case 'H':
			if (toggle(settings, &settings->opt.html3, "HTML3.0 tabular output"))
				settings->opt.standard = 0;
			break;
		case 'o':
			setFout(settings, optarg);
			break;
		case 'p':
			if (query)
			{
				fputs(query, stdout);
				fputc('\n', stdout);
			}
			break;
		case 'q':				/* \q is quit */
			status = CMD_TERMINATE;
			break;
		case 'r':				/* reset(clear) the buffer */
			query[0] = '\0';
			if (!settings->quiet)
				printf("buffer reset(cleared)\n");
			break;
		case 's':				/* \s is save history to a file */
			if (!optarg)
				optarg = "/dev/tty";
#ifdef HAVE_HISTORY
			if (write_history(optarg) != 0)
				fprintf(stderr, "cannot write history to %s\n", optarg);
#endif
			break;
		case 'm':				/* monitor like type-setting */
			if (toggle(settings, &settings->opt.standard, "standard SQL separaters and padding"))
			{
				settings->opt.html3 = settings->opt.expanded = 0;
				settings->opt.align = settings->opt.header = 1;
				if (settings->opt.fieldSep)
					free(settings->opt.fieldSep);
				settings->opt.fieldSep = strdup("|");
				if (!settings->quiet)
					printf("field separator changed to '%s'\n", settings->opt.fieldSep);
			}
			else
			{
				if (settings->opt.fieldSep)
					free(settings->opt.fieldSep);
				settings->opt.fieldSep = strdup(DEFAULT_FIELD_SEP);
				if (!settings->quiet)
					printf("field separator changed to '%s'\n", settings->opt.fieldSep);
			}
			break;
		case 'z':				/* list table rights (grant/revoke) */
			rightsList(settings);
			break;
		case 't':				/* toggle headers */
			toggle(settings, &settings->opt.header, "output headings and row count");
			break;
		case 'T':				/* define html <table ...> option */
			if (settings->opt.tableOpt)
				free(settings->opt.tableOpt);
			if (!optarg)
				settings->opt.tableOpt = NULL;
			else if (!(settings->opt.tableOpt = strdup(optarg)))
			{
				perror("malloc");
				exit(CMD_TERMINATE);
			}
			break;
		case 'x':
			toggle(settings, &settings->opt.expanded, "expanded table representation");
			break;
		case '!':
			do_shell(optarg);
			break;
		default:
		case '?':				/* \? is help */
			slashUsage(settings);
			break;
	}
	free(cmd);
	return status;
}								/* HandleSlashCmds() */

/* 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.
 */

static int
MainLoop(PsqlSettings *settings, FILE *source)
{
	char	   *line;			/* line of input */
	char	   *xcomment;		/* start of extended comment */
	int			len;			/* length of the line */
	char		query[MAX_QUERY_BUFFER];		/* multi-line query
												 * storage */
	int			successResult = 1;
	int			slashCmdStatus = CMD_SEND;

	/*
	 * slashCmdStatus can be: CMD_UNKNOWN	  - send currently constructed
	 * query to backend (i.e. we got a \g) CMD_SEND		   - send
	 * currently constructed query to backend (i.e. we got a \g)
	 * CMD_SKIP_LINE   - skip processing of this line, continue building
	 * up query CMD_TERMINATE	- terminate processing of this query
	 * entirely CMD_NEWEDIT		- new query supplied by edit
	 */

	bool		querySent = false;
	bool		interactive;
	READ_ROUTINE GetNextLine;
	bool		eof = 0;

	/* We've reached the end of our command input. */
	bool		success;
	bool		in_quote;
	int			paren_level;
	char	   *query_start;

	interactive = ((source == stdin) && !settings->notty);
	if (interactive)
	{
		if (settings->prompt)
			free(settings->prompt);
		settings->prompt =
			malloc(strlen(PQdb(settings->db)) + strlen(PROMPT) + 1);
		if (settings->quiet)
			settings->prompt[0] = '\0';
		else
			sprintf(settings->prompt, "%s%s", PQdb(settings->db), PROMPT);
		if (settings->useReadline)
		{
#ifdef HAVE_HISTORY
			using_history();
#endif
			GetNextLine = gets_readline;
		}
		else
			GetNextLine = gets_noreadline;
	}
	else
		GetNextLine = gets_fromFile;

	query[0] = '\0';
	xcomment = NULL;
	in_quote = false;
	paren_level = 0;
	slashCmdStatus = CMD_UNKNOWN;		/* set default */

	/* main loop to get queries and execute them */
	while (!eof)
	{

		/*
		 * just returned from editing the line? then just copy to the
		 * input buffer
		 */
		if (slashCmdStatus == CMD_NEWEDIT)
		{
			paren_level = 0;
			line = strdup(query);
			query[0] = '\0';

			/*
			 * otherwise, get another line and set interactive prompt if
			 * necessary
			 */
		}
		else
		{
			if (interactive && !settings->quiet)
			{
				if (in_quote)
					settings->prompt[strlen(settings->prompt) - 3] = PROMPT_QUOTE;
				else if (xcomment != NULL)
					settings->prompt[strlen(settings->prompt) - 3] = PROMPT_COMMENT;
				else if (query[0] != '\0' && !querySent)
					settings->prompt[strlen(settings->prompt) - 3] = PROMPT_CONTINUE;
				else
					settings->prompt[strlen(settings->prompt) - 3] = PROMPT_READY;
			}
			line = GetNextLine(settings->prompt, source);
#ifdef HAVE_HISTORY
			if (interactive && settings->useReadline && line != NULL)
				add_history(line);		/* save non-empty lines in history */
#endif
		}

		/*
		 * query - pointer to current command query_start - placeholder
		 * for next command
		 */

		/* not currently inside an extended comment? */
		if (xcomment == NULL)
		{
			query_start = line;

			/* otherwise, continue the extended comment... */
		}
		else
		{
			query_start = line;
			xcomment = line;
		};

		if (line == NULL)
		{						/* No more input.  Time to quit */
			if (!settings->quiet)
				printf("EOF\n");/* Goes on prompt line */
			eof = true;
		}
		else
		{
			/* remove whitespaces on the right, incl. \n's */
			line = rightTrim(line);

			/* echo back if input is from file */
			if (!interactive && !settings->singleStep && !settings->quiet)
				fprintf(stderr, "%s\n", line);

			/* nothing on line after trimming? then ignore */
			if (line[0] == '\0')
			{
				free(line);
				continue;
			}

			len = strlen(line);

			if (settings->singleLineMode)
			{
				SendQuery(&success, settings, line, false, false, 0);
				successResult &= success;
				querySent = true;

			}
			else
			{
				int			i;

				for (i = 0; i < len; i++)
				{
					if (querySent && !isspace(line[i]))
					{
						query[0] = '\0';
						querySent = false;
					}

					/* inside a quote? */
					if (in_quote && (line[i] != '\''))
					{
						continue;

						/* inside an extended comment? */
					}
					else if (xcomment != NULL)
					{
						if (line[i] == '*' && line[i + 1] == '/')
						{
							xcomment = NULL;
							i++;
						};
						continue;

						/* possible backslash command? */
					}
					else if (line[i] == '\\')
					{
						char		hold_char = line[i];

						line[i] = '\0';
						if (query_start[0] != '\0')
						{
							if (query[0] != '\0')
							{
								strcat(query, "\n");
								strcat(query, query_start);
							}
							else
							{
								strcpy(query, query_start);
							};
						}
						line[i] = hold_char;
						query_start = line + i;
						break;	/* handle command */

						/* start an extended comment? */
					}
					else if (line[i] == '/' && line[i + 1] == '*')
					{
						xcomment = line + i;
						i++;
						continue;

						/* single-line comment? truncate line */
					}
					else if ((line[i] == '-' && line[i + 1] == '-') ||
							 (line[i] == '/' && line[i + 1] == '/'))
					{
						/* print comment at top of query */
						if (settings->singleStep)
							fprintf(stdout, "%s\n", line + i);
						line[i] = '\0'; /* remove comment */
						break;

					}
					else if (line[i] == '\'')
					{
						in_quote ^= 1;

						/* semi-colon? then send query now */
					}
					else if (!paren_level && line[i] == ';')
					{
						char		hold_char = line[i + 1];

						line[i + 1] = '\0';
						if (query_start[0] != '\0')
						{
							if (query[0] != '\0')
							{
								strcat(query, "\n");
								strcat(query, query_start);
							}
							else
								strcpy(query, query_start);
						}
						SendQuery(&success, settings, query, false, false, 0);
						successResult &= success;
						line[i + 1] = hold_char;
						query_start = line + i + 1;
						querySent = true;

					}
					else if (line[i] == '(')
					{
						paren_level++;

					}
					else if (paren_level && line[i] == ')')
					{
						paren_level--;
					};
				}
			}

			/* nothing on line after trimming? then ignore */
			if (line[0] == '\0')
			{
				free(line);
				continue;
			}

			slashCmdStatus = CMD_UNKNOWN;
			if (!in_quote && query_start[0] == '\\')
			{
				slashCmdStatus = HandleSlashCmds(settings,
												 query_start,
												 query);
				if (slashCmdStatus == CMD_SKIP_LINE)
				{
					if (query[0] == '\0')
						paren_level = 0;
					free(line);
					continue;
				}
				if (slashCmdStatus == CMD_TERMINATE)
				{
					free(line);
					break;
				}
				free(line);
			}
			else if (strlen(query) + strlen(query_start) > MAX_QUERY_BUFFER)
			{
				fprintf(stderr, "query buffer max length of %d exceeded\n",
						MAX_QUERY_BUFFER);
				fprintf(stderr, "query line ignored\n");
				free(line);
			}
			else
			{
				if (query_start[0] != '\0')
				{

					querySent = false;
					if (query[0] != '\0')
					{
						strcat(query, "\n");
						strcat(query, query_start);
					}
					else
						strcpy(query, query_start);
				}
				free(line);		/* PURIFY */
			}

			/* had a backslash-g? force the query to be sent */
			if (slashCmdStatus == CMD_SEND)
			{
#if FALSE
				if (!querySent)
				{
					SendQuery(&success, settings, query, false, false, 0);
					successResult &= success;
				}
#else
				SendQuery(&success, settings, query, false, false, 0);
				successResult &= success;
#endif
				querySent = true;
			}
		}
	}							/* while */
	return successResult;
}								/* MainLoop() */

int
main(int argc, char **argv)
{
	extern char *optarg;
	extern int	optind;

	char	   *dbname = NULL;
	char	   *host = NULL;
	char	   *port = NULL;
	char	   *qfilename = NULL;
	char		errbuf[ERROR_MSG_LENGTH];

	PsqlSettings settings;

	char	   *singleQuery = NULL;

	bool		listDatabases = 0;
	int			successResult = 1;
	bool		singleSlashCmd = 0;
	int			c;

	memset(&settings, 0, sizeof settings);
	settings.opt.align = 1;
	settings.opt.header = 1;
	settings.queryFout = stdout;
	settings.opt.fieldSep = strdup(DEFAULT_FIELD_SEP);
	settings.opt.pager = 1;
	if (!isatty(0) || !isatty(1))
		settings.quiet = settings.notty = 1;
#ifdef HAVE_LIBREADLINE
	else
		settings.useReadline = 1;
#endif
#ifdef PSQL_ALWAYS_GET_PASSWORDS
	settings.getPassword = 1;
#else
	settings.getPassword = 0;
#endif

	while ((c = getopt(argc, argv, "Aa:c:d:ef:F:lh:Hnso:p:qStT:ux")) != EOF)
	{
		switch (c)
		{
			case 'A':
				settings.opt.align = 0;
				break;
			case 'a':
				fe_setauthsvc(optarg, errbuf);
				break;
			case 'c':
				singleQuery = strdup(optarg);
				if (singleQuery[0] == '\\')
				{
					singleSlashCmd = 1;
				}
				break;
			case 'd':
				dbname = optarg;
				break;
			case 'e':
				settings.echoQuery = 1;
				break;
			case 'f':
				qfilename = optarg;
				break;
			case 'F':
				settings.opt.fieldSep = strdup(optarg);
				break;
			case 'l':
				listDatabases = 1;
				break;
			case 'h':
				host = optarg;
				break;
			case 'H':
				settings.opt.html3 = 1;
				break;
			case 'n':
				settings.useReadline = 0;
				break;
			case 'o':
				setFout(&settings, optarg);
				break;
			case 'p':
				port = optarg;
				break;
			case 'q':
				settings.quiet = 1;
				break;
			case 's':
				settings.singleStep = 1;
				break;
			case 'S':
				settings.singleLineMode = 1;
				break;
			case 't':
				settings.opt.header = 0;
				break;
			case 'T':
				settings.opt.tableOpt = strdup(optarg);
				break;
			case 'u':
				settings.getPassword = 1;
				break;
			case 'x':
				settings.opt.expanded = 1;
				break;
			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 (listDatabases)
		dbname = "template1";

	if (settings.getPassword)
	{
		char		username[9];
		char		password[9];
		char	   *connect_string;

		prompt_for_password(username, password);

		/* now use PQconnectdb so we can pass these options */
		connect_string = make_connect_string(host, port, dbname, username, password);
		settings.db = PQconnectdb(connect_string);
		free(connect_string);
	}
	else
	{
		settings.db = PQsetdb(host, port, NULL, NULL, dbname);
	}

	dbname = PQdb(settings.db);

	if (PQstatus(settings.db) == CONNECTION_BAD)
	{
		fprintf(stderr, "Connection to database '%s' failed.\n", dbname);
		fprintf(stderr, "%s", PQerrorMessage(settings.db));
		PQfinish(settings.db);
		exit(1);
	}
	if (listDatabases)
	{
		exit(listAllDbs(&settings));
	}
	if (!settings.quiet && !singleQuery && !qfilename)
	{
		printf("Welcome to the POSTGRESQL interactive sql monitor:\n");
		printf("  Please read the file COPYRIGHT for copyright terms "
			   "of POSTGRESQL\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 || singleSlashCmd)
	{

		/*
		 * read in a file full of queries instead of reading in queries
		 * interactively
		 */
		char	   *line;

		if (singleSlashCmd)
		{
			/* Not really a query, but "Do what I mean, not what I say." */
			line = singleQuery;
		}
		else
		{
			line = malloc(strlen(qfilename) + 5);
			sprintf(line, "\\i %s", qfilename);
		}
		HandleSlashCmds(&settings, line, "");
		free(line);				/* PURIFY */
	}
	else
	{
		if (singleQuery)
		{
			bool		success;/* The query succeeded at the backend */

			SendQuery(&success, &settings, singleQuery, false, false, 0);
			successResult = success;
		}
		else
			successResult = MainLoop(&settings, stdin);
	}

	PQfinish(settings.db);
	free(settings.opt.fieldSep);/* PURIFY */
	if (settings.prompt)
		free(settings.prompt);	/* PURIFY */

	return !successResult;
}

#define COPYBUFSIZ	8192

static void
handleCopyOut(PGresult *res, bool quiet, FILE *copystream)
{
	bool		copydone;
	char		copybuf[COPYBUFSIZ];
	int			ret;

	copydone = false;			/* Can't be done; haven't started. */

	while (!copydone)
	{
		ret = PQgetline(res->conn, copybuf, COPYBUFSIZ);

		if (copybuf[0] == '\\' &&
			copybuf[1] == '.' &&
			copybuf[2] == '\0')
		{
			copydone = true;	/* don't print this... */
		}
		else
		{
			fputs(copybuf, copystream);
			switch (ret)
			{
				case EOF:
					copydone = true;
					/* FALLTHROUGH */
				case 0:
					fputc('\n', copystream);
					break;
				case 1:
					break;
			}
		}
	}
	fflush(copystream);
	PQendcopy(res->conn);
}



static void
handleCopyIn(PGresult *res, const bool mustprompt, FILE *copystream)
{
	bool		copydone = false;
	bool		firstload;
	bool		linedone;
	char		copybuf[COPYBUFSIZ];
	char	   *s;
	int			buflen;
	int			c;

	if (mustprompt)
	{
		fputs("Enter info followed by a newline\n", stdout);
		fputs("End with a backslash and a "
			  "period on a line by itself.\n", stdout);
	}
	while (!copydone)
	{							/* for each input line ... */
		if (mustprompt)
		{
			fputs(">> ", stdout);
			fflush(stdout);
		}
		firstload = true;
		linedone = false;
		while (!linedone)
		{						/* for each buffer ... */
			s = copybuf;
			buflen = COPYBUFSIZ;
			for (; buflen > 1 &&
				 !(linedone = (c = getc(copystream)) == '\n' || c == EOF);
				 --buflen)
			{
				*s++ = c;
			}
			if (c == EOF)
			{
				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);
}



/*
 * try to open fname and return a FILE *, if it fails, use stdout, instead
 */

static FILE *
setFout(PsqlSettings *ps, char *fname)
{
	if (ps->queryFout && ps->queryFout != stdout)
	{
		if (ps->pipe)
			pclose(ps->queryFout);
		else
			fclose(ps->queryFout);
	}
	if (!fname)
	{
		ps->queryFout = stdout;
		pqsignal(SIGPIPE, SIG_DFL);
	}
	else
	{
		if (*fname == '|')
		{
			pqsignal(SIGPIPE, SIG_IGN);
			ps->queryFout = popen(fname + 1, "w");
			ps->pipe = 1;
		}
		else
		{
			ps->queryFout = fopen(fname, "w");
			pqsignal(SIGPIPE, SIG_DFL);
			ps->pipe = 0;
		}
		if (!ps->queryFout)
		{
			perror(fname);
			ps->queryFout = stdout;
		}
	}
	return ps->queryFout;
}

static void
prompt_for_password(char *username, char *password)
{
	int			length;

#ifdef HAVE_TERMIOS_H
	struct termios t_orig,
				t;

#endif

	printf("Username: ");
	fgets(username, 9, stdin);
	length = strlen(username);
	/* skip rest of the line */
	if (length > 0 && username[length - 1] != '\n')
	{
		static char buf[512];

		do
		{
			fgets(buf, 512, stdin);
		} while (buf[strlen(buf) - 1] != '\n');
	}
	if (length > 0 && username[length - 1] == '\n')
		username[length - 1] = '\0';

	printf("Password: ");
#ifdef HAVE_TERMIOS_H
	tcgetattr(0, &t);
	t_orig = t;
	t.c_lflag &= ~ECHO;
	tcsetattr(0, TCSADRAIN, &t);
#endif
	fgets(password, 9, stdin);
#ifdef HAVE_TERMIOS_H
	tcsetattr(0, TCSADRAIN, &t_orig);
#endif

	length = strlen(password);
	/* skip rest of the line */
	if (length > 0 && password[length - 1] != '\n')
	{
		static char buf[512];

		do
		{
			fgets(buf, 512, stdin);
		} while (buf[strlen(buf) - 1] != '\n');
	}
	if (length > 0 && password[length - 1] == '\n')
		password[length - 1] = '\0';

	printf("\n\n");
}

static char *
make_connect_string(char *host, char *port, char *dbname,
					char *username, char *password)
{
	int			connect_string_len = 0;
	char	   *connect_string;

	if (host)
		connect_string_len += 6 + strlen(host); /* 6 == "host=" + " " */
	if (username)
		connect_string_len += 6 + strlen(username);		/* 6 == "user=" + " " */
	if (password)
		connect_string_len += 10 + strlen(password);	/* 10 == "password=" + "
														 * " */
	if (port)
		connect_string_len += 6 + strlen(port); /* 6 == "port=" + " " */
	if (dbname)
		connect_string_len += 8 + strlen(dbname);		/* 8 == "dbname=" + " " */
	connect_string_len += 18;	/* "authtype=password" + null */

	connect_string = (char *) malloc(connect_string_len);
	if (!connect_string)
	{
		return 0;
	}
	connect_string[0] = '\0';
	if (host)
	{
		strcat(connect_string, "host=");
		strcat(connect_string, host);
		strcat(connect_string, " ");
	}
	if (username)
	{
		strcat(connect_string, "user=");
		strcat(connect_string, username);
		strcat(connect_string, " ");
	}
	if (password)
	{
		strcat(connect_string, "password=");
		strcat(connect_string, password);
		strcat(connect_string, " ");
	}
	if (port)
	{
		strcat(connect_string, "port=");
		strcat(connect_string, port);
		strcat(connect_string, " ");
	}
	if (dbname)
	{
		strcat(connect_string, "dbname=");
		strcat(connect_string, dbname);
		strcat(connect_string, " ");
	}
	strcat(connect_string, "authtype=password");

	return connect_string;
}
