attributes */
+ else if (strcmp(cmd, "T")==0)
+ success = do_pset("tableattr", options[0], &pset->popt, quiet);
+
+
+ /* \w -- write query buffer to file */
+ else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0 )
+ {
+ FILE *fd = NULL;
+ bool pipe = false;
+
+ if (!options[0]) {
+ fprintf(stderr, "Usage \\%s \n", cmd);
+ success = false;
+ }
+ else {
+ if (options[0][0] == '|') {
+ pipe = true;
+#ifndef __CYGWIN32__
+ fd = popen(&options[0][1], "w");
+#else
+ fd = popen(&options[0][1], "wb");
+#endif
+ }
+ else {
+#ifndef __CYGWIN32__
+ fd = fopen(options[0], "w");
+#else
+ fd = fopen(options[0], "wb");
+#endif
+ }
+
+ if (!fd) {
+ perror(options[0]);
+ success = false;
+ }
+ }
+
+ if (fd) {
+ int result;
+
+ if (query_buf && query_buf->len > 0)
+ fprintf(fd, "%s\n", query_buf->data);
+
+ if (pipe)
+ result = pclose(fd);
+ else
+ result = fclose(fd);
+
+ if (result == EOF) {
+ perror("close");
+ success = false;
+ }
+ }
+ }
+
+ /* \x -- toggle expanded table representation */
+ else if (strcmp(cmd, "x")==0)
+ success = do_pset("expanded", NULL, &pset->popt, quiet);
+
+
+ /* list table rights (grant/revoke) */
+ else if (strcmp(cmd, "z")==0)
+ success = permissionsList(options[0], pset);
+
+
+ else if (strcmp(cmd, "!")==0)
+ success = do_shell(options_string);
+
+ else if (strcmp(cmd, "?")==0)
+ slashUsage(pset);
+
+
+#ifdef NOT_USED
+ /* These commands don't do anything. I just use them to test the parser. */
+ else if (strcmp(cmd, "void")==0 || strcmp(cmd, "#")==0)
+ {
+ int i;
+ fprintf(stderr, "+ optline = |%s|\n", options_string);
+ for(i=0; options[i]; i++)
+ fprintf(stderr, "+ opt%d = |%s|\n", i, options[i]);
+ }
+#endif
+
+ else {
+ status = CMD_UNKNOWN;
+ }
+
+ if (!success) status = CMD_ERROR;
+ return status;
+}
+
+
+
+/*
+ * unescape
+ *
+ * Replaces \n, \t, and the like.
+ * Also interpolates ${variables}.
+ *
+ * The return value is malloc()'ed.
+ */
+static char *
+unescape(const char * source, PsqlSettings * pset)
+{
+ unsigned char *p;
+ bool esc = false; /* Last character we saw was the
+ escape character */
+ char *destination, *tmp;
+ size_t length;
+
+#ifdef USE_ASSERT_CHECKING
+ assert(source);
+#endif
+
+ length = strlen(source)+1;
+
+ tmp = destination = (char *) malloc(length);
+ if (!tmp) {
+ perror("malloc");
+ exit(EXIT_FAILURE);
+ }
+
+ for (p = (char *) source; *p; p += PQmblen(p)) {
+ if (esc) {
+ char c;
+
+ switch (*p)
+ {
+ case 'n':
+ c = '\n';
+ break;
+ case 'r':
+ c = '\r';
+ break;
+ case 't':
+ c = '\t';
+ break;
+ case 'f':
+ c = '\f';
+ 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);
+ c = l;
+ p = end-1;
+ break;
+ }
+ default:
+ c = *p;
+ }
+ *tmp++ = c;
+ esc = false;
+ }
+
+ else if (*p == '\\') {
+ esc = true;
+ }
+
+ else if (*p == '$')
+ {
+ if (*(p+1) == '{') {
+ unsigned int len;
+ char *copy;
+ const char *value;
+ void * new;
+ len = strcspn(p+2, "}");
+ copy = xstrdup(p+2);
+ copy[len] = '\0';
+ value = interpolate_var(copy, pset);
+
+ length += strlen(value) - (len+3);
+ new = realloc(destination, length);
+ if (!new) {
+ perror("realloc");
+ exit(EXIT_FAILURE);
+ }
+ tmp = new + (tmp - destination);
+ destination = new;
+
+ strcpy(tmp, value);
+ tmp += strlen(value);
+ p += len + 2;
+ free(copy);
+ }
+ else {
+ *tmp++ = '$';
+ }
+ }
+
+ else {
+ *tmp++ = *p;
+ esc = false;
+ }
+ }
+
+ *tmp = '\0';
+ return destination;
+}
+
+
+
+
+/* do_connect
+ * -- handler for \connect
+ *
+ * Connects to a database (new_dbname) as a certain user (new_user).
+ * The new user can be NULL. A db name of "-" is the same as the old one.
+ * (That is, the one currently in pset. But pset->db can also be NULL. A NULL
+ * dbname is handled by libpq.)
+ * Returns true if all ok, false if the new connection couldn't be established
+ * but the old one was set back. Otherwise it terminates the program.
+ */
+bool
+do_connect(const char *new_dbname, const char *new_user, PsqlSettings * pset)
+{
+ PGconn *oldconn = pset->db;
+ const char *dbparam = NULL;
+ const char *userparam = NULL;
+ char *pwparam = NULL;
+ char * prompted_password = NULL;
+ char * prompted_user = NULL;
+ bool need_pass;
+ bool success = false;
+
+ /* If dbname is "-" then use old name, else new one (even if NULL) */
+ if (new_dbname && PQdb(oldconn) && (strcmp(new_dbname, "-") == 0 || strcmp(new_dbname, PQdb(oldconn))==0))
+ dbparam = PQdb(oldconn);
+ else
+ dbparam = new_dbname;
+
+ /* If user is "" or "-" then use the old one */
+ if ( new_user && PQuser(oldconn) && ( strcmp(new_user, "")==0 || strcmp(new_user, "-")==0 || strcmp(new_user, PQuser(oldconn))==0 )) {
+ userparam = PQuser(oldconn);
+ }
+ /* If username is "?" then prompt */
+ else if (new_user && strcmp(new_user, "?")==0)
+ userparam = prompted_user = simple_prompt("Username: ", 100, true); /* save for free() */
+ else
+ userparam = new_user;
+
+ /* need to prompt for password? */
+ if (pset->getPassword)
+ pwparam = prompted_password = simple_prompt("Password: ", 100, false); /* need to save for free() */
+
+ /* Use old password if no new one given (if you didn't have an old one, fine) */
+ if (!pwparam)
+ pwparam = PQpass(oldconn);
+
+
+#ifdef MULTIBYTE
+ /*
+ * PGCLIENTENCODING may be set by the previous connection. if a
+ * user does not explicitly set PGCLIENTENCODING, we should
+ * discard PGCLIENTENCODING so that libpq could get the backend
+ * encoding as the default PGCLIENTENCODING value. -- 1998/12/12
+ * Tatsuo Ishii
+ */
+
+ if (!pset->has_client_encoding)
+ putenv("PGCLIENTENCODING=");
+#endif
+
+ do {
+ need_pass = false;
+ pset->db = PQsetdbLogin(PQhost(oldconn), PQport(oldconn),
+ NULL, NULL, dbparam, userparam, pwparam);
+
+ if (PQstatus(pset->db)==CONNECTION_BAD &&
+ strcmp(PQerrorMessage(pset->db), "fe_sendauth: no password supplied\n")==0) {
+ need_pass = true;
+ free(prompted_password);
+ prompted_password = NULL;
+ pwparam = prompted_password = simple_prompt("Password: ", 100, false);
+ }
+ } while (need_pass);
+
+ free(prompted_password);
+ free(prompted_user);
+
+ /* If connection failed, try at least keep the old one.
+ That's probably more convenient than just kicking you out of the
+ program. */
+ if (!pset->db || PQstatus(pset->db) == CONNECTION_BAD)
+ {
+ fprintf(stderr, "Could not establish database connection.\n%s", PQerrorMessage(pset->db));
+ PQfinish(pset->db);
+ if (!oldconn || !pset->cur_cmd_interactive) { /* we don't want unpredictable things to happen
+ in scripting mode */
+ fputs("Terminating.\n", stderr);
+ if (oldconn)
+ PQfinish(oldconn);
+ pset->db = NULL;
+ }
+ else {
+ fputs("Keeping old connection.\n", stderr);
+ pset->db = oldconn;
+ }
+ }
+ else {
+ if (!GetVariable(pset->vars, "quiet")) {
+ if (userparam != new_user) /* no new user */
+ printf("You are now connected to database %s.\n", dbparam);
+ else if (dbparam != new_dbname) /* no new db */
+ printf("You are now connected as new user %s.\n", new_user);
+ else /* both new */
+ printf("You are now connected to database %s as user %s.\n",
+ PQdb(pset->db), PQuser(pset->db));
+ }
+
+ if (oldconn)
+ PQfinish(oldconn);
+
+ success = true;
+ }
+
+ return success;
+}
+
+
+
+/*
+ * do_edit -- handler for \e
+ *
+ * If you do not specify a filename, the current query buffer will be copied
+ * into a temporary one.
+ */
+
+static bool
+editFile(const char *fname)
+{
+ char *editorName;
+ char *sys;
+ int result;
+
+#ifdef USE_ASSERT_CHECKING
+ assert(fname);
+#else
+ if (!fname) return false;
+#endif
+
+ /* Find an editor to use */
+ editorName = getenv("PSQL_EDITOR");
+ if (!editorName)
+ editorName = getenv("EDITOR");
+ if (!editorName)
+ editorName = getenv("VISUAL");
+ if (!editorName)
+ editorName = DEFAULT_EDITOR;
+
+ sys = malloc(strlen(editorName) + strlen(fname) + 32 + 1);
+ if (!sys)
+ return false;
+ sprintf(sys, "exec %s %s", editorName, fname);
+ result = system(sys);
+ if (result == -1 || result == 127)
+ perror(sys);
+ free(sys);
+
+ return result==0;
+}
+
+
+/* call this one */
+static bool
+do_edit(const char *filename_arg, PQExpBuffer query_buf)
+{
+ char fnametmp[64];
+ FILE * stream;
+ const char *fname;
+ bool error = false;
+#ifndef WIN32
+ struct stat before, after;
+#endif
+
+#ifdef USE_ASSERT_CHECKING
+ assert(query_buf);
+#else
+ if (!query_buf) return false;
+#endif
+
+
+ if (filename_arg)
+ fname = filename_arg;
+
+ else {
+ /* make a temp file to edit */
+#ifndef WIN32
+ mode_t oldumask;
+
+ sprintf(fnametmp, "/tmp/psql.edit.%ld.%ld", (long) geteuid(), (long) getpid());
+#else
+ GetTempFileName(".", "psql", 0, fnametmp);
+#endif
+ fname = (const char *)fnametmp;
+
+#ifndef WIN32
+ oldumask = umask(0177);
+#endif
+ stream = fopen(fname, "w");
+#ifndef WIN32
+ umask(oldumask);
+#endif
+
+ if (!stream) {
+ perror(fname);
+ error = true;
+ }
+ else {
+ unsigned int ql = query_buf->len;
+ if (ql == 0 || query_buf->data[ql - 1] != '\n') {
+ appendPQExpBufferChar(query_buf, '\n');
+ ql++;
+ }
+
+ if (fwrite(query_buf->data, 1, ql, stream) != ql) {
+ perror(fname);
+ fclose(stream);
+ remove(fname);
+ error = true;
+ }
+ else
+ fclose(stream);
+ }
+ }
+
+#ifndef WIN32
+ if (!error && stat(fname, &before) != 0) {
+ perror(fname);
+ error = true;
+ }
+#endif
+
+ /* call editor */
+ if (!error)
+ error = !editFile(fname);
+
+#ifndef WIN32
+ if (!error && stat(fname, &after) !=0) {
+ perror(fname);
+ error = true;
+ }
+
+ if (!error && before.st_mtime != after.st_mtime) {
+#else
+ if (!error) {
+#endif
+ stream = fopen(fname, "r");
+ if (!stream) {
+ perror(fname);
+ error = true;
+ }
+ else {
+ /* read file back in */
+ char line[1024];
+ size_t result;
+
+ resetPQExpBuffer(query_buf);
+ do {
+ result = fread(line, 1, 1024, stream);
+ if (ferror(stream)) {
+ perror(fname);
+ error = true;
+ break;
+ }
+ appendBinaryPQExpBuffer(query_buf, line, result);
+ } while (!feof(stream));
+ appendPQExpBufferChar(query_buf, '\0');
+
+ fclose(stream);
+ }
+
+ /* remove temp file */
+ if (!filename_arg)
+ remove(fname);
+ }
+
+ return !error;
+}
+
+
+
+/*
+ * process_file
+ *
+ * Read commands from filename and then them to the main processing loop
+ * Handler for \i, but can be used for other things as well.
+ */
+bool
+process_file(const char *filename, PsqlSettings *pset)
+{
+ FILE *fd;
+ int result;
+
+ if (!filename)
+ return false;
+
+#ifdef __CYGWIN32__
+ fd = fopen(filename, "rb");
+#else
+ fd = fopen(filename, "r");
+#endif
+
+ if (!fd) {
+ perror(filename);
+ return false;
+ }
+
+ result = MainLoop(pset, fd);
+ fclose(fd);
+ return (result == EXIT_SUCCESS);
+}
+
+
+
+/*
+ * do_pset
+ *
+ */
+static const char *
+_align2string(enum printFormat in)
+{
+ switch (in) {
+ case PRINT_NOTHING:
+ return "nothing";
+ break;
+ case PRINT_UNALIGNED:
+ return "unaligned";
+ break;
+ case PRINT_ALIGNED:
+ return "aligned";
+ break;
+ case PRINT_HTML:
+ return "html";
+ break;
+ case PRINT_LATEX:
+ return "latex";
+ break;
+ }
+ return "unknown";
+}
+
+
+bool
+do_pset(const char * param, const char * value, printQueryOpt * popt, bool quiet)
+{
+ size_t vallen = 0;
+#ifdef USE_ASSERT_CHECKING
+ assert(param);
+#else
+ if (!param) return false;
+#endif
+
+ if (value)
+ vallen = strlen(value);
+
+ /* set format */
+ if (strcmp(param, "format")==0) {
+ if (!value)
+ ;
+ else if (strncasecmp("unaligned", value, vallen)==0)
+ popt->topt.format = PRINT_UNALIGNED;
+ else if (strncasecmp("aligned", value, vallen)==0)
+ popt->topt.format = PRINT_ALIGNED;
+ else if (strncasecmp("html", value, vallen)==0)
+ popt->topt.format = PRINT_HTML;
+ else if (strncasecmp("latex", value, vallen)==0)
+ popt->topt.format = PRINT_LATEX;
+ else {
+ fprintf(stderr, "Allowed formats are unaligned, aligned, html, latex.\n");
+ return false;
+ }
+
+ if (!quiet)
+ printf("Output format is %s.\n", _align2string(popt->topt.format));
+ }
+
+ /* set border style/width */
+ else if (strcmp(param, "border")==0) {
+ if (value)
+ popt->topt.border = atoi(value);
+
+ if (!quiet)
+ printf("Border style is %d.\n", popt->topt.border);
+ }
+
+ /* set expanded/vertical mode */
+ else if (strcmp(param, "x")==0 || strcmp(param, "expanded")==0 || strcmp(param, "vertical")==0) {
+ popt->topt.expanded = !popt->topt.expanded;
+ if (!quiet)
+ printf("Expanded display is %s.\n", popt->topt.expanded ? "on" : "off");
+ }
+
+ /* null display */
+ else if (strcmp(param, "null")==0) {
+ if (value) {
+ free(popt->nullPrint);
+ popt->nullPrint = xstrdup(value);
+ }
+ if (!quiet)
+ printf("Null display is \"%s\".\n", popt->nullPrint ? popt->nullPrint : "");
+ }
+
+ /* field separator for unaligned text */
+ else if (strcmp(param, "fieldsep")==0) {
+ if (value) {
+ free(popt->topt.fieldSep);
+ popt->topt.fieldSep = xstrdup(value);
+ }
+ if (!quiet)
+ printf("Field separator is \"%s\".\n", popt->topt.fieldSep);
+ }
+
+ /* toggle between full and barebones format */
+ else if (strcmp(param, "t")==0 || strcmp(param, "tuples_only")==0) {
+ popt->topt.tuples_only = !popt->topt.tuples_only;
+ if (!quiet) {
+ if (popt->topt.tuples_only)
+ puts("Showing only tuples.");
+ else
+ puts("Tuples only is off.");
+ }
+ }
+
+ /* set title override */
+ else if (strcmp(param, "title")==0) {
+ free(popt->title);
+ if (!value)
+ popt->title = NULL;
+ else
+ popt->title = xstrdup(value);
+
+ if (!quiet) {
+ if (popt->title)
+ printf("Title is \"%s\".\n", popt->title);
+ else
+ printf("Title is unset.\n");
+ }
+ }
+
+ /* set HTML table tag options */
+ else if (strcmp(param, "T")==0 || strcmp(param, "tableattr")==0) {
+ free(popt->topt.tableAttr);
+ if (!value)
+ popt->topt.tableAttr = NULL;
+ else
+ popt->topt.tableAttr = xstrdup(value);
+
+ if (!quiet) {
+ if (popt->topt.tableAttr)
+ printf("Table attribute is \"%s\".\n", popt->topt.tableAttr);
+ else
+ printf("Table attributes unset.\n");
+ }
+ }
+
+ /* toggle use of pager */
+ else if (strcmp(param, "pager")==0) {
+ popt->topt.pager = !popt->topt.pager;
+ if (!quiet) {
+ if (popt->topt.pager)
+ puts("Using pager is on.");
+ else
+ puts("Using pager is off.");
+ }
+ }
+
+
+ else {
+ fprintf(stderr, "Unknown option: %s\n", param);
+ return false;
+ }
+
+ return true;
+}
+
+
+
+#define DEFAULT_SHELL "/bin/sh"
+
+static bool
+do_shell(const char *command)
+{
+ int result;
+
+ if (!command) {
+ char *sys;
+ char *shellName;
+
+ shellName = getenv("SHELL");
+ if (shellName == NULL)
+ shellName = DEFAULT_SHELL;
+
+ sys = malloc(strlen(shellName) + 16);
+ if (!sys)
+ return false;
+ sprintf(sys, "exec %s", shellName);
+ result = system(sys);
+ free(sys);
+ }
+ else
+ result = system(command);
+
+ if (result==127 || result==-1) {
+ perror("system");
+ return false;
+ }
+ return true;
+}
--- /dev/null
+#ifndef COMMAND_H
+#define COMMAND_H
+
+#include
+#include
+
+
+#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
--- /dev/null
+#include
+#include
+#include "common.h"
+
+#include
+#ifdef HAVE_TERMIOS_H
+#include
+#endif
+#include
+#include
+#ifndef HAVE_STRDUP
+#include
+#endif
+#include
+#ifndef WIN32
+#include /* for write() */
+#endif
+
+#include
+#include
+
+#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;
+}
--- /dev/null
+#ifndef COMMON_H
+#define COMMON_H
+
+#include
+#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 */
--- /dev/null
+#include
+#include
+#include "copy.h"
+
+#include
+#include
+#include
+#include
+#ifndef WIN32
+#include /* for isatty */
+#else
+#include /* I think */
+#endif
+
+#include
+
+#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 ['']
+ * (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);
+}
--- /dev/null
+#ifndef COPY_H
+#define COPY_H
+
+#include
+#include
+#include
+#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
--- /dev/null
+#!/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('', );
+ close FILE;
+
+ $filecontent =~ m!\s*SQL - Language Statements\s*!i
+ or next;
+
+ $filecontent =~ m!\s*([a-z ]+?)\s*!i && ($cmdname = $1);
+ $filecontent =~ m!\s*(.+?)\s*!i && ($cmddesc = $1);
+
+ $filecontent =~ m!\s*(.+?)\s*!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;
--- /dev/null
+#include
+#include
+#include "describe.h"
+
+#include
+
+#include
/* for VARHDRSZ, int4 type */
+#include
+
+#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
+ 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
+ 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 */
--- /dev/null
+#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 */
--- /dev/null
+#include
+#include
+#include "help.h"
+
+#include
+#include
+#include
+
+#ifndef WIN32
+#include /* for ioctl() */
+#ifdef HAVE_PWD_H
+#include
/* for getpwuid() */
+#endif
+#include /* (ditto) */
+#include /* for getuid() */
+#else
+#define strcasecmp(x,y) stricmp(x,y)
+#define popen(x,y) _popen(x,y)
+#define pclose(x) _pclose(x)
+#endif
+
+#include
+
+#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] [|- [|?]] -- connect to new database (currently '%s')\n", PQdb(pset->db));
+ fprintf(fout, " \\copy [binary]
[with oids] {from|to} [with delimiters '']\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 [
+ fprintf(fout, " \\df -- list functions\n");
+ fprintf(fout, " \\do -- list operators\n");
+ fprintf(fout, " \\dT -- list data types\n");
+ fprintf(fout, " \\e [] -- edit the current query buffer or with external editor\n");
+ fprintf(fout, " \\echo -- write text to stdout\n");
+ fprintf(fout, " \\g [] -- send query to backend (and results in or |pipe)\n");
+ fprintf(fout, " \\h [] -- help on syntax of sql commands, * for all commands\n");
+ fprintf(fout, " \\i -- 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 [] -- send all query results to , 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 -- write text to query output stream (see \\o)\n");
+ fprintf(fout, " \\r -- reset (clear) the query buffer\n");
+ fprintf(fout, " \\s [] -- print history or save it in \n");
+ fprintf(fout, " \\set [] -- 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 -- write current query buffer to a file\n");
+ fprintf(fout, " \\z -- list table access permissions\n");
+ fprintf(fout, " \\! [] -- 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 or \\help , where 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)"
+ );
+}
--- /dev/null
+#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
+
--- /dev/null
+#include
+#include
+#include "input.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
+}
--- /dev/null
+#ifndef INPUT_H
+#define INPUT_H
+
+#include
+#include
+#include
+#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
+#define USE_READLINE 1
+#else
+#if defined(HAVE_READLINE_READLINE_H)
+#include
+#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
+#define USE_HISTORY 1
+#else
+#if defined(HAVE_READLINE_HISTORY_H)
+#include
+#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
--- /dev/null
+#include
+#include
+#include "large_obj.h"
+
+#include
+#include
+
+#include
+
+#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
+ 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;
+}
--- /dev/null
+#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 */
--- /dev/null
+#include
+#include
+#include "mainloop.h"
+
+#include
+#include
+#include
+
+
+#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() */
+
--- /dev/null
+#ifndef MAINLOOP_H
+#define MAINLOOP_H
+
+#include
+#include "settings.h"
+
+int
+MainLoop(PsqlSettings *pset, FILE *source);
+
+#endif MAINLOOP_H
--- /dev/null
+#include
+#include
+#include "print.h"
+
+#include
+#include
+#include
+#include
+#ifndef WIN32
+#include /* for isatty() */
+#include /* for ioctl() */
+#else
+#define popen(x,y) _popen(x,y)
+#define pclose(x) _pclose(x)
+#endif
+
+#include
+#include
/* 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
+ for (j=0; j
+ fputc('-', fout);
+
+ if (i
+ 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
+ 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
+ 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
+ /* 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
+ 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 0 ? "-" : " ");
+ if (opt_border > 0)
+ strcat(divider, "-+-");
+ else
+ strcat(divider, " ");
+ for (i=0; i 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)
+ &nb