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 <[email protected]>:
> On Sun, Aug 1, 2010 at 10:47 AM, Tom Lane <[email protected]> wrote:
>> Pavel Stehule <[email protected]> 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 ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers