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 <robertmh...@gmail.com>:
> On Sun, Aug 1, 2010 at 10:47 AM, Tom Lane <t...@sss.pgh.pa.us> wrote:
>> Pavel Stehule <pavel.steh...@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
>
*** ./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;
*** ./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);
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to