psql :: support for \ev viewname and \sv viewname

Started by Petr Korobeinikovover 10 years ago24 messages
#1Petr Korobeinikov
pkorobeinikov@gmail.com
1 attachment(s)

Hackers!

I'm proposing to add two new subcommands in psql:
1. \ev viewname - edit view definition with external editor (like \ef for
function)
2. \sv viewname - show view definition (like \sf for function, for
consistency)

What's inside:
1. review-ready implementation of \ev and \sv psql subcommands for editing
and viewing view's definition.
2. psql's doc update with new subcommands description.
3. a bit of extracting common source code parts into separate functions.
4. psql internal help update.
5. tab completion update.

There is one narrow place in this patch: current implementation doesn't
support "create" statement formatting for recursive views.

Any comments? Suggestions?

Attachments:

psql-ev-sv-support.difftext/plain; charset=US-ASCII; name=psql-ev-sv-support.diffDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 62a3b21..d668777 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1651,6 +1651,28 @@ Tue Oct 26 21:40:57 CEST 1999
 
 
       <varlistentry>
+        <term><literal>\ev <optional> <replaceable class="parameter">viewname</> </optional> </literal></term>
+
+        <listitem>
+        <para>
+         This command fetches and edits the definition of the named view,
+         in the form of a <command>CREATE OR REPLACE VIEW</> command.
+         Editing is done in the same way as for <literal>\edit</>.
+         After the editor exits, the updated command waits in the query buffer;
+         type semicolon or <literal>\g</> to send it, or <literal>\r</>
+         to cancel.
+        </para>
+
+        <para>
+         If no view is specified, a blank <command>CREATE VEIW</>
+         template is presented for editing.
+        </para>
+
+        </listitem>
+      </varlistentry>
+
+
+      <varlistentry>
         <term><literal>\encoding [ <replaceable class="parameter">encoding</replaceable> ]</literal></term>
 
         <listitem>
@@ -2522,6 +2544,25 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
 
 
       <varlistentry>
+        <term><literal>\sv[+] <replaceable class="parameter">viewname</> </literal></term>
+
+        <listitem>
+        <para>
+         This command fetches and shows the definition of the named view,
+         in the form of a <command>CREATE OR REPLACE VIEW</> command.
+         The definition is printed to the current query output channel,
+         as set by <command>\o</command>.
+        </para>
+        
+        <para>
+         If <literal>+</literal> is appended to the command name, then the
+         output lines are numbered, with the first line of the view definition
+         being line 1.
+        </para>
+      </varlistentry>
+
+
+      <varlistentry>
         <term><literal>\t</literal></term>
         <listitem>
         <para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 70b7d3b..948e381 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -60,9 +60,17 @@ static bool do_connect(char *dbname, char *user, char *host, char *port);
 static bool do_shell(const char *command);
 static bool do_watch(PQExpBuffer query_buf, long sleep);
 static bool lookup_function_oid(const char *desc, Oid *foid);
+static bool lookup_view_oid(const char *desc, Oid *view_oid);
 static bool get_create_function_cmd(Oid oid, PQExpBuffer buf);
-static int	strip_lineno_from_funcdesc(char *func);
+static bool get_create_view_cmd(Oid oid, PQExpBuffer buf);
+static void format_create_view_cmd(char *view, PQExpBuffer buf);
+static int	strip_lineno_from_objdesc(char *func);
 static void minimal_error_message(PGresult *res);
+static int count_lines_in_buf(PQExpBuffer buf);
+static void print_with_linenumbers(FILE *output,
+								   char *lines,
+								   const char *header_cmp_keyword,
+								   size_t header_cmp_sz);
 
 static void printSSLInfo(void);
 static bool printPsetInfo(const char *param, struct printQueryOpt *popt);
@@ -612,7 +620,7 @@ exec_command(const char *cmd,
 
 			func = psql_scan_slash_option(scan_state,
 										  OT_WHOLE_LINE, NULL, true);
-			lineno = strip_lineno_from_funcdesc(func);
+			lineno = strip_lineno_from_objdesc(func);
 			if (lineno == 0)
 			{
 				/* error already reported */
@@ -682,6 +690,77 @@ exec_command(const char *cmd,
 		}
 	}
 
+	/*
+	 * \ev -- edit the named view, or present a blank CREATE VIEW viewname AS
+	 * template if no argument is given
+	 */
+	else if (strcmp(cmd, "ev") == 0)
+	{
+		int			lineno = -1;
+
+		if (pset.sversion < 70100)
+		{
+			psql_error("The server (version %d.%d) does not support editing view definition.\n",
+					   pset.sversion / 10000, (pset.sversion / 100) % 100);
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!query_buf)
+		{
+			psql_error("no query buffer\n");
+			status = PSQL_CMD_ERROR;
+		}
+		else
+		{
+			char	   *view;
+			Oid			view_oid = InvalidOid;
+
+			view = psql_scan_slash_option(scan_state,
+										  OT_WHOLE_LINE, NULL, true);
+			lineno = strip_lineno_from_objdesc(view);
+
+			if (lineno == 0)
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			if (!view)
+			{
+				/* set up an empty command to fill in */
+				printfPQExpBuffer(query_buf,
+								  "CREATE VIEW viewname AS \n"
+								  " SELECT \n"
+								  "  -- something... \n");
+			}
+			else if (!lookup_view_oid(view, &view_oid))
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			else if (!get_create_view_cmd(view_oid, query_buf))
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+
+			if (view) {
+				format_create_view_cmd(view, query_buf);
+				free(view);
+			}
+		}
+
+		if (status != PSQL_CMD_ERROR)
+		{
+			bool		edited = false;
+
+			if (!do_edit(NULL, query_buf, lineno, &edited))
+				status = PSQL_CMD_ERROR;
+			else if (!edited)
+				puts(_("No changes"));
+			else
+				status = PSQL_CMD_NEWEDIT;
+		}
+	}
+
 	/* \echo and \qecho */
 	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
 	{
@@ -1253,18 +1332,7 @@ exec_command(const char *cmd,
 			if (pset.queryFout == stdout)
 			{
 				/* count lines in function to see if pager is needed */
-				int			lineno = 0;
-				const char *lines = func_buf->data;
-
-				while (*lines != '\0')
-				{
-					lineno++;
-					/* find start of next line */
-					lines = strchr(lines, '\n');
-					if (!lines)
-						break;
-					lines++;
-				}
+				int lineno = count_lines_in_buf(func_buf);
 
 				output = PageOutput(lineno, &(pset.popt.topt));
 				is_pager = true;
@@ -1278,10 +1346,6 @@ exec_command(const char *cmd,
 
 			if (show_linenumbers)
 			{
-				bool		in_header = true;
-				int			lineno = 0;
-				char	   *lines = func_buf->data;
-
 				/*
 				 * lineno "1" should correspond to the first line of the
 				 * function body.  We expect that pg_get_functiondef() will
@@ -1291,32 +1355,9 @@ exec_command(const char *cmd,
 				 *
 				 * Note that this loop scribbles on func_buf.
 				 */
-				while (*lines != '\0')
-				{
-					char	   *eol;
-
-					if (in_header && strncmp(lines, "AS ", 3) == 0)
-						in_header = false;
-					/* increment lineno only for body's lines */
-					if (!in_header)
-						lineno++;
-
-					/* find and mark end of current line */
-					eol = strchr(lines, '\n');
-					if (eol != NULL)
-						*eol = '\0';
-
-					/* show current line as appropriate */
-					if (in_header)
-						fprintf(output, "        %s\n", lines);
-					else
-						fprintf(output, "%-7d %s\n", lineno, lines);
-
-					/* advance to next line, if any */
-					if (eol == NULL)
-						break;
-					lines = ++eol;
-				}
+
+				char *lines = func_buf->data;
+				print_with_linenumbers(output, lines, "AS ", 3);
 			}
 			else
 			{
@@ -1333,6 +1374,81 @@ exec_command(const char *cmd,
 		destroyPQExpBuffer(func_buf);
 	}
 
+	/* \sv -- show a view's source code */
+	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+	{
+		bool		show_linenumbers = (strcmp(cmd, "sv+") == 0);
+		PQExpBuffer view_buf;
+		char	   *view;
+		Oid			view_oid = InvalidOid;
+
+		view_buf = createPQExpBuffer();
+		view = psql_scan_slash_option(scan_state,
+									  OT_WHOLE_LINE, NULL, true);
+
+		if (pset.sversion < 70100)
+		{
+			psql_error("The server (version %d.%d) does not support showing view definition.\n",
+					   pset.sversion / 10000, (pset.sversion / 100) % 100);
+			status = PSQL_CMD_ERROR;
+		}
+		if (!view)
+		{
+			psql_error("view name is required\n");
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!lookup_view_oid(view, &view_oid))
+		{
+			/* error already reported */
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!get_create_view_cmd(view_oid, view_buf))
+		{
+			/* error already reported */
+			status = PSQL_CMD_ERROR;
+		}
+		else
+		{
+			FILE *output;
+			bool is_pager;
+
+			format_create_view_cmd(view, view_buf);
+
+			if (pset.queryFout == stdout)
+			{
+				/* count lines in view to see if pager is needed */
+				int lineno = count_lines_in_buf(view_buf);
+
+				output = PageOutput(lineno, &(pset.popt.topt));
+				is_pager = true;
+			}
+			else
+			{
+				/* use previously set output file, without pager */
+				output = pset.queryFout;
+				is_pager = false;
+			}
+
+			if (show_linenumbers)
+			{
+				char	   *lines = view_buf->data;
+				print_with_linenumbers(output, lines, " SELECT", 7);
+			}
+			else
+			{
+				/* just send the function definition to output */
+				fputs(view_buf->data, output);
+			}
+
+			if (is_pager)
+				ClosePager(output);
+		}
+
+		if (view)
+			free(view);
+		destroyPQExpBuffer(view_buf);
+	}
+
 	/* \t -- turn off headers and row count */
 	else if (strcmp(cmd, "t") == 0)
 	{
@@ -3001,7 +3117,7 @@ do_watch(PQExpBuffer query_buf, long sleep)
  * returns true unless we have ECHO_HIDDEN_NOEXEC.
  */
 static bool
-lookup_function_echo_hidden(char * query)
+lookup_object_echo_hidden(char * query)
 {
 	if (pset.echo_hidden != PSQL_ECHO_HIDDEN_OFF)
 	{
@@ -3024,35 +3140,21 @@ lookup_function_echo_hidden(char * query)
 	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 returned at *foid.  Note that we'll fail if the
- * function doesn't exist OR if there are multiple matching candidates
- * OR if there's something syntactically wrong with the function description;
- * unfortunately it can be hard to tell the difference.
- */
 static bool
-lookup_function_oid(const char *desc, Oid *foid)
+lookup_object_oid_internal(PQExpBuffer query, Oid *obj_oid)
 {
 	bool		result = true;
-	PQExpBuffer query;
 	PGresult   *res;
 
-	query = createPQExpBuffer();
-	appendPQExpBufferStr(query, "SELECT ");
-	appendStringLiteralConn(query, desc, pset.db);
-	appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
-					  strchr(desc, '(') ? "regprocedure" : "regproc");
-	if (!lookup_function_echo_hidden(query->data))
+	if (!lookup_object_echo_hidden(query->data))
 	{
 		destroyPQExpBuffer(query);
 		return false;
 	}
+
 	res = PQexec(pset.db, query->data);
 	if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
-		*foid = atooid(PQgetvalue(res, 0, 0));
+		*obj_oid = atooid(PQgetvalue(res, 0, 0));
 	else
 	{
 		minimal_error_message(res);
@@ -3066,24 +3168,58 @@ lookup_function_oid(const char *desc, Oid *foid)
 }
 
 /*
- * Fetches the "CREATE OR REPLACE FUNCTION ..." command that describes the
- * function with the given OID.  If successful, the result is stored in buf.
+ * 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 returned at *foid.  Note that we'll fail if the
+ * function doesn't exist OR if there are multiple matching candidates
+ * OR if there's something syntactically wrong with the function description;
+ * unfortunately it can be hard to tell the difference.
  */
 static bool
-get_create_function_cmd(Oid oid, PQExpBuffer buf)
+lookup_function_oid(const char *desc, Oid *foid)
 {
-	bool		result = true;
 	PQExpBuffer query;
-	PGresult   *res;
 
 	query = createPQExpBuffer();
-	printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_functiondef(%u)", oid);
+	appendPQExpBufferStr(query, "SELECT ");
+	appendStringLiteralConn(query, desc, pset.db);
+	appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
+					  strchr(desc, '(') ? "regprocedure" : "regproc");
+
+	return lookup_object_oid_internal(query, foid);
+}
+
+static bool
+lookup_view_oid(const char *desc, Oid *view_oid)
+{
+	PQExpBuffer query;
+
+	query = createPQExpBuffer();
+	appendPQExpBufferStr(query, "SELECT ");
+	appendStringLiteralConn(query, desc, pset.db);
+	appendPQExpBuffer(query, "::pg_catalog.regclass::pg_catalog.oid");
+
+	return lookup_object_oid_internal(query, view_oid);
+}
+
+/*
+ * Fetches the "CREATE ..." command that describes the
+ * database object by given query.
+ * If successful, the result is stored in buf.
+ */
+static bool
+get_create_object_cmd_internal(PQExpBuffer query, PQExpBuffer buf)
+{
+	bool		result = true;
+	PGresult   *res;
 
-	if (!lookup_function_echo_hidden(query->data))
+	if (!lookup_object_echo_hidden(query->data))
 	{
 		destroyPQExpBuffer(query);
 		return false;
 	}
+
 	res = PQexec(pset.db, query->data);
 	if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
 	{
@@ -3103,6 +3239,57 @@ get_create_function_cmd(Oid oid, PQExpBuffer buf)
 }
 
 /*
+ * Fetches the "CREATE OR REPLACE FUNCTION ..." command that describes the
+ * function with the given OID.  If successful, the result is stored in buf.
+ */
+static bool
+get_create_function_cmd(Oid oid, PQExpBuffer buf)
+{
+	PQExpBuffer query;
+
+	query = createPQExpBuffer();
+	printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_functiondef(%u)", oid);
+
+	return get_create_object_cmd_internal(query, buf);
+}
+
+/*
+ * Fetches a view definition that describes the
+ * view with the given OID.  If successful, the result is stored in buf.
+ */
+static bool
+get_create_view_cmd(Oid oid, PQExpBuffer buf)
+{
+	PQExpBuffer query;
+
+	query = createPQExpBuffer();
+	printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_viewdef(%u)", oid);
+
+	return get_create_object_cmd_internal(query, buf);
+}
+
+/*
+ * Unfortunately pg_get_viewdef() doesn't return "CREATE OR REPLACE"
+ * statement prefix.
+ * We need to format "CREATE" statement manually.
+ * Originally allocated buffer contents will be replaced with formatted one.
+ */
+static void
+format_create_view_cmd(char *view, PQExpBuffer buf)
+{
+	PQExpBuffer t = createPQExpBuffer();
+	printfPQExpBuffer(t,
+					  "CREATE OR REPLACE VIEW %s AS\n%s\n",
+					  view,
+					  buf->data);
+
+	resetPQExpBuffer(buf);
+	printfPQExpBuffer(buf, "%s", t->data);
+
+	destroyPQExpBuffer(t);
+}
+
+/*
  * If the given argument of \ef ends with a line number, delete the line
  * number from the argument string and return it as an integer.  (We need
  * this kluge because we're too lazy to parse \ef's function name argument
@@ -3112,7 +3299,7 @@ get_create_function_cmd(Oid oid, PQExpBuffer buf)
  * on success.
  */
 static int
-strip_lineno_from_funcdesc(char *func)
+strip_lineno_from_objdesc(char *func)
 {
 	char	   *c;
 	int			lineno;
@@ -3193,3 +3380,60 @@ minimal_error_message(PGresult *res)
 
 	destroyPQExpBuffer(msg);
 }
+
+static int
+count_lines_in_buf(PQExpBuffer buf)
+{
+	int			lineno = 0;
+	const char *lines = buf->data;
+
+	while (*lines != '\0')
+	{
+		lineno++;
+		/* find start of next line */
+		lines = strchr(lines, '\n');
+		if (!lines)
+			break;
+		lines++;
+	}
+
+	return lineno;
+}
+
+static void
+print_with_linenumbers(FILE *output,
+					   char *lines,
+					   const char *header_cmp_keyword,
+					   size_t header_cmp_sz)
+{
+	bool		in_header = true;
+	int			lineno = 0;
+
+	while (*lines != '\0')
+	{
+		char	   *eol;
+
+		if (in_header && strncmp(lines, header_cmp_keyword, header_cmp_sz) == 0)
+			in_header = false;
+
+		/* increment lineno only for body's lines */
+		if (!in_header)
+			lineno++;
+
+		/* find and mark end of current line */
+		eol = strchr(lines, '\n');
+		if (eol != NULL)
+			*eol = '\0';
+
+		/* show current line as appropriate */
+		if (in_header)
+			fprintf(output, "        %s\n", lines);
+		else
+			fprintf(output, "%-7d %s\n", lineno, lines);
+
+		/* advance to next line, if any */
+		if (eol == NULL)
+			break;
+		lines = ++eol;
+	}
+}
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index ea05c3e..c622635 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -181,6 +181,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("Query Buffer\n"));
 	fprintf(output, _("  \\e [FILE] [LINE]       edit the query buffer (or file) with external editor\n"));
 	fprintf(output, _("  \\ef [FUNCNAME [LINE]]  edit function definition with external editor\n"));
+	fprintf(output, _("  \\ev [VIEWNAME [LINE]]  edit view definition with external editor\n"));
 	fprintf(output, _("  \\p                     show the contents of the query buffer\n"));
 	fprintf(output, _("  \\r                     reset (clear) the query buffer\n"));
 #ifdef USE_READLINE
@@ -238,6 +239,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\dy     [PATTERN]      list event triggers\n"));
 	fprintf(output, _("  \\l[+]   [PATTERN]      list databases\n"));
 	fprintf(output, _("  \\sf[+] FUNCNAME        show a function's definition\n"));
+	fprintf(output, _("  \\sv[+] VIEWNAME        show a view's definition\n"));
 	fprintf(output, _("  \\z      [PATTERN]      same as \\dp\n"));
 	fprintf(output, "\n");
 
@@ -388,7 +390,7 @@ helpVariables(unsigned short int pager)
 	fprintf(output, _("  PGPASSWORD         connection password (not recommended)\n"));
 	fprintf(output, _("  PGPASSFILE         password file name\n"));
 	fprintf(output, _("  PSQL_EDITOR, EDITOR, VISUAL\n"
-					 "                     editor used by the \\e and \\ef commands\n"));
+					 "                     editor used by the \\e, \\ef, and \\ev commands\n"));
 	fprintf(output, _("  PSQL_EDITOR_LINENUMBER_ARG\n"
 					 "                     how to specify a line number when invoking the editor\n"));
 	fprintf(output, _("  PSQL_HISTORY       alternative location for the command history file\n"));
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 750e29d..38b2efe 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -895,11 +895,11 @@ psql_completion(const char *text, int start, int end)
 		"\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\dD", "\\des", "\\det", "\\deu", "\\dew", "\\df",
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
 		"\\dn", "\\do", "\\dp", "\\drds", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du", "\\dx",
-		"\\e", "\\echo", "\\ef", "\\encoding",
+		"\\e", "\\echo", "\\ef", "\\ev", "\\encoding",
 		"\\f", "\\g", "\\gset", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
 		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
 		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
-		"\\set", "\\sf", "\\t", "\\T",
+		"\\set", "\\sf", "\\sf+", "\\sv", "\\sv+", "\\t", "\\T",
 		"\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL
 	};
 
@@ -3776,6 +3776,8 @@ psql_completion(const char *text, int start, int end)
 
 	else if (strcmp(prev_wd, "\\ef") == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+	else if (strcmp(prev_wd, "\\ev") == 0)
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
 	else if (strcmp(prev_wd, "\\encoding") == 0)
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
@@ -3890,6 +3892,8 @@ psql_completion(const char *text, int start, int end)
 	}
 	else if (strcmp(prev_wd, "\\sf") == 0 || strcmp(prev_wd, "\\sf+") == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+	else if (strcmp(prev_wd, "\\sv") == 0 || strcmp(prev_wd, "\\sv+") == 0)
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 	else if (strcmp(prev_wd, "\\cd") == 0 ||
 			 strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 0 ||
 			 strcmp(prev_wd, "\\g") == 0 ||
#2Pavel Stehule
pavel.stehule@gmail.com
In reply to: Petr Korobeinikov (#1)
Re: psql :: support for \ev viewname and \sv viewname

2015-05-04 11:21 GMT+02:00 Petr Korobeinikov <pkorobeinikov@gmail.com>:

Hackers!

I'm proposing to add two new subcommands in psql:
1. \ev viewname - edit view definition with external editor (like \ef for
function)
2. \sv viewname - show view definition (like \sf for function, for
consistency)

+1

Pavel

Show quoted text

What's inside:
1. review-ready implementation of \ev and \sv psql subcommands for editing
and viewing view's definition.
2. psql's doc update with new subcommands description.
3. a bit of extracting common source code parts into separate functions.
4. psql internal help update.
5. tab completion update.

There is one narrow place in this patch: current implementation doesn't
support "create" statement formatting for recursive views.

Any comments? Suggestions?

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Fabrízio de Royes Mello
fabriziomello@gmail.com
In reply to: Pavel Stehule (#2)
Re: psql :: support for \ev viewname and \sv viewname

On Mon, May 4, 2015 at 6:26 AM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:

2015-05-04 11:21 GMT+02:00 Petr Korobeinikov <pkorobeinikov@gmail.com>:

Hackers!

I'm proposing to add two new subcommands in psql:
1. \ev viewname - edit view definition with external editor (like \ef for
function)
2. \sv viewname - show view definition (like \sf for function, for
consistency)

+1

+1

During the FISL13 [1]http://softwarelivre.org/fisl13?lang=en (year 2012) me and other friends (Leonardo César,
Dickson Guedes and Fernando Ike) implemented a very WIP patch [2]https://github.com/lhcezar/postgres/commit/b4bfc3b17b4a32850d6035165209b2b82746190a to
support the "\ev" subcommand in psql. Unfortunately we didn't go ahead with
this work. :-(

I'll do some reviews.

Regards,

[1]: http://softwarelivre.org/fisl13?lang=en
[2]: https://github.com/lhcezar/postgres/commit/b4bfc3b17b4a32850d6035165209b2b82746190a
https://github.com/lhcezar/postgres/commit/b4bfc3b17b4a32850d6035165209b2b82746190a

--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL

Show quoted text

Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello

#4Robert Haas
robertmhaas@gmail.com
In reply to: Petr Korobeinikov (#1)
Re: psql :: support for \ev viewname and \sv viewname

On Mon, May 4, 2015 at 5:21 AM, Petr Korobeinikov
<pkorobeinikov@gmail.com> wrote:

I'm proposing to add two new subcommands in psql:
1. \ev viewname - edit view definition with external editor (like \ef for
function)
2. \sv viewname - show view definition (like \sf for function, for
consistency)

Sounds nice. Make sure to add your patch to the open CommitFest so we
don't forget about it.

https://commitfest.postgresql.org/action/commitfest_view/open

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Petr A. Korobeinikov
pkorobeinikov@gmail.com
In reply to: Robert Haas (#4)
1 attachment(s)
Re: psql :: support for \ev viewname and \sv viewname

This version contains one little change.
In order to be consistent with “\d+ viewname” it uses pg_get_viewdef(oid, /* pretty */ true) to produce “pretty” output (without additional parentheses).

Attachments:

psql-ev-sv-support-v2.diffapplication/octet-stream; name=psql-ev-sv-support-v2.diffDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 62a3b21..d668777 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1651,6 +1651,28 @@ Tue Oct 26 21:40:57 CEST 1999
 
 
       <varlistentry>
+        <term><literal>\ev <optional> <replaceable class="parameter">viewname</> </optional> </literal></term>
+
+        <listitem>
+        <para>
+         This command fetches and edits the definition of the named view,
+         in the form of a <command>CREATE OR REPLACE VIEW</> command.
+         Editing is done in the same way as for <literal>\edit</>.
+         After the editor exits, the updated command waits in the query buffer;
+         type semicolon or <literal>\g</> to send it, or <literal>\r</>
+         to cancel.
+        </para>
+
+        <para>
+         If no view is specified, a blank <command>CREATE VEIW</>
+         template is presented for editing.
+        </para>
+
+        </listitem>
+      </varlistentry>
+
+
+      <varlistentry>
         <term><literal>\encoding [ <replaceable class="parameter">encoding</replaceable> ]</literal></term>
 
         <listitem>
@@ -2522,6 +2544,25 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
 
 
       <varlistentry>
+        <term><literal>\sv[+] <replaceable class="parameter">viewname</> </literal></term>
+
+        <listitem>
+        <para>
+         This command fetches and shows the definition of the named view,
+         in the form of a <command>CREATE OR REPLACE VIEW</> command.
+         The definition is printed to the current query output channel,
+         as set by <command>\o</command>.
+        </para>
+        
+        <para>
+         If <literal>+</literal> is appended to the command name, then the
+         output lines are numbered, with the first line of the view definition
+         being line 1.
+        </para>
+      </varlistentry>
+
+
+      <varlistentry>
         <term><literal>\t</literal></term>
         <listitem>
         <para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 70b7d3b..de0f2c5 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -60,9 +60,17 @@ static bool do_connect(char *dbname, char *user, char *host, char *port);
 static bool do_shell(const char *command);
 static bool do_watch(PQExpBuffer query_buf, long sleep);
 static bool lookup_function_oid(const char *desc, Oid *foid);
+static bool lookup_view_oid(const char *desc, Oid *view_oid);
 static bool get_create_function_cmd(Oid oid, PQExpBuffer buf);
-static int	strip_lineno_from_funcdesc(char *func);
+static bool get_create_view_cmd(Oid oid, PQExpBuffer buf);
+static void format_create_view_cmd(char *view, PQExpBuffer buf);
+static int	strip_lineno_from_objdesc(char *func);
 static void minimal_error_message(PGresult *res);
+static int count_lines_in_buf(PQExpBuffer buf);
+static void print_with_linenumbers(FILE *output,
+								   char *lines,
+								   const char *header_cmp_keyword,
+								   size_t header_cmp_sz);
 
 static void printSSLInfo(void);
 static bool printPsetInfo(const char *param, struct printQueryOpt *popt);
@@ -612,7 +620,7 @@ exec_command(const char *cmd,
 
 			func = psql_scan_slash_option(scan_state,
 										  OT_WHOLE_LINE, NULL, true);
-			lineno = strip_lineno_from_funcdesc(func);
+			lineno = strip_lineno_from_objdesc(func);
 			if (lineno == 0)
 			{
 				/* error already reported */
@@ -682,6 +690,77 @@ exec_command(const char *cmd,
 		}
 	}
 
+	/*
+	 * \ev -- edit the named view, or present a blank CREATE VIEW viewname AS
+	 * template if no argument is given
+	 */
+	else if (strcmp(cmd, "ev") == 0)
+	{
+		int			lineno = -1;
+
+		if (pset.sversion < 70100)
+		{
+			psql_error("The server (version %d.%d) does not support editing view definition.\n",
+					   pset.sversion / 10000, (pset.sversion / 100) % 100);
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!query_buf)
+		{
+			psql_error("no query buffer\n");
+			status = PSQL_CMD_ERROR;
+		}
+		else
+		{
+			char	   *view;
+			Oid			view_oid = InvalidOid;
+
+			view = psql_scan_slash_option(scan_state,
+										  OT_WHOLE_LINE, NULL, true);
+			lineno = strip_lineno_from_objdesc(view);
+
+			if (lineno == 0)
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			if (!view)
+			{
+				/* set up an empty command to fill in */
+				printfPQExpBuffer(query_buf,
+								  "CREATE VIEW viewname AS \n"
+								  " SELECT \n"
+								  "  -- something... \n");
+			}
+			else if (!lookup_view_oid(view, &view_oid))
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			else if (!get_create_view_cmd(view_oid, query_buf))
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+
+			if (view) {
+				format_create_view_cmd(view, query_buf);
+				free(view);
+			}
+		}
+
+		if (status != PSQL_CMD_ERROR)
+		{
+			bool		edited = false;
+
+			if (!do_edit(NULL, query_buf, lineno, &edited))
+				status = PSQL_CMD_ERROR;
+			else if (!edited)
+				puts(_("No changes"));
+			else
+				status = PSQL_CMD_NEWEDIT;
+		}
+	}
+
 	/* \echo and \qecho */
 	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
 	{
@@ -1253,18 +1332,7 @@ exec_command(const char *cmd,
 			if (pset.queryFout == stdout)
 			{
 				/* count lines in function to see if pager is needed */
-				int			lineno = 0;
-				const char *lines = func_buf->data;
-
-				while (*lines != '\0')
-				{
-					lineno++;
-					/* find start of next line */
-					lines = strchr(lines, '\n');
-					if (!lines)
-						break;
-					lines++;
-				}
+				int lineno = count_lines_in_buf(func_buf);
 
 				output = PageOutput(lineno, &(pset.popt.topt));
 				is_pager = true;
@@ -1278,10 +1346,6 @@ exec_command(const char *cmd,
 
 			if (show_linenumbers)
 			{
-				bool		in_header = true;
-				int			lineno = 0;
-				char	   *lines = func_buf->data;
-
 				/*
 				 * lineno "1" should correspond to the first line of the
 				 * function body.  We expect that pg_get_functiondef() will
@@ -1291,32 +1355,9 @@ exec_command(const char *cmd,
 				 *
 				 * Note that this loop scribbles on func_buf.
 				 */
-				while (*lines != '\0')
-				{
-					char	   *eol;
-
-					if (in_header && strncmp(lines, "AS ", 3) == 0)
-						in_header = false;
-					/* increment lineno only for body's lines */
-					if (!in_header)
-						lineno++;
-
-					/* find and mark end of current line */
-					eol = strchr(lines, '\n');
-					if (eol != NULL)
-						*eol = '\0';
-
-					/* show current line as appropriate */
-					if (in_header)
-						fprintf(output, "        %s\n", lines);
-					else
-						fprintf(output, "%-7d %s\n", lineno, lines);
-
-					/* advance to next line, if any */
-					if (eol == NULL)
-						break;
-					lines = ++eol;
-				}
+
+				char *lines = func_buf->data;
+				print_with_linenumbers(output, lines, "AS ", 3);
 			}
 			else
 			{
@@ -1333,6 +1374,81 @@ exec_command(const char *cmd,
 		destroyPQExpBuffer(func_buf);
 	}
 
+	/* \sv -- show a view's source code */
+	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+	{
+		bool		show_linenumbers = (strcmp(cmd, "sv+") == 0);
+		PQExpBuffer view_buf;
+		char	   *view;
+		Oid			view_oid = InvalidOid;
+
+		view_buf = createPQExpBuffer();
+		view = psql_scan_slash_option(scan_state,
+									  OT_WHOLE_LINE, NULL, true);
+
+		if (pset.sversion < 70100)
+		{
+			psql_error("The server (version %d.%d) does not support showing view definition.\n",
+					   pset.sversion / 10000, (pset.sversion / 100) % 100);
+			status = PSQL_CMD_ERROR;
+		}
+		if (!view)
+		{
+			psql_error("view name is required\n");
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!lookup_view_oid(view, &view_oid))
+		{
+			/* error already reported */
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!get_create_view_cmd(view_oid, view_buf))
+		{
+			/* error already reported */
+			status = PSQL_CMD_ERROR;
+		}
+		else
+		{
+			FILE *output;
+			bool is_pager;
+
+			format_create_view_cmd(view, view_buf);
+
+			if (pset.queryFout == stdout)
+			{
+				/* count lines in view to see if pager is needed */
+				int lineno = count_lines_in_buf(view_buf);
+
+				output = PageOutput(lineno, &(pset.popt.topt));
+				is_pager = true;
+			}
+			else
+			{
+				/* use previously set output file, without pager */
+				output = pset.queryFout;
+				is_pager = false;
+			}
+
+			if (show_linenumbers)
+			{
+				char	   *lines = view_buf->data;
+				print_with_linenumbers(output, lines, " SELECT", 7);
+			}
+			else
+			{
+				/* just send the function definition to output */
+				fputs(view_buf->data, output);
+			}
+
+			if (is_pager)
+				ClosePager(output);
+		}
+
+		if (view)
+			free(view);
+		destroyPQExpBuffer(view_buf);
+	}
+
 	/* \t -- turn off headers and row count */
 	else if (strcmp(cmd, "t") == 0)
 	{
@@ -3001,7 +3117,7 @@ do_watch(PQExpBuffer query_buf, long sleep)
  * returns true unless we have ECHO_HIDDEN_NOEXEC.
  */
 static bool
-lookup_function_echo_hidden(char * query)
+lookup_object_echo_hidden(char * query)
 {
 	if (pset.echo_hidden != PSQL_ECHO_HIDDEN_OFF)
 	{
@@ -3024,35 +3140,21 @@ lookup_function_echo_hidden(char * query)
 	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 returned at *foid.  Note that we'll fail if the
- * function doesn't exist OR if there are multiple matching candidates
- * OR if there's something syntactically wrong with the function description;
- * unfortunately it can be hard to tell the difference.
- */
 static bool
-lookup_function_oid(const char *desc, Oid *foid)
+lookup_object_oid_internal(PQExpBuffer query, Oid *obj_oid)
 {
 	bool		result = true;
-	PQExpBuffer query;
 	PGresult   *res;
 
-	query = createPQExpBuffer();
-	appendPQExpBufferStr(query, "SELECT ");
-	appendStringLiteralConn(query, desc, pset.db);
-	appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
-					  strchr(desc, '(') ? "regprocedure" : "regproc");
-	if (!lookup_function_echo_hidden(query->data))
+	if (!lookup_object_echo_hidden(query->data))
 	{
 		destroyPQExpBuffer(query);
 		return false;
 	}
+
 	res = PQexec(pset.db, query->data);
 	if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
-		*foid = atooid(PQgetvalue(res, 0, 0));
+		*obj_oid = atooid(PQgetvalue(res, 0, 0));
 	else
 	{
 		minimal_error_message(res);
@@ -3066,24 +3168,58 @@ lookup_function_oid(const char *desc, Oid *foid)
 }
 
 /*
- * Fetches the "CREATE OR REPLACE FUNCTION ..." command that describes the
- * function with the given OID.  If successful, the result is stored in buf.
+ * 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 returned at *foid.  Note that we'll fail if the
+ * function doesn't exist OR if there are multiple matching candidates
+ * OR if there's something syntactically wrong with the function description;
+ * unfortunately it can be hard to tell the difference.
  */
 static bool
-get_create_function_cmd(Oid oid, PQExpBuffer buf)
+lookup_function_oid(const char *desc, Oid *foid)
 {
-	bool		result = true;
 	PQExpBuffer query;
-	PGresult   *res;
 
 	query = createPQExpBuffer();
-	printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_functiondef(%u)", oid);
+	appendPQExpBufferStr(query, "SELECT ");
+	appendStringLiteralConn(query, desc, pset.db);
+	appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
+					  strchr(desc, '(') ? "regprocedure" : "regproc");
+
+	return lookup_object_oid_internal(query, foid);
+}
+
+static bool
+lookup_view_oid(const char *desc, Oid *view_oid)
+{
+	PQExpBuffer query;
+
+	query = createPQExpBuffer();
+	appendPQExpBufferStr(query, "SELECT ");
+	appendStringLiteralConn(query, desc, pset.db);
+	appendPQExpBuffer(query, "::pg_catalog.regclass::pg_catalog.oid");
+
+	return lookup_object_oid_internal(query, view_oid);
+}
+
+/*
+ * Fetches the "CREATE ..." command that describes the
+ * database object by given query.
+ * If successful, the result is stored in buf.
+ */
+static bool
+get_create_object_cmd_internal(PQExpBuffer query, PQExpBuffer buf)
+{
+	bool		result = true;
+	PGresult   *res;
 
-	if (!lookup_function_echo_hidden(query->data))
+	if (!lookup_object_echo_hidden(query->data))
 	{
 		destroyPQExpBuffer(query);
 		return false;
 	}
+
 	res = PQexec(pset.db, query->data);
 	if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
 	{
@@ -3103,6 +3239,57 @@ get_create_function_cmd(Oid oid, PQExpBuffer buf)
 }
 
 /*
+ * Fetches the "CREATE OR REPLACE FUNCTION ..." command that describes the
+ * function with the given OID.  If successful, the result is stored in buf.
+ */
+static bool
+get_create_function_cmd(Oid oid, PQExpBuffer buf)
+{
+	PQExpBuffer query;
+
+	query = createPQExpBuffer();
+	printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_functiondef(%u)", oid);
+
+	return get_create_object_cmd_internal(query, buf);
+}
+
+/*
+ * Fetches a view definition that describes the
+ * view with the given OID.  If successful, the result is stored in buf.
+ */
+static bool
+get_create_view_cmd(Oid oid, PQExpBuffer buf)
+{
+	PQExpBuffer query;
+
+	query = createPQExpBuffer();
+	printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_viewdef(%u, true)", oid);
+
+	return get_create_object_cmd_internal(query, buf);
+}
+
+/*
+ * Unfortunately pg_get_viewdef() doesn't return "CREATE OR REPLACE"
+ * statement prefix.
+ * We need to format "CREATE" statement manually.
+ * Originally allocated buffer contents will be replaced with formatted one.
+ */
+static void
+format_create_view_cmd(char *view, PQExpBuffer buf)
+{
+	PQExpBuffer t = createPQExpBuffer();
+	printfPQExpBuffer(t,
+					  "CREATE OR REPLACE VIEW %s AS\n%s\n",
+					  view,
+					  buf->data);
+
+	resetPQExpBuffer(buf);
+	printfPQExpBuffer(buf, "%s", t->data);
+
+	destroyPQExpBuffer(t);
+}
+
+/*
  * If the given argument of \ef ends with a line number, delete the line
  * number from the argument string and return it as an integer.  (We need
  * this kluge because we're too lazy to parse \ef's function name argument
@@ -3112,7 +3299,7 @@ get_create_function_cmd(Oid oid, PQExpBuffer buf)
  * on success.
  */
 static int
-strip_lineno_from_funcdesc(char *func)
+strip_lineno_from_objdesc(char *func)
 {
 	char	   *c;
 	int			lineno;
@@ -3193,3 +3380,60 @@ minimal_error_message(PGresult *res)
 
 	destroyPQExpBuffer(msg);
 }
+
+static int
+count_lines_in_buf(PQExpBuffer buf)
+{
+	int			lineno = 0;
+	const char *lines = buf->data;
+
+	while (*lines != '\0')
+	{
+		lineno++;
+		/* find start of next line */
+		lines = strchr(lines, '\n');
+		if (!lines)
+			break;
+		lines++;
+	}
+
+	return lineno;
+}
+
+static void
+print_with_linenumbers(FILE *output,
+					   char *lines,
+					   const char *header_cmp_keyword,
+					   size_t header_cmp_sz)
+{
+	bool		in_header = true;
+	int			lineno = 0;
+
+	while (*lines != '\0')
+	{
+		char	   *eol;
+
+		if (in_header && strncmp(lines, header_cmp_keyword, header_cmp_sz) == 0)
+			in_header = false;
+
+		/* increment lineno only for body's lines */
+		if (!in_header)
+			lineno++;
+
+		/* find and mark end of current line */
+		eol = strchr(lines, '\n');
+		if (eol != NULL)
+			*eol = '\0';
+
+		/* show current line as appropriate */
+		if (in_header)
+			fprintf(output, "        %s\n", lines);
+		else
+			fprintf(output, "%-7d %s\n", lineno, lines);
+
+		/* advance to next line, if any */
+		if (eol == NULL)
+			break;
+		lines = ++eol;
+	}
+}
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index ea05c3e..c622635 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -181,6 +181,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("Query Buffer\n"));
 	fprintf(output, _("  \\e [FILE] [LINE]       edit the query buffer (or file) with external editor\n"));
 	fprintf(output, _("  \\ef [FUNCNAME [LINE]]  edit function definition with external editor\n"));
+	fprintf(output, _("  \\ev [VIEWNAME [LINE]]  edit view definition with external editor\n"));
 	fprintf(output, _("  \\p                     show the contents of the query buffer\n"));
 	fprintf(output, _("  \\r                     reset (clear) the query buffer\n"));
 #ifdef USE_READLINE
@@ -238,6 +239,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\dy     [PATTERN]      list event triggers\n"));
 	fprintf(output, _("  \\l[+]   [PATTERN]      list databases\n"));
 	fprintf(output, _("  \\sf[+] FUNCNAME        show a function's definition\n"));
+	fprintf(output, _("  \\sv[+] VIEWNAME        show a view's definition\n"));
 	fprintf(output, _("  \\z      [PATTERN]      same as \\dp\n"));
 	fprintf(output, "\n");
 
@@ -388,7 +390,7 @@ helpVariables(unsigned short int pager)
 	fprintf(output, _("  PGPASSWORD         connection password (not recommended)\n"));
 	fprintf(output, _("  PGPASSFILE         password file name\n"));
 	fprintf(output, _("  PSQL_EDITOR, EDITOR, VISUAL\n"
-					 "                     editor used by the \\e and \\ef commands\n"));
+					 "                     editor used by the \\e, \\ef, and \\ev commands\n"));
 	fprintf(output, _("  PSQL_EDITOR_LINENUMBER_ARG\n"
 					 "                     how to specify a line number when invoking the editor\n"));
 	fprintf(output, _("  PSQL_HISTORY       alternative location for the command history file\n"));
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 750e29d..38b2efe 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -895,11 +895,11 @@ psql_completion(const char *text, int start, int end)
 		"\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\dD", "\\des", "\\det", "\\deu", "\\dew", "\\df",
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
 		"\\dn", "\\do", "\\dp", "\\drds", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du", "\\dx",
-		"\\e", "\\echo", "\\ef", "\\encoding",
+		"\\e", "\\echo", "\\ef", "\\ev", "\\encoding",
 		"\\f", "\\g", "\\gset", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
 		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
 		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
-		"\\set", "\\sf", "\\t", "\\T",
+		"\\set", "\\sf", "\\sf+", "\\sv", "\\sv+", "\\t", "\\T",
 		"\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL
 	};
 
@@ -3776,6 +3776,8 @@ psql_completion(const char *text, int start, int end)
 
 	else if (strcmp(prev_wd, "\\ef") == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+	else if (strcmp(prev_wd, "\\ev") == 0)
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
 	else if (strcmp(prev_wd, "\\encoding") == 0)
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
@@ -3890,6 +3892,8 @@ psql_completion(const char *text, int start, int end)
 	}
 	else if (strcmp(prev_wd, "\\sf") == 0 || strcmp(prev_wd, "\\sf+") == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+	else if (strcmp(prev_wd, "\\sv") == 0 || strcmp(prev_wd, "\\sv+") == 0)
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 	else if (strcmp(prev_wd, "\\cd") == 0 ||
 			 strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 0 ||
 			 strcmp(prev_wd, "\\g") == 0 ||
#6Petr Korobeinikov
pkorobeinikov@gmail.com
In reply to: Petr A. Korobeinikov (#5)
1 attachment(s)
Re: psql :: support for \ev viewname and \sv viewname

Just a merge after pgindent run (807b9e0dff663c5da875af7907a5106c0ff90673).

Attachments:

psql-ev-sv-support-v3.diffapplication/octet-stream; name=psql-ev-sv-support-v3.diffDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 62a3b21..d668777 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1651,6 +1651,28 @@ Tue Oct 26 21:40:57 CEST 1999
 
 
       <varlistentry>
+        <term><literal>\ev <optional> <replaceable class="parameter">viewname</> </optional> </literal></term>
+
+        <listitem>
+        <para>
+         This command fetches and edits the definition of the named view,
+         in the form of a <command>CREATE OR REPLACE VIEW</> command.
+         Editing is done in the same way as for <literal>\edit</>.
+         After the editor exits, the updated command waits in the query buffer;
+         type semicolon or <literal>\g</> to send it, or <literal>\r</>
+         to cancel.
+        </para>
+
+        <para>
+         If no view is specified, a blank <command>CREATE VEIW</>
+         template is presented for editing.
+        </para>
+
+        </listitem>
+      </varlistentry>
+
+
+      <varlistentry>
         <term><literal>\encoding [ <replaceable class="parameter">encoding</replaceable> ]</literal></term>
 
         <listitem>
@@ -2522,6 +2544,25 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
 
 
       <varlistentry>
+        <term><literal>\sv[+] <replaceable class="parameter">viewname</> </literal></term>
+
+        <listitem>
+        <para>
+         This command fetches and shows the definition of the named view,
+         in the form of a <command>CREATE OR REPLACE VIEW</> command.
+         The definition is printed to the current query output channel,
+         as set by <command>\o</command>.
+        </para>
+        
+        <para>
+         If <literal>+</literal> is appended to the command name, then the
+         output lines are numbered, with the first line of the view definition
+         being line 1.
+        </para>
+      </varlistentry>
+
+
+      <varlistentry>
         <term><literal>\t</literal></term>
         <listitem>
         <para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 38253fa..236f3d9 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -60,9 +60,17 @@ static bool do_connect(char *dbname, char *user, char *host, char *port);
 static bool do_shell(const char *command);
 static bool do_watch(PQExpBuffer query_buf, long sleep);
 static bool lookup_function_oid(const char *desc, Oid *foid);
+static bool lookup_view_oid(const char *desc, Oid *view_oid);
 static bool get_create_function_cmd(Oid oid, PQExpBuffer buf);
-static int	strip_lineno_from_funcdesc(char *func);
+static bool get_create_view_cmd(Oid oid, PQExpBuffer buf);
+static void format_create_view_cmd(char *view, PQExpBuffer buf);
+static int	strip_lineno_from_objdesc(char *func);
 static void minimal_error_message(PGresult *res);
+static int count_lines_in_buf(PQExpBuffer buf);
+static void print_with_linenumbers(FILE *output,
+								   char *lines,
+								   const char *header_cmp_keyword,
+								   size_t header_cmp_sz);
 
 static void printSSLInfo(void);
 static bool printPsetInfo(const char *param, struct printQueryOpt *popt);
@@ -612,7 +620,7 @@ exec_command(const char *cmd,
 
 			func = psql_scan_slash_option(scan_state,
 										  OT_WHOLE_LINE, NULL, true);
-			lineno = strip_lineno_from_funcdesc(func);
+			lineno = strip_lineno_from_objdesc(func);
 			if (lineno == 0)
 			{
 				/* error already reported */
@@ -682,6 +690,77 @@ exec_command(const char *cmd,
 		}
 	}
 
+	/*
+	 * \ev -- edit the named view, or present a blank CREATE VIEW viewname AS
+	 * template if no argument is given
+	 */
+	else if (strcmp(cmd, "ev") == 0)
+	{
+		int			lineno = -1;
+
+		if (pset.sversion < 70100)
+		{
+			psql_error("The server (version %d.%d) does not support editing view definition.\n",
+					   pset.sversion / 10000, (pset.sversion / 100) % 100);
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!query_buf)
+		{
+			psql_error("no query buffer\n");
+			status = PSQL_CMD_ERROR;
+		}
+		else
+		{
+			char	   *view;
+			Oid			view_oid = InvalidOid;
+
+			view = psql_scan_slash_option(scan_state,
+										  OT_WHOLE_LINE, NULL, true);
+			lineno = strip_lineno_from_objdesc(view);
+
+			if (lineno == 0)
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			if (!view)
+			{
+				/* set up an empty command to fill in */
+				printfPQExpBuffer(query_buf,
+								  "CREATE VIEW viewname AS \n"
+								  " SELECT \n"
+								  "  -- something... \n");
+			}
+			else if (!lookup_view_oid(view, &view_oid))
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			else if (!get_create_view_cmd(view_oid, query_buf))
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+
+			if (view) {
+				format_create_view_cmd(view, query_buf);
+				free(view);
+			}
+		}
+
+		if (status != PSQL_CMD_ERROR)
+		{
+			bool		edited = false;
+
+			if (!do_edit(NULL, query_buf, lineno, &edited))
+				status = PSQL_CMD_ERROR;
+			else if (!edited)
+				puts(_("No changes"));
+			else
+				status = PSQL_CMD_NEWEDIT;
+		}
+	}
+
 	/* \echo and \qecho */
 	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
 	{
@@ -1254,18 +1333,7 @@ exec_command(const char *cmd,
 			if (pset.queryFout == stdout)
 			{
 				/* count lines in function to see if pager is needed */
-				int			lineno = 0;
-				const char *lines = func_buf->data;
-
-				while (*lines != '\0')
-				{
-					lineno++;
-					/* find start of next line */
-					lines = strchr(lines, '\n');
-					if (!lines)
-						break;
-					lines++;
-				}
+				int lineno = count_lines_in_buf(func_buf);
 
 				output = PageOutput(lineno, &(pset.popt.topt));
 				is_pager = true;
@@ -1279,10 +1347,6 @@ exec_command(const char *cmd,
 
 			if (show_linenumbers)
 			{
-				bool		in_header = true;
-				int			lineno = 0;
-				char	   *lines = func_buf->data;
-
 				/*
 				 * lineno "1" should correspond to the first line of the
 				 * function body.  We expect that pg_get_functiondef() will
@@ -1292,32 +1356,9 @@ exec_command(const char *cmd,
 				 *
 				 * Note that this loop scribbles on func_buf.
 				 */
-				while (*lines != '\0')
-				{
-					char	   *eol;
-
-					if (in_header && strncmp(lines, "AS ", 3) == 0)
-						in_header = false;
-					/* increment lineno only for body's lines */
-					if (!in_header)
-						lineno++;
-
-					/* find and mark end of current line */
-					eol = strchr(lines, '\n');
-					if (eol != NULL)
-						*eol = '\0';
-
-					/* show current line as appropriate */
-					if (in_header)
-						fprintf(output, "        %s\n", lines);
-					else
-						fprintf(output, "%-7d %s\n", lineno, lines);
-
-					/* advance to next line, if any */
-					if (eol == NULL)
-						break;
-					lines = ++eol;
-				}
+
+				char *lines = func_buf->data;
+				print_with_linenumbers(output, lines, "AS ", 3);
 			}
 			else
 			{
@@ -1334,6 +1375,81 @@ exec_command(const char *cmd,
 		destroyPQExpBuffer(func_buf);
 	}
 
+	/* \sv -- show a view's source code */
+	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+	{
+		bool		show_linenumbers = (strcmp(cmd, "sv+") == 0);
+		PQExpBuffer view_buf;
+		char	   *view;
+		Oid			view_oid = InvalidOid;
+
+		view_buf = createPQExpBuffer();
+		view = psql_scan_slash_option(scan_state,
+									  OT_WHOLE_LINE, NULL, true);
+
+		if (pset.sversion < 70100)
+		{
+			psql_error("The server (version %d.%d) does not support showing view definition.\n",
+					   pset.sversion / 10000, (pset.sversion / 100) % 100);
+			status = PSQL_CMD_ERROR;
+		}
+		if (!view)
+		{
+			psql_error("view name is required\n");
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!lookup_view_oid(view, &view_oid))
+		{
+			/* error already reported */
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!get_create_view_cmd(view_oid, view_buf))
+		{
+			/* error already reported */
+			status = PSQL_CMD_ERROR;
+		}
+		else
+		{
+			FILE *output;
+			bool is_pager;
+
+			format_create_view_cmd(view, view_buf);
+
+			if (pset.queryFout == stdout)
+			{
+				/* count lines in view to see if pager is needed */
+				int lineno = count_lines_in_buf(view_buf);
+
+				output = PageOutput(lineno, &(pset.popt.topt));
+				is_pager = true;
+			}
+			else
+			{
+				/* use previously set output file, without pager */
+				output = pset.queryFout;
+				is_pager = false;
+			}
+
+			if (show_linenumbers)
+			{
+				char	   *lines = view_buf->data;
+				print_with_linenumbers(output, lines, " SELECT", 7);
+			}
+			else
+			{
+				/* just send the function definition to output */
+				fputs(view_buf->data, output);
+			}
+
+			if (is_pager)
+				ClosePager(output);
+		}
+
+		if (view)
+			free(view);
+		destroyPQExpBuffer(view_buf);
+	}
+
 	/* \t -- turn off headers and row count */
 	else if (strcmp(cmd, "t") == 0)
 	{
@@ -3002,7 +3118,7 @@ do_watch(PQExpBuffer query_buf, long sleep)
  * returns true unless we have ECHO_HIDDEN_NOEXEC.
  */
 static bool
-lookup_function_echo_hidden(char *query)
+lookup_object_echo_hidden(char *query)
 {
 	if (pset.echo_hidden != PSQL_ECHO_HIDDEN_OFF)
 	{
@@ -3025,35 +3141,21 @@ lookup_function_echo_hidden(char *query)
 	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 returned at *foid.  Note that we'll fail if the
- * function doesn't exist OR if there are multiple matching candidates
- * OR if there's something syntactically wrong with the function description;
- * unfortunately it can be hard to tell the difference.
- */
 static bool
-lookup_function_oid(const char *desc, Oid *foid)
+lookup_object_oid_internal(PQExpBuffer query, Oid *obj_oid)
 {
 	bool		result = true;
-	PQExpBuffer query;
 	PGresult   *res;
 
-	query = createPQExpBuffer();
-	appendPQExpBufferStr(query, "SELECT ");
-	appendStringLiteralConn(query, desc, pset.db);
-	appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
-					  strchr(desc, '(') ? "regprocedure" : "regproc");
-	if (!lookup_function_echo_hidden(query->data))
+	if (!lookup_object_echo_hidden(query->data))
 	{
 		destroyPQExpBuffer(query);
 		return false;
 	}
+
 	res = PQexec(pset.db, query->data);
 	if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
-		*foid = atooid(PQgetvalue(res, 0, 0));
+		*obj_oid = atooid(PQgetvalue(res, 0, 0));
 	else
 	{
 		minimal_error_message(res);
@@ -3067,24 +3169,58 @@ lookup_function_oid(const char *desc, Oid *foid)
 }
 
 /*
- * Fetches the "CREATE OR REPLACE FUNCTION ..." command that describes the
- * function with the given OID.  If successful, the result is stored in buf.
+ * 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 returned at *foid.  Note that we'll fail if the
+ * function doesn't exist OR if there are multiple matching candidates
+ * OR if there's something syntactically wrong with the function description;
+ * unfortunately it can be hard to tell the difference.
  */
 static bool
-get_create_function_cmd(Oid oid, PQExpBuffer buf)
+lookup_function_oid(const char *desc, Oid *foid)
 {
-	bool		result = true;
 	PQExpBuffer query;
-	PGresult   *res;
 
 	query = createPQExpBuffer();
-	printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_functiondef(%u)", oid);
+	appendPQExpBufferStr(query, "SELECT ");
+	appendStringLiteralConn(query, desc, pset.db);
+	appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
+					  strchr(desc, '(') ? "regprocedure" : "regproc");
+
+	return lookup_object_oid_internal(query, foid);
+}
+
+static bool
+lookup_view_oid(const char *desc, Oid *view_oid)
+{
+	PQExpBuffer query;
+
+	query = createPQExpBuffer();
+	appendPQExpBufferStr(query, "SELECT ");
+	appendStringLiteralConn(query, desc, pset.db);
+	appendPQExpBuffer(query, "::pg_catalog.regclass::pg_catalog.oid");
+
+	return lookup_object_oid_internal(query, view_oid);
+}
+
+/*
+ * Fetches the "CREATE ..." command that describes the
+ * database object by given query.
+ * If successful, the result is stored in buf.
+ */
+static bool
+get_create_object_cmd_internal(PQExpBuffer query, PQExpBuffer buf)
+{
+	bool		result = true;
+	PGresult   *res;
 
-	if (!lookup_function_echo_hidden(query->data))
+	if (!lookup_object_echo_hidden(query->data))
 	{
 		destroyPQExpBuffer(query);
 		return false;
 	}
+
 	res = PQexec(pset.db, query->data);
 	if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
 	{
@@ -3104,6 +3240,57 @@ get_create_function_cmd(Oid oid, PQExpBuffer buf)
 }
 
 /*
+ * Fetches the "CREATE OR REPLACE FUNCTION ..." command that describes the
+ * function with the given OID.  If successful, the result is stored in buf.
+ */
+static bool
+get_create_function_cmd(Oid oid, PQExpBuffer buf)
+{
+	PQExpBuffer query;
+
+	query = createPQExpBuffer();
+	printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_functiondef(%u)", oid);
+
+	return get_create_object_cmd_internal(query, buf);
+}
+
+/*
+ * Fetches a view definition that describes the
+ * view with the given OID.  If successful, the result is stored in buf.
+ */
+static bool
+get_create_view_cmd(Oid oid, PQExpBuffer buf)
+{
+	PQExpBuffer query;
+
+	query = createPQExpBuffer();
+	printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_viewdef(%u, true)", oid);
+
+	return get_create_object_cmd_internal(query, buf);
+}
+
+/*
+ * Unfortunately pg_get_viewdef() doesn't return "CREATE OR REPLACE"
+ * statement prefix.
+ * We need to format "CREATE" statement manually.
+ * Originally allocated buffer contents will be replaced with formatted one.
+ */
+static void
+format_create_view_cmd(char *view, PQExpBuffer buf)
+{
+	PQExpBuffer t = createPQExpBuffer();
+	printfPQExpBuffer(t,
+					  "CREATE OR REPLACE VIEW %s AS\n%s\n",
+					  view,
+					  buf->data);
+
+	resetPQExpBuffer(buf);
+	printfPQExpBuffer(buf, "%s", t->data);
+
+	destroyPQExpBuffer(t);
+}
+
+/*
  * If the given argument of \ef ends with a line number, delete the line
  * number from the argument string and return it as an integer.  (We need
  * this kluge because we're too lazy to parse \ef's function name argument
@@ -3113,7 +3300,7 @@ get_create_function_cmd(Oid oid, PQExpBuffer buf)
  * on success.
  */
 static int
-strip_lineno_from_funcdesc(char *func)
+strip_lineno_from_objdesc(char *func)
 {
 	char	   *c;
 	int			lineno;
@@ -3194,3 +3381,60 @@ minimal_error_message(PGresult *res)
 
 	destroyPQExpBuffer(msg);
 }
+
+static int
+count_lines_in_buf(PQExpBuffer buf)
+{
+	int			lineno = 0;
+	const char *lines = buf->data;
+
+	while (*lines != '\0')
+	{
+		lineno++;
+		/* find start of next line */
+		lines = strchr(lines, '\n');
+		if (!lines)
+			break;
+		lines++;
+	}
+
+	return lineno;
+}
+
+static void
+print_with_linenumbers(FILE *output,
+					   char *lines,
+					   const char *header_cmp_keyword,
+					   size_t header_cmp_sz)
+{
+	bool		in_header = true;
+	int			lineno = 0;
+
+	while (*lines != '\0')
+	{
+		char	   *eol;
+
+		if (in_header && strncmp(lines, header_cmp_keyword, header_cmp_sz) == 0)
+			in_header = false;
+
+		/* increment lineno only for body's lines */
+		if (!in_header)
+			lineno++;
+
+		/* find and mark end of current line */
+		eol = strchr(lines, '\n');
+		if (eol != NULL)
+			*eol = '\0';
+
+		/* show current line as appropriate */
+		if (in_header)
+			fprintf(output, "        %s\n", lines);
+		else
+			fprintf(output, "%-7d %s\n", lineno, lines);
+
+		/* advance to next line, if any */
+		if (eol == NULL)
+			break;
+		lines = ++eol;
+	}
+}
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index b523054..da9b954 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -181,6 +181,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("Query Buffer\n"));
 	fprintf(output, _("  \\e [FILE] [LINE]       edit the query buffer (or file) with external editor\n"));
 	fprintf(output, _("  \\ef [FUNCNAME [LINE]]  edit function definition with external editor\n"));
+	fprintf(output, _("  \\ev [VIEWNAME [LINE]]  edit view definition with external editor\n"));
 	fprintf(output, _("  \\p                     show the contents of the query buffer\n"));
 	fprintf(output, _("  \\r                     reset (clear) the query buffer\n"));
 #ifdef USE_READLINE
@@ -238,6 +239,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\dy     [PATTERN]      list event triggers\n"));
 	fprintf(output, _("  \\l[+]   [PATTERN]      list databases\n"));
 	fprintf(output, _("  \\sf[+] FUNCNAME        show a function's definition\n"));
+	fprintf(output, _("  \\sv[+] VIEWNAME        show a view's definition\n"));
 	fprintf(output, _("  \\z      [PATTERN]      same as \\dp\n"));
 	fprintf(output, "\n");
 
@@ -388,7 +390,7 @@ helpVariables(unsigned short int pager)
 	fprintf(output, _("  PGPASSWORD         connection password (not recommended)\n"));
 	fprintf(output, _("  PGPASSFILE         password file name\n"));
 	fprintf(output, _("  PSQL_EDITOR, EDITOR, VISUAL\n"
-		 "                     editor used by the \\e and \\ef commands\n"));
+		 "                     editor used by the \\e, \\ef, and \\ev commands\n"));
 	fprintf(output, _("  PSQL_EDITOR_LINENUMBER_ARG\n"
 					  "                     how to specify a line number when invoking the editor\n"));
 	fprintf(output, _("  PSQL_HISTORY       alternative location for the command history file\n"));
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b9f5acc..b1209da 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -895,11 +895,11 @@ psql_completion(const char *text, int start, int end)
 		"\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\dD", "\\des", "\\det", "\\deu", "\\dew", "\\df",
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
 		"\\dn", "\\do", "\\dp", "\\drds", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du", "\\dx",
-		"\\e", "\\echo", "\\ef", "\\encoding",
+		"\\e", "\\echo", "\\ef", "\\ev", "\\encoding",
 		"\\f", "\\g", "\\gset", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
 		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
 		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
-		"\\set", "\\sf", "\\t", "\\T",
+		"\\set", "\\sf", "\\sf+", "\\sv", "\\sv+", "\\t", "\\T",
 		"\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL
 	};
 
@@ -3784,6 +3784,8 @@ psql_completion(const char *text, int start, int end)
 
 	else if (strcmp(prev_wd, "\\ef") == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+	else if (strcmp(prev_wd, "\\ev") == 0)
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
 	else if (strcmp(prev_wd, "\\encoding") == 0)
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
@@ -3898,6 +3900,8 @@ psql_completion(const char *text, int start, int end)
 	}
 	else if (strcmp(prev_wd, "\\sf") == 0 || strcmp(prev_wd, "\\sf+") == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+	else if (strcmp(prev_wd, "\\sv") == 0 || strcmp(prev_wd, "\\sv+") == 0)
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 	else if (strcmp(prev_wd, "\\cd") == 0 ||
 			 strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 0 ||
 			 strcmp(prev_wd, "\\g") == 0 ||
#7Jeevan Chalke
jeevan.chalke@gmail.com
In reply to: Petr Korobeinikov (#6)
Re: psql :: support for \ev viewname and \sv viewname

The following review has been posted through the commitfest application:
make installcheck-world: tested, failed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: tested, failed

I have reviewed this patch. Most of the code is just rearranged.
Since this is based upon, \ef and \sf, code is almost similar.

However hare are my review comments:

1.
make failed with docs

2.

\ev vw1 3

This syntax is supported. But documentation only says:
\ev [ viewname ]
Missing optional line_number clause

3.

strip_lineno_from_objdesc(char *func)

Can we have parameter name as obj instead of func.
You have renamed the function name, as it is now called in case of views as
well. Better rename the parameter names as well.

4.
Also please update the comments above strip_lineno_from_objdesc().
It is specific to functions which is not the case now.

5.

print_with_linenumbers(FILE *output,
char *lines,
const char *header_cmp_keyword,
size_t header_cmp_sz)

Can't we calculate the length of header (header_cmp_sz) inside function?
This will avoid any sloppy changes like, change in the keyword but forgot to
change the size.
Lets just accept the keyword and calculate the size within the function.

6.

*
* Note that this loop scribbles on func_buf.
*/

These lines at commands.c:1357, looks NO more valid now as there is NO loop
there.

7.
I see few comment lines explaining which is line 1 in case of function, for
which "AS " is used. Similarly, for view "SELECT " is used.
Can you add similar kind of explanation there?

8.

get_create_object_cmd_internal
get_create_function_cmd
get_create_view_cmd

Can these three functions grouped together in just get_create_object_cmd().
This function will take an extra parameter to indicate the object type.
Say O_FUNC and O_VIEW for example.

For distinct part, just have a switch case over this type.

This will add a flexibility that if we add another such \e and \s options, we
don't need new functions, rather just need new enum like O_new and a new case
in this switch statement.
Also it will look good to read the code as well.

similarly you can do it for

lookup_object_oid_internal
get_create_function_cmd
lookup_function_oid

9.

static int count_lines_in_buf(PQExpBuffer buf)
static void print_with_linenumbers(FILE *output, .. )
static bool lookup_view_oid(const char *desc, Oid *view_oid)
static bool lookup_object_oid_internal(PQExpBuffer query, Oid *obj_oid)

Can we have smaller description, explaining what's the function doing for
these functions at the definition?

10.

+ "\\e", "\\echo", "\\ef", "\\ev", "\\encoding",

Can you keep this sorted?
It will be good if it sorted, but I see no such restriction as I see few out
of order options. But better keep it ordered.
Ignore if you dis-agree.

The new status of this patch is: Waiting on Author

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Petr Korobeinikov
pkorobeinikov@gmail.com
In reply to: Jeevan Chalke (#7)
1 attachment(s)
Re: psql :: support for \ev viewname and \sv viewname

1.
make failed with docs

Fixed.

2.

\ev vw1 3

This syntax is supported. But documentation only says:
\ev [ viewname ]
Missing optional line_number clause

Fixed. Documented.

3.

strip_lineno_from_objdesc(char *func)

Can we have parameter name as obj instead of func.
You have renamed the function name, as it is now called in case of views as
well. Better rename the parameter names as well.

Renamed.

4.

Also please update the comments above strip_lineno_from_objdesc().
It is specific to functions which is not the case now.

Comments updated.

5.

print_with_linenumbers(FILE *output,
char *lines,
const char *header_cmp_keyword,
size_t header_cmp_sz)

Can't we calculate the length of header (header_cmp_sz) inside function?
This will avoid any sloppy changes like, change in the keyword but forgot
to
change the size.
Lets just accept the keyword and calculate the size within the function.

Now header_cmp_sz calculated inside function.

6.

*
* Note that this loop scribbles on

func_buf.

*/

These lines at commands.c:1357, looks NO more valid now as there is NO loop
there.

Removed.

7.
I see few comment lines explaining which is line 1 in case of function, for
which "AS " is used. Similarly, for view "SELECT " is used.
Can you add similar kind of explanation there?

Explanation added.

8.

get_create_object_cmd_internal
get_create_function_cmd
get_create_view_cmd

Can these three functions grouped together in just get_create_object_cmd().
This function will take an extra parameter to indicate the object type.
Say O_FUNC and O_VIEW for example.

For distinct part, just have a switch case over this type.

This will add a flexibility that if we add another such \e and \s options,
we
don't need new functions, rather just need new enum like O_new and a new
case
in this switch statement.
Also it will look good to read the code as well.

similarly you can do it for

lookup_object_oid_internal
get_create_function_cmd
lookup_function_oid

Reworked.
New enum PgObjType introduced.

9.

static int count_lines_in_buf(PQExpBuffer buf)
static void print_with_linenumbers(FILE *output, .. )
static bool lookup_view_oid(const char *desc, Oid *view_oid)
static bool lookup_object_oid_internal(PQExpBuffer query, Oid *obj_oid)

Can we have smaller description, explaining what's the function doing for
these functions at the definition?

Description added.

10.

+ "\\e", "\\echo", "\\ef", "\\ev", "\\encoding",

Can you keep this sorted?
It will be good if it sorted, but I see no such restriction as I see few
out
of order options. But better keep it ordered.
Ignore if you dis-agree.

Hmm, sorted now.
Sort is based on my feelings.

Attachments:

psql-ev-sv-support-v4.diffapplication/octet-stream; name=psql-ev-sv-support-v4.diffDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 62a3b21..e1bbb81 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1651,6 +1651,34 @@ Tue Oct 26 21:40:57 CEST 1999
 
 
       <varlistentry>
+        <term><literal>\ev <optional> <replaceable class="parameter">viewname</> <optional>  <replaceable class="parameter">line_number</> </optional> </optional> </literal></term>
+
+        <listitem>
+        <para>
+         This command fetches and edits the definition of the named view,
+         in the form of a <command>CREATE OR REPLACE VIEW</> command.
+         Editing is done in the same way as for <literal>\edit</>.
+         After the editor exits, the updated command waits in the query buffer;
+         type semicolon or <literal>\g</> to send it, or <literal>\r</>
+         to cancel.
+        </para>
+
+        <para>
+         If no view is specified, a blank <command>CREATE VEIW</>
+         template is presented for editing.
+        </para>
+
+        <para>
+         If a line number is specified, <application>psql</application> will
+         position the cursor on the specified line of the view definition.
+         (Note that the view definition typically does not begin on the first
+         line of the file.)
+        </para>
+        </listitem>
+      </varlistentry>
+
+
+      <varlistentry>
         <term><literal>\encoding [ <replaceable class="parameter">encoding</replaceable> ]</literal></term>
 
         <listitem>
@@ -2522,6 +2550,26 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
 
 
       <varlistentry>
+        <term><literal>\sv[+] <replaceable class="parameter">viewname</> </literal></term>
+
+        <listitem>
+         <para>
+          This command fetches and shows the definition of the named view,
+          in the form of a <command>CREATE OR REPLACE VIEW</> command.
+          The definition is printed to the current query output channel,
+          as set by <command>\o</command>.
+         </para>
+
+         <para>
+          If <literal>+</literal> is appended to the command name, then the
+          output lines are numbered, with the first line of the view definition
+          being line 1.
+         </para>
+        </listitem>
+      </varlistentry>
+
+
+      <varlistentry>
         <term><literal>\t</literal></term>
         <listitem>
         <para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 38253fa..71a2dfd 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -49,6 +49,16 @@
 #include "settings.h"
 #include "variables.h"
 
+/*
+ * Editable database object types.
+ * Currently functions and views are supported.
+ */
+typedef enum PgObjType
+{
+	PgObjTypeFunction,
+	PgObjTypeView
+	/* add new editable object type here */
+} PgObjType;
 
 /* functions for use in this file */
 static backslashResult exec_command(const char *cmd,
@@ -59,10 +69,17 @@ 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);
 static bool do_watch(PQExpBuffer query_buf, long sleep);
-static bool lookup_function_oid(const char *desc, Oid *foid);
-static bool get_create_function_cmd(Oid oid, PQExpBuffer buf);
-static int	strip_lineno_from_funcdesc(char *func);
+static bool lookup_object_oid(const char *desc,
+							  PgObjType obj_type,
+							  Oid *obj_oid);
+static bool get_create_object_cmd(Oid oid, PQExpBuffer buf, PgObjType type);
+static void format_create_view_cmd(char *view, PQExpBuffer buf);
+static int	strip_lineno_from_objdesc(char *obj);
 static void minimal_error_message(PGresult *res);
+static int count_lines_in_buf(PQExpBuffer buf);
+static void print_with_linenumbers(FILE *output,
+								   char *lines,
+								   const char *header_cmp_keyword);
 
 static void printSSLInfo(void);
 static bool printPsetInfo(const char *param, struct printQueryOpt *popt);
@@ -612,7 +629,7 @@ exec_command(const char *cmd,
 
 			func = psql_scan_slash_option(scan_state,
 										  OT_WHOLE_LINE, NULL, true);
-			lineno = strip_lineno_from_funcdesc(func);
+			lineno = strip_lineno_from_objdesc(func);
 			if (lineno == 0)
 			{
 				/* error already reported */
@@ -629,12 +646,12 @@ exec_command(const char *cmd,
 								  "AS $function$\n"
 								  "\n$function$\n");
 			}
-			else if (!lookup_function_oid(func, &foid))
+			else if (!lookup_object_oid(func, PgObjTypeFunction, &foid))
 			{
 				/* error already reported */
 				status = PSQL_CMD_ERROR;
 			}
-			else if (!get_create_function_cmd(foid, query_buf))
+			else if (!get_create_object_cmd(foid, query_buf, PgObjTypeFunction))
 			{
 				/* error already reported */
 				status = PSQL_CMD_ERROR;
@@ -682,6 +699,108 @@ exec_command(const char *cmd,
 		}
 	}
 
+	/*
+	 * \ev -- edit the named view, or present a blank CREATE VIEW viewname AS
+	 * template if no argument is given
+	 */
+	else if (strcmp(cmd, "ev") == 0)
+	{
+		int			lineno = -1;
+
+		if (pset.sversion < 70100)
+		{
+			psql_error("The server (version %d.%d) does not support editing view definition.\n",
+					   pset.sversion / 10000, (pset.sversion / 100) % 100);
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!query_buf)
+		{
+			psql_error("no query buffer\n");
+			status = PSQL_CMD_ERROR;
+		}
+		else
+		{
+			char	   *view;
+			Oid			view_oid = InvalidOid;
+
+			view = psql_scan_slash_option(scan_state,
+										  OT_WHOLE_LINE, NULL, true);
+			lineno = strip_lineno_from_objdesc(view);
+
+			if (lineno == 0)
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			if (!view)
+			{
+				/* set up an empty command to fill in */
+				printfPQExpBuffer(query_buf,
+								  "CREATE VIEW viewname AS \n"
+								  " SELECT \n"
+								  "  -- something... \n");
+			}
+			else if (!lookup_object_oid(view, PgObjTypeView, &view_oid))
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			else if (!get_create_object_cmd(view_oid, query_buf, PgObjTypeView))
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			else if (lineno > 0)
+			{
+				/*
+				 * lineno "1" should correspond to the first line of the
+				 * view definition.  We expect that pg_get_viewdef() will
+				 * emit that on a line beginning with " SELECT", and that there
+				 * can be no such line before the real start of the view
+				 * definition.  Increment lineno by the number of lines before that
+				 * line, so that it becomes relative to the first line of the
+				 * view definition.
+				 */
+				const char *lines = query_buf->data;
+
+				while (*lines != '\0')
+				{
+					if (strncmp(lines, " SELECT", 7) == 0)
+						break;
+					lineno++;
+					/* find start of next line */
+					lines = strchr(lines, '\n');
+					if (!lines)
+						break;
+					lines++;
+				}
+			}
+
+			if (view) {
+				format_create_view_cmd(view, query_buf);
+				free(view);
+
+				/*
+				 * format_create_view_cmd() adds one extra line
+				 * so we need to compensate it.
+				 */
+				lineno++;
+			}
+		}
+
+		if (status != PSQL_CMD_ERROR)
+		{
+			bool		edited = false;
+
+			if (!do_edit(NULL, query_buf, lineno, &edited))
+				status = PSQL_CMD_ERROR;
+			else if (!edited)
+				puts(_("No changes"));
+			else
+				status = PSQL_CMD_NEWEDIT;
+		}
+	}
+
 	/* \echo and \qecho */
 	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
 	{
@@ -1235,12 +1354,12 @@ exec_command(const char *cmd,
 			psql_error("function name is required\n");
 			status = PSQL_CMD_ERROR;
 		}
-		else if (!lookup_function_oid(func, &foid))
+		else if (!lookup_object_oid(func, PgObjTypeFunction, &foid))
 		{
 			/* error already reported */
 			status = PSQL_CMD_ERROR;
 		}
-		else if (!get_create_function_cmd(foid, func_buf))
+		else if (!get_create_object_cmd(foid, func_buf, PgObjTypeFunction))
 		{
 			/* error already reported */
 			status = PSQL_CMD_ERROR;
@@ -1254,18 +1373,7 @@ exec_command(const char *cmd,
 			if (pset.queryFout == stdout)
 			{
 				/* count lines in function to see if pager is needed */
-				int			lineno = 0;
-				const char *lines = func_buf->data;
-
-				while (*lines != '\0')
-				{
-					lineno++;
-					/* find start of next line */
-					lines = strchr(lines, '\n');
-					if (!lines)
-						break;
-					lines++;
-				}
+				int lineno = count_lines_in_buf(func_buf);
 
 				output = PageOutput(lineno, &(pset.popt.topt));
 				is_pager = true;
@@ -1279,45 +1387,16 @@ exec_command(const char *cmd,
 
 			if (show_linenumbers)
 			{
-				bool		in_header = true;
-				int			lineno = 0;
-				char	   *lines = func_buf->data;
-
 				/*
 				 * lineno "1" should correspond to the first line of the
 				 * function body.  We expect that pg_get_functiondef() will
 				 * emit that on a line beginning with "AS ", and that there
 				 * can be no such line before the real start of the function
 				 * body.
-				 *
-				 * Note that this loop scribbles on func_buf.
 				 */
-				while (*lines != '\0')
-				{
-					char	   *eol;
-
-					if (in_header && strncmp(lines, "AS ", 3) == 0)
-						in_header = false;
-					/* increment lineno only for body's lines */
-					if (!in_header)
-						lineno++;
-
-					/* find and mark end of current line */
-					eol = strchr(lines, '\n');
-					if (eol != NULL)
-						*eol = '\0';
-
-					/* show current line as appropriate */
-					if (in_header)
-						fprintf(output, "        %s\n", lines);
-					else
-						fprintf(output, "%-7d %s\n", lineno, lines);
-
-					/* advance to next line, if any */
-					if (eol == NULL)
-						break;
-					lines = ++eol;
-				}
+
+				char *lines = func_buf->data;
+				print_with_linenumbers(output, lines, "AS ");
 			}
 			else
 			{
@@ -1334,6 +1413,89 @@ exec_command(const char *cmd,
 		destroyPQExpBuffer(func_buf);
 	}
 
+	/* \sv -- show a view's source code */
+	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+	{
+		bool		show_linenumbers = (strcmp(cmd, "sv+") == 0);
+		PQExpBuffer view_buf;
+		char	   *view;
+		Oid			view_oid = InvalidOid;
+
+		view_buf = createPQExpBuffer();
+		view = psql_scan_slash_option(scan_state,
+									  OT_WHOLE_LINE, NULL, true);
+
+		if (pset.sversion < 70100)
+		{
+			psql_error("The server (version %d.%d) does not support showing view definition.\n",
+					   pset.sversion / 10000, (pset.sversion / 100) % 100);
+			status = PSQL_CMD_ERROR;
+		}
+		if (!view)
+		{
+			psql_error("view name is required\n");
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!lookup_object_oid(view, PgObjTypeView, &view_oid))
+		{
+			/* error already reported */
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!get_create_object_cmd(view_oid, view_buf, PgObjTypeView))
+		{
+			/* error already reported */
+			status = PSQL_CMD_ERROR;
+		}
+		else
+		{
+			FILE *output;
+			bool is_pager;
+
+			format_create_view_cmd(view, view_buf);
+
+			if (pset.queryFout == stdout)
+			{
+				/* count lines in view to see if pager is needed */
+				int lineno = count_lines_in_buf(view_buf);
+
+				output = PageOutput(lineno, &(pset.popt.topt));
+				is_pager = true;
+			}
+			else
+			{
+				/* use previously set output file, without pager */
+				output = pset.queryFout;
+				is_pager = false;
+			}
+
+			if (show_linenumbers)
+			{
+				/*
+				 * lineno "1" should correspond to the first line of the
+				 * view definition.  We expect that pg_get_viewdef() will
+				 * emit that on a line beginning with " SELECT", and that there
+				 * can be no such line before the real start of the view
+				 * definition.
+				 */
+
+				char	   *lines = view_buf->data;
+				print_with_linenumbers(output, lines, " SELECT");
+			}
+			else
+			{
+				/* just send the function definition to output */
+				fputs(view_buf->data, output);
+			}
+
+			if (is_pager)
+				ClosePager(output);
+		}
+
+		if (view)
+			free(view);
+		destroyPQExpBuffer(view_buf);
+	}
+
 	/* \t -- turn off headers and row count */
 	else if (strcmp(cmd, "t") == 0)
 	{
@@ -3002,7 +3164,7 @@ do_watch(PQExpBuffer query_buf, long sleep)
  * returns true unless we have ECHO_HIDDEN_NOEXEC.
  */
 static bool
-lookup_function_echo_hidden(char *query)
+lookup_object_echo_hidden(char *query)
 {
 	if (pset.echo_hidden != PSQL_ECHO_HIDDEN_OFF)
 	{
@@ -3025,35 +3187,51 @@ lookup_function_echo_hidden(char *query)
 	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 returned at *foid.  Note that we'll fail if the
- * function doesn't exist OR if there are multiple matching candidates
- * OR if there's something syntactically wrong with the function description;
- * unfortunately it can be hard to tell the difference.
- */
 static bool
-lookup_function_oid(const char *desc, Oid *foid)
+lookup_object_oid(const char *desc, PgObjType obj_type, Oid *obj_oid)
 {
+	PQExpBuffer query = createPQExpBuffer();
+
 	bool		result = true;
-	PQExpBuffer query;
 	PGresult   *res;
 
-	query = createPQExpBuffer();
-	appendPQExpBufferStr(query, "SELECT ");
-	appendStringLiteralConn(query, desc, pset.db);
-	appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
-					  strchr(desc, '(') ? "regprocedure" : "regproc");
-	if (!lookup_function_echo_hidden(query->data))
+	if (!lookup_object_echo_hidden(query->data))
 	{
 		destroyPQExpBuffer(query);
 		return false;
 	}
+
+	switch (obj_type)
+	{
+		/*
+		 * We got a function description, e.g. "x" or "x(int)", and
+		 * issue 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 returned at *foid.  Note that we'll fail if the
+		 * function doesn't exist OR if there are multiple matching candidates
+		 * OR if there's something syntactically wrong with the function
+		 * description; unfortunately it can be hard to tell the difference.
+		 */
+		case PgObjTypeFunction:
+			appendPQExpBufferStr(query, "SELECT ");
+			appendStringLiteralConn(query, desc, pset.db);
+			appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
+							  strchr(desc, '(') ? "regprocedure" : "regproc");
+			break;
+
+		/*
+		 * Convert viewname to corresponding oid.
+		 */
+		case PgObjTypeView:
+			appendPQExpBufferStr(query, "SELECT ");
+			appendStringLiteralConn(query, desc, pset.db);
+			appendPQExpBuffer(query, "::pg_catalog.regclass::pg_catalog.oid");
+			break;
+	}
+
 	res = PQexec(pset.db, query->data);
 	if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
-		*foid = atooid(PQgetvalue(res, 0, 0));
+		*obj_oid = atooid(PQgetvalue(res, 0, 0));
 	else
 	{
 		minimal_error_message(res);
@@ -3067,24 +3245,48 @@ lookup_function_oid(const char *desc, Oid *foid)
 }
 
 /*
- * Fetches the "CREATE OR REPLACE FUNCTION ..." command that describes the
- * function with the given OID.  If successful, the result is stored in buf.
+ * Fetches the "CREATE ..." command that describes the
+ * database object by given query.
+ * If successful, the result is stored in buf.
  */
 static bool
-get_create_function_cmd(Oid oid, PQExpBuffer buf)
+get_create_object_cmd(Oid oid, PQExpBuffer buf, PgObjType type)
 {
+	PQExpBuffer query = createPQExpBuffer();
+
 	bool		result = true;
-	PQExpBuffer query;
 	PGresult   *res;
 
-	query = createPQExpBuffer();
-	printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_functiondef(%u)", oid);
-
-	if (!lookup_function_echo_hidden(query->data))
+	if (!lookup_object_echo_hidden(query->data))
 	{
 		destroyPQExpBuffer(query);
 		return false;
 	}
+
+	switch (type)
+	{
+		/*
+		 * Fetch the "CREATE OR REPLACE FUNCTION ..." command that describes
+		 * the function with the given OID.
+		 * If successful, the result is stored in buf.
+		 */
+		case PgObjTypeFunction:
+			printfPQExpBuffer(query,
+							  "SELECT pg_catalog.pg_get_functiondef(%u)",
+							  oid);
+			break;
+
+		/*
+		 * Fetch a view definition that describes the view with the given OID.
+		 * If successful, the result is stored in buf.
+		 */
+		case PgObjTypeView:
+			printfPQExpBuffer(query,
+							  "SELECT pg_catalog.pg_get_viewdef(%u, true)",
+							  oid);
+			break;
+	}
+
 	res = PQexec(pset.db, query->data);
 	if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
 	{
@@ -3104,24 +3306,45 @@ get_create_function_cmd(Oid oid, PQExpBuffer buf)
 }
 
 /*
- * If the given argument of \ef ends with a line number, delete the line
+ * Unfortunately pg_get_viewdef() doesn't return "CREATE OR REPLACE"
+ * statement prefix.
+ * We need to format "CREATE" statement manually.
+ * Originally allocated buffer contents will be replaced with formatted one.
+ */
+static void
+format_create_view_cmd(char *view, PQExpBuffer buf)
+{
+	PQExpBuffer t = createPQExpBuffer();
+	printfPQExpBuffer(t,
+					  "CREATE OR REPLACE VIEW %s AS\n%s\n",
+					  view,
+					  buf->data);
+
+	resetPQExpBuffer(buf);
+	printfPQExpBuffer(buf, "%s", t->data);
+
+	destroyPQExpBuffer(t);
+}
+
+/*
+ * If the given argument of \ef or \ev ends with a line number, delete the line
  * number from the argument string and return it as an integer.  (We need
- * this kluge because we're too lazy to parse \ef's function name argument
- * carefully --- we just slop it up in OT_WHOLE_LINE mode.)
+ * this kluge because we're too lazy to parse \ef's function or \ev's view
+ * argument carefully --- we just slop it up in OT_WHOLE_LINE mode.)
  *
  * Returns -1 if no line number is present, 0 on error, or a positive value
  * on success.
  */
 static int
-strip_lineno_from_funcdesc(char *func)
+strip_lineno_from_objdesc(char *obj)
 {
 	char	   *c;
 	int			lineno;
 
-	if (!func || func[0] == '\0')
+	if (!obj || obj[0] == '\0')
 		return -1;
 
-	c = func + strlen(func) - 1;
+	c = obj + strlen(obj) - 1;
 
 	/*
 	 * This business of parsing backwards is dangerous as can be in a
@@ -3134,20 +3357,20 @@ strip_lineno_from_funcdesc(char *func)
 	 */
 
 	/* skip trailing whitespace */
-	while (c > func && isascii((unsigned char) *c) && isspace((unsigned char) *c))
+	while (c > obj && isascii((unsigned char) *c) && isspace((unsigned char) *c))
 		c--;
 
 	/* must have a digit as last non-space char */
-	if (c == func || !isascii((unsigned char) *c) || !isdigit((unsigned char) *c))
+	if (c == obj || !isascii((unsigned char) *c) || !isdigit((unsigned char) *c))
 		return -1;
 
 	/* find start of digit string */
-	while (c > func && isascii((unsigned char) *c) && isdigit((unsigned char) *c))
+	while (c > obj && isascii((unsigned char) *c) && isdigit((unsigned char) *c))
 		c--;
 
 	/* digits must be separated from func name by space or closing paren */
 	/* notice also that we are not allowing an empty func name ... */
-	if (c == func || !isascii((unsigned char) *c) ||
+	if (c == obj || !isascii((unsigned char) *c) ||
 		!(isspace((unsigned char) *c) || *c == ')'))
 		return -1;
 
@@ -3194,3 +3417,69 @@ minimal_error_message(PGresult *res)
 
 	destroyPQExpBuffer(msg);
 }
+
+/*
+ * Count number of lines in the buffer.
+ * It is used to test that pager is needed or not.
+ */
+static int
+count_lines_in_buf(PQExpBuffer buf)
+{
+	int			lineno = 0;
+	const char *lines = buf->data;
+
+	while (*lines != '\0')
+	{
+		lineno++;
+		/* find start of next line */
+		lines = strchr(lines, '\n');
+		if (!lines)
+			break;
+		lines++;
+	}
+
+	return lineno;
+}
+
+/*
+ * Produce line-numbered output of object's definition.
+ * Note that the object definition does not begin on the first line.
+ */
+static void
+print_with_linenumbers(FILE *output,
+					   char *lines,
+					   const char *header_cmp_keyword)
+{
+	bool		in_header = true;
+	int			lineno = 0;
+
+	size_t header_cmp_sz = strlen(header_cmp_keyword);
+
+	while (*lines != '\0')
+	{
+		char	   *eol;
+
+		if (in_header && strncmp(lines, header_cmp_keyword, header_cmp_sz) == 0)
+			in_header = false;
+
+		/* increment lineno only for body's lines */
+		if (!in_header)
+			lineno++;
+
+		/* find and mark end of current line */
+		eol = strchr(lines, '\n');
+		if (eol != NULL)
+			*eol = '\0';
+
+		/* show current line as appropriate */
+		if (in_header)
+			fprintf(output, "        %s\n", lines);
+		else
+			fprintf(output, "%-7d %s\n", lineno, lines);
+
+		/* advance to next line, if any */
+		if (eol == NULL)
+			break;
+		lines = ++eol;
+	}
+}
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index b523054..da9b954 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -181,6 +181,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("Query Buffer\n"));
 	fprintf(output, _("  \\e [FILE] [LINE]       edit the query buffer (or file) with external editor\n"));
 	fprintf(output, _("  \\ef [FUNCNAME [LINE]]  edit function definition with external editor\n"));
+	fprintf(output, _("  \\ev [VIEWNAME [LINE]]  edit view definition with external editor\n"));
 	fprintf(output, _("  \\p                     show the contents of the query buffer\n"));
 	fprintf(output, _("  \\r                     reset (clear) the query buffer\n"));
 #ifdef USE_READLINE
@@ -238,6 +239,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\dy     [PATTERN]      list event triggers\n"));
 	fprintf(output, _("  \\l[+]   [PATTERN]      list databases\n"));
 	fprintf(output, _("  \\sf[+] FUNCNAME        show a function's definition\n"));
+	fprintf(output, _("  \\sv[+] VIEWNAME        show a view's definition\n"));
 	fprintf(output, _("  \\z      [PATTERN]      same as \\dp\n"));
 	fprintf(output, "\n");
 
@@ -388,7 +390,7 @@ helpVariables(unsigned short int pager)
 	fprintf(output, _("  PGPASSWORD         connection password (not recommended)\n"));
 	fprintf(output, _("  PGPASSFILE         password file name\n"));
 	fprintf(output, _("  PSQL_EDITOR, EDITOR, VISUAL\n"
-		 "                     editor used by the \\e and \\ef commands\n"));
+		 "                     editor used by the \\e, \\ef, and \\ev commands\n"));
 	fprintf(output, _("  PSQL_EDITOR_LINENUMBER_ARG\n"
 					  "                     how to specify a line number when invoking the editor\n"));
 	fprintf(output, _("  PSQL_HISTORY       alternative location for the command history file\n"));
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b9f5acc..d4a1c1f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -895,11 +895,11 @@ psql_completion(const char *text, int start, int end)
 		"\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\dD", "\\des", "\\det", "\\deu", "\\dew", "\\df",
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
 		"\\dn", "\\do", "\\dp", "\\drds", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du", "\\dx",
-		"\\e", "\\echo", "\\ef", "\\encoding",
+		"\\e", "\\ef", "\\ev", "\\echo", "\\encoding",
 		"\\f", "\\g", "\\gset", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
 		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
 		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
-		"\\set", "\\sf", "\\t", "\\T",
+		"\\set", "\\sf", "\\sf+", "\\sv", "\\sv+", "\\t", "\\T",
 		"\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL
 	};
 
@@ -3784,6 +3784,8 @@ psql_completion(const char *text, int start, int end)
 
 	else if (strcmp(prev_wd, "\\ef") == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+	else if (strcmp(prev_wd, "\\ev") == 0)
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
 	else if (strcmp(prev_wd, "\\encoding") == 0)
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
@@ -3898,6 +3900,8 @@ psql_completion(const char *text, int start, int end)
 	}
 	else if (strcmp(prev_wd, "\\sf") == 0 || strcmp(prev_wd, "\\sf+") == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+	else if (strcmp(prev_wd, "\\sv") == 0 || strcmp(prev_wd, "\\sv+") == 0)
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 	else if (strcmp(prev_wd, "\\cd") == 0 ||
 			 strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 0 ||
 			 strcmp(prev_wd, "\\g") == 0 ||
#9Jeevan Chalke
jeevan.chalke@enterprisedb.com
In reply to: Petr Korobeinikov (#8)
1 attachment(s)
Re: psql :: support for \ev viewname and \sv viewname

Hi

Patch looks excellent now. No issues.

Found a typo which I have fixed in the attached patch.

Thanks

--
Jeevan B Chalke
Principal Software Engineer, Product Development
EnterpriseDB Corporation
The Enterprise PostgreSQL Company

Attachments:

psql-ev-sv-support-v5-Jeevan.difftext/plain; charset=US-ASCII; name=psql-ev-sv-support-v5-Jeevan.diffDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 62a3b21..4467b8c 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1651,6 +1651,34 @@ Tue Oct 26 21:40:57 CEST 1999
 
 
       <varlistentry>
+        <term><literal>\ev <optional> <replaceable class="parameter">viewname</> <optional>  <replaceable class="parameter">line_number</> </optional> </optional> </literal></term>
+
+        <listitem>
+        <para>
+         This command fetches and edits the definition of the named view,
+         in the form of a <command>CREATE OR REPLACE VIEW</> command.
+         Editing is done in the same way as for <literal>\edit</>.
+         After the editor exits, the updated command waits in the query buffer;
+         type semicolon or <literal>\g</> to send it, or <literal>\r</>
+         to cancel.
+        </para>
+
+        <para>
+         If no view is specified, a blank <command>CREATE VIEW</>
+         template is presented for editing.
+        </para>
+
+        <para>
+         If a line number is specified, <application>psql</application> will
+         position the cursor on the specified line of the view definition.
+         (Note that the view definition typically does not begin on the first
+         line of the file.)
+        </para>
+        </listitem>
+      </varlistentry>
+
+
+      <varlistentry>
         <term><literal>\encoding [ <replaceable class="parameter">encoding</replaceable> ]</literal></term>
 
         <listitem>
@@ -2522,6 +2550,26 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
 
 
       <varlistentry>
+        <term><literal>\sv[+] <replaceable class="parameter">viewname</> </literal></term>
+
+        <listitem>
+         <para>
+          This command fetches and shows the definition of the named view,
+          in the form of a <command>CREATE OR REPLACE VIEW</> command.
+          The definition is printed to the current query output channel,
+          as set by <command>\o</command>.
+         </para>
+
+         <para>
+          If <literal>+</literal> is appended to the command name, then the
+          output lines are numbered, with the first line of the view definition
+          being line 1.
+         </para>
+        </listitem>
+      </varlistentry>
+
+
+      <varlistentry>
         <term><literal>\t</literal></term>
         <listitem>
         <para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 38253fa..71a2dfd 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -49,6 +49,16 @@
 #include "settings.h"
 #include "variables.h"
 
+/*
+ * Editable database object types.
+ * Currently functions and views are supported.
+ */
+typedef enum PgObjType
+{
+	PgObjTypeFunction,
+	PgObjTypeView
+	/* add new editable object type here */
+} PgObjType;
 
 /* functions for use in this file */
 static backslashResult exec_command(const char *cmd,
@@ -59,10 +69,17 @@ 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);
 static bool do_watch(PQExpBuffer query_buf, long sleep);
-static bool lookup_function_oid(const char *desc, Oid *foid);
-static bool get_create_function_cmd(Oid oid, PQExpBuffer buf);
-static int	strip_lineno_from_funcdesc(char *func);
+static bool lookup_object_oid(const char *desc,
+							  PgObjType obj_type,
+							  Oid *obj_oid);
+static bool get_create_object_cmd(Oid oid, PQExpBuffer buf, PgObjType type);
+static void format_create_view_cmd(char *view, PQExpBuffer buf);
+static int	strip_lineno_from_objdesc(char *obj);
 static void minimal_error_message(PGresult *res);
+static int count_lines_in_buf(PQExpBuffer buf);
+static void print_with_linenumbers(FILE *output,
+								   char *lines,
+								   const char *header_cmp_keyword);
 
 static void printSSLInfo(void);
 static bool printPsetInfo(const char *param, struct printQueryOpt *popt);
@@ -612,7 +629,7 @@ exec_command(const char *cmd,
 
 			func = psql_scan_slash_option(scan_state,
 										  OT_WHOLE_LINE, NULL, true);
-			lineno = strip_lineno_from_funcdesc(func);
+			lineno = strip_lineno_from_objdesc(func);
 			if (lineno == 0)
 			{
 				/* error already reported */
@@ -629,12 +646,12 @@ exec_command(const char *cmd,
 								  "AS $function$\n"
 								  "\n$function$\n");
 			}
-			else if (!lookup_function_oid(func, &foid))
+			else if (!lookup_object_oid(func, PgObjTypeFunction, &foid))
 			{
 				/* error already reported */
 				status = PSQL_CMD_ERROR;
 			}
-			else if (!get_create_function_cmd(foid, query_buf))
+			else if (!get_create_object_cmd(foid, query_buf, PgObjTypeFunction))
 			{
 				/* error already reported */
 				status = PSQL_CMD_ERROR;
@@ -682,6 +699,108 @@ exec_command(const char *cmd,
 		}
 	}
 
+	/*
+	 * \ev -- edit the named view, or present a blank CREATE VIEW viewname AS
+	 * template if no argument is given
+	 */
+	else if (strcmp(cmd, "ev") == 0)
+	{
+		int			lineno = -1;
+
+		if (pset.sversion < 70100)
+		{
+			psql_error("The server (version %d.%d) does not support editing view definition.\n",
+					   pset.sversion / 10000, (pset.sversion / 100) % 100);
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!query_buf)
+		{
+			psql_error("no query buffer\n");
+			status = PSQL_CMD_ERROR;
+		}
+		else
+		{
+			char	   *view;
+			Oid			view_oid = InvalidOid;
+
+			view = psql_scan_slash_option(scan_state,
+										  OT_WHOLE_LINE, NULL, true);
+			lineno = strip_lineno_from_objdesc(view);
+
+			if (lineno == 0)
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			if (!view)
+			{
+				/* set up an empty command to fill in */
+				printfPQExpBuffer(query_buf,
+								  "CREATE VIEW viewname AS \n"
+								  " SELECT \n"
+								  "  -- something... \n");
+			}
+			else if (!lookup_object_oid(view, PgObjTypeView, &view_oid))
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			else if (!get_create_object_cmd(view_oid, query_buf, PgObjTypeView))
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			else if (lineno > 0)
+			{
+				/*
+				 * lineno "1" should correspond to the first line of the
+				 * view definition.  We expect that pg_get_viewdef() will
+				 * emit that on a line beginning with " SELECT", and that there
+				 * can be no such line before the real start of the view
+				 * definition.  Increment lineno by the number of lines before that
+				 * line, so that it becomes relative to the first line of the
+				 * view definition.
+				 */
+				const char *lines = query_buf->data;
+
+				while (*lines != '\0')
+				{
+					if (strncmp(lines, " SELECT", 7) == 0)
+						break;
+					lineno++;
+					/* find start of next line */
+					lines = strchr(lines, '\n');
+					if (!lines)
+						break;
+					lines++;
+				}
+			}
+
+			if (view) {
+				format_create_view_cmd(view, query_buf);
+				free(view);
+
+				/*
+				 * format_create_view_cmd() adds one extra line
+				 * so we need to compensate it.
+				 */
+				lineno++;
+			}
+		}
+
+		if (status != PSQL_CMD_ERROR)
+		{
+			bool		edited = false;
+
+			if (!do_edit(NULL, query_buf, lineno, &edited))
+				status = PSQL_CMD_ERROR;
+			else if (!edited)
+				puts(_("No changes"));
+			else
+				status = PSQL_CMD_NEWEDIT;
+		}
+	}
+
 	/* \echo and \qecho */
 	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
 	{
@@ -1235,12 +1354,12 @@ exec_command(const char *cmd,
 			psql_error("function name is required\n");
 			status = PSQL_CMD_ERROR;
 		}
-		else if (!lookup_function_oid(func, &foid))
+		else if (!lookup_object_oid(func, PgObjTypeFunction, &foid))
 		{
 			/* error already reported */
 			status = PSQL_CMD_ERROR;
 		}
-		else if (!get_create_function_cmd(foid, func_buf))
+		else if (!get_create_object_cmd(foid, func_buf, PgObjTypeFunction))
 		{
 			/* error already reported */
 			status = PSQL_CMD_ERROR;
@@ -1254,18 +1373,7 @@ exec_command(const char *cmd,
 			if (pset.queryFout == stdout)
 			{
 				/* count lines in function to see if pager is needed */
-				int			lineno = 0;
-				const char *lines = func_buf->data;
-
-				while (*lines != '\0')
-				{
-					lineno++;
-					/* find start of next line */
-					lines = strchr(lines, '\n');
-					if (!lines)
-						break;
-					lines++;
-				}
+				int lineno = count_lines_in_buf(func_buf);
 
 				output = PageOutput(lineno, &(pset.popt.topt));
 				is_pager = true;
@@ -1279,45 +1387,16 @@ exec_command(const char *cmd,
 
 			if (show_linenumbers)
 			{
-				bool		in_header = true;
-				int			lineno = 0;
-				char	   *lines = func_buf->data;
-
 				/*
 				 * lineno "1" should correspond to the first line of the
 				 * function body.  We expect that pg_get_functiondef() will
 				 * emit that on a line beginning with "AS ", and that there
 				 * can be no such line before the real start of the function
 				 * body.
-				 *
-				 * Note that this loop scribbles on func_buf.
 				 */
-				while (*lines != '\0')
-				{
-					char	   *eol;
-
-					if (in_header && strncmp(lines, "AS ", 3) == 0)
-						in_header = false;
-					/* increment lineno only for body's lines */
-					if (!in_header)
-						lineno++;
-
-					/* find and mark end of current line */
-					eol = strchr(lines, '\n');
-					if (eol != NULL)
-						*eol = '\0';
-
-					/* show current line as appropriate */
-					if (in_header)
-						fprintf(output, "        %s\n", lines);
-					else
-						fprintf(output, "%-7d %s\n", lineno, lines);
-
-					/* advance to next line, if any */
-					if (eol == NULL)
-						break;
-					lines = ++eol;
-				}
+
+				char *lines = func_buf->data;
+				print_with_linenumbers(output, lines, "AS ");
 			}
 			else
 			{
@@ -1334,6 +1413,89 @@ exec_command(const char *cmd,
 		destroyPQExpBuffer(func_buf);
 	}
 
+	/* \sv -- show a view's source code */
+	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+	{
+		bool		show_linenumbers = (strcmp(cmd, "sv+") == 0);
+		PQExpBuffer view_buf;
+		char	   *view;
+		Oid			view_oid = InvalidOid;
+
+		view_buf = createPQExpBuffer();
+		view = psql_scan_slash_option(scan_state,
+									  OT_WHOLE_LINE, NULL, true);
+
+		if (pset.sversion < 70100)
+		{
+			psql_error("The server (version %d.%d) does not support showing view definition.\n",
+					   pset.sversion / 10000, (pset.sversion / 100) % 100);
+			status = PSQL_CMD_ERROR;
+		}
+		if (!view)
+		{
+			psql_error("view name is required\n");
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!lookup_object_oid(view, PgObjTypeView, &view_oid))
+		{
+			/* error already reported */
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!get_create_object_cmd(view_oid, view_buf, PgObjTypeView))
+		{
+			/* error already reported */
+			status = PSQL_CMD_ERROR;
+		}
+		else
+		{
+			FILE *output;
+			bool is_pager;
+
+			format_create_view_cmd(view, view_buf);
+
+			if (pset.queryFout == stdout)
+			{
+				/* count lines in view to see if pager is needed */
+				int lineno = count_lines_in_buf(view_buf);
+
+				output = PageOutput(lineno, &(pset.popt.topt));
+				is_pager = true;
+			}
+			else
+			{
+				/* use previously set output file, without pager */
+				output = pset.queryFout;
+				is_pager = false;
+			}
+
+			if (show_linenumbers)
+			{
+				/*
+				 * lineno "1" should correspond to the first line of the
+				 * view definition.  We expect that pg_get_viewdef() will
+				 * emit that on a line beginning with " SELECT", and that there
+				 * can be no such line before the real start of the view
+				 * definition.
+				 */
+
+				char	   *lines = view_buf->data;
+				print_with_linenumbers(output, lines, " SELECT");
+			}
+			else
+			{
+				/* just send the function definition to output */
+				fputs(view_buf->data, output);
+			}
+
+			if (is_pager)
+				ClosePager(output);
+		}
+
+		if (view)
+			free(view);
+		destroyPQExpBuffer(view_buf);
+	}
+
 	/* \t -- turn off headers and row count */
 	else if (strcmp(cmd, "t") == 0)
 	{
@@ -3002,7 +3164,7 @@ do_watch(PQExpBuffer query_buf, long sleep)
  * returns true unless we have ECHO_HIDDEN_NOEXEC.
  */
 static bool
-lookup_function_echo_hidden(char *query)
+lookup_object_echo_hidden(char *query)
 {
 	if (pset.echo_hidden != PSQL_ECHO_HIDDEN_OFF)
 	{
@@ -3025,35 +3187,51 @@ lookup_function_echo_hidden(char *query)
 	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 returned at *foid.  Note that we'll fail if the
- * function doesn't exist OR if there are multiple matching candidates
- * OR if there's something syntactically wrong with the function description;
- * unfortunately it can be hard to tell the difference.
- */
 static bool
-lookup_function_oid(const char *desc, Oid *foid)
+lookup_object_oid(const char *desc, PgObjType obj_type, Oid *obj_oid)
 {
+	PQExpBuffer query = createPQExpBuffer();
+
 	bool		result = true;
-	PQExpBuffer query;
 	PGresult   *res;
 
-	query = createPQExpBuffer();
-	appendPQExpBufferStr(query, "SELECT ");
-	appendStringLiteralConn(query, desc, pset.db);
-	appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
-					  strchr(desc, '(') ? "regprocedure" : "regproc");
-	if (!lookup_function_echo_hidden(query->data))
+	if (!lookup_object_echo_hidden(query->data))
 	{
 		destroyPQExpBuffer(query);
 		return false;
 	}
+
+	switch (obj_type)
+	{
+		/*
+		 * We got a function description, e.g. "x" or "x(int)", and
+		 * issue 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 returned at *foid.  Note that we'll fail if the
+		 * function doesn't exist OR if there are multiple matching candidates
+		 * OR if there's something syntactically wrong with the function
+		 * description; unfortunately it can be hard to tell the difference.
+		 */
+		case PgObjTypeFunction:
+			appendPQExpBufferStr(query, "SELECT ");
+			appendStringLiteralConn(query, desc, pset.db);
+			appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
+							  strchr(desc, '(') ? "regprocedure" : "regproc");
+			break;
+
+		/*
+		 * Convert viewname to corresponding oid.
+		 */
+		case PgObjTypeView:
+			appendPQExpBufferStr(query, "SELECT ");
+			appendStringLiteralConn(query, desc, pset.db);
+			appendPQExpBuffer(query, "::pg_catalog.regclass::pg_catalog.oid");
+			break;
+	}
+
 	res = PQexec(pset.db, query->data);
 	if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
-		*foid = atooid(PQgetvalue(res, 0, 0));
+		*obj_oid = atooid(PQgetvalue(res, 0, 0));
 	else
 	{
 		minimal_error_message(res);
@@ -3067,24 +3245,48 @@ lookup_function_oid(const char *desc, Oid *foid)
 }
 
 /*
- * Fetches the "CREATE OR REPLACE FUNCTION ..." command that describes the
- * function with the given OID.  If successful, the result is stored in buf.
+ * Fetches the "CREATE ..." command that describes the
+ * database object by given query.
+ * If successful, the result is stored in buf.
  */
 static bool
-get_create_function_cmd(Oid oid, PQExpBuffer buf)
+get_create_object_cmd(Oid oid, PQExpBuffer buf, PgObjType type)
 {
+	PQExpBuffer query = createPQExpBuffer();
+
 	bool		result = true;
-	PQExpBuffer query;
 	PGresult   *res;
 
-	query = createPQExpBuffer();
-	printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_functiondef(%u)", oid);
-
-	if (!lookup_function_echo_hidden(query->data))
+	if (!lookup_object_echo_hidden(query->data))
 	{
 		destroyPQExpBuffer(query);
 		return false;
 	}
+
+	switch (type)
+	{
+		/*
+		 * Fetch the "CREATE OR REPLACE FUNCTION ..." command that describes
+		 * the function with the given OID.
+		 * If successful, the result is stored in buf.
+		 */
+		case PgObjTypeFunction:
+			printfPQExpBuffer(query,
+							  "SELECT pg_catalog.pg_get_functiondef(%u)",
+							  oid);
+			break;
+
+		/*
+		 * Fetch a view definition that describes the view with the given OID.
+		 * If successful, the result is stored in buf.
+		 */
+		case PgObjTypeView:
+			printfPQExpBuffer(query,
+							  "SELECT pg_catalog.pg_get_viewdef(%u, true)",
+							  oid);
+			break;
+	}
+
 	res = PQexec(pset.db, query->data);
 	if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
 	{
@@ -3104,24 +3306,45 @@ get_create_function_cmd(Oid oid, PQExpBuffer buf)
 }
 
 /*
- * If the given argument of \ef ends with a line number, delete the line
+ * Unfortunately pg_get_viewdef() doesn't return "CREATE OR REPLACE"
+ * statement prefix.
+ * We need to format "CREATE" statement manually.
+ * Originally allocated buffer contents will be replaced with formatted one.
+ */
+static void
+format_create_view_cmd(char *view, PQExpBuffer buf)
+{
+	PQExpBuffer t = createPQExpBuffer();
+	printfPQExpBuffer(t,
+					  "CREATE OR REPLACE VIEW %s AS\n%s\n",
+					  view,
+					  buf->data);
+
+	resetPQExpBuffer(buf);
+	printfPQExpBuffer(buf, "%s", t->data);
+
+	destroyPQExpBuffer(t);
+}
+
+/*
+ * If the given argument of \ef or \ev ends with a line number, delete the line
  * number from the argument string and return it as an integer.  (We need
- * this kluge because we're too lazy to parse \ef's function name argument
- * carefully --- we just slop it up in OT_WHOLE_LINE mode.)
+ * this kluge because we're too lazy to parse \ef's function or \ev's view
+ * argument carefully --- we just slop it up in OT_WHOLE_LINE mode.)
  *
  * Returns -1 if no line number is present, 0 on error, or a positive value
  * on success.
  */
 static int
-strip_lineno_from_funcdesc(char *func)
+strip_lineno_from_objdesc(char *obj)
 {
 	char	   *c;
 	int			lineno;
 
-	if (!func || func[0] == '\0')
+	if (!obj || obj[0] == '\0')
 		return -1;
 
-	c = func + strlen(func) - 1;
+	c = obj + strlen(obj) - 1;
 
 	/*
 	 * This business of parsing backwards is dangerous as can be in a
@@ -3134,20 +3357,20 @@ strip_lineno_from_funcdesc(char *func)
 	 */
 
 	/* skip trailing whitespace */
-	while (c > func && isascii((unsigned char) *c) && isspace((unsigned char) *c))
+	while (c > obj && isascii((unsigned char) *c) && isspace((unsigned char) *c))
 		c--;
 
 	/* must have a digit as last non-space char */
-	if (c == func || !isascii((unsigned char) *c) || !isdigit((unsigned char) *c))
+	if (c == obj || !isascii((unsigned char) *c) || !isdigit((unsigned char) *c))
 		return -1;
 
 	/* find start of digit string */
-	while (c > func && isascii((unsigned char) *c) && isdigit((unsigned char) *c))
+	while (c > obj && isascii((unsigned char) *c) && isdigit((unsigned char) *c))
 		c--;
 
 	/* digits must be separated from func name by space or closing paren */
 	/* notice also that we are not allowing an empty func name ... */
-	if (c == func || !isascii((unsigned char) *c) ||
+	if (c == obj || !isascii((unsigned char) *c) ||
 		!(isspace((unsigned char) *c) || *c == ')'))
 		return -1;
 
@@ -3194,3 +3417,69 @@ minimal_error_message(PGresult *res)
 
 	destroyPQExpBuffer(msg);
 }
+
+/*
+ * Count number of lines in the buffer.
+ * It is used to test that pager is needed or not.
+ */
+static int
+count_lines_in_buf(PQExpBuffer buf)
+{
+	int			lineno = 0;
+	const char *lines = buf->data;
+
+	while (*lines != '\0')
+	{
+		lineno++;
+		/* find start of next line */
+		lines = strchr(lines, '\n');
+		if (!lines)
+			break;
+		lines++;
+	}
+
+	return lineno;
+}
+
+/*
+ * Produce line-numbered output of object's definition.
+ * Note that the object definition does not begin on the first line.
+ */
+static void
+print_with_linenumbers(FILE *output,
+					   char *lines,
+					   const char *header_cmp_keyword)
+{
+	bool		in_header = true;
+	int			lineno = 0;
+
+	size_t header_cmp_sz = strlen(header_cmp_keyword);
+
+	while (*lines != '\0')
+	{
+		char	   *eol;
+
+		if (in_header && strncmp(lines, header_cmp_keyword, header_cmp_sz) == 0)
+			in_header = false;
+
+		/* increment lineno only for body's lines */
+		if (!in_header)
+			lineno++;
+
+		/* find and mark end of current line */
+		eol = strchr(lines, '\n');
+		if (eol != NULL)
+			*eol = '\0';
+
+		/* show current line as appropriate */
+		if (in_header)
+			fprintf(output, "        %s\n", lines);
+		else
+			fprintf(output, "%-7d %s\n", lineno, lines);
+
+		/* advance to next line, if any */
+		if (eol == NULL)
+			break;
+		lines = ++eol;
+	}
+}
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index b523054..da9b954 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -181,6 +181,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("Query Buffer\n"));
 	fprintf(output, _("  \\e [FILE] [LINE]       edit the query buffer (or file) with external editor\n"));
 	fprintf(output, _("  \\ef [FUNCNAME [LINE]]  edit function definition with external editor\n"));
+	fprintf(output, _("  \\ev [VIEWNAME [LINE]]  edit view definition with external editor\n"));
 	fprintf(output, _("  \\p                     show the contents of the query buffer\n"));
 	fprintf(output, _("  \\r                     reset (clear) the query buffer\n"));
 #ifdef USE_READLINE
@@ -238,6 +239,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\dy     [PATTERN]      list event triggers\n"));
 	fprintf(output, _("  \\l[+]   [PATTERN]      list databases\n"));
 	fprintf(output, _("  \\sf[+] FUNCNAME        show a function's definition\n"));
+	fprintf(output, _("  \\sv[+] VIEWNAME        show a view's definition\n"));
 	fprintf(output, _("  \\z      [PATTERN]      same as \\dp\n"));
 	fprintf(output, "\n");
 
@@ -388,7 +390,7 @@ helpVariables(unsigned short int pager)
 	fprintf(output, _("  PGPASSWORD         connection password (not recommended)\n"));
 	fprintf(output, _("  PGPASSFILE         password file name\n"));
 	fprintf(output, _("  PSQL_EDITOR, EDITOR, VISUAL\n"
-		 "                     editor used by the \\e and \\ef commands\n"));
+		 "                     editor used by the \\e, \\ef, and \\ev commands\n"));
 	fprintf(output, _("  PSQL_EDITOR_LINENUMBER_ARG\n"
 					  "                     how to specify a line number when invoking the editor\n"));
 	fprintf(output, _("  PSQL_HISTORY       alternative location for the command history file\n"));
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b9f5acc..d4a1c1f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -895,11 +895,11 @@ psql_completion(const char *text, int start, int end)
 		"\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\dD", "\\des", "\\det", "\\deu", "\\dew", "\\df",
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
 		"\\dn", "\\do", "\\dp", "\\drds", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du", "\\dx",
-		"\\e", "\\echo", "\\ef", "\\encoding",
+		"\\e", "\\ef", "\\ev", "\\echo", "\\encoding",
 		"\\f", "\\g", "\\gset", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
 		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
 		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
-		"\\set", "\\sf", "\\t", "\\T",
+		"\\set", "\\sf", "\\sf+", "\\sv", "\\sv+", "\\t", "\\T",
 		"\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL
 	};
 
@@ -3784,6 +3784,8 @@ psql_completion(const char *text, int start, int end)
 
 	else if (strcmp(prev_wd, "\\ef") == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+	else if (strcmp(prev_wd, "\\ev") == 0)
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
 	else if (strcmp(prev_wd, "\\encoding") == 0)
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
@@ -3898,6 +3900,8 @@ psql_completion(const char *text, int start, int end)
 	}
 	else if (strcmp(prev_wd, "\\sf") == 0 || strcmp(prev_wd, "\\sf+") == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+	else if (strcmp(prev_wd, "\\sv") == 0 || strcmp(prev_wd, "\\sv+") == 0)
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 	else if (strcmp(prev_wd, "\\cd") == 0 ||
 			 strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 0 ||
 			 strcmp(prev_wd, "\\g") == 0 ||
#10Jeevan Chalke
jeevan.chalke@gmail.com
In reply to: Jeevan Chalke (#9)
Re: psql :: support for \ev viewname and \sv viewname

The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: tested, passed

Patch looks good to pass to committer.

The new status of this patch is: Ready for Committer

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Tom Lane
tgl@sss.pgh.pa.us
In reply to: Jeevan Chalke (#9)
Re: psql :: support for \ev viewname and \sv viewname

Jeevan Chalke <jeevan.chalke@enterprisedb.com> writes:

Patch looks excellent now. No issues.
Found a typo which I have fixed in the attached patch.

Starting to look at this ...

The business with numbering lines from SELECT seems to me to be completely
nonsensical. In the first place, it fails to allow for views containing
WITH clauses. But really it looks like it was cargo-culted over from
\ef/\sf without understanding why those commands number lines the way
they do. The reason they do that is that for errors occurring inside a
function definition, the PL will typically report a line number relative
to the function body text, and so we're trying to be helpful about
interpreting line numbers of that kind. But there's no comparable
behavior in the case of a view. If you fat-finger a view, you'll get
a line number relative to the text of the whole CREATE command, eg

regression=# create or replace view z as
regression-# select 1/col
regression-# from bar;
ERROR: relation "bar" does not exist
LINE 3: from bar;
^

So AFAICS, \ev and \sv should just number lines straightforwardly, with
"1" being the first line of the CREATE command text. Am I missing
something?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Petr Korobeinikov
pkorobeinikov@gmail.com
In reply to: Tom Lane (#11)
1 attachment(s)
Re: psql :: support for \ev viewname and \sv viewname

пт, 3 июля 2015 г. в 19:30, Tom Lane <tgl@sss.pgh.pa.us>:

So AFAICS, \ev and \sv should just number lines straightforwardly, with
"1" being the first line of the CREATE command text. Am I missing
something?

Fixed. Now both \ev and \sv numbering lines starting with "1". New version
attached.

As I've already noticed that pg_get_viewdef() does not support full syntax
of creating or replacing views. In my opinion, psql source code isn't the
place where some formatting hacks should be. So, can you give me an idea
how to produce already formatted output supporting "WITH" statement without
breaking backward compatibility of pg_get_viewdef() internals?

Attachments:

psql-ev-sv-support-v6.diffapplication/octet-stream; name=psql-ev-sv-support-v6.diffDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 62a3b21..4467b8c 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1651,6 +1651,34 @@ Tue Oct 26 21:40:57 CEST 1999
 
 
       <varlistentry>
+        <term><literal>\ev <optional> <replaceable class="parameter">viewname</> <optional>  <replaceable class="parameter">line_number</> </optional> </optional> </literal></term>
+
+        <listitem>
+        <para>
+         This command fetches and edits the definition of the named view,
+         in the form of a <command>CREATE OR REPLACE VIEW</> command.
+         Editing is done in the same way as for <literal>\edit</>.
+         After the editor exits, the updated command waits in the query buffer;
+         type semicolon or <literal>\g</> to send it, or <literal>\r</>
+         to cancel.
+        </para>
+
+        <para>
+         If no view is specified, a blank <command>CREATE VIEW</>
+         template is presented for editing.
+        </para>
+
+        <para>
+         If a line number is specified, <application>psql</application> will
+         position the cursor on the specified line of the view definition.
+         (Note that the view definition typically does not begin on the first
+         line of the file.)
+        </para>
+        </listitem>
+      </varlistentry>
+
+
+      <varlistentry>
         <term><literal>\encoding [ <replaceable class="parameter">encoding</replaceable> ]</literal></term>
 
         <listitem>
@@ -2522,6 +2550,26 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
 
 
       <varlistentry>
+        <term><literal>\sv[+] <replaceable class="parameter">viewname</> </literal></term>
+
+        <listitem>
+         <para>
+          This command fetches and shows the definition of the named view,
+          in the form of a <command>CREATE OR REPLACE VIEW</> command.
+          The definition is printed to the current query output channel,
+          as set by <command>\o</command>.
+         </para>
+
+         <para>
+          If <literal>+</literal> is appended to the command name, then the
+          output lines are numbered, with the first line of the view definition
+          being line 1.
+         </para>
+        </listitem>
+      </varlistentry>
+
+
+      <varlistentry>
         <term><literal>\t</literal></term>
         <listitem>
         <para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 38253fa..12ee868 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -49,6 +49,16 @@
 #include "settings.h"
 #include "variables.h"
 
+/*
+ * Editable database object types.
+ * Currently functions and views are supported.
+ */
+typedef enum PgObjType
+{
+	PgObjTypeFunction,
+	PgObjTypeView
+	/* add new editable object type here */
+} PgObjType;
 
 /* functions for use in this file */
 static backslashResult exec_command(const char *cmd,
@@ -59,10 +69,17 @@ 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);
 static bool do_watch(PQExpBuffer query_buf, long sleep);
-static bool lookup_function_oid(const char *desc, Oid *foid);
-static bool get_create_function_cmd(Oid oid, PQExpBuffer buf);
-static int	strip_lineno_from_funcdesc(char *func);
+static bool lookup_object_oid(const char *desc,
+							  PgObjType obj_type,
+							  Oid *obj_oid);
+static bool get_create_object_cmd(Oid oid, PQExpBuffer buf, PgObjType type);
+static void format_create_view_cmd(char *view, PQExpBuffer buf);
+static int	strip_lineno_from_objdesc(char *obj);
 static void minimal_error_message(PGresult *res);
+static int count_lines_in_buf(PQExpBuffer buf);
+static void print_with_linenumbers(FILE *output,
+								   char *lines,
+								   const char *header_cmp_keyword);
 
 static void printSSLInfo(void);
 static bool printPsetInfo(const char *param, struct printQueryOpt *popt);
@@ -612,7 +629,7 @@ exec_command(const char *cmd,
 
 			func = psql_scan_slash_option(scan_state,
 										  OT_WHOLE_LINE, NULL, true);
-			lineno = strip_lineno_from_funcdesc(func);
+			lineno = strip_lineno_from_objdesc(func);
 			if (lineno == 0)
 			{
 				/* error already reported */
@@ -629,12 +646,12 @@ exec_command(const char *cmd,
 								  "AS $function$\n"
 								  "\n$function$\n");
 			}
-			else if (!lookup_function_oid(func, &foid))
+			else if (!lookup_object_oid(func, PgObjTypeFunction, &foid))
 			{
 				/* error already reported */
 				status = PSQL_CMD_ERROR;
 			}
-			else if (!get_create_function_cmd(foid, query_buf))
+			else if (!get_create_object_cmd(foid, query_buf, PgObjTypeFunction))
 			{
 				/* error already reported */
 				status = PSQL_CMD_ERROR;
@@ -682,6 +699,77 @@ exec_command(const char *cmd,
 		}
 	}
 
+	/*
+	 * \ev -- edit the named view, or present a blank CREATE VIEW viewname AS
+	 * template if no argument is given
+	 */
+	else if (strcmp(cmd, "ev") == 0)
+	{
+		int			lineno = -1;
+
+		if (pset.sversion < 70100)
+		{
+			psql_error("The server (version %d.%d) does not support editing view definition.\n",
+					   pset.sversion / 10000, (pset.sversion / 100) % 100);
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!query_buf)
+		{
+			psql_error("no query buffer\n");
+			status = PSQL_CMD_ERROR;
+		}
+		else
+		{
+			char	   *view;
+			Oid			view_oid = InvalidOid;
+
+			view = psql_scan_slash_option(scan_state,
+										  OT_WHOLE_LINE, NULL, true);
+			lineno = strip_lineno_from_objdesc(view);
+
+			if (lineno == 0)
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			if (!view)
+			{
+				/* set up an empty command to fill in */
+				printfPQExpBuffer(query_buf,
+								  "CREATE VIEW viewname AS \n"
+								  " SELECT \n"
+								  "  -- something... \n");
+			}
+			else if (!lookup_object_oid(view, PgObjTypeView, &view_oid))
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			else if (!get_create_object_cmd(view_oid, query_buf, PgObjTypeView))
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+
+			if (view) {
+				format_create_view_cmd(view, query_buf);
+				free(view);
+			}
+		}
+
+		if (status != PSQL_CMD_ERROR)
+		{
+			bool		edited = false;
+
+			if (!do_edit(NULL, query_buf, lineno, &edited))
+				status = PSQL_CMD_ERROR;
+			else if (!edited)
+				puts(_("No changes"));
+			else
+				status = PSQL_CMD_NEWEDIT;
+		}
+	}
+
 	/* \echo and \qecho */
 	else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
 	{
@@ -1235,12 +1323,12 @@ exec_command(const char *cmd,
 			psql_error("function name is required\n");
 			status = PSQL_CMD_ERROR;
 		}
-		else if (!lookup_function_oid(func, &foid))
+		else if (!lookup_object_oid(func, PgObjTypeFunction, &foid))
 		{
 			/* error already reported */
 			status = PSQL_CMD_ERROR;
 		}
-		else if (!get_create_function_cmd(foid, func_buf))
+		else if (!get_create_object_cmd(foid, func_buf, PgObjTypeFunction))
 		{
 			/* error already reported */
 			status = PSQL_CMD_ERROR;
@@ -1254,18 +1342,7 @@ exec_command(const char *cmd,
 			if (pset.queryFout == stdout)
 			{
 				/* count lines in function to see if pager is needed */
-				int			lineno = 0;
-				const char *lines = func_buf->data;
-
-				while (*lines != '\0')
-				{
-					lineno++;
-					/* find start of next line */
-					lines = strchr(lines, '\n');
-					if (!lines)
-						break;
-					lines++;
-				}
+				int lineno = count_lines_in_buf(func_buf);
 
 				output = PageOutput(lineno, &(pset.popt.topt));
 				is_pager = true;
@@ -1279,45 +1356,16 @@ exec_command(const char *cmd,
 
 			if (show_linenumbers)
 			{
-				bool		in_header = true;
-				int			lineno = 0;
-				char	   *lines = func_buf->data;
-
 				/*
 				 * lineno "1" should correspond to the first line of the
 				 * function body.  We expect that pg_get_functiondef() will
 				 * emit that on a line beginning with "AS ", and that there
 				 * can be no such line before the real start of the function
 				 * body.
-				 *
-				 * Note that this loop scribbles on func_buf.
 				 */
-				while (*lines != '\0')
-				{
-					char	   *eol;
-
-					if (in_header && strncmp(lines, "AS ", 3) == 0)
-						in_header = false;
-					/* increment lineno only for body's lines */
-					if (!in_header)
-						lineno++;
-
-					/* find and mark end of current line */
-					eol = strchr(lines, '\n');
-					if (eol != NULL)
-						*eol = '\0';
-
-					/* show current line as appropriate */
-					if (in_header)
-						fprintf(output, "        %s\n", lines);
-					else
-						fprintf(output, "%-7d %s\n", lineno, lines);
-
-					/* advance to next line, if any */
-					if (eol == NULL)
-						break;
-					lines = ++eol;
-				}
+
+				char *lines = func_buf->data;
+				print_with_linenumbers(output, lines, "AS ");
 			}
 			else
 			{
@@ -1334,6 +1382,89 @@ exec_command(const char *cmd,
 		destroyPQExpBuffer(func_buf);
 	}
 
+	/* \sv -- show a view's source code */
+	else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
+	{
+		bool		show_linenumbers = (strcmp(cmd, "sv+") == 0);
+		PQExpBuffer view_buf;
+		char	   *view;
+		Oid			view_oid = InvalidOid;
+
+		view_buf = createPQExpBuffer();
+		view = psql_scan_slash_option(scan_state,
+									  OT_WHOLE_LINE, NULL, true);
+
+		if (pset.sversion < 70100)
+		{
+			psql_error("The server (version %d.%d) does not support showing view definition.\n",
+					   pset.sversion / 10000, (pset.sversion / 100) % 100);
+			status = PSQL_CMD_ERROR;
+		}
+		if (!view)
+		{
+			psql_error("view name is required\n");
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!lookup_object_oid(view, PgObjTypeView, &view_oid))
+		{
+			/* error already reported */
+			status = PSQL_CMD_ERROR;
+		}
+		else if (!get_create_object_cmd(view_oid, view_buf, PgObjTypeView))
+		{
+			/* error already reported */
+			status = PSQL_CMD_ERROR;
+		}
+		else
+		{
+			FILE *output;
+			bool is_pager;
+
+			format_create_view_cmd(view, view_buf);
+
+			if (pset.queryFout == stdout)
+			{
+				/* count lines in view to see if pager is needed */
+				int lineno = count_lines_in_buf(view_buf);
+
+				output = PageOutput(lineno, &(pset.popt.topt));
+				is_pager = true;
+			}
+			else
+			{
+				/* use previously set output file, without pager */
+				output = pset.queryFout;
+				is_pager = false;
+			}
+
+			if (show_linenumbers)
+			{
+				/*
+				 * lineno "1" should correspond to the first line of the
+				 * view definition.  We expect that pg_get_viewdef() will
+				 * emit that on a line beginning with " SELECT", and that there
+				 * can be no such line before the real start of the view
+				 * definition.
+				 */
+
+				char	   *lines = view_buf->data;
+				print_with_linenumbers(output, lines, "CREATE");
+			}
+			else
+			{
+				/* just send the function definition to output */
+				fputs(view_buf->data, output);
+			}
+
+			if (is_pager)
+				ClosePager(output);
+		}
+
+		if (view)
+			free(view);
+		destroyPQExpBuffer(view_buf);
+	}
+
 	/* \t -- turn off headers and row count */
 	else if (strcmp(cmd, "t") == 0)
 	{
@@ -3002,7 +3133,7 @@ do_watch(PQExpBuffer query_buf, long sleep)
  * returns true unless we have ECHO_HIDDEN_NOEXEC.
  */
 static bool
-lookup_function_echo_hidden(char *query)
+lookup_object_echo_hidden(char *query)
 {
 	if (pset.echo_hidden != PSQL_ECHO_HIDDEN_OFF)
 	{
@@ -3025,35 +3156,51 @@ lookup_function_echo_hidden(char *query)
 	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 returned at *foid.  Note that we'll fail if the
- * function doesn't exist OR if there are multiple matching candidates
- * OR if there's something syntactically wrong with the function description;
- * unfortunately it can be hard to tell the difference.
- */
 static bool
-lookup_function_oid(const char *desc, Oid *foid)
+lookup_object_oid(const char *desc, PgObjType obj_type, Oid *obj_oid)
 {
+	PQExpBuffer query = createPQExpBuffer();
+
 	bool		result = true;
-	PQExpBuffer query;
 	PGresult   *res;
 
-	query = createPQExpBuffer();
-	appendPQExpBufferStr(query, "SELECT ");
-	appendStringLiteralConn(query, desc, pset.db);
-	appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
-					  strchr(desc, '(') ? "regprocedure" : "regproc");
-	if (!lookup_function_echo_hidden(query->data))
+	if (!lookup_object_echo_hidden(query->data))
 	{
 		destroyPQExpBuffer(query);
 		return false;
 	}
+
+	switch (obj_type)
+	{
+		/*
+		 * We got a function description, e.g. "x" or "x(int)", and
+		 * issue 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 returned at *foid.  Note that we'll fail if the
+		 * function doesn't exist OR if there are multiple matching candidates
+		 * OR if there's something syntactically wrong with the function
+		 * description; unfortunately it can be hard to tell the difference.
+		 */
+		case PgObjTypeFunction:
+			appendPQExpBufferStr(query, "SELECT ");
+			appendStringLiteralConn(query, desc, pset.db);
+			appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
+							  strchr(desc, '(') ? "regprocedure" : "regproc");
+			break;
+
+		/*
+		 * Convert viewname to corresponding oid.
+		 */
+		case PgObjTypeView:
+			appendPQExpBufferStr(query, "SELECT ");
+			appendStringLiteralConn(query, desc, pset.db);
+			appendPQExpBuffer(query, "::pg_catalog.regclass::pg_catalog.oid");
+			break;
+	}
+
 	res = PQexec(pset.db, query->data);
 	if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
-		*foid = atooid(PQgetvalue(res, 0, 0));
+		*obj_oid = atooid(PQgetvalue(res, 0, 0));
 	else
 	{
 		minimal_error_message(res);
@@ -3067,24 +3214,48 @@ lookup_function_oid(const char *desc, Oid *foid)
 }
 
 /*
- * Fetches the "CREATE OR REPLACE FUNCTION ..." command that describes the
- * function with the given OID.  If successful, the result is stored in buf.
+ * Fetches the "CREATE ..." command that describes the
+ * database object by given query.
+ * If successful, the result is stored in buf.
  */
 static bool
-get_create_function_cmd(Oid oid, PQExpBuffer buf)
+get_create_object_cmd(Oid oid, PQExpBuffer buf, PgObjType type)
 {
+	PQExpBuffer query = createPQExpBuffer();
+
 	bool		result = true;
-	PQExpBuffer query;
 	PGresult   *res;
 
-	query = createPQExpBuffer();
-	printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_functiondef(%u)", oid);
-
-	if (!lookup_function_echo_hidden(query->data))
+	if (!lookup_object_echo_hidden(query->data))
 	{
 		destroyPQExpBuffer(query);
 		return false;
 	}
+
+	switch (type)
+	{
+		/*
+		 * Fetch the "CREATE OR REPLACE FUNCTION ..." command that describes
+		 * the function with the given OID.
+		 * If successful, the result is stored in buf.
+		 */
+		case PgObjTypeFunction:
+			printfPQExpBuffer(query,
+							  "SELECT pg_catalog.pg_get_functiondef(%u)",
+							  oid);
+			break;
+
+		/*
+		 * Fetch a view definition that describes the view with the given OID.
+		 * If successful, the result is stored in buf.
+		 */
+		case PgObjTypeView:
+			printfPQExpBuffer(query,
+							  "SELECT pg_catalog.pg_get_viewdef(%u, true)",
+							  oid);
+			break;
+	}
+
 	res = PQexec(pset.db, query->data);
 	if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
 	{
@@ -3104,24 +3275,45 @@ get_create_function_cmd(Oid oid, PQExpBuffer buf)
 }
 
 /*
- * If the given argument of \ef ends with a line number, delete the line
+ * Unfortunately pg_get_viewdef() doesn't return "CREATE OR REPLACE"
+ * statement prefix.
+ * We need to format "CREATE" statement manually.
+ * Originally allocated buffer contents will be replaced with formatted one.
+ */
+static void
+format_create_view_cmd(char *view, PQExpBuffer buf)
+{
+	PQExpBuffer t = createPQExpBuffer();
+	printfPQExpBuffer(t,
+					  "CREATE OR REPLACE VIEW %s AS\n%s\n",
+					  view,
+					  buf->data);
+
+	resetPQExpBuffer(buf);
+	printfPQExpBuffer(buf, "%s", t->data);
+
+	destroyPQExpBuffer(t);
+}
+
+/*
+ * If the given argument of \ef or \ev ends with a line number, delete the line
  * number from the argument string and return it as an integer.  (We need
- * this kluge because we're too lazy to parse \ef's function name argument
- * carefully --- we just slop it up in OT_WHOLE_LINE mode.)
+ * this kluge because we're too lazy to parse \ef's function or \ev's view
+ * argument carefully --- we just slop it up in OT_WHOLE_LINE mode.)
  *
  * Returns -1 if no line number is present, 0 on error, or a positive value
  * on success.
  */
 static int
-strip_lineno_from_funcdesc(char *func)
+strip_lineno_from_objdesc(char *obj)
 {
 	char	   *c;
 	int			lineno;
 
-	if (!func || func[0] == '\0')
+	if (!obj || obj[0] == '\0')
 		return -1;
 
-	c = func + strlen(func) - 1;
+	c = obj + strlen(obj) - 1;
 
 	/*
 	 * This business of parsing backwards is dangerous as can be in a
@@ -3134,20 +3326,20 @@ strip_lineno_from_funcdesc(char *func)
 	 */
 
 	/* skip trailing whitespace */
-	while (c > func && isascii((unsigned char) *c) && isspace((unsigned char) *c))
+	while (c > obj && isascii((unsigned char) *c) && isspace((unsigned char) *c))
 		c--;
 
 	/* must have a digit as last non-space char */
-	if (c == func || !isascii((unsigned char) *c) || !isdigit((unsigned char) *c))
+	if (c == obj || !isascii((unsigned char) *c) || !isdigit((unsigned char) *c))
 		return -1;
 
 	/* find start of digit string */
-	while (c > func && isascii((unsigned char) *c) && isdigit((unsigned char) *c))
+	while (c > obj && isascii((unsigned char) *c) && isdigit((unsigned char) *c))
 		c--;
 
 	/* digits must be separated from func name by space or closing paren */
 	/* notice also that we are not allowing an empty func name ... */
-	if (c == func || !isascii((unsigned char) *c) ||
+	if (c == obj || !isascii((unsigned char) *c) ||
 		!(isspace((unsigned char) *c) || *c == ')'))
 		return -1;
 
@@ -3194,3 +3386,69 @@ minimal_error_message(PGresult *res)
 
 	destroyPQExpBuffer(msg);
 }
+
+/*
+ * Count number of lines in the buffer.
+ * It is used to test that pager is needed or not.
+ */
+static int
+count_lines_in_buf(PQExpBuffer buf)
+{
+	int			lineno = 0;
+	const char *lines = buf->data;
+
+	while (*lines != '\0')
+	{
+		lineno++;
+		/* find start of next line */
+		lines = strchr(lines, '\n');
+		if (!lines)
+			break;
+		lines++;
+	}
+
+	return lineno;
+}
+
+/*
+ * Produce line-numbered output of object's definition.
+ * Note that the object definition does not begin on the first line.
+ */
+static void
+print_with_linenumbers(FILE *output,
+					   char *lines,
+					   const char *header_cmp_keyword)
+{
+	bool		in_header = true;
+	int			lineno = 0;
+
+	size_t header_cmp_sz = strlen(header_cmp_keyword);
+
+	while (*lines != '\0')
+	{
+		char	   *eol;
+
+		if (in_header && strncmp(lines, header_cmp_keyword, header_cmp_sz) == 0)
+			in_header = false;
+
+		/* increment lineno only for body's lines */
+		if (!in_header)
+			lineno++;
+
+		/* find and mark end of current line */
+		eol = strchr(lines, '\n');
+		if (eol != NULL)
+			*eol = '\0';
+
+		/* show current line as appropriate */
+		if (in_header)
+			fprintf(output, "        %s\n", lines);
+		else
+			fprintf(output, "%-7d %s\n", lineno, lines);
+
+		/* advance to next line, if any */
+		if (eol == NULL)
+			break;
+		lines = ++eol;
+	}
+}
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index b523054..da9b954 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -181,6 +181,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("Query Buffer\n"));
 	fprintf(output, _("  \\e [FILE] [LINE]       edit the query buffer (or file) with external editor\n"));
 	fprintf(output, _("  \\ef [FUNCNAME [LINE]]  edit function definition with external editor\n"));
+	fprintf(output, _("  \\ev [VIEWNAME [LINE]]  edit view definition with external editor\n"));
 	fprintf(output, _("  \\p                     show the contents of the query buffer\n"));
 	fprintf(output, _("  \\r                     reset (clear) the query buffer\n"));
 #ifdef USE_READLINE
@@ -238,6 +239,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\dy     [PATTERN]      list event triggers\n"));
 	fprintf(output, _("  \\l[+]   [PATTERN]      list databases\n"));
 	fprintf(output, _("  \\sf[+] FUNCNAME        show a function's definition\n"));
+	fprintf(output, _("  \\sv[+] VIEWNAME        show a view's definition\n"));
 	fprintf(output, _("  \\z      [PATTERN]      same as \\dp\n"));
 	fprintf(output, "\n");
 
@@ -388,7 +390,7 @@ helpVariables(unsigned short int pager)
 	fprintf(output, _("  PGPASSWORD         connection password (not recommended)\n"));
 	fprintf(output, _("  PGPASSFILE         password file name\n"));
 	fprintf(output, _("  PSQL_EDITOR, EDITOR, VISUAL\n"
-		 "                     editor used by the \\e and \\ef commands\n"));
+		 "                     editor used by the \\e, \\ef, and \\ev commands\n"));
 	fprintf(output, _("  PSQL_EDITOR_LINENUMBER_ARG\n"
 					  "                     how to specify a line number when invoking the editor\n"));
 	fprintf(output, _("  PSQL_HISTORY       alternative location for the command history file\n"));
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5b32fde..c0c4203 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -900,11 +900,11 @@ psql_completion(const char *text, int start, int end)
 		"\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\dD", "\\des", "\\det", "\\deu", "\\dew", "\\df",
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
 		"\\dn", "\\do", "\\dp", "\\drds", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du", "\\dx",
-		"\\e", "\\echo", "\\ef", "\\encoding",
+		"\\e", "\\ef", "\\ev", "\\echo", "\\encoding",
 		"\\f", "\\g", "\\gset", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
 		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
 		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
-		"\\set", "\\sf", "\\t", "\\T",
+		"\\set", "\\sf", "\\sf+", "\\sv", "\\sv+", "\\t", "\\T",
 		"\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL
 	};
 
@@ -3798,6 +3798,8 @@ psql_completion(const char *text, int start, int end)
 
 	else if (strcmp(prev_wd, "\\ef") == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+	else if (strcmp(prev_wd, "\\ev") == 0)
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
 	else if (strcmp(prev_wd, "\\encoding") == 0)
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
@@ -3912,6 +3914,8 @@ psql_completion(const char *text, int start, int end)
 	}
 	else if (strcmp(prev_wd, "\\sf") == 0 || strcmp(prev_wd, "\\sf+") == 0)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+	else if (strcmp(prev_wd, "\\sv") == 0 || strcmp(prev_wd, "\\sv+") == 0)
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 	else if (strcmp(prev_wd, "\\cd") == 0 ||
 			 strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 0 ||
 			 strcmp(prev_wd, "\\g") == 0 ||
#13Tom Lane
tgl@sss.pgh.pa.us
In reply to: Petr Korobeinikov (#12)
Re: psql :: support for \ev viewname and \sv viewname

Petr Korobeinikov <pkorobeinikov@gmail.com> writes:

Fixed. Now both \ev and \sv numbering lines starting with "1". New version
attached.

Applied with a fair amount of mostly-cosmetic adjustment.

As I've already noticed that pg_get_viewdef() does not support full syntax
of creating or replacing views.

Oh? If that were true, pg_dump wouldn't work on such views. It is kind
of a PITA for this purpose that it doesn't include the CREATE text for
you, but we're surely not changing that behavior now.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#13)
Re: psql :: support for \ev viewname and \sv viewname

On 3 July 2015 at 20:50, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Petr Korobeinikov <pkorobeinikov@gmail.com> writes:

Fixed. Now both \ev and \sv numbering lines starting with "1". New version
attached.

Applied with a fair amount of mostly-cosmetic adjustment.

As I've already noticed that pg_get_viewdef() does not support full syntax
of creating or replacing views.

Oh? If that were true, pg_dump wouldn't work on such views. It is kind
of a PITA for this purpose that it doesn't include the CREATE text for
you, but we're surely not changing that behavior now.

This appears to be missing support for view options (WITH CHECK OPTION
and security_barrier), so editing a view with either of those options
will cause them to be stripped off.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#15Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dean Rasheed (#14)
Re: psql :: support for \ev viewname and \sv viewname

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

On 3 July 2015 at 20:50, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Oh? If that were true, pg_dump wouldn't work on such views. It is kind
of a PITA for this purpose that it doesn't include the CREATE text for
you, but we're surely not changing that behavior now.

This appears to be missing support for view options (WITH CHECK OPTION
and security_barrier), so editing a view with either of those options
will cause them to be stripped off.

Hm. Why exactly were those not implemented as part of pg_get_viewdef?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Tom Lane (#15)
Re: psql :: support for \ev viewname and \sv viewname

On 22 July 2015 at 21:37, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Dean Rasheed <dean.a.rasheed@gmail.com> writes:

On 3 July 2015 at 20:50, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Oh? If that were true, pg_dump wouldn't work on such views. It is kind
of a PITA for this purpose that it doesn't include the CREATE text for
you, but we're surely not changing that behavior now.

This appears to be missing support for view options (WITH CHECK OPTION
and security_barrier), so editing a view with either of those options
will cause them to be stripped off.

Hm. Why exactly were those not implemented as part of pg_get_viewdef?

pg_get_viewdef in its current form is needed for the
information_schema "views" view, which has separate columns for the
view's query and its CHECK OPTIONs.

Arguably another function could be added. However, given the need for
psql to support older server versions, a new function wouldn't
actually help much, since psql would still need to be able to do it
the hard way in the absence of that new function on the server.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Dean Rasheed (#16)
1 attachment(s)
Re: psql :: support for \ev viewname and \sv viewname

[Looking back over old threads]

On 22 July 2015 at 22:00, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

This appears to be missing support for view options (WITH CHECK OPTION
and security_barrier), so editing a view with either of those options
will cause them to be stripped off.

It seems like this issue was never addressed, and it needs to be fixed for 9.6.

Here is a rough patch based on the way pg_dump handles this. It still
needs a bit of polishing -- in particular I think fmtReloptionsArray()
(copied from pg_dump) should probably be moved to string_utils.c so
that it can be shared between pg_dump and psql. Also, I'm not sure
that's the best name for it -- I think appendReloptionsArray() is a
more accurate description of what is does.

Regards,
Dean

Attachments:

psql-ev-sv.patchtext/x-diff; charset=US-ASCII; name=psql-ev-sv.patchDownload
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
new file mode 100644
index 4fa7760..96bc64d
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -72,6 +72,7 @@ static bool lookup_object_oid(EditableOb
 				  Oid *obj_oid);
 static bool get_create_object_cmd(EditableObjectType obj_type, Oid oid,
 					  PQExpBuffer buf);
+static bool fmtReloptionsArray(PQExpBuffer buffer, const char *reloptions);
 static int	strip_lineno_from_objdesc(char *obj);
 static int	count_lines_in_buf(PQExpBuffer buf);
 static void print_with_linenumbers(FILE *output, char *lines,
@@ -3274,12 +3275,51 @@ get_create_object_cmd(EditableObjectType
 			 * CREATE for ourselves.  We must fully qualify the view name to
 			 * ensure the right view gets replaced.  Also, check relation kind
 			 * to be sure it's a view.
+			 *
+			 * Starting with 9.2, views may have reloptions (security_barrier)
+			 * and from 9.4 onwards they may also have WITH [LOCAL|CASCADED]
+			 * CHECK OPTION.  These are not part of the view definition
+			 * returned by pg_get_viewdef() and so need to be retrieved
+			 * separately.  Materialized views (introduced in 9.3) may have
+			 * arbitrary storage parameter reloptions.
 			 */
-			printfPQExpBuffer(query,
-							  "SELECT nspname, relname, relkind, pg_catalog.pg_get_viewdef(c.oid, true) FROM "
-				 "pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n "
-							  "ON c.relnamespace = n.oid WHERE c.oid = %u",
-							  oid);
+			if (pset.sversion >= 90400)
+			{
+				printfPQExpBuffer(query,
+								  "SELECT nspname, relname, relkind, "
+								  "pg_catalog.pg_get_viewdef(c.oid, true), "
+								  "array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, "
+								  "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
+								  "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption "
+								  "FROM pg_catalog.pg_class c "
+								  "LEFT JOIN pg_catalog.pg_namespace n "
+								"ON c.relnamespace = n.oid WHERE c.oid = %u",
+								  oid);
+			}
+			else if (pset.sversion >= 90200)
+			{
+				printfPQExpBuffer(query,
+								  "SELECT nspname, relname, relkind, "
+								  "pg_catalog.pg_get_viewdef(c.oid, true), "
+								  "c.reloptions AS reloptions, "
+								  "NULL AS checkoption "
+								  "FROM pg_catalog.pg_class c "
+								  "LEFT JOIN pg_catalog.pg_namespace n "
+								"ON c.relnamespace = n.oid WHERE c.oid = %u",
+								  oid);
+			}
+			else
+			{
+				printfPQExpBuffer(query,
+								  "SELECT nspname, relname, relkind, "
+								  "pg_catalog.pg_get_viewdef(c.oid, true), "
+								  "NULL AS reloptions, "
+								  "NULL AS checkoption "
+								  "FROM pg_catalog.pg_class c "
+								  "LEFT JOIN pg_catalog.pg_namespace n "
+								"ON c.relnamespace = n.oid WHERE c.oid = %u",
+								  oid);
+			}
 			break;
 	}
 
@@ -3304,6 +3344,8 @@ get_create_object_cmd(EditableObjectType
 					char	   *relname = PQgetvalue(res, 0, 1);
 					char	   *relkind = PQgetvalue(res, 0, 2);
 					char	   *viewdef = PQgetvalue(res, 0, 3);
+					char	   *reloptions = PQgetvalue(res, 0, 4);
+					char	   *checkoption = PQgetvalue(res, 0, 5);
 
 					/*
 					 * If the backend ever supports CREATE OR REPLACE
@@ -3328,11 +3370,30 @@ get_create_object_cmd(EditableObjectType
 							break;
 					}
 					appendPQExpBuffer(buf, "%s.", fmtId(nspname));
-					appendPQExpBuffer(buf, "%s AS\n", fmtId(relname));
-					appendPQExpBufferStr(buf, viewdef);
+					appendPQExpBufferStr(buf, fmtId(relname));
+
+					/* reloptions, if not an empty array "{}" */
+					if (reloptions != NULL && strlen(reloptions) > 2)
+					{
+						appendPQExpBufferStr(buf, "\n WITH (");
+						if (!fmtReloptionsArray(buf, reloptions))
+						{
+							psql_error("Could not parse reloptions array\n");
+							result = false;
+						}
+						appendPQExpBufferStr(buf, ")");
+					}
+
+					/* View definition from pg_get_viewdef (a SELECT query) */
+					appendPQExpBuffer(buf, " AS\n%s", viewdef);
+
 					/* Get rid of the semicolon that pg_get_viewdef appends */
 					if (buf->len > 0 && buf->data[buf->len - 1] == ';')
 						buf->data[--(buf->len)] = '\0';
+
+					/* WITH [LOCAL|CASCADED] CHECK OPTION */
+					if (checkoption && checkoption[0] != '\0')
+						appendPQExpBuffer(buf, "\n WITH %s CHECK OPTION", checkoption);
 				}
 				break;
 		}
@@ -3353,6 +3414,74 @@ get_create_object_cmd(EditableObjectType
 }
 
 /*
+ * Format a reloptions array and append it to the given buffer.
+ *
+ * Note: this logic should generally match the backend's flatten_reloptions()
+ * (in adt/ruleutils.c).
+ *
+ * Returns false if the reloptions array could not be parsed (in which case
+ * nothing will have been appended to the buffer), or true on success.
+ */
+static bool
+fmtReloptionsArray(PQExpBuffer buffer, const char *reloptions)
+{
+	char	  **options;
+	int			noptions;
+	int			i;
+
+	if (!parsePGArray(reloptions, &options, &noptions))
+	{
+		if (options)
+			free(options);
+		return false;
+	}
+
+	for (i = 0; i < noptions; i++)
+	{
+		char	   *option = options[i];
+		char	   *name;
+		char	   *separator;
+		char	   *value;
+
+		/*
+		 * Each array element should have the form name=value.  If the "=" is
+		 * missing for some reason, treat it like an empty value.
+		 */
+		name = option;
+		separator = strchr(option, '=');
+		if (separator)
+		{
+			*separator = '\0';
+			value = separator + 1;
+		}
+		else
+			value = "";
+
+		if (i > 0)
+			appendPQExpBufferStr(buffer, ", ");
+		appendPQExpBuffer(buffer, "%s=", fmtId(name));
+
+		/*
+		 * In general we need to quote the value; but to avoid unnecessary
+		 * clutter, do not quote if it is an identifier that would not need
+		 * quoting.  (We could also allow numbers, but that is a bit trickier
+		 * than it looks --- for example, are leading zeroes significant?  We
+		 * don't want to assume very much here about what custom reloptions
+		 * might mean.)
+		 */
+		if (strcmp(fmtId(value), value) == 0)
+			appendPQExpBufferStr(buffer, value);
+		else
+			appendStringLiteralConn(buffer, value, pset.db);
+	}
+
+	if (options)
+		free(options);
+
+	return true;
+}
+
+/*
  * If the given argument of \ef or \ev ends with a line number, delete the line
  * number from the argument string and return it as an integer.  (We need
  * this kluge because we're too lazy to parse \ef's function or \ev's view
#18Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Dean Rasheed (#17)
2 attachment(s)
Re: psql :: support for \ev viewname and \sv viewname

On 27 April 2016 at 08:36, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:

Here is a rough patch based on the way pg_dump handles this. It still
needs a bit of polishing -- in particular I think fmtReloptionsArray()
(copied from pg_dump) should probably be moved to string_utils.c so
that it can be shared between pg_dump and psql. Also, I'm not sure
that's the best name for it -- I think appendReloptionsArray() is a
more accurate description of what is does.

Here are updated patches doing that --- the first moves
fmtReloptionsArray() from pg_dump.c to fe_utils/string_utils.c so that
it can be shared by pg_dump and psql, and renames it to
appendReloptionsArray(). The second patch fixes the actual psql bug.

Regards,
Dean

Attachments:

0001-Move-and-rename-fmtReloptionsArray.patchtext/x-patch; charset=US-ASCII; name=0001-Move-and-rename-fmtReloptionsArray.patchDownload
From a01c897bf2945f8ebef39bf3c58bb14e1739abf9 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Mon, 2 May 2016 11:27:15 +0100
Subject: [PATCH 1/2] Move and rename fmtReloptionsArray().

Move fmtReloptionsArray() from pg_dump.c to string_utils.c so that it
is available to other frontend code. In particular psql's \ev and \sv
commands need it to handle view reloptions. Also rename the function
to appendReloptionsArray(), which is a more accurate description of
what it does.
---
 src/bin/pg_dump/pg_backup.h         |  7 ++++
 src/bin/pg_dump/pg_dump.c           | 80 +++----------------------------------
 src/fe_utils/string_utils.c         | 72 +++++++++++++++++++++++++++++++++
 src/include/fe_utils/string_utils.h |  3 ++
 4 files changed, 88 insertions(+), 74 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 83f6029..6b162a4 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -288,4 +288,11 @@ extern int	archprintf(Archive *AH, const char *fmt,...) pg_attribute_printf(2, 3
 #define appendStringLiteralAH(buf,str,AH) \
 	appendStringLiteral(buf, str, (AH)->encoding, (AH)->std_strings)
 
+#define appendReloptionsArrayAH(buf,reloptions,prefix,AH) \
+	do { \
+		if (!appendReloptionsArray(buf, reloptions, prefix, \
+								   (AH)->encoding, (AH)->std_strings)) \
+			write_msg(NULL, "WARNING: could not parse reloptions array\n"); \
+	} while (0)
+
 #endif   /* PG_BACKUP_H */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d3f5157..3005bf5 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -261,8 +261,6 @@ static void binary_upgrade_extension_member(PQExpBuffer upgrade_buffer,
 static const char *getAttrName(int attrnum, TableInfo *tblInfo);
 static const char *fmtCopyColumnList(const TableInfo *ti, PQExpBuffer buffer);
 static bool nonemptyReloptions(const char *reloptions);
-static void fmtReloptionsArray(Archive *fout, PQExpBuffer buffer,
-				   const char *reloptions, const char *prefix);
 static char *get_synchronized_snapshot(Archive *fout);
 static PGresult *ExecuteSqlQueryForSingleRow(Archive *fout, char *query);
 static void setupDumpWorker(Archive *AHX);
@@ -15046,7 +15044,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (nonemptyReloptions(tbinfo->reloptions))
 		{
 			appendPQExpBufferStr(q, " WITH (");
-			fmtReloptionsArray(fout, q, tbinfo->reloptions, "");
+			appendReloptionsArrayAH(q, tbinfo->reloptions, "", fout);
 			appendPQExpBufferChar(q, ')');
 		}
 		result = createViewAsClause(fout, tbinfo);
@@ -15301,13 +15299,14 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			if (nonemptyReloptions(tbinfo->reloptions))
 			{
 				addcomma = true;
-				fmtReloptionsArray(fout, q, tbinfo->reloptions, "");
+				appendReloptionsArrayAH(q, tbinfo->reloptions, "", fout);
 			}
 			if (nonemptyReloptions(tbinfo->toast_reloptions))
 			{
 				if (addcomma)
 					appendPQExpBufferStr(q, ", ");
-				fmtReloptionsArray(fout, q, tbinfo->toast_reloptions, "toast.");
+				appendReloptionsArrayAH(q, tbinfo->toast_reloptions, "toast.",
+										fout);
 			}
 			appendPQExpBufferChar(q, ')');
 		}
@@ -15908,7 +15907,7 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
 			if (nonemptyReloptions(indxinfo->indreloptions))
 			{
 				appendPQExpBufferStr(q, " WITH (");
-				fmtReloptionsArray(fout, q, indxinfo->indreloptions, "");
+				appendReloptionsArrayAH(q, indxinfo->indreloptions, "", fout);
 				appendPQExpBufferChar(q, ')');
 			}
 
@@ -16809,7 +16808,7 @@ dumpRule(Archive *fout, RuleInfo *rinfo)
 	{
 		appendPQExpBuffer(cmd, "ALTER VIEW %s SET (",
 						  fmtId(tbinfo->dobj.name));
-		fmtReloptionsArray(fout, cmd, rinfo->reloptions, "");
+		appendReloptionsArrayAH(cmd, rinfo->reloptions, "", fout);
 		appendPQExpBufferStr(cmd, ");\n");
 	}
 
@@ -17704,73 +17703,6 @@ nonemptyReloptions(const char *reloptions)
 }
 
 /*
- * Format a reloptions array and append it to the given buffer.
- *
- * "prefix" is prepended to the option names; typically it's "" or "toast.".
- *
- * Note: this logic should generally match the backend's flatten_reloptions()
- * (in adt/ruleutils.c).
- */
-static void
-fmtReloptionsArray(Archive *fout, PQExpBuffer buffer, const char *reloptions,
-				   const char *prefix)
-{
-	char	  **options;
-	int			noptions;
-	int			i;
-
-	if (!parsePGArray(reloptions, &options, &noptions))
-	{
-		write_msg(NULL, "WARNING: could not parse reloptions array\n");
-		if (options)
-			free(options);
-		return;
-	}
-
-	for (i = 0; i < noptions; i++)
-	{
-		char	   *option = options[i];
-		char	   *name;
-		char	   *separator;
-		char	   *value;
-
-		/*
-		 * Each array element should have the form name=value.  If the "=" is
-		 * missing for some reason, treat it like an empty value.
-		 */
-		name = option;
-		separator = strchr(option, '=');
-		if (separator)
-		{
-			*separator = '\0';
-			value = separator + 1;
-		}
-		else
-			value = "";
-
-		if (i > 0)
-			appendPQExpBufferStr(buffer, ", ");
-		appendPQExpBuffer(buffer, "%s%s=", prefix, fmtId(name));
-
-		/*
-		 * In general we need to quote the value; but to avoid unnecessary
-		 * clutter, do not quote if it is an identifier that would not need
-		 * quoting.  (We could also allow numbers, but that is a bit trickier
-		 * than it looks --- for example, are leading zeroes significant?  We
-		 * don't want to assume very much here about what custom reloptions
-		 * might mean.)
-		 */
-		if (strcmp(fmtId(value), value) == 0)
-			appendPQExpBufferStr(buffer, value);
-		else
-			appendStringLiteralAH(buffer, value, fout);
-	}
-
-	if (options)
-		free(options);
-}
-
-/*
  * Execute an SQL query and verify that we got exactly one row back.
  */
 static PGresult *
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index c57d836..aeef12c 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -462,6 +462,78 @@ parsePGArray(const char *atext, char ***itemarray, int *nitems)
 
 
 /*
+ * Format a reloptions array and append it to the given buffer.
+ *
+ * "prefix" is prepended to the option names; typically it's "" or "toast.".
+ *
+ * Returns false if the reloptions array could not be parsed (in which case
+ * nothing will have been appended to the buffer), or true on success.
+ *
+ * Note: this logic should generally match the backend's flatten_reloptions()
+ * (in adt/ruleutils.c).
+ */
+bool
+appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
+					  const char *prefix, int encoding, bool std_strings)
+{
+	char	  **options;
+	int			noptions;
+	int			i;
+
+	if (!parsePGArray(reloptions, &options, &noptions))
+	{
+		if (options)
+			free(options);
+		return false;
+	}
+
+	for (i = 0; i < noptions; i++)
+	{
+		char	   *option = options[i];
+		char	   *name;
+		char	   *separator;
+		char	   *value;
+
+		/*
+		 * Each array element should have the form name=value.  If the "=" is
+		 * missing for some reason, treat it like an empty value.
+		 */
+		name = option;
+		separator = strchr(option, '=');
+		if (separator)
+		{
+			*separator = '\0';
+			value = separator + 1;
+		}
+		else
+			value = "";
+
+		if (i > 0)
+			appendPQExpBufferStr(buffer, ", ");
+		appendPQExpBuffer(buffer, "%s%s=", prefix, fmtId(name));
+
+		/*
+		 * In general we need to quote the value; but to avoid unnecessary
+		 * clutter, do not quote if it is an identifier that would not need
+		 * quoting.  (We could also allow numbers, but that is a bit trickier
+		 * than it looks --- for example, are leading zeroes significant?  We
+		 * don't want to assume very much here about what custom reloptions
+		 * might mean.)
+		 */
+		if (strcmp(fmtId(value), value) == 0)
+			appendPQExpBufferStr(buffer, value);
+		else
+			appendStringLiteral(buffer, value, encoding, std_strings);
+	}
+
+	if (options)
+		free(options);
+
+	return true;
+}
+
+
+/*
  * processSQLNamePattern
  *
  * Scan a wildcard-pattern string and generate appropriate WHERE clauses
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index 5d3fcc2..733e337 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -42,6 +42,9 @@ extern void appendByteaLiteral(PQExpBuffer buf,
 
 extern bool parsePGArray(const char *atext, char ***itemarray, int *nitems);
 
+extern bool appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
+					  const char *prefix, int encoding, bool std_strings);
+
 extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
 					  const char *pattern,
 					  bool have_where, bool force_escape,
-- 
2.6.6

0002-Make-psql-s-ev-and-sv-commands-handle-view-reloption.patchtext/x-patch; charset=US-ASCII; name=0002-Make-psql-s-ev-and-sv-commands-handle-view-reloption.patchDownload
From 432be5ec0d672840f2b3bd92a1860003bd2658a2 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Mon, 2 May 2016 12:09:01 +0100
Subject: [PATCH 2/2] Make psql's \ev and \sv commands handle view reloptions.

Commit 8eb6407aaeb6cbd972839e356b436bb698f51cff added support for
editing and showing view definitions, but neglected to account for
view options such as security_barrier and WITH CHECK OPTION which are
not returned by pg_get_viewdef() and so need special handling.
---
 src/bin/psql/command.c | 77 +++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 70 insertions(+), 7 deletions(-)

diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 4fa7760..87adfce 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -3274,12 +3274,51 @@ get_create_object_cmd(EditableObjectType obj_type, Oid oid,
 			 * CREATE for ourselves.  We must fully qualify the view name to
 			 * ensure the right view gets replaced.  Also, check relation kind
 			 * to be sure it's a view.
+			 *
+			 * Starting with 9.2, views may have reloptions (security_barrier)
+			 * and from 9.4 onwards they may also have WITH [LOCAL|CASCADED]
+			 * CHECK OPTION.  These are not part of the view definition
+			 * returned by pg_get_viewdef() and so need to be retrieved
+			 * separately.  Materialized views (introduced in 9.3) may have
+			 * arbitrary storage parameter reloptions.
 			 */
-			printfPQExpBuffer(query,
-							  "SELECT nspname, relname, relkind, pg_catalog.pg_get_viewdef(c.oid, true) FROM "
-				 "pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n "
-							  "ON c.relnamespace = n.oid WHERE c.oid = %u",
-							  oid);
+			if (pset.sversion >= 90400)
+			{
+				printfPQExpBuffer(query,
+								  "SELECT nspname, relname, relkind, "
+								  "pg_catalog.pg_get_viewdef(c.oid, true), "
+								  "array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, "
+								  "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
+								  "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption "
+								  "FROM pg_catalog.pg_class c "
+								  "LEFT JOIN pg_catalog.pg_namespace n "
+								"ON c.relnamespace = n.oid WHERE c.oid = %u",
+								  oid);
+			}
+			else if (pset.sversion >= 90200)
+			{
+				printfPQExpBuffer(query,
+								  "SELECT nspname, relname, relkind, "
+								  "pg_catalog.pg_get_viewdef(c.oid, true), "
+								  "c.reloptions AS reloptions, "
+								  "NULL AS checkoption "
+								  "FROM pg_catalog.pg_class c "
+								  "LEFT JOIN pg_catalog.pg_namespace n "
+								"ON c.relnamespace = n.oid WHERE c.oid = %u",
+								  oid);
+			}
+			else
+			{
+				printfPQExpBuffer(query,
+								  "SELECT nspname, relname, relkind, "
+								  "pg_catalog.pg_get_viewdef(c.oid, true), "
+								  "NULL AS reloptions, "
+								  "NULL AS checkoption "
+								  "FROM pg_catalog.pg_class c "
+								  "LEFT JOIN pg_catalog.pg_namespace n "
+								"ON c.relnamespace = n.oid WHERE c.oid = %u",
+								  oid);
+			}
 			break;
 	}
 
@@ -3304,6 +3343,8 @@ get_create_object_cmd(EditableObjectType obj_type, Oid oid,
 					char	   *relname = PQgetvalue(res, 0, 1);
 					char	   *relkind = PQgetvalue(res, 0, 2);
 					char	   *viewdef = PQgetvalue(res, 0, 3);
+					char	   *reloptions = PQgetvalue(res, 0, 4);
+					char	   *checkoption = PQgetvalue(res, 0, 5);
 
 					/*
 					 * If the backend ever supports CREATE OR REPLACE
@@ -3328,11 +3369,33 @@ get_create_object_cmd(EditableObjectType obj_type, Oid oid,
 							break;
 					}
 					appendPQExpBuffer(buf, "%s.", fmtId(nspname));
-					appendPQExpBuffer(buf, "%s AS\n", fmtId(relname));
-					appendPQExpBufferStr(buf, viewdef);
+					appendPQExpBufferStr(buf, fmtId(relname));
+
+					/* reloptions, if not an empty array "{}" */
+					if (reloptions != NULL && strlen(reloptions) > 2)
+					{
+						appendPQExpBufferStr(buf, "\n WITH (");
+						if (!appendReloptionsArray(buf, reloptions, "",
+												   pset.encoding,
+												   standard_strings()))
+						{
+							psql_error("Could not parse reloptions array\n");
+							result = false;
+						}
+						appendPQExpBufferStr(buf, ")");
+					}
+
+					/* View definition from pg_get_viewdef (a SELECT query) */
+					appendPQExpBuffer(buf, " AS\n%s", viewdef);
+
 					/* Get rid of the semicolon that pg_get_viewdef appends */
 					if (buf->len > 0 && buf->data[buf->len - 1] == ';')
 						buf->data[--(buf->len)] = '\0';
+
+					/* WITH [LOCAL|CASCADED] CHECK OPTION */
+					if (checkoption && checkoption[0] != '\0')
+						appendPQExpBuffer(buf, "\n WITH %s CHECK OPTION",
+										  checkoption);
 				}
 				break;
 		}
-- 
2.6.6

#19Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Dean Rasheed (#18)
Re: psql :: support for \ev viewname and \sv viewname

On 5/2/16 8:53 AM, Dean Rasheed wrote:

Here are updated patches doing that --- the first moves
fmtReloptionsArray() from pg_dump.c to fe_utils/string_utils.c so that
it can be shared by pg_dump and psql, and renames it to
appendReloptionsArray(). The second patch fixes the actual psql bug.

This looks reasonable.

I would change appendReloptionsArrayAH() to a function and keep AH as
the first argument (similar to other functions that take such a handle).

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Peter Eisentraut (#19)
Re: psql :: support for \ev viewname and \sv viewname

On 3 May 2016 at 16:52, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

I would change appendReloptionsArrayAH() to a function and keep AH as the
first argument (similar to other functions that take such a handle).

I can understand changing it to a function, but I don't think AH
should be the first argument. All other append*() functions that
append to a buffer have the buffer as the first argument, including
the appendStringLiteralAH() macro on which this is based.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#21Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Dean Rasheed (#20)
Re: psql :: support for \ev viewname and \sv viewname

On 5/3/16 3:10 PM, Dean Rasheed wrote:

On 3 May 2016 at 16:52, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

I would change appendReloptionsArrayAH() to a function and keep AH as the
first argument (similar to other functions that take such a handle).

I can understand changing it to a function, but I don't think AH
should be the first argument. All other append*() functions that
append to a buffer have the buffer as the first argument, including
the appendStringLiteralAH() macro on which this is based.

Well, all the functions that take archive handles have that as the first
argument, so how do we consolidate that?

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#22Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Peter Eisentraut (#21)
Re: psql :: support for \ev viewname and \sv viewname

On 4 May 2016 at 01:35, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 5/3/16 3:10 PM, Dean Rasheed wrote:

On 3 May 2016 at 16:52, Peter Eisentraut

I would change appendReloptionsArrayAH() to a function and keep AH as the
first argument (similar to other functions that take such a handle).

I can understand changing it to a function, but I don't think AH
should be the first argument. All other append*() functions that
append to a buffer have the buffer as the first argument, including
the appendStringLiteralAH() macro on which this is based.

Well, all the functions that take archive handles have that as the first
argument, so how do we consolidate that?

Well, appendStringLiteralAH() takes both, so that sets a precedent.

And I think that makes sense too. The functions that take an archive
handle as their first argument are mostly functions whose primary
concern is to operate on the archive in some way. All the append*()
functions that take a buffer as their first argument are primarily
concerned with operating on the buffer. I'd say
appendStringLiteralAH() and appendReloptionsArrayAH() fall very much
into that second category. They only take an archive handle to get the
encoding and std_strings settings controlling *how* they operate on
the buffer. The main purpose of those append*() functions is to append
to a buffer, so it makes sense that that is their first argument.

All the append*() functions are consistent in their argument ordering,
including those that also take an archive handle, so I think
appendReloptionsArrayAH() should follow that pattern.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#23Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Dean Rasheed (#22)
Re: psql :: support for \ev viewname and \sv viewname

On 5/4/16 3:21 AM, Dean Rasheed wrote:

Well, appendStringLiteralAH() takes both, so that sets a precedent.

Works for me. Could you supply an updated patch with a static function
instead of a macro? Then I think this is good to go.

(bonus points for some tests)

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#24Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Peter Eisentraut (#23)
Re: psql :: support for \ev viewname and \sv viewname

On 4 May 2016 at 13:23, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 5/4/16 3:21 AM, Dean Rasheed wrote:

Well, appendStringLiteralAH() takes both, so that sets a precedent.

Works for me. Could you supply an updated patch with a static function
instead of a macro? Then I think this is good to go.

(bonus points for some tests)

OK, pushed that way.

I didn't get round to adding any tests though. I strikes me that the
most likely bugs in this area are bugs of omission, like this and the
missing PARALLEL SAFE recently fixed for functions. Ideally tests
would be able to spot those kinds of issues, but it's not obvious how
to write such tests.

Regards,
Dean

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers