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

Reply via email to