On 2019-12-16 19:53, Peter Eisentraut wrote:
SQL:2016 has a feature called polymorphic table functions (PTF) that
addresses this. The full PTF feature is much larger, so I just carved
out this particular piece of functionality. Here is a link to some
related information:
https://modern-sql.com/blog/2018-11/whats-new-in-oracle-database-18c#ptf
The idea is that you attach a helper function to the main function. The
helper function is called at parse time with the constant arguments of
the main function call and can compute a result row description (a
TupleDesc in our case).
Here is an updated patch for the record, since the previous patch had
accumulated some significant merge conflicts.
I will reply to the discussions elsewhere in the thread.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
From a05a926ccb3be7f61bda0b075cfa92fe6f7305bf Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Fri, 24 Jan 2020 09:06:38 +0100
Subject: [PATCH v2] Polymorphic table functions
---
contrib/tablefunc/expected/tablefunc.out | 46 +++++++++++++++
contrib/tablefunc/sql/tablefunc.sql | 8 +++
contrib/tablefunc/tablefunc--1.0.sql | 7 +++
contrib/tablefunc/tablefunc.c | 69 ++++++++++++++++++++++
contrib/xml2/expected/xml2.out | 10 ++++
contrib/xml2/sql/xml2.sql | 6 ++
contrib/xml2/xml2--1.1.sql | 6 ++
contrib/xml2/xpath.c | 72 +++++++++++++++++++++++
doc/src/sgml/catalogs.sgml | 12 ++++
doc/src/sgml/queries.sgml | 8 +++
doc/src/sgml/ref/create_function.sgml | 14 +++++
doc/src/sgml/xfunc.sgml | 66 +++++++++++++++++++++
src/backend/catalog/pg_aggregate.c | 1 +
src/backend/catalog/pg_proc.c | 11 ++++
src/backend/commands/functioncmds.c | 30 +++++++++-
src/backend/commands/proclang.c | 3 +
src/backend/commands/typecmds.c | 1 +
src/backend/executor/execSRF.c | 1 +
src/backend/executor/nodeFunctionscan.c | 1 +
src/backend/optimizer/prep/prepjointree.c | 1 +
src/backend/optimizer/util/clauses.c | 3 +-
src/backend/parser/gram.y | 7 ++-
src/backend/parser/parse_relation.c | 2 +
src/backend/utils/adt/jsonfuncs.c | 48 +++++++++++++++
src/backend/utils/fmgr/funcapi.c | 49 ++++++++++++++-
src/include/catalog/pg_class.dat | 2 +-
src/include/catalog/pg_proc.dat | 6 +-
src/include/catalog/pg_proc.h | 4 ++
src/include/funcapi.h | 1 +
src/include/parser/kwlist.h | 1 +
src/interfaces/ecpg/preproc/ecpg.tokens | 2 +-
src/interfaces/ecpg/preproc/ecpg.trailer | 11 ++--
src/interfaces/ecpg/preproc/ecpg_kwlist.h | 1 -
src/test/regress/expected/json.out | 6 ++
src/test/regress/sql/json.sql | 2 +
35 files changed, 504 insertions(+), 14 deletions(-)
diff --git a/contrib/tablefunc/expected/tablefunc.out
b/contrib/tablefunc/expected/tablefunc.out
index fffadc6e1b..485ddfba87 100644
--- a/contrib/tablefunc/expected/tablefunc.out
+++ b/contrib/tablefunc/expected/tablefunc.out
@@ -328,6 +328,29 @@ SELECT * FROM connectby('connectby_text', 'keyid',
'parent_keyid', 'pos', 'row2'
row8 | row6 | 3 | 6
(6 rows)
+-- PTF
+SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0,
'~');
+ keyid | parent_keyid | level | branch
+-------+--------------+-------+---------------------
+ row2 | | 0 | row2
+ row4 | row2 | 1 | row2~row4
+ row6 | row4 | 2 | row2~row4~row6
+ row8 | row6 | 3 | row2~row4~row6~row8
+ row5 | row2 | 1 | row2~row5
+ row9 | row5 | 2 | row2~row5~row9
+(6 rows)
+
+SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0);
+ keyid | parent_keyid | level
+-------+--------------+-------
+ row2 | | 0
+ row4 | row2 | 1
+ row6 | row4 | 2
+ row8 | row6 | 3
+ row5 | row2 | 1
+ row9 | row5 | 2
+(6 rows)
+
-- test connectby with int based hierarchy
CREATE TABLE connectby_int(keyid int, parent_keyid int);
\copy connectby_int from 'data/connectby_int.data'
@@ -355,6 +378,29 @@ SELECT * FROM connectby('connectby_int', 'keyid',
'parent_keyid', '2', 0) AS t(k
9 | 5 | 2
(6 rows)
+-- PTF
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~');
+ keyid | parent_keyid | level | branch
+-------+--------------+-------+---------
+ 2 | | 0 | 2
+ 4 | 2 | 1 | 2~4
+ 6 | 4 | 2 | 2~4~6
+ 8 | 6 | 3 | 2~4~6~8
+ 5 | 2 | 1 | 2~5
+ 9 | 5 | 2 | 2~5~9
+(6 rows)
+
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0);
+ keyid | parent_keyid | level
+-------+--------------+-------
+ 2 | | 0
+ 4 | 2 | 1
+ 6 | 4 | 2
+ 8 | 6 | 3
+ 5 | 2 | 1
+ 9 | 5 | 2
+(6 rows)
+
-- recursion detection
INSERT INTO connectby_int VALUES(10,9);
INSERT INTO connectby_int VALUES(11,10);
diff --git a/contrib/tablefunc/sql/tablefunc.sql
b/contrib/tablefunc/sql/tablefunc.sql
index ec375b05c6..375f59bc7a 100644
--- a/contrib/tablefunc/sql/tablefunc.sql
+++ b/contrib/tablefunc/sql/tablefunc.sql
@@ -158,6 +158,10 @@ CREATE TABLE connectby_text(keyid text, parent_keyid text,
pos int);
-- without branch, with orderby
SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos',
'row2', 0) AS t(keyid text, parent_keyid text, level int, pos int) ORDER BY
t.pos;
+-- PTF
+SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0,
'~');
+SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0);
+
-- test connectby with int based hierarchy
CREATE TABLE connectby_int(keyid int, parent_keyid int);
\copy connectby_int from 'data/connectby_int.data'
@@ -168,6 +172,10 @@ CREATE TABLE connectby_int(keyid int, parent_keyid int);
-- without branch
SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0) AS
t(keyid int, parent_keyid int, level int);
+-- PTF
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~');
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0);
+
-- recursion detection
INSERT INTO connectby_int VALUES(10,9);
INSERT INTO connectby_int VALUES(11,10);
diff --git a/contrib/tablefunc/tablefunc--1.0.sql
b/contrib/tablefunc/tablefunc--1.0.sql
index 8681ff4706..e75bdc0510 100644
--- a/contrib/tablefunc/tablefunc--1.0.sql
+++ b/contrib/tablefunc/tablefunc--1.0.sql
@@ -65,13 +65,20 @@ CREATE FUNCTION crosstab(text,text)
AS 'MODULE_PATHNAME','crosstab_hash'
LANGUAGE C STABLE STRICT;
+CREATE FUNCTION connectby_describe(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME', 'connectby_describe'
+LANGUAGE C;
+
CREATE FUNCTION connectby(text,text,text,text,int,text)
RETURNS setof record
+DESCRIBE WITH connectby_describe
AS 'MODULE_PATHNAME','connectby_text'
LANGUAGE C STABLE STRICT;
CREATE FUNCTION connectby(text,text,text,text,int)
RETURNS setof record
+DESCRIBE WITH connectby_describe
AS 'MODULE_PATHNAME','connectby_text'
LANGUAGE C STABLE STRICT;
diff --git a/contrib/tablefunc/tablefunc.c b/contrib/tablefunc/tablefunc.c
index 3802ae905e..a823347019 100644
--- a/contrib/tablefunc/tablefunc.c
+++ b/contrib/tablefunc/tablefunc.c
@@ -35,6 +35,7 @@
#include <math.h>
#include "access/htup_details.h"
+#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "executor/spi.h"
#include "funcapi.h"
@@ -42,6 +43,8 @@
#include "miscadmin.h"
#include "tablefunc.h"
#include "utils/builtins.h"
+#include "utils/regproc.h"
+#include "utils/lsyscache.h"
PG_MODULE_MAGIC;
@@ -1587,3 +1590,69 @@ compatCrosstabTupleDescs(TupleDesc ret_tupdesc,
TupleDesc sql_tupdesc)
/* OK, the two tupdescs are compatible for our purposes */
return true;
}
+
+PG_FUNCTION_INFO_V1(connectby_describe);
+
+Datum
+connectby_describe(PG_FUNCTION_ARGS)
+{
+ char *relname;
+ char *key_fld;
+ char *parent_key_fld;
+ bool show_branch;
+ TupleDesc tupdesc;
+ List *names;
+ Oid relid;
+ AttrNumber keyattnum;
+ Oid keytype;
+ int32 keytypmod;
+ Oid keycoll;
+
+ if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2))
+ PG_RETURN_NULL();
+
+ show_branch = (PG_NARGS() == 6);
+
+ if (show_branch && PG_ARGISNULL(5))
+ PG_RETURN_NULL();
+
+ relname = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ key_fld = text_to_cstring(PG_GETARG_TEXT_PP(1));
+ parent_key_fld = text_to_cstring(PG_GETARG_TEXT_PP(2));
+
+ tupdesc = CreateTemplateTupleDesc(3 + (show_branch ? 1 : 0));
+
+ names = stringToQualifiedNameList(relname);
+ relid = RangeVarGetRelid(makeRangeVarFromNameList(names),
AccessShareLock, false);
+
+ keyattnum = get_attnum(relid, key_fld);
+ if (keyattnum == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does
not exist",
+ key_fld, relname)));
+ get_atttypetypmodcoll(relid, keyattnum,
+ &keytype, &keytypmod,
&keycoll);
+ TupleDescInitEntry(tupdesc, 1, key_fld, keytype, keytypmod, 0);
+ TupleDescInitEntryCollation(tupdesc, 1, keycoll);
+
+ keyattnum = get_attnum(relid, parent_key_fld);
+ if (keyattnum == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does
not exist",
+ parent_key_fld, relname)));
+ get_atttypetypmodcoll(relid, keyattnum,
+ &keytype, &keytypmod,
&keycoll);
+ TupleDescInitEntry(tupdesc, 2, parent_key_fld, keytype, keytypmod, 0);
+ TupleDescInitEntryCollation(tupdesc, 2, keycoll);
+
+ TupleDescInitEntry(tupdesc, 3, "level", INT4OID, -1, 0);
+
+ if (show_branch)
+ TupleDescInitEntry(tupdesc, 4, "branch", TEXTOID, -1, 0);
+
+ BlessTupleDesc(tupdesc);
+
+ PG_RETURN_POINTER(tupdesc);
+}
diff --git a/contrib/xml2/expected/xml2.out b/contrib/xml2/expected/xml2.out
index eba6ae6036..1df04be6c4 100644
--- a/contrib/xml2/expected/xml2.out
+++ b/contrib/xml2/expected/xml2.out
@@ -88,6 +88,16 @@ as t(id int4, doc int4);
1 | 1
(1 row)
+-- PTF
+DROP TABLE xpath_test;
+CREATE TABLE xpath_test (id integer NOT NULL, t text);
+INSERT INTO xpath_test VALUES (1,
'<doc><data>foo</data><stuff>bar</stuff></doc>');
+SELECT * FROM xpath_table('id', 't', 'xpath_test', '/doc/data|/doc/stuff',
'true');
+ id | t1 | t2
+----+-----+-----
+ 1 | foo | bar
+(1 row)
+
create table articles (article_id integer, article_xml xml, date_entered date);
insert into articles (article_id, article_xml, date_entered)
values (2, '<article><author>test</author><pages>37</pages></article>', now());
diff --git a/contrib/xml2/sql/xml2.sql b/contrib/xml2/sql/xml2.sql
index ac49cfa7c5..d1a9cb3494 100644
--- a/contrib/xml2/sql/xml2.sql
+++ b/contrib/xml2/sql/xml2.sql
@@ -34,6 +34,12 @@ CREATE TABLE xpath_test (id integer NOT NULL, t text);
SELECT * FROM xpath_table('id', 't', 'xpath_test', '/doc/int', 'true')
as t(id int4, doc int4);
+-- PTF
+DROP TABLE xpath_test;
+CREATE TABLE xpath_test (id integer NOT NULL, t text);
+INSERT INTO xpath_test VALUES (1,
'<doc><data>foo</data><stuff>bar</stuff></doc>');
+SELECT * FROM xpath_table('id', 't', 'xpath_test', '/doc/data|/doc/stuff',
'true');
+
create table articles (article_id integer, article_xml xml, date_entered date);
insert into articles (article_id, article_xml, date_entered)
values (2, '<article><author>test</author><pages>37</pages></article>', now());
diff --git a/contrib/xml2/xml2--1.1.sql b/contrib/xml2/xml2--1.1.sql
index 671372cb27..02588266d0 100644
--- a/contrib/xml2/xml2--1.1.sql
+++ b/contrib/xml2/xml2--1.1.sql
@@ -54,8 +54,14 @@ CREATE FUNCTION xpath_nodeset(text,text,text)
-- Table function
+CREATE FUNCTION xpath_table_describe(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C;
+
CREATE FUNCTION xpath_table(text,text,text,text,text)
RETURNS setof record
+DESCRIBE WITH xpath_table_describe
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT STABLE PARALLEL SAFE;
diff --git a/contrib/xml2/xpath.c b/contrib/xml2/xpath.c
index 1e5b71d9a0..67731509f9 100644
--- a/contrib/xml2/xpath.c
+++ b/contrib/xml2/xpath.c
@@ -7,12 +7,16 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_type.h"
#include "executor/spi.h"
#include "fmgr.h"
#include "funcapi.h"
#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/regproc.h"
#include "utils/xml.h"
/* libxml includes */
@@ -519,6 +523,74 @@ pgxml_result_to_text(xmlXPathObjectPtr res,
* xpath_table is a table function. It needs some tidying (as do the
* other functions here!
*/
+PG_FUNCTION_INFO_V1(xpath_table_describe);
+
+Datum
+xpath_table_describe(PG_FUNCTION_ARGS)
+{
+ char *pkeyfield;
+ char *xmlfield;
+ char *relname;
+ List *names;
+ Oid relid;
+ AttrNumber pkeyattnum;
+ Oid pkeytype;
+ int32 pkeytypmod;
+ Oid pkeycoll;
+ char *xpathset;
+ const char *pathsep = "|";
+ int numpaths;
+ TupleDesc tupdesc;
+
+ if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(3))
+ PG_RETURN_NULL();
+
+ pkeyfield = text_to_cstring(PG_GETARG_TEXT_PP(0));
+ xmlfield = text_to_cstring(PG_GETARG_TEXT_PP(1));
+ relname = text_to_cstring(PG_GETARG_TEXT_PP(2));
+ xpathset = text_to_cstring(PG_GETARG_TEXT_PP(3));
+
+ names = stringToQualifiedNameList(relname);
+ relid = RangeVarGetRelid(makeRangeVarFromNameList(names),
AccessShareLock, false);
+
+ pkeyattnum = get_attnum(relid, pkeyfield);
+ if (pkeyattnum == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does
not exist",
+ pkeyfield, relname)));
+ get_atttypetypmodcoll(relid, pkeyattnum,
+ &pkeytype, &pkeytypmod,
&pkeycoll);
+
+ /* count XPaths */
+ numpaths = 1;
+ for (char *pos = xpathset;;)
+ {
+ pos = strstr(pos, pathsep);
+ if (!pos)
+ break;
+ numpaths++;
+ pos++;
+ }
+
+ tupdesc = CreateTemplateTupleDesc(numpaths + 1);
+
+ TupleDescInitEntry(tupdesc, 1, pkeyfield, pkeytype, pkeytypmod, 0);
+ TupleDescInitEntryCollation(tupdesc, 1, pkeycoll);
+
+ for (int i = 0; i < numpaths; i++)
+ {
+ AttrNumber attnum = 2 + i;
+ char *attname = psprintf("%s%d", xmlfield, i + 1);
+
+ TupleDescInitEntry(tupdesc, attnum, attname, TEXTOID, -1, 0);
+ }
+
+ BlessTupleDesc(tupdesc);
+
+ PG_RETURN_POINTER(tupdesc);
+}
+
PG_FUNCTION_INFO_V1(xpath_table);
Datum
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 85ac79f07e..cb5ed0cb2d 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -5328,6 +5328,18 @@ <title><structname>pg_proc</structname> Columns</title>
<entry>Data type of the return value</entry>
</row>
+ <row>
+ <entry><structfield>prodescribe</structfield></entry>
+ <entry><type>regproc</type></entry>
+ <entry><literal><link
linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+ <entry>
+ For functions returning type <type>record</type>, this can point to
+ another function that returns a row description of the result row of
+ this function invocation. See <xref linkend="xfunc-describe"/> for
+ details.
+ </entry>
+ </row>
+
<row>
<entry><structfield>proargtypes</structfield></entry>
<entry><type>oidvector</type></entry>
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index 22252556be..5a9d2f9489 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -803,6 +803,14 @@ <title>Table Functions</title>
that the parser knows, for example, what <literal>*</literal> should
expand to.
</para>
+
+ <para>
+ Some functions returning type <type>record</type> allow the column list
+ to be omitted if they can compute the result row type from the constant
+ input arguments. See <xref linkend="xfunc-describe"/> for details. In
+ that case, such functions can be called like functions with a specific
+ composite type return type.
+ </para>
</sect3>
<sect3 id="queries-lateral">
diff --git a/doc/src/sgml/ref/create_function.sgml
b/doc/src/sgml/ref/create_function.sgml
index dd6a2f7304..f53c8bc85f 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -25,6 +25,7 @@
[ RETURNS <replaceable class="parameter">rettype</replaceable>
| RETURNS TABLE ( <replaceable
class="parameter">column_name</replaceable> <replaceable
class="parameter">column_type</replaceable> [, ...] ) ]
{ LANGUAGE <replaceable class="parameter">lang_name</replaceable>
+ | DESCRIBE WITH <replaceable class="parameter">describe_func</replaceable>
| TRANSFORM { FOR TYPE <replaceable
class="parameter">type_name</replaceable> } [, ... ]
| WINDOW
| IMMUTABLE | STABLE | VOLATILE | [ NOT ] LEAKPROOF
@@ -262,6 +263,19 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>DESCRIBE WITH <replaceable
class="parameter">describe_func</replaceable></literal></term>
+
+ <listitem>
+ <para>
+ For functions returning type <type>record</type>, this optional clause
+ points to another function that is called to compute the return row
+ structure of a particular call. See <xref linkend="xfunc-describe"/>
+ for more information.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>TRANSFORM { FOR TYPE <replaceable
class="parameter">type_name</replaceable> } [, ... ] }</literal></term>
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index ca5e6efd7e..29bc399c63 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3239,6 +3239,72 @@ <title>Polymorphic Arguments and Return Types</title>
</para>
</sect2>
+ <sect2 id="xfunc-describe">
+ <title>Polymorphic Composite Return Type Describe Function</title>
+
+ <para>
+ A function that returns type <type>record</type> normally needs to be
+ called with an explicit column list to specify the names and types of the
+ result columns (see <xref linkend="queries-tablefunctions"/>). In some
+ cases, this can be avoided if the function can inform the database system
+ about the result row structure based on the input parameters of a
+ specific call. To do that, a helper function called the
+ <firstterm>describe</firstterm> function is attached to the
+ record-returning function to communicate the result row structure.
+ </para>
+
+ <para>
+ Consider, as an example, a function that unpacks a CSV document, passed
+ as a single text argument, into a set of rows. It might be defined like
+ this:
+<programlisting>
+CREATE FUNCTION csvtable(doc text) RETURNS SETOF record
+ LANGUAGE C
+ ...
+</programlisting>
+ Because the structure of the CSV document varies, the return type is
+ declared as <type>record</type>. However, if the passed document
+ argument is a constant, then the return row structure can be computed at
+ parse time. To do that, first define a separate describe function:
+<programlisting>
+CREATE FUNCTION cvstable_describe(internal) RETURNS internal
+ LANGUAGE C
+ ...
+</programlisting>
+ and specify that in the definition of the original function:
+<programlisting>
+CREATE FUNCTION csvtable(doc text) RETURNS SETOF record
+ DESCRIBE WITH csvtable_describe
+ LANGUAGE C
+ ...
+</programlisting>
+ (The name of the describe function does not matter.)
+ </para>
+
+ <para>
+ The describe function will be called at parse time with the same argument
+ types as the parent function. Only arguments that are constant at parse
+ time are passed; all other arguments are passed as null values. The
+ describe function must either return a <type>TupleDesc</type> or may
+ return SQL null (not C <symbol>NULL</symbol>) if it cannot compute the
+ row structure with the provided information.
+ </para>
+
+ <para>
+ In the example of the CSV processing function, the describe function
+ could parse the first line of the document to discover the number of
+ fields and their names.
+ </para>
+
+ <para>
+ If the describe function returns a null value, the function call will
+ error. But the same function call could still succeed if the caller
+ provides an explicit column list, as with record-returning functions
+ without a describe helper. If an explicit column list is provided, the
+ describe function is not called at all.
+ </para>
+ </sect2>
+
<sect2 id="xfunc-shared-addin">
<title>Shared Memory and LWLocks</title>
diff --git a/src/backend/catalog/pg_aggregate.c
b/src/backend/catalog/pg_aggregate.c
index 0b7face4cc..cfff80c493 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -615,6 +615,7 @@ AggregateCreate(const char *aggName,
replace, /*
maybe replacement */
false, /* doesn't
return a set */
finaltype, /*
returnType */
+ InvalidOid, /*
describe */
GetUserId(), /*
proowner */
INTERNALlanguageId,
/* languageObjectId */
InvalidOid, /* no
validator */
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 5194dcaac0..a165aea63e 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -70,6 +70,7 @@ ProcedureCreate(const char *procedureName,
bool replace,
bool returnsSet,
Oid returnType,
+ Oid describeFuncId,
Oid proowner,
Oid languageObjectId,
Oid languageValidator,
@@ -331,6 +332,7 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_pronargs - 1] = UInt16GetDatum(parameterCount);
values[Anum_pg_proc_pronargdefaults - 1] =
UInt16GetDatum(list_length(parameterDefaults));
values[Anum_pg_proc_prorettype - 1] = ObjectIdGetDatum(returnType);
+ values[Anum_pg_proc_prodescribe - 1] = ObjectIdGetDatum(describeFuncId);
values[Anum_pg_proc_proargtypes - 1] = PointerGetDatum(parameterTypes);
if (allParameterTypes != PointerGetDatum(NULL))
values[Anum_pg_proc_proallargtypes - 1] = allParameterTypes;
@@ -629,6 +631,15 @@ ProcedureCreate(const char *procedureName,
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ /* dependency on describe function */
+ if (describeFuncId)
+ {
+ referenced.classId = ProcedureRelationId;
+ referenced.objectId = describeFuncId;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
/* dependency on transform used by return type, if any */
if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
{
diff --git a/src/backend/commands/functioncmds.c
b/src/backend/commands/functioncmds.c
index c31c57e5e9..342b7fbd38 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -695,6 +695,7 @@ compute_function_attributes(ParseState *pstate,
List *options,
List **as,
char **language,
+ Node **describe,
Node **transform,
bool *windowfunc_p,
char *volatility_p,
@@ -709,6 +710,7 @@ compute_function_attributes(ParseState *pstate,
{
ListCell *option;
DefElem *as_item = NULL;
+ DefElem *describe_item = NULL;
DefElem *language_item = NULL;
DefElem *transform_item = NULL;
DefElem *windowfunc_item = NULL;
@@ -735,6 +737,15 @@ compute_function_attributes(ParseState *pstate,
parser_errposition(pstate,
defel->location)));
as_item = defel;
}
+ else if (strcmp(defel->defname, "describe") == 0)
+ {
+ if (describe_item)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or
redundant options"),
+ parser_errposition(pstate,
defel->location)));
+ describe_item = defel;
+ }
else if (strcmp(defel->defname, "language") == 0)
{
if (language_item)
@@ -810,6 +821,8 @@ compute_function_attributes(ParseState *pstate,
}
/* process optional items */
+ if (describe_item)
+ *describe = describe_item->arg;
if (transform_item)
*transform = transform_item->arg;
if (windowfunc_item)
@@ -926,6 +939,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
char *language;
Oid languageOid;
Oid languageValidator;
+ Node *describeDefElem = NULL;
Node *transformDefElem = NULL;
char *funcname;
Oid namespaceId;
@@ -936,6 +950,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
ArrayType *parameterNames;
List *parameterDefaults;
Oid variadicArgType;
+ Oid describeFuncOid = InvalidOid;
List *trftypes_list = NIL;
ArrayType *trftypes;
Oid requiredResultType;
@@ -979,7 +994,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
compute_function_attributes(pstate,
stmt->is_procedure,
stmt->options,
- &as_clause,
&language, &transformDefElem,
+ &as_clause,
&language,
+
&describeDefElem, &transformDefElem,
&isWindowFunc,
&volatility,
&isStrict,
&security, &isLeakProof,
&proconfig,
&procost, &prorows,
@@ -1029,6 +1045,17 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt
*stmt)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("only superuser can define a leakproof
function")));
+ if (describeDefElem)
+ {
+ describeFuncOid = LookupFuncWithArgs(OBJECT_FUNCTION,
castNode(ObjectWithArgs, describeDefElem), false);
+
+ if (get_func_rettype(describeFuncOid) != INTERNALOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("describe function must return
type %s",
+ "internal")));
+ }
+
if (transformDefElem)
{
ListCell *lc;
@@ -1152,6 +1179,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt
*stmt)
stmt->replace,
returnsSet,
prorettype,
+ describeFuncOid,
GetUserId(),
languageOid,
languageValidator,
diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c
index cdff43d3ce..cf0c745997 100644
--- a/src/backend/commands/proclang.c
+++ b/src/backend/commands/proclang.c
@@ -122,6 +122,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
false, /* replace */
false, /* returnsSet */
LANGUAGE_HANDLEROID,
+
InvalidOid, /* describe */
BOOTSTRAP_SUPERUSERID,
ClanguageId,
F_FMGR_C_VALIDATOR,
@@ -162,6 +163,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
false, /* replace */
false, /* returnsSet */
VOIDOID,
+
InvalidOid, /* describe */
BOOTSTRAP_SUPERUSERID,
ClanguageId,
F_FMGR_C_VALIDATOR,
@@ -205,6 +207,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
false, /* replace */
false, /* returnsSet */
VOIDOID,
+
InvalidOid, /* describe */
BOOTSTRAP_SUPERUSERID,
ClanguageId,
F_FMGR_C_VALIDATOR,
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 52097363fd..9829730f93 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1648,6 +1648,7 @@ makeRangeConstructors(const char *name, Oid namespace,
false, /*
replace */
false, /*
returns set */
rangeOid,
/* return type */
+ InvalidOid,
/* describe */
BOOTSTRAP_SUPERUSERID, /* proowner */
INTERNALlanguageId, /* language */
F_FMGR_INTERNAL_VALIDATOR, /* language validator */
diff --git a/src/backend/executor/execSRF.c b/src/backend/executor/execSRF.c
index 2312cc7142..555f7a7838 100644
--- a/src/backend/executor/execSRF.c
+++ b/src/backend/executor/execSRF.c
@@ -733,6 +733,7 @@ init_sexpr(Oid foid, Oid input_collation, Expr *node,
MemoryContext oldcontext;
functypclass = get_expr_result_type(sexpr->func.fn_expr,
+
false,
&funcrettype,
&tupdesc);
diff --git a/src/backend/executor/nodeFunctionscan.c
b/src/backend/executor/nodeFunctionscan.c
index ccb66ce1aa..bafdac1357 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -368,6 +368,7 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate,
int eflags)
* was made; we have to ignore any columns beyond "colcount".
*/
functypclass = get_expr_result_type(funcexpr,
+
rtfunc->funccolnames ? false : true,
&funcrettype,
&tupdesc);
diff --git a/src/backend/optimizer/prep/prepjointree.c
b/src/backend/optimizer/prep/prepjointree.c
index 14521728c6..52dc049d05 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1719,6 +1719,7 @@ pull_up_constant_function(PlannerInfo *root, Node *jtnode,
return jtnode; /* definitely composite */
functypclass = get_expr_result_type(rtf->funcexpr,
+
false,
&funcrettype,
&tupdesc);
if (functypclass != TYPEFUNC_SCALAR)
diff --git a/src/backend/optimizer/util/clauses.c
b/src/backend/optimizer/util/clauses.c
index 2d3ec22407..aeeaca3690 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4492,6 +4492,7 @@ inline_function(Oid funcid, Oid result_type, Oid
result_collid,
/* fexpr also provides a convenient way to resolve a composite result */
(void) get_expr_result_type((Node *) fexpr,
+ false,
NULL,
&rettupdesc);
@@ -5028,7 +5029,7 @@ inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte)
* function is just declared to return RECORD, dig the info out of the
AS
* clause.
*/
- functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
+ functypclass = get_expr_result_type((Node *) fexpr, false, NULL,
&rettupdesc);
if (functypclass == TYPEFUNC_RECORD)
rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
rtfunc->funccoltypes,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ba5916b4d2..8b1184de30 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -634,7 +634,7 @@ static Node *makeRecursiveViewSelect(char *relname, List
*aliases, Node *query);
CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
- DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
+ DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
DESCRIBE
DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
@@ -7962,6 +7962,10 @@ createfunc_opt_item:
{
$$ = makeDefElem("as", (Node *)$2, @1);
}
+ | DESCRIBE WITH function_with_argtypes
+ {
+ $$ = makeDefElem("describe", (Node
*)$3, @1);
+ }
| LANGUAGE NonReservedWord_or_Sconst
{
$$ = makeDefElem("language", (Node
*)makeString($2), @1);
@@ -15193,6 +15197,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DESCRIBE
| DETACH
| DICTIONARY
| DISABLE_P
diff --git a/src/backend/parser/parse_relation.c
b/src/backend/parser/parse_relation.c
index b875a50646..176957d5fb 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1732,6 +1732,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
* Now determine if the function returns a simple or composite
type.
*/
functypclass = get_expr_result_type(funcexpr,
+
coldeflist ? false : true,
&funcrettype,
&tupdesc);
@@ -2588,6 +2589,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int
sublevels_up,
TupleDesc tupdesc;
functypclass =
get_expr_result_type(rtfunc->funcexpr,
+
rtfunc->funccolnames ? false : true,
&funcrettype,
&tupdesc);
if (functypclass == TYPEFUNC_COMPOSITE
||
diff --git a/src/backend/utils/adt/jsonfuncs.c
b/src/backend/utils/adt/jsonfuncs.c
index 38758a626b..2f71725735 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2256,6 +2256,54 @@ json_to_record(PG_FUNCTION_ARGS)
true, false);
}
+Datum
+json_to_record_describe(PG_FUNCTION_ARGS)
+{
+ text *arg;
+ JsonLexContext *lex;
+ JsonSemAction *sem;
+ OkeysState *state;
+ TupleDesc tupdesc;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ arg = PG_GETARG_TEXT_PP(0);
+
+ lex = makeJsonLexContext(arg, true);
+
+ state = palloc(sizeof(OkeysState));
+ sem = palloc0(sizeof(JsonSemAction));
+
+ state->lex = lex;
+ state->result_size = 256;
+ state->result_count = 0;
+ state->sent_count = 0;
+ state->result = palloc(256 * sizeof(char *));
+
+ sem->semstate = (void *) state;
+ sem->array_start = okeys_array_start;
+ sem->scalar = okeys_scalar;
+ sem->object_field_start = okeys_object_field_start;
+ /* remainder are all NULL, courtesy of palloc0 above */
+
+ pg_parse_json(lex, sem);
+ /* keys are now in state->result */
+
+ pfree(lex->strval->data);
+ pfree(lex->strval);
+ pfree(lex);
+ pfree(sem);
+
+ tupdesc = CreateTemplateTupleDesc(state->result_count);
+ for (int i = 0; i < state->result_count; i++)
+ TupleDescInitEntry(tupdesc, i + 1, state->result[i], TEXTOID,
-1, 0);
+
+ BlessTupleDesc(tupdesc);
+
+ PG_RETURN_POINTER(tupdesc);
+}
+
/* helper function for diagnostics */
static void
populate_array_report_expected_array(PopulateArrayContext *ctx, int ndim)
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index b7eee3da1d..4cfc95fa30 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -34,6 +34,7 @@
static void shutdown_MultiFuncCall(Datum arg);
static TypeFuncClass internal_get_result_type(Oid funcid,
Node *call_expr,
+
bool try_describe,
ReturnSetInfo *rsinfo,
Oid *resultTypeId,
TupleDesc *resultTupleDesc);
@@ -199,6 +200,7 @@ get_call_result_type(FunctionCallInfo fcinfo,
{
return internal_get_result_type(fcinfo->flinfo->fn_oid,
fcinfo->flinfo->fn_expr,
+ false,
(ReturnSetInfo *) fcinfo->resultinfo,
resultTypeId,
resultTupleDesc);
@@ -210,6 +212,7 @@ get_call_result_type(FunctionCallInfo fcinfo,
*/
TypeFuncClass
get_expr_result_type(Node *expr,
+ bool try_describe,
Oid *resultTypeId,
TupleDesc *resultTupleDesc)
{
@@ -218,12 +221,14 @@ get_expr_result_type(Node *expr,
if (expr && IsA(expr, FuncExpr))
result = internal_get_result_type(((FuncExpr *) expr)->funcid,
expr,
+
try_describe,
NULL,
resultTypeId,
resultTupleDesc);
else if (expr && IsA(expr, OpExpr))
result = internal_get_result_type(get_opcode(((OpExpr *)
expr)->opno),
expr,
+
try_describe,
NULL,
resultTypeId,
resultTupleDesc);
@@ -292,6 +297,7 @@ get_func_result_type(Oid functionId,
{
return internal_get_result_type(functionId,
NULL,
+ false,
NULL,
resultTypeId,
resultTupleDesc);
@@ -308,6 +314,7 @@ get_func_result_type(Oid functionId,
static TypeFuncClass
internal_get_result_type(Oid funcid,
Node *call_expr,
+ bool try_describe,
ReturnSetInfo *rsinfo,
Oid *resultTypeId,
TupleDesc *resultTupleDesc)
@@ -362,6 +369,46 @@ internal_get_result_type(Oid funcid,
return result;
}
+ if (rettype == RECORDOID && procform->prodescribe && try_describe)
+ {
+ FmgrInfo flinfo;
+ LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
+ Datum funcres;
+ FuncExpr *fexpr;
+
+ fmgr_info(procform->prodescribe, &flinfo);
+ InitFunctionCallInfoData(*fcinfo, &flinfo, procform->pronargs,
InvalidOid, NULL, NULL);
+
+ Assert(call_expr);
+ fexpr = castNode(FuncExpr, call_expr);
+
+ for (int i = 0; i < procform->pronargs; i++)
+ {
+ Node *arg = list_nth(fexpr->args, i);
+
+ if (IsA(arg, Const))
+ {
+ Const *c = castNode(Const, arg);
+
+ fcinfo->args[i].value = c->constvalue;
+ fcinfo->args[i].isnull = c->constisnull;
+ }
+ else
+ fcinfo->args[i].isnull = true;
+ }
+
+ funcres = FunctionCallInvoke(fcinfo);
+
+ if (!fcinfo->isnull)
+ {
+ if (resultTupleDesc)
+ *resultTupleDesc = (TupleDesc)
DatumGetPointer(funcres);
+
+ ReleaseSysCache(tp);
+ return TYPEFUNC_COMPOSITE;
+ }
+ }
+
/*
* If scalar polymorphic result, try to resolve it.
*/
@@ -431,7 +478,7 @@ get_expr_result_tupdesc(Node *expr, bool noError)
TupleDesc tupleDesc;
TypeFuncClass functypclass;
- functypclass = get_expr_result_type(expr, NULL, &tupleDesc);
+ functypclass = get_expr_result_type(expr, true, NULL, &tupleDesc);
if (functypclass == TYPEFUNC_COMPOSITE ||
functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
diff --git a/src/include/catalog/pg_class.dat b/src/include/catalog/pg_class.dat
index f70d5bacb9..490bf87082 100644
--- a/src/include/catalog/pg_class.dat
+++ b/src/include/catalog/pg_class.dat
@@ -44,7 +44,7 @@
relname => 'pg_proc', reltype => 'pg_proc', relam => 'heap',
relfilenode => '0', relpages => '0', reltuples => '0', relallvisible => '0',
reltoastrelid => '0', relhasindex => 'f', relisshared => 'f',
- relpersistence => 'p', relkind => 'r', relnatts => '29', relchecks => '0',
+ relpersistence => 'p', relkind => 'r', relnatts => '30', relchecks => '0',
relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f',
relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't',
relreplident => 'n', relispartition => 'f', relfrozenxid => '3',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fcf2a1214c..b7b86709ef 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8348,7 +8348,11 @@
proargtypes => 'anyelement json bool', prosrc => 'json_populate_recordset' },
{ oid => '3204', descr => 'get record fields from a json object',
proname => 'json_to_record', provolatile => 's', prorettype => 'record',
- proargtypes => 'json', prosrc => 'json_to_record' },
+ proargtypes => 'json', prosrc => 'json_to_record',
+ prodescribe => 'json_to_record_describe' },
+{ oid => '3434', descr => 'describe function for json_to_record',
+ proname => 'json_to_record_describe', provolatile => 's', prorettype =>
'internal',
+ proargtypes => 'internal', prosrc => 'json_to_record_describe' },
{ oid => '3205',
descr => 'get set of records with fields from a json array of objects',
proname => 'json_to_recordset', prorows => '100', proisstrict => 'f',
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ee3959da09..e95a6310ec 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -86,6 +86,9 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP
BKI_ROWTYPE_OID(81,Proce
/* OID of result type */
Oid prorettype BKI_LOOKUP(pg_type);
+ /* function that describes the result row of this function, if 0 if
none */
+ regproc prodescribe BKI_DEFAULT(0) BKI_LOOKUP(pg_proc);
+
/*
* variable-length fields start here, but we allow direct access to
* proargtypes
@@ -182,6 +185,7 @@ extern ObjectAddress ProcedureCreate(const char
*procedureName,
bool
replace,
bool
returnsSet,
Oid
returnType,
+ Oid
describeFuncId,
Oid
proowner,
Oid
languageObjectId,
Oid
languageValidator,
diff --git a/src/include/funcapi.h b/src/include/funcapi.h
index f9b75ae390..2d80a0ff8c 100644
--- a/src/include/funcapi.h
+++ b/src/include/funcapi.h
@@ -156,6 +156,7 @@ extern TypeFuncClass get_call_result_type(FunctionCallInfo
fcinfo,
Oid *resultTypeId,
TupleDesc *resultTupleDesc);
extern TypeFuncClass get_expr_result_type(Node *expr,
+
bool try_describe,
Oid *resultTypeId,
TupleDesc *resultTupleDesc);
extern TypeFuncClass get_func_result_type(Oid functionId,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b1184c2d15..10702cc611 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -129,6 +129,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("describe", DESCRIBE, UNRESERVED_KEYWORD)
PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
diff --git a/src/interfaces/ecpg/preproc/ecpg.tokens
b/src/interfaces/ecpg/preproc/ecpg.tokens
index 8e0527fdb7..cb64ef9999 100644
--- a/src/interfaces/ecpg/preproc/ecpg.tokens
+++ b/src/interfaces/ecpg/preproc/ecpg.tokens
@@ -5,7 +5,7 @@
SQL_CARDINALITY SQL_CONNECT
SQL_COUNT
SQL_DATETIME_INTERVAL_CODE
- SQL_DATETIME_INTERVAL_PRECISION SQL_DESCRIBE
+ SQL_DATETIME_INTERVAL_PRECISION
SQL_DESCRIPTOR SQL_DISCONNECT SQL_FOUND
SQL_FREE SQL_GET SQL_GO SQL_GOTO SQL_IDENTIFIED
SQL_INDICATOR SQL_KEY_MEMBER SQL_LENGTH
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer
b/src/interfaces/ecpg/preproc/ecpg.trailer
index 0dbdfdc122..f3843146ce 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -1058,14 +1058,14 @@ UsingConst: Iconst { $$ = $1; }
/*
* We accept DESCRIBE [OUTPUT] but do nothing with DESCRIBE INPUT so far.
*/
-ECPGDescribe: SQL_DESCRIBE INPUT_P prepared_name using_descriptor
+ECPGDescribe: DESCRIBE INPUT_P prepared_name using_descriptor
{
const char *con = connection ? connection : "NULL";
mmerror(PARSE_ERROR, ET_WARNING, "using unsupported DESCRIBE
statement");
$$ = (char *) mm_alloc(sizeof("1, , ") + strlen(con) +
strlen($3));
sprintf($$, "1, %s, %s", con, $3);
}
- | SQL_DESCRIBE opt_output prepared_name using_descriptor
+ | DESCRIBE opt_output prepared_name using_descriptor
{
const char *con = connection ? connection : "NULL";
struct variable *var;
@@ -1077,20 +1077,20 @@ ECPGDescribe: SQL_DESCRIBE INPUT_P prepared_name
using_descriptor
$$ = (char *) mm_alloc(sizeof("0, , ") + strlen(con) +
strlen($3));
sprintf($$, "0, %s, %s", con, $3);
}
- | SQL_DESCRIBE opt_output prepared_name into_descriptor
+ | DESCRIBE opt_output prepared_name into_descriptor
{
const char *con = connection ? connection : "NULL";
$$ = (char *) mm_alloc(sizeof("0, , ") + strlen(con) +
strlen($3));
sprintf($$, "0, %s, %s", con, $3);
}
- | SQL_DESCRIBE INPUT_P prepared_name into_sqlda
+ | DESCRIBE INPUT_P prepared_name into_sqlda
{
const char *con = connection ? connection : "NULL";
mmerror(PARSE_ERROR, ET_WARNING, "using unsupported DESCRIBE
statement");
$$ = (char *) mm_alloc(sizeof("1, , ") + strlen(con) +
strlen($3));
sprintf($$, "1, %s, %s", con, $3);
}
- | SQL_DESCRIBE opt_output prepared_name into_sqlda
+ | DESCRIBE opt_output prepared_name into_sqlda
{
const char *con = connection ? connection : "NULL";
$$ = (char *) mm_alloc(sizeof("0, , ") + strlen(con) +
strlen($3));
@@ -1502,7 +1502,6 @@ ECPGKeywords_vanames: SQL_BREAK { $$ =
mm_strdup("break"); }
;
ECPGKeywords_rest: SQL_CONNECT { $$ = mm_strdup("connect"); }
- | SQL_DESCRIBE { $$ =
mm_strdup("describe"); }
| SQL_DISCONNECT { $$ =
mm_strdup("disconnect"); }
| SQL_OPEN { $$ =
mm_strdup("open"); }
| SQL_VAR { $$ =
mm_strdup("var"); }
diff --git a/src/interfaces/ecpg/preproc/ecpg_kwlist.h
b/src/interfaces/ecpg/preproc/ecpg_kwlist.h
index 0170bfefdc..e8e15f3d8e 100644
--- a/src/interfaces/ecpg/preproc/ecpg_kwlist.h
+++ b/src/interfaces/ecpg/preproc/ecpg_kwlist.h
@@ -33,7 +33,6 @@ PG_KEYWORD("connect", SQL_CONNECT)
PG_KEYWORD("count", SQL_COUNT)
PG_KEYWORD("datetime_interval_code", SQL_DATETIME_INTERVAL_CODE)
PG_KEYWORD("datetime_interval_precision", SQL_DATETIME_INTERVAL_PRECISION)
-PG_KEYWORD("describe", SQL_DESCRIBE)
PG_KEYWORD("descriptor", SQL_DESCRIPTOR)
PG_KEYWORD("disconnect", SQL_DISCONNECT)
PG_KEYWORD("found", SQL_FOUND)
diff --git a/src/test/regress/expected/json.out
b/src/test/regress/expected/json.out
index c4156cf2a6..66548702b2 100644
--- a/src/test/regress/expected/json.out
+++ b/src/test/regress/expected/json.out
@@ -2219,6 +2219,12 @@ select json_object('{a,b,"","d e f"}','{1,2,3,"a b c"}');
(1 row)
-- json_to_record and json_to_recordset
+select * from json_to_record('{"a":1,"b":"foo","c":"bar"}');
+ a | b | c
+---+-----+-----
+ 1 | foo | bar
+(1 row)
+
select * from json_to_record('{"a":1,"b":"foo","c":"bar"}')
as x(a int, b text, d text);
a | b | d
diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql
index 20354f04e3..7f18d8d900 100644
--- a/src/test/regress/sql/json.sql
+++ b/src/test/regress/sql/json.sql
@@ -724,6 +724,8 @@ CREATE TEMP TABLE foo (serial_num int, name text, type
text);
-- json_to_record and json_to_recordset
+select * from json_to_record('{"a":1,"b":"foo","c":"bar"}');
+
select * from json_to_record('{"a":1,"b":"foo","c":"bar"}')
as x(a int, b text, d text);
base-commit: 6de7bcb76f6593dcd107a6bfed645f2142bf3225
--
2.25.0