aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bin/psql/Makefile.in14
-rw-r--r--src/bin/psql/command.c121
-rw-r--r--src/bin/psql/command.h4
-rw-r--r--src/bin/psql/common.c102
-rw-r--r--src/bin/psql/describe.c761
-rw-r--r--src/bin/psql/describe.h32
-rw-r--r--src/bin/psql/help.c2
-rw-r--r--src/bin/psql/input.c6
-rw-r--r--src/bin/psql/input.h15
-rw-r--r--src/bin/psql/large_obj.c16
-rw-r--r--src/bin/psql/large_obj.h3
-rw-r--r--src/bin/psql/mainloop.c158
-rw-r--r--src/bin/psql/print.c68
-rw-r--r--src/bin/psql/print.h4
-rw-r--r--src/bin/psql/prompt.c2
-rw-r--r--src/bin/psql/settings.h6
-rw-r--r--src/bin/psql/startup.c22
-rw-r--r--src/bin/psql/tab-complete.c812
-rw-r--r--src/bin/psql/tab-complete.h8
-rw-r--r--src/bin/psql/variables.h3
-rw-r--r--src/bin/psql/win32.mak-old72
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", "\"'`", '\\', &quote, &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", "\"'`", '\\', &quote, &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) $<
-<<