Skip to content

Commit 303b26c

Browse files
committed
Fix psql's \sf and \ef for new-style SQL functions.
Some options of these commands need to be able to identify the start of the function body within the output of pg_get_functiondef(). It used to be that that always began with "AS", but since the introduction of new-style SQL functions, it might also start with "BEGIN" or "RETURN". Fix that on the psql side, and add some regression tests. Noted by me awhile ago, but I didn't do anything about it. Thanks to David Johnston for a nag. Discussion: https://postgr.es/m/AM9PR01MB8268D5CDABDF044EE9F42173FE8C9@AM9PR01MB8268.eurprd01.prod.exchangelabs.com
1 parent 47e1224 commit 303b26c

File tree

4 files changed

+94
-26
lines changed

4 files changed

+94
-26
lines changed

src/backend/utils/adt/ruleutils.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2778,8 +2778,8 @@ pg_get_serial_sequence(PG_FUNCTION_ARGS)
27782778
*
27792779
* Note: if you change the output format of this function, be careful not
27802780
* to break psql's rules (in \ef and \sf) for identifying the start of the
2781-
* function body. To wit: the function body starts on a line that begins
2782-
* with "AS ", and no preceding line will look like that.
2781+
* function body. To wit: the function body starts on a line that begins with
2782+
* "AS ", "BEGIN ", or "RETURN ", and no preceding line will look like that.
27832783
*/
27842784
Datum
27852785
pg_get_functiondef(PG_FUNCTION_ARGS)

src/bin/psql/command.c

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,7 @@ static bool get_create_object_cmd(EditableObjectType obj_type, Oid oid,
164164
PQExpBuffer buf);
165165
static int strip_lineno_from_objdesc(char *obj);
166166
static int count_lines_in_buf(PQExpBuffer buf);
167-
static void print_with_linenumbers(FILE *output, char *lines,
168-
const char *header_keyword);
167+
static void print_with_linenumbers(FILE *output, char *lines, bool is_func);
169168
static void minimal_error_message(PGresult *res);
170169

171170
static void printSSLInfo(void);
@@ -1165,17 +1164,19 @@ exec_command_ef_ev(PsqlScanState scan_state, bool active_branch,
11651164
/*
11661165
* lineno "1" should correspond to the first line of the
11671166
* function body. We expect that pg_get_functiondef() will
1168-
* emit that on a line beginning with "AS ", and that there
1169-
* can be no such line before the real start of the function
1170-
* body. Increment lineno by the number of lines before that
1171-
* line, so that it becomes relative to the first line of the
1172-
* function definition.
1167+
* emit that on a line beginning with "AS ", "BEGIN ", or
1168+
* "RETURN ", and that there can be no such line before the
1169+
* real start of the function body. Increment lineno by the
1170+
* number of lines before that line, so that it becomes
1171+
* relative to the first line of the function definition.
11731172
*/
11741173
const char *lines = query_buf->data;
11751174

11761175
while (*lines != '\0')
11771176
{
1178-
if (strncmp(lines, "AS ", 3) == 0)
1177+
if (strncmp(lines, "AS ", 3) == 0 ||
1178+
strncmp(lines, "BEGIN ", 6) == 0 ||
1179+
strncmp(lines, "RETURN ", 7) == 0)
11791180
break;
11801181
lineno++;
11811182
/* find start of next line */
@@ -2452,15 +2453,8 @@ exec_command_sf_sv(PsqlScanState scan_state, bool active_branch,
24522453

24532454
if (show_linenumbers)
24542455
{
2455-
/*
2456-
* For functions, lineno "1" should correspond to the first
2457-
* line of the function body. We expect that
2458-
* pg_get_functiondef() will emit that on a line beginning
2459-
* with "AS ", and that there can be no such line before the
2460-
* real start of the function body.
2461-
*/
2462-
print_with_linenumbers(output, buf->data,
2463-
is_func ? "AS " : NULL);
2456+
/* add line numbers */
2457+
print_with_linenumbers(output, buf->data, is_func);
24642458
}
24652459
else
24662460
{
@@ -5353,24 +5347,28 @@ count_lines_in_buf(PQExpBuffer buf)
53535347
/*
53545348
* Write text at *lines to output with line numbers.
53555349
*
5356-
* If header_keyword isn't NULL, then line 1 should be the first line beginning
5357-
* with header_keyword; lines before that are unnumbered.
5350+
* For functions, lineno "1" should correspond to the first line of the
5351+
* function body; lines before that are unnumbered. We expect that
5352+
* pg_get_functiondef() will emit that on a line beginning with "AS ",
5353+
* "BEGIN ", or "RETURN ", and that there can be no such line before
5354+
* the real start of the function body.
53585355
*
53595356
* Caution: this scribbles on *lines.
53605357
*/
53615358
static void
5362-
print_with_linenumbers(FILE *output, char *lines,
5363-
const char *header_keyword)
5359+
print_with_linenumbers(FILE *output, char *lines, bool is_func)
53645360
{
5365-
bool in_header = (header_keyword != NULL);
5366-
size_t header_sz = in_header ? strlen(header_keyword) : 0;
5361+
bool in_header = is_func;
53675362
int lineno = 0;
53685363

53695364
while (*lines != '\0')
53705365
{
53715366
char *eol;
53725367

5373-
if (in_header && strncmp(lines, header_keyword, header_sz) == 0)
5368+
if (in_header &&
5369+
(strncmp(lines, "AS ", 3) == 0 ||
5370+
strncmp(lines, "BEGIN ", 6) == 0 ||
5371+
strncmp(lines, "RETURN ", 7) == 0))
53745372
in_header = false;
53755373

53765374
/* increment lineno only for body's lines */

src/test/regress/expected/psql.out

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5165,6 +5165,13 @@ List of access methods
51655165
pg_catalog | bit_xor | smallint | smallint | agg
51665166
(3 rows)
51675167

5168+
\df *._pg_expandarray
5169+
List of functions
5170+
Schema | Name | Result data type | Argument data types | Type
5171+
--------------------+-----------------+------------------+-------------------------------------------+------
5172+
information_schema | _pg_expandarray | SETOF record | anyarray, OUT x anyelement, OUT n integer | func
5173+
(1 row)
5174+
51685175
\do - pg_catalog.int4
51695176
List of operators
51705177
Schema | Name | Left arg type | Right arg type | Result type | Description
@@ -5179,6 +5186,61 @@ List of access methods
51795186
pg_catalog | && | anyarray | anyarray | boolean | overlaps
51805187
(1 row)
51815188

5189+
-- check \sf
5190+
\sf information_schema._pg_expandarray
5191+
CREATE OR REPLACE FUNCTION information_schema._pg_expandarray(anyarray, OUT x anyelement, OUT n integer)
5192+
RETURNS SETOF record
5193+
LANGUAGE sql
5194+
IMMUTABLE PARALLEL SAFE STRICT
5195+
AS $function$select $1[s],
5196+
s operator(pg_catalog.-) pg_catalog.array_lower($1,1) operator(pg_catalog.+) 1
5197+
from pg_catalog.generate_series(pg_catalog.array_lower($1,1),
5198+
pg_catalog.array_upper($1,1),
5199+
1) as g(s)$function$
5200+
\sf+ information_schema._pg_expandarray
5201+
CREATE OR REPLACE FUNCTION information_schema._pg_expandarray(anyarray, OUT x anyelement, OUT n integer)
5202+
RETURNS SETOF record
5203+
LANGUAGE sql
5204+
IMMUTABLE PARALLEL SAFE STRICT
5205+
1 AS $function$select $1[s],
5206+
2 s operator(pg_catalog.-) pg_catalog.array_lower($1,1) operator(pg_catalog.+) 1
5207+
3 from pg_catalog.generate_series(pg_catalog.array_lower($1,1),
5208+
4 pg_catalog.array_upper($1,1),
5209+
5 1) as g(s)$function$
5210+
\sf+ interval_pl_time
5211+
CREATE OR REPLACE FUNCTION pg_catalog.interval_pl_time(interval, time without time zone)
5212+
RETURNS time without time zone
5213+
LANGUAGE sql
5214+
IMMUTABLE PARALLEL SAFE STRICT COST 1
5215+
1 RETURN ($2 + $1)
5216+
\sf ts_debug(text)
5217+
CREATE OR REPLACE FUNCTION pg_catalog.ts_debug(document text, OUT alias text, OUT description text, OUT token text, OUT dictionaries regdictionary[], OUT dictionary regdictionary, OUT lexemes text[])
5218+
RETURNS SETOF record
5219+
LANGUAGE sql
5220+
STABLE PARALLEL SAFE STRICT
5221+
BEGIN ATOMIC
5222+
SELECT ts_debug.alias,
5223+
ts_debug.description,
5224+
ts_debug.token,
5225+
ts_debug.dictionaries,
5226+
ts_debug.dictionary,
5227+
ts_debug.lexemes
5228+
FROM ts_debug(get_current_ts_config(), ts_debug.document) ts_debug(alias, description, token, dictionaries, dictionary, lexemes);
5229+
END
5230+
\sf+ ts_debug(text)
5231+
CREATE OR REPLACE FUNCTION pg_catalog.ts_debug(document text, OUT alias text, OUT description text, OUT token text, OUT dictionaries regdictionary[], OUT dictionary regdictionary, OUT lexemes text[])
5232+
RETURNS SETOF record
5233+
LANGUAGE sql
5234+
STABLE PARALLEL SAFE STRICT
5235+
1 BEGIN ATOMIC
5236+
2 SELECT ts_debug.alias,
5237+
3 ts_debug.description,
5238+
4 ts_debug.token,
5239+
5 ts_debug.dictionaries,
5240+
6 ts_debug.dictionary,
5241+
7 ts_debug.lexemes
5242+
8 FROM ts_debug(get_current_ts_config(), ts_debug.document) ts_debug(alias, description, token, dictionaries, dictionary, lexemes);
5243+
9 END
51825244
-- check describing invalid multipart names
51835245
\dA regression.heap
51845246
improper qualified name (too many dotted names): regression.heap

src/test/regress/sql/psql.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,9 +1239,17 @@ drop role regress_partitioning_role;
12391239
\df has_database_privilege oid text
12401240
\df has_database_privilege oid text -
12411241
\dfa bit* small*
1242+
\df *._pg_expandarray
12421243
\do - pg_catalog.int4
12431244
\do && anyarray *
12441245

1246+
-- check \sf
1247+
\sf information_schema._pg_expandarray
1248+
\sf+ information_schema._pg_expandarray
1249+
\sf+ interval_pl_time
1250+
\sf ts_debug(text)
1251+
\sf+ ts_debug(text)
1252+
12451253
-- check describing invalid multipart names
12461254
\dA regression.heap
12471255
\dA nonesuch.heap

0 commit comments

Comments
 (0)