Refactoring pg_dump was more work than I had time to do right now, and I
wanted \ef to work, so I hacked up the attached (by copying dumpFunc and
its dependencies to src/bin/psql/dumpfunc.[ch]).
-- ams
*** a/src/bin/psql/Makefile
--- b/src/bin/psql/Makefile
***************
*** 21,27 **** override CPPFLAGS := -I$(srcdir) -I$(libpq_srcdir) -I$(top_srcdir)/src/bin/pg_du
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 \
! psqlscan.o tab-complete.o mbprint.o dumputils.o $(WIN32RES)
EXTRA_OBJS = $(top_builddir)/src/backend/parser/keywords.o
--- 21,27 ----
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 \
! psqlscan.o tab-complete.o mbprint.o dumputils.o dumpfunc.o $(WIN32RES)
EXTRA_OBJS = $(top_builddir)/src/backend/parser/keywords.o
*** a/src/bin/psql/command.c
--- b/src/bin/psql/command.c
***************
*** 38,43 ****
--- 38,44 ----
#include "libpq-fe.h"
#include "pqexpbuffer.h"
#include "dumputils.h"
+ #include "dumpfunc.h"
#include "common.h"
#include "copy.h"
***************
*** 56,62 ****
static backslashResult exec_command(const char *cmd,
PsqlScanState scan_state,
PQExpBuffer query_buf);
! static bool do_edit(const char *filename_arg, PQExpBuffer query_buf);
static bool do_connect(char *dbname, char *user, char *host, char *port);
static bool do_shell(const char *command);
--- 57,64 ----
static backslashResult exec_command(const char *cmd,
PsqlScanState scan_state,
PQExpBuffer query_buf);
! static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! bool *edited);
static bool do_connect(char *dbname, char *user, char *host, char *port);
static bool do_shell(const char *command);
***************
*** 444,454 **** exec_command(const char *cmd,
expand_tilde(&fname);
if (fname)
canonicalize_path(fname);
! status = do_edit(fname, query_buf) ? PSQL_CMD_NEWEDIT : PSQL_CMD_ERROR;
free(fname);
}
}
/* \echo and \qecho */
else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
{
--- 446,521 ----
expand_tilde(&fname);
if (fname)
canonicalize_path(fname);
! if (do_edit(fname, query_buf, NULL))
! status = PSQL_CMD_NEWEDIT;
! else
! status = PSQL_CMD_ERROR;
free(fname);
}
}
+ /*
+ * \ef -- edit the named function in $EDITOR.
+ */
+
+ else if (strcmp(cmd, "ef") == 0)
+ {
+ Oid foid;
+ char *func;
+
+ func = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
+ if (!func)
+ {
+ psql_error("no function name specified\n");
+ status = PSQL_CMD_ERROR;
+ }
+ else if (!lookup_function_oid(pset.db, func, &foid))
+ {
+ psql_error(PQerrorMessage(pset.db));
+ status = PSQL_CMD_ERROR;
+ }
+ else {
+ termPQExpBuffer(query_buf);
+ if (foid)
+ {
+ char *s = create_or_replace_function_text(pset.db, foid);
+ if (s)
+ {
+ appendPQExpBufferStr(query_buf, s);
+ free(s);
+ }
+ else
+ status = PSQL_CMD_ERROR;
+ }
+ else
+ {
+ printfPQExpBuffer(query_buf,
+ "CREATE FUNCTION %s%s RETURNS ... AS $$\n"
+ "...\n"
+ "$$ LANGUAGE '...'\n",
+ func, strchr(func,'(') ? "" : "(...)" );
+ }
+ }
+
+ if (status != PSQL_CMD_ERROR)
+ {
+ bool edited = false;
+ if (!do_edit(0, query_buf, &edited))
+ {
+ status = PSQL_CMD_ERROR;
+ }
+ else if (!edited)
+ {
+ printf("No changes\n");
+ }
+ else
+ {
+ status = PSQL_CMD_SEND;
+ }
+ free(func);
+ }
+ }
+
/* \echo and \qecho */
else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
{
***************
*** 1410,1416 **** editFile(const char *fname)
/* call this one */
static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf)
{
char fnametmp[MAXPGPATH];
FILE *stream = NULL;
--- 1477,1483 ----
/* call this one */
static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
{
char fnametmp[MAXPGPATH];
FILE *stream = NULL;
***************
*** 1532,1537 **** do_edit(const char *filename_arg, PQExpBuffer query_buf)
--- 1599,1608 ----
psql_error("%s: %s\n", fname, strerror(errno));
error = true;
}
+ else if (edited)
+ {
+ *edited = true;
+ }
fclose(stream);
}
*** /dev/null
--- b/src/bin/psql/dumpfunc.c
***************
*** 0 ****
--- 1,496 ----
+ #include "dumpfunc.h"
+
+ #include "libpq-fe.h"
+ #include "pqexpbuffer.h"
+ #include "dumputils.h"
+ #include "common.h"
+ #include "catalog/pg_proc.h"
+
+ #define atooid(x) ((Oid) strtoul((x), NULL, 10))
+
+ /*
+ * This function takes a function description, e.g. "x" or "x(int)", and
+ * issues a query on the given connection to retrieve the function's oid
+ * using a cast to regproc or regprocedure (as appropriate). The result,
+ * if there is one, is stored in the integer pointed to by result, which
+ * is assumed to be non-zero. If there are no results (i.e. the function
+ * does not exist), 0 is stored. The function then returns true.
+ *
+ * If the oid lookup query fails (which it will, for example, when
+ * multiple functions match the given description), it returns false.
+ */
+
+ bool
+ lookup_function_oid(PGconn *conn, const char *desc, Oid *result)
+ {
+ PGresult *res;
+ PQExpBuffer buf;
+
+ buf = createPQExpBuffer();
+ printfPQExpBuffer(buf, "SELECT '%s'::%s::oid",
+ desc, strchr(desc, '(') ? "regprocedure" : "regproc");
+
+ res = PQexec(conn, buf->data);
+ if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
+ return false;
+
+ *result = 0;
+ if (PQntuples(res) > 0)
+ *result = atooid(PQgetvalue(res, 0, 0));
+
+ destroyPQExpBuffer(buf);
+ PQclear(res);
+
+ return true;
+ }
+
+ /*
+ * Returns a nicely-formatted type name for the given type oid.
+ * (This is copied from the function in src/bin/pg_dump/pg_dump.c)
+ */
+
+ static char *
+ getFormattedTypeName(PGconn *conn, Oid oid)
+ {
+ char *result;
+ PQExpBuffer query;
+ PGresult *res;
+ int ntups;
+
+ if (oid == 0)
+ return strdup("opaque");
+
+ query = createPQExpBuffer();
+ appendPQExpBuffer(query,
+ "SELECT pg_catalog.format_type('%u'::pg_catalog.oid, NULL)",
+ oid);
+ res = PQexec(conn, query->data);
+
+ if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ const char *err;
+ if (res)
+ err = PQresultErrorMessage(res);
+ else
+ err = PQerrorMessage(conn);
+ psql_error("query: %s, error: %s\n", query->data, err);
+ return NULL;
+ }
+
+ ntups = PQntuples(res);
+ if (ntups != 1)
+ {
+ psql_error("query returned %d rows instead of one: %s\n",
+ ntups, query->data);
+ return NULL;
+ }
+
+ result = strdup(PQgetvalue(res, 0, 0));
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+
+ return result;
+ }
+
+ /*
+ * Returns the function name and argument list.
+ * (This is also copied from the function in pg_dump.c)
+ */
+
+ static char *
+ format_function_arguments(PGconn *conn, const char *name, int nallargs,
+ Oid *argtypes, char **allargtypes,
+ char **argmodes, char **argnames)
+ {
+ PQExpBufferData fn;
+ int j;
+
+ initPQExpBuffer(&fn);
+ appendPQExpBuffer(&fn, "%s(", fmtId(name));
+ for (j = 0; j < nallargs; j++)
+ {
+ Oid typid;
+ char *typname;
+ const char *argmode;
+ const char *argname;
+
+ typid = allargtypes ? atooid(allargtypes[j]) : argtypes[j];
+ typname = getFormattedTypeName(conn, typid);
+ if (!typname)
+ return NULL;
+
+ if (argmodes)
+ {
+ switch (argmodes[j][0])
+ {
+ case 'i':
+ argmode = "";
+ break;
+ case 'o':
+ argmode = "OUT ";
+ break;
+ case 'b':
+ argmode = "INOUT ";
+ break;
+ default:
+ argmode = "";
+ break;
+ }
+ }
+ else
+ argmode = "";
+
+ argname = argnames ? argnames[j] : (char *) NULL;
+ if (argname && argname[0] == '\0')
+ argname = NULL;
+
+ appendPQExpBuffer(&fn, "%s%s%s%s%s",
+ (j > 0) ? ", " : "",
+ argmode,
+ argname ? fmtId(argname) : "",
+ argname ? " " : "",
+ typname);
+ free(typname);
+ }
+ appendPQExpBuffer(&fn, ")");
+ return fn.data;
+ }
+
+ /*
+ * Parses arraysize oids from str and puts them into array.
+ * (Copied from pg_dump/common.c)
+ */
+
+ static bool
+ parseOidArray(const char *str, Oid *array, int arraysize)
+ {
+ int j,
+ argNum;
+ char temp[100];
+ char s;
+
+ argNum = 0;
+ j = 0;
+ for (;;)
+ {
+ s = *str++;
+ if (s == ' ' || s == '\0')
+ {
+ if (j > 0)
+ {
+ if (argNum >= arraysize)
+ return false;
+ temp[j] = '\0';
+ array[argNum++] = atooid(temp);
+ j = 0;
+ }
+ if (s == '\0')
+ break;
+ }
+ else
+ {
+ if (!(isdigit((unsigned char) s) || s == '-') ||
+ j >= sizeof(temp) - 1)
+ {
+ return false;
+ }
+ temp[j++] = s;
+ }
+ }
+
+ while (argNum < arraysize)
+ array[argNum++] = InvalidOid;
+
+ return true;
+ }
+
+
+ /*
+ * Returns the "CREATE OR REPLACE FUNCTION ..." statement that was used
+ * to create the function with the given oid, which is assumed to be the
+ * result of lookup_function_oid() (i.e. a valid oid from pg_proc).
+ */
+
+ const char *
+ create_or_replace_function_text(PGconn *conn, Oid oid)
+ {
+ int nargs;
+ Oid *argtypes;
+ Oid prorettype;
+ PQExpBuffer query;
+ PQExpBuffer q;
+ PQExpBuffer asPart;
+ PGresult *res;
+ char *funcsig;
+ int ntups;
+ char *proretset;
+ char *prosrc;
+ char *probin;
+ char *proallargtypes;
+ char *proargmodes;
+ char *proargnames;
+ char *provolatile;
+ char *proisstrict;
+ char *prosecdef;
+ char *proconfig;
+ char *procost;
+ char *prorows;
+ char *lanname;
+ char *rettypename;
+ int nallargs;
+ char **allargtypes = NULL;
+ char **argmodes = NULL;
+ char **argnames = NULL;
+ char **configitems = NULL;
+ int nconfigitems = 0;
+ int i;
+ const char *proname;
+ const char *result = NULL;
+
+ q = createPQExpBuffer();
+ asPart = createPQExpBuffer();
+ query = createPQExpBuffer();
+
+ appendPQExpBuffer(query,
+ "SELECT proname, pronargs, proretset, prosrc, probin, "
+ "proargtypes, proallargtypes, prorettype, proargmodes, "
+ "proargnames, provolatile, proisstrict, prosecdef, "
+ "proconfig, procost, prorows, "
+ "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) as lanname "
+ "FROM pg_catalog.pg_proc "
+ "WHERE oid = '%u'::pg_catalog.oid",
+ oid);
+ res = PQexec(conn, query->data);
+ if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ const char *err;
+ if (res)
+ err = PQresultErrorMessage(res);
+ else
+ err = PQerrorMessage(conn);
+ psql_error("query: %s, error: %s\n", query->data, err);
+ goto out;
+ }
+
+ ntups = PQntuples(res);
+ if (ntups != 1)
+ {
+ psql_error("query returned %d rows instead of one: %s\n",
+ ntups, query->data);
+ goto out;
+ }
+
+ proname = PQgetvalue(res, 0, PQfnumber(res, "proname"));
+ prorettype = atooid(PQgetvalue(res, 0, PQfnumber(res, "prorettype")));
+
+ nargs = atoi(PQgetvalue(res, 0, PQfnumber(res, "pronargs")));
+ if (nargs == 0)
+ argtypes = NULL;
+ else
+ {
+ bool ok = false;
+
+ argtypes = (Oid *) malloc(nargs * sizeof(Oid));
+ ok = parseOidArray(PQgetvalue(res, 0, PQfnumber(res, "proargtypes")),
+ argtypes, nargs);
+ if (!ok)
+ {
+ psql_error("Could not parse proargtypes\n");
+ goto out;
+ }
+ }
+
+ proretset = PQgetvalue(res, 0, PQfnumber(res, "proretset"));
+ prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
+ probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ proallargtypes = PQgetvalue(res, 0, PQfnumber(res, "proallargtypes"));
+ proargmodes = PQgetvalue(res, 0, PQfnumber(res, "proargmodes"));
+ proargnames = PQgetvalue(res, 0, PQfnumber(res, "proargnames"));
+ provolatile = PQgetvalue(res, 0, PQfnumber(res, "provolatile"));
+ proisstrict = PQgetvalue(res, 0, PQfnumber(res, "proisstrict"));
+ prosecdef = PQgetvalue(res, 0, PQfnumber(res, "prosecdef"));
+ proconfig = PQgetvalue(res, 0, PQfnumber(res, "proconfig"));
+ procost = PQgetvalue(res, 0, PQfnumber(res, "procost"));
+ prorows = PQgetvalue(res, 0, PQfnumber(res, "prorows"));
+ lanname = PQgetvalue(res, 0, PQfnumber(res, "lanname"));
+
+ /*
+ * See backend/commands/define.c for details of how the 'AS' clause is
+ * used.
+ */
+ if (strcmp(probin, "-") != 0)
+ {
+ appendPQExpBuffer(asPart, "AS ");
+ appendStringLiteralConn(asPart, probin, conn);
+ if (strcmp(prosrc, "-") != 0)
+ {
+ appendPQExpBuffer(asPart, ", ");
+ appendStringLiteralDQ(asPart, prosrc, NULL);
+ }
+ }
+ else
+ {
+ if (strcmp(prosrc, "-") != 0)
+ {
+ appendPQExpBuffer(asPart, "AS ");
+ /* with no bin, dollar quote src unconditionally if allowed */
+ appendStringLiteralDQ(asPart, prosrc, NULL);
+ }
+ }
+
+ nallargs = nargs; /* unless we learn different from allargs */
+
+ if (proallargtypes && *proallargtypes)
+ {
+ int nitems = 0;
+
+ if (!parsePGArray(proallargtypes, &allargtypes, &nitems) ||
+ nitems < nargs)
+ {
+ if (allargtypes)
+ free(allargtypes);
+ allargtypes = NULL;
+ }
+ else
+ nallargs = nitems;
+ }
+
+ if (proargmodes && *proargmodes)
+ {
+ int nitems = 0;
+
+ if (!parsePGArray(proargmodes, &argmodes, &nitems) ||
+ nitems != nallargs)
+ {
+ if (argmodes)
+ free(argmodes);
+ argmodes = NULL;
+ }
+ }
+
+ if (proargnames && *proargnames)
+ {
+ int nitems = 0;
+
+ if (!parsePGArray(proargnames, &argnames, &nitems) ||
+ nitems != nallargs)
+ {
+ if (argnames)
+ free(argnames);
+ argnames = NULL;
+ }
+ }
+
+ if (proconfig && *proconfig)
+ {
+ if (!parsePGArray(proconfig, &configitems, &nconfigitems))
+ {
+ if (configitems)
+ free(configitems);
+ configitems = NULL;
+ nconfigitems = 0;
+ }
+ }
+
+ funcsig = format_function_arguments(conn, proname, nallargs, argtypes,
+ allargtypes, argmodes, argnames);
+ rettypename = getFormattedTypeName(conn, prorettype);
+ if (!funcsig || !rettypename)
+ goto out;
+
+ appendPQExpBuffer(q, "CREATE OR REPLACE FUNCTION %s ", funcsig);
+ appendPQExpBuffer(q, "RETURNS %s%s",
+ (proretset[0] == 't') ? "SETOF " : "",
+ rettypename);
+ free(rettypename);
+
+ appendPQExpBuffer(q, "\n LANGUAGE %s", fmtId(lanname));
+ if (provolatile[0] != PROVOLATILE_VOLATILE)
+ {
+ if (provolatile[0] == PROVOLATILE_IMMUTABLE)
+ appendPQExpBuffer(q, " IMMUTABLE");
+ else if (provolatile[0] == PROVOLATILE_STABLE)
+ appendPQExpBuffer(q, " STABLE");
+ else if (provolatile[0] != PROVOLATILE_VOLATILE)
+ {
+ psql_error("unrecognized provolatile value for function \"%s\"\n",
+ proname);
+ goto out;
+ }
+ }
+
+ if (proisstrict[0] == 't')
+ appendPQExpBuffer(q, " STRICT");
+
+ if (prosecdef[0] == 't')
+ appendPQExpBuffer(q, " SECURITY DEFINER");
+
+ /*
+ * COST and ROWS are emitted only if present and not default, so as not to
+ * break backwards-compatibility of the dump without need. Keep this code
+ * in sync with the defaults in functioncmds.c.
+ */
+ if (strcmp(procost, "0") != 0)
+ {
+ if (strcmp(lanname, "internal") == 0 || strcmp(lanname, "c") == 0)
+ {
+ /* default cost is 1 */
+ if (strcmp(procost, "1") != 0)
+ appendPQExpBuffer(q, " COST %s", procost);
+ }
+ else
+ {
+ /* default cost is 100 */
+ if (strcmp(procost, "100") != 0)
+ appendPQExpBuffer(q, " COST %s", procost);
+ }
+ }
+ if (proretset[0] == 't' &&
+ strcmp(prorows, "0") != 0 && strcmp(prorows, "1000") != 0)
+ appendPQExpBuffer(q, " ROWS %s", prorows);
+
+ for (i = 0; i < nconfigitems; i++)
+ {
+ /* we feel free to scribble on configitems[] here */
+ char *configitem = configitems[i];
+ char *pos;
+
+ pos = strchr(configitem, '=');
+ if (pos == NULL)
+ continue;
+ *pos++ = '\0';
+ appendPQExpBuffer(q, "\n SET %s TO ", fmtId(configitem));
+
+ /*
+ * Some GUC variable names are 'LIST' type and hence must not be
+ * quoted.
+ */
+ if (pg_strcasecmp(configitem, "DateStyle") == 0
+ || pg_strcasecmp(configitem, "search_path") == 0)
+ appendPQExpBuffer(q, "%s", pos);
+ else
+ appendStringLiteralConn(q, pos, conn);
+ }
+
+ appendPQExpBuffer(q, "\n%s;\n", asPart->data);
+
+ result = q->data;
+
+ out:
+ PQclear(res);
+ free(q);
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(asPart);
+ free(funcsig);
+ if (allargtypes)
+ free(allargtypes);
+ if (argmodes)
+ free(argmodes);
+ if (argnames)
+ free(argnames);
+ if (configitems)
+ free(configitems);
+
+ return result;
+ }
*** /dev/null
--- b/src/bin/psql/dumpfunc.h
***************
*** 0 ****
--- 1,10 ----
+ #ifndef DUMPFUNC_H
+ #define DUMPFUNC_H
+
+ #include "postgres_fe.h"
+ #include "libpq-fe.h"
+
+ bool lookup_function_oid(PGconn *conn, const char *desc, Oid *result);
+ const char *create_or_replace_function_text(PGconn *conn, Oid oid);
+
+ #endif
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers