mirror of
https://github.com/postgres/postgres.git
synced 2025-04-22 23:02:54 +03:00
3318 lines
79 KiB
C
3318 lines
79 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* psql.c
|
|
* an interactive front-end to postgreSQL
|
|
*
|
|
* Copyright (c) 1996, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $Header: /cvsroot/pgsql/src/bin/psql/Attic/psql.c,v 1.185 1999/07/19 16:46:53 momjian Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#ifdef WIN32
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#include <io.h>
|
|
#else
|
|
#include <sys/param.h> /* for MAXPATHLEN */
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <ctype.h>
|
|
|
|
#include "postgres.h"
|
|
#include "libpq-fe.h"
|
|
#include "pqsignal.h"
|
|
#include "stringutils.h"
|
|
#include "psqlHelp.h"
|
|
|
|
#ifndef HAVE_STRDUP
|
|
#include "strdup.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_TERMIOS_H
|
|
#include <termios.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_GETOPT_H
|
|
#include <getopt.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_LIBREADLINE
|
|
#ifdef HAVE_READLINE_H
|
|
#include <readline.h>
|
|
#define USE_READLINE 1
|
|
#if defined(HAVE_HISTORY_H)
|
|
#include <history.h>
|
|
#define USE_HISTORY 1
|
|
#endif
|
|
#else
|
|
#if defined(HAVE_READLINE_READLINE_H)
|
|
#include <readline/readline.h>
|
|
#define USE_READLINE 1
|
|
#if defined(HAVE_READLINE_HISTORY_H)
|
|
#include <readline/history.h>
|
|
#define USE_HISTORY 1
|
|
#endif
|
|
#endif
|
|
#endif
|
|
#if defined(HAVE_HISTORY) && !defined(USE_HISTORY)
|
|
#define USE_HISTORY 1
|
|
#endif
|
|
#endif
|
|
|
|
|
|
#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)
|
|
#define pqsignal(x,y)
|
|
#define MAXPATHLEN MAX_PATH
|
|
#define R_OK 0
|
|
|
|
/* getopt is not in the standard includes on Win32 */
|
|
extern char *optarg;
|
|
extern int optind,
|
|
opterr,
|
|
optopt;
|
|
int getopt(int, char *const[], const char *);
|
|
char *__progname = "psql";
|
|
|
|
#endif
|
|
|
|
#ifdef MULTIBYTE
|
|
/* flag to indicate if PGCLIENTENCODING has been set by a user */
|
|
static char *has_client_encoding = 0;
|
|
|
|
#endif
|
|
|
|
/* This prompt string is assumed to have at least 3 characters by code in MainLoop().
|
|
* A character two characters from the end is replaced each time by a mode character.
|
|
*/
|
|
#define PROMPT "=> "
|
|
|
|
#define PROMPT_READY '='
|
|
#define PROMPT_CONTINUE '-'
|
|
#define PROMPT_COMMENT '*'
|
|
#define PROMPT_SINGLEQUOTE '\''
|
|
#define PROMPT_DOUBLEQUOTE '"'
|
|
|
|
/* Backslash command handling:
|
|
* 0 - send currently constructed query to backend (i.e. we got a \g)
|
|
* 1 - skip processing of this line, continue building up query
|
|
* 2 - terminate processing of this query entirely
|
|
* 3 - new query supplied by edit
|
|
*/
|
|
#define CMD_UNKNOWN -1
|
|
#define CMD_SEND 0
|
|
#define CMD_SKIP_LINE 1
|
|
#define CMD_TERMINATE 2
|
|
#define CMD_NEWEDIT 3
|
|
|
|
#define MAX_QUERY_BUFFER MAX_QUERY_SIZE
|
|
|
|
#define COPYBUFSIZ 8192
|
|
|
|
#define DEFAULT_FIELD_SEP "|"
|
|
#define DEFAULT_EDITOR "vi"
|
|
#define DEFAULT_SHELL "/bin/sh"
|
|
|
|
typedef struct _psqlSettings
|
|
{
|
|
PGconn *db; /* connection to backend */
|
|
FILE *queryFout; /* where to send the query results */
|
|
PQprintOpt opt; /* options to be passed to PQprint */
|
|
char *prompt; /* prompt to display */
|
|
char *gfname; /* one-shot file output argument for \g */
|
|
bool notty; /* input or output is not a tty */
|
|
bool pipe; /* queryFout is from a popen() */
|
|
bool echoQuery; /* echo the query before sending it */
|
|
bool echoAllQueries; /* echo all queries before sending it */
|
|
bool quiet; /* run quietly, no messages, no promt */
|
|
bool singleStep; /* prompt before for each query */
|
|
bool singleLineMode; /* query terminated by newline */
|
|
bool useReadline; /* use libreadline routines */
|
|
bool getPassword; /* prompt the user for a username and
|
|
* password */
|
|
} PsqlSettings;
|
|
|
|
/*
|
|
* 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 = NULL; /* current source of command input */
|
|
static bool cur_cmd_interactive = false; /* is it an interactive
|
|
* source? */
|
|
|
|
|
|
#ifdef TIOCGWINSZ
|
|
struct winsize screen_size;
|
|
|
|
#else
|
|
struct winsize
|
|
{
|
|
int ws_row;
|
|
int ws_col;
|
|
} screen_size;
|
|
|
|
#endif
|
|
|
|
/* declarations for functions in this file */
|
|
static void usage(char *progname);
|
|
static void slashUsage();
|
|
static bool handleCopyOut(PGconn *conn, FILE *copystream);
|
|
static bool handleCopyIn(PGconn *conn, const bool mustprompt,
|
|
FILE *copystream);
|
|
static int tableList(PsqlSettings *pset, bool deep_tablelist,
|
|
char info_type, bool system_tables);
|
|
static int tableDesc(PsqlSettings *pset, char *table, FILE *fout);
|
|
static int objectDescription(PsqlSettings *pset, char *object);
|
|
static int rightsList(PsqlSettings *pset);
|
|
static void emitNtimes(FILE *fout, const char *str, int N);
|
|
static void prompt_for_password(char *username, char *password);
|
|
|
|
static char *gets_noreadline(char *prompt, FILE *source);
|
|
static char *gets_readline(char *prompt, FILE *source);
|
|
static char *gets_fromFile(char *prompt, FILE *source);
|
|
static int listAllDbs(PsqlSettings *pset);
|
|
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);
|
|
static int MainLoop(PsqlSettings *pset, char *query, FILE *source);
|
|
static FILE *setFout(PsqlSettings *pset, char *fname);
|
|
|
|
static char *selectVersion(PsqlSettings *pset);
|
|
|
|
/*
|
|
* usage print out usage for command line arguments
|
|
*/
|
|
|
|
static void
|
|
usage(char *progname)
|
|
{
|
|
fprintf(stderr, "Usage: %s [options] [dbname]\n", progname);
|
|
fprintf(stderr, "\t -a authsvc set authentication service\n");
|
|
fprintf(stderr, "\t -A turn off alignment when printing out attributes\n");
|
|
fprintf(stderr, "\t -c query run single query (slash commands too)\n");
|
|
fprintf(stderr, "\t -d dbName specify database name\n");
|
|
fprintf(stderr, "\t -e echo the query sent to the backend\n");
|
|
fprintf(stderr, "\t -E echo all queries sent to the backend\n");
|
|
fprintf(stderr, "\t -f filename use file as a source of queries\n");
|
|
fprintf(stderr, "\t -F sep set the field separator (default is '|')\n");
|
|
fprintf(stderr, "\t -h host set database server host\n");
|
|
fprintf(stderr, "\t -H turn on html3.0 table output\n");
|
|
fprintf(stderr, "\t -l list available databases\n");
|
|
fprintf(stderr, "\t -n don't use readline library\n");
|
|
fprintf(stderr, "\t -o filename send output to filename or (|pipe)\n");
|
|
fprintf(stderr, "\t -p port set port number\n");
|
|
fprintf(stderr, "\t -q run quietly (no messages, no prompts)\n");
|
|
fprintf(stderr, "\t -s single step mode (prompts for each query)\n");
|
|
fprintf(stderr, "\t -S single line mode (i.e. query terminated by newline)\n");
|
|
fprintf(stderr, "\t -t turn off printing of headings and row count\n");
|
|
fprintf(stderr, "\t -T html set html3.0 table command options (cf. -H)\n");
|
|
fprintf(stderr, "\t -u ask for a username and password for authentication\n");
|
|
fprintf(stderr, "\t -x turn on expanded output (field names on left)\n");
|
|
exit(1);
|
|
}
|
|
|
|
/*
|
|
* slashUsage print out usage for the backslash commands
|
|
*/
|
|
|
|
static char *
|
|
on(bool f)
|
|
{
|
|
return f ? "on" : "off";
|
|
}
|
|
|
|
static void
|
|
slashUsage(PsqlSettings *pset)
|
|
{
|
|
int usePipe = 0;
|
|
char *pagerenv;
|
|
FILE *fout;
|
|
|
|
#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
|
|
|
|
if (pset->notty == 0 &&
|
|
(pagerenv = getenv("PAGER")) &&
|
|
(pagerenv[0] != '\0') &&
|
|
screen_size.ws_row <= 35 &&
|
|
(fout = popen(pagerenv, "w")))
|
|
{
|
|
usePipe = 1;
|
|
pqsignal(SIGPIPE, SIG_IGN);
|
|
}
|
|
else
|
|
fout = stdout;
|
|
|
|
/* if you add/remove a line here, change the row test above */
|
|
fprintf(fout, " \\? -- help\n");
|
|
fprintf(fout, " \\a -- toggle field-alignment (currently %s)\n", on(pset->opt.align));
|
|
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));
|
|
fprintf(fout, " \\copy table {from | to} <fname>\n");
|
|
fprintf(fout, " \\d [<table>] -- list tables and indices, columns in <table>, or * for all\n");
|
|
fprintf(fout, " \\da -- list aggregates\n");
|
|
fprintf(fout, " \\dd [<object>]- list comment for table, field, type, function, or operator.\n");
|
|
fprintf(fout, " \\df -- list functions\n");
|
|
fprintf(fout, " \\di -- list only indices\n");
|
|
fprintf(fout, " \\do -- list operators\n");
|
|
fprintf(fout, " \\ds -- list only sequences\n");
|
|
fprintf(fout, " \\dS -- list system tables and indexes\n");
|
|
fprintf(fout, " \\dt -- list only tables\n");
|
|
fprintf(fout, " \\dT -- list types\n");
|
|
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");
|
|
fprintf(fout, " \\f [<sep>] -- change field separater (currently '%s')\n", pset->opt.fieldSep);
|
|
fprintf(fout, " \\g [<fname>] [|<cmd>] -- send query to backend [and results in <fname> or pipe]\n");
|
|
fprintf(fout, " \\h [<cmd>] -- help on syntax of sql commands, * for all commands\n");
|
|
fprintf(fout, " \\H -- toggle html3 output (currently %s)\n", on(pset->opt.html3));
|
|
fprintf(fout, " \\i <fname> -- read and execute queries from filename\n");
|
|
fprintf(fout, " \\l -- list all databases\n");
|
|
fprintf(fout, " \\m -- toggle monitor-like table display (currently %s)\n", on(pset->opt.standard));
|
|
fprintf(fout, " \\o [<fname>] [|<cmd>] -- send all query results to stdout, <fname>, or pipe\n");
|
|
fprintf(fout, " \\p -- print the current query buffer\n");
|
|
fprintf(fout, " \\q -- quit\n");
|
|
fprintf(fout, " \\r -- reset(clear) the query buffer\n");
|
|
fprintf(fout, " \\s [<fname>] -- print history or save it in <fname>\n");
|
|
fprintf(fout, " \\t -- toggle table headings and row count (currently %s)\n", on(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));
|
|
fprintf(fout, " \\w <fname> -- output current buffer to a file\n");
|
|
fprintf(fout, " \\z -- list current grant/revoke permissions\n");
|
|
fprintf(fout, " \\! [<cmd>] -- shell escape or command\n");
|
|
|
|
if (usePipe)
|
|
{
|
|
pclose(fout);
|
|
pqsignal(SIGPIPE, SIG_DFL);
|
|
}
|
|
}
|
|
|
|
static PGresult *
|
|
PSQLexec(PsqlSettings *pset, char *query)
|
|
{
|
|
PGresult *res;
|
|
|
|
if (pset->echoAllQueries)
|
|
{
|
|
fprintf(stderr, "QUERY: %s\n", query);
|
|
fprintf(stderr, "\n");
|
|
fflush(stderr);
|
|
}
|
|
|
|
res = PQexec(pset->db, query);
|
|
if (!res)
|
|
fputs(PQerrorMessage(pset->db), stderr);
|
|
else
|
|
{
|
|
if (PQresultStatus(res) == PGRES_COMMAND_OK ||
|
|
PQresultStatus(res) == PGRES_TUPLES_OK)
|
|
return res;
|
|
if (!pset->quiet)
|
|
fputs(PQerrorMessage(pset->db), stderr);
|
|
PQclear(res);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
* is safe only because PQrequestCancel is carefully written to
|
|
* make it so. We have to be very careful what else we do in the
|
|
* signal handler.
|
|
* Writing on stderr is potentially dangerous, if the signal interrupted
|
|
* some stdio operation on stderr. On Unix we can avoid trouble by using
|
|
* write() instead; on Windows that's probably not workable, but we can
|
|
* at least avoid trusting printf by using the more primitive fputs.
|
|
*/
|
|
|
|
static PGconn *cancelConn = NULL; /* connection to try cancel on */
|
|
|
|
static void
|
|
safe_write_stderr(const char *s)
|
|
{
|
|
#ifdef WIN32
|
|
fputs(s, stderr);
|
|
#else
|
|
write(fileno(stderr), s, strlen(s));
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
handle_sigint(SIGNAL_ARGS)
|
|
{
|
|
if (cancelConn == NULL)
|
|
exit(1); /* accept signal if no connection */
|
|
/* Try to send cancel request */
|
|
if (PQrequestCancel(cancelConn))
|
|
safe_write_stderr("\nCANCEL request sent\n");
|
|
else
|
|
{
|
|
safe_write_stderr("\nCannot send cancel request:\n");
|
|
safe_write_stderr(PQerrorMessage(cancelConn));
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* listAllDbs
|
|
*
|
|
* list all the databases in the system returns 0 if all went well
|
|
*
|
|
*
|
|
*/
|
|
|
|
static int
|
|
listAllDbs(PsqlSettings *pset)
|
|
{
|
|
PGresult *results;
|
|
char *query = "select * from pg_database;";
|
|
|
|
if (!(results = PSQLexec(pset, query)))
|
|
return 1;
|
|
else
|
|
{
|
|
PQprint(pset->queryFout,
|
|
results,
|
|
&pset->opt);
|
|
PQclear(results);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* List The Database Tables returns 0 if all went well
|
|
*
|
|
*/
|
|
static int
|
|
tableList(PsqlSettings *pset, bool deep_tablelist, char info_type,
|
|
bool system_tables)
|
|
{
|
|
char listbuf[512];
|
|
int nColumns;
|
|
int i;
|
|
char *rk;
|
|
char *rr;
|
|
PGresult *res;
|
|
int usePipe = 0;
|
|
bool haveIndexes = false;
|
|
char *pagerenv;
|
|
FILE *fout;
|
|
|
|
#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
|
|
|
|
listbuf[0] = '\0';
|
|
strcat(listbuf, "SELECT usename, relname, relkind, relhasrules ");
|
|
strcat(listbuf, "FROM pg_class, pg_user ");
|
|
strcat(listbuf, "WHERE usesysid = relowner ");
|
|
switch (info_type)
|
|
{
|
|
case 't':
|
|
strcat(listbuf, "and ( relkind = 'r') ");
|
|
break;
|
|
case 'i':
|
|
strcat(listbuf, "and ( relkind = 'i') ");
|
|
haveIndexes = true;
|
|
break;
|
|
case 'S':
|
|
strcat(listbuf, "and ( relkind = 'S') ");
|
|
break;
|
|
case 'b':
|
|
default:
|
|
strcat(listbuf, "and ( relkind = 'r' OR relkind = 'i' OR relkind = 'S') ");
|
|
haveIndexes = true;
|
|
break;
|
|
}
|
|
if (!system_tables)
|
|
strcat(listbuf, "and relname !~ '^pg_' ");
|
|
else
|
|
strcat(listbuf, "and relname ~ '^pg_' ");
|
|
/*
|
|
* Large-object relations are automatically ignored because they have
|
|
* relkind 'l'. However, we want to ignore their indexes as well.
|
|
* The clean way to do that would be to do a join to find out which
|
|
* table each index is for. The ugly but fast way is to know that
|
|
* large object indexes have names starting with 'xinx'.
|
|
*/
|
|
if (haveIndexes)
|
|
strcat(listbuf, "and (relkind != 'i' OR relname !~ '^xinx') ");
|
|
|
|
strcat(listbuf, " ORDER BY relname ");
|
|
if (!(res = PSQLexec(pset, listbuf)))
|
|
return -1;
|
|
/* first, print out the attribute names */
|
|
nColumns = PQntuples(res);
|
|
if (nColumns > 0)
|
|
{
|
|
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;
|
|
|
|
if (deep_tablelist)
|
|
{
|
|
/* describe everything here */
|
|
char **table;
|
|
|
|
table = (char **) malloc(nColumns * sizeof(char *));
|
|
if (table == NULL)
|
|
perror("malloc");
|
|
|
|
/* load table table */
|
|
|
|
/*
|
|
* Put double quotes around the table name to allow for
|
|
* mixed-case and whitespaces in the table name. - BGA
|
|
* 1998-11-14
|
|
*/
|
|
for (i = 0; i < nColumns; i++)
|
|
{
|
|
table[i] = (char *) malloc(PQgetlength(res, i, 1) * sizeof(char) + 3);
|
|
if (table[i] == NULL)
|
|
perror("malloc");
|
|
strcpy(table[i], "\"");
|
|
strcat(table[i], PQgetvalue(res, i, 1));
|
|
strcat(table[i], "\"");
|
|
}
|
|
|
|
PQclear(res);
|
|
for (i = 0; i < nColumns; i++)
|
|
tableDesc(pset, table[i], fout);
|
|
free(table);
|
|
}
|
|
else
|
|
{
|
|
/* Display the information */
|
|
|
|
fprintf(fout, "Database = %s\n", PQdb(pset->db));
|
|
fprintf(fout, " +------------------+----------------------------------+----------+\n");
|
|
fprintf(fout, " | Owner | Relation | Type |\n");
|
|
fprintf(fout, " +------------------+----------------------------------+----------+\n");
|
|
|
|
/* next, print out the instances */
|
|
for (i = 0; i < PQntuples(res); i++)
|
|
{
|
|
fprintf(fout, " | %-16.16s", PQgetvalue(res, i, 0));
|
|
fprintf(fout, " | %-32.32s | ", PQgetvalue(res, i, 1));
|
|
rk = PQgetvalue(res, i, 2);
|
|
rr = PQgetvalue(res, i, 3);
|
|
if (strcmp(rk, "r") == 0)
|
|
fprintf(fout, "%-8.8s |", (rr[0] == 't') ? "view?" : "table");
|
|
else if (strcmp(rk, "i") == 0)
|
|
fprintf(fout, "%-8.8s |", "index");
|
|
else
|
|
fprintf(fout, "%-8.8s |", "sequence");
|
|
fprintf(fout, "\n");
|
|
}
|
|
fprintf(fout, " +------------------+----------------------------------+----------+\n");
|
|
fprintf(fout, "\n");
|
|
PQclear(res);
|
|
}
|
|
if (usePipe)
|
|
{
|
|
pclose(fout);
|
|
pqsignal(SIGPIPE, SIG_DFL);
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
else
|
|
{
|
|
PQclear(res);
|
|
switch (info_type)
|
|
{
|
|
case 't':
|
|
fprintf(stderr, "Couldn't find any tables!\n");
|
|
break;
|
|
case 'i':
|
|
fprintf(stderr, "Couldn't find any indices!\n");
|
|
break;
|
|
case 'S':
|
|
fprintf(stderr, "Couldn't find any sequences!\n");
|
|
break;
|
|
case 'b':
|
|
default:
|
|
fprintf(stderr, "Couldn't find any tables, sequences or indices!\n");
|
|
break;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* List Tables Grant/Revoke Permissions returns 0 if all went well
|
|
*
|
|
*/
|
|
static int
|
|
rightsList(PsqlSettings *pset)
|
|
{
|
|
char listbuf[512];
|
|
int nColumns;
|
|
int i;
|
|
int maxCol1Len;
|
|
int maxCol2Len;
|
|
int usePipe = 0;
|
|
char *pagerenv;
|
|
FILE *fout;
|
|
PGresult *res;
|
|
|
|
#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
|
|
|
|
listbuf[0] = '\0';
|
|
strcat(listbuf, "SELECT relname, relacl ");
|
|
strcat(listbuf, "FROM pg_class ");
|
|
/* Currently, we ignore indexes since they have no meaningful rights */
|
|
strcat(listbuf, "WHERE ( relkind = 'r' OR relkind = 'S') ");
|
|
strcat(listbuf, " and relname !~ '^pg_'");
|
|
strcat(listbuf, " ORDER BY relname ");
|
|
if (!(res = PSQLexec(pset, listbuf)))
|
|
return -1;
|
|
/* first, print out the attribute names */
|
|
nColumns = PQntuples(res);
|
|
if (nColumns > 0)
|
|
{
|
|
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;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* Display the information */
|
|
|
|
fprintf(fout, "Database = %s\n", PQdb(pset->db));
|
|
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");
|
|
|
|
/* next, print out the instances */
|
|
for (i = 0; i < PQntuples(res); i++)
|
|
{
|
|
fprintf(fout, " | %-*s | %-*s |\n",
|
|
maxCol1Len, PQgetvalue(res, i, 0),
|
|
maxCol2Len, PQgetvalue(res, i, 1));
|
|
}
|
|
|
|
fprintf(fout, " +");
|
|
emitNtimes(fout, "-", maxCol1Len + 2);
|
|
fprintf(fout, "+");
|
|
emitNtimes(fout, "-", maxCol2Len + 2);
|
|
fprintf(fout, "+\n");
|
|
|
|
PQclear(res);
|
|
if (usePipe)
|
|
{
|
|
pclose(fout);
|
|
pqsignal(SIGPIPE, SIG_DFL);
|
|
}
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
PQclear(res);
|
|
fprintf(stderr, "Couldn't find any tables!\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static void
|
|
emitNtimes(FILE *fout, const char *str, int N)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < N; i++)
|
|
fputs(str, fout);
|
|
}
|
|
|
|
/*
|
|
* Describe a table
|
|
*
|
|
* Describe the columns in a database table. returns 0 if all went well
|
|
*
|
|
*
|
|
*/
|
|
static int
|
|
tableDesc(PsqlSettings *pset, char *table, FILE *fout)
|
|
{
|
|
char descbuf[512];
|
|
int nColumns,
|
|
nIndices;
|
|
char *rtype;
|
|
char *rnotnull;
|
|
char *rhasdef;
|
|
int i;
|
|
int attlen,
|
|
atttypmod;
|
|
PGresult *res,
|
|
*res2;
|
|
int usePipe = 0;
|
|
char *pagerenv;
|
|
|
|
#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
|
|
|
|
/* Build the query */
|
|
|
|
/*
|
|
* if the table name is surrounded by double-quotes, then don't
|
|
* convert case
|
|
*/
|
|
if (*table == '"')
|
|
{
|
|
table++;
|
|
if (*(table + strlen(table) - 1) == '"')
|
|
*(table + strlen(table) - 1) = '\0';
|
|
}
|
|
else
|
|
{
|
|
#ifdef MULTIBYTE
|
|
for (i = 0; table[i]; i += PQmblen(table + i))
|
|
#else
|
|
for (i = 0; table[i]; i++)
|
|
#endif
|
|
if (isascii((unsigned char) table[i]) &&
|
|
isupper(table[i]))
|
|
table[i] = tolower(table[i]);
|
|
}
|
|
|
|
descbuf[0] = '\0';
|
|
strcat(descbuf, "SELECT a.attnum, a.attname, t.typname, a.attlen, ");
|
|
strcat(descbuf, "a.atttypmod, a.attnotnull, a.atthasdef ");
|
|
strcat(descbuf, "FROM pg_class c, pg_attribute a, pg_type t ");
|
|
strcat(descbuf, "WHERE c.relname = '");
|
|
strcat(descbuf, table);
|
|
strcat(descbuf, "'");
|
|
strcat(descbuf, " and a.attnum > 0 ");
|
|
strcat(descbuf, " and a.attrelid = c.oid ");
|
|
strcat(descbuf, " and a.atttypid = t.oid ");
|
|
strcat(descbuf, " ORDER BY attnum ");
|
|
if (!(res = PSQLexec(pset, descbuf)))
|
|
return -1;
|
|
/* first, print out the attribute names */
|
|
nColumns = PQntuples(res);
|
|
if (nColumns > 0)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/*
|
|
* Display the information
|
|
*/
|
|
if (PQntuples(res2))
|
|
{
|
|
|
|
/*
|
|
* display the query. o * -Ryan 2/14/99
|
|
*/
|
|
fprintf(fout, "View = %s\n", table);
|
|
fprintf(fout, "Query = %s\n", PQgetvalue(res2, 0, 1));
|
|
}
|
|
else
|
|
fprintf(fout, "Table = %s\n", table);
|
|
PQclear(res2);
|
|
|
|
fprintf(fout, "+----------------------------------+----------------------------------+-------+\n");
|
|
fprintf(fout, "| Field | Type | Length|\n");
|
|
fprintf(fout, "+----------------------------------+----------------------------------+-------+\n");
|
|
|
|
/* next, print out the instances */
|
|
for (i = 0; i < PQntuples(res); i++)
|
|
{
|
|
char type_str[33];
|
|
|
|
fprintf(fout, "| %-32.32s | ", PQgetvalue(res, i, 1));
|
|
rtype = PQgetvalue(res, i, 2);
|
|
attlen = atoi(PQgetvalue(res, i, 3));
|
|
atttypmod = atoi(PQgetvalue(res, i, 4));
|
|
rnotnull = PQgetvalue(res, i, 5);
|
|
rhasdef = PQgetvalue(res, i, 6);
|
|
|
|
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] == '_')
|
|
{
|
|
strcpy(type_str, rtype + 1);
|
|
strncat(type_str, "[]", 32 - strlen(type_str));
|
|
type_str[32] = '\0';
|
|
}
|
|
|
|
if (rnotnull[0] == 't')
|
|
{
|
|
strncat(type_str, " not null", 32 - strlen(type_str));
|
|
type_str[32] = '\0';
|
|
}
|
|
if (rhasdef[0] == 't')
|
|
{
|
|
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;
|
|
strcat(type_str, " default ");
|
|
strncat(type_str, PQgetvalue(res2, 0, 0), 32 - strlen(type_str));
|
|
type_str[32] = '\0';
|
|
}
|
|
fprintf(fout, "%-32.32s |", type_str);
|
|
|
|
if (strcmp(rtype, "text") == 0)
|
|
fprintf(fout, "%6s |", "var");
|
|
else if (strcmp(rtype, "bpchar") == 0 ||
|
|
strcmp(rtype, "varchar") == 0)
|
|
fprintf(fout, "%6i |", atttypmod != -1 ? atttypmod - VARHDRSZ : 0);
|
|
else if (strcmp(rtype, "numeric") == 0)
|
|
fprintf(fout, "%3i.%-2i |",
|
|
((atttypmod - VARHDRSZ) >> 16) & 0xffff,
|
|
(atttypmod - VARHDRSZ) & 0xffff);
|
|
else
|
|
{
|
|
if (attlen > 0)
|
|
fprintf(fout, "%6i |", attlen);
|
|
else
|
|
fprintf(fout, "%6s |", "var");
|
|
}
|
|
fprintf(fout, "\n");
|
|
}
|
|
fprintf(fout, "+----------------------------------+----------------------------------+-------+\n");
|
|
PQclear(res);
|
|
|
|
/* 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)
|
|
{
|
|
|
|
/*
|
|
* Display the information
|
|
*/
|
|
|
|
if (nIndices == 1)
|
|
fprintf(fout, "Index: ");
|
|
else
|
|
fprintf(fout, "Indices: ");
|
|
|
|
/* next, print out the instances */
|
|
for (i = 0; i < PQntuples(res); i++)
|
|
if (i == 0)
|
|
fprintf(fout, "%s\n", PQgetvalue(res, i, 0));
|
|
else
|
|
fprintf(fout, " %s\n", PQgetvalue(res, i, 0));
|
|
fprintf(fout, "\n");
|
|
}
|
|
PQclear(res);
|
|
}
|
|
if (usePipe)
|
|
{
|
|
pclose(fout);
|
|
pqsignal(SIGPIPE, SIG_DFL);
|
|
}
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
PQclear(res);
|
|
fprintf(stderr, "Couldn't find table %s!\n", table);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get object comments
|
|
*
|
|
* Describe the columns in a database table. returns 0 if all went well
|
|
*
|
|
*
|
|
*/
|
|
static int
|
|
objectDescription(PsqlSettings *pset, char *object)
|
|
{
|
|
char descbuf[512];
|
|
PGresult *res;
|
|
int i;
|
|
bool success;
|
|
|
|
/* Build the query */
|
|
|
|
while (isspace(*object))
|
|
object++;
|
|
|
|
/*
|
|
* if the object name is surrounded by double-quotes, then don't
|
|
* convert case
|
|
*/
|
|
if (*object == '"')
|
|
{
|
|
object++;
|
|
if (*(object + strlen(object) - 1) == '"')
|
|
*(object + strlen(object) - 1) = '\0';
|
|
}
|
|
else
|
|
{
|
|
#ifdef MULTIBYTE
|
|
for (i = 0; object[i]; i += PQmblen(object + i))
|
|
#else
|
|
for (i = 0; object[i]; i++)
|
|
#endif
|
|
if (isupper(object[i]))
|
|
object[i] = tolower(object[i]);
|
|
}
|
|
|
|
descbuf[0] = '\0';
|
|
if (strchr(object, '.') != NULL)
|
|
{
|
|
char table[NAMEDATALEN],
|
|
column[NAMEDATALEN];
|
|
|
|
StrNCpy(table, object,
|
|
((strchr(object, '.') - object + 1) < NAMEDATALEN) ?
|
|
(strchr(object, '.') - object + 1) : NAMEDATALEN);
|
|
StrNCpy(column, strchr(object, '.') + 1, NAMEDATALEN);
|
|
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 ");
|
|
strcat(descbuf, " pg_attribute.oid = pg_description.objoid ");
|
|
if (!(res = PSQLexec(pset, descbuf)))
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
strcat(descbuf, "SELECT DISTINCT description ");
|
|
strcat(descbuf, "FROM pg_class, pg_description ");
|
|
strcat(descbuf, "WHERE pg_class.relname ~ '^");
|
|
strcat(descbuf, object);
|
|
strcat(descbuf, "'");
|
|
strcat(descbuf, " and pg_class.oid = pg_description.objoid ");
|
|
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 ");
|
|
strcat(descbuf, "WHERE pg_type.typname ~ '^");
|
|
strcat(descbuf, object);
|
|
strcat(descbuf, "' and ");
|
|
strcat(descbuf, " pg_type.oid = pg_description.objoid ");
|
|
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_proc, pg_description ");
|
|
strcat(descbuf, "WHERE pg_proc.proname ~ '^");
|
|
strcat(descbuf, object);
|
|
strcat(descbuf, "'");
|
|
strcat(descbuf, " and pg_proc.oid = pg_description.objoid ");
|
|
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_operator, pg_description ");
|
|
strcat(descbuf, "WHERE pg_operator.oprname ~ '^");
|
|
strcat(descbuf, object);
|
|
strcat(descbuf, "'");
|
|
/* operator descriptions are attached to the proc */
|
|
strcat(descbuf, " and RegprocToOid(pg_operator.oprcode) = pg_description.objoid ");
|
|
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_aggregate, pg_description ");
|
|
strcat(descbuf, "WHERE pg_aggregate.aggname ~ '^");
|
|
strcat(descbuf, object);
|
|
strcat(descbuf, "'");
|
|
strcat(descbuf, " and pg_aggregate.oid = pg_description.objoid ");
|
|
if (!(res = PSQLexec(pset, descbuf)))
|
|
return -1;
|
|
else if (PQntuples(res) <= 0)
|
|
{
|
|
PQclear(res);
|
|
descbuf[0] = '\0';
|
|
strcat(descbuf, "SELECT 'no description' as description ");
|
|
if (!(res = PSQLexec(pset, descbuf)))
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PQclear(res);
|
|
|
|
success = SendQuery(pset, descbuf, NULL, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
typedef char *(*READ_ROUTINE) (char *prompt, FILE *source);
|
|
|
|
/*
|
|
* gets_noreadline prompt source gets a line of input without calling
|
|
* readline, the source is ignored
|
|
*/
|
|
static char *
|
|
gets_noreadline(char *prompt, FILE *source)
|
|
{
|
|
fputs(prompt, stdout);
|
|
fflush(stdout);
|
|
return gets_fromFile(prompt, stdin);
|
|
}
|
|
|
|
/*
|
|
* gets_readline prompt source the routine to get input from GNU readline(),
|
|
* the source is ignored the prompt argument is used as the prompting string
|
|
*/
|
|
static char *
|
|
gets_readline(char *prompt, FILE *source)
|
|
{
|
|
char *s;
|
|
|
|
#ifdef USE_READLINE
|
|
s = readline(prompt);
|
|
#else
|
|
char buf[500];
|
|
|
|
printf("%s", prompt);
|
|
s = fgets(buf, 500, stdin);
|
|
#endif
|
|
fputc('\r', stdout);
|
|
fflush(stdout);
|
|
return s;
|
|
}
|
|
|
|
/*
|
|
* gets_fromFile prompt source
|
|
*
|
|
* the routine to read from a file, the prompt argument is ignored the source
|
|
* argument is a FILE *
|
|
*/
|
|
static char *
|
|
gets_fromFile(char *prompt, FILE *source)
|
|
{
|
|
char *line;
|
|
|
|
line = malloc(MAX_QUERY_BUFFER);
|
|
|
|
/* read up to MAX_QUERY_BUFFER characters */
|
|
if (fgets(line, MAX_QUERY_BUFFER, source) == NULL)
|
|
{
|
|
free(line);
|
|
return NULL;
|
|
}
|
|
|
|
line[MAX_QUERY_BUFFER - 1] = '\0'; /* this is unnecessary, I think */
|
|
if (strlen(line) == MAX_QUERY_BUFFER - 1)
|
|
{
|
|
fprintf(stderr, "line read exceeds maximum length. Truncating at %d\n",
|
|
MAX_QUERY_BUFFER - 1);
|
|
}
|
|
return line;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
static bool
|
|
SendQuery(PsqlSettings *pset, const char *query,
|
|
FILE *copy_in_stream, FILE *copy_out_stream)
|
|
{
|
|
bool success = false;
|
|
PGresult *results;
|
|
PGnotify *notify;
|
|
|
|
if (pset->singleStep)
|
|
fprintf(stdout, "\n**************************************"
|
|
"*****************************************\n");
|
|
|
|
if (pset->echoQuery || pset->singleStep)
|
|
{
|
|
fprintf(stderr, "QUERY: %s\n", query);
|
|
fflush(stderr);
|
|
}
|
|
if (pset->singleStep)
|
|
{
|
|
fprintf(stdout, "\n**************************************"
|
|
"*****************************************\n");
|
|
fflush(stdout);
|
|
printf("\npress return to continue ..\n");
|
|
gets_fromFile("", stdin);
|
|
}
|
|
results = PQexec(pset->db, query);
|
|
if (results == NULL)
|
|
{
|
|
fprintf(stderr, "%s", PQerrorMessage(pset->db));
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
switch (PQresultStatus(results))
|
|
{
|
|
case PGRES_TUPLES_OK:
|
|
if (pset->gfname)
|
|
{
|
|
PsqlSettings settings_copy = *pset;
|
|
FILE *fp;
|
|
|
|
settings_copy.queryFout = stdout;
|
|
fp = setFout(&settings_copy, pset->gfname);
|
|
if (!fp || fp == stdout)
|
|
{
|
|
success = false;
|
|
break;
|
|
}
|
|
PQprint(fp,
|
|
results,
|
|
&pset->opt);
|
|
if (settings_copy.pipe)
|
|
pclose(fp);
|
|
else
|
|
fclose(fp);
|
|
free(pset->gfname);
|
|
pset->gfname = NULL;
|
|
success = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
success = true;
|
|
PQprint(pset->queryFout,
|
|
results,
|
|
&(pset->opt));
|
|
fflush(pset->queryFout);
|
|
}
|
|
break;
|
|
case PGRES_EMPTY_QUERY:
|
|
success = true;
|
|
break;
|
|
case PGRES_COMMAND_OK:
|
|
success = true;
|
|
if (!pset->quiet)
|
|
printf("%s\n", PQcmdStatus(results));
|
|
break;
|
|
case PGRES_COPY_OUT:
|
|
if (copy_out_stream)
|
|
success = handleCopyOut(pset->db, copy_out_stream);
|
|
else
|
|
{
|
|
if (pset->queryFout == stdout && !pset->quiet)
|
|
printf("Copy command returns...\n");
|
|
|
|
success = handleCopyOut(pset->db, pset->queryFout);
|
|
}
|
|
break;
|
|
case PGRES_COPY_IN:
|
|
if (copy_in_stream)
|
|
success = handleCopyIn(pset->db, false, copy_in_stream);
|
|
else
|
|
success = handleCopyIn(pset->db,
|
|
cur_cmd_interactive && !pset->quiet,
|
|
cur_cmd_source);
|
|
break;
|
|
case PGRES_NONFATAL_ERROR:
|
|
case PGRES_FATAL_ERROR:
|
|
case PGRES_BAD_RESPONSE:
|
|
success = false;
|
|
fprintf(stderr, "%s", PQerrorMessage(pset->db));
|
|
break;
|
|
}
|
|
|
|
if (PQstatus(pset->db) == CONNECTION_BAD)
|
|
{
|
|
fprintf(stderr,
|
|
"We have lost the connection to the backend, so "
|
|
"further processing is impossible. "
|
|
"Terminating.\n");
|
|
exit(2); /* we are out'ta here */
|
|
}
|
|
/* check for asynchronous returns */
|
|
while ((notify = PQnotifies(pset->db)) != NULL)
|
|
{
|
|
fprintf(stderr,
|
|
"ASYNC NOTIFY of '%s' from backend pid '%d' received\n",
|
|
notify->relname, notify->be_pid);
|
|
free(notify);
|
|
}
|
|
if (results)
|
|
PQclear(results);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
editFile(char *fname)
|
|
{
|
|
char *editorName;
|
|
char *sys;
|
|
|
|
editorName = getenv("EDITOR");
|
|
if (!editorName)
|
|
editorName = DEFAULT_EDITOR;
|
|
sys = malloc(strlen(editorName) + strlen(fname) + 32 + 1);
|
|
if (!sys)
|
|
{
|
|
perror("malloc");
|
|
exit(1);
|
|
}
|
|
sprintf(sys, "exec '%s' '%s'", editorName, fname);
|
|
system(sys);
|
|
free(sys);
|
|
}
|
|
|
|
static bool
|
|
toggle(PsqlSettings *pset, bool *sw, char *msg)
|
|
{
|
|
*sw = !*sw;
|
|
if (!pset->quiet)
|
|
printf("turned %s %s\n", on(*sw), msg);
|
|
return *sw;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
unescape(char *dest, const char *source)
|
|
{
|
|
/*-----------------------------------------------------------------------------
|
|
Return as the string <dest> the value of string <source> with escape
|
|
sequences turned into the bytes they represent.
|
|
-----------------------------------------------------------------------------*/
|
|
char *p;
|
|
bool esc; /* Last character we saw was the escape
|
|
* character (/) */
|
|
|
|
esc = false; /* Haven't seen escape character yet */
|
|
for (p = (char *) source; *p; p++)
|
|
{
|
|
char c; /* Our output character */
|
|
|
|
if (esc)
|
|
{
|
|
switch (*p)
|
|
{
|
|
case 'n':
|
|
c = '\n';
|
|
break;
|
|
case 'r':
|
|
c = '\r';
|
|
break;
|
|
case 't':
|
|
c = '\t';
|
|
break;
|
|
case 'f':
|
|
c = '\f';
|
|
break;
|
|
case '\\':
|
|
c = '\\';
|
|
break;
|
|
default:
|
|
c = *p;
|
|
}
|
|
esc = false;
|
|
}
|
|
else if (*p == '\\')
|
|
{
|
|
esc = true;
|
|
c = ' '; /* meaningless, but compiler doesn't know
|
|
* that */
|
|
}
|
|
else
|
|
{
|
|
c = *p;
|
|
esc = false;
|
|
}
|
|
if (!esc)
|
|
*dest++ = c;
|
|
}
|
|
*dest = '\0'; /* Terminating null character */
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
parse_slash_copy(const char *args, char *table, const int table_len,
|
|
char *file, const int file_len,
|
|
bool *from_p, bool *error_p)
|
|
{
|
|
|
|
char work_args[200];
|
|
|
|
/*
|
|
* A copy of the \copy command arguments, except that we modify it as
|
|
* we parse to suit our parsing needs.
|
|
*/
|
|
char *table_tok,
|
|
*fromto_tok;
|
|
|
|
strncpy(work_args, args, sizeof(work_args));
|
|
work_args[sizeof(work_args) - 1] = '\0';
|
|
|
|
*error_p = false; /* initial assumption */
|
|
|
|
table_tok = strtok(work_args, " ");
|
|
if (table_tok == NULL)
|
|
{
|
|
fprintf(stderr, "\\copy needs arguments.\n");
|
|
*error_p = true;
|
|
}
|
|
else
|
|
{
|
|
strncpy(table, table_tok, table_len);
|
|
file[table_len - 1] = '\0';
|
|
|
|
fromto_tok = strtok(NULL, " ");
|
|
if (fromto_tok == NULL)
|
|
{
|
|
fprintf(stderr, "'FROM' or 'TO' must follow table name.\n");
|
|
*error_p = true;
|
|
}
|
|
else
|
|
{
|
|
if (strcasecmp(fromto_tok, "from") == 0)
|
|
*from_p = true;
|
|
else if (strcasecmp(fromto_tok, "to") == 0)
|
|
*from_p = false;
|
|
else
|
|
{
|
|
fprintf(stderr,
|
|
"Unrecognized token found where "
|
|
"'FROM' or 'TO' expected: '%s'.\n",
|
|
fromto_tok);
|
|
*error_p = true;
|
|
}
|
|
if (!*error_p)
|
|
{
|
|
char *file_tok;
|
|
|
|
file_tok = strtok(NULL, " ");
|
|
if (file_tok == NULL)
|
|
{
|
|
fprintf(stderr, "A file pathname must follow '%s'.\n",
|
|
fromto_tok);
|
|
*error_p = true;
|
|
}
|
|
else
|
|
{
|
|
strncpy(file, file_tok, file_len);
|
|
file[file_len - 1] = '\0';
|
|
if (strtok(NULL, " ") != NULL)
|
|
{
|
|
fprintf(stderr,
|
|
"You have extra tokens after the filename.\n");
|
|
*error_p = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
do_copy(const char *args, PsqlSettings *pset)
|
|
{
|
|
/*---------------------------------------------------------------------------
|
|
Execute a \copy command (frontend copy). We have to open a file, then
|
|
submit a COPY query to the backend and either feed it data from the
|
|
file or route its response into the file.
|
|
|
|
We do a text copy with default (tab) column delimiters. Some day, we
|
|
should do all the things a backend copy can do.
|
|
|
|
----------------------------------------------------------------------------*/
|
|
char query[200];
|
|
|
|
/* The COPY command we send to the back end */
|
|
bool from;
|
|
|
|
/* The direction of the copy is from a file to a table. */
|
|
char file[MAXPATHLEN + 1];
|
|
|
|
/* The pathname of the file from/to which we copy */
|
|
char table[NAMEDATALEN];
|
|
|
|
/* The name of the table from/to which we copy */
|
|
bool syntax_error;
|
|
|
|
/* The \c command has invalid syntax */
|
|
FILE *copystream;
|
|
|
|
parse_slash_copy(args, table, sizeof(table), file, sizeof(file),
|
|
&from, &syntax_error);
|
|
|
|
if (!syntax_error)
|
|
{
|
|
strcpy(query, "COPY ");
|
|
strcat(query, table);
|
|
|
|
if (from)
|
|
strcat(query, " FROM stdin");
|
|
else
|
|
strcat(query, " TO stdout");
|
|
|
|
if (from)
|
|
#ifndef __CYGWIN32__
|
|
copystream = fopen(file, "r");
|
|
#else
|
|
copystream = fopen(file, "rb");
|
|
#endif
|
|
else
|
|
#ifndef __CYGWIN32__
|
|
copystream = fopen(file, "w");
|
|
#else
|
|
copystream = fopen(file, "wb");
|
|
#endif
|
|
if (copystream == NULL)
|
|
fprintf(stderr,
|
|
"Unable to open file %s which to copy, errno = %s (%d).",
|
|
from ? "from" : "to", strerror(errno), errno);
|
|
else
|
|
{
|
|
bool success;/* The query succeeded at the backend */
|
|
|
|
success = SendQuery(pset, query,
|
|
from ? copystream : (FILE *) NULL,
|
|
!from ? copystream : (FILE *) NULL);
|
|
fclose(copystream);
|
|
if (!pset->quiet)
|
|
{
|
|
if (success)
|
|
printf("Successfully copied.\n");
|
|
else
|
|
printf("Copy failed.\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
do_connect(const char *new_dbname,
|
|
const char *new_user,
|
|
PsqlSettings *pset)
|
|
{
|
|
if (!new_dbname)
|
|
fprintf(stderr, "\\connect must be followed by a database name\n");
|
|
else if (new_user != NULL && pset->getPassword)
|
|
fprintf(stderr, "You can't specify a username when using passwords.\n");
|
|
else
|
|
{
|
|
PGconn *olddb = pset->db;
|
|
const char *dbparam;
|
|
const char *userparam;
|
|
const char *pwparam;
|
|
|
|
if (strcmp(new_dbname, "-") != 0)
|
|
dbparam = new_dbname;
|
|
else
|
|
dbparam = PQdb(olddb);
|
|
|
|
if (new_user != NULL && strcmp(new_user, "-") != 0)
|
|
userparam = new_user;
|
|
else
|
|
userparam = PQuser(olddb);
|
|
|
|
/* FIXME: if changing user, ought to prompt for a new password? */
|
|
pwparam = PQpass(olddb);
|
|
|
|
#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)
|
|
{
|
|
static const char ev[] = "PGCLIENTENCODING=";
|
|
|
|
putenv(ev);
|
|
}
|
|
#endif
|
|
|
|
pset->db = PQsetdbLogin(PQhost(olddb), PQport(olddb),
|
|
NULL, NULL, dbparam, userparam, pwparam);
|
|
|
|
if (!pset->quiet)
|
|
{
|
|
if (!new_user)
|
|
printf("connecting to new database: %s\n", dbparam);
|
|
else if (dbparam != new_dbname)
|
|
printf("connecting as new user: %s\n", new_user);
|
|
else
|
|
printf("connecting to new database: %s as user: %s\n",
|
|
dbparam, new_user);
|
|
}
|
|
|
|
if (PQstatus(pset->db) == CONNECTION_BAD)
|
|
{
|
|
fprintf(stderr, "%s\n", PQerrorMessage(pset->db));
|
|
fprintf(stderr, "Could not connect to new database. exiting\n");
|
|
exit(2);
|
|
}
|
|
else
|
|
{
|
|
cancelConn = pset->db; /* redirect sigint's loving
|
|
* attentions */
|
|
PQfinish(olddb);
|
|
free(pset->prompt);
|
|
pset->prompt = malloc(strlen(PQdb(pset->db)) + 10);
|
|
sprintf(pset->prompt, "%s%s", PQdb(pset->db), PROMPT);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
do_edit(const char *filename_arg, char *query, int *status_p)
|
|
{
|
|
|
|
int fd;
|
|
char tmp[64];
|
|
char *fname;
|
|
int cc;
|
|
const int ql = strlen(query);
|
|
bool error;
|
|
|
|
if (filename_arg)
|
|
{
|
|
fname = (char *) filename_arg;
|
|
error = false;
|
|
}
|
|
else
|
|
{
|
|
#ifndef WIN32
|
|
sprintf(tmp, "/tmp/psql.%ld.%ld", (long) geteuid(), (long) getpid());
|
|
#else
|
|
GetTempFileName(".", "psql", 0, tmp);
|
|
#endif
|
|
fname = tmp;
|
|
unlink(tmp);
|
|
if (ql > 0)
|
|
{
|
|
if ((fd = open(tmp, O_EXCL | O_CREAT | O_WRONLY, 0600)) == -1)
|
|
{
|
|
perror(tmp);
|
|
error = true;
|
|
}
|
|
else
|
|
{
|
|
if (query[ql - 1] != '\n')
|
|
strcat(query, "\n");
|
|
if (write(fd, query, ql) != ql)
|
|
{
|
|
perror(tmp);
|
|
close(fd);
|
|
unlink(tmp);
|
|
error = true;
|
|
}
|
|
else
|
|
error = false;
|
|
close(fd);
|
|
}
|
|
}
|
|
else
|
|
error = false;
|
|
}
|
|
|
|
if (error)
|
|
*status_p = CMD_SKIP_LINE;
|
|
else
|
|
{
|
|
editFile(fname);
|
|
if ((fd = open(fname, O_RDONLY, 0)) == -1)
|
|
{
|
|
perror(fname);
|
|
if (!filename_arg)
|
|
unlink(fname);
|
|
*status_p = CMD_SKIP_LINE;
|
|
}
|
|
else
|
|
{
|
|
if ((cc = read(fd, query, MAX_QUERY_BUFFER)) == -1)
|
|
{
|
|
perror(fname);
|
|
close(fd);
|
|
if (!filename_arg)
|
|
unlink(fname);
|
|
*status_p = CMD_SKIP_LINE;
|
|
}
|
|
else
|
|
{
|
|
query[cc] = '\0';
|
|
close(fd);
|
|
if (!filename_arg)
|
|
unlink(fname);
|
|
rightTrim(query);
|
|
*status_p = CMD_NEWEDIT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
do_help(PsqlSettings *pset, const char *topic)
|
|
{
|
|
|
|
if (!topic)
|
|
{
|
|
char left_center_right; /* Which column we're displaying */
|
|
int i; /* Index into QL_HELP[] */
|
|
|
|
printf("type \\h <cmd> where <cmd> is one of the following:\n");
|
|
|
|
left_center_right = 'L';/* Start with left column */
|
|
i = 0;
|
|
while (QL_HELP[i].cmd != NULL)
|
|
{
|
|
switch (left_center_right)
|
|
{
|
|
case 'L':
|
|
printf(" %-25s", QL_HELP[i].cmd);
|
|
left_center_right = 'C';
|
|
break;
|
|
case 'C':
|
|
printf("%-25s", QL_HELP[i].cmd);
|
|
left_center_right = 'R';
|
|
break;
|
|
case 'R':
|
|
printf("%-25s\n", QL_HELP[i].cmd);
|
|
left_center_right = 'L';
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
if (left_center_right != 'L')
|
|
puts("\n");
|
|
printf("type \\h * for a complete description of all commands\n");
|
|
}
|
|
else
|
|
{
|
|
int i; /* Index into QL_HELP[] */
|
|
bool help_found; /* We found the help he asked for */
|
|
|
|
int usePipe = 0;
|
|
char *pagerenv;
|
|
FILE *fout;
|
|
|
|
if (strcmp(topic, "*") == 0 &&
|
|
(pset->notty == 0) &&
|
|
(pagerenv = getenv("PAGER")) &&
|
|
(pagerenv[0] != '\0') &&
|
|
(fout = popen(pagerenv, "w")))
|
|
{
|
|
usePipe = 1;
|
|
pqsignal(SIGPIPE, SIG_IGN);
|
|
}
|
|
else
|
|
fout = stdout;
|
|
|
|
help_found = false; /* Haven't found it yet */
|
|
for (i = 0; QL_HELP[i].cmd; i++)
|
|
{
|
|
if (strcasecmp(QL_HELP[i].cmd, topic) == 0 ||
|
|
strcmp(topic, "*") == 0)
|
|
{
|
|
help_found = true;
|
|
fprintf(fout, "Command: %s\n", QL_HELP[i].cmd);
|
|
fprintf(fout, "Description: %s\n", QL_HELP[i].help);
|
|
fprintf(fout, "Syntax:\n");
|
|
fprintf(fout, "%s\n", QL_HELP[i].syntax);
|
|
fprintf(fout, "\n");
|
|
}
|
|
}
|
|
|
|
if (usePipe)
|
|
{
|
|
pclose(fout);
|
|
pqsignal(SIGPIPE, SIG_DFL);
|
|
}
|
|
|
|
if (!help_found)
|
|
fprintf(stderr, "command not found, "
|
|
"try \\h with no arguments to see available help\n");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
do_shell(const char *command)
|
|
{
|
|
|
|
if (!command)
|
|
{
|
|
char *sys;
|
|
char *shellName;
|
|
|
|
shellName = getenv("SHELL");
|
|
if (shellName == NULL)
|
|
shellName = DEFAULT_SHELL;
|
|
sys = malloc(strlen(shellName) + 16);
|
|
if (!sys)
|
|
{
|
|
perror("malloc");
|
|
exit(1);
|
|
}
|
|
sprintf(sys, "exec %s", shellName);
|
|
system(sys);
|
|
free(sys);
|
|
}
|
|
else
|
|
system(command);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* HandleSlashCmds:
|
|
*
|
|
* Handles all the different commands that start with \
|
|
* db_ptr is a pointer to the TgDb* structure line is the current input
|
|
* line prompt_ptr is a pointer to the prompt string, a pointer is used
|
|
* because the prompt can be used with a connection to a new database.
|
|
* Returns a status:
|
|
* 0 - send currently constructed query to backend (i.e. we got a \g)
|
|
* 1 - skip processing of this line, continue building up query
|
|
* 2 - terminate processing of this query entirely
|
|
* 3 - new query supplied by edit
|
|
*/
|
|
static int
|
|
HandleSlashCmds(PsqlSettings *pset,
|
|
char *line,
|
|
char *query)
|
|
{
|
|
int status = CMD_SKIP_LINE;
|
|
char *optarg;
|
|
bool success;
|
|
|
|
/*
|
|
* Pointer inside the <cmd> string to the argument of the slash
|
|
* command, assuming it is a one-character slash command. If it's not
|
|
* a one-character command, this is meaningless.
|
|
*/
|
|
char *optarg2;
|
|
|
|
/*
|
|
* Pointer inside the <cmd> string to the argument of the slash
|
|
* command assuming it's not a one-character command. If it's a
|
|
* one-character command, this is meaningless.
|
|
*/
|
|
char *cmd;
|
|
|
|
/*
|
|
* String: value of the slash command, less the slash and with escape
|
|
* sequences decoded.
|
|
*/
|
|
int blank_loc;
|
|
|
|
/* Offset within <cmd> of first blank */
|
|
|
|
cmd = malloc(strlen(line)); /* unescaping better not make string grow. */
|
|
|
|
unescape(cmd, line + 1); /* sets cmd string */
|
|
|
|
if (strlen(cmd) >= 1 && cmd[strlen(cmd) - 1] == ';') /* strip trailing ; */
|
|
cmd[strlen(cmd) - 1] = '\0';
|
|
|
|
/*
|
|
* Originally, there were just single character commands. Now, we
|
|
* define some longer, friendly commands, but we have to keep the old
|
|
* single character commands too. \c used to be what \connect is now.
|
|
* Complicating matters is the fact that with the single-character
|
|
* commands, you can start the argument right after the single
|
|
* character, so "\copy" would mean "connect to database named 'opy'".
|
|
*/
|
|
|
|
if (strlen(cmd) > 1)
|
|
optarg = cmd + 1 + strspn(cmd + 1, " \t");
|
|
else
|
|
optarg = NULL;
|
|
|
|
blank_loc = strcspn(cmd, " \t");
|
|
if (blank_loc == 0 || !cmd[blank_loc])
|
|
optarg2 = NULL;
|
|
else
|
|
optarg2 = cmd + blank_loc + strspn(cmd + blank_loc, " \t");
|
|
|
|
switch (cmd[0])
|
|
{
|
|
case 'a': /* toggles to align fields on output */
|
|
toggle(pset, &pset->opt.align, "field alignment");
|
|
break;
|
|
|
|
case 'C': /* define new caption */
|
|
if (pset->opt.caption)
|
|
{
|
|
free(pset->opt.caption);
|
|
pset->opt.caption = NULL;
|
|
}
|
|
if (optarg && !(pset->opt.caption = strdup(optarg)))
|
|
{
|
|
perror("malloc");
|
|
exit(CMD_TERMINATE);
|
|
}
|
|
break;
|
|
|
|
case 'c':
|
|
{
|
|
if (strncmp(cmd, "copy ", strlen("copy ")) == 0 ||
|
|
strncmp(cmd, "copy ", strlen("copy ")) == 0)
|
|
do_copy(optarg2, pset);
|
|
else if (strcmp(cmd, "copy") == 0)
|
|
{
|
|
fprintf(stderr, "See \\? for help\n");
|
|
break;
|
|
}
|
|
else if (strncmp(cmd, "connect ", strlen("connect ")) == 0 ||
|
|
strcmp(cmd, "connect") == 0 /* issue error message */ )
|
|
{
|
|
char *optarg3 = NULL;
|
|
int blank_loc2;
|
|
|
|
if (optarg2)
|
|
{
|
|
blank_loc2 = strcspn(optarg2, " \t");
|
|
if (blank_loc2 == 0 || *(optarg2 + blank_loc2) == '\0')
|
|
optarg3 = NULL;
|
|
else
|
|
{
|
|
optarg3 = optarg2 + blank_loc2 +
|
|
strspn(optarg2 + blank_loc2, " \t");
|
|
*(optarg2 + blank_loc2) = '\0';
|
|
}
|
|
}
|
|
do_connect(optarg2, optarg3, pset);
|
|
}
|
|
else
|
|
{
|
|
char *optarg3 = NULL;
|
|
int blank_loc2;
|
|
|
|
if (optarg)
|
|
{
|
|
blank_loc2 = strcspn(optarg, " \t");
|
|
if (blank_loc2 == 0 || *(optarg + blank_loc2) == '\0')
|
|
optarg3 = NULL;
|
|
else
|
|
{
|
|
optarg3 = optarg + blank_loc2 +
|
|
strspn(optarg + blank_loc2, " \t");
|
|
*(optarg + blank_loc2) = '\0';
|
|
}
|
|
}
|
|
do_connect(optarg, optarg3, pset);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'd': /* \d describe database information */
|
|
|
|
/*
|
|
* if the optarg2 name is surrounded by double-quotes, then
|
|
* don't convert case
|
|
*/
|
|
if (optarg2)
|
|
{
|
|
if (*optarg2 == '"')
|
|
{
|
|
optarg2++;
|
|
if (*(optarg2 + strlen(optarg2) - 1) == '"')
|
|
*(optarg2 + strlen(optarg2) - 1) = '\0';
|
|
}
|
|
else
|
|
{
|
|
int i;
|
|
|
|
#ifdef MULTIBYTE
|
|
for (i = 0; optarg2[i]; i += PQmblen(optarg2 + i))
|
|
#else
|
|
for (i = 0; optarg2[i]; i++)
|
|
#endif
|
|
if (isupper(optarg2[i]))
|
|
optarg2[i] = tolower(optarg2[i]);
|
|
}
|
|
}
|
|
|
|
#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
|
|
if (strncmp(cmd, "da", 2) == 0)
|
|
{
|
|
char descbuf[4096];
|
|
|
|
/* aggregates */
|
|
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)
|
|
{
|
|
strcat(descbuf, "AND a.aggname ~ '^");
|
|
strcat(descbuf, optarg2);
|
|
strcat(descbuf, "' ");
|
|
}
|
|
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)
|
|
{
|
|
strcat(descbuf, "AND a.aggname ~ '^");
|
|
strcat(descbuf, optarg2);
|
|
strcat(descbuf, "' ");
|
|
}
|
|
strcat(descbuf, "ORDER BY aggname, type;");
|
|
success = SendQuery(pset, descbuf, NULL, NULL);
|
|
}
|
|
else if (strncmp(cmd, "dd", 2) == 0)
|
|
/* descriptions */
|
|
objectDescription(pset, optarg + 1);
|
|
else if (strncmp(cmd, "df", 2) == 0)
|
|
{
|
|
char descbuf[4096];
|
|
|
|
/* functions/procedures */
|
|
|
|
/*
|
|
* we skip in/out funcs by excluding functions that take
|
|
* some arguments, but have no types defined for those
|
|
* arguments
|
|
*/
|
|
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)
|
|
{
|
|
strcat(descbuf, "AND p.proname ~ '^");
|
|
strcat(descbuf, optarg2);
|
|
strcat(descbuf, "' ");
|
|
}
|
|
strcat(descbuf, "ORDER BY result, function, arguments;");
|
|
success = SendQuery(pset, descbuf, NULL, NULL);
|
|
}
|
|
else if (strncmp(cmd, "di", 2) == 0)
|
|
/* only indices */
|
|
tableList(pset, false, 'i', false);
|
|
else if (strncmp(cmd, "do", 2) == 0)
|
|
{
|
|
char descbuf[4096];
|
|
|
|
/* operators */
|
|
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)
|
|
{
|
|
strcat(descbuf, "AND o.oprname ~ '^");
|
|
strcat(descbuf, optarg2);
|
|
strcat(descbuf, "' ");
|
|
}
|
|
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)
|
|
{
|
|
strcat(descbuf, "AND o.oprname ~ '^");
|
|
strcat(descbuf, optarg2);
|
|
strcat(descbuf, "' ");
|
|
}
|
|
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)
|
|
{
|
|
strcat(descbuf, "AND o.oprname ~ '^");
|
|
strcat(descbuf, optarg2);
|
|
strcat(descbuf, "' ");
|
|
}
|
|
strcat(descbuf, "ORDER BY op, left_arg, right_arg, result;");
|
|
success = SendQuery(pset, descbuf, NULL, NULL);
|
|
}
|
|
else if (strncmp(cmd, "ds", 2) == 0)
|
|
/* only sequences */
|
|
tableList(pset, false, 'S', false);
|
|
else if (strncmp(cmd, "dS", 2) == 0)
|
|
/* system tables */
|
|
tableList(pset, false, 'b', true);
|
|
else if (strncmp(cmd, "dt", 2) == 0)
|
|
/* only tables */
|
|
tableList(pset, false, 't', false);
|
|
else if (strncmp(cmd, "dT", 2) == 0)
|
|
{
|
|
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, "' ");
|
|
}
|
|
success = SendQuery(pset, descbuf, NULL, NULL);
|
|
}
|
|
else if (!optarg)
|
|
/* show tables, sequences and indices */
|
|
tableList(pset, false, 'b', false);
|
|
else if (strcmp(optarg, "*") == 0)
|
|
{ /* show everything */
|
|
if (tableList(pset, false, 'b', false) == 0)
|
|
tableList(pset, true, 'b', false);
|
|
}
|
|
else if (strncmp(cmd, "d ", 2) == 0)
|
|
/* describe the specified table */
|
|
tableDesc(pset, optarg, NULL);
|
|
else
|
|
slashUsage(pset);
|
|
break;
|
|
|
|
case 'e': /* edit */
|
|
{
|
|
do_edit(optarg, query, &status);
|
|
break;
|
|
}
|
|
|
|
case 'E':
|
|
{
|
|
FILE *fd;
|
|
static char *lastfile;
|
|
struct stat st,
|
|
st2;
|
|
|
|
if (optarg)
|
|
{
|
|
if (lastfile)
|
|
free(lastfile);
|
|
lastfile = malloc(strlen(optarg + 1));
|
|
if (!lastfile)
|
|
{
|
|
perror("malloc");
|
|
exit(CMD_TERMINATE);
|
|
}
|
|
strcpy(lastfile, optarg);
|
|
}
|
|
else if (!lastfile)
|
|
{
|
|
fprintf(stderr, "\\r must be followed by a file name initially\n");
|
|
break;
|
|
}
|
|
stat(lastfile, &st);
|
|
editFile(lastfile);
|
|
#ifndef __CYGWIN32__
|
|
if ((stat(lastfile, &st2) == -1) || ((fd = fopen(lastfile, "r")) == NULL))
|
|
#else
|
|
if ((stat(lastfile, &st2) == -1) || ((fd = fopen(lastfile, "rb")) == NULL))
|
|
#endif
|
|
{
|
|
perror(lastfile);
|
|
break;
|
|
}
|
|
if (st2.st_mtime == st.st_mtime)
|
|
{
|
|
if (!pset->quiet)
|
|
fprintf(stderr, "warning: %s not modified. query not executed\n", lastfile);
|
|
fclose(fd);
|
|
break;
|
|
}
|
|
MainLoop(pset, query, fd);
|
|
fclose(fd);
|
|
break;
|
|
}
|
|
|
|
case 'f':
|
|
{
|
|
char *fs = DEFAULT_FIELD_SEP;
|
|
|
|
if (optarg)
|
|
fs = optarg;
|
|
/* handle \f \{space} */
|
|
if (optarg && !*optarg && strlen(cmd) > 1)
|
|
{
|
|
int i;
|
|
|
|
/* line and cmd match until the first blank space */
|
|
for (i = 2; isspace(line[i]); i++)
|
|
;
|
|
fs = cmd + i - 1;
|
|
}
|
|
if (pset->opt.fieldSep)
|
|
free(pset->opt.fieldSep);
|
|
if (!(pset->opt.fieldSep = strdup(fs)))
|
|
{
|
|
perror("malloc");
|
|
exit(CMD_TERMINATE);
|
|
}
|
|
if (!pset->quiet)
|
|
printf("field separator changed to '%s'\n", pset->opt.fieldSep);
|
|
break;
|
|
}
|
|
case 'g': /* \g means send query */
|
|
if (!optarg)
|
|
pset->gfname = NULL;
|
|
else if (!(pset->gfname = strdup(optarg)))
|
|
{
|
|
perror("malloc");
|
|
exit(CMD_TERMINATE);
|
|
}
|
|
status = CMD_SEND;
|
|
break;
|
|
|
|
case 'h': /* help */
|
|
{
|
|
do_help(pset, optarg);
|
|
break;
|
|
}
|
|
|
|
case 'i': /* \i is include file */
|
|
{
|
|
FILE *fd;
|
|
|
|
if (!optarg)
|
|
{
|
|
fprintf(stderr, "\\i must be followed by a file name\n");
|
|
break;
|
|
}
|
|
#ifndef __CYGWIN32__
|
|
if ((fd = fopen(optarg, "r")) == NULL)
|
|
#else
|
|
if ((fd = fopen(optarg, "rb")) == NULL)
|
|
#endif
|
|
{
|
|
fprintf(stderr, "file named %s could not be opened\n", optarg);
|
|
break;
|
|
}
|
|
MainLoop(pset, query, fd);
|
|
fclose(fd);
|
|
break;
|
|
}
|
|
|
|
case 'H':
|
|
if (toggle(pset, &pset->opt.html3, "HTML3.0 tabular output"))
|
|
pset->opt.standard = 0;
|
|
break;
|
|
|
|
case 'l': /* \l is list database */
|
|
listAllDbs(pset);
|
|
break;
|
|
|
|
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);
|
|
}
|
|
break;
|
|
|
|
case 'o':
|
|
setFout(pset, optarg);
|
|
break;
|
|
|
|
case 'p':
|
|
if (query)
|
|
{
|
|
fputs(query, stdout);
|
|
fputc('\n', stdout);
|
|
}
|
|
break;
|
|
|
|
case 'q': /* \q is quit */
|
|
status = CMD_TERMINATE;
|
|
break;
|
|
|
|
case 'r': /* reset(clear) the buffer */
|
|
query[0] = '\0';
|
|
if (!pset->quiet)
|
|
printf("buffer reset(cleared)\n");
|
|
break;
|
|
|
|
case 's': /* \s is save history to a file */
|
|
if (!optarg)
|
|
optarg = "/dev/tty";
|
|
#ifdef USE_HISTORY
|
|
if (write_history(optarg) != 0)
|
|
fprintf(stderr, "cannot write history to %s\n", optarg);
|
|
#endif
|
|
break;
|
|
|
|
case 't': /* toggle headers */
|
|
toggle(pset, &pset->opt.header, "output headings and row count");
|
|
break;
|
|
|
|
case 'T': /* define html <table ...> option */
|
|
if (pset->opt.tableOpt)
|
|
free(pset->opt.tableOpt);
|
|
if (!optarg)
|
|
pset->opt.tableOpt = NULL;
|
|
else if (!(pset->opt.tableOpt = strdup(optarg)))
|
|
{
|
|
perror("malloc");
|
|
exit(CMD_TERMINATE);
|
|
}
|
|
break;
|
|
|
|
case 'w':
|
|
{
|
|
FILE *fd;
|
|
|
|
if (!optarg)
|
|
{
|
|
fprintf(stderr, "\\w must be followed by a file name\n");
|
|
break;
|
|
}
|
|
#ifndef __CYGWIN32__
|
|
if ((fd = fopen(optarg, "w")) == NULL)
|
|
#else
|
|
if ((fd = fopen(optarg, "w")) == NULL)
|
|
#endif
|
|
{
|
|
fprintf(stderr, "file named %s could not be opened\n", optarg);
|
|
break;
|
|
}
|
|
fputs(query, fd);
|
|
fputs("\n", fd);
|
|
fclose(fd);
|
|
break;
|
|
}
|
|
|
|
case 'x':
|
|
toggle(pset, &pset->opt.expanded, "expanded table representation");
|
|
break;
|
|
|
|
case 'z': /* list table rights (grant/revoke) */
|
|
rightsList(pset);
|
|
break;
|
|
|
|
case '!':
|
|
do_shell(optarg);
|
|
break;
|
|
default:
|
|
|
|
case '?': /* \? is help */
|
|
slashUsage(pset);
|
|
break;
|
|
}
|
|
free(cmd);
|
|
return status;
|
|
}
|
|
|
|
/* MainLoop()
|
|
* Main processing loop for reading lines of input
|
|
* and sending them to the backend.
|
|
*
|
|
* This loop is re-entrant. May be called by \i command
|
|
* which reads input from a file.
|
|
* db_ptr must be initialized and set.
|
|
*/
|
|
|
|
static int
|
|
MainLoop(PsqlSettings *pset, char *query, FILE *source)
|
|
{
|
|
char *line; /* line of input */
|
|
char *xcomment; /* start of extended comment */
|
|
int len; /* length of the line */
|
|
int successResult = 1;
|
|
int slashCmdStatus = CMD_SEND;
|
|
|
|
/*--------------------------------------------------------------
|
|
* slashCmdStatus can be:
|
|
* CMD_UNKNOWN - send currently constructed query to backend
|
|
* (i.e. we got a \g)
|
|
* CMD_SEND - send currently constructed query to backend
|
|
* (i.e. we got a \g)
|
|
* CMD_SKIP_LINE - skip processing of this line, continue building
|
|
* up query
|
|
* CMD_TERMINATE - terminate processing of this query entirely
|
|
* CMD_NEWEDIT - new query supplied by edit
|
|
*---------------------------------------------------------------
|
|
*/
|
|
|
|
bool querySent = false;
|
|
READ_ROUTINE GetNextLine;
|
|
bool eof = false; /* end of our command input? */
|
|
bool success;
|
|
char in_quote; /* == 0 for no in_quote */
|
|
bool was_bslash; /* backslash */
|
|
int paren_level;
|
|
char *query_start;
|
|
|
|
/* Stack the prior command source */
|
|
FILE *prev_cmd_source = cur_cmd_source;
|
|
bool prev_cmd_interactive = cur_cmd_interactive;
|
|
|
|
/* Establish new source */
|
|
cur_cmd_source = source;
|
|
cur_cmd_interactive = ((source == stdin) && !pset->notty);
|
|
|
|
if ((query = malloc(MAX_QUERY_BUFFER)) == NULL)
|
|
perror("Memory Allocation Failed");
|
|
|
|
if (cur_cmd_interactive)
|
|
{
|
|
if (pset->prompt)
|
|
free(pset->prompt);
|
|
pset->prompt = malloc(strlen(PQdb(pset->db)) + strlen(PROMPT) + 1);
|
|
if (pset->quiet)
|
|
pset->prompt[0] = '\0';
|
|
else
|
|
sprintf(pset->prompt, "%s%s", PQdb(pset->db), PROMPT);
|
|
if (pset->useReadline)
|
|
{
|
|
#ifdef USE_HISTORY
|
|
using_history();
|
|
#endif
|
|
GetNextLine = gets_readline;
|
|
}
|
|
else
|
|
GetNextLine = gets_noreadline;
|
|
}
|
|
else
|
|
GetNextLine = gets_fromFile;
|
|
|
|
query[0] = '\0';
|
|
xcomment = NULL;
|
|
in_quote = false;
|
|
paren_level = 0;
|
|
slashCmdStatus = CMD_UNKNOWN; /* set default */
|
|
|
|
/* main loop to get queries and execute them */
|
|
while (!eof)
|
|
{
|
|
|
|
/*
|
|
* just returned from editing the line? then just copy to the
|
|
* input buffer
|
|
*/
|
|
if (slashCmdStatus == CMD_NEWEDIT)
|
|
{
|
|
paren_level = 0;
|
|
line = strdup(query);
|
|
query[0] = '\0';
|
|
|
|
/*
|
|
* otherwise, get another line and set interactive prompt if
|
|
* necessary
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
if (cur_cmd_interactive && !pset->quiet)
|
|
{
|
|
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;
|
|
else if (xcomment != NULL)
|
|
pset->prompt[strlen(pset->prompt) - 3] = PROMPT_COMMENT;
|
|
else if (query[0] != '\0' && !querySent)
|
|
pset->prompt[strlen(pset->prompt) - 3] = PROMPT_CONTINUE;
|
|
else
|
|
pset->prompt[strlen(pset->prompt) - 3] = PROMPT_READY;
|
|
}
|
|
line = GetNextLine(pset->prompt, source);
|
|
#ifdef USE_HISTORY
|
|
if (cur_cmd_interactive && pset->useReadline && line != NULL)
|
|
add_history(line); /* save non-empty lines in history */
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* query - pointer to current command query_start - placeholder
|
|
* for next command
|
|
*/
|
|
if (line == NULL || (!cur_cmd_interactive && *line == '\0'))
|
|
{ /* No more input. Time to quit, or \i
|
|
* done */
|
|
if (!pset->quiet)
|
|
printf("EOF\n");/* Goes on prompt line */
|
|
eof = true;
|
|
continue;
|
|
}
|
|
|
|
/* not currently inside an extended comment? */
|
|
if (xcomment == NULL)
|
|
{
|
|
query_start = line;
|
|
|
|
/* otherwise, continue the extended comment... */
|
|
}
|
|
else
|
|
{
|
|
query_start = line;
|
|
xcomment = line;
|
|
}
|
|
|
|
/* remove whitespaces on the right, incl. \n's */
|
|
line = rightTrim(line);
|
|
|
|
/* echo back if input is from file */
|
|
if (!cur_cmd_interactive && !pset->singleStep && !pset->quiet)
|
|
fprintf(stderr, "%s\n", line);
|
|
|
|
slashCmdStatus = CMD_UNKNOWN;
|
|
/* nothing on line after trimming? then ignore */
|
|
if (line[0] == '\0')
|
|
{
|
|
free(line);
|
|
continue;
|
|
}
|
|
|
|
len = strlen(line);
|
|
|
|
if (pset->singleLineMode)
|
|
{
|
|
success = SendQuery(pset, line, NULL, NULL);
|
|
successResult &= success;
|
|
querySent = true;
|
|
}
|
|
else
|
|
{
|
|
int i;
|
|
|
|
/*
|
|
* The current character is at line[i], the prior character at
|
|
* line[i - prevlen], the next character at line[i + thislen].
|
|
*/
|
|
#ifdef MULTIBYTE
|
|
int prevlen = 0;
|
|
int thislen = (len > 0) ? PQmblen(line) : 0;
|
|
|
|
#define ADVANCE_I (prevlen = thislen, i += thislen, thislen = PQmblen(line+i))
|
|
#else
|
|
#define prevlen 1
|
|
#define thislen 1
|
|
#define ADVANCE_I (i++)
|
|
#endif
|
|
|
|
was_bslash = false;
|
|
for (i = 0; i < len; ADVANCE_I)
|
|
{
|
|
if (line[i] == '\\' && !in_quote)
|
|
{
|
|
char hold_char = line[i];
|
|
|
|
line[i] = '\0';
|
|
if (query_start[0] != '\0')
|
|
{
|
|
if (query[0] != '\0')
|
|
{
|
|
strcat(query, "\n");
|
|
strcat(query, query_start);
|
|
}
|
|
else
|
|
strcpy(query, query_start);
|
|
}
|
|
line[i] = hold_char;
|
|
query_start = line + i;
|
|
break; /* handle command */
|
|
}
|
|
|
|
if (querySent &&
|
|
isascii((unsigned char) (line[i])) &&
|
|
!isspace(line[i]))
|
|
{
|
|
query[0] = '\0';
|
|
querySent = false;
|
|
}
|
|
|
|
if (was_bslash)
|
|
was_bslash = false;
|
|
else if (i > 0 && line[i - prevlen] == '\\')
|
|
was_bslash = true;
|
|
|
|
/* inside a quote? */
|
|
if (in_quote && (line[i] != in_quote || was_bslash))
|
|
/* do nothing */ ;
|
|
/* inside an extended comment? */
|
|
else if (xcomment != NULL)
|
|
{
|
|
if (line[i] == '*' && line[i + thislen] == '/')
|
|
{
|
|
xcomment = NULL;
|
|
ADVANCE_I;
|
|
}
|
|
}
|
|
/* start of extended comment? */
|
|
else if (line[i] == '/' && line[i + thislen] == '*')
|
|
{
|
|
xcomment = line + i;
|
|
ADVANCE_I;
|
|
}
|
|
/* single-line comment? truncate line */
|
|
else if ((line[i] == '-' && line[i + thislen] == '-') ||
|
|
(line[i] == '/' && line[i + thislen] == '/'))
|
|
{
|
|
/* print comment at top of query */
|
|
if (pset->singleStep)
|
|
fprintf(stdout, "%s\n", line + i);
|
|
line[i] = '\0'; /* remove comment */
|
|
break;
|
|
}
|
|
else if (in_quote && line[i] == in_quote)
|
|
in_quote = false;
|
|
else if (!in_quote && (line[i] == '\'' || line[i] == '"'))
|
|
in_quote = line[i];
|
|
/* semi-colon? then send query now */
|
|
else if (!paren_level && line[i] == ';')
|
|
{
|
|
char hold_char = line[i + thislen];
|
|
|
|
line[i + thislen] = '\0';
|
|
if (query_start[0] != '\0')
|
|
{
|
|
if (query[0] != '\0')
|
|
{
|
|
strcat(query, "\n");
|
|
strcat(query, query_start);
|
|
}
|
|
else
|
|
strcpy(query, query_start);
|
|
}
|
|
success = SendQuery(pset, query, NULL, NULL);
|
|
successResult &= success;
|
|
line[i + thislen] = hold_char;
|
|
query_start = line + i + thislen;
|
|
/* sometimes, people do ';\g', don't execute twice */
|
|
if (*query_start == '\\' &&
|
|
*(query_start + 1) == 'g')
|
|
query_start += 2;
|
|
querySent = true;
|
|
}
|
|
else if (line[i] == '(')
|
|
{
|
|
paren_level++;
|
|
|
|
}
|
|
else if (paren_level && line[i] == ')')
|
|
paren_level--;
|
|
}
|
|
}
|
|
|
|
/* nothing on line after trimming? then ignore */
|
|
if (line[0] == '\0')
|
|
{
|
|
free(line);
|
|
continue;
|
|
}
|
|
|
|
if (!in_quote && query_start[0] == '\\')
|
|
{
|
|
/* handle \p\g and other backslash combinations */
|
|
while (query_start[0] != '\0')
|
|
{
|
|
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;
|
|
}
|
|
free(line);
|
|
/* They did \q, leave the loop */
|
|
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')
|
|
{
|
|
querySent = false;
|
|
if (query[0] != '\0')
|
|
{
|
|
strcat(query, "\n");
|
|
strcat(query, query_start);
|
|
}
|
|
else
|
|
strcpy(query, query_start);
|
|
}
|
|
free(line);
|
|
}
|
|
|
|
/* had a backslash-g? force the query to be sent */
|
|
if (slashCmdStatus == CMD_SEND)
|
|
{
|
|
success = SendQuery(pset, query, NULL, NULL);
|
|
successResult &= success;
|
|
xcomment = NULL;
|
|
in_quote = false;
|
|
paren_level = 0;
|
|
querySent = true;
|
|
}
|
|
} /* while */
|
|
|
|
if (query)
|
|
free(query);
|
|
|
|
cur_cmd_source = prev_cmd_source;
|
|
cur_cmd_interactive = prev_cmd_interactive;
|
|
|
|
return successResult;
|
|
} /* MainLoop() */
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
extern char *optarg;
|
|
extern int optind;
|
|
|
|
char *dbname = NULL;
|
|
char *host = NULL;
|
|
char *port = NULL;
|
|
char *qfilename = NULL;
|
|
|
|
PsqlSettings settings;
|
|
|
|
char *singleQuery = NULL;
|
|
|
|
bool listDatabases = 0;
|
|
int successResult = 1;
|
|
bool singleSlashCmd = 0;
|
|
int c;
|
|
|
|
char *home = NULL; /* Used to store $HOME */
|
|
char *version = NULL; /* PostgreSQL version */
|
|
|
|
/*
|
|
* initialize cur_cmd_source in case we do not use MainLoop ... some
|
|
* systems fail if we try to use a static initializer for this :-(
|
|
*/
|
|
cur_cmd_source = stdin;
|
|
cur_cmd_interactive = false;
|
|
|
|
MemSet(&settings, 0, sizeof settings);
|
|
settings.opt.align = 1;
|
|
settings.opt.header = 1;
|
|
settings.queryFout = stdout;
|
|
settings.opt.fieldSep = strdup(DEFAULT_FIELD_SEP);
|
|
settings.opt.pager = 1;
|
|
if (!isatty(0) || !isatty(1))
|
|
{
|
|
/* Noninteractive defaults */
|
|
settings.notty = 1;
|
|
}
|
|
else
|
|
{
|
|
/* Interactive defaults */
|
|
pqsignal(SIGINT, handle_sigint); /* control-C => cancel */
|
|
#ifdef USE_READLINE
|
|
settings.useReadline = 1;
|
|
{
|
|
/*
|
|
* Set the application name, used for parsing .inputrc -- dz
|
|
*/
|
|
char *progname = strrchr(argv[0], SEP_CHAR);
|
|
rl_readline_name = (progname ? progname+1 : argv[0]);
|
|
}
|
|
#endif
|
|
}
|
|
#ifdef PSQL_ALWAYS_GET_PASSWORDS
|
|
settings.getPassword = 1;
|
|
#else
|
|
settings.getPassword = 0;
|
|
#endif
|
|
|
|
#ifdef MULTIBYTE
|
|
has_client_encoding = getenv("PGCLIENTENCODING");
|
|
#endif
|
|
|
|
while ((c = getopt(argc, argv, "Aa:c:d:eEf:F:lh:Hnso:p:qStT:ux")) != EOF)
|
|
{
|
|
switch (c)
|
|
{
|
|
case 'A':
|
|
settings.opt.align = 0;
|
|
break;
|
|
case 'a':
|
|
#ifdef NOT_USED /* this no longer does anything */
|
|
fe_setauthsvc(optarg, errbuf);
|
|
#endif
|
|
break;
|
|
case 'c':
|
|
singleQuery = strdup(optarg);
|
|
if (singleQuery[0] == '\\')
|
|
singleSlashCmd = 1;
|
|
break;
|
|
case 'd':
|
|
dbname = optarg;
|
|
break;
|
|
case 'e':
|
|
settings.echoQuery = 1;
|
|
break;
|
|
case 'E':
|
|
settings.echoAllQueries = 1;
|
|
settings.echoQuery = 1;
|
|
break;
|
|
case 'f':
|
|
qfilename = optarg;
|
|
break;
|
|
case 'F':
|
|
settings.opt.fieldSep = strdup(optarg);
|
|
break;
|
|
case 'l':
|
|
listDatabases = 1;
|
|
break;
|
|
case 'h':
|
|
host = optarg;
|
|
break;
|
|
case 'H':
|
|
settings.opt.html3 = 1;
|
|
break;
|
|
case 'n':
|
|
settings.useReadline = 0;
|
|
break;
|
|
case 'o':
|
|
setFout(&settings, optarg);
|
|
break;
|
|
case 'p':
|
|
port = optarg;
|
|
break;
|
|
case 'q':
|
|
settings.quiet = 1;
|
|
break;
|
|
case 's':
|
|
settings.singleStep = 1;
|
|
break;
|
|
case 'S':
|
|
settings.singleLineMode = 1;
|
|
break;
|
|
case 't':
|
|
settings.opt.header = 0;
|
|
break;
|
|
case 'T':
|
|
settings.opt.tableOpt = strdup(optarg);
|
|
break;
|
|
case 'u':
|
|
settings.getPassword = 1;
|
|
break;
|
|
case 'x':
|
|
settings.opt.expanded = 1;
|
|
break;
|
|
default:
|
|
usage(argv[0]);
|
|
break;
|
|
}
|
|
}
|
|
/* if we still have an argument, use it as the database name */
|
|
if (argc - optind == 1)
|
|
dbname = argv[optind];
|
|
|
|
if (listDatabases)
|
|
dbname = "template1";
|
|
|
|
if (settings.getPassword)
|
|
{
|
|
char username[100];
|
|
char password[100];
|
|
|
|
prompt_for_password(username, password);
|
|
|
|
settings.db = PQsetdbLogin(host, port, NULL, NULL, dbname,
|
|
username, password);
|
|
}
|
|
else
|
|
settings.db = PQsetdb(host, port, NULL, NULL, dbname);
|
|
|
|
dbname = PQdb(settings.db);
|
|
|
|
if (PQstatus(settings.db) == CONNECTION_BAD)
|
|
{
|
|
fprintf(stderr, "Connection to database '%s' failed.\n", dbname);
|
|
fprintf(stderr, "%s\n", PQerrorMessage(settings.db));
|
|
PQfinish(settings.db);
|
|
exit(1);
|
|
}
|
|
|
|
cancelConn = settings.db; /* enable SIGINT to send cancel */
|
|
|
|
if (listDatabases)
|
|
exit(listAllDbs(&settings));
|
|
if (!settings.quiet && !settings.notty && !singleQuery && !qfilename)
|
|
{
|
|
printf("Welcome to the POSTGRESQL interactive sql monitor:\n");
|
|
printf(" Please read the file COPYRIGHT for copyright terms "
|
|
"of POSTGRESQL\n");
|
|
|
|
if ((version = selectVersion(&settings)) != NULL)
|
|
printf("[%s]\n", version);
|
|
|
|
printf("\n");
|
|
printf(" type \\? for help on slash commands\n");
|
|
printf(" type \\q to quit\n");
|
|
printf(" type \\g or terminate with semicolon to execute query\n");
|
|
printf(" You are currently connected to the database: %s\n\n", dbname);
|
|
}
|
|
|
|
/*
|
|
* 20.06.97 ACRM See if we've got a /etc/psqlrc or .psqlrc file
|
|
*/
|
|
if (!access("/etc/psqlrc", R_OK))
|
|
HandleSlashCmds(&settings, "\\i /etc/psqlrc", "");
|
|
if ((home = getenv("HOME")) != NULL)
|
|
{
|
|
char *psqlrc = NULL,
|
|
*line = NULL;
|
|
|
|
if ((psqlrc = (char *) malloc(strlen(home) + 10)) != NULL)
|
|
{
|
|
sprintf(psqlrc, "%s/.psqlrc", home);
|
|
if (!access(psqlrc, R_OK))
|
|
{
|
|
if ((line = (char *) malloc(strlen(psqlrc) + 5)) != NULL)
|
|
{
|
|
sprintf(line, "\\i %s", psqlrc);
|
|
HandleSlashCmds(&settings, line, "");
|
|
free(line);
|
|
}
|
|
}
|
|
free(psqlrc);
|
|
}
|
|
}
|
|
/* End of check for psqlrc files */
|
|
|
|
if (qfilename || singleSlashCmd)
|
|
{
|
|
|
|
/*
|
|
* read in a file full of queries instead of reading in queries
|
|
* interactively
|
|
*/
|
|
char *line;
|
|
|
|
if (singleSlashCmd)
|
|
{
|
|
/* Not really a query, but "Do what I mean, not what I say." */
|
|
line = singleQuery;
|
|
}
|
|
else
|
|
{
|
|
line = malloc(strlen(qfilename) + 5);
|
|
sprintf(line, "\\i %s", qfilename);
|
|
}
|
|
HandleSlashCmds(&settings, line, "");
|
|
free(line);
|
|
}
|
|
else
|
|
{
|
|
if (singleQuery)
|
|
successResult = SendQuery(&settings, singleQuery, NULL, NULL);
|
|
else
|
|
successResult = MainLoop(&settings, NULL, stdin);
|
|
}
|
|
|
|
PQfinish(settings.db);
|
|
free(settings.opt.fieldSep);
|
|
if (settings.prompt)
|
|
free(settings.prompt);
|
|
|
|
return !successResult;
|
|
}
|
|
|
|
#define COPYBUFSIZ 8192
|
|
|
|
static bool
|
|
handleCopyOut(PGconn *conn, FILE *copystream)
|
|
{
|
|
bool copydone;
|
|
char copybuf[COPYBUFSIZ];
|
|
int ret;
|
|
|
|
copydone = false; /* Can't be done; haven't started. */
|
|
|
|
while (!copydone)
|
|
{
|
|
ret = PQgetline(conn, copybuf, COPYBUFSIZ);
|
|
|
|
if (copybuf[0] == '\\' &&
|
|
copybuf[1] == '.' &&
|
|
copybuf[2] == '\0')
|
|
{
|
|
copydone = true; /* don't print this... */
|
|
}
|
|
else
|
|
{
|
|
fputs(copybuf, copystream);
|
|
switch (ret)
|
|
{
|
|
case EOF:
|
|
copydone = true;
|
|
/* FALLTHROUGH */
|
|
case 0:
|
|
fputc('\n', copystream);
|
|
break;
|
|
case 1:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
fflush(copystream);
|
|
return !PQendcopy(conn);
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
handleCopyIn(PGconn *conn, const bool mustprompt, FILE *copystream)
|
|
{
|
|
bool copydone = false;
|
|
bool firstload;
|
|
bool linedone;
|
|
char copybuf[COPYBUFSIZ];
|
|
char *s;
|
|
int buflen;
|
|
int c = 0;
|
|
|
|
if (mustprompt)
|
|
{
|
|
fputs("Enter info followed by a newline\n", stdout);
|
|
fputs("End with a backslash and a "
|
|
"period on a line by itself.\n", stdout);
|
|
}
|
|
while (!copydone)
|
|
{ /* for each input line ... */
|
|
if (mustprompt)
|
|
{
|
|
fputs(">> ", stdout);
|
|
fflush(stdout);
|
|
}
|
|
firstload = true;
|
|
linedone = false;
|
|
while (!linedone)
|
|
{ /* for each buffer ... */
|
|
s = copybuf;
|
|
for (buflen = COPYBUFSIZ; buflen > 1; buflen--)
|
|
{
|
|
c = getc(copystream);
|
|
if (c == '\n' || c == EOF)
|
|
{
|
|
linedone = true;
|
|
break;
|
|
}
|
|
*s++ = c;
|
|
}
|
|
*s = '\0';
|
|
if (c == EOF)
|
|
{
|
|
PQputline(conn, "\\.");
|
|
copydone = true;
|
|
break;
|
|
}
|
|
PQputline(conn, copybuf);
|
|
if (firstload)
|
|
{
|
|
if (!strcmp(copybuf, "\\."))
|
|
copydone = true;
|
|
firstload = false;
|
|
}
|
|
}
|
|
PQputline(conn, "\n");
|
|
}
|
|
return !PQendcopy(conn);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* try to open fname and return a FILE *, if it fails, use stdout, instead
|
|
*/
|
|
|
|
static FILE *
|
|
setFout(PsqlSettings *pset, char *fname)
|
|
{
|
|
if (pset->queryFout && pset->queryFout != stdout)
|
|
{
|
|
if (pset->pipe)
|
|
pclose(pset->queryFout);
|
|
else
|
|
fclose(pset->queryFout);
|
|
}
|
|
if (!fname)
|
|
{
|
|
pset->queryFout = stdout;
|
|
pqsignal(SIGPIPE, SIG_DFL);
|
|
}
|
|
else
|
|
{
|
|
if (*fname == '|')
|
|
{
|
|
pqsignal(SIGPIPE, SIG_IGN);
|
|
#ifndef __CYGWIN32__
|
|
pset->queryFout = popen(fname + 1, "w");
|
|
#else
|
|
pset->queryFout = popen(fname + 1, "wb");
|
|
#endif
|
|
pset->pipe = 1;
|
|
}
|
|
else
|
|
{
|
|
pset->queryFout = fopen(fname, "w");
|
|
pqsignal(SIGPIPE, SIG_DFL);
|
|
pset->pipe = 0;
|
|
}
|
|
if (!pset->queryFout)
|
|
{
|
|
perror(fname);
|
|
pset->queryFout = stdout;
|
|
}
|
|
}
|
|
return pset->queryFout;
|
|
}
|
|
|
|
static void
|
|
prompt_for_password(char *username, char *password)
|
|
{
|
|
char buf[512];
|
|
int length;
|
|
|
|
#ifdef HAVE_TERMIOS_H
|
|
struct termios t_orig,
|
|
t;
|
|
|
|
#endif
|
|
|
|
printf("Username: ");
|
|
fgets(username, 100, stdin);
|
|
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: ");
|
|
#ifdef HAVE_TERMIOS_H
|
|
tcgetattr(0, &t);
|
|
t_orig = t;
|
|
t.c_lflag &= ~ECHO;
|
|
tcsetattr(0, TCSADRAIN, &t);
|
|
#endif
|
|
fgets(password, 100, stdin);
|
|
#ifdef HAVE_TERMIOS_H
|
|
tcsetattr(0, TCSADRAIN, &t_orig);
|
|
#endif
|
|
|
|
length = strlen(password);
|
|
/* skip rest of the line */
|
|
if (length > 0 && password[length - 1] != '\n')
|
|
{
|
|
do
|
|
{
|
|
fgets(buf, 512, stdin);
|
|
} while (buf[strlen(buf) - 1] != '\n');
|
|
}
|
|
if (length > 0 && password[length - 1] == '\n')
|
|
password[length - 1] = '\0';
|
|
|
|
printf("\n\n");
|
|
}
|
|
|
|
static char *
|
|
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);
|
|
}
|
|
}
|