mirror of
https://github.com/postgres/postgres.git
synced 2025-08-22 21:53:06 +03:00
been applied. The patches are in the .tar.gz attachment at the end: varchar-array.patch this patch adds support for arrays of bpchar() and varchar(), which where always missing from postgres. These datatypes can be used to replace the _char4, _char8, etc., which were dropped some time ago. block-size.patch this patch fixes many errors in the parser and other program which happen with very large query statements (> 8K) when using a page size larger than 8192. This patch is needed if you want to submit queries larger than 8K. Postgres supports tuples up to 32K but you can't insert them because you can't submit queries larger than 8K. My patch fixes this problem. The patch also replaces all the occurrences of `8192' and `1<<13' in the sources with the proper constants defined in include files. You should now never find 8192 hardwired in C code, just to make code clearer. -- Massimo Dal Zotto
3263 lines
78 KiB
C
3263 lines
78 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.177 1999/05/03 19:10:08 momjian Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#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 __CYGWIN32__
|
|
#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 = stdin; /* 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;
|
|
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 ");
|
|
switch (info_type)
|
|
{
|
|
case 't':
|
|
strcat(listbuf, "WHERE ( relkind = 'r') ");
|
|
break;
|
|
case 'i':
|
|
strcat(listbuf, "WHERE ( relkind = 'i') ");
|
|
break;
|
|
case 'S':
|
|
strcat(listbuf, "WHERE ( relkind = 'S') ");
|
|
break;
|
|
case 'b':
|
|
default:
|
|
strcat(listbuf, "WHERE ( relkind = 'r' OR relkind = 'i' OR relkind = 'S') ");
|
|
break;
|
|
}
|
|
if (!system_tables)
|
|
strcat(listbuf, " and relname !~ '^pg_'");
|
|
else
|
|
strcat(listbuf, " and relname ~ '^pg_'");
|
|
strcat(listbuf, " and usesysid = relowner");
|
|
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, pg_user ");
|
|
strcat(listbuf, "WHERE ( relkind = 'r' OR relkind = 'i' OR relkind = 'S') ");
|
|
strcat(listbuf, " and relname !~ '^pg_'");
|
|
strcat(listbuf, " and usesysid = relowner");
|
|
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 (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 */
|
|
|
|
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;
|
|
#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;
|
|
|
|
if (mustprompt)
|
|
{
|
|
fputs("Enter info followed by a newline\n", stdout);
|
|
fputs("End with a backslash and a "
|
|
"period on a line by itself.\n", stdout);
|
|
}
|
|
while (!copydone)
|
|
{ /* for each input line ... */
|
|
if (mustprompt)
|
|
{
|
|
fputs(">> ", stdout);
|
|
fflush(stdout);
|
|
}
|
|
firstload = true;
|
|
linedone = false;
|
|
while (!linedone)
|
|
{ /* for each buffer ... */
|
|
s = copybuf;
|
|
buflen = COPYBUFSIZ;
|
|
for (; buflen > 1 &&
|
|
!(linedone = (c = getc(copystream)) == '\n' || c == EOF);
|
|
--buflen)
|
|
*s++ = c;
|
|
if (c == EOF)
|
|
{
|
|
PQputline(conn, "\\.");
|
|
copydone = true;
|
|
break;
|
|
}
|
|
*s = '\0';
|
|
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);
|
|
}
|
|
}
|