mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-02-17 19:30:00 +08:00
Major psql overhaul by Peter Eisentraut.
This commit is contained in:
parent
2ea3b6d63a
commit
a45195a191
@ -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
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
49
src/bin/psql/command.h
Normal 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
518
src/bin/psql/common.c
Normal 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
25
src/bin/psql/common.h
Normal 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
390
src/bin/psql/copy.c
Normal 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", "\"", '\\', "e, NULL);
|
||||
if (!token)
|
||||
error = true;
|
||||
else {
|
||||
if (!quote && strcasecmp(token, "binary")==0) {
|
||||
result->binary = true;
|
||||
token = strtokx(NULL, " \t", "\"", '\\', "e, 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
22
src/bin/psql/copy.h
Normal 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
|
91
src/bin/psql/create_help.pl
Normal file
91
src/bin/psql/create_help.pl
Normal 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
816
src/bin/psql/describe.c
Normal 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
42
src/bin/psql/describe.h
Normal 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
306
src/bin/psql/help.c
Normal 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
16
src/bin/psql/help.h
Normal 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
162
src/bin/psql/input.c
Normal 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
56
src/bin/psql/input.h
Normal 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
311
src/bin/psql/large_obj.c
Normal 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
11
src/bin/psql/large_obj.h
Normal 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
368
src/bin/psql/mainloop.c
Normal 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
10
src/bin/psql/mainloop.h
Normal 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
975
src/bin/psql/print.c
Normal 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("&", fout);
|
||||
break;
|
||||
case '<':
|
||||
fputs("<", fout);
|
||||
break;
|
||||
case '>':
|
||||
fputs(">", 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(" ", 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> </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(" ", 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
66
src/bin/psql/print.h
Normal 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
256
src/bin/psql/prompt.c
Normal 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
19
src/bin/psql/prompt.h
Normal 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
57
src/bin/psql/settings.h
Normal 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
253
src/bin/psql/sql_help.h
Normal 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
543
src/bin/psql/startup.c
Normal 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);
|
||||
}
|
@ -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
|
||||
|
@ -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
132
src/bin/psql/variables.c
Normal 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
33
src/bin/psql/variables.h
Normal 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 */
|
72
src/bin/psql/win32.mak-old
Normal file
72
src/bin/psql/win32.mak-old
Normal 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) $<
|
||||
<<
|
Loading…
Reference in New Issue
Block a user