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