Major psql overhaul by Peter Eisentraut.

This commit is contained in:
Bruce Momjian 1999-11-04 21:56:02 +00:00
parent 2ea3b6d63a
commit a45195a191
30 changed files with 7026 additions and 152 deletions

View File

@ -1,13 +1,13 @@
#-------------------------------------------------------------------------
#
# Makefile.inc--
# Makefile.in--
# Makefile for bin/psql
#
# Copyright (c) 1994, Regents of the University of California
#
#
# IDENTIFICATION
# $Header: /cvsroot/pgsql/src/bin/psql/Attic/Makefile.in,v 1.15 1999/01/17 06:19:19 momjian Exp $
# $Header: /cvsroot/pgsql/src/bin/psql/Attic/Makefile.in,v 1.16 1999/11/04 21:56:01 momjian Exp $
#
#-------------------------------------------------------------------------
@ -28,7 +28,9 @@ ifdef MULTIBYTE
CFLAGS+= $(MBFLAGS)
endif
OBJS= psql.o stringutils.o @STRDUP@ @STRERROR2@
OBJS=command.o common.o help.o input.o stringutils.o mainloop.o \
copy.o startup.o prompt.o variables.o large_obj.o print.o describe.o \
@STRDUP@ @STRERROR2@
all: submake psql
@ -38,6 +40,18 @@ psql: $(OBJS) $(LIBPQDIR)/libpq.a
../../utils/strdup.o:
$(MAKE) -C ../../utils strdup.o
OBJS:
$(CC) $(CFLAGS) -c $< -o $@
help.o: sql_help.h
ifneq ($(strip $(PERL)),)
sql_help.h: ../../../doc/src/sgml/ref/*.sgml create_help.pl
$(PERL) create_help.pl sql_help.h
else
sql_help.h:
endif
.PHONY: submake
submake:
$(MAKE) -C $(LIBPQDIR) libpq.a
@ -46,14 +60,18 @@ install: psql
$(INSTALL) $(INSTL_EXE_OPTS) psql$(X) $(BINDIR)/psql$(X)
depend dep:
$(CC) -MM $(CFLAGS) *.c >depend
$(CC) -MM -MG $(CFLAGS) *.c >depend
clean:
rm -f psql$(X) $(OBJS)
rm -f psql$(X) $(OBJS)
# Some people might get in trouble if they do a make clean and the
# sql_help.h is gone, for it needs the docs in the right place to be
# regenerated. -- (pe)
distclean: clean
rm -f sql_help.h
ifeq (depend,$(wildcard depend))
include depend
endif

1228
src/bin/psql/command.c Normal file

File diff suppressed because it is too large Load Diff

49
src/bin/psql/command.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef COMMAND_H
#define COMMAND_H
#include <config.h>
#include <c.h>
#include <pqexpbuffer.h>
#include "settings.h"
#include "print.h"
typedef enum _backslashResult {
CMD_UNKNOWN = 0, /* not done parsing yet (internal only) */
CMD_SEND, /* query complete; send off */
CMD_SKIP_LINE, /* keep building query */
CMD_TERMINATE, /* quit program */
CMD_NEWEDIT, /* query buffer was changed (e.g., via \e) */
CMD_ERROR /* the execution of the backslash command resulted
in an error */
} backslashResult;
backslashResult
HandleSlashCmds(PsqlSettings *pset,
const char *line,
PQExpBuffer query_buf,
const char ** end_of_cmd);
bool
do_connect(const char *new_dbname,
const char *new_user,
PsqlSettings *pset);
bool
process_file(const char *filename,
PsqlSettings *pset);
bool
do_pset(const char * param,
const char * value,
printQueryOpt * popt,
bool quiet);
#endif

518
src/bin/psql/common.c Normal file
View File

@ -0,0 +1,518 @@
#include <config.h>
#include <c.h>
#include "common.h"
#include <stdlib.h>
#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif
#include <stdio.h>
#include <string.h>
#ifndef HAVE_STRDUP
#include <strdup.h>
#endif
#include <signal.h>
#include <assert.h>
#ifndef WIN32
#include <unistd.h> /* for write() */
#endif
#include <libpq-fe.h>
#include <pqsignal.h>
#include <version.h>
#include "settings.h"
#include "variables.h"
#include "copy.h"
#include "prompt.h"
#include "print.h"
#ifdef WIN32
#define popen(x,y) _popen(x,y)
#define pclose(x) _pclose(x)
#endif
/* xstrdup()
*
* "Safe" wrapper around strdup()
* (Using this also avoids writing #ifdef HAVE_STRDUP in every file :)
*/
char * xstrdup(const char * string)
{
char * tmp;
if (!string) {
fprintf(stderr, "xstrdup: Cannot duplicate null pointer.\n");
exit(EXIT_FAILURE);
}
tmp = strdup(string);
if (!tmp) {
perror("strdup");
exit(EXIT_FAILURE);
}
return tmp;
}
/*
* setQFout
* -- handler for -o command line option and \o command
*
* Tries to open file fname (or pipe if fname starts with '|')
* and stores the file handle in pset)
* Upon failure, sets stdout and returns false.
*/
bool
setQFout(const char *fname, PsqlSettings *pset)
{
bool status = true;
#ifdef USE_ASSERT_CHECKING
assert(pset);
#else
if (!pset) return false;
#endif
/* Close old file/pipe */
if (pset->queryFout && pset->queryFout != stdout && pset->queryFout != stderr)
{
if (pset->queryFoutPipe)
pclose(pset->queryFout);
else
fclose(pset->queryFout);
}
/* If no filename, set stdout */
if (!fname || fname[0]=='\0')
{
pset->queryFout = stdout;
pset->queryFoutPipe = false;
}
else if (*fname == '|')
{
const char * pipename = fname+1;
#ifndef __CYGWIN32__
pset->queryFout = popen(pipename, "w");
#else
pset->queryFout = popen(pipename, "wb");
#endif
pset->queryFoutPipe = true;
}
else
{
#ifndef __CYGWIN32__
pset->queryFout = fopen(fname, "w");
#else
pset->queryFout = fopen(fname, "wb");
#endif
pset->queryFoutPipe = false;
}
if (!pset->queryFout)
{
perror(fname);
pset->queryFout = stdout;
pset->queryFoutPipe = false;
status = false;
}
/* Direct signals */
if (pset->queryFoutPipe)
pqsignal(SIGPIPE, SIG_IGN);
else
pqsignal(SIGPIPE, SIG_DFL);
return status;
}
/*
* simple_prompt
*
* Generalized function especially intended for reading in usernames and
* password interactively. Reads from stdin.
*
* prompt: The prompt to print
* maxlen: How many characters to accept
* echo: Set to false if you want to hide what is entered (for passwords)
*
* Returns a malloc()'ed string with the input (w/o trailing newline).
*/
char *
simple_prompt(const char *prompt, int maxlen, bool echo)
{
int length;
char * destination;
#ifdef HAVE_TERMIOS_H
struct termios t_orig, t;
#endif
destination = (char *) malloc(maxlen+2);
if (!destination)
return NULL;
if (prompt) fputs(prompt, stdout);
#ifdef HAVE_TERMIOS_H
if (!echo)
{
tcgetattr(0, &t);
t_orig = t;
t.c_lflag &= ~ECHO;
tcsetattr(0, TCSADRAIN, &t);
}
#endif
fgets(destination, maxlen, stdin);
#ifdef HAVE_TERMIOS_H
if (!echo) {
tcsetattr(0, TCSADRAIN, &t_orig);
puts("");
}
#endif
length = strlen(destination);
if (length > 0 && destination[length - 1] != '\n') {
/* eat rest of the line */
char buf[512];
do {
fgets(buf, 512, stdin);
} while (buf[strlen(buf) - 1] != '\n');
}
if (length > 0 && destination[length - 1] == '\n')
/* remove trailing newline */
destination[length - 1] = '\0';
return destination;
}
/*
* interpolate_var()
*
* If the variable is a regular psql variable, just return its value.
* If it's a magic variable, return that value.
*
* This function only returns NULL if you feed in NULL. Otherwise it's ready for
* immediate consumption.
*/
const char *
interpolate_var(const char * name, PsqlSettings * pset)
{
const char * var;
#ifdef USE_ASSERT_CHECKING
assert(name);
assert(pset);
#else
if (!name || !pset) return NULL;
#endif
if (strspn(name, VALID_VARIABLE_CHARS) == strlen(name)) {
var = GetVariable(pset->vars, name);
if (var)
return var;
else
return "";
}
/* otherwise return magic variable */
/* (by convention these should be capitalized (but not all caps), to not be
shadowed by regular vars or to shadow env vars) */
if (strcmp(name, "Version")==0)
return PG_VERSION_STR;
if (strcmp(name, "Database")==0) {
if (PQdb(pset->db))
return PQdb(pset->db);
else
return "";
}
if (strcmp(name, "User")==0) {
if (PQuser(pset->db))
return PQuser(pset->db);
else
return "";
}
if (strcmp(name, "Host")==0) {
if (PQhost(pset->db))
return PQhost(pset->db);
else
return "";
}
if (strcmp(name, "Port")==0) {
if (PQport(pset->db))
return PQport(pset->db);
else
return "";
}
/* env vars (if env vars are all caps there should be no prob, otherwise
you're on your own */
if ((var = getenv(name)))
return var;
return "";
}
/*
* Code to support command cancellation.
*
* If interactive, we enable a SIGINT signal catcher before we start a
* query 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().
*/
PGconn * cancelConn;
#ifdef WIN32
#define safe_write_stderr(String) fputs(s, stderr)
#else
#define safe_write_stderr(String) write(fileno(stderr), String, strlen(String))
#endif
static void
handle_sigint(SIGNAL_ARGS)
{
/* accept signal if no connection */
if (cancelConn == NULL)
exit(1);
/* Try to send cancel request */
if (PQrequestCancel(cancelConn))
safe_write_stderr("\nCANCEL request sent\n");
else {
safe_write_stderr("\nCould not send cancel request: ");
safe_write_stderr(PQerrorMessage(cancelConn));
}
}
/*
* PSQLexec
*
* This is the way to send "backdoor" queries (those not directly entered
* by the user). It is subject to -E (echo_secret) but not -e (echo).
*/
PGresult *
PSQLexec(PsqlSettings *pset, const char *query)
{
PGresult *res;
const char * var;
if (!pset->db) {
fputs("You are not currently connected to a database.\n", stderr);
return NULL;
}
var = GetVariable(pset->vars, "echo_secret");
if (var) {
printf("********* QUERY *********\n%s\n*************************\n\n", query);
fflush(stdout);
}
if (var && strcmp(var, "noexec")==0)
return NULL;
cancelConn = pset->db;
pqsignal(SIGINT, handle_sigint); /* control-C => cancel */
res = PQexec(pset->db, query);
pqsignal(SIGINT, SIG_DFL); /* no control-C is back to normal */
if (PQstatus(pset->db) == CONNECTION_BAD)
{
fputs("The connection to the server was lost. Attempting reset: ", stderr);
PQreset(pset->db);
if (PQstatus(pset->db) == CONNECTION_BAD) {
fputs("Failed.\n", stderr);
PQfinish(pset->db);
PQclear(res);
pset->db = NULL;
return NULL;
}
else
fputs("Succeeded.\n", stderr);
}
if (res && (PQresultStatus(res) == PGRES_COMMAND_OK ||
PQresultStatus(res) == PGRES_TUPLES_OK ||
PQresultStatus(res) == PGRES_COPY_IN ||
PQresultStatus(res) == PGRES_COPY_OUT)
)
return res;
else {
fprintf(stderr, "%s", PQerrorMessage(pset->db));
PQclear(res);
return NULL;
}
}
/*
* SendQuery: send the query string to the backend
* (and print out results)
*
* Note: This is the "front door" way to send a query. That is, use it to
* send queries actually entered by the user. These queries will be subject to
* single step mode.
* To send "back door" queries (generated by slash commands, etc.) in a
* controlled way, use PSQLexec().
*
* Returns true if the query executed successfully, false otherwise.
*/
bool
SendQuery(PsqlSettings *pset, const char *query)
{
bool success = false;
PGresult *results;
PGnotify *notify;
if (!pset->db) {
fputs("You are not currently connected to a database.\n", stderr);
return false;
}
if (GetVariableBool(pset->vars, "singlestep")) {
char buf[3];
fprintf(stdout, "***(Single step mode: Verify query)*********************************************\n"
"QUERY: %s\n"
"***(press return to proceed or enter x and return to cancel)********************\n",
query);
fflush(stdout);
fgets(buf, 3, stdin);
if (buf[0]=='x')
return false;
fflush(stdin);
}
cancelConn = pset->db;
pqsignal(SIGINT, handle_sigint);
results = PQexec(pset->db, query);
pqsignal(SIGINT, SIG_DFL);
if (results == NULL)
{
fputs(PQerrorMessage(pset->db), pset->queryFout);
success = false;
}
else
{
switch (PQresultStatus(results))
{
case PGRES_TUPLES_OK:
if (pset->gfname)
{
PsqlSettings settings_copy = *pset;
settings_copy.queryFout = stdout;
if (!setQFout(pset->gfname, &settings_copy)) {
success = false;
break;
}
printQuery(results, &settings_copy.popt, settings_copy.queryFout);
/* close file/pipe */
setQFout(NULL, &settings_copy);
free(pset->gfname);
pset->gfname = NULL;
success = true;
break;
}
else
{
success = true;
printQuery(results, &pset->popt, pset->queryFout);
fflush(pset->queryFout);
}
break;
case PGRES_EMPTY_QUERY:
success = true;
break;
case PGRES_COMMAND_OK:
success = true;
fprintf(pset->queryFout, "%s\n", PQcmdStatus(results));
break;
case PGRES_COPY_OUT:
if (pset->cur_cmd_interactive && !GetVariable(pset->vars, "quiet"))
puts("Copy command returns:");
success = handleCopyOut(pset->db, pset->queryFout);
break;
case PGRES_COPY_IN:
if (pset->cur_cmd_interactive && !GetVariable(pset->vars, "quiet"))
puts("Enter data to be copied followed by a newline.\n"
"End with a backslash and a period on a line by itself.");
success = handleCopyIn(pset->db, pset->cur_cmd_source,
pset->cur_cmd_interactive ? get_prompt(pset, PROMPT_COPY) : NULL);
break;
case PGRES_NONFATAL_ERROR:
case PGRES_FATAL_ERROR:
case PGRES_BAD_RESPONSE:
success = false;
fputs(PQerrorMessage(pset->db), pset->queryFout);
break;
}
if (PQstatus(pset->db) == CONNECTION_BAD)
{
fputs("The connection to the server was lost. Attempting reset: ", stderr);
PQreset(pset->db);
if (PQstatus(pset->db) == CONNECTION_BAD) {
fputs("Failed.\n", stderr);
PQfinish(pset->db);
PQclear(results);
pset->db = NULL;
return false;
}
else
fputs("Succeeded.\n", stderr);
}
/* check for asynchronous notification returns */
while ((notify = PQnotifies(pset->db)) != NULL)
{
fprintf(pset->queryFout, "Asynchronous NOTIFY '%s' from backend with pid '%d' received.\n",
notify->relname, notify->be_pid);
free(notify);
}
if (results)
PQclear(results);
}
return success;
}

25
src/bin/psql/common.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef COMMON_H
#define COMMON_H
#include <c.h>
#include "settings.h"
char *
xstrdup(const char * string);
bool
setQFout(const char *fname, PsqlSettings *pset);
char *
simple_prompt(const char *prompt, int maxlen, bool echo);
const char *
interpolate_var(const char * name, PsqlSettings * pset);
PGresult *
PSQLexec(PsqlSettings *pset, const char *query);
bool
SendQuery(PsqlSettings *pset, const char *query);
#endif /* COMMON_H */

390
src/bin/psql/copy.c Normal file
View File

@ -0,0 +1,390 @@
#include <config.h>
#include <c.h>
#include "copy.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#ifndef WIN32
#include <unistd.h> /* for isatty */
#else
#include <io.h> /* I think */
#endif
#include <libpq-fe.h>
#include "settings.h"
#include "common.h"
#include "stringutils.h"
#ifdef WIN32
#define strcasecmp(x,y) stricmp(x,y)
#endif
/*
* parse_slash_copy
* -- parses \copy command line
*
* Accepted syntax: \copy [binary] table|"table" [with oids] from|to filename|'filename' using delimiters ['<char>']
* (binary is not here yet)
*
* returns a malloc'ed structure with the options, or NULL on parsing error
*/
struct copy_options {
char * table;
char * file;
bool from;
bool binary;
bool oids;
char * delim;
};
static void
free_copy_options(struct copy_options * ptr)
{
if (!ptr)
return;
free(ptr->table);
free(ptr->file);
free(ptr->delim);
free(ptr);
}
static struct copy_options *
parse_slash_copy(const char *args)
{
struct copy_options * result;
char * line;
char * token;
bool error = false;
char quote;
line = xstrdup(args);
if (!(result = calloc(1, sizeof (struct copy_options)))) {
perror("calloc");
exit(EXIT_FAILURE);
}
token = strtokx(line, " \t", "\"", '\\', &quote, NULL);
if (!token)
error = true;
else {
if (!quote && strcasecmp(token, "binary")==0) {
result->binary = true;
token = strtokx(NULL, " \t", "\"", '\\', &quote, NULL);
if (!token)
error = true;
}
if (token)
result->table = xstrdup(token);
}
#ifdef USE_ASSERT_CHECKING
assert(error || result->table);
#endif
if (!error) {
token = strtokx(NULL, " \t", NULL, '\\', NULL, NULL);
if (!token)
error = true;
else {
if (strcasecmp(token, "with")==0) {
token = strtokx(NULL, " \t", NULL, '\\', NULL, NULL);
if (!token || strcasecmp(token, "oids")!=0)
error = true;
else
result->oids = true;
if (!error) {
token = strtokx(NULL, " \t", NULL, '\\', NULL, NULL);
if (!token)
error = true;
}
}
if (!error && strcasecmp(token, "from")==0)
result->from = true;
else if (!error && strcasecmp(token, "to")==0)
result->from = false;
else
error = true;
}
}
if (!error) {
token = strtokx(NULL, " \t", "'", '\\', NULL, NULL);
if (!token)
error = true;
else
result->file=xstrdup(token);
}
#ifdef USE_ASSERT_CHECKING
assert(error || result->file);
#endif
if (!error) {
token = strtokx(NULL, " \t", NULL, '\\', NULL, NULL);
if (token) {
if (strcasecmp(token, "using")!=0)
error = true;
else {
token = strtokx(NULL, " \t", NULL, '\\', NULL, NULL);
if (!token || strcasecmp(token, "delimiters")!=0)
error = true;
else {
token = strtokx(NULL, " \t", "'", '\\', NULL, NULL);
if (token)
result->delim = xstrdup(token);
else
error = true;
}
}
}
}
free(line);
if (error) {
fputs("Parse error at ", stderr);
if (!token)
fputs("end of line.", stderr);
else
fprintf(stderr, "'%s'.", token);
fputs("\n", stderr);
free(result);
return NULL;
}
else
return result;
}
/*
* 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.
*/
bool
do_copy(const char * args, PsqlSettings *pset)
{
char query[128 + NAMEDATALEN];
FILE *copystream;
struct copy_options *options;
PGresult *result;
bool success;
/* parse options */
options = parse_slash_copy(args);
if (!options)
return false;
strcpy(query, "COPY ");
if (options->binary)
fputs("Warning: \\copy binary is not implemented. Resorting to text output.\n", stderr);
/* strcat(query, "BINARY "); */
strcat(query, "\"");
strncat(query, options->table, NAMEDATALEN);
strcat(query, "\" ");
if (options->oids)
strcat(query, "WITH OIDS ");
if (options->from)
strcat(query, "FROM stdin");
else
strcat(query, "TO stdout");
if (options->delim) {
/* backend copy only uses the first character here,
but that might be the escape backslash
(makes me wonder though why it's called delimiterS) */
strncat(query, " USING DELIMITERS '", 2);
strcat(query, options->delim);
strcat(query, "'");
}
if (options->from)
#ifndef __CYGWIN32__
copystream = fopen(options->file, "r");
#else
copystream = fopen(options->file, "rb");
#endif
else
#ifndef __CYGWIN32__
copystream = fopen(options->file, "w");
#else
copystream = fopen(options->file, "wb");
#endif
if (!copystream) {
fprintf(stderr,
"Unable to open file %s which to copy: %s\n",
options->from ? "from" : "to", strerror(errno));
free_copy_options(options);
return false;
}
result = PSQLexec(pset, query);
switch (PQresultStatus(result))
{
case PGRES_COPY_OUT:
success = handleCopyOut(pset->db, copystream);
break;
case PGRES_COPY_IN:
success = handleCopyIn(pset->db, copystream, NULL);
break;
case PGRES_NONFATAL_ERROR:
case PGRES_FATAL_ERROR:
case PGRES_BAD_RESPONSE:
success = false;
fputs(PQerrorMessage(pset->db), stderr);
break;
default:
success = false;
fprintf(stderr, "Unexpected response (%d)\n", PQresultStatus(result));
}
PQclear(result);
if (!GetVariable(pset->vars, "quiet")) {
if (success)
puts("Successfully copied.");
else
puts("Copy failed.");
}
fclose(copystream);
free_copy_options(options);
return success;
}
#define COPYBUFSIZ BLCKSZ
/*
* handeCopyOut
* receives data as a result of a COPY ... TO stdout command
*
* If you want to use COPY TO in your application, this is the code to steal :)
*
* conn should be a database connection that you just called COPY TO on
* (and which gave you PGRES_COPY_OUT back);
* copystream is the file stream you want the output to go to
*/
bool
handleCopyOut(PGconn *conn, FILE *copystream)
{
bool copydone = false; /* haven't started yet */
char copybuf[COPYBUFSIZ];
int ret;
while (!copydone)
{
ret = PQgetline(conn, copybuf, COPYBUFSIZ);
if (copybuf[0] == '\\' &&
copybuf[1] == '.' &&
copybuf[2] == '\0')
{
copydone = true; /* we're at the end */
}
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);
}
/*
* handeCopyOut
* receives data as a result of a COPY ... FROM stdin command
*
* Again, if you want to use COPY FROM in your application, copy this.
*
* conn should be a database connection that you just called COPY FROM on
* (and which gave you PGRES_COPY_IN back);
* copystream is the file stream you want the input to come from
* prompt is something to display to request user input (only makes sense
* if stdin is an interactive tty)
*/
bool
handleCopyIn(PGconn *conn, FILE *copystream, const char * prompt)
{
bool copydone = false;
bool firstload;
bool linedone;
char copybuf[COPYBUFSIZ];
char *s;
int buflen;
int c = 0;
while (!copydone)
{ /* for each input line ... */
if (prompt && isatty(fileno(stdin)))
{
fputs(prompt, 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);
}

22
src/bin/psql/copy.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef COPY_H
#define COPY_H
#include <c.h>
#include <stdio.h>
#include <libpq-fe.h>
#include "settings.h"
/* handler for \copy */
bool
do_copy(const char *args, PsqlSettings *pset);
/* lower level processors for copy in/out streams */
bool
handleCopyOut(PGconn *conn, FILE *copystream);
bool
handleCopyIn(PGconn *conn, FILE *copystream, const char * prompt);
#endif

View File

@ -0,0 +1,91 @@
#!/usr/bin/perl
#
# This script automatically generates the help on SQL in psql from the
# SGML docs. So far the format of the docs was consistent enough that
# this worked, but this here is my no means an SGML parser.
#
# It might be a good idea that this is just done once before distribution
# so people that don't have the docs or have slightly messed up docs or
# don't have perl, etc. won't have to bother.
#
# Call: perl create_help.pl sql_help.h
# (Do not rely on this script to be executable.)
# The name of the header file doesn't matter to this script, but it sure
# does matter to the rest of the source.
#
# A rule for this is also in the psql makefile.
#
$docdir = "./../../../doc/src/sgml/ref";
$outputfile = $ARGV[0] or die "Missing required argument.\n";
$define = $outputfile;
$define =~ tr/a-z/A-Z/;
$define =~ s/\W/_/g;
opendir DIR, $docdir or die "Couldn't open documentation sources: $!\n";
open OUT, ">$outputfile" or die "Couldn't open output file '$outputfile': $!\n";
print OUT
"/*
* This file is automatically generated from the SGML documentation.
* Direct changes here will be overwritten.
*/
#ifndef $define
#define $define
struct _helpStruct
{
char *cmd; /* the command name */
char *help; /* the help associated with it */
char *syntax; /* the syntax associated with it */
};
static struct _helpStruct QL_HELP[] = {
";
foreach $file (readdir DIR) {
my ($cmdname, $cmddesc, $cmdsynopsis);
$file =~ /\.sgml$/ || next;
open FILE, "$docdir/$file" or next;
$filecontent = join('', <FILE>);
close FILE;
$filecontent =~ m!<refmiscinfo>\s*SQL - Language Statements\s*</refmiscinfo>!i
or next;
$filecontent =~ m!<refname>\s*([a-z ]+?)\s*</refname>!i && ($cmdname = $1);
$filecontent =~ m!<refpurpose>\s*(.+?)\s*</refpurpose>!i && ($cmddesc = $1);
$filecontent =~ m!<synopsis>\s*(.+?)\s*</synopsis>!is && ($cmdsynopsis = $1);
if ($cmdname && $cmddesc && $cmdsynopsis) {
$cmdname =~ s/\"/\\"/g;
$cmddesc =~ s/<\/?.+?>//sg;
$cmddesc =~ s/\n/ /g;
$cmddesc =~ s/\"/\\"/g;
$cmdsynopsis =~ s/<\/?.+?>//sg;
$cmdsynopsis =~ s/\n/\\n/g;
$cmdsynopsis =~ s/\"/\\"/g;
print OUT " { \"$cmdname\",\n \"$cmddesc\",\n \"$cmdsynopsis\" },\n\n";
}
else {
print STDERR "Couldn't parse file '$file'. (N='$cmdname' D='$cmddesc')\n";
}
}
print OUT "
{ NULL, NULL, NULL } /* End of list marker */
};
#endif /* $define */
";
close OUT;
closedir DIR;

816
src/bin/psql/describe.c Normal file
View File

@ -0,0 +1,816 @@
#include <config.h>
#include <c.h>
#include "describe.h"
#include <string.h>
#include <postgres.h> /* for VARHDRSZ, int4 type */
#include <postgres_ext.h>
#include <libpq-fe.h>
#include "common.h"
#include "settings.h"
#include "print.h"
#include "variables.h"
/*----------------
* Handlers for various slash commands displaying some sort of list
* of things in the database.
*
* If you add something here, consider this:
* - If (and only if) the variable "description" is set, the description/
* comment for the object should be displayed.
* - Try to format the query to look nice in -E output.
*----------------
*/
/* the maximal size of regular expression we'll accept here */
/* (it is save to just change this here) */
#define REGEXP_CUTOFF 10 * NAMEDATALEN
/* \da
* takes an optional regexp to match specific aggregates by name
*/
bool
describeAggregates(const char * name, PsqlSettings * pset)
{
char descbuf[384 + 2*REGEXP_CUTOFF]; /* observe/adjust this if you change the query */
PGresult * res;
bool description = GetVariableBool(pset->vars, "description");
printQueryOpt myopt = pset->popt;
descbuf[0] = '\0';
/* There are two kinds of aggregates: ones that work on particular types
ones that work on all */
strcat(descbuf,
"SELECT a.aggname AS \"Name\", t.typname AS \"Type\"");
if (description)
strcat(descbuf,
",\n obj_description(a.oid) as \"Description\"");
strcat(descbuf,
"\nFROM pg_aggregate a, pg_type t\n"
"WHERE a.aggbasetype = t.oid\n");
if (name) {
strcat(descbuf, " AND a.aggname ~* '^");
strncat(descbuf, name, REGEXP_CUTOFF);
strcat(descbuf, "'\n");
}
strcat(descbuf,
"UNION\n"
"SELECT a.aggname AS \"Name\", '(all types)' as \"Type\"");
if (description)
strcat(descbuf,
",\n obj_description(a.oid) as \"Description\"");
strcat(descbuf,
"\nFROM pg_aggregate a\n"
"WHERE a.aggbasetype = 0\n");
if (name)
{
strcat(descbuf, " AND a.aggname ~* '^");
strncat(descbuf, name, REGEXP_CUTOFF);
strcat(descbuf, "'\n");
}
strcat(descbuf, "ORDER BY \"Name\", \"Type\"");
res = PSQLexec(pset, descbuf);
if (!res)
return false;
myopt.topt.tuples_only = false;
myopt.nullPrint = NULL;
myopt.title = "List of aggregates";
printQuery(res, &myopt, pset->queryFout);
PQclear(res);
return true;
}
/* \df
* takes an optional regexp to narrow down the function name
*/
bool
describeFunctions(const char * name, PsqlSettings * pset)
{
char descbuf[384 + REGEXP_CUTOFF];
PGresult * res;
printQueryOpt myopt = pset->popt;
/*
* 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\", p.proname as \"Function\",\n"
" oid8types(p.proargtypes) as \"Arguments\"");
if (GetVariableBool(pset->vars, "description"))
strcat(descbuf, "\n, obj_description(p.oid) as \"Description\"");
strcat(descbuf, "\nFROM pg_proc p, pg_type t\n"
"WHERE p.prorettype = t.oid and (pronargs = 0 or oid8types(p.proargtypes) != '')\n");
if (name)
{
strcat(descbuf, " AND p.proname ~* '^");
strncat(descbuf, name, REGEXP_CUTOFF);
strcat(descbuf, "'\n");
}
strcat(descbuf, "ORDER BY \"Function\", \"Result\", \"Arguments\"");
res = PSQLexec(pset, descbuf);
if (!res)
return false;
myopt.topt.tuples_only = false;
myopt.nullPrint = NULL;
myopt.title = "List of functions";
printQuery(res, &myopt, pset->queryFout);
PQclear(res);
return true;
}
/*
* describeTypes
*
* for \dT
*/
bool
describeTypes(const char * name, PsqlSettings * pset)
{
char descbuf[256 + REGEXP_CUTOFF];
PGresult * res;
printQueryOpt myopt = pset->popt;
descbuf[0] = '\0';
strcat(descbuf, "SELECT typname AS \"Type\"");
if (GetVariableBool(pset->vars, "description"))
strcat(descbuf, ", obj_description(p.oid) as \"Description\"");
strcat(descbuf, "\nFROM pg_type\n"
"WHERE typrelid = 0 AND typname !~ '^_.*'\n");
if (name) {
strcat(descbuf, " AND typname ~* '^");
strncat(descbuf, name, REGEXP_CUTOFF);
strcat(descbuf, "' ");
}
strcat(descbuf, "ORDER BY typname;");
res = PSQLexec(pset, descbuf);
if (!res)
return false;
myopt.topt.tuples_only = false;
myopt.nullPrint = NULL;
myopt.title = "List of types";
printQuery(res, &myopt, pset->queryFout);
PQclear(res);
return true;
}
/* \do
* NOTE: The (optional) argument here is _not_ a regexp since with all the
* funny chars floating around that would probably confuse people. It's an
* exact match string.
*/
bool
describeOperators(const char * name, PsqlSettings * pset)
{
char descbuf[1536 + 3 * 32]; /* 32 is max length for operator name */
PGresult * res;
bool description = GetVariableBool(pset->vars, "description");
printQueryOpt myopt = pset->popt;
descbuf[0] = '\0';
strcat(descbuf, "SELECT o.oprname AS \"Op\",\n"
" t1.typname AS \"Left arg\",\n"
" t2.typname AS \"Right arg\",\n"
" t0.typname AS \"Result\"");
if (description)
strcat(descbuf, ",\n obj_description(p.oid) as \"Description\"");
strcat(descbuf, "\nFROM pg_proc p, pg_type t0,\n"
" pg_type t1, pg_type t2,\n"
" pg_operator o\n"
"WHERE p.prorettype = t0.oid AND\n"
" RegprocToOid(o.oprcode) = p.oid AND\n"
" p.pronargs = 2 AND\n"
" o.oprleft = t1.oid AND\n"
" o.oprright = t2.oid\n");
if (name)
{
strcat(descbuf, " AND o.oprname = '");
strncat(descbuf, name, 32);
strcat(descbuf, "'\n");
}
strcat(descbuf, "\nUNION\n\n"
"SELECT o.oprname as \"Op\",\n"
" ''::name AS \"Left arg\",\n"
" t1.typname AS \"Right arg\",\n"
" t0.typname AS \"Result\"");
if (description)
strcat(descbuf, ",\n obj_description(p.oid) as \"Description\"");
strcat(descbuf, "\nFROM pg_operator o, pg_proc p, pg_type t0, pg_type t1\n"
"WHERE RegprocToOid(o.oprcode) = p.oid AND\n"
" o.oprresult = t0.oid AND\n"
" o.oprkind = 'l' AND\n"
" o.oprright = t1.oid\n");
if (name)
{
strcat(descbuf, "AND o.oprname = '");
strncat(descbuf, name, 32);
strcat(descbuf, "'\n");
}
strcat(descbuf, "\nUNION\n\n"
"SELECT o.oprname as \"Op\",\n"
" t1.typname AS \"Left arg\",\n"
" ''::name AS \"Right arg\",\n"
" t0.typname AS \"Result\"");
if (description)
strcat(descbuf, ",\n obj_description(p.oid) as \"Description\"");
strcat(descbuf, "\nFROM pg_operator o, pg_proc p, pg_type t0, pg_type t1\n"
"WHERE RegprocToOid(o.oprcode) = p.oid AND\n"
" o.oprresult = t0.oid AND\n"
" o.oprkind = 'r' AND\n"
" o.oprleft = t1.oid\n");
if (name)
{
strcat(descbuf, "AND o.oprname = '");
strncat(descbuf, name, 32);
strcat(descbuf, "'\n");
}
strcat(descbuf, "\nORDER BY \"Op\", \"Left arg\", \"Right arg\", \"Result\"");
res = PSQLexec(pset, descbuf);
if (!res)
return false;
myopt.topt.tuples_only = false;
myopt.nullPrint = NULL;
myopt.title = "List of operators";
printQuery(res, &myopt, pset->queryFout);
PQclear(res);
return true;
}
/*
* listAllDbs
*
* for \l, \list, and -l switch
*/
bool
listAllDbs(PsqlSettings *pset)
{
PGresult *res;
char descbuf[256];
printQueryOpt myopt = pset->popt;
descbuf[0] = '\0';
strcat(descbuf, "SELECT pg_database.datname as \"Database\",\n"
" pg_user.usename as \"Owner\""
#ifdef MULTIBYTE
",\n pg_database.encoding as \"Encoding\""
#endif
);
if (GetVariableBool(pset->vars, "description"))
strcat(descbuf, ",\n obj_description(pg_database.oid) as \"Description\"\n");
strcat(descbuf, "FROM pg_database, pg_user\n"
"WHERE pg_database.datdba = pg_user.usesysid\n"
"ORDER BY \"Database\"");
res = PSQLexec(pset, descbuf);
if (!res)
return false;
myopt.topt.tuples_only = false;
myopt.nullPrint = NULL;
myopt.title = "List of databases";
printQuery(res, &myopt, pset->queryFout);
PQclear(res);
return true;
}
/* List Tables Grant/Revoke Permissions
* \z (now also \dp -- perhaps more mnemonic)
*
*/
bool
permissionsList(const char * name, PsqlSettings *pset)
{
char descbuf[256 + REGEXP_CUTOFF];
PGresult *res;
printQueryOpt myopt = pset->popt;
descbuf[0] = '\0';
/* Currently, we ignore indexes since they have no meaningful rights */
strcat(descbuf, "SELECT relname as \"Relation\",\n"
" relacl as \"Access permissions\"\n"
"FROM pg_class\n"
"WHERE ( relkind = 'r' OR relkind = 'S') AND\n"
" relname !~ '^pg_'\n");
if (name) {
strcat(descbuf, " AND rename ~ '^");
strncat(descbuf, name, REGEXP_CUTOFF);
strcat(descbuf, "'\n");
}
strcat (descbuf, "ORDER BY relname");
res = PSQLexec(pset, descbuf);
if (!res)
return false;
if (PQntuples(res) == 0) {
fputs("Couldn't find any tables.\n", pset->queryFout);
}
else {
myopt.topt.tuples_only = false;
myopt.nullPrint = NULL;
sprintf(descbuf, "Access permissions for database \"%s\"", PQdb(pset->db));
myopt.title = descbuf;
printQuery(res, &myopt, pset->queryFout);
}
PQclear(res);
return true;
}
/*
* Get object comments
*
* \dd [foo]
*
* Note: This only lists things that actually have a description. For complete
* lists of things, there are other \d? commands.
*/
bool
objectDescription(const char * object, PsqlSettings *pset)
{
char descbuf[2048 + 7*REGEXP_CUTOFF];
PGresult *res;
printQueryOpt myopt = pset->popt;
descbuf[0] = '\0';
/* Aggregate descriptions */
strcat(descbuf, "SELECT DISTINCT a.aggname as \"Name\", 'aggregate'::text as \"What\", d.description as \"Description\"\n"
"FROM pg_aggregate a, pg_description d\n"
"WHERE a.oid = d.objoid\n");
if (object) {
strcat(descbuf," AND a.aggname ~* '^");
strncat(descbuf, object, REGEXP_CUTOFF);
strcat(descbuf,"'\n");
}
/* Function descriptions (except in/outs for datatypes) */
strcat(descbuf, "\nUNION ALL\n\n");
strcat(descbuf, "SELECT DISTINCT p.proname as \"Name\", 'function'::text as \"What\", d.description as \"Description\"\n"
"FROM pg_proc p, pg_description d\n"
"WHERE p.oid = d.objoid AND (p.pronargs = 0 or oid8types(p.proargtypes) != '')\n");
if (object) {
strcat(descbuf," AND p.proname ~* '^");
strncat(descbuf, object, REGEXP_CUTOFF);
strcat(descbuf,"'\n");
}
/* Operator descriptions */
strcat(descbuf, "\nUNION ALL\n\n");
strcat(descbuf, "SELECT DISTINCT o.oprname as \"Name\", 'operator'::text as \"What\", d.description as \"Description\"\n"
"FROM pg_operator o, pg_description d\n"
// must get comment via associated function
"WHERE RegprocToOid(o.oprcode) = d.objoid\n");
if (object) {
strcat(descbuf," AND o.oprname = '");
strncat(descbuf, object, REGEXP_CUTOFF);
strcat(descbuf,"'\n");
}
/* Type description */
strcat(descbuf, "\nUNION ALL\n\n");
strcat(descbuf, "SELECT DISTINCT t.typname as \"Name\", 'type'::text as \"What\", d.description as \"Description\"\n"
"FROM pg_type t, pg_description d\n"
"WHERE t.oid = d.objoid\n");
if (object) {
strcat(descbuf," AND t.typname ~* '^");
strncat(descbuf, object, REGEXP_CUTOFF);
strcat(descbuf,"'\n");
}
/* Relation (tables, views, indices, sequences) descriptions */
strcat(descbuf, "\nUNION ALL\n\n");
strcat(descbuf, "SELECT DISTINCT c.relname as \"Name\", 'relation'::text||'('||c.relkind||')' as \"What\", d.description as \"Description\"\n"
"FROM pg_class c, pg_description d\n"
"WHERE c.oid = d.objoid\n");
if (object) {
strcat(descbuf," AND c.relname ~* '^");
strncat(descbuf, object, REGEXP_CUTOFF);
strcat(descbuf,"'\n");
}
/* Rule description (ignore rules for views) */
strcat(descbuf, "\nUNION ALL\n\n");
strcat(descbuf, "SELECT DISTINCT r.rulename as \"Name\", 'rule'::text as \"What\", d.description as \"Description\"\n"
"FROM pg_rewrite r, pg_description d\n"
"WHERE r.oid = d.objoid AND r.rulename !~ '^_RET'\n");
if (object) {
strcat(descbuf," AND r.rulename ~* '^");
strncat(descbuf, object, REGEXP_CUTOFF);
strcat(descbuf,"'\n");
}
/* Trigger description */
strcat(descbuf, "\nUNION ALL\n\n");
strcat(descbuf, "SELECT DISTINCT t.tgname as \"Name\", 'trigger'::text as \"What\", d.description as \"Description\"\n"
"FROM pg_trigger t, pg_description d\n"
"WHERE t.oid = d.objoid\n");
if (object) {
strcat(descbuf," AND t.tgname ~* '^");
strncat(descbuf, object, REGEXP_CUTOFF);
strcat(descbuf,"'\n");
}
strcat(descbuf, "\nORDER BY \"Name\"");
res = PSQLexec(pset, descbuf);
if (!res)
return false;
myopt.topt.tuples_only = false;
myopt.nullPrint = NULL;
myopt.title = "Object descriptions";
printQuery(res, &myopt, pset->queryFout);
PQclear(res);
return true;
}
/*
* describeTableDetails (for \d)
*
* Unfortunately, the information presented here is so complicated that it
* be done in a single query. So we have to assemble the printed table by hand
* and pass it to the underlying printTable() function.
*
*/
static void * xmalloc(size_t size)
{
void * tmp;
tmp = malloc(size);
if (!tmp) {
perror("malloc");
exit(EXIT_FAILURE);
}
return tmp;
}
bool
describeTableDetails(const char * name, PsqlSettings * pset)
{
char descbuf[512 + NAMEDATALEN];
PGresult *res = NULL, *res2 = NULL, *res3 = NULL;
printTableOpt myopt = pset->popt.topt;
bool description = GetVariableBool(pset->vars, "description");
int i;
char * view_def = NULL;
char * headers[5];
char ** cells = NULL;
char * title = NULL;
char ** footers = NULL;
char ** ptr;
unsigned int cols;
cols = 3 + (description ? 1 : 0);
headers[0] = "Attribute";
headers[1] = "Type";
headers[2] = "Info";
if (description) {
headers[3] = "Description";
headers[4] = NULL;
}
else
headers[3] = NULL;
/* Get general table info */
strcpy(descbuf, "SELECT a.attname, t.typname, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, a.attnum");
if (description)
strcat(descbuf, ", obj_description(a.oid)");
strcat(descbuf, "\nFROM pg_class c, pg_attribute a, pg_type t\n"
"WHERE c.relname = '");
strncat(descbuf, name, NAMEDATALEN);
strcat(descbuf, "'\n AND a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid\n"
"ORDER BY a.attnum");
res = PSQLexec(pset, descbuf);
if (!res)
return false;
/* Did we get anything? */
if (PQntuples(res)==0) {
if (!GetVariableBool(pset->vars, "quiet"))
fprintf(stdout, "Did not find any class named \"%s\".\n", name);
PQclear(res);
return false;
}
/* Check if table is a view */
strcpy(descbuf, "SELECT definition FROM pg_views WHERE viewname = '");
strncat(descbuf, name, NAMEDATALEN);
strcat(descbuf, "'");
res2 = PSQLexec(pset, descbuf);
if (!res2)
return false;
if (PQntuples(res2) > 0)
view_def = PQgetvalue(res2,0,0);
/* Generate table cells to be printed */
cells = calloc(PQntuples(res) * cols + 1, sizeof(*cells));
if (!cells) {
perror("calloc");
exit(EXIT_FAILURE);
}
for (i = 0; i < PQntuples(res); i++) {
int4 attypmod = atoi(PQgetvalue(res, i, 3));
char * attype = PQgetvalue(res, i, 1);
/* Name */
cells[i*cols + 0] = PQgetvalue(res, i, 0); /* don't free this afterwards */
/* Type */
cells[i*cols + 1] = xmalloc(NAMEDATALEN + 16);
if (strcmp(attype, "bpchar")==0)
sprintf(cells[i*cols + 1], "char(%d)", attypmod != -1 ? attypmod - VARHDRSZ : 0);
else if (strcmp(attype, "varchar")==0)
sprintf(cells[i*cols + 1], "varchar(%d)", attypmod != -1 ? attypmod - VARHDRSZ : 0);
else if (strcmp(attype, "numeric")==0)
sprintf(cells[i*cols + 1], "numeric(%d,%d)", ((attypmod - VARHDRSZ) >> 16) & 0xffff,
(attypmod - VARHDRSZ) & 0xffff );
else if (attype[0] == '_')
sprintf(cells[i*cols + 1], "%s[]", attype+1);
else
strcpy(cells[i*cols + 1], attype);
/* Info */
cells[i*cols + 2] = xmalloc(128 + 128); /* I'm cutting off the default string at 128 */
cells[i*cols + 2][0] = '\0';
if (strcmp(PQgetvalue(res, i, 4), "t") == 0)
strcat(cells[i*cols + 2], "not null");
if (strcmp(PQgetvalue(res, i, 5), "t") == 0) {
/* handle "default" here */
strcpy(descbuf, "SELECT substring(d.adsrc for 128) FROM pg_attrdef d, pg_class c\n"
"WHERE c.relname = '");
strncat(descbuf, name, NAMEDATALEN);
strcat(descbuf, "' AND c.oid = d.adrelid AND d.adnum = ");
strcat(descbuf, PQgetvalue(res, i, 6));
res3 = PSQLexec(pset, descbuf);
if (!res) return false;
if (cells[i*cols+2][0]) strcat(cells[i*cols+2], " ");
strcat(cells[i*cols + 2], "default ");
strcat(cells[i*cols + 2], PQgetvalue(res3, 0, 0));
}
/* Description */
if (description)
cells[i*cols + 3] = PQgetvalue(res, i, 7);
}
/* Make title */
title = xmalloc(10 + strlen(name));
if (view_def)
sprintf(title, "View \"%s\"", name);
else
sprintf(title, "Table \"%s\"", name);
/* Make footers */
if (view_def) {
footers = xmalloc(2 * sizeof(*footers));
footers[0] = xmalloc(20 + strlen(view_def));
sprintf(footers[0], "View definition: %s", view_def);
footers[1] = NULL;
}
else {
/* display indices */
strcpy(descbuf, "SELECT c2.relname\n"
"FROM pg_class c, pg_class c2, pg_index i\n"
"WHERE c.relname = '");
strncat(descbuf, name, NAMEDATALEN);
strcat(descbuf, "' AND c.oid = i.indrelid AND i.indexrelid = c2.oid\n"
"ORDER BY c2.relname");
res3 = PSQLexec(pset, descbuf);
if (!res3)
return false;
if (PQntuples(res3) > 0) {
footers = xmalloc((PQntuples(res3) + 1) * sizeof(*footers));
for (i=0; i<PQntuples(res3); i++) {
footers[i] = xmalloc(10 + NAMEDATALEN);
if (PQntuples(res3)==1)
sprintf(footers[i], "Index: %s", PQgetvalue(res3, i, 0));
else if (i==0)
sprintf(footers[i], "Indices: %s", PQgetvalue(res3, i, 0));
else
sprintf(footers[i], " %s", PQgetvalue(res3, i, 0));
}
footers[i] = NULL;
}
}
myopt.tuples_only = false;
printTable(title, headers, cells, footers, "llll", &myopt, pset->queryFout);
/* clean up */
free(title);
for (i = 0; i<PQntuples(res); i++) {
free(cells[i*cols + 1]);
free(cells[i*cols + 2]);
}
free(cells);
for (ptr = footers; footers && *ptr; ptr++)
free(*ptr);
free(footers);
PQclear(res);
PQclear(res2);
PQclear(res3);
return true;
}
/*
* listTables()
*
* handler for \d, \dt, etc.
*
* The infotype is an array of characters, specifying what info is desired:
* t - tables
* i - indices
* v - views
* s - sequences
* S - systems tables (~'^pg_')
* (any order of the above is fine)
*/
bool
listTables(const char * infotype, const char * name, PsqlSettings * pset)
{
bool showTables = strchr(infotype, 't') != NULL;
bool showIndices= strchr(infotype, 'i') != NULL;
bool showViews = strchr(infotype, 'v') != NULL;
bool showSeq = strchr(infotype, 's') != NULL;
bool showSystem = strchr(infotype, 'S') != NULL;
bool description = GetVariableBool(pset->vars, "description");
char descbuf[1536 + 4 * REGEXP_CUTOFF];
PGresult *res;
printQueryOpt myopt = pset->popt;
descbuf[0] = '\0';
/* tables */
if (showTables) {
strcat(descbuf, "SELECT u.usename as \"Owner\", c.relname as \"Name\", 'table'::text as \"Type\"");
if (description)
strcat(descbuf, ", obj_description(c.oid) as \"Description\"");
strcat(descbuf, "\nFROM pg_class c, pg_user u\n"
"WHERE c.relowner = u.usesysid AND c.relkind = 'r'\n"
" AND not exists (select 1 from pg_views where viewname = c.relname)\n");
strcat(descbuf, showSystem ? " AND c.relname ~ '^pg_'\n" : " AND c.relname !~ '^pg_'\n");
if (name) {
strcat(descbuf, " AND c.relname ~ '^");
strncat(descbuf, name, REGEXP_CUTOFF);
strcat(descbuf, "'\n");
}
}
/* views */
if (showViews) {
if (descbuf[0])
strcat(descbuf, "\nUNION\n\n");
strcat(descbuf, "SELECT u.usename as \"Owner\", c.relname as \"Name\", 'view'::text as \"Type\"");
if (description)
strcat(descbuf, ", obj_description(c.oid) as \"Description\"");
strcat(descbuf, "\nFROM pg_class c, pg_user u\n"
"WHERE c.relowner = u.usesysid AND c.relkind = 'r'\n"
" AND exists (select 1 from pg_views where viewname = c.relname)\n");
strcat(descbuf, showSystem ? " AND c.relname ~ '^pg_'\n" : " AND c.relname !~ '^pg_'\n");
if (name) {
strcat(descbuf, " AND c.relname ~ '^");
strncat(descbuf, name, REGEXP_CUTOFF);
strcat(descbuf, "'\n");
}
}
/* indices, sequences */
if (showIndices || showSeq) {
if (descbuf[0])
strcat(descbuf, "\nUNION\n\n");
strcat(descbuf, "SELECT u.usename as \"Owner\", c.relname as \"Name\",\n"
" (CASE WHEN relkind = 'S' THEN 'sequence'::text ELSE 'index'::text END) as \"Type\"");
if (description)
strcat(descbuf, ", obj_description(c.oid) as \"Description\"");
strcat(descbuf, "\nFROM pg_class c, pg_user u\n"
"WHERE c.relowner = u.usesysid AND relkind in (");
if (showIndices && showSeq)
strcat(descbuf, "'i', 'S'");
else if (showIndices)
strcat(descbuf, "'i'");
else
strcat(descbuf, "'S'");
strcat(descbuf, ")\n");
/* ignore large-obj indices */
if (showIndices)
strcat(descbuf, " AND (c.relkind != 'i' OR c.relname !~ '^xinx')\n");
strcat(descbuf, showSystem ? " AND c.relname ~ '^pg_'\n" : " AND c.relname !~ '^pg_'\n");
if (name) {
strcat(descbuf, " AND c.relname ~ '^");
strncat(descbuf, name, REGEXP_CUTOFF);
strcat(descbuf, "'\n");
}
}
/* real system catalogue tables */
if (showSystem && showTables) {
if (descbuf[0])
strcat(descbuf, "\nUNION\n\n");
strcat(descbuf, "SELECT u.usename as \"Owner\", c.relname as \"Name\", 'system'::text as \"Type\"");
if (description)
strcat(descbuf, ", obj_description(c.oid) as \"Description\"");
strcat(descbuf, "\nFROM pg_class c, pg_user u\n"
"WHERE c.relowner = u.usesysid AND c.relkind = 's'\n");
if (name) {
strcat(descbuf, " AND c.relname ~ '^");
strncat(descbuf, name, REGEXP_CUTOFF);
strcat(descbuf, "'\n");
}
}
strcat(descbuf, "\nORDER BY \"Name\"");
res = PSQLexec(pset, descbuf);
if (!res)
return false;
if (PQntuples(res) == 0)
fprintf(pset->queryFout, "No matching classes found.\n");
else {
myopt.topt.tuples_only = false;
myopt.nullPrint = NULL;
myopt.title = "List of classes";
printQuery(res, &myopt, pset->queryFout);
}
PQclear(res);
return true;
}
/* the end */

42
src/bin/psql/describe.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef DESCRIBE_H
#define DESCRIBE_H
#include "settings.h"
/* \da */
bool
describeAggregates(const char * name, PsqlSettings * pset);
/* \df */
bool
describeFunctions(const char * name, PsqlSettings * pset);
/* \dT */
bool
describeTypes(const char * name, PsqlSettings * pset);
/* \do */
bool
describeOperators(const char * name, PsqlSettings * pset);
/* \dp (formerly \z) */
bool
permissionsList(const char * name, PsqlSettings *pset);
/* \dd */
bool
objectDescription(const char * object, PsqlSettings *pset);
/* \d foo */
bool
describeTableDetails(const char * name, PsqlSettings * pset);
/* \l */
bool
listAllDbs(PsqlSettings *pset);
/* \dt, \di, \dS, etc. */
bool
listTables(const char * infotype, const char * name, PsqlSettings * pset);
#endif /* DESCRIBE_H */

306
src/bin/psql/help.c Normal file
View File

@ -0,0 +1,306 @@
#include <config.h>
#include <c.h>
#include "help.h"
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#ifndef WIN32
#include <sys/ioctl.h> /* for ioctl() */
#ifdef HAVE_PWD_H
#include <pwd.h> /* for getpwuid() */
#endif
#include <sys/types.h> /* (ditto) */
#include <unistd.h> /* for getuid() */
#else
#define strcasecmp(x,y) stricmp(x,y)
#define popen(x,y) _popen(x,y)
#define pclose(x) _pclose(x)
#endif
#include <pqsignal.h>
#include <libpq-fe.h>
#include "settings.h"
#include "common.h"
#include "sql_help.h"
/*
* usage
*
* print out command line arguments and exit
*/
#define ON(var) (var ? "on" : "off")
void usage(void)
{
const char *env;
const char *user;
#ifndef WIN32
struct passwd *pw = NULL;
#endif
/* Find default user, in case we need it. */
user = getenv("USER");
if (!user) {
#ifndef WIN32
pw = getpwuid(getuid());
if (pw) user = pw->pw_name;
else {
perror("getpwuid()");
exit(EXIT_FAILURE);
}
#else
user = "?";
#endif
}
/* If string begins " here, then it ought to end there to fit on an 80 column terminal> > > > > > > " */
fprintf(stderr, "Usage: psql [options] [dbname [username]] \n");
fprintf(stderr, " -A Unaligned table output mode (-P format=unaligned)\n");
fprintf(stderr, " -c query Run single query (slash commands, too) and exit\n");
/* Display default database */
env = getenv("PGDATABASE");
if (!env) env=user;
fprintf(stderr, " -d dbname Specify database name to connect to (default: %s)\n", env);
fprintf(stderr, " -e Echo all input in non-interactive mode\n");
fprintf(stderr, " -E Display queries that internal commands generate\n");
fprintf(stderr, " -f filename Execute queries from file, then exit\n");
fprintf(stderr, " -F sep Set field separator (default: '" DEFAULT_FIELD_SEP "') (-P fieldsep=)\n");
/* Display default host */
env = getenv("PGHOST");
fprintf(stderr, " -h host Specify database server host (default: ");
if (env)
fprintf(stderr, env);
else
fprintf(stderr, "domain socket");
fprintf(stderr, ")\n");
fprintf(stderr, " -H HTML table output mode (-P format=html)\n");
fprintf(stderr, " -l List available databases, then exit\n");
fprintf(stderr, " -n Do not use readline and history\n");
fprintf(stderr, " -o filename Send query output to filename (or |pipe)\n");
/* Display default port */
env = getenv("PGPORT");
fprintf(stderr, " -p port Specify database server port (default: %s)\n",
env ? env : "hardwired");
fprintf(stderr, " -P var[=arg] Set printing option 'var' to 'arg'. (see \\pset command)\n");
fprintf(stderr, " -q Run quietly (no messages, no prompts)\n");
fprintf(stderr, " -s Single step mode (confirm each query)\n");
fprintf(stderr, " -S Single line mode (newline sends query)\n");
fprintf(stderr, " -t Don't print headings and row count (-P tuples_only)\n");
fprintf(stderr, " -T text Set HTML table tag options (e.g., width, border)\n");
fprintf(stderr, " -u Prompt for username and password (same as \"-U ? -W\")\n");
/* Display default user */
env = getenv("PGUSER");
if (!env) env=user;
fprintf(stderr, " -U [username] Specifiy username, \"?\"=prompt (default user: %s)\n", env);
fprintf(stderr, " -x Turn on expanded table output (-P expanded)\n");
fprintf(stderr, " -v name=val Set psql variable 'name' to 'value'\n");
fprintf(stderr, " -V Show version information and exit\n");
fprintf(stderr, " -W Prompt for password (should happen automatically)\n");
fprintf(stderr, "Consult the documentation for the complete details.\n");
#ifndef WIN32
if (pw) free(pw);
#endif
}
/*
* slashUsage
*
* print out help for the backslash commands
*/
#ifndef TIOCGWINSZ
struct winsize {
int ws_row;
int ws_col;
};
#endif
void
slashUsage(PsqlSettings *pset)
{
bool usePipe = false;
const char *pagerenv;
FILE *fout;
struct winsize screen_size;
#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 <= 36 &&
(fout = popen(pagerenv, "w")))
{
usePipe = true;
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, " \\c[onnect] [<dbname>|- [<user>|?]] -- connect to new database (currently '%s')\n", PQdb(pset->db));
fprintf(fout, " \\copy [binary] <table> [with oids] {from|to} <fname> [with delimiters '<char>']\n");
fprintf(fout, " \\copyright -- show PostgreSQL copyright\n");
fprintf(fout, " \\d -- list tables, views, and sequences\n");
fprintf(fout, " \\distvS -- list only indices/sequences/tables/views/system tables\n");
fprintf(fout, " \\da -- list aggregates\n");
fprintf(fout, " \\dd [<object>]- list comment for table, type, function, or operator\n");
fprintf(fout, " \\df -- list functions\n");
fprintf(fout, " \\do -- list operators\n");
fprintf(fout, " \\dT -- list data types\n");
fprintf(fout, " \\e [<fname>] -- edit the current query buffer or <fname> with external editor\n");
fprintf(fout, " \\echo <text> -- write text to stdout\n");
fprintf(fout, " \\g [<fname>] -- 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, " \\i <fname> -- read and execute queries from filename\n");
fprintf(fout, " \\l -- list all databases\n");
fprintf(fout, " \\lo_export, \\lo_import, \\lo_list, \\lo_unlink -- large object operations\n");
fprintf(fout, " \\o [<fname>] -- send all query results to <fname>, or |pipe\n");
fprintf(fout, " \\p -- print the content of the current query buffer\n");
fprintf(fout, " \\pset -- set table output options\n");
fprintf(fout, " \\q -- quit\n");
fprintf(fout, " \\qecho <text>-- write text to query output stream (see \\o)\n");
fprintf(fout, " \\r -- reset (clear) the query buffer\n");
fprintf(fout, " \\s [<fname>] -- print history or save it in <fname>\n");
fprintf(fout, " \\set <var> [<value>] -- set/unset internal variable\n");
fprintf(fout, " \\t -- don't show table headers or footers (currently %s)\n", ON(pset->popt.topt.tuples_only));
fprintf(fout, " \\x -- toggle expanded output (currently %s)\n", ON(pset->popt.topt.expanded));
fprintf(fout, " \\w <fname> -- write current query buffer to a file\n");
fprintf(fout, " \\z -- list table access permissions\n");
fprintf(fout, " \\! [<cmd>] -- shell escape or command\n");
if (usePipe) {
pclose(fout);
pqsignal(SIGPIPE, SIG_DFL);
}
}
/*
* helpSQL -- help with SQL commands
*
*/
void
helpSQL(const char *topic)
{
if (!topic || strlen(topic)==0)
{
char left_center_right; /* Which column we're displaying */
int i; /* Index into QL_HELP[] */
puts("Syntax: \\h <cmd> or \\help <cmd>, where <cmd> is one of the following:");
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");
puts("Or type \\h * for a complete description of all commands.");
}
else
{
int i;
bool help_found = false;
for (i = 0; QL_HELP[i].cmd; i++)
{
if (strcasecmp(QL_HELP[i].cmd, topic) == 0 ||
strcmp(topic, "*") == 0)
{
help_found = true;
printf("Command: %s\nDescription: %s\nSyntax:\n%s\n\n",
QL_HELP[i].cmd, QL_HELP[i].help, QL_HELP[i].syntax);
}
}
if (!help_found)
printf("No help available for '%s'.\nTry \\h with no arguments to see available help.\n", topic);
}
}
void
print_copyright(void)
{
puts(
"
PostgreSQL Data Base Management System
Copyright (c) 1996-9 PostgreSQL Global Development Group
This software is based on Postgres95, formerly known as Postgres, which
contains the following notice:
Copyright (c) 1994-7 Regents of the University of California
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose, without fee, and without a written agreement
is hereby granted, provided that the above copyright notice and this paragraph
and the following two paragraphs appear in all copies.
IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST
PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN \"AS IS\" BASIS,
AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE,
SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
(end of terms)"
);
}

16
src/bin/psql/help.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef HELP_H
#define HELP_H
#include "settings.h"
void usage(void);
void slashUsage(PsqlSettings *pset);
void helpSQL(const char *topic);
void print_copyright(void);
#endif

162
src/bin/psql/input.c Normal file
View File

@ -0,0 +1,162 @@
#include <config.h>
#include <c.h>
#include "input.h"
#include <pqexpbuffer.h>
/* Note that this file does not depend on any other files in psql. */
/* Runtime options for turning off readline and history */
/* (of course there is no runtime command for doing that :) */
#ifdef USE_READLINE
static bool useReadline;
#endif
#ifdef USE_HISTORY
static bool useHistory;
#endif
/*
* gets_interactive()
*
* Gets a line of interactive input, using readline of desired.
* The result is malloced.
*/
char *
gets_interactive(const char *prompt)
{
char * s;
#ifdef USE_READLINE
if (useReadline) {
s = readline(prompt);
fputc('\r', stdout);
fflush(stdout);
}
else {
#endif
fputs(prompt, stdout);
fflush(stdout);
s = gets_fromFile(stdin);
#ifdef USE_READLINE
}
#endif
#ifdef USE_HISTORY
if (useHistory && s && s[0] != '\0')
add_history(s);
#endif
return s;
}
/*
* gets_fromFile
*
* Gets a line of noninteractive input from a file (which could be stdin).
*/
char *
gets_fromFile(FILE *source)
{
PQExpBufferData buffer;
char line[1024];
initPQExpBuffer(&buffer);
while (fgets(line, 1024, source) != NULL) {
appendPQExpBufferStr(&buffer, line);
if (buffer.data[buffer.len-1] == '\n') {
buffer.data[buffer.len-1] = '\0';
return buffer.data;
}
}
if (buffer.len > 0)
return buffer.data; /* EOF after reading some bufferload(s) */
/* EOF, so return null */
termPQExpBuffer(&buffer);
return NULL;
}
/*
* Put any startup stuff related to input in here. It's good to maintain
* abstraction this way.
*
* The only "flag" right now is 1 for use readline & history.
*/
void
initializeInput(int flags)
{
#ifdef USE_READLINE
if (flags == 1) {
useReadline = true;
rl_readline_name = "psql";
}
#endif
#ifdef USE_HISTORY
if (flags == 1) {
const char * home;
useHistory = true;
using_history();
home = getenv("HOME");
if (home) {
char * psql_history = (char *) malloc(strlen(home) + 20);
if (psql_history) {
sprintf(psql_history, "%s/.psql_history", home);
read_history(psql_history);
free(psql_history);
}
}
}
#endif
}
bool
saveHistory(const char *fname)
{
#ifdef USE_HISTORY
if (useHistory) {
if (write_history(fname) != 0) {
perror(fname);
return false;
}
return true;
}
else
return false;
#else
return false;
#endif
}
void
finishInput(void)
{
#ifdef USE_HISTORY
if (useHistory) {
char * home;
char * psql_history;
home = getenv("HOME");
if (home) {
psql_history = (char *) malloc(strlen(home) + 20);
if (psql_history) {
sprintf(psql_history, "%s/.psql_history", home);
write_history(psql_history);
free(psql_history);
}
}
}
#endif
}

56
src/bin/psql/input.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef INPUT_H
#define INPUT_H
#include <config.h>
#include <c.h>
#include <stdio.h>
#include "settings.h"
/* If some other file needs to have access to readline/history, include this
* file and save yourself all this work.
*
* USE_READLINE and USE_HISTORY are the definite pointers regarding existence or not.
*/
#ifdef HAVE_LIBREADLINE
#ifdef HAVE_READLINE_H
#include <readline.h>
#define USE_READLINE 1
#else
#if defined(HAVE_READLINE_READLINE_H)
#include <readline/readline.h>
#define USE_READLINE 1
#endif
#endif
#endif
#if defined(HAVE_LIBHISTORY) || (defined(HAVE_LIBREADLINE) && defined(HAVE_HISTORY_IN_READLINE))
#if defined(HAVE_HISTORY_H)
#include <history.h>
#define USE_HISTORY 1
#else
#if defined(HAVE_READLINE_HISTORY_H)
#include <readline/history.h>
#define USE_HISTORY 1
#endif
#endif
#endif
char *
gets_interactive(const char *prompt);
char *
gets_fromFile(FILE *source);
void
initializeInput(int flags);
bool
saveHistory(const char *fname);
void
finishInput(void);
#endif

311
src/bin/psql/large_obj.c Normal file
View File

@ -0,0 +1,311 @@
#include <config.h>
#include <c.h>
#include "large_obj.h"
#include <stdio.h>
#include <string.h>
#include <libpq-fe.h>
#include <postgres.h>
#include "settings.h"
#include "variables.h"
#include "common.h"
#include "print.h"
/*
* Since all large object ops must be in a transaction, we must do some magic
* here. You can set the variable lo_transaction to one of commit|rollback|
* nothing to get your favourite behaviour regarding any transaction in
* progress. Rollback is default.
*/
static char notice[80];
static void
_my_notice_handler(void * arg, const char * message) {
(void)arg;
strncpy(notice, message, 79);
notice[79] = '\0';
}
static bool
handle_transaction(PsqlSettings * pset)
{
const char * var = GetVariable(pset->vars, "lo_transaction");
PGresult * res;
bool commit;
PQnoticeProcessor old_notice_hook;
if (var && strcmp(var, "nothing")==0)
return true;
commit = (var && strcmp(var, "commit")==0);
notice[0] = '\0';
old_notice_hook = PQsetNoticeProcessor(pset->db, _my_notice_handler, NULL);
res = PSQLexec(pset, commit ? "COMMIT" : "ROLLBACK");
if (!res)
return false;
if (notice[0]) {
if ( (!commit && strcmp(notice, "NOTICE: UserAbortTransactionBlock and not in in-progress state\n")!=0) ||
(commit && strcmp(notice, "NOTICE: EndTransactionBlock and not inprogress/abort state\n")!=0) )
fputs(notice, stderr);
}
else if (!GetVariableBool(pset->vars, "quiet")) {
if (commit)
puts("Warning: Your transaction in progress has been committed.");
else
puts("Warning: Your transaction in progress has been rolled back.");
}
PQsetNoticeProcessor(pset->db, old_notice_hook, NULL);
return true;
}
/*
* do_lo_export()
*
* Write a large object to a file
*/
bool
do_lo_export(PsqlSettings * pset, const char * loid_arg, const char * filename_arg)
{
PGresult * res;
int status;
bool own_transaction = true;
const char * var = GetVariable(pset->vars, "lo_transaction");
if (var && strcmp(var, "nothing")==0)
own_transaction = false;
if (!pset->db) {
fputs("You are not connected to a database.\n", stderr);
return false;
}
if (own_transaction) {
if (!handle_transaction(pset))
return false;
if (!(res = PSQLexec(pset, "BEGIN")))
return false;
PQclear(res);
}
status = lo_export(pset->db, atol(loid_arg), (char *)filename_arg);
if (status != 1) { /* of course this status is documented nowhere :( */
fputs(PQerrorMessage(pset->db), stderr);
if (own_transaction) {
res = PQexec(pset->db, "ROLLBACK");
PQclear(res);
}
return false;
}
if (own_transaction) {
if (!(res = PSQLexec(pset, "COMMIT"))) {
res = PQexec(pset->db, "ROLLBACK");
PQclear(res);
return false;
}
PQclear(res);
}
fprintf(pset->queryFout, "lo_export\n");
return true;
}
/*
* do_lo_import()
*
* Copy large object from file to database
*/
bool
do_lo_import(PsqlSettings * pset, const char * filename_arg, const char * comment_arg)
{
PGresult * res;
Oid loid;
char buf[1024];
unsigned int i;
bool own_transaction = true;
const char * var = GetVariable(pset->vars, "lo_transaction");
if (var && strcmp(var, "nothing")==0)
own_transaction = false;
if (!pset->db) {
fputs("You are not connected to a database.\n", stderr);
return false;
}
if (own_transaction) {
if (!handle_transaction(pset))
return false;
if (!(res = PSQLexec(pset, "BEGIN")))
return false;
PQclear(res);
}
loid = lo_import(pset->db, (char *)filename_arg);
if (loid == InvalidOid) {
fputs(PQerrorMessage(pset->db), stderr);
if (own_transaction) {
res = PQexec(pset->db, "ROLLBACK");
PQclear(res);
}
return false;
}
/* insert description if given */
if (comment_arg) {
sprintf(buf, "INSERT INTO pg_description VALUES (%d, '", loid);
for (i=0; i<strlen(comment_arg); i++)
if (comment_arg[i]=='\'')
strcat(buf, "\\'");
else
strncat(buf, &comment_arg[i], 1);
strcat(buf, "')");
if (!(res = PSQLexec(pset, buf))) {
if (own_transaction) {
res = PQexec(pset->db, "ROLLBACK");
PQclear(res);
}
return false;
}
}
if (own_transaction) {
if (!(res = PSQLexec(pset, "COMMIT"))) {
res = PQexec(pset->db, "ROLLBACK");
PQclear(res);
return false;
}
PQclear(res);
}
fprintf(pset->queryFout, "lo_import %d\n", loid);
return true;
}
/*
* do_lo_unlink()
*
* removes a large object out of the database
*/
bool do_lo_unlink(PsqlSettings * pset, const char * loid_arg)
{
PGresult * res;
int status;
Oid loid = (Oid)atol(loid_arg);
char buf[256];
bool own_transaction = true;
const char * var = GetVariable(pset->vars, "lo_transaction");
if (var && strcmp(var, "nothing")==0)
own_transaction = false;
if (!pset->db) {
fputs("You are not connected to a database.\n", stderr);
return false;
}
if (own_transaction) {
if (!handle_transaction(pset))
return false;
if (!(res = PSQLexec(pset, "BEGIN")))
return false;
PQclear(res);
}
status = lo_unlink(pset->db, loid);
if (status == -1) {
fputs(PQerrorMessage(pset->db), stderr);
if (own_transaction) {
res = PQexec(pset->db, "ROLLBACK");
PQclear(res);
}
return false;
}
/* remove the comment as well */
sprintf(buf, "DELETE FROM pg_description WHERE objoid = %d", loid);
if (!(res = PSQLexec(pset, buf))) {
if (own_transaction) {
res = PQexec(pset->db, "ROLLBACK");
PQclear(res);
}
return false;
}
if (own_transaction) {
if (!(res = PSQLexec(pset, "COMMIT"))) {
res = PQexec(pset->db, "ROLLBACK");
PQclear(res);
return false;
}
PQclear(res);
}
fprintf(pset->queryFout, "lo_unlink %d\n", loid);
return true;
}
/*
* do_lo_list()
*
* Show all large objects in database, with comments if desired
*/
bool do_lo_list(PsqlSettings * pset)
{
PGresult * res;
char descbuf[512];
printQueryOpt myopt = pset->popt;
descbuf[0] = '\0';
strcat(descbuf, "SELECT usename as \"Owner\", substring(relname from 5) as \"ID\"");
if (GetVariableBool(pset->vars, "description"))
strcat(descbuf, ",\n obj_description(pg_class.oid) as \"Description\"");
strcat(descbuf,"\nFROM pg_class, pg_user\n"
"WHERE usesysid = relowner AND relkind = 'l'\n"
"ORDER BY \"ID\"");
res = PSQLexec(pset, descbuf);
if (!res)
return false;
myopt.topt.tuples_only = false;
myopt.nullPrint = NULL;
myopt.title = "Large objects";
printQuery(res, &myopt, pset->queryFout);
PQclear(res);
return true;
}

11
src/bin/psql/large_obj.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef LARGE_OBJ_H
#define LARGE_OBJ_H
#include "settings.h"
bool do_lo_export(PsqlSettings * pset, const char * loid_arg, const char * filename_arg);
bool do_lo_import(PsqlSettings * pset, const char * filename_arg, const char * comment_arg);
bool do_lo_unlink(PsqlSettings * pset, const char * loid_arg);
bool do_lo_list(PsqlSettings * pset);
#endif /* LARGE_OBJ_H */

368
src/bin/psql/mainloop.c Normal file
View File

@ -0,0 +1,368 @@
#include <config.h>
#include <c.h>
#include "mainloop.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pqexpbuffer.h>
#include "settings.h"
#include "prompt.h"
#include "input.h"
#include "common.h"
#include "command.h"
/* 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.
*/
int
MainLoop(PsqlSettings *pset, FILE *source)
{
PQExpBuffer query_buf; /* buffer for query being accumulated */
char *line; /* current line of input */
char *xcomment; /* start of extended comment */
int len; /* length of the line */
int successResult = EXIT_SUCCESS;
backslashResult slashCmdStatus;
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;
unsigned int query_start;
int i, prevlen, thislen;
/* Save the prior command source */
FILE *prev_cmd_source;
bool prev_cmd_interactive;
bool die_on_error;
const char *interpol_char;
/* Save old settings */
prev_cmd_source = pset->cur_cmd_source;
prev_cmd_interactive = pset->cur_cmd_interactive;
/* Establish new source */
pset->cur_cmd_source = source;
pset->cur_cmd_interactive = ((source == stdin) && !pset->notty);
query_buf = createPQExpBuffer();
if (!query_buf) {
perror("createPQExpBuffer");
exit(EXIT_FAILURE);
}
xcomment = NULL;
in_quote = 0;
paren_level = 0;
slashCmdStatus = CMD_UNKNOWN; /* set default */
/* main loop to get queries and execute them */
while (!eof)
{
if (slashCmdStatus == CMD_NEWEDIT)
{
/*
* just returned from editing the line? then just copy to the
* input buffer
*/
line = strdup(query_buf->data);
resetPQExpBuffer(query_buf);
/* reset parsing state since we are rescanning whole query */
xcomment = NULL;
in_quote = 0;
paren_level = 0;
}
else
{
/*
* otherwise, set interactive prompt if necessary
* and get another line
*/
if (pset->cur_cmd_interactive)
{
int prompt_status;
if (in_quote && in_quote == '\'')
prompt_status = PROMPT_SINGLEQUOTE;
else if (in_quote && in_quote == '"')
prompt_status= PROMPT_DOUBLEQUOTE;
else if (xcomment != NULL)
prompt_status = PROMPT_COMMENT;
else if (query_buf->len > 0)
prompt_status = PROMPT_CONTINUE;
else
prompt_status = PROMPT_READY;
line = gets_interactive(get_prompt(pset, prompt_status));
}
else
line = gets_fromFile(source);
}
/* Setting these will not have effect until next line */
die_on_error = GetVariableBool(pset->vars, "die_on_error");
interpol_char = GetVariable(pset->vars, "sql_interpol");;
/*
* query_buf holds query already accumulated. line is the malloc'd
* new line of input (note it must be freed before looping around!)
* query_start is the next command start location within the line.
*/
/* No more input. Time to quit, or \i done */
if (line == NULL || (!pset->cur_cmd_interactive && *line == '\0'))
{
if (GetVariableBool(pset->vars, "echo") && !GetVariableBool(pset->vars, "quiet"))
puts("EOF");
eof = true;
continue;
}
/* not currently inside an extended comment? */
if (xcomment)
xcomment = line;
/* strip trailing backslashes, they don't have a clear meaning */
while (1) {
char * cp = strrchr(line, '\\');
if (cp && (*(cp + 1) == '\0'))
*cp = '\0';
else
break;
}
/* echo back if input is from file and flag is set */
if (!pset->cur_cmd_interactive && GetVariableBool(pset->vars, "echo"))
fprintf(stderr, "%s\n", line);
/* interpolate variables into SQL */
len = strlen(line);
thislen = PQmblen(line);
for (i = 0; line[i]; i += (thislen = PQmblen(&line[i])) ) {
if (interpol_char && interpol_char[0] != '\0' && interpol_char[0] == line[i]) {
size_t in_length, out_length;
const char * value;
char * new;
bool closer; /* did we have a closing delimiter or just an end of line? */
in_length = strcspn(&line[i+thislen], interpol_char);
closer = line[i + thislen + in_length] == line[i];
line[i + thislen + in_length] = '\0';
value = interpolate_var(&line[i + thislen], pset);
out_length = strlen(value);
new = malloc(len + out_length - (in_length + (closer ? 2 : 1)) + 1);
if (!new) {
perror("malloc");
exit(EXIT_FAILURE);
}
new[0] = '\0';
strncat(new, line, i);
strcat(new, value);
if (closer)
strcat(new, line + i + 2 + in_length);
free(line);
line = new;
i += out_length;
}
}
/* nothing left on line? then ignore */
if (line[0] == '\0') {
free(line);
continue;
}
slashCmdStatus = CMD_UNKNOWN;
len = strlen(line);
query_start = 0;
/*
* Parse line, looking for command separators.
*
* The current character is at line[i], the prior character at
* line[i - prevlen], the next character at line[i + thislen].
*/
prevlen = 0;
thislen = (len > 0) ? PQmblen(line) : 0;
#define ADVANCE_1 (prevlen = thislen, i += thislen, thislen = PQmblen(line+i))
success = true;
for (i = 0; i < len; ADVANCE_1) {
if (!success && die_on_error)
break;
/* was the previous character a backslash? */
if (i > 0 && line[i - prevlen] == '\\')
was_bslash = true;
else
was_bslash = false;
/* in quote? */
if (in_quote) {
/* end of quote */
if (line[i] == in_quote && !was_bslash)
in_quote = '\0';
}
/* start of quote */
else if (line[i] == '\'' || line[i] == '"')
in_quote = line[i];
/* in extended comment? */
else if (xcomment != NULL) {
if (line[i] == '*' && line[i + thislen] == '/') {
xcomment = NULL;
ADVANCE_1;
}
}
/* start of extended comment? */
else if (line[i] == '/' && line[i + thislen] == '*') {
xcomment = &line[i];
ADVANCE_1;
}
/* single-line comment? truncate line */
else if ((line[i] == '-' && line[i + thislen] == '-') ||
(line[i] == '/' && line[i + thislen] == '/'))
{
line[i] = '\0'; /* remove comment */
break;
}
/* count nested parentheses */
else if (line[i] == '(')
paren_level++;
else if (line[i] == ')' && paren_level > 0)
paren_level--;
/* semicolon? then send query */
else if (line[i] == ';' && !was_bslash && paren_level==0) {
line[i] = '\0';
/* is there anything else on the line? */
if (line[query_start + strspn(line + query_start, " \t")]!='\0') {
/* insert a cosmetic newline, if this is not the first line in the buffer */
if (query_buf->len > 0)
appendPQExpBufferChar(query_buf, '\n');
/* append the line to the query buffer */
appendPQExpBufferStr(query_buf, line + query_start);
}
/* execute query */
success = SendQuery(pset, query_buf->data);
resetPQExpBuffer(query_buf);
query_start = i + thislen;
}
/* backslash command */
else if (was_bslash) {
const char * end_of_cmd = NULL;
line[i - prevlen] = '\0'; /* overwrites backslash */
/* is there anything else on the line? */
if (line[query_start + strspn(line + query_start, " \t")]!='\0') {
/* insert a cosmetic newline, if this is not the first line in the buffer */
if (query_buf->len > 0)
appendPQExpBufferChar(query_buf, '\n');
/* append the line to the query buffer */
appendPQExpBufferStr(query_buf, line + query_start);
}
/* handle backslash command */
slashCmdStatus = HandleSlashCmds(pset, &line[i], query_buf, &end_of_cmd);
success = slashCmdStatus != CMD_ERROR;
if (slashCmdStatus == CMD_SEND) {
success = SendQuery(pset, query_buf->data);
resetPQExpBuffer(query_buf);
query_start = i + thislen;
}
/* is there anything left after the backslash command? */
if (end_of_cmd) {
i += end_of_cmd - &line[i];
query_start = i;
}
else
break;
}
}
if (!success && die_on_error && !pset->cur_cmd_interactive) {
successResult = EXIT_USER;
break;
}
if (slashCmdStatus == CMD_TERMINATE) {
successResult = EXIT_SUCCESS;
break;
}
/* Put the rest of the line in the query buffer. */
if (line[query_start + strspn(line + query_start, " \t")]!='\0') {
if (query_buf->len > 0)
appendPQExpBufferChar(query_buf, '\n');
appendPQExpBufferStr(query_buf, line + query_start);
}
free(line);
/* In single line mode, send off the query if any */
if (query_buf->data[0] != '\0' && GetVariableBool(pset->vars, "singleline")) {
success = SendQuery(pset, query_buf->data);
resetPQExpBuffer(query_buf);
}
/* Have we lost the db connection? */
if (pset->db == NULL && !pset->cur_cmd_interactive) {
successResult = EXIT_BADCONN;
break;
}
} /* while */
destroyPQExpBuffer(query_buf);
pset->cur_cmd_source = prev_cmd_source;
pset->cur_cmd_interactive = prev_cmd_interactive;
return successResult;
} /* MainLoop() */

10
src/bin/psql/mainloop.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef MAINLOOP_H
#define MAINLOOP_H
#include <stdio.h>
#include "settings.h"
int
MainLoop(PsqlSettings *pset, FILE *source);
#endif MAINLOOP_H

975
src/bin/psql/print.c Normal file
View File

@ -0,0 +1,975 @@
#include <config.h>
#include <c.h>
#include "print.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <signal.h>
#ifndef WIN32
#include <unistd.h> /* for isatty() */
#include <sys/ioctl.h> /* for ioctl() */
#else
#define popen(x,y) _popen(x,y)
#define pclose(x) _pclose(x)
#endif
#include <pqsignal.h>
#include <libpq-fe.h>
#include <postgres_ext.h> /* for Oid type */
#define DEFAULT_PAGER "/bin/more"
/*************************/
/* Unaligned text */
/*************************/
static void
print_unaligned_text(const char * title, char ** headers, char ** cells, char ** footers,
const char * opt_fieldsep, bool opt_barebones,
FILE * fout)
{
unsigned int col_count = 0;
unsigned int i;
char ** ptr;
if (!opt_fieldsep)
opt_fieldsep = "";
/* print title */
if (!opt_barebones && title)
fprintf(fout, "%s\n", title);
/* print headers and count columns */
for (ptr = headers; *ptr; ptr++) {
col_count++;
if (!opt_barebones) {
if (col_count>1)
fputs(opt_fieldsep, fout);
fputs(*ptr, fout);
}
}
if (!opt_barebones)
fputs("\n", fout);
/* print cells */
i = 0;
for (ptr = cells; *ptr; ptr++) {
fputs(*ptr, fout);
if ((i+1) % col_count)
fputs(opt_fieldsep, fout);
else
fputs("\n", fout);
i++;
}
/* print footers */
if (!opt_barebones && footers)
for (ptr = footers; *ptr; ptr++)
fprintf(fout, "%s\n", *ptr);
}
static void
print_unaligned_vertical(const char * title, char ** headers, char ** cells, char ** footers,
const char * opt_fieldsep, bool opt_barebones,
FILE * fout)
{
unsigned int col_count = 0;
unsigned int i;
unsigned int record = 1;
char ** ptr;
if (!opt_fieldsep)
opt_fieldsep = "";
/* print title */
if (!opt_barebones && title)
fprintf(fout, "%s\n", title);
/* count columns */
for (ptr = headers; *ptr; ptr++) {
col_count++;
}
/* print records */
for (i=0, ptr = cells; *ptr; i++, ptr++) {
if (i % col_count == 0) {
if (!opt_barebones)
fprintf(fout, "-- RECORD %d\n", record++);
else
fputc('\n', fout);
}
fprintf(fout, "%s%s%s\n", headers[i%col_count], opt_fieldsep, *ptr);
}
/* print footers */
if (!opt_barebones && footers) {
fputs("--- END ---\n", fout);
for (ptr = footers; *ptr; ptr++)
fprintf(fout, "%s\n", *ptr);
}
}
/********************/
/* Aligned text */
/********************/
/* draw "line" */
static void
_print_horizontal_line(const unsigned int col_count, const unsigned int * widths, unsigned short border, FILE * fout)
{
unsigned int i, j;
if (border == 1)
fputc('-', fout);
else if (border == 2)
fputs("+-", fout);
for (i=0; i<col_count; i++) {
for (j=0; j<widths[i]; j++)
fputc('-', fout);
if (i<col_count-1) {
if (border == 0)
fputc(' ', fout);
else
fputs("-+-", fout);
}
}
if (border == 2)
fputs("-+", fout);
else if (border == 1)
fputc('-', fout);
fputc('\n', fout);
}
static void
print_aligned_text(const char * title, char ** headers, char ** cells, char ** footers,
const char * opt_align, bool opt_barebones, unsigned short int opt_border,
FILE * fout)
{
unsigned int col_count = 0;
unsigned int i, tmp;
unsigned int * widths, total_w;
char ** ptr;
/* count columns */
for (ptr = headers; *ptr; ptr++)
col_count++;
widths = calloc(col_count, sizeof (*widths));
if (!widths) {
perror("calloc");
exit(EXIT_FAILURE);
}
/* calc column widths */
for (i=0; i<col_count; i++)
if ((tmp = strlen(headers[i])) > widths[i])
widths[i] = tmp; /* don't wanna call strlen twice */
for (i=0, ptr = cells; *ptr; ptr++, i++)
if ((tmp = strlen(*ptr)) > widths[i % col_count])
widths[i % col_count] = tmp;
if (opt_border==0)
total_w = col_count - 1;
else if (opt_border==1)
total_w = col_count*3 - 2;
else
total_w = col_count*3 + 1;
for (i=0; i<col_count; i++)
total_w += widths[i];
/* print title */
if (title && !opt_barebones) {
if (strlen(title)>=total_w)
fprintf(fout, "%s\n", title);
else
fprintf(fout, "%-*s%s\n", (total_w-strlen(title))/2, "", title);
}
/* print headers */
if (!opt_barebones) {
if (opt_border==2)
_print_horizontal_line(col_count, widths, opt_border, fout);
if (opt_border==2)
fputs("| ", fout);
else if (opt_border==1)
fputc(' ', fout);
for (i=0; i<col_count; i++) {
/* centered */
fprintf(fout, "%-*s%s%-*s", (int)floor((widths[i]-strlen(headers[i]))/2.0), "", headers[i], (int)ceil((widths[i]-strlen(headers[i]))/2.0) , "");
if (i<col_count-1) {
if (opt_border==0)
fputc(' ', fout);
else
fputs(" | ", fout);
}
}
if (opt_border==2)
fputs(" |", fout);
else if (opt_border==1)
fputc(' ', fout);;
fputc('\n', fout);
_print_horizontal_line(col_count, widths, opt_border, fout);
}
/* print cells */
for (i=0, ptr = cells; *ptr; i++, ptr++) {
/* beginning of line */
if (i % col_count == 0) {
if (opt_border==2)
fputs("| ", fout);
else if (opt_border==1)
fputc(' ', fout);
}
/* content */
if (opt_align[(i) % col_count ] == 'r')
fprintf(fout, "%*s", widths[i%col_count], cells[i]);
else {
if ((i+1) % col_count == 0 && opt_border != 2)
fputs(cells[i], fout);
else
fprintf(fout, "%-*s", widths[i%col_count], cells[i]);
}
/* divider */
if ((i+1) % col_count) {
if (opt_border==0)
fputc(' ', fout);
else
fputs(" | ", fout);
}
/* end of line */
else {
if (opt_border==2)
fputs(" |", fout);
fputc('\n', fout);
}
}
if (opt_border==2)
_print_horizontal_line(col_count, widths, opt_border, fout);
/* print footers */
if (footers && !opt_barebones)
for (ptr = footers; *ptr; ptr++)
fprintf(fout, "%s\n", *ptr);
fputc('\n', fout);
/* clean up */
free(widths);
}
static void
print_aligned_vertical(const char * title, char ** headers, char ** cells, char ** footers,
bool opt_barebones, unsigned short int opt_border,
FILE * fout)
{
unsigned int col_count = 0;
unsigned int record = 1;
char ** ptr;
unsigned int i, tmp, hwidth=0, dwidth=0;
char * divider;
/* count columns and find longest header */
for (ptr = headers; *ptr; ptr++) {
col_count++;
if ((tmp = strlen(*ptr)) > hwidth)
hwidth = tmp; /* don't wanna call strlen twice */
}
/* find longest data cell */
for (ptr = cells; *ptr; ptr++)
if ((tmp = strlen(*ptr)) > dwidth)
dwidth = tmp;
/* print title */
if (!opt_barebones && title)
fprintf(fout, "%s\n", title);
/* make horizontal border */
divider = malloc(hwidth + dwidth + 10);
if (!divider) {
perror("malloc");
exit(EXIT_FAILURE);
}
divider[0] = '\0';
if (opt_border == 2)
strcat(divider, "+-");
for (i=0; i<hwidth; i++) strcat(divider, opt_border > 0 ? "-" : " ");
if (opt_border > 0)
strcat(divider, "-+-");
else
strcat(divider, " ");
for (i=0; i<dwidth; i++) strcat(divider, opt_border > 0 ? "-" : " ");
if (opt_border == 2)
strcat(divider, "-+");
/* print records */
for (i=0, ptr = cells; *ptr; i++, ptr++) {
if (i % col_count == 0) {
if (!opt_barebones) {
char * div_copy = strdup(divider);
char * record_str = malloc(32);
size_t record_str_len;
if (!div_copy || !record_str) {
perror("malloc");
exit(EXIT_FAILURE);
}
if (opt_border==0)
sprintf(record_str, "* Record %d", record++);
else
sprintf(record_str, "[ RECORD %d ]", record++);
record_str_len = strlen(record_str);
if (record_str_len + opt_border > strlen(div_copy)) {
void * new;
new = realloc(div_copy, record_str_len + opt_border);
if (!new) {
perror("realloc");
exit(EXIT_FAILURE);
}
div_copy = new;
}
strncpy(div_copy + opt_border, record_str, record_str_len);
fprintf(fout, "%s\n", div_copy);
free(record_str);
free(div_copy);
}
else if (i != 0 && opt_border < 2)
fprintf(fout, "%s\n", divider);
}
if (opt_border == 2)
fputs("| ", fout);
fprintf(fout, "%-*s", hwidth, headers[i%col_count]);
if (opt_border > 0)
fputs(" | ", fout);
else
fputs(" ", fout);
if (opt_border < 2)
fprintf(fout, "%s\n", *ptr);
else
fprintf(fout, "%-*s |\n", dwidth, *ptr);
}
if (opt_border == 2)
fprintf(fout, "%s\n", divider);
/* print footers */
if (!opt_barebones && footers && *footers) {
if (opt_border < 2)
fputc('\n', fout);
for (ptr = footers; *ptr; ptr++)
fprintf(fout, "%s\n", *ptr);
}
fputc('\n', fout);
free(divider);
}
/**********************/
/* HTML printing ******/
/**********************/
static void
html_escaped_print(const char * in, FILE * fout)
{
const char * p;
for (p=in; *p; p++)
switch (*p) {
case '&':
fputs("&amp;", fout);
break;
case '<':
fputs("&lt;", fout);
break;
case '>':
fputs("&gt;", fout);
break;
case '\n':
fputs("<br>", fout);
break;
default:
fputc(*p, fout);
}
}
static void
print_html_text(const char * title, char ** headers, char ** cells, char ** footers,
const char * opt_align, bool opt_barebones, unsigned short int opt_border,
char * opt_table_attr,
FILE * fout)
{
unsigned int col_count = 0;
unsigned int i;
char ** ptr;
fprintf(fout, "<table border=%d", opt_border);
if (opt_table_attr)
fprintf(fout, " %s", opt_table_attr);
fputs(">\n", fout);
/* print title */
if (!opt_barebones && title) {
fputs(" <caption>", fout);
html_escaped_print(title, fout);
fputs("</caption>\n", fout);
}
/* print headers and count columns */
if (!opt_barebones)
fputs(" <tr>\n", fout);
for (i=0, ptr = headers; *ptr; i++, ptr++) {
col_count++;
if (!opt_barebones) {
fputs(" <th align=center>", fout);
html_escaped_print(*ptr, fout);
fputs("</th>\n", fout);
}
}
if (!opt_barebones)
fputs(" </tr>\n", fout);
/* print cells */
for (i=0, ptr = cells; *ptr; i++, ptr++) {
if ( i % col_count == 0 )
fputs(" <tr valign=top>\n", fout);
fprintf(fout, " <td align=%s>", opt_align[(i)%col_count] == 'r' ? "right" : "left");
if ( (*ptr)[strspn(*ptr, " \t")] == '\0' ) /* is string only whitespace? */
fputs("&nbsp;", fout);
else
html_escaped_print(*ptr, fout);
fputs("</td>\n", fout);
if ( (i+1) % col_count == 0 )
fputs(" </tr>\n", fout);
}
fputs("</table>\n", fout);
/* print footers */
if (footers && !opt_barebones)
for (ptr = footers; *ptr; ptr++) {
html_escaped_print(*ptr, fout);
fputs("<br>\n", fout);
}
fputc('\n', fout);
}
static void
print_html_vertical(const char * title, char ** headers, char ** cells, char ** footers,
const char * opt_align, bool opt_barebones, unsigned short int opt_border,
char * opt_table_attr,
FILE * fout)
{
unsigned int col_count = 0;
unsigned int i;
unsigned int record = 1;
char ** ptr;
fprintf(fout, "<table border=%d", opt_border);
if (opt_table_attr)
fprintf(fout, " %s", opt_table_attr);
fputs(">\n", fout);
/* print title */
if (!opt_barebones && title) {
fputs(" <caption>", fout);
html_escaped_print(title, fout);
fputs("</caption>\n", fout);
}
/* count columns */
for (ptr = headers; *ptr; ptr++)
col_count++;
/* print records */
for (i=0, ptr = cells; *ptr; i++, ptr++) {
if (i % col_count == 0) {
if (!opt_barebones)
fprintf(fout, "\n <tr><td colspan=2 align=center>Record %d</td></tr>\n", record++);
else
fputs("\n <tr><td colspan=2>&nbsp;</td></tr>\n", fout);
}
fputs(" <tr valign=top>\n"
" <th>", fout);
html_escaped_print(headers[i%col_count], fout);
fputs("</th>\n", fout);
fprintf(fout, " <td align=%s>", opt_align[i%col_count] == 'r' ? "right" : "left");
if ( (*ptr)[strspn(*ptr, " \t")] == '\0' ) /* is string only whitespace? */
fputs("&nbsp;", fout);
else
html_escaped_print(*ptr, fout);
fputs("</td>\n </tr>\n", fout);
}
fputs("</table>\n", fout);
/* print footers */
if (footers && !opt_barebones)
for (ptr = footers; *ptr; ptr++) {
html_escaped_print(*ptr, fout);
fputs("<br>\n", fout);
}
fputc('\n', fout);
}
/*************************/
/* LaTeX */
/*************************/
static void
latex_escaped_print(const char * in, FILE * fout)
{
const char * p;
for (p=in; *p; p++)
switch (*p) {
case '&':
fputs("\\&", fout);
break;
case '%':
fputs("\\%", fout);
break;
case '$':
fputs("\\$", fout);
break;
case '{':
fputs("\\{", fout);
break;
case '}':
fputs("\\}", fout);
break;
case '\\':
fputs("\\backslash", fout);
break;
case '\n':
fputs("\\\\", fout);
break;
default:
fputc(*p, fout);
}
}
static void
print_latex_text(const char * title, char ** headers, char ** cells, char ** footers,
const char * opt_align, bool opt_barebones, unsigned short int opt_border,
FILE * fout)
{
unsigned int col_count = 0;
unsigned int i;
const char * cp;
char ** ptr;
/* print title */
if (!opt_barebones && title) {
fputs("\begin{center}\n", fout);
latex_escaped_print(title, fout);
fputs("\n\end{center}\n\n", fout);
}
/* begin environment and set alignments and borders */
fputs("\\begin{tabular}{", fout);
if (opt_border==0)
fputs(opt_align, fout);
else if (opt_border==1) {
for (cp = opt_align; *cp; cp++) {
if (cp != opt_align) fputc('|', fout);
fputc(*cp, fout);
}
}
else if (opt_border==2) {
for (cp = opt_align; *cp; cp++) {
fputc('|', fout);
fputc(*cp, fout);
}
fputc('|', fout);
}
fputs("}\n", fout);
if (!opt_barebones && opt_border==2)
fputs("\\hline\n", fout);
/* print headers and count columns */
for (i=0, ptr = headers; *ptr; i++, ptr++) {
col_count++;
if (!opt_barebones) {
if (i!=0)
fputs(" & ", fout);
latex_escaped_print(*ptr, fout);
}
}
if (!opt_barebones) {
fputs(" \\\\\n", fout);
fputs("\\hline\n", fout);
}
/* print cells */
for (i=0, ptr = cells; *ptr; i++, ptr++) {
latex_escaped_print(*ptr, fout);
if ( (i+1) % col_count == 0 )
fputs(" \\\\\n", fout);
else
fputs(" & ", fout);
}
if (opt_border==2)
fputs("\\hline\n", fout);
fputs("\\end{tabular}\n\n", fout);
/* print footers */
if (footers && !opt_barebones)
for (ptr = footers; *ptr; ptr++) {
latex_escaped_print(*ptr, fout);
fputs(" \\\\\n", fout);
}
fputc('\n', fout);
}
static void
print_latex_vertical(const char * title, char ** headers, char ** cells, char ** footers,
const char * opt_align, bool opt_barebones, unsigned short int opt_border,
FILE * fout)
{
unsigned int col_count = 0;
unsigned int i;
char ** ptr;
unsigned int record = 1;
(void)opt_align; /* currently unused parameter */
/* print title */
if (!opt_barebones && title) {
fputs("\begin{center}\n", fout);
latex_escaped_print(title, fout);
fputs("\n\end{center}\n\n", fout);
}
/* begin environment and set alignments and borders */
fputs("\\begin{tabular}{", fout);
if (opt_border==0)
fputs("cl", fout);
else if (opt_border==1)
fputs("c|l", fout);
else if (opt_border==2)
fputs("|c|l|", fout);
fputs("}\n", fout);
/* count columns */
for (ptr = headers; *ptr; ptr++)
col_count++;
/* print records */
for (i=0, ptr = cells; *ptr; i++, ptr++) {
/* new record */
if (i % col_count == 0) {
if (!opt_barebones) {
if (opt_border == 2)
fputs("\\hline\n", fout);
fprintf(fout, "\\multicolumn{2}{c}{Record %d} \\\\\n", record++);
}
if (opt_border >= 1)
fputs("\\hline\n", fout);
}
latex_escaped_print(headers[i%col_count], fout);
fputs(" & ", fout);
latex_escaped_print(*ptr, fout);
fputs(" \\\\\n", fout);
}
if (opt_border==2)
fputs("\\hline\n", fout);
fputs("\\end{tabular}\n\n", fout);
/* print footers */
if (footers && !opt_barebones)
for (ptr = footers; *ptr; ptr++) {
latex_escaped_print(*ptr, fout);
fputs(" \\\\\n", fout);
}
fputc('\n', fout);
}
/********************************/
/* Public functions */
/********************************/
void
printTable(const char * title, char ** headers, char ** cells, char ** footers,
const char * align,
const printTableOpt * opt, FILE * fout)
{
char * default_footer[] = { NULL };
unsigned short int border = opt->border;
FILE * pager = NULL,
* output;
if (opt->format == PRINT_NOTHING)
return;
if (!footers)
footers = default_footer;
if (opt->format != PRINT_HTML && border > 2)
border = 2;
/* check whether we need / can / are supposed to use pager */
if (fout == stdout && opt->pager
#ifndef WIN32
&&
isatty(fileno(stdin)) &&
isatty(fileno(stdout))
#endif
)
{
const char * pagerprog;
#ifdef TIOCGWINSZ
unsigned int col_count=0, row_count=0, lines;
char ** ptr;
int result;
struct winsize screen_size;
/* rough estimate of columns and rows */
if (headers)
for (ptr=headers; *ptr; ptr++)
col_count++;
if (cells)
for (ptr=cells; *ptr; ptr++)
row_count++;
row_count /= col_count;
if (opt->expanded)
lines = (col_count+1) * row_count;
else
lines = row_count+1;
if (!opt->tuples_only)
lines += 5;
result = ioctl(fileno(stdout), TIOCGWINSZ, &screen_size);
if (result==-1 || lines > screen_size.ws_row) {
#endif
pagerprog = getenv("PAGER");
if (!pagerprog) pagerprog = DEFAULT_PAGER;
pager = popen(pagerprog, "w");
#ifdef TIOCGWINSZ
}
#endif
}
if (pager) {
output = pager;
pqsignal(SIGPIPE, SIG_IGN);
}
else
output = fout;
/* print the stuff */
switch (opt->format) {
case PRINT_UNALIGNED:
if (opt->expanded)
print_unaligned_vertical(title, headers, cells, footers, opt->fieldSep, opt->tuples_only, output);
else
print_unaligned_text(title, headers, cells, footers, opt->fieldSep, opt->tuples_only, output);
break;
case PRINT_ALIGNED:
if (opt->expanded)
print_aligned_vertical(title, headers, cells, footers, opt->tuples_only, border, output);
else
print_aligned_text(title, headers, cells, footers, align, opt->tuples_only, border, output);
break;
case PRINT_HTML:
if (opt->expanded)
print_html_vertical(title, headers, cells, footers, align, opt->tuples_only, border, opt->tableAttr, output);
else
print_html_text(title, headers, cells, footers, align, opt->tuples_only, border, opt->tableAttr, output);
break;
case PRINT_LATEX:
if (opt->expanded)
print_latex_vertical(title, headers, cells, footers, align, opt->tuples_only, border, output);
else
print_latex_text(title, headers, cells, footers, align, opt->tuples_only, border, output);
break;
default:
fprintf(stderr, "+ Oops, you shouldn't see this!\n");
}
if (pager) {
pclose(pager);
pqsignal(SIGPIPE, SIG_DFL);
}
}
void
printQuery(PGresult * result, const printQueryOpt * opt, FILE * fout)
{
int nfields;
char ** headers;
char ** cells;
char ** footers;
char * align;
int i;
/* extract headers */
nfields = PQnfields(result);
headers = calloc(nfields+1, sizeof(*headers));
if (!headers) {
perror("calloc");
exit(EXIT_FAILURE);
}
for (i=0; i<nfields; i++)
headers[i] = PQfname(result, i);
/* set cells */
cells = calloc(nfields * PQntuples(result) + 1, sizeof(*cells));
if (!cells) {
perror("calloc");
exit(EXIT_FAILURE);
}
for (i=0; i< nfields * PQntuples(result); i++) {
if (PQgetisnull(result, i / nfields, i % nfields))
cells[i] = opt->nullPrint ? opt->nullPrint : "";
else
cells[i] = PQgetvalue(result, i / nfields, i % nfields);
}
/* set footers */
if (opt->footers)
footers = opt->footers;
else if (!opt->topt.expanded) {
footers = calloc(2, sizeof(*footers));
if (!footers) {
perror("calloc");
exit(EXIT_FAILURE);
}
footers[0] = malloc(100);
if (PQntuples(result)==1)
strcpy(footers[0], "(1 row)");
else
sprintf(footers[0], "(%d rows)", PQntuples(result));
}
else
footers = NULL;
/* set alignment */
align = calloc(nfields+1, sizeof(*align));
if (!align) {
perror("calloc");
exit(EXIT_FAILURE);
}
for (i=0; i<nfields; i++) {
Oid ftype = PQftype(result, i);
if ( ftype == 20 || /* int8 */
ftype == 21 || /* int2 */
ftype == 23 || /* int4 */
(ftype >=26 && ftype <=30) || /* ?id */
ftype == 700 || /* float4 */
ftype == 701 || /* float8 */
ftype == 790 || /* money */
ftype == 1700 /* numeric */
)
align[i] = 'r';
else
align[i] = 'l';
}
/* call table printer */
printTable(opt->title, headers, cells, footers ? footers : opt->footers, align,
&opt->topt, fout);
free(headers);
free(cells);
if (footers) {
free(footers[0]);
free(footers);
}
}
/* the end */

66
src/bin/psql/print.h Normal file
View File

@ -0,0 +1,66 @@
#ifndef PRINT_H
#define PRINT_H
#include <config.h>
#include <c.h>
#include <stdio.h>
#include <libpq-fe.h>
enum printFormat {
PRINT_NOTHING = 0, /* to make sure someone initializes this */
PRINT_UNALIGNED,
PRINT_ALIGNED,
PRINT_HTML,
PRINT_LATEX
/* add your favourite output format here ... */
};
typedef struct _printTableOpt {
enum printFormat format; /* one of the above */
bool expanded; /* expanded/vertical output (if supported by output format) */
bool pager; /* use pager for output (if to stdout and stdout is a tty) */
bool tuples_only; /* don't output headers, row counts, etc. */
unsigned short int border; /* Print a border around the table. 0=none, 1=dividing lines, 2=full */
char *fieldSep; /* field separator for unaligned text mode */
char *tableAttr; /* attributes for HTML <table ...> */
} printTableOpt;
/*
* Use this to print just any table in the supported formats.
* - title is just any string (NULL is fine)
* - headers is the column headings (NULL ptr terminated). It must be given and
* complete since the column count is generated from this.
* - cells are the data cells to be printed. Now you know why the correct
* column count is important
* - footers are lines to be printed below the table
* - align is an 'l' or an 'r' for every column, if the output format needs it.
* (You must specify this long enough. Otherwise anything could happen.)
*/
void
printTable(const char * title, char ** headers, char ** cells, char ** footers,
const char * align,
const printTableOpt * opt, FILE * fout);
typedef struct _printQueryOpt {
printTableOpt topt; /* the options above */
char * nullPrint; /* how to print null entities */
bool quote; /* quote all values as much as possible */
char * title; /* override title */
char ** footers; /* override footer (default is "(xx rows)") */
} printQueryOpt;
/*
* Use this to print query results
*
* It calls the printTable above with all the things set straight.
*/
void
printQuery(PGresult * result, const printQueryOpt * opt, FILE * fout);
#endif /* PRINT_H */

256
src/bin/psql/prompt.c Normal file
View File

@ -0,0 +1,256 @@
#include <config.h>
#include <c.h>
#include "prompt.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>
#include "settings.h"
#include "common.h"
#ifdef WIN32
#define popen(x,y) _popen(x,y)
#define pclose(x) _pclose(x)
#endif
/*--------------------------
* get_prompt
*
* Returns a statically allocated prompt made by interpolating certain
* tcsh style escape sequences into pset->vars "prompt1|2|3".
* (might not be completely multibyte safe)
*
* Defined interpolations are:
* %M - database server hostname (or "." if not TCP/IP)
* %m - like %M but hostname truncated after first dot
* %> - database server port number (or "." if not TCP/IP)
* %n - database user name
* %/ - current database
* %~ - like %/ but "~" when database name equals user name
* %# - "#" if the username is postgres, ">" otherwise
* %R - in prompt1 normally =, or ^ if single line mode,
* or a ! if session is not connected to a database;
* in prompt2 -, *, ', or ";
* in prompt3 nothing
* %? - the error code of the last query (not yet implemented)
* %% - a percent sign
*
* %[0-9] - the character with the given decimal code
* %0[0-7] - the character with the given octal code
* %0x[0-9A-Fa-f] - the character with the given hexadecimal code
*
* %`command` - The result of executing command in /bin/sh with trailing
* newline stripped.
* %$name$ - The value of the psql/environment/magic varible 'name'
* (same rules as for, e.g., \echo $foo)
* (those will not be rescanned for more escape sequences!)
*
*
* If the application-wide prompts became NULL somehow, the returned string
* will be empty (not NULL!). Do not free() the result of this function unless
* you want trouble.
*--------------------------
*/
const char *
get_prompt(PsqlSettings *pset, promptStatus_t status)
{
#define MAX_PROMPT_SIZE 256
static char destination[MAX_PROMPT_SIZE+1];
char buf[MAX_PROMPT_SIZE+1];
bool esc = false;
const char *p;
const char * prompt_string;
if (GetVariable(pset->vars, "quiet"))
return "";
if (status == PROMPT_READY)
prompt_string = GetVariable(pset->vars, "prompt1");
else if (status == PROMPT_CONTINUE || status == PROMPT_SINGLEQUOTE || status == PROMPT_DOUBLEQUOTE || status == PROMPT_COMMENT)
prompt_string = GetVariable(pset->vars, "prompt2");
else if (status == PROMPT_COPY)
prompt_string = GetVariable(pset->vars, "prompt3");
else
prompt_string = "? ";
destination[0] = '\0';
for (p = prompt_string;
p && *p && strlen(destination)<MAX_PROMPT_SIZE;
p++)
{
MemSet(buf, 0, MAX_PROMPT_SIZE+1);
if (esc)
{
switch (*p)
{
case '%':
strcpy(buf, "%");
break;
/* Current database */
case '/':
if (pset->db)
strncpy(buf, PQdb(pset->db), MAX_PROMPT_SIZE);
break;
case '~': {
const char * var;
if (pset->db) {
if ( strcmp(PQdb(pset->db), PQuser(pset->db))==0 ||
( (var = getenv("PGDATABASE")) && strcmp(var, PQdb(pset->db))==0) )
strcpy(buf, "~");
else
strncpy(buf, PQdb(pset->db), MAX_PROMPT_SIZE);
}
break;
}
/* DB server hostname (long/short) */
case 'M':
case 'm':
if (pset->db) {
if (PQhost(pset->db)) {
strncpy(buf, PQhost(pset->db), MAX_PROMPT_SIZE);
if (*p == 'm')
buf[strcspn(buf,".")] = '\0';
}
else
buf[0] = '.';
}
break;
/* DB server port number */
case '>':
if (pset->db) {
if (PQhost(pset->db))
strncpy(buf, PQport(pset->db), MAX_PROMPT_SIZE);
else
buf[0] = '.';
}
break;
/* DB server user name */
case 'n':
if (pset->db)
strncpy(buf, PQuser(pset->db), MAX_PROMPT_SIZE);
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
{
long int l;
char * end;
l = strtol(p, &end, 0);
sprintf(buf, "%c", (unsigned char)l);
p = end-1;
break;
}
case 'R':
switch(status) {
case PROMPT_READY:
if (!pset->db)
buf[0] = '!';
else if (!GetVariableBool(pset->vars, "singleline"))
buf[0] = '=';
else
buf[0] = '^';
break;
case PROMPT_CONTINUE:
buf[0] = '-';
break;
case PROMPT_SINGLEQUOTE:
buf[0] = '\'';
break;
case PROMPT_DOUBLEQUOTE:
buf[0] = '"';
break;
case PROMPT_COMMENT:
buf[0] = '*';
break;
default:
buf[0] = '\0';
break;
}
case '?':
/* not here yet */
break;
case '#':
{
if (pset->db && strcmp(PQuser(pset->db), "postgres")==0)
buf[0] = '#';
else
buf[0] = '>';
break;
}
/* execute command */
case '`':
{
FILE * fd = NULL;
char * file = strdup(p+1);
int cmdend;
cmdend = strcspn(file, "`");
file[cmdend] = '\0';
if (file)
fd = popen(file, "r");
if (fd) {
fgets(buf, MAX_PROMPT_SIZE-1, fd);
pclose(fd);
}
if (buf[strlen(buf)-1] == '\n')
buf[strlen(buf)-1] = '\0';
free(file);
p += cmdend+1;
break;
}
/* interpolate variable */
case '$':
{
char *name;
const char *val;
int nameend;
name = strdup(p+1);
nameend = strcspn(name, "$");
name[nameend] = '\0';
val = interpolate_var(name, pset);
if (val)
strncpy(buf, val, MAX_PROMPT_SIZE);
free(name);
p += nameend+1;
break;
}
default:
buf[0] = *p;
buf[1] = '\0';
}
esc = false;
}
else if (*p == '%')
esc = true;
else
{
buf[0] = *p;
buf[1] = '\0';
esc = false;
}
if (!esc) {
strncat(destination, buf, MAX_PROMPT_SIZE-strlen(destination));
}
}
destination[MAX_PROMPT_SIZE] = '\0';
return destination;
}

19
src/bin/psql/prompt.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef PROMPT_H
#define PROMPT_H
#include "settings.h"
typedef enum _promptStatus {
PROMPT_READY,
PROMPT_CONTINUE,
PROMPT_COMMENT,
PROMPT_SINGLEQUOTE,
PROMPT_DOUBLEQUOTE,
PROMPT_COPY
} promptStatus_t;
const char *
get_prompt(PsqlSettings *pset, promptStatus_t status);
#endif /* PROMPT_H */

57
src/bin/psql/settings.h Normal file
View File

@ -0,0 +1,57 @@
#ifndef SETTINGS_H
#define SETTINGS_H
#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>
#include <c.h>
#include "variables.h"
#include "print.h"
#define DEFAULT_FIELD_SEP "|"
#define DEFAULT_EDITOR "/bin/vi"
#define DEFAULT_PROMPT1 "%/%R%# "
#define DEFAULT_PROMPT2 "%/%R%# "
#define DEFAULT_PROMPT3 ">> "
typedef struct _psqlSettings
{
PGconn *db; /* connection to backend */
FILE *queryFout; /* where to send the query results */
bool queryFoutPipe; /* queryFout is from a popen() */
printQueryOpt popt;
VariableSpace vars; /* "shell variable" repository */
char *gfname; /* one-shot file output argument for \g */
bool notty; /* stdin or stdout is not a tty (as determined on startup) */
bool useReadline; /* use libreadline routines */
bool useHistory;
bool getPassword; /* prompt the user for a username and
password */
FILE * cur_cmd_source; /* describe the status of the current main loop */
bool cur_cmd_interactive;
bool has_client_encoding; /* was PGCLIENTENCODING set on startup? */
} PsqlSettings;
#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif
#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif
#define EXIT_BADCONN 2
#define EXIT_USER 3
#endif

253
src/bin/psql/sql_help.h Normal file
View File

@ -0,0 +1,253 @@
/*
* This file is automatically generated from the SGML documentation.
* Direct changes here will be overwritten.
*/
#ifndef SQL_HELP_H
#define SQL_HELP_H
struct _helpStruct
{
char *cmd; /* the command name */
char *help; /* the help associated with it */
char *syntax; /* the syntax associated with it */
};
static struct _helpStruct QL_HELP[] = {
{ "ABORT",
"Aborts the current transaction",
"ABORT [ WORK | TRANSACTION ]" },
{ "ALTER TABLE",
"Modifies table properties",
"ALTER TABLE table\n [ * ] ADD [ COLUMN ] ER\">coBLE> type\nALTER TABLE table\n [ * ] RENAME [ COLUMN ] ER\">coBLE> TO newcolumn\nALTER TABLE table\n RENAME TO newtable" },
{ "ALTER USER",
"Modifies user account information",
"ALTER USER username [ WITH PASSWORD password ]\n [ CREATEDB | NOCREATEDB ] [ CREATEUSER | NOCREATEUSER ]\n [ IN GROUP groupname [, ...] ]\n [ VALID UNTIL 'abstime' ]" },
{ "BEGIN",
"Begins a transaction in chained mode",
"BEGIN [ WORK | TRANSACTION ]" },
{ "CLOSE",
"Close a cursor",
"CLOSE cursor" },
{ "CLUSTER",
"Gives storage clustering advice to the server",
"CLUSTER indexname ON table" },
{ "COMMIT",
"Commits the current transaction",
"COMMIT [ WORK | TRANSACTION ]" },
{ "COPY",
"Copies data between files and tables",
"COPY [ BINARY ] table [ WITH OIDS ]\n FROM { 'filename' | stdin }\n [ USING DELIMITERS 'delimiter' ]\nCOPY [ BINARY ] table [ WITH OIDS ]\n TO { 'filename' | stdout }\n [ USING DELIMITERS 'delimiter' ]" },
{ "CREATE AGGREGATE",
"Defines a new aggregate function",
"CREATE AGGREGATE name [ AS ] ( BASETYPE = data_type\n [ , SFUNC1 = sfunc1, STYPE1 = sfunc1_return_type ]\n [ , SFUNC2 = sfunc2, STYPE2 = sfunc2_return_type ]\n [ , FINALFUNC = ffunc ]\n [ , INITCOND1 = initial_condition1 ]\n [ , INITCOND2 = initial_condition2 ] )" },
{ "CREATE DATABASE",
"Creates a new database",
"CREATE DATABASE name [ WITH LOCATION = 'dbpath' ]" },
{ "CREATE FUNCTION",
"Defines a new function",
"CREATE FUNCTION name ( [ ftype [, ...] ] )\n RETURNS rtype\n AS definition\n LANGUAGE 'langname'" },
{ "CREATE INDEX",
"Constructs a secondary index",
"CREATE [ UNIQUE ] INDEX index_name ON table\n [ USING acc_name ] ( column [ ops_name] [, ...] )\nCREATE [ UNIQUE ] INDEX index_name ON table\n [ USING acc_name ] ( func_name( r\">colle> [, ... ]) ops_name )" },
{ "CREATE LANGUAGE",
"Defines a new language for functions",
"CREATE [ TRUSTED ] PROCEDURAL LANGUAGE 'langname'\n HANDLER call_handler\n LANCOMPILER 'comment'" },
{ "CREATE OPERATOR",
"Defines a new user operator",
"CREATE OPERATOR name ( PROCEDURE = func_name\n [, LEFTARG = type1 ] [, RIGHTARG = type2 ]\n [, COMMUTATOR = com_op ] [, NEGATOR = neg_op ]\n [, RESTRICT = res_proc ] [, JOIN = join_proc ]\n [, HASHES ] [, SORT1 = left_sort_op ] [, SORT2 = right_sort_op ] )" },
{ "CREATE RULE",
"Defines a new rule",
"CREATE RULE name AS ON event\n TO object [ WHERE condition ]\n DO [ INSTEAD ] [ action | NOTHING ]" },
{ "CREATE SEQUENCE",
"Creates a new sequence number generator",
"CREATE SEQUENCE seqname [ INCREMENT increment ]\n [ MINVALUE minvalue ] [ MAXVALUE maxvalue ]\n [ START start ] [ CACHE cache ] [ CYCLE ]" },
{ "CREATE TABLE",
"Creates a new table",
"CREATE [ TEMPORARY | TEMP ] TABLE table (\n column type\n [ NULL | NOT NULL ] [ UNIQUE ] [ DEFAULT value ]\n [column_constraint_clause | PRIMARY KEY } [ ... ] ]\n [, ... ]\n [, PRIMARY KEY ( column [, ...] ) ]\n [, CHECK ( condition ) ]\n [, table_constraint_clause ]\n ) [ INHERITS ( inherited_table [, ...] ) ]" },
{ "CREATE TABLE AS",
"Creates a new table",
"CREATE TABLE table [ (column [, ...] ) ]\n AS select_clause" },
{ "CREATE TRIGGER",
"Creates a new trigger",
"CREATE TRIGGER name { BEFORE | AFTER } { event [OR ...] }\n ON table FOR EACH { ROW | STATEMENT }\n EXECUTE PROCEDURE ER\">funcBLE> ( arguments )" },
{ "CREATE TYPE",
"Defines a new base data type",
"CREATE TYPE typename ( INPUT = input_function, OUTPUT = output_function\n , INTERNALLENGTH = { internallength | VARIABLE } [ , EXTERNALLENGTH = { externallength | VARIABLE } ]\n [ , DEFAULT = \"default\" ]\n [ , ELEMENT = element ] [ , DELIMITER = delimiter ]\n [ , SEND = send_function ] [ , RECEIVE = receive_function ]\n [ , PASSEDBYVALUE ] )" },
{ "CREATE USER",
"Creates account information for a new user",
"CREATE USER username\n [ WITH PASSWORD password ]\n [ CREATEDB | NOCREATEDB ] [ CREATEUSER | NOCREATEUSER ]\n [ IN GROUP groupname [, ...] ]\n [ VALID UNTIL 'abstime' ]" },
{ "CREATE VIEW",
"Constructs a virtual table",
"CREATE VIEW view AS SELECT query" },
{ "DECLARE",
"Defines a cursor for table access",
"DECLARE cursor [ BINARY ] [ INSENSITIVE ] [ SCROLL ]\n CURSOR FOR query\n [ FOR { READ ONLY | UPDATE [ OF column [, ...] ] ]" },
{ "DELETE",
"Deletes rows from a table",
"DELETE FROM table [ WHERE condition ]" },
{ "DROP AGGREGATE",
"Removes the definition of an aggregate function",
"DROP AGGREGATE name type" },
{ "DROP DATABASE",
"Destroys an existing database",
"DROP DATABASE name" },
{ "END",
"Commits the current transaction",
"END [ WORK | TRANSACTION ]" },
{ "DROP FUNCTION",
"Removes a user-defined C function",
"DROP FUNCTION name ( [ type [, ...] ] )" },
{ "DROP INDEX",
"Removes an index from a database",
"DROP INDEX index_name" },
{ "DROP LANGUAGE",
"Removes a user-defined procedural language",
"DROP PROCEDURAL LANGUAGE 'name'" },
{ "DROP OPERATOR",
"Removes an operator from the database",
"DROP OPERATOR id ( type | NONE [,...] )" },
{ "DROP RULE",
"Removes an existing rule from the database",
"DROP RULE name" },
{ "DROP SEQUENCE",
"Removes an existing sequence",
"DROP SEQUENCE name [, ...]" },
{ "DROP TABLE",
"Removes existing tables from a database",
"DROP TABLE name [, ...]" },
{ "DROP TRIGGER",
"Removes the definition of a trigger",
"DROP TRIGGER name ON table" },
{ "DROP TYPE",
"Removes a user-defined type from the system catalogs",
"DROP TYPE typename" },
{ "DROP USER",
"Removes an user account information",
"DROP USER name" },
{ "DROP VIEW",
"Removes an existing view from a database",
"DROP VIEW name" },
{ "EXPLAIN",
"Shows statement execution details",
"EXPLAIN [ VERBOSE ] query" },
{ "FETCH",
"Gets rows using a cursor",
"FETCH [ selector ] [ count ] { IN | FROM } cursor\nFETCH [ RELATIVE ] [ { [ # | ALL | NEXT | PRIOR ] } ] FROM ] cursor" },
{ "GRANT",
"Grants access privilege to a user, a group or all users",
"GRANT privilege [, ...] ON object [, ...]\n TO { PUBLIC | GROUP group | username }" },
{ "INSERT",
"Inserts new rows into a table",
"INSERT INTO table [ ( column [, ...] ) ]\n { VALUES ( expression [, ...] ) | SELECT query }" },
{ "LISTEN",
"Listen for a response on a notify condition",
"LISTEN name" },
{ "LOAD",
"Dynamically loads an object file",
"LOAD 'filename'" },
{ "LOCK",
"Explicit lock of a table inside a transaction",
"LOCK [ TABLE ] table\nLOCK [ TABLE ] table IN [ ROW | ACCESS ] { SHARE | EXCLUSIVE } MODE\nLOCK [ TABLE ] table IN SHARE ROW EXCLUSIVE MODE" },
{ "MOVE",
"Moves cursor position",
"MOVE [ selector ] [ count ] \n { IN | FROM } cursor\n FETCH [ RELATIVE ] [ { [ # | ALL | NEXT | PRIOR ] } ] FROM ] cursor" },
{ "NOTIFY",
"Signals all frontends and backends listening on a notify condition",
"NOTIFY name" },
{ "RESET",
"Restores run-time parameters for session to default values",
"RESET variable" },
{ "REVOKE",
"Revokes access privilege from a user, a group or all users.",
"REVOKE privilege [, ...]\n ON object [, ...]\n FROM { PUBLIC | GROUP ER\">gBLE> | username }" },
{ "ROLLBACK",
"Aborts the current transaction",
"ROLLBACK [ WORK | TRANSACTION ]" },
{ "SELECT",
"Retrieve rows from a table or view.",
"SELECT [ ALL | DISTINCT [ ON column ] ]\n expression [ AS name ] [, ...]\n [ INTO [ TEMPORARY | TEMP ] [ TABLE ] new_table ]\n [ FROM table [ alias ] [, ...] ]\n [ WHERE condition ]\n [ GROUP BY column [, ...] ]\n [ HAVING condition [, ...] ]\n [ { UNION [ ALL ] | INTERSECT | EXCEPT } select ]\n [ ORDER BY column [ ASC | DESC ] [, ...] ]\n [ FOR UPDATE [ OF class_name... ] ]\n [ LIMIT { count | ALL } [ { OFFSET | , } count ] ]" },
{ "SELECT INTO",
"Create a new table from an existing table or view",
"SELECT [ ALL | DISTINCT ] expression [ AS name ] [, ...]\n INTO [TEMP] [ TABLE ] new_table ]\n [ FROM table [alias] [, ...] ]\n [ WHERE condition ]\n [ GROUP BY column [, ...] ]\n [ HAVING condition [, ...] ]\n [ { UNION [ALL] | INTERSECT | EXCEPT } select]\n [ ORDER BY column [ ASC | DESC ] [, ...] ]\n [ FOR UPDATE [OF class_name...]]\n [ LIMIT count [OFFSET|, count]]" },
{ "SET",
"Set run-time parameters for session",
"SET variable { TO | = } { 'value' | DEFAULT }\nSET TIME ZONE { 'timezone' | LOCAL | DEFAULT }\nSET TRANSACTION ISOLATION LEVEL { READ COMMITTED | SERIALIZABLE }" },
{ "SHOW",
"Shows run-time parameters for session",
"SHOW keyword" },
{ "TRUNCATE",
"Close a cursor",
"TRUNCATE TABLE table" },
{ "UPDATE",
"Replaces values of columns in a table",
"UPDATE table SET R\">colle> = expression [, ...]\n [ FROM fromlist ]\n [ WHERE condition ]" },
{ "UNLISTEN",
"Stop listening for notification",
"UNLISTEN { notifyname | * }" },
{ "VACUUM",
"Clean and analyze a Postgres database",
"VACUUM [ VERBOSE ] [ ANALYZE ] [ table ]\nVACUUM [ VERBOSE ] ANALYZE [ ER\">tBLE> [ (column [, ...] ) ] ]" },
{ NULL, NULL, NULL } /* End of list marker */
};
#endif /* SQL_HELP_H */

543
src/bin/psql/startup.c Normal file
View File

@ -0,0 +1,543 @@
#include <config.h>
#include <c.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#ifdef WIN32
#include <io.h>
#include <window.h>
#else
#include <unistd.h>
#endif
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#include <libpq-fe.h>
#include <pqsignal.h>
#include <version.h>
#include "settings.h"
#include "command.h"
#include "help.h"
#include "mainloop.h"
#include "common.h"
#include "input.h"
#include "variables.h"
#include "print.h"
#include "describe.h"
static void
process_psqlrc(PsqlSettings * pset);
static void
showVersion(PsqlSettings *pset, bool verbose);
/* Structures to pass information between the option parsing routine
* and the main function
*/
enum _actions { ACT_NOTHING = 0,
ACT_SINGLE_SLASH,
ACT_LIST_DB,
ACT_SHOW_VER,
ACT_SINGLE_QUERY,
ACT_FILE
};
struct adhoc_opts {
char * dbname;
char * host;
char * port;
char * username;
enum _actions action;
char * action_string;
bool no_readline;
};
static void
parse_options(int argc, char *argv[], PsqlSettings * pset, struct adhoc_opts * options);
/*
*
* main()
*
*/
int
main(int argc, char **argv)
{
PsqlSettings settings;
struct adhoc_opts options;
int successResult;
char * username = NULL;
char * password = NULL;
bool need_pass;
MemSet(&settings, 0, sizeof settings);
settings.cur_cmd_source = stdin;
settings.cur_cmd_interactive = false;
settings.vars = CreateVariableSpace();
settings.popt.topt.format = PRINT_ALIGNED;
settings.queryFout = stdout;
settings.popt.topt.fieldSep = strdup(DEFAULT_FIELD_SEP);
settings.popt.topt.border = 1;
SetVariable(settings.vars, "prompt1", DEFAULT_PROMPT1);
SetVariable(settings.vars, "prompt2", DEFAULT_PROMPT2);
SetVariable(settings.vars, "prompt3", DEFAULT_PROMPT3);
settings.notty = (!isatty(fileno(stdin)) || !isatty(fileno(stdout)));
/* This is obsolete and will be removed very soon. */
#ifdef PSQL_ALWAYS_GET_PASSWORDS
settings.getPassword = true;
#else
settings.getPassword = false;
#endif
#ifdef MULTIBYTE
settings.has_client_encoding = (getenv("PGCLIENTENCODING") != NULL);
#endif
parse_options(argc, argv, &settings, &options);
if (options.action==ACT_LIST_DB || options.action==ACT_SHOW_VER)
options.dbname = "template1";
if (options.username) {
if (strcmp(options.username, "?")==0)
username = simple_prompt("Username: ", 100, true);
else
username = strdup(options.username);
}
if (settings.getPassword)
password = simple_prompt("Password: ", 100, false);
/* loop until we have a password if requested by backend */
do {
need_pass = false;
settings.db = PQsetdbLogin(options.host, options.port, NULL, NULL, options.dbname, username, password);
if (PQstatus(settings.db)==CONNECTION_BAD &&
strcmp(PQerrorMessage(settings.db), "fe_sendauth: no password supplied\n")==0) {
need_pass = true;
free(password);
password = NULL;
password = simple_prompt("Password: ", 100, false);
}
} while (need_pass);
free(username);
free(password);
if (PQstatus(settings.db) == CONNECTION_BAD) {
fprintf(stderr, "Connection to database '%s' failed.\n%s\n", PQdb(settings.db), PQerrorMessage(settings.db));
PQfinish(settings.db);
exit(EXIT_BADCONN);
}
if (options.action == ACT_LIST_DB) {
int success = listAllDbs(&settings);
PQfinish(settings.db);
exit (!success);
}
if (options.action == ACT_SHOW_VER) {
showVersion(&settings, true);
PQfinish(settings.db);
exit (EXIT_SUCCESS);
}
if (!GetVariable(settings.vars, "quiet") && !settings.notty && !options.action)
{
puts("Welcome to psql, the PostgreSQL interactive terminal.\n"
"(Please type \\copyright to see the distribution terms of PostgreSQL.)");
// showVersion(&settings, false);
puts("\n"
"Type \\h for help with SQL commands,\n"
" \\? for help on internal slash commands,\n"
" \\q to quit,\n"
" \\g or terminate with semicolon to execute query.");
}
process_psqlrc(&settings);
initializeInput(options.no_readline ? 0 : 1);
/* Now find something to do */
/* process file given by -f */
if (options.action == ACT_FILE)
successResult = process_file(options.action_string, &settings) ? 0 : 1;
/* process slash command if one was given to -c */
else if (options.action == ACT_SINGLE_SLASH)
successResult = HandleSlashCmds(&settings, options.action_string, NULL, NULL) != CMD_ERROR ? 0 : 1;
/* If the query given to -c was a normal one, send it */
else if (options.action == ACT_SINGLE_QUERY)
successResult = SendQuery(&settings, options.action_string) ? 0 : 1;
/* or otherwise enter interactive main loop */
else
successResult = MainLoop(&settings, stdin);
/* clean up */
finishInput();
PQfinish(settings.db);
setQFout(NULL, &settings);
DestroyVariableSpace(settings.vars);
return successResult;
}
/*
* Parse command line options
*/
#ifdef WIN32
/* getopt is not in the standard includes on Win32 */
int getopt(int, char *const[], const char *);
#endif
static void
parse_options(int argc, char *argv[], PsqlSettings * pset, struct adhoc_opts * options)
{
#ifdef HAVE_GETOPT_LONG
static struct option long_options[] = {
{ "no-align", no_argument, NULL, 'A' },
{ "command", required_argument, NULL, 'c' },
{ "database", required_argument, NULL, 'd' },
{ "dbname", required_argument, NULL, 'd' },
{ "echo", no_argument, NULL, 'e' },
{ "echo-queries", no_argument, NULL, 'e' },
{ "echo-all", no_argument, NULL, 'E' },
{ "echo-all-queries", no_argument, NULL, 'E' },
{ "file", required_argument, NULL, 'f' },
{ "field-sep", required_argument, NULL, 'F' },
{ "host", required_argument, NULL, 'h' },
{ "html", no_argument, NULL, 'H' },
{ "list", no_argument, NULL, 'l' },
{ "no-readline", no_argument, NULL, 'n' },
{ "out", required_argument, NULL, 'o' },
{ "to-file", required_argument, NULL, 'o' },
{ "port", required_argument, NULL, 'p' },
{ "pset", required_argument, NULL, 'P' },
{ "quiet", no_argument, NULL, 'q' },
{ "single-step", no_argument, NULL, 's' },
{ "single-line", no_argument, NULL, 'S' },
{ "tuples-only", no_argument, NULL, 't' },
{ "table-attr", required_argument, NULL, 'T' },
{ "username", required_argument, NULL, 'U' },
{ "expanded", no_argument, NULL, 'x' },
{ "set", required_argument, NULL, 'v' },
{ "variable", required_argument, NULL, 'v' },
{ "version", no_argument, NULL, 'V' },
{ "password", no_argument, NULL, 'W' },
{ "help", no_argument, NULL, '?' },
};
int optindex;
#endif
extern char *optarg;
extern int optind;
int c;
MemSet(options, 0, sizeof *options);
#ifdef HAVE_GETOPT_LONG
while ((c = getopt_long(argc, argv, "Ac:d:eEf:F:lh:Hno:p:P:qsStT:uU:v:VWx?", long_options, &optindex)) != -1)
#else
/* Be sure to leave the '-' in here, so we can catch accidental long options. */
while ((c = getopt(argc, argv, "Ac:d:eEf:F:lh:Hno:p:P:qsStT:uU:v:VWx?-")) != -1)
#endif
{
switch (c)
{
case 'A':
pset->popt.topt.format = PRINT_UNALIGNED;
break;
case 'c':
options->action_string = optarg;
if (optarg[0] == '\\')
options->action = ACT_SINGLE_SLASH;
else
options->action = ACT_SINGLE_QUERY;
break;
case 'd':
options->dbname = optarg;
break;
case 'e':
SetVariable(pset->vars, "echo", "");
break;
case 'E':
SetVariable(pset->vars, "echo_secret", "");
break;
case 'f':
options->action = ACT_FILE;
options->action_string = optarg;
break;
case 'F':
pset->popt.topt.fieldSep = strdup(optarg);
break;
case 'h':
options->host = optarg;
break;
case 'H':
pset->popt.topt.format = PRINT_HTML;
break;
case 'l':
options->action = ACT_LIST_DB;
break;
case 'n':
options->no_readline = true;
break;
case 'o':
setQFout(optarg, pset);
break;
case 'p':
options->port = optarg;
break;
case 'P':
{
char *value;
char *equal_loc;
bool result;
value = xstrdup(optarg);
equal_loc = strchr(value, '=');
if (!equal_loc)
result = do_pset(value, NULL, &pset->popt, true);
else {
*equal_loc = '\0';
result = do_pset(value, equal_loc+1, &pset->popt, true);
}
if (!result) {
fprintf(stderr, "Couldn't set printing paramter %s.\n", value);
exit(EXIT_FAILURE);
}
free(value);
break;
}
case 'q':
SetVariable(pset->vars, "quiet", "");
break;
case 's':
SetVariable(pset->vars, "singlestep", "");
break;
case 'S':
SetVariable(pset->vars, "singleline", "");
break;
case 't':
pset->popt.topt.tuples_only = true;
break;
case 'T':
pset->popt.topt.tableAttr = xstrdup(optarg);
break;
case 'u':
pset->getPassword = true;
options->username = "?";
break;
case 'U':
options->username = optarg;
break;
case 'x':
pset->popt.topt.expanded = true;
break;
case 'v':
{
char *value;
char *equal_loc;
value = xstrdup(optarg);
equal_loc = strchr(value, '=');
if (!equal_loc) {
if (!DeleteVariable(pset->vars, value)) {
fprintf(stderr, "Couldn't delete variable %s.\n", value);
exit(EXIT_FAILURE);
}
}
else {
*equal_loc = '\0';
if (!SetVariable(pset->vars, value, equal_loc+1)) {
fprintf(stderr, "Couldn't set variable %s to %s.\n", value, equal_loc);
exit(EXIT_FAILURE);
}
}
free(value);
break;
}
case 'V':
options->action = ACT_SHOW_VER;
break;
case 'W':
pset->getPassword = true;
break;
case '?':
usage();
exit(EXIT_SUCCESS);
break;
#ifndef HAVE_GETOPT_LONG
case '-':
fprintf(stderr, "This version of psql was compiled without support for long options.\n"
"Use -? for help on invocation options.\n");
exit(EXIT_FAILURE);
break;
#endif
default:
usage();
exit(EXIT_FAILURE);
break;
}
}
/* if we still have arguments, use it as the database name and username */
while (argc - optind >= 1) {
if (!options->dbname)
options->dbname = argv[optind];
else if (!options->username)
options->username = argv[optind];
else
fprintf(stderr, "Warning: extra option %s ignored.\n", argv[optind]);
optind++;
}
}
/*
* Load /etc/psqlrc or .psqlrc file, if found.
*/
static void
process_psqlrc(PsqlSettings * pset)
{
char *psqlrc;
char * home;
#ifdef WIN32
#define R_OK 0
#endif
/* System-wide startup file */
if (access("/etc/psqlrc-"PG_RELEASE"."PG_VERSION"."PG_SUBVERSION, R_OK) == 0)
process_file("/etc/psqlrc-"PG_RELEASE"."PG_VERSION"."PG_SUBVERSION, pset);
else if (access("/etc/psqlrc", R_OK) == 0)
process_file("/etc/psqlrc", pset);
/* Look for one in the home dir */
home = getenv("HOME");
if (home) {
psqlrc = (char *) malloc(strlen(home) + 20);
if (!psqlrc) {
perror("malloc");
exit(EXIT_FAILURE);
}
sprintf(psqlrc, "%s/.psqlrc-"PG_RELEASE"."PG_VERSION"."PG_SUBVERSION, home);
if (access(psqlrc, R_OK) == 0)
process_file(psqlrc, pset);
else {
sprintf(psqlrc, "%s/.psqlrc", home);
if (access(psqlrc, R_OK) == 0)
process_file(psqlrc, pset);
}
free(psqlrc);
}
}
/* showVersion
*
* Displays the database backend version.
* Also checks against the version psql was compiled for and makes
* sure that there are no problems.
*
* Returns false if there was a problem retrieving the information
* or a mismatch was detected.
*/
static void
showVersion(PsqlSettings *pset, bool verbose)
{
PGresult *res;
char *versionstr = NULL;
long int release = 0, version = 0, subversion = 0;
/* get backend version */
res = PSQLexec(pset, "SELECT version()");
if (PQresultStatus(res) == PGRES_TUPLES_OK)
versionstr = PQgetvalue(res, 0, 0);
if (!verbose) {
if (versionstr) puts(versionstr);
PQclear(res);
return;
}
if (strncmp(versionstr, "PostgreSQL ", 11) == 0) {
char *tmp;
release = strtol(&versionstr[11], &tmp, 10);
version = strtol(tmp+1, &tmp, 10);
subversion = strtol(tmp+1, &tmp, 10);
}
printf("Server: %s\npsql", versionstr ? versionstr : "(could not connected)");
if (strcmp(versionstr, PG_VERSION_STR) != 0)
printf(&PG_VERSION_STR[strcspn(PG_VERSION_STR, " ")]);
printf(" ("__DATE__" "__TIME__")");
#ifdef MULTIBYTE
printf(", multibyte");
#endif
#ifdef HAVE_GETOPT_LONG
printf(", long options");
#endif
#ifdef USE_READLINE
printf(", readline");
#endif
#ifdef USE_HISTORY
printf(", history");
#endif
#ifdef USE_LOCALE
printf(", locale");
#endif
#ifdef PSQL_ALWAYS_GET_PASSWORDS
printf(", always password");
#endif
#ifdef USE_ASSERT_CHECKING
printf(", assert checks");
#endif
puts("");
if (release < 6 || (release == 6 && version < 5))
puts ("\nWarning: The server you are connected to is potentially too old for this client\n"
"version. You should ideally be using clients and servers from the same\n"
"distribution.");
PQclear(res);
}

View File

@ -1,119 +1,179 @@
/*-------------------------------------------------------------------------
*
* stringutils.c
* simple string manipulation routines
*
* Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/bin/psql/stringutils.c,v 1.17 1999/07/17 20:18:24 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include <ctype.h>
#include "postgres.h"
#ifndef HAVE_STRDUP
#include "strdup.h"
#endif
#include <config.h>
#include <c.h>
#include "stringutils.h"
/* all routines assume null-terminated strings! */
//#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
//#include <stdio.h>
/* The following routines remove whitespaces from the left, right
and both sides of a string */
/* MODIFIES the string passed in and returns the head of it */
#include <postgres.h>
#ifndef HAVE_STRDUP
#include <strdup.h>
#endif
#include <libpq-fe.h>
#ifdef NOT_USED
static char *
leftTrim(char *s)
static void
unescape_quotes(char *source, char quote, char escape);
/*
* Replacement for strtok() (a.k.a. poor man's flex)
*
* The calling convention is similar to that of strtok.
* s - string to parse, if NULL continue parsing the last string
* delim - set of characters that delimit tokens (usually whitespace)
* quote - set of characters that quote stuff, they're not part of the token
* escape - character than can quote quotes
* was_quoted - if not NULL, stores the quoting character if any was encountered
* token_pos - if not NULL, receives a count to the start of the token in the
* parsed string
*
* Note that the string s is _not_ overwritten in this implementation.
*/
char * strtokx(const char *s,
const char *delim,
const char *quote,
char escape,
char * was_quoted,
unsigned int * token_pos)
{
char *s2 = s;
int shift = 0;
int j = 0;
static char * storage = NULL; /* store the local copy of the users string here */
static char * string = NULL; /* pointer into storage where to continue on next call */
/* variously abused variables: */
unsigned int offset;
char * start;
char *cp = NULL;
while (isspace(*s))
{
s++;
shift++;
}
if (shift > 0)
{
while ((s2[j] = s2[j + shift]) != '\0')
j++;
}
if (s) {
free(storage);
storage = strdup(s);
string = storage;
}
return s2;
if (!storage)
return NULL;
/* skip leading "whitespace" */
offset = strspn(string, delim);
/* end of string reached */
if (string[offset] == '\0') {
/* technically we don't need to free here, but we're nice */
free(storage);
storage = NULL;
string = NULL;
return NULL;
}
/* test if quoting character */
if (quote)
cp = strchr(quote, string[offset]);
if (cp) {
/* okay, we have a quoting character, now scan for the closer */
char *p;
start = &string[offset+1];
if (token_pos)
*token_pos = start - storage;
for(p = start;
*p && (*p != *cp || *(p-1) == escape) ;
#ifdef MULTIBYTE
p += PQmblen(p)
#else
p++
#endif
);
/* not yet end of string? */
if (*p != '\0') {
*p = '\0';
string = p + 1;
if (was_quoted)
*was_quoted = *cp;
unescape_quotes (start, *cp, escape);
return start;
}
else {
if (was_quoted)
*was_quoted = *cp;
string = p;
unescape_quotes (start, *cp, escape);
return start;
}
}
/* otherwise no quoting character. scan till next delimiter */
start = &string[offset];
if (token_pos)
*token_pos = start - storage;
offset = strcspn(start, delim);
if (was_quoted)
*was_quoted = 0;
if (start[offset] != '\0') {
start[offset] = '\0';
string = &start[offset]+1;
return start;
}
else {
string = &start[offset];
return start;
}
}
/*
* unescape_quotes
*
* Resolves escaped quotes. Used by strtokx above.
*/
static void
unescape_quotes(char *source, char quote, char escape)
{
char *p;
char *destination, *tmp;
#ifdef USE_ASSERT_CHECKING
assert(source);
#endif
char *
rightTrim(char *s)
{
char *sEnd,
*bsEnd;
bool in_bs = false;
destination = (char *) calloc(1, strlen(source)+1);
if (!destination) {
perror("calloc");
exit(EXIT_FAILURE);
}
sEnd = s + strlen(s) - 1;
while (sEnd >= s && isspace(*sEnd))
sEnd--;
bsEnd = sEnd;
while (bsEnd >= s && *bsEnd == '\\')
{
in_bs = (in_bs == false);
bsEnd--;
tmp = destination;
for (p = source; *p; p++)
{
char c;
if (*p == escape && *(p+1) && quote == *(p+1)) {
c = *(p+1);
p++;
}
if (in_bs && *sEnd)
sEnd++;
if (sEnd < s)
s[0] = '\0';
else
s[sEnd - s + 1] = '\0';
return s;
c = *p;
*tmp = c;
tmp ++;
}
/* Terminating null character */
*tmp = '\0';
strcpy(source, destination);
}
#ifdef NOT_USED
static char *
doubleTrim(char *s)
{
strcpy(s, leftTrim(rightTrim(s)));
return s;
}
#endif
#ifdef STRINGUTILS_TEST
void
testStringUtils()
{
static char *tests[] = {" goodbye \n", /* space on both ends */
"hello world", /* no spaces to trim */
"", /* empty string */
"a", /* string with one char */
" ", /* string with one whitespace */
NULL_STR};
int i = 0;
while (tests[i] != NULL_STR)
{
char *t;
t = strdup(tests[i]);
printf("leftTrim(%s) = ", t);
printf("%sEND\n", leftTrim(t));
t = strdup(tests[i]);
printf("rightTrim(%s) = ", t);
printf("%sEND\n", rightTrim(t));
t = strdup(tests[i]);
printf("doubleTrim(%s) = ", t);
printf("%sEND\n", doubleTrim(t));
i++;
}
}
#endif

View File

@ -1,45 +1,14 @@
/*-------------------------------------------------------------------------
*
* stringutils.h
*
*
* Copyright (c) 1994, Regents of the University of California
*
* $Id: stringutils.h,v 1.8 1999/02/13 23:20:42 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#ifndef STRINGUTILS_H
#define STRINGUTILS_H
/* use this for memory checking of alloc and free using Tcl's memory check
package*/
#ifdef TCL_MEM_DEBUG
#include <tcl.h>
#define malloc(x) ckalloc(x)
#define free(x) ckfree(x)
#define realloc(x,y) ckrealloc(x,y)
#endif
/* string fiddling utilties */
/* all routines assume null-terminated strings! as arguments */
/* removes whitespaces from the left, right and both sides of a string */
/* MODIFIES the string passed in and returns the head of it */
extern char *rightTrim(char *s);
#ifdef STRINGUTILS_TEST
extern void testStringUtils();
#endif
#ifndef NULL_STR
#define NULL_STR (char*)0
#endif
#ifndef NULL
#define NULL 0
#endif
/* The cooler version of strtok() which knows about quotes and doesn't
* overwrite your input */
extern char *
strtokx(const char *s,
const char *delim,
const char *quote,
char escape,
char * was_quoted,
unsigned int * token_pos);
#endif /* STRINGUTILS_H */

132
src/bin/psql/variables.c Normal file
View File

@ -0,0 +1,132 @@
#include <config.h>
#include <c.h>
#include "variables.h"
#include <stdlib.h>
#include <assert.h>
VariableSpace CreateVariableSpace(void)
{
struct _variable *ptr;
ptr = calloc(1, sizeof *ptr);
if (!ptr) return NULL;
ptr->name = strdup("@");
ptr->value = strdup("");
if (!ptr->name || !ptr->value) {
free(ptr->name);
free(ptr->value);
free(ptr);
return NULL;
}
return ptr;
}
const char * GetVariable(VariableSpace space, const char * name)
{
struct _variable *current;
if (!space)
return NULL;
if (strspn(name, VALID_VARIABLE_CHARS) != strlen(name)) return NULL;
for (current = space; current; current = current->next) {
#ifdef USE_ASSERT_CHECKING
assert(current->name);
assert(current->value);
#endif
if (strcmp(current->name, name)==0)
return current->value;
}
return NULL;
}
bool GetVariableBool(VariableSpace space, const char * name)
{
return GetVariable(space, name)!=NULL ? true : false;
}
bool SetVariable(VariableSpace space, const char * name, const char * value)
{
struct _variable *current, *previous;
if (!space)
return false;
if (!value)
return DeleteVariable(space, name);
if (strspn(name, VALID_VARIABLE_CHARS) != strlen(name)) return false;
for (current = space; current; previous = current, current = current->next) {
#ifdef USE_ASSERT_CHECKING
assert(current->name);
assert(current->value);
#endif
if (strcmp(current->name, name)==0) {
free (current->value);
current->value = strdup(value);
return current->value ? true : false;
}
}
previous->next = calloc(1, sizeof *(previous->next));
if (!previous->next)
return false;
previous->next->name = strdup(name);
if (!previous->next->name)
return false;
previous->next->value = strdup(value);
return previous->next->value ? true : false;
}
bool DeleteVariable(VariableSpace space, const char * name)
{
struct _variable *current, *previous;
if (!space)
return false;
if (strspn(name, VALID_VARIABLE_CHARS) != strlen(name)) return false;
for (current = space, previous = NULL; current; previous = current, current = current->next) {
#ifdef USE_ASSERT_CHECKING
assert(current->name);
assert(current->value);
#endif
if (strcmp(current->name, name)==0) {
free (current->name);
free (current->value);
if (previous)
previous->next = current->next;
free(current);
return true;
}
}
return true;
}
void DestroyVariableSpace(VariableSpace space)
{
if (!space)
return;
DestroyVariableSpace(space->next);
free(space);
}

33
src/bin/psql/variables.h Normal file
View File

@ -0,0 +1,33 @@
/* This implements a sort of variable repository. One could also think of it
* as cheap version of an associative array. In each one of these
* datastructures you can store name/value pairs.
*
* All functions (should) follow the Shit-In-Shit-Out (SISO) principle, i.e.,
* you can pass them NULL pointers and the like and they will return something
* appropriate.
*/
#ifndef VARIABLES_H
#define VARIABLES_H
#include <c.h>
#define VALID_VARIABLE_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_"
struct _variable {
char * name;
char * value;
struct _variable * next;
};
typedef struct _variable * VariableSpace;
VariableSpace CreateVariableSpace(void);
const char * GetVariable(VariableSpace space, const char * name);
bool GetVariableBool(VariableSpace space, const char * name);
bool SetVariable(VariableSpace space, const char * name, const char * value);
bool DeleteVariable(VariableSpace space, const char * name);
void DestroyVariableSpace(VariableSpace space);
#endif /* VARIABLES_H */

View File

@ -0,0 +1,72 @@
# Makefile for Microsoft Visual C++ 5.0 (or compat)
!IF "$(OS)" == "Windows_NT"
NULL=
!ELSE
NULL=nul
!ENDIF
CPP=cl.exe
OUTDIR=.\Release
INTDIR=.\Release
# Begin Custom Macros
OutDir=.\Release
# End Custom Macros
ALL : "$(OUTDIR)\psql.exe"
CLEAN :
-@erase "$(INTDIR)\psql.obj"
-@erase "$(INTDIR)\stringutils.obj"
-@erase "$(INTDIR)\getopt.obj"
-@erase "$(INTDIR)\vc50.idb"
-@erase "$(OUTDIR)\psql.exe"
"$(OUTDIR)" :
if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
CPP_PROJ=/nologo /ML /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D\
"_MBCS" /Fp"$(INTDIR)\psql.pch" /YX /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" /FD /c \
/I ..\..\include /I ..\..\interfaces\libpq /D "HAVE_STRDUP" /D "BLCKSZ=8192"
!IFDEF MULTIBYTE
!IFNDEF MBFLAGS
MBFLAGS="-DMULTIBYTE=$(MULTIBYTE)"
!ENDIF
CPP_PROJ=$(MBFLAGS) $(CPP_PROJ)
!ENDIF
CPP_OBJS=.\Release/
CPP_SBRS=.
LINK32=link.exe
LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib\
advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib\
odbccp32.lib wsock32.lib /nologo /subsystem:console /incremental:no\
/pdb:"$(OUTDIR)\psql.pdb" /machine:I386 /out:"$(OUTDIR)\psql.exe"
LINK32_OBJS= \
"$(INTDIR)\psql.obj" \
"$(INTDIR)\stringutils.obj" \
"$(INTDIR)\getopt.obj" \
"..\..\interfaces\libpq\Release\libpqdll.lib"
"$(OUTDIR)\psql.exe" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
$(LINK32) @<<
$(LINK32_FLAGS) $(LINK32_OBJS)
<<
"$(OUTDIR)\getopt.obj" : "$(OUTDIR)" ..\..\utils\getopt.c
$(CPP) @<<
$(CPP_PROJ) ..\..\utils\getopt.c
<<
.c{$(CPP_OBJS)}.obj::
$(CPP) @<<
$(CPP_PROJ) $<
<<
.cpp{$(CPP_OBJS)}.obj::
$(CPP) @<<
$(CPP_PROJ) $<
<<