review: psql: edit function, show function commands patch

Started by Jan Urbańskiover 15 years ago63 messages
#1Jan Urbański
wulczer@wulczer.org
1 attachment(s)

Hi,

here's a review of the \sf and \ef [num] patch from
http://archives.postgresql.org/message-id/162867791003290927y3ca44051p80e697bc6b19de29@mail.gmail.com

== Formatting ==

The patch has some small tabs/spaces and whitespace issues and it
applies with some offsets, I ran pgindent and rebased against HEAD,
attaching the resulting patch for your convenience.

== Functionality ==

The patch adds the following features:
* \e file.txt num -> starts a editor for the current query buffer
and puts the cursor on the [num] line
* \ef func num -> starts a editor for a function and puts the cursor
on the [num] line
* \sf func -> shows a full CREATE FUNCTION statement for the function
* \sf+ func -> the same, but with line numbers
* \sf[+] func num -> the same, but only from line num onward

It only touches psql, so no performance or backend stability worries.

In my humble opinion, only the \sf[+] is interesting, because it gives
you a copy/pasteable version of the function definition without opening
up an editor, and I can find that useful (OTOH: you can set PSQL_EDITOR
to cat and get the same effect with \ef... ok, just joking). Line
numbers are an extra touch, personally it does not thrill me too much,
but I've nothing against it.

The number variants of \e and \ef work by simply executing $EDITOR +num
file. I tried with some editors that came to my mind, and not all of
them support it (most do, though):

* emacs and emacsclient work
* vi works
* nano works
* pico works
* mcedit works
* kwrite does not work
* kedit does not work

not sure what other people (or for instance Windows people) use. Apart
from no universal support from editors, it does not save that many
keystrokes - at most a couple. In the end you can usually easily jump to
the line you want once you are inside your dream editor.

My recommendation would be to only integrate the \sf[+] part of the
patch, which will have the additional benefit of making it much smaller
and cleaner (will avoid the grotty splitting of the number from the
function name, for instance). But I'm just another user out there, maybe
others will find uses for the other cases.

I would personally not add the leading and trailing newlines to \sf
output, but that's a question of taste.

Docs could use some small grammar fixes, but other than that they're fine.

== Code ==

In \sf code there just a strncmp, so this works:
\sfblablabla funcname

The error for an empty \sf is not great, it should probably look more like
\sf: missing required argument
following the examples of \pset, \copy or \prompt.

Why is lnptr always being passed as a pointer? Looks like a unnecessary
complication and one more variable to care about. Can't we just pass lineno?

== End ==

Cheers,
Jan

Attachments:

editfce-v2.difftext/x-diff; name=editfce-v2.diffDownload
*** doc/src/sgml/ref/psql-ref.sgml
--- /tmp/CUVdHd_psql-ref.sgml	2010-07-16 13:31:53.362662393 +0200
***************
*** 1329,1335 ****
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1329,1335 ----
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional> <optional> linenumber </optional></literal></term>
  
          <listitem>
          <para>
***************
*** 1359,1370 ****
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1359,1376 ----
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor.
+         </para>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional> <optional> linenumber </optional> </literal></term>
  
          <listitem>
          <para>
***************
*** 1387,1392 ****
--- 1393,1405 ----
           If no function is specified, a blank <command>CREATE FUNCTION</>
           template is presented for editing.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor. It count lines from start of function body, not from
+         start of text.
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 2106,2111 ****
--- 2119,2136 ----
  
  
        <varlistentry>
+         <term><literal>\sf[+] <replaceable class="parameter">function_description</replaceable> <optional> linenumber </optional> </literal></term>
+ 
+         <listitem>
+         <para>
+          This command fetches and shows the definition of the named function,
+          in the form of a <command>CREATE OR REPLACE FUNCTION</> command.
+          If the form <literal>\sf+</literal> is used, then lines are numbered.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
          <term><literal>\t</literal></term>
          <listitem>
          <para>
***************
*** 2113,2118 ****
--- 2138,2149 ----
          footer. This command is equivalent to <literal>\pset
          tuples_only</literal> and is provided for convenience.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor.
+         </para>
          </listitem>
        </varlistentry>
  
*** src/bin/psql/command.c
--- /tmp/uM1Twe_command.c	2010-07-16 13:31:53.366666075 +0200
***************
*** 57,63 ****
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		bool *edited);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
--- 57,63 ----
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		bool *edited, int *lineno);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
***************
*** 488,500 ****
  		else
  		{
  			char	   *fname;
  
  			fname = psql_scan_slash_option(scan_state,
  										   OT_NORMAL, NULL, true);
  			expand_tilde(&fname);
  			if (fname)
  				canonicalize_path(fname);
! 			if (do_edit(fname, query_buf, NULL))
  				status = PSQL_CMD_NEWEDIT;
  			else
  				status = PSQL_CMD_ERROR;
--- 488,517 ----
  		else
  		{
  			char	   *fname;
+ 			char	   *lineno;
+ 			int			ln,
+ 					   *lnptr = NULL;
+ 
  
  			fname = psql_scan_slash_option(scan_state,
  										   OT_NORMAL, NULL, true);
+ 
+ 			/* try to get lineno */
+ 			if (fname)
+ 			{
+ 				lineno = psql_scan_slash_option(scan_state,
+ 												OT_NORMAL, NULL, true);
+ 				if (lineno)
+ 				{
+ 					ln = atoi(lineno);
+ 					lnptr = &ln;
+ 				}
+ 			}
+ 
  			expand_tilde(&fname);
  			if (fname)
  				canonicalize_path(fname);
! 			if (do_edit(fname, query_buf, NULL, lnptr))
  				status = PSQL_CMD_NEWEDIT;
  			else
  				status = PSQL_CMD_ERROR;
***************
*** 508,513 ****
--- 525,533 ----
  	 */
  	else if (strcmp(cmd, "ef") == 0)
  	{
+ 		int			lineno;
+ 		int		   *lnptr = NULL;
+ 
  		if (!query_buf)
  		{
  			psql_error("no query buffer\n");
***************
*** 520,525 ****
--- 540,574 ----
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
+ 
+ 			/*
+ 			 * parse linenumber from the right
+ 			 */
+ 			if (func && *func)
+ 			{
+ 				char	   *endfunc = func + strlen(func) - 1;
+ 				char	   *c;
+ 
+ 				c = endfunc;
+ 				while (c >= func)
+ 					if (!isdigit(*c))
+ 						break;
+ 					else
+ 						c--;
+ 
+ 				if (c < endfunc && c > func)
+ 				{
+ 					if (*c == ' ' || *c == ')')
+ 					{
+ 						c++;
+ 						/* append rows for SQL decoration */
+ 						lineno = atoi(c) + 3;
+ 						*c = '\0';
+ 						lnptr = &lineno;
+ 					}
+ 				}
+ 			}
+ 
  			if (!func)
  			{
  				/* set up an empty command to fill in */
***************
*** 549,555 ****
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, &edited))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
--- 598,604 ----
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, &edited, lnptr))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
***************
*** 944,949 ****
--- 993,1103 ----
  		free(fname);
  	}
  
+ 	/*
+ 	 * \sf -- show the named function
+ 	 */
+ 	else if (strncmp(cmd, "sf", 2) == 0)
+ 	{
+ 		int			lineno;
+ 		int		   *lnptr = NULL;
+ 		bool		with_lno = false;
+ 
+ 		if (strcmp(cmd, "sf+") == 0)
+ 			with_lno = true;
+ 
+ 		if (!query_buf)
+ 		{
+ 			psql_error("no query buffer\n");
+ 			status = PSQL_CMD_ERROR;
+ 		}
+ 		else
+ 		{
+ 			char	   *func;
+ 			Oid			foid = InvalidOid;
+ 
+ 			func = psql_scan_slash_option(scan_state,
+ 										  OT_WHOLE_LINE, NULL, true);
+ 
+ 			/*
+ 			 * parse linenumber from the right
+ 			 */
+ 			if (func && *func)
+ 			{
+ 				char	   *endfunc = func + strlen(func) - 1;
+ 				char	   *c;
+ 
+ 				c = endfunc;
+ 				while (c >= func)
+ 					if (!isdigit(*c))
+ 						break;
+ 					else
+ 						c--;
+ 
+ 				if (c < endfunc && c > func)
+ 				{
+ 					if (*c == ' ' || *c == ')')
+ 					{
+ 						c++;
+ 						/* append rows for SQL decoration */
+ 						lineno = atoi(c) + 3;
+ 						*c = '\0';
+ 						lnptr = &lineno;
+ 					}
+ 				}
+ 			}
+ 
+ 			if (!func)
+ 			{
+ 				/* show error for empty command */
+ 				psql_error("missing a function name\n");
+ 				status = PSQL_CMD_ERROR;
+ 			}
+ 			else if (!lookup_function_oid(pset.db, func, &foid))
+ 			{
+ 				/* error already reported */
+ 				status = PSQL_CMD_ERROR;
+ 			}
+ 			else if (!get_create_function_cmd(pset.db, foid, query_buf))
+ 			{
+ 				/* error already reported */
+ 				status = PSQL_CMD_ERROR;
+ 			}
+ 			if (func)
+ 				free(func);
+ 		}
+ 
+ 		if (status != PSQL_CMD_ERROR)
+ 		{
+ 			int			lineno = 0;
+ 			char	   *c = query_buf->data;
+ 			char	   *ptr;
+ 
+ 			printf("\n");
+ 			while (*c)
+ 			{
+ 				for (ptr = c; *c != '\n'; c++);
+ 				*c++ = '\0';
+ 				lineno++;
+ 
+ 				if (lnptr && (*lnptr > lineno))
+ 					continue;
+ 
+ 				if (!with_lno)
+ 					printf("%s\n", ptr);
+ 				else
+ 				{
+ 					/* don't show lineno for first three rows and last row */
+ 					if ((*c == '\0' && lineno != 3) || lineno < 4)
+ 						printf("****  %s\n", ptr);
+ 					else
+ 						printf("%4d  %s\n", lineno - 3, ptr);
+ 				}
+ 			}
+ 			printf("\n");
+ 			fflush(stdout);
+ 		}
+ 	}
+ 
  	/* \set -- generalized set variable/option command */
  	else if (strcmp(cmd, "set") == 0)
  	{
***************
*** 1517,1523 ****
   */
  
  static bool
! editFile(const char *fname)
  {
  	const char *editorName;
  	char	   *sys;
--- 1671,1677 ----
   */
  
  static bool
! editFile(const char *fname, int *lineno)
  {
  	const char *editorName;
  	char	   *sys;
***************
*** 1541,1551 ****
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
  #ifndef WIN32
! 	sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
--- 1695,1714 ----
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	sys = pg_malloc(strlen(editorName) + strlen(fname) + 20 + 1);
  #ifndef WIN32
! 	if (!lineno)
! 		sprintf(sys, "exec %s '%s'", editorName, fname);
! 	else
! 		sprintf(sys, "exec %s +%d '%s'", editorName, *lineno, fname);
  #else
! 	if (!lineno)
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
! 	else
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" +%d \"%s\"" SYSTEMQUOTE,
! 				editorName,
! 				*lineno,
! 				fname);
  #endif
  	result = system(sys);
  	if (result == -1)
***************
*** 1560,1566 ****
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
--- 1723,1729 ----
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited, int *lineno)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
***************
*** 1652,1658 ****
  
  	/* call editor */
  	if (!error)
! 		error = !editFile(fname);
  
  	if (!error && stat(fname, &after) != 0)
  	{
--- 1815,1821 ----
  
  	/* call editor */
  	if (!error)
! 		error = !editFile(fname, lineno);
  
  	if (!error && stat(fname, &after) != 0)
  	{
*** src/bin/psql/help.c
--- /tmp/g3JMge_help.c	2010-07-16 13:31:53.370666106 +0200
***************
*** 174,186 ****
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE]              edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME]         edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
--- 174,187 ----
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE] [lno]        edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME] [lno]   edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
+ 	fprintf(output, _("  \\sf[+] FUNCNAME [lno]  show finction definition\n"));
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
*** src/bin/psql/tab-complete.c
--- /tmp/ERVkIe_tab-complete.c	2010-07-16 13:31:53.370666106 +0200
***************
*** 644,650 ****
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
--- 644,650 ----
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\sf", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
***************
*** 2502,2507 ****
--- 2502,2510 ----
  	else if (strcmp(prev_wd, "\\ef") == 0)
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
  
+ 	else if (strncmp(prev_wd, "\\sf", 2) == 0)
+ 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+ 
  	else if (strcmp(prev_wd, "\\encoding") == 0)
  		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
  	else if (strcmp(prev_wd, "\\h") == 0 || strcmp(prev_wd, "\\help") == 0)
#2Pavel Stehule
pavel.stehule@gmail.com
In reply to: Jan Urbański (#1)
Re: review: psql: edit function, show function commands patch

Hello

2010/7/16 Jan Urbański <wulczer@wulczer.org>:

Hi,

here's a review of the \sf and \ef [num] patch from
http://archives.postgresql.org/message-id/162867791003290927y3ca44051p80e697bc6b19de29@mail.gmail.com

== Formatting ==

The patch has some small tabs/spaces and whitespace  issues and it applies
with some offsets, I ran pgindent and rebased against HEAD, attaching the
resulting patch for your convenience.

== Functionality ==

The patch adds the following features:
 * \e file.txt num  ->  starts a editor for the current query buffer and
puts the cursor on the [num] line
 * \ef func num -> starts a editor for a function and puts the cursor on the
[num] line
 * \sf func -> shows a full CREATE FUNCTION statement for the function
 * \sf+ func -> the same, but with line numbers
 * \sf[+] func num -> the same, but only from line num onward

It only touches psql, so no performance or backend stability worries.

In my humble opinion, only the \sf[+] is interesting, because it gives you a
copy/pasteable version of the function definition without opening up an
editor, and I can find that useful (OTOH: you can set PSQL_EDITOR to cat and
get the same effect with \ef... ok, just joking). Line numbers are an extra
touch, personally it does not thrill me too much, but I've nothing against
it.

The number variants of \e and \ef work by simply executing $EDITOR +num
file. I tried with some editors that came to my mind, and not all of them
support it (most do, though):

 * emacs and emacsclient work
 * vi works
 * nano works
 * pico works
 * mcedit works
 * kwrite does not work
 * kedit does not work

not sure what other people (or for instance Windows people) use. Apart from
no universal support from editors, it does not save that many keystrokes -
at most a couple. In the end you can usually easily jump to the line you
want once you are inside your dream editor.

I disagree. When I working on servers of my customers there are some
default configuration - default editor is usually vi or vim. I cannot
change my preferred editor there and \ef n - can help me very much (I
know only one command for vi - :q :)). I am looking on KDE. There is
usual parameter --line.

I propose following solution - to add a system variable
PSQL_EDITOR_GOTOLINE_COMMAND that will contains a command for editors
without general +n navigation.

so you can set a PSQL_EDITOR_GOTOLINE_COMMAND='--line %d'

and when this string will be used, when will not be empty without default.

My recommendation would be to only integrate the \sf[+] part of the patch,
which will have the additional benefit of making it much smaller and cleaner
(will avoid the grotty splitting of the number from the function name, for
instance). But I'm just another user out there, maybe others will find uses
for the other cases.

I would personally not add the leading and trailing newlines to \sf output,
but that's a question of taste.

Maybe better is using a title - so source code will use a format like
standard result set.

I'll look on it.

Docs could use some small grammar fixes, but other than that they're fine.

== Code ==

In \sf code there just a strncmp, so this works:
\sfblablabla funcname

will be fiexed

The error for an empty \sf is not great, it should probably look more like
\sf: missing required argument
following the examples of \pset, \copy or \prompt.

Why is lnptr always being passed as a pointer? Looks like a unnecessary
complication and one more variable to care about. Can't we just pass lineno?

because I would not to use a magic constant. when lnptr is NULL, then
lineno is undefined.

Thank you very much

Pavel Stehule

Show quoted text

== End ==

Cheers,
Jan

#3Pavel Stehule
pavel.stehule@gmail.com
In reply to: Jan Urbański (#1)
1 attachment(s)
Re: review: psql: edit function, show function commands patch

Hello

I am sending a actualised patch.

I understand to your criticism about line numbering. I have to agree.
With line numbering the patch is longer. I have a one significant
reason for it. There are not conformance between line numbers of
CREATE FUNCTION statement and line numbers of function's body. Raise
exception, syntactic errors use a function body line numbers. But
users doesn't see alone function's body. He see a CREATE FUNCTION
statement. What more - and this depend on programmer style sometimes
is necessary to correct line number with -1. Now I have enough
knowledges of plpgsql, and I am possible to see a problematic row, but
it little bit hard task for beginners. You can see.

**** CREATE OR REPLACE FUNCTION public.foo()
**** RETURNS integer
**** LANGUAGE plpgsql
**** AS $function$
1 begin
2 return 10/0;
3 end;
**** $function$

postgres=# select foo();
ERROR: division by zero
CONTEXT: SQL statement "SELECT 10/0"
PL/pgSQL function "foo" line 2 at RETURN
postgres=#

**** CREATE OR REPLACE FUNCTION public.foo()
**** RETURNS integer
**** LANGUAGE plpgsql
1 AS $function$ begin
2 return 10/0;
3 end;
**** $function$

postgres=# select foo();
ERROR: division by zero
CONTEXT: SQL statement "SELECT 10/0"
PL/pgSQL function "foo" line 2 at RETURN

This is very trivial example - for more complex functions, the correct
line numbering is more useful.

2010/7/16 Jan Urbański <wulczer@wulczer.org>:

Hi,

here's a review of the \sf and \ef [num] patch from
http://archives.postgresql.org/message-id/162867791003290927y3ca44051p80e697bc6b19de29@mail.gmail.com

== Formatting ==

The patch has some small tabs/spaces and whitespace  issues and it applies
with some offsets, I ran pgindent and rebased against HEAD, attaching the
resulting patch for your convenience.

== Functionality ==

The patch adds the following features:
 * \e file.txt num  ->  starts a editor for the current query buffer and
puts the cursor on the [num] line
 * \ef func num -> starts a editor for a function and puts the cursor on the
[num] line
 * \sf func -> shows a full CREATE FUNCTION statement for the function
 * \sf+ func -> the same, but with line numbers
 * \sf[+] func num -> the same, but only from line num onward

It only touches psql, so no performance or backend stability worries.

In my humble opinion, only the \sf[+] is interesting, because it gives you a
copy/pasteable version of the function definition without opening up an
editor, and I can find that useful (OTOH: you can set PSQL_EDITOR to cat and
get the same effect with \ef... ok, just joking). Line numbers are an extra
touch, personally it does not thrill me too much, but I've nothing against
it.

The number variants of \e and \ef work by simply executing $EDITOR +num
file. I tried with some editors that came to my mind, and not all of them
support it (most do, though):

 * emacs and emacsclient work
 * vi works
 * nano works
 * pico works
 * mcedit works
 * kwrite does not work
 * kedit does not work

not sure what other people (or for instance Windows people) use. Apart from
no universal support from editors, it does not save that many keystrokes -
at most a couple. In the end you can usually easily jump to the line you
want once you are inside your dream editor.

I found, so there are a few editor for ms win with support for direct
line navigation. There isn't any standart. Next I tested kwrite and
KDE. There is usual a parameter --line. So you can you use a system
variable PSQL_NAVIGATION_COMMAND - for example - for KDE

PSQL_NAVIGATION_COMMAND="--line "

default is +n

My recommendation would be to only integrate the \sf[+] part of the patch,
which will have the additional benefit of making it much smaller and cleaner
(will avoid the grotty splitting of the number from the function name, for
instance). But I'm just another user out there, maybe others will find uses
for the other cases.

I disagree. You cannot use a text editor command, because SQL
linenumbers are not equal to body line numbers.

I would personally not add the leading and trailing newlines to \sf output,
but that's a question of taste.

Docs could use some small grammar fixes, but other than that they're fine.

== Code ==

In \sf code there just a strncmp, so this works:
\sfblablabla funcname

fixed

The error for an empty \sf is not great, it should probably look more like
\sf: missing required argument
following the examples of \pset, \copy or \prompt.

Why is lnptr always being passed as a pointer? Looks like a unnecessary
complication and one more variable to care about. Can't we just pass lineno?

fixed

I removed redundant code and appended a more comments/

Regards

Pavel Stehule

Show quoted text

== End ==

Cheers,
Jan

Attachments:

editfce.diffapplication/octet-stream; name=editfce.diffDownload
*** ./doc/src/sgml/ref/psql-ref.sgml.orig	2010-03-21 01:43:40.000000000 +0100
--- ./doc/src/sgml/ref/psql-ref.sgml	2010-03-29 18:19:24.496359110 +0200
***************
*** 1328,1334 ****
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1328,1334 ----
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional> <optional> linenumber </optional></literal></term>
  
          <listitem>
          <para>
***************
*** 1358,1369 ****
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1358,1375 ----
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor.
+         </para>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional> <optional> linenumber </optional> </literal></term>
  
          <listitem>
          <para>
***************
*** 1386,1391 ****
--- 1392,1404 ----
           If no function is specified, a blank <command>CREATE FUNCTION</>
           template is presented for editing.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor. It count lines from start of function body, not from
+         start of text.
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 2105,2110 ****
--- 2118,2135 ----
  
  
        <varlistentry>
+         <term><literal>\sf[+] <replaceable class="parameter">function_description</replaceable> <optional> linenumber </optional> </literal></term>
+ 
+         <listitem>
+         <para>
+          This command fetches and shows the definition of the named function,
+          in the form of a <command>CREATE OR REPLACE FUNCTION</> command.
+          If the form <literal>\sf+</literal> is used, then lines are numbered.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
          <term><literal>\t</literal></term>
          <listitem>
          <para>
***************
*** 2112,2117 ****
--- 2137,2148 ----
          footer. This command is equivalent to <literal>\pset
          tuples_only</literal> and is provided for convenience.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor.
+         </para>
          </listitem>
        </varlistentry>
  
*** ./src/bin/psql/command.c.orig	2010-03-29 13:46:55.227484734 +0200
--- ./src/bin/psql/command.c	2010-07-21 14:25:32.453803925 +0200
***************
*** 57,63 ****
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		bool *edited);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
--- 57,63 ----
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		bool *edited, int lineno);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
***************
*** 65,70 ****
--- 65,72 ----
  static void minimal_error_message(PGresult *res);
  
  static void printSSLInfo(void);
+ static bool first_row_is_empty(char *src);
+ static int get_lineno_for_navigation(char *func, backslashResult *status);
  
  #ifdef WIN32
  static void checkWin32Codepage(void);
***************
*** 488,503 ****
  		else
  		{
  			char	   *fname;
! 
  			fname = psql_scan_slash_option(scan_state,
! 										   OT_NORMAL, NULL, true);
! 			expand_tilde(&fname);
  			if (fname)
! 				canonicalize_path(fname);
! 			if (do_edit(fname, query_buf, NULL))
! 				status = PSQL_CMD_NEWEDIT;
! 			else
! 				status = PSQL_CMD_ERROR;
  			free(fname);
  		}
  	}
--- 490,527 ----
  		else
  		{
  			char	   *fname;
! 			char		*ln;
! 			int  lineno;
! 			
  			fname = psql_scan_slash_option(scan_state,
! 								   OT_NORMAL, NULL, true);
! 			
! 			/* try to get lineno */
  			if (fname)
! 			{
! 				ln = psql_scan_slash_option(scan_state,
! 									   OT_NORMAL, NULL, true);
! 				if (ln)
! 				{
! 					if (atoi(ln) < 1)
! 					{
! 						psql_error("line number is unacceptable\n");
! 						status = PSQL_CMD_ERROR;
! 					}
! 					else
! 						lineno = atoi(ln);
! 				}
! 			}
! 			if (status != PSQL_CMD_ERROR)
! 			{
! 				expand_tilde(&fname);
! 				if (fname)
! 					canonicalize_path(fname);
! 				if (do_edit(fname, query_buf, NULL, lineno))
! 					status = PSQL_CMD_NEWEDIT;
! 				else
! 					status = PSQL_CMD_ERROR;
! 			}
  			free(fname);
  		}
  	}
***************
*** 508,513 ****
--- 532,539 ----
  	 */
  	else if (strcmp(cmd, "ef") == 0)
  	{
+ 		int	lineno;
+ 	
  		if (!query_buf)
  		{
  			psql_error("no query buffer\n");
***************
*** 520,555 ****
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			if (!func)
  			{
! 				/* set up an empty command to fill in */
! 				printfPQExpBuffer(query_buf,
! 								  "CREATE FUNCTION ( )\n"
! 								  " RETURNS \n"
! 								  " LANGUAGE \n"
! 								  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 								  "AS $function$\n"
! 								  "\n$function$\n");
! 			}
! 			else if (!lookup_function_oid(pset.db, func, &foid))
! 			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
! 			}
! 			else if (!get_create_function_cmd(pset.db, foid, query_buf))
! 			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
  			}
- 			if (func)
- 				free(func);
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, &edited))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
--- 546,590 ----
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			lineno = get_lineno_for_navigation(func, &status);
! 			
! 			if (status != PSQL_CMD_ERROR)
  			{
! 				if (!func)
! 				{
! 					/* set up an empty command to fill in */
! 					printfPQExpBuffer(query_buf,
! 									  "CREATE FUNCTION ( )\n"
! 									  " RETURNS \n"
! 									  " LANGUAGE \n"
! 									  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 									  "AS $function$\n"
! 									  "\n$function$\n");
! 				}
! 				else if (!lookup_function_oid(pset.db, func, &foid))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
! 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
! 				if (func)
! 					free(func);
  			}
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			/* correct lineno when wirst row of function's body is empty */
! 			if (!first_row_is_empty(query_buf->data))
! 				lineno--;
! 
! 			if (!do_edit(0, query_buf, &edited, lineno))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
***************
*** 933,938 ****
--- 968,1061 ----
  		free(fname);
  	}
  
+ 	/*
+ 	 * \sf -- show the named function
+ 	 */
+ 	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+ 	{
+ 		int	skip_lines = -1;
+ 		bool	with_lno;
+ 		
+ 		with_lno = (strcmp(cmd, "sf+") == 0);
+ 		
+ 		if (!query_buf)
+ 		{
+ 			psql_error("no query buffer\n");
+ 			status = PSQL_CMD_ERROR;
+ 		}
+ 		else
+ 		{
+ 			char	   *func;
+ 			Oid			foid = InvalidOid;
+ 
+ 			func = psql_scan_slash_option(scan_state,
+ 										  OT_WHOLE_LINE, NULL, true);
+ 			skip_lines = get_lineno_for_navigation(func, &status) - 1;
+ 			
+ 			if (status != PSQL_CMD_ERROR)
+ 			{
+ 				if (!func)
+ 				{
+ 					/* show error for empty command */
+ 					psql_error("missing a function name\n");
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!lookup_function_oid(pset.db, func, &foid))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 			}
+ 			if (func)
+ 				free(func);
+ 		}
+ 
+ 		if (status != PSQL_CMD_ERROR)
+ 		{
+ 			int	lineno = 0;
+ 			char	*c = query_buf->data;
+ 			char	*ptr;
+ 			
+ 			/* 
+ 			 * PL doesn't calculate first row of function's body
+ 			 * when first row is empty. So checks first row, and
+ 			 * correct lineno when it is necessary.
+ 			 */
+ 			if (first_row_is_empty(query_buf->data))
+ 				lineno--;
+ 
+ 			while (*c)
+ 			{
+ 				/* find next end of line */
+ 				for (ptr = c; *c != '\n'; c++);
+ 				*c++ = '\0';
+ 				lineno++;
+ 				
+ 				/* skip first n lines */
+ 				if (skip_lines > 0 && (skip_lines > lineno))
+ 					continue;
+ 				
+ 				if (!with_lno)
+ 					printf("%s\n", ptr);
+ 				else
+ 				{
+ 					/* don't show lineno for first three rows and last row */
+ 					if ((*c == '\0' && lineno != 3) || lineno < 4)
+ 						printf("****  %s\n", ptr);
+ 					else
+ 						printf("%4d  %s\n", lineno - 3, ptr);
+ 				}
+ 			}
+ 			printf("\n");
+ 			fflush(stdout);
+ 		}
+ 	}
+ 
  	/* \set -- generalized set variable/option command */
  	else if (strcmp(cmd, "set") == 0)
  	{
***************
*** 1506,1514 ****
   */
  
  static bool
! editFile(const char *fname)
  {
  	const char *editorName;
  	char	   *sys;
  	int			result;
  
--- 1629,1638 ----
   */
  
  static bool
! editFile(const char *fname, int lineno)
  {
  	const char *editorName;
+ 	const char *navigation_cmd;
  	char	   *sys;
  	int			result;
  
***************
*** 1522,1527 ****
--- 1646,1655 ----
  		editorName = getenv("VISUAL");
  	if (!editorName)
  		editorName = DEFAULT_EDITOR;
+ 	
+ 	navigation_cmd = getenv("PSQL_NAVIGATION_COMMAND");
+ 	if (!navigation_cmd)
+ 		navigation_cmd = DEFAULT_NAVIGATION_COMMAND;
  
  	/*
  	 * On Unix the EDITOR value should *not* be quoted, since it might include
***************
*** 1530,1540 ****
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
  #ifndef WIN32
! 	sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
--- 1658,1678 ----
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	sys = pg_malloc(strlen(editorName) + strlen(fname) + 20 + 1);
  #ifndef WIN32
! 	if (lineno > 0)
! 		sprintf(sys, "exec %s %s%d '%s'", editorName, navigation_cmd, lineno, fname);
! 	else
! 		sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	if (lineno > 0)
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" %s%d \"%s\"" SYSTEMQUOTE, 
! 									    editorName, 
! 									    navigation_cmd,
! 									    lineno, 
! 									    fname);
! 	else
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
***************
*** 1549,1555 ****
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
--- 1687,1693 ----
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited, int lineno)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
***************
*** 1641,1647 ****
  
  	/* call editor */
  	if (!error)
! 		error = !editFile(fname);
  
  	if (!error && stat(fname, &after) != 0)
  	{
--- 1779,1785 ----
  
  	/* call editor */
  	if (!error)
! 		error = !editFile(fname, lineno);
  
  	if (!error && stat(fname, &after) != 0)
  	{
***************
*** 2137,2142 ****
--- 2275,2303 ----
  	return result;
  }
  
+ 
+ /*
+  * When first row of prosrc has only new-line char, then is ignored.
+  * This functions returns true, when first row is empty, else returns
+  * false.
+  */
+ static bool
+ first_row_is_empty(char *src)
+ {
+ 	char *ptr;
+ 	
+ #define DEFAULT_BODY_SEPARATOR		"$function$"
+ 	
+ 	ptr = strstr(src, DEFAULT_BODY_SEPARATOR);
+ 	
+ 	psql_assert(ptr != NULL);
+ 	
+ 	ptr += strlen(DEFAULT_BODY_SEPARATOR);
+ 	
+ 	return *ptr == '\n';
+ }
+ 
+ 
  /*
   * Fetches the "CREATE OR REPLACE FUNCTION ..." command that describes the
   * function with the given OID.  If successful, the result is stored in buf.
***************
*** 2169,2174 ****
--- 2330,2336 ----
  	return result;
  }
  
+ 
  /*
   * Report just the primary error; this is to avoid cluttering the output
   * with, for instance, a redisplay of the internally generated query
***************
*** 2197,2199 ****
--- 2359,2435 ----
  
  	destroyPQExpBuffer(msg);
  }
+ 
+ 
+ /*
+  * Returns lineno used in \sf and \ef commands. 
+  *
+  * These commands can be completed with number used as line
+  * number for navigation in showed lines / open file. The most 
+  * simple method for parsing is reading isolated digits from 
+  * right - \ef foo nn, \ef foo(..)nn. Returns -1 when
+  * lineno isn't defined.
+ */
+ static int
+ get_lineno_for_navigation(char *func, backslashResult *status)
+ {
+ 	char *endfunc;
+ 	char *c;
+ 	int lineno = -1;
+ 	
+ 	if (!func)
+ 		return lineno;
+ 	
+ 	endfunc = func + strlen(func) - 1;
+ 	c = endfunc;
+ 	
+ 	/* skip useles whitespaces */
+ 	while (c >= func)
+ 		if (isblank(*c))
+ 			c--;
+ 		else
+ 			break;
+ 	
+ 	/* search the most left digit of continuously number */
+ 	while (c >= func)
+ 		if (!isdigit(*c))
+ 			break;
+ 		else
+ 			c--;
+ 	
+ 	/* 
+ 	 * when left char isn't blank and isn't a right parenthesis
+ 	 * then command hasn't a lineno.
+ 	 */
+ 	if (c < endfunc && c > func)
+ 	{
+ 		if (isblank(*c) || *c == ')')
+ 		{
+ 			c++;
+ 			
+ 			if (atoi(c) < 1)
+ 			{
+ 				psql_error("line number is unacceptable\n");
+ 				*status = PSQL_CMD_ERROR;
+ 			}
+ 			else
+ 			{
+ 				/*
+ 				 * Function get_create_function_cmd appends a few lines
+ 				 * to function's body. But we would to like use a line 
+ 				 * numbers use a PL parsers - so add three lines to lineno:
+ 				 *   CREATE OR REPLACE FUNCTION ..
+ 				 *     RETURNS ...
+ 				 *     LANGUAGE ...
+ 				 *     AS $finction$
+ 				 */
+ 				lineno = atoi(c) + 4;
+ 				
+ 				/* remove lineno from function descriptor */
+ 				*c = '\0';
+ 			}
+ 		}
+ 	}
+ 	
+ 	return lineno;
+ }
*** ./src/bin/psql/help.c.orig	2010-03-07 18:02:34.000000000 +0100
--- ./src/bin/psql/help.c	2010-03-29 16:31:31.563360499 +0200
***************
*** 174,186 ****
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE]              edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME]         edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
--- 174,187 ----
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE] [lno]        edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME] [lno]   edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
+ 	fprintf(output, _("  \\sf[+] FUNCNAME [lno]  show finction definition\n"));
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
*** ./src/bin/psql/settings.h.orig	2010-01-02 17:57:59.000000000 +0100
--- ./src/bin/psql/settings.h	2010-07-20 16:10:36.288804290 +0200
***************
*** 18,25 ****
--- 18,27 ----
  
  #if defined(WIN32) || defined(__CYGWIN__)
  #define DEFAULT_EDITOR	"notepad.exe"
+ #define DEFAULT_NAVIGATION_COMMAND	" /"
  #else
  #define DEFAULT_EDITOR	"vi"
+ #define DEFAULT_NAVIGATION_COMMAND 	" +"
  #endif
  
  #define DEFAULT_PROMPT1 "%/%R%# "
*** ./src/bin/psql/tab-complete.c.orig	2010-02-26 03:01:20.000000000 +0100
--- ./src/bin/psql/tab-complete.c	2010-03-29 16:27:06.543359758 +0200
***************
*** 639,645 ****
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
--- 639,645 ----
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\sf", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
***************
*** 2446,2451 ****
--- 2446,2454 ----
  
  	else if (strcmp(prev_wd, "\\ef") == 0)
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+ 	
+ 		else if (strncmp(prev_wd, "\\sf", 2) == 0)
+ 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
  
  	else if (strcmp(prev_wd, "\\encoding") == 0)
  		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
#4Dimitri Fontaine
dfontaine@hi-media.com
In reply to: Pavel Stehule (#3)
1 attachment(s)
Re: review: psql: edit function, show function commands patch

Pavel Stehule <pavel.stehule@gmail.com> writes:

**** CREATE OR REPLACE FUNCTION public.foo()
**** RETURNS integer
**** LANGUAGE plpgsql
1 AS $function$ begin
2 return 10/0;
3 end;
**** $function$

This is very trivial example - for more complex functions, the correct
line numbering is more useful.

I completely agree with this, in-functions line numbering is a
must-have. I'd like psql to handle that better.

That said, I usually edit functions in Emacs on my workstation. I did
implement a linum-mode extension to show PL/pgSQL line numbers in
addition to the buffer line numbers in emacs, but it failed to work with
this "AS $function$ begin" on the same line example. It's fixed in the
attached, should there be any users of it.

Regards,
--
dim

Attachments:

dim-pgsql.elapplication/emacs-lispDownload
#5Robert Haas
robertmhaas@gmail.com
In reply to: Jan Urbański (#1)
Re: review: psql: edit function, show function commands patch

On Fri, Jul 16, 2010 at 10:29 AM, Jan Urbański <wulczer@wulczer.org> wrote:

The patch adds the following features:
 * \e file.txt num  ->  starts a editor for the current query buffer and
puts the cursor on the [num] line
 * \ef func num -> starts a editor for a function and puts the cursor on the
[num] line
 * \sf func -> shows a full CREATE FUNCTION statement for the function
 * \sf+ func -> the same, but with line numbers
 * \sf[+] func num -> the same, but only from line num onward

It only touches psql, so no performance or backend stability worries.

In my humble opinion, only the \sf[+] is interesting, because it gives you a

FWIW, I think this is all pretty useful.

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

#6Jan Urbański
wulczer@wulczer.org
In reply to: Pavel Stehule (#3)
Re: review: psql: edit function, show function commands patch

On 21/07/10 14:43, Pavel Stehule wrote:

Hello

I am sending a actualised patch.

Hi, thanks!

I understand to your criticism about line numbering. I have to
agree. With line numbering the patch is longer. I have a one
significant reason for it.

**** CREATE OR REPLACE FUNCTION public.foo() **** RETURNS integer
**** LANGUAGE plpgsql **** AS $function$ 1 begin 2 return
10/0; 3 end; **** $function$

postgres=# select foo(); ERROR: division by zero CONTEXT: SQL
statement "SELECT 10/0" PL/pgSQL function "foo" line 2 at RETURN
postgres=#

OK, that convinced me, and also others seem to like the feature. So I'll
just make code remarks this time.

In the \e handling code, if the file name was given and there is no line
number, an uninitialised variable will be passed to do_edit. I see that
you want to avoid passing a magic number to do_edit, but I think you
should just treat anything <0 as that magic value, initialise lineno
with -1 and simplify all these nested ifs.

Typo in a comment:
when wirst row of function -> when first row of function

I think the #define for DEFAULT_BODY_SEPARATOR should either be at the
beginning of the file (and then also used in \ef handling) or just
hardcoded in both places.

Any reason why DEFAULT_NAVIGATION_COMMAND has a space in front of it?

Some lines have >80 characters.

If you agree that a -1 parameter do do_edit will mean "no lineno", then
I think you can change get_lineno_for_navigation to not take a
backslashResult argument and signal errors by returning -1.

In get_lineno_for_navigation you will have to protect the large comment
block with /*------ otherwise pgindent will reflow it.

PSQL_NAVIGATION_COMMAND needs to be documented in the SGML docs.

Uff, that's all from me, sorry for bringing up all these small issues, I
hope this will go in soon!

I'm going to change it to waiting on author again, mainly because of the
uninitialised variable in \d handling, the rest are just stylistic nitpicks.

Cheers,
Jan

#7Robert Haas
robertmhaas@gmail.com
In reply to: Jan Urbański (#6)
Re: review: psql: edit function, show function commands patch

On Thu, Jul 22, 2010 at 6:56 PM, Jan Urbański <wulczer@wulczer.org> wrote:

the rest are just stylistic nitpicks.

But, if the patch author doesn't fix them, the committer has to, so
your nitpicking is much appreciated, at least by me!

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

#8Pavel Stehule
pavel.stehule@gmail.com
In reply to: Jan Urbański (#6)
1 attachment(s)
Re: review: psql: edit function, show function commands patch

Hello

2010/7/23 Jan Urbański <wulczer@wulczer.org>:

On 21/07/10 14:43, Pavel Stehule wrote:

Hello

I am sending a actualised patch.

Hi, thanks!

I understand to your criticism about line numbering. I have to
agree. With line numbering the patch is longer. I have a one
significant reason for it.

****  CREATE OR REPLACE FUNCTION public.foo() ****   RETURNS integer
****   LANGUAGE plpgsql ****  AS $function$ 1  begin 2    return
10/0; 3  end; ****  $function$

postgres=# select foo(); ERROR:  division by zero CONTEXT:  SQL
statement "SELECT 10/0" PL/pgSQL function "foo" line 2 at RETURN
postgres=#

OK, that convinced me, and also others seem to like the feature. So I'll
just make code remarks this time.

In the \e handling code, if the file name was given and there is no line
number, an uninitialised variable will be passed to do_edit. I see that
you want to avoid passing a magic number to do_edit, but I think you
should just treat anything <0 as that magic value, initialise lineno
with -1 and simplify all these nested ifs.

fixed uninitialised variable. Second is little bit different - there
is three states, not only two - lineno is undefined, lineno is wrong
and lineno is correct. I would not to ignore a wrong lineno.

Typo in a comment:
when wirst row of function -> when first row of function

fixed

I think the #define for DEFAULT_BODY_SEPARATOR should either be at the
beginning of the file (and then also used in \ef handling) or just
hardcoded in both places.

this means some like only local constant - see PARAMS_ARRAY_SIZE in
same file. It is used only inside a first_row_is_empty function. It's
not used outside this function.

Any reason why DEFAULT_NAVIGATION_COMMAND has a space in front of it?

no it is useless - fixed

Some lines have >80 characters.

there are lot of longer lines - but I can't to modify other lines only
for formating. My code has max line about 90 characters (when it
doesn't respect more common form for some parts).

If you agree that a -1 parameter do do_edit will mean "no lineno", then
I think you can change get_lineno_for_navigation to not take a
backslashResult argument and signal errors by returning -1.

there are a too much magic constants, so I prefer a cleaner form with
backslashResult. The code isn't longer and it reacts better on wrong
entered value - negative value is nonsense for this purpose.

In get_lineno_for_navigation you will have to protect the large comment
block with /*------ otherwise pgindent will reflow it.

done

PSQL_NAVIGATION_COMMAND needs to be documented in the SGML docs.

I changed PSQL_NAVIGATION_COMMAND to PSQL_EDITOR_NAVIGATION_OPTION and
append a few lines - as I can - some one who can speak English has to
correct it.

Uff, that's all from me, sorry for bringing up all these small issues, I
hope this will go in soon!

It is your job :)

I'm going to change it to waiting on author again, mainly because of the
uninitialised variable in \d handling, the rest are just stylistic nitpicks.

Cheers,
Jan

attached updated patch

Thank you very much

Pavel Stehule

Attachments:

editfce.difftext/x-patch; charset=US-ASCII; name=editfce.diffDownload
*** ./doc/src/sgml/ref/psql-ref.sgml.orig	2010-07-20 05:54:19.000000000 +0200
--- ./doc/src/sgml/ref/psql-ref.sgml	2010-07-23 20:46:49.746690828 +0200
***************
*** 1339,1345 ****
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1339,1345 ----
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional> <optional> linenumber </optional></literal></term>
  
          <listitem>
          <para>
***************
*** 1369,1380 ****
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1369,1386 ----
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor.
+         </para>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional> <optional> linenumber </optional> </literal></term>
  
          <listitem>
          <para>
***************
*** 1397,1402 ****
--- 1403,1415 ----
           If no function is specified, a blank <command>CREATE FUNCTION</>
           template is presented for editing.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor. It count lines from start of function body, not from
+         start of text.
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 2116,2121 ****
--- 2129,2146 ----
  
  
        <varlistentry>
+         <term><literal>\sf[+] <replaceable class="parameter">function_description</replaceable> <optional> linenumber </optional> </literal></term>
+ 
+         <listitem>
+         <para>
+          This command fetches and shows the definition of the named function,
+          in the form of a <command>CREATE OR REPLACE FUNCTION</> command.
+          If the form <literal>\sf+</literal> is used, then lines are numbered.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
          <term><literal>\t</literal></term>
          <listitem>
          <para>
***************
*** 2123,2128 ****
--- 2148,2159 ----
          footer. This command is equivalent to <literal>\pset
          tuples_only</literal> and is provided for convenience.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor.
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 3066,3071 ****
--- 3097,3117 ----
     </varlistentry>
  
     <varlistentry>
+     <term><envar>PSQL_EDITOR_NAVIGATION_OPTION</envar></term>
+ 
+     <listitem>
+      <para>
+       Option used for navigation (go to line command) in external
+       editor. When it isn't defined, then it uses <option>+</option>
+       on Unix systems and <option>/</option> on Windows systems. For
+       wide used KDE editors is necessary to set this option to
+       <option>--line </option>. The space after <literal>line</literal>
+       is required.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><envar>SHELL</envar></term>
  
      <listitem>
*** ./src/bin/psql/command.c.orig	2010-07-23 16:56:54.000000000 +0200
--- ./src/bin/psql/command.c	2010-07-23 20:50:29.189688832 +0200
***************
*** 57,63 ****
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		bool *edited);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
--- 57,63 ----
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		bool *edited, int lineno);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
***************
*** 65,70 ****
--- 65,72 ----
  static void minimal_error_message(PGresult *res);
  
  static void printSSLInfo(void);
+ static bool first_row_is_empty(char *src);
+ static int get_lineno_for_navigation(char *func, backslashResult *status);
  
  #ifdef WIN32
  static void checkWin32Codepage(void);
***************
*** 513,528 ****
  		else
  		{
  			char	   *fname;
! 
  			fname = psql_scan_slash_option(scan_state,
! 										   OT_NORMAL, NULL, true);
! 			expand_tilde(&fname);
  			if (fname)
! 				canonicalize_path(fname);
! 			if (do_edit(fname, query_buf, NULL))
! 				status = PSQL_CMD_NEWEDIT;
! 			else
! 				status = PSQL_CMD_ERROR;
  			free(fname);
  		}
  	}
--- 515,552 ----
  		else
  		{
  			char	   *fname;
! 			char		*ln;
! 			int  lineno = -1;
! 			
  			fname = psql_scan_slash_option(scan_state,
! 								   OT_NORMAL, NULL, true);
! 			
! 			/* try to get lineno */
  			if (fname)
! 			{
! 				ln = psql_scan_slash_option(scan_state,
! 									   OT_NORMAL, NULL, true);
! 				if (ln)
! 				{
! 					if (atoi(ln) < 1)
! 					{
! 						psql_error("line number is unacceptable\n");
! 						status = PSQL_CMD_ERROR;
! 					}
! 					else
! 						lineno = atoi(ln);
! 				}
! 			}
! 			if (status != PSQL_CMD_ERROR)
! 			{
! 				expand_tilde(&fname);
! 				if (fname)
! 					canonicalize_path(fname);
! 				if (do_edit(fname, query_buf, NULL, lineno))
! 					status = PSQL_CMD_NEWEDIT;
! 				else
! 					status = PSQL_CMD_ERROR;
! 			}
  			free(fname);
  		}
  	}
***************
*** 533,538 ****
--- 557,564 ----
  	 */
  	else if (strcmp(cmd, "ef") == 0)
  	{
+ 		int	lineno;
+ 	
  		if (!query_buf)
  		{
  			psql_error("no query buffer\n");
***************
*** 545,580 ****
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			if (!func)
  			{
! 				/* set up an empty command to fill in */
! 				printfPQExpBuffer(query_buf,
! 								  "CREATE FUNCTION ( )\n"
! 								  " RETURNS \n"
! 								  " LANGUAGE \n"
! 								  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 								  "AS $function$\n"
! 								  "\n$function$\n");
! 			}
! 			else if (!lookup_function_oid(pset.db, func, &foid))
! 			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
! 			}
! 			else if (!get_create_function_cmd(pset.db, foid, query_buf))
! 			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
  			}
- 			if (func)
- 				free(func);
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, &edited))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
--- 571,615 ----
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			lineno = get_lineno_for_navigation(func, &status);
! 			
! 			if (status != PSQL_CMD_ERROR)
  			{
! 				if (!func)
! 				{
! 					/* set up an empty command to fill in */
! 					printfPQExpBuffer(query_buf,
! 									  "CREATE FUNCTION ( )\n"
! 									  " RETURNS \n"
! 									  " LANGUAGE \n"
! 									  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 									  "AS $function$\n"
! 									  "\n$function$\n");
! 				}
! 				else if (!lookup_function_oid(pset.db, func, &foid))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
! 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
! 				if (func)
! 					free(func);
  			}
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			/* correct lineno when first row of function's body is empty */
! 			if (!first_row_is_empty(query_buf->data))
! 				lineno--;
! 
! 			if (!do_edit(0, query_buf, &edited, lineno))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
***************
*** 969,974 ****
--- 1004,1097 ----
  		free(fname);
  	}
  
+ 	/*
+ 	 * \sf -- show the named function
+ 	 */
+ 	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+ 	{
+ 		int	skip_lines = -1;
+ 		bool	with_lno;
+ 		
+ 		with_lno = (strcmp(cmd, "sf+") == 0);
+ 		
+ 		if (!query_buf)
+ 		{
+ 			psql_error("no query buffer\n");
+ 			status = PSQL_CMD_ERROR;
+ 		}
+ 		else
+ 		{
+ 			char	   *func;
+ 			Oid			foid = InvalidOid;
+ 
+ 			func = psql_scan_slash_option(scan_state,
+ 										  OT_WHOLE_LINE, NULL, true);
+ 			skip_lines = get_lineno_for_navigation(func, &status) - 1;
+ 			
+ 			if (status != PSQL_CMD_ERROR)
+ 			{
+ 				if (!func)
+ 				{
+ 					/* show error for empty command */
+ 					psql_error("missing a function name\n");
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!lookup_function_oid(pset.db, func, &foid))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 			}
+ 			if (func)
+ 				free(func);
+ 		}
+ 
+ 		if (status != PSQL_CMD_ERROR)
+ 		{
+ 			int	lineno = 0;
+ 			char	*c = query_buf->data;
+ 			char	*ptr;
+ 			
+ 			/* 
+ 			 * PL doesn't calculate first row of function's body
+ 			 * when first row is empty. So checks first row, and
+ 			 * correct lineno when it is necessary.
+ 			 */
+ 			if (first_row_is_empty(query_buf->data))
+ 				lineno--;
+ 
+ 			while (*c)
+ 			{
+ 				/* find next end of line */
+ 				for (ptr = c; *c != '\n'; c++);
+ 				*c++ = '\0';
+ 				lineno++;
+ 				
+ 				/* skip first n lines */
+ 				if (skip_lines > 0 && (skip_lines > lineno))
+ 					continue;
+ 				
+ 				if (!with_lno)
+ 					printf("%s\n", ptr);
+ 				else
+ 				{
+ 					/* don't show lineno for first three rows and last row */
+ 					if ((*c == '\0' && lineno != 3) || lineno < 4)
+ 						printf("****  %s\n", ptr);
+ 					else
+ 						printf("%4d  %s\n", lineno - 3, ptr);
+ 				}
+ 			}
+ 			printf("\n");
+ 			fflush(stdout);
+ 		}
+ 	}
+ 
  	/* \set -- generalized set variable/option command */
  	else if (strcmp(cmd, "set") == 0)
  	{
***************
*** 1550,1558 ****
   */
  
  static bool
! editFile(const char *fname)
  {
  	const char *editorName;
  	char	   *sys;
  	int			result;
  
--- 1673,1682 ----
   */
  
  static bool
! editFile(const char *fname, int lineno)
  {
  	const char *editorName;
+ 	const char *navigation_cmd;
  	char	   *sys;
  	int			result;
  
***************
*** 1566,1571 ****
--- 1690,1699 ----
  		editorName = getenv("VISUAL");
  	if (!editorName)
  		editorName = DEFAULT_EDITOR;
+ 	
+ 	navigation_cmd = getenv("PSQL_EDITOR_NAVIGATION_OPTION");
+ 	if (!navigation_cmd)
+ 		navigation_cmd = DEFAULT_EDITOR_NAVIGATION_OPTION;
  
  	/*
  	 * On Unix the EDITOR value should *not* be quoted, since it might include
***************
*** 1574,1584 ****
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
  #ifndef WIN32
! 	sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
--- 1702,1722 ----
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	sys = pg_malloc(strlen(editorName) + strlen(fname) + 20 + 1);
  #ifndef WIN32
! 	if (lineno > 0)
! 		sprintf(sys, "exec %s %s%d '%s'", editorName, navigation_cmd, lineno, fname);
! 	else
! 		sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	if (lineno > 0)
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" %s%d \"%s\"" SYSTEMQUOTE, 
! 									    editorName, 
! 									    navigation_cmd,
! 									    lineno, 
! 									    fname);
! 	else
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
***************
*** 1593,1599 ****
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
--- 1731,1737 ----
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited, int lineno)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
***************
*** 1685,1691 ****
  
  	/* call editor */
  	if (!error)
! 		error = !editFile(fname);
  
  	if (!error && stat(fname, &after) != 0)
  	{
--- 1823,1829 ----
  
  	/* call editor */
  	if (!error)
! 		error = !editFile(fname, lineno);
  
  	if (!error && stat(fname, &after) != 0)
  	{
***************
*** 2181,2186 ****
--- 2319,2347 ----
  	return result;
  }
  
+ 
+ /*
+  * When first row of prosrc has only new-line char, then is ignored.
+  * This functions returns true, when first row is empty, else returns
+  * false.
+  */
+ static bool
+ first_row_is_empty(char *src)
+ {
+ 	char *ptr;
+ 	
+ #define DEFAULT_BODY_SEPARATOR		"$function$"
+ 	
+ 	ptr = strstr(src, DEFAULT_BODY_SEPARATOR);
+ 	
+ 	psql_assert(ptr != NULL);
+ 	
+ 	ptr += strlen(DEFAULT_BODY_SEPARATOR);
+ 	
+ 	return *ptr == '\n';
+ }
+ 
+ 
  /*
   * Fetches the "CREATE OR REPLACE FUNCTION ..." command that describes the
   * function with the given OID.  If successful, the result is stored in buf.
***************
*** 2213,2218 ****
--- 2374,2380 ----
  	return result;
  }
  
+ 
  /*
   * Report just the primary error; this is to avoid cluttering the output
   * with, for instance, a redisplay of the internally generated query
***************
*** 2241,2243 ****
--- 2403,2480 ----
  
  	destroyPQExpBuffer(msg);
  }
+ 
+ 
+ /*
+  * Returns lineno used in \sf and \ef commands. 
+  *
+  * These commands can be completed with number used as line
+  * number for navigation in showed lines / open file. The most 
+  * simple method for parsing is reading isolated digits from 
+  * right - \ef foo nn, \ef foo(..)nn. Returns -1 when
+  * lineno isn't defined.
+ */
+ static int
+ get_lineno_for_navigation(char *func, backslashResult *status)
+ {
+ 	char *endfunc;
+ 	char *c;
+ 	int lineno = -1;
+ 	
+ 	if (!func)
+ 		return lineno;
+ 	
+ 	endfunc = func + strlen(func) - 1;
+ 	c = endfunc;
+ 	
+ 	/* skip useles whitespaces */
+ 	while (c >= func)
+ 		if (isblank(*c))
+ 			c--;
+ 		else
+ 			break;
+ 	
+ 	/* search the most left digit of continuously number */
+ 	while (c >= func)
+ 		if (!isdigit(*c))
+ 			break;
+ 		else
+ 			c--;
+ 	
+ 	/* 
+ 	 * when left char isn't blank and isn't a right parenthesis
+ 	 * then command hasn't a lineno.
+ 	 */
+ 	if (c < endfunc && c > func)
+ 	{
+ 		if (isblank(*c) || *c == ')')
+ 		{
+ 			c++;
+ 			
+ 			if (atoi(c) < 1)
+ 			{
+ 				psql_error("line number is unacceptable\n");
+ 				*status = PSQL_CMD_ERROR;
+ 			}
+ 			else
+ 			{
+ 				/*----------
+ 				 * Function get_create_function_cmd appends a few lines
+ 				 * to function's body. But we would to like use a line 
+ 				 * numbers use a PL parsers - so add three lines to
+ 				 * lineno:
+ 				 *   CREATE OR REPLACE FUNCTION ..
+ 				 *     RETURNS ...
+ 				 *     LANGUAGE ...
+ 				 *     AS $finction$
+ 				 */
+ 				lineno = atoi(c) + 4;
+ 				
+ 				/* remove lineno from function descriptor */
+ 				*c = '\0';
+ 			}
+ 		}
+ 	}
+ 	
+ 	return lineno;
+ }
*** ./src/bin/psql/help.c.orig	2010-07-23 19:51:20.039690385 +0200
--- ./src/bin/psql/help.c	2010-07-23 19:51:39.905689608 +0200
***************
*** 174,186 ****
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE]              edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME]         edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
--- 174,187 ----
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE] [lno]        edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME] [lno]   edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
+ 	fprintf(output, _("  \\sf[+] FUNCNAME [lno]  show finction definition\n"));
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
*** ./src/bin/psql/settings.h.orig	2010-07-23 19:51:20.043690094 +0200
--- ./src/bin/psql/settings.h	2010-07-23 20:51:12.235691465 +0200
***************
*** 18,25 ****
--- 18,27 ----
  
  #if defined(WIN32) || defined(__CYGWIN__)
  #define DEFAULT_EDITOR	"notepad.exe"
+ #define DEFAULT_EDITOR_NAVIGATION_OPTION	"/"
  #else
  #define DEFAULT_EDITOR	"vi"
+ #define DEFAULT_EDITOR_NAVIGATION_OPTION	"+"
  #endif
  
  #define DEFAULT_PROMPT1 "%/%R%# "
*** ./src/bin/psql/tab-complete.c.orig	2010-07-20 05:54:19.000000000 +0200
--- ./src/bin/psql/tab-complete.c	2010-07-23 19:51:39.909687081 +0200
***************
*** 644,650 ****
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
--- 644,650 ----
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\sf", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
***************
*** 2501,2506 ****
--- 2501,2509 ----
  
  	else if (strcmp(prev_wd, "\\ef") == 0)
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+ 	
+ 		else if (strncmp(prev_wd, "\\sf", 2) == 0)
+ 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
  
  	else if (strcmp(prev_wd, "\\encoding") == 0)
  		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
#9Jan Urbański
wulczer@wulczer.org
In reply to: Pavel Stehule (#8)
Re: review: psql: edit function, show function commands patch

On 23/07/10 20:55, Pavel Stehule wrote:

Hello

2010/7/23 Jan Urbański <wulczer@wulczer.org>:

On 21/07/10 14:43, Pavel Stehule wrote:

Hello

I am sending a actualised patch.

OK, thanks. This time the only thing I'm not happy about is the error
message from doing:

\ef func 0
\e /etc/passwd xxx

which gives:

line number is unacceptable

where I think it should do:

\ef: line number is unacceptable
\e: line number is unacceptable

but that's too trivial to go through another round of review and I think
the committer can fix this easily.

The documentation likely needs some spelling fixes, but I'll leave that
to a native English speaker.

I'm setting this as ready for committer.

Thanks,
Jan

#10Pavel Stehule
pavel.stehule@gmail.com
In reply to: Jan Urbański (#9)
Re: review: psql: edit function, show function commands patch

2010/7/25 Jan Urbański <wulczer@wulczer.org>:

On 23/07/10 20:55, Pavel Stehule wrote:

Hello

2010/7/23 Jan Urbański <wulczer@wulczer.org>:

On 21/07/10 14:43, Pavel Stehule wrote:

Hello

I am sending a actualised patch.

OK, thanks. This time the only thing I'm not happy about is the error
message from doing:

\ef func 0
\e /etc/passwd xxx

which gives:

line number is unacceptable

where I think it should do:

\ef: line number is unacceptable
\e: line number is unacceptable

but that's too trivial to go through another round of review and I think
the committer can fix this easily.

The documentation likely needs some spelling fixes, but I'll leave that
to a native English speaker.

I'm setting this as ready for committer.

Thank you very much

Pavel

Show quoted text

Thanks,
Jan

#11Robert Haas
robertmhaas@gmail.com
In reply to: Pavel Stehule (#10)
Re: review: psql: edit function, show function commands patch

On Sun, Jul 25, 2010 at 11:42 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I'm setting this as ready for committer.

Thank you very much

I took a look at this tonight and am a bit mystified by the following bit:

+                       /*
+                        * PL doesn't calculate first row of function's body
+                        * when first row is empty. So checks first row, and
+                        * correct lineno when it is necessary.
+                        */

Is that true of any PL, or just some particular PL? Is the behavior
described here a bug that we should fix, or is it, for some reason,
considered correct? Is it documented in our documentation?

The implementation of first_row_is_empty() looks pretty kludgey, too.
It seems to me that it will fail completely if the text of the
function definition happens to contain $function$.

CREATE OR REPLACE FUNCTION boom() RETURNS text LANGUAGE plpgsql AS $$
BEGIN SELECT '$function$'; END $$;

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

#12Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#11)
Re: review: psql: edit function, show function commands patch

Robert Haas <robertmhaas@gmail.com> writes:

I took a look at this tonight and am a bit mystified by the following bit:

+                       /*
+                        * PL doesn't calculate first row of function's body
+                        * when first row is empty. So checks first row, and
+                        * correct lineno when it is necessary.
+                        */

Is that true of any PL, or just some particular PL?

plpgsql has an old bit of logic that deliberately ignores an initial
newline in the function body:

/*----------
* Hack: skip any initial newline, so that in the common coding layout
* CREATE FUNCTION ... AS $$
* code body
* $$ LANGUAGE plpgsql;
* we will think "line 1" is what the programmer thinks of as line 1.
*----------
*/
if (*cur_line_start == '\r')
cur_line_start++;
if (*cur_line_start == '\n')
cur_line_start++;

None of the other standard PLs do that AFAIK.

Is it documented in our documentation?

I don't think so.

regards, tom lane

#13Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#11)
Re: review: psql: edit function, show function commands patch

2010/8/1 Robert Haas <robertmhaas@gmail.com>:

On Sun, Jul 25, 2010 at 11:42 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I'm setting this as ready for committer.

Thank you very much

I took a look at this tonight and am a bit mystified by the following bit:

+                       /*
+                        * PL doesn't calculate first row of function's body
+                        * when first row is empty. So checks first row, and
+                        * correct lineno when it is necessary.
+                        */

Is that true of any PL, or just some particular PL?  Is the behavior
described here a bug that we should fix, or is it, for some reason,
considered correct?  Is it documented in our documentation?

it is primary plpgsql issue.

The implementation of first_row_is_empty() looks pretty kludgey, too.
It seems to me that it will fail completely if the text of the
function definition happens to contain $function$.

CREATE OR REPLACE FUNCTION boom() RETURNS text LANGUAGE plpgsql AS $$
BEGIN SELECT '$function$'; END $$;

I can enhance algorithm on client side - but it will not be a pretty
code - it better do it on server side - for example
pg_get_formated_functiondef ...

CREATE OR REPLACE FUNCTION pg_get_formated_function_def(in oid,
linenum bool:= false, OUT funcdef text, OUT first_line_isignored bool)

this can remove any ugly code

Regards

Pavel

Show quoted text

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

#14Robert Haas
robertmhaas@gmail.com
In reply to: Pavel Stehule (#13)
Re: review: psql: edit function, show function commands patch

On Sun, Aug 1, 2010 at 12:15 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

2010/8/1 Robert Haas <robertmhaas@gmail.com>:

On Sun, Jul 25, 2010 at 11:42 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I'm setting this as ready for committer.

Thank you very much

I took a look at this tonight and am a bit mystified by the following bit:

+                       /*
+                        * PL doesn't calculate first row of function's body
+                        * when first row is empty. So checks first row, and
+                        * correct lineno when it is necessary.
+                        */

Is that true of any PL, or just some particular PL?  Is the behavior
described here a bug that we should fix, or is it, for some reason,
considered correct?  Is it documented in our documentation?

it is primary plpgsql issue.

Yeah, that seems like a problem. If your editor is vi, for example,
the following are the same number of keystrokes:

\ef foo 417<enter>
and
\ef foo<enter>417G

So the major advantage of the former over the latter is that you'll
(hopefully) end up on EXACTLY the right line rather than maybe being
off by a few lines. With the current code, that won't necessarily
happen, because PL/pgsql (and any third-party PLs based on it) use one
line-numbering convention and other PLs don't necessarily include that
hack. Admittedly, you'll probably only be off by one line instead of
three or four, so maybe people think that's good enough, but I'm not
totally convinced. It seems like the easiest way to fix this would be
remove the hack from PL/pgsql, but I'm not sure how people feel about
that. If we don't do that, I'm not sure there's any real good
solution here.

The implementation of first_row_is_empty() looks pretty kludgey, too.
It seems to me that it will fail completely if the text of the
function definition happens to contain $function$.

CREATE OR REPLACE FUNCTION boom() RETURNS text LANGUAGE plpgsql AS $$
BEGIN SELECT '$function$'; END $$;

I can enhance algorithm on client side - but it will not be a pretty
code - it better do it on server side - for example
pg_get_formated_functiondef ...

CREATE OR REPLACE FUNCTION pg_get_formated_function_def(in oid,
linenum bool:= false, OUT funcdef text, OUT first_line_isignored bool)

this can remove any ugly code

I don't really see why this can't be done on the client side - can't
you just scan until you find the second dollars sign and then see
whether the following character is a newline? Seems like this could
be done in a very small number of lines of code using strchr().

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

#15Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#14)
Re: review: psql: edit function, show function commands patch

2010/8/1 Robert Haas <robertmhaas@gmail.com>:

On Sun, Aug 1, 2010 at 12:15 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

2010/8/1 Robert Haas <robertmhaas@gmail.com>:

On Sun, Jul 25, 2010 at 11:42 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I'm setting this as ready for committer.

Thank you very much

I took a look at this tonight and am a bit mystified by the following bit:

+                       /*
+                        * PL doesn't calculate first row of function's body
+                        * when first row is empty. So checks first row, and
+                        * correct lineno when it is necessary.
+                        */

Is that true of any PL, or just some particular PL?  Is the behavior
described here a bug that we should fix, or is it, for some reason,
considered correct?  Is it documented in our documentation?

it is primary plpgsql issue.

Yeah, that seems like a problem.  If your editor is vi, for example,
the following are the same number of keystrokes:

\ef foo 417<enter>
and
\ef foo<enter>417G

it not is too much important, navigation command is secondary
function. Primary functionality is showing of source code with correct
row numbers.

So the major advantage of the former over the latter is that you'll
(hopefully) end up on EXACTLY the right line rather than maybe being
off by a few lines.  With the current code, that won't necessarily
happen, because PL/pgsql (and any third-party PLs based on it) use one
line-numbering convention and other PLs don't necessarily include that
hack.  Admittedly, you'll probably only be off by one line instead of
three or four, so maybe people think that's good enough, but I'm not
totally convinced.  It seems like the easiest way to fix this would be
remove the hack from PL/pgsql, but I'm not sure how people feel about
that.  If we don't do that, I'm not sure there's any real good
solution here.

.. :( without this feature - this patch has minimal value.

I don't believe so change plpgsql numbering is good way. It was
changed from good reasons. Because empty row is invisible but it isn't
empty for people, because there are " AS " keyword.

This patch must to working with plpgsql and have to work correctly.

The implementation of first_row_is_empty() looks pretty kludgey, too.
It seems to me that it will fail completely if the text of the
function definition happens to contain $function$.

CREATE OR REPLACE FUNCTION boom() RETURNS text LANGUAGE plpgsql AS $$
BEGIN SELECT '$function$'; END $$;

I can enhance algorithm on client side - but it will not be a pretty
code - it better do it on server side - for example
pg_get_formated_functiondef ...

CREATE OR REPLACE FUNCTION pg_get_formated_function_def(in oid,
linenum bool:= false, OUT funcdef text, OUT first_line_isignored bool)

this can remove any ugly code

I don't really see why this can't be done on the client side - can't
you just scan until you find the second dollars sign and then see
whether the following character is a newline?  Seems like this could
be done in a very small number of lines of code using strchr().

you have a true - it is possible, but still I am supply a parser on
client side. On server side I have all data in structured form.

but I don't would to complicate a possible commiting

so my plan

a) fix problem with ambiguous $function* like you proposed
b) fix problem with "first row excepting" - I can activate a detection
only for plpgsql language - I can identify LANGUAGE before.

all will be done on client

It is acceptable for you?

Regards

Pavel Stehule

Show quoted text

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

#16Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#15)
Re: review: psql: edit function, show function commands patch

Pavel Stehule <pavel.stehule@gmail.com> writes:

so my plan

a) fix problem with ambiguous $function* like you proposed
b) fix problem with "first row excepting" - I can activate a detection
only for plpgsql language - I can identify LANGUAGE before.

Ick. We should absolutely NOT have a client-side special case for plpgsql.

Personally I'd be fine with dropping the special case from the plpgsql
parser --- I don't believe that that behavior was ever discussed, much
less documented, and I doubt that many people rely on it or even know
it exists. The need to count lines manually in function definitions is
far less than it was back when that kluge was put in.

If anyone can make a convincing case that it's a good idea to ignore
leading newlines, we should reimplement the behavior in such a way that
it applies across the board to all PLs (ie, make CREATE FUNCTION strip
a leading newline before storing the text). However, then you'd have
issues about whether or when to put back the newline, so I'm not really
in favor of that route.

regards, tom lane

#17Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#16)
Re: review: psql: edit function, show function commands patch

On Sun, Aug 1, 2010 at 10:47 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Pavel Stehule <pavel.stehule@gmail.com> writes:

so my plan

a) fix problem with ambiguous $function* like you proposed
b) fix problem with "first row excepting" - I can activate a detection
only for plpgsql language - I can identify LANGUAGE before.

Ick.  We should absolutely NOT have a client-side special case for plpgsql.

Personally I'd be fine with dropping the special case from the plpgsql
parser --- I don't believe that that behavior was ever discussed, much
less documented, and I doubt that many people rely on it or even know
it exists.

+1.

The need to count lines manually in function definitions is
far less than it was back when that kluge was put in.

Why?

If anyone can make a convincing case that it's a good idea to ignore
leading newlines, we should reimplement the behavior in such a way that
it applies across the board to all PLs (ie, make CREATE FUNCTION strip
a leading newline before storing the text).  However, then you'd have
issues about whether or when to put back the newline, so I'm not really
in favor of that route.

Ditto.

As a procedural note, if we decide to go this route, this should be
split into two patches - one that removes the line-numbering kludge,
and a second for the psql changes.

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

#18Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#17)
Re: review: psql: edit function, show function commands patch

2010/8/1 Robert Haas <robertmhaas@gmail.com>:

On Sun, Aug 1, 2010 at 10:47 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Pavel Stehule <pavel.stehule@gmail.com> writes:

so my plan

a) fix problem with ambiguous $function* like you proposed
b) fix problem with "first row excepting" - I can activate a detection
only for plpgsql language - I can identify LANGUAGE before.

Ick.  We should absolutely NOT have a client-side special case for plpgsql.

Personally I'd be fine with dropping the special case from the plpgsql
parser --- I don't believe that that behavior was ever discussed, much
less documented, and I doubt that many people rely on it or even know
it exists.

+1.

The need to count lines manually in function definitions is
far less than it was back when that kluge was put in.

Why?

If anyone can make a convincing case that it's a good idea to ignore
leading newlines, we should reimplement the behavior in such a way that
it applies across the board to all PLs (ie, make CREATE FUNCTION strip
a leading newline before storing the text).  However, then you'd have
issues about whether or when to put back the newline, so I'm not really
in favor of that route.

Ditto.

As a procedural note, if we decide to go this route, this should be
split into two patches - one that removes the line-numbering kludge,
and a second for the psql changes.

ok - tomorrow I'll send a patch

Regards

Pavel

Show quoted text

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

#19Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#17)
Re: review: psql: edit function, show function commands patch

Robert Haas <robertmhaas@gmail.com> writes:

On Sun, Aug 1, 2010 at 10:47 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

The need to count lines manually in function definitions is
far less than it was back when that kluge was put in.

Why?

That hack goes back to plpgsql's prehistory (it's there, though sans
comment, in plpgsql's scan.l 1.1). We had none of the current support
for identifying error locations by cursor position and/or quoting part
of the source text back at you. Let me illustrate what happened with
a simple syntax error in PG 7.0:

play=> create function fool() returns int as '
play'> begin
play'> fool
play'> end' language 'plpgsql';
CREATE
play=> select fool();
NOTICE: plpgsql: ERROR during compile of fool near line 2
ERROR: missing ; at end of SQL statement
play=>

So you *had* to count lines, and do it accurately too, to figure out
even pretty simple syntax errors.

Personally, rather than sweat about what the exact definition of line
numbers is, I think we should be moving further in the direction of
being able to regurgitate source text to identify error locations.

regards, tom lane

#20Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#19)
Re: review: psql: edit function, show function commands patch

On Sun, Aug 1, 2010 at 11:35 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Sun, Aug 1, 2010 at 10:47 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

The need to count lines manually in function definitions is
far less than it was back when that kluge was put in.

Why?

That hack goes back to plpgsql's prehistory (it's there, though sans
comment, in plpgsql's scan.l 1.1).  We had none of the current support
for identifying error locations by cursor position and/or quoting part
of the source text back at you.  Let me illustrate what happened with
a simple syntax error in PG 7.0:

play=> create function fool() returns int as '
play'> begin
play'>   fool
play'> end' language 'plpgsql';
CREATE
play=> select fool();
NOTICE:  plpgsql: ERROR during compile of fool near line 2
ERROR:  missing ; at end of SQL statement
play=>

So you *had* to count lines, and do it accurately too, to figure out
even pretty simple syntax errors.

Personally, rather than sweat about what the exact definition of line
numbers is, I think we should be moving further in the direction of
being able to regurgitate source text to identify error locations.

I basically agree with that; but on the other hand, in a large
PL/pgsql function, you may have very similar-looking text in multiple
places. So line numbers are good, too: but then you weren't proposing
to remove those, I assume, just to augment them with additional
information.

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

#21Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#20)
Re: review: psql: edit function, show function commands patch

Robert Haas <robertmhaas@gmail.com> writes:

On Sun, Aug 1, 2010 at 11:35 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Personally, rather than sweat about what the exact definition of line
numbers is, I think we should be moving further in the direction of
being able to regurgitate source text to identify error locations.

I basically agree with that; but on the other hand, in a large
PL/pgsql function, you may have very similar-looking text in multiple
places. So line numbers are good, too: but then you weren't proposing
to remove those, I assume, just to augment them with additional
information.

Right. I'm just suggesting that source text is better than line numbers
for exact position identification, so I don't see a lot of value in
preserving historical behaviors that change line numbers by one count
one way or another. If some of the PLs used zero-based instead of
one-based line numbers, we'd be looking to standardize that not cater to
their individual idiosyncrasies.

regards, tom lane

#22Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#17)
2 attachment(s)
Re: review: psql: edit function, show function commands patch

Hello

I am sending a modified patch - changes:

a) remove special row number handling of plpgsql (first patch)
b) more robust algorithm for header rows identification

Regards

Pavel Stehule

2010/8/1 Robert Haas <robertmhaas@gmail.com>:

Show quoted text

On Sun, Aug 1, 2010 at 10:47 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Pavel Stehule <pavel.stehule@gmail.com> writes:

so my plan

a) fix problem with ambiguous $function* like you proposed
b) fix problem with "first row excepting" - I can activate a detection
only for plpgsql language - I can identify LANGUAGE before.

Ick.  We should absolutely NOT have a client-side special case for plpgsql.

Personally I'd be fine with dropping the special case from the plpgsql
parser --- I don't believe that that behavior was ever discussed, much
less documented, and I doubt that many people rely on it or even know
it exists.

+1.

The need to count lines manually in function definitions is
far less than it was back when that kluge was put in.

Why?

If anyone can make a convincing case that it's a good idea to ignore
leading newlines, we should reimplement the behavior in such a way that
it applies across the board to all PLs (ie, make CREATE FUNCTION strip
a leading newline before storing the text).  However, then you'd have
issues about whether or when to put back the newline, so I'm not really
in favor of that route.

Ditto.

As a procedural note, if we decide to go this route, this should be
split into two patches - one that removes the line-numbering kludge,
and a second for the psql changes.

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

Attachments:

remove_skip_first_row.patchtext/x-patch; charset=US-ASCII; name=remove_skip_first_row.patchDownload
*** ./src/pl/plpgsql/src/pl_scanner.c.orig	2010-02-26 03:01:35.000000000 +0100
--- ./src/pl/plpgsql/src/pl_scanner.c	2010-08-01 20:56:35.000000000 +0200
***************
*** 519,537 ****
  	cur_line_start = scanorig;
  	cur_line_num = 1;
  
- 	/*----------
- 	 * Hack: skip any initial newline, so that in the common coding layout
- 	 *		CREATE FUNCTION ... AS $$
- 	 *			code body
- 	 *		$$ LANGUAGE plpgsql;
- 	 * we will think "line 1" is what the programmer thinks of as line 1.
- 	 *----------
- 	 */
- 	if (*cur_line_start == '\r')
- 		cur_line_start++;
- 	if (*cur_line_start == '\n')
- 		cur_line_start++;
- 
  	cur_line_end = strchr(cur_line_start, '\n');
  }
  
--- 519,524 ----
*** ./src/test/regress/expected/domain.out.orig	2008-06-11 23:53:49.000000000 +0200
--- ./src/test/regress/expected/domain.out	2010-08-01 20:57:33.000000000 +0200
***************
*** 436,442 ****
  end$$ language plpgsql;
  select doubledecrement(3); -- fail because of implicit null assignment
  ERROR:  domain pos_int does not allow null values
! CONTEXT:  PL/pgSQL function "doubledecrement" line 2 during statement block local variable initialization
  create or replace function doubledecrement(p1 pos_int) returns pos_int as $$
  declare v pos_int := 0;
  begin
--- 436,442 ----
  end$$ language plpgsql;
  select doubledecrement(3); -- fail because of implicit null assignment
  ERROR:  domain pos_int does not allow null values
! CONTEXT:  PL/pgSQL function "doubledecrement" line 3 during statement block local variable initialization
  create or replace function doubledecrement(p1 pos_int) returns pos_int as $$
  declare v pos_int := 0;
  begin
***************
*** 444,450 ****
  end$$ language plpgsql;
  select doubledecrement(3); -- fail at initialization assignment
  ERROR:  value for domain pos_int violates check constraint "pos_int_check"
! CONTEXT:  PL/pgSQL function "doubledecrement" line 2 during statement block local variable initialization
  create or replace function doubledecrement(p1 pos_int) returns pos_int as $$
  declare v pos_int := 1;
  begin
--- 444,450 ----
  end$$ language plpgsql;
  select doubledecrement(3); -- fail at initialization assignment
  ERROR:  value for domain pos_int violates check constraint "pos_int_check"
! CONTEXT:  PL/pgSQL function "doubledecrement" line 3 during statement block local variable initialization
  create or replace function doubledecrement(p1 pos_int) returns pos_int as $$
  declare v pos_int := 1;
  begin
***************
*** 457,463 ****
  ERROR:  value for domain pos_int violates check constraint "pos_int_check"
  select doubledecrement(1); -- fail at assignment to v
  ERROR:  value for domain pos_int violates check constraint "pos_int_check"
! CONTEXT:  PL/pgSQL function "doubledecrement" line 3 at assignment
  select doubledecrement(2); -- fail at return
  ERROR:  value for domain pos_int violates check constraint "pos_int_check"
  CONTEXT:  PL/pgSQL function "doubledecrement" while casting return value to function's return type
--- 457,463 ----
  ERROR:  value for domain pos_int violates check constraint "pos_int_check"
  select doubledecrement(1); -- fail at assignment to v
  ERROR:  value for domain pos_int violates check constraint "pos_int_check"
! CONTEXT:  PL/pgSQL function "doubledecrement" line 4 at assignment
  select doubledecrement(2); -- fail at return
  ERROR:  value for domain pos_int violates check constraint "pos_int_check"
  CONTEXT:  PL/pgSQL function "doubledecrement" while casting return value to function's return type
*** ./src/test/regress/expected/guc.out.orig	2010-02-16 23:34:57.000000000 +0100
--- ./src/test/regress/expected/guc.out	2010-08-01 20:57:31.000000000 +0200
***************
*** 686,692 ****
  select myfunc(0);
  ERROR:  division by zero
  CONTEXT:  SQL statement "SELECT 1/$1"
! PL/pgSQL function "myfunc" line 3 at PERFORM
  select current_setting('work_mem');
   current_setting 
  -----------------
--- 686,692 ----
  select myfunc(0);
  ERROR:  division by zero
  CONTEXT:  SQL statement "SELECT 1/$1"
! PL/pgSQL function "myfunc" line 4 at PERFORM
  select current_setting('work_mem');
   current_setting 
  -----------------
*** ./src/test/regress/expected/plancache.out.orig	2009-01-27 13:40:15.000000000 +0100
--- ./src/test/regress/expected/plancache.out	2010-08-01 20:57:32.000000000 +0200
***************
*** 235,241 ****
  select cachebug();
  NOTICE:  table "temptable" does not exist, skipping
  CONTEXT:  SQL statement "drop table if exists temptable cascade"
! PL/pgSQL function "cachebug" line 3 at SQL statement
  NOTICE:  1
  NOTICE:  2
  NOTICE:  3
--- 235,241 ----
  select cachebug();
  NOTICE:  table "temptable" does not exist, skipping
  CONTEXT:  SQL statement "drop table if exists temptable cascade"
! PL/pgSQL function "cachebug" line 4 at SQL statement
  NOTICE:  1
  NOTICE:  2
  NOTICE:  3
***************
*** 247,253 ****
  select cachebug();
  NOTICE:  drop cascades to view vv
  CONTEXT:  SQL statement "drop table if exists temptable cascade"
! PL/pgSQL function "cachebug" line 3 at SQL statement
  NOTICE:  1
  NOTICE:  2
  NOTICE:  3
--- 247,253 ----
  select cachebug();
  NOTICE:  drop cascades to view vv
  CONTEXT:  SQL statement "drop table if exists temptable cascade"
! PL/pgSQL function "cachebug" line 4 at SQL statement
  NOTICE:  1
  NOTICE:  2
  NOTICE:  3
*** ./src/test/regress/expected/plpgsql.out.orig	2010-06-25 18:40:13.000000000 +0200
--- ./src/test/regress/expected/plpgsql.out	2010-08-01 20:57:36.000000000 +0200
***************
*** 1518,1533 ****
  DETAIL:  Key (name)=(PF1_1) already exists.
  update PSlot set backlink = 'WS.not.there' where slotname = 'PS.base.a1';
  ERROR:  WS.not.there         does not exist
! CONTEXT:  PL/pgSQL function "tg_backlink_a" line 16 at assignment
  update PSlot set backlink = 'XX.illegal' where slotname = 'PS.base.a1';
  ERROR:  illegal backlink beginning with XX
! CONTEXT:  PL/pgSQL function "tg_backlink_a" line 16 at assignment
  update PSlot set slotlink = 'PS.not.there' where slotname = 'PS.base.a1';
  ERROR:  PS.not.there         does not exist
! CONTEXT:  PL/pgSQL function "tg_slotlink_a" line 16 at assignment
  update PSlot set slotlink = 'XX.illegal' where slotname = 'PS.base.a1';
  ERROR:  illegal slotlink beginning with XX
! CONTEXT:  PL/pgSQL function "tg_slotlink_a" line 16 at assignment
  insert into HSlot values ('HS', 'base.hub1', 1, '');
  ERROR:  duplicate key value violates unique constraint "hslot_name"
  DETAIL:  Key (slotname)=(HS.base.hub1.1      ) already exists.
--- 1518,1533 ----
  DETAIL:  Key (name)=(PF1_1) already exists.
  update PSlot set backlink = 'WS.not.there' where slotname = 'PS.base.a1';
  ERROR:  WS.not.there         does not exist
! CONTEXT:  PL/pgSQL function "tg_backlink_a" line 17 at assignment
  update PSlot set backlink = 'XX.illegal' where slotname = 'PS.base.a1';
  ERROR:  illegal backlink beginning with XX
! CONTEXT:  PL/pgSQL function "tg_backlink_a" line 17 at assignment
  update PSlot set slotlink = 'PS.not.there' where slotname = 'PS.base.a1';
  ERROR:  PS.not.there         does not exist
! CONTEXT:  PL/pgSQL function "tg_slotlink_a" line 17 at assignment
  update PSlot set slotlink = 'XX.illegal' where slotname = 'PS.base.a1';
  ERROR:  illegal slotlink beginning with XX
! CONTEXT:  PL/pgSQL function "tg_slotlink_a" line 17 at assignment
  insert into HSlot values ('HS', 'base.hub1', 1, '');
  ERROR:  duplicate key value violates unique constraint "hslot_name"
  DETAIL:  Key (slotname)=(HS.base.hub1.1      ) already exists.
***************
*** 2067,2079 ****
  select test_variable_storage();
  NOTICE:  should see this
  CONTEXT:  SQL statement "SELECT trap_zero_divide(-100)"
! PL/pgSQL function "test_variable_storage" line 7 at PERFORM
  NOTICE:  should see this only if -100 <> 0
  CONTEXT:  SQL statement "SELECT trap_zero_divide(-100)"
! PL/pgSQL function "test_variable_storage" line 7 at PERFORM
  NOTICE:  should see this only if -100 fits in smallint
  CONTEXT:  SQL statement "SELECT trap_zero_divide(-100)"
! PL/pgSQL function "test_variable_storage" line 7 at PERFORM
   test_variable_storage 
  -----------------------
   123456789012
--- 2067,2079 ----
  select test_variable_storage();
  NOTICE:  should see this
  CONTEXT:  SQL statement "SELECT trap_zero_divide(-100)"
! PL/pgSQL function "test_variable_storage" line 8 at PERFORM
  NOTICE:  should see this only if -100 <> 0
  CONTEXT:  SQL statement "SELECT trap_zero_divide(-100)"
! PL/pgSQL function "test_variable_storage" line 8 at PERFORM
  NOTICE:  should see this only if -100 fits in smallint
  CONTEXT:  SQL statement "SELECT trap_zero_divide(-100)"
! PL/pgSQL function "test_variable_storage" line 8 at PERFORM
   test_variable_storage 
  -----------------------
   123456789012
***************
*** 2302,2308 ****
  $$ language plpgsql;
  select raise_test1(5);
  ERROR:  too many parameters specified for RAISE
! CONTEXT:  PL/pgSQL function "raise_test1" line 2 at RAISE
  create function raise_test2(int) returns int as $$
  begin
      raise notice 'This message has too few parameters: %, %, %', $1, $1;
--- 2302,2308 ----
  $$ language plpgsql;
  select raise_test1(5);
  ERROR:  too many parameters specified for RAISE
! CONTEXT:  PL/pgSQL function "raise_test1" line 3 at RAISE
  create function raise_test2(int) returns int as $$
  begin
      raise notice 'This message has too few parameters: %, %, %', $1, $1;
***************
*** 2311,2317 ****
  $$ language plpgsql;
  select raise_test2(10);
  ERROR:  too few parameters specified for RAISE
! CONTEXT:  PL/pgSQL function "raise_test2" line 2 at RAISE
  --
  -- reject function definitions that contain malformed SQL queries at
  -- compile-time, where possible
--- 2311,2317 ----
  $$ language plpgsql;
  select raise_test2(10);
  ERROR:  too few parameters specified for RAISE
! CONTEXT:  PL/pgSQL function "raise_test2" line 3 at RAISE
  --
  -- reject function definitions that contain malformed SQL queries at
  -- compile-time, where possible
***************
*** 2424,2430 ****
  LINE 1: SELECT sqlstate
                 ^
  QUERY:  SELECT sqlstate
! CONTEXT:  PL/pgSQL function "excpt_test1" line 2 at RAISE
  create function excpt_test2() returns void as $$
  begin
      begin
--- 2424,2430 ----
  LINE 1: SELECT sqlstate
                 ^
  QUERY:  SELECT sqlstate
! CONTEXT:  PL/pgSQL function "excpt_test1" line 3 at RAISE
  create function excpt_test2() returns void as $$
  begin
      begin
***************
*** 2439,2445 ****
  LINE 1: SELECT sqlstate
                 ^
  QUERY:  SELECT sqlstate
! CONTEXT:  PL/pgSQL function "excpt_test2" line 4 at RAISE
  create function excpt_test3() returns void as $$
  begin
      begin
--- 2439,2445 ----
  LINE 1: SELECT sqlstate
                 ^
  QUERY:  SELECT sqlstate
! CONTEXT:  PL/pgSQL function "excpt_test2" line 5 at RAISE
  create function excpt_test3() returns void as $$
  begin
      begin
***************
*** 2821,2827 ****
  end$$ language plpgsql;
  select footest();
  ERROR:  query returned more than one row
! CONTEXT:  PL/pgSQL function "footest" line 4 at SQL statement
  create or replace function footest() returns void as $$
  declare x record;
  begin
--- 2821,2827 ----
  end$$ language plpgsql;
  select footest();
  ERROR:  query returned more than one row
! CONTEXT:  PL/pgSQL function "footest" line 5 at SQL statement
  create or replace function footest() returns void as $$
  declare x record;
  begin
***************
*** 2884,2890 ****
  end$$ language plpgsql;
  select footest();
  ERROR:  query returned no rows
! CONTEXT:  PL/pgSQL function "footest" line 4 at SQL statement
  create or replace function footest() returns void as $$
  declare x record;
  begin
--- 2884,2890 ----
  end$$ language plpgsql;
  select footest();
  ERROR:  query returned no rows
! CONTEXT:  PL/pgSQL function "footest" line 5 at SQL statement
  create or replace function footest() returns void as $$
  declare x record;
  begin
***************
*** 2894,2900 ****
  end$$ language plpgsql;
  select footest();
  ERROR:  query returned more than one row
! CONTEXT:  PL/pgSQL function "footest" line 4 at SQL statement
  create or replace function footest() returns void as $$
  declare x record;
  begin
--- 2894,2900 ----
  end$$ language plpgsql;
  select footest();
  ERROR:  query returned more than one row
! CONTEXT:  PL/pgSQL function "footest" line 5 at SQL statement
  create or replace function footest() returns void as $$
  declare x record;
  begin
***************
*** 2918,2924 ****
  end$$ language plpgsql;
  select footest();
  ERROR:  query returned no rows
! CONTEXT:  PL/pgSQL function "footest" line 4 at EXECUTE statement
  create or replace function footest() returns void as $$
  declare x record;
  begin
--- 2918,2924 ----
  end$$ language plpgsql;
  select footest();
  ERROR:  query returned no rows
! CONTEXT:  PL/pgSQL function "footest" line 5 at EXECUTE statement
  create or replace function footest() returns void as $$
  declare x record;
  begin
***************
*** 2928,2934 ****
  end$$ language plpgsql;
  select footest();
  ERROR:  query returned more than one row
! CONTEXT:  PL/pgSQL function "footest" line 4 at EXECUTE statement
  drop function footest();
  -- test scrollable cursor support
  create function sc_test() returns setof integer as $$
--- 2928,2934 ----
  end$$ language plpgsql;
  select footest();
  ERROR:  query returned more than one row
! CONTEXT:  PL/pgSQL function "footest" line 5 at EXECUTE statement
  drop function footest();
  -- test scrollable cursor support
  create function sc_test() returns setof integer as $$
***************
*** 2972,2978 ****
  select * from sc_test();  -- fails because of NO SCROLL specification
  ERROR:  cursor can only scan forward
  HINT:  Declare it with SCROLL option to enable backward scan.
! CONTEXT:  PL/pgSQL function "sc_test" line 6 at FETCH
  create or replace function sc_test() returns setof integer as $$
  declare
    c refcursor;
--- 2972,2978 ----
  select * from sc_test();  -- fails because of NO SCROLL specification
  ERROR:  cursor can only scan forward
  HINT:  Declare it with SCROLL option to enable backward scan.
! CONTEXT:  PL/pgSQL function "sc_test" line 7 at FETCH
  create or replace function sc_test() returns setof integer as $$
  declare
    c refcursor;
***************
*** 3559,3565 ****
  $$ language plpgsql;
  select raise_test();
  ERROR:  RAISE option already specified: MESSAGE
! CONTEXT:  PL/pgSQL function "raise_test" line 2 at RAISE
  -- conflict on errcode
  create or replace function raise_test() returns void as $$
  begin
--- 3559,3565 ----
  $$ language plpgsql;
  select raise_test();
  ERROR:  RAISE option already specified: MESSAGE
! CONTEXT:  PL/pgSQL function "raise_test" line 3 at RAISE
  -- conflict on errcode
  create or replace function raise_test() returns void as $$
  begin
***************
*** 3568,3574 ****
  $$ language plpgsql;
  select raise_test();
  ERROR:  RAISE option already specified: ERRCODE
! CONTEXT:  PL/pgSQL function "raise_test" line 2 at RAISE
  -- nothing to re-RAISE
  create or replace function raise_test() returns void as $$
  begin
--- 3568,3574 ----
  $$ language plpgsql;
  select raise_test();
  ERROR:  RAISE option already specified: ERRCODE
! CONTEXT:  PL/pgSQL function "raise_test" line 3 at RAISE
  -- nothing to re-RAISE
  create or replace function raise_test() returns void as $$
  begin
***************
*** 3639,3645 ****
  select case_test(5); -- fails
  ERROR:  case not found
  HINT:  CASE statement is missing ELSE part.
! CONTEXT:  PL/pgSQL function "case_test" line 4 at CASE
  select case_test(8);
        case_test       
  ----------------------
--- 3639,3645 ----
  select case_test(5); -- fails
  ERROR:  case not found
  HINT:  CASE statement is missing ELSE part.
! CONTEXT:  PL/pgSQL function "case_test" line 5 at CASE
  select case_test(8);
        case_test       
  ----------------------
***************
*** 3667,3673 ****
  select case_test(13); -- fails
  ERROR:  case not found
  HINT:  CASE statement is missing ELSE part.
! CONTEXT:  PL/pgSQL function "case_test" line 4 at CASE
  create or replace function catch() returns void as $$
  begin
    raise notice '%', case_test(6);
--- 3667,3673 ----
  select case_test(13); -- fails
  ERROR:  case not found
  HINT:  CASE statement is missing ELSE part.
! CONTEXT:  PL/pgSQL function "case_test" line 5 at CASE
  create or replace function catch() returns void as $$
  begin
    raise notice '%', case_test(6);
***************
*** 3943,3949 ****
                 ^
  HINT:  Use the escape string syntax for backslashes, e.g., E'\\'.
  QUERY:  SELECT 'foo\\bar\041baz'
! CONTEXT:  PL/pgSQL function "strtest" line 3 at RETURN
     strtest   
  -------------
   foo\bar!baz
--- 3943,3949 ----
                 ^
  HINT:  Use the escape string syntax for backslashes, e.g., E'\\'.
  QUERY:  SELECT 'foo\\bar\041baz'
! CONTEXT:  PL/pgSQL function "strtest" line 4 at RETURN
     strtest   
  -------------
   foo\bar!baz
***************
*** 4026,4032 ****
  LINE 1: SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomn...
                                          ^
  QUERY:  SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno
! CONTEXT:  PL/pgSQL function "inline_code_block" line 3 at FOR over SELECT rows
  -- Check variable scoping -- a var is not available in its own or prior
  -- default expressions.
  create function scope_test() returns int as $$
--- 4026,4032 ----
  LINE 1: SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomn...
                                          ^
  QUERY:  SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno
! CONTEXT:  PL/pgSQL function "inline_code_block" line 4 at FOR over SELECT rows
  -- Check variable scoping -- a var is not available in its own or prior
  -- default expressions.
  create function scope_test() returns int as $$
***************
*** 4063,4069 ****
                 ^
  DETAIL:  It could refer to either a PL/pgSQL variable or a table column.
  QUERY:  select q1,q2 from int8_tbl
! CONTEXT:  PL/pgSQL function "conflict_test" line 4 at FOR over SELECT rows
  create or replace function conflict_test() returns setof int8_tbl as $$
  #variable_conflict use_variable
  declare r record;
--- 4063,4069 ----
                 ^
  DETAIL:  It could refer to either a PL/pgSQL variable or a table column.
  QUERY:  select q1,q2 from int8_tbl
! CONTEXT:  PL/pgSQL function "conflict_test" line 5 at FOR over SELECT rows
  create or replace function conflict_test() returns setof int8_tbl as $$
  #variable_conflict use_variable
  declare r record;
edit2.difftext/x-patch; charset=US-ASCII; name=edit2.diffDownload
*** ./doc/src/sgml/ref/psql-ref.sgml.orig	2010-08-01 21:05:15.000000000 +0200
--- ./doc/src/sgml/ref/psql-ref.sgml	2010-08-01 21:05:54.000000000 +0200
***************
*** 1339,1345 ****
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1339,1345 ----
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional> <optional> linenumber </optional></literal></term>
  
          <listitem>
          <para>
***************
*** 1369,1380 ****
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1369,1386 ----
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor.
+         </para>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional> <optional> linenumber </optional> </literal></term>
  
          <listitem>
          <para>
***************
*** 1397,1402 ****
--- 1403,1415 ----
           If no function is specified, a blank <command>CREATE FUNCTION</>
           template is presented for editing.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor. It count lines from start of function body, not from
+         start of text.
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 2116,2121 ****
--- 2129,2146 ----
  
  
        <varlistentry>
+         <term><literal>\sf[+] <replaceable class="parameter">function_description</replaceable> <optional> linenumber </optional> </literal></term>
+ 
+         <listitem>
+         <para>
+          This command fetches and shows the definition of the named function,
+          in the form of a <command>CREATE OR REPLACE FUNCTION</> command.
+          If the form <literal>\sf+</literal> is used, then lines are numbered.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
          <term><literal>\t</literal></term>
          <listitem>
          <para>
***************
*** 2123,2128 ****
--- 2148,2159 ----
          footer. This command is equivalent to <literal>\pset
          tuples_only</literal> and is provided for convenience.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor.
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 3066,3071 ****
--- 3097,3117 ----
     </varlistentry>
  
     <varlistentry>
+     <term><envar>PSQL_EDITOR_NAVIGATION_OPTION</envar></term>
+ 
+     <listitem>
+      <para>
+       Option used for navigation (go to line command) in external
+       editor. When it isn't defined, then it uses <option>+</option>
+       on Unix systems and <option>/</option> on Windows systems. For
+       wide used KDE editors is necessary to set this option to
+       <option>--line </option>. The space after <literal>line</literal>
+       is required.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><envar>SHELL</envar></term>
  
      <listitem>
*** ./src/bin/psql/command.c.orig	2010-08-01 21:05:15.000000000 +0200
--- ./src/bin/psql/command.c	2010-08-01 22:44:17.000000000 +0200
***************
*** 57,63 ****
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		bool *edited);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
--- 57,63 ----
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		bool *edited, int lineno);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
***************
*** 65,70 ****
--- 65,73 ----
  static void minimal_error_message(PGresult *res);
  
  static void printSSLInfo(void);
+ static int get_lineno_for_navigation(char *func, backslashResult *status);
+ 
+ static char *extract_separator(char *src);
  
  #ifdef WIN32
  static void checkWin32Codepage(void);
***************
*** 513,528 ****
  		else
  		{
  			char	   *fname;
! 
  			fname = psql_scan_slash_option(scan_state,
! 										   OT_NORMAL, NULL, true);
! 			expand_tilde(&fname);
  			if (fname)
! 				canonicalize_path(fname);
! 			if (do_edit(fname, query_buf, NULL))
! 				status = PSQL_CMD_NEWEDIT;
! 			else
! 				status = PSQL_CMD_ERROR;
  			free(fname);
  		}
  	}
--- 516,553 ----
  		else
  		{
  			char	   *fname;
! 			char		*ln;
! 			int  lineno = -1;
! 			
  			fname = psql_scan_slash_option(scan_state,
! 								   OT_NORMAL, NULL, true);
! 			
! 			/* try to get lineno */
  			if (fname)
! 			{
! 				ln = psql_scan_slash_option(scan_state,
! 									   OT_NORMAL, NULL, true);
! 				if (ln)
! 				{
! 					if (atoi(ln) < 1)
! 					{
! 						psql_error("line number is unacceptable\n");
! 						status = PSQL_CMD_ERROR;
! 					}
! 					else
! 						lineno = atoi(ln);
! 				}
! 			}
! 			if (status != PSQL_CMD_ERROR)
! 			{
! 				expand_tilde(&fname);
! 				if (fname)
! 					canonicalize_path(fname);
! 				if (do_edit(fname, query_buf, NULL, lineno))
! 					status = PSQL_CMD_NEWEDIT;
! 				else
! 					status = PSQL_CMD_ERROR;
! 			}
  			free(fname);
  		}
  	}
***************
*** 533,538 ****
--- 558,565 ----
  	 */
  	else if (strcmp(cmd, "ef") == 0)
  	{
+ 		int	lineno;
+ 	
  		if (!query_buf)
  		{
  			psql_error("no query buffer\n");
***************
*** 545,580 ****
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			if (!func)
  			{
! 				/* set up an empty command to fill in */
! 				printfPQExpBuffer(query_buf,
! 								  "CREATE FUNCTION ( )\n"
! 								  " RETURNS \n"
! 								  " LANGUAGE \n"
! 								  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 								  "AS $function$\n"
! 								  "\n$function$\n");
! 			}
! 			else if (!lookup_function_oid(pset.db, func, &foid))
! 			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
! 			}
! 			else if (!get_create_function_cmd(pset.db, foid, query_buf))
! 			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
  			}
- 			if (func)
- 				free(func);
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, &edited))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
--- 572,612 ----
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			lineno = get_lineno_for_navigation(func, &status);
! 			
! 			if (status != PSQL_CMD_ERROR)
  			{
! 				if (!func)
! 				{
! 					/* set up an empty command to fill in */
! 					printfPQExpBuffer(query_buf,
! 									  "CREATE FUNCTION ( )\n"
! 									  " RETURNS \n"
! 									  " LANGUAGE \n"
! 									  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 									  "AS $function$\n"
! 									  "\n$function$\n");
! 				}
! 				else if (!lookup_function_oid(pset.db, func, &foid))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
! 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
! 				if (func)
! 					free(func);
  			}
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, &edited, lineno))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
***************
*** 969,974 ****
--- 1001,1121 ----
  		free(fname);
  	}
  
+ 	/*
+ 	 * \sf -- show the named function
+ 	 */
+ 	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+ 	{
+ 		int	skip_lines = -1;
+ 		bool	with_lno;
+ 		
+ 		with_lno = (strcmp(cmd, "sf+") == 0);
+ 		
+ 		if (!query_buf)
+ 		{
+ 			psql_error("no query buffer\n");
+ 			status = PSQL_CMD_ERROR;
+ 		}
+ 		else
+ 		{
+ 			char	   *func;
+ 			Oid			foid = InvalidOid;
+ 
+ 			func = psql_scan_slash_option(scan_state,
+ 										  OT_WHOLE_LINE, NULL, true);
+ 			skip_lines = get_lineno_for_navigation(func, &status) - 1;
+ 			
+ 			if (status != PSQL_CMD_ERROR)
+ 			{
+ 				if (!func)
+ 				{
+ 					/* show error for empty command */
+ 					psql_error("missing a function name\n");
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!lookup_function_oid(pset.db, func, &foid))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 			}
+ 			if (func)
+ 				free(func);
+ 		}
+ 
+ 		if (status != PSQL_CMD_ERROR)
+ 		{
+ 			int	lineno = 0;
+ 			char	*lines = query_buf->data;
+ 			char	   *bsep;
+ 			bool	is_header = true;
+ 			bool	is_body = false;
+ 			bool	is_footer = false;
+ 			char	  *end_of_line;
+ 			
+ 			while (*lines)
+ 			{
+ 				/* find next end of line */
+ 				end_of_line = strchr(lines, '\n');
+ 				if (end_of_line)
+ 					*end_of_line = '\0';
+ 				
+ 				if (is_header)
+ 				{
+ 					/* detect end of header */
+ 					bsep = extract_separator(lines);
+ 					if (bsep)
+ 					{
+ 						is_header = false;
+ 						is_body = true;
+ 						lineno = 1;
+ 					}
+ 				}
+ 				else if (is_body)
+ 				{
+ 					lineno++;
+ 					if (strcmp(lines, bsep) == 0)
+ 					{
+ 						is_body = false;
+ 						is_footer = true;
+ 					}
+ 				}
+ 				
+ 				/* can we show rows? */
+ 				if (skip_lines < 0 || (skip_lines < lineno))
+ 				{
+ 					if (with_lno)
+ 					{
+ 						if (is_header || is_footer)
+ 							printf("**** %s", lines);
+ 						else 
+ 							printf("%4d %s", lineno, lines);
+ 					}
+ 					else
+ 						printf("%s", lines);
+ 					
+ 					/* return back replaced "\n" */
+ 					if (end_of_line)
+ 						printf("\n");
+ 				}
+ 				
+ 				if (end_of_line)
+ 					lines = end_of_line + 1;
+ 				else
+ 					break;
+ 			}
+ 			
+ 			free(bsep);
+ 			printf("\n");
+ 			fflush(stdout);
+ 		}
+ 	}
+ 
  	/* \set -- generalized set variable/option command */
  	else if (strcmp(cmd, "set") == 0)
  	{
***************
*** 1550,1558 ****
   */
  
  static bool
! editFile(const char *fname)
  {
  	const char *editorName;
  	char	   *sys;
  	int			result;
  
--- 1697,1706 ----
   */
  
  static bool
! editFile(const char *fname, int lineno)
  {
  	const char *editorName;
+ 	const char *navigation_cmd;
  	char	   *sys;
  	int			result;
  
***************
*** 1566,1571 ****
--- 1714,1723 ----
  		editorName = getenv("VISUAL");
  	if (!editorName)
  		editorName = DEFAULT_EDITOR;
+ 	
+ 	navigation_cmd = getenv("PSQL_EDITOR_NAVIGATION_OPTION");
+ 	if (!navigation_cmd)
+ 		navigation_cmd = DEFAULT_EDITOR_NAVIGATION_OPTION;
  
  	/*
  	 * On Unix the EDITOR value should *not* be quoted, since it might include
***************
*** 1574,1584 ****
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
  #ifndef WIN32
! 	sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
--- 1726,1746 ----
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	sys = pg_malloc(strlen(editorName) + strlen(fname) + 20 + 1);
  #ifndef WIN32
! 	if (lineno > 0)
! 		sprintf(sys, "exec %s %s%d '%s'", editorName, navigation_cmd, lineno, fname);
! 	else
! 		sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	if (lineno > 0)
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" %s%d \"%s\"" SYSTEMQUOTE, 
! 									    editorName, 
! 									    navigation_cmd,
! 									    lineno, 
! 									    fname);
! 	else
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
***************
*** 1593,1599 ****
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
--- 1755,1761 ----
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited, int lineno)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
***************
*** 1685,1691 ****
  
  	/* call editor */
  	if (!error)
! 		error = !editFile(fname);
  
  	if (!error && stat(fname, &after) != 0)
  	{
--- 1847,1885 ----
  
  	/* call editor */
  	if (!error)
! 	{
! 		/* skip header lines */
! 		if (lineno != -1)
! 		{
! 			char   *lines = query_buf->data;
! 			char	*bsep;
! 			
! 			/* we have to detect number of header lines */
! 			while (*lines)
! 			{
! 				char    *end_of_line = strchr(lines, '\n');
! 			
! 				if (end_of_line)
! 					*end_of_line = '\0';
! 			
! 				bsep = extract_separator(lines);
! 				if (bsep)
! 				{
! 					free(bsep);
! 					break;
! 				}
! 				else
! 					lineno++;
! 				
! 				if (end_of_line)
! 					lines = end_of_line + 1;
! 				else
! 					break;
! 			}
! 		}
! 	
! 		error = !editFile(fname, lineno);
! 	}
  
  	if (!error && stat(fname, &after) != 0)
  	{
***************
*** 2213,2218 ****
--- 2407,2413 ----
  	return result;
  }
  
+ 
  /*
   * Report just the primary error; this is to avoid cluttering the output
   * with, for instance, a redisplay of the internally generated query
***************
*** 2241,2243 ****
--- 2436,2544 ----
  
  	destroyPQExpBuffer(msg);
  }
+ 
+ 
+ /*
+  * Returns lineno used in \sf and \ef commands. 
+  *
+  * These commands can be completed with number used as line
+  * number for navigation in showed lines / open file. The most 
+  * simple method for parsing is reading isolated digits from 
+  * right - \ef foo nn, \ef foo(..)nn. Returns -1 when
+  * lineno isn't defined.
+ */
+ static int
+ get_lineno_for_navigation(char *func, backslashResult *status)
+ {
+ 	char *endfunc;
+ 	char *c;
+ 	int lineno = -1;
+ 	
+ 	if (!func)
+ 		return lineno;
+ 	
+ 	endfunc = func + strlen(func) - 1;
+ 	c = endfunc;
+ 	
+ 	/* skip useles whitespaces */
+ 	while (c >= func)
+ 		if (isblank(*c))
+ 			c--;
+ 		else
+ 			break;
+ 	
+ 	/* search the most left digit of continuously number */
+ 	while (c >= func)
+ 		if (!isdigit(*c))
+ 			break;
+ 		else
+ 			c--;
+ 	
+ 	/* 
+ 	 * when left char isn't blank and isn't a right parenthesis
+ 	 * then command hasn't a lineno.
+ 	 */
+ 	if (c < endfunc && c > func)
+ 	{
+ 		if (isblank(*c) || *c == ')')
+ 		{
+ 			c++;
+ 			
+ 			if (atoi(c) < 1)
+ 			{
+ 				psql_error("line number is unacceptable\n");
+ 				*status = PSQL_CMD_ERROR;
+ 			}
+ 			else
+ 			{
+ 				/*----------
+ 				 * Function get_create_function_cmd appends a few lines
+ 				 * to function's body. But we would to like use a line 
+ 				 * numbers use a PL parsers - so add three lines to
+ 				 * lineno:
+ 				 *   CREATE OR REPLACE FUNCTION ..
+ 				 *     RETURNS ...
+ 				 *     LANGUAGE ...
+ 				 *     AS $finction$
+ 				 */
+ 				lineno = atoi(c);
+ 				
+ 				/* remove lineno from function descriptor */
+ 				*c = '\0';
+ 			}
+ 		}
+ 	}
+ 	
+ 	return lineno;
+ }
+ 
+ /*
+  * Returns used body separator, when it is found on line,
+  * else it returns NULL.
+  */
+ static char *
+ extract_separator(char *src)
+ {
+ 	if (strncmp(src, "AS $function", 12) == 0)
+ 	{
+ 		char *rdolar_ptr;
+ 		char *ldolar_ptr;
+ 		int	len;
+ 		char	   *result;
+ 		
+ 		/* diagnose real length of body separator */
+ 		ldolar_ptr = src + strlen("AS ");
+ 		rdolar_ptr = strchr(src + strlen("AS $function"), '$');
+ 		
+ 		psql_assert(rdolar_ptr);
+ 		
+ 		len = rdolar_ptr - ldolar_ptr + 1;
+ 		result = pg_malloc(len + 1);
+ 		memcpy(result, ldolar_ptr, len);
+ 		result[len] = '\0';
+ 		
+ 		return result;
+ 	}
+ 	else
+ 		return NULL;
+ }
*** ./src/bin/psql/help.c.orig	2010-08-01 21:05:15.000000000 +0200
--- ./src/bin/psql/help.c	2010-08-01 21:05:54.000000000 +0200
***************
*** 174,186 ****
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE]              edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME]         edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
--- 174,187 ----
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE] [lno]        edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME] [lno]   edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
+ 	fprintf(output, _("  \\sf[+] FUNCNAME [lno]  show finction definition\n"));
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
*** ./src/bin/psql/settings.h.orig	2010-08-01 21:05:15.000000000 +0200
--- ./src/bin/psql/settings.h	2010-08-01 21:05:54.000000000 +0200
***************
*** 18,25 ****
--- 18,27 ----
  
  #if defined(WIN32) || defined(__CYGWIN__)
  #define DEFAULT_EDITOR	"notepad.exe"
+ #define DEFAULT_EDITOR_NAVIGATION_OPTION	"/"
  #else
  #define DEFAULT_EDITOR	"vi"
+ #define DEFAULT_EDITOR_NAVIGATION_OPTION	"+"
  #endif
  
  #define DEFAULT_PROMPT1 "%/%R%# "
*** ./src/bin/psql/tab-complete.c.orig	2010-08-01 21:05:15.000000000 +0200
--- ./src/bin/psql/tab-complete.c	2010-08-01 21:05:54.000000000 +0200
***************
*** 644,650 ****
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
--- 644,650 ----
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\sf", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
***************
*** 2501,2506 ****
--- 2501,2509 ----
  
  	else if (strcmp(prev_wd, "\\ef") == 0)
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+ 	
+ 		else if (strncmp(prev_wd, "\\sf", 2) == 0)
+ 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
  
  	else if (strcmp(prev_wd, "\\encoding") == 0)
  		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
#23Robert Haas
robertmhaas@gmail.com
In reply to: Pavel Stehule (#22)
Re: review: psql: edit function, show function commands patch

On Sun, Aug 1, 2010 at 4:53 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

a) remove special row number handling of plpgsql (first patch)

Committed.

b) more robust algorithm for header rows identification

Have not gotten to this one yet.

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

#24Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#23)
Re: review: psql: edit function, show function commands patch

On Sun, Aug 1, 2010 at 11:48 PM, Robert Haas <robertmhaas@gmail.com> wrote:

b) more robust algorithm for header rows identification

Have not gotten to this one yet.

I notIce that on WIN32 the default editor is notepad.exe and the
default editor navigation option is "/". Does "notepad.exe /lineno
filename" actually work on Windows? A quick Google search suggests
that the answer is "no", which seems somewhat unfortunate: it means
we'd be shipping "broken on Win32 out of the box".

http://www.robvanderwoude.com/commandlineswitches.php#Notepad

This is actually my biggest concern about this patch - that it may be
just too much of a hassle to actually make it work for people. I just
tried setting $EDITOR to MacOS's TextEdit program, and it turns out
that TextEdit doesn't understand +. I'm afraid that we're going to
end up with a situation where it only works for people using emacs or
vi, and everyone else ends up with a broken install (and, possibly, no
clear idea how to fix it). Perhaps it would be better to accept \sf
and reject \sf+ and \ef <func> <lineno>.

Assuming we get past that threshold issue, I'm also wondering whether
the "navigation option" terminology is best; e.g. set
PSQL_EDITOR_NAVIGATION_OPTION to configure it. It doesn't seem
terrible, but it doesn't seem great, either. Anyone have a better
idea?

The docs are a little rough; they could benefit from some editing by a
fluent English speaker. If anyone has time to work on this, it would
be much appreciated.

Instead of "line number is unacceptable", I think you should write
"invalid line number".

"dollar" should not be spelled "dolar". "function" should not be
spelled "finction".

This change looks suspiciously like magic. If it's actually safe, it
needs a comment explaining why:

-       sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
+       sys = pg_malloc(strlen(editorName) + strlen(fname) + 20 + 1);

Attempting to compile with this patch applied emits a warning on my
machine; whether or not the warning is valid, you need to make it go
away:

command.c: In function ‘HandleSlashCmds’:
command.c:1055: warning: ‘bsep’ may be used uninitialized in this function
command.c:1055: note: ‘bsep’ was declared here

Why does the \sf output have a trailing blank line? This seems weird,
especially because \ef puts no such trailing blank line in the editor.

Instead of:

+       /* skip useles whitespaces */
+       while (c >= func)
+               if (isblank(*c))
+                       c--;
+               else
+                       break;

...wouldn't it be just as good to write:

while (c >= func && isblank(*c))
c--;

(and similarly elsewhere)

In extract_separator, if you invert the sense of the first if-test,
then you can avoid having to indent the entire function contents.

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

#25Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#24)
Re: review: psql: edit function, show function commands patch

Robert Haas <robertmhaas@gmail.com> writes:

This is actually my biggest concern about this patch - that it may be
just too much of a hassle to actually make it work for people. I just
tried setting $EDITOR to MacOS's TextEdit program, and it turns out
that TextEdit doesn't understand +. I'm afraid that we're going to
end up with a situation where it only works for people using emacs or
vi, and everyone else ends up with a broken install (and, possibly, no
clear idea how to fix it).

[ disclaimer: I've not looked at the proposed patch yet ]

It seems like this ought to be fairly easily surmountable as long as
the patch is designed for failure. The fallback position is just that
the line number does nothing, ie \ef foo just opens the editor and
doesn't try to position the cursor anywhere special; nobody can complain
about that because it's no worse than before. What we need is to not
try to force positioning if we don't recognize the editor. I'm tempted
to suggest forgetting about any user-configurable parameter and just
provide code that strcmp's the $EDITOR value to see if it recognizes the
editor name, otherwise do nothing.

regards, tom lane

#26Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#25)
Re: review: psql: edit function, show function commands patch

On Mon, Aug 2, 2010 at 10:49 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

This is actually my biggest concern about this patch - that it may be
just too much of a hassle to actually make it work for people.  I just
tried setting $EDITOR to MacOS's TextEdit program, and it turns out
that TextEdit doesn't understand +.  I'm afraid that we're going to
end up with a situation where it only works for people using emacs or
vi, and everyone else ends up with a broken install (and, possibly, no
clear idea how to fix it).

[ disclaimer: I've not looked at the proposed patch yet ]

It seems like this ought to be fairly easily surmountable as long as
the patch is designed for failure.

It isn't.

The fallback position is just that
the line number does nothing, ie \ef foo just opens the editor and
doesn't try to position the cursor anywhere special; nobody can complain
about that because it's no worse than before.  What we need is to not
try to force positioning if we don't recognize the editor.

Supposing for the moment that we could make it work that way, that
would be reasonable.

I'm tempted
to suggest forgetting about any user-configurable parameter and just
provide code that strcmp's the $EDITOR value to see if it recognizes the
editor name, otherwise do nothing.

With all due respect, that sounds like an amazingly bad idea. Surely,
we'll be forever getting patches to add $MYFAVORITEEDITOR to the list,
or complaints that it's not already included. While this is
superficially a Nice Thing to Have and I would certainly support it if
+linenumber were relatively universal, it's really a pretty minor
convenience when you come right down to it, and I am not at all
convinced it is worth the hassle of trying to divine what piece of
syntax will equip the user's choice of editor with the necessary
amount of clue.

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

#27Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#26)
Re: review: psql: edit function, show function commands patch

Robert Haas <robertmhaas@gmail.com> writes:

On Mon, Aug 2, 2010 at 10:49 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I'm tempted
to suggest forgetting about any user-configurable parameter and just
provide code that strcmp's the $EDITOR value to see if it recognizes the
editor name, otherwise do nothing.

With all due respect, that sounds like an amazingly bad idea. Surely,
we'll be forever getting patches to add $MYFAVORITEEDITOR to the list,
or complaints that it's not already included.

Well, yeah, that's the idea. I say that beats a constant stream of
complaints that $MYFAVORITEEDITOR no longer works at all --- which
is what your concern was, no?

While this is
superficially a Nice Thing to Have and I would certainly support it if
+linenumber were relatively universal, it's really a pretty minor
convenience when you come right down to it, and I am not at all
convinced it is worth the hassle of trying to divine what piece of
syntax will equip the user's choice of editor with the necessary
amount of clue.

The other approach we could take is that this whole thing is disabled by
default, and you have to set a psql variable EDITOR_LINENUMBER_SWITCH
to turn it on. If you haven't read the documentation enough to find
out that variable exists, well, no harm no foul.

regards, tom lane

#28Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#27)
Re: review: psql: edit function, show function commands patch

On Mon, Aug 2, 2010 at 11:17 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Mon, Aug 2, 2010 at 10:49 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I'm tempted
to suggest forgetting about any user-configurable parameter and just
provide code that strcmp's the $EDITOR value to see if it recognizes the
editor name, otherwise do nothing.

With all due respect, that sounds like an amazingly bad idea.  Surely,
we'll be forever getting patches to add $MYFAVORITEEDITOR to the list,
or complaints that it's not already included.

Well, yeah, that's the idea.  I say that beats a constant stream of
complaints that $MYFAVORITEEDITOR no longer works at all --- which
is what your concern was, no?

Well, it'd still work fine for \e foo. It'll just blow up for \e foo
3. My concern isn't really that things that which work now will break
so much as that this new feature will fail to work for a large
percentage of the people who try to use it, including virtually
everyone who tries to use it on Win32.

While this is
superficially a Nice Thing to Have and I would certainly support it if
+linenumber were relatively universal, it's really a pretty minor
convenience when you come right down to it, and I am not at all
convinced it is worth the hassle of trying to divine what piece of
syntax will equip the user's choice of editor with the necessary
amount of clue.

The other approach we could take is that this whole thing is disabled by
default, and you have to set a psql variable EDITOR_LINENUMBER_SWITCH
to turn it on.  If you haven't read the documentation enough to find
out that variable exists, well, no harm no foul.

That might be reasonable. Right now the default behavior is to do
+line on Linux and /line on Windows. But maybe a more sensible
default would be to fail with an error message that says "you have to
set thus-and-so variable if you want to use this feature". That seems
better than sometimes working and sometimes failing depending on what
editor the user has configured.

A side question is whether this should be an environment variable or a
psql variable.

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

#29Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#24)
Re: review: psql: edit function, show function commands patch

2010/8/3 Robert Haas <robertmhaas@gmail.com>:

On Sun, Aug 1, 2010 at 11:48 PM, Robert Haas <robertmhaas@gmail.com> wrote:

b) more robust algorithm for header rows identification

Have not gotten to this one yet.

I notIce that on WIN32 the default editor is notepad.exe and the
default editor navigation option is "/".  Does "notepad.exe /lineno
filename" actually work on Windows?  A quick Google search suggests
that the answer is "no", which seems somewhat unfortunate: it means
we'd be shipping "broken on Win32 out of the box".

http://www.robvanderwoude.com/commandlineswitches.php#Notepad

notapad supports nothing :( Microsoft doesn't use command line. I
found this syntax in pspad. Next other editor is notepad++. It use a
syntax -n. Wide used KDE editors use --line syntax

This is actually my biggest concern about this patch - that it may be
just too much of a hassle to actually make it work for people.  I just
tried setting $EDITOR to MacOS's TextEdit program, and it turns out
that TextEdit doesn't understand +.  I'm afraid that we're going to
end up with a situation where it only works for people using emacs or
vi, and everyone else ends up with a broken install (and, possibly, no
clear idea how to fix it).  Perhaps it would be better to accept \sf
and reject \sf+ and \ef <func> <lineno>.

\sf+ is base - we really need a source output with line numbers. \ef
foo func is just user very friendly function. I agree, so this topic
have to be better documented

Assuming we get past that threshold issue, I'm also wondering whether
the "navigation option" terminology is best; e.g. set
PSQL_EDITOR_NAVIGATION_OPTION to configure it.  It doesn't seem
terrible, but it doesn't seem great, either.  Anyone have a better
idea?

The docs are a little rough; they could benefit from some editing by a
fluent English speaker.  If anyone has time to work on this, it would
be much appreciated.

Instead of "line number is unacceptable", I think you should write
"invalid line number".

"dollar" should not be spelled "dolar".  "function" should not be
spelled "finction".

This change looks suspiciously like magic.  If it's actually safe, it
needs a comment explaining why:

-       sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
+       sys = pg_malloc(strlen(editorName) + strlen(fname) + 20 + 1);

Attempting to compile with this patch applied emits a warning on my
machine; whether or not the warning is valid, you need to make it go
away:

command.c: In function ‘HandleSlashCmds’:
command.c:1055: warning: ‘bsep’ may be used uninitialized in this function
command.c:1055: note: ‘bsep’ was declared here

Why does the \sf output have a trailing blank line?  This seems weird,
especially because \ef puts no such trailing blank line in the editor.

Instead of:

+       /* skip useles whitespaces */
+       while (c >= func)
+               if (isblank(*c))
+                       c--;
+               else
+                       break;

...wouldn't it be just as good to write:

while (c >= func && isblank(*c))
   c--;

(and similarly elsewhere)

In extract_separator, if you invert the sense of the first if-test,
then you can avoid having to indent the entire function contents.

--

I'' recheck these issue

Pavel

Show quoted text

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

#30Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#28)
Re: review: psql: edit function, show function commands patch

2010/8/3 Robert Haas <robertmhaas@gmail.com>:

On Mon, Aug 2, 2010 at 11:17 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Mon, Aug 2, 2010 at 10:49 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I'm tempted
to suggest forgetting about any user-configurable parameter and just
provide code that strcmp's the $EDITOR value to see if it recognizes the
editor name, otherwise do nothing.

With all due respect, that sounds like an amazingly bad idea.  Surely,
we'll be forever getting patches to add $MYFAVORITEEDITOR to the list,
or complaints that it's not already included.

Well, yeah, that's the idea.  I say that beats a constant stream of
complaints that $MYFAVORITEEDITOR no longer works at all --- which
is what your concern was, no?

Well, it'd still work fine for \e foo.  It'll just blow up for \e foo
3.  My concern isn't really that things that which work now will break
so much as that this new feature will fail to work for a large
percentage of the people who try to use it, including virtually
everyone who tries to use it on Win32.

While this is
superficially a Nice Thing to Have and I would certainly support it if
+linenumber were relatively universal, it's really a pretty minor
convenience when you come right down to it, and I am not at all
convinced it is worth the hassle of trying to divine what piece of
syntax will equip the user's choice of editor with the necessary
amount of clue.

The other approach we could take is that this whole thing is disabled by
default, and you have to set a psql variable EDITOR_LINENUMBER_SWITCH
to turn it on.  If you haven't read the documentation enough to find
out that variable exists, well, no harm no foul.

That might be reasonable.  Right now the default behavior is to do
+line on Linux and /line on Windows.  But maybe a more sensible
default would be to fail with an error message that says "you have to
set thus-and-so variable if you want to use this feature".  That seems
better than sometimes working and sometimes failing depending on what
editor the user has configured.

I like this idea

A side question is whether this should be an environment variable or a
psql variable.

it can be just psql variable.

Pavel

Show quoted text

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

#31Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#24)
1 attachment(s)
Re: review: psql: edit function, show function commands patch

attached updated patch

* don't use a default option for navigation in editor - user have to
set this option explicitly
* name for this psql variable is EDITOR_LINENUMBER_SWITCH -
* updated comments, doc and some issues described by Robert

Regards

Pavel Stehule

2010/8/3 Robert Haas <robertmhaas@gmail.com>:

Show quoted text

On Sun, Aug 1, 2010 at 11:48 PM, Robert Haas <robertmhaas@gmail.com> wrote:

b) more robust algorithm for header rows identification

Have not gotten to this one yet.

I notIce that on WIN32 the default editor is notepad.exe and the
default editor navigation option is "/".  Does "notepad.exe /lineno
filename" actually work on Windows?  A quick Google search suggests
that the answer is "no", which seems somewhat unfortunate: it means
we'd be shipping "broken on Win32 out of the box".

http://www.robvanderwoude.com/commandlineswitches.php#Notepad

This is actually my biggest concern about this patch - that it may be
just too much of a hassle to actually make it work for people.  I just
tried setting $EDITOR to MacOS's TextEdit program, and it turns out
that TextEdit doesn't understand +.  I'm afraid that we're going to
end up with a situation where it only works for people using emacs or
vi, and everyone else ends up with a broken install (and, possibly, no
clear idea how to fix it).  Perhaps it would be better to accept \sf
and reject \sf+ and \ef <func> <lineno>.

Assuming we get past that threshold issue, I'm also wondering whether
the "navigation option" terminology is best; e.g. set
PSQL_EDITOR_NAVIGATION_OPTION to configure it.  It doesn't seem
terrible, but it doesn't seem great, either.  Anyone have a better
idea?

The docs are a little rough; they could benefit from some editing by a
fluent English speaker.  If anyone has time to work on this, it would
be much appreciated.

Instead of "line number is unacceptable", I think you should write
"invalid line number".

"dollar" should not be spelled "dolar".  "function" should not be
spelled "finction".

This change looks suspiciously like magic.  If it's actually safe, it
needs a comment explaining why:

-       sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
+       sys = pg_malloc(strlen(editorName) + strlen(fname) + 20 + 1);

Attempting to compile with this patch applied emits a warning on my
machine; whether or not the warning is valid, you need to make it go
away:

command.c: In function ‘HandleSlashCmds’:
command.c:1055: warning: ‘bsep’ may be used uninitialized in this function
command.c:1055: note: ‘bsep’ was declared here

Why does the \sf output have a trailing blank line?  This seems weird,
especially because \ef puts no such trailing blank line in the editor.

Instead of:

+       /* skip useles whitespaces */
+       while (c >= func)
+               if (isblank(*c))
+                       c--;
+               else
+                       break;

...wouldn't it be just as good to write:

while (c >= func && isblank(*c))
   c--;

(and similarly elsewhere)

In extract_separator, if you invert the sense of the first if-test,
then you can avoid having to indent the entire function contents.

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

Attachments:

edit3.difftext/x-patch; charset=US-ASCII; name=edit3.diffDownload
*** ./doc/src/sgml/ref/psql-ref.sgml.orig	2010-08-03 09:00:48.384710383 +0200
--- ./doc/src/sgml/ref/psql-ref.sgml	2010-08-03 10:44:57.312835131 +0200
***************
*** 1339,1345 ****
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1339,1345 ----
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional> <optional> linenumber </optional></literal></term>
  
          <listitem>
          <para>
***************
*** 1369,1380 ****
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1369,1387 ----
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor (The psql's variable <varname>EDITOR_LINENUMBER_SWITCH</varname>
+         have to be filled).
+         </para>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional> <optional> linenumber </optional> </literal></term>
  
          <listitem>
          <para>
***************
*** 1397,1402 ****
--- 1404,1417 ----
           If no function is specified, a blank <command>CREATE FUNCTION</>
           template is presented for editing.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor. It count lines from start of function body, not from
+         start of text (The psql's variable <varname>EDITOR_LINENUMBER_SWITCH</varname>
+         have to be filled).
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 2116,2121 ****
--- 2131,2148 ----
  
  
        <varlistentry>
+         <term><literal>\sf[+] <replaceable class="parameter">function_description</replaceable> <optional> linenumber </optional> </literal></term>
+ 
+         <listitem>
+         <para>
+          This command fetches and shows the definition of the named function,
+          in the form of a <command>CREATE OR REPLACE FUNCTION</> command.
+          If the form <literal>\sf+</literal> is used, then lines are numbered.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
          <term><literal>\t</literal></term>
          <listitem>
          <para>
***************
*** 2123,2128 ****
--- 2150,2161 ----
          footer. This command is equivalent to <literal>\pset
          tuples_only</literal> and is provided for convenience.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor.
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 2459,2464 ****
--- 2492,2511 ----
        </varlistentry>
  
        <varlistentry>
+         <term><varname>EDITOR_LINENUMBER_SWITCH</varname></term>
+         <listitem>
+         <para>
+         Option used for navigation (go to line command) in external
+         editor. When it isn't defined, then you cannot to specify
+         line numbers for <command>\edit</command> and <command>\ef</command>
+         commands. On unix platforms are possible to use a '<option>+</option>'
+         or '<option>--line </option>'. The space after <literal>line</literal> 
+         is required.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
          <term><varname>ENCODING</varname></term>
          <listitem>
          <para>
***************
*** 3065,3070 ****
--- 3112,3118 ----
      </listitem>
     </varlistentry>
  
+ 
     <varlistentry>
      <term><envar>SHELL</envar></term>
  
*** ./src/bin/psql/command.c.orig	2010-08-03 09:00:48.386710435 +0200
--- ./src/bin/psql/command.c	2010-08-03 10:56:43.901709512 +0200
***************
*** 57,63 ****
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		bool *edited);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
--- 57,63 ----
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		bool *edited, int lineno);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
***************
*** 66,71 ****
--- 66,74 ----
  
  static void printSSLInfo(void);
  
+ static int extract_lineno_from_funcdesc(char *func, backslashResult *status);
+ static char *get_dq_tag(char *row);
+ 
  #ifdef WIN32
  static void checkWin32Codepage(void);
  #endif
***************
*** 513,528 ****
  		else
  		{
  			char	   *fname;
! 
  			fname = psql_scan_slash_option(scan_state,
! 										   OT_NORMAL, NULL, true);
! 			expand_tilde(&fname);
  			if (fname)
! 				canonicalize_path(fname);
! 			if (do_edit(fname, query_buf, NULL))
! 				status = PSQL_CMD_NEWEDIT;
! 			else
! 				status = PSQL_CMD_ERROR;
  			free(fname);
  		}
  	}
--- 516,553 ----
  		else
  		{
  			char	   *fname;
! 			char		*ln;
! 			int  lineno = -1;
! 			
  			fname = psql_scan_slash_option(scan_state,
! 								   OT_NORMAL, NULL, true);
! 			
! 			/* try to get lineno */
  			if (fname)
! 			{
! 				ln = psql_scan_slash_option(scan_state,
! 									   OT_NORMAL, NULL, true);
! 				if (ln)
! 				{
! 					if (atoi(ln) < 1)
! 					{
! 						psql_error("invalid line number\n");
! 						status = PSQL_CMD_ERROR;
! 					}
! 					else
! 						lineno = atoi(ln);
! 				}
! 			}
! 			if (status != PSQL_CMD_ERROR)
! 			{
! 				expand_tilde(&fname);
! 				if (fname)
! 					canonicalize_path(fname);
! 				if (do_edit(fname, query_buf, NULL, lineno))
! 					status = PSQL_CMD_NEWEDIT;
! 				else
! 					status = PSQL_CMD_ERROR;
! 			}
  			free(fname);
  		}
  	}
***************
*** 533,538 ****
--- 558,565 ----
  	 */
  	else if (strcmp(cmd, "ef") == 0)
  	{
+ 		int	lineno;
+ 	
  		if (!query_buf)
  		{
  			psql_error("no query buffer\n");
***************
*** 545,580 ****
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			if (!func)
  			{
! 				/* set up an empty command to fill in */
! 				printfPQExpBuffer(query_buf,
! 								  "CREATE FUNCTION ( )\n"
! 								  " RETURNS \n"
! 								  " LANGUAGE \n"
! 								  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 								  "AS $function$\n"
! 								  "\n$function$\n");
! 			}
! 			else if (!lookup_function_oid(pset.db, func, &foid))
! 			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
! 			}
! 			else if (!get_create_function_cmd(pset.db, foid, query_buf))
! 			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
  			}
- 			if (func)
- 				free(func);
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, &edited))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
--- 572,612 ----
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			lineno = extract_lineno_from_funcdesc(func, &status);
! 			
! 			if (status != PSQL_CMD_ERROR)
  			{
! 				if (!func)
! 				{
! 					/* set up an empty command to fill in */
! 					printfPQExpBuffer(query_buf,
! 									  "CREATE FUNCTION ( )\n"
! 									  " RETURNS \n"
! 									  " LANGUAGE \n"
! 									  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 									  "AS $function$\n"
! 									  "\n$function$\n");
! 				}
! 				else if (!lookup_function_oid(pset.db, func, &foid))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
! 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
! 				if (func)
! 					free(func);
  			}
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, &edited, lineno))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
***************
*** 969,974 ****
--- 1001,1123 ----
  		free(fname);
  	}
  
+ 	/*
+ 	 * \sf -- show the named function
+ 	 */
+ 	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+ 	{
+ 		int	skip_lines = -1;
+ 		bool	with_lno;
+ 		
+ 		with_lno = (strcmp(cmd, "sf+") == 0);
+ 		
+ 		if (!query_buf)
+ 		{
+ 			psql_error("no query buffer\n");
+ 			status = PSQL_CMD_ERROR;
+ 		}
+ 		else
+ 		{
+ 			char	   *func;
+ 			Oid			foid = InvalidOid;
+ 
+ 			func = psql_scan_slash_option(scan_state,
+ 										  OT_WHOLE_LINE, NULL, true);
+ 			skip_lines = extract_lineno_from_funcdesc(func, &status);
+ 			
+ 			if (status != PSQL_CMD_ERROR)
+ 			{
+ 				if (!func)
+ 				{
+ 					/* show error for empty command */
+ 					psql_error("missing a function name\n");
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!lookup_function_oid(pset.db, func, &foid))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 			}
+ 			if (func)
+ 				free(func);
+ 		}
+ 
+ 		if (status != PSQL_CMD_ERROR)
+ 		{
+ 			int	lineno = 0;
+ 			char	*lines = query_buf->data;
+ 			char	   *dqtag = NULL;
+ 			bool	is_header = true;
+ 			bool	is_body = false;
+ 			bool	is_footer = false;
+ 			char	  *end_of_line;
+ 			
+ 			while (*lines)
+ 			{
+ 				/* find next end of line */
+ 				end_of_line = strchr(lines, '\n');
+ 				if (end_of_line)
+ 					*end_of_line = '\0';
+ 				
+ 				if (is_header)
+ 				{
+ 					/* detect end of header */
+ 					dqtag = get_dq_tag(lines);
+ 					if (dqtag)
+ 					{
+ 						is_header = false;
+ 						is_body = true;
+ 						lineno = 1;
+ 					}
+ 				}
+ 				else if (is_body)
+ 				{
+ 					lineno++;
+ 					if (strcmp(lines, dqtag) == 0)
+ 					{
+ 						is_body = false;
+ 						is_footer = true;
+ 					}
+ 				}
+ 				
+ 				/* can we show rows? */
+ 				if (skip_lines < 0 || (skip_lines <= lineno))
+ 				{
+ 					if (with_lno)
+ 					{
+ 						if (is_header || is_footer)
+ 							printf("**** %s", lines);
+ 						else 
+ 							printf("%4d %s", lineno, lines);
+ 					}
+ 					else
+ 						printf("%s", lines);
+ 					
+ 					/* return back replaced "\n" */
+ 					if (end_of_line)
+ 						printf("\n");
+ 				}
+ 				
+ 				if (end_of_line)
+ 					lines = end_of_line + 1;
+ 				else
+ 					break;
+ 			}
+ 			
+ 			/* function pg_get_functiondef uses dollar quoted strings always */
+ 			psql_assert(dqtag);
+ 			free(dqtag);
+ 
+ 			fflush(stdout);
+ 		}
+ 	}
+ 
  	/* \set -- generalized set variable/option command */
  	else if (strcmp(cmd, "set") == 0)
  	{
***************
*** 1550,1558 ****
   */
  
  static bool
! editFile(const char *fname)
  {
  	const char *editorName;
  	char	   *sys;
  	int			result;
  
--- 1699,1708 ----
   */
  
  static bool
! editFile(const char *fname, int lineno)
  {
  	const char *editorName;
+ 	const char *editor_lineno_switch;
  	char	   *sys;
  	int			result;
  
***************
*** 1566,1572 ****
  		editorName = getenv("VISUAL");
  	if (!editorName)
  		editorName = DEFAULT_EDITOR;
! 
  	/*
  	 * On Unix the EDITOR value should *not* be quoted, since it might include
  	 * switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
--- 1716,1733 ----
  		editorName = getenv("VISUAL");
  	if (!editorName)
  		editorName = DEFAULT_EDITOR;
! 	
! 	/* Find a EDITOR_LINENUMBER_SWITCH when lineno is used */
! 	if (lineno >= 1)
! 	{
! 		editor_lineno_switch = GetVariable(pset.vars, "EDITOR_LINENUMBER_SWITCH");
! 		if (!editor_lineno_switch)
! 		{
! 			psql_error("cannot use a editor navigation without setting EDITOR_LINENUMBER_SWITCH variable\n");
! 			return false;
! 		}
! 	}
! 	
  	/*
  	 * On Unix the EDITOR value should *not* be quoted, since it might include
  	 * switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
***************
*** 1574,1584 ****
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
  #ifndef WIN32
! 	sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
--- 1735,1767 ----
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	if (lineno >= 1)
! 		/*
! 		 * allocate sufficient memory for command line content and
! 		 * increase it about space for editor_lineno_switch and lineno 
! 		 * (signed int ~ 10 digits) and one space more.
! 		 */
! 		sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 
! 				    + strlen(editor_lineno_switch) + 10 + 1 + 1);
! 	
! 	else
! 		/* allocate sufficient memory for command line content */
! 		sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
! 	
  #ifndef WIN32
! 	if (lineno > 0)
! 		sprintf(sys, "exec %s %s%d '%s'", editorName, editor_lineno_switch, lineno, fname);
! 	else
! 		sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	if (lineno > 0)
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" %s%d \"%s\"" SYSTEMQUOTE, 
! 									    editorName, 
! 									    editor_lineno_switch,
! 									    lineno, 
! 									    fname);
! 	else
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
***************
*** 1593,1599 ****
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
--- 1776,1782 ----
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited, int lineno)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
***************
*** 1685,1691 ****
  
  	/* call editor */
  	if (!error)
! 		error = !editFile(fname);
  
  	if (!error && stat(fname, &after) != 0)
  	{
--- 1868,1906 ----
  
  	/* call editor */
  	if (!error)
! 	{
! 		/* skip header lines */
! 		if (lineno != -1)
! 		{
! 			char   *lines = query_buf->data;
! 			char	*dqtag;
! 			
! 			/* we have to detect number of header lines */
! 			while (*lines)
! 			{
! 				char    *end_of_line = strchr(lines, '\n');
! 			
! 				if (end_of_line)
! 					*end_of_line = '\0';
! 			
! 				dqtag = get_dq_tag(lines);
! 				if (dqtag)
! 				{
! 					free(dqtag);
! 					break;
! 				}
! 				else
! 					lineno++;
! 				
! 				if (end_of_line)
! 					lines = end_of_line + 1;
! 				else
! 					break;
! 			}
! 		}
! 	
! 		error = !editFile(fname, lineno);
! 	}
  
  	if (!error && stat(fname, &after) != 0)
  	{
***************
*** 2213,2218 ****
--- 2428,2434 ----
  	return result;
  }
  
+ 
  /*
   * Report just the primary error; this is to avoid cluttering the output
   * with, for instance, a redisplay of the internally generated query
***************
*** 2241,2243 ****
--- 2457,2549 ----
  
  	destroyPQExpBuffer(msg);
  }
+ 
+ 
+ /*
+  * Returns lineno used in \sf and \ef commands. 
+  *
+  * These commands can be completed with number used as line
+  * number for navigation in showed lines / open file. The most 
+  * simple method for parsing is reading isolated digits from 
+  * right - \ef foo nn, \ef foo(..)nn. Returns -1 when
+  * lineno isn't used in function's descriptor.
+ */
+ static int
+ extract_lineno_from_funcdesc(char *func, backslashResult *status)
+ {
+ 	char *endfunc;
+ 	char *c;
+ 	int lineno = -1;
+ 	
+ 	if (!func)
+ 		return lineno;
+ 	
+ 	endfunc = func + strlen(func) - 1;
+ 	c = endfunc;
+ 	
+ 	/* skip useles whitespaces */
+ 	while (c >= func && isblank(*c))
+ 		c--;
+ 	
+ 	/* search the most left digit of continuously number */
+ 	while (c >= func && isdigit(*c))
+ 		c--;
+ 	
+ 	/* 
+ 	 * when left char isn't blank and isn't a right parenthesis
+ 	 * then command hasn't a lineno.
+ 	 */
+ 	if (c < endfunc && c > func)
+ 	{
+ 		if (isblank(*c) || *c == ')')
+ 		{
+ 			c++;
+ 			
+ 			if (atoi(c) < 1)
+ 			{
+ 				psql_error("invalid line number\n");
+ 				*status = PSQL_CMD_ERROR;
+ 			}
+ 			else
+ 			{
+ 				lineno = atoi(c);
+ 				/* remove lineno from function descriptor */
+ 				*c = '\0';
+ 			}
+ 		}
+ 	}
+ 	
+ 	return lineno;
+ }
+ 
+ /*
+  * Returns tag of dollar quoted string used as function body, It parses
+  * only result of pg_get_functiondef function, so there are not possibility
+  * use just "'" char. When row doesn't contain a AS part of CREATE FUNCTION
+  * command, then it returns NULL.
+  */
+ static char *
+ get_dq_tag(char *row)
+ {
+ 	char *starttag;
+ 	char *endtag;
+ 	int	len;
+ 	char	*result;
+ 
+ 	/* leave when line doesn't contain a body separator */
+ 	if (strncmp(row, "AS $function", 12) != 0)
+ 		return NULL;
+ 		
+ 	/* detect body's tag */
+ 	starttag = row + strlen("AS ");
+ 	endtag = strchr(row + strlen("AS $function"), '$');
+ 	
+ 	psql_assert(endtag);
+ 	
+ 	len = endtag - starttag + 1;
+ 	result = pg_malloc(len + 1);
+ 	memcpy(result, starttag, len);
+ 	result[len] = '\0';
+ 		
+ 	return result;
+ }
*** ./src/bin/psql/help.c.orig	2010-08-03 09:00:48.387710077 +0200
--- ./src/bin/psql/help.c	2010-08-03 09:01:05.994713167 +0200
***************
*** 174,186 ****
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE]              edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME]         edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
--- 174,187 ----
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE] [lno]        edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME] [lno]   edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
+ 	fprintf(output, _("  \\sf[+] FUNCNAME [lno]  show finction definition\n"));
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
*** ./src/bin/psql/tab-complete.c.orig	2010-08-03 09:00:48.390710399 +0200
--- ./src/bin/psql/tab-complete.c	2010-08-03 09:01:05.996710353 +0200
***************
*** 644,650 ****
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
--- 644,650 ----
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\sf", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
***************
*** 2501,2506 ****
--- 2501,2509 ----
  
  	else if (strcmp(prev_wd, "\\ef") == 0)
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+ 	
+ 		else if (strncmp(prev_wd, "\\sf", 2) == 0)
+ 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
  
  	else if (strcmp(prev_wd, "\\encoding") == 0)
  		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
#32Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#31)
1 attachment(s)
Re: review: psql: edit function, show function commands patch

Hello

2010/8/3 Pavel Stehule <pavel.stehule@gmail.com>:

attached updated patch

* don't use a default option for navigation in editor - user have to
set this option explicitly
* name for this psql variable is EDITOR_LINENUMBER_SWITCH -
* updated comments, doc and some issues described by Robert

Regards

Pavel Stehule

I found a small bug - the last patch better handle parsing lineno
after function descriptor

Regards

Pavel

Show quoted text

2010/8/3 Robert Haas <robertmhaas@gmail.com>:

On Sun, Aug 1, 2010 at 11:48 PM, Robert Haas <robertmhaas@gmail.com> wrote:

b) more robust algorithm for header rows identification

Have not gotten to this one yet.

I notIce that on WIN32 the default editor is notepad.exe and the
default editor navigation option is "/".  Does "notepad.exe /lineno
filename" actually work on Windows?  A quick Google search suggests
that the answer is "no", which seems somewhat unfortunate: it means
we'd be shipping "broken on Win32 out of the box".

http://www.robvanderwoude.com/commandlineswitches.php#Notepad

This is actually my biggest concern about this patch - that it may be
just too much of a hassle to actually make it work for people.  I just
tried setting $EDITOR to MacOS's TextEdit program, and it turns out
that TextEdit doesn't understand +.  I'm afraid that we're going to
end up with a situation where it only works for people using emacs or
vi, and everyone else ends up with a broken install (and, possibly, no
clear idea how to fix it).  Perhaps it would be better to accept \sf
and reject \sf+ and \ef <func> <lineno>.

Assuming we get past that threshold issue, I'm also wondering whether
the "navigation option" terminology is best; e.g. set
PSQL_EDITOR_NAVIGATION_OPTION to configure it.  It doesn't seem
terrible, but it doesn't seem great, either.  Anyone have a better
idea?

The docs are a little rough; they could benefit from some editing by a
fluent English speaker.  If anyone has time to work on this, it would
be much appreciated.

Instead of "line number is unacceptable", I think you should write
"invalid line number".

"dollar" should not be spelled "dolar".  "function" should not be
spelled "finction".

This change looks suspiciously like magic.  If it's actually safe, it
needs a comment explaining why:

-       sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
+       sys = pg_malloc(strlen(editorName) + strlen(fname) + 20 + 1);

Attempting to compile with this patch applied emits a warning on my
machine; whether or not the warning is valid, you need to make it go
away:

command.c: In function ‘HandleSlashCmds’:
command.c:1055: warning: ‘bsep’ may be used uninitialized in this function
command.c:1055: note: ‘bsep’ was declared here

Why does the \sf output have a trailing blank line?  This seems weird,
especially because \ef puts no such trailing blank line in the editor.

Instead of:

+       /* skip useles whitespaces */
+       while (c >= func)
+               if (isblank(*c))
+                       c--;
+               else
+                       break;

...wouldn't it be just as good to write:

while (c >= func && isblank(*c))
   c--;

(and similarly elsewhere)

In extract_separator, if you invert the sense of the first if-test,
then you can avoid having to indent the entire function contents.

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

Attachments:

edit4.difftext/x-patch; charset=US-ASCII; name=edit4.diffDownload
*** ./doc/src/sgml/ref/psql-ref.sgml.orig	2010-08-03 09:00:48.384710383 +0200
--- ./doc/src/sgml/ref/psql-ref.sgml	2010-08-03 10:44:57.312835131 +0200
***************
*** 1339,1345 ****
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1339,1345 ----
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional> <optional> linenumber </optional></literal></term>
  
          <listitem>
          <para>
***************
*** 1369,1380 ****
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1369,1387 ----
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor (The psql's variable <varname>EDITOR_LINENUMBER_SWITCH</varname>
+         have to be filled).
+         </para>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional> <optional> linenumber </optional> </literal></term>
  
          <listitem>
          <para>
***************
*** 1397,1402 ****
--- 1404,1417 ----
           If no function is specified, a blank <command>CREATE FUNCTION</>
           template is presented for editing.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor. It count lines from start of function body, not from
+         start of text (The psql's variable <varname>EDITOR_LINENUMBER_SWITCH</varname>
+         have to be filled).
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 2116,2121 ****
--- 2131,2148 ----
  
  
        <varlistentry>
+         <term><literal>\sf[+] <replaceable class="parameter">function_description</replaceable> <optional> linenumber </optional> </literal></term>
+ 
+         <listitem>
+         <para>
+          This command fetches and shows the definition of the named function,
+          in the form of a <command>CREATE OR REPLACE FUNCTION</> command.
+          If the form <literal>\sf+</literal> is used, then lines are numbered.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
          <term><literal>\t</literal></term>
          <listitem>
          <para>
***************
*** 2123,2128 ****
--- 2150,2161 ----
          footer. This command is equivalent to <literal>\pset
          tuples_only</literal> and is provided for convenience.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor.
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 2459,2464 ****
--- 2492,2511 ----
        </varlistentry>
  
        <varlistentry>
+         <term><varname>EDITOR_LINENUMBER_SWITCH</varname></term>
+         <listitem>
+         <para>
+         Option used for navigation (go to line command) in external
+         editor. When it isn't defined, then you cannot to specify
+         line numbers for <command>\edit</command> and <command>\ef</command>
+         commands. On unix platforms are possible to use a '<option>+</option>'
+         or '<option>--line </option>'. The space after <literal>line</literal> 
+         is required.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
          <term><varname>ENCODING</varname></term>
          <listitem>
          <para>
***************
*** 3065,3070 ****
--- 3112,3118 ----
      </listitem>
     </varlistentry>
  
+ 
     <varlistentry>
      <term><envar>SHELL</envar></term>
  
*** ./src/bin/psql/command.c.orig	2010-08-03 09:00:48.386710435 +0200
--- ./src/bin/psql/command.c	2010-08-03 11:22:45.970709579 +0200
***************
*** 57,63 ****
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		bool *edited);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
--- 57,63 ----
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		bool *edited, int lineno);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
***************
*** 66,71 ****
--- 66,74 ----
  
  static void printSSLInfo(void);
  
+ static int extract_lineno_from_funcdesc(char *func, backslashResult *status);
+ static char *get_dq_tag(char *row);
+ 
  #ifdef WIN32
  static void checkWin32Codepage(void);
  #endif
***************
*** 513,528 ****
  		else
  		{
  			char	   *fname;
! 
  			fname = psql_scan_slash_option(scan_state,
! 										   OT_NORMAL, NULL, true);
! 			expand_tilde(&fname);
  			if (fname)
! 				canonicalize_path(fname);
! 			if (do_edit(fname, query_buf, NULL))
! 				status = PSQL_CMD_NEWEDIT;
! 			else
! 				status = PSQL_CMD_ERROR;
  			free(fname);
  		}
  	}
--- 516,553 ----
  		else
  		{
  			char	   *fname;
! 			char		*ln;
! 			int  lineno = -1;
! 			
  			fname = psql_scan_slash_option(scan_state,
! 								   OT_NORMAL, NULL, true);
! 			
! 			/* try to get lineno */
  			if (fname)
! 			{
! 				ln = psql_scan_slash_option(scan_state,
! 									   OT_NORMAL, NULL, true);
! 				if (ln)
! 				{
! 					if (atoi(ln) < 1)
! 					{
! 						psql_error("invalid line number\n");
! 						status = PSQL_CMD_ERROR;
! 					}
! 					else
! 						lineno = atoi(ln);
! 				}
! 			}
! 			if (status != PSQL_CMD_ERROR)
! 			{
! 				expand_tilde(&fname);
! 				if (fname)
! 					canonicalize_path(fname);
! 				if (do_edit(fname, query_buf, NULL, lineno))
! 					status = PSQL_CMD_NEWEDIT;
! 				else
! 					status = PSQL_CMD_ERROR;
! 			}
  			free(fname);
  		}
  	}
***************
*** 533,538 ****
--- 558,565 ----
  	 */
  	else if (strcmp(cmd, "ef") == 0)
  	{
+ 		int	lineno;
+ 	
  		if (!query_buf)
  		{
  			psql_error("no query buffer\n");
***************
*** 545,580 ****
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			if (!func)
  			{
! 				/* set up an empty command to fill in */
! 				printfPQExpBuffer(query_buf,
! 								  "CREATE FUNCTION ( )\n"
! 								  " RETURNS \n"
! 								  " LANGUAGE \n"
! 								  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 								  "AS $function$\n"
! 								  "\n$function$\n");
! 			}
! 			else if (!lookup_function_oid(pset.db, func, &foid))
! 			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
! 			}
! 			else if (!get_create_function_cmd(pset.db, foid, query_buf))
! 			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
  			}
- 			if (func)
- 				free(func);
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, &edited))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
--- 572,612 ----
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			lineno = extract_lineno_from_funcdesc(func, &status);
! 			
! 			if (status != PSQL_CMD_ERROR)
  			{
! 				if (!func)
! 				{
! 					/* set up an empty command to fill in */
! 					printfPQExpBuffer(query_buf,
! 									  "CREATE FUNCTION ( )\n"
! 									  " RETURNS \n"
! 									  " LANGUAGE \n"
! 									  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 									  "AS $function$\n"
! 									  "\n$function$\n");
! 				}
! 				else if (!lookup_function_oid(pset.db, func, &foid))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
! 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
! 				if (func)
! 					free(func);
  			}
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, &edited, lineno))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
***************
*** 969,974 ****
--- 1001,1123 ----
  		free(fname);
  	}
  
+ 	/*
+ 	 * \sf -- show the named function
+ 	 */
+ 	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+ 	{
+ 		int	skip_lines = -1;
+ 		bool	with_lno;
+ 		
+ 		with_lno = (strcmp(cmd, "sf+") == 0);
+ 		
+ 		if (!query_buf)
+ 		{
+ 			psql_error("no query buffer\n");
+ 			status = PSQL_CMD_ERROR;
+ 		}
+ 		else
+ 		{
+ 			char	   *func;
+ 			Oid			foid = InvalidOid;
+ 
+ 			func = psql_scan_slash_option(scan_state,
+ 										  OT_WHOLE_LINE, NULL, true);
+ 			skip_lines = extract_lineno_from_funcdesc(func, &status);
+ 			
+ 			if (status != PSQL_CMD_ERROR)
+ 			{
+ 				if (!func)
+ 				{
+ 					/* show error for empty command */
+ 					psql_error("missing a function name\n");
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!lookup_function_oid(pset.db, func, &foid))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 			}
+ 			if (func)
+ 				free(func);
+ 		}
+ 
+ 		if (status != PSQL_CMD_ERROR)
+ 		{
+ 			int	lineno = 0;
+ 			char	*lines = query_buf->data;
+ 			char	   *dqtag = NULL;
+ 			bool	is_header = true;
+ 			bool	is_body = false;
+ 			bool	is_footer = false;
+ 			char	  *end_of_line;
+ 			
+ 			while (*lines)
+ 			{
+ 				/* find next end of line */
+ 				end_of_line = strchr(lines, '\n');
+ 				if (end_of_line)
+ 					*end_of_line = '\0';
+ 				
+ 				if (is_header)
+ 				{
+ 					/* detect end of header */
+ 					dqtag = get_dq_tag(lines);
+ 					if (dqtag)
+ 					{
+ 						is_header = false;
+ 						is_body = true;
+ 						lineno = 1;
+ 					}
+ 				}
+ 				else if (is_body)
+ 				{
+ 					lineno++;
+ 					if (strcmp(lines, dqtag) == 0)
+ 					{
+ 						is_body = false;
+ 						is_footer = true;
+ 					}
+ 				}
+ 				
+ 				/* can we show rows? */
+ 				if (skip_lines < 0 || (skip_lines <= lineno))
+ 				{
+ 					if (with_lno)
+ 					{
+ 						if (is_header || is_footer)
+ 							printf("**** %s", lines);
+ 						else 
+ 							printf("%4d %s", lineno, lines);
+ 					}
+ 					else
+ 						printf("%s", lines);
+ 					
+ 					/* return back replaced "\n" */
+ 					if (end_of_line)
+ 						printf("\n");
+ 				}
+ 				
+ 				if (end_of_line)
+ 					lines = end_of_line + 1;
+ 				else
+ 					break;
+ 			}
+ 			
+ 			/* function pg_get_functiondef uses dollar quoted strings always */
+ 			psql_assert(dqtag);
+ 			free(dqtag);
+ 
+ 			fflush(stdout);
+ 		}
+ 	}
+ 
  	/* \set -- generalized set variable/option command */
  	else if (strcmp(cmd, "set") == 0)
  	{
***************
*** 1550,1558 ****
   */
  
  static bool
! editFile(const char *fname)
  {
  	const char *editorName;
  	char	   *sys;
  	int			result;
  
--- 1699,1708 ----
   */
  
  static bool
! editFile(const char *fname, int lineno)
  {
  	const char *editorName;
+ 	const char *editor_lineno_switch;
  	char	   *sys;
  	int			result;
  
***************
*** 1566,1572 ****
  		editorName = getenv("VISUAL");
  	if (!editorName)
  		editorName = DEFAULT_EDITOR;
! 
  	/*
  	 * On Unix the EDITOR value should *not* be quoted, since it might include
  	 * switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
--- 1716,1733 ----
  		editorName = getenv("VISUAL");
  	if (!editorName)
  		editorName = DEFAULT_EDITOR;
! 	
! 	/* Find a EDITOR_LINENUMBER_SWITCH when lineno is used */
! 	if (lineno >= 1)
! 	{
! 		editor_lineno_switch = GetVariable(pset.vars, "EDITOR_LINENUMBER_SWITCH");
! 		if (!editor_lineno_switch)
! 		{
! 			psql_error("cannot use a editor navigation without setting EDITOR_LINENUMBER_SWITCH variable\n");
! 			return false;
! 		}
! 	}
! 	
  	/*
  	 * On Unix the EDITOR value should *not* be quoted, since it might include
  	 * switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
***************
*** 1574,1584 ****
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
  #ifndef WIN32
! 	sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
--- 1735,1767 ----
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	if (lineno >= 1)
! 		/*
! 		 * allocate sufficient memory for command line content and
! 		 * increase it about space for editor_lineno_switch and lineno 
! 		 * (signed int ~ 10 digits) and one space more.
! 		 */
! 		sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 
! 				    + strlen(editor_lineno_switch) + 10 + 1 + 1);
! 	
! 	else
! 		/* allocate sufficient memory for command line content */
! 		sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
! 	
  #ifndef WIN32
! 	if (lineno > 0)
! 		sprintf(sys, "exec %s %s%d '%s'", editorName, editor_lineno_switch, lineno, fname);
! 	else
! 		sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	if (lineno > 0)
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" %s%d \"%s\"" SYSTEMQUOTE, 
! 									    editorName, 
! 									    editor_lineno_switch,
! 									    lineno, 
! 									    fname);
! 	else
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
***************
*** 1593,1599 ****
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
--- 1776,1782 ----
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited, int lineno)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
***************
*** 1685,1691 ****
  
  	/* call editor */
  	if (!error)
! 		error = !editFile(fname);
  
  	if (!error && stat(fname, &after) != 0)
  	{
--- 1868,1906 ----
  
  	/* call editor */
  	if (!error)
! 	{
! 		/* skip header lines */
! 		if (lineno != -1)
! 		{
! 			char   *lines = query_buf->data;
! 			char	*dqtag;
! 			
! 			/* we have to detect number of header lines */
! 			while (*lines)
! 			{
! 				char    *end_of_line = strchr(lines, '\n');
! 			
! 				if (end_of_line)
! 					*end_of_line = '\0';
! 			
! 				dqtag = get_dq_tag(lines);
! 				if (dqtag)
! 				{
! 					free(dqtag);
! 					break;
! 				}
! 				else
! 					lineno++;
! 				
! 				if (end_of_line)
! 					lines = end_of_line + 1;
! 				else
! 					break;
! 			}
! 		}
! 	
! 		error = !editFile(fname, lineno);
! 	}
  
  	if (!error && stat(fname, &after) != 0)
  	{
***************
*** 2213,2218 ****
--- 2428,2434 ----
  	return result;
  }
  
+ 
  /*
   * Report just the primary error; this is to avoid cluttering the output
   * with, for instance, a redisplay of the internally generated query
***************
*** 2241,2243 ****
--- 2457,2554 ----
  
  	destroyPQExpBuffer(msg);
  }
+ 
+ 
+ /*
+  * Returns lineno used in \sf and \ef commands. 
+  *
+  * These commands can be completed with number used as line
+  * number for navigation in showed lines / open file. The most 
+  * simple method for parsing is reading isolated digits from 
+  * right - \ef foo nn, \ef foo(..)nn. Returns -1 when
+  * lineno isn't used in function's descriptor.
+ */
+ static int
+ extract_lineno_from_funcdesc(char *func, backslashResult *status)
+ {
+ 	char *endfunc;
+ 	char *c;
+ 	int lineno = -1;
+ 	
+ 	if (!func)
+ 		return lineno;
+ 	
+ 	endfunc = func + strlen(func) - 1;
+ 	c = endfunc;
+ 	
+ 	/* skip useles whitespaces */
+ 	while (c >= func && isblank(*c))
+ 		c--;
+ 	
+ 	/* search the most left digit of continuously number */
+ 	while (c >= func && isdigit(*c))
+ 		c--;
+ 	
+ 	/* 
+ 	 * when left char isn't blank and isn't a right parenthesis
+ 	 * then command hasn't a lineno.
+ 	 */
+ 	if (c < endfunc && c > func)
+ 	{
+ 		/* 
+ 		 * digits have to be a separated from identifier by right 
+ 		 * parenthesis or by space, and there have to be entered
+ 		 * minimal one digit.
+ 		 */
+ 		if (isdigit(c[1]) && ( isblank(*c) || *c == ')' ))
+ 		{
+ 			c++;
+ 			
+ 			if (atoi(c) < 1)
+ 			{
+ 				psql_error("invalid line number\n");
+ 				*status = PSQL_CMD_ERROR;
+ 			}
+ 			else
+ 			{
+ 				lineno = atoi(c);
+ 				/* remove lineno from function descriptor */
+ 				*c = '\0';
+ 			}
+ 		}
+ 	}
+ 	
+ 	return lineno;
+ }
+ 
+ /*
+  * Returns tag of dollar quoted string used as function body, It parses
+  * only result of pg_get_functiondef function, so there are not possibility
+  * use just "'" char. When row doesn't contain a AS part of CREATE FUNCTION
+  * command, then it returns NULL.
+  */
+ static char *
+ get_dq_tag(char *row)
+ {
+ 	char *starttag;
+ 	char *endtag;
+ 	int	len;
+ 	char	*result;
+ 
+ 	/* leave when line doesn't contain a body separator */
+ 	if (strncmp(row, "AS $function", 12) != 0)
+ 		return NULL;
+ 		
+ 	/* detect body's tag */
+ 	starttag = row + strlen("AS ");
+ 	endtag = strchr(row + strlen("AS $function"), '$');
+ 	
+ 	psql_assert(endtag);
+ 	
+ 	len = endtag - starttag + 1;
+ 	result = pg_malloc(len + 1);
+ 	memcpy(result, starttag, len);
+ 	result[len] = '\0';
+ 		
+ 	return result;
+ }
*** ./src/bin/psql/help.c.orig	2010-08-03 09:00:48.387710077 +0200
--- ./src/bin/psql/help.c	2010-08-03 09:01:05.994713167 +0200
***************
*** 174,186 ****
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE]              edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME]         edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
--- 174,187 ----
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE] [lno]        edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME] [lno]   edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
+ 	fprintf(output, _("  \\sf[+] FUNCNAME [lno]  show finction definition\n"));
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
*** ./src/bin/psql/tab-complete.c.orig	2010-08-03 09:00:48.390710399 +0200
--- ./src/bin/psql/tab-complete.c	2010-08-03 09:01:05.996710353 +0200
***************
*** 644,650 ****
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
--- 644,650 ----
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\sf", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
***************
*** 2501,2506 ****
--- 2501,2509 ----
  
  	else if (strcmp(prev_wd, "\\ef") == 0)
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+ 	
+ 		else if (strncmp(prev_wd, "\\sf", 2) == 0)
+ 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
  
  	else if (strcmp(prev_wd, "\\encoding") == 0)
  		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
#33Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#32)
1 attachment(s)
Re: review: psql: edit function, show function commands patch

Hello

I hope so I found and fixed last issue - the longer functions was
showed directly - without a pager.

Regards

Pavel

2010/8/3 Pavel Stehule <pavel.stehule@gmail.com>:

Show quoted text

Hello

2010/8/3 Pavel Stehule <pavel.stehule@gmail.com>:

attached updated patch

* don't use a default option for navigation in editor - user have to
set this option explicitly
* name for this psql variable is EDITOR_LINENUMBER_SWITCH -
* updated comments, doc and some issues described by Robert

Regards

Pavel Stehule

I found a small bug - the last patch better handle parsing lineno
after function descriptor

Regards

Pavel

2010/8/3 Robert Haas <robertmhaas@gmail.com>:

On Sun, Aug 1, 2010 at 11:48 PM, Robert Haas <robertmhaas@gmail.com> wrote:

b) more robust algorithm for header rows identification

Have not gotten to this one yet.

I notIce that on WIN32 the default editor is notepad.exe and the
default editor navigation option is "/".  Does "notepad.exe /lineno
filename" actually work on Windows?  A quick Google search suggests
that the answer is "no", which seems somewhat unfortunate: it means
we'd be shipping "broken on Win32 out of the box".

http://www.robvanderwoude.com/commandlineswitches.php#Notepad

This is actually my biggest concern about this patch - that it may be
just too much of a hassle to actually make it work for people.  I just
tried setting $EDITOR to MacOS's TextEdit program, and it turns out
that TextEdit doesn't understand +.  I'm afraid that we're going to
end up with a situation where it only works for people using emacs or
vi, and everyone else ends up with a broken install (and, possibly, no
clear idea how to fix it).  Perhaps it would be better to accept \sf
and reject \sf+ and \ef <func> <lineno>.

Assuming we get past that threshold issue, I'm also wondering whether
the "navigation option" terminology is best; e.g. set
PSQL_EDITOR_NAVIGATION_OPTION to configure it.  It doesn't seem
terrible, but it doesn't seem great, either.  Anyone have a better
idea?

The docs are a little rough; they could benefit from some editing by a
fluent English speaker.  If anyone has time to work on this, it would
be much appreciated.

Instead of "line number is unacceptable", I think you should write
"invalid line number".

"dollar" should not be spelled "dolar".  "function" should not be
spelled "finction".

This change looks suspiciously like magic.  If it's actually safe, it
needs a comment explaining why:

-       sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
+       sys = pg_malloc(strlen(editorName) + strlen(fname) + 20 + 1);

Attempting to compile with this patch applied emits a warning on my
machine; whether or not the warning is valid, you need to make it go
away:

command.c: In function ‘HandleSlashCmds’:
command.c:1055: warning: ‘bsep’ may be used uninitialized in this function
command.c:1055: note: ‘bsep’ was declared here

Why does the \sf output have a trailing blank line?  This seems weird,
especially because \ef puts no such trailing blank line in the editor.

Instead of:

+       /* skip useles whitespaces */
+       while (c >= func)
+               if (isblank(*c))
+                       c--;
+               else
+                       break;

...wouldn't it be just as good to write:

while (c >= func && isblank(*c))
   c--;

(and similarly elsewhere)

In extract_separator, if you invert the sense of the first if-test,
then you can avoid having to indent the entire function contents.

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

Attachments:

edit5.difftext/x-patch; charset=US-ASCII; name=edit5.diffDownload
*** ./doc/src/sgml/ref/psql-ref.sgml.orig	2010-08-03 09:00:48.384710383 +0200
--- ./doc/src/sgml/ref/psql-ref.sgml	2010-08-03 10:44:57.312835131 +0200
***************
*** 1339,1345 ****
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1339,1345 ----
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional> <optional> linenumber </optional></literal></term>
  
          <listitem>
          <para>
***************
*** 1369,1380 ****
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1369,1387 ----
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor (The psql's variable <varname>EDITOR_LINENUMBER_SWITCH</varname>
+         have to be filled).
+         </para>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional> <optional> linenumber </optional> </literal></term>
  
          <listitem>
          <para>
***************
*** 1397,1402 ****
--- 1404,1417 ----
           If no function is specified, a blank <command>CREATE FUNCTION</>
           template is presented for editing.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor. It count lines from start of function body, not from
+         start of text (The psql's variable <varname>EDITOR_LINENUMBER_SWITCH</varname>
+         have to be filled).
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 2116,2121 ****
--- 2131,2148 ----
  
  
        <varlistentry>
+         <term><literal>\sf[+] <replaceable class="parameter">function_description</replaceable> <optional> linenumber </optional> </literal></term>
+ 
+         <listitem>
+         <para>
+          This command fetches and shows the definition of the named function,
+          in the form of a <command>CREATE OR REPLACE FUNCTION</> command.
+          If the form <literal>\sf+</literal> is used, then lines are numbered.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
          <term><literal>\t</literal></term>
          <listitem>
          <para>
***************
*** 2123,2128 ****
--- 2150,2161 ----
          footer. This command is equivalent to <literal>\pset
          tuples_only</literal> and is provided for convenience.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor.
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 2459,2464 ****
--- 2492,2511 ----
        </varlistentry>
  
        <varlistentry>
+         <term><varname>EDITOR_LINENUMBER_SWITCH</varname></term>
+         <listitem>
+         <para>
+         Option used for navigation (go to line command) in external
+         editor. When it isn't defined, then you cannot to specify
+         line numbers for <command>\edit</command> and <command>\ef</command>
+         commands. On unix platforms are possible to use a '<option>+</option>'
+         or '<option>--line </option>'. The space after <literal>line</literal> 
+         is required.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
          <term><varname>ENCODING</varname></term>
          <listitem>
          <para>
***************
*** 3065,3070 ****
--- 3112,3118 ----
      </listitem>
     </varlistentry>
  
+ 
     <varlistentry>
      <term><envar>SHELL</envar></term>
  
*** ./src/bin/psql/command.c.orig	2010-08-03 09:00:48.386710435 +0200
--- ./src/bin/psql/command.c	2010-08-03 13:12:36.822834943 +0200
***************
*** 46,54 ****
--- 46,56 ----
  #include "input.h"
  #include "large_obj.h"
  #include "mainloop.h"
+ #include "pqsignal.h"
  #include "print.h"
  #include "psqlscan.h"
  #include "settings.h"
+ #include <signal.h>
  #include "variables.h"
  
  
***************
*** 57,63 ****
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		bool *edited);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
--- 59,65 ----
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		bool *edited, int lineno);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
***************
*** 66,71 ****
--- 68,76 ----
  
  static void printSSLInfo(void);
  
+ static int extract_lineno_from_funcdesc(char *func, backslashResult *status);
+ static char *get_dq_tag(char *row);
+ 
  #ifdef WIN32
  static void checkWin32Codepage(void);
  #endif
***************
*** 513,528 ****
  		else
  		{
  			char	   *fname;
! 
  			fname = psql_scan_slash_option(scan_state,
! 										   OT_NORMAL, NULL, true);
! 			expand_tilde(&fname);
  			if (fname)
! 				canonicalize_path(fname);
! 			if (do_edit(fname, query_buf, NULL))
! 				status = PSQL_CMD_NEWEDIT;
! 			else
! 				status = PSQL_CMD_ERROR;
  			free(fname);
  		}
  	}
--- 518,555 ----
  		else
  		{
  			char	   *fname;
! 			char		*ln;
! 			int  lineno = -1;
! 			
  			fname = psql_scan_slash_option(scan_state,
! 								   OT_NORMAL, NULL, true);
! 			
! 			/* try to get lineno */
  			if (fname)
! 			{
! 				ln = psql_scan_slash_option(scan_state,
! 									   OT_NORMAL, NULL, true);
! 				if (ln)
! 				{
! 					if (atoi(ln) < 1)
! 					{
! 						psql_error("invalid line number\n");
! 						status = PSQL_CMD_ERROR;
! 					}
! 					else
! 						lineno = atoi(ln);
! 				}
! 			}
! 			if (status != PSQL_CMD_ERROR)
! 			{
! 				expand_tilde(&fname);
! 				if (fname)
! 					canonicalize_path(fname);
! 				if (do_edit(fname, query_buf, NULL, lineno))
! 					status = PSQL_CMD_NEWEDIT;
! 				else
! 					status = PSQL_CMD_ERROR;
! 			}
  			free(fname);
  		}
  	}
***************
*** 533,538 ****
--- 560,567 ----
  	 */
  	else if (strcmp(cmd, "ef") == 0)
  	{
+ 		int	lineno;
+ 	
  		if (!query_buf)
  		{
  			psql_error("no query buffer\n");
***************
*** 545,580 ****
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			if (!func)
! 			{
! 				/* set up an empty command to fill in */
! 				printfPQExpBuffer(query_buf,
! 								  "CREATE FUNCTION ( )\n"
! 								  " RETURNS \n"
! 								  " LANGUAGE \n"
! 								  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 								  "AS $function$\n"
! 								  "\n$function$\n");
! 			}
! 			else if (!lookup_function_oid(pset.db, func, &foid))
  			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
! 			}
! 			else if (!get_create_function_cmd(pset.db, foid, query_buf))
! 			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
  			}
- 			if (func)
- 				free(func);
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, &edited))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
--- 574,614 ----
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			lineno = extract_lineno_from_funcdesc(func, &status);
! 			
! 			if (status != PSQL_CMD_ERROR)
  			{
! 				if (!func)
! 				{
! 					/* set up an empty command to fill in */
! 					printfPQExpBuffer(query_buf,
! 									  "CREATE FUNCTION ( )\n"
! 									  " RETURNS \n"
! 									  " LANGUAGE \n"
! 									  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 									  "AS $function$\n"
! 									  "\n$function$\n");
! 				}
! 				else if (!lookup_function_oid(pset.db, func, &foid))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
! 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
! 				if (func)
! 					free(func);
  			}
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, &edited, lineno))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
***************
*** 969,974 ****
--- 1003,1151 ----
  		free(fname);
  	}
  
+ 	/*
+ 	 * \sf -- show the named function
+ 	 */
+ 	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+ 	{
+ 		int	first_visible_row = -1;
+ 		bool	with_lno;
+ 		
+ 		with_lno = (strcmp(cmd, "sf+") == 0);
+ 		
+ 		if (!query_buf)
+ 		{
+ 			psql_error("no query buffer\n");
+ 			status = PSQL_CMD_ERROR;
+ 		}
+ 		else
+ 		{
+ 			char	   *func;
+ 			Oid			foid = InvalidOid;
+ 
+ 			func = psql_scan_slash_option(scan_state,
+ 										  OT_WHOLE_LINE, NULL, true);
+ 			
+ 			first_visible_row = extract_lineno_from_funcdesc(func, &status);
+ 			
+ 			if (status != PSQL_CMD_ERROR)
+ 			{
+ 				if (!func)
+ 				{
+ 					/* show error for empty command */
+ 					psql_error("missing a function name\n");
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!lookup_function_oid(pset.db, func, &foid))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 			}
+ 			if (func)
+ 				free(func);
+ 		}
+ 
+ 		if (status != PSQL_CMD_ERROR)
+ 		{
+ 			int	lineno = 0;
+ 			char	*row;
+ 			char	   *dqtag = NULL;
+ 			bool	is_header = true;
+ 			bool	is_body = false;
+ 			bool	is_footer = false;
+ 			char	  *end_of_line;
+ 			int	lines;
+ 			FILE	   *output;
+ 			
+ 			
+ 			/* get number of lines of functiondef - used for possible using a pager */
+ 			row = query_buf->data;
+ 			lines = 0;
+ 			while (*row)
+ 			{
+ 				lines++;
+ 				end_of_line = strchr(row, '\n');
+ 				if (end_of_line)
+ 					row = end_of_line + 1;
+ 				else
+ 					break;
+ 			}
+ 			
+ 			output = PageOutput(lines, pset.popt.topt.pager);
+ 
+ 			row = query_buf->data;
+ 			while (*row)
+ 			{
+ 				/* find next end of line */
+ 				end_of_line = strchr(row, '\n');
+ 				if (end_of_line)
+ 					*end_of_line = '\0';
+ 				
+ 				if (is_header)
+ 				{
+ 					/* detect end of header */
+ 					dqtag = get_dq_tag(row);
+ 					if (dqtag)
+ 					{
+ 						is_header = false;
+ 						is_body = true;
+ 						lineno = 1;
+ 					}
+ 				}
+ 				else if (is_body)
+ 				{
+ 					lineno++;
+ 					if (strcmp(row, dqtag) == 0)
+ 					{
+ 						is_body = false;
+ 						is_footer = true;
+ 					}
+ 				}
+ 				
+ 				/* can we show rows? */
+ 				if (first_visible_row < 0 || (first_visible_row <= lineno))
+ 				{
+ 					if (with_lno)
+ 					{
+ 						if (is_header || is_footer)
+ 							fprintf(output, "**** %s", row);
+ 						else 
+ 							fprintf(output, "%4d %s", lineno, row);
+ 					}
+ 					else
+ 						fprintf(output, "%s", row);
+ 					
+ 					/* return back replaced "\n" */
+ 					if (end_of_line)
+ 						fprintf(output, "\n");
+ 				}
+ 				
+ 				if (end_of_line)
+ 					row = end_of_line + 1;
+ 				else
+ 					break;
+ 			}
+ 			
+ 			/* function pg_get_functiondef uses dollar quoted strings always */
+ 			psql_assert(dqtag);
+ 			free(dqtag);
+ 			
+ 			if (output != stdout)
+ 			{
+ 				pclose(output);
+ #ifndef WIN32
+ 				pqsignal(SIGPIPE, SIG_DFL);
+ #endif
+ 			}
+ 		}
+ 	}
+ 
  	/* \set -- generalized set variable/option command */
  	else if (strcmp(cmd, "set") == 0)
  	{
***************
*** 1550,1558 ****
   */
  
  static bool
! editFile(const char *fname)
  {
  	const char *editorName;
  	char	   *sys;
  	int			result;
  
--- 1727,1736 ----
   */
  
  static bool
! editFile(const char *fname, int lineno)
  {
  	const char *editorName;
+ 	const char *editor_lineno_switch;
  	char	   *sys;
  	int			result;
  
***************
*** 1566,1572 ****
  		editorName = getenv("VISUAL");
  	if (!editorName)
  		editorName = DEFAULT_EDITOR;
! 
  	/*
  	 * On Unix the EDITOR value should *not* be quoted, since it might include
  	 * switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
--- 1744,1761 ----
  		editorName = getenv("VISUAL");
  	if (!editorName)
  		editorName = DEFAULT_EDITOR;
! 	
! 	/* Find a EDITOR_LINENUMBER_SWITCH when lineno is used */
! 	if (lineno >= 1)
! 	{
! 		editor_lineno_switch = GetVariable(pset.vars, "EDITOR_LINENUMBER_SWITCH");
! 		if (!editor_lineno_switch)
! 		{
! 			psql_error("cannot use a editor navigation without setting EDITOR_LINENUMBER_SWITCH variable\n");
! 			return false;
! 		}
! 	}
! 	
  	/*
  	 * On Unix the EDITOR value should *not* be quoted, since it might include
  	 * switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
***************
*** 1574,1584 ****
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
  #ifndef WIN32
! 	sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
--- 1763,1795 ----
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	if (lineno >= 1)
! 		/*
! 		 * allocate sufficient memory for command line content and
! 		 * increase it about space for editor_lineno_switch and lineno 
! 		 * (signed int ~ 10 digits) and one space more.
! 		 */
! 		sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 
! 				    + strlen(editor_lineno_switch) + 10 + 1 + 1);
! 	
! 	else
! 		/* allocate sufficient memory for command line content */
! 		sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
! 	
  #ifndef WIN32
! 	if (lineno > 0)
! 		sprintf(sys, "exec %s %s%d '%s'", editorName, editor_lineno_switch, lineno, fname);
! 	else
! 		sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	if (lineno > 0)
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" %s%d \"%s\"" SYSTEMQUOTE, 
! 									    editorName, 
! 									    editor_lineno_switch,
! 									    lineno, 
! 									    fname);
! 	else
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
***************
*** 1593,1599 ****
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
--- 1804,1810 ----
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited, int lineno)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
***************
*** 1685,1691 ****
  
  	/* call editor */
  	if (!error)
! 		error = !editFile(fname);
  
  	if (!error && stat(fname, &after) != 0)
  	{
--- 1896,1934 ----
  
  	/* call editor */
  	if (!error)
! 	{
! 		/* skip header lines */
! 		if (lineno != -1)
! 		{
! 			char   *lines = query_buf->data;
! 			char	*dqtag;
! 			
! 			/* we have to detect number of header lines */
! 			while (*lines)
! 			{
! 				char    *end_of_line = strchr(lines, '\n');
! 			
! 				if (end_of_line)
! 					*end_of_line = '\0';
! 			
! 				dqtag = get_dq_tag(lines);
! 				if (dqtag)
! 				{
! 					free(dqtag);
! 					break;
! 				}
! 				else
! 					lineno++;
! 				
! 				if (end_of_line)
! 					lines = end_of_line + 1;
! 				else
! 					break;
! 			}
! 		}
! 	
! 		error = !editFile(fname, lineno);
! 	}
  
  	if (!error && stat(fname, &after) != 0)
  	{
***************
*** 2213,2218 ****
--- 2456,2462 ----
  	return result;
  }
  
+ 
  /*
   * Report just the primary error; this is to avoid cluttering the output
   * with, for instance, a redisplay of the internally generated query
***************
*** 2241,2243 ****
--- 2485,2582 ----
  
  	destroyPQExpBuffer(msg);
  }
+ 
+ 
+ /*
+  * Returns lineno used in \sf and \ef commands. 
+  *
+  * These commands can be completed with number used as line
+  * number for navigation in showed lines / open file. The most 
+  * simple method for parsing is reading isolated digits from 
+  * right - \ef foo nn, \ef foo(..)nn. Returns -1 when
+  * lineno isn't used in function's descriptor.
+ */
+ static int
+ extract_lineno_from_funcdesc(char *func, backslashResult *status)
+ {
+ 	char *endfunc;
+ 	char *c;
+ 	int lineno = -1;
+ 	
+ 	if (!func)
+ 		return lineno;
+ 	
+ 	endfunc = func + strlen(func) - 1;
+ 	c = endfunc;
+ 	
+ 	/* skip useles whitespaces */
+ 	while (c >= func && isblank(*c))
+ 		c--;
+ 	
+ 	/* search the most left digit of continuously number */
+ 	while (c >= func && isdigit(*c))
+ 		c--;
+ 	
+ 	/* 
+ 	 * when left char isn't blank and isn't a right parenthesis
+ 	 * then command hasn't a lineno.
+ 	 */
+ 	if (c < endfunc && c > func)
+ 	{
+ 		/* 
+ 		 * digits have to be a separated from identifier by right 
+ 		 * parenthesis or by space, and there have to be entered
+ 		 * minimal one digit.
+ 		 */
+ 		if (isdigit(c[1]) && ( isblank(*c) || *c == ')' ))
+ 		{
+ 			c++;
+ 			
+ 			if (atoi(c) < 1)
+ 			{
+ 				psql_error("invalid line number\n");
+ 				*status = PSQL_CMD_ERROR;
+ 			}
+ 			else
+ 			{
+ 				lineno = atoi(c);
+ 				/* remove lineno from function descriptor */
+ 				*c = '\0';
+ 			}
+ 		}
+ 	}
+ 	
+ 	return lineno;
+ }
+ 
+ /*
+  * Returns tag of dollar quoted string used as function body, It parses
+  * only result of pg_get_functiondef function, so there are not possibility
+  * use just "'" char. When row doesn't contain a AS part of CREATE FUNCTION
+  * command, then it returns NULL.
+  */
+ static char *
+ get_dq_tag(char *row)
+ {
+ 	char *starttag;
+ 	char *endtag;
+ 	int	len;
+ 	char	*result;
+ 
+ 	/* leave when line doesn't contain a body separator */
+ 	if (strncmp(row, "AS $function", 12) != 0)
+ 		return NULL;
+ 		
+ 	/* detect body's tag */
+ 	starttag = row + strlen("AS ");
+ 	endtag = strchr(row + strlen("AS $function"), '$');
+ 	
+ 	psql_assert(endtag);
+ 	
+ 	len = endtag - starttag + 1;
+ 	result = pg_malloc(len + 1);
+ 	memcpy(result, starttag, len);
+ 	result[len] = '\0';
+ 		
+ 	return result;
+ }
*** ./src/bin/psql/help.c.orig	2010-08-03 09:00:48.387710077 +0200
--- ./src/bin/psql/help.c	2010-08-03 13:16:22.264713224 +0200
***************
*** 162,168 ****
  {
  	FILE	   *output;
  
! 	output = PageOutput(87, pager);
  
  	/* if you add/remove a line here, change the row count above */
  
--- 162,168 ----
  {
  	FILE	   *output;
  
! 	output = PageOutput(90, pager);
  
  	/* if you add/remove a line here, change the row count above */
  
***************
*** 174,186 ****
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE]              edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME]         edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
--- 174,187 ----
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE] [lno]        edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME] [lno]   edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
+ 	fprintf(output, _("  \\sf[+] FUNCNAME [lno]  show finction definition\n"));
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
*** ./src/bin/psql/tab-complete.c.orig	2010-08-03 09:00:48.390710399 +0200
--- ./src/bin/psql/tab-complete.c	2010-08-03 09:01:05.996710353 +0200
***************
*** 644,650 ****
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
--- 644,650 ----
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\sf", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
***************
*** 2501,2506 ****
--- 2501,2509 ----
  
  	else if (strcmp(prev_wd, "\\ef") == 0)
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+ 	
+ 		else if (strncmp(prev_wd, "\\sf", 2) == 0)
+ 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
  
  	else if (strcmp(prev_wd, "\\encoding") == 0)
  		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
#34Robert Haas
robertmhaas@gmail.com
In reply to: Pavel Stehule (#33)
Re: review: psql: edit function, show function commands patch

On Tue, Aug 3, 2010 at 7:20 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I hope so I found and fixed last issue - the longer functions was
showed directly - without a pager.

As a matter of style, I suggest leaving bool *edited as the last
argument to do_edit() and inserting int lineno as the second-to-last
argument.

! int lineno = -1;
[...]
! if (atoi(ln) < 1)
! {
! psql_error("invalid line number\n");
! status = PSQL_CMD_ERROR;
! }
! else
! lineno = atoi(ln);

Why call atoi(n) twice? Can't you just write:

lineno = atoi(n);
if (lineno < 1) {
...error stuff...
}

I suggested writing psql_assert(datag != NULL) rather than just
psql_assert(datag).

Instead of: cannot use a editor navigation without setting
EDITOR_LINENUMBER_SWITCH variable
I suggest: cannot edit a specific line because the
EDITOR_LINENUMBER_SWITCH variable is not set

I don't find the name get_dg_tag() to be very mnemonic. How about
get_function_dollarquote_tag()?

In help.c, it looks like you've only added one line but incremented
the pager count by three.

In this bit:

! dqtag = get_dq_tag(lines);
! if (dqtag)
! {
! free(dqtag);
! break;
! }
! else
! lineno++;

The "else" is unnecessary. And just after that:

! if (end_of_line)
! lines = end_of_line + 1;
! else
! break;

You can write this more cleanly by saying if (!end_of_line) break;
lines = end_of_line + 1.

The following diff hunk (2213,2218) just adds a blank line and is
therefore unnecessary. There's a similar hunk in the docs portion of
the patch.

In this part:

func = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, true);
! lineno = extract_lineno_from_funcdesc(func, &status);
!
! if (status != PSQL_CMD_ERROR)
{
! if (!func)
! {
! /* set up an empty command to fill in */
! printfPQExpBuffer(query_buf,
! "CREATE FUNCTION ( )\n"
! " RETURNS \n"
! " LANGUAGE \n"
! " -- common options: IMMUTABLE STABLE STRICT SECURITY
DEFINER\n"
! "AS $function$\n"
! "\n$function$\n");
! }
! else if (!lookup_function_oid(pset.db, func, &foid))
! {
! /* error already reported */
! status = PSQL_CMD_ERROR;
! }
! else if (!get_create_function_cmd(pset.db, foid, query_buf))
! {
! /* error already reported */
! status = PSQL_CMD_ERROR;
! }
! if (func)
! free(func);
}

Why is it correct for if (func) free(func) to be inside the if (status
!= PSQL_CMD_ERROR) block? It looks to me like func gets initialized
first, before status is set.

It seems unnecessary for extract_lineno_from_funcdesc() to return the
line number and have a separate out parameter to return a
backslashResult. Can't you just return -1 to indicate an error? (You
might need to move the if (!func) test at the top to the caller, but
that seems OK.)

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

#35David Fetter
david@fetter.org
In reply to: Robert Haas (#28)
Re: review: psql: edit function, show function commands patch

On Mon, Aug 02, 2010 at 11:34:02PM -0400, Robert Haas wrote:

On Mon, Aug 2, 2010 at 11:17 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Mon, Aug 2, 2010 at 10:49 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Well, it'd still work fine for \e foo. It'll just blow up for \e
foo 3. My concern isn't really that things that which work now will
break so much as that this new feature will fail to work for a large
percentage of the people who try to use it, including virtually
everyone who tries to use it on Win32.

That concern is a show-stopper.

While this is superficially a Nice Thing to Have and I would
certainly support it if +linenumber were relatively universal,
it's really a pretty minor convenience when you come right down
to it, and I am not at all convinced it is worth the hassle of
trying to divine what piece of syntax will equip the user's
choice of editor with the necessary amount of clue.

The other approach we could take is that this whole thing is
disabled by default, and you have to set a psql variable
EDITOR_LINENUMBER_SWITCH to turn it on. �If you haven't read the
documentation enough to find out that variable exists, well, no
harm no foul.

That might be reasonable. Right now the default behavior is to do
+line on Linux and /line on Windows. But maybe a more sensible
default would be to fail with an error message that says "you have
to set thus-and-so variable if you want to use this feature". That
seems better than sometimes working and sometimes failing depending
on what editor the user has configured.

A side question is whether this should be an environment variable or
a psql variable.

I'd say "yes." As with $EDITOR/PSQL_EDITOR, there should be something
that looks for an overriding psql variable, drops through to look for
an environment variable, and then to a sane default, for some
reasonable value of "sane." Perhaps this default could depend on OS
(Windows vs. Everything Else) to start with.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#36Robert Haas
robertmhaas@gmail.com
In reply to: David Fetter (#35)
Re: review: psql: edit function, show function commands patch

On Wed, Aug 4, 2010 at 10:10 AM, David Fetter <david@fetter.org> wrote:

On Mon, Aug 02, 2010 at 11:34:02PM -0400, Robert Haas wrote:

On Mon, Aug 2, 2010 at 11:17 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Mon, Aug 2, 2010 at 10:49 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Well, it'd still work fine for \e foo.  It'll just blow up for \e
foo 3.  My concern isn't really that things that which work now will
break so much as that this new feature will fail to work for a large
percentage of the people who try to use it, including virtually
everyone who tries to use it on Win32.

That concern is a show-stopper.

See downthread; I believe we have a resolution to this issue.

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

#37Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#34)
1 attachment(s)
Re: review: psql: edit function, show function commands patch

Hello

updated patch attached

2010/8/4 Robert Haas <robertmhaas@gmail.com>:

On Tue, Aug 3, 2010 at 7:20 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I hope so I found and fixed last issue - the longer functions was
showed directly - without a pager.

As a matter of style, I suggest leaving bool *edited as the last
argument to do_edit() and inserting int lineno as the second-to-last
argument.

!                       int  lineno = -1;
[...]
!                                       if (atoi(ln) < 1)
!                                       {
!                                               psql_error("invalid line number\n");
!                                               status = PSQL_CMD_ERROR;
!                                       }
!                                       else
!                                               lineno = atoi(ln);

Why call atoi(n) twice?  Can't you just write:

lineno = atoi(n);
if (lineno < 1) {
  ...error stuff...
}

fixed

I suggested writing psql_assert(datag != NULL) rather than just
psql_assert(datag).

Instead of: cannot use a editor navigation without setting
EDITOR_LINENUMBER_SWITCH variable
I suggest: cannot edit a specific line because the
EDITOR_LINENUMBER_SWITCH variable is not set

fixed

I don't find the name get_dg_tag() to be very mnemonic.  How about
get_function_dollarquote_tag()?

I used get_functiondef_dollarquote_tag

In help.c, it looks like you've only added one line but incremented
the pager count by three.

The original value for pager is probably wrong (not actual). I
rechecked real row numbers in external editor.

In this bit:

!                               dqtag = get_dq_tag(lines);
!                               if (dqtag)
!                               {
!                                       free(dqtag);
!                                       break;
!                               }
!                               else
!                                       lineno++;

The "else" is unnecessary.  And just after that:

!                               if (end_of_line)
!                                       lines = end_of_line + 1;
!                               else
!                                       break;

You can write this more cleanly by saying if (!end_of_line) break;
lines = end_of_line + 1.

fixed

The following diff hunk (2213,2218) just adds a blank line and is
therefore unnecessary.  There's a similar hunk in the docs portion of
the patch.

fixed

In this part:

                       func = psql_scan_slash_option(scan_state,
                                                                                 OT_WHOLE_LINE, NULL, true);
!                       lineno = extract_lineno_from_funcdesc(func, &status);
!
!                       if (status != PSQL_CMD_ERROR)
                       {
!                               if (!func)
!                               {
!                                       /* set up an empty command to fill in */
!                                       printfPQExpBuffer(query_buf,
!                                                                         "CREATE FUNCTION ( )\n"
!                                                                         " RETURNS \n"
!                                                                         " LANGUAGE \n"
!                                                                         " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY
DEFINER\n"
!                                                                         "AS $function$\n"
!                                                                         "\n$function$\n");
!                               }
!                               else if (!lookup_function_oid(pset.db, func, &foid))
!                               {
!                                       /* error already reported */
!                                       status = PSQL_CMD_ERROR;
!                               }
!                               else if (!get_create_function_cmd(pset.db, foid, query_buf))
!                               {
!                                       /* error already reported */
!                                       status = PSQL_CMD_ERROR;
!                               }
!                               if (func)
!                                       free(func);
                       }

Why is it correct for if (func) free(func) to be inside the if (status
!= PSQL_CMD_ERROR) block?  It looks to me like func gets initialized
first, before status is set.

fixed

It seems unnecessary for extract_lineno_from_funcdesc() to return the
line number and have a separate out parameter to return a
backslashResult.  Can't you just return -1 to indicate an error?  (You
might need to move the if (!func) test at the top to the caller, but
that seems OK.)

I can't to do it. There are three states
1) lineno is wrong
2) lineno is undefined
3) lineno is defined

@1 is solved via backslashResult, @2 lineno is negative, @3 lineno is positive

Regards

Pavel

Show quoted text

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

Attachments:

edit6.difftext/x-patch; charset=US-ASCII; name=edit6.diffDownload
*** ./doc/src/sgml/ref/psql-ref.sgml.orig	2010-08-03 09:00:48.384710383 +0200
--- ./doc/src/sgml/ref/psql-ref.sgml	2010-08-03 10:44:57.312835131 +0200
***************
*** 1339,1345 ****
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1339,1345 ----
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional> <optional> linenumber </optional></literal></term>
  
          <listitem>
          <para>
***************
*** 1369,1380 ****
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1369,1387 ----
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor (The psql's variable <varname>EDITOR_LINENUMBER_SWITCH</varname>
+         have to be filled).
+         </para>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional> <optional> linenumber </optional> </literal></term>
  
          <listitem>
          <para>
***************
*** 1397,1402 ****
--- 1404,1417 ----
           If no function is specified, a blank <command>CREATE FUNCTION</>
           template is presented for editing.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor. It count lines from start of function body, not from
+         start of text (The psql's variable <varname>EDITOR_LINENUMBER_SWITCH</varname>
+         have to be filled).
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 2116,2121 ****
--- 2131,2148 ----
  
  
        <varlistentry>
+         <term><literal>\sf[+] <replaceable class="parameter">function_description</replaceable> <optional> linenumber </optional> </literal></term>
+ 
+         <listitem>
+         <para>
+          This command fetches and shows the definition of the named function,
+          in the form of a <command>CREATE OR REPLACE FUNCTION</> command.
+          If the form <literal>\sf+</literal> is used, then lines are numbered.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
          <term><literal>\t</literal></term>
          <listitem>
          <para>
***************
*** 2123,2128 ****
--- 2150,2161 ----
          footer. This command is equivalent to <literal>\pset
          tuples_only</literal> and is provided for convenience.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor.
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 2459,2464 ****
--- 2492,2511 ----
        </varlistentry>
  
        <varlistentry>
+         <term><varname>EDITOR_LINENUMBER_SWITCH</varname></term>
+         <listitem>
+         <para>
+         Option used for navigation (go to line command) in external
+         editor. When it isn't defined, then you cannot to specify
+         line numbers for <command>\edit</command> and <command>\ef</command>
+         commands. On unix platforms are possible to use a '<option>+</option>'
+         or '<option>--line </option>'. The space after <literal>line</literal> 
+         is required.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
          <term><varname>ENCODING</varname></term>
          <listitem>
          <para>
***************
*** 3065,3070 ****
--- 3112,3118 ----
      </listitem>
     </varlistentry>
  
+ 
     <varlistentry>
      <term><envar>SHELL</envar></term>
  
*** ./src/bin/psql/command.c.orig	2010-08-03 09:00:48.386710435 +0200
--- ./src/bin/psql/command.c	2010-08-04 16:32:38.388093597 +0200
***************
*** 46,54 ****
--- 46,56 ----
  #include "input.h"
  #include "large_obj.h"
  #include "mainloop.h"
+ #include "pqsignal.h"
  #include "print.h"
  #include "psqlscan.h"
  #include "settings.h"
+ #include <signal.h>
  #include "variables.h"
  
  
***************
*** 57,63 ****
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		bool *edited);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
--- 59,65 ----
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 			 int lineno, bool *edited);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
***************
*** 66,71 ****
--- 68,76 ----
  
  static void printSSLInfo(void);
  
+ static int extract_lineno_from_funcdesc(char *func, backslashResult *status);
+ static char *get_functiondef_dollarquote_tag(char *row);
+ 
  #ifdef WIN32
  static void checkWin32Codepage(void);
  #endif
***************
*** 513,528 ****
  		else
  		{
  			char	   *fname;
! 
  			fname = psql_scan_slash_option(scan_state,
! 										   OT_NORMAL, NULL, true);
! 			expand_tilde(&fname);
  			if (fname)
! 				canonicalize_path(fname);
! 			if (do_edit(fname, query_buf, NULL))
! 				status = PSQL_CMD_NEWEDIT;
! 			else
! 				status = PSQL_CMD_ERROR;
  			free(fname);
  		}
  	}
--- 518,554 ----
  		else
  		{
  			char	   *fname;
! 			char		*ln;
! 			int  lineno = -1;
! 			
  			fname = psql_scan_slash_option(scan_state,
! 								   OT_NORMAL, NULL, true);
! 			
! 			/* try to get lineno */
  			if (fname)
! 			{
! 				ln = psql_scan_slash_option(scan_state,
! 									   OT_NORMAL, NULL, true);
! 				if (ln)
! 				{
! 					lineno = atoi(ln);
! 					if (lineno < 1)
! 					{
! 						psql_error("invalid line number\n");
! 						status = PSQL_CMD_ERROR;
! 					}
! 				}
! 			}
! 			if (status != PSQL_CMD_ERROR)
! 			{
! 				expand_tilde(&fname);
! 				if (fname)
! 					canonicalize_path(fname);
! 				if (do_edit(fname, query_buf, lineno, NULL))
! 					status = PSQL_CMD_NEWEDIT;
! 				else
! 					status = PSQL_CMD_ERROR;
! 			}
  			free(fname);
  		}
  	}
***************
*** 533,538 ****
--- 559,566 ----
  	 */
  	else if (strcmp(cmd, "ef") == 0)
  	{
+ 		int	lineno;
+ 	
  		if (!query_buf)
  		{
  			psql_error("no query buffer\n");
***************
*** 545,580 ****
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			if (!func)
! 			{
! 				/* set up an empty command to fill in */
! 				printfPQExpBuffer(query_buf,
! 								  "CREATE FUNCTION ( )\n"
! 								  " RETURNS \n"
! 								  " LANGUAGE \n"
! 								  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 								  "AS $function$\n"
! 								  "\n$function$\n");
! 			}
! 			else if (!lookup_function_oid(pset.db, func, &foid))
! 			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
! 			}
! 			else if (!get_create_function_cmd(pset.db, foid, query_buf))
  			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
  			}
  			if (func)
  				free(func);
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, &edited))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
--- 573,615 ----
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			lineno = extract_lineno_from_funcdesc(func, &status);
! 			
! 			if (status != PSQL_CMD_ERROR)
  			{
! 				if (!func)
! 				{
! 					/* set up an empty command to fill in */
! 					printfPQExpBuffer(query_buf,
! 									  "CREATE FUNCTION ( )\n"
! 									  " RETURNS \n"
! 									  " LANGUAGE \n"
! 									  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 									  "AS $function$\n"
! 									  "\n$function$\n");
! 				}
! 				else if (!lookup_function_oid(pset.db, func, &foid))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
! 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
  			}
+ 			
  			if (func)
  				free(func);
+ 			
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, lineno, &edited))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
***************
*** 969,974 ****
--- 1004,1152 ----
  		free(fname);
  	}
  
+ 	/*
+ 	 * \sf -- show the named function
+ 	 */
+ 	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+ 	{
+ 		int	first_visible_row = -1;
+ 		bool	with_lno;
+ 		
+ 		with_lno = (strcmp(cmd, "sf+") == 0);
+ 		
+ 		if (!query_buf)
+ 		{
+ 			psql_error("no query buffer\n");
+ 			status = PSQL_CMD_ERROR;
+ 		}
+ 		else
+ 		{
+ 			char	   *func;
+ 			Oid			foid = InvalidOid;
+ 
+ 			func = psql_scan_slash_option(scan_state,
+ 										  OT_WHOLE_LINE, NULL, true);
+ 			
+ 			first_visible_row = extract_lineno_from_funcdesc(func, &status);
+ 			
+ 			if (status != PSQL_CMD_ERROR)
+ 			{
+ 				if (!func)
+ 				{
+ 					/* show error for empty command */
+ 					psql_error("missing a function name\n");
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!lookup_function_oid(pset.db, func, &foid))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 			}
+ 			if (func)
+ 				free(func);
+ 		}
+ 
+ 		if (status != PSQL_CMD_ERROR)
+ 		{
+ 			int	lineno = 0;
+ 			char	*row;
+ 			char	   *dqtag = NULL;
+ 			bool	is_header = true;
+ 			bool	is_body = false;
+ 			bool	is_footer = false;
+ 			char	  *end_of_line;
+ 			int	lines;
+ 			FILE	   *output;
+ 			
+ 			
+ 			/* get number of lines of functiondef - used for possible using a pager */
+ 			row = query_buf->data;
+ 			lines = 0;
+ 			while (*row)
+ 			{
+ 				lines++;
+ 				end_of_line = strchr(row, '\n');
+ 				if (end_of_line)
+ 					row = end_of_line + 1;
+ 				else
+ 					break;
+ 			}
+ 			
+ 			output = PageOutput(lines, pset.popt.topt.pager);
+ 
+ 			row = query_buf->data;
+ 			while (*row)
+ 			{
+ 				/* find next end of line */
+ 				end_of_line = strchr(row, '\n');
+ 				if (end_of_line)
+ 					*end_of_line = '\0';
+ 				
+ 				if (is_header)
+ 				{
+ 					/* detect end of header */
+ 					dqtag = get_functiondef_dollarquote_tag(row);
+ 					if (dqtag)
+ 					{
+ 						is_header = false;
+ 						is_body = true;
+ 						lineno = 1;
+ 					}
+ 				}
+ 				else if (is_body)
+ 				{
+ 					lineno++;
+ 					if (strcmp(row, dqtag) == 0)
+ 					{
+ 						is_body = false;
+ 						is_footer = true;
+ 					}
+ 				}
+ 				
+ 				/* can we show rows? */
+ 				if (first_visible_row < 0 || (first_visible_row <= lineno))
+ 				{
+ 					if (with_lno)
+ 					{
+ 						if (is_header || is_footer)
+ 							fprintf(output, "**** %s", row);
+ 						else 
+ 							fprintf(output, "%4d %s", lineno, row);
+ 					}
+ 					else
+ 						fprintf(output, "%s", row);
+ 					
+ 					/* return back replaced "\n" */
+ 					if (end_of_line)
+ 						fprintf(output, "\n");
+ 				}
+ 				
+ 				if (end_of_line)
+ 					row = end_of_line + 1;
+ 				else
+ 					break;
+ 			}
+ 			
+ 			/* function pg_get_functiondef uses dollar quoted strings always */
+ 			psql_assert(dqtag != NULL);
+ 			free(dqtag);
+ 			
+ 			if (output != stdout)
+ 			{
+ 				pclose(output);
+ #ifndef WIN32
+ 				pqsignal(SIGPIPE, SIG_DFL);
+ #endif
+ 			}
+ 		}
+ 	}
+ 
  	/* \set -- generalized set variable/option command */
  	else if (strcmp(cmd, "set") == 0)
  	{
***************
*** 1550,1558 ****
   */
  
  static bool
! editFile(const char *fname)
  {
  	const char *editorName;
  	char	   *sys;
  	int			result;
  
--- 1728,1737 ----
   */
  
  static bool
! editFile(const char *fname, int lineno)
  {
  	const char *editorName;
+ 	const char *editor_lineno_switch;
  	char	   *sys;
  	int			result;
  
***************
*** 1566,1572 ****
  		editorName = getenv("VISUAL");
  	if (!editorName)
  		editorName = DEFAULT_EDITOR;
! 
  	/*
  	 * On Unix the EDITOR value should *not* be quoted, since it might include
  	 * switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
--- 1745,1762 ----
  		editorName = getenv("VISUAL");
  	if (!editorName)
  		editorName = DEFAULT_EDITOR;
! 	
! 	/* Find a EDITOR_LINENUMBER_SWITCH when lineno is used */
! 	if (lineno >= 1)
! 	{
! 		editor_lineno_switch = GetVariable(pset.vars, "EDITOR_LINENUMBER_SWITCH");
! 		if (!editor_lineno_switch)
! 		{
! 			psql_error("EDITOR_LINENUMBER_SWITCH variable is not set\n");
! 			return false;
! 		}
! 	}
! 	
  	/*
  	 * On Unix the EDITOR value should *not* be quoted, since it might include
  	 * switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
***************
*** 1574,1584 ****
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
  #ifndef WIN32
! 	sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
--- 1764,1796 ----
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	if (lineno >= 1)
! 		/*
! 		 * allocate sufficient memory for command line content and
! 		 * increase it about space for editor_lineno_switch and lineno 
! 		 * (signed int ~ 10 digits) and one space more.
! 		 */
! 		sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 
! 				    + strlen(editor_lineno_switch) + 10 + 1 + 1);
! 	
! 	else
! 		/* allocate sufficient memory for command line content */
! 		sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
! 	
  #ifndef WIN32
! 	if (lineno > 0)
! 		sprintf(sys, "exec %s %s%d '%s'", editorName, editor_lineno_switch, lineno, fname);
! 	else
! 		sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	if (lineno > 0)
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" %s%d \"%s\"" SYSTEMQUOTE, 
! 									    editorName, 
! 									    editor_lineno_switch,
! 									    lineno, 
! 									    fname);
! 	else
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
***************
*** 1593,1599 ****
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
--- 1805,1811 ----
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, int lineno, bool *edited)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
***************
*** 1685,1691 ****
  
  	/* call editor */
  	if (!error)
! 		error = !editFile(fname);
  
  	if (!error && stat(fname, &after) != 0)
  	{
--- 1897,1936 ----
  
  	/* call editor */
  	if (!error)
! 	{
! 		/* skip header lines */
! 		if (lineno != -1)
! 		{
! 			char   *lines = query_buf->data;
! 			char	*dqtag;
! 			
! 			/* we have to detect number of header lines */
! 			while (*lines)
! 			{
! 				char    *end_of_line = strchr(lines, '\n');
! 			
! 				if (end_of_line)
! 					*end_of_line = '\0';
! 			
! 				dqtag = get_functiondef_dollarquote_tag(lines);
! 				if (dqtag)
! 				{
! 					free(dqtag);
! 					break;
! 				}
! 				
! 				lineno++;
! 				
! 				/* leave when there are not next row */
! 				if (!end_of_line)
! 					break;
! 					
! 				lines = end_of_line + 1;
! 			}
! 		}
! 	
! 		error = !editFile(fname, lineno);
! 	}
  
  	if (!error && stat(fname, &after) != 0)
  	{
***************
*** 2241,2243 ****
--- 2486,2583 ----
  
  	destroyPQExpBuffer(msg);
  }
+ 
+ 
+ /*
+  * Returns lineno used in \sf and \ef commands. 
+  *
+  * These commands can be completed with number used as line
+  * number for navigation in showed lines / open file. The most 
+  * simple method for parsing is reading isolated digits from 
+  * right - \ef foo nn, \ef foo(..)nn. Returns -1 when
+  * lineno isn't used in function's descriptor.
+ */
+ static int
+ extract_lineno_from_funcdesc(char *func, backslashResult *status)
+ {
+ 	char *endfunc;
+ 	char *c;
+ 	int lineno = -1;
+ 	
+ 	if (!func)
+ 		return lineno;
+ 	
+ 	endfunc = func + strlen(func) - 1;
+ 	c = endfunc;
+ 	
+ 	/* skip useles whitespaces */
+ 	while (c >= func && isblank(*c))
+ 		c--;
+ 	
+ 	/* search the most left digit of continuously number */
+ 	while (c >= func && isdigit(*c))
+ 		c--;
+ 	
+ 	/* 
+ 	 * when left char isn't blank and isn't a right parenthesis
+ 	 * then command hasn't a lineno.
+ 	 */
+ 	if (c < endfunc && c > func)
+ 	{
+ 		/* 
+ 		 * digits have to be a separated from identifier by right 
+ 		 * parenthesis or by space, and there have to be entered
+ 		 * minimal one digit.
+ 		 */
+ 		if (isdigit(c[1]) && ( isblank(*c) || *c == ')' ))
+ 		{
+ 			c++;
+ 			
+ 			lineno = atoi(c);
+ 			if (lineno < 1)
+ 			{
+ 				psql_error("invalid line number\n");
+ 				*status = PSQL_CMD_ERROR;
+ 			}
+ 			else
+ 			{
+ 				/* remove lineno from function descriptor */
+ 				*c = '\0';
+ 			}
+ 		}
+ 	}
+ 	
+ 	return lineno;
+ }
+ 
+ /*
+  * Returns tag of dollar quoted string used as function body, It parses
+  * only result of pg_get_functiondef function, so there are not possibility
+  * use just "'" char. When row doesn't contain a AS part of CREATE FUNCTION
+  * command, then it returns NULL.
+  */
+ static char *
+ get_functiondef_dollarquote_tag(char *row)
+ {
+ 	char *starttag;
+ 	char *endtag;
+ 	int	len;
+ 	char	*result;
+ 
+ 	/* leave when line doesn't contain a body separator */
+ 	if (strncmp(row, "AS $function", 12) != 0)
+ 		return NULL;
+ 		
+ 	/* detect body's tag */
+ 	starttag = row + strlen("AS ");
+ 	endtag = strchr(row + strlen("AS $function"), '$');
+ 	
+ 	psql_assert(endtag);
+ 	
+ 	len = endtag - starttag + 1;
+ 	result = pg_malloc(len + 1);
+ 	memcpy(result, starttag, len);
+ 	result[len] = '\0';
+ 		
+ 	return result;
+ }
*** ./src/bin/psql/help.c.orig	2010-08-03 09:00:48.387710077 +0200
--- ./src/bin/psql/help.c	2010-08-03 13:16:22.264713224 +0200
***************
*** 162,168 ****
  {
  	FILE	   *output;
  
! 	output = PageOutput(87, pager);
  
  	/* if you add/remove a line here, change the row count above */
  
--- 162,168 ----
  {
  	FILE	   *output;
  
! 	output = PageOutput(90, pager);
  
  	/* if you add/remove a line here, change the row count above */
  
***************
*** 174,186 ****
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE]              edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME]         edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
--- 174,187 ----
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE] [lno]        edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME] [lno]   edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
+ 	fprintf(output, _("  \\sf[+] FUNCNAME [lno]  show finction definition\n"));
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
*** ./src/bin/psql/tab-complete.c.orig	2010-08-03 09:00:48.390710399 +0200
--- ./src/bin/psql/tab-complete.c	2010-08-03 09:01:05.996710353 +0200
***************
*** 644,650 ****
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
--- 644,650 ----
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\sf", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
***************
*** 2501,2506 ****
--- 2501,2509 ----
  
  	else if (strcmp(prev_wd, "\\ef") == 0)
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+ 	
+ 		else if (strncmp(prev_wd, "\\sf", 2) == 0)
+ 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
  
  	else if (strcmp(prev_wd, "\\encoding") == 0)
  		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
#38Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Fetter (#35)
Re: review: psql: edit function, show function commands patch

David Fetter <david@fetter.org> writes:

On Mon, Aug 02, 2010 at 11:34:02PM -0400, Robert Haas wrote:

A side question is whether this should be an environment variable or
a psql variable.

I'd say "yes." As with $EDITOR/PSQL_EDITOR, there should be something
that looks for an overriding psql variable, drops through to look for
an environment variable, and then to a sane default, for some
reasonable value of "sane." Perhaps this default could depend on OS
(Windows vs. Everything Else) to start with.

Well, the thing about $EDITOR is that it's a very-widely-understood
convention. This one won't be, so the argument for making it an
environment variable seems pretty thin. It'd be saner to set it in
your ~/.psqlrc file than to add another few nanoseconds to every
process launch you ever do.

regards, tom lane

#39Greg Stark
gsstark@mit.edu
In reply to: Tom Lane (#38)
Re: review: psql: edit function, show function commands patch

On Wed, Aug 4, 2010 at 3:35 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Well, the thing about $EDITOR is that it's a very-widely-understood
convention.  This one won't be, so the argument for making it an
environment variable seems pretty thin.

Fwiw the +linenumber convention has been part of $EDITOR since
basically as long as vi has existed.

--
greg

#40Robert Haas
robertmhaas@gmail.com
In reply to: Greg Stark (#39)
Re: review: psql: edit function, show function commands patch

On Wed, Aug 4, 2010 at 8:50 PM, Greg Stark <gsstark@mit.edu> wrote:

On Wed, Aug 4, 2010 at 3:35 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Well, the thing about $EDITOR is that it's a very-widely-understood
convention.  This one won't be, so the argument for making it an
environment variable seems pretty thin.

Fwiw the +linenumber convention has been part of $EDITOR since
basically as long as vi has existed.

What precisely do you mean by that? We've pretty much established
that the convention is nothing like universally accepted by the
editors that are out there.

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

#41Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#40)
Re: review: psql: edit function, show function commands patch

Robert Haas <robertmhaas@gmail.com> writes:

On Wed, Aug 4, 2010 at 8:50 PM, Greg Stark <gsstark@mit.edu> wrote:

On Wed, Aug 4, 2010 at 3:35 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Well, the thing about $EDITOR is that it's a very-widely-understood
convention. �This one won't be, so the argument for making it an
environment variable seems pretty thin.

Fwiw the +linenumber convention has been part of $EDITOR since
basically as long as vi has existed.

What precisely do you mean by that? We've pretty much established
that the convention is nothing like universally accepted by the
editors that are out there.

More to the point, what I was saying is that there is no convention out
there for a second environment variable that tells programs calling
$EDITOR how to specify a linenumber argument.

regards, tom lane

#42Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#37)
Re: review: psql: edit function, show function commands patch

Pavel Stehule <pavel.stehule@gmail.com> writes:

updated patch attached

What exactly is the point of the \sf command? It seems like quite a lot
of added code for a feature that nobody has requested, and whose
definition is about as ad-hoc as could be. Personally I'd much sooner
use \ef for looking at a function definition. I think if \sf had been
submitted as a separate patch, rather than being snuck in with a feature
people do want, it wouldn't be accepted.

The current patch doesn't even compile warning-free :-(

command.c: In function `exec_command':
command.c:559: warning: `lineno' might be used uninitialized in this function
command.c: In function `editFile':
command.c:1729: warning: `editor_lineno_switch' might be used uninitialized in this function

regards, tom lane

#43Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#42)
Re: review: psql: edit function, show function commands patch

2010/8/8 Tom Lane <tgl@sss.pgh.pa.us>:

Pavel Stehule <pavel.stehule@gmail.com> writes:

updated patch attached

What exactly is the point of the \sf command?  It seems like quite a lot
of added code for a feature that nobody has requested, and whose
definition is about as ad-hoc as could be.  Personally I'd much sooner
use \ef for looking at a function definition.  I think if \sf had been
submitted as a separate patch, rather than being snuck in with a feature
people do want, it wouldn't be accepted.

I disagree. Now, you cannot to show a detail of function in well
readable form. Personally I prefer \sf+ form. Because I can see line
numbers, but \sf form is important for some copy paste operations. I
don't think, so \ef can replace \sf. It is based on my personal
experience. When I have to do some fast fix or fast decision I need to
see a source code of some functions (but I am in customer's
environment). Starting a external editor is slow and usually you can
not there to start your preferable editor.

If I return back then my first idea was to modify current \df command
to some practical form. Later in discussion was decided so new command
will be better.

The current patch doesn't even compile warning-free :-(

command.c: In function `exec_command':
command.c:559: warning: `lineno' might be used uninitialized in this function
command.c: In function `editFile':
command.c:1729: warning: `editor_lineno_switch' might be used uninitialized in this function

there is some strange - it didn't find it in my environment. But I
recheck it tomorrow morning.

Regards

Pavel Stehule

Show quoted text

                       regards, tom lane

#44Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#42)
Re: review: psql: edit function, show function commands patch

On Sun, Aug 8, 2010 at 1:14 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Pavel Stehule <pavel.stehule@gmail.com> writes:

updated patch attached

What exactly is the point of the \sf command?  It seems like quite a lot
of added code for a feature that nobody has requested, and whose
definition is about as ad-hoc as could be.  Personally I'd much sooner
use \ef for looking at a function definition.  I think if \sf had been
submitted as a separate patch, rather than being snuck in with a feature
people do want, it wouldn't be accepted.

I rather like \sf, actually; in fact, I think there's a decent
argument to be made that it's more useful than the line-numbering
stuff for \ef. I don't particularly like the name "\sf", but that's
more because I think backslash commands are a fundamentally unscalable
approach to providing administrative functionality than because I
think there's a better option in this particular case. It's rather
hard right now to get a function definition out of the database in
easily cut-and-pastable format. pg_dump won't do it, unless you'd
like to dump the whole schema (I think we should add an option there,
too, actually). Using \ef is reasonable but if the definition is more
than one page and you actually want to cut-and-paste it rather than
writing it to a file some place, it's not convenient. (Hopefully you
understand the problem I'm talking about here: cut-and-paste can
scroll past one screen, but the editor doesn't actually write it out
that way; it displays it a page at a time.) Now, admittedly, this is
only a minor convenience we're talking about: and if this get shot
down I won't cry into my beer, but I do think it's useful.

The current patch doesn't even compile warning-free :-(

command.c: In function `exec_command':
command.c:559: warning: `lineno' might be used uninitialized in this function
command.c: In function `editFile':
command.c:1729: warning: `editor_lineno_switch' might be used uninitialized in this function

That obviously needs to be fixed.

(BTW, if you want to take this one instead of me, that's fine.
Otherwise, I'll get to it this week.)

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

#45Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#44)
Re: review: psql: edit function, show function commands patch

Robert Haas <robertmhaas@gmail.com> writes:

On Sun, Aug 8, 2010 at 1:14 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

What exactly is the point of the \sf command?

I rather like \sf, actually; in fact, I think there's a decent
argument to be made that it's more useful than the line-numbering
stuff for \ef. I don't particularly like the name "\sf", but that's
more because I think backslash commands are a fundamentally unscalable
approach to providing administrative functionality than because I
think there's a better option in this particular case. It's rather
hard right now to get a function definition out of the database in
easily cut-and-pastable format.

Um, but \sf *doesn't* give you anything that's usefully copy and
pasteable. And if that were the goal, why doesn't it have an option to
write to a file?

But it's really the line numbers shoved in front that I'm on about here.
I can't see *any* use for that behavior except to figure out what part of
your function an error message with line number is referring to; and as
I said upthread, there are better ways to be attacking that problem.
If you've got a thousand-line function (yes, they're out there) do you
really want to be scrolling through \sf output to find out what line 714
is?

regards, tom lane

#46Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#45)
Re: review: psql: edit function, show function commands patch

2010/8/9 Tom Lane <tgl@sss.pgh.pa.us>:

Robert Haas <robertmhaas@gmail.com> writes:

On Sun, Aug 8, 2010 at 1:14 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

What exactly is the point of the \sf command?

I rather like \sf, actually; in fact, I think there's a decent
argument to be made that it's more useful than the line-numbering
stuff for \ef.  I don't particularly like the name "\sf", but that's
more because I think backslash commands are a fundamentally unscalable
approach to providing administrative functionality than because I
think there's a better option in this particular case.  It's rather
hard right now to get a function definition out of the database in
easily cut-and-pastable format.

Um, but \sf *doesn't* give you anything that's usefully copy and
pasteable.  And if that were the goal, why doesn't it have an option to
write to a file?

there are not a line numbers. And you can't to use a result of \df+ too.

But it's really the line numbers shoved in front that I'm on about here.
I can't see *any* use for that behavior except to figure out what part of
your function an error message with line number is referring to; and as
I said upthread, there are better ways to be attacking that problem.
If you've got a thousand-line function (yes, they're out there) do you
really want to be scrolling through \sf output to find out what line 714

\sf supports a pager
\sf can show lines from entered number

so
\sf foo 700 -- show from line 700

Best regards

Pavel Stehule

Show quoted text

is?

                       regards, tom lane

#47Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#42)
1 attachment(s)
Re: review: psql: edit function, show function commands patch

Hello

2010/8/8 Tom Lane <tgl@sss.pgh.pa.us>:

Pavel Stehule <pavel.stehule@gmail.com> writes:

updated patch attached

What exactly is the point of the \sf command?  It seems like quite a lot
of added code for a feature that nobody has requested, and whose
definition is about as ad-hoc as could be.  Personally I'd much sooner
use \ef for looking at a function definition.  I think if \sf had been
submitted as a separate patch, rather than being snuck in with a feature
people do want, it wouldn't be accepted.

The current patch doesn't even compile warning-free :-(

command.c: In function `exec_command':
command.c:559: warning: `lineno' might be used uninitialized in this function
command.c: In function `editFile':
command.c:1729: warning: `editor_lineno_switch' might be used uninitialized in this function

This warnings depends on gcc version, probably :(. On new fedora I see
nothing. So updated patch attached - these variables are initialised
in declaration now.

Regards

Pavel Stehule

Show quoted text

                       regards, tom lane

Attachments:

edit7.difftext/x-patch; charset=US-ASCII; name=edit7.diffDownload
*** ./doc/src/sgml/ref/psql-ref.sgml.orig	2010-08-03 09:00:48.384710383 +0200
--- ./doc/src/sgml/ref/psql-ref.sgml	2010-08-03 10:44:57.312835131 +0200
***************
*** 1339,1345 ****
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1339,1345 ----
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional> <optional> linenumber </optional></literal></term>
  
          <listitem>
          <para>
***************
*** 1369,1380 ****
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1369,1387 ----
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor (The psql's variable <varname>EDITOR_LINENUMBER_SWITCH</varname>
+         have to be filled).
+         </para>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional> <optional> linenumber </optional> </literal></term>
  
          <listitem>
          <para>
***************
*** 1397,1402 ****
--- 1404,1417 ----
           If no function is specified, a blank <command>CREATE FUNCTION</>
           template is presented for editing.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor. It count lines from start of function body, not from
+         start of text (The psql's variable <varname>EDITOR_LINENUMBER_SWITCH</varname>
+         have to be filled).
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 2116,2121 ****
--- 2131,2148 ----
  
  
        <varlistentry>
+         <term><literal>\sf[+] <replaceable class="parameter">function_description</replaceable> <optional> linenumber </optional> </literal></term>
+ 
+         <listitem>
+         <para>
+          This command fetches and shows the definition of the named function,
+          in the form of a <command>CREATE OR REPLACE FUNCTION</> command.
+          If the form <literal>\sf+</literal> is used, then lines are numbered.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
          <term><literal>\t</literal></term>
          <listitem>
          <para>
***************
*** 2123,2128 ****
--- 2150,2161 ----
          footer. This command is equivalent to <literal>\pset
          tuples_only</literal> and is provided for convenience.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor.
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 2459,2464 ****
--- 2492,2511 ----
        </varlistentry>
  
        <varlistentry>
+         <term><varname>EDITOR_LINENUMBER_SWITCH</varname></term>
+         <listitem>
+         <para>
+         Option used for navigation (go to line command) in external
+         editor. When it isn't defined, then you cannot to specify
+         line numbers for <command>\edit</command> and <command>\ef</command>
+         commands. On unix platforms are possible to use a '<option>+</option>'
+         or '<option>--line </option>'. The space after <literal>line</literal> 
+         is required.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
          <term><varname>ENCODING</varname></term>
          <listitem>
          <para>
***************
*** 3065,3070 ****
--- 3112,3118 ----
      </listitem>
     </varlistentry>
  
+ 
     <varlistentry>
      <term><envar>SHELL</envar></term>
  
*** ./src/bin/psql/command.c.orig	2010-08-03 09:00:48.386710435 +0200
--- ./src/bin/psql/command.c	2010-08-09 08:54:00.466222215 +0200
***************
*** 46,54 ****
--- 46,56 ----
  #include "input.h"
  #include "large_obj.h"
  #include "mainloop.h"
+ #include "pqsignal.h"
  #include "print.h"
  #include "psqlscan.h"
  #include "settings.h"
+ #include <signal.h>
  #include "variables.h"
  
  
***************
*** 57,63 ****
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		bool *edited);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
--- 59,65 ----
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 			 int lineno, bool *edited);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
***************
*** 66,71 ****
--- 68,76 ----
  
  static void printSSLInfo(void);
  
+ static int extract_lineno_from_funcdesc(char *func, backslashResult *status);
+ static char *get_functiondef_dollarquote_tag(char *row);
+ 
  #ifdef WIN32
  static void checkWin32Codepage(void);
  #endif
***************
*** 513,528 ****
  		else
  		{
  			char	   *fname;
! 
  			fname = psql_scan_slash_option(scan_state,
! 										   OT_NORMAL, NULL, true);
! 			expand_tilde(&fname);
  			if (fname)
! 				canonicalize_path(fname);
! 			if (do_edit(fname, query_buf, NULL))
! 				status = PSQL_CMD_NEWEDIT;
! 			else
! 				status = PSQL_CMD_ERROR;
  			free(fname);
  		}
  	}
--- 518,554 ----
  		else
  		{
  			char	   *fname;
! 			char		*ln;
! 			int  lineno = -1;
! 			
  			fname = psql_scan_slash_option(scan_state,
! 								   OT_NORMAL, NULL, true);
! 			
! 			/* try to get lineno */
  			if (fname)
! 			{
! 				ln = psql_scan_slash_option(scan_state,
! 									   OT_NORMAL, NULL, true);
! 				if (ln)
! 				{
! 					lineno = atoi(ln);
! 					if (lineno < 1)
! 					{
! 						psql_error("invalid line number\n");
! 						status = PSQL_CMD_ERROR;
! 					}
! 				}
! 			}
! 			if (status != PSQL_CMD_ERROR)
! 			{
! 				expand_tilde(&fname);
! 				if (fname)
! 					canonicalize_path(fname);
! 				if (do_edit(fname, query_buf, lineno, NULL))
! 					status = PSQL_CMD_NEWEDIT;
! 				else
! 					status = PSQL_CMD_ERROR;
! 			}
  			free(fname);
  		}
  	}
***************
*** 533,538 ****
--- 559,566 ----
  	 */
  	else if (strcmp(cmd, "ef") == 0)
  	{
+ 		int	lineno = -1;		/* be compiler quiet */
+ 	
  		if (!query_buf)
  		{
  			psql_error("no query buffer\n");
***************
*** 545,580 ****
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			if (!func)
! 			{
! 				/* set up an empty command to fill in */
! 				printfPQExpBuffer(query_buf,
! 								  "CREATE FUNCTION ( )\n"
! 								  " RETURNS \n"
! 								  " LANGUAGE \n"
! 								  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 								  "AS $function$\n"
! 								  "\n$function$\n");
! 			}
! 			else if (!lookup_function_oid(pset.db, func, &foid))
! 			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
! 			}
! 			else if (!get_create_function_cmd(pset.db, foid, query_buf))
  			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
  			}
  			if (func)
  				free(func);
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, &edited))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
--- 573,615 ----
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			lineno = extract_lineno_from_funcdesc(func, &status);
! 			
! 			if (status != PSQL_CMD_ERROR)
  			{
! 				if (!func)
! 				{
! 					/* set up an empty command to fill in */
! 					printfPQExpBuffer(query_buf,
! 									  "CREATE FUNCTION ( )\n"
! 									  " RETURNS \n"
! 									  " LANGUAGE \n"
! 									  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 									  "AS $function$\n"
! 									  "\n$function$\n");
! 				}
! 				else if (!lookup_function_oid(pset.db, func, &foid))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
! 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
  			}
+ 			
  			if (func)
  				free(func);
+ 			
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, lineno, &edited))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
***************
*** 969,974 ****
--- 1004,1152 ----
  		free(fname);
  	}
  
+ 	/*
+ 	 * \sf -- show the named function
+ 	 */
+ 	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+ 	{
+ 		int	first_visible_row = -1;
+ 		bool	with_lno;
+ 		
+ 		with_lno = (strcmp(cmd, "sf+") == 0);
+ 		
+ 		if (!query_buf)
+ 		{
+ 			psql_error("no query buffer\n");
+ 			status = PSQL_CMD_ERROR;
+ 		}
+ 		else
+ 		{
+ 			char	   *func;
+ 			Oid			foid = InvalidOid;
+ 
+ 			func = psql_scan_slash_option(scan_state,
+ 										  OT_WHOLE_LINE, NULL, true);
+ 			
+ 			first_visible_row = extract_lineno_from_funcdesc(func, &status);
+ 			
+ 			if (status != PSQL_CMD_ERROR)
+ 			{
+ 				if (!func)
+ 				{
+ 					/* show error for empty command */
+ 					psql_error("missing a function name\n");
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!lookup_function_oid(pset.db, func, &foid))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 			}
+ 			if (func)
+ 				free(func);
+ 		}
+ 
+ 		if (status != PSQL_CMD_ERROR)
+ 		{
+ 			int	lineno = 0;
+ 			char	*row;
+ 			char	   *dqtag = NULL;
+ 			bool	is_header = true;
+ 			bool	is_body = false;
+ 			bool	is_footer = false;
+ 			char	  *end_of_line;
+ 			int	lines;
+ 			FILE	   *output;
+ 			
+ 			
+ 			/* get number of lines of functiondef - used for possible using a pager */
+ 			row = query_buf->data;
+ 			lines = 0;
+ 			while (*row)
+ 			{
+ 				lines++;
+ 				end_of_line = strchr(row, '\n');
+ 				if (end_of_line)
+ 					row = end_of_line + 1;
+ 				else
+ 					break;
+ 			}
+ 			
+ 			output = PageOutput(lines, pset.popt.topt.pager);
+ 
+ 			row = query_buf->data;
+ 			while (*row)
+ 			{
+ 				/* find next end of line */
+ 				end_of_line = strchr(row, '\n');
+ 				if (end_of_line)
+ 					*end_of_line = '\0';
+ 				
+ 				if (is_header)
+ 				{
+ 					/* detect end of header */
+ 					dqtag = get_functiondef_dollarquote_tag(row);
+ 					if (dqtag)
+ 					{
+ 						is_header = false;
+ 						is_body = true;
+ 						lineno = 1;
+ 					}
+ 				}
+ 				else if (is_body)
+ 				{
+ 					lineno++;
+ 					if (strcmp(row, dqtag) == 0)
+ 					{
+ 						is_body = false;
+ 						is_footer = true;
+ 					}
+ 				}
+ 				
+ 				/* can we show rows? */
+ 				if (first_visible_row < 0 || (first_visible_row <= lineno))
+ 				{
+ 					if (with_lno)
+ 					{
+ 						if (is_header || is_footer)
+ 							fprintf(output, "**** %s", row);
+ 						else 
+ 							fprintf(output, "%4d %s", lineno, row);
+ 					}
+ 					else
+ 						fprintf(output, "%s", row);
+ 					
+ 					/* return back replaced "\n" */
+ 					if (end_of_line)
+ 						fprintf(output, "\n");
+ 				}
+ 				
+ 				if (end_of_line)
+ 					row = end_of_line + 1;
+ 				else
+ 					break;
+ 			}
+ 			
+ 			/* function pg_get_functiondef uses dollar quoted strings always */
+ 			psql_assert(dqtag != NULL);
+ 			free(dqtag);
+ 			
+ 			if (output != stdout)
+ 			{
+ 				pclose(output);
+ #ifndef WIN32
+ 				pqsignal(SIGPIPE, SIG_DFL);
+ #endif
+ 			}
+ 		}
+ 	}
+ 
  	/* \set -- generalized set variable/option command */
  	else if (strcmp(cmd, "set") == 0)
  	{
***************
*** 1548,1558 ****
   * If you do not specify a filename, the current query buffer will be copied
   * into a temporary one.
   */
- 
  static bool
! editFile(const char *fname)
  {
  	const char *editorName;
  	char	   *sys;
  	int			result;
  
--- 1726,1736 ----
   * If you do not specify a filename, the current query buffer will be copied
   * into a temporary one.
   */
  static bool
! editFile(const char *fname, int lineno)
  {
  	const char *editorName;
+ 	const char *editor_lineno_switch = NULL;	/* be compiler quiet */
  	char	   *sys;
  	int			result;
  
***************
*** 1566,1572 ****
  		editorName = getenv("VISUAL");
  	if (!editorName)
  		editorName = DEFAULT_EDITOR;
! 
  	/*
  	 * On Unix the EDITOR value should *not* be quoted, since it might include
  	 * switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
--- 1744,1761 ----
  		editorName = getenv("VISUAL");
  	if (!editorName)
  		editorName = DEFAULT_EDITOR;
! 	
! 	/* Find a EDITOR_LINENUMBER_SWITCH when lineno is used */
! 	if (lineno >= 1)
! 	{
! 		editor_lineno_switch = GetVariable(pset.vars, "EDITOR_LINENUMBER_SWITCH");
! 		if (!editor_lineno_switch)
! 		{
! 			psql_error("EDITOR_LINENUMBER_SWITCH variable is not set\n");
! 			return false;
! 		}
! 	}
! 	
  	/*
  	 * On Unix the EDITOR value should *not* be quoted, since it might include
  	 * switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
***************
*** 1574,1584 ****
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
  #ifndef WIN32
! 	sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
--- 1763,1795 ----
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	if (lineno >= 1)
! 		/*
! 		 * allocate sufficient memory for command line content and
! 		 * increase it about space for editor_lineno_switch and lineno 
! 		 * (signed int ~ 10 digits) and one space more.
! 		 */
! 		sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 
! 				    + strlen(editor_lineno_switch) + 10 + 1 + 1);
! 	
! 	else
! 		/* allocate sufficient memory for command line content */
! 		sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
! 	
  #ifndef WIN32
! 	if (lineno > 0)
! 		sprintf(sys, "exec %s %s%d '%s'", editorName, editor_lineno_switch, lineno, fname);
! 	else
! 		sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	if (lineno > 0)
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" %s%d \"%s\"" SYSTEMQUOTE, 
! 									    editorName, 
! 									    editor_lineno_switch,
! 									    lineno, 
! 									    fname);
! 	else
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
***************
*** 1593,1599 ****
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
--- 1804,1810 ----
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, int lineno, bool *edited)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
***************
*** 1685,1691 ****
  
  	/* call editor */
  	if (!error)
! 		error = !editFile(fname);
  
  	if (!error && stat(fname, &after) != 0)
  	{
--- 1896,1935 ----
  
  	/* call editor */
  	if (!error)
! 	{
! 		/* skip header lines */
! 		if (lineno != -1)
! 		{
! 			char   *lines = query_buf->data;
! 			char	*dqtag;
! 			
! 			/* we have to detect number of header lines */
! 			while (*lines)
! 			{
! 				char    *end_of_line = strchr(lines, '\n');
! 			
! 				if (end_of_line)
! 					*end_of_line = '\0';
! 			
! 				dqtag = get_functiondef_dollarquote_tag(lines);
! 				if (dqtag)
! 				{
! 					free(dqtag);
! 					break;
! 				}
! 				
! 				lineno++;
! 				
! 				/* leave when there are not next row */
! 				if (!end_of_line)
! 					break;
! 					
! 				lines = end_of_line + 1;
! 			}
! 		}
! 	
! 		error = !editFile(fname, lineno);
! 	}
  
  	if (!error && stat(fname, &after) != 0)
  	{
***************
*** 2241,2243 ****
--- 2485,2582 ----
  
  	destroyPQExpBuffer(msg);
  }
+ 
+ 
+ /*
+  * Returns lineno used in \sf and \ef commands. 
+  *
+  * These commands can be completed with number used as line
+  * number for navigation in showed lines / open file. The most 
+  * simple method for parsing is reading isolated digits from 
+  * right - \ef foo nn, \ef foo(..)nn. Returns -1 when
+  * lineno isn't used in function's descriptor.
+ */
+ static int
+ extract_lineno_from_funcdesc(char *func, backslashResult *status)
+ {
+ 	char *endfunc;
+ 	char *c;
+ 	int lineno = -1;
+ 	
+ 	if (!func)
+ 		return lineno;
+ 	
+ 	endfunc = func + strlen(func) - 1;
+ 	c = endfunc;
+ 	
+ 	/* skip useles whitespaces */
+ 	while (c >= func && isblank(*c))
+ 		c--;
+ 	
+ 	/* search the most left digit of continuously number */
+ 	while (c >= func && isdigit(*c))
+ 		c--;
+ 	
+ 	/* 
+ 	 * when left char isn't blank and isn't a right parenthesis
+ 	 * then command hasn't a lineno.
+ 	 */
+ 	if (c < endfunc && c > func)
+ 	{
+ 		/* 
+ 		 * digits have to be a separated from identifier by right 
+ 		 * parenthesis or by space, and there have to be entered
+ 		 * minimal one digit.
+ 		 */
+ 		if (isdigit(c[1]) && ( isblank(*c) || *c == ')' ))
+ 		{
+ 			c++;
+ 			
+ 			lineno = atoi(c);
+ 			if (lineno < 1)
+ 			{
+ 				psql_error("invalid line number\n");
+ 				*status = PSQL_CMD_ERROR;
+ 			}
+ 			else
+ 			{
+ 				/* remove lineno from function descriptor */
+ 				*c = '\0';
+ 			}
+ 		}
+ 	}
+ 	
+ 	return lineno;
+ }
+ 
+ /*
+  * Returns tag of dollar quoted string used as function body, It parses
+  * only result of pg_get_functiondef function, so there are not possibility
+  * use just "'" char. When row doesn't contain a AS part of CREATE FUNCTION
+  * command, then it returns NULL.
+  */
+ static char *
+ get_functiondef_dollarquote_tag(char *row)
+ {
+ 	char *starttag;
+ 	char *endtag;
+ 	int	len;
+ 	char	*result;
+ 
+ 	/* leave when line doesn't contain a body separator */
+ 	if (strncmp(row, "AS $function", 12) != 0)
+ 		return NULL;
+ 		
+ 	/* detect body's tag */
+ 	starttag = row + strlen("AS ");
+ 	endtag = strchr(row + strlen("AS $function"), '$');
+ 	
+ 	psql_assert(endtag);
+ 	
+ 	len = endtag - starttag + 1;
+ 	result = pg_malloc(len + 1);
+ 	memcpy(result, starttag, len);
+ 	result[len] = '\0';
+ 		
+ 	return result;
+ }
*** ./src/bin/psql/help.c.orig	2010-08-03 09:00:48.387710077 +0200
--- ./src/bin/psql/help.c	2010-08-03 13:16:22.264713224 +0200
***************
*** 162,168 ****
  {
  	FILE	   *output;
  
! 	output = PageOutput(87, pager);
  
  	/* if you add/remove a line here, change the row count above */
  
--- 162,168 ----
  {
  	FILE	   *output;
  
! 	output = PageOutput(90, pager);
  
  	/* if you add/remove a line here, change the row count above */
  
***************
*** 174,186 ****
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE]              edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME]         edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
--- 174,187 ----
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE] [lno]        edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME] [lno]   edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
+ 	fprintf(output, _("  \\sf[+] FUNCNAME [lno]  show finction definition\n"));
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
*** ./src/bin/psql/tab-complete.c.orig	2010-08-03 09:00:48.390710399 +0200
--- ./src/bin/psql/tab-complete.c	2010-08-03 09:01:05.996710353 +0200
***************
*** 644,650 ****
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
--- 644,650 ----
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\sf", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
***************
*** 2501,2506 ****
--- 2501,2509 ----
  
  	else if (strcmp(prev_wd, "\\ef") == 0)
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+ 	
+ 		else if (strncmp(prev_wd, "\\sf", 2) == 0)
+ 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
  
  	else if (strcmp(prev_wd, "\\encoding") == 0)
  		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
#48Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#45)
Re: review: psql: edit function, show function commands patch

On Sun, Aug 8, 2010 at 11:38 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Um, but \sf *doesn't* give you anything that's usefully copy and
pasteable.

Works for me.

\sf ts_debug(regconfig, text)

And if that were the goal, why doesn't it have an option to
write to a file?

Well, you cut-and-paste from a terminal window, not a file, so that's
a slightly different problem, although perhaps also a good one to
solve. But I'd rather see us solve that problem via some new pg_dump
functionality.

Hmm... or perhaps \sf should respect \o. I notice that \d does.

But it's really the line numbers shoved in front that I'm on about here.
I can't see *any* use for that behavior except to figure out what part of
your function an error message with line number is referring to; and as
I said upthread, there are better ways to be attacking that problem.
If you've got a thousand-line function (yes, they're out there) do you
really want to be scrolling through \sf output to find out what line 714
is?

Well, as Pavel points out, I guess you could use the "line number"
argument to \sf to start at around the place you're interested in,
athough I suspect that I would probably choose to use \ef in that
case. I suspect \sf is in general most useful with somewhat shorter
functions (I'd copy and paste a 100 line function, perhaps, but for a
1000 line function I'd probably try to get the definition into a file
and scp it or whatever).

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

#49Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#48)
1 attachment(s)
Re: review: psql: edit function, show function commands patch

2010/8/9 Robert Haas <robertmhaas@gmail.com>:

On Sun, Aug 8, 2010 at 11:38 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Um, but \sf *doesn't* give you anything that's usefully copy and
pasteable.

Works for me.

\sf ts_debug(regconfig, text)

And if that were the goal, why doesn't it have an option to
write to a file?

Well, you cut-and-paste from a terminal window, not a file, so that's
a slightly different problem, although perhaps also a good one to
solve.  But I'd rather see us solve that problem via some new pg_dump
functionality.

Hmm... or perhaps \sf should respect \o.  I notice that \d does.

it's not a bad idea.

updated patch attached

Show quoted text

But it's really the line numbers shoved in front that I'm on about here.
I can't see *any* use for that behavior except to figure out what part of
your function an error message with line number is referring to; and as
I said upthread, there are better ways to be attacking that problem.
If you've got a thousand-line function (yes, they're out there) do you
really want to be scrolling through \sf output to find out what line 714
is?

Well, as Pavel points out, I guess you could use the "line number"
argument to \sf to start at around the place you're interested in,
athough I suspect that I would probably choose to use \ef in that
case.  I suspect \sf is in general most useful with somewhat shorter
functions (I'd copy and paste a 100 line function, perhaps, but for a
1000 line function I'd probably try to get the definition into a file
and scp it or whatever).

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

Attachments:

edit8.difftext/x-patch; charset=US-ASCII; name=edit8.diffDownload
*** ./doc/src/sgml/ref/psql-ref.sgml.orig	2010-08-03 09:00:48.384710383 +0200
--- ./doc/src/sgml/ref/psql-ref.sgml	2010-08-03 10:44:57.312835131 +0200
***************
*** 1339,1345 ****
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1339,1345 ----
  
  
        <varlistentry>
!         <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional> <optional> linenumber </optional></literal></term>
  
          <listitem>
          <para>
***************
*** 1369,1380 ****
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional></literal></term>
  
          <listitem>
          <para>
--- 1369,1387 ----
          systems, <filename>notepad.exe</filename> on Windows systems.
          </para>
          </tip>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor (The psql's variable <varname>EDITOR_LINENUMBER_SWITCH</varname>
+         have to be filled).
+         </para>
          </listitem>
        </varlistentry>
  
  
        <varlistentry>
!         <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional> <optional> linenumber </optional> </literal></term>
  
          <listitem>
          <para>
***************
*** 1397,1402 ****
--- 1404,1417 ----
           If no function is specified, a blank <command>CREATE FUNCTION</>
           template is presented for editing.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor. It count lines from start of function body, not from
+         start of text (The psql's variable <varname>EDITOR_LINENUMBER_SWITCH</varname>
+         have to be filled).
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 2116,2121 ****
--- 2131,2148 ----
  
  
        <varlistentry>
+         <term><literal>\sf[+] <replaceable class="parameter">function_description</replaceable> <optional> linenumber </optional> </literal></term>
+ 
+         <listitem>
+         <para>
+          This command fetches and shows the definition of the named function,
+          in the form of a <command>CREATE OR REPLACE FUNCTION</> command.
+          If the form <literal>\sf+</literal> is used, then lines are numbered.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
          <term><literal>\t</literal></term>
          <listitem>
          <para>
***************
*** 2123,2128 ****
--- 2150,2161 ----
          footer. This command is equivalent to <literal>\pset
          tuples_only</literal> and is provided for convenience.
          </para>
+ 
+         <para>
+         If <replaceable class="parameter">linenumber</replaceable> is
+         specified, then cursor is moved on this line after start of 
+         editor.
+         </para>
          </listitem>
        </varlistentry>
  
***************
*** 2459,2464 ****
--- 2492,2511 ----
        </varlistentry>
  
        <varlistentry>
+         <term><varname>EDITOR_LINENUMBER_SWITCH</varname></term>
+         <listitem>
+         <para>
+         Option used for navigation (go to line command) in external
+         editor. When it isn't defined, then you cannot to specify
+         line numbers for <command>\edit</command> and <command>\ef</command>
+         commands. On unix platforms are possible to use a '<option>+</option>'
+         or '<option>--line </option>'. The space after <literal>line</literal> 
+         is required.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
          <term><varname>ENCODING</varname></term>
          <listitem>
          <para>
***************
*** 3065,3070 ****
--- 3112,3118 ----
      </listitem>
     </varlistentry>
  
+ 
     <varlistentry>
      <term><envar>SHELL</envar></term>
  
*** ./src/bin/psql/command.c.orig	2010-08-03 09:00:48.386710435 +0200
--- ./src/bin/psql/command.c	2010-08-09 13:39:37.432222179 +0200
***************
*** 46,54 ****
--- 46,56 ----
  #include "input.h"
  #include "large_obj.h"
  #include "mainloop.h"
+ #include "pqsignal.h"
  #include "print.h"
  #include "psqlscan.h"
  #include "settings.h"
+ #include <signal.h>
  #include "variables.h"
  
  
***************
*** 57,63 ****
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 		bool *edited);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
--- 59,65 ----
  			 PsqlScanState scan_state,
  			 PQExpBuffer query_buf);
  static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
! 			 int lineno, bool *edited);
  static bool do_connect(char *dbname, char *user, char *host, char *port);
  static bool do_shell(const char *command);
  static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
***************
*** 66,71 ****
--- 68,76 ----
  
  static void printSSLInfo(void);
  
+ static int extract_lineno_from_funcdesc(char *func, backslashResult *status);
+ static char *get_functiondef_dollarquote_tag(char *row);
+ 
  #ifdef WIN32
  static void checkWin32Codepage(void);
  #endif
***************
*** 513,528 ****
  		else
  		{
  			char	   *fname;
! 
  			fname = psql_scan_slash_option(scan_state,
! 										   OT_NORMAL, NULL, true);
! 			expand_tilde(&fname);
  			if (fname)
! 				canonicalize_path(fname);
! 			if (do_edit(fname, query_buf, NULL))
! 				status = PSQL_CMD_NEWEDIT;
! 			else
! 				status = PSQL_CMD_ERROR;
  			free(fname);
  		}
  	}
--- 518,554 ----
  		else
  		{
  			char	   *fname;
! 			char		*ln;
! 			int  lineno = -1;
! 			
  			fname = psql_scan_slash_option(scan_state,
! 								   OT_NORMAL, NULL, true);
! 			
! 			/* try to get lineno */
  			if (fname)
! 			{
! 				ln = psql_scan_slash_option(scan_state,
! 									   OT_NORMAL, NULL, true);
! 				if (ln)
! 				{
! 					lineno = atoi(ln);
! 					if (lineno < 1)
! 					{
! 						psql_error("invalid line number\n");
! 						status = PSQL_CMD_ERROR;
! 					}
! 				}
! 			}
! 			if (status != PSQL_CMD_ERROR)
! 			{
! 				expand_tilde(&fname);
! 				if (fname)
! 					canonicalize_path(fname);
! 				if (do_edit(fname, query_buf, lineno, NULL))
! 					status = PSQL_CMD_NEWEDIT;
! 				else
! 					status = PSQL_CMD_ERROR;
! 			}
  			free(fname);
  		}
  	}
***************
*** 533,538 ****
--- 559,566 ----
  	 */
  	else if (strcmp(cmd, "ef") == 0)
  	{
+ 		int	lineno = -1;		/* be compiler quiet */
+ 	
  		if (!query_buf)
  		{
  			psql_error("no query buffer\n");
***************
*** 545,580 ****
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			if (!func)
! 			{
! 				/* set up an empty command to fill in */
! 				printfPQExpBuffer(query_buf,
! 								  "CREATE FUNCTION ( )\n"
! 								  " RETURNS \n"
! 								  " LANGUAGE \n"
! 								  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 								  "AS $function$\n"
! 								  "\n$function$\n");
! 			}
! 			else if (!lookup_function_oid(pset.db, func, &foid))
  			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
! 			}
! 			else if (!get_create_function_cmd(pset.db, foid, query_buf))
! 			{
! 				/* error already reported */
! 				status = PSQL_CMD_ERROR;
  			}
  			if (func)
  				free(func);
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, &edited))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
--- 573,615 ----
  
  			func = psql_scan_slash_option(scan_state,
  										  OT_WHOLE_LINE, NULL, true);
! 			lineno = extract_lineno_from_funcdesc(func, &status);
! 			
! 			if (status != PSQL_CMD_ERROR)
  			{
! 				if (!func)
! 				{
! 					/* set up an empty command to fill in */
! 					printfPQExpBuffer(query_buf,
! 									  "CREATE FUNCTION ( )\n"
! 									  " RETURNS \n"
! 									  " LANGUAGE \n"
! 									  " -- common options:  IMMUTABLE  STABLE  STRICT  SECURITY DEFINER\n"
! 									  "AS $function$\n"
! 									  "\n$function$\n");
! 				}
! 				else if (!lookup_function_oid(pset.db, func, &foid))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
! 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
! 				{
! 					/* error already reported */
! 					status = PSQL_CMD_ERROR;
! 				}
  			}
+ 			
  			if (func)
  				free(func);
+ 			
  		}
  
  		if (status != PSQL_CMD_ERROR)
  		{
  			bool		edited = false;
  
! 			if (!do_edit(0, query_buf, lineno, &edited))
  				status = PSQL_CMD_ERROR;
  			else if (!edited)
  				puts(_("No changes"));
***************
*** 969,974 ****
--- 1004,1160 ----
  		free(fname);
  	}
  
+ 	/*
+ 	 * \sf -- show the named function
+ 	 */
+ 	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+ 	{
+ 		int	first_visible_row = -1;
+ 		bool	with_lno;
+ 		
+ 		with_lno = (strcmp(cmd, "sf+") == 0);
+ 		
+ 		if (!query_buf)
+ 		{
+ 			psql_error("no query buffer\n");
+ 			status = PSQL_CMD_ERROR;
+ 		}
+ 		else
+ 		{
+ 			char	   *func;
+ 			Oid			foid = InvalidOid;
+ 
+ 			func = psql_scan_slash_option(scan_state,
+ 										  OT_WHOLE_LINE, NULL, true);
+ 			
+ 			first_visible_row = extract_lineno_from_funcdesc(func, &status);
+ 			
+ 			if (status != PSQL_CMD_ERROR)
+ 			{
+ 				if (!func)
+ 				{
+ 					/* show error for empty command */
+ 					psql_error("missing a function name\n");
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!lookup_function_oid(pset.db, func, &foid))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 				else if (!get_create_function_cmd(pset.db, foid, query_buf))
+ 				{
+ 					/* error already reported */
+ 					status = PSQL_CMD_ERROR;
+ 				}
+ 			}
+ 			if (func)
+ 				free(func);
+ 		}
+ 
+ 		if (status != PSQL_CMD_ERROR)
+ 		{
+ 			int	lineno = 0;
+ 			char	*row;
+ 			char	   *dqtag = NULL;
+ 			bool	is_header = true;
+ 			bool	is_body = false;
+ 			bool	is_footer = false;
+ 			char	  *end_of_line;
+ 			int	lines;
+ 			FILE	   *output;
+ 			bool	is_pager;
+ 			
+ 			if (pset.queryFout == stdout)
+ 			{
+ 				row = query_buf->data;
+ 				lines = 0;
+ 				while (*row)
+ 				{
+ 					lines++;
+ 					end_of_line = strchr(row, '\n');
+ 					if (end_of_line)
+ 						row = end_of_line + 1;
+ 					else
+ 						break;
+ 				}
+ 				
+ 				output = PageOutput(lines, pset.popt.topt.pager);
+ 				is_pager = output != stdout;
+ 			}
+ 			else
+ 			{
+ 				output = pset.queryFout;
+ 				is_pager = false;
+ 			}
+ 
+ 			row = query_buf->data;
+ 			while (*row)
+ 			{
+ 				/* find next end of line */
+ 				end_of_line = strchr(row, '\n');
+ 				if (end_of_line)
+ 					*end_of_line = '\0';
+ 				
+ 				if (is_header)
+ 				{
+ 					/* detect end of header */
+ 					dqtag = get_functiondef_dollarquote_tag(row);
+ 					if (dqtag)
+ 					{
+ 						is_header = false;
+ 						is_body = true;
+ 						lineno = 1;
+ 					}
+ 				}
+ 				else if (is_body)
+ 				{
+ 					lineno++;
+ 					if (strcmp(row, dqtag) == 0)
+ 					{
+ 						is_body = false;
+ 						is_footer = true;
+ 					}
+ 				}
+ 				
+ 				/* can we show rows? */
+ 				if (first_visible_row < 0 || (first_visible_row <= lineno))
+ 				{
+ 					if (with_lno)
+ 					{
+ 						if (is_header || is_footer)
+ 							fprintf(output, "**** %s", row);
+ 						else 
+ 							fprintf(output, "%4d %s", lineno, row);
+ 					}
+ 					else
+ 						fprintf(output, "%s", row);
+ 					
+ 					/* return back replaced "\n" */
+ 					if (end_of_line)
+ 						fprintf(output, "\n");
+ 				}
+ 				
+ 				if (end_of_line)
+ 					row = end_of_line + 1;
+ 				else
+ 					break;
+ 			}
+ 			
+ 			/* function pg_get_functiondef uses dollar quoted strings always */
+ 			psql_assert(dqtag != NULL);
+ 			free(dqtag);
+ 			
+ 			if (is_pager)
+ 			{
+ 				pclose(output);
+ #ifndef WIN32
+ 				pqsignal(SIGPIPE, SIG_DFL);
+ #endif
+ 			}
+ 		}
+ 	}
+ 
  	/* \set -- generalized set variable/option command */
  	else if (strcmp(cmd, "set") == 0)
  	{
***************
*** 1548,1558 ****
   * If you do not specify a filename, the current query buffer will be copied
   * into a temporary one.
   */
- 
  static bool
! editFile(const char *fname)
  {
  	const char *editorName;
  	char	   *sys;
  	int			result;
  
--- 1734,1744 ----
   * If you do not specify a filename, the current query buffer will be copied
   * into a temporary one.
   */
  static bool
! editFile(const char *fname, int lineno)
  {
  	const char *editorName;
+ 	const char *editor_lineno_switch = NULL;	/* be compiler quiet */
  	char	   *sys;
  	int			result;
  
***************
*** 1566,1572 ****
  		editorName = getenv("VISUAL");
  	if (!editorName)
  		editorName = DEFAULT_EDITOR;
! 
  	/*
  	 * On Unix the EDITOR value should *not* be quoted, since it might include
  	 * switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
--- 1752,1769 ----
  		editorName = getenv("VISUAL");
  	if (!editorName)
  		editorName = DEFAULT_EDITOR;
! 	
! 	/* Find a EDITOR_LINENUMBER_SWITCH when lineno is used */
! 	if (lineno >= 1)
! 	{
! 		editor_lineno_switch = GetVariable(pset.vars, "EDITOR_LINENUMBER_SWITCH");
! 		if (!editor_lineno_switch)
! 		{
! 			psql_error("EDITOR_LINENUMBER_SWITCH variable is not set\n");
! 			return false;
! 		}
! 	}
! 	
  	/*
  	 * On Unix the EDITOR value should *not* be quoted, since it might include
  	 * switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
***************
*** 1574,1584 ****
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
  #ifndef WIN32
! 	sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
--- 1771,1803 ----
  	 * severe brain damage in their command shell plus the fact that standard
  	 * program paths include spaces.
  	 */
! 	if (lineno >= 1)
! 		/*
! 		 * allocate sufficient memory for command line content and
! 		 * increase it about space for editor_lineno_switch and lineno 
! 		 * (signed int ~ 10 digits) and one space more.
! 		 */
! 		sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 
! 				    + strlen(editor_lineno_switch) + 10 + 1 + 1);
! 	
! 	else
! 		/* allocate sufficient memory for command line content */
! 		sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
! 	
  #ifndef WIN32
! 	if (lineno > 0)
! 		sprintf(sys, "exec %s %s%d '%s'", editorName, editor_lineno_switch, lineno, fname);
! 	else
! 		sprintf(sys, "exec %s '%s'", editorName, fname);
  #else
! 	if (lineno > 0)
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" %s%d \"%s\"" SYSTEMQUOTE, 
! 									    editorName, 
! 									    editor_lineno_switch,
! 									    lineno, 
! 									    fname);
! 	else
! 		sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
  #endif
  	result = system(sys);
  	if (result == -1)
***************
*** 1593,1599 ****
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
--- 1812,1818 ----
  
  /* call this one */
  static bool
! do_edit(const char *filename_arg, PQExpBuffer query_buf, int lineno, bool *edited)
  {
  	char		fnametmp[MAXPGPATH];
  	FILE	   *stream = NULL;
***************
*** 1685,1691 ****
  
  	/* call editor */
  	if (!error)
! 		error = !editFile(fname);
  
  	if (!error && stat(fname, &after) != 0)
  	{
--- 1904,1943 ----
  
  	/* call editor */
  	if (!error)
! 	{
! 		/* skip header lines */
! 		if (lineno != -1)
! 		{
! 			char   *lines = query_buf->data;
! 			char	*dqtag;
! 			
! 			/* we have to detect number of header lines */
! 			while (*lines)
! 			{
! 				char    *end_of_line = strchr(lines, '\n');
! 			
! 				if (end_of_line)
! 					*end_of_line = '\0';
! 			
! 				dqtag = get_functiondef_dollarquote_tag(lines);
! 				if (dqtag)
! 				{
! 					free(dqtag);
! 					break;
! 				}
! 				
! 				lineno++;
! 				
! 				/* leave when there are not next row */
! 				if (!end_of_line)
! 					break;
! 					
! 				lines = end_of_line + 1;
! 			}
! 		}
! 	
! 		error = !editFile(fname, lineno);
! 	}
  
  	if (!error && stat(fname, &after) != 0)
  	{
***************
*** 2241,2243 ****
--- 2493,2590 ----
  
  	destroyPQExpBuffer(msg);
  }
+ 
+ 
+ /*
+  * Returns lineno used in \sf and \ef commands. 
+  *
+  * These commands can be completed with number used as line
+  * number for navigation in showed lines / open file. The most 
+  * simple method for parsing is reading isolated digits from 
+  * right - \ef foo nn, \ef foo(..)nn. Returns -1 when
+  * lineno isn't used in function's descriptor.
+ */
+ static int
+ extract_lineno_from_funcdesc(char *func, backslashResult *status)
+ {
+ 	char *endfunc;
+ 	char *c;
+ 	int lineno = -1;
+ 	
+ 	if (!func)
+ 		return lineno;
+ 	
+ 	endfunc = func + strlen(func) - 1;
+ 	c = endfunc;
+ 	
+ 	/* skip useles whitespaces */
+ 	while (c >= func && isblank(*c))
+ 		c--;
+ 	
+ 	/* search the most left digit of continuously number */
+ 	while (c >= func && isdigit(*c))
+ 		c--;
+ 	
+ 	/* 
+ 	 * when left char isn't blank and isn't a right parenthesis
+ 	 * then command hasn't a lineno.
+ 	 */
+ 	if (c < endfunc && c > func)
+ 	{
+ 		/* 
+ 		 * digits have to be a separated from identifier by right 
+ 		 * parenthesis or by space, and there have to be entered
+ 		 * minimal one digit.
+ 		 */
+ 		if (isdigit(c[1]) && ( isblank(*c) || *c == ')' ))
+ 		{
+ 			c++;
+ 			
+ 			lineno = atoi(c);
+ 			if (lineno < 1)
+ 			{
+ 				psql_error("invalid line number\n");
+ 				*status = PSQL_CMD_ERROR;
+ 			}
+ 			else
+ 			{
+ 				/* remove lineno from function descriptor */
+ 				*c = '\0';
+ 			}
+ 		}
+ 	}
+ 	
+ 	return lineno;
+ }
+ 
+ /*
+  * Returns tag of dollar quoted string used as function body, It parses
+  * only result of pg_get_functiondef function, so there are not possibility
+  * use just "'" char. When row doesn't contain a AS part of CREATE FUNCTION
+  * command, then it returns NULL.
+  */
+ static char *
+ get_functiondef_dollarquote_tag(char *row)
+ {
+ 	char *starttag;
+ 	char *endtag;
+ 	int	len;
+ 	char	*result;
+ 
+ 	/* leave when line doesn't contain a body separator */
+ 	if (strncmp(row, "AS $function", 12) != 0)
+ 		return NULL;
+ 		
+ 	/* detect body's tag */
+ 	starttag = row + strlen("AS ");
+ 	endtag = strchr(row + strlen("AS $function"), '$');
+ 	
+ 	psql_assert(endtag);
+ 	
+ 	len = endtag - starttag + 1;
+ 	result = pg_malloc(len + 1);
+ 	memcpy(result, starttag, len);
+ 	result[len] = '\0';
+ 		
+ 	return result;
+ }
*** ./src/bin/psql/help.c.orig	2010-08-03 09:00:48.387710077 +0200
--- ./src/bin/psql/help.c	2010-08-03 13:16:22.264713224 +0200
***************
*** 162,168 ****
  {
  	FILE	   *output;
  
! 	output = PageOutput(87, pager);
  
  	/* if you add/remove a line here, change the row count above */
  
--- 162,168 ----
  {
  	FILE	   *output;
  
! 	output = PageOutput(90, pager);
  
  	/* if you add/remove a line here, change the row count above */
  
***************
*** 174,186 ****
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE]              edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME]         edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
--- 174,187 ----
  	fprintf(output, "\n");
  
  	fprintf(output, _("Query Buffer\n"));
! 	fprintf(output, _("  \\e [FILE] [lno]        edit the query buffer (or file) with external editor\n"));
! 	fprintf(output, _("  \\ef [FUNCNAME] [lno]   edit function 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
  	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
  #endif
+ 	fprintf(output, _("  \\sf[+] FUNCNAME [lno]  show finction definition\n"));
  	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
  	fprintf(output, "\n");
  
*** ./src/bin/psql/tab-complete.c.orig	2010-08-03 09:00:48.390710399 +0200
--- ./src/bin/psql/tab-complete.c	2010-08-03 09:01:05.996710353 +0200
***************
*** 644,650 ****
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
--- 644,650 ----
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\sf", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
***************
*** 2501,2506 ****
--- 2501,2509 ----
  
  	else if (strcmp(prev_wd, "\\ef") == 0)
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+ 	
+ 		else if (strncmp(prev_wd, "\\sf", 2) == 0)
+ 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
  
  	else if (strcmp(prev_wd, "\\encoding") == 0)
  		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
#50David E. Wheeler
david@kineticode.com
In reply to: Tom Lane (#45)
Re: review: psql: edit function, show function commands patch

On Aug 8, 2010, at 8:38 PM, Tom Lane wrote:

Um, but \sf *doesn't* give you anything that's usefully copy and
pasteable. And if that were the goal, why doesn't it have an option to
write to a file?

But it's really the line numbers shoved in front that I'm on about here.
I can't see *any* use for that behavior except to figure out what part of
your function an error message with line number is referring to; and as
I said upthread, there are better ways to be attacking that problem.
If you've got a thousand-line function (yes, they're out there) do you
really want to be scrolling through \sf output to find out what line 714
is?

Suggestion:

\sf without line numbers
\sf+ with line numbers

Best,

David

#51Pavel Stehule
pavel.stehule@gmail.com
In reply to: David E. Wheeler (#50)
Re: review: psql: edit function, show function commands patch

2010/8/9 David E. Wheeler <david@kineticode.com>:

On Aug 8, 2010, at 8:38 PM, Tom Lane wrote:

Um, but \sf *doesn't* give you anything that's usefully copy and
pasteable.  And if that were the goal, why doesn't it have an option to
write to a file?

But it's really the line numbers shoved in front that I'm on about here.
I can't see *any* use for that behavior except to figure out what part of
your function an error message with line number is referring to; and as
I said upthread, there are better ways to be attacking that problem.
If you've got a thousand-line function (yes, they're out there) do you
really want to be scrolling through \sf output to find out what line 714
is?

Suggestion:

\sf without line numbers
\sf+ with line numbers

it did it :)

Pavel

Show quoted text

Best,

David

#52Robert Haas
robertmhaas@gmail.com
In reply to: Pavel Stehule (#49)
2 attachment(s)
Re: review: psql: edit function, show function commands patch

On Mon, Aug 9, 2010 at 7:40 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

updated patch attached

I spent some time cleaning this up tonight. I think that the \e and
\ef portions are now ready to commit, but I am not quite happy with
the \sf stuff yet, so I've broken that out into a separate patch,
which is also attached.

Barring objections, I'll commit the \e and \ef portions of this in the
morning after one final read-through.

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

Attachments:

edit8-rmh.patchapplication/octet-stream; name=edit8-rmh.patchDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index b803ef9..85adf55 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1338,7 +1338,7 @@ testdb=&gt;
 
 
       <varlistentry>
-        <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional></literal></term>
+        <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional> <optional> linenumber </optional></literal></term>
 
         <listitem>
         <para>
@@ -1368,12 +1368,19 @@ testdb=&gt;
         systems, <filename>notepad.exe</filename> on Windows systems.
         </para>
         </tip>
+
+        <para>
+        If a line number is specified, <application>psql</application> will
+        attempt to position the cursor on the specified line of the file.
+        An error will occur if <varname>EDITOR_LINENUMBER_SWITCH</varname> 
+        is not set.
+        </para>
         </listitem>
       </varlistentry>
 
 
       <varlistentry>
-        <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional></literal></term>
+        <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional> <optional> linenumber </optional> </literal></term>
 
         <listitem>
         <para>
@@ -1396,6 +1403,14 @@ testdb=&gt;
          If no function is specified, a blank <command>CREATE FUNCTION</>
          template is presented for editing.
         </para>
+
+        <para>
+        If a line number is specified, <application>psql</application> will
+        attempt to position the cursor on the specified line of the function
+        body (note that the function body typically does not begin on the
+        first line of the file).  An error will occur if
+        <varname>EDITOR_LINENUMBER_SWITCH</varname> is not set.
+        </para>
         </listitem>
       </varlistentry>
 
@@ -2458,6 +2473,25 @@ bar
       </varlistentry>
 
       <varlistentry>
+        <term><varname>EDITOR_LINENUMBER_SWITCH</varname></term>
+        <listitem>
+        <para>
+        When <command>\edit</command> or <command>\ef</command> is used with
+        a line number argument, this variable specifies the switch used to
+        pass the line number to the user's editor.  For editors such as
+        <productname>emacs</> or <productname>vi</>, you can simply set this
+        variable to a single plus sign.  In some cases, it might be necessary
+        to include a trailing space in the value assigned to this variable.
+        For example:
+
+<programlisting>
+\set EDIT_LINENUMBER_SWITCH '--line '
+</programlisting>
+        </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
         <term><varname>ENCODING</varname></term>
         <listitem>
         <para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index a3e55c5..2ee89bc 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -57,7 +57,7 @@ static backslashResult exec_command(const char *cmd,
 			 PsqlScanState scan_state,
 			 PQExpBuffer query_buf);
 static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
-		bool *edited);
+			 int lineno, bool *edited);
 static bool do_connect(char *dbname, char *user, char *host, char *port);
 static bool do_shell(const char *command);
 static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
@@ -66,6 +66,9 @@ static void minimal_error_message(PGresult *res);
 
 static void printSSLInfo(void);
 
+static int strip_lineno_from_funcdesc(char *func);
+static char *get_functiondef_dollarquote_tag(char *line);
+
 #ifdef WIN32
 static void checkWin32Codepage(void);
 #endif
@@ -509,18 +512,40 @@ exec_command(const char *cmd,
 		}
 		else
 		{
-			char	   *fname;
-
+			char   *fname;
+			char   *ln;
+			int	lineno = -1;
+			
 			fname = psql_scan_slash_option(scan_state,
-										   OT_NORMAL, NULL, true);
-			expand_tilde(&fname);
+								   OT_NORMAL, NULL, true);
+			
+			/* try to get lineno */
 			if (fname)
-				canonicalize_path(fname);
-			if (do_edit(fname, query_buf, NULL))
-				status = PSQL_CMD_NEWEDIT;
-			else
-				status = PSQL_CMD_ERROR;
-			free(fname);
+			{
+				ln = psql_scan_slash_option(scan_state,
+									   OT_NORMAL, NULL, true);
+				if (ln)
+				{
+					lineno = atoi(ln);
+					if (lineno < 1)
+					{
+						psql_error("invalid line number\n");
+						status = PSQL_CMD_ERROR;
+					}
+				}
+			}
+			if (status != PSQL_CMD_ERROR)
+			{
+				expand_tilde(&fname);
+				if (fname)
+					canonicalize_path(fname);
+				if (do_edit(fname, query_buf, lineno, NULL))
+					status = PSQL_CMD_NEWEDIT;
+				else
+					status = PSQL_CMD_ERROR;
+			}
+			if (fname)
+				free(fname);
 		}
 	}
 
@@ -530,6 +555,8 @@ exec_command(const char *cmd,
 	 */
 	else if (strcmp(cmd, "ef") == 0)
 	{
+		int	lineno = -1;		/* keep compiler quiet */
+	
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
@@ -542,7 +569,13 @@ exec_command(const char *cmd,
 
 			func = psql_scan_slash_option(scan_state,
 										  OT_WHOLE_LINE, NULL, true);
-			if (!func)
+			lineno = strip_lineno_from_funcdesc(func);
+			if (lineno == 0)
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			else if (!func)
 			{
 				/* set up an empty command to fill in */
 				printfPQExpBuffer(query_buf,
@@ -571,7 +604,7 @@ exec_command(const char *cmd,
 		{
 			bool		edited = false;
 
-			if (!do_edit(0, query_buf, &edited))
+			if (!do_edit(0, query_buf, lineno, &edited))
 				status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
@@ -1543,11 +1576,11 @@ UnsyncVariables(void)
  * If you do not specify a filename, the current query buffer will be copied
  * into a temporary one.
  */
-
 static bool
-editFile(const char *fname)
+editFile(const char *fname, int lineno)
 {
 	const char *editorName;
+	const char *editor_lineno_switch = NULL;	/* be compiler quiet */
 	char	   *sys;
 	int			result;
 
@@ -1562,6 +1595,18 @@ editFile(const char *fname)
 	if (!editorName)
 		editorName = DEFAULT_EDITOR;
 
+	/* Find a EDITOR_LINENUMBER_SWITCH when lineno is used */
+	if (lineno >= 1)
+	{
+		editor_lineno_switch = GetVariable(pset.vars,
+										   "EDITOR_LINENUMBER_SWITCH");
+		if (!editor_lineno_switch)
+		{
+			psql_error("EDITOR_LINENUMBER_SWITCH variable is not set\n");
+			return false;
+		}
+	}
+
 	/*
 	 * On Unix the EDITOR value should *not* be quoted, since it might include
 	 * switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
@@ -1569,11 +1614,36 @@ editFile(const char *fname)
 	 * severe brain damage in their command shell plus the fact that standard
 	 * program paths include spaces.
 	 */
-	sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
+	if (lineno >= 1)
+	{
+		/*
+		 * allocate sufficient memory for command line content and
+		 * increase it about space for editor_lineno_switch and lineno 
+		 * (signed int ~ 10 digits) and one space more.
+		 */
+		sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 
+				    + strlen(editor_lineno_switch) + 10 + 1 + 1);
+	}
+	else
+	{
+		/* allocate sufficient memory for command line content */
+		sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
+	}
+
 #ifndef WIN32
-	sprintf(sys, "exec %s '%s'", editorName, fname);
+	if (lineno > 0)
+		sprintf(sys, "exec %s %s%d '%s'", editorName, editor_lineno_switch, lineno, fname);
+	else
+		sprintf(sys, "exec %s '%s'", editorName, fname);
 #else
-	sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
+	if (lineno > 0)
+		sprintf(sys, SYSTEMQUOTE "\"%s\" %s%d \"%s\"" SYSTEMQUOTE, 
+									    editorName, 
+									    editor_lineno_switch,
+									    lineno, 
+									    fname);
+	else
+		sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
 #endif
 	result = system(sys);
 	if (result == -1)
@@ -1588,7 +1658,7 @@ editFile(const char *fname)
 
 /* call this one */
 static bool
-do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
+do_edit(const char *filename_arg, PQExpBuffer query_buf, int lineno, bool *edited)
 {
 	char		fnametmp[MAXPGPATH];
 	FILE	   *stream = NULL;
@@ -1680,7 +1750,34 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
 
 	/* call editor */
 	if (!error)
-		error = !editFile(fname);
+	{
+		/* skip header lines */
+		if (lineno != -1)
+		{
+			char   *lines = query_buf->data;
+			char	*dqtag;
+
+			/* skip header lines */
+			while (*lines != '\0')
+			{
+				dqtag = get_functiondef_dollarquote_tag(lines);
+				if (dqtag)
+				{
+					free(dqtag);
+					break;
+				}
+				lineno++;
+
+				/* find start of next line */
+				lines = strchr(lines, '\n');
+				if (!lines)
+					break;
+				lines++;
+			}
+		}
+
+		error = !editFile(fname, lineno);
+	}
 
 	if (!error && stat(fname, &after) != 0)
 	{
@@ -2236,3 +2333,75 @@ minimal_error_message(PGresult *res)
 
 	destroyPQExpBuffer(msg);
 }
+
+
+/*
+ * Strips an optional trailing line number off of a backslash command.
+ *
+ * Retuns -1 if no line number is present, 0 on error, or a positive value
+ * otherwise.
+ */
+static int
+strip_lineno_from_funcdesc(char *func)
+{
+	char	   *endfunc;
+	char	   *c;
+	int			lineno;
+
+	if (!func)
+		return -1;
+	endfunc = func + strlen(func) - 1;
+	c = endfunc;
+
+	/* skip trailing whitespace */
+	while (c >= func && isblank(*c))
+		c--;
+	if (c == func || !isdigit(*c))
+		return -1;
+
+	/* find start of digit string */
+	while (c >= func && isdigit(*c))
+		c--;
+
+	/* digits must be separated from identifier by blank or closing paren */
+	if (c == func || (!isblank(*c) && *c != ')'))
+		return -1;
+
+	/* parse digit string */
+	lineno = atoi(c + 1);
+	if (lineno < 1)
+	{
+		psql_error("invalid line number\n");
+		return 0;
+	}
+	c[1] = '\0';
+	return lineno;
+}
+
+/*
+ * Returns tag of dollar quoted string used as function body.  We assume
+ * that the function body is returned by pg_get_functiondef() and therefore
+ * must be begin on a line that starts with "AS $function$".  If the line is
+ * not of that format, we return NULL.
+ */
+static char *
+get_functiondef_dollarquote_tag(char *line)
+{
+	char	   *starttag;
+	char	   *endtag;
+	int			len;
+	char	   *result;
+
+	if (strncmp(line, "AS $function", 12) != 0)
+		return NULL;
+	starttag = line + 3;
+	endtag = strchr(line + 12, '$');
+	psql_assert(endtag != NULL);
+
+	len = endtag - starttag + 1;
+	result = pg_malloc(len + 1);
+	memcpy(result, starttag, len);
+	result[len] = '\0';
+
+	return result;
+}
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 19c807d..6a00a1f 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -162,7 +162,7 @@ slashUsage(unsigned short int pager)
 {
 	FILE	   *output;
 
-	output = PageOutput(87, pager);
+	output = PageOutput(89, pager);
 
 	/* if you add/remove a line here, change the row count above */
 
@@ -174,8 +174,8 @@ slashUsage(unsigned short int pager)
 	fprintf(output, "\n");
 
 	fprintf(output, _("Query Buffer\n"));
-	fprintf(output, _("  \\e [FILE]              edit the query buffer (or file) with external editor\n"));
-	fprintf(output, _("  \\ef [FUNCNAME]         edit function definition with external editor\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, _("  \\p                     show the contents of the query buffer\n"));
 	fprintf(output, _("  \\r                     reset (clear) the query buffer\n"));
 #ifdef USE_READLINE
sf.patchapplication/octet-stream; name=sf.patchDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 85adf55..7f8ceab 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2130,6 +2130,23 @@ lo_import 152801
 
 
       <varlistentry>
+        <term><literal>\sf[+] <replaceable class="parameter">function_description</replaceable> <optional> linenumber </optional> </literal></term>
+
+        <listitem>
+        <para>
+         This command displays the definition of the named function,
+         in the form of a <command>CREATE OR REPLACE FUNCTION</> command.
+         If the form <literal>\sf+</literal> is used, the lines are numbered.
+        </para>
+
+        <para>
+         If a line number is specified, the display begins at the specified
+         line of the function.
+        </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 2ee89bc..8ebc9c3 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -46,6 +46,7 @@
 #include "input.h"
 #include "large_obj.h"
 #include "mainloop.h"
+#include "pqsignal.h"
 #include "print.h"
 #include "psqlscan.h"
 #include "settings.h"
@@ -1047,6 +1048,156 @@ exec_command(const char *cmd,
 		free(opt0);
 	}
 
+	/*
+	 * \sf -- show the named function
+	 */
+	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+	{
+		int	first_visible_row = -1;
+		bool	with_lno = (cmd[2] == '+');
+
+		if (!query_buf)
+		{
+			psql_error("no query buffer\n");
+			status = PSQL_CMD_ERROR;
+		}
+		else
+		{
+			char	   *func;
+			Oid			foid = InvalidOid;
+
+			func = psql_scan_slash_option(scan_state,
+										  OT_WHOLE_LINE, NULL, true);
+			
+			first_visible_row = strip_lineno_from_funcdesc(func);
+
+			if (first_visible_row == 0)
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			else if (!func)
+			{
+				/* show error for empty command */
+				psql_error("missing a function name\n");
+				status = PSQL_CMD_ERROR;
+			}
+			else if (!lookup_function_oid(pset.db, func, &foid))
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			else if (!get_create_function_cmd(pset.db, foid, query_buf))
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			if (func)
+				free(func);
+		}
+
+		if (status != PSQL_CMD_ERROR)
+		{
+			int	lineno = 0;
+			char   *line;
+			char   *dqtag = NULL;
+			bool	is_header = true;
+			bool	is_body = false;
+			bool	is_footer = false;
+			char   *end_of_line;
+			int	lines;
+			FILE   *output;
+			bool	is_pager;
+			
+			if (pset.queryFout == stdout)
+			{
+				line = query_buf->data;
+				lines = 0;
+				while (*line != '\0')
+				{
+					lines++;
+					end_of_line = strchr(line, '\n');
+					if (!end_of_line)
+						break;
+					line = end_of_line + 1;
+				}
+
+				output = PageOutput(lines, pset.popt.topt.pager);
+				is_pager = output != stdout;
+			}
+			else
+			{
+				output = pset.queryFout;
+				is_pager = false;
+			}
+
+			line = query_buf->data;
+			while (*line != '\0')
+			{
+				/* find next end of line */
+				end_of_line = strchr(line, '\n');
+				if (end_of_line)
+					*end_of_line = '\0';
+
+				if (is_header)
+				{
+					/* detect end of header */
+					dqtag = get_functiondef_dollarquote_tag(line);
+					if (dqtag)
+					{
+						is_header = false;
+						is_body = true;
+						lineno = 1;
+					}
+				}
+				else if (is_body)
+				{
+					lineno++;
+					if (strcmp(line, dqtag) == 0)
+					{
+						is_body = false;
+						is_footer = true;
+					}
+				}
+
+				/* can we show rows? */
+				if (first_visible_row < 0 || (first_visible_row <= lineno))
+				{
+					if (with_lno)
+					{
+						if (is_header || is_footer)
+							fprintf(output, "**** %s", line);
+						else 
+							fprintf(output, "%4d %s", lineno, line);
+					}
+					else
+						fprintf(output, "%s", line);
+
+					/* return back replaced "\n" */
+					if (end_of_line)
+						fprintf(output, "\n");
+				}
+
+				if (end_of_line)
+					line = end_of_line + 1;
+				else
+					break;
+			}
+
+			/* function pg_get_functiondef uses dollar quoted strings always */
+			psql_assert(dqtag != NULL);
+			free(dqtag);
+
+			if (is_pager)
+			{
+				pclose(output);
+#ifndef WIN32
+				pqsignal(SIGPIPE, SIG_DFL);
+#endif
+			}
+		}
+	}
+
 	/* \t -- turn off headers and row count */
 	else if (strcmp(cmd, "t") == 0)
 	{
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 6a00a1f..9b0645a 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -162,7 +162,7 @@ slashUsage(unsigned short int pager)
 {
 	FILE	   *output;
 
-	output = PageOutput(89, pager);
+	output = PageOutput(90, pager);
 
 	/* if you add/remove a line here, change the row count above */
 
@@ -181,6 +181,7 @@ slashUsage(unsigned short int pager)
 #ifdef USE_READLINE
 	fprintf(output, _("  \\s [FILE]              display history or save it to file\n"));
 #endif
+	fprintf(output, _("  \\sf[+] FUNCNAME [LINE] show function definition\n"));
 	fprintf(output, _("  \\w FILE                write query buffer to file\n"));
 	fprintf(output, "\n");
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b43c478..1ff3dd9 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -644,7 +644,7 @@ psql_completion(char *text, int start, int end)
 		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
 		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
 		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
-		"\\set", "\\t", "\\T",
+		"\\set", "\\sf", "\\t", "\\T",
 		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
 	};
 
@@ -2517,6 +2517,8 @@ psql_completion(char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(my_list);
 	}
+	else if (strncmp(prev_wd, "\\sf", 2) == 0)
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 	else if (strcmp(prev_wd, "\\cd") == 0 ||
 			 strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 0 ||
 			 strcmp(prev_wd, "\\g") == 0 ||
#53Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#52)
Re: review: psql: edit function, show function commands patch

Robert Haas <robertmhaas@gmail.com> writes:

I spent some time cleaning this up tonight. I think that the \e and
\ef portions are now ready to commit, but I am not quite happy with
the \sf stuff yet, so I've broken that out into a separate patch,
which is also attached.

Barring objections, I'll commit the \e and \ef portions of this in the
morning after one final read-through.

The \e patch definitely needs another read-through.  I noticed a number
of comments that were still pretty poor English, and one ---
	/* skip header lines */
--- that seems just plain wrong.  The actual intent of that next bit is
to increase lineno to account for header lines, which is not well
conveyed by "skip".

BTW, at least in the usage in that loop, get_functiondef_dollarquote_tag
seems grossly overdesigned. It would be clearer, shorter, and faster if
you just had a strncmp test for "AS $function" there. Also, the entire
thing is subject to misbehavior in the case of \e (as opposed to \ef),
which really cannot safely assert() that it's reading the output of
pg_get_functiondef(). My inclination is to pull that part out of
do_edit and put it into \ef-specific code.

Also, there seemed to be some gratuitous inconsistency in the handling
of tests on line number variables, eg some places lineno > 0 and others
lineno >= 1.

regards, tom lane

#54Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#53)
1 attachment(s)
Re: review: psql: edit function, show function commands patch

On Tue, Aug 10, 2010 at 11:58 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

The \e patch definitely needs another read-through.  I noticed a number
of comments that were still pretty poor English, and one ---
       /* skip header lines */
--- that seems just plain wrong.  The actual intent of that next bit is
to increase lineno to account for header lines, which is not well
conveyed by "skip".

Interestingly, I had already rewritten pretty much every comment in
the patch, and the entirety of the documentation, but I found a very
small number of stragglers this morning and made a few more
adjustments. If you're still unhappy with it, you're going to need to
be more specific, or hack on it yourself.

BTW, at least in the usage in that loop, get_functiondef_dollarquote_tag
seems grossly overdesigned.  It would be clearer, shorter, and faster if
you just had a strncmp test for "AS $function" there.

As far as I can see, the only purpose of that code is to support the
desire to have \sf+ display **** rather than a line number for the
lines that FOLLOW the function body. But I'm wondering if we should
just forget about that and let the numbering run continuously from the
first "AS $function" line to end of file. That would get rid of a
bunch of rather grotty code in the \sf patch, also.

Also, the entire
thing is subject to misbehavior in the case of \e (as opposed to \ef),
which really cannot safely assert() that it's reading the output of
pg_get_functiondef().  My inclination is to pull that part out of
do_edit and put it into \ef-specific code.

Oh, for pity's sake. I had thought that code WAS \ef-specific
(because it doesn't make any sense otherwise) but I see that you are
correct.

Also, there seemed to be some gratuitous inconsistency in the handling
of tests on line number variables, eg some places lineno > 0 and others
lineno >= 1.

I think this is now fixed.

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

Attachments:

edit8-rmh-v2.patchapplication/octet-stream; name=edit8-rmh-v2.patchDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index b803ef9..85adf55 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1338,7 +1338,7 @@ testdb=&gt;
 
 
       <varlistentry>
-        <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional></literal></term>
+        <term><literal>\edit</literal> (or <literal>\e</literal>) <literal><optional> <replaceable class="parameter">filename</replaceable> </optional> <optional> linenumber </optional></literal></term>
 
         <listitem>
         <para>
@@ -1368,12 +1368,19 @@ testdb=&gt;
         systems, <filename>notepad.exe</filename> on Windows systems.
         </para>
         </tip>
+
+        <para>
+        If a line number is specified, <application>psql</application> will
+        attempt to position the cursor on the specified line of the file.
+        An error will occur if <varname>EDITOR_LINENUMBER_SWITCH</varname> 
+        is not set.
+        </para>
         </listitem>
       </varlistentry>
 
 
       <varlistentry>
-        <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional></literal></term>
+        <term><literal>\ef <optional> <replaceable class="parameter">function_description</replaceable> </optional> <optional> linenumber </optional> </literal></term>
 
         <listitem>
         <para>
@@ -1396,6 +1403,14 @@ testdb=&gt;
          If no function is specified, a blank <command>CREATE FUNCTION</>
          template is presented for editing.
         </para>
+
+        <para>
+        If a line number is specified, <application>psql</application> will
+        attempt to position the cursor on the specified line of the function
+        body (note that the function body typically does not begin on the
+        first line of the file).  An error will occur if
+        <varname>EDITOR_LINENUMBER_SWITCH</varname> is not set.
+        </para>
         </listitem>
       </varlistentry>
 
@@ -2458,6 +2473,25 @@ bar
       </varlistentry>
 
       <varlistentry>
+        <term><varname>EDITOR_LINENUMBER_SWITCH</varname></term>
+        <listitem>
+        <para>
+        When <command>\edit</command> or <command>\ef</command> is used with
+        a line number argument, this variable specifies the switch used to
+        pass the line number to the user's editor.  For editors such as
+        <productname>emacs</> or <productname>vi</>, you can simply set this
+        variable to a single plus sign.  In some cases, it might be necessary
+        to include a trailing space in the value assigned to this variable.
+        For example:
+
+<programlisting>
+\set EDIT_LINENUMBER_SWITCH '--line '
+</programlisting>
+        </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
         <term><varname>ENCODING</varname></term>
         <listitem>
         <para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index a3e55c5..697a906 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -57,7 +57,7 @@ static backslashResult exec_command(const char *cmd,
 			 PsqlScanState scan_state,
 			 PQExpBuffer query_buf);
 static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
-		bool *edited);
+		int lineno, bool *edited);
 static bool do_connect(char *dbname, char *user, char *host, char *port);
 static bool do_shell(const char *command);
 static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
@@ -66,6 +66,9 @@ static void minimal_error_message(PGresult *res);
 
 static void printSSLInfo(void);
 
+static int strip_lineno_from_funcdesc(char *func);
+static char *get_functiondef_dollarquote_tag(char *line);
+
 #ifdef WIN32
 static void checkWin32Codepage(void);
 #endif
@@ -510,17 +513,39 @@ exec_command(const char *cmd,
 		else
 		{
 			char	   *fname;
+			char	   *ln;
+			int			lineno = -1;
 
 			fname = psql_scan_slash_option(scan_state,
 										   OT_NORMAL, NULL, true);
-			expand_tilde(&fname);
+
+			/* try to get lineno */
 			if (fname)
-				canonicalize_path(fname);
-			if (do_edit(fname, query_buf, NULL))
-				status = PSQL_CMD_NEWEDIT;
-			else
-				status = PSQL_CMD_ERROR;
-			free(fname);
+			{
+				ln = psql_scan_slash_option(scan_state,
+									   OT_NORMAL, NULL, true);
+				if (ln)
+				{
+					lineno = atoi(ln);
+					if (lineno < 1)
+					{
+						psql_error("invalid line number\n");
+						status = PSQL_CMD_ERROR;
+					}
+				}
+			}
+			if (status != PSQL_CMD_ERROR)
+			{
+				expand_tilde(&fname);
+				if (fname)
+					canonicalize_path(fname);
+				if (do_edit(fname, query_buf, lineno, NULL))
+					status = PSQL_CMD_NEWEDIT;
+				else
+					status = PSQL_CMD_ERROR;
+			}
+			if (fname)
+				free(fname);
 		}
 	}
 
@@ -530,6 +555,8 @@ exec_command(const char *cmd,
 	 */
 	else if (strcmp(cmd, "ef") == 0)
 	{
+		int	lineno = -1;		/* keep compiler quiet */
+
 		if (!query_buf)
 		{
 			psql_error("no query buffer\n");
@@ -542,7 +569,13 @@ exec_command(const char *cmd,
 
 			func = psql_scan_slash_option(scan_state,
 										  OT_WHOLE_LINE, NULL, true);
-			if (!func)
+			lineno = strip_lineno_from_funcdesc(func);
+			if (lineno == 0)
+			{
+				/* error already reported */
+				status = PSQL_CMD_ERROR;
+			}
+			else if (!func)
 			{
 				/* set up an empty command to fill in */
 				printfPQExpBuffer(query_buf,
@@ -571,7 +604,7 @@ exec_command(const char *cmd,
 		{
 			bool		edited = false;
 
-			if (!do_edit(0, query_buf, &edited))
+			if (!do_edit(0, query_buf, lineno, &edited))
 				status = PSQL_CMD_ERROR;
 			else if (!edited)
 				puts(_("No changes"));
@@ -1543,11 +1576,11 @@ UnsyncVariables(void)
  * If you do not specify a filename, the current query buffer will be copied
  * into a temporary one.
  */
-
 static bool
-editFile(const char *fname)
+editFile(const char *fname, int lineno)
 {
 	const char *editorName;
+	const char *editor_lineno_switch = NULL;	/* be compiler quiet */
 	char	   *sys;
 	int			result;
 
@@ -1562,6 +1595,25 @@ editFile(const char *fname)
 	if (!editorName)
 		editorName = DEFAULT_EDITOR;
 
+	/* Get line number switch, if we need it. */
+	if (lineno > 0)
+	{
+		editor_lineno_switch = GetVariable(pset.vars,
+										   "EDITOR_LINENUMBER_SWITCH");
+		if (editor_lineno_switch == NULL)
+		{
+			psql_error("EDITOR_LINENUMBER_SWITCH variable is not set\n");
+			return false;
+		}
+	}
+
+	/* Allocate sufficient memory for command line. */
+	if (lineno > 1)
+		sys = pg_malloc(strlen(editorName) + strlen(editor_lineno_switch) + 10
+						+ 1 + strlen(fname) + 10 + 1);
+	else
+		sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
+
 	/*
 	 * On Unix the EDITOR value should *not* be quoted, since it might include
 	 * switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
@@ -1569,11 +1621,20 @@ editFile(const char *fname)
 	 * severe brain damage in their command shell plus the fact that standard
 	 * program paths include spaces.
 	 */
-	sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
 #ifndef WIN32
-	sprintf(sys, "exec %s '%s'", editorName, fname);
+	if (lineno > 0)
+		sprintf(sys, "exec %s %s%d '%s'", editorName, editor_lineno_switch, lineno, fname);
+	else
+		sprintf(sys, "exec %s '%s'", editorName, fname);
 #else
-	sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
+	if (lineno > 0)
+		sprintf(sys, SYSTEMQUOTE "\"%s\" %s%d \"%s\"" SYSTEMQUOTE, 
+									    editorName, 
+									    editor_lineno_switch,
+									    lineno, 
+									    fname);
+	else
+		sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE, editorName, fname);
 #endif
 	result = system(sys);
 	if (result == -1)
@@ -1588,7 +1649,7 @@ editFile(const char *fname)
 
 /* call this one */
 static bool
-do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
+do_edit(const char *filename_arg, PQExpBuffer query_buf, int lineno, bool *edited)
 {
 	char		fnametmp[MAXPGPATH];
 	FILE	   *stream = NULL;
@@ -1678,9 +1739,34 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf, bool *edited)
 		error = true;
 	}
 
+	/* adjust line number based on start of actual function body */
+	if (!error && lineno != -1)
+	{
+		char   *lines = query_buf->data;
+		char	*dqtag;
+
+		/* skip header lines */
+		while (*lines != '\0')
+		{
+			dqtag = get_functiondef_dollarquote_tag(lines);
+			if (dqtag)
+			{
+				free(dqtag);
+				break;
+			}
+			lineno++;
+
+			/* find start of next line */
+			lines = strchr(lines, '\n');
+			if (!lines)
+				break;
+			lines++;
+		}
+	}
+
 	/* call editor */
 	if (!error)
-		error = !editFile(fname);
+		error = !editFile(fname, lineno);
 
 	if (!error && stat(fname, &after) != 0)
 	{
@@ -2236,3 +2322,75 @@ minimal_error_message(PGresult *res)
 
 	destroyPQExpBuffer(msg);
 }
+
+
+/*
+ * Strips an optional trailing line number off of a backslash command.
+ *
+ * Retuns -1 if no line number is present, 0 on error, or a positive value
+ * otherwise.
+ */
+static int
+strip_lineno_from_funcdesc(char *func)
+{
+	char	   *endfunc;
+	char	   *c;
+	int			lineno;
+
+	if (!func)
+		return -1;
+	endfunc = func + strlen(func) - 1;
+	c = endfunc;
+
+	/* skip trailing whitespace */
+	while (c >= func && isblank(*c))
+		c--;
+	if (c == func || !isdigit(*c))
+		return -1;
+
+	/* find start of digit string */
+	while (c >= func && isdigit(*c))
+		c--;
+
+	/* digits must be separated from identifier by blank or closing paren */
+	if (c == func || (!isblank(*c) && *c != ')'))
+		return -1;
+
+	/* parse digit string */
+	lineno = atoi(c + 1);
+	if (lineno < 1)
+	{
+		psql_error("invalid line number\n");
+		return 0;
+	}
+	c[1] = '\0';
+	return lineno;
+}
+
+/*
+ * Returns tag of dollar quoted string used as function body.  We assume
+ * that the function body is returned by pg_get_functiondef() and therefore
+ * must be begin on a line that starts with "AS $function$".  If the line is
+ * not of that format, we return NULL.
+ */
+static char *
+get_functiondef_dollarquote_tag(char *line)
+{
+	char	   *starttag;
+	char	   *endtag;
+	int			len;
+	char	   *result;
+
+	if (strncmp(line, "AS $function", 12) != 0)
+		return NULL;
+	starttag = line + 3;
+	endtag = strchr(line + 12, '$');
+	psql_assert(endtag != NULL);
+
+	len = endtag - starttag + 1;
+	result = pg_malloc(len + 1);
+	memcpy(result, starttag, len);
+	result[len] = '\0';
+
+	return result;
+}
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 19c807d..6a00a1f 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -162,7 +162,7 @@ slashUsage(unsigned short int pager)
 {
 	FILE	   *output;
 
-	output = PageOutput(87, pager);
+	output = PageOutput(89, pager);
 
 	/* if you add/remove a line here, change the row count above */
 
@@ -174,8 +174,8 @@ slashUsage(unsigned short int pager)
 	fprintf(output, "\n");
 
 	fprintf(output, _("Query Buffer\n"));
-	fprintf(output, _("  \\e [FILE]              edit the query buffer (or file) with external editor\n"));
-	fprintf(output, _("  \\ef [FUNCNAME]         edit function definition with external editor\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, _("  \\p                     show the contents of the query buffer\n"));
 	fprintf(output, _("  \\r                     reset (clear) the query buffer\n"));
 #ifdef USE_READLINE
#55Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#54)
Re: review: psql: edit function, show function commands patch

Robert Haas <robertmhaas@gmail.com> writes:

On Tue, Aug 10, 2010 at 11:58 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

BTW, at least in the usage in that loop, get_functiondef_dollarquote_tag
seems grossly overdesigned. �It would be clearer, shorter, and faster if
you just had a strncmp test for "AS $function" there.

As far as I can see, the only purpose of that code is to support the
desire to have \sf+ display **** rather than a line number for the
lines that FOLLOW the function body. But I'm wondering if we should
just forget about that and let the numbering run continuously from the
first "AS $function" line to end of file. That would get rid of a
bunch of rather grotty code in the \sf patch, also.

Oh? Considering that in the standard pg_get_functiondef output, the
ending $function$ delimiter is always on the very last line, that sounds
pretty useless. +1 for just numbering forward from the start line.

BTW, the last I looked, \sf+ was using what I thought to be a quite ugly
and poorly-considered formatting for the line number. I would suggest
eight blanks for a header line and "%-7d " as the prefix format for a
numbered line. The reason for making sure the prefix is 8 columns rather
than some other width is to not mess up tab-based formatting of the
function body. I would also prefer a lot more visual separation between
the line number and the code than "%4d " will offer; and as for the
stars, they're just useless and distracting.

regards, tom lane

#56Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#55)
Re: review: psql: edit function, show function commands patch

On Wed, Aug 11, 2010 at 12:28 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Tue, Aug 10, 2010 at 11:58 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

BTW, at least in the usage in that loop, get_functiondef_dollarquote_tag
seems grossly overdesigned.  It would be clearer, shorter, and faster if
you just had a strncmp test for "AS $function" there.

As far as I can see, the only purpose of that code is to support the
desire to have \sf+ display **** rather than a line number for the
lines that FOLLOW the function body.  But I'm wondering if we should
just forget about that and let the numbering run continuously from the
first "AS $function" line to end of file.  That would get rid of a
bunch of rather grotty code in the \sf patch, also.

Oh?  Considering that in the standard pg_get_functiondef output, the
ending $function$ delimiter is always on the very last line, that sounds
pretty useless.  +1 for just numbering forward from the start line.

OK.

BTW, the last I looked, \sf+ was using what I thought to be a quite ugly
and poorly-considered formatting for the line number.  I would suggest
eight blanks for a header line and "%-7d " as the prefix format for a
numbered line.  The reason for making sure the prefix is 8 columns rather
than some other width is to not mess up tab-based formatting of the
function body.  I would also prefer a lot more visual separation between
the line number and the code than "%4d " will offer; and as for the
stars, they're just useless and distracting.

I don't have a strong preference, but that seems reasonable. I
suggest that we punt the \sf portion of this patch back for rework for
the next CommitFest, and focus on getting the \e and \ef changes
committed. I think the \sf code can be a lot simpler if we get rid of
the code that's intended to recognize the ending delimeter.

Another thought is that we might want to add a comment to
pg_get_functiondef() noting that anyone changing the output format
should be careful not to break the line-number-finding form of \ef in
the process.

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

#57Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#54)
Re: review: psql: edit function, show function commands patch

Robert Haas <robertmhaas@gmail.com> writes:

... If you're still unhappy with it, you're going to need to
be more specific, or hack on it yourself.

I'm doing another pass over this. I notice that the documentation
claims the syntax of \e is "\e [FILE] [LINE]", but what is actually
implemented is "\e [FILE [LINE]]", ie it is not possible to specify a
line number without a file. Now, it seems to me that specifying a line
number in the query buffer would actually be a pretty darn useful thing
to do, if you'd typed in a large query and the backend had spit back
"LINE 42: some problem or other". So I think we should fix it so that
case works and the documentation isn't lying. This would require
interpreting \e followed by a digit string as a line number not a file
... anybody have a problem with that? If you're really eager to edit a
numerically-named file you could fake it out with "\e 1234 1".

BTW, there doesn't seem to be a need to do anything similar for \ef.
It does have the ability to omit a func name, but then you get a blank
CREATE FUNCTION template you're going to have to fill in, so there's
no advantage to positioning the cursor beyond the first line to start.

regards, tom lane

#58Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#57)
Re: review: psql: edit function, show function commands patch

On Wed, Aug 11, 2010 at 6:21 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

...  If you're still unhappy with it, you're going to need to
be more specific, or hack on it yourself.

I'm doing another pass over this.  I notice that the documentation
claims the syntax of \e is "\e [FILE] [LINE]", but what is actually
implemented is "\e [FILE [LINE]]", ie it is not possible to specify a
line number without a file.  Now, it seems to me that specifying a line
number in the query buffer would actually be a pretty darn useful thing
to do, if you'd typed in a large query and the backend had spit back
"LINE 42: some problem or other".  So I think we should fix it so that
case works and the documentation isn't lying.  This would require
interpreting \e followed by a digit string as a line number not a file
... anybody have a problem with that?  If you're really eager to edit a
numerically-named file you could fake it out with "\e 1234 1".

Or \e ./1234

It's a minor incompatibility, but it's probably reasonable to allow that.

BTW, there doesn't seem to be a need to do anything similar for \ef.
It does have the ability to omit a func name, but then you get a blank
CREATE FUNCTION template you're going to have to fill in, so there's
no advantage to positioning the cursor beyond the first line to start.

Hmm, OK.

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

#59Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#56)
Re: review: psql: edit function, show function commands patch

Robert Haas <robertmhaas@gmail.com> writes:

I suggest that we punt the \sf portion of this patch back for rework for
the next CommitFest, and focus on getting the \e and \ef changes
committed. I think the \sf code can be a lot simpler if we get rid of
the code that's intended to recognize the ending delimeter.

I've committed the \e/\ef part after some further tweaking. I concur
with marking the \sf part as Returned With Feedback.

Another thought is that we might want to add a comment to
pg_get_functiondef() noting that anyone changing the output format
should be careful not to break the line-number-finding form of \ef in
the process.

Done.

regards, tom lane

#60Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#56)
Re: review: psql: edit function, show function commands patch

2010/8/11 Robert Haas <robertmhaas@gmail.com>:

On Wed, Aug 11, 2010 at 12:28 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Tue, Aug 10, 2010 at 11:58 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

BTW, at least in the usage in that loop, get_functiondef_dollarquote_tag
seems grossly overdesigned.  It would be clearer, shorter, and faster if
you just had a strncmp test for "AS $function" there.

As far as I can see, the only purpose of that code is to support the
desire to have \sf+ display **** rather than a line number for the
lines that FOLLOW the function body.  But I'm wondering if we should
just forget about that and let the numbering run continuously from the
first "AS $function" line to end of file.  That would get rid of a
bunch of rather grotty code in the \sf patch, also.

Oh?  Considering that in the standard pg_get_functiondef output, the
ending $function$ delimiter is always on the very last line, that sounds
pretty useless.  +1 for just numbering forward from the start line.

OK.

BTW, the last I looked, \sf+ was using what I thought to be a quite ugly
and poorly-considered formatting for the line number.  I would suggest
eight blanks for a header line and "%-7d " as the prefix format for a
numbered line.  The reason for making sure the prefix is 8 columns rather
than some other width is to not mess up tab-based formatting of the
function body.  I would also prefer a lot more visual separation between
the line number and the code than "%4d " will offer; and as for the
stars, they're just useless and distracting.

I don't have a strong preference, but that seems reasonable.  I
suggest that we punt the \sf portion of this patch back for rework for
the next CommitFest, and focus on getting the \e and \ef changes
committed.  I think the \sf code can be a lot simpler if we get rid of
the code that's intended to recognize the ending delimeter.

the proposed changes are not complex, and there are not reason to move
\sf to next commitfest. I am thinking about little bit simplification
- there can by only one cycle without two. After \e commiting there
are other complex code. If some code isn't clean, then it is because
there are \o and pager support.

Another thought is that we might want to add a comment to
pg_get_functiondef() noting that anyone changing the output format
should be careful not to break the line-number-finding form of \ef in
the process.

+1

Regards

Pavel Stehule

Show quoted text

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

#61Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#55)
1 attachment(s)
Re: review: psql: edit function, show function commands patch

Hello

attached updated \sf implementation. It is little bit simplyfied with
support a pager and output forwarding. Formating was updated per Tom's
request.

Regards

Pavel Stehule

Show quoted text

BTW, the last I looked, \sf+ was using what I thought to be a quite ugly
and poorly-considered formatting for the line number.  I would suggest
eight blanks for a header line and "%-7d " as the prefix format for a
numbered line.  The reason for making sure the prefix is 8 columns rather
than some other width is to not mess up tab-based formatting of the
function body.  I would also prefer a lot more visual separation between
the line number and the code than "%4d " will offer; and as for the
stars, they're just useless and distracting.

                       regards, tom lane

Attachments:

showfuncdef.difftext/x-patch; charset=US-ASCII; name=showfuncdef.diffDownload
*** ./doc/src/sgml/ref/psql-ref.sgml.orig	2010-08-12 02:40:59.000000000 +0200
--- ./doc/src/sgml/ref/psql-ref.sgml	2010-08-12 15:01:04.339404200 +0200
***************
*** 2100,2105 ****
--- 2100,2131 ----
  
  
        <varlistentry>
+         <term><literal>\sf[+] <optional> <replaceable class="parameter">function_description</> <optional>  <replaceable class="parameter">line_number</> </optional> </optional> </literal></term>
+ 
+         <listitem>
+         <para>
+          This command fetches and shows the definition of the named function,
+          in the form of a <command>CREATE OR REPLACE FUNCTION</> command.
+          If <literal>+</literal> is appended to the command name, then output
+          lines has a line number.
+         </para>
+ 
+         <para>
+          The target function can be specified by name alone, or by name
+          and arguments, for example <literal>foo(integer, text)</>.
+          The argument types must be given if there is more
+          than one function of the same name.
+         </para>
+ 
+         <para>
+         If a line number is specified, <application>psql</application> will
+         show the specified line as first line. Previous lines are skiped.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+ 
+       <varlistentry>
          <term><literal>\set [ <replaceable class="parameter">name</replaceable> [ <replaceable class="parameter">value</replaceable> [ ... ] ] ]</literal></term>
  
          <listitem>
*** ./src/bin/psql/command.c.orig	2010-08-12 02:40:59.000000000 +0200
--- ./src/bin/psql/command.c	2010-08-12 14:39:22.334403954 +0200
***************
*** 32,37 ****
--- 32,38 ----
  #ifdef USE_SSL
  #include <openssl/ssl.h>
  #endif
+ #include <signal.h>
  
  #include "portability/instr_time.h"
  
***************
*** 46,51 ****
--- 47,53 ----
  #include "input.h"
  #include "large_obj.h"
  #include "mainloop.h"
+ #include "pqsignal.h"
  #include "print.h"
  #include "psqlscan.h"
  #include "settings.h"
***************
*** 1083,1088 ****
--- 1085,1232 ----
  		free(opt0);
  	}
  
+ 	/* \sf = show a function source code */
+ 	else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+ 	{
+ 		bool show_lineno;
+ 		int	first_visible_line = -1;
+ 		
+ 		show_lineno = (strcmp(cmd, "sf+") == 0);
+ 		
+ 		if (!query_buf)
+ 		{
+ 			psql_error("no query buffer\n");
+ 			status = PSQL_CMD_ERROR;
+ 		}
+ 		else
+ 		{
+ 			char	*func;
+ 			Oid		foid = InvalidOid;
+ 			
+ 			func = psql_scan_slash_option(scan_state,
+ 									OT_WHOLE_LINE, NULL, true);
+ 			first_visible_line = strip_lineno_from_funcdesc(func);
+ 			if (first_visible_line == 0)
+ 			{
+ 				/* error already reported */
+ 				status = PSQL_CMD_ERROR;
+ 			}
+ 			else if (!func)
+ 			{
+ 				psql_error("missing a function name\n");
+ 				status = PSQL_CMD_ERROR;
+ 			}
+ 			else if (!lookup_function_oid(pset.db, func, &foid))
+ 			{
+ 				/* error already reported */
+ 				status = PSQL_CMD_ERROR;
+ 			}
+ 			else if (!get_create_function_cmd(pset.db, foid, query_buf))
+ 			{
+ 				/* error already reported */
+ 				status = PSQL_CMD_ERROR;
+ 			}
+ 			
+ 			if (func)
+ 				free(func);
+ 			
+ 			if (status != PSQL_CMD_ERROR)
+ 			{
+ 				FILE *output;
+ 				bool	is_pager = false;
+ 				
+ 				/*
+ 				 * Count a lines in function definition - it's used for opening
+ 				 * a pager. Get a output stream - stdout, pager or forwarded output.
+ 				 */
+ 				if (pset.queryFout == stdout)
+ 				{
+ 					int	lc = 0;
+ 					const char *lines = query_buf->data;
+ 					
+ 					while (*lines != '\0')
+ 					{
+ 						lc++;
+ 						/* find start of next line */
+ 						lines = strchr(lines, '\n');
+ 						if (!lines)
+ 							break;
+ 						lines++;
+ 					}
+ 					
+ 					output = PageOutput(lc, pset.popt.topt.pager);
+ 					is_pager = output != stdout;
+ 				}
+ 				else
+ 				{
+ 					/* use a prepared query output, pager isn't activated */
+ 					output = pset.queryFout;
+ 					is_pager = false;
+ 				}
+ 				
+ 				if (first_visible_line > 0 || show_lineno)
+ 				{
+ 					bool	is_header = true;		/* true, when header lines is processed */
+ 					int	lineno = 0;
+ 					char *lines = query_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 $function" is real start of the function body.
+ 					 */
+ 					while (*lines != '\0')
+ 					{
+ 						char *eol;
+ 						
+ 						if (is_header && strncmp(lines, "AS $function", 12) == 0)
+ 							is_header = false;
+ 						
+ 						/* increment lineno only for body's lines */
+ 						if (!is_header)
+ 							lineno++;
+ 						/* find a end of current line */
+ 						eol = strchr(lines, '\n');
+ 						/* show a current line, when it is desirable */
+ 						if (first_visible_line == -1 || lineno >= first_visible_line)
+ 						{
+ 							/* disjoin a current line from a next line */
+ 							if (eol != NULL)
+ 								*eol = '\0';
+ 							if (!show_lineno)
+ 								fprintf(output, "%s", lines);
+ 							else if (is_header)
+ 								fprintf(output, "        %s", lines);
+ 							else
+ 								fprintf(output, "%-7d %s", lineno, lines);
+ 							/* print a replaced \n char */
+ 							if (eol)
+ 								fprintf(output, "\n");
+ 						}
+ 						/* leave when there are not a next line */
+ 						if (!eol)
+ 							break;
+ 						/* move to next line */
+ 						lines = ++eol;
+ 					}
+ 				}
+ 				else
+ 				{
+ 					/* just send a function definition to output */
+ 					fprintf(output, "%s", query_buf->data);
+ 				}
+ 				
+ 				if (is_pager)
+ 				{
+ 					pclose(output);
+ #ifndef WIN32
+ 					pqsignal(SIGPIPE, SIG_DFL);
+ #endif
+ 				}
+ 			}
+ 		}
+ 	}
+ 
  	/* \t -- turn off headers and row count */
  	else if (strcmp(cmd, "t") == 0)
  	{
*** ./src/bin/psql/help.c.orig	2010-08-12 02:40:59.000000000 +0200
--- ./src/bin/psql/help.c	2010-08-12 14:53:25.491278641 +0200
***************
*** 162,168 ****
  {
  	FILE	   *output;
  
! 	output = PageOutput(89, pager);
  
  	/* if you add/remove a line here, change the row count above */
  
--- 162,168 ----
  {
  	FILE	   *output;
  
! 	output = PageOutput(90, pager);
  
  	/* if you add/remove a line here, change the row count above */
  
***************
*** 223,228 ****
--- 223,229 ----
  	fprintf(output, _("  \\dT[S+] [PATTERN]      list data types\n"));
  	fprintf(output, _("  \\du[+]  [PATTERN]      list roles (users)\n"));
  	fprintf(output, _("  \\dv[S+] [PATTERN]      list views\n"));
+ 	fprintf(output, _("  \\sf[+] FUNCNAME [LINE]] show the function definition\n"));
  	fprintf(output, _("  \\l[+]                  list all databases\n"));
  	fprintf(output, _("  \\z      [PATTERN]      same as \\dp\n"));
  	fprintf(output, "\n");
*** ./src/bin/psql/tab-complete.c.orig	2010-07-20 05:54:19.000000000 +0200
--- ./src/bin/psql/tab-complete.c	2010-08-12 14:49:19.490403596 +0200
***************
*** 644,650 ****
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
--- 644,650 ----
  		"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
  		"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
  		"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
! 		"\\set", "\\sf", "\\t", "\\T",
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
***************
*** 2501,2506 ****
--- 2501,2508 ----
  
  	else if (strcmp(prev_wd, "\\ef") == 0)
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+ 	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, "\\encoding") == 0)
  		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
#62Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#61)
Re: review: psql: edit function, show function commands patch

Pavel Stehule <pavel.stehule@gmail.com> writes:

attached updated \sf implementation. It is little bit simplyfied with
support a pager and output forwarding.

The line number argument to this greatly complicates the code but
doesn't appear to me to have much practical use. Why would you bother
with that?

regards, tom lane

#63Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#61)
Re: review: psql: edit function, show function commands patch

Pavel Stehule <pavel.stehule@gmail.com> writes:

attached updated \sf implementation. It is little bit simplyfied with
support a pager and output forwarding. Formating was updated per Tom's
request.

Applied with corrections --- mostly, fixing it to not trash the query
buffer, which would certainly not be expected behavior for such a
command.

Also, as previously mentioned, I took out the line number argument,
which seemed to me to have little if any use-case while substantially
complicating the code. (It's not so much that it cost a lot in the
patch as submitted, it's that it *would* cost a lot if you were
honoring it in the pager calculation...)

One other thing: I took a look at the pg_get_functiondef() code and
realized that in fact it does NOT guarantee that the function body
will start with "AS $function...". In languages with a nonnull
probin field, that's not what the line will look like. I think though
that looking for just "AS " at the start of the line is sufficient
for our purposes here. I will go change the \ef and \sf code for
that.

regards, tom lane