mirror of https://github.com/postgres/postgres
parent
2ea3b6d63a
commit
a45195a191
File diff suppressed because it is too large
Load Diff
@ -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 |
||||
@ -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; |
||||
} |
||||
@ -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 */ |
||||
@ -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); |
||||
} |
||||
@ -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 |
||||
@ -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; |
||||
@ -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 */ |
||||
@ -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 */ |
||||
@ -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)" |
||||
); |
||||
} |
||||
@ -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 |
||||
|
||||
@ -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 |
||||
} |
||||
@ -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 |
||||
@ -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; |
||||
} |
||||
@ -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 */ |
||||
@ -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() */ |
||||
|
||||
@ -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 |
||||
@ -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 */ |
||||
@ -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 */ |
||||
@ -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; |
||||
} |
||||
|
||||
@ -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 */ |
||||
@ -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 |
||||
@ -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 */ |
||||
@ -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 <config.h> |
||||
#include <c.h> |
||||
#include "stringutils.h" |
||||
|
||||
//#include <ctype.h>
|
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <assert.h> |
||||
//#include <stdio.h>
|
||||
|
||||
#include "postgres.h" |
||||
#include <postgres.h> |
||||
#ifndef HAVE_STRDUP |
||||
#include "strdup.h" |
||||
#include <strdup.h> |
||||
#endif |
||||
#include <libpq-fe.h> |
||||
|
||||
|
||||
#include "stringutils.h" |
||||
|
||||
/* all routines assume null-terminated strings! */ |
||||
static void |
||||
unescape_quotes(char *source, char quote, char escape); |
||||
|
||||
/* 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 */ |
||||
|
||||
#ifdef NOT_USED |
||||
static char * |
||||
leftTrim(char *s) |
||||
/*
|
||||
* 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; |
||||
|
||||
while (isspace(*s)) |
||||
{ |
||||
s++; |
||||
shift++; |
||||
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; |
||||
|
||||
if (s) { |
||||
free(storage); |
||||
storage = strdup(s); |
||||
string = storage; |
||||
} |
||||
|
||||
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; |
||||
} |
||||
if (shift > 0) |
||||
{ |
||||
while ((s2[j] = s2[j + shift]) != '\0') |
||||
j++; |
||||
else { |
||||
if (was_quoted) |
||||
*was_quoted = *cp; |
||||
string = p; |
||||
|
||||
unescape_quotes (start, *cp, escape); |
||||
return start; |
||||
} |
||||
} |
||||
|
||||
return s2; |
||||
} |
||||
/* otherwise no quoting character. scan till next delimiter */ |
||||
start = &string[offset]; |
||||
|
||||
#endif |
||||
if (token_pos) |
||||
*token_pos = start - storage; |
||||
|
||||
char * |
||||
rightTrim(char *s) |
||||
{ |
||||
char *sEnd, |
||||
*bsEnd; |
||||
bool in_bs = false; |
||||
|
||||
sEnd = s + strlen(s) - 1; |
||||
while (sEnd >= s && isspace(*sEnd)) |
||||
sEnd--; |
||||
bsEnd = sEnd; |
||||
while (bsEnd >= s && *bsEnd == '\\') |
||||
{ |
||||
in_bs = (in_bs == false); |
||||
bsEnd--; |
||||
} |
||||
if (in_bs && *sEnd) |
||||
sEnd++; |
||||
if (sEnd < s) |
||||
s[0] = '\0'; |
||||
else |
||||
s[sEnd - s + 1] = '\0'; |
||||
return s; |
||||
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; |
||||
} |
||||
} |
||||
|
||||
#ifdef NOT_USED |
||||
static char * |
||||
doubleTrim(char *s) |
||||
|
||||
|
||||
|
||||
/*
|
||||
* unescape_quotes |
||||
* |
||||
* Resolves escaped quotes. Used by strtokx above. |
||||
*/ |
||||
static void |
||||
unescape_quotes(char *source, char quote, char escape) |
||||
{ |
||||
strcpy(s, leftTrim(rightTrim(s))); |
||||
return s; |
||||
} |
||||
char *p; |
||||
char *destination, *tmp; |
||||
|
||||
#ifdef USE_ASSERT_CHECKING |
||||
assert(source); |
||||
#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++; |
||||
destination = (char *) calloc(1, strlen(source)+1); |
||||
if (!destination) { |
||||
perror("calloc"); |
||||
exit(EXIT_FAILURE); |
||||
} |
||||
|
||||
tmp = destination; |
||||
|
||||
for (p = source; *p; p++) |
||||
{ |
||||
char c; |
||||
|
||||
if (*p == escape && *(p+1) && quote == *(p+1)) { |
||||
c = *(p+1); |
||||
p++; |
||||
} |
||||
else |
||||
c = *p; |
||||
|
||||
} |
||||
*tmp = c; |
||||
tmp ++; |
||||
} |
||||
|
||||
#endif |
||||
/* Terminating null character */ |
||||
*tmp = '\0'; |
||||
|
||||
strcpy(source, destination); |
||||
} |
||||
|
||||
@ -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 */ |
||||
|
||||
@ -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); |
||||
} |
||||
@ -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 */ |
||||
@ -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