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 (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers