diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bin/psql/Makefile.in | 14 | ||||
-rw-r--r-- | src/bin/psql/command.c | 121 | ||||
-rw-r--r-- | src/bin/psql/command.h | 4 | ||||
-rw-r--r-- | src/bin/psql/common.c | 102 | ||||
-rw-r--r-- | src/bin/psql/describe.c | 761 | ||||
-rw-r--r-- | src/bin/psql/describe.h | 32 | ||||
-rw-r--r-- | src/bin/psql/help.c | 2 | ||||
-rw-r--r-- | src/bin/psql/input.c | 6 | ||||
-rw-r--r-- | src/bin/psql/input.h | 15 | ||||
-rw-r--r-- | src/bin/psql/large_obj.c | 16 | ||||
-rw-r--r-- | src/bin/psql/large_obj.h | 3 | ||||
-rw-r--r-- | src/bin/psql/mainloop.c | 158 | ||||
-rw-r--r-- | src/bin/psql/print.c | 68 | ||||
-rw-r--r-- | src/bin/psql/print.h | 4 | ||||
-rw-r--r-- | src/bin/psql/prompt.c | 2 | ||||
-rw-r--r-- | src/bin/psql/settings.h | 6 | ||||
-rw-r--r-- | src/bin/psql/startup.c | 22 | ||||
-rw-r--r-- | src/bin/psql/tab-complete.c | 812 | ||||
-rw-r--r-- | src/bin/psql/tab-complete.h | 8 | ||||
-rw-r--r-- | src/bin/psql/variables.h | 3 | ||||
-rw-r--r-- | src/bin/psql/win32.mak-old | 72 |
21 files changed, 1606 insertions, 625 deletions
diff --git a/src/bin/psql/Makefile.in b/src/bin/psql/Makefile.in index ccceb69eeeb..e140de5aced 100644 --- a/src/bin/psql/Makefile.in +++ b/src/bin/psql/Makefile.in @@ -7,7 +7,7 @@ # # # IDENTIFICATION -# $Header: /cvsroot/pgsql/src/bin/psql/Attic/Makefile.in,v 1.17 1999/11/08 15:59:59 momjian Exp $ +# $Header: /cvsroot/pgsql/src/bin/psql/Attic/Makefile.in,v 1.18 1999/11/26 04:24:16 momjian Exp $ # #------------------------------------------------------------------------- @@ -30,10 +30,18 @@ endif OBJS=command.o common.o help.o input.o stringutils.o mainloop.o \ copy.o startup.o prompt.o variables.o large_obj.o print.o describe.o \ -@STRDUP@ @STRERROR2@ +tab-complete.o @STRDUP@ @STRERROR2@ @SNPRINTF@ all: submake psql +# Move this to the utils directory +ifneq (@SNPRINTF@,) +OBJS+=../../backend/port/snprintf.o + +../../backend/port/snprintf.o: + $(MAKE) -C ../../backend/port snprintf.o +endif + psql: $(OBJS) $(LIBPQDIR)/libpq.a $(CC) -o psql -L$(LIBPQDIR) $(OBJS) -lpq $(LDFLAGS) @@ -69,7 +77,7 @@ clean: # sql_help.h is gone, for it needs the docs in the right place to be # regenerated. -- (pe) -distclean: clean +maintainer-clean: clean rm -f sql_help.h ifeq (depend,$(wildcard depend)) diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 0883d438f1c..0db6cd5562e 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -33,7 +33,7 @@ #endif -/* functions for use in this file only */ +/* functions for use in this file */ static backslashResult exec_command(const char *cmd, char *const * options, @@ -41,15 +41,23 @@ static backslashResult exec_command(const char *cmd, PQExpBuffer query_buf, PsqlSettings *pset); -static bool - do_edit(const char *filename_arg, PQExpBuffer query_buf); +static bool do_edit(const char *filename_arg, PQExpBuffer query_buf); -static char * - unescape(const char *source, PsqlSettings *pset); +static char * unescape(const char *source, PsqlSettings *pset); + +static bool do_connect(const char *new_dbname, + const char *new_user, + PsqlSettings *pset); -static bool - do_shell(const char *command); +static bool do_shell(const char *command); + +/* + * Perhaps this should be changed to "infinity", + * but there is no convincing reason to bother + * at this point. + */ +#define NR_OPTIONS 16 /*---------- @@ -78,7 +86,7 @@ HandleSlashCmds(PsqlSettings *pset, { backslashResult status = CMD_SKIP_LINE; char *my_line; - char *options[17] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + char *options[NR_OPTIONS+1]; char *token; const char *options_string = NULL; const char *cmd; @@ -120,7 +128,7 @@ HandleSlashCmds(PsqlSettings *pset, i = 0; token = strtokx(options_string, " \t", "\"'`", '\\', "e, &pos); - for (i = 0; token && i < 16; i++) + for (i = 0; token && i < NR_OPTIONS; i++) { switch (quote) { @@ -194,14 +202,15 @@ HandleSlashCmds(PsqlSettings *pset, options[i] = xstrdup(interpolate_var(token + 1, pset)); else options[i] = xstrdup(token); - break; } if (continue_parse) break; token = strtokx(NULL, " \t", "\"'`", '\\', "e, &pos); - } + } /* for */ + + options[i] = NULL; } cmd = my_line; @@ -216,11 +225,11 @@ HandleSlashCmds(PsqlSettings *pset, * arguments to start immediately after the command, but that is * no longer encouraged. */ - const char *new_options[17]; + const char *new_options[NR_OPTIONS+1]; char new_cmd[2]; int i; - for (i = 1; i < 17; i++) + for (i = 1; i < NR_OPTIONS+1; i++) new_options[i] = options[i - 1]; new_options[0] = cmd + 1; @@ -249,7 +258,7 @@ HandleSlashCmds(PsqlSettings *pset, } /* clean up */ - for (i = 0; i < 16 && options[i]; i++) + for (i = 0; i < NR_OPTIONS && options[i]; i++) free(options[i]); free(my_line); @@ -274,10 +283,7 @@ exec_command(const char *cmd, backslashResult status = CMD_SKIP_LINE; - /* - * \a -- toggle field alignment This is deprecated and makes no sense, - * but we keep it around. - */ + /* \a -- toggle field alignment This makes little sense but we keep it around. */ if (strcmp(cmd, "a") == 0) { if (pset->popt.topt.format != PRINT_ALIGNED) @@ -287,22 +293,19 @@ exec_command(const char *cmd, } - /* - * \C -- override table title (formerly change HTML caption) This is - * deprecated. - */ + /* \C -- override table title (formerly change HTML caption) */ else if (strcmp(cmd, "C") == 0) success = do_pset("title", options[0], &pset->popt, quiet); - - /* + /*---------- * \c or \connect -- connect to new database or as different user * - * \c foo bar : connect to db "foo" as user "bar" \c foo [-] : - * connect to db "foo" as current user \c - bar : connect to - * current db as user "bar" \c : connect to default db as - * default user + * \c foo bar: connect to db "foo" as user "bar" + * \c foo [-]: connect to db "foo" as current user + * \c - bar: connect to current db as user "bar" + * \c: connect to default db as default user + *---------- */ else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0) { @@ -335,35 +338,39 @@ exec_command(const char *cmd, /* \d* commands */ else if (cmd[0] == 'd') { + bool show_verbose = strchr(cmd, '+') ? true : false; + bool show_desc = strchr(cmd, '?') ? true : false; + switch (cmd[1]) { case '\0': + case '?': if (options[0]) - success = describeTableDetails(options[0], pset); + success = describeTableDetails(options[0], pset, show_desc); else - success = listTables("tvs", NULL, pset); /* standard listing of - * interesting things */ + /* standard listing of interesting things */ + success = listTables("tvs", NULL, pset, show_desc); break; case 'a': - success = describeAggregates(options[0], pset); + success = describeAggregates(options[0], pset, show_verbose, show_desc); break; case 'd': success = objectDescription(options[0], pset); break; case 'f': - success = describeFunctions(options[0], pset); + success = describeFunctions(options[0], pset, show_verbose, show_desc); break; case 'l': - success = do_lo_list(pset); + success = do_lo_list(pset, show_desc); break; case 'o': - success = describeOperators(options[0], pset); + success = describeOperators(options[0], pset, show_verbose, show_desc); break; case 'p': success = permissionsList(options[0], pset); break; case 'T': - success = describeTypes(options[0], pset); + success = describeTypes(options[0], pset, show_verbose, show_desc); break; case 't': case 'v': @@ -371,9 +378,9 @@ exec_command(const char *cmd, case 's': case 'S': if (cmd[1] == 'S' && cmd[2] == '\0') - success = listTables("Stvs", NULL, pset); + success = listTables("Stvs", NULL, pset, show_desc); else - success = listTables(&cmd[1], options[0], pset); + success = listTables(&cmd[1], options[0], pset, show_desc); break; default: status = CMD_UNKNOWN; @@ -399,14 +406,10 @@ exec_command(const char *cmd, fputs("\n", stdout); } - /* - * \f -- change field separator (This is deprecated in favour of - * \pset.) - */ + /* \f -- change field separator */ else if (strcmp(cmd, "f") == 0) success = do_pset("fieldsep", options[0], &pset->popt, quiet); - /* \g means send query */ else if (strcmp(cmd, "g") == 0) { @@ -419,12 +422,27 @@ exec_command(const char *cmd, /* help */ else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0) - helpSQL(options_string); - + { + char buf[256] = ""; + int i; + for (i=0; options && options[i] && strlen(buf)<255; i++) + { + strncat(buf, options[i], 255 - strlen(buf)); + if (strlen(buf)<255 && options[i+1]) + strcat(buf, " "); + } + buf[255] = '\0'; + helpSQL(buf); + } /* HTML mode */ else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0) - success = do_pset("format", "html", &pset->popt, quiet); + { + if (pset->popt.topt.format != PRINT_HTML) + success = do_pset("format", "html", &pset->popt, quiet); + else + success = do_pset("format", "aligned", &pset->popt, quiet); + } /* \i is include file */ @@ -439,9 +457,12 @@ exec_command(const char *cmd, success = process_file(options[0], pset); } + /* \l is list databases */ else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0) - success = listAllDbs(pset); + success = listAllDbs(pset, false); + else if (strcmp(cmd, "l?") == 0 || strcmp(cmd, "list?") == 0) + success = listAllDbs(pset, true); /* large object things */ @@ -470,7 +491,9 @@ exec_command(const char *cmd, } else if (strcmp(cmd + 3, "list") == 0) - success = do_lo_list(pset); + success = do_lo_list(pset, false); + else if (strcmp(cmd + 3, "list?") == 0) + success = do_lo_list(pset, true); else if (strcmp(cmd + 3, "unlink") == 0) { @@ -828,7 +851,7 @@ unescape(const char *source, PsqlSettings *pset) * 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 +static bool do_connect(const char *new_dbname, const char *new_user, PsqlSettings *pset) { PGconn *oldconn = pset->db; diff --git a/src/bin/psql/command.h b/src/bin/psql/command.h index a7850263c31..029937e3371 100644 --- a/src/bin/psql/command.h +++ b/src/bin/psql/command.h @@ -29,10 +29,6 @@ backslashResult HandleSlashCmds(PsqlSettings *pset, 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); diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c index 82c5d12a13d..30167847a0f 100644 --- a/src/bin/psql/common.c +++ b/src/bin/psql/common.c @@ -3,11 +3,11 @@ #include "common.h" #include <stdlib.h> +#include <stdio.h> +#include <string.h> #ifdef HAVE_TERMIOS_H #include <termios.h> #endif -#include <stdio.h> -#include <string.h> #ifndef HAVE_STRDUP #include <strdup.h> #endif @@ -15,9 +15,12 @@ #include <assert.h> #ifndef WIN32 #include <unistd.h> /* for write() */ +#else +#include <io.h> /* for _write() */ #endif #include <libpq-fe.h> +#include <postgres_ext.h> #include <pqsignal.h> #include <version.h> @@ -30,6 +33,7 @@ #ifdef WIN32 #define popen(x,y) _popen(x,y) #define pclose(x) _pclose(x) +#define write(a,b,c) _write(a,b,c) #endif @@ -210,11 +214,13 @@ simple_prompt(const char *prompt, int maxlen, bool echo) /* * interpolate_var() * - * If the variable is a regular psql variable, just return its value. - * If it's a magic variable, return that value. + * The idea here is that certain variables have a "magic" meaning, such as + * LastOid. However, you can assign to those variables, but that will shadow + * the magic meaning, until you unset it. If nothing matches, the value of + * the environment variable is used. * - * This function only returns NULL if you feed in NULL. Otherwise it's ready for - * immediate consumption. + * This function only returns NULL if you feed in NULL's (don't do that). + * Otherwise, the return value is ready for immediate consumption. */ const char * interpolate_var(const char *name, PsqlSettings *pset) @@ -229,14 +235,9 @@ interpolate_var(const char *name, PsqlSettings *pset) return NULL; #endif - if (strspn(name, VALID_VARIABLE_CHARS) == strlen(name)) - { - var = GetVariable(pset->vars, name); - if (var) - return var; - else - return ""; - } + var = GetVariable(pset->vars, name); + if (var) + return var; /* otherwise return magic variable */ @@ -279,11 +280,16 @@ interpolate_var(const char *name, PsqlSettings *pset) return ""; } - /* - * env vars (if env vars are all caps there should be no prob, - * otherwise you're on your own - */ + if (strcmp(name, "LastOid") == 0) + { + static char buf[24]; + if (pset->lastOid == InvalidOid) + return ""; + sprintf(buf, "%u", pset->lastOid); + return buf; + } + /* env vars */ if ((var = getenv(name))) return var; @@ -293,42 +299,31 @@ interpolate_var(const char *name, PsqlSettings *pset) /* - * 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. + * Code to support query cancellation * - * 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(). + * Before we start a query, we enable a SIGINT signal catcher that sends a + * cancel request to the backend. Note that sending the cancel directly from + * the signal handler is safe because PQrequestCancel() is written to make it + * so. We have to be very careful what else we do in the signal handler. This + * includes using write() for output. */ -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 PGconn *cancelConn; +#define write_stderr(String) write(fileno(stderr), String, strlen(String)) static void handle_sigint(SIGNAL_ARGS) { - /* accept signal if no connection */ if (cancelConn == NULL) - exit(1); + return; /* Try to send cancel request */ if (PQrequestCancel(cancelConn)) - safe_write_stderr("\nCANCEL request sent\n"); + write_stderr("\nCancel request sent\n"); else { - safe_write_stderr("\nCould not send cancel request: "); - safe_write_stderr(PQerrorMessage(cancelConn)); + write_stderr("\nCould not send cancel request: "); + write_stderr(PQerrorMessage(cancelConn)); } } @@ -348,7 +343,7 @@ PSQLexec(PsqlSettings *pset, const char *query) if (!pset->db) { - fputs("You are not currently connected to a database.\n", stderr); + fputs("You are currently not connected to a database.\n", stderr); return NULL; } @@ -367,7 +362,7 @@ PSQLexec(PsqlSettings *pset, const char *query) res = PQexec(pset->db, query); - pqsignal(SIGINT, SIG_DFL); /* no control-C is back to normal */ + pqsignal(SIGINT, SIG_DFL); /* now control-C is back to normal */ if (PQstatus(pset->db) == CONNECTION_BAD) { @@ -393,7 +388,7 @@ PSQLexec(PsqlSettings *pset, const char *query) return res; else { - fprintf(stderr, "%s", PQerrorMessage(pset->db)); + fputs(PQerrorMessage(pset->db), pset->queryFout); PQclear(res); return NULL; } @@ -422,7 +417,7 @@ SendQuery(PsqlSettings *pset, const char *query) if (!pset->db) { - fputs("You are not currently connected to a database.\n", stderr); + fputs("You are currently not connected to a database.\n", stderr); return false; } @@ -430,10 +425,10 @@ SendQuery(PsqlSettings *pset, const char *query) { 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); + printf("***(Single step mode: Verify query)*********************************************\n" + "%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') @@ -492,11 +487,15 @@ SendQuery(PsqlSettings *pset, const char *query) break; case PGRES_COMMAND_OK: success = true; - fprintf(pset->queryFout, "%s\n", PQcmdStatus(results)); + pset->lastOid = PQoidValue(results); + if (!GetVariableBool(pset->vars, "quiet")) { + fprintf(pset->queryFout, "%s\n", PQcmdStatus(results)); + fflush(pset->queryFout); + } break; case PGRES_COPY_OUT: - if (pset->cur_cmd_interactive && !GetVariable(pset->vars, "quiet")) + if (pset->cur_cmd_interactive && !GetVariableBool(pset->vars, "quiet")) puts("Copy command returns:"); success = handleCopyOut(pset->db, pset->queryFout); @@ -516,6 +515,7 @@ SendQuery(PsqlSettings *pset, const char *query) case PGRES_BAD_RESPONSE: success = false; fputs(PQerrorMessage(pset->db), pset->queryFout); + fflush(pset->queryFout); break; } diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 6af2b06a8b5..b0b4f9d48d4 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -34,55 +34,60 @@ * takes an optional regexp to match specific aggregates by name */ bool -describeAggregates(const char *name, PsqlSettings *pset) +describeAggregates(const char *name, PsqlSettings *pset, bool verbose, bool desc) { - char descbuf[384 + 2 * REGEXP_CUTOFF]; /* observe/adjust this - * if you change the - * query */ + char buf[384 + 2 * REGEXP_CUTOFF]; 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, + strcpy(buf, "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 (verbose) + strcat(buf, " ,u.usename as \"Owner\""); + if (desc) + strcat(buf, ",\n obj_description(a.oid) as \"Description\""); + strcat(buf, !verbose ? + ("\nFROM pg_aggregate a, pg_type t\n" + "WHERE a.aggbasetype = t.oid\n") : + ("\nFROM pg_aggregate a, pg_type t, pg_user u\n" + "WHERE a.aggbasetype = t.oid AND a.aggowner = u.usesysid\n") + ); + if (name) { - strcat(descbuf, " AND a.aggname ~* '^"); - strncat(descbuf, name, REGEXP_CUTOFF); - strcat(descbuf, "'\n"); + strcat(buf, " AND a.aggname ~* '"); + strncat(buf, name, REGEXP_CUTOFF); + strcat(buf, "'\n"); } - strcat(descbuf, + strcat(buf, "UNION\n" "SELECT a.aggname AS \"Name\", '(all types)' as \"Type\""); - if (description) - strcat(descbuf, + if (verbose) + strcat(buf, " ,u.usename as \"Owner\""); + if (desc) + strcat(buf, ",\n obj_description(a.oid) as \"Description\""); - strcat(descbuf, - "\nFROM pg_aggregate a\n" - "WHERE a.aggbasetype = 0\n"); + strcat(buf, !verbose ? + ("\nFROM pg_aggregate a\n" + "WHERE a.aggbasetype = 0\n") : + ("\nFROM pg_aggregate a, pg_user u\n" + "WHERE a.aggbasetype = 0 AND a.aggowner = u.usesysid\n") + ); if (name) { - strcat(descbuf, " AND a.aggname ~* '^"); - strncat(descbuf, name, REGEXP_CUTOFF); - strcat(descbuf, "'\n"); + strcat(buf, " AND a.aggname ~* '"); + strncat(buf, name, REGEXP_CUTOFF); + strcat(buf, "'\n"); } - strcat(descbuf, "ORDER BY \"Name\", \"Type\""); + strcat(buf, "ORDER BY \"Name\", \"Type\""); - res = PSQLexec(pset, descbuf); + res = PSQLexec(pset, buf); if (!res) return false; @@ -98,12 +103,12 @@ describeAggregates(const char *name, PsqlSettings *pset) /* \df - * takes an optional regexp to narrow down the function name + * Takes an optional regexp to narrow down the function name */ bool -describeFunctions(const char *name, PsqlSettings *pset) +describeFunctions(const char *name, PsqlSettings *pset, bool verbose, bool desc) { - char descbuf[384 + REGEXP_CUTOFF]; + char buf[384 + REGEXP_CUTOFF]; PGresult *res; printQueryOpt myopt = pset->popt; @@ -111,23 +116,33 @@ describeFunctions(const char *name, PsqlSettings *pset) * 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" + strcpy(buf, + "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 (verbose) + strcat(buf, ",\n u.usename as \"Owner\", l.lanname as \"Language\", p.prosrc as \"Source\""); + if (desc) + strcat(buf, ",\n obj_description(p.oid) as \"Description\""); + + if (!verbose) + strcat(buf, + "\nFROM pg_proc p, pg_type t\n" + "WHERE p.prorettype = t.oid and (pronargs = 0 or oid8types(p.proargtypes) != '')\n"); + else + strcat(buf, + "\nFROM pg_proc p, pg_type t, pg_language l, pg_user u\n" + "WHERE p.prorettype = t.oid AND p.prolang = l.oid AND p.proowner = u.usesysid\n" + " AND (pronargs = 0 or oid8types(p.proargtypes) != '')\n"); + if (name) { - strcat(descbuf, " AND p.proname ~* '^"); - strncat(descbuf, name, REGEXP_CUTOFF); - strcat(descbuf, "'\n"); + strcat(buf, " AND p.proname ~* '"); + strncat(buf, name, REGEXP_CUTOFF); + strcat(buf, "'\n"); } - strcat(descbuf, "ORDER BY \"Function\", \"Result\", \"Arguments\""); + strcat(buf, "ORDER BY \"Function\", \"Result\", \"Arguments\""); - res = PSQLexec(pset, descbuf); + res = PSQLexec(pset, buf); if (!res) return false; @@ -144,33 +159,44 @@ describeFunctions(const char *name, PsqlSettings *pset) /* - * describeTypes - * - * for \dT + * \dT + * describe types */ bool -describeTypes(const char *name, PsqlSettings *pset) +describeTypes(const char *name, PsqlSettings *pset, bool verbose, bool desc) { - char descbuf[256 + REGEXP_CUTOFF]; + char buf[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(oid) as \"Description\""); - strcat(descbuf, "\nFROM pg_type\n" - "WHERE typrelid = 0 AND typname !~ '^_.*'\n"); + strcpy(buf, "SELECT t.typname AS \"Type\""); + if (verbose) + strcat(buf, + ",\n (CASE WHEN t.typlen=-1 THEN 'var'::text ELSE t.typlen::text END) as \"Length\"" + ",\n u.usename as \"Owner\"" + ); + if (desc) + strcat(buf, ",\n obj_description(t.oid) as \"Description\""); + /* + * do not include array types (start with underscore), + * do not include user relations (typrelid!=0) + */ + strcat(buf, !verbose ? + ("\nFROM pg_type t\n" + "WHERE t.typrelid = 0 AND t.typname !~ '^_.*'\n") : + ("\nFROM pg_type t, pg_user u\n" + "WHERE t.typrelid = 0 AND t.typname !~ '^_.*' AND t.typowner = u.usesysid\n") + ); if (name) { - strcat(descbuf, " AND typname ~* '^"); - strncat(descbuf, name, REGEXP_CUTOFF); - strcat(descbuf, "' "); + strcat(buf, " AND t.typname ~* '"); + strncat(buf, name, REGEXP_CUTOFF); + strcat(buf, "' "); } - strcat(descbuf, "ORDER BY typname;"); + strcat(buf, "ORDER BY t.typname;"); - res = PSQLexec(pset, descbuf); + res = PSQLexec(pset, buf); if (!res) return false; @@ -187,28 +213,28 @@ describeTypes(const char *name, PsqlSettings *pset) /* \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) +describeOperators(const char *name, PsqlSettings *pset, bool verbose, bool desc) { - char descbuf[1536 + 3 * 32]; /* 32 is max length for operator - * name */ + char buf[1536 + 3 * REGEXP_CUTOFF]; PGresult *res; - bool description = GetVariableBool(pset->vars, "description"); printQueryOpt myopt = pset->popt; - descbuf[0] = '\0'; + /* Not used right now. Maybe later. */ + (void)verbose; - strcat(descbuf, "SELECT o.oprname AS \"Op\",\n" + /* FIXME: Use outer joins here when ready */ + + strcpy(buf, + "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" + if (desc) + strcat(buf, ",\n obj_description(p.oid) as \"Description\""); + strcat(buf, + "\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" @@ -218,51 +244,51 @@ describeOperators(const char *name, PsqlSettings *pset) " o.oprright = t2.oid\n"); if (name) { - strcat(descbuf, " AND o.oprname = '"); - strncat(descbuf, name, 32); - strcat(descbuf, "'\n"); + strcat(buf, " AND o.oprname ~ '"); + strncat(buf, name, REGEXP_CUTOFF); + strcat(buf, "'\n"); } - strcat(descbuf, "\nUNION\n\n" + strcat(buf, "\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" + if (desc) + strcat(buf, ",\n obj_description(p.oid) as \"Description\""); + strcat(buf, "\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(buf, "AND o.oprname ~ '"); + strncat(buf, name, REGEXP_CUTOFF); + strcat(buf, "'\n"); } - strcat(descbuf, "\nUNION\n\n" + strcat(buf, "\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" + if (desc) + strcat(buf, ",\n obj_description(p.oid) as \"Description\""); + strcat(buf, "\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(buf, "AND o.oprname ~ '"); + strncat(buf, name, REGEXP_CUTOFF); + strcat(buf, "'\n"); } - strcat(descbuf, "\nORDER BY \"Op\", \"Left arg\", \"Right arg\", \"Result\""); + strcat(buf, "\nORDER BY \"Op\", \"Left arg\", \"Right arg\", \"Result\""); - res = PSQLexec(pset, descbuf); + res = PSQLexec(pset, buf); if (!res) return false; @@ -283,26 +309,26 @@ describeOperators(const char *name, PsqlSettings *pset) * for \l, \list, and -l switch */ bool -listAllDbs(PsqlSettings *pset) +listAllDbs(PsqlSettings *pset, bool desc) { PGresult *res; - char descbuf[256]; + char buf[256]; printQueryOpt myopt = pset->popt; - descbuf[0] = '\0'; - strcat(descbuf, "SELECT pg_database.datname as \"Database\",\n" + strcpy(buf, + "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" + if (desc) + strcat(buf, ",\n obj_description(pg_database.oid) as \"Description\"\n"); + strcat(buf, "FROM pg_database, pg_user\n" "WHERE pg_database.datdba = pg_user.usesysid\n" "ORDER BY \"Database\""); - res = PSQLexec(pset, descbuf); + res = PSQLexec(pset, buf); if (!res) return false; @@ -337,7 +363,7 @@ permissionsList(const char *name, PsqlSettings *pset) " relname !~ '^pg_'\n"); if (name) { - strcat(descbuf, " AND rename ~ '^"); + strcat(descbuf, " AND rename ~ '"); strncat(descbuf, name, REGEXP_CUTOFF); strcat(descbuf, "'\n"); } @@ -389,7 +415,7 @@ objectDescription(const char *object, PsqlSettings *pset) "WHERE a.oid = d.objoid\n"); if (object) { - strcat(descbuf, " AND a.aggname ~* '^"); + strcat(descbuf, " AND a.aggname ~* '"); strncat(descbuf, object, REGEXP_CUTOFF); strcat(descbuf, "'\n"); } @@ -401,7 +427,7 @@ objectDescription(const char *object, PsqlSettings *pset) "WHERE p.oid = d.objoid AND (p.pronargs = 0 or oid8types(p.proargtypes) != '')\n"); if (object) { - strcat(descbuf, " AND p.proname ~* '^"); + strcat(descbuf, " AND p.proname ~* '"); strncat(descbuf, object, REGEXP_CUTOFF); strcat(descbuf, "'\n"); } @@ -410,11 +436,11 @@ objectDescription(const char *object, PsqlSettings *pset) 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 + /* must get comment via associated function */ "WHERE RegprocToOid(o.oprcode) = d.objoid\n"); if (object) { - strcat(descbuf, " AND o.oprname = '"); + strcat(descbuf, " AND o.oprname ~ '"); strncat(descbuf, object, REGEXP_CUTOFF); strcat(descbuf, "'\n"); } @@ -426,7 +452,7 @@ objectDescription(const char *object, PsqlSettings *pset) "WHERE t.oid = d.objoid\n"); if (object) { - strcat(descbuf, " AND t.typname ~* '^"); + strcat(descbuf, " AND t.typname ~* '"); strncat(descbuf, object, REGEXP_CUTOFF); strcat(descbuf, "'\n"); } @@ -438,7 +464,7 @@ objectDescription(const char *object, PsqlSettings *pset) "WHERE c.oid = d.objoid\n"); if (object) { - strcat(descbuf, " AND c.relname ~* '^"); + strcat(descbuf, " AND c.relname ~* '"); strncat(descbuf, object, REGEXP_CUTOFF); strcat(descbuf, "'\n"); } @@ -450,7 +476,7 @@ objectDescription(const char *object, PsqlSettings *pset) "WHERE r.oid = d.objoid AND r.rulename !~ '^_RET'\n"); if (object) { - strcat(descbuf, " AND r.rulename ~* '^"); + strcat(descbuf, " AND r.rulename ~* '"); strncat(descbuf, object, REGEXP_CUTOFF); strcat(descbuf, "'\n"); } @@ -462,7 +488,7 @@ objectDescription(const char *object, PsqlSettings *pset) "WHERE t.oid = d.objoid\n"); if (object) { - strcat(descbuf, " AND t.tgname ~* '^"); + strcat(descbuf, " AND t.tgname ~* '"); strncat(descbuf, object, REGEXP_CUTOFF); strcat(descbuf, "'\n"); } @@ -510,89 +536,122 @@ xmalloc(size_t size) bool -describeTableDetails(const char *name, PsqlSettings *pset) +describeTableDetails(const char *name, PsqlSettings *pset, bool desc) { - char descbuf[512 + NAMEDATALEN]; - PGresult *res = NULL, - *res2 = NULL, - *res3 = NULL; + char buf[512 + 8 * NAMEDATALEN]; + PGresult *res = NULL; printTableOpt myopt = pset->popt.topt; - bool description = GetVariableBool(pset->vars, "description"); int i; - const char *view_def = NULL; + const char *view_def = NULL; const char *headers[5]; char **cells = NULL; char *title = NULL; char **footers = NULL; char **ptr; unsigned int cols; + struct { bool hasindex; char relkind; int16 checks; int16 triggers; bool hasrules; } tableinfo; + bool error = false; - 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; + /* truncate table name */ + if (strlen(name) > NAMEDATALEN) { + char *my_name = xmalloc(NAMEDATALEN+1); + strncpy(my_name, name, NAMEDATALEN); + my_name[NAMEDATALEN] = '\0'; + name = my_name; + } /* 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; + sprintf(buf, + "SELECT relhasindex, relkind, relchecks, reltriggers, relhasrules\n" + "FROM pg_class WHERE relname='%s'", + name); + res = PSQLexec(pset, buf); + 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); + fprintf(stdout, "Did not find any relation 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; + /* FIXME: check for null pointers here? */ + tableinfo.hasindex = strcmp(PQgetvalue(res,0,0),"t")==0; + tableinfo.relkind = *(PQgetvalue(res,0,1)); + tableinfo.checks = atoi(PQgetvalue(res,0,2)); + tableinfo.triggers = atoi(PQgetvalue(res,0,3)); + tableinfo.hasrules = strcmp(PQgetvalue(res,0,4),"t")==0; + PQclear(res); - if (PQntuples(res2) > 0) - view_def = PQgetvalue(res2, 0, 0); + headers[0] = "Attribute"; + headers[1] = "Type"; + cols = 2; + if (tableinfo.relkind == 'r' || tableinfo.relkind == 's') + { + cols++; + headers[cols-1] = "Extra"; + } - /* Generate table cells to be printed */ - cells = calloc(PQntuples(res) * cols + 1, sizeof(*cells)); - if (!cells) + if (desc) { - perror("calloc"); - exit(EXIT_FAILURE); + cols++; + headers[cols-1] = "Description"; } + headers[cols] = NULL; + + + /* Get column info */ + strcpy(buf, "SELECT a.attname, t.typname, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, a.attnum"); + if (desc) + strcat(buf, ", obj_description(a.oid)"); + strcat(buf, "\nFROM pg_class c, pg_attribute a, pg_type t\n" + "WHERE c.relname = '"); + strncat(buf, name, NAMEDATALEN); + strcat(buf, "'\n AND a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid\n" + "ORDER BY a.attnum"); + + res = PSQLexec(pset, buf); + if (!res) + return false; + + /* Check if table is a view */ + if (tableinfo.hasrules) { + PGresult *result; + sprintf(buf, "SELECT definition FROM pg_views WHERE viewname = '%s'", name); + result = PSQLexec(pset, buf); + if (!result) + { + PQclear(res); + PQclear(result); + return false; + } + + if (PQntuples(result) > 0) + view_def = xstrdup(PQgetvalue(result, 0, 0)); + PQclear(result); + } + + + /* Generate table cells to be printed */ + cells = xmalloc((PQntuples(res) * cols + 1) * sizeof(*cells)); + cells[PQntuples(res) * cols] = NULL; /* end of list */ + for (i = 0; i < PQntuples(res); i++) { int4 attypmod = atoi(PQgetvalue(res, i, 3)); const char *attype = PQgetvalue(res, i, 1); /* Name */ - cells[i * cols + 0] = (char*)PQgetvalue(res, i, 0); /* don't free this afterwards */ - + cells[i * cols + 0] = (char *)PQgetvalue(res, i, 0); /* don't free this afterwards */ + /* Type */ + /* (convert some internal type names to "readable") */ cells[i * cols + 1] = xmalloc(NAMEDATALEN + 16); if (strcmp(attype, "bpchar") == 0) sprintf(cells[i * cols + 1], "char(%d)", attypmod != -1 ? attypmod - VARHDRSZ : 0); @@ -606,85 +665,235 @@ describeTableDetails(const char *name, PsqlSettings *pset) 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)); - } + + /* Extra: not null and default */ + /* (I'm cutting off the 'default' string at 128) */ + if (tableinfo.relkind == 'r' || tableinfo.relkind == 's') + { + cells[i * cols + 2] = xmalloc(128 + 128); + cells[i * cols + 2][0] = '\0'; + if (strcmp(PQgetvalue(res, i, 4), "t") == 0) + strcat(cells[i * cols + 2], "not null"); + + /* handle "default" here */ + if (strcmp(PQgetvalue(res, i, 5), "t") == 0) + { + PGresult *result; + + sprintf(buf, "SELECT substring(d.adsrc for 128) FROM pg_attrdef d, pg_class c\n" + "WHERE c.relname = '%s' AND c.oid = d.adrelid AND d.adnum = %s", + name, PQgetvalue(res, i, 6)); + + result = PSQLexec(pset, buf); + if (!result) + error = true; + else + { + if (cells[i * cols + 2][0]) + strcat(cells[i * cols + 2], " "); + strcat(cells[i * cols + 2], "default "); + strcat(cells[i * cols + 2], PQgetvalue(result, 0, 0)); + PQclear(result); + } + } + } + + if (error) + break; /* Description */ - if (description) - cells[i * cols + 3] = (char*)PQgetvalue(res, i, 7); + if (desc) + cells[i * cols + cols-1] = (char*)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); + title = xmalloc(20 + strlen(name)); + switch (tableinfo.relkind) { + case 'r': + if (view_def) + sprintf(title, "View \"%s\"", name); + else + sprintf(title, "Table \"%s\"", name); + break; + case 'S': + sprintf(title, "Sequence \"%s\"", name); + break; + case 'i': + sprintf(title, "Index \"%s\"", name); + break; + case 's': + sprintf(title, "System table \"%s\"", name); + break; + default: + sprintf(title, "?%c?", tableinfo.relkind); + } /* Make footers */ - if (view_def) + /* Information about the index */ + if (tableinfo.relkind == 'i') + { + PGresult * result; + + sprintf(buf, "SELECT i.indisunique, i.indisprimary, a.amname\n" + "FROM pg_index i, pg_class c, pg_am a\n" + "WHERE i.indexrelid = c.oid AND c.relname = '%s' AND c.relam = a.oid", + name); + + result = PSQLexec(pset, buf); + if (!result) + error = true; + else + { + footers = xmalloc(2 * sizeof(*footers)); + footers[0] = xmalloc(NAMEDATALEN + 32); + sprintf(footers[0], "%s%s", + strcmp(PQgetvalue(result, 0, 0), "t")==0 ? "unique " : "", + PQgetvalue(result, 0, 2) + ); + if (strcmp(PQgetvalue(result, 0, 1), "t")==0) + strcat(footers[0], " (primary key)"); + footers[1] = NULL; + } + } + /* Information about the view */ + else if (tableinfo.relkind == 'r' && 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 + + /* Information about the table */ + else if (tableinfo.relkind == 'r') { - /* display indices */ - strcpy(descbuf, "SELECT c2.relname\n" - "FROM pg_class c, pg_class c2, pg_index i\n" - "WHERE c.relname = '"); - strncat(descbuf, name, NAMEDATALEN); - strcat(descbuf, "' AND c.oid = i.indrelid AND i.indexrelid = c2.oid\n" - "ORDER BY c2.relname"); - res3 = PSQLexec(pset, descbuf); - if (!res3) - return false; - - if (PQntuples(res3) > 0) - { - footers = xmalloc((PQntuples(res3) + 1) * sizeof(*footers)); - - for (i = 0; i < PQntuples(res3); i++) - { - footers[i] = xmalloc(10 + NAMEDATALEN); - if (PQntuples(res3) == 1) - sprintf(footers[i], "Index: %s", PQgetvalue(res3, i, 0)); - else if (i == 0) - sprintf(footers[i], "Indices: %s", PQgetvalue(res3, i, 0)); - else - sprintf(footers[i], " %s", PQgetvalue(res3, i, 0)); - } - - footers[i] = NULL; + PGresult *result1=NULL, *result2=NULL, *result3=NULL, *result4=NULL; + int index_count=0, constr_count=0, rule_count=0, trigger_count=0; + int count_footers=0; + + /* count indices */ + if (!error && tableinfo.hasindex) + { + sprintf(buf, "SELECT c2.relname\n" + "FROM pg_class c, pg_class c2, pg_index i\n" + "WHERE c.relname = '%s' AND c.oid = i.indrelid AND i.indexrelid = c2.oid\n" + "ORDER BY c2.relname", + name); + result1 = PSQLexec(pset, buf); + if (!result1) + error = true; + else + index_count = PQntuples(result1); + } + + /* count table (and column) constraints */ + if (!error && tableinfo.checks) + { + sprintf(buf, "SELECT rcsrc\n" + "FROM pg_relcheck r, pg_class c\n" + "WHERE c.relname='%s' AND c.oid = r.rcrelid", + name); + result2 = PSQLexec(pset, buf); + if (!result2) + error = true; + else + constr_count = PQntuples(result2); + } + + /* count rules */ + if (!error && tableinfo.hasrules) + { + sprintf(buf, + "SELECT r.rulename\n" + "FROM pg_rewrite r, pg_class c\n" + "WHERE c.relname='%s' AND c.oid = r.ev_class", + name); + result3 = PSQLexec(pset, buf); + if (!result3) + error = true; + else + rule_count = PQntuples(result3); + } + + /* count triggers */ + if (!error && tableinfo.hasrules) + { + sprintf(buf, + "SELECT t.tgname\n" + "FROM pg_trigger t, pg_class c\n" + "WHERE c.relname='%s' AND c.oid = t.tgrelid", + name); + result4 = PSQLexec(pset, buf); + if (!result4) + error = true; + else + trigger_count = PQntuples(result4); + } + + footers = xmalloc((index_count + constr_count + rule_count + trigger_count + 1) * sizeof(*footers)); + + /* print indices */ + for (i = 0; i < index_count; i++) + { + sprintf(buf, "%s %s", + index_count==1 ? "Index:" : (i==0 ? "Indices:" : " "), + PQgetvalue(result1, i, 0) + ); + if (i < index_count-1) + strcat(buf, ","); + + footers[count_footers++] = xstrdup(buf); + } + + /* print contraints */ + for (i = 0; i < constr_count; i++) + { + sprintf(buf, "%s %s", + constr_count==1 ? "Constraint:" : (i==0 ? "Constraints:" : " "), + PQgetvalue(result2, i, 0) + ); + footers[count_footers++] = xstrdup(buf); + } + + /* print rules */ + for (i = 0; i < rule_count; i++) + { + sprintf(buf, "%s %s", + rule_count==1 ? "Rule:" : (i==0 ? "Rules:" : " "), + PQgetvalue(result3, i, 0) + ); + if (i < rule_count-1) + strcat(buf, ","); + + footers[count_footers++] = xstrdup(buf); + } + + /* print triggers */ + for (i = 0; i < trigger_count; i++) + { + sprintf(buf, "%s %s", + trigger_count==1 ? "Trigger:" : (i==0 ? "Triggers:" : " "), + PQgetvalue(result4, i, 0) + ); + if (i < trigger_count-1) + strcat(buf, ","); + + footers[count_footers++] = xstrdup(buf); } - } + /* end of list marker */ + footers[count_footers] = NULL; + + PQclear(result1); + PQclear(result2); + PQclear(result3); + PQclear(result4); + } - myopt.tuples_only = false; - printTable(title, headers, (const char**)cells, (const char**)footers, "llll", &myopt, pset->queryFout); + if (!error) { + myopt.tuples_only = false; + printTable(title, headers, (const char**)cells, (const char**)footers, "llll", &myopt, pset->queryFout); + } /* clean up */ free(title); @@ -692,7 +901,8 @@ describeTableDetails(const char *name, PsqlSettings *pset) for (i = 0; i < PQntuples(res); i++) { free(cells[i * cols + 1]); - free(cells[i * cols + 2]); + if (tableinfo.relkind == 'r' || tableinfo.relkind == 's') + free(cells[i * cols + 2]); } free(cells); @@ -701,10 +911,8 @@ describeTableDetails(const char *name, PsqlSettings *pset) free(footers); PQclear(res); - PQclear(res2); - PQclear(res3); - return true; + return !error; } @@ -719,11 +927,11 @@ describeTableDetails(const char *name, PsqlSettings *pset) * i - indices * v - views * s - sequences - * S - systems tables (~'^pg_') + * S - systems tables (~ '^pg_') * (any order of the above is fine) */ bool -listTables(const char *infotype, const char *name, PsqlSettings *pset) +listTables(const char *infotype, const char *name, PsqlSettings *pset, bool desc) { bool showTables = strchr(infotype, 't') != NULL; bool showIndices = strchr(infotype, 'i') != NULL; @@ -731,120 +939,121 @@ listTables(const char *infotype, const char *name, PsqlSettings *pset) bool showSeq = strchr(infotype, 's') != NULL; bool showSystem = strchr(infotype, 'S') != NULL; - bool description = GetVariableBool(pset->vars, "description"); - - char descbuf[1536 + 4 * REGEXP_CUTOFF]; + char buf[1536 + 4 * REGEXP_CUTOFF]; PGresult *res; printQueryOpt myopt = pset->popt; - descbuf[0] = '\0'; + buf[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" + strcat(buf, "SELECT c.relname as \"Name\", 'table'::text as \"Type\", u.usename as \"Owner\""); + if (desc) + strcat(buf, ", obj_description(c.oid) as \"Description\""); + strcat(buf, "\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"); + strcat(buf, 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"); + strcat(buf, " AND c.relname ~ '"); + strncat(buf, name, REGEXP_CUTOFF); + strcat(buf, "'\n"); } } /* views */ if (showViews) { - if (descbuf[0]) - strcat(descbuf, "\nUNION\n\n"); + if (buf[0]) + strcat(buf, "\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" + strcat(buf, "SELECT c.relname as \"Name\", 'view'::text as \"Type\", u.usename as \"Owner\""); + if (desc) + strcat(buf, ", obj_description(c.oid) as \"Description\""); + strcat(buf, "\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"); + strcat(buf, 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"); + strcat(buf, " AND c.relname ~ '"); + strncat(buf, name, REGEXP_CUTOFF); + strcat(buf, "'\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" + if (buf[0]) + strcat(buf, "\nUNION\n\n"); + + strcat(buf, + "SELECT c.relname as \"Name\",\n" + " (CASE WHEN relkind = 'S' THEN 'sequence'::text ELSE 'index'::text END) as \"Type\",\n" + " u.usename as \"Owner\"" + ); + if (desc) + strcat(buf, ", obj_description(c.oid) as \"Description\""); + strcat(buf, "\nFROM pg_class c, pg_user u\n" "WHERE c.relowner = u.usesysid AND relkind in ("); if (showIndices && showSeq) - strcat(descbuf, "'i', 'S'"); + strcat(buf, "'i', 'S'"); else if (showIndices) - strcat(descbuf, "'i'"); + strcat(buf, "'i'"); else - strcat(descbuf, "'S'"); - strcat(descbuf, ")\n"); + strcat(buf, "'S'"); + strcat(buf, ")\n"); /* ignore large-obj indices */ if (showIndices) - strcat(descbuf, " AND (c.relkind != 'i' OR c.relname !~ '^xinx')\n"); + strcat(buf, " AND (c.relkind != 'i' OR c.relname !~ '^xinx')\n"); - strcat(descbuf, showSystem ? " AND c.relname ~ '^pg_'\n" : " AND c.relname !~ '^pg_'\n"); + strcat(buf, 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"); + strcat(buf, " AND c.relname ~ '"); + strncat(buf, name, REGEXP_CUTOFF); + strcat(buf, "'\n"); } } /* real system catalogue tables */ if (showSystem && showTables) { - if (descbuf[0]) - strcat(descbuf, "\nUNION\n\n"); + if (buf[0]) + strcat(buf, "\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" + strcat(buf, "SELECT c.relname as \"Name\", 'system'::text as \"Type\", u.usename as \"Owner\""); + if (desc) + strcat(buf, ", obj_description(c.oid) as \"Description\""); + strcat(buf, "\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(buf, " AND c.relname ~ '"); + strncat(buf, name, REGEXP_CUTOFF); + strcat(buf, "'\n"); } } - strcat(descbuf, "\nORDER BY \"Name\""); + strcat(buf, "\nORDER BY \"Name\""); - res = PSQLexec(pset, descbuf); + res = PSQLexec(pset, buf); if (!res) return false; if (PQntuples(res) == 0) - fprintf(pset->queryFout, "No matching classes found.\n"); + fprintf(pset->queryFout, "No matching relations found.\n"); else { myopt.topt.tuples_only = false; myopt.nullPrint = NULL; - myopt.title = "List of classes"; + myopt.title = "List of relations"; printQuery(res, &myopt, pset->queryFout); } @@ -854,4 +1063,4 @@ listTables(const char *infotype, const char *name, PsqlSettings *pset) } -/* the end */ +/* end of file */ diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h index 3ab6367c8df..164c7044e70 100644 --- a/src/bin/psql/describe.h +++ b/src/bin/psql/describe.h @@ -1,42 +1,34 @@ #ifndef DESCRIBE_H #define DESCRIBE_H +#include <c.h> #include "settings.h" /* \da */ -bool - describeAggregates(const char *name, PsqlSettings *pset); +bool describeAggregates(const char *name, PsqlSettings *pset, bool verbose, bool desc); /* \df */ -bool - describeFunctions(const char *name, PsqlSettings *pset); +bool describeFunctions(const char *name, PsqlSettings *pset, bool verbose, bool desc); /* \dT */ -bool - describeTypes(const char *name, PsqlSettings *pset); +bool describeTypes(const char *name, PsqlSettings *pset, bool verbose, bool desc); /* \do */ -bool - describeOperators(const char *name, PsqlSettings *pset); +bool describeOperators(const char *name, PsqlSettings *pset, bool verbose, bool desc); -/* \dp (formerly \z) */ -bool - permissionsList(const char *name, PsqlSettings *pset); +/* \z (or \dp) */ +bool permissionsList(const char *name, PsqlSettings *pset); /* \dd */ -bool - objectDescription(const char *object, PsqlSettings *pset); +bool objectDescription(const char *object, PsqlSettings *pset); /* \d foo */ -bool - describeTableDetails(const char *name, PsqlSettings *pset); +bool describeTableDetails(const char *name, PsqlSettings *pset, bool desc); /* \l */ -bool - listAllDbs(PsqlSettings *pset); +bool listAllDbs(PsqlSettings *pset, bool desc); -/* \dt, \di, \dS, etc. */ -bool - listTables(const char *infotype, const char *name, PsqlSettings *pset); +/* \dt, \di, \ds, \dS, etc. */ +bool listTables(const char *infotype, const char *name, PsqlSettings *pset, bool desc); #endif /* DESCRIBE_H */ diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index ec5a8882893..e200f391390 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -180,7 +180,7 @@ slashUsage(PsqlSettings *pset) fprintf(fout, " \\c[onnect] [dbname|- [user|?]] -- connect to new database (now '%s')\n", PQdb(pset->db)); fprintf(fout, " \\copy [binary] <table> [with oids] {from|to} <fname>[using delimiters '<char>']\n"); fprintf(fout, " \\copyright -- show PostgreSQL copyright\n"); - fprintf(fout, " \\d -- list tables, views, and sequences\n"); + fprintf(fout, " \\d <table> -- describe table (or view, index, sequence)\n"); fprintf(fout, " \\d{i|s|t|v|S}-- list only indices/sequences/tables/views/system tables\n"); fprintf(fout, " \\da -- list aggregates\n"); fprintf(fout, " \\dd [object] -- list comment for table, type, function, or operator\n"); diff --git a/src/bin/psql/input.c b/src/bin/psql/input.c index d8ceebab935..31fe4ce7737 100644 --- a/src/bin/psql/input.c +++ b/src/bin/psql/input.c @@ -4,7 +4,8 @@ #include <pqexpbuffer.h> -/* Note that this file does not depend on any other files in psql. */ +#include "settings.h" +#include "tab-complete.h" /* Runtime options for turning off readline and history */ /* (of course there is no runtime command for doing that :) */ @@ -96,13 +97,14 @@ gets_fromFile(FILE *source) * The only "flag" right now is 1 for use readline & history. */ void -initializeInput(int flags) +initializeInput(int flags, PsqlSettings *pset) { #ifdef USE_READLINE if (flags == 1) { useReadline = true; rl_readline_name = "psql"; + initialize_readline(&(pset->db)); } #endif diff --git a/src/bin/psql/input.h b/src/bin/psql/input.h index c91e9ca7b19..938170ee83b 100644 --- a/src/bin/psql/input.h +++ b/src/bin/psql/input.h @@ -37,20 +37,15 @@ #endif -char * - gets_interactive(const char *prompt); +char * gets_interactive(const char *prompt); -char * - gets_fromFile(FILE *source); +char * gets_fromFile(FILE *source); -void - initializeInput(int flags); +void initializeInput(int flags, PsqlSettings *pset); -bool - saveHistory(const char *fname); +bool saveHistory(const char *fname); -void - finishInput(void); +void finishInput(void); #endif diff --git a/src/bin/psql/large_obj.c b/src/bin/psql/large_obj.c index 885a18cd4c0..5358528833d 100644 --- a/src/bin/psql/large_obj.c +++ b/src/bin/psql/large_obj.c @@ -220,6 +220,7 @@ do_lo_import(PsqlSettings *pset, const char *filename_arg, const char *comment_a fprintf(pset->queryFout, "lo_import %d\n", loid); + pset->lastOid = loid; return true; } @@ -311,21 +312,20 @@ do_lo_unlink(PsqlSettings *pset, const char *loid_arg) * Show all large objects in database, with comments if desired */ bool -do_lo_list(PsqlSettings *pset) +do_lo_list(PsqlSettings *pset, bool desc) { PGresult *res; - char descbuf[512]; + char buf[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" + strcpy(buf, "SELECT usename as \"Owner\", substring(relname from 5) as \"ID\""); + if (desc) + strcat(buf, ",\n obj_description(pg_class.oid) as \"Description\""); + strcat(buf, "\nFROM pg_class, pg_user\n" "WHERE usesysid = relowner AND relkind = 'l'\n" "ORDER BY \"ID\""); - res = PSQLexec(pset, descbuf); + res = PSQLexec(pset, buf); if (!res) return false; diff --git a/src/bin/psql/large_obj.h b/src/bin/psql/large_obj.h index 4f3f2fc428a..bb06867a2ec 100644 --- a/src/bin/psql/large_obj.h +++ b/src/bin/psql/large_obj.h @@ -1,11 +1,12 @@ #ifndef LARGE_OBJ_H #define LARGE_OBJ_H +#include <c.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); +bool do_lo_list(PsqlSettings *pset, bool desc); #endif /* LARGE_OBJ_H */ diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c index 3db4a459014..067be54e2cc 100644 --- a/src/bin/psql/mainloop.c +++ b/src/bin/psql/mainloop.c @@ -16,19 +16,20 @@ -/* 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. + * + * FIXME: rewrite this whole thing with flex */ 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; @@ -37,6 +38,7 @@ MainLoop(PsqlSettings *pset, FILE *source) bool success; char in_quote; /* == 0 for no in_quote */ bool was_bslash; /* backslash */ + bool xcomment; /* in extended comment */ int paren_level; unsigned int query_start; @@ -49,7 +51,6 @@ MainLoop(PsqlSettings *pset, FILE *source) bool prev_cmd_interactive; bool die_on_error; - const char *interpol_char; /* Save old settings */ @@ -68,7 +69,7 @@ MainLoop(PsqlSettings *pset, FILE *source) exit(EXIT_FAILURE); } - xcomment = NULL; + xcomment = false; in_quote = 0; paren_level = 0; slashCmdStatus = CMD_UNKNOWN; /* set default */ @@ -87,7 +88,7 @@ MainLoop(PsqlSettings *pset, FILE *source) line = strdup(query_buf->data); resetPQExpBuffer(query_buf); /* reset parsing state since we are rescanning whole query */ - xcomment = NULL; + xcomment = false; in_quote = 0; paren_level = 0; } @@ -106,7 +107,7 @@ MainLoop(PsqlSettings *pset, FILE *source) prompt_status = PROMPT_SINGLEQUOTE; else if (in_quote && in_quote == '"') prompt_status = PROMPT_DOUBLEQUOTE; - else if (xcomment != NULL) + else if (xcomment) prompt_status = PROMPT_COMMENT; else if (query_buf->len > 0) prompt_status = PROMPT_CONTINUE; @@ -120,10 +121,11 @@ MainLoop(PsqlSettings *pset, FILE *source) } - /* Setting these will not have effect until next line */ + /* Setting this will not have effect until next line. (Faster. + Also think about what happens if there is an error processing + _this_ command.) + */ 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 @@ -144,11 +146,6 @@ MainLoop(PsqlSettings *pset, FILE *source) continue; } - /* not currently inside an extended comment? */ - if (xcomment) - xcomment = line; - - /* strip trailing backslashes, they don't have a clear meaning */ while (1) { @@ -160,52 +157,6 @@ MainLoop(PsqlSettings *pset, FILE *source) 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') { @@ -213,6 +164,12 @@ MainLoop(PsqlSettings *pset, FILE *source) continue; } + + /* echo back if input is from file and flag is set */ + if (!pset->cur_cmd_interactive && GetVariableBool(pset->vars, "echo")) + puts(line); + + slashCmdStatus = CMD_UNKNOWN; len = strlen(line); @@ -224,24 +181,15 @@ MainLoop(PsqlSettings *pset, FILE *source) * 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)) +#define ADVANCE_1 (prevlen = thislen, i += thislen, thislen = PQmblen(line+i)) success = true; - for (i = 0; i < len; ADVANCE_1) + for (i = 0, prevlen = 0, thislen = (len > 0) ? PQmblen(line) : 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; - + was_bslash = (i > 0 && line[i - prevlen] == '\\'); /* in quote? */ if (in_quote) @@ -256,11 +204,11 @@ MainLoop(PsqlSettings *pset, FILE *source) in_quote = line[i]; /* in extended comment? */ - else if (xcomment != NULL) + else if (xcomment) { if (line[i] == '*' && line[i + thislen] == '/') { - xcomment = NULL; + xcomment = false; ADVANCE_1; } } @@ -268,7 +216,7 @@ MainLoop(PsqlSettings *pset, FILE *source) /* start of extended comment? */ else if (line[i] == '/' && line[i + thislen] == '*') { - xcomment = &line[i]; + xcomment = true; ADVANCE_1; } @@ -287,8 +235,45 @@ MainLoop(PsqlSettings *pset, FILE *source) else if (line[i] == ')' && paren_level > 0) paren_level--; + /* colon -> substitute variable */ + /* we need to be on the watch for the '::' operator */ + else if (line[i] == ':' && !was_bslash && + strspn(line+i+thislen, VALID_VARIABLE_CHARS)>0 && + (prevlen > 0 && line[i-prevlen]!=':') + ) + { + size_t in_length, + out_length; + const char *value; + char *new; + char after; /* the character after the variable name + will be temporarily overwritten */ + + in_length = strspn(&line[i + thislen], VALID_VARIABLE_CHARS); + after = line[i + thislen + in_length]; + line[i + thislen + in_length] = '\0'; + value = interpolate_var(&line[i + thislen], pset); + out_length = strlen(value); + + new = malloc(len + out_length - (1 + in_length) + 1); + if (!new) + { + perror("malloc"); + exit(EXIT_FAILURE); + } + + sprintf(new, "%.*s%s%c", i, line, value, after); + if (after) + strcat(new, line + i + 1 + in_length + 1); + + free(line); + line = new; + continue; /* reparse the just substituted */ + } + + /* semicolon? then send query */ - else if (line[i] == ';' && !was_bslash && paren_level == 0) + else if (line[i] == ';' && !was_bslash) { line[i] = '\0'; /* is there anything else on the line? */ @@ -312,6 +297,15 @@ MainLoop(PsqlSettings *pset, FILE *source) query_start = i + thislen; } + /* if you have a burning need to send a semicolon or colon to + the backend ... */ + else if (was_bslash && (line[i] == ';' || line[i] == ':')) + { + /* remove the backslash */ + memmove(line + i - prevlen, line + i, len - i + 1); + len--; + } + /* backslash command */ else if (was_bslash) { @@ -355,7 +349,13 @@ MainLoop(PsqlSettings *pset, FILE *source) else break; } - } + + + /* stop the script after error */ + if (!success && die_on_error) + break; + + } /* for (line) */ if (!success && die_on_error && !pset->cur_cmd_interactive) diff --git a/src/bin/psql/print.c b/src/bin/psql/print.c index 90b4f59d612..481f76f343e 100644 --- a/src/bin/psql/print.c +++ b/src/bin/psql/print.c @@ -30,8 +30,8 @@ static void print_unaligned_text(const char *title, const char * const * headers, const char * const * cells, const char * const * footers, - const char *opt_fieldsep, bool opt_barebones, - FILE *fout) + const char *opt_fieldsep, bool opt_barebones, + FILE *fout) { unsigned int col_count = 0; unsigned int i; @@ -83,8 +83,8 @@ print_unaligned_text(const char *title, const char * const * headers, static void print_unaligned_vertical(const char *title, const char * const * headers, const char * const * cells, const char * const * footers, - const char *opt_fieldsep, bool opt_barebones, - FILE *fout) + const char *opt_fieldsep, bool opt_barebones, + FILE *fout) { unsigned int col_count = 0; unsigned int i; @@ -172,7 +172,7 @@ static void print_aligned_text(const char *title, const char * const * headers, const char * const * cells, const char * const * footers, const char *opt_align, bool opt_barebones, unsigned short int opt_border, - FILE *fout) + FILE *fout) { unsigned int col_count = 0; unsigned int i, @@ -313,8 +313,8 @@ print_aligned_text(const char *title, const char * const * headers, static void print_aligned_vertical(const char *title, const char * const * headers, const char * const * cells, const char * const * footers, - bool opt_barebones, unsigned short int opt_border, - FILE *fout) + bool opt_barebones, unsigned short int opt_border, + FILE *fout) { unsigned int col_count = 0; unsigned int record = 1; @@ -371,11 +371,10 @@ print_aligned_vertical(const char *title, const char * const * headers, { if (!opt_barebones) { - char *div_copy = strdup(divider); char *record_str = malloc(32); size_t record_str_len; - if (!div_copy || !record_str) + if (!record_str) { perror("malloc"); exit(EXIT_FAILURE); @@ -386,29 +385,32 @@ print_aligned_vertical(const char *title, const char * const * headers, 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); + + if (record_str_len + opt_border > strlen(divider)) + fprintf(fout, "%.*s%s\n", opt_border, divider, record_str); + else + { + char *div_copy = strdup(divider); + + if (!div_copy) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + strncpy(div_copy + opt_border, record_str, record_str_len); + fprintf(fout, "%s\n", div_copy); + free(div_copy); + } free(record_str); - free(div_copy); } - else if (i != 0 && opt_border < 2) + 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 @@ -479,7 +481,7 @@ print_html_text(const char *title, const char * const * headers, const char * const * cells, const char * const * footers, const char *opt_align, bool opt_barebones, unsigned short int opt_border, const char *opt_table_attr, - FILE *fout) + FILE *fout) { unsigned int col_count = 0; unsigned int i; @@ -661,7 +663,7 @@ static void print_latex_text(const char *title, const char * const * headers, const char * const * cells, const char * const * footers, const char *opt_align, bool opt_barebones, unsigned short int opt_border, - FILE *fout) + FILE *fout) { unsigned int col_count = 0; unsigned int i; @@ -757,7 +759,7 @@ static void print_latex_vertical(const char *title, const char * const * headers, const char * const * cells, const char * const * footers, const char *opt_align, bool opt_barebones, unsigned short int opt_border, - FILE *fout) + FILE *fout) { unsigned int col_count = 0; unsigned int i; @@ -836,7 +838,7 @@ print_latex_vertical(const char *title, const char * const * headers, /********************************/ -/* Public functions */ +/* Public functions */ /********************************/ @@ -845,8 +847,8 @@ printTable(const char *title, const char * const * headers, const char * const * cells, const char * const * footers, - const char *align, - const printTableOpt * opt, FILE *fout) + const char *align, + const printTableOpt * opt, FILE *fout) { const char *default_footer[] = {NULL}; unsigned short int border = opt->border; @@ -968,8 +970,8 @@ printQuery(const PGresult *result, const printQueryOpt * opt, FILE *fout) int nfields; const char **headers; const char **cells; - char **footers; - char *align; + char **footers; + char *align; int i; /* extract headers */ diff --git a/src/bin/psql/print.h b/src/bin/psql/print.h index 5b1c7671b11..277441ee045 100644 --- a/src/bin/psql/print.h +++ b/src/bin/psql/print.h @@ -46,8 +46,8 @@ typedef struct _printTableOpt */ void printTable(const char *title, const char * const * headers, const char * const * cells, const char * const * footers, - const char *align, - const printTableOpt * opt, FILE *fout); + const char *align, + const printTableOpt * opt, FILE *fout); diff --git a/src/bin/psql/prompt.c b/src/bin/psql/prompt.c index 2912bd16eb8..22ca579351c 100644 --- a/src/bin/psql/prompt.c +++ b/src/bin/psql/prompt.c @@ -85,7 +85,7 @@ get_prompt(PsqlSettings *pset, promptStatus_t status) p && *p && strlen(destination) < MAX_PROMPT_SIZE; p++) { - MemSet(buf, 0, MAX_PROMPT_SIZE + 1); + memset(buf, 0, MAX_PROMPT_SIZE + 1); if (esc) { switch (*p) diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h index 5687e1801f2..828dbd40d0f 100644 --- a/src/bin/psql/settings.h +++ b/src/bin/psql/settings.h @@ -1,11 +1,13 @@ #ifndef SETTINGS_H #define SETTINGS_H +#include <config.h> +#include <c.h> #include <stdio.h> #include <stdlib.h> #include <libpq-fe.h> -#include <c.h> +#include <postgres_ext.h> #include "variables.h" #include "print.h" @@ -41,6 +43,8 @@ typedef struct _psqlSettings bool has_client_encoding; /* was PGCLIENTENCODING set on * startup? */ + Oid lastOid; /* saves oid from insert command + because people want it so badly */ } PsqlSettings; diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c index 1a1688f081e..62011b53d22 100644 --- a/src/bin/psql/startup.c +++ b/src/bin/psql/startup.c @@ -88,7 +88,7 @@ main(int argc, char **argv) char *password = NULL; bool need_pass; - MemSet(&settings, 0, sizeof settings); + memset(&settings, 0, sizeof settings); settings.cur_cmd_source = stdin; settings.cur_cmd_interactive = false; @@ -161,7 +161,7 @@ main(int argc, char **argv) if (options.action == ACT_LIST_DB) { - int success = listAllDbs(&settings); + int success = listAllDbs(&settings, false); PQfinish(settings.db); exit(!success); @@ -179,15 +179,15 @@ main(int argc, char **argv) { puts("Welcome to psql, the PostgreSQL interactive terminal.\n\n" "Type: \\copyright for distribution terms\n" - " \\h for help with SQL commands\n" - " \\? for help on internal slash commands\n" - " \\g or terminate with semicolon to execute query\n" - " \\q to quit\n"); + " \\h for help with SQL commands\n" + " \\? for help on internal slash commands\n" + " \\g or terminate with semicolon to execute query\n" + " \\q to quit\n"); } process_psqlrc(&settings); - initializeInput(options.no_readline ? 0 : 1); + initializeInput(options.no_readline ? 0 : 1, &settings); /* Now find something to do */ @@ -270,7 +270,7 @@ parse_options(int argc, char *argv[], PsqlSettings *pset, struct adhoc_opts * op extern int optind; int c; - MemSet(options, 0, sizeof *options); + memset(options, 0, sizeof *options); #ifdef HAVE_GETOPT_LONG while ((c = getopt_long(argc, argv, "Ac:d:eEf:F:lh:Hno:p:P:qsStT:uU:v:VWx?", long_options, &optindex)) != -1) @@ -516,8 +516,8 @@ showVersion(PsqlSettings *pset) /* get backend version */ if (pset->db && PQstatus(pset->db) == CONNECTION_OK) { - res = PSQLexec(pset, "SELECT version()"); - if (PQresultStatus(res) == PGRES_TUPLES_OK) + res = PSQLexec(pset, "SELECT version()"); + if (PQresultStatus(res) == PGRES_TUPLES_OK) versionstr = PQgetvalue(res, 0, 0); } @@ -566,5 +566,5 @@ showVersion(PsqlSettings *pset) "distribution."); if (res) - PQclear(res); + PQclear(res); } diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c new file mode 100644 index 00000000000..eebfaa835d0 --- /dev/null +++ b/src/bin/psql/tab-complete.c @@ -0,0 +1,812 @@ +/*----------- + This file implements a somewhat more sophisticated readline "TAB completion" + in psql. It is not intended to be AI, to replace learning SQL, or to relieve + you from thinking about what you're doing. Also it does not always give you + all the syntactically legal completions, only those that are the most common + or the ones that the programmer felt most like implementing. + + CAVEAT: Tab completion causes queries to be sent to the backend. The number + tuples returned gets limited, in most default installations to 101, but if + you still don't like this prospect, you can turn off tab completion in your + ~/.inputrc (or else ${INPUTRC}) file so: + $if psql + TAB: self-insert + $endif + See `man 3 readline` or `info readline` for the full details. Also, hence the + + BUGS: + * If you split your queries across lines, this whole things gets confused. + (To fix this, one would have to read psql's query buffer rather than + readline's line buffer, which would require some major revisions of + things.) + * Table or attribute names with spaces in it will equally confuse it. + * Quotes, parenthesis, and other funny characters are not handled all that + gracefully. +-------------*/ + +#include <config.h> +#include <c.h> +#include "tab-complete.h" + +#include "input.h" + +/* If we don't have this, we might as well forget about the whole thing: */ +#ifdef USE_READLINE + +#include <stdio.h> +#include <string.h> +#include <ctype.h> /* toupper */ +#include <stdlib.h> /* malloc, free */ +#ifdef USE_ASSERT_CHECKING +#include <assert.h> +#endif + +#include <libpq-fe.h> + +#include "common.h" + + +#define BUF_SIZE 2048 +#define ERROR_QUERY_TOO_LONG /* empty */ + +/* This pointer saves the place where psql stores its own pointer to the + currently active database connection. This is probably a less than ideal way + of passing this around, but this way I only had to make minimal changes to + psql.c. */ +static PGconn ** database_connection; + + +/* Forward declaration of functions */ +static char ** psql_completion(char *text, int start, int end); +static char * create_command_generator(char *text, int state); +static char * complete_from_query(char *text, int state); +static char * complete_from_const(char *text, int state); +static char * complete_from_list(char *text, int state); + +static PGresult * exec_query(char * query); +char * quote_file_name(char *text, int match_type, char * quote_pointer); +//char * dequote_file_name(char *text, char quote_char); +static char * previous_word(int point, int skip); + +/* These variables are used to pass information into the completion functions. + Realizing that this is the cardinal sin of programming, I don't see a better + way. */ +char * completion_charp; /* if you need to pass a string */ +char ** completion_charpp; /* if you need to pass a list of strings */ +char * completion_info_charp; /* if you need to pass another string */ + +/* Store how many records from a database query we want to return at most +(implemented via SELECT ... LIMIT xx). */ +static int completion_max_records; + + +static void * xmalloc(size_t length) +{ + void *tmp = malloc(length); + if (!tmp) { + perror("malloc"); + exit(EXIT_FAILURE); + } + return tmp; +} + + +/* Initialize the readline library for our purposes. */ +void initialize_readline(PGconn ** conn) +{ + rl_readline_name = "psql"; + rl_attempted_completion_function = psql_completion; + + rl_filename_quoting_function = quote_file_name; +// rl_filename_dequoting_function = dequote_file_name; + rl_filename_quote_characters = "qwertyuioplkjhgfdsazxcvbnm"; + + rl_special_prefixes = "()'"; + rl_basic_word_break_characters = "\t\n\"'`@$><=;|&{ "; + + completion_max_records = rl_completion_query_items + 1; + + database_connection = conn; +} + + +/* This is a list of all "things" in Pgsql, which can show up after CREATE or + DROP; and there is also a query to get a list of them. + The %s will be replaced by the text entered so far, the %d by it's length. + If you change the order here or insert things, make sure to also adjust the + referencing macros below. +*/ +typedef struct { + char * name; + char * query; +} pgsql_thing_t; + +pgsql_thing_t words_after_create[] = { + { "AGGREGATE", "SELECT distinct aggname FROM pg_aggregate WHERE substr(aggname,1,%d)='%s'" }, + { "DATABASE", "SELECT datname FROM pg_database WHERE substr(datname,1,%d)='%s'" }, + { "FUNCTION", "SELECT distinct proname FROM pg_proc WHERE substr(proname,1,%d)='%s'" }, + { "INDEX", "SELECT relname FROM pg_class WHERE relkind='i' and substr(relname,1,%d)='%s'" }, + { "OPERATOR", NULL }, /* Querying for this is probably not such a good idea. */ + { "RULE", "SELECT rulename FROM pg_rules WHERE substr(rulename,1,%d)='%s'" }, + { "SEQUENCE", "SELECT relname FROM pg_class WHERE relkind='S' and substr(relname,1,%d)='%s'" }, + { "TABLE", "SELECT relname FROM pg_class WHERE relkind='r' and substr(relname,1,%d)='%s'" }, + { "TEMP", NULL }, /* for CREATE TEMP TABLE ... */ + { "TRIGGER", "SELECT tgname FROM pg_trigger WHERE substr(tgname,1,%d)='%s'" }, + { "TYPE", "SELECT typname FROM pg_type WHERE substr(typname,1,%d)='%s'" }, + { "UNIQUE", NULL }, /* for CREATE UNIQUE INDEX ... */ + { "USER", "SELECT usename FROM pg_user WHERE substr(usename,1,%d)='%s'" }, + { "VIEW", NULL }, /* Telling a view from a table is not the easiest + thing in the world, and the solutions I've seen + don't really work, so I'll wait on this. */ + { NULL, NULL } /* end of list */ +}; + + +/* The query to get a list of tables and a list of indexes, which are used at + various places. */ +#define Query_for_list_of_tables words_after_create[7].query +#define Query_for_list_of_indexes words_after_create[3].query +#define Query_for_list_of_databases words_after_create[1].query +#define Query_for_list_of_attributes "SELECT a.attname FROM pg_attribute a, pg_class c WHERE c.oid = a.attrelid and a.attnum>0 and substr(a.attname,1,%d)='%s' and c.relname='%s'" + + +/* A couple of macros to ease typing. You can use these to complete the given + string with + 1) The results from a query you pass it. (Perhaps one of those right above?) + 2) The items from a null-pointer-terminated list. + 3) A string constant + 4) The list of attributes to the given table. +*/ +#define COMPLETE_WITH_QUERY(query) \ +do { completion_charp = query; matches = completion_matches(text, complete_from_query); } while(0) +#define COMPLETE_WITH_LIST(list) \ +do { completion_charpp = list; matches = completion_matches(text, complete_from_list); } while(0) +#define COMPLETE_WITH_CONST(string) \ +do { completion_charp = string; matches = completion_matches(text, complete_from_const); } while(0) +#define COMPLETE_WITH_ATTR(table) \ +do {completion_charp = Query_for_list_of_attributes; completion_info_charp = table; matches = completion_matches(text, complete_from_query); } while(0) + + +/* The completion function. Acc. to readline spec this gets passed the text + entered to far and its start and end in the readline buffer. The return value + is some partially obscure list format that can be generated by the readline + libraries completion_matches() function, so we don't have to worry about it. +*/ +char ** psql_completion(char *text, int start, int end) +{ + /* This is the variable we'll return. */ + char **matches = NULL; + /* These are going to contain some scannage of the input line. */ + char *prev_wd, *prev2_wd, *prev3_wd, *prev4_wd; + + static char * sql_commands[] = { + "ABORT", "ALTER", "BEGIN", "CLOSE", "CLUSTER", "COMMIT", "COPY", + "CREATE", "DECLARE", "DELETE", "DROP", "EXPLAIN", "FETCH", "GRANT", + "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "RESET", + "REVOKE", "ROLLBACK", "SELECT", "SET", "SHOW", "UNLISTEN", "UPDATE", + "VACUUM", NULL + }; + + static char * pgsql_variables[] = { + "Client_Encoding", "Names", "DateStyle", "Server_Encoding", "TimeZone", + "TRANSACTION", "Cost_Heap", "Cost_Index", "GEQO", "KSQO", "Query_Limit", + NULL + }; + + static char * backslash_commands[] = { + "\\connect", "\\copy", "\\d", "\\di", "\\di", "\\ds", "\\dS", "\\dv", + "\\da", "\\df", "\\do", "\\dt", "\\e", "\\echo", "\\g", "\\h", "\\i", "\\l", + "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink", + "\\o", "\\p", "\\pset", "\\q", "\\qecho", "\\r", "\\set", "\\t", "\\x", + "\\w", "\\z", "\\!", NULL + }; + + (void)end; /* not used */ + + rl_filename_quoting_desired = 1; + + /* Clear a few things. */ + completion_charp = NULL; + completion_charpp = NULL; + completion_info_charp = NULL; + + /* Scan the input line before our current position for the last four words. + According to those we'll make some smart decisions on what the user is + probably intending to type. + TODO: Use strtokx() to do this. + */ + prev_wd = previous_word(start,0); + prev2_wd = previous_word(start,1); + prev3_wd = previous_word(start,2); + prev4_wd = previous_word(start,3); + + /* If a backslash command was started, continue */ + if (text[0]=='\\') + COMPLETE_WITH_LIST(backslash_commands); + + /* If no previous word, suggest one of the basic sql commands */ + else if (!prev_wd) + COMPLETE_WITH_LIST(sql_commands); + +/* CREATE or DROP */ + /* complete with something you can create or drop */ + else if( strcasecmp(prev_wd, "CREATE") == 0 || strcasecmp(prev_wd, "DROP") == 0 ) + matches = completion_matches(text, create_command_generator); + +/* ALTER */ + /* complete with what you can alter (TABLE or USER) */ + else if( strcasecmp(prev_wd, "ALTER") == 0 ) { + char * list_ALTER[] = { "TABLE", "USER", NULL }; + COMPLETE_WITH_LIST(list_ALTER); + } + /* If we detect ALTER TABLE <name>, suggest either "ADD" or "RENAME" */ + else if( strcasecmp(prev3_wd, "ALTER")==0 && strcasecmp(prev2_wd, "TABLE")==0 ) { + char * list_ALTER2[] = { "ADD", "RENAME", NULL }; + COMPLETE_WITH_LIST(list_ALTER2); + } + /* If we have TABLE <sth> ADD|RENAME, provide list of columns */ + else if( strcasecmp(prev3_wd, "TABLE")==0 && + (strcasecmp(prev_wd,"ADD")==0 || strcasecmp(prev_wd,"RENAME")==0) ) + COMPLETE_WITH_ATTR(prev2_wd); + +/* CLUSTER */ + /* If the previous word is CLUSTER, produce list of indexes. */ + else if( strcasecmp(prev_wd, "CLUSTER") == 0 ) + COMPLETE_WITH_QUERY(Query_for_list_of_indexes); + /* If we have CLUSTER <sth>, then add "ON" */ + else if( strcasecmp(prev2_wd, "CLUSTER") == 0 ) + COMPLETE_WITH_CONST("ON"); + /* If we have CLUSTER <sth> ON, then add the correct tablename as well. */ + else if ( strcasecmp(prev3_wd, "CLUSTER")==0 && strcasecmp(prev_wd, "ON")==0 ) { + char query_buffer[BUF_SIZE]; /* Some room to build queries. */ + if(snprintf(query_buffer, BUF_SIZE, + "SELECT c1.relname FROM pg_class c1, pg_class c2, pg_index i WHERE c1.oid=i.indrelid and i.indexrelid=c2.oid and c2.relname='%s'", + prev2_wd) == -1) + ERROR_QUERY_TOO_LONG; + else + COMPLETE_WITH_QUERY(query_buffer); + } + +/* COPY */ + /* If we have COPY [BINARY] (which you'd have to type yourself), offer list of tables + (Also cover the analogous backslash command) */ + else if( strcasecmp(prev_wd, "COPY")==0 || + strcasecmp(prev_wd, "\\copy")==0 || + (strcasecmp(prev2_wd,"COPY")==0 && strcasecmp(prev_wd,"BINARY")==0) ) + COMPLETE_WITH_QUERY(Query_for_list_of_tables); + /* If we have COPY|BINARY <sth>, complete it with "TO" or "FROM" */ + else if( strcasecmp(prev2_wd, "COPY")==0 || + strcasecmp(prev2_wd, "\\copy")==0 || + strcasecmp(prev2_wd, "BINARY")==0 ) { + char * list_FROMTO[] = { "FROM", "TO", NULL }; + COMPLETE_WITH_LIST(list_FROMTO); + } + +/* CREATE INDEX */ + /* First off we complete CREATE UNIQUE with "INDEX" */ + else if( strcasecmp(prev2_wd, "CREATE")==0 && strcasecmp(prev_wd, "UNIQUE")==0 ) + COMPLETE_WITH_CONST("INDEX"); + /* If we have CREATE|UNIQUE INDEX <sth>, then add "ON" */ + else if( strcasecmp(prev2_wd, "INDEX") == 0 && + (strcasecmp(prev3_wd,"CREATE")==0 || strcasecmp(prev3_wd,"UNIQUE")==0) ) + COMPLETE_WITH_CONST("ON"); + /* Complete ... INDEX <name> ON with a list of tables */ + else if( (strcasecmp(prev3_wd, "INDEX")==0 && strcasecmp(prev_wd, "ON")==0) || (0) ) + COMPLETE_WITH_QUERY(Query_for_list_of_tables); + /* Complete INDEX <name> ON <table> with a list of table columns (which should really be in parens) */ + else if( (strcasecmp(prev4_wd, "INDEX")==0 && strcasecmp(prev2_wd, "ON")==0) ) + COMPLETE_WITH_ATTR(prev_wd); + /* same if you put in USING */ + else if ((strcasecmp(prev4_wd,"ON")==0 && strcasecmp(prev2_wd,"USING")==0) ) + COMPLETE_WITH_ATTR(prev3_wd); + /* Complete USING with an index method */ + else if( strcasecmp(prev_wd, "USING")==0 ) { + char * index_mth[] = { "BTREE", "RTREE", "HASH", NULL }; + COMPLETE_WITH_LIST(index_mth); + } + +/* CREATE RULE */ + /* Complete "CREATE RULE <sth>" with "AS" */ + else if( strcasecmp(prev3_wd,"CREATE")==0 && strcasecmp(prev2_wd,"RULE")==0 ) + COMPLETE_WITH_CONST("AS"); + /* Complete "CREATE RULE <sth> AS with "ON" */ + else if( strcasecmp(prev4_wd,"CREATE")==0 && + strcasecmp(prev3_wd,"RULE")==0 && + strcasecmp(prev_wd,"AS")==0 ) + COMPLETE_WITH_CONST("ON"); + /* Complete "RULE * AS ON" with SELECT|UPDATE|DELETE|INSERT */ + else if( strcasecmp(prev4_wd,"RULE")==0 && + strcasecmp(prev2_wd,"AS")==0 && + strcasecmp(prev_wd,"ON")==0 ) { + char * rule_events[] = { "SELECT", "UPDATE", "INSERT", "DELETE", NULL }; + COMPLETE_WITH_LIST(rule_events); + } + /* Complete "AS ON <sth with a 'T' :)>" with a "TO" */ + else if( strcasecmp(prev3_wd,"AS")==0 && + strcasecmp(prev2_wd,"ON")==0 && + (toupper(prev_wd[4])=='T' || toupper(prev_wd[5])=='T') ) + COMPLETE_WITH_CONST("TO"); + /* Complete "AS ON <sth> TO" with a table name */ + else if( strcasecmp(prev4_wd,"AS")==0 && + strcasecmp(prev3_wd,"ON")==0 && + strcasecmp(prev_wd,"TO")==0 ) + COMPLETE_WITH_QUERY(Query_for_list_of_tables); + +/* CREATE TABLE */ + /* Complete CREATE TEMP with "TABLE" */ + else if( strcasecmp(prev2_wd, "CREATE")==0 && strcasecmp(prev_wd, "TEMP")==0 ) + COMPLETE_WITH_CONST("TABLE"); + +/* CREATE TRIGGER */ + /* is on the agenda . . . */ + +/* CREATE VIEW */ + /* Complete "CREATE VIEW <name>" with "AS" */ + else if( strcasecmp(prev3_wd,"CREATE")==0 && strcasecmp(prev2_wd,"VIEW")==0 ) + COMPLETE_WITH_CONST("AS"); + /* Complete "CREATE VIEW <sth> AS with "SELECT" */ + else if( strcasecmp(prev4_wd,"CREATE")==0 && + strcasecmp(prev3_wd,"VIEW")==0 && + strcasecmp(prev_wd,"AS")==0 ) + COMPLETE_WITH_CONST("SELECT"); + +/* DELETE */ + /* Complete DELETE with FROM (only if the word before that is not "ON" (cf. + rules) or "BEFORE" or "AFTER" (cf. triggers) ) */ + else if( strcasecmp(prev_wd,"DELETE")==0 && + !(strcasecmp(prev2_wd,"ON")==0 || + strcasecmp(prev2_wd,"BEFORE")==0 || + strcasecmp(prev2_wd,"AFTER")==0) ) + COMPLETE_WITH_CONST("FROM"); + /* Complete DELETE FROM with a list of tables */ + else if( strcasecmp(prev2_wd,"DELETE")==0 && strcasecmp(prev_wd,"FROM")==0 ) + COMPLETE_WITH_QUERY(Query_for_list_of_tables); + /* Complete DELETE FROM <table> with "WHERE" (perhaps a safe idea?) */ + else if( strcasecmp(prev3_wd,"DELETE")==0 && strcasecmp(prev2_wd,"FROM")==0 ) + COMPLETE_WITH_CONST("WHERE"); + +/* EXPLAIN */ + /* Complete EXPLAIN [VERBOSE] (which you'd have to type yourself) with the list of SQL commands */ + else if( strcasecmp(prev_wd,"EXPLAIN")==0 || + (strcasecmp(prev2_wd,"EXPLAIN")==0 && strcasecmp(prev_wd,"VERBOSE")==0) ) + COMPLETE_WITH_LIST(sql_commands); + +/* FETCH && MOVE */ + /* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */ + else if( strcasecmp(prev_wd,"FETCH")==0 || strcasecmp(prev_wd,"MOVE")==0 ) { + char * list_FETCH1[] = { "FORWARD", "BACKWARD", "RELATIVE", NULL }; + COMPLETE_WITH_LIST(list_FETCH1); + } + /* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */ + else if( strcasecmp(prev2_wd,"FETCH")==0 || strcasecmp(prev2_wd,"MOVE")==0 ) { + char * list_FETCH2[] = { "ALL", "NEXT", "PRIOR", NULL }; + COMPLETE_WITH_LIST(list_FETCH2); + } + /* Complete FETCH <sth1> <sth2> with "FROM" or "TO". + (Is there a difference? If not, remove one.) */ + else if( strcasecmp(prev3_wd,"FETCH")==0 || strcasecmp(prev3_wd,"MOVE")==0 ) { + char * list_FROMTO[] = { "FROM", "TO", NULL }; + COMPLETE_WITH_LIST(list_FROMTO); + } + +/* GRANT && REVOKE*/ + /* Complete GRANT/REVOKE with a list of privileges */ + else if( strcasecmp(prev_wd,"GRANT")==0 || strcasecmp(prev_wd,"REVOKE")==0 ) { + char * list_privileg[] = { "SELECT", "INSERT", "UPDATE", "DELETE", "RULE", "ALL", NULL }; + COMPLETE_WITH_LIST(list_privileg); + } + /* Complete GRANT/REVOKE <sth> with "ON" */ + else if( strcasecmp(prev2_wd,"GRANT")==0 || strcasecmp(prev2_wd,"REVOKE")==0 ) + COMPLETE_WITH_CONST("ON"); + /* Complete GRANT/REVOKE <sth> ON with a list of tables, views, sequences, and indexes */ + else if( (strcasecmp(prev3_wd,"GRANT")==0 || strcasecmp(prev3_wd,"REVOKE")==0) && + strcasecmp(prev_wd,"ON")==0 ) + COMPLETE_WITH_QUERY("SELECT relname FROM pg_class WHERE relkind in ('r','i','s') and substr(relname,1,%d)='%s'"); + /* Complete "GRANT * ON * " with "TO" */ + else if( strcasecmp(prev4_wd,"GRANT")==0 && strcasecmp(prev2_wd,"ON")==0 ) + COMPLETE_WITH_CONST("TO"); + /* Complete "REVOKE * ON * " with "FROM" */ + else if( strcasecmp(prev4_wd,"REVOKE")==0 && strcasecmp(prev2_wd,"ON")==0 ) + COMPLETE_WITH_CONST("FROM"); + /* TODO: to complete with user name we need prev5_wd -- wait for a more general solution there */ + +/* INSERT */ + /* Complete INSERT with "INTO" */ + else if( strcasecmp(prev_wd,"INSERT")==0 ) + COMPLETE_WITH_CONST("INTO"); + /* Complete INSERT INTO with table names */ + else if( strcasecmp(prev2_wd, "INSERT")==0 && strcasecmp(prev_wd,"INTO")==0 ) + COMPLETE_WITH_QUERY(Query_for_list_of_tables); + /* Complete INSERT INTO <table> with "VALUES" or "SELECT" */ + else if( strcasecmp(prev3_wd, "INSERT")==0 && strcasecmp(prev2_wd,"INTO")==0 ) { + char * list_INSERT[] = { "SELECT", "VALUES", NULL }; + COMPLETE_WITH_LIST(list_INSERT); + } + /* Insert an open parenthesis after "VALUES" */ + else if( strcasecmp(prev_wd,"VALUES")==0 ) + COMPLETE_WITH_CONST("("); + +/* LOCK */ + /* Complete with list of tables */ + else if( strcasecmp(prev_wd, "LOCK") == 0 ) + COMPLETE_WITH_QUERY(Query_for_list_of_tables); + /* (If you want more with LOCK, you better think about it yourself.) */ + +/* SELECT */ + /* naah . . . */ + +/* SET, RESET, SHOW */ + /* Complete with a variable name */ + else if( (strcasecmp(prev_wd,"SET")==0 && strcasecmp(prev3_wd,"UPDATE")!=0) || + strcasecmp(prev_wd,"RESET")==0 || + strcasecmp(prev_wd,"SHOW")==0 ) + COMPLETE_WITH_LIST(pgsql_variables); + /* Complete "SET TRANSACTION ISOLOLATION LEVEL" */ + else if( strcasecmp(prev2_wd,"SET")==0 && strcasecmp(prev_wd,"TRANSACTION")==0 ) + COMPLETE_WITH_CONST("ISOLATION"); + else if( strcasecmp(prev3_wd,"SET")==0 && + strcasecmp(prev2_wd,"TRANSACTION")==0 && + strcasecmp(prev_wd,"ISOLATION")==0 ) + COMPLETE_WITH_CONST("LEVEL"); + else if( strcasecmp(prev4_wd,"SET")==0 && + strcasecmp(prev3_wd,"TRANSACTION")==0 && + strcasecmp(prev2_wd,"ISOLATION")==0 && + strcasecmp(prev_wd,"LEVEL")==0 ) { + char * my_list[] = {"READ","SERIALIZED",NULL}; + COMPLETE_WITH_LIST(my_list); + } + else if( strcasecmp(prev4_wd,"TRANSACTION")==0 && + strcasecmp(prev3_wd,"ISOLATION")==0 && + strcasecmp(prev2_wd,"LEVEL")==0 && + strcasecmp(prev_wd,"READ")==0 ) + COMPLETE_WITH_CONST("COMMITTED"); + /* Complete SET <var> with "TO" */ + else if( strcasecmp(prev2_wd,"SET")==0 && + strcasecmp(prev4_wd,"UPDATE")!=0 ) + COMPLETE_WITH_CONST("TO"); + /* Suggest possible variable values */ + else if( strcasecmp(prev3_wd,"SET")==0 && + (strcasecmp(prev_wd,"TO")==0 || strcmp(prev_wd,"=")==0) ) { + if ( strcasecmp(prev2_wd,"DateStyle")==0 ) { + char * my_list[] = {"'ISO'", "'SQL'", "'Postgres'", "'European'", "'NonEuropean'", "'German'", "DEFAULT", NULL}; + COMPLETE_WITH_LIST(my_list); + } + else if( strcasecmp(prev2_wd,"GEQO")==0 || strcasecmp(prev2_wd,"KSQO")==0 ) { + char * my_list[] = {"ON", "OFF", "DEFAULT", NULL}; + COMPLETE_WITH_LIST(my_list); + } + else { + char * my_list[] = {"DEFAULT", NULL}; + COMPLETE_WITH_LIST(my_list); + } + } + +/* UPDATE */ + /* If prev. word is UPDATE suggest a list of tables */ + else if( strcasecmp(prev_wd, "UPDATE") == 0 ) + COMPLETE_WITH_QUERY(Query_for_list_of_tables); + /* Complete UPDATE <table> with "SET" */ + else if( strcasecmp(prev2_wd, "UPDATE") == 0 ) + COMPLETE_WITH_CONST("SET"); + /* If the previous word is SET (and it wasn't caught above as the _first_ + word) the word before it was (hopefully) a table name and we'll now make + a list of attributes. */ + else if( strcasecmp(prev_wd, "SET") == 0 ) + COMPLETE_WITH_ATTR(prev2_wd); + +/* VACUUM */ + else if( strcasecmp(prev_wd, "VACUUM") == 0 ) + COMPLETE_WITH_QUERY("SELECT relname FROM pg_class WHERE relkind='r' and substr(relname,1,%d)='%s' UNION SELECT 'ANALYZE'::text"); + else if( strcasecmp(prev2_wd, "VACUUM")==0 && strcasecmp(prev_wd, "ANALYZE")==0 ) + COMPLETE_WITH_QUERY(Query_for_list_of_tables); + + +/* Backslash commands */ + else if (strcmp(prev_wd, "\\connect")==0 || strcmp(prev_wd, "\\c")==0) + COMPLETE_WITH_QUERY(Query_for_list_of_databases); + else if (strcmp(prev_wd, "\\d")==0) + COMPLETE_WITH_QUERY(Query_for_list_of_tables); + else if (strcmp(prev_wd, "\\h")==0 || strcmp(prev_wd, "\\help")==0) + COMPLETE_WITH_LIST(sql_commands); + else if (strcmp(prev_wd, "\\pset")==0) { + char * my_list[] = { "format", "border", "expanded", "null", "fieldsep", + "tuples_only", "title", "tableattr", "pager" }; + COMPLETE_WITH_LIST(my_list); + } + else if( strcmp(prev_wd, "\\e")==0 || strcmp(prev_wd, "\\edit")==0 || + strcmp(prev_wd, "\\g")==0 || + strcmp(prev_wd, "\\i")==0 || strcmp(prev_wd, "\\include")==0 || + strcmp(prev_wd, "\\o")==0 || strcmp(prev_wd, "\\out")==0 || + strcmp(prev_wd, "\\s")==0 || + strcmp(prev_wd, "\\w")==0 || strcmp(prev_wd, "\\write")==0 + ) { + matches = completion_matches(text, filename_completion_function); + } + + + /* Finally, we look through the list of "things", such as TABLE, INDEX and + check if that was the previous word. If so, execute the query to get a + list of them. */ + else { + int i; + for(i=0; words_after_create[i].name; i++) + if ( strcasecmp(prev_wd, words_after_create[i].name) == 0 ) { + COMPLETE_WITH_QUERY(words_after_create[i].query); + break; + } + } + + + /* If we still don't have anything to match we have to fabricate some sort + of default list. If we were to just return NULL, readline automatically + attempts filename completion, and that's usually no good. */ + if (matches == NULL) { + char * my_list[] = { "", "", NULL }; + COMPLETE_WITH_LIST(my_list); + } + + + /* free storage */ + free(prev_wd); + free(prev2_wd); + free(prev3_wd); + free(prev4_wd); + + /* Return our Grand List O' Matches */ + return matches; +} + + + +/* GENERATOR FUNCTIONS + + These functions do all the actual work of completing the input. They get + passed the text so far and the count how many times they have been called so + far with the same text. + If you read the above carefully, you'll see that these don't get called + directly but through the readline interface. + The return value is expected to be the full completion of the text, going + through a list each time, or NULL if there are no more matches. The string + will be free()'d be readline, so you must run it through strdup() or + something of that sort. +*/ + +/* This one gives you one from a list of things you can put after CREATE or DROP + as defined above. +*/ +char * create_command_generator(char *text, int state) +{ + static int list_index, string_length; + char *name; + + /* If this is the first time for this completion, init some values */ + if (state == 0) { + list_index = 0; + string_length = strlen(text); + } + + /* find something that matches */ + while ( (name = words_after_create[list_index++].name) ) + if ( strncasecmp(name, text, string_length) == 0 ) + return xstrdup(name); + + /* if nothing matches, return NULL */ + return NULL; +} + + +/* This creates a list of matching things, according to a query pointed to + by completion_charp. The query needs to have a %d and a %s in it, which will + be replaced by the string length of the text and the text itself. See some + example queries at the top. + The query may also have another %s in it, which will be replaced by the value + of completion_info_charp. + Ordinarily this would be used to get a list of matching tables or functions, + etc. +*/ +char * complete_from_query(char *text, int state) +{ + static int list_index, string_length; + static PGresult *result = NULL; + char query_buffer[BUF_SIZE]; + const char * item; + + /* If this ist the first time for this completion, we fetch a list of our + "things" from the backend. */ + if (state == 0) { + list_index = 0; + string_length = strlen(text); + + /* Need to have a query */ + if (completion_charp == NULL) return NULL; + + if (snprintf(query_buffer, BUF_SIZE, completion_charp, string_length, text, completion_info_charp) == -1) { + ERROR_QUERY_TOO_LONG; + return NULL; + } + + result = exec_query(query_buffer); + } + + /* Find something that matches */ + if( result && PQresultStatus(result) == PGRES_TUPLES_OK ) + while( list_index < PQntuples(result) && (item = PQgetvalue(result, list_index++, 0)) ) + if ( strncasecmp(text, item, string_length) == 0) + return xstrdup(item); + + /* If nothing matches, free the db structure and return null */ + PQclear(result); + result = NULL; + return NULL; +} + + +/* This function returns in order one of a fixed, NULL pointer terminated list + of strings (if matching). This can be used if there are only a fixed number + SQL words that can appear at certain spot. +*/ +char * complete_from_list(char *text, int state) { + static int string_length, list_index; + char * item; + + /* need to have a list */ +#ifdef USE_ASSERT_CHECKING + assert(completion_charpp); +#endif + + /* Initialization */ + if (state == 0) { + list_index = 0; + string_length = strlen(text); + } + + while( (item = completion_charpp[list_index++]) ) + if ( strncasecmp(text,item,string_length) == 0 ) + return xstrdup(item); + + /* If no more matches, return null. */ + return NULL; +} + + +/* This function returns one fixed string the first time even if it doesn't + match what's there, and nothing the second time. This should be used if there + is only one possibility that can appear at a certain spot, so misspellings + will be overwritten. + The string to be passed must be in completion_charp. +*/ +char * complete_from_const(char *text, int state) +{ + (void)text; /* We don't care about what was entered already. */ +#ifdef USE_ASSERT_CHECKING + assert(completion_charp); +#endif + if (state==0) + return xstrdup(completion_charp); + else + return NULL; +} + + + +/* HELPER FUNCTIONS */ + + +/* Execute a query and report any errors. This should be the preferred way of + talking to the database in this file. + Note that the query passed in here must not have a semicolon at the end + because we need to append LIMIT xxx. +*/ +PGresult * exec_query(char * query) +{ + PGresult * result; + char query_buffer[BUF_SIZE]; + + if (query == NULL || PQstatus(*database_connection) != CONNECTION_OK) + return NULL; +#ifdef USE_ASSERT_CHECKING + assert( query[strlen(query)-1] != ';' ); +#endif + + if( snprintf(query_buffer, BUF_SIZE, "%s LIMIT %d;", query, completion_max_records) == -1 ) { + ERROR_QUERY_TOO_LONG; + return NULL; + } + + result = PQexec(*database_connection, query); + + if (result != NULL && PQresultStatus(result) != PGRES_TUPLES_OK) { + fprintf(stderr, "\nThe completion query \"%s\" failed thus: %s\n", + query, PQresStatus(PQresultStatus(result))); + PQclear(result); + result = NULL; + } + + return result; +} + + + +/* Return the word (space delimited) before point. Set skip > 0 to skip that + many words; e.g. skip=1 finds the word before the previous one. + TODO: Take account of quotes. (Right now, if you table names contain spaces + you're screwed.) +*/ +char * previous_word(int point, int skip) { + int i, start=0, end=-1; + char * s; + + while (skip-- >=0) { + /* first we look for a space before the current word */ + for(i=point; i>=0; i--) + if (rl_line_buffer[i] == ' ') + break; + + /* now find the first non-space which then constitutes the end */ + for(; i>=0; i--) + if (rl_line_buffer[i] != ' ') { + end = i; + break; + } + + /* If no end found we return null, because there is no word before the point */ + if (end == -1) + return NULL; + + /* Otherwise we now look for the start. The start is either the last + character before any space going backwards from the end, or it's + simply character 0 */ + for (start=end; start>0; start--) + if (rl_line_buffer[start-1] == ' ') + break; + + point=start; + } + + /* make a copy */ + s = (char *)xmalloc(end-start+2); + strncpy(s, &rl_line_buffer[start], end-start+1); + s[end-start+1] = '\0'; + + return s; +} + + +/* Surround a string with single quotes. This works for both SQL and + psql internal. Doesn't work so well yet. +*/ +char * quote_file_name(char *text, int match_type, char * quote_pointer) +{ + char *s; + size_t length; + + (void)quote_pointer; /* not used */ + + length = strlen(text) + ( match_type==SINGLE_MATCH ? 3 : 2 ); + s = xmalloc(length); + s[0] = '\''; + strcpy(s+1, text); + if (match_type==SINGLE_MATCH) + s[length-2] = '\''; + s[length-1] = '\0'; + return s; +} + + +char * dequote_file_name(char *text, char quote_char) +{ + char *s; + size_t length; + + if (!quote_char) + return xstrdup(text); + + length = strlen(text); + s = xmalloc(length-2+1); + strncpy(s, text+1, length-2); + s[length] = '\0'; + + return s; +} + +#endif /* USE_READLINE */ diff --git a/src/bin/psql/tab-complete.h b/src/bin/psql/tab-complete.h new file mode 100644 index 00000000000..04d5a8bcee1 --- /dev/null +++ b/src/bin/psql/tab-complete.h @@ -0,0 +1,8 @@ +#ifndef TAB_COMPLETE_H +#define TAB_COMPLETE_H + +#include <libpq-fe.h> + +void initialize_readline(PGconn ** conn); + +#endif diff --git a/src/bin/psql/variables.h b/src/bin/psql/variables.h index c8c3a0adaea..06493b49d86 100644 --- a/src/bin/psql/variables.h +++ b/src/bin/psql/variables.h @@ -11,7 +11,8 @@ #define VARIABLES_H #include <c.h> -#define VALID_VARIABLE_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_" +#define VALID_VARIABLE_CHARS "abcdefghijklmnopqrstuvwxyz"\ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789_" struct _variable { diff --git a/src/bin/psql/win32.mak-old b/src/bin/psql/win32.mak-old index 0ebc3e512c3..e69de29bb2d 100644 --- a/src/bin/psql/win32.mak-old +++ b/src/bin/psql/win32.mak-old @@ -1,72 +0,0 @@ -# Makefile for Microsoft Visual C++ 5.0 (or compat) - -!IF "$(OS)" == "Windows_NT" -NULL= -!ELSE -NULL=nul -!ENDIF - -CPP=cl.exe - -OUTDIR=.\Release -INTDIR=.\Release -# Begin Custom Macros -OutDir=.\Release -# End Custom Macros - -ALL : "$(OUTDIR)\psql.exe" - -CLEAN : - -@erase "$(INTDIR)\psql.obj" - -@erase "$(INTDIR)\stringutils.obj" - -@erase "$(INTDIR)\getopt.obj" - -@erase "$(INTDIR)\vc50.idb" - -@erase "$(OUTDIR)\psql.exe" - -"$(OUTDIR)" : - if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" - -CPP_PROJ=/nologo /ML /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D\ - "_MBCS" /Fp"$(INTDIR)\psql.pch" /YX /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" /FD /c \ - /I ..\..\include /I ..\..\interfaces\libpq /D "HAVE_STRDUP" /D "BLCKSZ=8192" - -!IFDEF MULTIBYTE -!IFNDEF MBFLAGS -MBFLAGS="-DMULTIBYTE=$(MULTIBYTE)" -!ENDIF -CPP_PROJ=$(MBFLAGS) $(CPP_PROJ) -!ENDIF - -CPP_OBJS=.\Release/ -CPP_SBRS=. - -LINK32=link.exe -LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib\ - advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib\ - odbccp32.lib wsock32.lib /nologo /subsystem:console /incremental:no\ - /pdb:"$(OUTDIR)\psql.pdb" /machine:I386 /out:"$(OUTDIR)\psql.exe" -LINK32_OBJS= \ - "$(INTDIR)\psql.obj" \ - "$(INTDIR)\stringutils.obj" \ - "$(INTDIR)\getopt.obj" \ - "..\..\interfaces\libpq\Release\libpqdll.lib" - -"$(OUTDIR)\psql.exe" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) - $(LINK32) @<< - $(LINK32_FLAGS) $(LINK32_OBJS) -<< - -"$(OUTDIR)\getopt.obj" : "$(OUTDIR)" ..\..\utils\getopt.c - $(CPP) @<< - $(CPP_PROJ) ..\..\utils\getopt.c -<< - -.c{$(CPP_OBJS)}.obj:: - $(CPP) @<< - $(CPP_PROJ) $< -<< - -.cpp{$(CPP_OBJS)}.obj:: - $(CPP) @<< - $(CPP_PROJ) $< -<< |