]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix psql's \sf and \ef for new-style SQL functions.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 2 Dec 2022 19:24:44 +0000 (14:24 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 2 Dec 2022 19:24:44 +0000 (14:24 -0500)
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

src/backend/utils/adt/ruleutils.c
src/bin/psql/command.c
src/test/regress/expected/psql.out
src/test/regress/sql/psql.sql

index 8e36745b181a442d608671d9b3bedc3fee2bffcc..3edd30bec0a683a026d5e85b440aebe7bb428211 100644 (file)
@@ -2872,8 +2872,8 @@ pg_get_serial_sequence(PG_FUNCTION_ARGS)
  *
  * Note: if you change the output format of this function, be careful not
  * to break psql's rules (in \ef and \sf) for identifying the start of the
- * function body.  To wit: the function body starts on a line that begins
- * with "AS ", and no preceding line will look like that.
+ * function body.  To wit: the function body starts on a line that begins with
+ * "AS ", "BEGIN ", or "RETURN ", and no preceding line will look like that.
  */
 Datum
 pg_get_functiondef(PG_FUNCTION_ARGS)
index fb1e69ec4c20a1f4dd92f86bae8e8a1e1e7fff92..bb94df50a63de4ea3c541310364c0c519330bf9b 100644 (file)
@@ -167,8 +167,7 @@ static bool get_create_object_cmd(EditableObjectType obj_type, Oid oid,
                                                                  PQExpBuffer buf);
 static int     strip_lineno_from_objdesc(char *obj);
 static int     count_lines_in_buf(PQExpBuffer buf);
-static void print_with_linenumbers(FILE *output, char *lines,
-                                                                  const char *header_keyword);
+static void print_with_linenumbers(FILE *output, char *lines, bool is_func);
 static void minimal_error_message(PGresult *res);
 
 static void printSSLInfo(void);
@@ -1170,17 +1169,19 @@ exec_command_ef_ev(PsqlScanState scan_state, bool active_branch,
                                /*
                                 * lineno "1" should correspond to the first line of the
                                 * function body.  We expect that pg_get_functiondef() will
-                                * emit that on a line beginning with "AS ", and that there
-                                * can be no such line before the real start of the function
-                                * body.  Increment lineno by the number of lines before that
-                                * line, so that it becomes relative to the first line of the
-                                * function definition.
+                                * emit that on a line beginning with "AS ", "BEGIN ", or
+                                * "RETURN ", and that there can be no such line before the
+                                * real start of the function body.  Increment lineno by the
+                                * number of lines before that line, so that it becomes
+                                * relative to the first line of the function definition.
                                 */
                                const char *lines = query_buf->data;
 
                                while (*lines != '\0')
                                {
-                                       if (strncmp(lines, "AS ", 3) == 0)
+                                       if (strncmp(lines, "AS ", 3) == 0 ||
+                                               strncmp(lines, "BEGIN ", 6) == 0 ||
+                                               strncmp(lines, "RETURN ", 7) == 0)
                                                break;
                                        lineno++;
                                        /* find start of next line */
@@ -2502,15 +2503,8 @@ exec_command_sf_sv(PsqlScanState scan_state, bool active_branch,
 
                        if (show_linenumbers)
                        {
-                               /*
-                                * For functions, lineno "1" should correspond to the first
-                                * line of the function body.  We expect that
-                                * pg_get_functiondef() will emit that on a line beginning
-                                * with "AS ", and that there can be no such line before the
-                                * real start of the function body.
-                                */
-                               print_with_linenumbers(output, buf->data,
-                                                                          is_func ? "AS " : NULL);
+                               /* add line numbers */
+                               print_with_linenumbers(output, buf->data, is_func);
                        }
                        else
                        {
@@ -5530,24 +5524,28 @@ count_lines_in_buf(PQExpBuffer buf)
 /*
  * Write text at *lines to output with line numbers.
  *
- * If header_keyword isn't NULL, then line 1 should be the first line beginning
- * with header_keyword; lines before that are unnumbered.
+ * For functions, lineno "1" should correspond to the first line of the
+ * function body; lines before that are unnumbered.  We expect that
+ * pg_get_functiondef() will emit that on a line beginning with "AS ",
+ * "BEGIN ", or "RETURN ", and that there can be no such line before
+ * the real start of the function body.
  *
  * Caution: this scribbles on *lines.
  */
 static void
-print_with_linenumbers(FILE *output, char *lines,
-                                          const char *header_keyword)
+print_with_linenumbers(FILE *output, char *lines, bool is_func)
 {
-       bool            in_header = (header_keyword != NULL);
-       size_t          header_sz = in_header ? strlen(header_keyword) : 0;
+       bool            in_header = is_func;
        int                     lineno = 0;
 
        while (*lines != '\0')
        {
                char       *eol;
 
-               if (in_header && strncmp(lines, header_keyword, header_sz) == 0)
+               if (in_header &&
+                       (strncmp(lines, "AS ", 3) == 0 ||
+                        strncmp(lines, "BEGIN ", 6) == 0 ||
+                        strncmp(lines, "RETURN ", 7) == 0))
                        in_header = false;
 
                /* increment lineno only for body's lines */
index 60acbd1241e57507bc92e01bacfe6e607b97699f..9855abf2db77e4c90e5677af36db115493413dcf 100644 (file)
@@ -5194,6 +5194,13 @@ reset work_mem;
  pg_catalog | bit_xor | smallint         | smallint            | agg
 (3 rows)
 
+\df *._pg_expandarray
+                                             List of functions
+       Schema       |      Name       | Result data type |            Argument data types            | Type 
+--------------------+-----------------+------------------+-------------------------------------------+------
+ information_schema | _pg_expandarray | SETOF record     | anyarray, OUT x anyelement, OUT n integer | func
+(1 row)
+
 \do - pg_catalog.int4
                                List of operators
    Schema   | Name | Left arg type | Right arg type | Result type | Description 
@@ -5208,6 +5215,61 @@ reset work_mem;
  pg_catalog | &&   | anyarray      | anyarray       | boolean     | overlaps
 (1 row)
 
+-- check \sf
+\sf information_schema._pg_expandarray
+CREATE OR REPLACE FUNCTION information_schema._pg_expandarray(anyarray, OUT x anyelement, OUT n integer)
+ RETURNS SETOF record
+ LANGUAGE sql
+ IMMUTABLE PARALLEL SAFE STRICT
+AS $function$select $1[s],
+        s operator(pg_catalog.-) pg_catalog.array_lower($1,1) operator(pg_catalog.+) 1
+        from pg_catalog.generate_series(pg_catalog.array_lower($1,1),
+                                        pg_catalog.array_upper($1,1),
+                                        1) as g(s)$function$
+\sf+ information_schema._pg_expandarray
+        CREATE OR REPLACE FUNCTION information_schema._pg_expandarray(anyarray, OUT x anyelement, OUT n integer)
+         RETURNS SETOF record
+         LANGUAGE sql
+         IMMUTABLE PARALLEL SAFE STRICT
+1       AS $function$select $1[s],
+2               s operator(pg_catalog.-) pg_catalog.array_lower($1,1) operator(pg_catalog.+) 1
+3               from pg_catalog.generate_series(pg_catalog.array_lower($1,1),
+4                                               pg_catalog.array_upper($1,1),
+5                                               1) as g(s)$function$
+\sf+ interval_pl_time
+        CREATE OR REPLACE FUNCTION pg_catalog.interval_pl_time(interval, time without time zone)
+         RETURNS time without time zone
+         LANGUAGE sql
+         IMMUTABLE PARALLEL SAFE STRICT COST 1
+1       RETURN ($2 + $1)
+\sf ts_debug(text)
+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[])
+ RETURNS SETOF record
+ LANGUAGE sql
+ STABLE PARALLEL SAFE STRICT
+BEGIN ATOMIC
+ SELECT ts_debug.alias,
+     ts_debug.description,
+     ts_debug.token,
+     ts_debug.dictionaries,
+     ts_debug.dictionary,
+     ts_debug.lexemes
+    FROM ts_debug(get_current_ts_config(), ts_debug.document) ts_debug(alias, description, token, dictionaries, dictionary, lexemes);
+END
+\sf+ ts_debug(text)
+        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[])
+         RETURNS SETOF record
+         LANGUAGE sql
+         STABLE PARALLEL SAFE STRICT
+1       BEGIN ATOMIC
+2        SELECT ts_debug.alias,
+3            ts_debug.description,
+4            ts_debug.token,
+5            ts_debug.dictionaries,
+6            ts_debug.dictionary,
+7            ts_debug.lexemes
+8           FROM ts_debug(get_current_ts_config(), ts_debug.document) ts_debug(alias, description, token, dictionaries, dictionary, lexemes);
+9       END
 -- AUTOCOMMIT
 CREATE TABLE ac_test (a int);
 \set AUTOCOMMIT off
index 1149c6a839ef756d6fbde6542275ffeb06e1e16c..0a4be827d881ab8e70a1740f074a23401a27d472 100644 (file)
@@ -1257,9 +1257,17 @@ reset work_mem;
 \df has_database_privilege oid text
 \df has_database_privilege oid text -
 \dfa bit* small*
+\df *._pg_expandarray
 \do - pg_catalog.int4
 \do && anyarray *
 
+-- check \sf
+\sf information_schema._pg_expandarray
+\sf+ information_schema._pg_expandarray
+\sf+ interval_pl_time
+\sf ts_debug(text)
+\sf+ ts_debug(text)
+
 -- AUTOCOMMIT
 
 CREATE TABLE ac_test (a int);