psql.c 78.4 KB
Newer Older
Bruce Momjian's avatar
Bruce Momjian committed
1
/*-------------------------------------------------------------------------
2
 *
3
 * psql.c
4
 *	  an interactive front-end to postgreSQL
5 6 7 8 9
 *
 * Copyright (c) 1996, Regents of the University of California
 *
 *
 * IDENTIFICATION
10
 *	  $Header: /cvsroot/pgsql/src/bin/psql/Attic/psql.c,v 1.175 1999/04/15 02:24:41 tgl Exp $
11 12 13 14
 *
 *-------------------------------------------------------------------------
 */
#include <stdio.h>
15
#include <stdlib.h>
16 17
#include <string.h>
#include <signal.h>
Bruce Momjian's avatar
Bruce Momjian committed
18
#include <errno.h>
19
#include <sys/types.h>
20 21 22 23 24
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <io.h>
#else
25
#include <sys/param.h>			/* for MAXPATHLEN */
26
#include <sys/ioctl.h>
27
#include <unistd.h>
28 29
#endif
#include <sys/stat.h>
30
#include <fcntl.h>
31
#include <ctype.h>
Bruce Momjian's avatar
Bruce Momjian committed
32
#include "postgres.h"
33
#include "libpq-fe.h"
34
#include "pqsignal.h"
35 36
#include "stringutils.h"
#include "psqlHelp.h"
37
#ifndef HAVE_STRDUP
38 39
#include "strdup.h"
#endif
40 41 42
#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif
43 44 45
#ifdef __CYGWIN32__
#include <getopt.h>
#endif
46

47
#ifdef HAVE_LIBREADLINE
48 49
#ifdef HAVE_READLINE_H
#include <readline.h>
50
#define USE_READLINE 1
51
#if defined(HAVE_HISTORY_H)
52
#include <history.h>
53
#define USE_HISTORY 1
54 55
#endif
#else
56
#if defined(HAVE_READLINE_READLINE_H)
57
#include <readline/readline.h>
58
#define USE_READLINE 1
59 60
#if defined(HAVE_READLINE_HISTORY_H)
#include <readline/history.h>
61 62
#define USE_HISTORY 1
#endif
63 64
#endif
#endif
65 66 67
#if defined(HAVE_HISTORY) && !defined(USE_HISTORY)
#define USE_HISTORY 1
#endif
68 69
#endif

70 71 72 73 74 75

#ifdef WIN32
#define popen(x,y) _popen(x,y)
#define pclose(x) _pclose(x)
#define open(x,y,z) _open(x,y,z)
#define strcasecmp(x,y) stricmp(x,y)
76
#define pqsignal(x,y)
77 78 79 80 81
#define MAXPATHLEN MAX_PATH
#define R_OK 0

/* getopt is not in the standard includes on Win32 */
extern char *optarg;
82 83 84 85 86 87
extern int	optind,
			opterr,
			optopt;
int			getopt(int, char *const[], const char *);
char	   *__progname = "psql";

88 89
#endif

90 91
#ifdef MULTIBYTE
/* flag to indicate if PGCLIENTENCODING has been set by a user */
92
static	char	   *has_client_encoding = 0;
93 94
#endif

95 96 97
/* 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.
 */
98 99
#define PROMPT "=> "

100
#define PROMPT_READY	'='
101
#define PROMPT_CONTINUE '-'
102
#define PROMPT_COMMENT	'*'
Bruce Momjian's avatar
Bruce Momjian committed
103 104
#define PROMPT_SINGLEQUOTE	'\''
#define PROMPT_DOUBLEQUOTE	'"'
105 106

/* Backslash command handling:
107 108 109 110
 *	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
111
 */
112 113
#define CMD_UNKNOWN		-1
#define CMD_SEND		0
114 115
#define CMD_SKIP_LINE	1
#define CMD_TERMINATE	2
116
#define CMD_NEWEDIT		3
117

118 119
#define MAX_QUERY_BUFFER 20000

120
#define COPYBUFSIZ	8192
121

122
#define DEFAULT_FIELD_SEP "|"
123
#define DEFAULT_EDITOR	"vi"
124 125
#define DEFAULT_SHELL  "/bin/sh"

126 127
typedef struct _psqlSettings
{
128 129 130 131 132 133 134 135
	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 */
136
	bool		echoAllQueries;		/* echo all queries before sending it*/
137 138 139 140 141
	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
142
								 * password */
143
} PsqlSettings;
144

145 146 147 148 149 150 151 152 153 154 155
/*
 * cur_cmd_source and cur_cmd_interactive are the top of a stack of
 * source files (one stack level per recursive invocation of MainLoop).
 * It's kinda grotty to make these global variables, but the alternative
 * of passing them around through many function parameter lists seems
 * worse.
 */
static FILE * cur_cmd_source = stdin; /* current source of command input */
static bool cur_cmd_interactive = false; /* is it an interactive source? */


156 157
#ifdef TIOCGWINSZ
struct winsize screen_size;
158

159 160 161 162 163 164
#else
struct winsize
{
	int			ws_row;
	int			ws_col;
}			screen_size;
165

166 167
#endif

168
/* declarations for functions in this file */
169 170
static void usage(char *progname);
static void slashUsage();
171
static bool handleCopyOut(PGconn *conn, FILE *copystream);
Bruce Momjian's avatar
Bruce Momjian committed
172
static bool handleCopyIn(PGconn *conn, const bool mustprompt,
173
			 FILE *copystream);
174
static int tableList(PsqlSettings *pset, bool deep_tablelist,
175
		  char info_type, bool system_tables);
176
static int	tableDesc(PsqlSettings *pset, char *table, FILE *fout);
177
static int	objectDescription(PsqlSettings *pset, char *object);
178
static int	rightsList(PsqlSettings *pset);
179
static void emitNtimes (FILE *fout, const char *str, int N);
180
static void prompt_for_password(char *username, char *password);
181

182 183 184
static char *gets_noreadline(char *prompt, FILE *source);
static char *gets_readline(char *prompt, FILE *source);
static char *gets_fromFile(char *prompt, FILE *source);
185
static int	listAllDbs(PsqlSettings *pset);
186 187 188
static bool SendQuery(PsqlSettings *pset, const char *query,
					  FILE *copy_in_stream, FILE *copy_out_stream);
static int	HandleSlashCmds(PsqlSettings *pset, char *line, char *query);
189
static int	MainLoop(PsqlSettings *pset, char *query, FILE *source);
190
static FILE *setFout(PsqlSettings *pset, char *fname);
191

192 193
static char *selectVersion(PsqlSettings *pset);

194
/*
195
 * usage print out usage for command line arguments
196 197
 */

198
static void
199
usage(char *progname)
200
{
201 202 203 204 205 206
	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");
207
	fprintf(stderr, "\t -E                      echo all queries sent to the backend\n");
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
	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);
224 225 226
}

/*
227
 * slashUsage print out usage for the backslash commands
228 229
 */

230
static char *
231
on(bool f)
232
{
233
	return f ? "on" : "off";
234 235
}

236
static void
237
slashUsage(PsqlSettings *pset)
238
{
239 240 241
	int			usePipe = 0;
	char	   *pagerenv;
	FILE	   *fout;
242

243 244 245 246 247 248 249 250 251 252 253 254 255
#ifdef TIOCGWINSZ
	if (pset->notty == 0 &&
		(ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) == -1 ||
		 screen_size.ws_col == 0 ||
		 screen_size.ws_row == 0))
	{
#endif
		screen_size.ws_row = 24;
		screen_size.ws_col = 80;
#ifdef TIOCGWINSZ
	}
#endif

256
	if (pset->notty == 0 &&
257 258
		(pagerenv = getenv("PAGER")) &&
		(pagerenv[0] != '\0') &&
259
		screen_size.ws_row <= 35 &&
260 261 262 263 264 265 266 267
		(fout = popen(pagerenv, "w")))
	{
		usePipe = 1;
		pqsignal(SIGPIPE, SIG_IGN);
	}
	else
		fout = stdout;

268
	/* if you add/remove a line here, change the row test above */
269
	fprintf(fout, " \\?           -- help\n");
270
	fprintf(fout, " \\a           -- toggle field-alignment (currently %s)\n", on(pset->opt.align));
271 272
	fprintf(fout, " \\C [<captn>] -- set html3 caption (currently '%s')\n", pset->opt.caption ? pset->opt.caption : "");
	fprintf(fout, " \\connect <dbname|-> <user> -- connect to new database (currently '%s')\n", PQdb(pset->db));
273
	fprintf(fout, " \\copy table {from | to} <fname>\n");
274 275
	fprintf(fout, " \\d [<table>] -- list tables and indices, columns in <table>, or * for all\n");
	fprintf(fout, " \\da          -- list aggregates\n");
Bruce Momjian's avatar
Bruce Momjian committed
276
	fprintf(fout, " \\dd [<object>]- list comment for table, field, type, function, or operator.\n");
277
	fprintf(fout, " \\df          -- list functions\n");
278 279 280
	fprintf(fout, " \\di          -- list only indices\n");
	fprintf(fout, " \\do          -- list operators\n");
	fprintf(fout, " \\ds          -- list only sequences\n");
281
	fprintf(fout, " \\dS          -- list system tables and indexes\n");
282 283
	fprintf(fout, " \\dt          -- list only tables\n");
	fprintf(fout, " \\dT          -- list types\n");
284 285
	fprintf(fout, " \\e [<fname>] -- edit the current query buffer or <fname>\n");
	fprintf(fout, " \\E [<fname>] -- edit the current query buffer or <fname>, and execute\n");
286
	fprintf(fout, " \\f [<sep>]   -- change field separater (currently '%s')\n", pset->opt.fieldSep);
287 288
	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");
289
	fprintf(fout, " \\H           -- toggle html3 output (currently %s)\n", on(pset->opt.html3));
290 291
	fprintf(fout, " \\i <fname>   -- read and execute queries from filename\n");
	fprintf(fout, " \\l           -- list all databases\n");
292
	fprintf(fout, " \\m           -- toggle monitor-like table display (currently %s)\n", on(pset->opt.standard));
293 294 295 296 297
	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");
298 299 300
	fprintf(fout, " \\t           -- toggle table headings and row count (currently %s)\n", on(pset->opt.header));
	fprintf(fout, " \\T [<html>]  -- set html3.0 <table ...> options (currently '%s')\n", pset->opt.tableOpt ? pset->opt.tableOpt : "");
	fprintf(fout, " \\x           -- toggle expanded output (currently %s)\n", on(pset->opt.expanded));
301
	fprintf(fout, " \\w <fname>   -- output current buffer to a file\n");
302 303 304 305 306 307 308 309
	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);
	}
310 311
}

312
static PGresult *
313
PSQLexec(PsqlSettings *pset, char *query)
314
{
315
	PGresult   *res;
316

317 318 319 320 321 322 323
        if (pset->echoAllQueries)
        {
                fprintf(stderr, "QUERY: %s\n", query);
                fprintf(stderr, "\n");
                fflush(stderr);
        }

324
	res = PQexec(pset->db, query);
325
	if (!res)
326
		fputs(PQerrorMessage(pset->db), stderr);
327 328 329 330 331
	else
	{
		if (PQresultStatus(res) == PGRES_COMMAND_OK ||
			PQresultStatus(res) == PGRES_TUPLES_OK)
			return res;
332 333
		if (!pset->quiet)
			fputs(PQerrorMessage(pset->db), stderr);
334 335 336
		PQclear(res);
	}
	return NULL;
337
}
338

Bruce Momjian's avatar
Bruce Momjian committed
339 340 341 342 343
/*
 * Code to support command cancellation.
 * If interactive, we enable a SIGINT signal catcher that sends
 * a cancel request to the backend.
 * Note that sending the cancel directly from the signal handler
344
 * is safe only because PQrequestCancel is carefully written to
345
 * make it so.	We have to be very careful what else we do in the
346 347
 * signal handler.
 * Writing on stderr is potentially dangerous, if the signal interrupted
348
 * some stdio operation on stderr.	On Unix we can avoid trouble by using
349 350
 * write() instead; on Windows that's probably not workable, but we can
 * at least avoid trusting printf by using the more primitive fputs.
Bruce Momjian's avatar
Bruce Momjian committed
351 352
 */

353
static PGconn *cancelConn = NULL;		/* connection to try cancel on */
Bruce Momjian's avatar
Bruce Momjian committed
354

355
static void
356
safe_write_stderr(const char *s)
357 358 359 360 361 362 363 364
{
#ifdef WIN32
	fputs(s, stderr);
#else
	write(fileno(stderr), s, strlen(s));
#endif
}

Bruce Momjian's avatar
Bruce Momjian committed
365
static void
366
handle_sigint(SIGNAL_ARGS)
Bruce Momjian's avatar
Bruce Momjian committed
367 368 369 370 371
{
	if (cancelConn == NULL)
		exit(1);				/* accept signal if no connection */
	/* Try to send cancel request */
	if (PQrequestCancel(cancelConn))
372
		safe_write_stderr("\nCANCEL request sent\n");
Bruce Momjian's avatar
Bruce Momjian committed
373 374
	else
	{
375 376
		safe_write_stderr("\nCannot send cancel request:\n");
		safe_write_stderr(PQerrorMessage(cancelConn));
Bruce Momjian's avatar
Bruce Momjian committed
377 378 379 380
	}
}


381 382
/*
 * listAllDbs
383
 *
384
 * list all the databases in the system returns 0 if all went well
385 386
 *
 *
387
 */
388

389
static int
390
listAllDbs(PsqlSettings *pset)
391
{
392 393
	PGresult   *results;
	char	   *query = "select * from pg_database;";
394

395
	if (!(results = PSQLexec(pset, query)))
396 397 398
		return 1;
	else
	{
399
		PQprint(pset->queryFout,
400
				results,
401
				&pset->opt);
402 403 404
		PQclear(results);
		return 0;
	}
405 406 407
}

/*
408
 * List The Database Tables returns 0 if all went well
409
 *
410
 */
411
static int
412
tableList(PsqlSettings *pset, bool deep_tablelist, char info_type,
413
		  bool system_tables)
414
{
415
	char		listbuf[512];
416 417 418 419 420
	int			nColumns;
	int			i;
	char	   *rk;
	char	   *rr;
	PGresult   *res;
421 422 423
	int			usePipe = 0;
	char	   *pagerenv;
	FILE	   *fout;
424

425 426 427 428 429 430 431 432 433 434 435 436
#ifdef TIOCGWINSZ
	if (pset->notty == 0 &&
		(ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) == -1 ||
		 screen_size.ws_col == 0 ||
		 screen_size.ws_row == 0))
	{
#endif
		screen_size.ws_row = 24;
		screen_size.ws_col = 80;
#ifdef TIOCGWINSZ
	}
#endif
437

438
	listbuf[0] = '\0';
439
	strcat(listbuf, "SELECT usename, relname, relkind, relhasrules ");
440
	strcat(listbuf, "FROM pg_class, pg_user ");
441 442
	switch (info_type)
	{
443 444 445 446 447 448 449 450 451 452 453 454 455
		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;
456
	}
457
	if (!system_tables)
458
		strcat(listbuf, "  and relname !~ '^pg_'");
459
	else
460
		strcat(listbuf, "  and relname ~ '^pg_'");
461 462
	strcat(listbuf, " and usesysid = relowner");
	strcat(listbuf, " ORDER BY relname ");
463
	if (!(res = PSQLexec(pset, listbuf)))
464 465 466 467 468
		return -1;
	/* first, print out the attribute names */
	nColumns = PQntuples(res);
	if (nColumns > 0)
	{
469 470 471 472 473 474 475 476 477 478 479 480 481
		if (pset->notty == 0 &&
			(pagerenv = getenv("PAGER")) &&
			pagerenv[0] != '\0' &&
			(deep_tablelist ||
			 screen_size.ws_row <= nColumns + 7) &&
			(fout = popen(pagerenv, "w")))
		{
			usePipe = 1;
			pqsignal(SIGPIPE, SIG_IGN);
		}
		else
			fout = stdout;

482 483 484
		if (deep_tablelist)
		{
			/* describe everything here */
485
			char	  **table;
486 487 488 489 490 491

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

			/* load table table */
492 493 494
			/* Put double quotes around the table name to allow for mixed-case
			 * and whitespaces in the table name. - BGA 1998-11-14
			 */
495 496
			for (i = 0; i < nColumns; i++)
			{
497
				table[i] = (char *) malloc(PQgetlength(res, i, 1) * sizeof(char) + 3);
498 499
				if (table[i] == NULL)
					perror("malloc");
500 501 502
				strcpy(table[i], "\"");
				strcat(table[i], PQgetvalue(res, i, 1));
				strcat(table[i], "\"");
503 504
			}

505
			PQclear(res);
506
			for (i = 0; i < nColumns; i++)
507
				tableDesc(pset, table[i], fout);
508 509
			free(table);
		}
510
		else
511 512 513
		{
			/* Display the information */

514
			fprintf(fout, "Database    = %s\n", PQdb(pset->db));
515 516 517
			fprintf(fout, " +------------------+----------------------------------+----------+\n");
			fprintf(fout, " |  Owner           |             Relation             |   Type   |\n");
			fprintf(fout, " +------------------+----------------------------------+----------+\n");
518 519 520 521

			/* next, print out the instances */
			for (i = 0; i < PQntuples(res); i++)
			{
522 523
				fprintf(fout, " | %-16.16s", PQgetvalue(res, i, 0));
				fprintf(fout, " | %-32.32s | ", PQgetvalue(res, i, 1));
524 525 526
				rk = PQgetvalue(res, i, 2);
				rr = PQgetvalue(res, i, 3);
				if (strcmp(rk, "r") == 0)
527
					fprintf(fout, "%-8.8s |", (rr[0] == 't') ? "view?" : "table");
528
				else if (strcmp(rk, "i") == 0)
529
					fprintf(fout, "%-8.8s |", "index");
530
				else
531 532
					fprintf(fout, "%-8.8s |", "sequence");
				fprintf(fout, "\n");
533
			}
534
			fprintf(fout, " +------------------+----------------------------------+----------+\n");
535
                        fprintf(fout, "\n") ;
536 537
			PQclear(res);
		}
538 539 540 541 542
		if (usePipe)
		{
			pclose(fout);
			pqsignal(SIGPIPE, SIG_DFL);
		}
543
		return 0;
544 545 546 547

	}
	else
	{
548
		PQclear(res);
549 550
		switch (info_type)
		{
551 552 553 554 555 556 557 558 559 560 561 562 563
			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;
564
		}
565
		return -1;
566
	}
567 568
}

569 570
/*
 * List Tables Grant/Revoke Permissions returns 0 if all went well
571
 *
572
 */
573
static int
574
rightsList(PsqlSettings *pset)
575
{
576
	char		listbuf[512];
577 578
	int			nColumns;
	int			i;
579 580
	int			maxCol1Len;
	int			maxCol2Len;
Bruce Momjian's avatar
Bruce Momjian committed
581 582 583
	int			usePipe = 0;
	char	   *pagerenv;
	FILE	   *fout;
584
	PGresult   *res;
585

Bruce Momjian's avatar
Bruce Momjian committed
586 587 588 589 590 591 592 593 594 595 596 597 598
#ifdef TIOCGWINSZ
	if (pset->notty == 0 &&
		(ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) == -1 ||
		 screen_size.ws_col == 0 ||
		 screen_size.ws_row == 0))
	{
#endif
		screen_size.ws_row = 24;
		screen_size.ws_col = 80;
#ifdef TIOCGWINSZ
	}
#endif

599
	listbuf[0] = '\0';
600
	strcat(listbuf, "SELECT relname, relacl ");
601
	strcat(listbuf, "FROM pg_class, pg_user ");
Bruce Momjian's avatar
Bruce Momjian committed
602
	strcat(listbuf, "WHERE ( relkind = 'r' OR relkind = 'i' OR relkind = 'S') ");
603
	strcat(listbuf, "  and relname !~ '^pg_'");
604 605
	strcat(listbuf, "  and usesysid = relowner");
	strcat(listbuf, "  ORDER BY relname ");
606
	if (!(res = PSQLexec(pset, listbuf)))
607
		return -1;
Bruce Momjian's avatar
Bruce Momjian committed
608
	/* first, print out the attribute names */
609 610 611
	nColumns = PQntuples(res);
	if (nColumns > 0)
	{
Bruce Momjian's avatar
Bruce Momjian committed
612 613 614 615 616 617 618 619 620 621 622 623
		if (pset->notty == 0 &&
			(pagerenv = getenv("PAGER")) &&
			pagerenv[0] != '\0' &&
			screen_size.ws_row <= nColumns + 7 &&
			(fout = popen(pagerenv, "w")))
		{
			usePipe = 1;
			pqsignal(SIGPIPE, SIG_IGN);
		}
		else
			fout = stdout;

624 625 626 627 628 629 630 631 632 633 634 635 636
		/* choose column widths */
		maxCol1Len = strlen("Relation");
        maxCol2Len = strlen("Grant/Revoke Permissions");
		for (i = 0; i < PQntuples(res); i++)
		{
			int l = strlen(PQgetvalue(res, i, 0));
			if (l > maxCol1Len)
				maxCol1Len = l;
			l = strlen(PQgetvalue(res, i, 1));
			if (l > maxCol2Len)
				maxCol2Len = l;
		}

637 638
		/* Display the information */

639
		fprintf(fout, "Database    = %s\n", PQdb(pset->db));
640 641 642 643 644 645 646 647 648 649 650 651 652
		fprintf(fout, " +");
		emitNtimes(fout, "-", maxCol1Len+2);
		fprintf(fout, "+");
		emitNtimes(fout, "-", maxCol2Len+2);
		fprintf(fout, "+\n");
		fprintf(fout, " | %-*s | %-*s |\n",
				maxCol1Len, "Relation",
				maxCol2Len, "Grant/Revoke Permissions");
		fprintf(fout, " +");
		emitNtimes(fout, "-", maxCol1Len+2);
		fprintf(fout, "+");
		emitNtimes(fout, "-", maxCol2Len+2);
		fprintf(fout, "+\n");
653 654 655 656

		/* next, print out the instances */
		for (i = 0; i < PQntuples(res); i++)
		{
657 658 659
			fprintf(fout, " | %-*s | %-*s |\n",
					maxCol1Len, PQgetvalue(res, i, 0),
					maxCol2Len, PQgetvalue(res, i, 1));
660
		}
661 662 663 664 665 666 667

		fprintf(fout, " +");
		emitNtimes(fout, "-", maxCol1Len+2);
		fprintf(fout, "+");
		emitNtimes(fout, "-", maxCol2Len+2);
		fprintf(fout, "+\n");

668
		PQclear(res);
Bruce Momjian's avatar
Bruce Momjian committed
669 670 671 672 673
		if (usePipe)
		{
			pclose(fout);
			pqsignal(SIGPIPE, SIG_DFL);
		}
674
		return 0;
675 676 677
	}
	else
	{
678
		PQclear(res);
679
		fprintf(stderr, "Couldn't find any tables!\n");
680
		return -1;
681
	}
682
}
683

684 685 686 687 688 689 690 691
static void emitNtimes (FILE *fout, const char *str, int N)
{
	int i;
	for (i = 0; i < N; i++) {
		fputs(str, fout);
	}
}

692
/*
693
 * Describe a table
694
 *
695
 * Describe the columns in a database table. returns 0 if all went well
696 697
 *
 *
698
 */
699
static int
700
tableDesc(PsqlSettings *pset, char *table, FILE *fout)
701
{
702
	char		descbuf[512];
703 704
	int			nColumns,
				nIndices;
705
	char	   *rtype;
706 707
	char	   *rnotnull;
	char	   *rhasdef;
708
	int			i;
709 710 711 712
	int			attlen,
				atttypmod;
	PGresult   *res,
			   *res2;
713 714
	int			usePipe = 0;
	char	   *pagerenv;
715

716
#ifdef TIOCGWINSZ
717
	if (pset->notty == 0 &&
718 719 720 721 722 723 724 725 726 727
		(ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) == -1 ||
		 screen_size.ws_col == 0 ||
		 screen_size.ws_row == 0))
	{
#endif
		screen_size.ws_row = 24;
		screen_size.ws_col = 80;
#ifdef TIOCGWINSZ
	}
#endif
728

729 730
	/* Build the query */

731 732 733 734
	/*
	 * if the table name is surrounded by double-quotes, then don't
	 * convert case
	 */
735 736 737
	if (*table == '"')
	{
		table++;
738 739
		if (*(table + strlen(table) - 1) == '"')
			*(table + strlen(table) - 1) = '\0';
740 741 742
	}
	else
	{
743
#ifdef MULTIBYTE
744
		for (i = 0; table[i]; i += PQmblen(table + i))
745
#else
746
		for (i = 0; table[i]; i++)
747
#endif
748 749
			if (isascii((unsigned char) table[i]) &&
				isupper(table[i]))
750 751
				table[i] = tolower(table[i]);
	}
752 753

	descbuf[0] = '\0';
754 755
	strcat(descbuf, "SELECT a.attnum, a.attname, t.typname, a.attlen, ");
	strcat(descbuf, "a.atttypmod, a.attnotnull, a.atthasdef ");
756 757
	strcat(descbuf, "FROM pg_class c, pg_attribute a, pg_type t ");
	strcat(descbuf, "WHERE c.relname = '");
758 759 760 761 762 763
	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 ");
764
	if (!(res = PSQLexec(pset, descbuf)))
765 766 767 768 769
		return -1;
	/* first, print out the attribute names */
	nColumns = PQntuples(res);
	if (nColumns > 0)
	{
770 771 772 773 774 775 776 777 778 779 780 781 782 783
		if (fout == NULL)
		{
			if (pset->notty == 0 &&
				(pagerenv = getenv("PAGER")) &&
				pagerenv[0] != '\0' &&
				screen_size.ws_row <= nColumns + 7 &&
				(fout = popen(pagerenv, "w")))
			{
				usePipe = 1;
				pqsignal(SIGPIPE, SIG_IGN);
			}
			else
				fout = stdout;
		}
784

785 786 787 788 789 790 791 792 793 794 795 796 797 798
		/*
		 * Extract the veiw name and veiw definition from pg_views.
                 * -Ryan 2/14/99
		 */

		descbuf[0] = '\0';
		strcat(descbuf, "SELECT viewname, definition ");
		strcat(descbuf, "FROM pg_views ");
		strcat(descbuf, "WHERE viewname like '");
		strcat(descbuf, table);
		strcat(descbuf, "' ");
		if(!(res2 = PSQLexec(pset, descbuf)))
		  return -1;

799
		/*
800
		 * Display the information
801
		 */
802 803 804
		if(PQntuples(res2)) {
		  /*
		   * display the query.
805
o		   * -Ryan 2/14/99
806
		   */
807
		  fprintf(fout, "View    = %s\n", table);
808 809
		  fprintf(fout, "Query   = %s\n", PQgetvalue(res2, 0, 1));
		} else {
810
		  fprintf(fout, "Table    = %s\n", table);
811 812
		}
		PQclear(res2);
813

814 815 816
		fprintf(fout, "+----------------------------------+----------------------------------+-------+\n");
		fprintf(fout, "|              Field               |              Type                | Length|\n");
		fprintf(fout, "+----------------------------------+----------------------------------+-------+\n");
817 818 819 820

		/* next, print out the instances */
		for (i = 0; i < PQntuples(res); i++)
		{
821 822 823
			char		type_str[33];

			fprintf(fout, "| %-32.32s | ", PQgetvalue(res, i, 1));
824
			rtype = PQgetvalue(res, i, 2);
825 826 827 828
			attlen = atoi(PQgetvalue(res, i, 3));
			atttypmod = atoi(PQgetvalue(res, i, 4));
			rnotnull = PQgetvalue(res, i, 5);
			rhasdef = PQgetvalue(res, i, 6);
829 830 831 832 833 834 835

			strcpy(type_str, rtype);
			if (strcmp(rtype, "bpchar") == 0)
				strcpy(type_str, "char()");
			else if (strcmp(rtype, "varchar") == 0)
				strcpy(type_str, "varchar()");
			else if (rtype[0] == '_')
836
			{
837 838 839
				strcpy(type_str, rtype + 1);
				strncat(type_str, "[]", 32 - strlen(type_str));
				type_str[32] = '\0';
840
			}
841

842
			if (rnotnull[0] == 't')
843
			{
844
				strncat(type_str, " not null", 32 - strlen(type_str));
845
				type_str[32] = '\0';
846
			}
847
			if (rhasdef[0] == 't')
848
			{
849 850 851 852 853 854 855 856 857 858 859
				descbuf[0] = '\0';
				strcat(descbuf, "SELECT d.adsrc ");
				strcat(descbuf, "FROM pg_attrdef d, pg_class c ");
				strcat(descbuf, "WHERE c.relname = '");
				strcat(descbuf, table);
				strcat(descbuf, "'");
				strcat(descbuf, "    and c.oid = d.adrelid ");
				strcat(descbuf, "    and d.adnum = ");
				strcat(descbuf, PQgetvalue(res, i, 0));
				if (!(res2 = PSQLexec(pset, descbuf)))
					return -1;
860 861
				strcat(type_str, " default ");
				strncat(type_str, PQgetvalue(res2, 0, 0), 32 - strlen(type_str));
862
				type_str[32] = '\0';
863
			}
864
			fprintf(fout, "%-32.32s |", type_str);
865 866

			if (strcmp(rtype, "text") == 0)
867
				fprintf(fout, "%6s |", "var");
868 869
			else if (strcmp(rtype, "bpchar") == 0 ||
					 strcmp(rtype, "varchar") == 0)
870
				fprintf(fout, "%6i |", atttypmod != -1 ? atttypmod - VARHDRSZ : 0);
871 872
			else
			{
873
				if (attlen > 0)
874
					fprintf(fout, "%6i |", attlen);
875
				else
876
					fprintf(fout, "%6s |", "var");
877
			}
878
			fprintf(fout, "\n");
879
		}
880
		fprintf(fout, "+----------------------------------+----------------------------------+-------+\n");
881
		PQclear(res);
882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897

		/* display defined indexes for this table */
		descbuf[0] = '\0';
		strcat(descbuf, "SELECT c2.relname ");
		strcat(descbuf, "FROM pg_class c, pg_class c2, pg_index i ");
		strcat(descbuf, "WHERE c.relname = '");
		strcat(descbuf, table);
		strcat(descbuf, "'");
		strcat(descbuf, "    and c.oid = i.indrelid ");
		strcat(descbuf, "    and i.indexrelid = c2.oid ");
		strcat(descbuf, "  ORDER BY c2.relname ");
		if ((res = PSQLexec(pset, descbuf)))
		{
			nIndices = PQntuples(res);
			if (nIndices > 0)
			{
898

899 900 901 902 903
				/*
				 * Display the information
				 */

				if (nIndices == 1)
Bruce Momjian's avatar
Bruce Momjian committed
904
					fprintf(fout, "Index:    ");
905
				else
Bruce Momjian's avatar
Bruce Momjian committed
906
					fprintf(fout, "Indices:  ");
907

908 909
				/* next, print out the instances */
				for (i = 0; i < PQntuples(res); i++)
910 911 912 913
					if (i == 0)
						fprintf(fout, "%s\n", PQgetvalue(res, i, 0));
					else
						fprintf(fout, "          %s\n", PQgetvalue(res, i, 0));
914
				fprintf(fout, "\n");
915 916 917
			}
			PQclear(res);
		}
918 919 920 921 922
		if (usePipe)
		{
			pclose(fout);
			pqsignal(SIGPIPE, SIG_DFL);
		}
923
		return 0;
924 925 926
	}
	else
	{
927
		PQclear(res);
928
		fprintf(stderr, "Couldn't find table %s!\n", table);
929
		return -1;
930
	}
931 932
}

933 934 935 936 937 938 939
/*
 * Get object comments
 *
 * Describe the columns in a database table. returns 0 if all went well
 *
 *
 */
940
static int
941
objectDescription(PsqlSettings *pset, char *object)
942
{
943
	char		descbuf[512];
944
	PGresult   *res;
945 946
	int			i;
	bool		success;
947

948 949 950 951
	/* Build the query */

	while (isspace(*object))
		object++;
952 953 954 955 956

	/*
	 * if the object name is surrounded by double-quotes, then don't
	 * convert case
	 */
957 958 959
	if (*object == '"')
	{
		object++;
960 961
		if (*(object + strlen(object) - 1) == '"')
			*(object + strlen(object) - 1) = '\0';
962 963 964
	}
	else
	{
965
#ifdef MULTIBYTE
966
		for (i = 0; object[i]; i += PQmblen(object + i))
967
#else
968
		for (i = 0; object[i]; i++)
969
#endif
970 971 972 973 974
			if (isupper(object[i]))
				object[i] = tolower(object[i]);
	}

	descbuf[0] = '\0';
975
	if (strchr(object, '.') != NULL)
976
	{
977 978
		char		table[NAMEDATALEN],
					column[NAMEDATALEN];
979

980 981 982 983
		StrNCpy(table, object,
				((strchr(object, '.') - object + 1) < NAMEDATALEN) ?
				(strchr(object, '.') - object + 1) : NAMEDATALEN);
		StrNCpy(column, strchr(object, '.') + 1, NAMEDATALEN);
984 985 986 987 988 989 990 991 992
		strcat(descbuf, "SELECT DISTINCT description ");
		strcat(descbuf, "FROM pg_class, pg_attribute, pg_description ");
		strcat(descbuf, "WHERE pg_class.relname = '");
		strcat(descbuf, table);
		strcat(descbuf, "' and ");
		strcat(descbuf, "pg_class.oid = pg_attribute.attrelid and ");
		strcat(descbuf, "pg_attribute.attname = '");
		strcat(descbuf, column);
		strcat(descbuf, "' and ");
993
		strcat(descbuf, " pg_attribute.oid = pg_description.objoid ");
994 995 996 997
		if (!(res = PSQLexec(pset, descbuf)))
			return -1;
	}
	else
998
	{
999 1000
		strcat(descbuf, "SELECT DISTINCT description ");
		strcat(descbuf, "FROM pg_class, pg_description ");
1001
		strcat(descbuf, "WHERE pg_class.relname ~ '^");
1002
		strcat(descbuf, object);
1003 1004
		strcat(descbuf, "'");
		strcat(descbuf, " and pg_class.oid = pg_description.objoid ");
1005 1006 1007 1008 1009 1010 1011 1012
		if (!(res = PSQLexec(pset, descbuf)))
			return -1;
		else if (PQntuples(res) <= 0)
		{
			PQclear(res);
			descbuf[0] = '\0';
			strcat(descbuf, "SELECT DISTINCT description ");
			strcat(descbuf, "FROM pg_type, pg_description ");
1013
			strcat(descbuf, "WHERE pg_type.typname ~ '^");
1014
			strcat(descbuf, object);
1015
			strcat(descbuf, "' and ");
1016
			strcat(descbuf, " pg_type.oid = pg_description.objoid ");
1017 1018
			if (!(res = PSQLexec(pset, descbuf)))
				return -1;
Bruce Momjian's avatar
Bruce Momjian committed
1019
			else if (PQntuples(res) <= 0)
1020 1021 1022 1023
			{
				PQclear(res);
				descbuf[0] = '\0';
				strcat(descbuf, "SELECT DISTINCT description ");
1024
				strcat(descbuf, "FROM pg_proc, pg_description ");
1025
				strcat(descbuf, "WHERE pg_proc.proname ~ '^");
1026
				strcat(descbuf, object);
1027 1028
				strcat(descbuf, "'");
				strcat(descbuf, " and pg_proc.oid = pg_description.objoid ");
1029 1030 1031 1032 1033 1034 1035
				if (!(res = PSQLexec(pset, descbuf)))
					return -1;
				else if (PQntuples(res) <= 0)
				{
					PQclear(res);
					descbuf[0] = '\0';
					strcat(descbuf, "SELECT DISTINCT description ");
1036
					strcat(descbuf, "FROM pg_operator, pg_description ");
1037
					strcat(descbuf, "WHERE pg_operator.oprname ~ '^");
1038
					strcat(descbuf, object);
1039
					strcat(descbuf, "'");
Bruce Momjian's avatar
Bruce Momjian committed
1040
					/* operator descriptions are attached to the proc */
1041
					strcat(descbuf, " and RegprocToOid(pg_operator.oprcode) = pg_description.objoid ");
1042 1043 1044 1045 1046 1047 1048
					if (!(res = PSQLexec(pset, descbuf)))
						return -1;
					else if (PQntuples(res) <= 0)
					{
						PQclear(res);
						descbuf[0] = '\0';
						strcat(descbuf, "SELECT DISTINCT description ");
1049
						strcat(descbuf, "FROM pg_aggregate, pg_description ");
1050
						strcat(descbuf, "WHERE pg_aggregate.aggname ~ '^");
1051
						strcat(descbuf, object);
1052 1053
						strcat(descbuf, "'");
						strcat(descbuf, " and pg_aggregate.oid = pg_description.objoid ");
1054 1055
						if (!(res = PSQLexec(pset, descbuf)))
							return -1;
1056 1057 1058 1059 1060 1061 1062 1063
						else if (PQntuples(res) <= 0)
						{
							PQclear(res);
							descbuf[0] = '\0';
							strcat(descbuf, "SELECT 'no description' as description ");
							if (!(res = PSQLexec(pset, descbuf)))
								return -1;
						}
1064 1065 1066 1067 1068 1069
					}
				}
			}
		}
	}

1070
	PQclear(res);
1071

1072
	success = SendQuery(pset, descbuf, NULL, NULL);
1073

1074
	return 0;
1075 1076
}

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

1079
/*
1080
 * gets_noreadline	prompt source gets a line of input without calling
1081 1082
 * readline, the source is ignored
 */
1083
static char *
1084
gets_noreadline(char *prompt, FILE *source)
1085
{
1086 1087
	fputs(prompt, stdout);
	fflush(stdout);
1088
	return gets_fromFile(prompt, stdin);
1089 1090 1091
}

/*
1092 1093
 * 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
1094
 */
1095
static char *
1096
gets_readline(char *prompt, FILE *source)
1097
{
1098
	char	   *s;
1099

1100
#ifdef USE_READLINE
1101
	s = readline(prompt);
1102
#else
1103
	char		buf[500];
1104 1105 1106

	printf("%s", prompt);
	s = fgets(buf, 500, stdin);
1107
#endif
1108 1109 1110
	fputc('\r', stdout);
	fflush(stdout);
	return s;
1111 1112 1113 1114
}

/*
 * gets_fromFile  prompt source
1115
 *
1116 1117
 * the routine to read from a file, the prompt argument is ignored the source
 * argument is a FILE *
1118
 */
1119
static char *
1120
gets_fromFile(char *prompt, FILE *source)
1121
{
1122 1123
	char	   *line;
	int			len;
1124

1125
	line = malloc(MAX_QUERY_BUFFER + 1);
1126

1127 1128
	/* read up to MAX_QUERY_BUFFER characters */
	if (fgets(line, MAX_QUERY_BUFFER, source) == NULL)
1129
	{
1130 1131
		free(line);
		return NULL;
1132
	}
1133

1134 1135 1136 1137 1138 1139 1140 1141
	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;
1142 1143 1144
}

/*
1145 1146 1147 1148 1149 1150
 * SendQuery: send the query string to the backend.
 *
 * Return true if the query executed successfully, false otherwise.
 *
 * If not NULL, copy_in_stream and copy_out_stream are files to redirect
 * copy in/out data to.
1151
 */
1152 1153 1154
static bool
SendQuery(PsqlSettings *pset, const char *query,
		  FILE *copy_in_stream, FILE *copy_out_stream)
1155
{
1156
	bool		success = false;
1157 1158
	PGresult   *results;
	PGnotify   *notify;
1159

1160
	if (pset->singleStep)
1161 1162
		fprintf(stdout, "\n**************************************"
				"*****************************************\n");
1163

1164
	if (pset->echoQuery || pset->singleStep)
1165 1166 1167
	{
		fprintf(stderr, "QUERY: %s\n", query);
		fflush(stderr);
1168
	}
1169
	if (pset->singleStep)
1170 1171 1172 1173 1174 1175 1176
	{
		fprintf(stdout, "\n**************************************"
				"*****************************************\n");
		fflush(stdout);
		printf("\npress return to continue ..\n");
		gets_fromFile("", stdin);
	}
1177
	results = PQexec(pset->db, query);
1178 1179
	if (results == NULL)
	{
1180
		fprintf(stderr, "%s", PQerrorMessage(pset->db));
1181
		success = false;
1182
	}
1183 1184 1185 1186
	else
	{
		switch (PQresultStatus(results))
		{
1187
			case PGRES_TUPLES_OK:
1188
				if (pset->gfname)
1189
				{
1190
					PsqlSettings settings_copy = *pset;
1191 1192
					FILE	   *fp;

1193 1194
					settings_copy.queryFout = stdout;
					fp = setFout(&settings_copy, pset->gfname);
1195 1196
					if (!fp || fp == stdout)
					{
1197
						success = false;
1198 1199 1200 1201
						break;
					}
					PQprint(fp,
							results,
1202 1203
							&pset->opt);
					if (settings_copy.pipe)
1204 1205 1206
						pclose(fp);
					else
						fclose(fp);
1207 1208
					free(pset->gfname);
					pset->gfname = NULL;
1209
					success = true;
1210 1211 1212
					break;
				}
				else
1213
				{
1214
					success = true;
1215
					PQprint(pset->queryFout,
1216
							results,
1217 1218
							&(pset->opt));
					fflush(pset->queryFout);
1219
				}
1220
				break;
1221
			case PGRES_EMPTY_QUERY:
1222
				success = true;
1223 1224
				break;
			case PGRES_COMMAND_OK:
1225
				success = true;
1226
				if (!pset->quiet)
1227 1228 1229
					printf("%s\n", PQcmdStatus(results));
				break;
			case PGRES_COPY_OUT:
1230 1231
				if (copy_out_stream)
					success = handleCopyOut(pset->db, copy_out_stream);
1232 1233
				else
				{
1234
					if (pset->queryFout == stdout && !pset->quiet)
1235
						printf("Copy command returns...\n");
1236

1237
					success = handleCopyOut(pset->db, pset->queryFout);
1238 1239 1240
				}
				break;
			case PGRES_COPY_IN:
1241 1242
				if (copy_in_stream)
					success = handleCopyIn(pset->db, false, copy_in_stream);
1243
				else
1244 1245 1246
					success = handleCopyIn(pset->db,
										   cur_cmd_interactive && !pset->quiet,
										   cur_cmd_source);
1247 1248 1249 1250
				break;
			case PGRES_NONFATAL_ERROR:
			case PGRES_FATAL_ERROR:
			case PGRES_BAD_RESPONSE:
1251
				success = false;
1252
				fprintf(stderr, "%s", PQerrorMessage(pset->db));
1253
				break;
1254 1255
		}

1256
		if (PQstatus(pset->db) == CONNECTION_BAD)
1257 1258 1259 1260 1261 1262 1263 1264
		{
			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 */
Bruce Momjian's avatar
Bruce Momjian committed
1265
		while ((notify = PQnotifies(pset->db)) != NULL)
1266 1267 1268 1269 1270 1271 1272 1273
		{
			fprintf(stderr,
				 "ASYNC NOTIFY of '%s' from backend pid '%d' received\n",
					notify->relname, notify->be_pid);
			free(notify);
		}
		if (results)
			PQclear(results);
1274
	}
1275
	return success;
1276
}
1277 1278 1279



1280
static void
1281 1282
editFile(char *fname)
{
1283 1284
	char	   *editorName;
	char	   *sys;
1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297

	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);
1298 1299
}

1300
static bool
1301
toggle(PsqlSettings *pset, bool *sw, char *msg)
1302
{
1303
	*sw = !*sw;
1304
	if (!pset->quiet)
1305 1306
		printf("turned %s %s\n", on(*sw), msg);
	return *sw;
1307 1308
}

1309 1310


1311
static void
1312 1313
unescape(char *dest, const char *source)
{
1314 1315 1316 1317
	/*-----------------------------------------------------------------------------
	  Return as the string <dest> the value of string <source> with escape
	  sequences turned into the bytes they represent.
	-----------------------------------------------------------------------------*/
1318 1319
	char	   *p;
	bool		esc;			/* Last character we saw was the escape
1320 1321 1322 1323 1324
								 * character (/) */

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

		if (esc)
		{
			switch (*p)
			{
1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347
				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;
1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363
			}
			esc = false;
		}
		else if (*p == '\\')
		{
			esc = true;
			c = ' ';			/* meaningless, but compiler doesn't know
								 * that */
		}
		else
		{
			c = *p;
			esc = false;
		}
		if (!esc)
			*dest++ = c;
1364
	}
1365
	*dest = '\0';				/* Terminating null character */
1366 1367
}

1368 1369


1370
static void
1371
parse_slash_copy(const char *args, char *table, const int table_len,
1372
				 char *file, const int file_len,
1373
				 bool *from_p, bool *error_p)
1374 1375
{

1376
	char		work_args[200];
1377 1378 1379 1380 1381

	/*
	 * A copy of the \copy command arguments, except that we modify it as
	 * we parse to suit our parsing needs.
	 */
1382 1383
	char	   *table_tok,
			   *fromto_tok;
1384 1385 1386 1387 1388 1389 1390 1391 1392 1393

	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");
1394
		*error_p = true;
1395 1396 1397 1398 1399 1400 1401 1402 1403 1404
	}
	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");
1405 1406
			*error_p = true;
		}
1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422
		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)
			{
1423
				char	   *file_tok;
1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444

				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;
					}
				}
			}
		}
1445
	}
1446 1447 1448 1449
}



1450
static void
1451
do_copy(const char *args, PsqlSettings *pset)
1452
{
1453 1454 1455 1456
	/*---------------------------------------------------------------------------
	  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.
1457

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

	----------------------------------------------------------------------------*/
1462
	char		query[200];
1463 1464

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

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

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

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

	/* The \c command has invalid syntax */
1477
	FILE	   *copystream;
1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492

	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)
1493
#ifndef __CYGWIN32__
1494
			copystream = fopen(file, "r");
1495 1496 1497
#else
			copystream = fopen(file, "rb");
#endif
1498
		else
1499
#ifndef __CYGWIN32__
1500
			copystream = fopen(file, "w");
1501 1502 1503
#else
			copystream = fopen(file, "wb");
#endif
1504 1505 1506 1507
		if (copystream == NULL)
			fprintf(stderr,
				"Unable to open file %s which to copy, errno = %s (%d).",
					from ? "from" : "to", strerror(errno), errno);
1508
		else
1509
		{
1510
			bool		success;/* The query succeeded at the backend */
1511

1512 1513 1514
			success = SendQuery(pset, query,
								from ? copystream : (FILE*) NULL,
								!from ? copystream : (FILE*) NULL);
1515
			fclose(copystream);
1516
			if (!pset->quiet)
1517 1518 1519 1520 1521 1522 1523
			{
				if (success)
					printf("Successfully copied.\n");
				else
					printf("Copy failed.\n");
			}
		}
1524
	}
1525 1526 1527
}


1528
static void
1529
do_connect(const char *new_dbname,
1530
		   const char *new_user,
1531
		   PsqlSettings *pset)
1532
{
1533 1534
	if (!new_dbname)
		fprintf(stderr, "\\connect must be followed by a database name\n");
1535
	else if (new_user != NULL && pset->getPassword)
1536
		fprintf(stderr, "You can't specify a username when using passwords.\n");
1537 1538
	else
	{
1539
		PGconn	   *olddb = pset->db;
1540
		const char *dbparam;
1541 1542
		const char *userparam;
		const char *pwparam;
1543

1544 1545 1546 1547 1548
		if (strcmp(new_dbname, "-") != 0)
			dbparam = new_dbname;
		else
			dbparam = PQdb(olddb);

1549 1550 1551 1552 1553
		if (new_user != NULL && strcmp(new_user, "-") != 0)
			userparam = new_user;
		else
			userparam = PQuser(olddb);

1554 1555
		/* FIXME: if changing user, ought to prompt for a new password? */
		pwparam = PQpass(olddb);
1556

1557 1558 1559 1560 1561 1562 1563 1564
#ifdef MULTIBYTE
		/* PGCLIENTENCODING may be set by the previous connection.
		   if a user does not explicitly set PGCLIENTENCODING,
		   we should discard PGCLIENTENCODING so that
		   libpq could get the backend encoding as the default
		   PGCLIENTENCODING value. -- 1998/12/12 Tatsuo Ishii */
		   
		if (!has_client_encoding) {
1565 1566
		        static const char ev[] = "PGCLIENTENCODING=";
			putenv(ev);
1567 1568 1569
		}
#endif

1570 1571 1572
		pset->db = PQsetdbLogin(PQhost(olddb), PQport(olddb),
								NULL, NULL, dbparam, userparam, pwparam);

1573
		if (!pset->quiet)
1574 1575 1576 1577 1578 1579 1580 1581 1582
		{
			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);
		}
1583

1584
		if (PQstatus(pset->db) == CONNECTION_BAD)
1585
		{
1586
			fprintf(stderr, "%s\n", PQerrorMessage(pset->db));
1587 1588 1589 1590 1591
			fprintf(stderr, "Could not connect to new database. exiting\n");
			exit(2);
		}
		else
		{
1592 1593
			cancelConn = pset->db;		/* redirect sigint's loving
										 * attentions */
1594
			PQfinish(olddb);
1595 1596 1597
			free(pset->prompt);
			pset->prompt = malloc(strlen(PQdb(pset->db)) + 10);
			sprintf(pset->prompt, "%s%s", PQdb(pset->db), PROMPT);
1598
		}
1599
	}
1600 1601 1602
}


1603
static void
1604 1605
do_edit(const char *filename_arg, char *query, int *status_p)
{
1606

1607 1608 1609 1610 1611 1612
	int			fd;
	char		tmp[64];
	char	   *fname;
	int			cc;
	const int	ql = strlen(query);
	bool		error;
1613 1614 1615 1616 1617 1618 1619 1620

	if (filename_arg)
	{
		fname = (char *) filename_arg;
		error = false;
	}
	else
	{
1621
#ifndef WIN32
1622
		sprintf(tmp, "/tmp/psql.%ld.%ld", (long) geteuid(), (long) getpid());
1623
#else
1624
		GetTempFileName(".", "psql", 0, tmp);
1625
#endif
1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655
		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)
1656
		*status_p = CMD_SKIP_LINE;
1657 1658 1659
	else
	{
		editFile(fname);
1660
		if ((fd = open(fname, O_RDONLY, 0)) == -1)
1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686
		{
			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;
			}
		}
1687
	}
1688 1689 1690 1691
}



1692
static void
1693
do_help(PsqlSettings *pset, const char *topic)
1694
{
1695

1696
	if (!topic)
1697
	{
1698 1699
		char		left_center_right;	/* Which column we're displaying */
		int			i;			/* Index into QL_HELP[] */
1700 1701 1702 1703 1704 1705 1706 1707 1708

		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)
			{
1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720
				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;
Bruce Momjian's avatar
Bruce Momjian committed
1721
			}
1722 1723 1724 1725 1726
			i++;
		}
		if (left_center_right != 'L')
			puts("\n");
		printf("type \\h * for a complete description of all commands\n");
1727 1728 1729
	}
	else
	{
1730 1731
		int			i;			/* Index into QL_HELP[] */
		bool		help_found; /* We found the help he asked for */
1732

1733 1734 1735
		int			usePipe = 0;
		char	   *pagerenv;
		FILE	   *fout;
1736 1737

		if (strcmp(topic, "*") == 0 &&
1738
			(pset->notty == 0) &&
1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751
			(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++)
		{
1752
			if (strcasecmp(QL_HELP[i].cmd, topic) == 0 ||
1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768
				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);
		}
1769

1770 1771 1772 1773
		if (!help_found)
			fprintf(stderr, "command not found, "
					"try \\h with no arguments to see available help\n");
	}
1774 1775 1776 1777
}



1778
static void
1779 1780
do_shell(const char *command)
{
1781

1782 1783
	if (!command)
	{
1784 1785
		char	   *sys;
		char	   *shellName;
1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798

		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);
1799
	}
1800 1801
	else
		system(command);
1802 1803 1804 1805
}



1806
/*
1807
 * HandleSlashCmds:
1808
 *
1809 1810 1811 1812
 * 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.
1813
 * Returns a status:
1814 1815 1816 1817
 *	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
1818
 */
1819
static int
1820
HandleSlashCmds(PsqlSettings *pset,
1821 1822
				char *line,
				char *query)
1823
{
1824 1825
	int			status = CMD_SKIP_LINE;
	char	   *optarg;
1826
	bool		success;
1827

1828 1829 1830 1831 1832
	/*
	 * 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.
	 */
1833
	char	   *optarg2;
1834 1835 1836 1837 1838 1839

	/*
	 * 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.
	 */
1840
	char	   *cmd;
1841 1842 1843 1844 1845

	/*
	 * String: value of the slash command, less the slash and with escape
	 * sequences decoded.
	 */
1846
	int			blank_loc;
1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877

	/* 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])
1878
	{
1879
		case 'a':				/* toggles to align fields on output */
1880
			toggle(pset, &pset->opt.align, "field alignment");
1881
			break;
1882

1883
		case 'C':				/* define new caption */
1884
			if (pset->opt.caption)
1885
			{
1886 1887
				free(pset->opt.caption);
				pset->opt.caption = NULL;
1888
			}
1889
			if (optarg && !(pset->opt.caption = strdup(optarg)))
1890 1891 1892 1893 1894
			{
				perror("malloc");
				exit(CMD_TERMINATE);
			}
			break;
1895

1896
		case 'c':
1897
			{
1898
				if (strncmp(cmd, "copy ", strlen("copy ")) == 0 ||
1899
					strncmp(cmd, "copy	", strlen("copy	")) == 0)
1900
					do_copy(optarg2, pset);
1901 1902 1903 1904 1905
				else if (strcmp(cmd, "copy") == 0)
				{
					fprintf(stderr, "See \\? for help\n");
					break;
				}
1906 1907 1908 1909 1910
				else if (strncmp(cmd, "connect ", strlen("connect ")) == 0 ||
				  strcmp(cmd, "connect") == 0 /* issue error message */ )
				{
					char	   *optarg3 = NULL;
					int			blank_loc2;
1911

1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923
					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';
						}
					}
1924
					do_connect(optarg2, optarg3, pset);
1925 1926
				}
				else
1927
				{
1928 1929 1930 1931
					char	   *optarg3 = NULL;
					int			blank_loc2;

					if (optarg)
1932
					{
1933 1934 1935 1936 1937 1938 1939 1940 1941
						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';
						}
1942
					}
1943
					do_connect(optarg, optarg3, pset);
1944
				}
1945 1946
			}
			break;
1947

1948
		case 'd':				/* \d describe database information */
1949

1950
			/*
1951 1952
			 * if the optarg2 name is surrounded by double-quotes, then
			 * don't convert case
1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963
			 */
			if (optarg2)
			{
				if (*optarg2 == '"')
				{
					optarg2++;
					if (*(optarg2 + strlen(optarg2) - 1) == '"')
						*(optarg2 + strlen(optarg2) - 1) = '\0';
				}
				else
				{
1964 1965
					int			i;

1966
#ifdef MULTIBYTE
1967
					for (i = 0; optarg2[i]; i += PQmblen(optarg2 + i))
1968 1969 1970 1971 1972 1973 1974
#else
					for (i = 0; optarg2[i]; i++)
#endif
						if (isupper(optarg2[i]))
							optarg2[i] = tolower(optarg2[i]);
				}
			}
1975

1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987
#ifdef TIOCGWINSZ
			if (pset->notty == 0 &&
				(ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) == -1 ||
				 screen_size.ws_col == 0 ||
				 screen_size.ws_row == 0))
			{
#endif
				screen_size.ws_row = 24;
				screen_size.ws_col = 80;
#ifdef TIOCGWINSZ
			}
#endif
1988
			if (strncmp(cmd, "da", 2) == 0)
1989
			{
1990
				char		descbuf[4096];
1991

1992
				/* aggregates */
1993 1994 1995 1996 1997 1998 1999 2000
				descbuf[0] = '\0';
				strcat(descbuf, "SELECT	a.aggname AS aggname, ");
				strcat(descbuf, "		t.typname AS type, ");
				strcat(descbuf, "		obj_description(a.oid) as description ");
				strcat(descbuf, "FROM	pg_aggregate a, pg_type t ");
				strcat(descbuf, "WHERE	a.aggbasetype = t.oid ");
				if (optarg2)
				{
2001 2002 2003
					strcat(descbuf, "AND a.aggname ~ '^");
					strcat(descbuf, optarg2);
					strcat(descbuf, "' ");
2004 2005 2006 2007 2008 2009 2010 2011 2012
				}
				strcat(descbuf, "UNION ");
				strcat(descbuf, "SELECT	a.aggname AS aggname, ");
				strcat(descbuf, "		'all types' as type, ");
				strcat(descbuf, "		obj_description(a.oid) as description ");
				strcat(descbuf, "FROM	pg_aggregate a ");
				strcat(descbuf, "WHERE	a.aggbasetype = 0 ");
				if (optarg2)
				{
2013 2014 2015
					strcat(descbuf, "AND a.aggname ~ '^");
					strcat(descbuf, optarg2);
					strcat(descbuf, "' ");
2016 2017
				}
				strcat(descbuf, "ORDER BY aggname, type;");
2018
				success = SendQuery(pset, descbuf, NULL, NULL);
2019
			}
2020
			else if (strncmp(cmd, "dd", 2) == 0)
2021
				/* descriptions */
2022
				objectDescription(pset, optarg + 1);
2023
			else if (strncmp(cmd, "df", 2) == 0)
Bruce Momjian's avatar
Bruce Momjian committed
2024
			{
2025 2026
				char		descbuf[4096];

2027 2028 2029 2030 2031 2032 2033
				/* functions/procedures */

				/*
				 * we skip in/out funcs by excluding functions that take
				 * some arguments, but have no types defined for those
				 * arguments
				 */
2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049
				descbuf[0] = '\0';
				strcat(descbuf, "SELECT	t.typname as result, ");
				strcat(descbuf, "		p.proname as function, ");
				if (screen_size.ws_col <= 80)
					strcat(descbuf, "	substr(oid8types(p.proargtypes),1,14) as arguments, ");
				else
					strcat(descbuf, "	oid8types(p.proargtypes) as arguments, ");
				if (screen_size.ws_col <= 80)
					strcat(descbuf, "	substr(obj_description(p.oid),1,34) as description ");
				else
					strcat(descbuf, "	obj_description(p.oid) as description ");
				strcat(descbuf, "FROM 	pg_proc p, pg_type t ");
				strcat(descbuf, "WHERE 	p.prorettype = t.oid and ");
				strcat(descbuf, "(pronargs = 0 or oid8types(p.proargtypes) != '') ");
				if (optarg2)
				{
2050 2051 2052
					strcat(descbuf, "AND p.proname ~ '^");
					strcat(descbuf, optarg2);
					strcat(descbuf, "' ");
2053 2054
				}
				strcat(descbuf, "ORDER BY result, function, arguments;");
2055
				success = SendQuery(pset, descbuf, NULL, NULL);
Bruce Momjian's avatar
Bruce Momjian committed
2056
			}
2057
			else if (strncmp(cmd, "di", 2) == 0)
2058
				/* only indices */
2059
				tableList(pset, false, 'i', false);
2060 2061
			else if (strncmp(cmd, "do", 2) == 0)
			{
2062 2063
				char		descbuf[4096];

2064
				/* operators */
2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083
				descbuf[0] = '\0';
				strcat(descbuf, "SELECT	o.oprname AS op, ");
				strcat(descbuf, "		t1.typname AS left_arg, ");
				strcat(descbuf, "		t2.typname AS right_arg, ");
				strcat(descbuf, "		t0.typname AS result, ");
				if (screen_size.ws_col <= 80)
					strcat(descbuf, "	substr(obj_description(p.oid),1,41) as description ");
				else
					strcat(descbuf, "	obj_description(p.oid) as description ");
				strcat(descbuf, "FROM	pg_proc p, pg_type t0, ");
				strcat(descbuf, "		pg_type t1, pg_type t2, ");
				strcat(descbuf, "		pg_operator o ");
				strcat(descbuf, "WHERE	p.prorettype = t0.oid AND ");
				strcat(descbuf, "		RegprocToOid(o.oprcode) = p.oid AND ");
				strcat(descbuf, "		p.pronargs = 2 AND ");
				strcat(descbuf, "		o.oprleft = t1.oid AND ");
				strcat(descbuf, "		o.oprright = t2.oid ");
				if (optarg2)
				{
2084 2085 2086
					strcat(descbuf, "AND o.oprname ~ '^");
					strcat(descbuf, optarg2);
					strcat(descbuf, "' ");
2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103
				}
				strcat(descbuf, "UNION ");
				strcat(descbuf, "SELECT	o.oprname as op, ");
				strcat(descbuf, "		''::name AS left_arg, ");
				strcat(descbuf, "		t1.typname AS right_arg, ");
				strcat(descbuf, "		t0.typname AS result, ");
				if (screen_size.ws_col <= 80)
					strcat(descbuf, "	substr(obj_description(p.oid),1,41) as description ");
				else
					strcat(descbuf, "	obj_description(p.oid) as description ");
				strcat(descbuf, "FROM	pg_operator o, pg_proc p, pg_type t0, pg_type t1 ");
				strcat(descbuf, "WHERE	RegprocToOid(o.oprcode) = p.oid AND ");
				strcat(descbuf, "		o.oprresult = t0.oid AND ");
				strcat(descbuf, "		o.oprkind = 'l' AND ");
				strcat(descbuf, "		o.oprright = t1.oid ");
				if (optarg2)
				{
2104 2105 2106
					strcat(descbuf, "AND o.oprname ~ '^");
					strcat(descbuf, optarg2);
					strcat(descbuf, "' ");
2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123
				}
				strcat(descbuf, "UNION ");
				strcat(descbuf, "SELECT	o.oprname  as op, ");
				strcat(descbuf, "		t1.typname AS left_arg, ");
				strcat(descbuf, "		''::name AS right_arg, ");
				strcat(descbuf, "		t0.typname AS result, ");
				if (screen_size.ws_col <= 80)
					strcat(descbuf, "	substr(obj_description(p.oid),1,41) as description ");
				else
					strcat(descbuf, "	obj_description(p.oid) as description ");
				strcat(descbuf, "FROM	pg_operator o, pg_proc p, pg_type t0, pg_type t1 ");
				strcat(descbuf, "WHERE	RegprocToOid(o.oprcode) = p.oid AND ");
				strcat(descbuf, "		o.oprresult = t0.oid AND ");
				strcat(descbuf, "		o.oprkind = 'r' AND ");
				strcat(descbuf, "		o.oprleft = t1.oid ");
				if (optarg2)
				{
2124 2125 2126
					strcat(descbuf, "AND o.oprname ~ '^");
					strcat(descbuf, optarg2);
					strcat(descbuf, "' ");
2127 2128
				}
				strcat(descbuf, "ORDER BY op, left_arg, right_arg, result;");
2129
				success = SendQuery(pset, descbuf, NULL, NULL);
2130
			}
2131
			else if (strncmp(cmd, "ds", 2) == 0)
2132
				/* only sequences */
2133 2134
				tableList(pset, false, 'S', false);
			else if (strncmp(cmd, "dS", 2) == 0)
2135
				/* system tables */
2136
				tableList(pset, false, 'b', true);
2137
			else if (strncmp(cmd, "dt", 2) == 0)
2138
				/* only tables */
2139
				tableList(pset, false, 't', false);
2140
			else if (strncmp(cmd, "dT", 2) == 0)
2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157
			{
				char		descbuf[4096];

				/* types */
				descbuf[0] = '\0';
				strcat(descbuf, "SELECT	typname AS type, ");
				strcat(descbuf, "		obj_description(oid) as description ");
				strcat(descbuf, "FROM	pg_type ");
				strcat(descbuf, "WHERE	typrelid = 0 AND ");
				strcat(descbuf, "		typname !~ '^_.*' ");
				strcat(descbuf, "ORDER BY type;");
				if (optarg2)
				{
					strcat(descbuf, "AND typname ~ '^");
					strcat(descbuf, optarg2);
					strcat(descbuf, "' ");
				}
2158
				success = SendQuery(pset, descbuf, NULL, NULL);
2159
			}
2160
			else if (!optarg)
2161
				/* show tables, sequences and indices */
2162
				tableList(pset, false, 'b', false);
2163 2164
			else if (strcmp(optarg, "*") == 0)
			{					/* show everything */
2165 2166
				if (tableList(pset, false, 'b', false) == 0)
					tableList(pset, true, 'b', false);
2167
			}
2168
			else if (strncmp(cmd, "d ", 2) == 0)
2169
				/* describe the specified table */
2170
				tableDesc(pset, optarg, NULL);
2171 2172
			else
				slashUsage(pset);
2173
			break;
2174

2175 2176 2177 2178 2179
		case 'e':				/* edit */
			{
				do_edit(optarg, query, &status);
				break;
			}
2180

2181
		case 'E':
2182
			{
2183 2184 2185 2186
				FILE	   *fd;
				static char *lastfile;
				struct stat st,
							st2;
2187 2188 2189

				if (optarg)
				{
2190 2191 2192 2193
					if (lastfile)
						free(lastfile);
					lastfile = malloc(strlen(optarg + 1));
					if (!lastfile)
2194
					{
2195 2196
						perror("malloc");
						exit(CMD_TERMINATE);
2197
					}
2198
					strcpy(lastfile, optarg);
2199
				}
2200 2201 2202 2203 2204 2205 2206
				else if (!lastfile)
				{
					fprintf(stderr, "\\r must be followed by a file name initially\n");
					break;
				}
				stat(lastfile, &st);
				editFile(lastfile);
2207
#ifndef __CYGWIN32__
2208
				if ((stat(lastfile, &st2) == -1) || ((fd = fopen(lastfile, "r")) == NULL))
2209 2210 2211
#else
				if ((stat(lastfile, &st2) == -1) || ((fd = fopen(lastfile, "rb")) == NULL))
#endif
2212 2213 2214 2215 2216 2217
				{
					perror(lastfile);
					break;
				}
				if (st2.st_mtime == st.st_mtime)
				{
2218
					if (!pset->quiet)
2219 2220 2221 2222
						fprintf(stderr, "warning: %s not modified. query not executed\n", lastfile);
					fclose(fd);
					break;
				}
2223
				MainLoop(pset, query, fd);
2224 2225
				fclose(fd);
				break;
2226
			}
2227

2228
		case 'f':
2229
			{
2230 2231 2232 2233
				char	   *fs = DEFAULT_FIELD_SEP;

				if (optarg)
					fs = optarg;
Bruce Momjian's avatar
Bruce Momjian committed
2234 2235 2236 2237
				/* handle \f \{space} */
				if (optarg && !*optarg && strlen(cmd) > 1)
				{
					int			i;
2238

Bruce Momjian's avatar
Bruce Momjian committed
2239
					/* line and cmd match until the first blank space */
2240
					for (i = 2; isspace(line[i]); i++)
Bruce Momjian's avatar
Bruce Momjian committed
2241 2242 2243
						;
					fs = cmd + i - 1;
				}
2244 2245 2246
				if (pset->opt.fieldSep)
					free(pset->opt.fieldSep);
				if (!(pset->opt.fieldSep = strdup(fs)))
2247 2248 2249 2250
				{
					perror("malloc");
					exit(CMD_TERMINATE);
				}
2251 2252
				if (!pset->quiet)
					printf("field separator changed to '%s'\n", pset->opt.fieldSep);
2253
				break;
2254
			}
2255 2256
		case 'g':				/* \g means send query */
			if (!optarg)
2257 2258
				pset->gfname = NULL;
			else if (!(pset->gfname = strdup(optarg)))
2259
			{
2260 2261
				perror("malloc");
				exit(CMD_TERMINATE);
2262
			}
2263 2264
			status = CMD_SEND;
			break;
2265

2266
		case 'h':				/* help */
2267
			{
2268
				do_help(pset, optarg);
2269 2270
				break;
			}
2271

2272
		case 'i':				/* \i is include file */
2273
			{
2274 2275 2276 2277 2278 2279 2280
				FILE	   *fd;

				if (!optarg)
				{
					fprintf(stderr, "\\i must be followed by a file name\n");
					break;
				}
2281
#ifndef __CYGWIN32__
2282
				if ((fd = fopen(optarg, "r")) == NULL)
2283 2284 2285
#else
				if ((fd = fopen(optarg, "rb")) == NULL)
#endif
2286 2287 2288 2289
				{
					fprintf(stderr, "file named %s could not be opened\n", optarg);
					break;
				}
2290
				MainLoop(pset, query, fd);
2291 2292 2293
				fclose(fd);
				break;
			}
2294

2295 2296 2297 2298 2299
		case 'H':
			if (toggle(pset, &pset->opt.html3, "HTML3.0 tabular output"))
				pset->opt.standard = 0;
			break;

2300
		case 'l':				/* \l is list database */
2301
			listAllDbs(pset);
2302
			break;
2303

2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322
		case 'm':				/* monitor like type-setting */
			if (toggle(pset, &pset->opt.standard, "standard SQL separaters and padding"))
			{
				pset->opt.html3 = pset->opt.expanded = 0;
				pset->opt.align = pset->opt.header = 1;
				if (pset->opt.fieldSep)
					free(pset->opt.fieldSep);
				pset->opt.fieldSep = strdup("|");
				if (!pset->quiet)
					printf("field separator changed to '%s'\n", pset->opt.fieldSep);
			}
			else
			{
				if (pset->opt.fieldSep)
					free(pset->opt.fieldSep);
				pset->opt.fieldSep = strdup(DEFAULT_FIELD_SEP);
				if (!pset->quiet)
					printf("field separator changed to '%s'\n", pset->opt.fieldSep);
			}
2323
			break;
2324

2325
		case 'o':
2326
			setFout(pset, optarg);
2327
			break;
2328

2329 2330
		case 'p':
			if (query)
2331
			{
2332 2333
				fputs(query, stdout);
				fputc('\n', stdout);
2334 2335
			}
			break;
2336

2337 2338
		case 'q':				/* \q is quit */
			status = CMD_TERMINATE;
2339
			break;
2340

2341 2342
		case 'r':				/* reset(clear) the buffer */
			query[0] = '\0';
2343
			if (!pset->quiet)
2344 2345
				printf("buffer reset(cleared)\n");
			break;
2346

2347
		case 's':				/* \s is save history to a file */
2348
			if (!optarg)
2349
				optarg = "/dev/tty";
2350
#ifdef USE_HISTORY
2351 2352 2353 2354
			if (write_history(optarg) != 0)
				fprintf(stderr, "cannot write history to %s\n", optarg);
#endif
			break;
2355

2356
		case 't':				/* toggle headers */
2357
			toggle(pset, &pset->opt.header, "output headings and row count");
2358
			break;
2359

2360
		case 'T':				/* define html <table ...> option */
2361 2362
			if (pset->opt.tableOpt)
				free(pset->opt.tableOpt);
2363
			if (!optarg)
2364 2365
				pset->opt.tableOpt = NULL;
			else if (!(pset->opt.tableOpt = strdup(optarg)))
2366 2367 2368 2369 2370
			{
				perror("malloc");
				exit(CMD_TERMINATE);
			}
			break;
2371

2372 2373 2374 2375 2376 2377 2378 2379 2380
		case 'w':
			{
				FILE	   *fd;

				if (!optarg)
				{
					fprintf(stderr, "\\w must be followed by a file name\n");
					break;
				}
2381 2382 2383
#ifndef __CYGWIN32__
				if ((fd = fopen(optarg, "w")) == NULL)
#else
2384
				if ((fd = fopen(optarg, "w")) == NULL)
2385
#endif
2386 2387 2388 2389 2390 2391 2392 2393 2394 2395
				{
					fprintf(stderr, "file named %s could not be opened\n", optarg);
					break;
				}
				fputs(query, fd);
				fputs("\n", fd);
				fclose(fd);
				break;
			}

2396
		case 'x':
2397
			toggle(pset, &pset->opt.expanded, "expanded table representation");
2398
			break;
2399

2400 2401 2402 2403
		case 'z':				/* list table rights (grant/revoke) */
			rightsList(pset);
			break;

2404 2405 2406 2407
		case '!':
			do_shell(optarg);
			break;
		default:
2408

2409
		case '?':				/* \? is help */
2410
			slashUsage(pset);
2411
			break;
2412
	}
2413 2414
	free(cmd);
	return status;
2415
}
2416

2417 2418
/* MainLoop()
 * Main processing loop for reading lines of input
2419 2420
 *	and sending them to the backend.
 *
2421
 * This loop is re-entrant. May be called by \i command
2422
 *	which reads input from a file.
2423
 * db_ptr must be initialized and set.
2424
 */
2425

2426
static int
2427
MainLoop(PsqlSettings *pset, char *query, FILE *source)
2428
{
2429 2430 2431 2432 2433
	char	   *line;			/* line of input */
	char	   *xcomment;		/* start of extended comment */
	int			len;			/* length of the line */
	int			successResult = 1;
	int			slashCmdStatus = CMD_SEND;
2434

2435
	/*--------------------------------------------------------------
2436
	 * slashCmdStatus can be:
2437 2438 2439
	 * CMD_UNKNOWN		- send currently constructed query to backend
	 *					  (i.e. we got a \g)
	 * CMD_SEND			- send currently constructed query to backend
2440
	 *					  (i.e. we got a \g)
2441 2442 2443
	 * CMD_SKIP_LINE	- skip processing of this line, continue building
	 *					  up query
	 * CMD_TERMINATE	- terminate processing of this query entirely
2444 2445
	 * CMD_NEWEDIT		- new query supplied by edit
	 *---------------------------------------------------------------
2446 2447
	 */

2448 2449
	bool		querySent = false;
	READ_ROUTINE GetNextLine;
2450
	bool		eof = false;	/* end of our command input? */
2451
	bool		success;
2452
	char		in_quote;		/* == 0 for no in_quote */
2453
	bool		was_bslash;		/* backslash */
2454 2455
	int			paren_level;
	char	   *query_start;
2456 2457 2458
	/* Stack the prior command source */
	FILE	   *prev_cmd_source = cur_cmd_source;
	bool		prev_cmd_interactive = cur_cmd_interactive;
2459

2460 2461 2462
	/* Establish new source */
	cur_cmd_source = source;
	cur_cmd_interactive = ((source == stdin) && !pset->notty);
2463

2464 2465
	if ((query = malloc(MAX_QUERY_BUFFER)) == NULL)
		perror("Memory Allocation Failed");
2466

2467
	if (cur_cmd_interactive)
2468
	{
2469 2470
		if (pset->prompt)
			free(pset->prompt);
2471
		pset->prompt = malloc(strlen(PQdb(pset->db)) + strlen(PROMPT) + 1);
2472 2473
		if (pset->quiet)
			pset->prompt[0] = '\0';
2474
		else
2475 2476
			sprintf(pset->prompt, "%s%s", PQdb(pset->db), PROMPT);
		if (pset->useReadline)
2477
		{
2478
#ifdef USE_HISTORY
2479
			using_history();
2480
#endif
2481 2482 2483 2484
			GetNextLine = gets_readline;
		}
		else
			GetNextLine = gets_noreadline;
2485
	}
2486 2487
	else
		GetNextLine = gets_fromFile;
2488

2489 2490 2491 2492 2493
	query[0] = '\0';
	xcomment = NULL;
	in_quote = false;
	paren_level = 0;
	slashCmdStatus = CMD_UNKNOWN;		/* set default */
2494

2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507
	/* 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';
2508

2509 2510 2511 2512 2513 2514 2515
			/*
			 * otherwise, get another line and set interactive prompt if
			 * necessary
			 */
		}
		else
		{
2516
			if (cur_cmd_interactive && !pset->quiet)
2517
			{
Bruce Momjian's avatar
Bruce Momjian committed
2518 2519 2520 2521
				if (in_quote && in_quote == PROMPT_SINGLEQUOTE)
					pset->prompt[strlen(pset->prompt) - 3] = PROMPT_SINGLEQUOTE;
				else if (in_quote && in_quote == PROMPT_DOUBLEQUOTE)
					pset->prompt[strlen(pset->prompt) - 3] = PROMPT_DOUBLEQUOTE;
2522
				else if (xcomment != NULL)
2523
					pset->prompt[strlen(pset->prompt) - 3] = PROMPT_COMMENT;
2524
				else if (query[0] != '\0' && !querySent)
2525
					pset->prompt[strlen(pset->prompt) - 3] = PROMPT_CONTINUE;
2526
				else
2527
					pset->prompt[strlen(pset->prompt) - 3] = PROMPT_READY;
2528
			}
2529
			line = GetNextLine(pset->prompt, source);
2530
#ifdef USE_HISTORY
2531
			if (cur_cmd_interactive && pset->useReadline && line != NULL)
2532 2533 2534
				add_history(line);		/* save non-empty lines in history */
#endif
		}
2535

2536 2537 2538 2539
		/*
		 * query - pointer to current command query_start - placeholder
		 * for next command
		 */
2540
		if (line == NULL || (!cur_cmd_interactive && *line == '\0'))
2541 2542
		{						/* No more input.  Time to quit, or \i
								 * done */
2543 2544 2545 2546 2547 2548
			if (!pset->quiet)
				printf("EOF\n");/* Goes on prompt line */
			eof = true;
			continue;
		}

2549 2550 2551 2552
		/* not currently inside an extended comment? */
		if (xcomment == NULL)
		{
			query_start = line;
2553

2554 2555 2556 2557 2558 2559
			/* otherwise, continue the extended comment... */
		}
		else
		{
			query_start = line;
			xcomment = line;
Bruce Momjian's avatar
Bruce Momjian committed
2560
		}
2561

2562 2563
		/* remove whitespaces on the right, incl. \n's */
		line = rightTrim(line);
2564

2565
		/* echo back if input is from file */
2566
		if (!cur_cmd_interactive && !pset->singleStep && !pset->quiet)
2567
			fprintf(stderr, "%s\n", line);
2568

Bruce Momjian's avatar
Bruce Momjian committed
2569
		slashCmdStatus = CMD_UNKNOWN;
2570 2571 2572 2573 2574 2575
		/* nothing on line after trimming? then ignore */
		if (line[0] == '\0')
		{
			free(line);
			continue;
		}
2576

2577
		len = strlen(line);
2578

2579 2580
		if (pset->singleLineMode)
		{
2581
			success = SendQuery(pset, line, NULL, NULL);
2582 2583 2584 2585 2586 2587
			successResult &= success;
			querySent = true;
		}
		else
		{
			int			i;
2588

2589
#ifdef MULTIBYTE
2590 2591
			int			mblen = 1;

2592
#endif
Bruce Momjian's avatar
Bruce Momjian committed
2593

2594
			was_bslash = false;
2595
#ifdef MULTIBYTE
2596
			for (i = 0; i < len; mblen = PQmblen(line + i), i += mblen)
2597
#else
2598
			for (i = 0; i < len; i++)
2599
#endif
2600 2601
			{
				if (line[i] == '\\' && !in_quote)
2602
				{
2603
					char		hold_char = line[i];
Bruce Momjian's avatar
Bruce Momjian committed
2604

2605 2606 2607 2608
					line[i] = '\0';
					if (query_start[0] != '\0')
					{
						if (query[0] != '\0')
Bruce Momjian's avatar
Bruce Momjian committed
2609
						{
2610 2611 2612 2613 2614
							strcat(query, "\n");
							strcat(query, query_start);
						}
						else
							strcpy(query, query_start);
Bruce Momjian's avatar
Bruce Momjian committed
2615
					}
2616 2617
					line[i] = hold_char;
					query_start = line + i;
2618
					break;		/* handle command */
Bruce Momjian's avatar
Bruce Momjian committed
2619

2620 2621
					/* start an extended comment? */
				}
2622

2623
				if (querySent &&
2624 2625
					isascii((unsigned char) (line[i])) &&
					!isspace(line[i]))
2626 2627 2628 2629
				{
					query[0] = '\0';
					querySent = false;
				}
Bruce Momjian's avatar
Bruce Momjian committed
2630

2631 2632
				if (was_bslash)
					was_bslash = false;
2633
#ifdef MULTIBYTE
2634 2635
				else if (i > 0 && line[i - mblen] == '\\')
#else
2636
				else if (i > 0 && line[i - 1] == '\\')
2637
#endif
2638
					was_bslash = true;
2639

2640
				/* inside a quote? */
Bruce Momjian's avatar
Bruce Momjian committed
2641
				if (in_quote && (line[i] != in_quote || was_bslash))
2642 2643 2644
					 /* do nothing */ ;
				else if (xcomment != NULL)		/* inside an extended
												 * comment? */
2645
				{
2646
#ifdef MULTIBYTE
2647 2648
					if (line[i] == '*' && line[i + mblen] == '/')
#else
2649
					if (line[i] == '*' && line[i + 1] == '/')
2650
#endif
2651
					{
2652
						xcomment = NULL;
2653
#ifdef MULTIBYTE
2654 2655
						i += mblen;
#else
2656
						i++;
2657
#endif
2658
					}
2659
				}
2660
				/* possible backslash command? */
2661
#ifdef MULTIBYTE
2662 2663
				else if (line[i] == '/' && line[i + mblen] == '*')
#else
2664
				else if (line[i] == '/' && line[i + 1] == '*')
2665
#endif
2666 2667
				{
					xcomment = line + i;
2668
#ifdef MULTIBYTE
2669 2670
					i += mblen;
#else
2671
					i++;
2672
#endif
2673
				}
2674
				/* single-line comment? truncate line */
2675
#ifdef MULTIBYTE
2676 2677 2678
				else if ((line[i] == '-' && line[i + mblen] == '-') ||
						 (line[i] == '/' && line[i + mblen] == '/'))
#else
2679 2680
				else if ((line[i] == '-' && line[i + 1] == '-') ||
						 (line[i] == '/' && line[i + 1] == '/'))
2681
#endif
2682 2683 2684 2685
				{
					/* print comment at top of query */
					if (pset->singleStep)
						fprintf(stdout, "%s\n", line + i);
2686
					line[i] = '\0';		/* remove comment */
2687 2688
					break;
				}
Bruce Momjian's avatar
Bruce Momjian committed
2689 2690 2691 2692
				else if (in_quote && line[i] == in_quote)
					in_quote = false;
				else if (!in_quote && (line[i] == '\'' || line[i] == '"'))
					in_quote = line[i];
2693
				/* semi-colon? then send query now */
2694 2695 2696
				else if (!paren_level && line[i] == ';')
				{
					char		hold_char = line[i + 1];
2697

2698 2699 2700 2701
					line[i + 1] = '\0';
					if (query_start[0] != '\0')
					{
						if (query[0] != '\0')
2702
						{
2703 2704
							strcat(query, "\n");
							strcat(query, query_start);
2705
						}
2706 2707
						else
							strcpy(query, query_start);
2708
					}
2709
					success = SendQuery(pset, query, NULL, NULL);
2710 2711 2712
					successResult &= success;
					line[i + 1] = hold_char;
					query_start = line + i + 1;
2713
					/* sometimes, people do ';\g', don't execute twice */
2714 2715
					if (*query_start && /* keeps us from going off the end */
						*query_start == '\\' &&
2716
						*(query_start + 1) == 'g')
2717 2718 2719 2720 2721 2722
						query_start += 2;
					querySent = true;
				}
				else if (line[i] == '(')
				{
					paren_level++;
2723

2724 2725 2726
				}
				else if (paren_level && line[i] == ')')
					paren_level--;
2727
			}
2728
		}
2729

2730 2731 2732 2733 2734 2735
		/* nothing on line after trimming? then ignore */
		if (line[0] == '\0')
		{
			free(line);
			continue;
		}
2736

2737 2738
		if (!in_quote && query_start[0] == '\\')
		{
2739 2740
			/* handle \p\g and other backslash combinations */
			while (query_start[0] != '\0')
2741
			{
2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772
				char	hold_char;

#ifndef WIN32
				/* I believe \w \dos\system\x would cause a problem */
				/* do we have '\p\g' or '\p  \g' ? */
				if (strlen(query_start) > 2 &&
					query_start[2 + strspn(query_start + 2, " \t")] == '\\')
				{
					hold_char = query_start[2 + strspn(query_start + 2, " \t")];
					query_start[2 + strspn(query_start + 2, " \t")] = '\0';
				}
				else /* spread over #endif */
#endif
					hold_char = '\0';

				slashCmdStatus = HandleSlashCmds(pset,
												 query_start,
												 query);

				if (slashCmdStatus == CMD_SKIP_LINE && !hold_char)
				{
					if (query[0] == '\0')
						paren_level = 0;
					break;
				}
				if (slashCmdStatus == CMD_TERMINATE)
					break;

				query_start += strlen(query_start);
				if (hold_char)
					query_start[0] = hold_char;
2773
			}
2774 2775
			free(line);
			/* They did \q, leave the loop */
2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788
			if (slashCmdStatus == CMD_TERMINATE)
				break;
		}
		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')
2789
			{
2790 2791
				querySent = false;
				if (query[0] != '\0')
2792
				{
2793 2794
					strcat(query, "\n");
					strcat(query, query_start);
2795
				}
2796 2797
				else
					strcpy(query, query_start);
2798
			}
2799 2800
			free(line);
		}
2801

2802 2803 2804
		/* had a backslash-g? force the query to be sent */
		if (slashCmdStatus == CMD_SEND)
		{
2805
			success = SendQuery(pset, query, NULL, NULL);
2806
			successResult &= success;
2807 2808 2809
			xcomment = NULL;
			in_quote = false;
			paren_level = 0;
2810
			querySent = true;
2811 2812
		}
	}							/* while */
2813 2814

	if (query)
2815 2816
		free(query);

2817 2818 2819
	cur_cmd_source = prev_cmd_source;
	cur_cmd_interactive = prev_cmd_interactive;

2820
	return successResult;
2821
}	/* MainLoop() */
2822

2823
int
2824
main(int argc, char **argv)
2825
{
2826 2827
	extern char *optarg;
	extern int	optind;
2828

2829 2830 2831 2832
	char	   *dbname = NULL;
	char	   *host = NULL;
	char	   *port = NULL;
	char	   *qfilename = NULL;
2833

2834
	PsqlSettings settings;
2835

2836
	char	   *singleQuery = NULL;
2837

2838
	bool		listDatabases = 0;
2839
	int			successResult = 1;
2840
	bool		singleSlashCmd = 0;
2841
	int			c;
2842

2843
	char	   *home = NULL;	/* Used to store $HOME */
2844
	char       *version = NULL;     /* PostgreSQL version */
2845

Bruce Momjian's avatar
Bruce Momjian committed
2846
	MemSet(&settings, 0, sizeof settings);
2847 2848 2849 2850 2851 2852
	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))
Bruce Momjian's avatar
Bruce Momjian committed
2853 2854
	{
		/* Noninteractive defaults */
2855
		settings.notty = 1;
Bruce Momjian's avatar
Bruce Momjian committed
2856
	}
2857
	else
Bruce Momjian's avatar
Bruce Momjian committed
2858 2859
	{
		/* Interactive defaults */
2860
		pqsignal(SIGINT, handle_sigint);		/* control-C => cancel */
Bruce Momjian's avatar
Bruce Momjian committed
2861
#ifdef USE_READLINE
2862
		settings.useReadline = 1;
2863
#endif
Bruce Momjian's avatar
Bruce Momjian committed
2864
	}
2865
#ifdef PSQL_ALWAYS_GET_PASSWORDS
2866
	settings.getPassword = 1;
2867
#else
2868
	settings.getPassword = 0;
2869
#endif
Marc G. Fournier's avatar
Marc G. Fournier committed
2870

2871
#ifdef MULTIBYTE
2872 2873 2874
	has_client_encoding = getenv("PGCLIENTENCODING");
#endif

2875
	while ((c = getopt(argc, argv, "Aa:c:d:eEf:F:lh:Hnso:p:qStT:ux")) != EOF)
2876 2877 2878
	{
		switch (c)
		{
2879 2880 2881 2882
			case 'A':
				settings.opt.align = 0;
				break;
			case 'a':
2883
#ifdef NOT_USED					/* this no longer does anything */
2884
				fe_setauthsvc(optarg, errbuf);
2885
#endif
2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897
				break;
			case 'c':
				singleQuery = strdup(optarg);
				if (singleQuery[0] == '\\')
					singleSlashCmd = 1;
				break;
			case 'd':
				dbname = optarg;
				break;
			case 'e':
				settings.echoQuery = 1;
				break;
2898 2899 2900 2901
			case 'E':
				settings.echoAllQueries = 1;
				settings.echoQuery = 1;
				break;
2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949
			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;
2950
		}
2951
	}
2952 2953 2954 2955 2956 2957 2958 2959 2960
	/* 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)
	{
2961 2962
		char		username[100];
		char		password[100];
2963 2964 2965

		prompt_for_password(username, password);

2966 2967
		settings.db = PQsetdbLogin(host, port, NULL, NULL, dbname,
								   username, password);
2968 2969 2970 2971 2972 2973 2974 2975 2976
	}
	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);
2977
		fprintf(stderr, "%s\n", PQerrorMessage(settings.db));
2978 2979 2980
		PQfinish(settings.db);
		exit(1);
	}
Bruce Momjian's avatar
Bruce Momjian committed
2981 2982 2983

	cancelConn = settings.db;	/* enable SIGINT to send cancel */

2984 2985
	if (listDatabases)
		exit(listAllDbs(&settings));
2986
	if (!settings.quiet && !settings.notty && !singleQuery && !qfilename)
2987 2988 2989
	{
		printf("Welcome to the POSTGRESQL interactive sql monitor:\n");
		printf("  Please read the file COPYRIGHT for copyright terms "
2990 2991 2992 2993 2994 2995
			   "of POSTGRESQL\n");

                if ( (version = selectVersion(&settings)) != NULL ) 
	                printf("[%s]\n", version); 

	        printf("\n");
2996 2997 2998 2999 3000
		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);
	}
3001 3002 3003 3004

	/*
	 * 20.06.97 ACRM See if we've got a /etc/psqlrc or .psqlrc file
	 */
3005
	if (!access("/etc/psqlrc", R_OK))
3006
		HandleSlashCmds(&settings, "\\i /etc/psqlrc", "");
3007 3008 3009 3010
	if ((home = getenv("HOME")) != NULL)
	{
		char	   *psqlrc = NULL,
				   *line = NULL;
3011

3012 3013
		if ((psqlrc = (char *) malloc(strlen(home) + 10)) != NULL)
		{
3014
			sprintf(psqlrc, "%s/.psqlrc", home);
3015 3016 3017 3018
			if (!access(psqlrc, R_OK))
			{
				if ((line = (char *) malloc(strlen(psqlrc) + 5)) != NULL)
				{
3019 3020 3021 3022 3023 3024 3025 3026 3027 3028
					sprintf(line, "\\i %s", psqlrc);
					HandleSlashCmds(&settings, line, "");
					free(line);
				}
			}
			free(psqlrc);
		}
	}
	/* End of check for psqlrc files */

3029 3030 3031 3032 3033 3034 3035
	if (qfilename || singleSlashCmd)
	{

		/*
		 * read in a file full of queries instead of reading in queries
		 * interactively
		 */
3036
		char	   *line;
3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048

		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, "");
3049
		free(line);
3050 3051 3052 3053
	}
	else
	{
		if (singleQuery)
3054
			successResult = SendQuery(&settings, singleQuery, NULL, NULL);
3055
		else
3056
			successResult = MainLoop(&settings, NULL, stdin);
3057
	}
3058 3059

	PQfinish(settings.db);
3060
	free(settings.opt.fieldSep);
3061
	if (settings.prompt)
3062
		free(settings.prompt);
3063 3064

	return !successResult;
3065 3066
}

3067
#define COPYBUFSIZ	8192
3068

3069
static bool
3070
handleCopyOut(PGconn *conn, FILE *copystream)
3071
{
3072 3073 3074
	bool		copydone;
	char		copybuf[COPYBUFSIZ];
	int			ret;
3075 3076 3077 3078 3079

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

	while (!copydone)
	{
3080
		ret = PQgetline(conn, copybuf, COPYBUFSIZ);
3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092

		if (copybuf[0] == '\\' &&
			copybuf[1] == '.' &&
			copybuf[2] == '\0')
		{
			copydone = true;	/* don't print this... */
		}
		else
		{
			fputs(copybuf, copystream);
			switch (ret)
			{
3093 3094 3095 3096 3097 3098 3099 3100
				case EOF:
					copydone = true;
					/* FALLTHROUGH */
				case 0:
					fputc('\n', copystream);
					break;
				case 1:
					break;
3101 3102
			}
		}
3103
	}
3104
	fflush(copystream);
3105
	return ! PQendcopy(conn);
3106 3107 3108
}


3109

3110
static bool
3111
handleCopyIn(PGconn *conn, const bool mustprompt, FILE *copystream)
3112
{
3113 3114 3115 3116 3117 3118 3119
	bool		copydone = false;
	bool		firstload;
	bool		linedone;
	char		copybuf[COPYBUFSIZ];
	char	   *s;
	int			buflen;
	int			c;
3120 3121 3122 3123 3124 3125

	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);
3126
	}
3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145
	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)
			{
3146
				PQputline(conn, "\\.");
3147 3148 3149 3150
				copydone = true;
				break;
			}
			*s = '\0';
3151
			PQputline(conn, copybuf);
3152 3153 3154 3155 3156 3157 3158
			if (firstload)
			{
				if (!strcmp(copybuf, "\\."))
					copydone = true;
				firstload = false;
			}
		}
3159
		PQputline(conn, "\n");
3160
	}
3161
	return ! PQendcopy(conn);
3162 3163
}

3164 3165


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

3170
static FILE *
3171
setFout(PsqlSettings *pset, char *fname)
3172
{
3173
	if (pset->queryFout && pset->queryFout != stdout)
3174
	{
3175 3176
		if (pset->pipe)
			pclose(pset->queryFout);
3177
		else
3178
			fclose(pset->queryFout);
3179 3180 3181
	}
	if (!fname)
	{
3182
		pset->queryFout = stdout;
3183
		pqsignal(SIGPIPE, SIG_DFL);
3184
	}
3185 3186 3187 3188 3189
	else
	{
		if (*fname == '|')
		{
			pqsignal(SIGPIPE, SIG_IGN);
3190
#ifndef __CYGWIN32__
3191
			pset->queryFout = popen(fname + 1, "w");
3192 3193 3194
#else
			pset->queryFout = popen(fname + 1, "wb");
#endif
3195
			pset->pipe = 1;
3196 3197 3198
		}
		else
		{
3199
			pset->queryFout = fopen(fname, "w");
3200
			pqsignal(SIGPIPE, SIG_DFL);
3201
			pset->pipe = 0;
3202
		}
3203
		if (!pset->queryFout)
3204 3205
		{
			perror(fname);
3206
			pset->queryFout = stdout;
3207
		}
3208
	}
3209
	return pset->queryFout;
3210
}
3211

3212 3213
static void
prompt_for_password(char *username, char *password)
3214
{
3215
	char		buf[512];
3216
	int			length;
3217

3218
#ifdef HAVE_TERMIOS_H
3219 3220
	struct termios t_orig,
				t;
3221

3222 3223
#endif

3224
	printf("Username: ");
3225
	fgets(username, 100, stdin);
3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238
	length = strlen(username);
	/* skip rest of the line */
	if (length > 0 && username[length - 1] != '\n')
	{
		do
		{
			fgets(buf, 512, stdin);
		} while (buf[strlen(buf) - 1] != '\n');
	}
	if (length > 0 && username[length - 1] == '\n')
		username[length - 1] = '\0';

	printf("Password: ");
3239
#ifdef HAVE_TERMIOS_H
3240 3241 3242 3243
	tcgetattr(0, &t);
	t_orig = t;
	t.c_lflag &= ~ECHO;
	tcsetattr(0, TCSADRAIN, &t);
3244
#endif
3245
	fgets(password, 100, stdin);
3246
#ifdef HAVE_TERMIOS_H
3247
	tcsetattr(0, TCSADRAIN, &t_orig);
3248 3249
#endif

3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262
	length = strlen(password);
	/* skip rest of the line */
	if (length > 0 && password[length - 1] != '\n')
	{
		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");
3263
}
3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288

static char *
selectVersion(PsqlSettings *pset)
{
#define PGVERSIONBUFSZ 128
	static char version[PGVERSIONBUFSZ+1];
	PGresult   *res;
	char	   *query = "select version();";

	if (!(res = PQexec(pset->db, query))) return(NULL);

	if (PQresultStatus(res) == PGRES_COMMAND_OK ||
	    PQresultStatus(res) == PGRES_TUPLES_OK  ) 
	{
		strncpy(version, PQgetvalue(res,0,0), PGVERSIONBUFSZ);
		version[PGVERSIONBUFSZ] = '\0';
		PQclear(res);
		return(version);
        }
	else 
	{
		PQclear(res);
		return(NULL);
	}
}