Thread: [PATCH] Add result_types column to pg_prepared_statements view

[PATCH] Add result_types column to pg_prepared_statements view

From
Dagfinn Ilmari Mannsåker
Date:
Hi hackers,

Prompted by a question on IRC, here's a patch to add a result_types
column to the pg_prepared_statements view, so that one can see the types
of the columns returned by a prepared statement, not just the parameter
types.

I'm not quite sure about the column name, suggestions welcome.

- ilmari

From 5045cd5a173fefb5346ed81d355ba35c1c922105 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dagfinn=20Ilmari=20Manns=C3=A5ker?= <ilmari@ilmari.org>
Date: Thu, 19 May 2022 15:54:56 +0100
Subject: [PATCH] Add result_types column to pg_prepared_statements view

Containing the types of the columns returned by the prepared
statement.

Prompted by question from IRC user mlvzk.
---
 doc/src/sgml/catalogs.sgml            | 12 +++++
 src/backend/commands/prepare.c        | 19 +++++--
 src/include/catalog/pg_proc.dat       |  6 +--
 src/test/regress/expected/prepare.out | 72 +++++++++++++--------------
 src/test/regress/expected/rules.out   |  3 +-
 src/test/regress/sql/prepare.sql      | 12 ++---
 6 files changed, 73 insertions(+), 51 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index d96c72e531..ae7627ae48 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -11492,6 +11492,18 @@
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>result_types</structfield> <type>regtype[]</type>
+      </para>
+      <para>
+       The types of the columns returned by the prepared statement in the
+       form of an array of <type>regtype</type>. The OID corresponding
+       to an element of this array can be obtained by casting the
+       <type>regtype</type> value to <type>oid</type>.
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>from_sql</structfield> <type>bool</type>
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 80738547ed..7c8537fbd2 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -683,8 +683,16 @@ pg_prepared_statement(PG_FUNCTION_ARGS)
         hash_seq_init(&hash_seq, prepared_queries);
         while ((prep_stmt = hash_seq_search(&hash_seq)) != NULL)
         {
-            Datum        values[7];
-            bool        nulls[7];
+            Datum        values[8];
+            bool        nulls[8];
+            TupleDesc    result_desc;
+            Oid           *result_types;
+
+            result_desc = prep_stmt->plansource->resultDesc;
+            result_types = (Oid *) palloc(result_desc->natts * sizeof(Oid));
+
+            for (int i = 0; i < result_desc->natts; i++)
+                result_types[i] = result_desc->attrs[i].atttypid;
 
             MemSet(nulls, 0, sizeof(nulls));
 
@@ -693,9 +701,10 @@ pg_prepared_statement(PG_FUNCTION_ARGS)
             values[2] = TimestampTzGetDatum(prep_stmt->prepare_time);
             values[3] = build_regtype_array(prep_stmt->plansource->param_types,
                                             prep_stmt->plansource->num_params);
-            values[4] = BoolGetDatum(prep_stmt->from_sql);
-            values[5] = Int64GetDatumFast(prep_stmt->plansource->num_generic_plans);
-            values[6] = Int64GetDatumFast(prep_stmt->plansource->num_custom_plans);
+            values[4] = build_regtype_array(result_types, result_desc->natts);
+            values[5] = BoolGetDatum(prep_stmt->from_sql);
+            values[6] = Int64GetDatumFast(prep_stmt->plansource->num_generic_plans);
+            values[7] = Int64GetDatumFast(prep_stmt->plansource->num_custom_plans);
 
             tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
                                  values, nulls);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87aa571a33..cb953c6411 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8025,9 +8025,9 @@
   proname => 'pg_prepared_statement', prorows => '1000', proretset => 't',
   provolatile => 's', proparallel => 'r', prorettype => 'record',
   proargtypes => '',
-  proallargtypes => '{text,text,timestamptz,_regtype,bool,int8,int8}',
-  proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,statement,prepare_time,parameter_types,from_sql,generic_plans,custom_plans}',
+  proallargtypes => '{text,text,timestamptz,_regtype,_regtype,bool,int8,int8}',
+  proargmodes => '{o,o,o,o,o,o,o,o}',
+  proargnames => '{name,statement,prepare_time,parameter_types,result_types,from_sql,generic_plans,custom_plans}',
   prosrc => 'pg_prepared_statement' },
 { oid => '2511', descr => 'get the open cursors for this session',
   proname => 'pg_cursor', prorows => '1000', proretset => 't',
diff --git a/src/test/regress/expected/prepare.out b/src/test/regress/expected/prepare.out
index 3306c696b1..faf07f620b 100644
--- a/src/test/regress/expected/prepare.out
+++ b/src/test/regress/expected/prepare.out
@@ -1,9 +1,9 @@
 -- Regression tests for prepareable statements. We query the content
 -- of the pg_prepared_statements view as prepared statements are
 -- created and removed.
-SELECT name, statement, parameter_types FROM pg_prepared_statements;
- name | statement | parameter_types 
-------+-----------+-----------------
+SELECT name, statement, parameter_types, result_types FROM pg_prepared_statements;
+ name | statement | parameter_types | result_types 
+------+-----------+-----------------+--------------
 (0 rows)
 
 PREPARE q1 AS SELECT 1 AS a;
@@ -13,10 +13,10 @@ EXECUTE q1;
  1
 (1 row)
 
-SELECT name, statement, parameter_types FROM pg_prepared_statements;
- name |          statement           | parameter_types 
-------+------------------------------+-----------------
- q1   | PREPARE q1 AS SELECT 1 AS a; | {}
+SELECT name, statement, parameter_types, result_types FROM pg_prepared_statements;
+ name |          statement           | parameter_types | result_types 
+------+------------------------------+-----------------+--------------
+ q1   | PREPARE q1 AS SELECT 1 AS a; | {}              | {integer}
 (1 row)
 
 -- should fail
@@ -32,26 +32,26 @@ EXECUTE q1;
 (1 row)
 
 PREPARE q2 AS SELECT 2 AS b;
-SELECT name, statement, parameter_types FROM pg_prepared_statements;
- name |          statement           | parameter_types 
-------+------------------------------+-----------------
- q1   | PREPARE q1 AS SELECT 2;      | {}
- q2   | PREPARE q2 AS SELECT 2 AS b; | {}
+SELECT name, statement, parameter_types, result_types FROM pg_prepared_statements;
+ name |          statement           | parameter_types | result_types 
+------+------------------------------+-----------------+--------------
+ q1   | PREPARE q1 AS SELECT 2;      | {}              | {integer}
+ q2   | PREPARE q2 AS SELECT 2 AS b; | {}              | {integer}
 (2 rows)
 
 -- sql92 syntax
 DEALLOCATE PREPARE q1;
-SELECT name, statement, parameter_types FROM pg_prepared_statements;
- name |          statement           | parameter_types 
-------+------------------------------+-----------------
- q2   | PREPARE q2 AS SELECT 2 AS b; | {}
+SELECT name, statement, parameter_types, result_types FROM pg_prepared_statements;
+ name |          statement           | parameter_types | result_types 
+------+------------------------------+-----------------+--------------
+ q2   | PREPARE q2 AS SELECT 2 AS b; | {}              | {integer}
 (1 row)
 
 DEALLOCATE PREPARE q2;
 -- the view should return the empty set again
-SELECT name, statement, parameter_types FROM pg_prepared_statements;
- name | statement | parameter_types 
-------+-----------+-----------------
+SELECT name, statement, parameter_types, result_types FROM pg_prepared_statements;
+ name | statement | parameter_types | result_types 
+------+-----------+-----------------+--------------
 (0 rows)
 
 -- parameterized queries
@@ -159,24 +159,24 @@ PREPARE q6 AS
     SELECT * FROM tenk1 WHERE unique1 = $1 AND stringu1 = $2;
 PREPARE q7(unknown) AS
     SELECT * FROM road WHERE thepath = $1;
-SELECT name, statement, parameter_types FROM pg_prepared_statements
+SELECT name, statement, parameter_types, result_types FROM pg_prepared_statements
     ORDER BY name;
- name |                            statement                             |                  parameter_types
      
 

-------+------------------------------------------------------------------+----------------------------------------------------
- q2   | PREPARE q2(text) AS                                             +| {text}
-      |         SELECT datname, datistemplate, datallowconn             +|
-      |         FROM pg_database WHERE datname = $1;                     | 
- q3   | PREPARE q3(text, int, float, boolean, smallint) AS              +| {text,integer,"double
precision",boolean,smallint}
-      |         SELECT * FROM tenk1 WHERE string4 = $1 AND (four = $2 OR+| 
-      |         ten = $3::bigint OR true = $4 OR odd = $5::int)         +| 
-      |         ORDER BY unique1;                                        | 
- q5   | PREPARE q5(int, text) AS                                        +| {integer,text}
-      |         SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2 +| 
-      |         ORDER BY unique1;                                        | 
- q6   | PREPARE q6 AS                                                   +| {integer,name}
-      |     SELECT * FROM tenk1 WHERE unique1 = $1 AND stringu1 = $2;    | 
- q7   | PREPARE q7(unknown) AS                                          +| {path}
-      |     SELECT * FROM road WHERE thepath = $1;                       | 
+ name |                            statement                             |                  parameter_types
      |                                                       result_types
        
 

+------+------------------------------------------------------------------+----------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------
+ q2   | PREPARE q2(text) AS                                             +| {text}
      | {name,boolean,boolean}
 
+      |         SELECT datname, datistemplate, datallowconn             +|
      | 
 
+      |         FROM pg_database WHERE datname = $1;                     |
      | 
 
+ q3   | PREPARE q3(text, int, float, boolean, smallint) AS              +| {text,integer,"double
precision",boolean,smallint}|
{integer,integer,integer,integer,integer,integer,integer,integer,integer,integer,integer,integer,integer,name,name,name}
+      |         SELECT * FROM tenk1 WHERE string4 = $1 AND (four = $2 OR+|
      | 
 
+      |         ten = $3::bigint OR true = $4 OR odd = $5::int)         +|
      | 
 
+      |         ORDER BY unique1;                                        |
      | 
 
+ q5   | PREPARE q5(int, text) AS                                        +| {integer,text}
      |
{integer,integer,integer,integer,integer,integer,integer,integer,integer,integer,integer,integer,integer,name,name,name}
+      |         SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2 +|
      | 
 
+      |         ORDER BY unique1;                                        |
      | 
 
+ q6   | PREPARE q6 AS                                                   +| {integer,name}
      |
{integer,integer,integer,integer,integer,integer,integer,integer,integer,integer,integer,integer,integer,name,name,name}
+      |     SELECT * FROM tenk1 WHERE unique1 = $1 AND stringu1 = $2;    |
      | 
 
+ q7   | PREPARE q7(unknown) AS                                          +| {path}
      | {text,path}
 
+      |     SELECT * FROM road WHERE thepath = $1;                       |
      | 
 
 (5 rows)
 
 -- test DEALLOCATE ALL;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index fc3cde3226..7ec3d2688f 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1423,10 +1423,11 @@ pg_prepared_statements| SELECT p.name,
     p.statement,
     p.prepare_time,
     p.parameter_types,
+    p.result_types,
     p.from_sql,
     p.generic_plans,
     p.custom_plans
-   FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, from_sql, generic_plans,
custom_plans);
+   FROM pg_prepared_statement() p(name, statement, prepare_time, parameter_types, result_types, from_sql,
generic_plans,custom_plans);
 
 pg_prepared_xacts| SELECT p.transaction,
     p.gid,
     p.prepared,
diff --git a/src/test/regress/sql/prepare.sql b/src/test/regress/sql/prepare.sql
index 985d0f05c9..55eef91d2a 100644
--- a/src/test/regress/sql/prepare.sql
+++ b/src/test/regress/sql/prepare.sql
@@ -2,12 +2,12 @@
 -- of the pg_prepared_statements view as prepared statements are
 -- created and removed.
 
-SELECT name, statement, parameter_types FROM pg_prepared_statements;
+SELECT name, statement, parameter_types, result_types FROM pg_prepared_statements;
 
 PREPARE q1 AS SELECT 1 AS a;
 EXECUTE q1;
 
-SELECT name, statement, parameter_types FROM pg_prepared_statements;
+SELECT name, statement, parameter_types, result_types FROM pg_prepared_statements;
 
 -- should fail
 PREPARE q1 AS SELECT 2;
@@ -18,16 +18,16 @@ PREPARE q1 AS SELECT 2;
 EXECUTE q1;
 
 PREPARE q2 AS SELECT 2 AS b;
-SELECT name, statement, parameter_types FROM pg_prepared_statements;
+SELECT name, statement, parameter_types, result_types FROM pg_prepared_statements;
 
 -- sql92 syntax
 DEALLOCATE PREPARE q1;
 
-SELECT name, statement, parameter_types FROM pg_prepared_statements;
+SELECT name, statement, parameter_types, result_types FROM pg_prepared_statements;
 
 DEALLOCATE PREPARE q2;
 -- the view should return the empty set again
-SELECT name, statement, parameter_types FROM pg_prepared_statements;
+SELECT name, statement, parameter_types, result_types FROM pg_prepared_statements;
 
 -- parameterized queries
 PREPARE q2(text) AS
@@ -71,7 +71,7 @@ PREPARE q6 AS
 PREPARE q7(unknown) AS
     SELECT * FROM road WHERE thepath = $1;
 
-SELECT name, statement, parameter_types FROM pg_prepared_statements
+SELECT name, statement, parameter_types, result_types FROM pg_prepared_statements
     ORDER BY name;
 
 -- test DEALLOCATE ALL;
-- 
2.30.2


Re: [PATCH] Add result_types column to pg_prepared_statements view

From
Dagfinn Ilmari Mannsåker
Date:
Dagfinn Ilmari Mannsåker <ilmari@ilmari.org> writes:

> Hi hackers,
>
> Prompted by a question on IRC, here's a patch to add a result_types
> column to the pg_prepared_statements view, so that one can see the types
> of the columns returned by a prepared statement, not just the parameter
> types.

Added to the 2022-07 commitfest: https://commitfest.postgresql.org/38/3644/

- ilmari



Re: [PATCH] Add result_types column to pg_prepared_statements view

From
Peter Eisentraut
Date:
On 19.05.22 17:34, Dagfinn Ilmari Mannsåker wrote:
> Prompted by a question on IRC, here's a patch to add a result_types
> column to the pg_prepared_statements view, so that one can see the types
> of the columns returned by a prepared statement, not just the parameter
> types.
> 
> I'm not quite sure about the column name, suggestions welcome.

I think this patch is sensible.

I see one issue: When you describe a prepared statement via the 
protocol, if a result field has a domain as its type, the RowDescription 
message sends the underlying base type, not the domain type directly 
(see SendRowDescriptionMessage()).  But it doesn't do that for the 
parameters (see exec_describe_statement_message()).  I don't know why 
that is; the protocol documentation doesn't mention it.  Might be worth 
looking into, and checking whether the analogous information contained 
in this view should be made consistent.



Re: [PATCH] Add result_types column to pg_prepared_statements view

From
Dagfinn Ilmari Mannsåker
Date:
Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

> On 19.05.22 17:34, Dagfinn Ilmari Mannsåker wrote:
>> Prompted by a question on IRC, here's a patch to add a result_types
>> column to the pg_prepared_statements view, so that one can see the types
>> of the columns returned by a prepared statement, not just the parameter
>> types.
>> I'm not quite sure about the column name, suggestions welcome.
>
> I think this patch is sensible.
>
> I see one issue: When you describe a prepared statement via the
> protocol, if a result field has a domain as its type, the RowDescription 
> message sends the underlying base type, not the domain type directly
> (see SendRowDescriptionMessage()).  But it doesn't do that for the 
> parameters (see exec_describe_statement_message()).  I don't know why
> that is; the protocol documentation doesn't mention it.  Might be worth 
> looking into, and checking whether the analogous information contained
> in this view should be made consistent.

A bit of git archaeology shows that the change was made by Tom (Cc-ed)
in 7.4:

commit d9b679c13a820eb7b464a1eeb1f177c3fea13ece
Author: Tom Lane <tgl@sss.pgh.pa.us>
Date:   2003-05-13 18:39:50 +0000

    In RowDescription messages, report columns of domain datatypes as having
    the type OID and typmod of the underlying base type.  Per discussions
    a few weeks ago with Andreas Pflug and others.  Note that this behavioral
    change affects both old- and new-protocol clients.

I can't find that discussion in the archive, but someone did complain
about it shortly after:

https://www.postgresql.org/message-id/flat/D71A1574-A772-11D7-913D-0030656EE7B2%40icx.net

I think in this case returning the domain type is more useful, since
it's easy to get from that to the base type, but not vice versa.

The arguments about client-side type-specific value handling for
RowDescription don't apply here IMO, since this view is more
user-facing.

- ilmari



Re: [PATCH] Add result_types column to pg_prepared_statements view

From
Peter Eisentraut
Date:
On 01.07.22 14:27, Dagfinn Ilmari Mannsåker wrote:
> Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:
> 
>> On 19.05.22 17:34, Dagfinn Ilmari Mannsåker wrote:
>>> Prompted by a question on IRC, here's a patch to add a result_types
>>> column to the pg_prepared_statements view, so that one can see the types
>>> of the columns returned by a prepared statement, not just the parameter
>>> types.
>>> I'm not quite sure about the column name, suggestions welcome.
>>
>> I think this patch is sensible.

> The arguments about client-side type-specific value handling for
> RowDescription don't apply here IMO, since this view is more
> user-facing.

I agree.  It's also easy to change if needed.  Committed as is.



Re: [PATCH] Add result_types column to pg_prepared_statements view

From
Dagfinn Ilmari Mannsåker
Date:
On Tue, 5 Jul 2022, at 06:34, Peter Eisentraut wrote:
> On 01.07.22 14:27, Dagfinn Ilmari Mannsåker wrote:
>> Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:
>>
>>> On 19.05.22 17:34, Dagfinn Ilmari Mannsåker wrote:
>>>> Prompted by a question on IRC, here's a patch to add a result_types
>>>> column to the pg_prepared_statements view, so that one can see the types
>>>> of the columns returned by a prepared statement, not just the parameter
>>>> types.
>>>> I'm not quite sure about the column name, suggestions welcome.
>>>
>>> I think this patch is sensible.
>
>> The arguments about client-side type-specific value handling for
>> RowDescription don't apply here IMO, since this view is more
>> user-facing.
>
> I agree.  It's also easy to change if needed.  Committed as is.

Thanks!



Re: [PATCH] Add result_types column to pg_prepared_statements view

From
Peter Eisentraut
Date:
On 05.07.22 09:31, Dagfinn Ilmari Mannsåker wrote:
> On Tue, 5 Jul 2022, at 06:34, Peter Eisentraut wrote:
>> On 01.07.22 14:27, Dagfinn Ilmari Mannsåker wrote:
>>> Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:
>>>
>>>> On 19.05.22 17:34, Dagfinn Ilmari Mannsåker wrote:
>>>>> Prompted by a question on IRC, here's a patch to add a result_types
>>>>> column to the pg_prepared_statements view, so that one can see the types
>>>>> of the columns returned by a prepared statement, not just the parameter
>>>>> types.
>>>>> I'm not quite sure about the column name, suggestions welcome.
>>>>
>>>> I think this patch is sensible.
>>
>>> The arguments about client-side type-specific value handling for
>>> RowDescription don't apply here IMO, since this view is more
>>> user-facing.
>>
>> I agree.  It's also easy to change if needed.  Committed as is.
> 
> Thanks!

There was a problem that we didn't cover: Not all prepared statements 
have result descriptors (e.g., DML statements), so that would crash as 
written.  I have changed it to return null for result_types in that 
case, and added a test case.



Re: [PATCH] Add result_types column to pg_prepared_statements view

From
Dagfinn Ilmari Mannsåker
Date:
Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:

> There was a problem that we didn't cover: Not all prepared statements
> have result descriptors (e.g., DML statements), so that would crash as 
> written. 

D'oh!

> I have changed it to return null for result_types in that case, and
> added a test case.

Thanks for spotting and fixing that.

- ilmari