diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 9e6923f..fd61034 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -56,9 +56,12 @@
 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_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);
+static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *result);
+static const char *create_or_replace_function_text(PGconn *conn, Oid oid);
 
 #ifdef USE_SSL
 static void printSSLInfo(void);
@@ -444,11 +447,76 @@ 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;
+			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,7 +1478,7 @@ editFile(const char *fname)
 
 /* call this one */
 static bool
-do_edit(const char *filename_arg, PQExpBuffer query_buf)
+do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
 {
 	char		fnametmp[MAXPGPATH];
 	FILE	   *stream = NULL;
@@ -1532,6 +1600,10 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf)
 				psql_error("%s: %s\n", fname, strerror(errno));
 				error = true;
 			}
+			else if (edited)
+			{
+				*edited = true;
+			}
 
 			fclose(stream);
 		}
@@ -1912,3 +1984,66 @@ do_shell(const char *command)
 	}
 	return true;
 }
+
+/*
+ * 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.
+ */
+
+static 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 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).
+ */
+
+static const char *
+create_or_replace_function_text(PGconn *conn, Oid oid)
+{
+	PGresult *res;
+	PQExpBuffer buf;
+	const char *s = 0;
+
+	buf = createPQExpBuffer();
+	printfPQExpBuffer(buf, "SELECT pg_get_functiondef(%d)", oid);
+
+	res = PQexec(conn, buf->data);
+	if (!res || PQresultStatus(res) != PGRES_TUPLES_OK || PQntuples(res) != 1)
+		return NULL;
+	s = pg_strdup(PQgetvalue(res, 0, 0));
+
+	destroyPQExpBuffer(buf);
+	PQclear(res);
+
+	return s;
+}
