SQL-standard function body
This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.
Instead of the PostgreSQL-specific AS $$ string literal $$ syntax,
this allows writing out the SQL statements making up the body
unquoted, either as a single statement:
CREATE FUNCTION add(a integer, b integer) RETURNS integer
LANGUAGE SQL
RETURN a + b;
or as a block
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
BEGIN ATOMIC
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
END;
The function body is parsed at function definition time and stored as
expression nodes in probin. So at run time, no further parsing is
required.
However, this form does not support polymorphic arguments, because
there is no more parse analysis done at call time.
Dependencies between the function and the objects it uses are fully
tracked.
A new RETURN statement is introduced. This can only be used inside
function bodies. Internally, it is treated much like a SELECT
statement.
psql needs some new intelligence to keep track of function body
boundaries so that it doesn't send off statements when it sees
semicolons that are inside a function body.
Also, per SQL standard, LANGUAGE SQL is the default, so it does not
need to be specified anymore.
Note: Some parts of the patch look better under git diff -w (ignoring
whitespace changes) because if/else blocks were introduced around
existing code.
TODOs and discussion points:
- pg_dump is not yet supported. As a consequence, the pg_upgrade
tests don't pass yet. I'm thinking about changing pg_dump to use
pg_get_functiondef here instead of coding everything by hand. Some
initial experimenting showed that this would be possible with minimal
tweaking and it would surely be beneficial in the long run.
- The compiled function body is stored in the probin field of pg_proc.
This matches the historical split similar to adsrc/adbin, consrc/conbin,
but this has now been abandoned. Also, this field should ideally be of
type pg_node_tree, so reusing probin for that is probably not good.
Seems like a new field might be best.
- More test coverage is needed. Surprisingly, there wasn't actually any
test AFAICT that just creates and SQL function and runs it. Most of
that code is tested incidentally, but there is very little or no
targeted testing of this functionality.
- Some of the changes in pg_proc.c, functioncmds.c, and functions.c in
particular were jammed in and could use some reorganization after the
basic ideas are solidified.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
v1-0001-SQL-standard-function-body.patchtext/plain; charset=UTF-8; name=v1-0001-SQL-standard-function-body.patch; x-mac-creator=0; x-mac-type=0Download
From 9611034103216bf57a76546cc212786fa8fe5b73 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Tue, 30 Jun 2020 19:42:08 +0200
Subject: [PATCH v1] SQL-standard function body
This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.
Instead of the PostgreSQL-specific AS $$ string literal $$ syntax,
this allows writing out the SQL statements making up the body
unquoted, either as a single statement:
CREATE FUNCTION add(a integer, b integer) RETURNS integer
LANGUAGE SQL
RETURN a + b;
or as a block
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
BEGIN ATOMIC
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
END;
The function body is parsed at function definition time and stored as
expression nodes in probin. So at run time, no further parsing is
required.
However, this form does not support polymorphic arguments, because
there is no more parse analysis done at call time.
Dependencies between the function and the objects it uses are fully
tracked.
A new RETURN statement is introduced. This can only be used inside
function bodies. Internally, it is treated much like a SELECT
statement.
psql needs some new intelligence to keep track of function body
boundaries so that it doesn't send off statements when it sees
semicolons that are inside a function body.
Also, per SQL standard, LANGUAGE SQL is the default, so it does not
need to be specified anymore.
TODO: pg_dump is not yet supported. As a consequence, the pg_upgrade
tests don't pass yet.
---
doc/src/sgml/ref/create_function.sgml | 126 +++++++++++--
doc/src/sgml/ref/create_procedure.sgml | 62 ++++++-
src/backend/catalog/pg_proc.c | 148 ++++++++-------
src/backend/commands/aggregatecmds.c | 2 +
src/backend/commands/functioncmds.c | 96 +++++++---
src/backend/executor/functions.c | 79 ++++----
src/backend/nodes/copyfuncs.c | 15 ++
src/backend/nodes/equalfuncs.c | 13 ++
src/backend/nodes/outfuncs.c | 12 ++
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/util/clauses.c | 25 ++-
src/backend/parser/analyze.c | 35 ++++
src/backend/parser/gram.y | 126 ++++++++++---
src/backend/tcop/postgres.c | 3 +-
src/backend/utils/adt/ruleutils.c | 76 +++++++-
src/fe_utils/psqlscan.l | 23 ++-
src/include/commands/defrem.h | 2 +
src/include/executor/functions.h | 15 ++
src/include/fe_utils/psqlscan_int.h | 2 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 13 ++
src/include/parser/kwlist.h | 2 +
src/include/tcop/tcopprot.h | 1 +
src/interfaces/ecpg/preproc/ecpg.addons | 6 +
src/interfaces/ecpg/preproc/ecpg.trailer | 4 +-
.../regress/expected/create_function_3.out | 170 +++++++++++++++++-
.../regress/expected/create_procedure.out | 58 ++++++
src/test/regress/sql/create_function_3.sql | 77 +++++++-
src/test/regress/sql/create_procedure.sql | 26 +++
29 files changed, 1041 insertions(+), 178 deletions(-)
diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index f81cedc823..f7cc428773 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -38,6 +38,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -257,8 +258,9 @@ <title>Parameters</title>
The name of the language that the function is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g. <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g. <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -577,6 +579,44 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> function. This can
+ either be a single statement
+<programlisting>
+RETURN <replaceable>expression</replaceable>
+</programlisting>
+ or a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the function body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at function definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at function definition time. This form tracks dependencies between the
+ function and objects used in the function body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling functions. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
<para>
@@ -669,6 +709,15 @@ <title>Examples</title>
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
+</programlisting>
+ The same function written in a more SQL-conforming style, using argument
+ names and an unquoted body:
+<programlisting>
+CREATE FUNCTION add(a integer, b integer) RETURNS integer
+ LANGUAGE SQL
+ IMMUTABLE
+ RETURNS NULL ON NULL INPUT
+ RETURN a + b;
</programlisting>
</para>
@@ -799,23 +848,74 @@ <title>Writing <literal>SECURITY DEFINER</literal> Functions Safely</title>
<title>Compatibility</title>
<para>
- A <command>CREATE FUNCTION</command> command is defined in the SQL standard.
- The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. The attributes are not portable, neither are the
- different available languages.
+ A <command>CREATE FUNCTION</command> command is defined in the SQL
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. Conversely, the SQL
+ standard specifies a number of optional features that are not implemented
+ in <productname>PostgreSQL</productname>.
</para>
<para>
- For compatibility with some other database systems,
- <replaceable class="parameter">argmode</replaceable> can be written
- either before or after <replaceable class="parameter">argname</replaceable>.
- But only the first way is standard-compliant.
+ The following are important compatibility issues:
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>OR REPLACE</literal> is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For compatibility with some other database systems, <replaceable
+ class="parameter">argmode</replaceable> can be written either before or
+ after <replaceable class="parameter">argname</replaceable>. But only
+ the first way is standard-compliant.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For parameter defaults, the SQL standard specifies only the syntax with
+ the <literal>DEFAULT</literal> key word. The syntax with
+ <literal>=</literal> is used in T-SQL and Firebird.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The <literal>SETOF</literal> modifier is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Only <literal>SQL</literal> is standardized as a language.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ All other attributes except <literal>CALLED ON NULL INPUT</literal> and
+ <literal>RETURNS NULL ON NULL INPUT</literal> are not standardized.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For the body of <literal>LANGUAGE SQL</literal> functions, the SQL
+ standard only specifies the <replaceable>sql_body</replaceable> form.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
<para>
- For parameter defaults, the SQL standard specifies only the syntax with
- the <literal>DEFAULT</literal> key word. The syntax
- with <literal>=</literal> is used in T-SQL and Firebird.
+ Simple <literal>LANGUAGE SQL</literal> functions can be written in a way
+ that is both standard-conforming and portable to other implementations.
+ More complex functions using advanced features, optimization attributes, or
+ other languages will necessarily be specific to PostgreSQL in a significant
+ way.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_procedure.sgml b/doc/src/sgml/ref/create_procedure.sgml
index 0ea6513cb5..9a84132a2b 100644
--- a/doc/src/sgml/ref/create_procedure.sgml
+++ b/doc/src/sgml/ref/create_procedure.sgml
@@ -29,6 +29,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -164,8 +165,9 @@ <title>Parameters</title>
The name of the language that the procedure is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g. <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g. <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -301,6 +303,41 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> procedure. This should
+ be a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the procedure body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at procedure definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at procedure definition time. This form tracks dependencies between the
+ procedure and objects used in the procedure body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling procedures. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
@@ -320,6 +357,7 @@ <title>Notes</title>
<refsect1 id="sql-createprocedure-examples">
<title>Examples</title>
+ <para>
<programlisting>
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
@@ -327,9 +365,21 @@ <title>Examples</title>
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
$$;
-
+</programlisting>
+ or
+<programlisting>
+CREATE PROCEDURE insert_data(a integer, b integer)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO tbl VALUES (a);
+ INSERT INTO tbl VALUES (b);
+END;
+</programlisting>
+ and call like this:
+<programlisting>
CALL insert_data(1, 2);
</programlisting>
+ </para>
</refsect1>
<refsect1 id="sql-createprocedure-compat">
@@ -337,9 +387,9 @@ <title>Compatibility</title>
<para>
A <command>CREATE PROCEDURE</command> command is defined in the SQL
- standard. The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. For details see
- also <xref linkend="sql-createfunction"/>.
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. For details see also
+ <xref linkend="sql-createfunction"/>.
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 6cdda35d1c..4474a73d25 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -32,6 +32,7 @@
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_type.h"
#include "tcop/pquery.h"
@@ -118,8 +119,6 @@ ProcedureCreate(const char *procedureName,
/*
* sanity checks
*/
- Assert(PointerIsValid(prosrc));
-
parameterCount = parameterTypes->dim1;
if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
ereport(ERROR,
@@ -330,7 +329,10 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_protrftypes - 1] = trftypes;
else
nulls[Anum_pg_proc_protrftypes - 1] = true;
- values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ if (prosrc)
+ values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ else
+ nulls[Anum_pg_proc_prosrc - 1] = true;
if (probin)
values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin);
else
@@ -648,6 +650,10 @@ ProcedureCreate(const char *procedureName,
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
+ /* dependency on SQL routine body */
+ if (languageObjectId == SQLlanguageId && probin)
+ recordDependencyOnExpr(&myself, stringToNode(probin), NIL, DEPENDENCY_NORMAL);
+
/* dependency on owner */
if (!is_update)
recordDependencyOnOwner(ProcedureRelationId, retval, proowner);
@@ -816,16 +822,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
Oid funcoid = PG_GETARG_OID(0);
HeapTuple tuple;
Form_pg_proc proc;
- List *raw_parsetree_list;
- List *querytree_list;
- ListCell *lc;
- bool isnull;
- Datum tmp;
- char *prosrc;
- parse_error_callback_arg callback_arg;
- ErrorContextCallback sqlerrcontext;
bool haspolyarg;
- int i;
if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
PG_RETURN_VOID();
@@ -849,7 +846,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
/* Disallow pseudotypes in arguments */
/* except for polymorphic */
haspolyarg = false;
- for (i = 0; i < proc->pronargs; i++)
+ for (int i = 0; i < proc->pronargs; i++)
{
if (get_typtype(proc->proargtypes.values[i]) == TYPTYPE_PSEUDO)
{
@@ -866,72 +863,93 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
/* Postpone body checks if !check_function_bodies */
if (check_function_bodies)
{
+ Datum tmp;
+ bool isnull;
+ List *querytree_list = NIL;
+
tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
if (isnull)
- elog(ERROR, "null prosrc");
-
- prosrc = TextDatumGetCString(tmp);
+ {
+ char *probin;
- /*
- * Setup error traceback support for ereport().
- */
- callback_arg.proname = NameStr(proc->proname);
- callback_arg.prosrc = prosrc;
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_probin, &isnull);
+ if (isnull)
+ elog(ERROR, "null probin and prosrc");
- sqlerrcontext.callback = sql_function_parse_error_callback;
- sqlerrcontext.arg = (void *) &callback_arg;
- sqlerrcontext.previous = error_context_stack;
- error_context_stack = &sqlerrcontext;
+ probin = TextDatumGetCString(tmp);
+ querytree_list = castNode(List, stringToNode(probin));
+ }
+ else
+ {
+ char *prosrc;
+ List *raw_parsetree_list;
+ parse_error_callback_arg callback_arg;
+ ErrorContextCallback sqlerrcontext;
- /*
- * We can't do full prechecking of the function definition if there
- * are any polymorphic input types, because actual datatypes of
- * expression results will be unresolvable. The check will be done at
- * runtime instead.
- *
- * We can run the text through the raw parser though; this will at
- * least catch silly syntactic errors.
- */
- raw_parsetree_list = pg_parse_query(prosrc);
+ prosrc = TextDatumGetCString(tmp);
- if (!haspolyarg)
- {
/*
- * OK to do full precheck: analyze and rewrite the queries, then
- * verify the result type.
+ * Setup error traceback support for ereport().
*/
- SQLFunctionParseInfoPtr pinfo;
- Oid rettype;
- TupleDesc rettupdesc;
+ callback_arg.proname = NameStr(proc->proname);
+ callback_arg.prosrc = prosrc;
- /* But first, set up parameter information */
- pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
+ sqlerrcontext.callback = sql_function_parse_error_callback;
+ sqlerrcontext.arg = (void *) &callback_arg;
+ sqlerrcontext.previous = error_context_stack;
+ error_context_stack = &sqlerrcontext;
- querytree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ /*
+ * We can't do full prechecking of the function definition if there
+ * are any polymorphic input types, because actual datatypes of
+ * expression results will be unresolvable. The check will be done at
+ * runtime instead.
+ *
+ * We can run the text through the raw parser though; this will at
+ * least catch silly syntactic errors.
+ */
+ raw_parsetree_list = pg_parse_query(prosrc);
+
+ if (!haspolyarg)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *querytree_sublist;
-
- querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
- prosrc,
- (ParserSetupHook) sql_fn_parser_setup,
- pinfo,
- NULL);
- querytree_list = list_concat(querytree_list,
- querytree_sublist);
+ /*
+ * OK to do full precheck: analyze and rewrite the queries, then
+ * verify the result type.
+ */
+ ListCell *lc;
+ SQLFunctionParseInfoPtr pinfo;
+ Oid rettype;
+ TupleDesc rettupdesc;
+
+ /* But first, set up parameter information */
+ pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
+
+ querytree_list = NIL;
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *querytree_sublist;
+
+ querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ prosrc,
+ (ParserSetupHook) sql_fn_parser_setup,
+ pinfo,
+ NULL);
+ querytree_list = list_concat(querytree_list,
+ querytree_sublist);
+ }
+
+ check_sql_fn_statements(querytree_list);
+
+ (void) get_func_result_type(funcoid, &rettype, &rettupdesc);
+
+ (void) check_sql_fn_retval(querytree_list,
+ rettype, rettupdesc,
+ false, NULL);
}
- check_sql_fn_statements(querytree_list);
-
- (void) get_func_result_type(funcoid, &rettype, &rettupdesc);
-
- (void) check_sql_fn_retval(querytree_list,
- rettype, rettupdesc,
- false, NULL);
+ error_context_stack = sqlerrcontext.previous;
}
-
- error_context_stack = sqlerrcontext.previous;
}
ReleaseSysCache(tuple);
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 6bf54e64f8..26b3fa27de 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -313,9 +313,11 @@ DefineAggregate(ParseState *pstate,
InvalidOid,
OBJECT_AGGREGATE,
¶meterTypes,
+ NULL,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ NULL,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 1b5bdcec8b..859bc72974 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -53,9 +53,11 @@
#include "commands/proclang.h"
#include "executor/execdesc.h"
#include "executor/executor.h"
+#include "executor/functions.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
@@ -186,9 +188,11 @@ interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType)
@@ -300,6 +304,8 @@ interpret_function_parameter_list(ParseState *pstate,
errmsg("VARIADIC parameter must be the last input parameter")));
inTypes[inCount++] = toid;
isinput = true;
+ if (parameterTypes_list)
+ *parameterTypes_list = lappend_oid(*parameterTypes_list, toid);
}
/* handle output parameters */
@@ -376,6 +382,9 @@ interpret_function_parameter_list(ParseState *pstate,
have_names = true;
}
+ if (inParameterNames_list)
+ *inParameterNames_list = lappend(*inParameterNames_list, makeString(fp->name ? fp->name : pstrdup("")));
+
if (fp->defexpr)
{
Node *def;
@@ -790,28 +799,10 @@ compute_function_attributes(ParseState *pstate,
defel->defname);
}
- /* process required items */
if (as_item)
*as = (List *) as_item->arg;
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no function body specified")));
- *as = NIL; /* keep compiler quiet */
- }
-
if (language_item)
*language = strVal(language_item->arg);
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no language specified")));
- *language = NULL; /* keep compiler quiet */
- }
-
- /* process optional items */
if (transform_item)
*transform = transform_item->arg;
if (windowfunc_item)
@@ -860,10 +851,19 @@ compute_function_attributes(ParseState *pstate,
*/
static void
interpret_AS_clause(Oid languageOid, const char *languageName,
- char *funcname, List *as,
+ char *funcname, List *as, List *sql_body,
+ List *parameterTypes, List *inParameterNames,
char **prosrc_str_p, char **probin_str_p)
{
- Assert(as != NIL);
+ if (sql_body && as)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("duplicate function body specified")));
+
+ if (sql_body && languageOid != SQLlanguageId)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("inline SQL function body only valid for language SQL")));
if (languageOid == ClanguageId)
{
@@ -885,6 +885,53 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
*prosrc_str_p = funcname;
}
}
+ else if (sql_body)
+ {
+ List *transformed_stmts = NIL;
+ ListCell *lc;
+ SQLFunctionParseInfoPtr pinfo;
+
+ pinfo = (SQLFunctionParseInfoPtr) palloc0(sizeof(SQLFunctionParseInfo));
+
+ pinfo->fname = funcname;
+ pinfo->nargs = list_length(parameterTypes);
+ pinfo->argtypes = (Oid *) palloc(pinfo->nargs * sizeof(Oid));
+ pinfo->argnames = (char **) palloc(pinfo->nargs * sizeof(char *));
+ for (int i = 0; i < list_length(parameterTypes); i++)
+ {
+ char *s = strVal(list_nth(inParameterNames, i));
+
+ pinfo->argtypes[i] = list_nth_oid(parameterTypes, i);
+ if (IsPolymorphicType(pinfo->argtypes[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("SQL function with unquoted function body cannot have polymorphic arguments")));
+
+ if (s[0] != '\0')
+ pinfo->argnames[i] = s;
+ else
+ pinfo->argnames[i] = NULL;
+ }
+
+ foreach(lc, sql_body)
+ {
+ Node *stmt = lfirst(lc);
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ /* ignore NULL statement; see gram.y */
+ if (!stmt)
+ continue;
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, stmt);
+ transformed_stmts = lappend(transformed_stmts, q);
+ free_parsestate(pstate);
+ }
+
+ *probin_str_p = nodeToString(transformed_stmts);
+ *prosrc_str_p = NULL;
+ }
else
{
/* Everything else wants the given string in prosrc. */
@@ -933,9 +980,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
Oid namespaceId;
AclResult aclresult;
oidvector *parameterTypes;
+ List *parameterTypes_list = NIL;
ArrayType *allParameterTypes;
ArrayType *parameterModes;
ArrayType *parameterNames;
+ List *inParameterNames_list = NIL;
List *parameterDefaults;
Oid variadicArgType;
List *trftypes_list = NIL;
@@ -966,6 +1015,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
get_namespace_name(namespaceId));
/* Set default attributes */
+ as_clause = NULL;
+ language = "sql";
isWindowFunc = false;
isStrict = false;
security = false;
@@ -1057,9 +1108,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageOid,
stmt->is_procedure ? OBJECT_PROCEDURE : OBJECT_FUNCTION,
¶meterTypes,
+ ¶meterTypes_list,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ &inParameterNames_list,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
@@ -1116,7 +1169,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
trftypes = NULL;
}
- interpret_AS_clause(languageOid, language, funcname, as_clause,
+ interpret_AS_clause(languageOid, language, funcname, as_clause, stmt->sql_body,
+ parameterTypes_list, inParameterNames_list,
&prosrc_str, &probin_str);
/*
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index f940f48c6d..911149aa27 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -26,6 +26,7 @@
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_func.h"
+#include "rewrite/rewriteHandler.h"
#include "storage/proc.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
@@ -128,21 +129,6 @@ typedef struct
typedef SQLFunctionCache *SQLFunctionCachePtr;
-/*
- * Data structure needed by the parser callback hooks to resolve parameter
- * references during parsing of a SQL function's body. This is separate from
- * SQLFunctionCache since we sometimes do parsing separately from execution.
- */
-typedef struct SQLFunctionParseInfo
-{
- char *fname; /* function's name */
- int nargs; /* number of input arguments */
- Oid *argtypes; /* resolved types of input arguments */
- char **argnames; /* names of input arguments; NULL if none */
- /* Note that argnames[i] can be NULL, if some args are unnamed */
- Oid collation; /* function's input collation, if known */
-} SQLFunctionParseInfo;
-
/* non-export function prototypes */
static Node *sql_fn_param_ref(ParseState *pstate, ParamRef *pref);
@@ -606,7 +592,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
HeapTuple procedureTuple;
Form_pg_proc procedureStruct;
SQLFunctionCachePtr fcache;
- List *raw_parsetree_list;
List *queryTree_list;
List *flat_query_list;
List *resulttlist;
@@ -682,9 +667,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
procedureTuple,
Anum_pg_proc_prosrc,
&isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", foid);
- fcache->src = TextDatumGetCString(tmp);
/*
* Parse and rewrite the queries in the function text. Use sublists to
@@ -701,22 +683,55 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
* but we'll not worry about it until the module is rewritten to use
* plancache.c.
*/
- raw_parsetree_list = pg_parse_query(fcache->src);
-
queryTree_list = NIL;
flat_query_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (isNull)
+ {
+ char *binstr;
+ List *stored_query_list;
+
+ tmp = SysCacheGetAttr(PROCOID,
+ procedureTuple,
+ Anum_pg_proc_probin,
+ &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and probin for function %u", foid);
+
+ binstr = TextDatumGetCString(tmp);
+ stored_query_list = stringToNode(binstr);
+
+ foreach(lc, stored_query_list)
+ {
+ Query *parsetree = lfirst_node(Query, lc);
+ List *queryTree_sublist;
+
+ AcquireRewriteLocks(parsetree, true, false);
+ queryTree_sublist = pg_rewrite_query(parsetree);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ flat_query_list = list_concat(flat_query_list, queryTree_sublist);
+ }
+ }
+ else
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *queryTree_sublist;
-
- queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
- fcache->src,
- (ParserSetupHook) sql_fn_parser_setup,
- fcache->pinfo,
- NULL);
- queryTree_list = lappend(queryTree_list, queryTree_sublist);
- flat_query_list = list_concat(flat_query_list, queryTree_sublist);
+ List *raw_parsetree_list;
+
+ fcache->src = TextDatumGetCString(tmp);
+
+ raw_parsetree_list = pg_parse_query(fcache->src);
+
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *queryTree_sublist;
+
+ queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ fcache->src,
+ (ParserSetupHook) sql_fn_parser_setup,
+ fcache->pinfo,
+ NULL);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ flat_query_list = list_concat(flat_query_list, queryTree_sublist);
+ }
}
check_sql_fn_statements(flat_query_list);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d8cf87e6d0..1f8b6b2d9c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3069,6 +3069,7 @@ _copyQuery(const Query *from)
COPY_SCALAR_FIELD(hasModifyingCTE);
COPY_SCALAR_FIELD(hasForUpdate);
COPY_SCALAR_FIELD(hasRowSecurity);
+ COPY_SCALAR_FIELD(isReturn);
COPY_NODE_FIELD(cteList);
COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(jointree);
@@ -3197,6 +3198,16 @@ _copySetOperationStmt(const SetOperationStmt *from)
return newnode;
}
+static ReturnStmt *
+_copyReturnStmt(const ReturnStmt *from)
+{
+ ReturnStmt *newnode = makeNode(ReturnStmt);
+
+ COPY_NODE_FIELD(returnval);
+
+ return newnode;
+}
+
static AlterTableStmt *
_copyAlterTableStmt(const AlterTableStmt *from)
{
@@ -3573,6 +3584,7 @@ _copyCreateFunctionStmt(const CreateFunctionStmt *from)
COPY_NODE_FIELD(parameters);
COPY_NODE_FIELD(returnType);
COPY_NODE_FIELD(options);
+ COPY_NODE_FIELD(sql_body);
return newnode;
}
@@ -5226,6 +5238,9 @@ copyObjectImpl(const void *from)
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
+ case T_ReturnStmt:
+ retval = _copyReturnStmt(from);
+ break;
case T_AlterTableStmt:
retval = _copyAlterTableStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 627b026b19..44d2f31bd3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -966,6 +966,7 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_SCALAR_FIELD(hasModifyingCTE);
COMPARE_SCALAR_FIELD(hasForUpdate);
COMPARE_SCALAR_FIELD(hasRowSecurity);
+ COMPARE_SCALAR_FIELD(isReturn);
COMPARE_NODE_FIELD(cteList);
COMPARE_NODE_FIELD(rtable);
COMPARE_NODE_FIELD(jointree);
@@ -1082,6 +1083,14 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
return true;
}
+static bool
+_equalReturnStmt(const ReturnStmt *a, const ReturnStmt *b)
+{
+ COMPARE_NODE_FIELD(returnval);
+
+ return true;
+}
+
static bool
_equalAlterTableStmt(const AlterTableStmt *a, const AlterTableStmt *b)
{
@@ -1394,6 +1403,7 @@ _equalCreateFunctionStmt(const CreateFunctionStmt *a, const CreateFunctionStmt *
COMPARE_NODE_FIELD(parameters);
COMPARE_NODE_FIELD(returnType);
COMPARE_NODE_FIELD(options);
+ COMPARE_NODE_FIELD(sql_body);
return true;
}
@@ -3278,6 +3288,9 @@ equal(const void *a, const void *b)
case T_SetOperationStmt:
retval = _equalSetOperationStmt(a, b);
break;
+ case T_ReturnStmt:
+ retval = _equalReturnStmt(a, b);
+ break;
case T_AlterTableStmt:
retval = _equalAlterTableStmt(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..2153fcf59b 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2758,6 +2758,14 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
WRITE_NODE_FIELD(rarg);
}
+static void
+_outReturnStmt(StringInfo str, const ReturnStmt *node)
+{
+ WRITE_NODE_TYPE("RETURN");
+
+ WRITE_NODE_FIELD(returnval);
+}
+
static void
_outFuncCall(StringInfo str, const FuncCall *node)
{
@@ -2946,6 +2954,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_BOOL_FIELD(hasModifyingCTE);
WRITE_BOOL_FIELD(hasForUpdate);
WRITE_BOOL_FIELD(hasRowSecurity);
+ WRITE_BOOL_FIELD(isReturn);
WRITE_NODE_FIELD(cteList);
WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(jointree);
@@ -4196,6 +4205,9 @@ outNode(StringInfo str, const void *obj)
case T_SelectStmt:
_outSelectStmt(str, obj);
break;
+ case T_ReturnStmt:
+ _outReturnStmt(str, obj);
+ break;
case T_ColumnDef:
_outColumnDef(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..c6d0009820 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -263,6 +263,7 @@ _readQuery(void)
READ_BOOL_FIELD(hasModifyingCTE);
READ_BOOL_FIELD(hasForUpdate);
READ_BOOL_FIELD(hasRowSecurity);
+ READ_BOOL_FIELD(isReturn);
READ_NODE_FIELD(cteList);
READ_NODE_FIELD(rtable);
READ_NODE_FIELD(jointree);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 0c6fe0115a..531adad3d4 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4439,7 +4439,22 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
Anum_pg_proc_prosrc,
&isNull);
if (isNull)
- elog(ERROR, "null prosrc for function %u", funcid);
+ {
+ char *probin;
+ List *querytree_list;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_probin, &isNull);
+ if (isNull)
+ elog(ERROR, "null probin and prosrc");
+
+ probin = TextDatumGetCString(tmp);
+ querytree_list = castNode(List, stringToNode(probin));
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
src = TextDatumGetCString(tmp);
/*
@@ -4497,6 +4512,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
querytree = transformTopLevelStmt(pstate, linitial(raw_parsetree_list));
free_parsestate(pstate);
+ }
/*
* The single command must be a simple "SELECT expression".
@@ -4984,7 +5000,11 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
Anum_pg_proc_prosrc,
&isNull);
if (isNull)
- elog(ERROR, "null prosrc for function %u", func_oid);
+ {
+ goto fail; // TODO
+ }
+ else
+ {
src = TextDatumGetCString(tmp);
/*
@@ -5036,6 +5056,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
if (list_length(querytree_list) != 1)
goto fail;
querytree = linitial(querytree_list);
+ }
/*
* The single command must be a plain SELECT.
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 401da5dedf..29b120b2ea 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -66,6 +66,7 @@ static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
bool isTopLevel, List **targetlist);
static void determineRecursiveColTypes(ParseState *pstate,
Node *larg, List *nrtargetlist);
+static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList);
static List *transformUpdateTargetList(ParseState *pstate,
@@ -304,6 +305,10 @@ transformStmt(ParseState *pstate, Node *parseTree)
}
break;
+ case T_ReturnStmt:
+ result = transformReturnStmt(pstate, (ReturnStmt *) parseTree);
+ break;
+
/*
* Special cases
*/
@@ -2221,6 +2226,36 @@ determineRecursiveColTypes(ParseState *pstate, Node *larg, List *nrtargetlist)
}
+/*
+ * transformReturnStmt -
+ * transforms a return statement
+ */
+static Query *
+transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+
+ qry->commandType = CMD_SELECT;
+ qry->isReturn = true;
+
+ qry->targetList = list_make1(makeTargetEntry((Expr *) transformExpr(pstate, stmt->returnval, EXPR_KIND_SELECT_TARGET),
+ 1, NULL, false));
+
+ if (pstate->p_resolve_unknowns)
+ resolveTargetListUnknowns(pstate, qry->targetList);
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasAggs = pstate->p_hasAggs;
+
+ assign_query_collations(pstate, qry);
+
+ return qry;
+}
+
+
/*
* transformUpdateStmt -
* transforms an update statement
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e669d75a5a..4f5ed7f229 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -253,7 +253,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct SelectLimit *selectlimit;
}
-%type <node> stmt schema_stmt
+%type <node> stmt toplevel_stmt schema_stmt routine_body_stmt
AlterEventTrigStmt AlterCollationStmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
@@ -280,9 +280,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
- RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
+ RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt
RuleActionStmt RuleActionStmtOrEmpty RuleStmt
- SecLabelStmt SelectStmt TransactionStmt TruncateStmt
+ SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
UnlistenStmt UpdateStmt VacuumStmt
VariableResetStmt VariableSetStmt VariableShowStmt
ViewStmt CheckPointStmt CreateConversionStmt
@@ -385,14 +385,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> vacuum_relation
%type <selectlimit> opt_select_limit select_limit limit_clause
-%type <list> stmtblock stmtmulti
+%type <list> stmtblock stmtmulti routine_body_stmt_list
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
reloptions opt_reloptions
OptWith distinct_clause opt_all_clause opt_definition func_args func_args_list
func_args_with_defaults func_args_with_defaults_list
aggr_args aggr_args_list
- func_as createfunc_opt_list alterfunc_opt_list
+ func_as createfunc_opt_list opt_createfunc_opt_list alterfunc_opt_list
old_aggr_definition old_aggr_list
oper_argtypes RuleActionList RuleActionMulti
opt_column_list columnList opt_name_list
@@ -418,6 +418,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
vacuum_relation_list opt_vacuum_relation_list
drop_option_list
+%type <list> opt_routine_body
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
@@ -627,7 +628,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATOMIC ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
@@ -689,7 +690,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING
REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
- RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
+ RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
@@ -816,7 +817,7 @@ stmtblock: stmtmulti
* we'd get -1 for the location in such cases.
* We also take care to discard empty statements entirely.
*/
-stmtmulti: stmtmulti ';' stmt
+stmtmulti: stmtmulti ';' toplevel_stmt
{
if ($1 != NIL)
{
@@ -828,7 +829,7 @@ stmtmulti: stmtmulti ';' stmt
else
$$ = $1;
}
- | stmt
+ | toplevel_stmt
{
if ($1 != NULL)
$$ = list_make1(makeRawStmt($1, 0));
@@ -837,7 +838,16 @@ stmtmulti: stmtmulti ';' stmt
}
;
-stmt :
+/*
+ * toplevel_stmt includes BEGIN and END. stmt does not include them, because
+ * those words have different meanings in function bodys.
+ */
+toplevel_stmt:
+ stmt
+ | TransactionStmtLegacy
+ ;
+
+stmt:
AlterEventTrigStmt
| AlterCollationStmt
| AlterDatabaseStmt
@@ -7343,7 +7353,7 @@ opt_nulls_order: NULLS_LA FIRST_P { $$ = SORTBY_NULLS_FIRST; }
CreateFunctionStmt:
CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS func_return createfunc_opt_list
+ RETURNS func_return opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7352,10 +7362,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = $7;
n->options = $8;
+ n->sql_body = $9;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS TABLE '(' table_func_column_list ')' createfunc_opt_list
+ RETURNS TABLE '(' table_func_column_list ')' opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7365,10 +7376,11 @@ CreateFunctionStmt:
n->returnType = TableFuncTypeName($9);
n->returnType->location = @7;
n->options = $11;
+ n->sql_body = $12;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7377,10 +7389,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
| CREATE opt_or_replace PROCEDURE func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = true;
@@ -7389,6 +7402,7 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
;
@@ -7668,6 +7682,11 @@ aggregate_with_argtypes_list:
{ $$ = lappend($1, $3); }
;
+opt_createfunc_opt_list:
+ createfunc_opt_list
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
createfunc_opt_list:
/* Must be at least one to prevent conflict */
createfunc_opt_item { $$ = list_make1($1); }
@@ -7779,6 +7798,50 @@ func_as: Sconst { $$ = list_make1(makeString($1)); }
}
;
+ReturnStmt: RETURN a_expr
+ {
+ ReturnStmt *r = makeNode(ReturnStmt);
+ r->returnval = (Node *) $2;
+ $$ = (Node *) r;
+ }
+ ;
+
+opt_routine_body:
+ ReturnStmt
+ {
+ $$ = list_make1($1);
+ }
+ | BEGIN_P ATOMIC routine_body_stmt_list END_P
+ {
+ $$ = $3;
+ }
+ | /*EMPTY*/
+ {
+ $$ = NIL;
+ }
+ ;
+
+routine_body_stmt_list:
+ routine_body_stmt_list routine_body_stmt ';'
+ {
+ $$ = lappend($1, $2);
+ }
+ | /*EMPTY*/
+ {
+ /*
+ * For an empty body we insert a single fake element, so
+ * that the parse analysis code can tell apart an empty
+ * body from no body at all.
+ */
+ $$ = list_make1(NULL);
+ }
+ ;
+
+routine_body_stmt:
+ stmt
+ | ReturnStmt
+ ;
+
transform_type_list:
FOR TYPE_P Typename { $$ = list_make1($3); }
| transform_type_list ',' FOR TYPE_P Typename { $$ = lappend($1, $5); }
@@ -9725,13 +9788,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | BEGIN_P opt_transaction transaction_mode_list_or_empty
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_BEGIN;
- n->options = $3;
- $$ = (Node *)n;
- }
| START TRANSACTION transaction_mode_list_or_empty
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9747,14 +9803,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | END_P opt_transaction opt_transaction_chain
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_COMMIT;
- n->options = NIL;
- n->chain = $3;
- $$ = (Node *)n;
- }
| ROLLBACK opt_transaction opt_transaction_chain
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9821,6 +9869,24 @@ TransactionStmt:
}
;
+TransactionStmtLegacy:
+ BEGIN_P opt_transaction transaction_mode_list_or_empty
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_BEGIN;
+ n->options = $3;
+ $$ = (Node *)n;
+ }
+ | END_P opt_transaction opt_transaction_chain
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_COMMIT;
+ n->options = NIL;
+ n->chain = $3;
+ $$ = (Node *)n;
+ }
+ ;
+
opt_transaction: WORK {}
| TRANSACTION {}
| /*EMPTY*/ {}
@@ -15039,6 +15105,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| BACKWARD
@@ -15237,6 +15304,7 @@ unreserved_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| ROLE
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index c9424f167c..b63c330653 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -176,7 +176,6 @@ static int interactive_getc(void);
static int SocketBackend(StringInfo inBuf);
static int ReadCommand(StringInfo inBuf);
static void forbidden_in_wal_sender(char firstchar);
-static List *pg_rewrite_query(Query *query);
static bool check_log_statement(List *stmt_list);
static int errdetail_execute(List *raw_parsetree_list);
static int errdetail_params(ParamListInfo params);
@@ -761,7 +760,7 @@ pg_analyze_and_rewrite_params(RawStmt *parsetree,
* Note: query must just have come from the parser, because we do not do
* AcquireRewriteLocks() on it.
*/
-static List *
+List *
pg_rewrite_query(Query *query)
{
List *querytree_list;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 076c3c019f..0ecda56e54 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -174,6 +174,9 @@ typedef struct
List *outer_tlist; /* referent for OUTER_VAR Vars */
List *inner_tlist; /* referent for INNER_VAR Vars */
List *index_tlist; /* referent for INDEX_VAR Vars */
+ /* Special namespace representing a function signature: */
+ int numargs;
+ char **argnames;
} deparse_namespace;
/*
@@ -2800,9 +2803,54 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
}
/* And finally the function definition ... */
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
+ if (proc->prolang == SQLlanguageId && !isnull)
+ {
+ List *stmts = castNode(List, stringToNode(TextDatumGetCString(tmp)));
+ ListCell *lc;
+ int numargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ deparse_namespace dpns = {0};
+ bool need_block;
+
+ numargs = get_func_arg_info(proctup,
+ &argtypes, &argnames, &argmodes);
+ dpns.numargs = numargs;
+ dpns.argnames = argnames;
+
+ /*
+ * We need a BEGIN ATOMIC/END block unless the body is a single RETURN
+ * statement.
+ */
+ need_block = true;
+ if (list_length(stmts) == 1)
+ {
+ Query *query = linitial_node(Query, stmts);
+
+ if (query->isReturn)
+ need_block = false;
+ }
+
+ if (need_block)
+ appendStringInfoString(&buf, "BEGIN ATOMIC\n");
+ foreach(lc, stmts)
+ {
+ Query *query = lfirst_node(Query, lc);
+
+ get_query_def(query, &buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ if (lc != list_tail(stmts))
+ appendStringInfoChar(&buf, ';');
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (need_block)
+ appendStringInfoString(&buf, "END");
+ }
+ else
+ {
appendStringInfoString(&buf, "AS ");
- tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
if (!isnull)
{
simple_quote_literal(&buf, TextDatumGetCString(tmp));
@@ -2831,6 +2879,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, dq.data, dq.len);
appendStringInfoString(&buf, prosrc);
appendBinaryStringInfo(&buf, dq.data, dq.len);
+ }
appendStringInfoChar(&buf, '\n');
@@ -5428,7 +5477,10 @@ get_basic_select_query(Query *query, deparse_context *context,
/*
* Build up the query string - first we say SELECT
*/
- appendStringInfoString(buf, "SELECT");
+ if (query->isReturn)
+ appendStringInfoString(buf, "RETURN");
+ else
+ appendStringInfoString(buf, "SELECT");
/* Add the DISTINCT clause if given */
if (query->distinctClause != NIL)
@@ -7560,6 +7612,26 @@ get_parameter(Param *param, deparse_context *context)
return;
}
+ /*
+ * If it's an external parameter, see if the outermost namespace provides
+ * function argument names.
+ */
+ if (param->paramkind == PARAM_EXTERN)
+ {
+ dpns = lfirst(list_tail(context->namespaces));
+ if (dpns->argnames)
+ {
+ char *argname = dpns->argnames[param->paramid - 1];
+
+ if (argname)
+ {
+ // TODO: qualify with function name if necessary
+ appendStringInfo(context->buf, "%s", quote_identifier(argname));
+ return;
+ }
+ }
+ }
+
/*
* Not PARAM_EXEC, or couldn't find referent: just print $N.
*/
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 08dffde1ba..ee34463e67 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -645,10 +645,11 @@ other .
";" {
ECHO;
- if (cur_state->paren_depth == 0)
+ if (cur_state->paren_depth == 0 && cur_state->begin_depth == 0)
{
/* Terminate lexing temporarily */
cur_state->start_state = YY_START;
+ cur_state->identifier_count = 0;
return LEXRES_SEMI;
}
}
@@ -661,6 +662,8 @@ other .
"\\"[;:] {
/* Force a semi-colon or colon into the query buffer */
psqlscan_emit(cur_state, yytext + 1, 1);
+ if (yytext[1] == ';')
+ cur_state->identifier_count = 0;
}
"\\" {
@@ -867,6 +870,17 @@ other .
{identifier} {
+ cur_state->identifier_count++;
+ if (pg_strcasecmp(yytext, "begin") == 0)
+ {
+ if (cur_state->identifier_count > 1)
+ cur_state->begin_depth++;
+ }
+ else if (pg_strcasecmp(yytext, "end") == 0)
+ {
+ if (cur_state->begin_depth > 0)
+ cur_state->begin_depth--;
+ }
ECHO;
}
@@ -1054,6 +1068,11 @@ psql_scan(PsqlScanState state,
result = PSCAN_INCOMPLETE;
*prompt = PROMPT_PAREN;
}
+ if (state->begin_depth > 0)
+ {
+ result = PSCAN_INCOMPLETE;
+ *prompt = PROMPT_CONTINUE;
+ }
else if (query_buf->len > 0)
{
result = PSCAN_EOL;
@@ -1170,6 +1189,8 @@ psql_scan_reset(PsqlScanState state)
if (state->dolqstart)
free(state->dolqstart);
state->dolqstart = NULL;
+ state->identifier_count = 0;
+ state->begin_depth = 0;
}
/*
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index c26a102b17..4d93afbd4b 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -68,9 +68,11 @@ extern void interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType);
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index cb13428a5a..5d547c66ed 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -20,6 +20,21 @@
/* This struct is known only within executor/functions.c */
typedef struct SQLFunctionParseInfo *SQLFunctionParseInfoPtr;
+/*
+ * Data structure needed by the parser callback hooks to resolve parameter
+ * references during parsing of a SQL function's body. This is separate from
+ * SQLFunctionCache since we sometimes do parsing separately from execution.
+ */
+typedef struct SQLFunctionParseInfo
+{
+ char *fname; /* function's name */
+ int nargs; /* number of input arguments */
+ Oid *argtypes; /* resolved types of input arguments */
+ char **argnames; /* names of input arguments; NULL if none */
+ /* Note that argnames[i] can be NULL, if some args are unnamed */
+ Oid collation; /* function's input collation, if known */
+} SQLFunctionParseInfo;
+
extern Datum fmgr_sql(PG_FUNCTION_ARGS);
extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTuple,
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 311f80394a..fb8f58aa29 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -114,6 +114,8 @@ typedef struct PsqlScanStateData
int paren_depth; /* depth of nesting in parentheses */
int xcdepth; /* depth of nesting in slash-star comments */
char *dolqstart; /* current $foo$ quote start string */
+ int identifier_count; /* identifiers since start of statement */
+ int begin_depth; /* depth of begin/end routine body blocks */
/*
* Callback functions provided by the program making use of the lexer,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..ab8b745665 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -316,6 +316,7 @@ typedef enum NodeTag
T_DeleteStmt,
T_UpdateStmt,
T_SelectStmt,
+ T_ReturnStmt,
T_AlterTableStmt,
T_AlterTableCmd,
T_AlterDomainStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5e1ffafb91..5c436516a3 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -132,6 +132,8 @@ typedef struct Query
bool hasForUpdate; /* FOR [KEY] UPDATE/SHARE was specified */
bool hasRowSecurity; /* rewriter has applied some RLS policy */
+ bool isReturn; /* is a RETURN statement */
+
List *cteList; /* WITH list (of CommonTableExpr's) */
List *rtable; /* list of range table entries */
@@ -1670,6 +1672,16 @@ typedef struct SetOperationStmt
} SetOperationStmt;
+/*
+ * RETURN statement (inside SQL function body)
+ */
+typedef struct ReturnStmt
+{
+ NodeTag type;
+ Node *returnval;
+} ReturnStmt;
+
+
/*****************************************************************************
* Other Statements (no optimizations required)
*
@@ -2840,6 +2852,7 @@ typedef struct CreateFunctionStmt
List *parameters; /* a list of FunctionParameter */
TypeName *returnType; /* the return type */
List *options; /* a list of DefElem */
+ List *sql_body;
} CreateFunctionStmt;
typedef enum FunctionParameterMode
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 08f22ce211..c972c6f2ba 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -48,6 +48,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("atomic", ATOMIC, UNRESERVED_KEYWORD)
PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
@@ -344,6 +345,7 @@ PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD)
PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD)
PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD)
PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD)
+PG_KEYWORD("return", RETURN, UNRESERVED_KEYWORD)
PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD)
PG_KEYWORD("returns", RETURNS, UNRESERVED_KEYWORD)
PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD)
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index bd30607b07..e626c8eafd 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -43,6 +43,7 @@ typedef enum
extern PGDLLIMPORT int log_statement;
extern List *pg_parse_query(const char *query_string);
+extern List *pg_rewrite_query(Query *query);
extern List *pg_analyze_and_rewrite(RawStmt *parsetree,
const char *query_string,
Oid *paramTypes, int numParams,
diff --git a/src/interfaces/ecpg/preproc/ecpg.addons b/src/interfaces/ecpg/preproc/ecpg.addons
index 300381eaad..0441561c52 100644
--- a/src/interfaces/ecpg/preproc/ecpg.addons
+++ b/src/interfaces/ecpg/preproc/ecpg.addons
@@ -87,6 +87,12 @@ ECPG: stmtTransactionStmt block
whenever_action(2);
free($1);
}
+ECPG: toplevel_stmtTransactionStmtLegacy block
+ {
+ fprintf(base_yyout, "{ ECPGtrans(__LINE__, %s, \"%s\");", connection ? connection : "NULL", $1);
+ whenever_action(2);
+ free($1);
+ }
ECPG: stmtViewStmt rule
| ECPGAllocateDescr
{
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer
index 6ccc8ab916..7f4be655f9 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -4,8 +4,8 @@ statements: /*EMPTY*/
| statements statement
;
-statement: ecpgstart at stmt ';' { connection = NULL; }
- | ecpgstart stmt ';'
+statement: ecpgstart at toplevel_stmt ';' { connection = NULL; }
+ | ecpgstart toplevel_stmt ';'
| ecpgstart ECPGVarDeclaration
{
fprintf(base_yyout, "%s", $2);
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index ba260df996..38436b3ccc 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -17,7 +17,7 @@ SET search_path TO temp_func_test, public;
CREATE FUNCTION functest_A_1(text, date) RETURNS bool LANGUAGE 'sql'
AS 'SELECT $1 = ''abcd'' AND $2 > ''2001-01-01''';
CREATE FUNCTION functest_A_2(text[]) RETURNS int LANGUAGE 'sql'
- AS 'SELECT $1[0]::int';
+ AS 'SELECT $1[1]::int';
CREATE FUNCTION functest_A_3() RETURNS bool LANGUAGE 'sql'
AS 'SELECT false';
SELECT proname, prorettype::regtype, proargtypes::regtype[] FROM pg_proc
@@ -31,6 +31,24 @@ SELECT proname, prorettype::regtype, proargtypes::regtype[] FROM pg_proc
functest_a_3 | boolean | {}
(3 rows)
+SELECT functest_A_1('abcd', '2020-01-01');
+ functest_a_1
+--------------
+ t
+(1 row)
+
+SELECT functest_A_2(ARRAY['1', '2', '3']);
+ functest_a_2
+--------------
+ 1
+(1 row)
+
+SELECT functest_A_3();
+ functest_a_3
+--------------
+ f
+(1 row)
+
--
-- IMMUTABLE | STABLE | VOLATILE
--
@@ -237,6 +255,135 @@ SELECT pg_get_functiondef('functest_F_2'::regproc);
(1 row)
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+ERROR: SQL function with unquoted function body cannot have polymorphic arguments
+SELECT functest_S_1('abcd', '2020-01-01');
+ functest_s_1
+--------------
+ t
+(1 row)
+
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+ functest_s_2
+--------------
+ 1
+(1 row)
+
+SELECT functest_S_3();
+ functest_s_3
+--------------
+ f
+(1 row)
+
+SELECT functest_S_10('abcd', '2020-01-01');
+ functest_s_10
+---------------
+ t
+(1 row)
+
+SELECT functest_S_13();
+ functest_s_13
+---------------
+ f
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_1(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN ((a = 'abcd'::text) AND (b > '01-01-2001'::date)) +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_2(a text[])+
+ RETURNS integer +
+ LANGUAGE sql +
+ RETURN ((a)[1])::integer +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+ pg_get_functiondef
+----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN false +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+ pg_get_functiondef
+-------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_10(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT ((a = 'abcd'::text) AND (b > '01-01-2001'::date)) +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_13()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT 1; +
+ SELECT false AS bool +
+ END +
+
+(1 row)
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+SELECT functest_S_14();
+ functest_s_14
+---------------
+ 2
+(1 row)
+
+DROP TABLE functest3 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view functestv3
+drop cascades to function functest_s_14()
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
RETURNS int
@@ -266,6 +413,20 @@ SELECT routine_name, ordinal_position, parameter_name, parameter_default
(7 rows)
DROP FUNCTION functest_IS_1(int, int, text), functest_IS_2(int), functest_IS_3(int);
+CREATE TABLE functest1 (a int, b int);
+CREATE SEQUENCE functest2;
+CREATE FUNCTION functest_IS_4()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest1);
+CREATE FUNCTION functest_IS_5()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest2');
+DROP TABLE functest1 CASCADE;
+NOTICE: drop cascades to function functest_is_4()
+DROP SEQUENCE functest2 CASCADE;
+NOTICE: drop cascades to function functest_is_5()
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
@@ -342,7 +503,7 @@ SELECT * FROM voidtest5(3);
-- Cleanup
DROP SCHEMA temp_func_test CASCADE;
-NOTICE: drop cascades to 21 other objects
+NOTICE: drop cascades to 26 other objects
DETAIL: drop cascades to function functest_a_1(text,date)
drop cascades to function functest_a_2(text[])
drop cascades to function functest_a_3()
@@ -358,6 +519,11 @@ drop cascades to function functest_f_1(integer)
drop cascades to function functest_f_2(integer)
drop cascades to function functest_f_3(integer)
drop cascades to function functest_f_4(integer)
+drop cascades to function functest_s_1(text,date)
+drop cascades to function functest_s_2(text[])
+drop cascades to function functest_s_3()
+drop cascades to function functest_s_10(text,date)
+drop cascades to function functest_s_13()
drop cascades to function functest_b_2(bigint)
drop cascades to function voidtest1(integer)
drop cascades to function voidtest2(integer,integer)
diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out
index 211a42cefa..c927a2894a 100644
--- a/src/test/regress/expected/create_procedure.out
+++ b/src/test/regress/expected/create_procedure.out
@@ -65,6 +65,41 @@ SELECT * FROM cp_test ORDER BY b COLLATE "C";
1 | xyzzy
(3 rows)
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+\df ptest1s
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+---------+------------------+---------------------+------
+ public | ptest1s | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest1s'::regproc);
+ pg_get_functiondef
+----------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest1s(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ INSERT INTO cp_test (a, b) +
+ VALUES (1, x) +
+ END +
+
+(1 row)
+
+CALL ptest1s('b');
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+ a | b
+---+-------
+ 1 | 0
+ 1 | a
+ 1 | b
+ 1 | xyzzy
+(4 rows)
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -146,6 +181,28 @@ AS $$
SELECT a = b;
$$;
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+\df ptest8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+--------+------------------+---------------------+------
+ public | ptest8 | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest8'::regproc);
+ pg_get_functiondef
+---------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest8(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ END +
+
+(1 row)
+
+CALL ptest8('');
-- various error cases
CALL version(); -- error: not a procedure
ERROR: version() is not a procedure
@@ -204,6 +261,7 @@ ALTER ROUTINE ptest1a RENAME TO ptest1;
DROP ROUTINE cp_testfunc1(int);
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
DROP USER regress_cp_user1;
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
index 7a2df0ea8a..91071bfb9f 100644
--- a/src/test/regress/sql/create_function_3.sql
+++ b/src/test/regress/sql/create_function_3.sql
@@ -23,7 +23,7 @@ CREATE SCHEMA temp_func_test;
CREATE FUNCTION functest_A_1(text, date) RETURNS bool LANGUAGE 'sql'
AS 'SELECT $1 = ''abcd'' AND $2 > ''2001-01-01''';
CREATE FUNCTION functest_A_2(text[]) RETURNS int LANGUAGE 'sql'
- AS 'SELECT $1[0]::int';
+ AS 'SELECT $1[1]::int';
CREATE FUNCTION functest_A_3() RETURNS bool LANGUAGE 'sql'
AS 'SELECT false';
SELECT proname, prorettype::regtype, proargtypes::regtype[] FROM pg_proc
@@ -31,6 +31,10 @@ CREATE FUNCTION functest_A_3() RETURNS bool LANGUAGE 'sql'
'functest_A_2'::regproc,
'functest_A_3'::regproc) ORDER BY proname;
+SELECT functest_A_1('abcd', '2020-01-01');
+SELECT functest_A_2(ARRAY['1', '2', '3']);
+SELECT functest_A_3();
+
--
-- IMMUTABLE | STABLE | VOLATILE
--
@@ -149,6 +153,60 @@ CREATE FUNCTION functest_F_4(int) RETURNS bool LANGUAGE 'sql'
SELECT pg_get_functiondef('functest_F_2'::regproc);
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+
+SELECT functest_S_1('abcd', '2020-01-01');
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+SELECT functest_S_3();
+
+SELECT functest_S_10('abcd', '2020-01-01');
+SELECT functest_S_13();
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+
+SELECT functest_S_14();
+
+DROP TABLE functest3 CASCADE;
+
+
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
@@ -173,6 +231,23 @@ CREATE FUNCTION functest_IS_3(a int default 1, out b int)
DROP FUNCTION functest_IS_1(int, int, text), functest_IS_2(int), functest_IS_3(int);
+CREATE TABLE functest1 (a int, b int);
+CREATE SEQUENCE functest2;
+
+CREATE FUNCTION functest_IS_4()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest1);
+
+CREATE FUNCTION functest_IS_5()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest2');
+
+DROP TABLE functest1 CASCADE;
+DROP SEQUENCE functest2 CASCADE;
+
+
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
diff --git a/src/test/regress/sql/create_procedure.sql b/src/test/regress/sql/create_procedure.sql
index 89b96d580f..acbe92fb9a 100644
--- a/src/test/regress/sql/create_procedure.sql
+++ b/src/test/regress/sql/create_procedure.sql
@@ -28,6 +28,21 @@ CREATE PROCEDURE ptest1(x text)
SELECT * FROM cp_test ORDER BY b COLLATE "C";
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+
+\df ptest1s
+SELECT pg_get_functiondef('ptest1s'::regproc);
+
+CALL ptest1s('b');
+
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -112,6 +127,16 @@ CREATE PROCEDURE ptest7(a text, b text)
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+
+\df ptest8
+SELECT pg_get_functiondef('ptest8'::regproc);
+CALL ptest8('');
+
+
-- various error cases
CALL version(); -- error: not a procedure
@@ -159,6 +184,7 @@ CREATE USER regress_cp_user1;
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
base-commit: 68de1440c79d75e529ff8c7395d698252370f992
--
2.27.0
On Tue, Jun 30, 2020 at 1:49 PM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:
This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.
With what other implementations is it compatible?
The function body is parsed at function definition time and stored as
expression nodes in probin. So at run time, no further parsing is
required.However, this form does not support polymorphic arguments, because
there is no more parse analysis done at call time.Dependencies between the function and the objects it uses are fully
tracked.A new RETURN statement is introduced. This can only be used inside
function bodies. Internally, it is treated much like a SELECT
statement.psql needs some new intelligence to keep track of function body
boundaries so that it doesn't send off statements when it sees
semicolons that are inside a function body.Also, per SQL standard, LANGUAGE SQL is the default, so it does not
need to be specified anymore.
Hmm, this all seems like a pretty big semantic change. IIUC, right
now, a SQL function can only contain one statement, but it seems like
with this patch you can have a block in there with a bunch of
statements, sorta like plpgsql. But probably you don't have all of the
functionality of plpgsql available. Also, the fact that you're doing
parsing earlier means that e.g. creating a table and inserting into it
won't work. Maybe that's fine. But it almost seems like you are
inventing a whole new PL....
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Greetings,
* Robert Haas (robertmhaas@gmail.com) wrote:
Hmm, this all seems like a pretty big semantic change. IIUC, right
now, a SQL function can only contain one statement, but it seems like
with this patch you can have a block in there with a bunch of
statements, sorta like plpgsql.
From our docs:
CREATE FUNCTION tf1 (accountno integer, debit numeric) RETURNS numeric AS $$
UPDATE bank
SET balance = balance - debit
WHERE accountno = tf1.accountno;
SELECT 1;
$$ LANGUAGE SQL;
https://www.postgresql.org/docs/current/xfunc-sql.html
Haven't looked at the patch, tho if it adds support for something the
SQL standard defines, that generally seems like a positive to me.
Thanks,
Stephen
út 30. 6. 2020 v 19:58 odesílatel Robert Haas <robertmhaas@gmail.com>
napsal:
On Tue, Jun 30, 2020 at 1:49 PM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.With what other implementations is it compatible?
The function body is parsed at function definition time and stored as
expression nodes in probin. So at run time, no further parsing is
required.However, this form does not support polymorphic arguments, because
there is no more parse analysis done at call time.Dependencies between the function and the objects it uses are fully
tracked.A new RETURN statement is introduced. This can only be used inside
function bodies. Internally, it is treated much like a SELECT
statement.psql needs some new intelligence to keep track of function body
boundaries so that it doesn't send off statements when it sees
semicolons that are inside a function body.Also, per SQL standard, LANGUAGE SQL is the default, so it does not
need to be specified anymore.Hmm, this all seems like a pretty big semantic change. IIUC, right
now, a SQL function can only contain one statement, but it seems like
with this patch you can have a block in there with a bunch of
statements, sorta like plpgsql. But probably you don't have all of the
functionality of plpgsql available. Also, the fact that you're doing
parsing earlier means that e.g. creating a table and inserting into it
won't work. Maybe that's fine. But it almost seems like you are
inventing a whole new PL....
It is SQL/PSM and can be nice to have it.
I am a little bit afraid about performance - SQL functions doesn't use plan
cache and simple expressions. Without inlining it can be too slow.
Regards
Pavel
Show quoted text
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Robert Haas <robertmhaas@gmail.com> writes:
On Tue, Jun 30, 2020 at 1:49 PM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.
With what other implementations is it compatible?
Yeah ... I'm sort of wondering exactly what this really accomplishes.
I think "portability" is a red herring unfortunately.
Tracking the dependencies of the function body sounds nice at first
glance, so it might be a feature. But given our experiences with having
to use check_function_bodies = off to not have impossible dependency loops
in dump/restore, I rather wonder whether it'll be a net loss in practice.
IIUC, this implementation is flat out incapable of doing the equivalent of
check_function_bodies = off, and that sounds like trouble.
Hmm, this all seems like a pretty big semantic change. IIUC, right
now, a SQL function can only contain one statement,
Not true, you can have more. However, it's nonetheless an enormous
semantic change, if only because the CREATE FUNCTION-time search_path
is now relevant instead of the execution-time path. That *will*
break use-cases I've heard of, where the same function is applied
to different tables by adjusting the path. It'd certainly be useful
from some perspectives (eg better security), but it's ... different.
Replicating the creation-time search path will be a big headache for
pg_dump, I bet.
But it almost seems like you are
inventing a whole new PL....
Yes. Having this replace the existing SQL PL would be a disaster,
because there are use-cases this simply can't meet (even assuming
that we can fix the polymorphism problem, which seems a bit unlikely).
We'd need to treat it as a new PL type.
Perhaps this is useful enough to justify all the work involved,
but I'm not sure.
regards, tom lane
I wrote:
Replicating the creation-time search path will be a big headache for
pg_dump, I bet.
On further thought, we probably don't have to. Re-parsing the function
body the same way is exactly the same problem as re-parsing a view or
matview body the same way. I don't want to claim that that's a 100%
solved problem, but I've heard few complaints in that area lately.
The point remains that exposing the function body's dependencies will
constrain restore order far more than we are accustomed to see. It
might be possible to build examples that flat out can't be restored,
even granting that we teach pg_dump how to break dependency loops
by first creating the function with empty body and later redefining
it with the real body. (Admittedly, if that's possible then you
likely could make it happen with views too. But somehow it seems
more likely that people would create spaghetti dependencies for
functions than views.)
regards, tom lane
Hi,
On 2020-06-30 19:49:04 +0200, Peter Eisentraut wrote:
The function body is parsed at function definition time and stored as
expression nodes in probin. So at run time, no further parsing is
required.
As raw parse tree or as a parse-analysed tree? I assume the latter?
Isn't a consequence of that that we'd get a lot more errors if any DDL
is done to tables involved in the query? In contrast to other languages
we'd not be able to handle column type changes etc, right?
Greetings,
Andres Freund
Andres Freund <andres@anarazel.de> writes:
On 2020-06-30 19:49:04 +0200, Peter Eisentraut wrote:
The function body is parsed at function definition time and stored as
expression nodes in probin. So at run time, no further parsing is
required.
Isn't a consequence of that that we'd get a lot more errors if any DDL
is done to tables involved in the query? In contrast to other languages
we'd not be able to handle column type changes etc, right?
I suppose it'd act like column references in a view, ie the dependency
mechanisms would forbid you from changing/dropping any column mentioned
in one of these functions.
regards, tom lane
On Tue, Jun 30, 2020 at 2:51 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
On further thought, we probably don't have to. Re-parsing the function
body the same way is exactly the same problem as re-parsing a view or
matview body the same way. I don't want to claim that that's a 100%
solved problem, but I've heard few complaints in that area lately.The point remains that exposing the function body's dependencies will
constrain restore order far more than we are accustomed to see. It
might be possible to build examples that flat out can't be restored,
even granting that we teach pg_dump how to break dependency loops
by first creating the function with empty body and later redefining
it with the real body. (Admittedly, if that's possible then you
likely could make it happen with views too. But somehow it seems
more likely that people would create spaghetti dependencies for
functions than views.)
In my experience, there's certainly demand for some kind of mode where
plpgsql functions get checked at function definition time, rather than
at execution time. The model we have is advantageous not only because
it simplifies dump and reload, but also because it handles cases where
the table is created on the fly properly. However, it also means that
you can have silly mistakes in your function definitions that you
don't find out about until runtime, and in my experience, people don't
like that behavior much at all. So I don't think that it's a bad idea
on principle, or anything like that, but the details seem like they
need a lot of thought. The dump and restore issues need to be
considered, but also, what about things like IF and WHILE? People are
going to want those constructs with these new semantics, too.
I actually don't have a very clear idea of what the standard has to
say about SQL-language functions. Does it just say it's a list of
statements, or does it involve variables and control-flow constructs
and stuff like that, too? If we go that direction with this, then
we're actually going to end up with two different implementations of
what's now plpgsql, or something. But if we don't, then I'm not sure
how far this takes us. I'm not saying it's bad, but the comment "I
love the early binding but where's my IF statement" seems like an
inevitable one.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
st 1. 7. 2020 v 15:37 odesílatel Robert Haas <robertmhaas@gmail.com> napsal:
On Tue, Jun 30, 2020 at 2:51 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
On further thought, we probably don't have to. Re-parsing the function
body the same way is exactly the same problem as re-parsing a view or
matview body the same way. I don't want to claim that that's a 100%
solved problem, but I've heard few complaints in that area lately.The point remains that exposing the function body's dependencies will
constrain restore order far more than we are accustomed to see. It
might be possible to build examples that flat out can't be restored,
even granting that we teach pg_dump how to break dependency loops
by first creating the function with empty body and later redefining
it with the real body. (Admittedly, if that's possible then you
likely could make it happen with views too. But somehow it seems
more likely that people would create spaghetti dependencies for
functions than views.)In my experience, there's certainly demand for some kind of mode where
plpgsql functions get checked at function definition time, rather than
at execution time. The model we have is advantageous not only because
it simplifies dump and reload, but also because it handles cases where
the table is created on the fly properly. However, it also means that
you can have silly mistakes in your function definitions that you
don't find out about until runtime, and in my experience, people don't
like that behavior much at all. So I don't think that it's a bad idea
on principle, or anything like that, but the details seem like they
need a lot of thought. The dump and restore issues need to be
considered, but also, what about things like IF and WHILE? People are
going to want those constructs with these new semantics, too.
plpgsql_check can be integrated to upstream.
https://github.com/okbob/plpgsql_check
I actually don't have a very clear idea of what the standard has to
say about SQL-language functions. Does it just say it's a list of
statements, or does it involve variables and control-flow constructs
and stuff like that, too? If we go that direction with this, then
we're actually going to end up with two different implementations of
what's now plpgsql, or something. But if we don't, then I'm not sure
how far this takes us. I'm not saying it's bad, but the comment "I
love the early binding but where's my IF statement" seems like an
inevitable one.
The standard SQL/PSM is a full functionality language with variables,
conditional statements, exception handlings, ..
https://postgres.cz/wiki/SQL/PSM_Manual
Unfortunately a basic implementation integrated into the main SQL parser
can be pretty hard work. First issue can be SET statement implementation.
Regards
Pavel
Show quoted text
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Robert Haas <robertmhaas@gmail.com> writes:
In my experience, there's certainly demand for some kind of mode where
plpgsql functions get checked at function definition time, rather than
at execution time.
Yeah, absolutely agreed. But I'm afraid this proposal takes us too
far in the other direction: with this, you *must* have a 100% parseable
and semantically valid function body, every time all the time.
So far as plpgsql is concerned, I could see extending the validator
to run parse analysis (not just raw parsing) on all SQL statements in
the body. This wouldn't happen of course with check_function_bodies off,
so it wouldn't affect dump/reload. But likely there would still be
demand for more fine-grained control over it ... or maybe it could
stop doing analysis as soon as it finds a DDL command?
regards, tom lane
st 1. 7. 2020 v 16:14 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Robert Haas <robertmhaas@gmail.com> writes:
In my experience, there's certainly demand for some kind of mode where
plpgsql functions get checked at function definition time, rather than
at execution time.Yeah, absolutely agreed. But I'm afraid this proposal takes us too
far in the other direction: with this, you *must* have a 100% parseable
and semantically valid function body, every time all the time.So far as plpgsql is concerned, I could see extending the validator
to run parse analysis (not just raw parsing) on all SQL statements in
the body. This wouldn't happen of course with check_function_bodies off,
so it wouldn't affect dump/reload. But likely there would still be
demand for more fine-grained control over it ... or maybe it could
stop doing analysis as soon as it finds a DDL command?
This simple analysis stops on first record type usage. PLpgSQL allows some
dynamic work that increases the complexity of static analysis.
Regards
Pavel
Show quoted text
regards, tom lane
On Wed, Jul 1, 2020 at 10:14:10AM -0400, Tom Lane wrote:
Robert Haas <robertmhaas@gmail.com> writes:
In my experience, there's certainly demand for some kind of mode where
plpgsql functions get checked at function definition time, rather than
at execution time.Yeah, absolutely agreed. But I'm afraid this proposal takes us too
far in the other direction: with this, you *must* have a 100% parseable
and semantically valid function body, every time all the time.So far as plpgsql is concerned, I could see extending the validator
to run parse analysis (not just raw parsing) on all SQL statements in
the body. This wouldn't happen of course with check_function_bodies off,
so it wouldn't affect dump/reload. But likely there would still be
demand for more fine-grained control over it ... or maybe it could
stop doing analysis as soon as it finds a DDL command?
Is the SQL-standard function body verified as preventing function
inlining? That seems to be a major downside.
--
Bruce Momjian <bruce@momjian.us> https://momjian.us
EnterpriseDB https://enterprisedb.com
The usefulness of a cup is in its emptiness, Bruce Lee
Bruce Momjian <bruce@momjian.us> writes:
Is the SQL-standard function body verified as preventing function
inlining? That seems to be a major downside.
I see no reason why that would make any difference. There might
be more code to be written than is in the patch, but in principle
inlining should not care whether the function is pre-parsed or not.
regards, tom lane
On 7/1/20 3:36 PM, Robert Haas wrote:
I actually don't have a very clear idea of what the standard has to
say about SQL-language functions. Does it just say it's a list of
statements, or does it involve variables and control-flow constructs
and stuff like that, too?
It's either a single sql statement, or a collection of them between
"begin atomic" and "end". There are no variables or flow control
constructs or anything like that, just as there are no such things
outside of a function.
(There are a few statements that are not allowed, such as COMMIT.)
--
Vik Fearing
st 1. 7. 2020 v 20:19 odesílatel Vik Fearing <vik@postgresfriends.org>
napsal:
On 7/1/20 3:36 PM, Robert Haas wrote:
I actually don't have a very clear idea of what the standard has to
say about SQL-language functions. Does it just say it's a list of
statements, or does it involve variables and control-flow constructs
and stuff like that, too?It's either a single sql statement, or a collection of them between
"begin atomic" and "end". There are no variables or flow control
constructs or anything like that, just as there are no such things
outside of a function.
What is the source of this comment? Maybe we are speaking (and thinking)
about different languages.
I thought the language of SQL functions (ANSI/SQL) is SQL/PSM.
Regards
Pavel
Show quoted text
(There are a few statements that are not allowed, such as COMMIT.)
--
Vik Fearing
On 7/1/20 9:32 PM, Pavel Stehule wrote:
st 1. 7. 2020 v 20:19 odesílatel Vik Fearing <vik@postgresfriends.org>
napsal:On 7/1/20 3:36 PM, Robert Haas wrote:
I actually don't have a very clear idea of what the standard has to
say about SQL-language functions. Does it just say it's a list of
statements, or does it involve variables and control-flow constructs
and stuff like that, too?It's either a single sql statement, or a collection of them between
"begin atomic" and "end". There are no variables or flow control
constructs or anything like that, just as there are no such things
outside of a function.What is the source of this comment?
The SQL Standard.
Maybe we are speaking (and thinking)
about different languages.
I think so, yes.
I thought the language of SQL functions (ANSI/SQL) is SQL/PSM.
That is something else entirely, and not at all what Peter's patch is about.
--
Vik Fearing
st 1. 7. 2020 v 22:31 odesílatel Vik Fearing <vik@postgresfriends.org>
napsal:
On 7/1/20 9:32 PM, Pavel Stehule wrote:
st 1. 7. 2020 v 20:19 odesílatel Vik Fearing <vik@postgresfriends.org>
napsal:On 7/1/20 3:36 PM, Robert Haas wrote:
I actually don't have a very clear idea of what the standard has to
say about SQL-language functions. Does it just say it's a list of
statements, or does it involve variables and control-flow constructs
and stuff like that, too?It's either a single sql statement, or a collection of them between
"begin atomic" and "end". There are no variables or flow control
constructs or anything like that, just as there are no such things
outside of a function.What is the source of this comment?
The SQL Standard.
The SQL Standard is really big, and is very possible so I miss this part.
Can you send me a link?
Regards
Pavel
Maybe we are speaking (and thinking)
about different languages.I think so, yes.
I thought the language of SQL functions (ANSI/SQL) is SQL/PSM.
That is something else entirely, and not at all what Peter's patch is
about.
--
Show quoted text
Vik Fearing
On 7/1/20 10:34 PM, Pavel Stehule wrote:
st 1. 7. 2020 v 22:31 odesílatel Vik Fearing <vik@postgresfriends.org>
napsal:On 7/1/20 9:32 PM, Pavel Stehule wrote:
st 1. 7. 2020 v 20:19 odesílatel Vik Fearing <vik@postgresfriends.org>
napsal:On 7/1/20 3:36 PM, Robert Haas wrote:
I actually don't have a very clear idea of what the standard has to
say about SQL-language functions. Does it just say it's a list of
statements, or does it involve variables and control-flow constructs
and stuff like that, too?It's either a single sql statement, or a collection of them between
"begin atomic" and "end". There are no variables or flow control
constructs or anything like that, just as there are no such things
outside of a function.What is the source of this comment?
The SQL Standard.
The SQL Standard is really big, and is very possible so I miss this part.
Can you send me a link?
ISO/IEC 9075-2:2016 Section 11.60 <SQL-invoked routine>
--
Vik Fearing
st 1. 7. 2020 v 22:54 odesílatel Vik Fearing <vik@postgresfriends.org>
napsal:
On 7/1/20 10:34 PM, Pavel Stehule wrote:
st 1. 7. 2020 v 22:31 odesílatel Vik Fearing <vik@postgresfriends.org>
napsal:On 7/1/20 9:32 PM, Pavel Stehule wrote:
st 1. 7. 2020 v 20:19 odesílatel Vik Fearing <vik@postgresfriends.org>
napsal:On 7/1/20 3:36 PM, Robert Haas wrote:
I actually don't have a very clear idea of what the standard has to
say about SQL-language functions. Does it just say it's a list of
statements, or does it involve variables and control-flow constructs
and stuff like that, too?It's either a single sql statement, or a collection of them between
"begin atomic" and "end". There are no variables or flow control
constructs or anything like that, just as there are no such things
outside of a function.What is the source of this comment?
The SQL Standard.
The SQL Standard is really big, and is very possible so I miss this part.
Can you send me a link?ISO/IEC 9075-2:2016 Section 11.60 <SQL-invoked routine>
Thank you
Pavel
--
Show quoted text
Vik Fearing
On Wed, Jul 1, 2020 at 5:58 AM Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Jun 30, 2020 at 1:49 PM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.With what other implementations is it compatible?
Judging by the Wikipedia article[1]https://en.wikipedia.org/wiki/SQL/PSM, it sounds like at least DB2 and
MySQL/MariaDB are purposely striving for conformance. When I worked
with DB2 a few years back I preferred to use their standard-conforming
PL stuff (as opposed to their be-more-like-Oracle PL/SQL mode), and I
always hoped that PostgreSQL would eventually understand the same
syntax; admittedly, anyone who has ever worked on large applications
that support multiple RDBMSs knows that there's more than just surface
syntax to worry about, but it still seems like a pretty solid plan to
conform to the standard that's in our name, so +1 from me on the
general direction (I didn't look at the patch).
Thomas Munro <thomas.munro@gmail.com> writes:
On Wed, Jul 1, 2020 at 5:58 AM Robert Haas <robertmhaas@gmail.com> wrote:
With what other implementations is it compatible?
Judging by the Wikipedia article[1], it sounds like at least DB2 and
MySQL/MariaDB are purposely striving for conformance.
[1] https://en.wikipedia.org/wiki/SQL/PSM
but ... but ... but ... that's about SQL/PSM, which is not this.
Having said that, I wonder whether this work could be repurposed
to be the start of a real SQL/PSM implementation. There's other
stuff in SQL/PSM, but a big part of it is routines that are written
with syntax like this.
regards, tom lane
st 1. 7. 2020 v 22:54 odesílatel Vik Fearing <vik@postgresfriends.org>
napsal:
On 7/1/20 10:34 PM, Pavel Stehule wrote:
st 1. 7. 2020 v 22:31 odesílatel Vik Fearing <vik@postgresfriends.org>
napsal:On 7/1/20 9:32 PM, Pavel Stehule wrote:
st 1. 7. 2020 v 20:19 odesílatel Vik Fearing <vik@postgresfriends.org>
napsal:On 7/1/20 3:36 PM, Robert Haas wrote:
I actually don't have a very clear idea of what the standard has to
say about SQL-language functions. Does it just say it's a list of
statements, or does it involve variables and control-flow constructs
and stuff like that, too?It's either a single sql statement, or a collection of them between
"begin atomic" and "end". There are no variables or flow control
constructs or anything like that, just as there are no such things
outside of a function.What is the source of this comment?
The SQL Standard.
The SQL Standard is really big, and is very possible so I miss this part.
Can you send me a link?ISO/IEC 9075-2:2016 Section 11.60 <SQL-invoked routine>
I am looking there, and it looks like a subset of SQL/PSM or better -
SQL/PSM is extending this. But this part is a little bit strange, because
it doesn't introduce its own variables, but it is working with the concept
of host variables and is a little bit messy (for me). Looks like it is
introduced for usage in triggers. If we support triggers without trigger
functions, then it has sense. Without it - It is hard for me to imagine a
use case for this reduced language.
Regards
Pavel
--
Show quoted text
Vik Fearing
On Wed, Jul 1, 2020 at 5:49 AM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:
- More test coverage is needed. Surprisingly, there wasn't actually any
test AFAICT that just creates and SQL function and runs it. Most of
that code is tested incidentally, but there is very little or no
targeted testing of this functionality.
FYI cfbot showed a sign of some kind of error_context_stack corruption
while running "DROP TABLE functest3 CASCADE;".
Thomas Munro <thomas.munro@gmail.com> writes:
On Wed, Jul 1, 2020 at 5:49 AM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:- More test coverage is needed. Surprisingly, there wasn't actually any
test AFAICT that just creates and SQL function and runs it. Most of
that code is tested incidentally, but there is very little or no
targeted testing of this functionality.
FYI cfbot showed a sign of some kind of error_context_stack corruption
while running "DROP TABLE functest3 CASCADE;".
BTW, it occurs to me after answering bug #16534 that
contrib/earthdistance's SQL functions would be great candidates for this
new syntax. Binding their references at creation time is really exactly
what we want.
I still feel that we can't just replace the existing implementation,
though, as that would kill too many use-cases where late binding is
helpful.
regards, tom lane
On 2020-06-30 19:49, Peter Eisentraut wrote:
This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.
Here is a new patch. The only significant change is that pg_dump
support is now fixed. Per the discussion in [0]/messages/by-id/9df8a3d3-13d2-116d-26ab-6a273c1ed38c@2ndquadrant.com, I have introduced a
new function pg_get_function_sqlbody() that just produces the formatted
SQL body, not the whole function definition. All the tests now pass.
As mentioned before, more tests are probably needed, so if reviewers
just want to play with this and find things that don't work, those could
be put into test cases, for example.
As a thought, a couple of things could probably be separated from this
patch and considered separately:
1. making LANGUAGE SQL the default
2. the RETURN statement
If reviewers think that would be sensible, I can prepare separate
patches for those.
[0]: /messages/by-id/9df8a3d3-13d2-116d-26ab-6a273c1ed38c@2ndquadrant.com
/messages/by-id/9df8a3d3-13d2-116d-26ab-6a273c1ed38c@2ndquadrant.com
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
v2-0001-SQL-standard-function-body.patchtext/plain; charset=UTF-8; name=v2-0001-SQL-standard-function-body.patch; x-mac-creator=0; x-mac-type=0Download
From 4df3c9db684cd1de6c9bfa622829308a4f8809a7 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Fri, 28 Aug 2020 07:19:10 +0200
Subject: [PATCH v2] SQL-standard function body
This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.
Instead of the PostgreSQL-specific AS $$ string literal $$ syntax,
this allows writing out the SQL statements making up the body
unquoted, either as a single statement:
CREATE FUNCTION add(a integer, b integer) RETURNS integer
LANGUAGE SQL
RETURN a + b;
or as a block
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
BEGIN ATOMIC
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
END;
The function body is parsed at function definition time and stored as
expression nodes in probin. So at run time, no further parsing is
required.
However, this form does not support polymorphic arguments, because
there is no more parse analysis done at call time.
Dependencies between the function and the objects it uses are fully
tracked.
A new RETURN statement is introduced. This can only be used inside
function bodies. Internally, it is treated much like a SELECT
statement.
psql needs some new intelligence to keep track of function body
boundaries so that it doesn't send off statements when it sees
semicolons that are inside a function body.
Also, per SQL standard, LANGUAGE SQL is the default, so it does not
need to be specified anymore.
Discussion: https://www.postgresql.org/message-id/flat/1c11f1eb-f00c-43b7-799d-2d44132c02d7@2ndquadrant.com
---
doc/src/sgml/ref/create_function.sgml | 126 +++++++++++--
doc/src/sgml/ref/create_procedure.sgml | 62 ++++++-
src/backend/catalog/pg_proc.c | 148 ++++++++-------
src/backend/commands/aggregatecmds.c | 2 +
src/backend/commands/functioncmds.c | 96 +++++++---
src/backend/executor/functions.c | 79 ++++----
src/backend/nodes/copyfuncs.c | 15 ++
src/backend/nodes/equalfuncs.c | 13 ++
src/backend/nodes/outfuncs.c | 12 ++
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/util/clauses.c | 25 ++-
src/backend/parser/analyze.c | 35 ++++
src/backend/parser/gram.y | 126 ++++++++++---
src/backend/tcop/postgres.c | 3 +-
src/backend/utils/adt/ruleutils.c | 110 +++++++++++-
src/bin/pg_dump/pg_dump.c | 45 +++--
src/fe_utils/psqlscan.l | 23 ++-
src/include/catalog/pg_proc.dat | 4 +
src/include/catalog/pg_proc.h | 2 +-
src/include/commands/defrem.h | 2 +
src/include/executor/functions.h | 15 ++
src/include/fe_utils/psqlscan_int.h | 2 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 13 ++
src/include/parser/kwlist.h | 2 +
src/include/tcop/tcopprot.h | 1 +
src/interfaces/ecpg/preproc/ecpg.addons | 6 +
src/interfaces/ecpg/preproc/ecpg.trailer | 4 +-
.../regress/expected/create_function_3.out | 170 +++++++++++++++++-
.../regress/expected/create_procedure.out | 58 ++++++
src/test/regress/sql/create_function_3.sql | 77 +++++++-
src/test/regress/sql/create_procedure.sql | 26 +++
32 files changed, 1114 insertions(+), 190 deletions(-)
diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index f81cedc823..f7cc428773 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -38,6 +38,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -257,8 +258,9 @@ <title>Parameters</title>
The name of the language that the function is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g. <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g. <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -577,6 +579,44 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> function. This can
+ either be a single statement
+<programlisting>
+RETURN <replaceable>expression</replaceable>
+</programlisting>
+ or a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the function body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at function definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at function definition time. This form tracks dependencies between the
+ function and objects used in the function body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling functions. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
<para>
@@ -669,6 +709,15 @@ <title>Examples</title>
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
+</programlisting>
+ The same function written in a more SQL-conforming style, using argument
+ names and an unquoted body:
+<programlisting>
+CREATE FUNCTION add(a integer, b integer) RETURNS integer
+ LANGUAGE SQL
+ IMMUTABLE
+ RETURNS NULL ON NULL INPUT
+ RETURN a + b;
</programlisting>
</para>
@@ -799,23 +848,74 @@ <title>Writing <literal>SECURITY DEFINER</literal> Functions Safely</title>
<title>Compatibility</title>
<para>
- A <command>CREATE FUNCTION</command> command is defined in the SQL standard.
- The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. The attributes are not portable, neither are the
- different available languages.
+ A <command>CREATE FUNCTION</command> command is defined in the SQL
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. Conversely, the SQL
+ standard specifies a number of optional features that are not implemented
+ in <productname>PostgreSQL</productname>.
</para>
<para>
- For compatibility with some other database systems,
- <replaceable class="parameter">argmode</replaceable> can be written
- either before or after <replaceable class="parameter">argname</replaceable>.
- But only the first way is standard-compliant.
+ The following are important compatibility issues:
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>OR REPLACE</literal> is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For compatibility with some other database systems, <replaceable
+ class="parameter">argmode</replaceable> can be written either before or
+ after <replaceable class="parameter">argname</replaceable>. But only
+ the first way is standard-compliant.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For parameter defaults, the SQL standard specifies only the syntax with
+ the <literal>DEFAULT</literal> key word. The syntax with
+ <literal>=</literal> is used in T-SQL and Firebird.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The <literal>SETOF</literal> modifier is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Only <literal>SQL</literal> is standardized as a language.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ All other attributes except <literal>CALLED ON NULL INPUT</literal> and
+ <literal>RETURNS NULL ON NULL INPUT</literal> are not standardized.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For the body of <literal>LANGUAGE SQL</literal> functions, the SQL
+ standard only specifies the <replaceable>sql_body</replaceable> form.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
<para>
- For parameter defaults, the SQL standard specifies only the syntax with
- the <literal>DEFAULT</literal> key word. The syntax
- with <literal>=</literal> is used in T-SQL and Firebird.
+ Simple <literal>LANGUAGE SQL</literal> functions can be written in a way
+ that is both standard-conforming and portable to other implementations.
+ More complex functions using advanced features, optimization attributes, or
+ other languages will necessarily be specific to PostgreSQL in a significant
+ way.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_procedure.sgml b/doc/src/sgml/ref/create_procedure.sgml
index 0ea6513cb5..9a84132a2b 100644
--- a/doc/src/sgml/ref/create_procedure.sgml
+++ b/doc/src/sgml/ref/create_procedure.sgml
@@ -29,6 +29,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -164,8 +165,9 @@ <title>Parameters</title>
The name of the language that the procedure is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g. <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g. <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -301,6 +303,41 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> procedure. This should
+ be a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the procedure body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at procedure definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at procedure definition time. This form tracks dependencies between the
+ procedure and objects used in the procedure body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling procedures. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
@@ -320,6 +357,7 @@ <title>Notes</title>
<refsect1 id="sql-createprocedure-examples">
<title>Examples</title>
+ <para>
<programlisting>
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
@@ -327,9 +365,21 @@ <title>Examples</title>
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
$$;
-
+</programlisting>
+ or
+<programlisting>
+CREATE PROCEDURE insert_data(a integer, b integer)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO tbl VALUES (a);
+ INSERT INTO tbl VALUES (b);
+END;
+</programlisting>
+ and call like this:
+<programlisting>
CALL insert_data(1, 2);
</programlisting>
+ </para>
</refsect1>
<refsect1 id="sql-createprocedure-compat">
@@ -337,9 +387,9 @@ <title>Compatibility</title>
<para>
A <command>CREATE PROCEDURE</command> command is defined in the SQL
- standard. The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. For details see
- also <xref linkend="sql-createfunction"/>.
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. For details see also
+ <xref linkend="sql-createfunction"/>.
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index a28ab74d60..99524126c9 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -32,6 +32,7 @@
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_type.h"
#include "tcop/pquery.h"
@@ -118,8 +119,6 @@ ProcedureCreate(const char *procedureName,
/*
* sanity checks
*/
- Assert(PointerIsValid(prosrc));
-
parameterCount = parameterTypes->dim1;
if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
ereport(ERROR,
@@ -330,7 +329,10 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_protrftypes - 1] = trftypes;
else
nulls[Anum_pg_proc_protrftypes - 1] = true;
- values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ if (prosrc)
+ values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ else
+ nulls[Anum_pg_proc_prosrc - 1] = true;
if (probin)
values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin);
else
@@ -632,6 +634,10 @@ ProcedureCreate(const char *procedureName,
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
+ /* dependency on SQL routine body */
+ if (languageObjectId == SQLlanguageId && probin)
+ recordDependencyOnExpr(&myself, stringToNode(probin), NIL, DEPENDENCY_NORMAL);
+
/* dependency on owner */
if (!is_update)
recordDependencyOnOwner(ProcedureRelationId, retval, proowner);
@@ -800,16 +806,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
Oid funcoid = PG_GETARG_OID(0);
HeapTuple tuple;
Form_pg_proc proc;
- List *raw_parsetree_list;
- List *querytree_list;
- ListCell *lc;
- bool isnull;
- Datum tmp;
- char *prosrc;
- parse_error_callback_arg callback_arg;
- ErrorContextCallback sqlerrcontext;
bool haspolyarg;
- int i;
if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
PG_RETURN_VOID();
@@ -833,7 +830,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
/* Disallow pseudotypes in arguments */
/* except for polymorphic */
haspolyarg = false;
- for (i = 0; i < proc->pronargs; i++)
+ for (int i = 0; i < proc->pronargs; i++)
{
if (get_typtype(proc->proargtypes.values[i]) == TYPTYPE_PSEUDO)
{
@@ -850,72 +847,93 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
/* Postpone body checks if !check_function_bodies */
if (check_function_bodies)
{
+ Datum tmp;
+ bool isnull;
+ List *querytree_list = NIL;
+
tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
if (isnull)
- elog(ERROR, "null prosrc");
-
- prosrc = TextDatumGetCString(tmp);
+ {
+ char *probin;
- /*
- * Setup error traceback support for ereport().
- */
- callback_arg.proname = NameStr(proc->proname);
- callback_arg.prosrc = prosrc;
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_probin, &isnull);
+ if (isnull)
+ elog(ERROR, "null probin and prosrc");
- sqlerrcontext.callback = sql_function_parse_error_callback;
- sqlerrcontext.arg = (void *) &callback_arg;
- sqlerrcontext.previous = error_context_stack;
- error_context_stack = &sqlerrcontext;
+ probin = TextDatumGetCString(tmp);
+ querytree_list = castNode(List, stringToNode(probin));
+ }
+ else
+ {
+ char *prosrc;
+ List *raw_parsetree_list;
+ parse_error_callback_arg callback_arg;
+ ErrorContextCallback sqlerrcontext;
- /*
- * We can't do full prechecking of the function definition if there
- * are any polymorphic input types, because actual datatypes of
- * expression results will be unresolvable. The check will be done at
- * runtime instead.
- *
- * We can run the text through the raw parser though; this will at
- * least catch silly syntactic errors.
- */
- raw_parsetree_list = pg_parse_query(prosrc);
+ prosrc = TextDatumGetCString(tmp);
- if (!haspolyarg)
- {
/*
- * OK to do full precheck: analyze and rewrite the queries, then
- * verify the result type.
+ * Setup error traceback support for ereport().
*/
- SQLFunctionParseInfoPtr pinfo;
- Oid rettype;
- TupleDesc rettupdesc;
+ callback_arg.proname = NameStr(proc->proname);
+ callback_arg.prosrc = prosrc;
- /* But first, set up parameter information */
- pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
+ sqlerrcontext.callback = sql_function_parse_error_callback;
+ sqlerrcontext.arg = (void *) &callback_arg;
+ sqlerrcontext.previous = error_context_stack;
+ error_context_stack = &sqlerrcontext;
- querytree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ /*
+ * We can't do full prechecking of the function definition if there
+ * are any polymorphic input types, because actual datatypes of
+ * expression results will be unresolvable. The check will be done at
+ * runtime instead.
+ *
+ * We can run the text through the raw parser though; this will at
+ * least catch silly syntactic errors.
+ */
+ raw_parsetree_list = pg_parse_query(prosrc);
+
+ if (!haspolyarg)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *querytree_sublist;
-
- querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
- prosrc,
- (ParserSetupHook) sql_fn_parser_setup,
- pinfo,
- NULL);
- querytree_list = list_concat(querytree_list,
- querytree_sublist);
+ /*
+ * OK to do full precheck: analyze and rewrite the queries, then
+ * verify the result type.
+ */
+ ListCell *lc;
+ SQLFunctionParseInfoPtr pinfo;
+ Oid rettype;
+ TupleDesc rettupdesc;
+
+ /* But first, set up parameter information */
+ pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
+
+ querytree_list = NIL;
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *querytree_sublist;
+
+ querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ prosrc,
+ (ParserSetupHook) sql_fn_parser_setup,
+ pinfo,
+ NULL);
+ querytree_list = list_concat(querytree_list,
+ querytree_sublist);
+ }
+
+ check_sql_fn_statements(querytree_list);
+
+ (void) get_func_result_type(funcoid, &rettype, &rettupdesc);
+
+ (void) check_sql_fn_retval(querytree_list,
+ rettype, rettupdesc,
+ false, NULL);
}
- check_sql_fn_statements(querytree_list);
-
- (void) get_func_result_type(funcoid, &rettype, &rettupdesc);
-
- (void) check_sql_fn_retval(querytree_list,
- rettype, rettupdesc,
- false, NULL);
+ error_context_stack = sqlerrcontext.previous;
}
-
- error_context_stack = sqlerrcontext.previous;
}
ReleaseSysCache(tuple);
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 6bf54e64f8..26b3fa27de 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -313,9 +313,11 @@ DefineAggregate(ParseState *pstate,
InvalidOid,
OBJECT_AGGREGATE,
¶meterTypes,
+ NULL,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ NULL,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 1b5bdcec8b..859bc72974 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -53,9 +53,11 @@
#include "commands/proclang.h"
#include "executor/execdesc.h"
#include "executor/executor.h"
+#include "executor/functions.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
@@ -186,9 +188,11 @@ interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType)
@@ -300,6 +304,8 @@ interpret_function_parameter_list(ParseState *pstate,
errmsg("VARIADIC parameter must be the last input parameter")));
inTypes[inCount++] = toid;
isinput = true;
+ if (parameterTypes_list)
+ *parameterTypes_list = lappend_oid(*parameterTypes_list, toid);
}
/* handle output parameters */
@@ -376,6 +382,9 @@ interpret_function_parameter_list(ParseState *pstate,
have_names = true;
}
+ if (inParameterNames_list)
+ *inParameterNames_list = lappend(*inParameterNames_list, makeString(fp->name ? fp->name : pstrdup("")));
+
if (fp->defexpr)
{
Node *def;
@@ -790,28 +799,10 @@ compute_function_attributes(ParseState *pstate,
defel->defname);
}
- /* process required items */
if (as_item)
*as = (List *) as_item->arg;
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no function body specified")));
- *as = NIL; /* keep compiler quiet */
- }
-
if (language_item)
*language = strVal(language_item->arg);
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no language specified")));
- *language = NULL; /* keep compiler quiet */
- }
-
- /* process optional items */
if (transform_item)
*transform = transform_item->arg;
if (windowfunc_item)
@@ -860,10 +851,19 @@ compute_function_attributes(ParseState *pstate,
*/
static void
interpret_AS_clause(Oid languageOid, const char *languageName,
- char *funcname, List *as,
+ char *funcname, List *as, List *sql_body,
+ List *parameterTypes, List *inParameterNames,
char **prosrc_str_p, char **probin_str_p)
{
- Assert(as != NIL);
+ if (sql_body && as)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("duplicate function body specified")));
+
+ if (sql_body && languageOid != SQLlanguageId)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("inline SQL function body only valid for language SQL")));
if (languageOid == ClanguageId)
{
@@ -885,6 +885,53 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
*prosrc_str_p = funcname;
}
}
+ else if (sql_body)
+ {
+ List *transformed_stmts = NIL;
+ ListCell *lc;
+ SQLFunctionParseInfoPtr pinfo;
+
+ pinfo = (SQLFunctionParseInfoPtr) palloc0(sizeof(SQLFunctionParseInfo));
+
+ pinfo->fname = funcname;
+ pinfo->nargs = list_length(parameterTypes);
+ pinfo->argtypes = (Oid *) palloc(pinfo->nargs * sizeof(Oid));
+ pinfo->argnames = (char **) palloc(pinfo->nargs * sizeof(char *));
+ for (int i = 0; i < list_length(parameterTypes); i++)
+ {
+ char *s = strVal(list_nth(inParameterNames, i));
+
+ pinfo->argtypes[i] = list_nth_oid(parameterTypes, i);
+ if (IsPolymorphicType(pinfo->argtypes[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("SQL function with unquoted function body cannot have polymorphic arguments")));
+
+ if (s[0] != '\0')
+ pinfo->argnames[i] = s;
+ else
+ pinfo->argnames[i] = NULL;
+ }
+
+ foreach(lc, sql_body)
+ {
+ Node *stmt = lfirst(lc);
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ /* ignore NULL statement; see gram.y */
+ if (!stmt)
+ continue;
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, stmt);
+ transformed_stmts = lappend(transformed_stmts, q);
+ free_parsestate(pstate);
+ }
+
+ *probin_str_p = nodeToString(transformed_stmts);
+ *prosrc_str_p = NULL;
+ }
else
{
/* Everything else wants the given string in prosrc. */
@@ -933,9 +980,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
Oid namespaceId;
AclResult aclresult;
oidvector *parameterTypes;
+ List *parameterTypes_list = NIL;
ArrayType *allParameterTypes;
ArrayType *parameterModes;
ArrayType *parameterNames;
+ List *inParameterNames_list = NIL;
List *parameterDefaults;
Oid variadicArgType;
List *trftypes_list = NIL;
@@ -966,6 +1015,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
get_namespace_name(namespaceId));
/* Set default attributes */
+ as_clause = NULL;
+ language = "sql";
isWindowFunc = false;
isStrict = false;
security = false;
@@ -1057,9 +1108,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageOid,
stmt->is_procedure ? OBJECT_PROCEDURE : OBJECT_FUNCTION,
¶meterTypes,
+ ¶meterTypes_list,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ &inParameterNames_list,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
@@ -1116,7 +1169,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
trftypes = NULL;
}
- interpret_AS_clause(languageOid, language, funcname, as_clause,
+ interpret_AS_clause(languageOid, language, funcname, as_clause, stmt->sql_body,
+ parameterTypes_list, inParameterNames_list,
&prosrc_str, &probin_str);
/*
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index f940f48c6d..911149aa27 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -26,6 +26,7 @@
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_func.h"
+#include "rewrite/rewriteHandler.h"
#include "storage/proc.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
@@ -128,21 +129,6 @@ typedef struct
typedef SQLFunctionCache *SQLFunctionCachePtr;
-/*
- * Data structure needed by the parser callback hooks to resolve parameter
- * references during parsing of a SQL function's body. This is separate from
- * SQLFunctionCache since we sometimes do parsing separately from execution.
- */
-typedef struct SQLFunctionParseInfo
-{
- char *fname; /* function's name */
- int nargs; /* number of input arguments */
- Oid *argtypes; /* resolved types of input arguments */
- char **argnames; /* names of input arguments; NULL if none */
- /* Note that argnames[i] can be NULL, if some args are unnamed */
- Oid collation; /* function's input collation, if known */
-} SQLFunctionParseInfo;
-
/* non-export function prototypes */
static Node *sql_fn_param_ref(ParseState *pstate, ParamRef *pref);
@@ -606,7 +592,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
HeapTuple procedureTuple;
Form_pg_proc procedureStruct;
SQLFunctionCachePtr fcache;
- List *raw_parsetree_list;
List *queryTree_list;
List *flat_query_list;
List *resulttlist;
@@ -682,9 +667,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
procedureTuple,
Anum_pg_proc_prosrc,
&isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", foid);
- fcache->src = TextDatumGetCString(tmp);
/*
* Parse and rewrite the queries in the function text. Use sublists to
@@ -701,22 +683,55 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
* but we'll not worry about it until the module is rewritten to use
* plancache.c.
*/
- raw_parsetree_list = pg_parse_query(fcache->src);
-
queryTree_list = NIL;
flat_query_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (isNull)
+ {
+ char *binstr;
+ List *stored_query_list;
+
+ tmp = SysCacheGetAttr(PROCOID,
+ procedureTuple,
+ Anum_pg_proc_probin,
+ &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and probin for function %u", foid);
+
+ binstr = TextDatumGetCString(tmp);
+ stored_query_list = stringToNode(binstr);
+
+ foreach(lc, stored_query_list)
+ {
+ Query *parsetree = lfirst_node(Query, lc);
+ List *queryTree_sublist;
+
+ AcquireRewriteLocks(parsetree, true, false);
+ queryTree_sublist = pg_rewrite_query(parsetree);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ flat_query_list = list_concat(flat_query_list, queryTree_sublist);
+ }
+ }
+ else
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *queryTree_sublist;
-
- queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
- fcache->src,
- (ParserSetupHook) sql_fn_parser_setup,
- fcache->pinfo,
- NULL);
- queryTree_list = lappend(queryTree_list, queryTree_sublist);
- flat_query_list = list_concat(flat_query_list, queryTree_sublist);
+ List *raw_parsetree_list;
+
+ fcache->src = TextDatumGetCString(tmp);
+
+ raw_parsetree_list = pg_parse_query(fcache->src);
+
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *queryTree_sublist;
+
+ queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ fcache->src,
+ (ParserSetupHook) sql_fn_parser_setup,
+ fcache->pinfo,
+ NULL);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ flat_query_list = list_concat(flat_query_list, queryTree_sublist);
+ }
}
check_sql_fn_statements(flat_query_list);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..15459ace81 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3069,6 +3069,7 @@ _copyQuery(const Query *from)
COPY_SCALAR_FIELD(hasModifyingCTE);
COPY_SCALAR_FIELD(hasForUpdate);
COPY_SCALAR_FIELD(hasRowSecurity);
+ COPY_SCALAR_FIELD(isReturn);
COPY_NODE_FIELD(cteList);
COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(jointree);
@@ -3197,6 +3198,16 @@ _copySetOperationStmt(const SetOperationStmt *from)
return newnode;
}
+static ReturnStmt *
+_copyReturnStmt(const ReturnStmt *from)
+{
+ ReturnStmt *newnode = makeNode(ReturnStmt);
+
+ COPY_NODE_FIELD(returnval);
+
+ return newnode;
+}
+
static AlterTableStmt *
_copyAlterTableStmt(const AlterTableStmt *from)
{
@@ -3573,6 +3584,7 @@ _copyCreateFunctionStmt(const CreateFunctionStmt *from)
COPY_NODE_FIELD(parameters);
COPY_NODE_FIELD(returnType);
COPY_NODE_FIELD(options);
+ COPY_NODE_FIELD(sql_body);
return newnode;
}
@@ -5226,6 +5238,9 @@ copyObjectImpl(const void *from)
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
+ case T_ReturnStmt:
+ retval = _copyReturnStmt(from);
+ break;
case T_AlterTableStmt:
retval = _copyAlterTableStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e3f33c40be..d847ed8399 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -966,6 +966,7 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_SCALAR_FIELD(hasModifyingCTE);
COMPARE_SCALAR_FIELD(hasForUpdate);
COMPARE_SCALAR_FIELD(hasRowSecurity);
+ COMPARE_SCALAR_FIELD(isReturn);
COMPARE_NODE_FIELD(cteList);
COMPARE_NODE_FIELD(rtable);
COMPARE_NODE_FIELD(jointree);
@@ -1082,6 +1083,14 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
return true;
}
+static bool
+_equalReturnStmt(const ReturnStmt *a, const ReturnStmt *b)
+{
+ COMPARE_NODE_FIELD(returnval);
+
+ return true;
+}
+
static bool
_equalAlterTableStmt(const AlterTableStmt *a, const AlterTableStmt *b)
{
@@ -1394,6 +1403,7 @@ _equalCreateFunctionStmt(const CreateFunctionStmt *a, const CreateFunctionStmt *
COMPARE_NODE_FIELD(parameters);
COMPARE_NODE_FIELD(returnType);
COMPARE_NODE_FIELD(options);
+ COMPARE_NODE_FIELD(sql_body);
return true;
}
@@ -3278,6 +3288,9 @@ equal(const void *a, const void *b)
case T_SetOperationStmt:
retval = _equalSetOperationStmt(a, b);
break;
+ case T_ReturnStmt:
+ retval = _equalReturnStmt(a, b);
+ break;
case T_AlterTableStmt:
retval = _equalAlterTableStmt(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..2153fcf59b 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2758,6 +2758,14 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
WRITE_NODE_FIELD(rarg);
}
+static void
+_outReturnStmt(StringInfo str, const ReturnStmt *node)
+{
+ WRITE_NODE_TYPE("RETURN");
+
+ WRITE_NODE_FIELD(returnval);
+}
+
static void
_outFuncCall(StringInfo str, const FuncCall *node)
{
@@ -2946,6 +2954,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_BOOL_FIELD(hasModifyingCTE);
WRITE_BOOL_FIELD(hasForUpdate);
WRITE_BOOL_FIELD(hasRowSecurity);
+ WRITE_BOOL_FIELD(isReturn);
WRITE_NODE_FIELD(cteList);
WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(jointree);
@@ -4196,6 +4205,9 @@ outNode(StringInfo str, const void *obj)
case T_SelectStmt:
_outSelectStmt(str, obj);
break;
+ case T_ReturnStmt:
+ _outReturnStmt(str, obj);
+ break;
case T_ColumnDef:
_outColumnDef(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..c6d0009820 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -263,6 +263,7 @@ _readQuery(void)
READ_BOOL_FIELD(hasModifyingCTE);
READ_BOOL_FIELD(hasForUpdate);
READ_BOOL_FIELD(hasRowSecurity);
+ READ_BOOL_FIELD(isReturn);
READ_NODE_FIELD(cteList);
READ_NODE_FIELD(rtable);
READ_NODE_FIELD(jointree);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 750586fceb..66aaa58501 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4422,7 +4422,22 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
Anum_pg_proc_prosrc,
&isNull);
if (isNull)
- elog(ERROR, "null prosrc for function %u", funcid);
+ {
+ char *probin;
+ List *querytree_list;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_probin, &isNull);
+ if (isNull)
+ elog(ERROR, "null probin and prosrc");
+
+ probin = TextDatumGetCString(tmp);
+ querytree_list = castNode(List, stringToNode(probin));
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
src = TextDatumGetCString(tmp);
/*
@@ -4480,6 +4495,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
querytree = transformTopLevelStmt(pstate, linitial(raw_parsetree_list));
free_parsestate(pstate);
+ }
/*
* The single command must be a simple "SELECT expression".
@@ -4967,7 +4983,11 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
Anum_pg_proc_prosrc,
&isNull);
if (isNull)
- elog(ERROR, "null prosrc for function %u", func_oid);
+ {
+ goto fail; // TODO
+ }
+ else
+ {
src = TextDatumGetCString(tmp);
/*
@@ -5019,6 +5039,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
if (list_length(querytree_list) != 1)
goto fail;
querytree = linitial(querytree_list);
+ }
/*
* The single command must be a plain SELECT.
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c159fb2957..ed0622c5d7 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -66,6 +66,7 @@ static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
bool isTopLevel, List **targetlist);
static void determineRecursiveColTypes(ParseState *pstate,
Node *larg, List *nrtargetlist);
+static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList);
static List *transformUpdateTargetList(ParseState *pstate,
@@ -304,6 +305,10 @@ transformStmt(ParseState *pstate, Node *parseTree)
}
break;
+ case T_ReturnStmt:
+ result = transformReturnStmt(pstate, (ReturnStmt *) parseTree);
+ break;
+
/*
* Special cases
*/
@@ -2221,6 +2226,36 @@ determineRecursiveColTypes(ParseState *pstate, Node *larg, List *nrtargetlist)
}
+/*
+ * transformReturnStmt -
+ * transforms a return statement
+ */
+static Query *
+transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+
+ qry->commandType = CMD_SELECT;
+ qry->isReturn = true;
+
+ qry->targetList = list_make1(makeTargetEntry((Expr *) transformExpr(pstate, stmt->returnval, EXPR_KIND_SELECT_TARGET),
+ 1, NULL, false));
+
+ if (pstate->p_resolve_unknowns)
+ resolveTargetListUnknowns(pstate, qry->targetList);
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasAggs = pstate->p_hasAggs;
+
+ assign_query_collations(pstate, qry);
+
+ return qry;
+}
+
+
/*
* transformUpdateStmt -
* transforms an update statement
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dbb47d4982..f7a7c4681b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -253,7 +253,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct SelectLimit *selectlimit;
}
-%type <node> stmt schema_stmt
+%type <node> stmt toplevel_stmt schema_stmt routine_body_stmt
AlterEventTrigStmt AlterCollationStmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
@@ -280,9 +280,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
- RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
+ RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt
RuleActionStmt RuleActionStmtOrEmpty RuleStmt
- SecLabelStmt SelectStmt TransactionStmt TruncateStmt
+ SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
UnlistenStmt UpdateStmt VacuumStmt
VariableResetStmt VariableSetStmt VariableShowStmt
ViewStmt CheckPointStmt CreateConversionStmt
@@ -385,14 +385,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> vacuum_relation
%type <selectlimit> opt_select_limit select_limit limit_clause
-%type <list> stmtblock stmtmulti
+%type <list> stmtblock stmtmulti routine_body_stmt_list
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
reloptions opt_reloptions
OptWith distinct_clause opt_all_clause opt_definition func_args func_args_list
func_args_with_defaults func_args_with_defaults_list
aggr_args aggr_args_list
- func_as createfunc_opt_list alterfunc_opt_list
+ func_as createfunc_opt_list opt_createfunc_opt_list alterfunc_opt_list
old_aggr_definition old_aggr_list
oper_argtypes RuleActionList RuleActionMulti
opt_column_list columnList opt_name_list
@@ -418,6 +418,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
vacuum_relation_list opt_vacuum_relation_list
drop_option_list
+%type <list> opt_routine_body
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
@@ -626,7 +627,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATOMIC ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
@@ -688,7 +689,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING
REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
- RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
+ RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
@@ -815,7 +816,7 @@ stmtblock: stmtmulti
* we'd get -1 for the location in such cases.
* We also take care to discard empty statements entirely.
*/
-stmtmulti: stmtmulti ';' stmt
+stmtmulti: stmtmulti ';' toplevel_stmt
{
if ($1 != NIL)
{
@@ -827,7 +828,7 @@ stmtmulti: stmtmulti ';' stmt
else
$$ = $1;
}
- | stmt
+ | toplevel_stmt
{
if ($1 != NULL)
$$ = list_make1(makeRawStmt($1, 0));
@@ -836,7 +837,16 @@ stmtmulti: stmtmulti ';' stmt
}
;
-stmt :
+/*
+ * toplevel_stmt includes BEGIN and END. stmt does not include them, because
+ * those words have different meanings in function bodys.
+ */
+toplevel_stmt:
+ stmt
+ | TransactionStmtLegacy
+ ;
+
+stmt:
AlterEventTrigStmt
| AlterCollationStmt
| AlterDatabaseStmt
@@ -7342,7 +7352,7 @@ opt_nulls_order: NULLS_LA FIRST_P { $$ = SORTBY_NULLS_FIRST; }
CreateFunctionStmt:
CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS func_return createfunc_opt_list
+ RETURNS func_return opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7351,10 +7361,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = $7;
n->options = $8;
+ n->sql_body = $9;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS TABLE '(' table_func_column_list ')' createfunc_opt_list
+ RETURNS TABLE '(' table_func_column_list ')' opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7364,10 +7375,11 @@ CreateFunctionStmt:
n->returnType = TableFuncTypeName($9);
n->returnType->location = @7;
n->options = $11;
+ n->sql_body = $12;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7376,10 +7388,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
| CREATE opt_or_replace PROCEDURE func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = true;
@@ -7388,6 +7401,7 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
;
@@ -7667,6 +7681,11 @@ aggregate_with_argtypes_list:
{ $$ = lappend($1, $3); }
;
+opt_createfunc_opt_list:
+ createfunc_opt_list
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
createfunc_opt_list:
/* Must be at least one to prevent conflict */
createfunc_opt_item { $$ = list_make1($1); }
@@ -7778,6 +7797,50 @@ func_as: Sconst { $$ = list_make1(makeString($1)); }
}
;
+ReturnStmt: RETURN a_expr
+ {
+ ReturnStmt *r = makeNode(ReturnStmt);
+ r->returnval = (Node *) $2;
+ $$ = (Node *) r;
+ }
+ ;
+
+opt_routine_body:
+ ReturnStmt
+ {
+ $$ = list_make1($1);
+ }
+ | BEGIN_P ATOMIC routine_body_stmt_list END_P
+ {
+ $$ = $3;
+ }
+ | /*EMPTY*/
+ {
+ $$ = NIL;
+ }
+ ;
+
+routine_body_stmt_list:
+ routine_body_stmt_list routine_body_stmt ';'
+ {
+ $$ = lappend($1, $2);
+ }
+ | /*EMPTY*/
+ {
+ /*
+ * For an empty body we insert a single fake element, so
+ * that the parse analysis code can tell apart an empty
+ * body from no body at all.
+ */
+ $$ = list_make1(NULL);
+ }
+ ;
+
+routine_body_stmt:
+ stmt
+ | ReturnStmt
+ ;
+
transform_type_list:
FOR TYPE_P Typename { $$ = list_make1($3); }
| transform_type_list ',' FOR TYPE_P Typename { $$ = lappend($1, $5); }
@@ -9724,13 +9787,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | BEGIN_P opt_transaction transaction_mode_list_or_empty
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_BEGIN;
- n->options = $3;
- $$ = (Node *)n;
- }
| START TRANSACTION transaction_mode_list_or_empty
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9746,14 +9802,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | END_P opt_transaction opt_transaction_chain
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_COMMIT;
- n->options = NIL;
- n->chain = $3;
- $$ = (Node *)n;
- }
| ROLLBACK opt_transaction opt_transaction_chain
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9820,6 +9868,24 @@ TransactionStmt:
}
;
+TransactionStmtLegacy:
+ BEGIN_P opt_transaction transaction_mode_list_or_empty
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_BEGIN;
+ n->options = $3;
+ $$ = (Node *)n;
+ }
+ | END_P opt_transaction opt_transaction_chain
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_COMMIT;
+ n->options = NIL;
+ n->chain = $3;
+ $$ = (Node *)n;
+ }
+ ;
+
opt_transaction: WORK {}
| TRANSACTION {}
| /*EMPTY*/ {}
@@ -15036,6 +15102,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| BACKWARD
@@ -15234,6 +15301,7 @@ unreserved_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| ROLE
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index c9424f167c..b63c330653 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -176,7 +176,6 @@ static int interactive_getc(void);
static int SocketBackend(StringInfo inBuf);
static int ReadCommand(StringInfo inBuf);
static void forbidden_in_wal_sender(char firstchar);
-static List *pg_rewrite_query(Query *query);
static bool check_log_statement(List *stmt_list);
static int errdetail_execute(List *raw_parsetree_list);
static int errdetail_params(ParamListInfo params);
@@ -761,7 +760,7 @@ pg_analyze_and_rewrite_params(RawStmt *parsetree,
* Note: query must just have come from the parser, because we do not do
* AcquireRewriteLocks() on it.
*/
-static List *
+List *
pg_rewrite_query(Query *query)
{
List *querytree_list;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 60dd80c23c..b7c41fff80 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -174,6 +174,9 @@ typedef struct
List *outer_tlist; /* referent for OUTER_VAR Vars */
List *inner_tlist; /* referent for INNER_VAR Vars */
List *index_tlist; /* referent for INDEX_VAR Vars */
+ /* Special namespace representing a function signature: */
+ int numargs;
+ char **argnames;
} deparse_namespace;
/*
@@ -349,6 +352,7 @@ static int print_function_arguments(StringInfo buf, HeapTuple proctup,
bool print_table_args, bool print_defaults);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
static void print_function_trftypes(StringInfo buf, HeapTuple proctup);
+static void print_function_sqlbody(StringInfo buf, HeapTuple proctup);
static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used);
static void set_deparse_for_query(deparse_namespace *dpns, Query *query,
@@ -2800,9 +2804,15 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
}
/* And finally the function definition ... */
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
+ if (proc->prolang == SQLlanguageId && !isnull)
+ {
+ print_function_sqlbody(&buf, proctup);
+ }
+ else
+ {
appendStringInfoString(&buf, "AS ");
- tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
if (!isnull)
{
simple_quote_literal(&buf, TextDatumGetCString(tmp));
@@ -2831,6 +2841,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, dq.data, dq.len);
appendStringInfoString(&buf, prosrc);
appendBinaryStringInfo(&buf, dq.data, dq.len);
+ }
appendStringInfoChar(&buf, '\n');
@@ -3213,6 +3224,78 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(string_to_text(str));
}
+static void
+print_function_sqlbody(StringInfo buf, HeapTuple proctup)
+{
+ Datum tmp;
+ bool isnull;
+ List *stmts;
+ ListCell *lc;
+ int numargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ deparse_namespace dpns = {0};
+ bool need_block;
+
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
+ Assert(!isnull);
+ stmts = castNode(List, stringToNode(TextDatumGetCString(tmp)));
+
+ numargs = get_func_arg_info(proctup,
+ &argtypes, &argnames, &argmodes);
+ dpns.numargs = numargs;
+ dpns.argnames = argnames;
+
+ /*
+ * We need a BEGIN ATOMIC/END block unless the body is a single RETURN
+ * statement.
+ */
+ need_block = true;
+ if (list_length(stmts) == 1)
+ {
+ Query *query = linitial_node(Query, stmts);
+
+ if (query->isReturn)
+ need_block = false;
+ }
+
+ if (need_block)
+ appendStringInfoString(buf, "BEGIN ATOMIC\n");
+ foreach(lc, stmts)
+ {
+ Query *query = lfirst_node(Query, lc);
+
+ get_query_def(query, buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ if (lc != list_tail(stmts))
+ appendStringInfoChar(buf, ';');
+ appendStringInfoChar(buf, '\n');
+ }
+ if (need_block)
+ appendStringInfoString(buf, "END");
+}
+
+Datum
+pg_get_function_sqlbody(PG_FUNCTION_ARGS)
+{
+ Oid funcid = PG_GETARG_OID(0);
+ StringInfoData buf;
+ HeapTuple proctup;
+
+ initStringInfo(&buf);
+
+ /* Look up the function */
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ PG_RETURN_NULL();
+
+ print_function_sqlbody(&buf, proctup);
+
+ ReleaseSysCache(proctup);
+
+ PG_RETURN_TEXT_P(cstring_to_text(buf.data));
+}
+
/*
* deparse_expression - General utility for deparsing expressions
@@ -5418,7 +5501,10 @@ get_basic_select_query(Query *query, deparse_context *context,
/*
* Build up the query string - first we say SELECT
*/
- appendStringInfoString(buf, "SELECT");
+ if (query->isReturn)
+ appendStringInfoString(buf, "RETURN");
+ else
+ appendStringInfoString(buf, "SELECT");
/* Add the DISTINCT clause if given */
if (query->distinctClause != NIL)
@@ -7550,6 +7636,26 @@ get_parameter(Param *param, deparse_context *context)
return;
}
+ /*
+ * If it's an external parameter, see if the outermost namespace provides
+ * function argument names.
+ */
+ if (param->paramkind == PARAM_EXTERN)
+ {
+ dpns = lfirst(list_tail(context->namespaces));
+ if (dpns->argnames)
+ {
+ char *argname = dpns->argnames[param->paramid - 1];
+
+ if (argname)
+ {
+ // TODO: qualify with function name if necessary
+ appendStringInfo(context->buf, "%s", quote_identifier(argname));
+ return;
+ }
+ }
+ }
+
/*
* Not PARAM_EXEC, or couldn't find referent: just print $N.
*/
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2cb3f9b083..716f97acb3 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11796,6 +11796,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
char *proretset;
char *prosrc;
char *probin;
+ char *prosqlbody;
char *funcargs;
char *funciargs;
char *funcresult;
@@ -11842,7 +11843,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"provolatile,\n"
"proisstrict,\n"
"prosecdef,\n"
- "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname,\n");
+ "lanname,\n");
if (fout->remoteVersion >= 80300)
appendPQExpBuffer(query,
@@ -11862,9 +11863,9 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
* pg_get_function_result instead of examining proallargtypes etc.
*/
appendPQExpBuffer(query,
- "pg_catalog.pg_get_function_arguments(oid) AS funcargs,\n"
- "pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs,\n"
- "pg_catalog.pg_get_function_result(oid) AS funcresult,\n");
+ "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n"
+ "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n"
+ "pg_catalog.pg_get_function_result(p.oid) AS funcresult,\n");
}
else if (fout->remoteVersion >= 80100)
appendPQExpBuffer(query,
@@ -11907,21 +11908,39 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
if (fout->remoteVersion >= 120000)
appendPQExpBuffer(query,
- "prosupport\n");
+ "prosupport,\n");
else
appendPQExpBuffer(query,
- "'-' AS prosupport\n");
+ "'-' AS prosupport,\n");
+
+ if (fout->remoteVersion >= 140000)
+ appendPQExpBuffer(query,
+ "CASE WHEN prosrc IS NULL AND lanname = 'sql' THEN pg_get_function_sqlbody(p.oid) END AS prosqlbody\n");
+ else
+ appendPQExpBuffer(query,
+ "NULL AS prosqlbody\n");
appendPQExpBuffer(query,
- "FROM pg_catalog.pg_proc "
- "WHERE oid = '%u'::pg_catalog.oid",
+ "FROM pg_catalog.pg_proc p, pg_catalog.pg_language l\n"
+ "WHERE p.oid = '%u'::pg_catalog.oid "
+ "AND l.oid = p.prolang",
finfo->dobj.catId.oid);
res = ExecuteSqlQueryForSingleRow(fout, query->data);
proretset = PQgetvalue(res, 0, PQfnumber(res, "proretset"));
- prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
- probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ if (PQgetisnull(res, 0, PQfnumber(res, "prosqlbody")))
+ {
+ prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
+ probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ prosqlbody = NULL;
+ }
+ else
+ {
+ prosrc = NULL;
+ probin = NULL;
+ prosqlbody = PQgetvalue(res, 0, PQfnumber(res, "prosqlbody"));
+ }
if (fout->remoteVersion >= 80400)
{
funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
@@ -11958,7 +11977,11 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
* versions would set it to "-". There are no known cases in which prosrc
* is unused, so the tests below for "-" are probably useless.
*/
- if (probin[0] != '\0' && strcmp(probin, "-") != 0)
+ if (prosqlbody)
+ {
+ appendPQExpBufferStr(asPart, prosqlbody);
+ }
+ else if (probin[0] != '\0' && strcmp(probin, "-") != 0)
{
appendPQExpBufferStr(asPart, "AS ");
appendStringLiteralAH(asPart, probin, fout);
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 08dffde1ba..ee34463e67 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -645,10 +645,11 @@ other .
";" {
ECHO;
- if (cur_state->paren_depth == 0)
+ if (cur_state->paren_depth == 0 && cur_state->begin_depth == 0)
{
/* Terminate lexing temporarily */
cur_state->start_state = YY_START;
+ cur_state->identifier_count = 0;
return LEXRES_SEMI;
}
}
@@ -661,6 +662,8 @@ other .
"\\"[;:] {
/* Force a semi-colon or colon into the query buffer */
psqlscan_emit(cur_state, yytext + 1, 1);
+ if (yytext[1] == ';')
+ cur_state->identifier_count = 0;
}
"\\" {
@@ -867,6 +870,17 @@ other .
{identifier} {
+ cur_state->identifier_count++;
+ if (pg_strcasecmp(yytext, "begin") == 0)
+ {
+ if (cur_state->identifier_count > 1)
+ cur_state->begin_depth++;
+ }
+ else if (pg_strcasecmp(yytext, "end") == 0)
+ {
+ if (cur_state->begin_depth > 0)
+ cur_state->begin_depth--;
+ }
ECHO;
}
@@ -1054,6 +1068,11 @@ psql_scan(PsqlScanState state,
result = PSCAN_INCOMPLETE;
*prompt = PROMPT_PAREN;
}
+ if (state->begin_depth > 0)
+ {
+ result = PSCAN_INCOMPLETE;
+ *prompt = PROMPT_CONTINUE;
+ }
else if (query_buf->len > 0)
{
result = PSCAN_EOL;
@@ -1170,6 +1189,8 @@ psql_scan_reset(PsqlScanState state)
if (state->dolqstart)
free(state->dolqstart);
state->dolqstart = NULL;
+ state->identifier_count = 0;
+ state->begin_depth = 0;
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 27989971db..8f76660058 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3676,6 +3676,10 @@
proname => 'pg_get_function_arg_default', provolatile => 's',
prorettype => 'text', proargtypes => 'oid int4',
prosrc => 'pg_get_function_arg_default' },
+{ oid => '9704', descr => 'function SQL body',
+ proname => 'pg_get_function_sqlbody', provolatile => 's',
+ prorettype => 'text', proargtypes => 'oid',
+ prosrc => 'pg_get_function_sqlbody' },
{ oid => '1686', descr => 'list of SQL keywords',
proname => 'pg_get_keywords', procost => '10', prorows => '400',
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index b50fa25dbd..48b53846f4 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -112,7 +112,7 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
Oid protrftypes[1] BKI_DEFAULT(_null_);
/* procedure source text */
- text prosrc BKI_FORCE_NOT_NULL;
+ text prosrc;
/* secondary procedure info (can be NULL) */
text probin BKI_DEFAULT(_null_);
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index c26a102b17..4d93afbd4b 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -68,9 +68,11 @@ extern void interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType);
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index cb13428a5a..5d547c66ed 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -20,6 +20,21 @@
/* This struct is known only within executor/functions.c */
typedef struct SQLFunctionParseInfo *SQLFunctionParseInfoPtr;
+/*
+ * Data structure needed by the parser callback hooks to resolve parameter
+ * references during parsing of a SQL function's body. This is separate from
+ * SQLFunctionCache since we sometimes do parsing separately from execution.
+ */
+typedef struct SQLFunctionParseInfo
+{
+ char *fname; /* function's name */
+ int nargs; /* number of input arguments */
+ Oid *argtypes; /* resolved types of input arguments */
+ char **argnames; /* names of input arguments; NULL if none */
+ /* Note that argnames[i] can be NULL, if some args are unnamed */
+ Oid collation; /* function's input collation, if known */
+} SQLFunctionParseInfo;
+
extern Datum fmgr_sql(PG_FUNCTION_ARGS);
extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTuple,
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 311f80394a..fb8f58aa29 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -114,6 +114,8 @@ typedef struct PsqlScanStateData
int paren_depth; /* depth of nesting in parentheses */
int xcdepth; /* depth of nesting in slash-star comments */
char *dolqstart; /* current $foo$ quote start string */
+ int identifier_count; /* identifiers since start of statement */
+ int begin_depth; /* depth of begin/end routine body blocks */
/*
* Callback functions provided by the program making use of the lexer,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..ab8b745665 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -316,6 +316,7 @@ typedef enum NodeTag
T_DeleteStmt,
T_UpdateStmt,
T_SelectStmt,
+ T_ReturnStmt,
T_AlterTableStmt,
T_AlterTableCmd,
T_AlterDomainStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 47d4c07306..818412e752 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -132,6 +132,8 @@ typedef struct Query
bool hasForUpdate; /* FOR [KEY] UPDATE/SHARE was specified */
bool hasRowSecurity; /* rewriter has applied some RLS policy */
+ bool isReturn; /* is a RETURN statement */
+
List *cteList; /* WITH list (of CommonTableExpr's) */
List *rtable; /* list of range table entries */
@@ -1670,6 +1672,16 @@ typedef struct SetOperationStmt
} SetOperationStmt;
+/*
+ * RETURN statement (inside SQL function body)
+ */
+typedef struct ReturnStmt
+{
+ NodeTag type;
+ Node *returnval;
+} ReturnStmt;
+
+
/*****************************************************************************
* Other Statements (no optimizations required)
*
@@ -2841,6 +2853,7 @@ typedef struct CreateFunctionStmt
List *parameters; /* a list of FunctionParameter */
TypeName *returnType; /* the return type */
List *options; /* a list of DefElem */
+ List *sql_body;
} CreateFunctionStmt;
typedef enum FunctionParameterMode
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 08f22ce211..c972c6f2ba 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -48,6 +48,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("atomic", ATOMIC, UNRESERVED_KEYWORD)
PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
@@ -344,6 +345,7 @@ PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD)
PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD)
PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD)
PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD)
+PG_KEYWORD("return", RETURN, UNRESERVED_KEYWORD)
PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD)
PG_KEYWORD("returns", RETURNS, UNRESERVED_KEYWORD)
PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD)
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index bd30607b07..e626c8eafd 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -43,6 +43,7 @@ typedef enum
extern PGDLLIMPORT int log_statement;
extern List *pg_parse_query(const char *query_string);
+extern List *pg_rewrite_query(Query *query);
extern List *pg_analyze_and_rewrite(RawStmt *parsetree,
const char *query_string,
Oid *paramTypes, int numParams,
diff --git a/src/interfaces/ecpg/preproc/ecpg.addons b/src/interfaces/ecpg/preproc/ecpg.addons
index 300381eaad..0441561c52 100644
--- a/src/interfaces/ecpg/preproc/ecpg.addons
+++ b/src/interfaces/ecpg/preproc/ecpg.addons
@@ -87,6 +87,12 @@ ECPG: stmtTransactionStmt block
whenever_action(2);
free($1);
}
+ECPG: toplevel_stmtTransactionStmtLegacy block
+ {
+ fprintf(base_yyout, "{ ECPGtrans(__LINE__, %s, \"%s\");", connection ? connection : "NULL", $1);
+ whenever_action(2);
+ free($1);
+ }
ECPG: stmtViewStmt rule
| ECPGAllocateDescr
{
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer
index 6ccc8ab916..7f4be655f9 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -4,8 +4,8 @@ statements: /*EMPTY*/
| statements statement
;
-statement: ecpgstart at stmt ';' { connection = NULL; }
- | ecpgstart stmt ';'
+statement: ecpgstart at toplevel_stmt ';' { connection = NULL; }
+ | ecpgstart toplevel_stmt ';'
| ecpgstart ECPGVarDeclaration
{
fprintf(base_yyout, "%s", $2);
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index ba260df996..38436b3ccc 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -17,7 +17,7 @@ SET search_path TO temp_func_test, public;
CREATE FUNCTION functest_A_1(text, date) RETURNS bool LANGUAGE 'sql'
AS 'SELECT $1 = ''abcd'' AND $2 > ''2001-01-01''';
CREATE FUNCTION functest_A_2(text[]) RETURNS int LANGUAGE 'sql'
- AS 'SELECT $1[0]::int';
+ AS 'SELECT $1[1]::int';
CREATE FUNCTION functest_A_3() RETURNS bool LANGUAGE 'sql'
AS 'SELECT false';
SELECT proname, prorettype::regtype, proargtypes::regtype[] FROM pg_proc
@@ -31,6 +31,24 @@ SELECT proname, prorettype::regtype, proargtypes::regtype[] FROM pg_proc
functest_a_3 | boolean | {}
(3 rows)
+SELECT functest_A_1('abcd', '2020-01-01');
+ functest_a_1
+--------------
+ t
+(1 row)
+
+SELECT functest_A_2(ARRAY['1', '2', '3']);
+ functest_a_2
+--------------
+ 1
+(1 row)
+
+SELECT functest_A_3();
+ functest_a_3
+--------------
+ f
+(1 row)
+
--
-- IMMUTABLE | STABLE | VOLATILE
--
@@ -237,6 +255,135 @@ SELECT pg_get_functiondef('functest_F_2'::regproc);
(1 row)
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+ERROR: SQL function with unquoted function body cannot have polymorphic arguments
+SELECT functest_S_1('abcd', '2020-01-01');
+ functest_s_1
+--------------
+ t
+(1 row)
+
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+ functest_s_2
+--------------
+ 1
+(1 row)
+
+SELECT functest_S_3();
+ functest_s_3
+--------------
+ f
+(1 row)
+
+SELECT functest_S_10('abcd', '2020-01-01');
+ functest_s_10
+---------------
+ t
+(1 row)
+
+SELECT functest_S_13();
+ functest_s_13
+---------------
+ f
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_1(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN ((a = 'abcd'::text) AND (b > '01-01-2001'::date)) +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_2(a text[])+
+ RETURNS integer +
+ LANGUAGE sql +
+ RETURN ((a)[1])::integer +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+ pg_get_functiondef
+----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN false +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+ pg_get_functiondef
+-------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_10(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT ((a = 'abcd'::text) AND (b > '01-01-2001'::date)) +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_13()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT 1; +
+ SELECT false AS bool +
+ END +
+
+(1 row)
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+SELECT functest_S_14();
+ functest_s_14
+---------------
+ 2
+(1 row)
+
+DROP TABLE functest3 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view functestv3
+drop cascades to function functest_s_14()
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
RETURNS int
@@ -266,6 +413,20 @@ SELECT routine_name, ordinal_position, parameter_name, parameter_default
(7 rows)
DROP FUNCTION functest_IS_1(int, int, text), functest_IS_2(int), functest_IS_3(int);
+CREATE TABLE functest1 (a int, b int);
+CREATE SEQUENCE functest2;
+CREATE FUNCTION functest_IS_4()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest1);
+CREATE FUNCTION functest_IS_5()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest2');
+DROP TABLE functest1 CASCADE;
+NOTICE: drop cascades to function functest_is_4()
+DROP SEQUENCE functest2 CASCADE;
+NOTICE: drop cascades to function functest_is_5()
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
@@ -342,7 +503,7 @@ SELECT * FROM voidtest5(3);
-- Cleanup
DROP SCHEMA temp_func_test CASCADE;
-NOTICE: drop cascades to 21 other objects
+NOTICE: drop cascades to 26 other objects
DETAIL: drop cascades to function functest_a_1(text,date)
drop cascades to function functest_a_2(text[])
drop cascades to function functest_a_3()
@@ -358,6 +519,11 @@ drop cascades to function functest_f_1(integer)
drop cascades to function functest_f_2(integer)
drop cascades to function functest_f_3(integer)
drop cascades to function functest_f_4(integer)
+drop cascades to function functest_s_1(text,date)
+drop cascades to function functest_s_2(text[])
+drop cascades to function functest_s_3()
+drop cascades to function functest_s_10(text,date)
+drop cascades to function functest_s_13()
drop cascades to function functest_b_2(bigint)
drop cascades to function voidtest1(integer)
drop cascades to function voidtest2(integer,integer)
diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out
index 211a42cefa..c927a2894a 100644
--- a/src/test/regress/expected/create_procedure.out
+++ b/src/test/regress/expected/create_procedure.out
@@ -65,6 +65,41 @@ SELECT * FROM cp_test ORDER BY b COLLATE "C";
1 | xyzzy
(3 rows)
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+\df ptest1s
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+---------+------------------+---------------------+------
+ public | ptest1s | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest1s'::regproc);
+ pg_get_functiondef
+----------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest1s(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ INSERT INTO cp_test (a, b) +
+ VALUES (1, x) +
+ END +
+
+(1 row)
+
+CALL ptest1s('b');
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+ a | b
+---+-------
+ 1 | 0
+ 1 | a
+ 1 | b
+ 1 | xyzzy
+(4 rows)
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -146,6 +181,28 @@ AS $$
SELECT a = b;
$$;
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+\df ptest8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+--------+------------------+---------------------+------
+ public | ptest8 | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest8'::regproc);
+ pg_get_functiondef
+---------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest8(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ END +
+
+(1 row)
+
+CALL ptest8('');
-- various error cases
CALL version(); -- error: not a procedure
ERROR: version() is not a procedure
@@ -204,6 +261,7 @@ ALTER ROUTINE ptest1a RENAME TO ptest1;
DROP ROUTINE cp_testfunc1(int);
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
DROP USER regress_cp_user1;
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
index 7a2df0ea8a..91071bfb9f 100644
--- a/src/test/regress/sql/create_function_3.sql
+++ b/src/test/regress/sql/create_function_3.sql
@@ -23,7 +23,7 @@ CREATE SCHEMA temp_func_test;
CREATE FUNCTION functest_A_1(text, date) RETURNS bool LANGUAGE 'sql'
AS 'SELECT $1 = ''abcd'' AND $2 > ''2001-01-01''';
CREATE FUNCTION functest_A_2(text[]) RETURNS int LANGUAGE 'sql'
- AS 'SELECT $1[0]::int';
+ AS 'SELECT $1[1]::int';
CREATE FUNCTION functest_A_3() RETURNS bool LANGUAGE 'sql'
AS 'SELECT false';
SELECT proname, prorettype::regtype, proargtypes::regtype[] FROM pg_proc
@@ -31,6 +31,10 @@ CREATE FUNCTION functest_A_3() RETURNS bool LANGUAGE 'sql'
'functest_A_2'::regproc,
'functest_A_3'::regproc) ORDER BY proname;
+SELECT functest_A_1('abcd', '2020-01-01');
+SELECT functest_A_2(ARRAY['1', '2', '3']);
+SELECT functest_A_3();
+
--
-- IMMUTABLE | STABLE | VOLATILE
--
@@ -149,6 +153,60 @@ CREATE FUNCTION functest_F_4(int) RETURNS bool LANGUAGE 'sql'
SELECT pg_get_functiondef('functest_F_2'::regproc);
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+
+SELECT functest_S_1('abcd', '2020-01-01');
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+SELECT functest_S_3();
+
+SELECT functest_S_10('abcd', '2020-01-01');
+SELECT functest_S_13();
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+
+SELECT functest_S_14();
+
+DROP TABLE functest3 CASCADE;
+
+
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
@@ -173,6 +231,23 @@ CREATE FUNCTION functest_IS_3(a int default 1, out b int)
DROP FUNCTION functest_IS_1(int, int, text), functest_IS_2(int), functest_IS_3(int);
+CREATE TABLE functest1 (a int, b int);
+CREATE SEQUENCE functest2;
+
+CREATE FUNCTION functest_IS_4()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest1);
+
+CREATE FUNCTION functest_IS_5()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest2');
+
+DROP TABLE functest1 CASCADE;
+DROP SEQUENCE functest2 CASCADE;
+
+
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
diff --git a/src/test/regress/sql/create_procedure.sql b/src/test/regress/sql/create_procedure.sql
index 89b96d580f..acbe92fb9a 100644
--- a/src/test/regress/sql/create_procedure.sql
+++ b/src/test/regress/sql/create_procedure.sql
@@ -28,6 +28,21 @@ CREATE PROCEDURE ptest1(x text)
SELECT * FROM cp_test ORDER BY b COLLATE "C";
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+
+\df ptest1s
+SELECT pg_get_functiondef('ptest1s'::regproc);
+
+CALL ptest1s('b');
+
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -112,6 +127,16 @@ CREATE PROCEDURE ptest7(a text, b text)
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+
+\df ptest8
+SELECT pg_get_functiondef('ptest8'::regproc);
+CALL ptest8('');
+
+
-- various error cases
CALL version(); -- error: not a procedure
@@ -159,6 +184,7 @@ CREATE USER regress_cp_user1;
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
base-commit: fe7fd4e9613f58262d30782a34b01cc0c4cbbeb5
--
2.28.0
Some conflicts have emerged, so here is an updated patch.
I have implemented/fixed the inlining of set-returning functions written
in the new style, which was previously marked TODO in the patch.
On 2020-08-28 07:33, Peter Eisentraut wrote:
On 2020-06-30 19:49, Peter Eisentraut wrote:
This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.Here is a new patch. The only significant change is that pg_dump
support is now fixed. Per the discussion in [0], I have introduced a
new function pg_get_function_sqlbody() that just produces the formatted
SQL body, not the whole function definition. All the tests now pass.
As mentioned before, more tests are probably needed, so if reviewers
just want to play with this and find things that don't work, those could
be put into test cases, for example.As a thought, a couple of things could probably be separated from this
patch and considered separately:1. making LANGUAGE SQL the default
2. the RETURN statement
If reviewers think that would be sensible, I can prepare separate
patches for those.[0]:
/messages/by-id/9df8a3d3-13d2-116d-26ab-6a273c1ed38c@2ndquadrant.com
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
v3-0001-SQL-standard-function-body.patchtext/plain; charset=UTF-8; name=v3-0001-SQL-standard-function-body.patch; x-mac-creator=0; x-mac-type=0Download
From f0ffa4106f43359ebde38f8d6d0a551fbd8764c2 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 7 Sep 2020 07:55:35 +0200
Subject: [PATCH v3] SQL-standard function body
This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.
Instead of the PostgreSQL-specific AS $$ string literal $$ syntax,
this allows writing out the SQL statements making up the body
unquoted, either as a single statement:
CREATE FUNCTION add(a integer, b integer) RETURNS integer
LANGUAGE SQL
RETURN a + b;
or as a block
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
BEGIN ATOMIC
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
END;
The function body is parsed at function definition time and stored as
expression nodes in probin. So at run time, no further parsing is
required.
However, this form does not support polymorphic arguments, because
there is no more parse analysis done at call time.
Dependencies between the function and the objects it uses are fully
tracked.
A new RETURN statement is introduced. This can only be used inside
function bodies. Internally, it is treated much like a SELECT
statement.
psql needs some new intelligence to keep track of function body
boundaries so that it doesn't send off statements when it sees
semicolons that are inside a function body.
Also, per SQL standard, LANGUAGE SQL is the default, so it does not
need to be specified anymore.
Discussion: https://www.postgresql.org/message-id/flat/1c11f1eb-f00c-43b7-799d-2d44132c02d7@2ndquadrant.com
---
doc/src/sgml/ref/create_function.sgml | 126 +++++++++--
doc/src/sgml/ref/create_procedure.sgml | 62 +++++-
src/backend/catalog/pg_proc.c | 145 +++++++------
src/backend/commands/aggregatecmds.c | 2 +
src/backend/commands/functioncmds.c | 96 +++++++--
src/backend/executor/functions.c | 79 ++++---
src/backend/nodes/copyfuncs.c | 15 ++
src/backend/nodes/equalfuncs.c | 13 ++
src/backend/nodes/outfuncs.c | 12 ++
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/util/clauses.c | 100 ++++++---
src/backend/parser/analyze.c | 35 ++++
src/backend/parser/gram.y | 126 ++++++++---
src/backend/tcop/postgres.c | 3 +-
src/backend/utils/adt/ruleutils.c | 110 +++++++++-
src/bin/pg_dump/pg_dump.c | 45 +++-
src/fe_utils/psqlscan.l | 23 ++-
src/include/catalog/pg_proc.dat | 4 +
src/include/catalog/pg_proc.h | 2 +-
src/include/commands/defrem.h | 2 +
src/include/executor/functions.h | 15 ++
src/include/fe_utils/psqlscan_int.h | 2 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 13 ++
src/include/parser/kwlist.h | 2 +
src/include/tcop/tcopprot.h | 1 +
src/interfaces/ecpg/preproc/ecpg.addons | 6 +
src/interfaces/ecpg/preproc/ecpg.trailer | 4 +-
.../regress/expected/create_function_3.out | 195 +++++++++++++++++-
.../regress/expected/create_procedure.out | 58 ++++++
src/test/regress/sql/create_function_3.sql | 94 +++++++++
src/test/regress/sql/create_procedure.sql | 26 +++
32 files changed, 1205 insertions(+), 213 deletions(-)
diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index 97285b7578..b3e280c290 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -38,6 +38,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -257,8 +258,9 @@ <title>Parameters</title>
The name of the language that the function is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -577,6 +579,44 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> function. This can
+ either be a single statement
+<programlisting>
+RETURN <replaceable>expression</replaceable>
+</programlisting>
+ or a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the function body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at function definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at function definition time. This form tracks dependencies between the
+ function and objects used in the function body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling functions. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
<para>
@@ -669,6 +709,15 @@ <title>Examples</title>
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
+</programlisting>
+ The same function written in a more SQL-conforming style, using argument
+ names and an unquoted body:
+<programlisting>
+CREATE FUNCTION add(a integer, b integer) RETURNS integer
+ LANGUAGE SQL
+ IMMUTABLE
+ RETURNS NULL ON NULL INPUT
+ RETURN a + b;
</programlisting>
</para>
@@ -799,23 +848,74 @@ <title>Writing <literal>SECURITY DEFINER</literal> Functions Safely</title>
<title>Compatibility</title>
<para>
- A <command>CREATE FUNCTION</command> command is defined in the SQL standard.
- The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. The attributes are not portable, neither are the
- different available languages.
+ A <command>CREATE FUNCTION</command> command is defined in the SQL
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. Conversely, the SQL
+ standard specifies a number of optional features that are not implemented
+ in <productname>PostgreSQL</productname>.
</para>
<para>
- For compatibility with some other database systems,
- <replaceable class="parameter">argmode</replaceable> can be written
- either before or after <replaceable class="parameter">argname</replaceable>.
- But only the first way is standard-compliant.
+ The following are important compatibility issues:
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>OR REPLACE</literal> is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For compatibility with some other database systems, <replaceable
+ class="parameter">argmode</replaceable> can be written either before or
+ after <replaceable class="parameter">argname</replaceable>. But only
+ the first way is standard-compliant.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For parameter defaults, the SQL standard specifies only the syntax with
+ the <literal>DEFAULT</literal> key word. The syntax with
+ <literal>=</literal> is used in T-SQL and Firebird.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The <literal>SETOF</literal> modifier is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Only <literal>SQL</literal> is standardized as a language.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ All other attributes except <literal>CALLED ON NULL INPUT</literal> and
+ <literal>RETURNS NULL ON NULL INPUT</literal> are not standardized.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For the body of <literal>LANGUAGE SQL</literal> functions, the SQL
+ standard only specifies the <replaceable>sql_body</replaceable> form.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
<para>
- For parameter defaults, the SQL standard specifies only the syntax with
- the <literal>DEFAULT</literal> key word. The syntax
- with <literal>=</literal> is used in T-SQL and Firebird.
+ Simple <literal>LANGUAGE SQL</literal> functions can be written in a way
+ that is both standard-conforming and portable to other implementations.
+ More complex functions using advanced features, optimization attributes, or
+ other languages will necessarily be specific to PostgreSQL in a significant
+ way.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_procedure.sgml b/doc/src/sgml/ref/create_procedure.sgml
index d225695626..d030e8a9f0 100644
--- a/doc/src/sgml/ref/create_procedure.sgml
+++ b/doc/src/sgml/ref/create_procedure.sgml
@@ -29,6 +29,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -164,8 +165,9 @@ <title>Parameters</title>
The name of the language that the procedure is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -301,6 +303,41 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> procedure. This should
+ be a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the procedure body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at procedure definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at procedure definition time. This form tracks dependencies between the
+ procedure and objects used in the procedure body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling procedures. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
@@ -320,6 +357,7 @@ <title>Notes</title>
<refsect1 id="sql-createprocedure-examples">
<title>Examples</title>
+ <para>
<programlisting>
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
@@ -327,9 +365,21 @@ <title>Examples</title>
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
$$;
-
+</programlisting>
+ or
+<programlisting>
+CREATE PROCEDURE insert_data(a integer, b integer)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO tbl VALUES (a);
+ INSERT INTO tbl VALUES (b);
+END;
+</programlisting>
+ and call like this:
+<programlisting>
CALL insert_data(1, 2);
</programlisting>
+ </para>
</refsect1>
<refsect1 id="sql-createprocedure-compat">
@@ -337,9 +387,9 @@ <title>Compatibility</title>
<para>
A <command>CREATE PROCEDURE</command> command is defined in the SQL
- standard. The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. For details see
- also <xref linkend="sql-createfunction"/>.
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. For details see also
+ <xref linkend="sql-createfunction"/>.
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 40d65dc6ba..e75073c6da 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -32,6 +32,7 @@
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_type.h"
#include "tcop/pquery.h"
@@ -119,8 +120,6 @@ ProcedureCreate(const char *procedureName,
/*
* sanity checks
*/
- Assert(PointerIsValid(prosrc));
-
parameterCount = parameterTypes->dim1;
if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
ereport(ERROR,
@@ -331,7 +330,10 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_protrftypes - 1] = trftypes;
else
nulls[Anum_pg_proc_protrftypes - 1] = true;
- values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ if (prosrc)
+ values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ else
+ nulls[Anum_pg_proc_prosrc - 1] = true;
if (probin)
values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin);
else
@@ -633,6 +635,10 @@ ProcedureCreate(const char *procedureName,
record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
free_object_addresses(addrs);
+ /* dependency on SQL routine body */
+ if (languageObjectId == SQLlanguageId && probin)
+ recordDependencyOnExpr(&myself, stringToNode(probin), NIL, DEPENDENCY_NORMAL);
+
/* dependency on parameter default expressions */
if (parameterDefaults)
recordDependencyOnExpr(&myself, (Node *) parameterDefaults,
@@ -806,14 +812,6 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
Oid funcoid = PG_GETARG_OID(0);
HeapTuple tuple;
Form_pg_proc proc;
- List *raw_parsetree_list;
- List *querytree_list;
- ListCell *lc;
- bool isnull;
- Datum tmp;
- char *prosrc;
- parse_error_callback_arg callback_arg;
- ErrorContextCallback sqlerrcontext;
bool haspolyarg;
int i;
@@ -856,72 +854,93 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
/* Postpone body checks if !check_function_bodies */
if (check_function_bodies)
{
+ Datum tmp;
+ bool isnull;
+ List *querytree_list = NIL;
+
tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
if (isnull)
- elog(ERROR, "null prosrc");
-
- prosrc = TextDatumGetCString(tmp);
+ {
+ char *probin;
- /*
- * Setup error traceback support for ereport().
- */
- callback_arg.proname = NameStr(proc->proname);
- callback_arg.prosrc = prosrc;
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_probin, &isnull);
+ if (isnull)
+ elog(ERROR, "null probin and prosrc");
- sqlerrcontext.callback = sql_function_parse_error_callback;
- sqlerrcontext.arg = (void *) &callback_arg;
- sqlerrcontext.previous = error_context_stack;
- error_context_stack = &sqlerrcontext;
+ probin = TextDatumGetCString(tmp);
+ querytree_list = castNode(List, stringToNode(probin));
+ }
+ else
+ {
+ char *prosrc;
+ List *raw_parsetree_list;
+ parse_error_callback_arg callback_arg;
+ ErrorContextCallback sqlerrcontext;
- /*
- * We can't do full prechecking of the function definition if there
- * are any polymorphic input types, because actual datatypes of
- * expression results will be unresolvable. The check will be done at
- * runtime instead.
- *
- * We can run the text through the raw parser though; this will at
- * least catch silly syntactic errors.
- */
- raw_parsetree_list = pg_parse_query(prosrc);
+ prosrc = TextDatumGetCString(tmp);
- if (!haspolyarg)
- {
/*
- * OK to do full precheck: analyze and rewrite the queries, then
- * verify the result type.
+ * Setup error traceback support for ereport().
*/
- SQLFunctionParseInfoPtr pinfo;
- Oid rettype;
- TupleDesc rettupdesc;
+ callback_arg.proname = NameStr(proc->proname);
+ callback_arg.prosrc = prosrc;
- /* But first, set up parameter information */
- pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
+ sqlerrcontext.callback = sql_function_parse_error_callback;
+ sqlerrcontext.arg = (void *) &callback_arg;
+ sqlerrcontext.previous = error_context_stack;
+ error_context_stack = &sqlerrcontext;
- querytree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ /*
+ * We can't do full prechecking of the function definition if there
+ * are any polymorphic input types, because actual datatypes of
+ * expression results will be unresolvable. The check will be done at
+ * runtime instead.
+ *
+ * We can run the text through the raw parser though; this will at
+ * least catch silly syntactic errors.
+ */
+ raw_parsetree_list = pg_parse_query(prosrc);
+
+ if (!haspolyarg)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *querytree_sublist;
-
- querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
- prosrc,
- (ParserSetupHook) sql_fn_parser_setup,
- pinfo,
- NULL);
- querytree_list = list_concat(querytree_list,
- querytree_sublist);
+ /*
+ * OK to do full precheck: analyze and rewrite the queries, then
+ * verify the result type.
+ */
+ ListCell *lc;
+ SQLFunctionParseInfoPtr pinfo;
+ Oid rettype;
+ TupleDesc rettupdesc;
+
+ /* But first, set up parameter information */
+ pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
+
+ querytree_list = NIL;
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *querytree_sublist;
+
+ querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ prosrc,
+ (ParserSetupHook) sql_fn_parser_setup,
+ pinfo,
+ NULL);
+ querytree_list = list_concat(querytree_list,
+ querytree_sublist);
+ }
+
+ check_sql_fn_statements(querytree_list);
+
+ (void) get_func_result_type(funcoid, &rettype, &rettupdesc);
+
+ (void) check_sql_fn_retval(querytree_list,
+ rettype, rettupdesc,
+ false, NULL);
}
- check_sql_fn_statements(querytree_list);
-
- (void) get_func_result_type(funcoid, &rettype, &rettupdesc);
-
- (void) check_sql_fn_retval(querytree_list,
- rettype, rettupdesc,
- false, NULL);
+ error_context_stack = sqlerrcontext.previous;
}
-
- error_context_stack = sqlerrcontext.previous;
}
ReleaseSysCache(tuple);
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 6bf54e64f8..26b3fa27de 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -313,9 +313,11 @@ DefineAggregate(ParseState *pstate,
InvalidOid,
OBJECT_AGGREGATE,
¶meterTypes,
+ NULL,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ NULL,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index e236581a8e..4e1461e334 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -53,9 +53,11 @@
#include "commands/proclang.h"
#include "executor/execdesc.h"
#include "executor/executor.h"
+#include "executor/functions.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
@@ -186,9 +188,11 @@ interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType)
@@ -300,6 +304,8 @@ interpret_function_parameter_list(ParseState *pstate,
errmsg("VARIADIC parameter must be the last input parameter")));
inTypes[inCount++] = toid;
isinput = true;
+ if (parameterTypes_list)
+ *parameterTypes_list = lappend_oid(*parameterTypes_list, toid);
}
/* handle output parameters */
@@ -376,6 +382,9 @@ interpret_function_parameter_list(ParseState *pstate,
have_names = true;
}
+ if (inParameterNames_list)
+ *inParameterNames_list = lappend(*inParameterNames_list, makeString(fp->name ? fp->name : pstrdup("")));
+
if (fp->defexpr)
{
Node *def;
@@ -790,28 +799,10 @@ compute_function_attributes(ParseState *pstate,
defel->defname);
}
- /* process required items */
if (as_item)
*as = (List *) as_item->arg;
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no function body specified")));
- *as = NIL; /* keep compiler quiet */
- }
-
if (language_item)
*language = strVal(language_item->arg);
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no language specified")));
- *language = NULL; /* keep compiler quiet */
- }
-
- /* process optional items */
if (transform_item)
*transform = transform_item->arg;
if (windowfunc_item)
@@ -860,10 +851,19 @@ compute_function_attributes(ParseState *pstate,
*/
static void
interpret_AS_clause(Oid languageOid, const char *languageName,
- char *funcname, List *as,
+ char *funcname, List *as, List *sql_body,
+ List *parameterTypes, List *inParameterNames,
char **prosrc_str_p, char **probin_str_p)
{
- Assert(as != NIL);
+ if (sql_body && as)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("duplicate function body specified")));
+
+ if (sql_body && languageOid != SQLlanguageId)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("inline SQL function body only valid for language SQL")));
if (languageOid == ClanguageId)
{
@@ -885,6 +885,53 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
*prosrc_str_p = funcname;
}
}
+ else if (sql_body)
+ {
+ List *transformed_stmts = NIL;
+ ListCell *lc;
+ SQLFunctionParseInfoPtr pinfo;
+
+ pinfo = (SQLFunctionParseInfoPtr) palloc0(sizeof(SQLFunctionParseInfo));
+
+ pinfo->fname = funcname;
+ pinfo->nargs = list_length(parameterTypes);
+ pinfo->argtypes = (Oid *) palloc(pinfo->nargs * sizeof(Oid));
+ pinfo->argnames = (char **) palloc(pinfo->nargs * sizeof(char *));
+ for (int i = 0; i < list_length(parameterTypes); i++)
+ {
+ char *s = strVal(list_nth(inParameterNames, i));
+
+ pinfo->argtypes[i] = list_nth_oid(parameterTypes, i);
+ if (IsPolymorphicType(pinfo->argtypes[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("SQL function with unquoted function body cannot have polymorphic arguments")));
+
+ if (s[0] != '\0')
+ pinfo->argnames[i] = s;
+ else
+ pinfo->argnames[i] = NULL;
+ }
+
+ foreach(lc, sql_body)
+ {
+ Node *stmt = lfirst(lc);
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ /* ignore NULL statement; see gram.y */
+ if (!stmt)
+ continue;
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, stmt);
+ transformed_stmts = lappend(transformed_stmts, q);
+ free_parsestate(pstate);
+ }
+
+ *probin_str_p = nodeToString(transformed_stmts);
+ *prosrc_str_p = NULL;
+ }
else
{
/* Everything else wants the given string in prosrc. */
@@ -933,9 +980,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
Oid namespaceId;
AclResult aclresult;
oidvector *parameterTypes;
+ List *parameterTypes_list = NIL;
ArrayType *allParameterTypes;
ArrayType *parameterModes;
ArrayType *parameterNames;
+ List *inParameterNames_list = NIL;
List *parameterDefaults;
Oid variadicArgType;
List *trftypes_list = NIL;
@@ -966,6 +1015,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
get_namespace_name(namespaceId));
/* Set default attributes */
+ as_clause = NULL;
+ language = "sql";
isWindowFunc = false;
isStrict = false;
security = false;
@@ -1057,9 +1108,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageOid,
stmt->is_procedure ? OBJECT_PROCEDURE : OBJECT_FUNCTION,
¶meterTypes,
+ ¶meterTypes_list,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ &inParameterNames_list,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
@@ -1116,7 +1169,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
trftypes = NULL;
}
- interpret_AS_clause(languageOid, language, funcname, as_clause,
+ interpret_AS_clause(languageOid, language, funcname, as_clause, stmt->sql_body,
+ parameterTypes_list, inParameterNames_list,
&prosrc_str, &probin_str);
/*
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index f940f48c6d..911149aa27 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -26,6 +26,7 @@
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_func.h"
+#include "rewrite/rewriteHandler.h"
#include "storage/proc.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
@@ -128,21 +129,6 @@ typedef struct
typedef SQLFunctionCache *SQLFunctionCachePtr;
-/*
- * Data structure needed by the parser callback hooks to resolve parameter
- * references during parsing of a SQL function's body. This is separate from
- * SQLFunctionCache since we sometimes do parsing separately from execution.
- */
-typedef struct SQLFunctionParseInfo
-{
- char *fname; /* function's name */
- int nargs; /* number of input arguments */
- Oid *argtypes; /* resolved types of input arguments */
- char **argnames; /* names of input arguments; NULL if none */
- /* Note that argnames[i] can be NULL, if some args are unnamed */
- Oid collation; /* function's input collation, if known */
-} SQLFunctionParseInfo;
-
/* non-export function prototypes */
static Node *sql_fn_param_ref(ParseState *pstate, ParamRef *pref);
@@ -606,7 +592,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
HeapTuple procedureTuple;
Form_pg_proc procedureStruct;
SQLFunctionCachePtr fcache;
- List *raw_parsetree_list;
List *queryTree_list;
List *flat_query_list;
List *resulttlist;
@@ -682,9 +667,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
procedureTuple,
Anum_pg_proc_prosrc,
&isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", foid);
- fcache->src = TextDatumGetCString(tmp);
/*
* Parse and rewrite the queries in the function text. Use sublists to
@@ -701,22 +683,55 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
* but we'll not worry about it until the module is rewritten to use
* plancache.c.
*/
- raw_parsetree_list = pg_parse_query(fcache->src);
-
queryTree_list = NIL;
flat_query_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (isNull)
+ {
+ char *binstr;
+ List *stored_query_list;
+
+ tmp = SysCacheGetAttr(PROCOID,
+ procedureTuple,
+ Anum_pg_proc_probin,
+ &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and probin for function %u", foid);
+
+ binstr = TextDatumGetCString(tmp);
+ stored_query_list = stringToNode(binstr);
+
+ foreach(lc, stored_query_list)
+ {
+ Query *parsetree = lfirst_node(Query, lc);
+ List *queryTree_sublist;
+
+ AcquireRewriteLocks(parsetree, true, false);
+ queryTree_sublist = pg_rewrite_query(parsetree);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ flat_query_list = list_concat(flat_query_list, queryTree_sublist);
+ }
+ }
+ else
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *queryTree_sublist;
-
- queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
- fcache->src,
- (ParserSetupHook) sql_fn_parser_setup,
- fcache->pinfo,
- NULL);
- queryTree_list = lappend(queryTree_list, queryTree_sublist);
- flat_query_list = list_concat(flat_query_list, queryTree_sublist);
+ List *raw_parsetree_list;
+
+ fcache->src = TextDatumGetCString(tmp);
+
+ raw_parsetree_list = pg_parse_query(fcache->src);
+
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *queryTree_sublist;
+
+ queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ fcache->src,
+ (ParserSetupHook) sql_fn_parser_setup,
+ fcache->pinfo,
+ NULL);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ flat_query_list = list_concat(flat_query_list, queryTree_sublist);
+ }
}
check_sql_fn_statements(flat_query_list);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 0409a40b82..8dc9efcd93 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3069,6 +3069,7 @@ _copyQuery(const Query *from)
COPY_SCALAR_FIELD(hasModifyingCTE);
COPY_SCALAR_FIELD(hasForUpdate);
COPY_SCALAR_FIELD(hasRowSecurity);
+ COPY_SCALAR_FIELD(isReturn);
COPY_NODE_FIELD(cteList);
COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(jointree);
@@ -3197,6 +3198,16 @@ _copySetOperationStmt(const SetOperationStmt *from)
return newnode;
}
+static ReturnStmt *
+_copyReturnStmt(const ReturnStmt *from)
+{
+ ReturnStmt *newnode = makeNode(ReturnStmt);
+
+ COPY_NODE_FIELD(returnval);
+
+ return newnode;
+}
+
static AlterTableStmt *
_copyAlterTableStmt(const AlterTableStmt *from)
{
@@ -3573,6 +3584,7 @@ _copyCreateFunctionStmt(const CreateFunctionStmt *from)
COPY_NODE_FIELD(parameters);
COPY_NODE_FIELD(returnType);
COPY_NODE_FIELD(options);
+ COPY_NODE_FIELD(sql_body);
return newnode;
}
@@ -5225,6 +5237,9 @@ copyObjectImpl(const void *from)
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
+ case T_ReturnStmt:
+ retval = _copyReturnStmt(from);
+ break;
case T_AlterTableStmt:
retval = _copyAlterTableStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b987bf..0051b1a88b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -966,6 +966,7 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_SCALAR_FIELD(hasModifyingCTE);
COMPARE_SCALAR_FIELD(hasForUpdate);
COMPARE_SCALAR_FIELD(hasRowSecurity);
+ COMPARE_SCALAR_FIELD(isReturn);
COMPARE_NODE_FIELD(cteList);
COMPARE_NODE_FIELD(rtable);
COMPARE_NODE_FIELD(jointree);
@@ -1082,6 +1083,14 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
return true;
}
+static bool
+_equalReturnStmt(const ReturnStmt *a, const ReturnStmt *b)
+{
+ COMPARE_NODE_FIELD(returnval);
+
+ return true;
+}
+
static bool
_equalAlterTableStmt(const AlterTableStmt *a, const AlterTableStmt *b)
{
@@ -1394,6 +1403,7 @@ _equalCreateFunctionStmt(const CreateFunctionStmt *a, const CreateFunctionStmt *
COMPARE_NODE_FIELD(parameters);
COMPARE_NODE_FIELD(returnType);
COMPARE_NODE_FIELD(options);
+ COMPARE_NODE_FIELD(sql_body);
return true;
}
@@ -3277,6 +3287,9 @@ equal(const void *a, const void *b)
case T_SetOperationStmt:
retval = _equalSetOperationStmt(a, b);
break;
+ case T_ReturnStmt:
+ retval = _equalReturnStmt(a, b);
+ break;
case T_AlterTableStmt:
retval = _equalAlterTableStmt(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..2153fcf59b 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2758,6 +2758,14 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
WRITE_NODE_FIELD(rarg);
}
+static void
+_outReturnStmt(StringInfo str, const ReturnStmt *node)
+{
+ WRITE_NODE_TYPE("RETURN");
+
+ WRITE_NODE_FIELD(returnval);
+}
+
static void
_outFuncCall(StringInfo str, const FuncCall *node)
{
@@ -2946,6 +2954,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_BOOL_FIELD(hasModifyingCTE);
WRITE_BOOL_FIELD(hasForUpdate);
WRITE_BOOL_FIELD(hasRowSecurity);
+ WRITE_BOOL_FIELD(isReturn);
WRITE_NODE_FIELD(cteList);
WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(jointree);
@@ -4196,6 +4205,9 @@ outNode(StringInfo str, const void *obj)
case T_SelectStmt:
_outSelectStmt(str, obj);
break;
+ case T_ReturnStmt:
+ _outReturnStmt(str, obj);
+ break;
case T_ColumnDef:
_outColumnDef(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..c6d0009820 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -263,6 +263,7 @@ _readQuery(void)
READ_BOOL_FIELD(hasModifyingCTE);
READ_BOOL_FIELD(hasForUpdate);
READ_BOOL_FIELD(hasRowSecurity);
+ READ_BOOL_FIELD(isReturn);
READ_NODE_FIELD(cteList);
READ_NODE_FIELD(rtable);
READ_NODE_FIELD(jointree);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 750586fceb..4a3d9b5253 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4422,7 +4422,22 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
Anum_pg_proc_prosrc,
&isNull);
if (isNull)
- elog(ERROR, "null prosrc for function %u", funcid);
+ {
+ char *probin;
+ List *querytree_list;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_probin, &isNull);
+ if (isNull)
+ elog(ERROR, "null probin and prosrc");
+
+ probin = TextDatumGetCString(tmp);
+ querytree_list = castNode(List, stringToNode(probin));
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
src = TextDatumGetCString(tmp);
/*
@@ -4480,6 +4495,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
querytree = transformTopLevelStmt(pstate, linitial(raw_parsetree_list));
free_parsestate(pstate);
+ }
/*
* The single command must be a simple "SELECT expression".
@@ -4735,12 +4751,15 @@ sql_inline_error_callback(void *arg)
int syntaxerrposition;
/* If it's a syntax error, convert to internal syntax error report */
- syntaxerrposition = geterrposition();
- if (syntaxerrposition > 0)
+ if (callback_arg->prosrc)
{
- errposition(0);
- internalerrposition(syntaxerrposition);
- internalerrquery(callback_arg->prosrc);
+ syntaxerrposition = geterrposition();
+ if (syntaxerrposition > 0)
+ {
+ errposition(0);
+ internalerrposition(syntaxerrposition);
+ internalerrquery(callback_arg->prosrc);
+ }
}
errcontext("SQL function \"%s\" during inlining", callback_arg->proname);
@@ -4852,7 +4871,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
Oid func_oid;
HeapTuple func_tuple;
Form_pg_proc funcform;
- char *src;
Datum tmp;
bool isNull;
MemoryContext oldcxt;
@@ -4961,27 +4979,50 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
- /* Fetch the function body */
- tmp = SysCacheGetAttr(PROCOID,
- func_tuple,
- Anum_pg_proc_prosrc,
- &isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", func_oid);
- src = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = src;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ {
+ char *probin;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_probin, &isNull);
+ if (isNull)
+ elog(ERROR, "null probin and prosrc");
+
+ probin = TextDatumGetCString(tmp);
+ querytree_list = castNode(List, stringToNode(probin));
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+
+ querytree_list = pg_rewrite_query(querytree);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
+ char *src;
+
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We can
* use the FuncExpr just created as the input for
@@ -4991,18 +5032,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
(Node *) fexpr,
fexpr->inputcollid);
- /*
- * Also resolve the actual function result tupdesc, if composite. If the
- * function is just declared to return RECORD, dig the info out of the AS
- * clause.
- */
- functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
- if (functypclass == TYPEFUNC_RECORD)
- rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
- rtfunc->funccoltypes,
- rtfunc->funccoltypmods,
- rtfunc->funccolcollations);
-
/*
* Parse, analyze, and rewrite (unlike inline_function(), we can't skip
* rewriting here). We can fail as soon as we find more than one query,
@@ -5019,6 +5048,19 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
if (list_length(querytree_list) != 1)
goto fail;
querytree = linitial(querytree_list);
+ }
+
+ /*
+ * Also resolve the actual function result tupdesc, if composite. If the
+ * function is just declared to return RECORD, dig the info out of the AS
+ * clause.
+ */
+ functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
+ if (functypclass == TYPEFUNC_RECORD)
+ rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
+ rtfunc->funccoltypes,
+ rtfunc->funccoltypmods,
+ rtfunc->funccolcollations);
/*
* The single command must be a plain SELECT.
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c159fb2957..ed0622c5d7 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -66,6 +66,7 @@ static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
bool isTopLevel, List **targetlist);
static void determineRecursiveColTypes(ParseState *pstate,
Node *larg, List *nrtargetlist);
+static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList);
static List *transformUpdateTargetList(ParseState *pstate,
@@ -304,6 +305,10 @@ transformStmt(ParseState *pstate, Node *parseTree)
}
break;
+ case T_ReturnStmt:
+ result = transformReturnStmt(pstate, (ReturnStmt *) parseTree);
+ break;
+
/*
* Special cases
*/
@@ -2221,6 +2226,36 @@ determineRecursiveColTypes(ParseState *pstate, Node *larg, List *nrtargetlist)
}
+/*
+ * transformReturnStmt -
+ * transforms a return statement
+ */
+static Query *
+transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+
+ qry->commandType = CMD_SELECT;
+ qry->isReturn = true;
+
+ qry->targetList = list_make1(makeTargetEntry((Expr *) transformExpr(pstate, stmt->returnval, EXPR_KIND_SELECT_TARGET),
+ 1, NULL, false));
+
+ if (pstate->p_resolve_unknowns)
+ resolveTargetListUnknowns(pstate, qry->targetList);
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasAggs = pstate->p_hasAggs;
+
+ assign_query_collations(pstate, qry);
+
+ return qry;
+}
+
+
/*
* transformUpdateStmt -
* transforms an update statement
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c5154b818c..6c4963f10f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -253,7 +253,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct SelectLimit *selectlimit;
}
-%type <node> stmt schema_stmt
+%type <node> stmt toplevel_stmt schema_stmt routine_body_stmt
AlterEventTrigStmt AlterCollationStmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
@@ -280,9 +280,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
- RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
+ RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt
RuleActionStmt RuleActionStmtOrEmpty RuleStmt
- SecLabelStmt SelectStmt TransactionStmt TruncateStmt
+ SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
UnlistenStmt UpdateStmt VacuumStmt
VariableResetStmt VariableSetStmt VariableShowStmt
ViewStmt CheckPointStmt CreateConversionStmt
@@ -385,14 +385,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> vacuum_relation
%type <selectlimit> opt_select_limit select_limit limit_clause
-%type <list> stmtblock stmtmulti
+%type <list> stmtblock stmtmulti routine_body_stmt_list
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
reloptions opt_reloptions
OptWith distinct_clause opt_all_clause opt_definition func_args func_args_list
func_args_with_defaults func_args_with_defaults_list
aggr_args aggr_args_list
- func_as createfunc_opt_list alterfunc_opt_list
+ func_as createfunc_opt_list opt_createfunc_opt_list alterfunc_opt_list
old_aggr_definition old_aggr_list
oper_argtypes RuleActionList RuleActionMulti
opt_column_list columnList opt_name_list
@@ -418,6 +418,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
vacuum_relation_list opt_vacuum_relation_list
drop_option_list
+%type <list> opt_routine_body
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
@@ -626,7 +627,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATOMIC ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
@@ -688,7 +689,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING
REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
- RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
+ RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
@@ -815,7 +816,7 @@ stmtblock: stmtmulti
* we'd get -1 for the location in such cases.
* We also take care to discard empty statements entirely.
*/
-stmtmulti: stmtmulti ';' stmt
+stmtmulti: stmtmulti ';' toplevel_stmt
{
if ($1 != NIL)
{
@@ -827,7 +828,7 @@ stmtmulti: stmtmulti ';' stmt
else
$$ = $1;
}
- | stmt
+ | toplevel_stmt
{
if ($1 != NULL)
$$ = list_make1(makeRawStmt($1, 0));
@@ -836,7 +837,16 @@ stmtmulti: stmtmulti ';' stmt
}
;
-stmt :
+/*
+ * toplevel_stmt includes BEGIN and END. stmt does not include them, because
+ * those words have different meanings in function bodys.
+ */
+toplevel_stmt:
+ stmt
+ | TransactionStmtLegacy
+ ;
+
+stmt:
AlterEventTrigStmt
| AlterCollationStmt
| AlterDatabaseStmt
@@ -7342,7 +7352,7 @@ opt_nulls_order: NULLS_LA FIRST_P { $$ = SORTBY_NULLS_FIRST; }
CreateFunctionStmt:
CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS func_return createfunc_opt_list
+ RETURNS func_return opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7351,10 +7361,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = $7;
n->options = $8;
+ n->sql_body = $9;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS TABLE '(' table_func_column_list ')' createfunc_opt_list
+ RETURNS TABLE '(' table_func_column_list ')' opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7364,10 +7375,11 @@ CreateFunctionStmt:
n->returnType = TableFuncTypeName($9);
n->returnType->location = @7;
n->options = $11;
+ n->sql_body = $12;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7376,10 +7388,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
| CREATE opt_or_replace PROCEDURE func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = true;
@@ -7388,6 +7401,7 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
;
@@ -7667,6 +7681,11 @@ aggregate_with_argtypes_list:
{ $$ = lappend($1, $3); }
;
+opt_createfunc_opt_list:
+ createfunc_opt_list
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
createfunc_opt_list:
/* Must be at least one to prevent conflict */
createfunc_opt_item { $$ = list_make1($1); }
@@ -7778,6 +7797,50 @@ func_as: Sconst { $$ = list_make1(makeString($1)); }
}
;
+ReturnStmt: RETURN a_expr
+ {
+ ReturnStmt *r = makeNode(ReturnStmt);
+ r->returnval = (Node *) $2;
+ $$ = (Node *) r;
+ }
+ ;
+
+opt_routine_body:
+ ReturnStmt
+ {
+ $$ = list_make1($1);
+ }
+ | BEGIN_P ATOMIC routine_body_stmt_list END_P
+ {
+ $$ = $3;
+ }
+ | /*EMPTY*/
+ {
+ $$ = NIL;
+ }
+ ;
+
+routine_body_stmt_list:
+ routine_body_stmt_list routine_body_stmt ';'
+ {
+ $$ = lappend($1, $2);
+ }
+ | /*EMPTY*/
+ {
+ /*
+ * For an empty body we insert a single fake element, so
+ * that the parse analysis code can tell apart an empty
+ * body from no body at all.
+ */
+ $$ = list_make1(NULL);
+ }
+ ;
+
+routine_body_stmt:
+ stmt
+ | ReturnStmt
+ ;
+
transform_type_list:
FOR TYPE_P Typename { $$ = list_make1($3); }
| transform_type_list ',' FOR TYPE_P Typename { $$ = lappend($1, $5); }
@@ -9728,13 +9791,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | BEGIN_P opt_transaction transaction_mode_list_or_empty
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_BEGIN;
- n->options = $3;
- $$ = (Node *)n;
- }
| START TRANSACTION transaction_mode_list_or_empty
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9750,14 +9806,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | END_P opt_transaction opt_transaction_chain
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_COMMIT;
- n->options = NIL;
- n->chain = $3;
- $$ = (Node *)n;
- }
| ROLLBACK opt_transaction opt_transaction_chain
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9824,6 +9872,24 @@ TransactionStmt:
}
;
+TransactionStmtLegacy:
+ BEGIN_P opt_transaction transaction_mode_list_or_empty
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_BEGIN;
+ n->options = $3;
+ $$ = (Node *)n;
+ }
+ | END_P opt_transaction opt_transaction_chain
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_COMMIT;
+ n->options = NIL;
+ n->chain = $3;
+ $$ = (Node *)n;
+ }
+ ;
+
opt_transaction: WORK {}
| TRANSACTION {}
| /*EMPTY*/ {}
@@ -15040,6 +15106,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| BACKWARD
@@ -15238,6 +15305,7 @@ unreserved_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| ROLE
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index c9424f167c..b63c330653 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -176,7 +176,6 @@ static int interactive_getc(void);
static int SocketBackend(StringInfo inBuf);
static int ReadCommand(StringInfo inBuf);
static void forbidden_in_wal_sender(char firstchar);
-static List *pg_rewrite_query(Query *query);
static bool check_log_statement(List *stmt_list);
static int errdetail_execute(List *raw_parsetree_list);
static int errdetail_params(ParamListInfo params);
@@ -761,7 +760,7 @@ pg_analyze_and_rewrite_params(RawStmt *parsetree,
* Note: query must just have come from the parser, because we do not do
* AcquireRewriteLocks() on it.
*/
-static List *
+List *
pg_rewrite_query(Query *query)
{
List *querytree_list;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 60dd80c23c..b7c41fff80 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -174,6 +174,9 @@ typedef struct
List *outer_tlist; /* referent for OUTER_VAR Vars */
List *inner_tlist; /* referent for INNER_VAR Vars */
List *index_tlist; /* referent for INDEX_VAR Vars */
+ /* Special namespace representing a function signature: */
+ int numargs;
+ char **argnames;
} deparse_namespace;
/*
@@ -349,6 +352,7 @@ static int print_function_arguments(StringInfo buf, HeapTuple proctup,
bool print_table_args, bool print_defaults);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
static void print_function_trftypes(StringInfo buf, HeapTuple proctup);
+static void print_function_sqlbody(StringInfo buf, HeapTuple proctup);
static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used);
static void set_deparse_for_query(deparse_namespace *dpns, Query *query,
@@ -2800,9 +2804,15 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
}
/* And finally the function definition ... */
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
+ if (proc->prolang == SQLlanguageId && !isnull)
+ {
+ print_function_sqlbody(&buf, proctup);
+ }
+ else
+ {
appendStringInfoString(&buf, "AS ");
- tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
if (!isnull)
{
simple_quote_literal(&buf, TextDatumGetCString(tmp));
@@ -2831,6 +2841,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, dq.data, dq.len);
appendStringInfoString(&buf, prosrc);
appendBinaryStringInfo(&buf, dq.data, dq.len);
+ }
appendStringInfoChar(&buf, '\n');
@@ -3213,6 +3224,78 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(string_to_text(str));
}
+static void
+print_function_sqlbody(StringInfo buf, HeapTuple proctup)
+{
+ Datum tmp;
+ bool isnull;
+ List *stmts;
+ ListCell *lc;
+ int numargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ deparse_namespace dpns = {0};
+ bool need_block;
+
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
+ Assert(!isnull);
+ stmts = castNode(List, stringToNode(TextDatumGetCString(tmp)));
+
+ numargs = get_func_arg_info(proctup,
+ &argtypes, &argnames, &argmodes);
+ dpns.numargs = numargs;
+ dpns.argnames = argnames;
+
+ /*
+ * We need a BEGIN ATOMIC/END block unless the body is a single RETURN
+ * statement.
+ */
+ need_block = true;
+ if (list_length(stmts) == 1)
+ {
+ Query *query = linitial_node(Query, stmts);
+
+ if (query->isReturn)
+ need_block = false;
+ }
+
+ if (need_block)
+ appendStringInfoString(buf, "BEGIN ATOMIC\n");
+ foreach(lc, stmts)
+ {
+ Query *query = lfirst_node(Query, lc);
+
+ get_query_def(query, buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ if (lc != list_tail(stmts))
+ appendStringInfoChar(buf, ';');
+ appendStringInfoChar(buf, '\n');
+ }
+ if (need_block)
+ appendStringInfoString(buf, "END");
+}
+
+Datum
+pg_get_function_sqlbody(PG_FUNCTION_ARGS)
+{
+ Oid funcid = PG_GETARG_OID(0);
+ StringInfoData buf;
+ HeapTuple proctup;
+
+ initStringInfo(&buf);
+
+ /* Look up the function */
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ PG_RETURN_NULL();
+
+ print_function_sqlbody(&buf, proctup);
+
+ ReleaseSysCache(proctup);
+
+ PG_RETURN_TEXT_P(cstring_to_text(buf.data));
+}
+
/*
* deparse_expression - General utility for deparsing expressions
@@ -5418,7 +5501,10 @@ get_basic_select_query(Query *query, deparse_context *context,
/*
* Build up the query string - first we say SELECT
*/
- appendStringInfoString(buf, "SELECT");
+ if (query->isReturn)
+ appendStringInfoString(buf, "RETURN");
+ else
+ appendStringInfoString(buf, "SELECT");
/* Add the DISTINCT clause if given */
if (query->distinctClause != NIL)
@@ -7550,6 +7636,26 @@ get_parameter(Param *param, deparse_context *context)
return;
}
+ /*
+ * If it's an external parameter, see if the outermost namespace provides
+ * function argument names.
+ */
+ if (param->paramkind == PARAM_EXTERN)
+ {
+ dpns = lfirst(list_tail(context->namespaces));
+ if (dpns->argnames)
+ {
+ char *argname = dpns->argnames[param->paramid - 1];
+
+ if (argname)
+ {
+ // TODO: qualify with function name if necessary
+ appendStringInfo(context->buf, "%s", quote_identifier(argname));
+ return;
+ }
+ }
+ }
+
/*
* Not PARAM_EXEC, or couldn't find referent: just print $N.
*/
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 784bceaec3..73df31e032 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11812,6 +11812,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
char *proretset;
char *prosrc;
char *probin;
+ char *prosqlbody;
char *funcargs;
char *funciargs;
char *funcresult;
@@ -11858,7 +11859,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"provolatile,\n"
"proisstrict,\n"
"prosecdef,\n"
- "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname,\n");
+ "lanname,\n");
if (fout->remoteVersion >= 80300)
appendPQExpBuffer(query,
@@ -11878,9 +11879,9 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
* pg_get_function_result instead of examining proallargtypes etc.
*/
appendPQExpBuffer(query,
- "pg_catalog.pg_get_function_arguments(oid) AS funcargs,\n"
- "pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs,\n"
- "pg_catalog.pg_get_function_result(oid) AS funcresult,\n");
+ "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n"
+ "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n"
+ "pg_catalog.pg_get_function_result(p.oid) AS funcresult,\n");
}
else if (fout->remoteVersion >= 80100)
appendPQExpBuffer(query,
@@ -11923,21 +11924,39 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
if (fout->remoteVersion >= 120000)
appendPQExpBuffer(query,
- "prosupport\n");
+ "prosupport,\n");
else
appendPQExpBuffer(query,
- "'-' AS prosupport\n");
+ "'-' AS prosupport,\n");
+
+ if (fout->remoteVersion >= 140000)
+ appendPQExpBuffer(query,
+ "CASE WHEN prosrc IS NULL AND lanname = 'sql' THEN pg_get_function_sqlbody(p.oid) END AS prosqlbody\n");
+ else
+ appendPQExpBuffer(query,
+ "NULL AS prosqlbody\n");
appendPQExpBuffer(query,
- "FROM pg_catalog.pg_proc "
- "WHERE oid = '%u'::pg_catalog.oid",
+ "FROM pg_catalog.pg_proc p, pg_catalog.pg_language l\n"
+ "WHERE p.oid = '%u'::pg_catalog.oid "
+ "AND l.oid = p.prolang",
finfo->dobj.catId.oid);
res = ExecuteSqlQueryForSingleRow(fout, query->data);
proretset = PQgetvalue(res, 0, PQfnumber(res, "proretset"));
- prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
- probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ if (PQgetisnull(res, 0, PQfnumber(res, "prosqlbody")))
+ {
+ prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
+ probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ prosqlbody = NULL;
+ }
+ else
+ {
+ prosrc = NULL;
+ probin = NULL;
+ prosqlbody = PQgetvalue(res, 0, PQfnumber(res, "prosqlbody"));
+ }
if (fout->remoteVersion >= 80400)
{
funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
@@ -11974,7 +11993,11 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
* versions would set it to "-". There are no known cases in which prosrc
* is unused, so the tests below for "-" are probably useless.
*/
- if (probin[0] != '\0' && strcmp(probin, "-") != 0)
+ if (prosqlbody)
+ {
+ appendPQExpBufferStr(asPart, prosqlbody);
+ }
+ else if (probin[0] != '\0' && strcmp(probin, "-") != 0)
{
appendPQExpBufferStr(asPart, "AS ");
appendStringLiteralAH(asPart, probin, fout);
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 08dffde1ba..ee34463e67 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -645,10 +645,11 @@ other .
";" {
ECHO;
- if (cur_state->paren_depth == 0)
+ if (cur_state->paren_depth == 0 && cur_state->begin_depth == 0)
{
/* Terminate lexing temporarily */
cur_state->start_state = YY_START;
+ cur_state->identifier_count = 0;
return LEXRES_SEMI;
}
}
@@ -661,6 +662,8 @@ other .
"\\"[;:] {
/* Force a semi-colon or colon into the query buffer */
psqlscan_emit(cur_state, yytext + 1, 1);
+ if (yytext[1] == ';')
+ cur_state->identifier_count = 0;
}
"\\" {
@@ -867,6 +870,17 @@ other .
{identifier} {
+ cur_state->identifier_count++;
+ if (pg_strcasecmp(yytext, "begin") == 0)
+ {
+ if (cur_state->identifier_count > 1)
+ cur_state->begin_depth++;
+ }
+ else if (pg_strcasecmp(yytext, "end") == 0)
+ {
+ if (cur_state->begin_depth > 0)
+ cur_state->begin_depth--;
+ }
ECHO;
}
@@ -1054,6 +1068,11 @@ psql_scan(PsqlScanState state,
result = PSCAN_INCOMPLETE;
*prompt = PROMPT_PAREN;
}
+ if (state->begin_depth > 0)
+ {
+ result = PSCAN_INCOMPLETE;
+ *prompt = PROMPT_CONTINUE;
+ }
else if (query_buf->len > 0)
{
result = PSCAN_EOL;
@@ -1170,6 +1189,8 @@ psql_scan_reset(PsqlScanState state)
if (state->dolqstart)
free(state->dolqstart);
state->dolqstart = NULL;
+ state->identifier_count = 0;
+ state->begin_depth = 0;
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 687509ba92..59f59801ba 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3685,6 +3685,10 @@
proname => 'pg_get_function_arg_default', provolatile => 's',
prorettype => 'text', proargtypes => 'oid int4',
prosrc => 'pg_get_function_arg_default' },
+{ oid => '9704', descr => 'function SQL body',
+ proname => 'pg_get_function_sqlbody', provolatile => 's',
+ prorettype => 'text', proargtypes => 'oid',
+ prosrc => 'pg_get_function_sqlbody' },
{ oid => '1686', descr => 'list of SQL keywords',
proname => 'pg_get_keywords', procost => '10', prorows => '400',
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index b50fa25dbd..48b53846f4 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -112,7 +112,7 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
Oid protrftypes[1] BKI_DEFAULT(_null_);
/* procedure source text */
- text prosrc BKI_FORCE_NOT_NULL;
+ text prosrc;
/* secondary procedure info (can be NULL) */
text probin BKI_DEFAULT(_null_);
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 3129b684f6..cedc772bed 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -68,9 +68,11 @@ extern void interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType);
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index cb13428a5a..5d547c66ed 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -20,6 +20,21 @@
/* This struct is known only within executor/functions.c */
typedef struct SQLFunctionParseInfo *SQLFunctionParseInfoPtr;
+/*
+ * Data structure needed by the parser callback hooks to resolve parameter
+ * references during parsing of a SQL function's body. This is separate from
+ * SQLFunctionCache since we sometimes do parsing separately from execution.
+ */
+typedef struct SQLFunctionParseInfo
+{
+ char *fname; /* function's name */
+ int nargs; /* number of input arguments */
+ Oid *argtypes; /* resolved types of input arguments */
+ char **argnames; /* names of input arguments; NULL if none */
+ /* Note that argnames[i] can be NULL, if some args are unnamed */
+ Oid collation; /* function's input collation, if known */
+} SQLFunctionParseInfo;
+
extern Datum fmgr_sql(PG_FUNCTION_ARGS);
extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTuple,
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 311f80394a..fb8f58aa29 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -114,6 +114,8 @@ typedef struct PsqlScanStateData
int paren_depth; /* depth of nesting in parentheses */
int xcdepth; /* depth of nesting in slash-star comments */
char *dolqstart; /* current $foo$ quote start string */
+ int identifier_count; /* identifiers since start of statement */
+ int begin_depth; /* depth of begin/end routine body blocks */
/*
* Callback functions provided by the program making use of the lexer,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..ab8b745665 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -316,6 +316,7 @@ typedef enum NodeTag
T_DeleteStmt,
T_UpdateStmt,
T_SelectStmt,
+ T_ReturnStmt,
T_AlterTableStmt,
T_AlterTableCmd,
T_AlterDomainStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e83329fd6d..1876c179f7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -132,6 +132,8 @@ typedef struct Query
bool hasForUpdate; /* FOR [KEY] UPDATE/SHARE was specified */
bool hasRowSecurity; /* rewriter has applied some RLS policy */
+ bool isReturn; /* is a RETURN statement */
+
List *cteList; /* WITH list (of CommonTableExpr's) */
List *rtable; /* list of range table entries */
@@ -1670,6 +1672,16 @@ typedef struct SetOperationStmt
} SetOperationStmt;
+/*
+ * RETURN statement (inside SQL function body)
+ */
+typedef struct ReturnStmt
+{
+ NodeTag type;
+ Node *returnval;
+} ReturnStmt;
+
+
/*****************************************************************************
* Other Statements (no optimizations required)
*
@@ -2841,6 +2853,7 @@ typedef struct CreateFunctionStmt
List *parameters; /* a list of FunctionParameter */
TypeName *returnType; /* the return type */
List *options; /* a list of DefElem */
+ List *sql_body;
} CreateFunctionStmt;
typedef enum FunctionParameterMode
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 08f22ce211..c972c6f2ba 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -48,6 +48,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("atomic", ATOMIC, UNRESERVED_KEYWORD)
PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
@@ -344,6 +345,7 @@ PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD)
PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD)
PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD)
PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD)
+PG_KEYWORD("return", RETURN, UNRESERVED_KEYWORD)
PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD)
PG_KEYWORD("returns", RETURNS, UNRESERVED_KEYWORD)
PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD)
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index bd30607b07..e626c8eafd 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -43,6 +43,7 @@ typedef enum
extern PGDLLIMPORT int log_statement;
extern List *pg_parse_query(const char *query_string);
+extern List *pg_rewrite_query(Query *query);
extern List *pg_analyze_and_rewrite(RawStmt *parsetree,
const char *query_string,
Oid *paramTypes, int numParams,
diff --git a/src/interfaces/ecpg/preproc/ecpg.addons b/src/interfaces/ecpg/preproc/ecpg.addons
index 300381eaad..0441561c52 100644
--- a/src/interfaces/ecpg/preproc/ecpg.addons
+++ b/src/interfaces/ecpg/preproc/ecpg.addons
@@ -87,6 +87,12 @@ ECPG: stmtTransactionStmt block
whenever_action(2);
free($1);
}
+ECPG: toplevel_stmtTransactionStmtLegacy block
+ {
+ fprintf(base_yyout, "{ ECPGtrans(__LINE__, %s, \"%s\");", connection ? connection : "NULL", $1);
+ whenever_action(2);
+ free($1);
+ }
ECPG: stmtViewStmt rule
| ECPGAllocateDescr
{
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer
index 6ccc8ab916..7f4be655f9 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -4,8 +4,8 @@ statements: /*EMPTY*/
| statements statement
;
-statement: ecpgstart at stmt ';' { connection = NULL; }
- | ecpgstart stmt ';'
+statement: ecpgstart at toplevel_stmt ';' { connection = NULL; }
+ | ecpgstart toplevel_stmt ';'
| ecpgstart ECPGVarDeclaration
{
fprintf(base_yyout, "%s", $2);
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index ce508ae1dc..b62c6393bb 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -255,6 +255,135 @@ SELECT pg_get_functiondef('functest_F_2'::regproc);
(1 row)
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+ERROR: SQL function with unquoted function body cannot have polymorphic arguments
+SELECT functest_S_1('abcd', '2020-01-01');
+ functest_s_1
+--------------
+ t
+(1 row)
+
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+ functest_s_2
+--------------
+ 1
+(1 row)
+
+SELECT functest_S_3();
+ functest_s_3
+--------------
+ f
+(1 row)
+
+SELECT functest_S_10('abcd', '2020-01-01');
+ functest_s_10
+---------------
+ t
+(1 row)
+
+SELECT functest_S_13();
+ functest_s_13
+---------------
+ f
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_1(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN ((a = 'abcd'::text) AND (b > '01-01-2001'::date)) +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_2(a text[])+
+ RETURNS integer +
+ LANGUAGE sql +
+ RETURN ((a)[1])::integer +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+ pg_get_functiondef
+----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN false +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+ pg_get_functiondef
+-------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_10(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT ((a = 'abcd'::text) AND (b > '01-01-2001'::date)) +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_13()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT 1; +
+ SELECT false AS bool +
+ END +
+
+(1 row)
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+SELECT functest_S_14();
+ functest_s_14
+---------------
+ 2
+(1 row)
+
+DROP TABLE functest3 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view functestv3
+drop cascades to function functest_s_14()
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
RETURNS int
@@ -284,6 +413,20 @@ SELECT routine_name, ordinal_position, parameter_name, parameter_default
(7 rows)
DROP FUNCTION functest_IS_1(int, int, text), functest_IS_2(int), functest_IS_3(int);
+CREATE TABLE functest1 (a int, b int);
+CREATE SEQUENCE functest2;
+CREATE FUNCTION functest_IS_4()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest1);
+CREATE FUNCTION functest_IS_5()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest2');
+DROP TABLE functest1 CASCADE;
+NOTICE: drop cascades to function functest_is_4()
+DROP SEQUENCE functest2 CASCADE;
+NOTICE: drop cascades to function functest_is_5()
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
@@ -302,6 +445,49 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
ERROR: cannot change routine kind
DETAIL: "functest1" is a function.
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+SELECT * FROM functest_sri1();
+ functest_sri1
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+SELECT * FROM functest_sri2();
+ functest_sri2
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
$$ SELECT a + 1 $$;
@@ -360,7 +546,7 @@ SELECT * FROM voidtest5(3);
-- Cleanup
DROP SCHEMA temp_func_test CASCADE;
-NOTICE: drop cascades to 21 other objects
+NOTICE: drop cascades to 28 other objects
DETAIL: drop cascades to function functest_a_1(text,date)
drop cascades to function functest_a_2(text[])
drop cascades to function functest_a_3()
@@ -376,7 +562,14 @@ drop cascades to function functest_f_1(integer)
drop cascades to function functest_f_2(integer)
drop cascades to function functest_f_3(integer)
drop cascades to function functest_f_4(integer)
+drop cascades to function functest_s_1(text,date)
+drop cascades to function functest_s_2(text[])
+drop cascades to function functest_s_3()
+drop cascades to function functest_s_10(text,date)
+drop cascades to function functest_s_13()
drop cascades to function functest_b_2(bigint)
+drop cascades to function functest_sri1()
+drop cascades to function functest_sri2()
drop cascades to function voidtest1(integer)
drop cascades to function voidtest2(integer,integer)
drop cascades to function voidtest3(integer)
diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out
index 211a42cefa..c927a2894a 100644
--- a/src/test/regress/expected/create_procedure.out
+++ b/src/test/regress/expected/create_procedure.out
@@ -65,6 +65,41 @@ SELECT * FROM cp_test ORDER BY b COLLATE "C";
1 | xyzzy
(3 rows)
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+\df ptest1s
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+---------+------------------+---------------------+------
+ public | ptest1s | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest1s'::regproc);
+ pg_get_functiondef
+----------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest1s(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ INSERT INTO cp_test (a, b) +
+ VALUES (1, x) +
+ END +
+
+(1 row)
+
+CALL ptest1s('b');
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+ a | b
+---+-------
+ 1 | 0
+ 1 | a
+ 1 | b
+ 1 | xyzzy
+(4 rows)
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -146,6 +181,28 @@ AS $$
SELECT a = b;
$$;
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+\df ptest8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+--------+------------------+---------------------+------
+ public | ptest8 | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest8'::regproc);
+ pg_get_functiondef
+---------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest8(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ END +
+
+(1 row)
+
+CALL ptest8('');
-- various error cases
CALL version(); -- error: not a procedure
ERROR: version() is not a procedure
@@ -204,6 +261,7 @@ ALTER ROUTINE ptest1a RENAME TO ptest1;
DROP ROUTINE cp_testfunc1(int);
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
DROP USER regress_cp_user1;
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
index bd108a918f..6a3c56bc0b 100644
--- a/src/test/regress/sql/create_function_3.sql
+++ b/src/test/regress/sql/create_function_3.sql
@@ -153,6 +153,60 @@ CREATE FUNCTION functest_F_4(int) RETURNS bool LANGUAGE 'sql'
SELECT pg_get_functiondef('functest_F_2'::regproc);
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+
+SELECT functest_S_1('abcd', '2020-01-01');
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+SELECT functest_S_3();
+
+SELECT functest_S_10('abcd', '2020-01-01');
+SELECT functest_S_13();
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+
+SELECT functest_S_14();
+
+DROP TABLE functest3 CASCADE;
+
+
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
@@ -177,6 +231,23 @@ CREATE FUNCTION functest_IS_3(a int default 1, out b int)
DROP FUNCTION functest_IS_1(int, int, text), functest_IS_2(int), functest_IS_3(int);
+CREATE TABLE functest1 (a int, b int);
+CREATE SEQUENCE functest2;
+
+CREATE FUNCTION functest_IS_4()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest1);
+
+CREATE FUNCTION functest_IS_5()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest2');
+
+DROP TABLE functest1 CASCADE;
+DROP SEQUENCE functest2 CASCADE;
+
+
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
@@ -194,6 +265,29 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+
+SELECT * FROM functest_sri1();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+
+SELECT * FROM functest_sri2();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
diff --git a/src/test/regress/sql/create_procedure.sql b/src/test/regress/sql/create_procedure.sql
index 89b96d580f..acbe92fb9a 100644
--- a/src/test/regress/sql/create_procedure.sql
+++ b/src/test/regress/sql/create_procedure.sql
@@ -28,6 +28,21 @@ CREATE PROCEDURE ptest1(x text)
SELECT * FROM cp_test ORDER BY b COLLATE "C";
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+
+\df ptest1s
+SELECT pg_get_functiondef('ptest1s'::regproc);
+
+CALL ptest1s('b');
+
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -112,6 +127,16 @@ CREATE PROCEDURE ptest7(a text, b text)
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+
+\df ptest8
+SELECT pg_get_functiondef('ptest8'::regproc);
+CALL ptest8('');
+
+
-- various error cases
CALL version(); -- error: not a procedure
@@ -159,6 +184,7 @@ CREATE USER regress_cp_user1;
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
base-commit: 58b5ae9d62bdd44879c6f9a570849903ccd6cd66
--
2.28.0
On Mon, Sep 07, 2020 at 08:00:08AM +0200, Peter Eisentraut wrote:
Some conflicts have emerged, so here is an updated patch.
I have implemented/fixed the inlining of set-returning functions written in
the new style, which was previously marked TODO in the patch.
The CF bot is telling that this patch fails to apply. Could you send
a rebase?
--
Michael
On 2020-09-29 07:42, Michael Paquier wrote:
On Mon, Sep 07, 2020 at 08:00:08AM +0200, Peter Eisentraut wrote:
Some conflicts have emerged, so here is an updated patch.
I have implemented/fixed the inlining of set-returning functions written in
the new style, which was previously marked TODO in the patch.The CF bot is telling that this patch fails to apply. Could you send
a rebase?
Here is a rebase, no functionality changes.
As indicated earlier, I'll also send some sub-patches as separate
submissions.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
v4-0001-SQL-standard-function-body.patchtext/plain; charset=UTF-8; name=v4-0001-SQL-standard-function-body.patch; x-mac-creator=0; x-mac-type=0Download
From 14be6cb14759636b407f89cee50fa2896ee98f5d Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Sat, 10 Oct 2020 10:36:28 +0200
Subject: [PATCH v4] SQL-standard function body
This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.
Instead of the PostgreSQL-specific AS $$ string literal $$ syntax,
this allows writing out the SQL statements making up the body
unquoted, either as a single statement:
CREATE FUNCTION add(a integer, b integer) RETURNS integer
LANGUAGE SQL
RETURN a + b;
or as a block
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
BEGIN ATOMIC
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
END;
The function body is parsed at function definition time and stored as
expression nodes in probin. So at run time, no further parsing is
required.
However, this form does not support polymorphic arguments, because
there is no more parse analysis done at call time.
Dependencies between the function and the objects it uses are fully
tracked.
A new RETURN statement is introduced. This can only be used inside
function bodies. Internally, it is treated much like a SELECT
statement.
psql needs some new intelligence to keep track of function body
boundaries so that it doesn't send off statements when it sees
semicolons that are inside a function body.
Also, per SQL standard, LANGUAGE SQL is the default, so it does not
need to be specified anymore.
Discussion: https://www.postgresql.org/message-id/flat/1c11f1eb-f00c-43b7-799d-2d44132c02d7@2ndquadrant.com
---
doc/src/sgml/ref/create_function.sgml | 126 +++++++++--
doc/src/sgml/ref/create_procedure.sgml | 62 +++++-
src/backend/catalog/pg_proc.c | 145 +++++++------
src/backend/commands/aggregatecmds.c | 2 +
src/backend/commands/functioncmds.c | 98 +++++++--
src/backend/executor/functions.c | 79 ++++---
src/backend/nodes/copyfuncs.c | 15 ++
src/backend/nodes/equalfuncs.c | 13 ++
src/backend/nodes/outfuncs.c | 12 ++
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/util/clauses.c | 100 ++++++---
src/backend/parser/analyze.c | 35 ++++
src/backend/parser/gram.y | 128 +++++++++---
src/backend/tcop/postgres.c | 3 +-
src/backend/utils/adt/ruleutils.c | 110 +++++++++-
src/bin/pg_dump/pg_dump.c | 45 +++-
src/fe_utils/psqlscan.l | 23 ++-
src/include/catalog/pg_proc.dat | 4 +
src/include/catalog/pg_proc.h | 2 +-
src/include/commands/defrem.h | 2 +
src/include/executor/functions.h | 15 ++
src/include/fe_utils/psqlscan_int.h | 2 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 13 ++
src/include/parser/kwlist.h | 2 +
src/include/tcop/tcopprot.h | 1 +
src/interfaces/ecpg/preproc/ecpg.addons | 6 +
src/interfaces/ecpg/preproc/ecpg.trailer | 4 +-
.../regress/expected/create_function_3.out | 195 +++++++++++++++++-
.../regress/expected/create_procedure.out | 58 ++++++
src/test/regress/sql/create_function_3.sql | 94 +++++++++
src/test/regress/sql/create_procedure.sql | 26 +++
32 files changed, 1209 insertions(+), 213 deletions(-)
diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index 3c1eaea651..1b5b9420db 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -38,6 +38,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -257,8 +258,9 @@ <title>Parameters</title>
The name of the language that the function is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -577,6 +579,44 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> function. This can
+ either be a single statement
+<programlisting>
+RETURN <replaceable>expression</replaceable>
+</programlisting>
+ or a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the function body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at function definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at function definition time. This form tracks dependencies between the
+ function and objects used in the function body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling functions. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
<para>
@@ -669,6 +709,15 @@ <title>Examples</title>
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
+</programlisting>
+ The same function written in a more SQL-conforming style, using argument
+ names and an unquoted body:
+<programlisting>
+CREATE FUNCTION add(a integer, b integer) RETURNS integer
+ LANGUAGE SQL
+ IMMUTABLE
+ RETURNS NULL ON NULL INPUT
+ RETURN a + b;
</programlisting>
</para>
@@ -799,23 +848,74 @@ <title>Writing <literal>SECURITY DEFINER</literal> Functions Safely</title>
<title>Compatibility</title>
<para>
- A <command>CREATE FUNCTION</command> command is defined in the SQL standard.
- The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. The attributes are not portable, neither are the
- different available languages.
+ A <command>CREATE FUNCTION</command> command is defined in the SQL
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. Conversely, the SQL
+ standard specifies a number of optional features that are not implemented
+ in <productname>PostgreSQL</productname>.
</para>
<para>
- For compatibility with some other database systems,
- <replaceable class="parameter">argmode</replaceable> can be written
- either before or after <replaceable class="parameter">argname</replaceable>.
- But only the first way is standard-compliant.
+ The following are important compatibility issues:
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>OR REPLACE</literal> is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For compatibility with some other database systems, <replaceable
+ class="parameter">argmode</replaceable> can be written either before or
+ after <replaceable class="parameter">argname</replaceable>. But only
+ the first way is standard-compliant.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For parameter defaults, the SQL standard specifies only the syntax with
+ the <literal>DEFAULT</literal> key word. The syntax with
+ <literal>=</literal> is used in T-SQL and Firebird.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The <literal>SETOF</literal> modifier is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Only <literal>SQL</literal> is standardized as a language.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ All other attributes except <literal>CALLED ON NULL INPUT</literal> and
+ <literal>RETURNS NULL ON NULL INPUT</literal> are not standardized.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For the body of <literal>LANGUAGE SQL</literal> functions, the SQL
+ standard only specifies the <replaceable>sql_body</replaceable> form.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
<para>
- For parameter defaults, the SQL standard specifies only the syntax with
- the <literal>DEFAULT</literal> key word. The syntax
- with <literal>=</literal> is used in T-SQL and Firebird.
+ Simple <literal>LANGUAGE SQL</literal> functions can be written in a way
+ that is both standard-conforming and portable to other implementations.
+ More complex functions using advanced features, optimization attributes, or
+ other languages will necessarily be specific to PostgreSQL in a significant
+ way.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_procedure.sgml b/doc/src/sgml/ref/create_procedure.sgml
index e258eca5ce..ecdeac1629 100644
--- a/doc/src/sgml/ref/create_procedure.sgml
+++ b/doc/src/sgml/ref/create_procedure.sgml
@@ -29,6 +29,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -162,8 +163,9 @@ <title>Parameters</title>
The name of the language that the procedure is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -299,6 +301,41 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> procedure. This should
+ be a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the procedure body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at procedure definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at procedure definition time. This form tracks dependencies between the
+ procedure and objects used in the procedure body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling procedures. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
@@ -318,6 +355,7 @@ <title>Notes</title>
<refsect1 id="sql-createprocedure-examples">
<title>Examples</title>
+ <para>
<programlisting>
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
@@ -325,9 +363,21 @@ <title>Examples</title>
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
$$;
-
+</programlisting>
+ or
+<programlisting>
+CREATE PROCEDURE insert_data(a integer, b integer)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO tbl VALUES (a);
+ INSERT INTO tbl VALUES (b);
+END;
+</programlisting>
+ and call like this:
+<programlisting>
CALL insert_data(1, 2);
</programlisting>
+ </para>
</refsect1>
<refsect1 id="sql-createprocedure-compat">
@@ -335,9 +385,9 @@ <title>Compatibility</title>
<para>
A <command>CREATE PROCEDURE</command> command is defined in the SQL
- standard. The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. For details see
- also <xref linkend="sql-createfunction"/>.
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. For details see also
+ <xref linkend="sql-createfunction"/>.
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index f7dab9925b..ed690c12e6 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -32,6 +32,7 @@
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_type.h"
#include "tcop/pquery.h"
@@ -119,8 +120,6 @@ ProcedureCreate(const char *procedureName,
/*
* sanity checks
*/
- Assert(PointerIsValid(prosrc));
-
parameterCount = parameterTypes->dim1;
if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
ereport(ERROR,
@@ -334,7 +333,10 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_protrftypes - 1] = trftypes;
else
nulls[Anum_pg_proc_protrftypes - 1] = true;
- values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ if (prosrc)
+ values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ else
+ nulls[Anum_pg_proc_prosrc - 1] = true;
if (probin)
values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin);
else
@@ -638,6 +640,10 @@ ProcedureCreate(const char *procedureName,
record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
free_object_addresses(addrs);
+ /* dependency on SQL routine body */
+ if (languageObjectId == SQLlanguageId && probin)
+ recordDependencyOnExpr(&myself, stringToNode(probin), NIL, DEPENDENCY_NORMAL);
+
/* dependency on parameter default expressions */
if (parameterDefaults)
recordDependencyOnExpr(&myself, (Node *) parameterDefaults,
@@ -811,14 +817,6 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
Oid funcoid = PG_GETARG_OID(0);
HeapTuple tuple;
Form_pg_proc proc;
- List *raw_parsetree_list;
- List *querytree_list;
- ListCell *lc;
- bool isnull;
- Datum tmp;
- char *prosrc;
- parse_error_callback_arg callback_arg;
- ErrorContextCallback sqlerrcontext;
bool haspolyarg;
int i;
@@ -861,72 +859,93 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
/* Postpone body checks if !check_function_bodies */
if (check_function_bodies)
{
+ Datum tmp;
+ bool isnull;
+ List *querytree_list = NIL;
+
tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
if (isnull)
- elog(ERROR, "null prosrc");
-
- prosrc = TextDatumGetCString(tmp);
+ {
+ char *probin;
- /*
- * Setup error traceback support for ereport().
- */
- callback_arg.proname = NameStr(proc->proname);
- callback_arg.prosrc = prosrc;
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_probin, &isnull);
+ if (isnull)
+ elog(ERROR, "null probin and prosrc");
- sqlerrcontext.callback = sql_function_parse_error_callback;
- sqlerrcontext.arg = (void *) &callback_arg;
- sqlerrcontext.previous = error_context_stack;
- error_context_stack = &sqlerrcontext;
+ probin = TextDatumGetCString(tmp);
+ querytree_list = castNode(List, stringToNode(probin));
+ }
+ else
+ {
+ char *prosrc;
+ List *raw_parsetree_list;
+ parse_error_callback_arg callback_arg;
+ ErrorContextCallback sqlerrcontext;
- /*
- * We can't do full prechecking of the function definition if there
- * are any polymorphic input types, because actual datatypes of
- * expression results will be unresolvable. The check will be done at
- * runtime instead.
- *
- * We can run the text through the raw parser though; this will at
- * least catch silly syntactic errors.
- */
- raw_parsetree_list = pg_parse_query(prosrc);
+ prosrc = TextDatumGetCString(tmp);
- if (!haspolyarg)
- {
/*
- * OK to do full precheck: analyze and rewrite the queries, then
- * verify the result type.
+ * Setup error traceback support for ereport().
*/
- SQLFunctionParseInfoPtr pinfo;
- Oid rettype;
- TupleDesc rettupdesc;
+ callback_arg.proname = NameStr(proc->proname);
+ callback_arg.prosrc = prosrc;
- /* But first, set up parameter information */
- pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
+ sqlerrcontext.callback = sql_function_parse_error_callback;
+ sqlerrcontext.arg = (void *) &callback_arg;
+ sqlerrcontext.previous = error_context_stack;
+ error_context_stack = &sqlerrcontext;
- querytree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ /*
+ * We can't do full prechecking of the function definition if there
+ * are any polymorphic input types, because actual datatypes of
+ * expression results will be unresolvable. The check will be done at
+ * runtime instead.
+ *
+ * We can run the text through the raw parser though; this will at
+ * least catch silly syntactic errors.
+ */
+ raw_parsetree_list = pg_parse_query(prosrc);
+
+ if (!haspolyarg)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *querytree_sublist;
-
- querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
- prosrc,
- (ParserSetupHook) sql_fn_parser_setup,
- pinfo,
- NULL);
- querytree_list = list_concat(querytree_list,
- querytree_sublist);
+ /*
+ * OK to do full precheck: analyze and rewrite the queries, then
+ * verify the result type.
+ */
+ ListCell *lc;
+ SQLFunctionParseInfoPtr pinfo;
+ Oid rettype;
+ TupleDesc rettupdesc;
+
+ /* But first, set up parameter information */
+ pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
+
+ querytree_list = NIL;
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *querytree_sublist;
+
+ querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ prosrc,
+ (ParserSetupHook) sql_fn_parser_setup,
+ pinfo,
+ NULL);
+ querytree_list = list_concat(querytree_list,
+ querytree_sublist);
+ }
+
+ check_sql_fn_statements(querytree_list);
+
+ (void) get_func_result_type(funcoid, &rettype, &rettupdesc);
+
+ (void) check_sql_fn_retval(querytree_list,
+ rettype, rettupdesc,
+ false, NULL);
}
- check_sql_fn_statements(querytree_list);
-
- (void) get_func_result_type(funcoid, &rettype, &rettupdesc);
-
- (void) check_sql_fn_retval(querytree_list,
- rettype, rettupdesc,
- false, NULL);
+ error_context_stack = sqlerrcontext.previous;
}
-
- error_context_stack = sqlerrcontext.previous;
}
ReleaseSysCache(tuple);
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 6bf54e64f8..26b3fa27de 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -313,9 +313,11 @@ DefineAggregate(ParseState *pstate,
InvalidOid,
OBJECT_AGGREGATE,
¶meterTypes,
+ NULL,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ NULL,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index c3ce480c8f..b6126dc1d7 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -53,9 +53,11 @@
#include "commands/proclang.h"
#include "executor/execdesc.h"
#include "executor/executor.h"
+#include "executor/functions.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
@@ -186,9 +188,11 @@ interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType)
@@ -283,7 +287,11 @@ interpret_function_parameter_list(ParseState *pstate,
/* handle input parameters */
if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
+ {
isinput = true;
+ if (parameterTypes_list)
+ *parameterTypes_list = lappend_oid(*parameterTypes_list, toid);
+ }
/* handle signature parameters */
if (fp->mode == FUNC_PARAM_IN || fp->mode == FUNC_PARAM_INOUT ||
@@ -372,6 +380,9 @@ interpret_function_parameter_list(ParseState *pstate,
have_names = true;
}
+ if (inParameterNames_list)
+ *inParameterNames_list = lappend(*inParameterNames_list, makeString(fp->name ? fp->name : pstrdup("")));
+
if (fp->defexpr)
{
Node *def;
@@ -786,28 +797,10 @@ compute_function_attributes(ParseState *pstate,
defel->defname);
}
- /* process required items */
if (as_item)
*as = (List *) as_item->arg;
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no function body specified")));
- *as = NIL; /* keep compiler quiet */
- }
-
if (language_item)
*language = strVal(language_item->arg);
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no language specified")));
- *language = NULL; /* keep compiler quiet */
- }
-
- /* process optional items */
if (transform_item)
*transform = transform_item->arg;
if (windowfunc_item)
@@ -856,10 +849,19 @@ compute_function_attributes(ParseState *pstate,
*/
static void
interpret_AS_clause(Oid languageOid, const char *languageName,
- char *funcname, List *as,
+ char *funcname, List *as, List *sql_body,
+ List *parameterTypes, List *inParameterNames,
char **prosrc_str_p, char **probin_str_p)
{
- Assert(as != NIL);
+ if (sql_body && as)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("duplicate function body specified")));
+
+ if (sql_body && languageOid != SQLlanguageId)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("inline SQL function body only valid for language SQL")));
if (languageOid == ClanguageId)
{
@@ -881,6 +883,53 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
*prosrc_str_p = funcname;
}
}
+ else if (sql_body)
+ {
+ List *transformed_stmts = NIL;
+ ListCell *lc;
+ SQLFunctionParseInfoPtr pinfo;
+
+ pinfo = (SQLFunctionParseInfoPtr) palloc0(sizeof(SQLFunctionParseInfo));
+
+ pinfo->fname = funcname;
+ pinfo->nargs = list_length(parameterTypes);
+ pinfo->argtypes = (Oid *) palloc(pinfo->nargs * sizeof(Oid));
+ pinfo->argnames = (char **) palloc(pinfo->nargs * sizeof(char *));
+ for (int i = 0; i < list_length(parameterTypes); i++)
+ {
+ char *s = strVal(list_nth(inParameterNames, i));
+
+ pinfo->argtypes[i] = list_nth_oid(parameterTypes, i);
+ if (IsPolymorphicType(pinfo->argtypes[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("SQL function with unquoted function body cannot have polymorphic arguments")));
+
+ if (s[0] != '\0')
+ pinfo->argnames[i] = s;
+ else
+ pinfo->argnames[i] = NULL;
+ }
+
+ foreach(lc, sql_body)
+ {
+ Node *stmt = lfirst(lc);
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ /* ignore NULL statement; see gram.y */
+ if (!stmt)
+ continue;
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, stmt);
+ transformed_stmts = lappend(transformed_stmts, q);
+ free_parsestate(pstate);
+ }
+
+ *probin_str_p = nodeToString(transformed_stmts);
+ *prosrc_str_p = NULL;
+ }
else
{
/* Everything else wants the given string in prosrc. */
@@ -929,9 +978,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
Oid namespaceId;
AclResult aclresult;
oidvector *parameterTypes;
+ List *parameterTypes_list = NIL;
ArrayType *allParameterTypes;
ArrayType *parameterModes;
ArrayType *parameterNames;
+ List *inParameterNames_list = NIL;
List *parameterDefaults;
Oid variadicArgType;
List *trftypes_list = NIL;
@@ -962,6 +1013,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
get_namespace_name(namespaceId));
/* Set default attributes */
+ as_clause = NULL;
+ language = "sql";
isWindowFunc = false;
isStrict = false;
security = false;
@@ -1053,9 +1106,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageOid,
stmt->is_procedure ? OBJECT_PROCEDURE : OBJECT_FUNCTION,
¶meterTypes,
+ ¶meterTypes_list,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ &inParameterNames_list,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
@@ -1112,7 +1167,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
trftypes = NULL;
}
- interpret_AS_clause(languageOid, language, funcname, as_clause,
+ interpret_AS_clause(languageOid, language, funcname, as_clause, stmt->sql_body,
+ parameterTypes_list, inParameterNames_list,
&prosrc_str, &probin_str);
/*
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index bf00a9c1e8..a2cd4992d6 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -26,6 +26,7 @@
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_func.h"
+#include "rewrite/rewriteHandler.h"
#include "storage/proc.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
@@ -128,21 +129,6 @@ typedef struct
typedef SQLFunctionCache *SQLFunctionCachePtr;
-/*
- * Data structure needed by the parser callback hooks to resolve parameter
- * references during parsing of a SQL function's body. This is separate from
- * SQLFunctionCache since we sometimes do parsing separately from execution.
- */
-typedef struct SQLFunctionParseInfo
-{
- char *fname; /* function's name */
- int nargs; /* number of input arguments */
- Oid *argtypes; /* resolved types of input arguments */
- char **argnames; /* names of input arguments; NULL if none */
- /* Note that argnames[i] can be NULL, if some args are unnamed */
- Oid collation; /* function's input collation, if known */
-} SQLFunctionParseInfo;
-
/* non-export function prototypes */
static Node *sql_fn_param_ref(ParseState *pstate, ParamRef *pref);
@@ -607,7 +593,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
HeapTuple procedureTuple;
Form_pg_proc procedureStruct;
SQLFunctionCachePtr fcache;
- List *raw_parsetree_list;
List *queryTree_list;
List *flat_query_list;
List *resulttlist;
@@ -683,9 +668,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
procedureTuple,
Anum_pg_proc_prosrc,
&isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", foid);
- fcache->src = TextDatumGetCString(tmp);
/*
* Parse and rewrite the queries in the function text. Use sublists to
@@ -702,22 +684,55 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
* but we'll not worry about it until the module is rewritten to use
* plancache.c.
*/
- raw_parsetree_list = pg_parse_query(fcache->src);
-
queryTree_list = NIL;
flat_query_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (isNull)
+ {
+ char *binstr;
+ List *stored_query_list;
+
+ tmp = SysCacheGetAttr(PROCOID,
+ procedureTuple,
+ Anum_pg_proc_probin,
+ &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and probin for function %u", foid);
+
+ binstr = TextDatumGetCString(tmp);
+ stored_query_list = stringToNode(binstr);
+
+ foreach(lc, stored_query_list)
+ {
+ Query *parsetree = lfirst_node(Query, lc);
+ List *queryTree_sublist;
+
+ AcquireRewriteLocks(parsetree, true, false);
+ queryTree_sublist = pg_rewrite_query(parsetree);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ flat_query_list = list_concat(flat_query_list, queryTree_sublist);
+ }
+ }
+ else
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *queryTree_sublist;
-
- queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
- fcache->src,
- (ParserSetupHook) sql_fn_parser_setup,
- fcache->pinfo,
- NULL);
- queryTree_list = lappend(queryTree_list, queryTree_sublist);
- flat_query_list = list_concat(flat_query_list, queryTree_sublist);
+ List *raw_parsetree_list;
+
+ fcache->src = TextDatumGetCString(tmp);
+
+ raw_parsetree_list = pg_parse_query(fcache->src);
+
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *queryTree_sublist;
+
+ queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ fcache->src,
+ (ParserSetupHook) sql_fn_parser_setup,
+ fcache->pinfo,
+ NULL);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ flat_query_list = list_concat(flat_query_list, queryTree_sublist);
+ }
}
check_sql_fn_statements(flat_query_list);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 0409a40b82..8dc9efcd93 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3069,6 +3069,7 @@ _copyQuery(const Query *from)
COPY_SCALAR_FIELD(hasModifyingCTE);
COPY_SCALAR_FIELD(hasForUpdate);
COPY_SCALAR_FIELD(hasRowSecurity);
+ COPY_SCALAR_FIELD(isReturn);
COPY_NODE_FIELD(cteList);
COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(jointree);
@@ -3197,6 +3198,16 @@ _copySetOperationStmt(const SetOperationStmt *from)
return newnode;
}
+static ReturnStmt *
+_copyReturnStmt(const ReturnStmt *from)
+{
+ ReturnStmt *newnode = makeNode(ReturnStmt);
+
+ COPY_NODE_FIELD(returnval);
+
+ return newnode;
+}
+
static AlterTableStmt *
_copyAlterTableStmt(const AlterTableStmt *from)
{
@@ -3573,6 +3584,7 @@ _copyCreateFunctionStmt(const CreateFunctionStmt *from)
COPY_NODE_FIELD(parameters);
COPY_NODE_FIELD(returnType);
COPY_NODE_FIELD(options);
+ COPY_NODE_FIELD(sql_body);
return newnode;
}
@@ -5225,6 +5237,9 @@ copyObjectImpl(const void *from)
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
+ case T_ReturnStmt:
+ retval = _copyReturnStmt(from);
+ break;
case T_AlterTableStmt:
retval = _copyAlterTableStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b987bf..0051b1a88b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -966,6 +966,7 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_SCALAR_FIELD(hasModifyingCTE);
COMPARE_SCALAR_FIELD(hasForUpdate);
COMPARE_SCALAR_FIELD(hasRowSecurity);
+ COMPARE_SCALAR_FIELD(isReturn);
COMPARE_NODE_FIELD(cteList);
COMPARE_NODE_FIELD(rtable);
COMPARE_NODE_FIELD(jointree);
@@ -1082,6 +1083,14 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
return true;
}
+static bool
+_equalReturnStmt(const ReturnStmt *a, const ReturnStmt *b)
+{
+ COMPARE_NODE_FIELD(returnval);
+
+ return true;
+}
+
static bool
_equalAlterTableStmt(const AlterTableStmt *a, const AlterTableStmt *b)
{
@@ -1394,6 +1403,7 @@ _equalCreateFunctionStmt(const CreateFunctionStmt *a, const CreateFunctionStmt *
COMPARE_NODE_FIELD(parameters);
COMPARE_NODE_FIELD(returnType);
COMPARE_NODE_FIELD(options);
+ COMPARE_NODE_FIELD(sql_body);
return true;
}
@@ -3277,6 +3287,9 @@ equal(const void *a, const void *b)
case T_SetOperationStmt:
retval = _equalSetOperationStmt(a, b);
break;
+ case T_ReturnStmt:
+ retval = _equalReturnStmt(a, b);
+ break;
case T_AlterTableStmt:
retval = _equalAlterTableStmt(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index f0386480ab..edfa975400 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2759,6 +2759,14 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
WRITE_NODE_FIELD(rarg);
}
+static void
+_outReturnStmt(StringInfo str, const ReturnStmt *node)
+{
+ WRITE_NODE_TYPE("RETURN");
+
+ WRITE_NODE_FIELD(returnval);
+}
+
static void
_outFuncCall(StringInfo str, const FuncCall *node)
{
@@ -2947,6 +2955,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_BOOL_FIELD(hasModifyingCTE);
WRITE_BOOL_FIELD(hasForUpdate);
WRITE_BOOL_FIELD(hasRowSecurity);
+ WRITE_BOOL_FIELD(isReturn);
WRITE_NODE_FIELD(cteList);
WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(jointree);
@@ -4197,6 +4206,9 @@ outNode(StringInfo str, const void *obj)
case T_SelectStmt:
_outSelectStmt(str, obj);
break;
+ case T_ReturnStmt:
+ _outReturnStmt(str, obj);
+ break;
case T_ColumnDef:
_outColumnDef(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..c6d0009820 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -263,6 +263,7 @@ _readQuery(void)
READ_BOOL_FIELD(hasModifyingCTE);
READ_BOOL_FIELD(hasForUpdate);
READ_BOOL_FIELD(hasRowSecurity);
+ READ_BOOL_FIELD(isReturn);
READ_NODE_FIELD(cteList);
READ_NODE_FIELD(rtable);
READ_NODE_FIELD(jointree);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 750586fceb..4a3d9b5253 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4422,7 +4422,22 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
Anum_pg_proc_prosrc,
&isNull);
if (isNull)
- elog(ERROR, "null prosrc for function %u", funcid);
+ {
+ char *probin;
+ List *querytree_list;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_probin, &isNull);
+ if (isNull)
+ elog(ERROR, "null probin and prosrc");
+
+ probin = TextDatumGetCString(tmp);
+ querytree_list = castNode(List, stringToNode(probin));
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
src = TextDatumGetCString(tmp);
/*
@@ -4480,6 +4495,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
querytree = transformTopLevelStmt(pstate, linitial(raw_parsetree_list));
free_parsestate(pstate);
+ }
/*
* The single command must be a simple "SELECT expression".
@@ -4735,12 +4751,15 @@ sql_inline_error_callback(void *arg)
int syntaxerrposition;
/* If it's a syntax error, convert to internal syntax error report */
- syntaxerrposition = geterrposition();
- if (syntaxerrposition > 0)
+ if (callback_arg->prosrc)
{
- errposition(0);
- internalerrposition(syntaxerrposition);
- internalerrquery(callback_arg->prosrc);
+ syntaxerrposition = geterrposition();
+ if (syntaxerrposition > 0)
+ {
+ errposition(0);
+ internalerrposition(syntaxerrposition);
+ internalerrquery(callback_arg->prosrc);
+ }
}
errcontext("SQL function \"%s\" during inlining", callback_arg->proname);
@@ -4852,7 +4871,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
Oid func_oid;
HeapTuple func_tuple;
Form_pg_proc funcform;
- char *src;
Datum tmp;
bool isNull;
MemoryContext oldcxt;
@@ -4961,27 +4979,50 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
- /* Fetch the function body */
- tmp = SysCacheGetAttr(PROCOID,
- func_tuple,
- Anum_pg_proc_prosrc,
- &isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", func_oid);
- src = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = src;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ {
+ char *probin;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_probin, &isNull);
+ if (isNull)
+ elog(ERROR, "null probin and prosrc");
+
+ probin = TextDatumGetCString(tmp);
+ querytree_list = castNode(List, stringToNode(probin));
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+
+ querytree_list = pg_rewrite_query(querytree);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
+ char *src;
+
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We can
* use the FuncExpr just created as the input for
@@ -4991,18 +5032,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
(Node *) fexpr,
fexpr->inputcollid);
- /*
- * Also resolve the actual function result tupdesc, if composite. If the
- * function is just declared to return RECORD, dig the info out of the AS
- * clause.
- */
- functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
- if (functypclass == TYPEFUNC_RECORD)
- rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
- rtfunc->funccoltypes,
- rtfunc->funccoltypmods,
- rtfunc->funccolcollations);
-
/*
* Parse, analyze, and rewrite (unlike inline_function(), we can't skip
* rewriting here). We can fail as soon as we find more than one query,
@@ -5019,6 +5048,19 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
if (list_length(querytree_list) != 1)
goto fail;
querytree = linitial(querytree_list);
+ }
+
+ /*
+ * Also resolve the actual function result tupdesc, if composite. If the
+ * function is just declared to return RECORD, dig the info out of the AS
+ * clause.
+ */
+ functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
+ if (functypclass == TYPEFUNC_RECORD)
+ rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
+ rtfunc->funccoltypes,
+ rtfunc->funccoltypmods,
+ rtfunc->funccolcollations);
/*
* The single command must be a plain SELECT.
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c159fb2957..ed0622c5d7 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -66,6 +66,7 @@ static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
bool isTopLevel, List **targetlist);
static void determineRecursiveColTypes(ParseState *pstate,
Node *larg, List *nrtargetlist);
+static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList);
static List *transformUpdateTargetList(ParseState *pstate,
@@ -304,6 +305,10 @@ transformStmt(ParseState *pstate, Node *parseTree)
}
break;
+ case T_ReturnStmt:
+ result = transformReturnStmt(pstate, (ReturnStmt *) parseTree);
+ break;
+
/*
* Special cases
*/
@@ -2221,6 +2226,36 @@ determineRecursiveColTypes(ParseState *pstate, Node *larg, List *nrtargetlist)
}
+/*
+ * transformReturnStmt -
+ * transforms a return statement
+ */
+static Query *
+transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+
+ qry->commandType = CMD_SELECT;
+ qry->isReturn = true;
+
+ qry->targetList = list_make1(makeTargetEntry((Expr *) transformExpr(pstate, stmt->returnval, EXPR_KIND_SELECT_TARGET),
+ 1, NULL, false));
+
+ if (pstate->p_resolve_unknowns)
+ resolveTargetListUnknowns(pstate, qry->targetList);
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasAggs = pstate->p_hasAggs;
+
+ assign_query_collations(pstate, qry);
+
+ return qry;
+}
+
+
/*
* transformUpdateStmt -
* transforms an update statement
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 480d168346..504126fe38 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -253,7 +253,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct SelectLimit *selectlimit;
}
-%type <node> stmt schema_stmt
+%type <node> stmt toplevel_stmt schema_stmt routine_body_stmt
AlterEventTrigStmt AlterCollationStmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
@@ -280,9 +280,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
- RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
+ RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt
RuleActionStmt RuleActionStmtOrEmpty RuleStmt
- SecLabelStmt SelectStmt TransactionStmt TruncateStmt
+ SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
UnlistenStmt UpdateStmt VacuumStmt
VariableResetStmt VariableSetStmt VariableShowStmt
ViewStmt CheckPointStmt CreateConversionStmt
@@ -385,14 +385,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> vacuum_relation
%type <selectlimit> opt_select_limit select_limit limit_clause
-%type <list> stmtblock stmtmulti
+%type <list> stmtblock stmtmulti routine_body_stmt_list
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
reloptions opt_reloptions
OptWith distinct_clause opt_all_clause opt_definition func_args func_args_list
func_args_with_defaults func_args_with_defaults_list
aggr_args aggr_args_list
- func_as createfunc_opt_list alterfunc_opt_list
+ func_as createfunc_opt_list opt_createfunc_opt_list alterfunc_opt_list
old_aggr_definition old_aggr_list
oper_argtypes RuleActionList RuleActionMulti
opt_column_list columnList opt_name_list
@@ -418,6 +418,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
vacuum_relation_list opt_vacuum_relation_list
drop_option_list
+%type <list> opt_routine_body
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
@@ -628,7 +629,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATOMIC ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
@@ -690,7 +691,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING
REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
- RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
+ RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
@@ -812,7 +813,7 @@ stmtblock: stmtmulti
* we'd get -1 for the location in such cases.
* We also take care to discard empty statements entirely.
*/
-stmtmulti: stmtmulti ';' stmt
+stmtmulti: stmtmulti ';' toplevel_stmt
{
if ($1 != NIL)
{
@@ -824,7 +825,7 @@ stmtmulti: stmtmulti ';' stmt
else
$$ = $1;
}
- | stmt
+ | toplevel_stmt
{
if ($1 != NULL)
$$ = list_make1(makeRawStmt($1, 0));
@@ -833,7 +834,16 @@ stmtmulti: stmtmulti ';' stmt
}
;
-stmt :
+/*
+ * toplevel_stmt includes BEGIN and END. stmt does not include them, because
+ * those words have different meanings in function bodys.
+ */
+toplevel_stmt:
+ stmt
+ | TransactionStmtLegacy
+ ;
+
+stmt:
AlterEventTrigStmt
| AlterCollationStmt
| AlterDatabaseStmt
@@ -7339,7 +7349,7 @@ opt_nulls_order: NULLS_LA FIRST_P { $$ = SORTBY_NULLS_FIRST; }
CreateFunctionStmt:
CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS func_return createfunc_opt_list
+ RETURNS func_return opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7348,10 +7358,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = $7;
n->options = $8;
+ n->sql_body = $9;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS TABLE '(' table_func_column_list ')' createfunc_opt_list
+ RETURNS TABLE '(' table_func_column_list ')' opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7361,10 +7372,11 @@ CreateFunctionStmt:
n->returnType = TableFuncTypeName($9);
n->returnType->location = @7;
n->options = $11;
+ n->sql_body = $12;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7373,10 +7385,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
| CREATE opt_or_replace PROCEDURE func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = true;
@@ -7385,6 +7398,7 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
;
@@ -7695,6 +7709,11 @@ aggregate_with_argtypes_list:
{ $$ = lappend($1, $3); }
;
+opt_createfunc_opt_list:
+ createfunc_opt_list
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
createfunc_opt_list:
/* Must be at least one to prevent conflict */
createfunc_opt_item { $$ = list_make1($1); }
@@ -7806,6 +7825,50 @@ func_as: Sconst { $$ = list_make1(makeString($1)); }
}
;
+ReturnStmt: RETURN a_expr
+ {
+ ReturnStmt *r = makeNode(ReturnStmt);
+ r->returnval = (Node *) $2;
+ $$ = (Node *) r;
+ }
+ ;
+
+opt_routine_body:
+ ReturnStmt
+ {
+ $$ = list_make1($1);
+ }
+ | BEGIN_P ATOMIC routine_body_stmt_list END_P
+ {
+ $$ = $3;
+ }
+ | /*EMPTY*/
+ {
+ $$ = NIL;
+ }
+ ;
+
+routine_body_stmt_list:
+ routine_body_stmt_list routine_body_stmt ';'
+ {
+ $$ = lappend($1, $2);
+ }
+ | /*EMPTY*/
+ {
+ /*
+ * For an empty body we insert a single fake element, so
+ * that the parse analysis code can tell apart an empty
+ * body from no body at all.
+ */
+ $$ = list_make1(NULL);
+ }
+ ;
+
+routine_body_stmt:
+ stmt
+ | ReturnStmt
+ ;
+
transform_type_list:
FOR TYPE_P Typename { $$ = list_make1($3); }
| transform_type_list ',' FOR TYPE_P Typename { $$ = lappend($1, $5); }
@@ -9756,13 +9819,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | BEGIN_P opt_transaction transaction_mode_list_or_empty
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_BEGIN;
- n->options = $3;
- $$ = (Node *)n;
- }
| START TRANSACTION transaction_mode_list_or_empty
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9778,14 +9834,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | END_P opt_transaction opt_transaction_chain
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_COMMIT;
- n->options = NIL;
- n->chain = $3;
- $$ = (Node *)n;
- }
| ROLLBACK opt_transaction opt_transaction_chain
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9852,6 +9900,24 @@ TransactionStmt:
}
;
+TransactionStmtLegacy:
+ BEGIN_P opt_transaction transaction_mode_list_or_empty
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_BEGIN;
+ n->options = $3;
+ $$ = (Node *)n;
+ }
+ | END_P opt_transaction opt_transaction_chain
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_COMMIT;
+ n->options = NIL;
+ n->chain = $3;
+ $$ = (Node *)n;
+ }
+ ;
+
opt_transaction: WORK {}
| TRANSACTION {}
| /*EMPTY*/ {}
@@ -15074,6 +15140,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| BACKWARD
@@ -15272,6 +15339,7 @@ unreserved_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| ROLE
@@ -15578,6 +15646,7 @@ bare_label_keyword:
| ASSIGNMENT
| ASYMMETRIC
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| AUTHORIZATION
@@ -15848,6 +15917,7 @@ bare_label_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| RIGHT
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 411cfadbff..e145f552c0 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -176,7 +176,6 @@ static int interactive_getc(void);
static int SocketBackend(StringInfo inBuf);
static int ReadCommand(StringInfo inBuf);
static void forbidden_in_wal_sender(char firstchar);
-static List *pg_rewrite_query(Query *query);
static bool check_log_statement(List *stmt_list);
static int errdetail_execute(List *raw_parsetree_list);
static int errdetail_params(ParamListInfo params);
@@ -761,7 +760,7 @@ pg_analyze_and_rewrite_params(RawStmt *parsetree,
* Note: query must just have come from the parser, because we do not do
* AcquireRewriteLocks() on it.
*/
-static List *
+List *
pg_rewrite_query(Query *query)
{
List *querytree_list;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 62023c20b2..65772abf5e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -174,6 +174,9 @@ typedef struct
List *outer_tlist; /* referent for OUTER_VAR Vars */
List *inner_tlist; /* referent for INNER_VAR Vars */
List *index_tlist; /* referent for INDEX_VAR Vars */
+ /* Special namespace representing a function signature: */
+ int numargs;
+ char **argnames;
} deparse_namespace;
/*
@@ -349,6 +352,7 @@ static int print_function_arguments(StringInfo buf, HeapTuple proctup,
bool print_table_args, bool print_defaults);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
static void print_function_trftypes(StringInfo buf, HeapTuple proctup);
+static void print_function_sqlbody(StringInfo buf, HeapTuple proctup);
static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used);
static void set_deparse_for_query(deparse_namespace *dpns, Query *query,
@@ -2800,9 +2804,15 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
}
/* And finally the function definition ... */
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
+ if (proc->prolang == SQLlanguageId && !isnull)
+ {
+ print_function_sqlbody(&buf, proctup);
+ }
+ else
+ {
appendStringInfoString(&buf, "AS ");
- tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
if (!isnull)
{
simple_quote_literal(&buf, TextDatumGetCString(tmp));
@@ -2831,6 +2841,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, dq.data, dq.len);
appendStringInfoString(&buf, prosrc);
appendBinaryStringInfo(&buf, dq.data, dq.len);
+ }
appendStringInfoChar(&buf, '\n');
@@ -3213,6 +3224,78 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(string_to_text(str));
}
+static void
+print_function_sqlbody(StringInfo buf, HeapTuple proctup)
+{
+ Datum tmp;
+ bool isnull;
+ List *stmts;
+ ListCell *lc;
+ int numargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ deparse_namespace dpns = {0};
+ bool need_block;
+
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
+ Assert(!isnull);
+ stmts = castNode(List, stringToNode(TextDatumGetCString(tmp)));
+
+ numargs = get_func_arg_info(proctup,
+ &argtypes, &argnames, &argmodes);
+ dpns.numargs = numargs;
+ dpns.argnames = argnames;
+
+ /*
+ * We need a BEGIN ATOMIC/END block unless the body is a single RETURN
+ * statement.
+ */
+ need_block = true;
+ if (list_length(stmts) == 1)
+ {
+ Query *query = linitial_node(Query, stmts);
+
+ if (query->isReturn)
+ need_block = false;
+ }
+
+ if (need_block)
+ appendStringInfoString(buf, "BEGIN ATOMIC\n");
+ foreach(lc, stmts)
+ {
+ Query *query = lfirst_node(Query, lc);
+
+ get_query_def(query, buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ if (lc != list_tail(stmts))
+ appendStringInfoChar(buf, ';');
+ appendStringInfoChar(buf, '\n');
+ }
+ if (need_block)
+ appendStringInfoString(buf, "END");
+}
+
+Datum
+pg_get_function_sqlbody(PG_FUNCTION_ARGS)
+{
+ Oid funcid = PG_GETARG_OID(0);
+ StringInfoData buf;
+ HeapTuple proctup;
+
+ initStringInfo(&buf);
+
+ /* Look up the function */
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ PG_RETURN_NULL();
+
+ print_function_sqlbody(&buf, proctup);
+
+ ReleaseSysCache(proctup);
+
+ PG_RETURN_TEXT_P(cstring_to_text(buf.data));
+}
+
/*
* deparse_expression - General utility for deparsing expressions
@@ -5418,7 +5501,10 @@ get_basic_select_query(Query *query, deparse_context *context,
/*
* Build up the query string - first we say SELECT
*/
- appendStringInfoString(buf, "SELECT");
+ if (query->isReturn)
+ appendStringInfoString(buf, "RETURN");
+ else
+ appendStringInfoString(buf, "SELECT");
/* Add the DISTINCT clause if given */
if (query->distinctClause != NIL)
@@ -7550,6 +7636,26 @@ get_parameter(Param *param, deparse_context *context)
return;
}
+ /*
+ * If it's an external parameter, see if the outermost namespace provides
+ * function argument names.
+ */
+ if (param->paramkind == PARAM_EXTERN)
+ {
+ dpns = lfirst(list_tail(context->namespaces));
+ if (dpns->argnames)
+ {
+ char *argname = dpns->argnames[param->paramid - 1];
+
+ if (argname)
+ {
+ // TODO: qualify with function name if necessary
+ appendStringInfo(context->buf, "%s", quote_identifier(argname));
+ return;
+ }
+ }
+ }
+
/*
* Not PARAM_EXEC, or couldn't find referent: just print $N.
*/
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 88bbbd9a9e..f25843e087 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11807,6 +11807,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
char *proretset;
char *prosrc;
char *probin;
+ char *prosqlbody;
char *funcargs;
char *funciargs;
char *funcresult;
@@ -11853,7 +11854,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"provolatile,\n"
"proisstrict,\n"
"prosecdef,\n"
- "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname,\n");
+ "lanname,\n");
if (fout->remoteVersion >= 80300)
appendPQExpBuffer(query,
@@ -11873,9 +11874,9 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
* pg_get_function_result instead of examining proallargtypes etc.
*/
appendPQExpBuffer(query,
- "pg_catalog.pg_get_function_arguments(oid) AS funcargs,\n"
- "pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs,\n"
- "pg_catalog.pg_get_function_result(oid) AS funcresult,\n");
+ "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n"
+ "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n"
+ "pg_catalog.pg_get_function_result(p.oid) AS funcresult,\n");
}
else if (fout->remoteVersion >= 80100)
appendPQExpBuffer(query,
@@ -11918,21 +11919,39 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
if (fout->remoteVersion >= 120000)
appendPQExpBuffer(query,
- "prosupport\n");
+ "prosupport,\n");
else
appendPQExpBuffer(query,
- "'-' AS prosupport\n");
+ "'-' AS prosupport,\n");
+
+ if (fout->remoteVersion >= 140000)
+ appendPQExpBuffer(query,
+ "CASE WHEN prosrc IS NULL AND lanname = 'sql' THEN pg_get_function_sqlbody(p.oid) END AS prosqlbody\n");
+ else
+ appendPQExpBuffer(query,
+ "NULL AS prosqlbody\n");
appendPQExpBuffer(query,
- "FROM pg_catalog.pg_proc "
- "WHERE oid = '%u'::pg_catalog.oid",
+ "FROM pg_catalog.pg_proc p, pg_catalog.pg_language l\n"
+ "WHERE p.oid = '%u'::pg_catalog.oid "
+ "AND l.oid = p.prolang",
finfo->dobj.catId.oid);
res = ExecuteSqlQueryForSingleRow(fout, query->data);
proretset = PQgetvalue(res, 0, PQfnumber(res, "proretset"));
- prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
- probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ if (PQgetisnull(res, 0, PQfnumber(res, "prosqlbody")))
+ {
+ prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
+ probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ prosqlbody = NULL;
+ }
+ else
+ {
+ prosrc = NULL;
+ probin = NULL;
+ prosqlbody = PQgetvalue(res, 0, PQfnumber(res, "prosqlbody"));
+ }
if (fout->remoteVersion >= 80400)
{
funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
@@ -11969,7 +11988,11 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
* versions would set it to "-". There are no known cases in which prosrc
* is unused, so the tests below for "-" are probably useless.
*/
- if (probin[0] != '\0' && strcmp(probin, "-") != 0)
+ if (prosqlbody)
+ {
+ appendPQExpBufferStr(asPart, prosqlbody);
+ }
+ else if (probin[0] != '\0' && strcmp(probin, "-") != 0)
{
appendPQExpBufferStr(asPart, "AS ");
appendStringLiteralAH(asPart, probin, fout);
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 08dffde1ba..ee34463e67 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -645,10 +645,11 @@ other .
";" {
ECHO;
- if (cur_state->paren_depth == 0)
+ if (cur_state->paren_depth == 0 && cur_state->begin_depth == 0)
{
/* Terminate lexing temporarily */
cur_state->start_state = YY_START;
+ cur_state->identifier_count = 0;
return LEXRES_SEMI;
}
}
@@ -661,6 +662,8 @@ other .
"\\"[;:] {
/* Force a semi-colon or colon into the query buffer */
psqlscan_emit(cur_state, yytext + 1, 1);
+ if (yytext[1] == ';')
+ cur_state->identifier_count = 0;
}
"\\" {
@@ -867,6 +870,17 @@ other .
{identifier} {
+ cur_state->identifier_count++;
+ if (pg_strcasecmp(yytext, "begin") == 0)
+ {
+ if (cur_state->identifier_count > 1)
+ cur_state->begin_depth++;
+ }
+ else if (pg_strcasecmp(yytext, "end") == 0)
+ {
+ if (cur_state->begin_depth > 0)
+ cur_state->begin_depth--;
+ }
ECHO;
}
@@ -1054,6 +1068,11 @@ psql_scan(PsqlScanState state,
result = PSCAN_INCOMPLETE;
*prompt = PROMPT_PAREN;
}
+ if (state->begin_depth > 0)
+ {
+ result = PSCAN_INCOMPLETE;
+ *prompt = PROMPT_CONTINUE;
+ }
else if (query_buf->len > 0)
{
result = PSCAN_EOL;
@@ -1170,6 +1189,8 @@ psql_scan_reset(PsqlScanState state)
if (state->dolqstart)
free(state->dolqstart);
state->dolqstart = NULL;
+ state->identifier_count = 0;
+ state->begin_depth = 0;
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 22340baf1c..c4bd6cf8bf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3681,6 +3681,10 @@
proname => 'pg_get_function_arg_default', provolatile => 's',
prorettype => 'text', proargtypes => 'oid int4',
prosrc => 'pg_get_function_arg_default' },
+{ oid => '9704', descr => 'function SQL body',
+ proname => 'pg_get_function_sqlbody', provolatile => 's',
+ prorettype => 'text', proargtypes => 'oid',
+ prosrc => 'pg_get_function_sqlbody' },
{ oid => '1686', descr => 'list of SQL keywords',
proname => 'pg_get_keywords', procost => '10', prorows => '500',
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 268c810896..498850979c 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -112,7 +112,7 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
Oid protrftypes[1] BKI_DEFAULT(_null_);
/* procedure source text */
- text prosrc BKI_FORCE_NOT_NULL;
+ text prosrc;
/* secondary procedure info (can be NULL) */
text probin BKI_DEFAULT(_null_);
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 7a079ef07f..a32cfba421 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -68,9 +68,11 @@ extern void interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType);
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index cb13428a5a..5d547c66ed 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -20,6 +20,21 @@
/* This struct is known only within executor/functions.c */
typedef struct SQLFunctionParseInfo *SQLFunctionParseInfoPtr;
+/*
+ * Data structure needed by the parser callback hooks to resolve parameter
+ * references during parsing of a SQL function's body. This is separate from
+ * SQLFunctionCache since we sometimes do parsing separately from execution.
+ */
+typedef struct SQLFunctionParseInfo
+{
+ char *fname; /* function's name */
+ int nargs; /* number of input arguments */
+ Oid *argtypes; /* resolved types of input arguments */
+ char **argnames; /* names of input arguments; NULL if none */
+ /* Note that argnames[i] can be NULL, if some args are unnamed */
+ Oid collation; /* function's input collation, if known */
+} SQLFunctionParseInfo;
+
extern Datum fmgr_sql(PG_FUNCTION_ARGS);
extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTuple,
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 311f80394a..fb8f58aa29 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -114,6 +114,8 @@ typedef struct PsqlScanStateData
int paren_depth; /* depth of nesting in parentheses */
int xcdepth; /* depth of nesting in slash-star comments */
char *dolqstart; /* current $foo$ quote start string */
+ int identifier_count; /* identifiers since start of statement */
+ int begin_depth; /* depth of begin/end routine body blocks */
/*
* Callback functions provided by the program making use of the lexer,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7ddd8c011b..f5716a3eb0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -315,6 +315,7 @@ typedef enum NodeTag
T_DeleteStmt,
T_UpdateStmt,
T_SelectStmt,
+ T_ReturnStmt,
T_AlterTableStmt,
T_AlterTableCmd,
T_AlterDomainStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 60c2f45466..7ce3834787 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -132,6 +132,8 @@ typedef struct Query
bool hasForUpdate; /* FOR [KEY] UPDATE/SHARE was specified */
bool hasRowSecurity; /* rewriter has applied some RLS policy */
+ bool isReturn; /* is a RETURN statement */
+
List *cteList; /* WITH list (of CommonTableExpr's) */
List *rtable; /* list of range table entries */
@@ -1671,6 +1673,16 @@ typedef struct SetOperationStmt
} SetOperationStmt;
+/*
+ * RETURN statement (inside SQL function body)
+ */
+typedef struct ReturnStmt
+{
+ NodeTag type;
+ Node *returnval;
+} ReturnStmt;
+
+
/*****************************************************************************
* Other Statements (no optimizations required)
*
@@ -2842,6 +2854,7 @@ typedef struct CreateFunctionStmt
List *parameters; /* a list of FunctionParameter */
TypeName *returnType; /* the return type */
List *options; /* a list of DefElem */
+ List *sql_body;
} CreateFunctionStmt;
typedef enum FunctionParameterMode
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 71dcdf2889..36317a14db 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -48,6 +48,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("atomic", ATOMIC, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -344,6 +345,7 @@ PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("return", RETURN, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD, AS_LABEL)
PG_KEYWORD("returns", RETURNS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index bd30607b07..e626c8eafd 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -43,6 +43,7 @@ typedef enum
extern PGDLLIMPORT int log_statement;
extern List *pg_parse_query(const char *query_string);
+extern List *pg_rewrite_query(Query *query);
extern List *pg_analyze_and_rewrite(RawStmt *parsetree,
const char *query_string,
Oid *paramTypes, int numParams,
diff --git a/src/interfaces/ecpg/preproc/ecpg.addons b/src/interfaces/ecpg/preproc/ecpg.addons
index 300381eaad..0441561c52 100644
--- a/src/interfaces/ecpg/preproc/ecpg.addons
+++ b/src/interfaces/ecpg/preproc/ecpg.addons
@@ -87,6 +87,12 @@ ECPG: stmtTransactionStmt block
whenever_action(2);
free($1);
}
+ECPG: toplevel_stmtTransactionStmtLegacy block
+ {
+ fprintf(base_yyout, "{ ECPGtrans(__LINE__, %s, \"%s\");", connection ? connection : "NULL", $1);
+ whenever_action(2);
+ free($1);
+ }
ECPG: stmtViewStmt rule
| ECPGAllocateDescr
{
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer
index 6ccc8ab916..7f4be655f9 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -4,8 +4,8 @@ statements: /*EMPTY*/
| statements statement
;
-statement: ecpgstart at stmt ';' { connection = NULL; }
- | ecpgstart stmt ';'
+statement: ecpgstart at toplevel_stmt ';' { connection = NULL; }
+ | ecpgstart toplevel_stmt ';'
| ecpgstart ECPGVarDeclaration
{
fprintf(base_yyout, "%s", $2);
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index ce508ae1dc..b62c6393bb 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -255,6 +255,135 @@ SELECT pg_get_functiondef('functest_F_2'::regproc);
(1 row)
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+ERROR: SQL function with unquoted function body cannot have polymorphic arguments
+SELECT functest_S_1('abcd', '2020-01-01');
+ functest_s_1
+--------------
+ t
+(1 row)
+
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+ functest_s_2
+--------------
+ 1
+(1 row)
+
+SELECT functest_S_3();
+ functest_s_3
+--------------
+ f
+(1 row)
+
+SELECT functest_S_10('abcd', '2020-01-01');
+ functest_s_10
+---------------
+ t
+(1 row)
+
+SELECT functest_S_13();
+ functest_s_13
+---------------
+ f
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_1(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN ((a = 'abcd'::text) AND (b > '01-01-2001'::date)) +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_2(a text[])+
+ RETURNS integer +
+ LANGUAGE sql +
+ RETURN ((a)[1])::integer +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+ pg_get_functiondef
+----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN false +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+ pg_get_functiondef
+-------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_10(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT ((a = 'abcd'::text) AND (b > '01-01-2001'::date)) +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_13()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT 1; +
+ SELECT false AS bool +
+ END +
+
+(1 row)
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+SELECT functest_S_14();
+ functest_s_14
+---------------
+ 2
+(1 row)
+
+DROP TABLE functest3 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view functestv3
+drop cascades to function functest_s_14()
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
RETURNS int
@@ -284,6 +413,20 @@ SELECT routine_name, ordinal_position, parameter_name, parameter_default
(7 rows)
DROP FUNCTION functest_IS_1(int, int, text), functest_IS_2(int), functest_IS_3(int);
+CREATE TABLE functest1 (a int, b int);
+CREATE SEQUENCE functest2;
+CREATE FUNCTION functest_IS_4()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest1);
+CREATE FUNCTION functest_IS_5()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest2');
+DROP TABLE functest1 CASCADE;
+NOTICE: drop cascades to function functest_is_4()
+DROP SEQUENCE functest2 CASCADE;
+NOTICE: drop cascades to function functest_is_5()
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
@@ -302,6 +445,49 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
ERROR: cannot change routine kind
DETAIL: "functest1" is a function.
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+SELECT * FROM functest_sri1();
+ functest_sri1
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+SELECT * FROM functest_sri2();
+ functest_sri2
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
$$ SELECT a + 1 $$;
@@ -360,7 +546,7 @@ SELECT * FROM voidtest5(3);
-- Cleanup
DROP SCHEMA temp_func_test CASCADE;
-NOTICE: drop cascades to 21 other objects
+NOTICE: drop cascades to 28 other objects
DETAIL: drop cascades to function functest_a_1(text,date)
drop cascades to function functest_a_2(text[])
drop cascades to function functest_a_3()
@@ -376,7 +562,14 @@ drop cascades to function functest_f_1(integer)
drop cascades to function functest_f_2(integer)
drop cascades to function functest_f_3(integer)
drop cascades to function functest_f_4(integer)
+drop cascades to function functest_s_1(text,date)
+drop cascades to function functest_s_2(text[])
+drop cascades to function functest_s_3()
+drop cascades to function functest_s_10(text,date)
+drop cascades to function functest_s_13()
drop cascades to function functest_b_2(bigint)
+drop cascades to function functest_sri1()
+drop cascades to function functest_sri2()
drop cascades to function voidtest1(integer)
drop cascades to function voidtest2(integer,integer)
drop cascades to function voidtest3(integer)
diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out
index 3838fa2324..99df366bcd 100644
--- a/src/test/regress/expected/create_procedure.out
+++ b/src/test/regress/expected/create_procedure.out
@@ -65,6 +65,41 @@ SELECT * FROM cp_test ORDER BY b COLLATE "C";
1 | xyzzy
(3 rows)
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+\df ptest1s
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+---------+------------------+---------------------+------
+ public | ptest1s | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest1s'::regproc);
+ pg_get_functiondef
+----------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest1s(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ INSERT INTO cp_test (a, b) +
+ VALUES (1, x) +
+ END +
+
+(1 row)
+
+CALL ptest1s('b');
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+ a | b
+---+-------
+ 1 | 0
+ 1 | a
+ 1 | b
+ 1 | xyzzy
+(4 rows)
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -146,6 +181,28 @@ AS $$
SELECT a = b;
$$;
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+\df ptest8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+--------+------------------+---------------------+------
+ public | ptest8 | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest8'::regproc);
+ pg_get_functiondef
+---------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest8(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ END +
+
+(1 row)
+
+CALL ptest8('');
-- OUT parameters
CREATE PROCEDURE ptest9(OUT a int)
LANGUAGE SQL
@@ -214,6 +271,7 @@ ALTER ROUTINE ptest1a RENAME TO ptest1;
DROP ROUTINE cp_testfunc1(int);
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
DROP USER regress_cp_user1;
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
index bd108a918f..6a3c56bc0b 100644
--- a/src/test/regress/sql/create_function_3.sql
+++ b/src/test/regress/sql/create_function_3.sql
@@ -153,6 +153,60 @@ CREATE FUNCTION functest_F_4(int) RETURNS bool LANGUAGE 'sql'
SELECT pg_get_functiondef('functest_F_2'::regproc);
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+
+SELECT functest_S_1('abcd', '2020-01-01');
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+SELECT functest_S_3();
+
+SELECT functest_S_10('abcd', '2020-01-01');
+SELECT functest_S_13();
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+
+SELECT functest_S_14();
+
+DROP TABLE functest3 CASCADE;
+
+
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
@@ -177,6 +231,23 @@ CREATE FUNCTION functest_IS_3(a int default 1, out b int)
DROP FUNCTION functest_IS_1(int, int, text), functest_IS_2(int), functest_IS_3(int);
+CREATE TABLE functest1 (a int, b int);
+CREATE SEQUENCE functest2;
+
+CREATE FUNCTION functest_IS_4()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest1);
+
+CREATE FUNCTION functest_IS_5()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest2');
+
+DROP TABLE functest1 CASCADE;
+DROP SEQUENCE functest2 CASCADE;
+
+
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
@@ -194,6 +265,29 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+
+SELECT * FROM functest_sri1();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+
+SELECT * FROM functest_sri2();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
diff --git a/src/test/regress/sql/create_procedure.sql b/src/test/regress/sql/create_procedure.sql
index 2ef1c82cea..8c0d70cb16 100644
--- a/src/test/regress/sql/create_procedure.sql
+++ b/src/test/regress/sql/create_procedure.sql
@@ -28,6 +28,21 @@ CREATE PROCEDURE ptest1(x text)
SELECT * FROM cp_test ORDER BY b COLLATE "C";
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+
+\df ptest1s
+SELECT pg_get_functiondef('ptest1s'::regproc);
+
+CALL ptest1s('b');
+
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -112,6 +127,16 @@ CREATE PROCEDURE ptest7(a text, b text)
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+
+\df ptest8
+SELECT pg_get_functiondef('ptest8'::regproc);
+CALL ptest8('');
+
+
-- OUT parameters
CREATE PROCEDURE ptest9(OUT a int)
@@ -170,6 +195,7 @@ CREATE USER regress_cp_user1;
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
base-commit: f13f2e484172a1c865cd067796cee3568467dd51
--
2.28.0
Here is another updated patch. I did some merging and some small fixes
and introduced the pg_proc column prosqlbody to store the parsed
function body separately from probin. Aside from one TODO left it seems
feature-complete to me for now.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
v5-0001-SQL-standard-function-body.patchtext/plain; charset=UTF-8; name=v5-0001-SQL-standard-function-body.patch; x-mac-creator=0; x-mac-type=0Download
From 566a30b33df2bca79dd7c6f45f2f55be42403b27 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Tue, 27 Oct 2020 14:40:14 +0100
Subject: [PATCH v5] SQL-standard function body
This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.
Instead of the PostgreSQL-specific AS $$ string literal $$ syntax,
this allows writing out the SQL statements making up the body
unquoted, either as a single statement:
CREATE FUNCTION add(a integer, b integer) RETURNS integer
LANGUAGE SQL
RETURN a + b;
or as a block
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
BEGIN ATOMIC
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
END;
The function body is parsed at function definition time and stored as
expression nodes in a new pg_proc column prosqlbody. So at run time,
no further parsing is required.
However, this form does not support polymorphic arguments, because
there is no more parse analysis done at call time.
Dependencies between the function and the objects it uses are fully
tracked.
A new RETURN statement is introduced. This can only be used inside
function bodies. Internally, it is treated much like a SELECT
statement.
psql needs some new intelligence to keep track of function body
boundaries so that it doesn't send off statements when it sees
semicolons that are inside a function body.
Also, per SQL standard, LANGUAGE SQL is the default, so it does not
need to be specified anymore.
Discussion: https://www.postgresql.org/message-id/flat/1c11f1eb-f00c-43b7-799d-2d44132c02d7@2ndquadrant.com
---
doc/src/sgml/catalogs.sgml | 10 +
doc/src/sgml/ref/create_function.sgml | 126 +++++++++--
doc/src/sgml/ref/create_procedure.sgml | 62 ++++-
src/backend/catalog/pg_aggregate.c | 1 +
src/backend/catalog/pg_proc.c | 116 ++++++----
src/backend/commands/aggregatecmds.c | 2 +
src/backend/commands/functioncmds.c | 119 ++++++++--
src/backend/commands/typecmds.c | 1 +
src/backend/executor/functions.c | 79 ++++---
src/backend/nodes/copyfuncs.c | 15 ++
src/backend/nodes/equalfuncs.c | 13 ++
src/backend/nodes/outfuncs.c | 12 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/util/clauses.c | 126 +++++++----
src/backend/parser/analyze.c | 35 +++
src/backend/parser/gram.y | 129 ++++++++---
src/backend/tcop/postgres.c | 3 +-
src/backend/utils/adt/ruleutils.c | 106 ++++++++-
src/bin/pg_dump/pg_dump.c | 45 +++-
src/bin/psql/describe.c | 15 +-
src/fe_utils/psqlscan.l | 23 +-
src/include/catalog/pg_proc.dat | 4 +
src/include/catalog/pg_proc.h | 6 +-
src/include/commands/defrem.h | 2 +
src/include/executor/functions.h | 15 ++
src/include/fe_utils/psqlscan_int.h | 2 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 13 ++
src/include/parser/kwlist.h | 2 +
src/include/tcop/tcopprot.h | 1 +
src/interfaces/ecpg/preproc/ecpg.addons | 6 +
src/interfaces/ecpg/preproc/ecpg.trailer | 4 +-
.../regress/expected/create_function_3.out | 212 +++++++++++++++++-
.../regress/expected/create_procedure.out | 58 +++++
src/test/regress/sql/create_function_3.sql | 99 ++++++++
src/test/regress/sql/create_procedure.sql | 26 +++
36 files changed, 1286 insertions(+), 204 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 5bd54cb218..a8ae9594e6 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -5972,6 +5972,16 @@ <title><structname>pg_proc</structname> Columns</title>
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>prosqlbody</structfield> <type>pg_node_tree</type>
+ </para>
+ <para>
+ Pre-parsed SQL function body. This will be used for language SQL
+ functions if the body is not specified as a string constant.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>proconfig</structfield> <type>text[]</type>
diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index 3c1eaea651..1b5b9420db 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -38,6 +38,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -257,8 +258,9 @@ <title>Parameters</title>
The name of the language that the function is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -577,6 +579,44 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> function. This can
+ either be a single statement
+<programlisting>
+RETURN <replaceable>expression</replaceable>
+</programlisting>
+ or a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the function body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at function definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at function definition time. This form tracks dependencies between the
+ function and objects used in the function body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling functions. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
<para>
@@ -669,6 +709,15 @@ <title>Examples</title>
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
+</programlisting>
+ The same function written in a more SQL-conforming style, using argument
+ names and an unquoted body:
+<programlisting>
+CREATE FUNCTION add(a integer, b integer) RETURNS integer
+ LANGUAGE SQL
+ IMMUTABLE
+ RETURNS NULL ON NULL INPUT
+ RETURN a + b;
</programlisting>
</para>
@@ -799,23 +848,74 @@ <title>Writing <literal>SECURITY DEFINER</literal> Functions Safely</title>
<title>Compatibility</title>
<para>
- A <command>CREATE FUNCTION</command> command is defined in the SQL standard.
- The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. The attributes are not portable, neither are the
- different available languages.
+ A <command>CREATE FUNCTION</command> command is defined in the SQL
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. Conversely, the SQL
+ standard specifies a number of optional features that are not implemented
+ in <productname>PostgreSQL</productname>.
</para>
<para>
- For compatibility with some other database systems,
- <replaceable class="parameter">argmode</replaceable> can be written
- either before or after <replaceable class="parameter">argname</replaceable>.
- But only the first way is standard-compliant.
+ The following are important compatibility issues:
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>OR REPLACE</literal> is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For compatibility with some other database systems, <replaceable
+ class="parameter">argmode</replaceable> can be written either before or
+ after <replaceable class="parameter">argname</replaceable>. But only
+ the first way is standard-compliant.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For parameter defaults, the SQL standard specifies only the syntax with
+ the <literal>DEFAULT</literal> key word. The syntax with
+ <literal>=</literal> is used in T-SQL and Firebird.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The <literal>SETOF</literal> modifier is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Only <literal>SQL</literal> is standardized as a language.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ All other attributes except <literal>CALLED ON NULL INPUT</literal> and
+ <literal>RETURNS NULL ON NULL INPUT</literal> are not standardized.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For the body of <literal>LANGUAGE SQL</literal> functions, the SQL
+ standard only specifies the <replaceable>sql_body</replaceable> form.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
<para>
- For parameter defaults, the SQL standard specifies only the syntax with
- the <literal>DEFAULT</literal> key word. The syntax
- with <literal>=</literal> is used in T-SQL and Firebird.
+ Simple <literal>LANGUAGE SQL</literal> functions can be written in a way
+ that is both standard-conforming and portable to other implementations.
+ More complex functions using advanced features, optimization attributes, or
+ other languages will necessarily be specific to PostgreSQL in a significant
+ way.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_procedure.sgml b/doc/src/sgml/ref/create_procedure.sgml
index e258eca5ce..ecdeac1629 100644
--- a/doc/src/sgml/ref/create_procedure.sgml
+++ b/doc/src/sgml/ref/create_procedure.sgml
@@ -29,6 +29,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -162,8 +163,9 @@ <title>Parameters</title>
The name of the language that the procedure is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -299,6 +301,41 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> procedure. This should
+ be a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the procedure body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at procedure definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at procedure definition time. This form tracks dependencies between the
+ procedure and objects used in the procedure body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling procedures. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
@@ -318,6 +355,7 @@ <title>Notes</title>
<refsect1 id="sql-createprocedure-examples">
<title>Examples</title>
+ <para>
<programlisting>
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
@@ -325,9 +363,21 @@ <title>Examples</title>
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
$$;
-
+</programlisting>
+ or
+<programlisting>
+CREATE PROCEDURE insert_data(a integer, b integer)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO tbl VALUES (a);
+ INSERT INTO tbl VALUES (b);
+END;
+</programlisting>
+ and call like this:
+<programlisting>
CALL insert_data(1, 2);
</programlisting>
+ </para>
</refsect1>
<refsect1 id="sql-createprocedure-compat">
@@ -335,9 +385,9 @@ <title>Compatibility</title>
<para>
A <command>CREATE PROCEDURE</command> command is defined in the SQL
- standard. The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. For details see
- also <xref linkend="sql-createfunction"/>.
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. For details see also
+ <xref linkend="sql-createfunction"/>.
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 0cf1da6ebb..9d6032bf88 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -622,6 +622,7 @@ AggregateCreate(const char *aggName,
InvalidOid, /* no validator */
"aggregate_dummy", /* placeholder proc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_AGGREGATE,
false, /* security invoker (currently not
* definable for agg) */
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 1dd9ecc063..808d48949a 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -32,6 +32,7 @@
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_type.h"
#include "tcop/pquery.h"
@@ -76,6 +77,7 @@ ProcedureCreate(const char *procedureName,
Oid languageValidator,
const char *prosrc,
const char *probin,
+ Node *prosqlbody,
char prokind,
bool security_definer,
bool isLeakProof,
@@ -119,8 +121,6 @@ ProcedureCreate(const char *procedureName,
/*
* sanity checks
*/
- Assert(PointerIsValid(prosrc));
-
parameterCount = parameterTypes->dim1;
if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
ereport(ERROR,
@@ -334,11 +334,18 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_protrftypes - 1] = trftypes;
else
nulls[Anum_pg_proc_protrftypes - 1] = true;
- values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ if (prosrc)
+ values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ else
+ nulls[Anum_pg_proc_prosrc - 1] = true;
if (probin)
values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin);
else
nulls[Anum_pg_proc_probin - 1] = true;
+ if (prosqlbody)
+ values[Anum_pg_proc_prosqlbody - 1] = CStringGetTextDatum(nodeToString(prosqlbody));
+ else
+ nulls[Anum_pg_proc_prosqlbody - 1] = true;
if (proconfig != PointerGetDatum(NULL))
values[Anum_pg_proc_proconfig - 1] = proconfig;
else
@@ -638,6 +645,10 @@ ProcedureCreate(const char *procedureName,
record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
free_object_addresses(addrs);
+ /* dependency on SQL routine body */
+ if (languageObjectId == SQLlanguageId && prosqlbody)
+ recordDependencyOnExpr(&myself, prosqlbody, NIL, DEPENDENCY_NORMAL);
+
/* dependency on parameter default expressions */
if (parameterDefaults)
recordDependencyOnExpr(&myself, (Node *) parameterDefaults,
@@ -861,61 +872,81 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
/* Postpone body checks if !check_function_bodies */
if (check_function_bodies)
{
- tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
- if (isnull)
- elog(ERROR, "null prosrc");
-
- prosrc = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport().
*/
callback_arg.proname = NameStr(proc->proname);
- callback_arg.prosrc = prosrc;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_function_parse_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
- /*
- * We can't do full prechecking of the function definition if there
- * are any polymorphic input types, because actual datatypes of
- * expression results will be unresolvable. The check will be done at
- * runtime instead.
- *
- * We can run the text through the raw parser though; this will at
- * least catch silly syntactic errors.
- */
- raw_parsetree_list = pg_parse_query(prosrc);
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ {
+ Node *n;
- if (!haspolyarg)
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosqlbody, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc and prosqlbody");
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = castNode(List, n);
+ else
+ querytree_list = list_make1(list_make1(n));
+ }
+ else
{
+ prosrc = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = prosrc;
+
/*
- * OK to do full precheck: analyze and rewrite the queries, then
- * verify the result type.
+ * We can't do full prechecking of the function definition if there
+ * are any polymorphic input types, because actual datatypes of
+ * expression results will be unresolvable. The check will be done at
+ * runtime instead.
+ *
+ * We can run the text through the raw parser though; this will at
+ * least catch silly syntactic errors.
*/
- SQLFunctionParseInfoPtr pinfo;
- Oid rettype;
- TupleDesc rettupdesc;
+ raw_parsetree_list = pg_parse_query(prosrc);
- /* But first, set up parameter information */
- pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
-
- querytree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (!haspolyarg)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *querytree_sublist;
-
- querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
- prosrc,
- (ParserSetupHook) sql_fn_parser_setup,
- pinfo,
- NULL);
- querytree_list = lappend(querytree_list,
- querytree_sublist);
+ /*
+ * OK to do full precheck: analyze and rewrite the queries, then
+ * verify the result type.
+ */
+ SQLFunctionParseInfoPtr pinfo;
+
+ /* But first, set up parameter information */
+ pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
+
+ querytree_list = NIL;
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *querytree_sublist;
+
+ querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ prosrc,
+ (ParserSetupHook) sql_fn_parser_setup,
+ pinfo,
+ NULL);
+ querytree_list = lappend(querytree_list,
+ querytree_sublist);
+ }
}
+ }
+
+ if (!haspolyarg)
+ {
+ Oid rettype;
+ TupleDesc rettupdesc;
check_sql_fn_statements(querytree_list);
@@ -968,6 +999,9 @@ function_parse_error_transpose(const char *prosrc)
int newerrposition;
const char *queryText;
+ if (!prosrc)
+ return false;
+
/*
* Nothing to do unless we are dealing with a syntax error that has a
* cursor position.
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 6bf54e64f8..26b3fa27de 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -313,9 +313,11 @@ DefineAggregate(ParseState *pstate,
InvalidOid,
OBJECT_AGGREGATE,
¶meterTypes,
+ NULL,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ NULL,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index c3ce480c8f..c48d3744a4 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -53,9 +53,11 @@
#include "commands/proclang.h"
#include "executor/execdesc.h"
#include "executor/executor.h"
+#include "executor/functions.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
@@ -186,9 +188,11 @@ interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType)
@@ -283,7 +287,11 @@ interpret_function_parameter_list(ParseState *pstate,
/* handle input parameters */
if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
+ {
isinput = true;
+ if (parameterTypes_list)
+ *parameterTypes_list = lappend_oid(*parameterTypes_list, toid);
+ }
/* handle signature parameters */
if (fp->mode == FUNC_PARAM_IN || fp->mode == FUNC_PARAM_INOUT ||
@@ -372,6 +380,9 @@ interpret_function_parameter_list(ParseState *pstate,
have_names = true;
}
+ if (inParameterNames_list)
+ *inParameterNames_list = lappend(*inParameterNames_list, makeString(fp->name ? fp->name : pstrdup("")));
+
if (fp->defexpr)
{
Node *def;
@@ -786,28 +797,10 @@ compute_function_attributes(ParseState *pstate,
defel->defname);
}
- /* process required items */
if (as_item)
*as = (List *) as_item->arg;
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no function body specified")));
- *as = NIL; /* keep compiler quiet */
- }
-
if (language_item)
*language = strVal(language_item->arg);
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no language specified")));
- *language = NULL; /* keep compiler quiet */
- }
-
- /* process optional items */
if (transform_item)
*transform = transform_item->arg;
if (windowfunc_item)
@@ -856,10 +849,21 @@ compute_function_attributes(ParseState *pstate,
*/
static void
interpret_AS_clause(Oid languageOid, const char *languageName,
- char *funcname, List *as,
- char **prosrc_str_p, char **probin_str_p)
+ char *funcname, List *as, Node *sql_body_in,
+ List *parameterTypes, List *inParameterNames,
+ char **prosrc_str_p, char **probin_str_p, Node **sql_body_out)
{
- Assert(as != NIL);
+ if (sql_body_in && as)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("duplicate function body specified")));
+
+ if (sql_body_in && languageOid != SQLlanguageId)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("inline SQL function body only valid for language SQL")));
+
+ *sql_body_out = NULL;
if (languageOid == ClanguageId)
{
@@ -881,6 +885,66 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
*prosrc_str_p = funcname;
}
}
+ else if (sql_body_in)
+ {
+ SQLFunctionParseInfoPtr pinfo;
+
+ pinfo = (SQLFunctionParseInfoPtr) palloc0(sizeof(SQLFunctionParseInfo));
+
+ pinfo->fname = funcname;
+ pinfo->nargs = list_length(parameterTypes);
+ pinfo->argtypes = (Oid *) palloc(pinfo->nargs * sizeof(Oid));
+ pinfo->argnames = (char **) palloc(pinfo->nargs * sizeof(char *));
+ for (int i = 0; i < list_length(parameterTypes); i++)
+ {
+ char *s = strVal(list_nth(inParameterNames, i));
+
+ pinfo->argtypes[i] = list_nth_oid(parameterTypes, i);
+ if (IsPolymorphicType(pinfo->argtypes[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("SQL function with unquoted function body cannot have polymorphic arguments")));
+
+ if (s[0] != '\0')
+ pinfo->argnames[i] = s;
+ else
+ pinfo->argnames[i] = NULL;
+ }
+
+ if (IsA(sql_body_in, List))
+ {
+ List *stmts = linitial_node(List, castNode(List, sql_body_in));
+ ListCell *lc;
+ List *transformed_stmts = NIL;
+
+ foreach(lc, stmts)
+ {
+ Node *stmt = lfirst(lc);
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, stmt);
+ transformed_stmts = lappend(transformed_stmts, q);
+ free_parsestate(pstate);
+ }
+
+ *sql_body_out = (Node *) list_make1(transformed_stmts);
+ }
+ else
+ {
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, sql_body_in);
+
+ *sql_body_out = (Node *) q;
+ }
+
+ *probin_str_p = NULL;
+ *prosrc_str_p = NULL;
+ }
else
{
/* Everything else wants the given string in prosrc. */
@@ -919,6 +983,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
{
char *probin_str;
char *prosrc_str;
+ Node *prosqlbody;
Oid prorettype;
bool returnsSet;
char *language;
@@ -929,9 +994,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
Oid namespaceId;
AclResult aclresult;
oidvector *parameterTypes;
+ List *parameterTypes_list = NIL;
ArrayType *allParameterTypes;
ArrayType *parameterModes;
ArrayType *parameterNames;
+ List *inParameterNames_list = NIL;
List *parameterDefaults;
Oid variadicArgType;
List *trftypes_list = NIL;
@@ -962,6 +1029,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
get_namespace_name(namespaceId));
/* Set default attributes */
+ as_clause = NULL;
+ language = "sql";
isWindowFunc = false;
isStrict = false;
security = false;
@@ -1053,9 +1122,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageOid,
stmt->is_procedure ? OBJECT_PROCEDURE : OBJECT_FUNCTION,
¶meterTypes,
+ ¶meterTypes_list,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ &inParameterNames_list,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
@@ -1112,8 +1183,9 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
trftypes = NULL;
}
- interpret_AS_clause(languageOid, language, funcname, as_clause,
- &prosrc_str, &probin_str);
+ interpret_AS_clause(languageOid, language, funcname, as_clause, stmt->sql_body,
+ parameterTypes_list, inParameterNames_list,
+ &prosrc_str, &probin_str, &prosqlbody);
/*
* Set default values for COST and ROWS depending on other parameters;
@@ -1155,6 +1227,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageValidator,
prosrc_str, /* converted to text later */
probin_str, /* converted to text later */
+ prosqlbody,
stmt->is_procedure ? PROKIND_PROCEDURE : (isWindowFunc ? PROKIND_WINDOW : PROKIND_FUNCTION),
security,
isLeakProof,
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 483bb65ddc..65aa91d80f 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1588,6 +1588,7 @@ makeRangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR, /* language validator */
prosrc[i], /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 459a33375b..3ca7e89d44 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -26,6 +26,7 @@
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_func.h"
+#include "rewrite/rewriteHandler.h"
#include "storage/proc.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
@@ -128,21 +129,6 @@ typedef struct
typedef SQLFunctionCache *SQLFunctionCachePtr;
-/*
- * Data structure needed by the parser callback hooks to resolve parameter
- * references during parsing of a SQL function's body. This is separate from
- * SQLFunctionCache since we sometimes do parsing separately from execution.
- */
-typedef struct SQLFunctionParseInfo
-{
- char *fname; /* function's name */
- int nargs; /* number of input arguments */
- Oid *argtypes; /* resolved types of input arguments */
- char **argnames; /* names of input arguments; NULL if none */
- /* Note that argnames[i] can be NULL, if some args are unnamed */
- Oid collation; /* function's input collation, if known */
-} SQLFunctionParseInfo;
-
/* non-export function prototypes */
static Node *sql_fn_param_ref(ParseState *pstate, ParamRef *pref);
@@ -607,7 +593,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
HeapTuple procedureTuple;
Form_pg_proc procedureStruct;
SQLFunctionCachePtr fcache;
- List *raw_parsetree_list;
List *queryTree_list;
List *resulttlist;
ListCell *lc;
@@ -682,9 +667,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
procedureTuple,
Anum_pg_proc_prosrc,
&isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", foid);
- fcache->src = TextDatumGetCString(tmp);
/*
* Parse and rewrite the queries in the function text. Use sublists to
@@ -695,20 +677,55 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
* but we'll not worry about it until the module is rewritten to use
* plancache.c.
*/
- raw_parsetree_list = pg_parse_query(fcache->src);
-
queryTree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (isNull)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *queryTree_sublist;
-
- queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
- fcache->src,
- (ParserSetupHook) sql_fn_parser_setup,
- fcache->pinfo,
- NULL);
- queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ Node *n;
+ List *stored_query_list;
+
+ tmp = SysCacheGetAttr(PROCOID,
+ procedureTuple,
+ Anum_pg_proc_prosqlbody,
+ &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", foid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ stored_query_list = linitial_node(List, castNode(List, n));
+ else
+ stored_query_list = list_make1(n);
+
+ foreach(lc, stored_query_list)
+ {
+ Query *parsetree = lfirst_node(Query, lc);
+ List *queryTree_sublist;
+
+ AcquireRewriteLocks(parsetree, true, false);
+ queryTree_sublist = pg_rewrite_query(parsetree);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ }
+ }
+ else
+ {
+ List *raw_parsetree_list;
+
+ fcache->src = TextDatumGetCString(tmp);
+
+ raw_parsetree_list = pg_parse_query(fcache->src);
+
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *queryTree_sublist;
+
+ queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ fcache->src,
+ (ParserSetupHook) sql_fn_parser_setup,
+ fcache->pinfo,
+ NULL);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ }
}
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2b4d7654cc..b973b3b967 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3067,6 +3067,7 @@ _copyQuery(const Query *from)
COPY_SCALAR_FIELD(hasModifyingCTE);
COPY_SCALAR_FIELD(hasForUpdate);
COPY_SCALAR_FIELD(hasRowSecurity);
+ COPY_SCALAR_FIELD(isReturn);
COPY_NODE_FIELD(cteList);
COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(jointree);
@@ -3195,6 +3196,16 @@ _copySetOperationStmt(const SetOperationStmt *from)
return newnode;
}
+static ReturnStmt *
+_copyReturnStmt(const ReturnStmt *from)
+{
+ ReturnStmt *newnode = makeNode(ReturnStmt);
+
+ COPY_NODE_FIELD(returnval);
+
+ return newnode;
+}
+
static AlterTableStmt *
_copyAlterTableStmt(const AlterTableStmt *from)
{
@@ -3571,6 +3582,7 @@ _copyCreateFunctionStmt(const CreateFunctionStmt *from)
COPY_NODE_FIELD(parameters);
COPY_NODE_FIELD(returnType);
COPY_NODE_FIELD(options);
+ COPY_NODE_FIELD(sql_body);
return newnode;
}
@@ -5223,6 +5235,9 @@ copyObjectImpl(const void *from)
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
+ case T_ReturnStmt:
+ retval = _copyReturnStmt(from);
+ break;
case T_AlterTableStmt:
retval = _copyAlterTableStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b987bf..0051b1a88b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -966,6 +966,7 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_SCALAR_FIELD(hasModifyingCTE);
COMPARE_SCALAR_FIELD(hasForUpdate);
COMPARE_SCALAR_FIELD(hasRowSecurity);
+ COMPARE_SCALAR_FIELD(isReturn);
COMPARE_NODE_FIELD(cteList);
COMPARE_NODE_FIELD(rtable);
COMPARE_NODE_FIELD(jointree);
@@ -1082,6 +1083,14 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
return true;
}
+static bool
+_equalReturnStmt(const ReturnStmt *a, const ReturnStmt *b)
+{
+ COMPARE_NODE_FIELD(returnval);
+
+ return true;
+}
+
static bool
_equalAlterTableStmt(const AlterTableStmt *a, const AlterTableStmt *b)
{
@@ -1394,6 +1403,7 @@ _equalCreateFunctionStmt(const CreateFunctionStmt *a, const CreateFunctionStmt *
COMPARE_NODE_FIELD(parameters);
COMPARE_NODE_FIELD(returnType);
COMPARE_NODE_FIELD(options);
+ COMPARE_NODE_FIELD(sql_body);
return true;
}
@@ -3277,6 +3287,9 @@ equal(const void *a, const void *b)
case T_SetOperationStmt:
retval = _equalSetOperationStmt(a, b);
break;
+ case T_ReturnStmt:
+ retval = _equalReturnStmt(a, b);
+ break;
case T_AlterTableStmt:
retval = _equalAlterTableStmt(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 08a049232e..a03b97e7d2 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2756,6 +2756,14 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
WRITE_NODE_FIELD(rarg);
}
+static void
+_outReturnStmt(StringInfo str, const ReturnStmt *node)
+{
+ WRITE_NODE_TYPE("RETURN");
+
+ WRITE_NODE_FIELD(returnval);
+}
+
static void
_outFuncCall(StringInfo str, const FuncCall *node)
{
@@ -2944,6 +2952,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_BOOL_FIELD(hasModifyingCTE);
WRITE_BOOL_FIELD(hasForUpdate);
WRITE_BOOL_FIELD(hasRowSecurity);
+ WRITE_BOOL_FIELD(isReturn);
WRITE_NODE_FIELD(cteList);
WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(jointree);
@@ -4194,6 +4203,9 @@ outNode(StringInfo str, const void *obj)
case T_SelectStmt:
_outSelectStmt(str, obj);
break;
+ case T_ReturnStmt:
+ _outReturnStmt(str, obj);
+ break;
case T_ColumnDef:
_outColumnDef(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ab7b535caa..d01746d0ee 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -263,6 +263,7 @@ _readQuery(void)
READ_BOOL_FIELD(hasModifyingCTE);
READ_BOOL_FIELD(hasForUpdate);
READ_BOOL_FIELD(hasRowSecurity);
+ READ_BOOL_FIELD(isReturn);
READ_NODE_FIELD(cteList);
READ_NODE_FIELD(rtable);
READ_NODE_FIELD(jointree);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index e7d814651b..f9d27562c0 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4416,27 +4416,47 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
- /* Fetch the function body */
- tmp = SysCacheGetAttr(PROCOID,
- func_tuple,
- Anum_pg_proc_prosrc,
- &isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", funcid);
- src = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = src;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ {
+ Node *n;
+ List *querytree_list;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", funcid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = linitial_node(List, castNode(List, n));
+ else
+ querytree_list = list_make1(n);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We need a
* dummy FuncExpr node containing the already-simplified arguments to pass
@@ -4480,6 +4500,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
querytree = transformTopLevelStmt(pstate, linitial(raw_parsetree_list));
free_parsestate(pstate);
+ }
/*
* The single command must be a simple "SELECT expression".
@@ -4736,12 +4757,15 @@ sql_inline_error_callback(void *arg)
int syntaxerrposition;
/* If it's a syntax error, convert to internal syntax error report */
- syntaxerrposition = geterrposition();
- if (syntaxerrposition > 0)
+ if (callback_arg->prosrc)
{
- errposition(0);
- internalerrposition(syntaxerrposition);
- internalerrquery(callback_arg->prosrc);
+ syntaxerrposition = geterrposition();
+ if (syntaxerrposition > 0)
+ {
+ errposition(0);
+ internalerrposition(syntaxerrposition);
+ internalerrquery(callback_arg->prosrc);
+ }
}
errcontext("SQL function \"%s\" during inlining", callback_arg->proname);
@@ -4853,7 +4877,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
Oid func_oid;
HeapTuple func_tuple;
Form_pg_proc funcform;
- char *src;
Datum tmp;
bool isNull;
MemoryContext oldcxt;
@@ -4962,27 +4985,53 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
- /* Fetch the function body */
- tmp = SysCacheGetAttr(PROCOID,
- func_tuple,
- Anum_pg_proc_prosrc,
- &isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", func_oid);
- src = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = src;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ {
+ Node *n;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", func_oid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = linitial_node(List, castNode(List, n));
+ else
+ querytree_list = list_make1(n);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+
+ querytree_list = pg_rewrite_query(querytree);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
+ char *src;
+
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We can
* use the FuncExpr just created as the input for
@@ -4992,18 +5041,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
(Node *) fexpr,
fexpr->inputcollid);
- /*
- * Also resolve the actual function result tupdesc, if composite. If the
- * function is just declared to return RECORD, dig the info out of the AS
- * clause.
- */
- functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
- if (functypclass == TYPEFUNC_RECORD)
- rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
- rtfunc->funccoltypes,
- rtfunc->funccoltypmods,
- rtfunc->funccolcollations);
-
/*
* Parse, analyze, and rewrite (unlike inline_function(), we can't skip
* rewriting here). We can fail as soon as we find more than one query,
@@ -5020,6 +5057,19 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
if (list_length(querytree_list) != 1)
goto fail;
querytree = linitial(querytree_list);
+ }
+
+ /*
+ * Also resolve the actual function result tupdesc, if composite. If the
+ * function is just declared to return RECORD, dig the info out of the AS
+ * clause.
+ */
+ functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
+ if (functypclass == TYPEFUNC_RECORD)
+ rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
+ rtfunc->funccoltypes,
+ rtfunc->funccoltypmods,
+ rtfunc->funccolcollations);
/*
* The single command must be a plain SELECT.
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c159fb2957..ed0622c5d7 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -66,6 +66,7 @@ static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
bool isTopLevel, List **targetlist);
static void determineRecursiveColTypes(ParseState *pstate,
Node *larg, List *nrtargetlist);
+static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList);
static List *transformUpdateTargetList(ParseState *pstate,
@@ -304,6 +305,10 @@ transformStmt(ParseState *pstate, Node *parseTree)
}
break;
+ case T_ReturnStmt:
+ result = transformReturnStmt(pstate, (ReturnStmt *) parseTree);
+ break;
+
/*
* Special cases
*/
@@ -2221,6 +2226,36 @@ determineRecursiveColTypes(ParseState *pstate, Node *larg, List *nrtargetlist)
}
+/*
+ * transformReturnStmt -
+ * transforms a return statement
+ */
+static Query *
+transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+
+ qry->commandType = CMD_SELECT;
+ qry->isReturn = true;
+
+ qry->targetList = list_make1(makeTargetEntry((Expr *) transformExpr(pstate, stmt->returnval, EXPR_KIND_SELECT_TARGET),
+ 1, NULL, false));
+
+ if (pstate->p_resolve_unknowns)
+ resolveTargetListUnknowns(pstate, qry->targetList);
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasAggs = pstate->p_hasAggs;
+
+ assign_query_collations(pstate, qry);
+
+ return qry;
+}
+
+
/*
* transformUpdateStmt -
* transforms an update statement
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 480d168346..e0f49a552f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -253,7 +253,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct SelectLimit *selectlimit;
}
-%type <node> stmt schema_stmt
+%type <node> stmt toplevel_stmt schema_stmt routine_body_stmt
AlterEventTrigStmt AlterCollationStmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
@@ -280,9 +280,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
- RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
+ RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt
RuleActionStmt RuleActionStmtOrEmpty RuleStmt
- SecLabelStmt SelectStmt TransactionStmt TruncateStmt
+ SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
UnlistenStmt UpdateStmt VacuumStmt
VariableResetStmt VariableSetStmt VariableShowStmt
ViewStmt CheckPointStmt CreateConversionStmt
@@ -385,14 +385,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> vacuum_relation
%type <selectlimit> opt_select_limit select_limit limit_clause
-%type <list> stmtblock stmtmulti
+%type <list> stmtblock stmtmulti routine_body_stmt_list
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
reloptions opt_reloptions
OptWith distinct_clause opt_all_clause opt_definition func_args func_args_list
func_args_with_defaults func_args_with_defaults_list
aggr_args aggr_args_list
- func_as createfunc_opt_list alterfunc_opt_list
+ func_as createfunc_opt_list opt_createfunc_opt_list alterfunc_opt_list
old_aggr_definition old_aggr_list
oper_argtypes RuleActionList RuleActionMulti
opt_column_list columnList opt_name_list
@@ -418,6 +418,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
vacuum_relation_list opt_vacuum_relation_list
drop_option_list
+%type <node> opt_routine_body
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
@@ -628,7 +629,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATOMIC ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
@@ -690,7 +691,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING
REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
- RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
+ RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
@@ -812,7 +813,7 @@ stmtblock: stmtmulti
* we'd get -1 for the location in such cases.
* We also take care to discard empty statements entirely.
*/
-stmtmulti: stmtmulti ';' stmt
+stmtmulti: stmtmulti ';' toplevel_stmt
{
if ($1 != NIL)
{
@@ -824,7 +825,7 @@ stmtmulti: stmtmulti ';' stmt
else
$$ = $1;
}
- | stmt
+ | toplevel_stmt
{
if ($1 != NULL)
$$ = list_make1(makeRawStmt($1, 0));
@@ -833,7 +834,16 @@ stmtmulti: stmtmulti ';' stmt
}
;
-stmt :
+/*
+ * toplevel_stmt includes BEGIN and END. stmt does not include them, because
+ * those words have different meanings in function bodys.
+ */
+toplevel_stmt:
+ stmt
+ | TransactionStmtLegacy
+ ;
+
+stmt:
AlterEventTrigStmt
| AlterCollationStmt
| AlterDatabaseStmt
@@ -7339,7 +7349,7 @@ opt_nulls_order: NULLS_LA FIRST_P { $$ = SORTBY_NULLS_FIRST; }
CreateFunctionStmt:
CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS func_return createfunc_opt_list
+ RETURNS func_return opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7348,10 +7358,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = $7;
n->options = $8;
+ n->sql_body = $9;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS TABLE '(' table_func_column_list ')' createfunc_opt_list
+ RETURNS TABLE '(' table_func_column_list ')' opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7361,10 +7372,11 @@ CreateFunctionStmt:
n->returnType = TableFuncTypeName($9);
n->returnType->location = @7;
n->options = $11;
+ n->sql_body = $12;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7373,10 +7385,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
| CREATE opt_or_replace PROCEDURE func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = true;
@@ -7385,6 +7398,7 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
;
@@ -7695,6 +7709,11 @@ aggregate_with_argtypes_list:
{ $$ = lappend($1, $3); }
;
+opt_createfunc_opt_list:
+ createfunc_opt_list
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
createfunc_opt_list:
/* Must be at least one to prevent conflict */
createfunc_opt_item { $$ = list_make1($1); }
@@ -7806,6 +7825,51 @@ func_as: Sconst { $$ = list_make1(makeString($1)); }
}
;
+ReturnStmt: RETURN a_expr
+ {
+ ReturnStmt *r = makeNode(ReturnStmt);
+ r->returnval = (Node *) $2;
+ $$ = (Node *) r;
+ }
+ ;
+
+opt_routine_body:
+ ReturnStmt
+ {
+ $$ = $1;
+ }
+ | BEGIN_P ATOMIC routine_body_stmt_list END_P
+ {
+ /*
+ * A compound statement is stored as a single-item list
+ * containing the list of statements as its member. That
+ * way, the parse analysis code can tell apart an empty
+ * body from no body at all.
+ */
+ $$ = (Node *) list_make1($3);
+ }
+ | /*EMPTY*/
+ {
+ $$ = NULL;
+ }
+ ;
+
+routine_body_stmt_list:
+ routine_body_stmt_list routine_body_stmt ';'
+ {
+ $$ = lappend($1, $2);
+ }
+ | /*EMPTY*/
+ {
+ $$ = NIL;
+ }
+ ;
+
+routine_body_stmt:
+ stmt
+ | ReturnStmt
+ ;
+
transform_type_list:
FOR TYPE_P Typename { $$ = list_make1($3); }
| transform_type_list ',' FOR TYPE_P Typename { $$ = lappend($1, $5); }
@@ -9756,13 +9820,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | BEGIN_P opt_transaction transaction_mode_list_or_empty
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_BEGIN;
- n->options = $3;
- $$ = (Node *)n;
- }
| START TRANSACTION transaction_mode_list_or_empty
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9778,14 +9835,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | END_P opt_transaction opt_transaction_chain
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_COMMIT;
- n->options = NIL;
- n->chain = $3;
- $$ = (Node *)n;
- }
| ROLLBACK opt_transaction opt_transaction_chain
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9852,6 +9901,24 @@ TransactionStmt:
}
;
+TransactionStmtLegacy:
+ BEGIN_P opt_transaction transaction_mode_list_or_empty
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_BEGIN;
+ n->options = $3;
+ $$ = (Node *)n;
+ }
+ | END_P opt_transaction opt_transaction_chain
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_COMMIT;
+ n->options = NIL;
+ n->chain = $3;
+ $$ = (Node *)n;
+ }
+ ;
+
opt_transaction: WORK {}
| TRANSACTION {}
| /*EMPTY*/ {}
@@ -15074,6 +15141,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| BACKWARD
@@ -15272,6 +15340,7 @@ unreserved_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| ROLE
@@ -15578,6 +15647,7 @@ bare_label_keyword:
| ASSIGNMENT
| ASYMMETRIC
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| AUTHORIZATION
@@ -15848,6 +15918,7 @@ bare_label_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| RIGHT
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 411cfadbff..e145f552c0 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -176,7 +176,6 @@ static int interactive_getc(void);
static int SocketBackend(StringInfo inBuf);
static int ReadCommand(StringInfo inBuf);
static void forbidden_in_wal_sender(char firstchar);
-static List *pg_rewrite_query(Query *query);
static bool check_log_statement(List *stmt_list);
static int errdetail_execute(List *raw_parsetree_list);
static int errdetail_params(ParamListInfo params);
@@ -761,7 +760,7 @@ pg_analyze_and_rewrite_params(RawStmt *parsetree,
* Note: query must just have come from the parser, because we do not do
* AcquireRewriteLocks() on it.
*/
-static List *
+List *
pg_rewrite_query(Query *query)
{
List *querytree_list;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 6c656586e8..6a52686cca 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -174,6 +174,9 @@ typedef struct
List *outer_tlist; /* referent for OUTER_VAR Vars */
List *inner_tlist; /* referent for INNER_VAR Vars */
List *index_tlist; /* referent for INDEX_VAR Vars */
+ /* Special namespace representing a function signature: */
+ int numargs;
+ char **argnames;
} deparse_namespace;
/*
@@ -349,6 +352,7 @@ static int print_function_arguments(StringInfo buf, HeapTuple proctup,
bool print_table_args, bool print_defaults);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
static void print_function_trftypes(StringInfo buf, HeapTuple proctup);
+static void print_function_sqlbody(StringInfo buf, HeapTuple proctup);
static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used);
static void set_deparse_for_query(deparse_namespace *dpns, Query *query,
@@ -2800,6 +2804,13 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
}
/* And finally the function definition ... */
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
+ if (proc->prolang == SQLlanguageId && !isnull)
+ {
+ print_function_sqlbody(&buf, proctup);
+ }
+ else
+ {
appendStringInfoString(&buf, "AS ");
tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
@@ -2831,6 +2842,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, dq.data, dq.len);
appendStringInfoString(&buf, prosrc);
appendBinaryStringInfo(&buf, dq.data, dq.len);
+ }
appendStringInfoChar(&buf, '\n');
@@ -3213,6 +3225,75 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(string_to_text(str));
}
+static void
+print_function_sqlbody(StringInfo buf, HeapTuple proctup)
+{
+ int numargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ deparse_namespace dpns = {0};
+ Datum tmp;
+ bool isnull;
+ Node *n;
+
+ numargs = get_func_arg_info(proctup,
+ &argtypes, &argnames, &argmodes);
+ dpns.numargs = numargs;
+ dpns.argnames = argnames;
+
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
+ Assert(!isnull);
+ n = stringToNode(TextDatumGetCString(tmp));
+
+ if (IsA(n, List))
+ {
+ List *stmts;
+ ListCell *lc;
+
+ stmts = linitial(castNode(List, n));
+
+ appendStringInfoString(buf, "BEGIN ATOMIC\n");
+
+ foreach(lc, stmts)
+ {
+ Query *query = lfirst_node(Query, lc);
+
+ get_query_def(query, buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ appendStringInfoChar(buf, ';');
+ appendStringInfoChar(buf, '\n');
+ }
+
+ appendStringInfoString(buf, "END");
+ }
+ else
+ {
+ get_query_def(castNode(Query, n), buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ appendStringInfoChar(buf, '\n');
+ }
+}
+
+Datum
+pg_get_function_sqlbody(PG_FUNCTION_ARGS)
+{
+ Oid funcid = PG_GETARG_OID(0);
+ StringInfoData buf;
+ HeapTuple proctup;
+
+ initStringInfo(&buf);
+
+ /* Look up the function */
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ PG_RETURN_NULL();
+
+ print_function_sqlbody(&buf, proctup);
+
+ ReleaseSysCache(proctup);
+
+ PG_RETURN_TEXT_P(cstring_to_text(buf.data));
+}
+
/*
* deparse_expression - General utility for deparsing expressions
@@ -5418,7 +5499,10 @@ get_basic_select_query(Query *query, deparse_context *context,
/*
* Build up the query string - first we say SELECT
*/
- appendStringInfoString(buf, "SELECT");
+ if (query->isReturn)
+ appendStringInfoString(buf, "RETURN");
+ else
+ appendStringInfoString(buf, "SELECT");
/* Add the DISTINCT clause if given */
if (query->distinctClause != NIL)
@@ -7550,6 +7634,26 @@ get_parameter(Param *param, deparse_context *context)
return;
}
+ /*
+ * If it's an external parameter, see if the outermost namespace provides
+ * function argument names.
+ */
+ if (param->paramkind == PARAM_EXTERN)
+ {
+ dpns = lfirst(list_tail(context->namespaces));
+ if (dpns->argnames)
+ {
+ char *argname = dpns->argnames[param->paramid - 1];
+
+ if (argname)
+ {
+ // TODO: qualify with function name if necessary
+ appendStringInfo(context->buf, "%s", quote_identifier(argname));
+ return;
+ }
+ }
+ }
+
/*
* Not PARAM_EXEC, or couldn't find referent: just print $N.
*/
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ff45e3fb8c..8acbab80c8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11803,6 +11803,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
char *proretset;
char *prosrc;
char *probin;
+ char *prosqlbody;
char *funcargs;
char *funciargs;
char *funcresult;
@@ -11849,7 +11850,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"provolatile,\n"
"proisstrict,\n"
"prosecdef,\n"
- "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname,\n");
+ "lanname,\n");
if (fout->remoteVersion >= 80300)
appendPQExpBufferStr(query,
@@ -11869,9 +11870,9 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
* pg_get_function_result instead of examining proallargtypes etc.
*/
appendPQExpBufferStr(query,
- "pg_catalog.pg_get_function_arguments(oid) AS funcargs,\n"
- "pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs,\n"
- "pg_catalog.pg_get_function_result(oid) AS funcresult,\n");
+ "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n"
+ "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n"
+ "pg_catalog.pg_get_function_result(p.oid) AS funcresult,\n");
}
else if (fout->remoteVersion >= 80100)
appendPQExpBufferStr(query,
@@ -11914,21 +11915,39 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
if (fout->remoteVersion >= 120000)
appendPQExpBufferStr(query,
- "prosupport\n");
+ "prosupport,\n");
else
appendPQExpBufferStr(query,
- "'-' AS prosupport\n");
+ "'-' AS prosupport,\n");
+
+ if (fout->remoteVersion >= 140000)
+ appendPQExpBufferStr(query,
+ "CASE WHEN prosrc IS NULL AND lanname = 'sql' THEN pg_get_function_sqlbody(p.oid) END AS prosqlbody\n");
+ else
+ appendPQExpBufferStr(query,
+ "NULL AS prosqlbody\n");
appendPQExpBuffer(query,
- "FROM pg_catalog.pg_proc "
- "WHERE oid = '%u'::pg_catalog.oid",
+ "FROM pg_catalog.pg_proc p, pg_catalog.pg_language l\n"
+ "WHERE p.oid = '%u'::pg_catalog.oid "
+ "AND l.oid = p.prolang",
finfo->dobj.catId.oid);
res = ExecuteSqlQueryForSingleRow(fout, query->data);
proretset = PQgetvalue(res, 0, PQfnumber(res, "proretset"));
- prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
- probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ if (PQgetisnull(res, 0, PQfnumber(res, "prosqlbody")))
+ {
+ prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
+ probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ prosqlbody = NULL;
+ }
+ else
+ {
+ prosrc = NULL;
+ probin = NULL;
+ prosqlbody = PQgetvalue(res, 0, PQfnumber(res, "prosqlbody"));
+ }
if (fout->remoteVersion >= 80400)
{
funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
@@ -11965,7 +11984,11 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
* versions would set it to "-". There are no known cases in which prosrc
* is unused, so the tests below for "-" are probably useless.
*/
- if (probin[0] != '\0' && strcmp(probin, "-") != 0)
+ if (prosqlbody)
+ {
+ appendPQExpBufferStr(asPart, prosqlbody);
+ }
+ else if (probin[0] != '\0' && strcmp(probin, "-") != 0)
{
appendPQExpBufferStr(asPart, "AS ");
appendStringLiteralAH(asPart, probin, fout);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 07d640021c..319c34ff2b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -505,11 +505,18 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
appendPQExpBufferStr(&buf, ",\n ");
printACLColumn(&buf, "p.proacl");
appendPQExpBuffer(&buf,
- ",\n l.lanname as \"%s\""
- ",\n p.prosrc as \"%s\""
+ ",\n l.lanname as \"%s\"",
+ gettext_noop("Language"));
+ if (pset.sversion >= 140000)
+ appendPQExpBuffer(&buf,
+ ",\n COALESCE(p.prosrc, pg_catalog.pg_get_function_sqlbody(p.oid)) as \"%s\"",
+ gettext_noop("Source code"));
+ else
+ appendPQExpBuffer(&buf,
+ ",\n p.prosrc as \"%s\"",
+ gettext_noop("Source code"));
+ appendPQExpBuffer(&buf,
",\n pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"",
- gettext_noop("Language"),
- gettext_noop("Source code"),
gettext_noop("Description"));
}
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 08dffde1ba..ee34463e67 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -645,10 +645,11 @@ other .
";" {
ECHO;
- if (cur_state->paren_depth == 0)
+ if (cur_state->paren_depth == 0 && cur_state->begin_depth == 0)
{
/* Terminate lexing temporarily */
cur_state->start_state = YY_START;
+ cur_state->identifier_count = 0;
return LEXRES_SEMI;
}
}
@@ -661,6 +662,8 @@ other .
"\\"[;:] {
/* Force a semi-colon or colon into the query buffer */
psqlscan_emit(cur_state, yytext + 1, 1);
+ if (yytext[1] == ';')
+ cur_state->identifier_count = 0;
}
"\\" {
@@ -867,6 +870,17 @@ other .
{identifier} {
+ cur_state->identifier_count++;
+ if (pg_strcasecmp(yytext, "begin") == 0)
+ {
+ if (cur_state->identifier_count > 1)
+ cur_state->begin_depth++;
+ }
+ else if (pg_strcasecmp(yytext, "end") == 0)
+ {
+ if (cur_state->begin_depth > 0)
+ cur_state->begin_depth--;
+ }
ECHO;
}
@@ -1054,6 +1068,11 @@ psql_scan(PsqlScanState state,
result = PSCAN_INCOMPLETE;
*prompt = PROMPT_PAREN;
}
+ if (state->begin_depth > 0)
+ {
+ result = PSCAN_INCOMPLETE;
+ *prompt = PROMPT_CONTINUE;
+ }
else if (query_buf->len > 0)
{
result = PSCAN_EOL;
@@ -1170,6 +1189,8 @@ psql_scan_reset(PsqlScanState state)
if (state->dolqstart)
free(state->dolqstart);
state->dolqstart = NULL;
+ state->identifier_count = 0;
+ state->begin_depth = 0;
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bbcac69d48..71bf2d8cc6 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3681,6 +3681,10 @@
proname => 'pg_get_function_arg_default', provolatile => 's',
prorettype => 'text', proargtypes => 'oid int4',
prosrc => 'pg_get_function_arg_default' },
+{ oid => '9704', descr => 'function SQL body',
+ proname => 'pg_get_function_sqlbody', provolatile => 's',
+ prorettype => 'text', proargtypes => 'oid',
+ prosrc => 'pg_get_function_sqlbody' },
{ oid => '1686', descr => 'list of SQL keywords',
proname => 'pg_get_keywords', procost => '10', prorows => '500',
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 268c810896..720d6c77ad 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -112,11 +112,14 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
Oid protrftypes[1] BKI_DEFAULT(_null_);
/* procedure source text */
- text prosrc BKI_FORCE_NOT_NULL;
+ text prosrc;
/* secondary procedure info (can be NULL) */
text probin BKI_DEFAULT(_null_);
+ /* pre-parsed SQL function body */
+ pg_node_tree prosqlbody BKI_DEFAULT(_null_);
+
/* procedure-local GUC settings */
text proconfig[1] BKI_DEFAULT(_null_);
@@ -187,6 +190,7 @@ extern ObjectAddress ProcedureCreate(const char *procedureName,
Oid languageValidator,
const char *prosrc,
const char *probin,
+ Node *prosqlbody,
char prokind,
bool security_definer,
bool isLeakProof,
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 7a079ef07f..a32cfba421 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -68,9 +68,11 @@ extern void interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType);
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index a0db24bde6..3111ff824a 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -20,6 +20,21 @@
/* This struct is known only within executor/functions.c */
typedef struct SQLFunctionParseInfo *SQLFunctionParseInfoPtr;
+/*
+ * Data structure needed by the parser callback hooks to resolve parameter
+ * references during parsing of a SQL function's body. This is separate from
+ * SQLFunctionCache since we sometimes do parsing separately from execution.
+ */
+typedef struct SQLFunctionParseInfo
+{
+ char *fname; /* function's name */
+ int nargs; /* number of input arguments */
+ Oid *argtypes; /* resolved types of input arguments */
+ char **argnames; /* names of input arguments; NULL if none */
+ /* Note that argnames[i] can be NULL, if some args are unnamed */
+ Oid collation; /* function's input collation, if known */
+} SQLFunctionParseInfo;
+
extern Datum fmgr_sql(PG_FUNCTION_ARGS);
extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTuple,
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 311f80394a..fb8f58aa29 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -114,6 +114,8 @@ typedef struct PsqlScanStateData
int paren_depth; /* depth of nesting in parentheses */
int xcdepth; /* depth of nesting in slash-star comments */
char *dolqstart; /* current $foo$ quote start string */
+ int identifier_count; /* identifiers since start of statement */
+ int begin_depth; /* depth of begin/end routine body blocks */
/*
* Callback functions provided by the program making use of the lexer,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7ddd8c011b..f5716a3eb0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -315,6 +315,7 @@ typedef enum NodeTag
T_DeleteStmt,
T_UpdateStmt,
T_SelectStmt,
+ T_ReturnStmt,
T_AlterTableStmt,
T_AlterTableCmd,
T_AlterDomainStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 60c2f45466..f0c0c307f9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -132,6 +132,8 @@ typedef struct Query
bool hasForUpdate; /* FOR [KEY] UPDATE/SHARE was specified */
bool hasRowSecurity; /* rewriter has applied some RLS policy */
+ bool isReturn; /* is a RETURN statement */
+
List *cteList; /* WITH list (of CommonTableExpr's) */
List *rtable; /* list of range table entries */
@@ -1671,6 +1673,16 @@ typedef struct SetOperationStmt
} SetOperationStmt;
+/*
+ * RETURN statement (inside SQL function body)
+ */
+typedef struct ReturnStmt
+{
+ NodeTag type;
+ Node *returnval;
+} ReturnStmt;
+
+
/*****************************************************************************
* Other Statements (no optimizations required)
*
@@ -2842,6 +2854,7 @@ typedef struct CreateFunctionStmt
List *parameters; /* a list of FunctionParameter */
TypeName *returnType; /* the return type */
List *options; /* a list of DefElem */
+ Node *sql_body;
} CreateFunctionStmt;
typedef enum FunctionParameterMode
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 71dcdf2889..36317a14db 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -48,6 +48,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("atomic", ATOMIC, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -344,6 +345,7 @@ PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("return", RETURN, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD, AS_LABEL)
PG_KEYWORD("returns", RETURNS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index bd30607b07..e626c8eafd 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -43,6 +43,7 @@ typedef enum
extern PGDLLIMPORT int log_statement;
extern List *pg_parse_query(const char *query_string);
+extern List *pg_rewrite_query(Query *query);
extern List *pg_analyze_and_rewrite(RawStmt *parsetree,
const char *query_string,
Oid *paramTypes, int numParams,
diff --git a/src/interfaces/ecpg/preproc/ecpg.addons b/src/interfaces/ecpg/preproc/ecpg.addons
index 300381eaad..0441561c52 100644
--- a/src/interfaces/ecpg/preproc/ecpg.addons
+++ b/src/interfaces/ecpg/preproc/ecpg.addons
@@ -87,6 +87,12 @@ ECPG: stmtTransactionStmt block
whenever_action(2);
free($1);
}
+ECPG: toplevel_stmtTransactionStmtLegacy block
+ {
+ fprintf(base_yyout, "{ ECPGtrans(__LINE__, %s, \"%s\");", connection ? connection : "NULL", $1);
+ whenever_action(2);
+ free($1);
+ }
ECPG: stmtViewStmt rule
| ECPGAllocateDescr
{
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer
index 6ccc8ab916..7f4be655f9 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -4,8 +4,8 @@ statements: /*EMPTY*/
| statements statement
;
-statement: ecpgstart at stmt ';' { connection = NULL; }
- | ecpgstart stmt ';'
+statement: ecpgstart at toplevel_stmt ';' { connection = NULL; }
+ | ecpgstart toplevel_stmt ';'
| ecpgstart ECPGVarDeclaration
{
fprintf(base_yyout, "%s", $2);
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index ce508ae1dc..dbd6358ffd 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -255,6 +255,151 @@ SELECT pg_get_functiondef('functest_F_2'::regproc);
(1 row)
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_3a() RETURNS boolean
+ BEGIN ATOMIC
+ RETURN false;
+ END;
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+ERROR: SQL function with unquoted function body cannot have polymorphic arguments
+SELECT functest_S_1('abcd', '2020-01-01');
+ functest_s_1
+--------------
+ t
+(1 row)
+
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+ functest_s_2
+--------------
+ 1
+(1 row)
+
+SELECT functest_S_3();
+ functest_s_3
+--------------
+ f
+(1 row)
+
+SELECT functest_S_10('abcd', '2020-01-01');
+ functest_s_10
+---------------
+ t
+(1 row)
+
+SELECT functest_S_13();
+ functest_s_13
+---------------
+ f
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_1(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN ((a = 'abcd'::text) AND (b > '01-01-2001'::date)) +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_2(a text[])+
+ RETURNS integer +
+ LANGUAGE sql +
+ RETURN ((a)[1])::integer +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+ pg_get_functiondef
+----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN false +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3a'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3a()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ RETURN false; +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+ pg_get_functiondef
+-------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_10(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT ((a = 'abcd'::text) AND (b > '01-01-2001'::date)); +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_13()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT 1; +
+ SELECT false AS bool; +
+ END +
+
+(1 row)
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+SELECT functest_S_14();
+ functest_s_14
+---------------
+ 2
+(1 row)
+
+DROP TABLE functest3 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view functestv3
+drop cascades to function functest_s_14()
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
RETURNS int
@@ -284,6 +429,20 @@ SELECT routine_name, ordinal_position, parameter_name, parameter_default
(7 rows)
DROP FUNCTION functest_IS_1(int, int, text), functest_IS_2(int), functest_IS_3(int);
+CREATE TABLE functest1 (a int, b int);
+CREATE SEQUENCE functest2;
+CREATE FUNCTION functest_IS_4()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest1);
+CREATE FUNCTION functest_IS_5()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest2');
+DROP TABLE functest1 CASCADE;
+NOTICE: drop cascades to function functest_is_4()
+DROP SEQUENCE functest2 CASCADE;
+NOTICE: drop cascades to function functest_is_5()
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
@@ -302,6 +461,49 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
ERROR: cannot change routine kind
DETAIL: "functest1" is a function.
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+SELECT * FROM functest_sri1();
+ functest_sri1
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+SELECT * FROM functest_sri2();
+ functest_sri2
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
$$ SELECT a + 1 $$;
@@ -360,7 +562,7 @@ SELECT * FROM voidtest5(3);
-- Cleanup
DROP SCHEMA temp_func_test CASCADE;
-NOTICE: drop cascades to 21 other objects
+NOTICE: drop cascades to 29 other objects
DETAIL: drop cascades to function functest_a_1(text,date)
drop cascades to function functest_a_2(text[])
drop cascades to function functest_a_3()
@@ -376,7 +578,15 @@ drop cascades to function functest_f_1(integer)
drop cascades to function functest_f_2(integer)
drop cascades to function functest_f_3(integer)
drop cascades to function functest_f_4(integer)
+drop cascades to function functest_s_1(text,date)
+drop cascades to function functest_s_2(text[])
+drop cascades to function functest_s_3()
+drop cascades to function functest_s_3a()
+drop cascades to function functest_s_10(text,date)
+drop cascades to function functest_s_13()
drop cascades to function functest_b_2(bigint)
+drop cascades to function functest_sri1()
+drop cascades to function functest_sri2()
drop cascades to function voidtest1(integer)
drop cascades to function voidtest2(integer,integer)
drop cascades to function voidtest3(integer)
diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out
index 3838fa2324..5d634570c4 100644
--- a/src/test/regress/expected/create_procedure.out
+++ b/src/test/regress/expected/create_procedure.out
@@ -65,6 +65,41 @@ SELECT * FROM cp_test ORDER BY b COLLATE "C";
1 | xyzzy
(3 rows)
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+\df ptest1s
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+---------+------------------+---------------------+------
+ public | ptest1s | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest1s'::regproc);
+ pg_get_functiondef
+----------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest1s(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ INSERT INTO cp_test (a, b) +
+ VALUES (1, x); +
+ END +
+
+(1 row)
+
+CALL ptest1s('b');
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+ a | b
+---+-------
+ 1 | 0
+ 1 | a
+ 1 | b
+ 1 | xyzzy
+(4 rows)
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -146,6 +181,28 @@ AS $$
SELECT a = b;
$$;
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+\df ptest8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+--------+------------------+---------------------+------
+ public | ptest8 | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest8'::regproc);
+ pg_get_functiondef
+---------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest8(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ END +
+
+(1 row)
+
+CALL ptest8('');
-- OUT parameters
CREATE PROCEDURE ptest9(OUT a int)
LANGUAGE SQL
@@ -214,6 +271,7 @@ ALTER ROUTINE ptest1a RENAME TO ptest1;
DROP ROUTINE cp_testfunc1(int);
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
DROP USER regress_cp_user1;
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
index bd108a918f..092e15b591 100644
--- a/src/test/regress/sql/create_function_3.sql
+++ b/src/test/regress/sql/create_function_3.sql
@@ -153,6 +153,65 @@ CREATE FUNCTION functest_F_4(int) RETURNS bool LANGUAGE 'sql'
SELECT pg_get_functiondef('functest_F_2'::regproc);
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_3a() RETURNS boolean
+ BEGIN ATOMIC
+ RETURN false;
+ END;
+
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+
+SELECT functest_S_1('abcd', '2020-01-01');
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+SELECT functest_S_3();
+
+SELECT functest_S_10('abcd', '2020-01-01');
+SELECT functest_S_13();
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+SELECT pg_get_functiondef('functest_S_3a'::regproc);
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+
+SELECT functest_S_14();
+
+DROP TABLE functest3 CASCADE;
+
+
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
@@ -177,6 +236,23 @@ CREATE FUNCTION functest_IS_3(a int default 1, out b int)
DROP FUNCTION functest_IS_1(int, int, text), functest_IS_2(int), functest_IS_3(int);
+CREATE TABLE functest1 (a int, b int);
+CREATE SEQUENCE functest2;
+
+CREATE FUNCTION functest_IS_4()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest1);
+
+CREATE FUNCTION functest_IS_5()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest2');
+
+DROP TABLE functest1 CASCADE;
+DROP SEQUENCE functest2 CASCADE;
+
+
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
@@ -194,6 +270,29 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+
+SELECT * FROM functest_sri1();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+
+SELECT * FROM functest_sri2();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
diff --git a/src/test/regress/sql/create_procedure.sql b/src/test/regress/sql/create_procedure.sql
index 2ef1c82cea..8c0d70cb16 100644
--- a/src/test/regress/sql/create_procedure.sql
+++ b/src/test/regress/sql/create_procedure.sql
@@ -28,6 +28,21 @@ CREATE PROCEDURE ptest1(x text)
SELECT * FROM cp_test ORDER BY b COLLATE "C";
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+
+\df ptest1s
+SELECT pg_get_functiondef('ptest1s'::regproc);
+
+CALL ptest1s('b');
+
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -112,6 +127,16 @@ CREATE PROCEDURE ptest7(a text, b text)
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+
+\df ptest8
+SELECT pg_get_functiondef('ptest8'::regproc);
+CALL ptest8('');
+
+
-- OUT parameters
CREATE PROCEDURE ptest9(OUT a int)
@@ -170,6 +195,7 @@ CREATE USER regress_cp_user1;
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
base-commit: 2f0760c9ff827bb3d23ee327e6b46038000c7ef9
--
2.28.0
Hi,
I noticed that this patch fails on the cfbot.
For this, I changed the status to: 'Waiting on Author'.
Cheers,
//Georgios
The new status of this patch is: Waiting on Author
On 2020-11-10 16:21, Georgios Kokolatos wrote:
Hi,
I noticed that this patch fails on the cfbot.
For this, I changed the status to: 'Waiting on Author'.Cheers,
//GeorgiosThe new status of this patch is: Waiting on Author
Here is an updated patch to get it building again.
--
Peter Eisentraut
2ndQuadrant, an EDB company
https://www.2ndquadrant.com/
Attachments:
v6-0001-SQL-standard-function-body.patchtext/plain; charset=UTF-8; name=v6-0001-SQL-standard-function-body.patch; x-mac-creator=0; x-mac-type=0Download
From 5fa3855abd88e0174cb00308a996819440b7e6b9 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Fri, 20 Nov 2020 08:23:30 +0100
Subject: [PATCH v6] SQL-standard function body
This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.
Instead of the PostgreSQL-specific AS $$ string literal $$ syntax,
this allows writing out the SQL statements making up the body
unquoted, either as a single statement:
CREATE FUNCTION add(a integer, b integer) RETURNS integer
LANGUAGE SQL
RETURN a + b;
or as a block
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
BEGIN ATOMIC
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
END;
The function body is parsed at function definition time and stored as
expression nodes in a new pg_proc column prosqlbody. So at run time,
no further parsing is required.
However, this form does not support polymorphic arguments, because
there is no more parse analysis done at call time.
Dependencies between the function and the objects it uses are fully
tracked.
A new RETURN statement is introduced. This can only be used inside
function bodies. Internally, it is treated much like a SELECT
statement.
psql needs some new intelligence to keep track of function body
boundaries so that it doesn't send off statements when it sees
semicolons that are inside a function body.
Also, per SQL standard, LANGUAGE SQL is the default, so it does not
need to be specified anymore.
Discussion: https://www.postgresql.org/message-id/flat/1c11f1eb-f00c-43b7-799d-2d44132c02d7@2ndquadrant.com
---
doc/src/sgml/catalogs.sgml | 10 +
doc/src/sgml/ref/create_function.sgml | 126 +++++++++--
doc/src/sgml/ref/create_procedure.sgml | 62 ++++-
src/backend/catalog/pg_aggregate.c | 1 +
src/backend/catalog/pg_proc.c | 116 ++++++----
src/backend/commands/aggregatecmds.c | 2 +
src/backend/commands/functioncmds.c | 119 ++++++++--
src/backend/commands/typecmds.c | 1 +
src/backend/executor/functions.c | 79 ++++---
src/backend/nodes/copyfuncs.c | 15 ++
src/backend/nodes/equalfuncs.c | 13 ++
src/backend/nodes/outfuncs.c | 12 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/util/clauses.c | 126 +++++++----
src/backend/parser/analyze.c | 35 +++
src/backend/parser/gram.y | 129 ++++++++---
src/backend/tcop/postgres.c | 3 +-
src/backend/utils/adt/ruleutils.c | 106 ++++++++-
src/bin/pg_dump/pg_dump.c | 45 +++-
src/bin/psql/describe.c | 15 +-
src/fe_utils/psqlscan.l | 23 +-
src/include/catalog/pg_proc.dat | 4 +
src/include/catalog/pg_proc.h | 6 +-
src/include/commands/defrem.h | 2 +
src/include/executor/functions.h | 15 ++
src/include/fe_utils/psqlscan_int.h | 2 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 13 ++
src/include/parser/kwlist.h | 2 +
src/include/tcop/tcopprot.h | 1 +
src/interfaces/ecpg/preproc/ecpg.addons | 6 +
src/interfaces/ecpg/preproc/ecpg.trailer | 4 +-
.../regress/expected/create_function_3.out | 212 +++++++++++++++++-
.../regress/expected/create_procedure.out | 58 +++++
src/test/regress/sql/create_function_3.sql | 99 ++++++++
src/test/regress/sql/create_procedure.sql | 26 +++
36 files changed, 1286 insertions(+), 204 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 569841398b..fda2e46d4b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -5973,6 +5973,16 @@ <title><structname>pg_proc</structname> Columns</title>
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>prosqlbody</structfield> <type>pg_node_tree</type>
+ </para>
+ <para>
+ Pre-parsed SQL function body. This will be used for language SQL
+ functions if the body is not specified as a string constant.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>proconfig</structfield> <type>text[]</type>
diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index 3c1eaea651..1b5b9420db 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -38,6 +38,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -257,8 +258,9 @@ <title>Parameters</title>
The name of the language that the function is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -577,6 +579,44 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> function. This can
+ either be a single statement
+<programlisting>
+RETURN <replaceable>expression</replaceable>
+</programlisting>
+ or a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the function body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at function definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at function definition time. This form tracks dependencies between the
+ function and objects used in the function body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling functions. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
<para>
@@ -669,6 +709,15 @@ <title>Examples</title>
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
+</programlisting>
+ The same function written in a more SQL-conforming style, using argument
+ names and an unquoted body:
+<programlisting>
+CREATE FUNCTION add(a integer, b integer) RETURNS integer
+ LANGUAGE SQL
+ IMMUTABLE
+ RETURNS NULL ON NULL INPUT
+ RETURN a + b;
</programlisting>
</para>
@@ -799,23 +848,74 @@ <title>Writing <literal>SECURITY DEFINER</literal> Functions Safely</title>
<title>Compatibility</title>
<para>
- A <command>CREATE FUNCTION</command> command is defined in the SQL standard.
- The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. The attributes are not portable, neither are the
- different available languages.
+ A <command>CREATE FUNCTION</command> command is defined in the SQL
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. Conversely, the SQL
+ standard specifies a number of optional features that are not implemented
+ in <productname>PostgreSQL</productname>.
</para>
<para>
- For compatibility with some other database systems,
- <replaceable class="parameter">argmode</replaceable> can be written
- either before or after <replaceable class="parameter">argname</replaceable>.
- But only the first way is standard-compliant.
+ The following are important compatibility issues:
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>OR REPLACE</literal> is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For compatibility with some other database systems, <replaceable
+ class="parameter">argmode</replaceable> can be written either before or
+ after <replaceable class="parameter">argname</replaceable>. But only
+ the first way is standard-compliant.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For parameter defaults, the SQL standard specifies only the syntax with
+ the <literal>DEFAULT</literal> key word. The syntax with
+ <literal>=</literal> is used in T-SQL and Firebird.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The <literal>SETOF</literal> modifier is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Only <literal>SQL</literal> is standardized as a language.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ All other attributes except <literal>CALLED ON NULL INPUT</literal> and
+ <literal>RETURNS NULL ON NULL INPUT</literal> are not standardized.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For the body of <literal>LANGUAGE SQL</literal> functions, the SQL
+ standard only specifies the <replaceable>sql_body</replaceable> form.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
<para>
- For parameter defaults, the SQL standard specifies only the syntax with
- the <literal>DEFAULT</literal> key word. The syntax
- with <literal>=</literal> is used in T-SQL and Firebird.
+ Simple <literal>LANGUAGE SQL</literal> functions can be written in a way
+ that is both standard-conforming and portable to other implementations.
+ More complex functions using advanced features, optimization attributes, or
+ other languages will necessarily be specific to PostgreSQL in a significant
+ way.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_procedure.sgml b/doc/src/sgml/ref/create_procedure.sgml
index e258eca5ce..ecdeac1629 100644
--- a/doc/src/sgml/ref/create_procedure.sgml
+++ b/doc/src/sgml/ref/create_procedure.sgml
@@ -29,6 +29,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -162,8 +163,9 @@ <title>Parameters</title>
The name of the language that the procedure is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -299,6 +301,41 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> procedure. This should
+ be a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the procedure body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at procedure definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at procedure definition time. This form tracks dependencies between the
+ procedure and objects used in the procedure body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling procedures. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
@@ -318,6 +355,7 @@ <title>Notes</title>
<refsect1 id="sql-createprocedure-examples">
<title>Examples</title>
+ <para>
<programlisting>
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
@@ -325,9 +363,21 @@ <title>Examples</title>
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
$$;
-
+</programlisting>
+ or
+<programlisting>
+CREATE PROCEDURE insert_data(a integer, b integer)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO tbl VALUES (a);
+ INSERT INTO tbl VALUES (b);
+END;
+</programlisting>
+ and call like this:
+<programlisting>
CALL insert_data(1, 2);
</programlisting>
+ </para>
</refsect1>
<refsect1 id="sql-createprocedure-compat">
@@ -335,9 +385,9 @@ <title>Compatibility</title>
<para>
A <command>CREATE PROCEDURE</command> command is defined in the SQL
- standard. The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. For details see
- also <xref linkend="sql-createfunction"/>.
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. For details see also
+ <xref linkend="sql-createfunction"/>.
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 7664bb6285..4456342bae 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -622,6 +622,7 @@ AggregateCreate(const char *aggName,
InvalidOid, /* no validator */
"aggregate_dummy", /* placeholder (no such proc) */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_AGGREGATE,
false, /* security invoker (currently not
* definable for agg) */
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 1dd9ecc063..808d48949a 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -32,6 +32,7 @@
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_type.h"
#include "tcop/pquery.h"
@@ -76,6 +77,7 @@ ProcedureCreate(const char *procedureName,
Oid languageValidator,
const char *prosrc,
const char *probin,
+ Node *prosqlbody,
char prokind,
bool security_definer,
bool isLeakProof,
@@ -119,8 +121,6 @@ ProcedureCreate(const char *procedureName,
/*
* sanity checks
*/
- Assert(PointerIsValid(prosrc));
-
parameterCount = parameterTypes->dim1;
if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
ereport(ERROR,
@@ -334,11 +334,18 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_protrftypes - 1] = trftypes;
else
nulls[Anum_pg_proc_protrftypes - 1] = true;
- values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ if (prosrc)
+ values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ else
+ nulls[Anum_pg_proc_prosrc - 1] = true;
if (probin)
values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin);
else
nulls[Anum_pg_proc_probin - 1] = true;
+ if (prosqlbody)
+ values[Anum_pg_proc_prosqlbody - 1] = CStringGetTextDatum(nodeToString(prosqlbody));
+ else
+ nulls[Anum_pg_proc_prosqlbody - 1] = true;
if (proconfig != PointerGetDatum(NULL))
values[Anum_pg_proc_proconfig - 1] = proconfig;
else
@@ -638,6 +645,10 @@ ProcedureCreate(const char *procedureName,
record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
free_object_addresses(addrs);
+ /* dependency on SQL routine body */
+ if (languageObjectId == SQLlanguageId && prosqlbody)
+ recordDependencyOnExpr(&myself, prosqlbody, NIL, DEPENDENCY_NORMAL);
+
/* dependency on parameter default expressions */
if (parameterDefaults)
recordDependencyOnExpr(&myself, (Node *) parameterDefaults,
@@ -861,61 +872,81 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
/* Postpone body checks if !check_function_bodies */
if (check_function_bodies)
{
- tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
- if (isnull)
- elog(ERROR, "null prosrc");
-
- prosrc = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport().
*/
callback_arg.proname = NameStr(proc->proname);
- callback_arg.prosrc = prosrc;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_function_parse_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
- /*
- * We can't do full prechecking of the function definition if there
- * are any polymorphic input types, because actual datatypes of
- * expression results will be unresolvable. The check will be done at
- * runtime instead.
- *
- * We can run the text through the raw parser though; this will at
- * least catch silly syntactic errors.
- */
- raw_parsetree_list = pg_parse_query(prosrc);
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ {
+ Node *n;
- if (!haspolyarg)
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosqlbody, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc and prosqlbody");
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = castNode(List, n);
+ else
+ querytree_list = list_make1(list_make1(n));
+ }
+ else
{
+ prosrc = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = prosrc;
+
/*
- * OK to do full precheck: analyze and rewrite the queries, then
- * verify the result type.
+ * We can't do full prechecking of the function definition if there
+ * are any polymorphic input types, because actual datatypes of
+ * expression results will be unresolvable. The check will be done at
+ * runtime instead.
+ *
+ * We can run the text through the raw parser though; this will at
+ * least catch silly syntactic errors.
*/
- SQLFunctionParseInfoPtr pinfo;
- Oid rettype;
- TupleDesc rettupdesc;
+ raw_parsetree_list = pg_parse_query(prosrc);
- /* But first, set up parameter information */
- pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
-
- querytree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (!haspolyarg)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *querytree_sublist;
-
- querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
- prosrc,
- (ParserSetupHook) sql_fn_parser_setup,
- pinfo,
- NULL);
- querytree_list = lappend(querytree_list,
- querytree_sublist);
+ /*
+ * OK to do full precheck: analyze and rewrite the queries, then
+ * verify the result type.
+ */
+ SQLFunctionParseInfoPtr pinfo;
+
+ /* But first, set up parameter information */
+ pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
+
+ querytree_list = NIL;
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *querytree_sublist;
+
+ querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ prosrc,
+ (ParserSetupHook) sql_fn_parser_setup,
+ pinfo,
+ NULL);
+ querytree_list = lappend(querytree_list,
+ querytree_sublist);
+ }
}
+ }
+
+ if (!haspolyarg)
+ {
+ Oid rettype;
+ TupleDesc rettupdesc;
check_sql_fn_statements(querytree_list);
@@ -968,6 +999,9 @@ function_parse_error_transpose(const char *prosrc)
int newerrposition;
const char *queryText;
+ if (!prosrc)
+ return false;
+
/*
* Nothing to do unless we are dealing with a syntax error that has a
* cursor position.
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 6892204a9a..584131dbe2 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -312,9 +312,11 @@ DefineAggregate(ParseState *pstate,
InvalidOid,
OBJECT_AGGREGATE,
¶meterTypes,
+ NULL,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ NULL,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index c3ce480c8f..c48d3744a4 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -53,9 +53,11 @@
#include "commands/proclang.h"
#include "executor/execdesc.h"
#include "executor/executor.h"
+#include "executor/functions.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
@@ -186,9 +188,11 @@ interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType)
@@ -283,7 +287,11 @@ interpret_function_parameter_list(ParseState *pstate,
/* handle input parameters */
if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
+ {
isinput = true;
+ if (parameterTypes_list)
+ *parameterTypes_list = lappend_oid(*parameterTypes_list, toid);
+ }
/* handle signature parameters */
if (fp->mode == FUNC_PARAM_IN || fp->mode == FUNC_PARAM_INOUT ||
@@ -372,6 +380,9 @@ interpret_function_parameter_list(ParseState *pstate,
have_names = true;
}
+ if (inParameterNames_list)
+ *inParameterNames_list = lappend(*inParameterNames_list, makeString(fp->name ? fp->name : pstrdup("")));
+
if (fp->defexpr)
{
Node *def;
@@ -786,28 +797,10 @@ compute_function_attributes(ParseState *pstate,
defel->defname);
}
- /* process required items */
if (as_item)
*as = (List *) as_item->arg;
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no function body specified")));
- *as = NIL; /* keep compiler quiet */
- }
-
if (language_item)
*language = strVal(language_item->arg);
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no language specified")));
- *language = NULL; /* keep compiler quiet */
- }
-
- /* process optional items */
if (transform_item)
*transform = transform_item->arg;
if (windowfunc_item)
@@ -856,10 +849,21 @@ compute_function_attributes(ParseState *pstate,
*/
static void
interpret_AS_clause(Oid languageOid, const char *languageName,
- char *funcname, List *as,
- char **prosrc_str_p, char **probin_str_p)
+ char *funcname, List *as, Node *sql_body_in,
+ List *parameterTypes, List *inParameterNames,
+ char **prosrc_str_p, char **probin_str_p, Node **sql_body_out)
{
- Assert(as != NIL);
+ if (sql_body_in && as)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("duplicate function body specified")));
+
+ if (sql_body_in && languageOid != SQLlanguageId)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("inline SQL function body only valid for language SQL")));
+
+ *sql_body_out = NULL;
if (languageOid == ClanguageId)
{
@@ -881,6 +885,66 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
*prosrc_str_p = funcname;
}
}
+ else if (sql_body_in)
+ {
+ SQLFunctionParseInfoPtr pinfo;
+
+ pinfo = (SQLFunctionParseInfoPtr) palloc0(sizeof(SQLFunctionParseInfo));
+
+ pinfo->fname = funcname;
+ pinfo->nargs = list_length(parameterTypes);
+ pinfo->argtypes = (Oid *) palloc(pinfo->nargs * sizeof(Oid));
+ pinfo->argnames = (char **) palloc(pinfo->nargs * sizeof(char *));
+ for (int i = 0; i < list_length(parameterTypes); i++)
+ {
+ char *s = strVal(list_nth(inParameterNames, i));
+
+ pinfo->argtypes[i] = list_nth_oid(parameterTypes, i);
+ if (IsPolymorphicType(pinfo->argtypes[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("SQL function with unquoted function body cannot have polymorphic arguments")));
+
+ if (s[0] != '\0')
+ pinfo->argnames[i] = s;
+ else
+ pinfo->argnames[i] = NULL;
+ }
+
+ if (IsA(sql_body_in, List))
+ {
+ List *stmts = linitial_node(List, castNode(List, sql_body_in));
+ ListCell *lc;
+ List *transformed_stmts = NIL;
+
+ foreach(lc, stmts)
+ {
+ Node *stmt = lfirst(lc);
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, stmt);
+ transformed_stmts = lappend(transformed_stmts, q);
+ free_parsestate(pstate);
+ }
+
+ *sql_body_out = (Node *) list_make1(transformed_stmts);
+ }
+ else
+ {
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, sql_body_in);
+
+ *sql_body_out = (Node *) q;
+ }
+
+ *probin_str_p = NULL;
+ *prosrc_str_p = NULL;
+ }
else
{
/* Everything else wants the given string in prosrc. */
@@ -919,6 +983,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
{
char *probin_str;
char *prosrc_str;
+ Node *prosqlbody;
Oid prorettype;
bool returnsSet;
char *language;
@@ -929,9 +994,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
Oid namespaceId;
AclResult aclresult;
oidvector *parameterTypes;
+ List *parameterTypes_list = NIL;
ArrayType *allParameterTypes;
ArrayType *parameterModes;
ArrayType *parameterNames;
+ List *inParameterNames_list = NIL;
List *parameterDefaults;
Oid variadicArgType;
List *trftypes_list = NIL;
@@ -962,6 +1029,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
get_namespace_name(namespaceId));
/* Set default attributes */
+ as_clause = NULL;
+ language = "sql";
isWindowFunc = false;
isStrict = false;
security = false;
@@ -1053,9 +1122,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageOid,
stmt->is_procedure ? OBJECT_PROCEDURE : OBJECT_FUNCTION,
¶meterTypes,
+ ¶meterTypes_list,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ &inParameterNames_list,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
@@ -1112,8 +1183,9 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
trftypes = NULL;
}
- interpret_AS_clause(languageOid, language, funcname, as_clause,
- &prosrc_str, &probin_str);
+ interpret_AS_clause(languageOid, language, funcname, as_clause, stmt->sql_body,
+ parameterTypes_list, inParameterNames_list,
+ &prosrc_str, &probin_str, &prosqlbody);
/*
* Set default values for COST and ROWS depending on other parameters;
@@ -1155,6 +1227,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageValidator,
prosrc_str, /* converted to text later */
probin_str, /* converted to text later */
+ prosqlbody,
stmt->is_procedure ? PROKIND_PROCEDURE : (isWindowFunc ? PROKIND_WINDOW : PROKIND_FUNCTION),
security,
isLeakProof,
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 483bb65ddc..65aa91d80f 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1588,6 +1588,7 @@ makeRangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR, /* language validator */
prosrc[i], /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 459a33375b..3ca7e89d44 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -26,6 +26,7 @@
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_func.h"
+#include "rewrite/rewriteHandler.h"
#include "storage/proc.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
@@ -128,21 +129,6 @@ typedef struct
typedef SQLFunctionCache *SQLFunctionCachePtr;
-/*
- * Data structure needed by the parser callback hooks to resolve parameter
- * references during parsing of a SQL function's body. This is separate from
- * SQLFunctionCache since we sometimes do parsing separately from execution.
- */
-typedef struct SQLFunctionParseInfo
-{
- char *fname; /* function's name */
- int nargs; /* number of input arguments */
- Oid *argtypes; /* resolved types of input arguments */
- char **argnames; /* names of input arguments; NULL if none */
- /* Note that argnames[i] can be NULL, if some args are unnamed */
- Oid collation; /* function's input collation, if known */
-} SQLFunctionParseInfo;
-
/* non-export function prototypes */
static Node *sql_fn_param_ref(ParseState *pstate, ParamRef *pref);
@@ -607,7 +593,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
HeapTuple procedureTuple;
Form_pg_proc procedureStruct;
SQLFunctionCachePtr fcache;
- List *raw_parsetree_list;
List *queryTree_list;
List *resulttlist;
ListCell *lc;
@@ -682,9 +667,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
procedureTuple,
Anum_pg_proc_prosrc,
&isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", foid);
- fcache->src = TextDatumGetCString(tmp);
/*
* Parse and rewrite the queries in the function text. Use sublists to
@@ -695,20 +677,55 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
* but we'll not worry about it until the module is rewritten to use
* plancache.c.
*/
- raw_parsetree_list = pg_parse_query(fcache->src);
-
queryTree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (isNull)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *queryTree_sublist;
-
- queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
- fcache->src,
- (ParserSetupHook) sql_fn_parser_setup,
- fcache->pinfo,
- NULL);
- queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ Node *n;
+ List *stored_query_list;
+
+ tmp = SysCacheGetAttr(PROCOID,
+ procedureTuple,
+ Anum_pg_proc_prosqlbody,
+ &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", foid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ stored_query_list = linitial_node(List, castNode(List, n));
+ else
+ stored_query_list = list_make1(n);
+
+ foreach(lc, stored_query_list)
+ {
+ Query *parsetree = lfirst_node(Query, lc);
+ List *queryTree_sublist;
+
+ AcquireRewriteLocks(parsetree, true, false);
+ queryTree_sublist = pg_rewrite_query(parsetree);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ }
+ }
+ else
+ {
+ List *raw_parsetree_list;
+
+ fcache->src = TextDatumGetCString(tmp);
+
+ raw_parsetree_list = pg_parse_query(fcache->src);
+
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *queryTree_sublist;
+
+ queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ fcache->src,
+ (ParserSetupHook) sql_fn_parser_setup,
+ fcache->pinfo,
+ NULL);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ }
}
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 5a591d0a75..eeb657a26b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3068,6 +3068,7 @@ _copyQuery(const Query *from)
COPY_SCALAR_FIELD(hasModifyingCTE);
COPY_SCALAR_FIELD(hasForUpdate);
COPY_SCALAR_FIELD(hasRowSecurity);
+ COPY_SCALAR_FIELD(isReturn);
COPY_NODE_FIELD(cteList);
COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(jointree);
@@ -3196,6 +3197,16 @@ _copySetOperationStmt(const SetOperationStmt *from)
return newnode;
}
+static ReturnStmt *
+_copyReturnStmt(const ReturnStmt *from)
+{
+ ReturnStmt *newnode = makeNode(ReturnStmt);
+
+ COPY_NODE_FIELD(returnval);
+
+ return newnode;
+}
+
static AlterTableStmt *
_copyAlterTableStmt(const AlterTableStmt *from)
{
@@ -3563,6 +3574,7 @@ _copyCreateFunctionStmt(const CreateFunctionStmt *from)
COPY_NODE_FIELD(parameters);
COPY_NODE_FIELD(returnType);
COPY_NODE_FIELD(options);
+ COPY_NODE_FIELD(sql_body);
return newnode;
}
@@ -5216,6 +5228,9 @@ copyObjectImpl(const void *from)
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
+ case T_ReturnStmt:
+ retval = _copyReturnStmt(from);
+ break;
case T_AlterTableStmt:
retval = _copyAlterTableStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2895a8985..255e83d398 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -966,6 +966,7 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_SCALAR_FIELD(hasModifyingCTE);
COMPARE_SCALAR_FIELD(hasForUpdate);
COMPARE_SCALAR_FIELD(hasRowSecurity);
+ COMPARE_SCALAR_FIELD(isReturn);
COMPARE_NODE_FIELD(cteList);
COMPARE_NODE_FIELD(rtable);
COMPARE_NODE_FIELD(jointree);
@@ -1082,6 +1083,14 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
return true;
}
+static bool
+_equalReturnStmt(const ReturnStmt *a, const ReturnStmt *b)
+{
+ COMPARE_NODE_FIELD(returnval);
+
+ return true;
+}
+
static bool
_equalAlterTableStmt(const AlterTableStmt *a, const AlterTableStmt *b)
{
@@ -1386,6 +1395,7 @@ _equalCreateFunctionStmt(const CreateFunctionStmt *a, const CreateFunctionStmt *
COMPARE_NODE_FIELD(parameters);
COMPARE_NODE_FIELD(returnType);
COMPARE_NODE_FIELD(options);
+ COMPARE_NODE_FIELD(sql_body);
return true;
}
@@ -3271,6 +3281,9 @@ equal(const void *a, const void *b)
case T_SetOperationStmt:
retval = _equalSetOperationStmt(a, b);
break;
+ case T_ReturnStmt:
+ retval = _equalReturnStmt(a, b);
+ break;
case T_AlterTableStmt:
retval = _equalAlterTableStmt(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index f26498cea2..4609fe87e1 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2756,6 +2756,14 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
WRITE_NODE_FIELD(rarg);
}
+static void
+_outReturnStmt(StringInfo str, const ReturnStmt *node)
+{
+ WRITE_NODE_TYPE("RETURN");
+
+ WRITE_NODE_FIELD(returnval);
+}
+
static void
_outFuncCall(StringInfo str, const FuncCall *node)
{
@@ -2945,6 +2953,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_BOOL_FIELD(hasModifyingCTE);
WRITE_BOOL_FIELD(hasForUpdate);
WRITE_BOOL_FIELD(hasRowSecurity);
+ WRITE_BOOL_FIELD(isReturn);
WRITE_NODE_FIELD(cteList);
WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(jointree);
@@ -4191,6 +4200,9 @@ outNode(StringInfo str, const void *obj)
case T_SelectStmt:
_outSelectStmt(str, obj);
break;
+ case T_ReturnStmt:
+ _outReturnStmt(str, obj);
+ break;
case T_ColumnDef:
_outColumnDef(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ab7b535caa..d01746d0ee 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -263,6 +263,7 @@ _readQuery(void)
READ_BOOL_FIELD(hasModifyingCTE);
READ_BOOL_FIELD(hasForUpdate);
READ_BOOL_FIELD(hasRowSecurity);
+ READ_BOOL_FIELD(isReturn);
READ_NODE_FIELD(cteList);
READ_NODE_FIELD(rtable);
READ_NODE_FIELD(jointree);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 85ef873caa..66db4d7ace 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4416,27 +4416,47 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
- /* Fetch the function body */
- tmp = SysCacheGetAttr(PROCOID,
- func_tuple,
- Anum_pg_proc_prosrc,
- &isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", funcid);
- src = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = src;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ {
+ Node *n;
+ List *querytree_list;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", funcid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = linitial_node(List, castNode(List, n));
+ else
+ querytree_list = list_make1(n);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We need a
* dummy FuncExpr node containing the already-simplified arguments to pass
@@ -4480,6 +4500,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
querytree = transformTopLevelStmt(pstate, linitial(raw_parsetree_list));
free_parsestate(pstate);
+ }
/*
* The single command must be a simple "SELECT expression".
@@ -4736,12 +4757,15 @@ sql_inline_error_callback(void *arg)
int syntaxerrposition;
/* If it's a syntax error, convert to internal syntax error report */
- syntaxerrposition = geterrposition();
- if (syntaxerrposition > 0)
+ if (callback_arg->prosrc)
{
- errposition(0);
- internalerrposition(syntaxerrposition);
- internalerrquery(callback_arg->prosrc);
+ syntaxerrposition = geterrposition();
+ if (syntaxerrposition > 0)
+ {
+ errposition(0);
+ internalerrposition(syntaxerrposition);
+ internalerrquery(callback_arg->prosrc);
+ }
}
errcontext("SQL function \"%s\" during inlining", callback_arg->proname);
@@ -4853,7 +4877,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
Oid func_oid;
HeapTuple func_tuple;
Form_pg_proc funcform;
- char *src;
Datum tmp;
bool isNull;
MemoryContext oldcxt;
@@ -4962,27 +4985,53 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
- /* Fetch the function body */
- tmp = SysCacheGetAttr(PROCOID,
- func_tuple,
- Anum_pg_proc_prosrc,
- &isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", func_oid);
- src = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = src;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ {
+ Node *n;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", func_oid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = linitial_node(List, castNode(List, n));
+ else
+ querytree_list = list_make1(n);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+
+ querytree_list = pg_rewrite_query(querytree);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
+ char *src;
+
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We can
* use the FuncExpr just created as the input for
@@ -4992,18 +5041,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
(Node *) fexpr,
fexpr->inputcollid);
- /*
- * Also resolve the actual function result tupdesc, if composite. If the
- * function is just declared to return RECORD, dig the info out of the AS
- * clause.
- */
- functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
- if (functypclass == TYPEFUNC_RECORD)
- rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
- rtfunc->funccoltypes,
- rtfunc->funccoltypmods,
- rtfunc->funccolcollations);
-
/*
* Parse, analyze, and rewrite (unlike inline_function(), we can't skip
* rewriting here). We can fail as soon as we find more than one query,
@@ -5020,6 +5057,19 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
if (list_length(querytree_list) != 1)
goto fail;
querytree = linitial(querytree_list);
+ }
+
+ /*
+ * Also resolve the actual function result tupdesc, if composite. If the
+ * function is just declared to return RECORD, dig the info out of the AS
+ * clause.
+ */
+ functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
+ if (functypclass == TYPEFUNC_RECORD)
+ rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
+ rtfunc->funccoltypes,
+ rtfunc->funccoltypmods,
+ rtfunc->funccolcollations);
/*
* The single command must be a plain SELECT.
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 084e00f73d..7e4b8ef279 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -66,6 +66,7 @@ static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
bool isTopLevel, List **targetlist);
static void determineRecursiveColTypes(ParseState *pstate,
Node *larg, List *nrtargetlist);
+static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList);
static List *transformUpdateTargetList(ParseState *pstate,
@@ -304,6 +305,10 @@ transformStmt(ParseState *pstate, Node *parseTree)
}
break;
+ case T_ReturnStmt:
+ result = transformReturnStmt(pstate, (ReturnStmt *) parseTree);
+ break;
+
/*
* Special cases
*/
@@ -2206,6 +2211,36 @@ determineRecursiveColTypes(ParseState *pstate, Node *larg, List *nrtargetlist)
}
+/*
+ * transformReturnStmt -
+ * transforms a return statement
+ */
+static Query *
+transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+
+ qry->commandType = CMD_SELECT;
+ qry->isReturn = true;
+
+ qry->targetList = list_make1(makeTargetEntry((Expr *) transformExpr(pstate, stmt->returnval, EXPR_KIND_SELECT_TARGET),
+ 1, NULL, false));
+
+ if (pstate->p_resolve_unknowns)
+ resolveTargetListUnknowns(pstate, qry->targetList);
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasAggs = pstate->p_hasAggs;
+
+ assign_query_collations(pstate, qry);
+
+ return qry;
+}
+
+
/*
* transformUpdateStmt -
* transforms an update statement
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index efc9c99754..4180070adb 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -253,7 +253,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct SelectLimit *selectlimit;
}
-%type <node> stmt schema_stmt
+%type <node> stmt toplevel_stmt schema_stmt routine_body_stmt
AlterEventTrigStmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
@@ -280,9 +280,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
- RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
+ RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt
RuleActionStmt RuleActionStmtOrEmpty RuleStmt
- SecLabelStmt SelectStmt TransactionStmt TruncateStmt
+ SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
UnlistenStmt UpdateStmt VacuumStmt
VariableResetStmt VariableSetStmt VariableShowStmt
ViewStmt CheckPointStmt CreateConversionStmt
@@ -385,14 +385,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> vacuum_relation
%type <selectlimit> opt_select_limit select_limit limit_clause
-%type <list> stmtblock stmtmulti
+%type <list> stmtblock stmtmulti routine_body_stmt_list
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
reloptions opt_reloptions
OptWith distinct_clause opt_definition func_args func_args_list
func_args_with_defaults func_args_with_defaults_list
aggr_args aggr_args_list
- func_as createfunc_opt_list alterfunc_opt_list
+ func_as createfunc_opt_list opt_createfunc_opt_list alterfunc_opt_list
old_aggr_definition old_aggr_list
oper_argtypes RuleActionList RuleActionMulti
opt_column_list columnList opt_name_list
@@ -418,6 +418,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
vacuum_relation_list opt_vacuum_relation_list
drop_option_list
+%type <node> opt_routine_body
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
@@ -628,7 +629,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATOMIC ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
@@ -690,7 +691,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING
REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
- RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
+ RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
@@ -812,7 +813,7 @@ stmtblock: stmtmulti
* we'd get -1 for the location in such cases.
* We also take care to discard empty statements entirely.
*/
-stmtmulti: stmtmulti ';' stmt
+stmtmulti: stmtmulti ';' toplevel_stmt
{
if ($1 != NIL)
{
@@ -824,7 +825,7 @@ stmtmulti: stmtmulti ';' stmt
else
$$ = $1;
}
- | stmt
+ | toplevel_stmt
{
if ($1 != NULL)
$$ = list_make1(makeRawStmt($1, 0));
@@ -833,7 +834,16 @@ stmtmulti: stmtmulti ';' stmt
}
;
-stmt :
+/*
+ * toplevel_stmt includes BEGIN and END. stmt does not include them, because
+ * those words have different meanings in function bodys.
+ */
+toplevel_stmt:
+ stmt
+ | TransactionStmtLegacy
+ ;
+
+stmt:
AlterEventTrigStmt
| AlterDatabaseStmt
| AlterDatabaseSetStmt
@@ -7352,7 +7362,7 @@ opt_nulls_order: NULLS_LA FIRST_P { $$ = SORTBY_NULLS_FIRST; }
CreateFunctionStmt:
CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS func_return createfunc_opt_list
+ RETURNS func_return opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7361,10 +7371,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = $7;
n->options = $8;
+ n->sql_body = $9;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS TABLE '(' table_func_column_list ')' createfunc_opt_list
+ RETURNS TABLE '(' table_func_column_list ')' opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7374,10 +7385,11 @@ CreateFunctionStmt:
n->returnType = TableFuncTypeName($9);
n->returnType->location = @7;
n->options = $11;
+ n->sql_body = $12;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7386,10 +7398,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
| CREATE opt_or_replace PROCEDURE func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = true;
@@ -7398,6 +7411,7 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
;
@@ -7708,6 +7722,11 @@ aggregate_with_argtypes_list:
{ $$ = lappend($1, $3); }
;
+opt_createfunc_opt_list:
+ createfunc_opt_list
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
createfunc_opt_list:
/* Must be at least one to prevent conflict */
createfunc_opt_item { $$ = list_make1($1); }
@@ -7819,6 +7838,51 @@ func_as: Sconst { $$ = list_make1(makeString($1)); }
}
;
+ReturnStmt: RETURN a_expr
+ {
+ ReturnStmt *r = makeNode(ReturnStmt);
+ r->returnval = (Node *) $2;
+ $$ = (Node *) r;
+ }
+ ;
+
+opt_routine_body:
+ ReturnStmt
+ {
+ $$ = $1;
+ }
+ | BEGIN_P ATOMIC routine_body_stmt_list END_P
+ {
+ /*
+ * A compound statement is stored as a single-item list
+ * containing the list of statements as its member. That
+ * way, the parse analysis code can tell apart an empty
+ * body from no body at all.
+ */
+ $$ = (Node *) list_make1($3);
+ }
+ | /*EMPTY*/
+ {
+ $$ = NULL;
+ }
+ ;
+
+routine_body_stmt_list:
+ routine_body_stmt_list routine_body_stmt ';'
+ {
+ $$ = lappend($1, $2);
+ }
+ | /*EMPTY*/
+ {
+ $$ = NIL;
+ }
+ ;
+
+routine_body_stmt:
+ stmt
+ | ReturnStmt
+ ;
+
transform_type_list:
FOR TYPE_P Typename { $$ = list_make1($3); }
| transform_type_list ',' FOR TYPE_P Typename { $$ = lappend($1, $5); }
@@ -9769,13 +9833,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | BEGIN_P opt_transaction transaction_mode_list_or_empty
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_BEGIN;
- n->options = $3;
- $$ = (Node *)n;
- }
| START TRANSACTION transaction_mode_list_or_empty
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9791,14 +9848,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | END_P opt_transaction opt_transaction_chain
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_COMMIT;
- n->options = NIL;
- n->chain = $3;
- $$ = (Node *)n;
- }
| ROLLBACK opt_transaction opt_transaction_chain
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9865,6 +9914,24 @@ TransactionStmt:
}
;
+TransactionStmtLegacy:
+ BEGIN_P opt_transaction transaction_mode_list_or_empty
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_BEGIN;
+ n->options = $3;
+ $$ = (Node *)n;
+ }
+ | END_P opt_transaction opt_transaction_chain
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_COMMIT;
+ n->options = NIL;
+ n->chain = $3;
+ $$ = (Node *)n;
+ }
+ ;
+
opt_transaction: WORK
| TRANSACTION
| /*EMPTY*/
@@ -15152,6 +15219,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| BACKWARD
@@ -15350,6 +15418,7 @@ unreserved_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| ROLE
@@ -15656,6 +15725,7 @@ bare_label_keyword:
| ASSIGNMENT
| ASYMMETRIC
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| AUTHORIZATION
@@ -15926,6 +15996,7 @@ bare_label_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| RIGHT
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7c5f7c775b..528ef11ca2 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -176,7 +176,6 @@ static int interactive_getc(void);
static int SocketBackend(StringInfo inBuf);
static int ReadCommand(StringInfo inBuf);
static void forbidden_in_wal_sender(char firstchar);
-static List *pg_rewrite_query(Query *query);
static bool check_log_statement(List *stmt_list);
static int errdetail_execute(List *raw_parsetree_list);
static int errdetail_params(ParamListInfo params);
@@ -761,7 +760,7 @@ pg_analyze_and_rewrite_params(RawStmt *parsetree,
* Note: query must just have come from the parser, because we do not do
* AcquireRewriteLocks() on it.
*/
-static List *
+List *
pg_rewrite_query(Query *query)
{
List *querytree_list;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c2c6df2a4f..66d53f2ffc 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -173,6 +173,9 @@ typedef struct
List *outer_tlist; /* referent for OUTER_VAR Vars */
List *inner_tlist; /* referent for INNER_VAR Vars */
List *index_tlist; /* referent for INDEX_VAR Vars */
+ /* Special namespace representing a function signature: */
+ int numargs;
+ char **argnames;
} deparse_namespace;
/*
@@ -348,6 +351,7 @@ static int print_function_arguments(StringInfo buf, HeapTuple proctup,
bool print_table_args, bool print_defaults);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
static void print_function_trftypes(StringInfo buf, HeapTuple proctup);
+static void print_function_sqlbody(StringInfo buf, HeapTuple proctup);
static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used);
static void set_deparse_for_query(deparse_namespace *dpns, Query *query,
@@ -2800,6 +2804,13 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
}
/* And finally the function definition ... */
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
+ if (proc->prolang == SQLlanguageId && !isnull)
+ {
+ print_function_sqlbody(&buf, proctup);
+ }
+ else
+ {
appendStringInfoString(&buf, "AS ");
tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
@@ -2831,6 +2842,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, dq.data, dq.len);
appendStringInfoString(&buf, prosrc);
appendBinaryStringInfo(&buf, dq.data, dq.len);
+ }
appendStringInfoChar(&buf, '\n');
@@ -3213,6 +3225,75 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(string_to_text(str));
}
+static void
+print_function_sqlbody(StringInfo buf, HeapTuple proctup)
+{
+ int numargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ deparse_namespace dpns = {0};
+ Datum tmp;
+ bool isnull;
+ Node *n;
+
+ numargs = get_func_arg_info(proctup,
+ &argtypes, &argnames, &argmodes);
+ dpns.numargs = numargs;
+ dpns.argnames = argnames;
+
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
+ Assert(!isnull);
+ n = stringToNode(TextDatumGetCString(tmp));
+
+ if (IsA(n, List))
+ {
+ List *stmts;
+ ListCell *lc;
+
+ stmts = linitial(castNode(List, n));
+
+ appendStringInfoString(buf, "BEGIN ATOMIC\n");
+
+ foreach(lc, stmts)
+ {
+ Query *query = lfirst_node(Query, lc);
+
+ get_query_def(query, buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ appendStringInfoChar(buf, ';');
+ appendStringInfoChar(buf, '\n');
+ }
+
+ appendStringInfoString(buf, "END");
+ }
+ else
+ {
+ get_query_def(castNode(Query, n), buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ appendStringInfoChar(buf, '\n');
+ }
+}
+
+Datum
+pg_get_function_sqlbody(PG_FUNCTION_ARGS)
+{
+ Oid funcid = PG_GETARG_OID(0);
+ StringInfoData buf;
+ HeapTuple proctup;
+
+ initStringInfo(&buf);
+
+ /* Look up the function */
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ PG_RETURN_NULL();
+
+ print_function_sqlbody(&buf, proctup);
+
+ ReleaseSysCache(proctup);
+
+ PG_RETURN_TEXT_P(cstring_to_text(buf.data));
+}
+
/*
* deparse_expression - General utility for deparsing expressions
@@ -5414,7 +5495,10 @@ get_basic_select_query(Query *query, deparse_context *context,
/*
* Build up the query string - first we say SELECT
*/
- appendStringInfoString(buf, "SELECT");
+ if (query->isReturn)
+ appendStringInfoString(buf, "RETURN");
+ else
+ appendStringInfoString(buf, "SELECT");
/* Add the DISTINCT clause if given */
if (query->distinctClause != NIL)
@@ -7546,6 +7630,26 @@ get_parameter(Param *param, deparse_context *context)
return;
}
+ /*
+ * If it's an external parameter, see if the outermost namespace provides
+ * function argument names.
+ */
+ if (param->paramkind == PARAM_EXTERN)
+ {
+ dpns = lfirst(list_tail(context->namespaces));
+ if (dpns->argnames)
+ {
+ char *argname = dpns->argnames[param->paramid - 1];
+
+ if (argname)
+ {
+ // TODO: qualify with function name if necessary
+ appendStringInfo(context->buf, "%s", quote_identifier(argname));
+ return;
+ }
+ }
+ }
+
/*
* Not PARAM_EXEC, or couldn't find referent: just print $N.
*/
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index dc1d41dd8d..8c0b46b0d2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11879,6 +11879,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
char *proretset;
char *prosrc;
char *probin;
+ char *prosqlbody;
char *funcargs;
char *funciargs;
char *funcresult;
@@ -11925,7 +11926,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"provolatile,\n"
"proisstrict,\n"
"prosecdef,\n"
- "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname,\n");
+ "lanname,\n");
if (fout->remoteVersion >= 80300)
appendPQExpBufferStr(query,
@@ -11945,9 +11946,9 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
* pg_get_function_result instead of examining proallargtypes etc.
*/
appendPQExpBufferStr(query,
- "pg_catalog.pg_get_function_arguments(oid) AS funcargs,\n"
- "pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs,\n"
- "pg_catalog.pg_get_function_result(oid) AS funcresult,\n");
+ "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n"
+ "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n"
+ "pg_catalog.pg_get_function_result(p.oid) AS funcresult,\n");
}
else if (fout->remoteVersion >= 80100)
appendPQExpBufferStr(query,
@@ -11990,21 +11991,39 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
if (fout->remoteVersion >= 120000)
appendPQExpBufferStr(query,
- "prosupport\n");
+ "prosupport,\n");
else
appendPQExpBufferStr(query,
- "'-' AS prosupport\n");
+ "'-' AS prosupport,\n");
+
+ if (fout->remoteVersion >= 140000)
+ appendPQExpBufferStr(query,
+ "CASE WHEN prosrc IS NULL AND lanname = 'sql' THEN pg_get_function_sqlbody(p.oid) END AS prosqlbody\n");
+ else
+ appendPQExpBufferStr(query,
+ "NULL AS prosqlbody\n");
appendPQExpBuffer(query,
- "FROM pg_catalog.pg_proc "
- "WHERE oid = '%u'::pg_catalog.oid",
+ "FROM pg_catalog.pg_proc p, pg_catalog.pg_language l\n"
+ "WHERE p.oid = '%u'::pg_catalog.oid "
+ "AND l.oid = p.prolang",
finfo->dobj.catId.oid);
res = ExecuteSqlQueryForSingleRow(fout, query->data);
proretset = PQgetvalue(res, 0, PQfnumber(res, "proretset"));
- prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
- probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ if (PQgetisnull(res, 0, PQfnumber(res, "prosqlbody")))
+ {
+ prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
+ probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ prosqlbody = NULL;
+ }
+ else
+ {
+ prosrc = NULL;
+ probin = NULL;
+ prosqlbody = PQgetvalue(res, 0, PQfnumber(res, "prosqlbody"));
+ }
if (fout->remoteVersion >= 80400)
{
funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
@@ -12041,7 +12060,11 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
* versions would set it to "-". There are no known cases in which prosrc
* is unused, so the tests below for "-" are probably useless.
*/
- if (probin[0] != '\0' && strcmp(probin, "-") != 0)
+ if (prosqlbody)
+ {
+ appendPQExpBufferStr(asPart, prosqlbody);
+ }
+ else if (probin[0] != '\0' && strcmp(probin, "-") != 0)
{
appendPQExpBufferStr(asPart, "AS ");
appendStringLiteralAH(asPart, probin, fout);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 07d640021c..319c34ff2b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -505,11 +505,18 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
appendPQExpBufferStr(&buf, ",\n ");
printACLColumn(&buf, "p.proacl");
appendPQExpBuffer(&buf,
- ",\n l.lanname as \"%s\""
- ",\n p.prosrc as \"%s\""
+ ",\n l.lanname as \"%s\"",
+ gettext_noop("Language"));
+ if (pset.sversion >= 140000)
+ appendPQExpBuffer(&buf,
+ ",\n COALESCE(p.prosrc, pg_catalog.pg_get_function_sqlbody(p.oid)) as \"%s\"",
+ gettext_noop("Source code"));
+ else
+ appendPQExpBuffer(&buf,
+ ",\n p.prosrc as \"%s\"",
+ gettext_noop("Source code"));
+ appendPQExpBuffer(&buf,
",\n pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"",
- gettext_noop("Language"),
- gettext_noop("Source code"),
gettext_noop("Description"));
}
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 08dffde1ba..ee34463e67 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -645,10 +645,11 @@ other .
";" {
ECHO;
- if (cur_state->paren_depth == 0)
+ if (cur_state->paren_depth == 0 && cur_state->begin_depth == 0)
{
/* Terminate lexing temporarily */
cur_state->start_state = YY_START;
+ cur_state->identifier_count = 0;
return LEXRES_SEMI;
}
}
@@ -661,6 +662,8 @@ other .
"\\"[;:] {
/* Force a semi-colon or colon into the query buffer */
psqlscan_emit(cur_state, yytext + 1, 1);
+ if (yytext[1] == ';')
+ cur_state->identifier_count = 0;
}
"\\" {
@@ -867,6 +870,17 @@ other .
{identifier} {
+ cur_state->identifier_count++;
+ if (pg_strcasecmp(yytext, "begin") == 0)
+ {
+ if (cur_state->identifier_count > 1)
+ cur_state->begin_depth++;
+ }
+ else if (pg_strcasecmp(yytext, "end") == 0)
+ {
+ if (cur_state->begin_depth > 0)
+ cur_state->begin_depth--;
+ }
ECHO;
}
@@ -1054,6 +1068,11 @@ psql_scan(PsqlScanState state,
result = PSCAN_INCOMPLETE;
*prompt = PROMPT_PAREN;
}
+ if (state->begin_depth > 0)
+ {
+ result = PSCAN_INCOMPLETE;
+ *prompt = PROMPT_CONTINUE;
+ }
else if (query_buf->len > 0)
{
result = PSCAN_EOL;
@@ -1170,6 +1189,8 @@ psql_scan_reset(PsqlScanState state)
if (state->dolqstart)
free(state->dolqstart);
state->dolqstart = NULL;
+ state->identifier_count = 0;
+ state->begin_depth = 0;
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 33dacfd340..0611e7c559 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3692,6 +3692,10 @@
proname => 'pg_get_function_arg_default', provolatile => 's',
prorettype => 'text', proargtypes => 'oid int4',
prosrc => 'pg_get_function_arg_default' },
+{ oid => '9704', descr => 'function SQL body',
+ proname => 'pg_get_function_sqlbody', provolatile => 's',
+ prorettype => 'text', proargtypes => 'oid',
+ prosrc => 'pg_get_function_sqlbody' },
{ oid => '1686', descr => 'list of SQL keywords',
proname => 'pg_get_keywords', procost => '10', prorows => '500',
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index f8e6dea22d..fe297b9ff1 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -112,11 +112,14 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
Oid protrftypes[1] BKI_DEFAULT(_null_);
/* procedure source text */
- text prosrc BKI_FORCE_NOT_NULL;
+ text prosrc;
/* secondary procedure info (can be NULL) */
text probin BKI_DEFAULT(_null_);
+ /* pre-parsed SQL function body */
+ pg_node_tree prosqlbody BKI_DEFAULT(_null_);
+
/* procedure-local GUC settings */
text proconfig[1] BKI_DEFAULT(_null_);
@@ -194,6 +197,7 @@ extern ObjectAddress ProcedureCreate(const char *procedureName,
Oid languageValidator,
const char *prosrc,
const char *probin,
+ Node *prosqlbody,
char prokind,
bool security_definer,
bool isLeakProof,
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 7a079ef07f..a32cfba421 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -68,9 +68,11 @@ extern void interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType);
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index a0db24bde6..3111ff824a 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -20,6 +20,21 @@
/* This struct is known only within executor/functions.c */
typedef struct SQLFunctionParseInfo *SQLFunctionParseInfoPtr;
+/*
+ * Data structure needed by the parser callback hooks to resolve parameter
+ * references during parsing of a SQL function's body. This is separate from
+ * SQLFunctionCache since we sometimes do parsing separately from execution.
+ */
+typedef struct SQLFunctionParseInfo
+{
+ char *fname; /* function's name */
+ int nargs; /* number of input arguments */
+ Oid *argtypes; /* resolved types of input arguments */
+ char **argnames; /* names of input arguments; NULL if none */
+ /* Note that argnames[i] can be NULL, if some args are unnamed */
+ Oid collation; /* function's input collation, if known */
+} SQLFunctionParseInfo;
+
extern Datum fmgr_sql(PG_FUNCTION_ARGS);
extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTuple,
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 311f80394a..fb8f58aa29 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -114,6 +114,8 @@ typedef struct PsqlScanStateData
int paren_depth; /* depth of nesting in parentheses */
int xcdepth; /* depth of nesting in slash-star comments */
char *dolqstart; /* current $foo$ quote start string */
+ int identifier_count; /* identifiers since start of statement */
+ int begin_depth; /* depth of begin/end routine body blocks */
/*
* Callback functions provided by the program making use of the lexer,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7ddd8c011b..f5716a3eb0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -315,6 +315,7 @@ typedef enum NodeTag
T_DeleteStmt,
T_UpdateStmt,
T_SelectStmt,
+ T_ReturnStmt,
T_AlterTableStmt,
T_AlterTableCmd,
T_AlterDomainStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d1f9ef29ca..117a925fd4 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -132,6 +132,8 @@ typedef struct Query
bool hasForUpdate; /* FOR [KEY] UPDATE/SHARE was specified */
bool hasRowSecurity; /* rewriter has applied some RLS policy */
+ bool isReturn; /* is a RETURN statement */
+
List *cteList; /* WITH list (of CommonTableExpr's) */
List *rtable; /* list of range table entries */
@@ -1675,6 +1677,16 @@ typedef struct SetOperationStmt
} SetOperationStmt;
+/*
+ * RETURN statement (inside SQL function body)
+ */
+typedef struct ReturnStmt
+{
+ NodeTag type;
+ Node *returnval;
+} ReturnStmt;
+
+
/*****************************************************************************
* Other Statements (no optimizations required)
*
@@ -2838,6 +2850,7 @@ typedef struct CreateFunctionStmt
List *parameters; /* a list of FunctionParameter */
TypeName *returnType; /* the return type */
List *options; /* a list of DefElem */
+ Node *sql_body;
} CreateFunctionStmt;
typedef enum FunctionParameterMode
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 71dcdf2889..36317a14db 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -48,6 +48,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("atomic", ATOMIC, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -344,6 +345,7 @@ PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("return", RETURN, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD, AS_LABEL)
PG_KEYWORD("returns", RETURNS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index bd30607b07..e626c8eafd 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -43,6 +43,7 @@ typedef enum
extern PGDLLIMPORT int log_statement;
extern List *pg_parse_query(const char *query_string);
+extern List *pg_rewrite_query(Query *query);
extern List *pg_analyze_and_rewrite(RawStmt *parsetree,
const char *query_string,
Oid *paramTypes, int numParams,
diff --git a/src/interfaces/ecpg/preproc/ecpg.addons b/src/interfaces/ecpg/preproc/ecpg.addons
index 300381eaad..0441561c52 100644
--- a/src/interfaces/ecpg/preproc/ecpg.addons
+++ b/src/interfaces/ecpg/preproc/ecpg.addons
@@ -87,6 +87,12 @@ ECPG: stmtTransactionStmt block
whenever_action(2);
free($1);
}
+ECPG: toplevel_stmtTransactionStmtLegacy block
+ {
+ fprintf(base_yyout, "{ ECPGtrans(__LINE__, %s, \"%s\");", connection ? connection : "NULL", $1);
+ whenever_action(2);
+ free($1);
+ }
ECPG: stmtViewStmt rule
| ECPGAllocateDescr
{
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer
index 0e4a041393..bcabf88341 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -4,8 +4,8 @@ statements: /*EMPTY*/
| statements statement
;
-statement: ecpgstart at stmt ';' { connection = NULL; }
- | ecpgstart stmt ';'
+statement: ecpgstart at toplevel_stmt ';' { connection = NULL; }
+ | ecpgstart toplevel_stmt ';'
| ecpgstart ECPGVarDeclaration
{
fprintf(base_yyout, "%s", $2);
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index ce508ae1dc..dbd6358ffd 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -255,6 +255,151 @@ SELECT pg_get_functiondef('functest_F_2'::regproc);
(1 row)
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_3a() RETURNS boolean
+ BEGIN ATOMIC
+ RETURN false;
+ END;
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+ERROR: SQL function with unquoted function body cannot have polymorphic arguments
+SELECT functest_S_1('abcd', '2020-01-01');
+ functest_s_1
+--------------
+ t
+(1 row)
+
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+ functest_s_2
+--------------
+ 1
+(1 row)
+
+SELECT functest_S_3();
+ functest_s_3
+--------------
+ f
+(1 row)
+
+SELECT functest_S_10('abcd', '2020-01-01');
+ functest_s_10
+---------------
+ t
+(1 row)
+
+SELECT functest_S_13();
+ functest_s_13
+---------------
+ f
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_1(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN ((a = 'abcd'::text) AND (b > '01-01-2001'::date)) +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_2(a text[])+
+ RETURNS integer +
+ LANGUAGE sql +
+ RETURN ((a)[1])::integer +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+ pg_get_functiondef
+----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN false +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3a'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3a()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ RETURN false; +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+ pg_get_functiondef
+-------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_10(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT ((a = 'abcd'::text) AND (b > '01-01-2001'::date)); +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_13()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT 1; +
+ SELECT false AS bool; +
+ END +
+
+(1 row)
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+SELECT functest_S_14();
+ functest_s_14
+---------------
+ 2
+(1 row)
+
+DROP TABLE functest3 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view functestv3
+drop cascades to function functest_s_14()
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
RETURNS int
@@ -284,6 +429,20 @@ SELECT routine_name, ordinal_position, parameter_name, parameter_default
(7 rows)
DROP FUNCTION functest_IS_1(int, int, text), functest_IS_2(int), functest_IS_3(int);
+CREATE TABLE functest1 (a int, b int);
+CREATE SEQUENCE functest2;
+CREATE FUNCTION functest_IS_4()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest1);
+CREATE FUNCTION functest_IS_5()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest2');
+DROP TABLE functest1 CASCADE;
+NOTICE: drop cascades to function functest_is_4()
+DROP SEQUENCE functest2 CASCADE;
+NOTICE: drop cascades to function functest_is_5()
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
@@ -302,6 +461,49 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
ERROR: cannot change routine kind
DETAIL: "functest1" is a function.
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+SELECT * FROM functest_sri1();
+ functest_sri1
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+SELECT * FROM functest_sri2();
+ functest_sri2
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
$$ SELECT a + 1 $$;
@@ -360,7 +562,7 @@ SELECT * FROM voidtest5(3);
-- Cleanup
DROP SCHEMA temp_func_test CASCADE;
-NOTICE: drop cascades to 21 other objects
+NOTICE: drop cascades to 29 other objects
DETAIL: drop cascades to function functest_a_1(text,date)
drop cascades to function functest_a_2(text[])
drop cascades to function functest_a_3()
@@ -376,7 +578,15 @@ drop cascades to function functest_f_1(integer)
drop cascades to function functest_f_2(integer)
drop cascades to function functest_f_3(integer)
drop cascades to function functest_f_4(integer)
+drop cascades to function functest_s_1(text,date)
+drop cascades to function functest_s_2(text[])
+drop cascades to function functest_s_3()
+drop cascades to function functest_s_3a()
+drop cascades to function functest_s_10(text,date)
+drop cascades to function functest_s_13()
drop cascades to function functest_b_2(bigint)
+drop cascades to function functest_sri1()
+drop cascades to function functest_sri2()
drop cascades to function voidtest1(integer)
drop cascades to function voidtest2(integer,integer)
drop cascades to function voidtest3(integer)
diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out
index 3838fa2324..5d634570c4 100644
--- a/src/test/regress/expected/create_procedure.out
+++ b/src/test/regress/expected/create_procedure.out
@@ -65,6 +65,41 @@ SELECT * FROM cp_test ORDER BY b COLLATE "C";
1 | xyzzy
(3 rows)
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+\df ptest1s
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+---------+------------------+---------------------+------
+ public | ptest1s | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest1s'::regproc);
+ pg_get_functiondef
+----------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest1s(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ INSERT INTO cp_test (a, b) +
+ VALUES (1, x); +
+ END +
+
+(1 row)
+
+CALL ptest1s('b');
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+ a | b
+---+-------
+ 1 | 0
+ 1 | a
+ 1 | b
+ 1 | xyzzy
+(4 rows)
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -146,6 +181,28 @@ AS $$
SELECT a = b;
$$;
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+\df ptest8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+--------+------------------+---------------------+------
+ public | ptest8 | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest8'::regproc);
+ pg_get_functiondef
+---------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest8(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ END +
+
+(1 row)
+
+CALL ptest8('');
-- OUT parameters
CREATE PROCEDURE ptest9(OUT a int)
LANGUAGE SQL
@@ -214,6 +271,7 @@ ALTER ROUTINE ptest1a RENAME TO ptest1;
DROP ROUTINE cp_testfunc1(int);
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
DROP USER regress_cp_user1;
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
index bd108a918f..092e15b591 100644
--- a/src/test/regress/sql/create_function_3.sql
+++ b/src/test/regress/sql/create_function_3.sql
@@ -153,6 +153,65 @@ CREATE FUNCTION functest_F_4(int) RETURNS bool LANGUAGE 'sql'
SELECT pg_get_functiondef('functest_F_2'::regproc);
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_3a() RETURNS boolean
+ BEGIN ATOMIC
+ RETURN false;
+ END;
+
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+
+SELECT functest_S_1('abcd', '2020-01-01');
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+SELECT functest_S_3();
+
+SELECT functest_S_10('abcd', '2020-01-01');
+SELECT functest_S_13();
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+SELECT pg_get_functiondef('functest_S_3a'::regproc);
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+
+SELECT functest_S_14();
+
+DROP TABLE functest3 CASCADE;
+
+
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
@@ -177,6 +236,23 @@ CREATE FUNCTION functest_IS_3(a int default 1, out b int)
DROP FUNCTION functest_IS_1(int, int, text), functest_IS_2(int), functest_IS_3(int);
+CREATE TABLE functest1 (a int, b int);
+CREATE SEQUENCE functest2;
+
+CREATE FUNCTION functest_IS_4()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest1);
+
+CREATE FUNCTION functest_IS_5()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest2');
+
+DROP TABLE functest1 CASCADE;
+DROP SEQUENCE functest2 CASCADE;
+
+
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
@@ -194,6 +270,29 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+
+SELECT * FROM functest_sri1();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+
+SELECT * FROM functest_sri2();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
diff --git a/src/test/regress/sql/create_procedure.sql b/src/test/regress/sql/create_procedure.sql
index 2ef1c82cea..8c0d70cb16 100644
--- a/src/test/regress/sql/create_procedure.sql
+++ b/src/test/regress/sql/create_procedure.sql
@@ -28,6 +28,21 @@ CREATE PROCEDURE ptest1(x text)
SELECT * FROM cp_test ORDER BY b COLLATE "C";
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+
+\df ptest1s
+SELECT pg_get_functiondef('ptest1s'::regproc);
+
+CALL ptest1s('b');
+
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -112,6 +127,16 @@ CREATE PROCEDURE ptest7(a text, b text)
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+
+\df ptest8
+SELECT pg_get_functiondef('ptest8'::regproc);
+CALL ptest8('');
+
+
-- OUT parameters
CREATE PROCEDURE ptest9(OUT a int)
@@ -170,6 +195,7 @@ CREATE USER regress_cp_user1;
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
base-commit: 49407dc32a2931550e4ff1dea314b6a25afdfc35
--
2.29.2
On 2020-11-20 08:25, Peter Eisentraut wrote:
On 2020-11-10 16:21, Georgios Kokolatos wrote:
Hi,
I noticed that this patch fails on the cfbot.
For this, I changed the status to: 'Waiting on Author'.Cheers,
//GeorgiosThe new status of this patch is: Waiting on Author
Here is an updated patch to get it building again.
Another updated patch to get things building again. I've also fixed the
last TODO I had in there in qualifying function arguments as necessary
in ruleutils.c.
--
Peter Eisentraut
2ndQuadrant, an EDB company
https://www.2ndquadrant.com/
Attachments:
v7-0001-SQL-standard-function-body.patchtext/plain; charset=UTF-8; name=v7-0001-SQL-standard-function-body.patch; x-mac-creator=0; x-mac-type=0Download
From 5204b882417e4a3c35f93bc8d22ea066c079a10e Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Thu, 11 Feb 2021 08:57:29 +0100
Subject: [PATCH v7] SQL-standard function body
This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.
Instead of the PostgreSQL-specific AS $$ string literal $$ syntax,
this allows writing out the SQL statements making up the body
unquoted, either as a single statement:
CREATE FUNCTION add(a integer, b integer) RETURNS integer
LANGUAGE SQL
RETURN a + b;
or as a block
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
BEGIN ATOMIC
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
END;
The function body is parsed at function definition time and stored as
expression nodes in a new pg_proc column prosqlbody. So at run time,
no further parsing is required.
However, this form does not support polymorphic arguments, because
there is no more parse analysis done at call time.
Dependencies between the function and the objects it uses are fully
tracked.
A new RETURN statement is introduced. This can only be used inside
function bodies. Internally, it is treated much like a SELECT
statement.
psql needs some new intelligence to keep track of function body
boundaries so that it doesn't send off statements when it sees
semicolons that are inside a function body.
Also, per SQL standard, LANGUAGE SQL is the default, so it does not
need to be specified anymore.
Discussion: https://www.postgresql.org/message-id/flat/1c11f1eb-f00c-43b7-799d-2d44132c02d7@2ndquadrant.com
---
doc/src/sgml/catalogs.sgml | 10 +
doc/src/sgml/ref/create_function.sgml | 126 +++++++++--
doc/src/sgml/ref/create_procedure.sgml | 62 ++++-
src/backend/catalog/pg_aggregate.c | 1 +
src/backend/catalog/pg_proc.c | 116 ++++++----
src/backend/commands/aggregatecmds.c | 2 +
src/backend/commands/functioncmds.c | 119 ++++++++--
src/backend/commands/typecmds.c | 4 +
src/backend/executor/functions.c | 79 ++++---
src/backend/nodes/copyfuncs.c | 15 ++
src/backend/nodes/equalfuncs.c | 13 ++
src/backend/nodes/outfuncs.c | 12 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/util/clauses.c | 126 +++++++----
src/backend/parser/analyze.c | 35 +++
src/backend/parser/gram.y | 129 ++++++++---
src/backend/tcop/postgres.c | 3 +-
src/backend/utils/adt/ruleutils.c | 132 ++++++++++-
src/bin/pg_dump/pg_dump.c | 45 +++-
src/bin/psql/describe.c | 15 +-
src/fe_utils/psqlscan.l | 23 +-
src/include/catalog/pg_proc.dat | 4 +
src/include/catalog/pg_proc.h | 6 +-
src/include/commands/defrem.h | 2 +
src/include/executor/functions.h | 15 ++
src/include/fe_utils/psqlscan_int.h | 2 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 13 ++
src/include/parser/kwlist.h | 2 +
src/include/tcop/tcopprot.h | 1 +
src/interfaces/ecpg/preproc/ecpg.addons | 6 +
src/interfaces/ecpg/preproc/ecpg.trailer | 4 +-
.../regress/expected/create_function_3.out | 212 +++++++++++++++++-
.../regress/expected/create_procedure.out | 58 +++++
src/test/regress/sql/create_function_3.sql | 99 ++++++++
src/test/regress/sql/create_procedure.sql | 26 +++
36 files changed, 1315 insertions(+), 204 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ea222c0464..f5f8ee186b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -5980,6 +5980,16 @@ <title><structname>pg_proc</structname> Columns</title>
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>prosqlbody</structfield> <type>pg_node_tree</type>
+ </para>
+ <para>
+ Pre-parsed SQL function body. This will be used for language SQL
+ functions if the body is not specified as a string constant.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>proconfig</structfield> <type>text[]</type>
diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index 3c1eaea651..1b5b9420db 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -38,6 +38,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -257,8 +258,9 @@ <title>Parameters</title>
The name of the language that the function is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -577,6 +579,44 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> function. This can
+ either be a single statement
+<programlisting>
+RETURN <replaceable>expression</replaceable>
+</programlisting>
+ or a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the function body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at function definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at function definition time. This form tracks dependencies between the
+ function and objects used in the function body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling functions. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
<para>
@@ -669,6 +709,15 @@ <title>Examples</title>
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
+</programlisting>
+ The same function written in a more SQL-conforming style, using argument
+ names and an unquoted body:
+<programlisting>
+CREATE FUNCTION add(a integer, b integer) RETURNS integer
+ LANGUAGE SQL
+ IMMUTABLE
+ RETURNS NULL ON NULL INPUT
+ RETURN a + b;
</programlisting>
</para>
@@ -799,23 +848,74 @@ <title>Writing <literal>SECURITY DEFINER</literal> Functions Safely</title>
<title>Compatibility</title>
<para>
- A <command>CREATE FUNCTION</command> command is defined in the SQL standard.
- The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. The attributes are not portable, neither are the
- different available languages.
+ A <command>CREATE FUNCTION</command> command is defined in the SQL
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. Conversely, the SQL
+ standard specifies a number of optional features that are not implemented
+ in <productname>PostgreSQL</productname>.
</para>
<para>
- For compatibility with some other database systems,
- <replaceable class="parameter">argmode</replaceable> can be written
- either before or after <replaceable class="parameter">argname</replaceable>.
- But only the first way is standard-compliant.
+ The following are important compatibility issues:
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>OR REPLACE</literal> is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For compatibility with some other database systems, <replaceable
+ class="parameter">argmode</replaceable> can be written either before or
+ after <replaceable class="parameter">argname</replaceable>. But only
+ the first way is standard-compliant.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For parameter defaults, the SQL standard specifies only the syntax with
+ the <literal>DEFAULT</literal> key word. The syntax with
+ <literal>=</literal> is used in T-SQL and Firebird.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The <literal>SETOF</literal> modifier is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Only <literal>SQL</literal> is standardized as a language.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ All other attributes except <literal>CALLED ON NULL INPUT</literal> and
+ <literal>RETURNS NULL ON NULL INPUT</literal> are not standardized.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For the body of <literal>LANGUAGE SQL</literal> functions, the SQL
+ standard only specifies the <replaceable>sql_body</replaceable> form.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
<para>
- For parameter defaults, the SQL standard specifies only the syntax with
- the <literal>DEFAULT</literal> key word. The syntax
- with <literal>=</literal> is used in T-SQL and Firebird.
+ Simple <literal>LANGUAGE SQL</literal> functions can be written in a way
+ that is both standard-conforming and portable to other implementations.
+ More complex functions using advanced features, optimization attributes, or
+ other languages will necessarily be specific to PostgreSQL in a significant
+ way.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_procedure.sgml b/doc/src/sgml/ref/create_procedure.sgml
index e258eca5ce..ecdeac1629 100644
--- a/doc/src/sgml/ref/create_procedure.sgml
+++ b/doc/src/sgml/ref/create_procedure.sgml
@@ -29,6 +29,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -162,8 +163,9 @@ <title>Parameters</title>
The name of the language that the procedure is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -299,6 +301,41 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> procedure. This should
+ be a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the procedure body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at procedure definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at procedure definition time. This form tracks dependencies between the
+ procedure and objects used in the procedure body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling procedures. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
@@ -318,6 +355,7 @@ <title>Notes</title>
<refsect1 id="sql-createprocedure-examples">
<title>Examples</title>
+ <para>
<programlisting>
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
@@ -325,9 +363,21 @@ <title>Examples</title>
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
$$;
-
+</programlisting>
+ or
+<programlisting>
+CREATE PROCEDURE insert_data(a integer, b integer)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO tbl VALUES (a);
+ INSERT INTO tbl VALUES (b);
+END;
+</programlisting>
+ and call like this:
+<programlisting>
CALL insert_data(1, 2);
</programlisting>
+ </para>
</refsect1>
<refsect1 id="sql-createprocedure-compat">
@@ -335,9 +385,9 @@ <title>Compatibility</title>
<para>
A <command>CREATE PROCEDURE</command> command is defined in the SQL
- standard. The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. For details see
- also <xref linkend="sql-createfunction"/>.
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. For details see also
+ <xref linkend="sql-createfunction"/>.
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 89f23d0add..5197076c76 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -622,6 +622,7 @@ AggregateCreate(const char *aggName,
InvalidOid, /* no validator */
"aggregate_dummy", /* placeholder (no such proc) */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_AGGREGATE,
false, /* security invoker (currently not
* definable for agg) */
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index e14eee5a19..a53b5f408e 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -32,6 +32,7 @@
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_type.h"
#include "tcop/pquery.h"
@@ -76,6 +77,7 @@ ProcedureCreate(const char *procedureName,
Oid languageValidator,
const char *prosrc,
const char *probin,
+ Node *prosqlbody,
char prokind,
bool security_definer,
bool isLeakProof,
@@ -119,8 +121,6 @@ ProcedureCreate(const char *procedureName,
/*
* sanity checks
*/
- Assert(PointerIsValid(prosrc));
-
parameterCount = parameterTypes->dim1;
if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
ereport(ERROR,
@@ -334,11 +334,18 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_protrftypes - 1] = trftypes;
else
nulls[Anum_pg_proc_protrftypes - 1] = true;
- values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ if (prosrc)
+ values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ else
+ nulls[Anum_pg_proc_prosrc - 1] = true;
if (probin)
values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin);
else
nulls[Anum_pg_proc_probin - 1] = true;
+ if (prosqlbody)
+ values[Anum_pg_proc_prosqlbody - 1] = CStringGetTextDatum(nodeToString(prosqlbody));
+ else
+ nulls[Anum_pg_proc_prosqlbody - 1] = true;
if (proconfig != PointerGetDatum(NULL))
values[Anum_pg_proc_proconfig - 1] = proconfig;
else
@@ -638,6 +645,10 @@ ProcedureCreate(const char *procedureName,
record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
free_object_addresses(addrs);
+ /* dependency on SQL routine body */
+ if (languageObjectId == SQLlanguageId && prosqlbody)
+ recordDependencyOnExpr(&myself, prosqlbody, NIL, DEPENDENCY_NORMAL);
+
/* dependency on parameter default expressions */
if (parameterDefaults)
recordDependencyOnExpr(&myself, (Node *) parameterDefaults,
@@ -861,61 +872,81 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
/* Postpone body checks if !check_function_bodies */
if (check_function_bodies)
{
- tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
- if (isnull)
- elog(ERROR, "null prosrc");
-
- prosrc = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport().
*/
callback_arg.proname = NameStr(proc->proname);
- callback_arg.prosrc = prosrc;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_function_parse_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
- /*
- * We can't do full prechecking of the function definition if there
- * are any polymorphic input types, because actual datatypes of
- * expression results will be unresolvable. The check will be done at
- * runtime instead.
- *
- * We can run the text through the raw parser though; this will at
- * least catch silly syntactic errors.
- */
- raw_parsetree_list = pg_parse_query(prosrc);
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ {
+ Node *n;
- if (!haspolyarg)
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosqlbody, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc and prosqlbody");
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = castNode(List, n);
+ else
+ querytree_list = list_make1(list_make1(n));
+ }
+ else
{
+ prosrc = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = prosrc;
+
/*
- * OK to do full precheck: analyze and rewrite the queries, then
- * verify the result type.
+ * We can't do full prechecking of the function definition if there
+ * are any polymorphic input types, because actual datatypes of
+ * expression results will be unresolvable. The check will be done at
+ * runtime instead.
+ *
+ * We can run the text through the raw parser though; this will at
+ * least catch silly syntactic errors.
*/
- SQLFunctionParseInfoPtr pinfo;
- Oid rettype;
- TupleDesc rettupdesc;
+ raw_parsetree_list = pg_parse_query(prosrc);
- /* But first, set up parameter information */
- pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
-
- querytree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (!haspolyarg)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *querytree_sublist;
-
- querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
- prosrc,
- (ParserSetupHook) sql_fn_parser_setup,
- pinfo,
- NULL);
- querytree_list = lappend(querytree_list,
- querytree_sublist);
+ /*
+ * OK to do full precheck: analyze and rewrite the queries, then
+ * verify the result type.
+ */
+ SQLFunctionParseInfoPtr pinfo;
+
+ /* But first, set up parameter information */
+ pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
+
+ querytree_list = NIL;
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *querytree_sublist;
+
+ querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ prosrc,
+ (ParserSetupHook) sql_fn_parser_setup,
+ pinfo,
+ NULL);
+ querytree_list = lappend(querytree_list,
+ querytree_sublist);
+ }
}
+ }
+
+ if (!haspolyarg)
+ {
+ Oid rettype;
+ TupleDesc rettupdesc;
check_sql_fn_statements(querytree_list);
@@ -968,6 +999,9 @@ function_parse_error_transpose(const char *prosrc)
int newerrposition;
const char *queryText;
+ if (!prosrc)
+ return false;
+
/*
* Nothing to do unless we are dealing with a syntax error that has a
* cursor position.
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 69c50ac087..046cf2df08 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -312,9 +312,11 @@ DefineAggregate(ParseState *pstate,
InvalidOid,
OBJECT_AGGREGATE,
¶meterTypes,
+ NULL,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ NULL,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 7a4e104623..8a9faccb03 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -53,9 +53,11 @@
#include "commands/proclang.h"
#include "executor/execdesc.h"
#include "executor/executor.h"
+#include "executor/functions.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
@@ -186,9 +188,11 @@ interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType)
@@ -283,7 +287,11 @@ interpret_function_parameter_list(ParseState *pstate,
/* handle input parameters */
if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
+ {
isinput = true;
+ if (parameterTypes_list)
+ *parameterTypes_list = lappend_oid(*parameterTypes_list, toid);
+ }
/* handle signature parameters */
if (fp->mode == FUNC_PARAM_IN || fp->mode == FUNC_PARAM_INOUT ||
@@ -372,6 +380,9 @@ interpret_function_parameter_list(ParseState *pstate,
have_names = true;
}
+ if (inParameterNames_list)
+ *inParameterNames_list = lappend(*inParameterNames_list, makeString(fp->name ? fp->name : pstrdup("")));
+
if (fp->defexpr)
{
Node *def;
@@ -786,28 +797,10 @@ compute_function_attributes(ParseState *pstate,
defel->defname);
}
- /* process required items */
if (as_item)
*as = (List *) as_item->arg;
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no function body specified")));
- *as = NIL; /* keep compiler quiet */
- }
-
if (language_item)
*language = strVal(language_item->arg);
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no language specified")));
- *language = NULL; /* keep compiler quiet */
- }
-
- /* process optional items */
if (transform_item)
*transform = transform_item->arg;
if (windowfunc_item)
@@ -856,10 +849,21 @@ compute_function_attributes(ParseState *pstate,
*/
static void
interpret_AS_clause(Oid languageOid, const char *languageName,
- char *funcname, List *as,
- char **prosrc_str_p, char **probin_str_p)
+ char *funcname, List *as, Node *sql_body_in,
+ List *parameterTypes, List *inParameterNames,
+ char **prosrc_str_p, char **probin_str_p, Node **sql_body_out)
{
- Assert(as != NIL);
+ if (sql_body_in && as)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("duplicate function body specified")));
+
+ if (sql_body_in && languageOid != SQLlanguageId)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("inline SQL function body only valid for language SQL")));
+
+ *sql_body_out = NULL;
if (languageOid == ClanguageId)
{
@@ -881,6 +885,66 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
*prosrc_str_p = funcname;
}
}
+ else if (sql_body_in)
+ {
+ SQLFunctionParseInfoPtr pinfo;
+
+ pinfo = (SQLFunctionParseInfoPtr) palloc0(sizeof(SQLFunctionParseInfo));
+
+ pinfo->fname = funcname;
+ pinfo->nargs = list_length(parameterTypes);
+ pinfo->argtypes = (Oid *) palloc(pinfo->nargs * sizeof(Oid));
+ pinfo->argnames = (char **) palloc(pinfo->nargs * sizeof(char *));
+ for (int i = 0; i < list_length(parameterTypes); i++)
+ {
+ char *s = strVal(list_nth(inParameterNames, i));
+
+ pinfo->argtypes[i] = list_nth_oid(parameterTypes, i);
+ if (IsPolymorphicType(pinfo->argtypes[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("SQL function with unquoted function body cannot have polymorphic arguments")));
+
+ if (s[0] != '\0')
+ pinfo->argnames[i] = s;
+ else
+ pinfo->argnames[i] = NULL;
+ }
+
+ if (IsA(sql_body_in, List))
+ {
+ List *stmts = linitial_node(List, castNode(List, sql_body_in));
+ ListCell *lc;
+ List *transformed_stmts = NIL;
+
+ foreach(lc, stmts)
+ {
+ Node *stmt = lfirst(lc);
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, stmt);
+ transformed_stmts = lappend(transformed_stmts, q);
+ free_parsestate(pstate);
+ }
+
+ *sql_body_out = (Node *) list_make1(transformed_stmts);
+ }
+ else
+ {
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, sql_body_in);
+
+ *sql_body_out = (Node *) q;
+ }
+
+ *probin_str_p = NULL;
+ *prosrc_str_p = NULL;
+ }
else
{
/* Everything else wants the given string in prosrc. */
@@ -919,6 +983,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
{
char *probin_str;
char *prosrc_str;
+ Node *prosqlbody;
Oid prorettype;
bool returnsSet;
char *language;
@@ -929,9 +994,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
Oid namespaceId;
AclResult aclresult;
oidvector *parameterTypes;
+ List *parameterTypes_list = NIL;
ArrayType *allParameterTypes;
ArrayType *parameterModes;
ArrayType *parameterNames;
+ List *inParameterNames_list = NIL;
List *parameterDefaults;
Oid variadicArgType;
List *trftypes_list = NIL;
@@ -962,6 +1029,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
get_namespace_name(namespaceId));
/* Set default attributes */
+ as_clause = NULL;
+ language = "sql";
isWindowFunc = false;
isStrict = false;
security = false;
@@ -1053,9 +1122,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageOid,
stmt->is_procedure ? OBJECT_PROCEDURE : OBJECT_FUNCTION,
¶meterTypes,
+ ¶meterTypes_list,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ &inParameterNames_list,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
@@ -1112,8 +1183,9 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
trftypes = NULL;
}
- interpret_AS_clause(languageOid, language, funcname, as_clause,
- &prosrc_str, &probin_str);
+ interpret_AS_clause(languageOid, language, funcname, as_clause, stmt->sql_body,
+ parameterTypes_list, inParameterNames_list,
+ &prosrc_str, &probin_str, &prosqlbody);
/*
* Set default values for COST and ROWS depending on other parameters;
@@ -1155,6 +1227,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageValidator,
prosrc_str, /* converted to text later */
probin_str, /* converted to text later */
+ prosqlbody,
stmt->is_procedure ? PROKIND_PROCEDURE : (isWindowFunc ? PROKIND_WINDOW : PROKIND_FUNCTION),
security,
isLeakProof,
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 76218fb47e..e975508ffa 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1775,6 +1775,7 @@ makeRangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR, /* language validator */
prosrc[i], /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
@@ -1839,6 +1840,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR,
"multirange_constructor0", /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
@@ -1882,6 +1884,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR,
"multirange_constructor1", /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
@@ -1922,6 +1925,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR,
"multirange_constructor2", /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 7bb752ace3..642683843e 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -26,6 +26,7 @@
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_func.h"
+#include "rewrite/rewriteHandler.h"
#include "storage/proc.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
@@ -128,21 +129,6 @@ typedef struct
typedef SQLFunctionCache *SQLFunctionCachePtr;
-/*
- * Data structure needed by the parser callback hooks to resolve parameter
- * references during parsing of a SQL function's body. This is separate from
- * SQLFunctionCache since we sometimes do parsing separately from execution.
- */
-typedef struct SQLFunctionParseInfo
-{
- char *fname; /* function's name */
- int nargs; /* number of input arguments */
- Oid *argtypes; /* resolved types of input arguments */
- char **argnames; /* names of input arguments; NULL if none */
- /* Note that argnames[i] can be NULL, if some args are unnamed */
- Oid collation; /* function's input collation, if known */
-} SQLFunctionParseInfo;
-
/* non-export function prototypes */
static Node *sql_fn_param_ref(ParseState *pstate, ParamRef *pref);
@@ -607,7 +593,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
HeapTuple procedureTuple;
Form_pg_proc procedureStruct;
SQLFunctionCachePtr fcache;
- List *raw_parsetree_list;
List *queryTree_list;
List *resulttlist;
ListCell *lc;
@@ -682,9 +667,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
procedureTuple,
Anum_pg_proc_prosrc,
&isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", foid);
- fcache->src = TextDatumGetCString(tmp);
/*
* Parse and rewrite the queries in the function text. Use sublists to
@@ -695,20 +677,55 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
* but we'll not worry about it until the module is rewritten to use
* plancache.c.
*/
- raw_parsetree_list = pg_parse_query(fcache->src);
-
queryTree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (isNull)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *queryTree_sublist;
-
- queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
- fcache->src,
- (ParserSetupHook) sql_fn_parser_setup,
- fcache->pinfo,
- NULL);
- queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ Node *n;
+ List *stored_query_list;
+
+ tmp = SysCacheGetAttr(PROCOID,
+ procedureTuple,
+ Anum_pg_proc_prosqlbody,
+ &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", foid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ stored_query_list = linitial_node(List, castNode(List, n));
+ else
+ stored_query_list = list_make1(n);
+
+ foreach(lc, stored_query_list)
+ {
+ Query *parsetree = lfirst_node(Query, lc);
+ List *queryTree_sublist;
+
+ AcquireRewriteLocks(parsetree, true, false);
+ queryTree_sublist = pg_rewrite_query(parsetree);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ }
+ }
+ else
+ {
+ List *raw_parsetree_list;
+
+ fcache->src = TextDatumGetCString(tmp);
+
+ raw_parsetree_list = pg_parse_query(fcache->src);
+
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *queryTree_sublist;
+
+ queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ fcache->src,
+ (ParserSetupHook) sql_fn_parser_setup,
+ fcache->pinfo,
+ NULL);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ }
}
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 65bbc18ecb..6fcdc3c107 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3105,6 +3105,7 @@ _copyQuery(const Query *from)
COPY_SCALAR_FIELD(hasModifyingCTE);
COPY_SCALAR_FIELD(hasForUpdate);
COPY_SCALAR_FIELD(hasRowSecurity);
+ COPY_SCALAR_FIELD(isReturn);
COPY_NODE_FIELD(cteList);
COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(jointree);
@@ -3233,6 +3234,16 @@ _copySetOperationStmt(const SetOperationStmt *from)
return newnode;
}
+static ReturnStmt *
+_copyReturnStmt(const ReturnStmt *from)
+{
+ ReturnStmt *newnode = makeNode(ReturnStmt);
+
+ COPY_NODE_FIELD(returnval);
+
+ return newnode;
+}
+
static PLAssignStmt *
_copyPLAssignStmt(const PLAssignStmt *from)
{
@@ -3616,6 +3627,7 @@ _copyCreateFunctionStmt(const CreateFunctionStmt *from)
COPY_NODE_FIELD(parameters);
COPY_NODE_FIELD(returnType);
COPY_NODE_FIELD(options);
+ COPY_NODE_FIELD(sql_body);
return newnode;
}
@@ -5269,6 +5281,9 @@ copyObjectImpl(const void *from)
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
+ case T_ReturnStmt:
+ retval = _copyReturnStmt(from);
+ break;
case T_PLAssignStmt:
retval = _copyPLAssignStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c2d73626fc..4642fa24fe 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -969,6 +969,7 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_SCALAR_FIELD(hasModifyingCTE);
COMPARE_SCALAR_FIELD(hasForUpdate);
COMPARE_SCALAR_FIELD(hasRowSecurity);
+ COMPARE_SCALAR_FIELD(isReturn);
COMPARE_NODE_FIELD(cteList);
COMPARE_NODE_FIELD(rtable);
COMPARE_NODE_FIELD(jointree);
@@ -1085,6 +1086,14 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
return true;
}
+static bool
+_equalReturnStmt(const ReturnStmt *a, const ReturnStmt *b)
+{
+ COMPARE_NODE_FIELD(returnval);
+
+ return true;
+}
+
static bool
_equalPLAssignStmt(const PLAssignStmt *a, const PLAssignStmt *b)
{
@@ -1403,6 +1412,7 @@ _equalCreateFunctionStmt(const CreateFunctionStmt *a, const CreateFunctionStmt *
COMPARE_NODE_FIELD(parameters);
COMPARE_NODE_FIELD(returnType);
COMPARE_NODE_FIELD(options);
+ COMPARE_NODE_FIELD(sql_body);
return true;
}
@@ -3318,6 +3328,9 @@ equal(const void *a, const void *b)
case T_SetOperationStmt:
retval = _equalSetOperationStmt(a, b);
break;
+ case T_ReturnStmt:
+ retval = _equalReturnStmt(a, b);
+ break;
case T_PLAssignStmt:
retval = _equalPLAssignStmt(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index f5dcedf6e8..9f30e8f9c9 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2773,6 +2773,14 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
WRITE_NODE_FIELD(rarg);
}
+static void
+_outReturnStmt(StringInfo str, const ReturnStmt *node)
+{
+ WRITE_NODE_TYPE("RETURN");
+
+ WRITE_NODE_FIELD(returnval);
+}
+
static void
_outPLAssignStmt(StringInfo str, const PLAssignStmt *node)
{
@@ -2975,6 +2983,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_BOOL_FIELD(hasModifyingCTE);
WRITE_BOOL_FIELD(hasForUpdate);
WRITE_BOOL_FIELD(hasRowSecurity);
+ WRITE_BOOL_FIELD(isReturn);
WRITE_NODE_FIELD(cteList);
WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(jointree);
@@ -4251,6 +4260,9 @@ outNode(StringInfo str, const void *obj)
case T_SelectStmt:
_outSelectStmt(str, obj);
break;
+ case T_ReturnStmt:
+ _outReturnStmt(str, obj);
+ break;
case T_PLAssignStmt:
_outPLAssignStmt(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 4388aae71d..138ed4b996 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -263,6 +263,7 @@ _readQuery(void)
READ_BOOL_FIELD(hasModifyingCTE);
READ_BOOL_FIELD(hasForUpdate);
READ_BOOL_FIELD(hasRowSecurity);
+ READ_BOOL_FIELD(isReturn);
READ_NODE_FIELD(cteList);
READ_NODE_FIELD(rtable);
READ_NODE_FIELD(jointree);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index f3786dd2b6..5f238acbe4 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4156,27 +4156,47 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
- /* Fetch the function body */
- tmp = SysCacheGetAttr(PROCOID,
- func_tuple,
- Anum_pg_proc_prosrc,
- &isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", funcid);
- src = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = src;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ {
+ Node *n;
+ List *querytree_list;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", funcid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = linitial_node(List, castNode(List, n));
+ else
+ querytree_list = list_make1(n);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We need a
* dummy FuncExpr node containing the already-simplified arguments to pass
@@ -4220,6 +4240,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
querytree = transformTopLevelStmt(pstate, linitial(raw_parsetree_list));
free_parsestate(pstate);
+ }
/*
* The single command must be a simple "SELECT expression".
@@ -4476,12 +4497,15 @@ sql_inline_error_callback(void *arg)
int syntaxerrposition;
/* If it's a syntax error, convert to internal syntax error report */
- syntaxerrposition = geterrposition();
- if (syntaxerrposition > 0)
+ if (callback_arg->prosrc)
{
- errposition(0);
- internalerrposition(syntaxerrposition);
- internalerrquery(callback_arg->prosrc);
+ syntaxerrposition = geterrposition();
+ if (syntaxerrposition > 0)
+ {
+ errposition(0);
+ internalerrposition(syntaxerrposition);
+ internalerrquery(callback_arg->prosrc);
+ }
}
errcontext("SQL function \"%s\" during inlining", callback_arg->proname);
@@ -4593,7 +4617,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
Oid func_oid;
HeapTuple func_tuple;
Form_pg_proc funcform;
- char *src;
Datum tmp;
bool isNull;
MemoryContext oldcxt;
@@ -4702,27 +4725,53 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
- /* Fetch the function body */
- tmp = SysCacheGetAttr(PROCOID,
- func_tuple,
- Anum_pg_proc_prosrc,
- &isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", func_oid);
- src = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = src;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ {
+ Node *n;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", func_oid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = linitial_node(List, castNode(List, n));
+ else
+ querytree_list = list_make1(n);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+
+ querytree_list = pg_rewrite_query(querytree);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
+ char *src;
+
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We can
* use the FuncExpr just created as the input for
@@ -4732,18 +4781,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
(Node *) fexpr,
fexpr->inputcollid);
- /*
- * Also resolve the actual function result tupdesc, if composite. If the
- * function is just declared to return RECORD, dig the info out of the AS
- * clause.
- */
- functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
- if (functypclass == TYPEFUNC_RECORD)
- rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
- rtfunc->funccoltypes,
- rtfunc->funccoltypmods,
- rtfunc->funccolcollations);
-
/*
* Parse, analyze, and rewrite (unlike inline_function(), we can't skip
* rewriting here). We can fail as soon as we find more than one query,
@@ -4760,6 +4797,19 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
if (list_length(querytree_list) != 1)
goto fail;
querytree = linitial(querytree_list);
+ }
+
+ /*
+ * Also resolve the actual function result tupdesc, if composite. If the
+ * function is just declared to return RECORD, dig the info out of the AS
+ * clause.
+ */
+ functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
+ if (functypclass == TYPEFUNC_RECORD)
+ rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
+ rtfunc->funccoltypes,
+ rtfunc->funccoltypmods,
+ rtfunc->funccolcollations);
/*
* The single command must be a plain SELECT.
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0f3a70c49a..554db99df2 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -68,6 +68,7 @@ static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
bool isTopLevel, List **targetlist);
static void determineRecursiveColTypes(ParseState *pstate,
Node *larg, List *nrtargetlist);
+static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList);
static List *transformUpdateTargetList(ParseState *pstate,
@@ -308,6 +309,10 @@ transformStmt(ParseState *pstate, Node *parseTree)
}
break;
+ case T_ReturnStmt:
+ result = transformReturnStmt(pstate, (ReturnStmt *) parseTree);
+ break;
+
case T_PLAssignStmt:
result = transformPLAssignStmt(pstate,
(PLAssignStmt *) parseTree);
@@ -2227,6 +2232,36 @@ determineRecursiveColTypes(ParseState *pstate, Node *larg, List *nrtargetlist)
}
+/*
+ * transformReturnStmt -
+ * transforms a return statement
+ */
+static Query *
+transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+
+ qry->commandType = CMD_SELECT;
+ qry->isReturn = true;
+
+ qry->targetList = list_make1(makeTargetEntry((Expr *) transformExpr(pstate, stmt->returnval, EXPR_KIND_SELECT_TARGET),
+ 1, NULL, false));
+
+ if (pstate->p_resolve_unknowns)
+ resolveTargetListUnknowns(pstate, qry->targetList);
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasAggs = pstate->p_hasAggs;
+
+ assign_query_collations(pstate, qry);
+
+ return qry;
+}
+
+
/*
* transformUpdateStmt -
* transforms an update statement
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dd72a9fc3c..e184e3ae07 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -252,7 +252,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct SelectLimit *selectlimit;
}
-%type <node> stmt schema_stmt
+%type <node> stmt toplevel_stmt schema_stmt routine_body_stmt
AlterEventTrigStmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
@@ -279,9 +279,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
- RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
+ RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt
RuleActionStmt RuleActionStmtOrEmpty RuleStmt
- SecLabelStmt SelectStmt TransactionStmt TruncateStmt
+ SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
UnlistenStmt UpdateStmt VacuumStmt
VariableResetStmt VariableSetStmt VariableShowStmt
ViewStmt CheckPointStmt CreateConversionStmt
@@ -385,14 +385,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> vacuum_relation
%type <selectlimit> opt_select_limit select_limit limit_clause
-%type <list> parse_toplevel stmtmulti
+%type <list> parse_toplevel stmtmulti routine_body_stmt_list
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
reloptions opt_reloptions
OptWith opt_definition func_args func_args_list
func_args_with_defaults func_args_with_defaults_list
aggr_args aggr_args_list
- func_as createfunc_opt_list alterfunc_opt_list
+ func_as createfunc_opt_list opt_createfunc_opt_list alterfunc_opt_list
old_aggr_definition old_aggr_list
oper_argtypes RuleActionList RuleActionMulti
opt_column_list columnList opt_name_list
@@ -418,6 +418,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
vacuum_relation_list opt_vacuum_relation_list
drop_option_list
+%type <node> opt_routine_body
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
@@ -623,7 +624,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATOMIC ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BREADTH BY
@@ -685,7 +686,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING
REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
- RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
+ RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
@@ -855,7 +856,7 @@ parse_toplevel:
* we'd get -1 for the location in such cases.
* We also take care to discard empty statements entirely.
*/
-stmtmulti: stmtmulti ';' stmt
+stmtmulti: stmtmulti ';' toplevel_stmt
{
if ($1 != NIL)
{
@@ -867,7 +868,7 @@ stmtmulti: stmtmulti ';' stmt
else
$$ = $1;
}
- | stmt
+ | toplevel_stmt
{
if ($1 != NULL)
$$ = list_make1(makeRawStmt($1, 0));
@@ -876,7 +877,16 @@ stmtmulti: stmtmulti ';' stmt
}
;
-stmt :
+/*
+ * toplevel_stmt includes BEGIN and END. stmt does not include them, because
+ * those words have different meanings in function bodys.
+ */
+toplevel_stmt:
+ stmt
+ | TransactionStmtLegacy
+ ;
+
+stmt:
AlterEventTrigStmt
| AlterDatabaseStmt
| AlterDatabaseSetStmt
@@ -7399,7 +7409,7 @@ opt_nulls_order: NULLS_LA FIRST_P { $$ = SORTBY_NULLS_FIRST; }
CreateFunctionStmt:
CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS func_return createfunc_opt_list
+ RETURNS func_return opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7408,10 +7418,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = $7;
n->options = $8;
+ n->sql_body = $9;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS TABLE '(' table_func_column_list ')' createfunc_opt_list
+ RETURNS TABLE '(' table_func_column_list ')' opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7421,10 +7432,11 @@ CreateFunctionStmt:
n->returnType = TableFuncTypeName($9);
n->returnType->location = @7;
n->options = $11;
+ n->sql_body = $12;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7433,10 +7445,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
| CREATE opt_or_replace PROCEDURE func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = true;
@@ -7445,6 +7458,7 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
;
@@ -7755,6 +7769,11 @@ aggregate_with_argtypes_list:
{ $$ = lappend($1, $3); }
;
+opt_createfunc_opt_list:
+ createfunc_opt_list
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
createfunc_opt_list:
/* Must be at least one to prevent conflict */
createfunc_opt_item { $$ = list_make1($1); }
@@ -7866,6 +7885,51 @@ func_as: Sconst { $$ = list_make1(makeString($1)); }
}
;
+ReturnStmt: RETURN a_expr
+ {
+ ReturnStmt *r = makeNode(ReturnStmt);
+ r->returnval = (Node *) $2;
+ $$ = (Node *) r;
+ }
+ ;
+
+opt_routine_body:
+ ReturnStmt
+ {
+ $$ = $1;
+ }
+ | BEGIN_P ATOMIC routine_body_stmt_list END_P
+ {
+ /*
+ * A compound statement is stored as a single-item list
+ * containing the list of statements as its member. That
+ * way, the parse analysis code can tell apart an empty
+ * body from no body at all.
+ */
+ $$ = (Node *) list_make1($3);
+ }
+ | /*EMPTY*/
+ {
+ $$ = NULL;
+ }
+ ;
+
+routine_body_stmt_list:
+ routine_body_stmt_list routine_body_stmt ';'
+ {
+ $$ = lappend($1, $2);
+ }
+ | /*EMPTY*/
+ {
+ $$ = NIL;
+ }
+ ;
+
+routine_body_stmt:
+ stmt
+ | ReturnStmt
+ ;
+
transform_type_list:
FOR TYPE_P Typename { $$ = list_make1($3); }
| transform_type_list ',' FOR TYPE_P Typename { $$ = lappend($1, $5); }
@@ -9799,13 +9863,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | BEGIN_P opt_transaction transaction_mode_list_or_empty
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_BEGIN;
- n->options = $3;
- $$ = (Node *)n;
- }
| START TRANSACTION transaction_mode_list_or_empty
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9821,14 +9878,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | END_P opt_transaction opt_transaction_chain
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_COMMIT;
- n->options = NIL;
- n->chain = $3;
- $$ = (Node *)n;
- }
| ROLLBACK opt_transaction opt_transaction_chain
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9895,6 +9944,24 @@ TransactionStmt:
}
;
+TransactionStmtLegacy:
+ BEGIN_P opt_transaction transaction_mode_list_or_empty
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_BEGIN;
+ n->options = $3;
+ $$ = (Node *)n;
+ }
+ | END_P opt_transaction opt_transaction_chain
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_COMMIT;
+ n->options = NIL;
+ n->chain = $3;
+ $$ = (Node *)n;
+ }
+ ;
+
opt_transaction: WORK
| TRANSACTION
| /*EMPTY*/
@@ -15261,6 +15328,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| BACKWARD
@@ -15461,6 +15529,7 @@ unreserved_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| ROLE
@@ -15767,6 +15836,7 @@ bare_label_keyword:
| ASSIGNMENT
| ASYMMETRIC
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| AUTHORIZATION
@@ -16039,6 +16109,7 @@ bare_label_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| RIGHT
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index cb5a96117f..44060e4090 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -177,7 +177,6 @@ static int interactive_getc(void);
static int SocketBackend(StringInfo inBuf);
static int ReadCommand(StringInfo inBuf);
static void forbidden_in_wal_sender(char firstchar);
-static List *pg_rewrite_query(Query *query);
static bool check_log_statement(List *stmt_list);
static int errdetail_execute(List *raw_parsetree_list);
static int errdetail_params(ParamListInfo params);
@@ -762,7 +761,7 @@ pg_analyze_and_rewrite_params(RawStmt *parsetree,
* Note: query must just have come from the parser, because we do not do
* AcquireRewriteLocks() on it.
*/
-static List *
+List *
pg_rewrite_query(Query *query)
{
List *querytree_list;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 4a9244f4f6..54f722182c 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -172,6 +172,10 @@ typedef struct
List *outer_tlist; /* referent for OUTER_VAR Vars */
List *inner_tlist; /* referent for INNER_VAR Vars */
List *index_tlist; /* referent for INDEX_VAR Vars */
+ /* Special namespace representing a function signature: */
+ char *funcname;
+ int numargs;
+ char **argnames;
} deparse_namespace;
/*
@@ -347,6 +351,7 @@ static int print_function_arguments(StringInfo buf, HeapTuple proctup,
bool print_table_args, bool print_defaults);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
static void print_function_trftypes(StringInfo buf, HeapTuple proctup);
+static void print_function_sqlbody(StringInfo buf, HeapTuple proctup);
static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used);
static void set_deparse_for_query(deparse_namespace *dpns, Query *query,
@@ -2799,6 +2804,13 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
}
/* And finally the function definition ... */
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
+ if (proc->prolang == SQLlanguageId && !isnull)
+ {
+ print_function_sqlbody(&buf, proctup);
+ }
+ else
+ {
appendStringInfoString(&buf, "AS ");
tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
@@ -2830,6 +2842,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, dq.data, dq.len);
appendStringInfoString(&buf, prosrc);
appendBinaryStringInfo(&buf, dq.data, dq.len);
+ }
appendStringInfoChar(&buf, '\n');
@@ -3213,6 +3226,76 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(string_to_text(str));
}
+static void
+print_function_sqlbody(StringInfo buf, HeapTuple proctup)
+{
+ int numargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ deparse_namespace dpns = {0};
+ Datum tmp;
+ bool isnull;
+ Node *n;
+
+ dpns.funcname = pstrdup(NameStr(((Form_pg_proc) GETSTRUCT(proctup))->proname));
+ numargs = get_func_arg_info(proctup,
+ &argtypes, &argnames, &argmodes);
+ dpns.numargs = numargs;
+ dpns.argnames = argnames;
+
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
+ Assert(!isnull);
+ n = stringToNode(TextDatumGetCString(tmp));
+
+ if (IsA(n, List))
+ {
+ List *stmts;
+ ListCell *lc;
+
+ stmts = linitial(castNode(List, n));
+
+ appendStringInfoString(buf, "BEGIN ATOMIC\n");
+
+ foreach(lc, stmts)
+ {
+ Query *query = lfirst_node(Query, lc);
+
+ get_query_def(query, buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ appendStringInfoChar(buf, ';');
+ appendStringInfoChar(buf, '\n');
+ }
+
+ appendStringInfoString(buf, "END");
+ }
+ else
+ {
+ get_query_def(castNode(Query, n), buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ appendStringInfoChar(buf, '\n');
+ }
+}
+
+Datum
+pg_get_function_sqlbody(PG_FUNCTION_ARGS)
+{
+ Oid funcid = PG_GETARG_OID(0);
+ StringInfoData buf;
+ HeapTuple proctup;
+
+ initStringInfo(&buf);
+
+ /* Look up the function */
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ PG_RETURN_NULL();
+
+ print_function_sqlbody(&buf, proctup);
+
+ ReleaseSysCache(proctup);
+
+ PG_RETURN_TEXT_P(cstring_to_text(buf.data));
+}
+
/*
* deparse_expression - General utility for deparsing expressions
@@ -5461,7 +5544,10 @@ get_basic_select_query(Query *query, deparse_context *context,
/*
* Build up the query string - first we say SELECT
*/
- appendStringInfoString(buf, "SELECT");
+ if (query->isReturn)
+ appendStringInfoString(buf, "RETURN");
+ else
+ appendStringInfoString(buf, "SELECT");
/* Add the DISTINCT clause if given */
if (query->distinctClause != NIL)
@@ -7593,6 +7679,50 @@ get_parameter(Param *param, deparse_context *context)
return;
}
+ /*
+ * If it's an external parameter, see if the outermost namespace provides
+ * function argument names.
+ */
+ if (param->paramkind == PARAM_EXTERN)
+ {
+ dpns = lfirst(list_tail(context->namespaces));
+ if (dpns->argnames)
+ {
+ char *argname = dpns->argnames[param->paramid - 1];
+
+ if (argname)
+ {
+ bool should_qualify = false;
+ ListCell *lc;
+
+ /*
+ * Qualify the parameter name if there are any other deparse
+ * namespaces with range tables. This avoids qualifying in
+ * trivial cases like "RETURN a + b", but makes it safe in all
+ * other cases.
+ */
+ foreach (lc, context->namespaces)
+ {
+ deparse_namespace *dpns = lfirst(lc);
+
+ if (list_length(dpns->rtable_names) > 0)
+ {
+ should_qualify = true;
+ break;
+ }
+ }
+ if (should_qualify)
+ {
+ appendStringInfoString(context->buf, quote_identifier(dpns->funcname));
+ appendStringInfoChar(context->buf, '.');
+ }
+
+ appendStringInfoString(context->buf, quote_identifier(argname));
+ return;
+ }
+ }
+ }
+
/*
* Not PARAM_EXEC, or couldn't find referent: just print $N.
*/
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index eb988d7eb4..98a74d1efb 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11979,6 +11979,7 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
char *proretset;
char *prosrc;
char *probin;
+ char *prosqlbody;
char *funcargs;
char *funciargs;
char *funcresult;
@@ -12025,7 +12026,7 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
"provolatile,\n"
"proisstrict,\n"
"prosecdef,\n"
- "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname,\n");
+ "lanname,\n");
if (fout->remoteVersion >= 80300)
appendPQExpBufferStr(query,
@@ -12045,9 +12046,9 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
* pg_get_function_result instead of examining proallargtypes etc.
*/
appendPQExpBufferStr(query,
- "pg_catalog.pg_get_function_arguments(oid) AS funcargs,\n"
- "pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs,\n"
- "pg_catalog.pg_get_function_result(oid) AS funcresult,\n");
+ "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n"
+ "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n"
+ "pg_catalog.pg_get_function_result(p.oid) AS funcresult,\n");
}
else if (fout->remoteVersion >= 80100)
appendPQExpBufferStr(query,
@@ -12090,21 +12091,39 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
if (fout->remoteVersion >= 120000)
appendPQExpBufferStr(query,
- "prosupport\n");
+ "prosupport,\n");
else
appendPQExpBufferStr(query,
- "'-' AS prosupport\n");
+ "'-' AS prosupport,\n");
+
+ if (fout->remoteVersion >= 140000)
+ appendPQExpBufferStr(query,
+ "CASE WHEN prosrc IS NULL AND lanname = 'sql' THEN pg_get_function_sqlbody(p.oid) END AS prosqlbody\n");
+ else
+ appendPQExpBufferStr(query,
+ "NULL AS prosqlbody\n");
appendPQExpBuffer(query,
- "FROM pg_catalog.pg_proc "
- "WHERE oid = '%u'::pg_catalog.oid",
+ "FROM pg_catalog.pg_proc p, pg_catalog.pg_language l\n"
+ "WHERE p.oid = '%u'::pg_catalog.oid "
+ "AND l.oid = p.prolang",
finfo->dobj.catId.oid);
res = ExecuteSqlQueryForSingleRow(fout, query->data);
proretset = PQgetvalue(res, 0, PQfnumber(res, "proretset"));
- prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
- probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ if (PQgetisnull(res, 0, PQfnumber(res, "prosqlbody")))
+ {
+ prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
+ probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ prosqlbody = NULL;
+ }
+ else
+ {
+ prosrc = NULL;
+ probin = NULL;
+ prosqlbody = PQgetvalue(res, 0, PQfnumber(res, "prosqlbody"));
+ }
if (fout->remoteVersion >= 80400)
{
funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
@@ -12141,7 +12160,11 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
* versions would set it to "-". There are no known cases in which prosrc
* is unused, so the tests below for "-" are probably useless.
*/
- if (probin[0] != '\0' && strcmp(probin, "-") != 0)
+ if (prosqlbody)
+ {
+ appendPQExpBufferStr(asPart, prosqlbody);
+ }
+ else if (probin[0] != '\0' && strcmp(probin, "-") != 0)
{
appendPQExpBufferStr(asPart, "AS ");
appendStringLiteralAH(asPart, probin, fout);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 20af5a92b4..a47c78d4bc 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -505,11 +505,18 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
appendPQExpBufferStr(&buf, ",\n ");
printACLColumn(&buf, "p.proacl");
appendPQExpBuffer(&buf,
- ",\n l.lanname as \"%s\""
- ",\n p.prosrc as \"%s\""
+ ",\n l.lanname as \"%s\"",
+ gettext_noop("Language"));
+ if (pset.sversion >= 140000)
+ appendPQExpBuffer(&buf,
+ ",\n COALESCE(p.prosrc, pg_catalog.pg_get_function_sqlbody(p.oid)) as \"%s\"",
+ gettext_noop("Source code"));
+ else
+ appendPQExpBuffer(&buf,
+ ",\n p.prosrc as \"%s\"",
+ gettext_noop("Source code"));
+ appendPQExpBuffer(&buf,
",\n pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"",
- gettext_noop("Language"),
- gettext_noop("Source code"),
gettext_noop("Description"));
}
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 216ff30993..a492a32416 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -645,10 +645,11 @@ other .
";" {
ECHO;
- if (cur_state->paren_depth == 0)
+ if (cur_state->paren_depth == 0 && cur_state->begin_depth == 0)
{
/* Terminate lexing temporarily */
cur_state->start_state = YY_START;
+ cur_state->identifier_count = 0;
return LEXRES_SEMI;
}
}
@@ -661,6 +662,8 @@ other .
"\\"[;:] {
/* Force a semi-colon or colon into the query buffer */
psqlscan_emit(cur_state, yytext + 1, 1);
+ if (yytext[1] == ';')
+ cur_state->identifier_count = 0;
}
"\\" {
@@ -867,6 +870,17 @@ other .
{identifier} {
+ cur_state->identifier_count++;
+ if (pg_strcasecmp(yytext, "begin") == 0)
+ {
+ if (cur_state->identifier_count > 1)
+ cur_state->begin_depth++;
+ }
+ else if (pg_strcasecmp(yytext, "end") == 0)
+ {
+ if (cur_state->begin_depth > 0)
+ cur_state->begin_depth--;
+ }
ECHO;
}
@@ -1054,6 +1068,11 @@ psql_scan(PsqlScanState state,
result = PSCAN_INCOMPLETE;
*prompt = PROMPT_PAREN;
}
+ if (state->begin_depth > 0)
+ {
+ result = PSCAN_INCOMPLETE;
+ *prompt = PROMPT_CONTINUE;
+ }
else if (query_buf->len > 0)
{
result = PSCAN_EOL;
@@ -1170,6 +1189,8 @@ psql_scan_reset(PsqlScanState state)
if (state->dolqstart)
free(state->dolqstart);
state->dolqstart = NULL;
+ state->identifier_count = 0;
+ state->begin_depth = 0;
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..38b57b62b4 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3689,6 +3689,10 @@
proname => 'pg_get_function_arg_default', provolatile => 's',
prorettype => 'text', proargtypes => 'oid int4',
prosrc => 'pg_get_function_arg_default' },
+{ oid => '9704', descr => 'function SQL body',
+ proname => 'pg_get_function_sqlbody', provolatile => 's',
+ prorettype => 'text', proargtypes => 'oid',
+ prosrc => 'pg_get_function_sqlbody' },
{ oid => '1686', descr => 'list of SQL keywords',
proname => 'pg_get_keywords', procost => '10', prorows => '500',
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 78f230894b..8d58067d03 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -112,11 +112,14 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
Oid protrftypes[1] BKI_DEFAULT(_null_) BKI_LOOKUP(pg_type);
/* procedure source text */
- text prosrc BKI_FORCE_NOT_NULL;
+ text prosrc;
/* secondary procedure info (can be NULL) */
text probin BKI_DEFAULT(_null_);
+ /* pre-parsed SQL function body */
+ pg_node_tree prosqlbody BKI_DEFAULT(_null_);
+
/* procedure-local GUC settings */
text proconfig[1] BKI_DEFAULT(_null_);
@@ -194,6 +197,7 @@ extern ObjectAddress ProcedureCreate(const char *procedureName,
Oid languageValidator,
const char *prosrc,
const char *probin,
+ Node *prosqlbody,
char prokind,
bool security_definer,
bool isLeakProof,
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 1a79540c94..2bb0fb10de 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -65,9 +65,11 @@ extern void interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType);
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index c975a93661..dcb8e18437 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -20,6 +20,21 @@
/* This struct is known only within executor/functions.c */
typedef struct SQLFunctionParseInfo *SQLFunctionParseInfoPtr;
+/*
+ * Data structure needed by the parser callback hooks to resolve parameter
+ * references during parsing of a SQL function's body. This is separate from
+ * SQLFunctionCache since we sometimes do parsing separately from execution.
+ */
+typedef struct SQLFunctionParseInfo
+{
+ char *fname; /* function's name */
+ int nargs; /* number of input arguments */
+ Oid *argtypes; /* resolved types of input arguments */
+ char **argnames; /* names of input arguments; NULL if none */
+ /* Note that argnames[i] can be NULL, if some args are unnamed */
+ Oid collation; /* function's input collation, if known */
+} SQLFunctionParseInfo;
+
extern Datum fmgr_sql(PG_FUNCTION_ARGS);
extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTuple,
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 2bc3cc4ae7..91d7d4d5c6 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -114,6 +114,8 @@ typedef struct PsqlScanStateData
int paren_depth; /* depth of nesting in parentheses */
int xcdepth; /* depth of nesting in slash-star comments */
char *dolqstart; /* current $foo$ quote start string */
+ int identifier_count; /* identifiers since start of statement */
+ int begin_depth; /* depth of begin/end routine body blocks */
/*
* Callback functions provided by the program making use of the lexer,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 40ae489c23..a34bf25cef 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -314,6 +314,7 @@ typedef enum NodeTag
T_DeleteStmt,
T_UpdateStmt,
T_SelectStmt,
+ T_ReturnStmt,
T_PLAssignStmt,
T_AlterTableStmt,
T_AlterTableCmd,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 236832a2ca..97908b597e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -132,6 +132,8 @@ typedef struct Query
bool hasForUpdate; /* FOR [KEY] UPDATE/SHARE was specified */
bool hasRowSecurity; /* rewriter has applied some RLS policy */
+ bool isReturn; /* is a RETURN statement */
+
List *cteList; /* WITH list (of CommonTableExpr's) */
List *rtable; /* list of range table entries */
@@ -1701,6 +1703,16 @@ typedef struct SetOperationStmt
} SetOperationStmt;
+/*
+ * RETURN statement (inside SQL function body)
+ */
+typedef struct ReturnStmt
+{
+ NodeTag type;
+ Node *returnval;
+} ReturnStmt;
+
+
/* ----------------------
* PL/pgSQL Assignment Statement
*
@@ -2884,6 +2896,7 @@ typedef struct CreateFunctionStmt
List *parameters; /* a list of FunctionParameter */
TypeName *returnType; /* the return type */
List *options; /* a list of DefElem */
+ Node *sql_body;
} CreateFunctionStmt;
typedef enum FunctionParameterMode
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 28083aaac9..ec467b5c25 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -48,6 +48,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("atomic", ATOMIC, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -346,6 +347,7 @@ PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("return", RETURN, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD, AS_LABEL)
PG_KEYWORD("returns", RETURNS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index e5472100a4..49700d151a 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -43,6 +43,7 @@ typedef enum
extern PGDLLIMPORT int log_statement;
extern List *pg_parse_query(const char *query_string);
+extern List *pg_rewrite_query(Query *query);
extern List *pg_analyze_and_rewrite(RawStmt *parsetree,
const char *query_string,
Oid *paramTypes, int numParams,
diff --git a/src/interfaces/ecpg/preproc/ecpg.addons b/src/interfaces/ecpg/preproc/ecpg.addons
index 300381eaad..0441561c52 100644
--- a/src/interfaces/ecpg/preproc/ecpg.addons
+++ b/src/interfaces/ecpg/preproc/ecpg.addons
@@ -87,6 +87,12 @@ ECPG: stmtTransactionStmt block
whenever_action(2);
free($1);
}
+ECPG: toplevel_stmtTransactionStmtLegacy block
+ {
+ fprintf(base_yyout, "{ ECPGtrans(__LINE__, %s, \"%s\");", connection ? connection : "NULL", $1);
+ whenever_action(2);
+ free($1);
+ }
ECPG: stmtViewStmt rule
| ECPGAllocateDescr
{
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer
index 0e4a041393..bcabf88341 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -4,8 +4,8 @@ statements: /*EMPTY*/
| statements statement
;
-statement: ecpgstart at stmt ';' { connection = NULL; }
- | ecpgstart stmt ';'
+statement: ecpgstart at toplevel_stmt ';' { connection = NULL; }
+ | ecpgstart toplevel_stmt ';'
| ecpgstart ECPGVarDeclaration
{
fprintf(base_yyout, "%s", $2);
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index ce508ae1dc..dbd6358ffd 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -255,6 +255,151 @@ SELECT pg_get_functiondef('functest_F_2'::regproc);
(1 row)
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_3a() RETURNS boolean
+ BEGIN ATOMIC
+ RETURN false;
+ END;
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+ERROR: SQL function with unquoted function body cannot have polymorphic arguments
+SELECT functest_S_1('abcd', '2020-01-01');
+ functest_s_1
+--------------
+ t
+(1 row)
+
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+ functest_s_2
+--------------
+ 1
+(1 row)
+
+SELECT functest_S_3();
+ functest_s_3
+--------------
+ f
+(1 row)
+
+SELECT functest_S_10('abcd', '2020-01-01');
+ functest_s_10
+---------------
+ t
+(1 row)
+
+SELECT functest_S_13();
+ functest_s_13
+---------------
+ f
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_1(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN ((a = 'abcd'::text) AND (b > '01-01-2001'::date)) +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_2(a text[])+
+ RETURNS integer +
+ LANGUAGE sql +
+ RETURN ((a)[1])::integer +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+ pg_get_functiondef
+----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN false +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3a'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3a()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ RETURN false; +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+ pg_get_functiondef
+-------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_10(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT ((a = 'abcd'::text) AND (b > '01-01-2001'::date)); +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_13()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT 1; +
+ SELECT false AS bool; +
+ END +
+
+(1 row)
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+SELECT functest_S_14();
+ functest_s_14
+---------------
+ 2
+(1 row)
+
+DROP TABLE functest3 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view functestv3
+drop cascades to function functest_s_14()
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
RETURNS int
@@ -284,6 +429,20 @@ SELECT routine_name, ordinal_position, parameter_name, parameter_default
(7 rows)
DROP FUNCTION functest_IS_1(int, int, text), functest_IS_2(int), functest_IS_3(int);
+CREATE TABLE functest1 (a int, b int);
+CREATE SEQUENCE functest2;
+CREATE FUNCTION functest_IS_4()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest1);
+CREATE FUNCTION functest_IS_5()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest2');
+DROP TABLE functest1 CASCADE;
+NOTICE: drop cascades to function functest_is_4()
+DROP SEQUENCE functest2 CASCADE;
+NOTICE: drop cascades to function functest_is_5()
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
@@ -302,6 +461,49 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
ERROR: cannot change routine kind
DETAIL: "functest1" is a function.
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+SELECT * FROM functest_sri1();
+ functest_sri1
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+SELECT * FROM functest_sri2();
+ functest_sri2
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
$$ SELECT a + 1 $$;
@@ -360,7 +562,7 @@ SELECT * FROM voidtest5(3);
-- Cleanup
DROP SCHEMA temp_func_test CASCADE;
-NOTICE: drop cascades to 21 other objects
+NOTICE: drop cascades to 29 other objects
DETAIL: drop cascades to function functest_a_1(text,date)
drop cascades to function functest_a_2(text[])
drop cascades to function functest_a_3()
@@ -376,7 +578,15 @@ drop cascades to function functest_f_1(integer)
drop cascades to function functest_f_2(integer)
drop cascades to function functest_f_3(integer)
drop cascades to function functest_f_4(integer)
+drop cascades to function functest_s_1(text,date)
+drop cascades to function functest_s_2(text[])
+drop cascades to function functest_s_3()
+drop cascades to function functest_s_3a()
+drop cascades to function functest_s_10(text,date)
+drop cascades to function functest_s_13()
drop cascades to function functest_b_2(bigint)
+drop cascades to function functest_sri1()
+drop cascades to function functest_sri2()
drop cascades to function voidtest1(integer)
drop cascades to function voidtest2(integer,integer)
drop cascades to function voidtest3(integer)
diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out
index 3838fa2324..b5f2d4e38c 100644
--- a/src/test/regress/expected/create_procedure.out
+++ b/src/test/regress/expected/create_procedure.out
@@ -65,6 +65,41 @@ SELECT * FROM cp_test ORDER BY b COLLATE "C";
1 | xyzzy
(3 rows)
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+\df ptest1s
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+---------+------------------+---------------------+------
+ public | ptest1s | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest1s'::regproc);
+ pg_get_functiondef
+----------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest1s(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ INSERT INTO cp_test (a, b) +
+ VALUES (1, ptest1s.x); +
+ END +
+
+(1 row)
+
+CALL ptest1s('b');
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+ a | b
+---+-------
+ 1 | 0
+ 1 | a
+ 1 | b
+ 1 | xyzzy
+(4 rows)
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -146,6 +181,28 @@ AS $$
SELECT a = b;
$$;
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+\df ptest8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+--------+------------------+---------------------+------
+ public | ptest8 | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest8'::regproc);
+ pg_get_functiondef
+---------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest8(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ END +
+
+(1 row)
+
+CALL ptest8('');
-- OUT parameters
CREATE PROCEDURE ptest9(OUT a int)
LANGUAGE SQL
@@ -214,6 +271,7 @@ ALTER ROUTINE ptest1a RENAME TO ptest1;
DROP ROUTINE cp_testfunc1(int);
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
DROP USER regress_cp_user1;
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
index bd108a918f..092e15b591 100644
--- a/src/test/regress/sql/create_function_3.sql
+++ b/src/test/regress/sql/create_function_3.sql
@@ -153,6 +153,65 @@ CREATE FUNCTION functest_F_4(int) RETURNS bool LANGUAGE 'sql'
SELECT pg_get_functiondef('functest_F_2'::regproc);
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_3a() RETURNS boolean
+ BEGIN ATOMIC
+ RETURN false;
+ END;
+
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+
+SELECT functest_S_1('abcd', '2020-01-01');
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+SELECT functest_S_3();
+
+SELECT functest_S_10('abcd', '2020-01-01');
+SELECT functest_S_13();
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+SELECT pg_get_functiondef('functest_S_3a'::regproc);
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+
+SELECT functest_S_14();
+
+DROP TABLE functest3 CASCADE;
+
+
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
@@ -177,6 +236,23 @@ CREATE FUNCTION functest_IS_3(a int default 1, out b int)
DROP FUNCTION functest_IS_1(int, int, text), functest_IS_2(int), functest_IS_3(int);
+CREATE TABLE functest1 (a int, b int);
+CREATE SEQUENCE functest2;
+
+CREATE FUNCTION functest_IS_4()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest1);
+
+CREATE FUNCTION functest_IS_5()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest2');
+
+DROP TABLE functest1 CASCADE;
+DROP SEQUENCE functest2 CASCADE;
+
+
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
@@ -194,6 +270,29 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+
+SELECT * FROM functest_sri1();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+
+SELECT * FROM functest_sri2();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
diff --git a/src/test/regress/sql/create_procedure.sql b/src/test/regress/sql/create_procedure.sql
index 2ef1c82cea..8c0d70cb16 100644
--- a/src/test/regress/sql/create_procedure.sql
+++ b/src/test/regress/sql/create_procedure.sql
@@ -28,6 +28,21 @@ CREATE PROCEDURE ptest1(x text)
SELECT * FROM cp_test ORDER BY b COLLATE "C";
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+
+\df ptest1s
+SELECT pg_get_functiondef('ptest1s'::regproc);
+
+CALL ptest1s('b');
+
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -112,6 +127,16 @@ CREATE PROCEDURE ptest7(a text, b text)
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+
+\df ptest8
+SELECT pg_get_functiondef('ptest8'::regproc);
+CALL ptest8('');
+
+
-- OUT parameters
CREATE PROCEDURE ptest9(OUT a int)
@@ -170,6 +195,7 @@ CREATE USER regress_cp_user1;
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
base-commit: 64990081504661ff5c04dbf20cc4252be66ab149
--
2.30.0
On 11.02.21 09:02, Peter Eisentraut wrote:
Here is an updated patch to get it building again.
Another updated patch to get things building again. I've also fixed the
last TODO I had in there in qualifying function arguments as necessary
in ruleutils.c.
Updated patch to resolve merge conflict. No changes in functionality.
Attachments:
v8-0001-SQL-standard-function-body.patchtext/plain; charset=UTF-8; name=v8-0001-SQL-standard-function-body.patch; x-mac-creator=0; x-mac-type=0Download
From 8e61da555d6083f9ec0f90791b02082376fe010b Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Tue, 2 Mar 2021 18:08:15 +0100
Subject: [PATCH v8] SQL-standard function body
This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.
Instead of the PostgreSQL-specific AS $$ string literal $$ syntax,
this allows writing out the SQL statements making up the body
unquoted, either as a single statement:
CREATE FUNCTION add(a integer, b integer) RETURNS integer
LANGUAGE SQL
RETURN a + b;
or as a block
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
BEGIN ATOMIC
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
END;
The function body is parsed at function definition time and stored as
expression nodes in a new pg_proc column prosqlbody. So at run time,
no further parsing is required.
However, this form does not support polymorphic arguments, because
there is no more parse analysis done at call time.
Dependencies between the function and the objects it uses are fully
tracked.
A new RETURN statement is introduced. This can only be used inside
function bodies. Internally, it is treated much like a SELECT
statement.
psql needs some new intelligence to keep track of function body
boundaries so that it doesn't send off statements when it sees
semicolons that are inside a function body.
Also, per SQL standard, LANGUAGE SQL is the default, so it does not
need to be specified anymore.
Discussion: https://www.postgresql.org/message-id/flat/1c11f1eb-f00c-43b7-799d-2d44132c02d7@2ndquadrant.com
---
doc/src/sgml/catalogs.sgml | 10 +
doc/src/sgml/ref/create_function.sgml | 126 +++++++++-
doc/src/sgml/ref/create_procedure.sgml | 62 ++++-
src/backend/catalog/pg_aggregate.c | 1 +
src/backend/catalog/pg_proc.c | 116 +++++----
src/backend/commands/aggregatecmds.c | 2 +
src/backend/commands/functioncmds.c | 119 +++++++--
src/backend/commands/typecmds.c | 4 +
src/backend/executor/functions.c | 79 +++---
src/backend/nodes/copyfuncs.c | 15 ++
src/backend/nodes/equalfuncs.c | 13 +
src/backend/nodes/outfuncs.c | 12 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/util/clauses.c | 126 +++++++---
src/backend/parser/analyze.c | 35 +++
src/backend/parser/gram.y | 129 +++++++---
src/backend/tcop/postgres.c | 3 +-
src/backend/utils/adt/ruleutils.c | 132 +++++++++-
src/bin/pg_dump/pg_dump.c | 45 +++-
src/bin/psql/describe.c | 15 +-
src/fe_utils/psqlscan.l | 23 +-
src/include/catalog/pg_proc.dat | 4 +
src/include/catalog/pg_proc.h | 6 +-
src/include/commands/defrem.h | 2 +
src/include/executor/functions.h | 15 ++
src/include/fe_utils/psqlscan_int.h | 2 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 13 +
src/include/parser/kwlist.h | 2 +
src/include/tcop/tcopprot.h | 1 +
src/interfaces/ecpg/preproc/ecpg.addons | 6 +
src/interfaces/ecpg/preproc/ecpg.trailer | 4 +-
.../regress/expected/create_function_3.out | 231 +++++++++++++++++-
.../regress/expected/create_procedure.out | 58 +++++
src/test/regress/sql/create_function_3.sql | 96 +++++++-
src/test/regress/sql/create_procedure.sql | 26 ++
36 files changed, 1321 insertions(+), 214 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index db29905e91..7b8e2e7227 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -5980,6 +5980,16 @@ <title><structname>pg_proc</structname> Columns</title>
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>prosqlbody</structfield> <type>pg_node_tree</type>
+ </para>
+ <para>
+ Pre-parsed SQL function body. This will be used for language SQL
+ functions if the body is not specified as a string constant.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>proconfig</structfield> <type>text[]</type>
diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index 3c1eaea651..1b5b9420db 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -38,6 +38,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -257,8 +258,9 @@ <title>Parameters</title>
The name of the language that the function is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -577,6 +579,44 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> function. This can
+ either be a single statement
+<programlisting>
+RETURN <replaceable>expression</replaceable>
+</programlisting>
+ or a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the function body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at function definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at function definition time. This form tracks dependencies between the
+ function and objects used in the function body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling functions. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
<para>
@@ -669,6 +709,15 @@ <title>Examples</title>
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
+</programlisting>
+ The same function written in a more SQL-conforming style, using argument
+ names and an unquoted body:
+<programlisting>
+CREATE FUNCTION add(a integer, b integer) RETURNS integer
+ LANGUAGE SQL
+ IMMUTABLE
+ RETURNS NULL ON NULL INPUT
+ RETURN a + b;
</programlisting>
</para>
@@ -799,23 +848,74 @@ <title>Writing <literal>SECURITY DEFINER</literal> Functions Safely</title>
<title>Compatibility</title>
<para>
- A <command>CREATE FUNCTION</command> command is defined in the SQL standard.
- The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. The attributes are not portable, neither are the
- different available languages.
+ A <command>CREATE FUNCTION</command> command is defined in the SQL
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. Conversely, the SQL
+ standard specifies a number of optional features that are not implemented
+ in <productname>PostgreSQL</productname>.
</para>
<para>
- For compatibility with some other database systems,
- <replaceable class="parameter">argmode</replaceable> can be written
- either before or after <replaceable class="parameter">argname</replaceable>.
- But only the first way is standard-compliant.
+ The following are important compatibility issues:
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>OR REPLACE</literal> is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For compatibility with some other database systems, <replaceable
+ class="parameter">argmode</replaceable> can be written either before or
+ after <replaceable class="parameter">argname</replaceable>. But only
+ the first way is standard-compliant.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For parameter defaults, the SQL standard specifies only the syntax with
+ the <literal>DEFAULT</literal> key word. The syntax with
+ <literal>=</literal> is used in T-SQL and Firebird.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The <literal>SETOF</literal> modifier is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Only <literal>SQL</literal> is standardized as a language.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ All other attributes except <literal>CALLED ON NULL INPUT</literal> and
+ <literal>RETURNS NULL ON NULL INPUT</literal> are not standardized.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For the body of <literal>LANGUAGE SQL</literal> functions, the SQL
+ standard only specifies the <replaceable>sql_body</replaceable> form.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
<para>
- For parameter defaults, the SQL standard specifies only the syntax with
- the <literal>DEFAULT</literal> key word. The syntax
- with <literal>=</literal> is used in T-SQL and Firebird.
+ Simple <literal>LANGUAGE SQL</literal> functions can be written in a way
+ that is both standard-conforming and portable to other implementations.
+ More complex functions using advanced features, optimization attributes, or
+ other languages will necessarily be specific to PostgreSQL in a significant
+ way.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_procedure.sgml b/doc/src/sgml/ref/create_procedure.sgml
index e258eca5ce..ecdeac1629 100644
--- a/doc/src/sgml/ref/create_procedure.sgml
+++ b/doc/src/sgml/ref/create_procedure.sgml
@@ -29,6 +29,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -162,8 +163,9 @@ <title>Parameters</title>
The name of the language that the procedure is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -299,6 +301,41 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> procedure. This should
+ be a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the procedure body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at procedure definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at procedure definition time. This form tracks dependencies between the
+ procedure and objects used in the procedure body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling procedures. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
@@ -318,6 +355,7 @@ <title>Notes</title>
<refsect1 id="sql-createprocedure-examples">
<title>Examples</title>
+ <para>
<programlisting>
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
@@ -325,9 +363,21 @@ <title>Examples</title>
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
$$;
-
+</programlisting>
+ or
+<programlisting>
+CREATE PROCEDURE insert_data(a integer, b integer)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO tbl VALUES (a);
+ INSERT INTO tbl VALUES (b);
+END;
+</programlisting>
+ and call like this:
+<programlisting>
CALL insert_data(1, 2);
</programlisting>
+ </para>
</refsect1>
<refsect1 id="sql-createprocedure-compat">
@@ -335,9 +385,9 @@ <title>Compatibility</title>
<para>
A <command>CREATE PROCEDURE</command> command is defined in the SQL
- standard. The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. For details see
- also <xref linkend="sql-createfunction"/>.
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. For details see also
+ <xref linkend="sql-createfunction"/>.
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 89f23d0add..5197076c76 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -622,6 +622,7 @@ AggregateCreate(const char *aggName,
InvalidOid, /* no validator */
"aggregate_dummy", /* placeholder (no such proc) */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_AGGREGATE,
false, /* security invoker (currently not
* definable for agg) */
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index e14eee5a19..a53b5f408e 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -32,6 +32,7 @@
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_type.h"
#include "tcop/pquery.h"
@@ -76,6 +77,7 @@ ProcedureCreate(const char *procedureName,
Oid languageValidator,
const char *prosrc,
const char *probin,
+ Node *prosqlbody,
char prokind,
bool security_definer,
bool isLeakProof,
@@ -119,8 +121,6 @@ ProcedureCreate(const char *procedureName,
/*
* sanity checks
*/
- Assert(PointerIsValid(prosrc));
-
parameterCount = parameterTypes->dim1;
if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
ereport(ERROR,
@@ -334,11 +334,18 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_protrftypes - 1] = trftypes;
else
nulls[Anum_pg_proc_protrftypes - 1] = true;
- values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ if (prosrc)
+ values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ else
+ nulls[Anum_pg_proc_prosrc - 1] = true;
if (probin)
values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin);
else
nulls[Anum_pg_proc_probin - 1] = true;
+ if (prosqlbody)
+ values[Anum_pg_proc_prosqlbody - 1] = CStringGetTextDatum(nodeToString(prosqlbody));
+ else
+ nulls[Anum_pg_proc_prosqlbody - 1] = true;
if (proconfig != PointerGetDatum(NULL))
values[Anum_pg_proc_proconfig - 1] = proconfig;
else
@@ -638,6 +645,10 @@ ProcedureCreate(const char *procedureName,
record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
free_object_addresses(addrs);
+ /* dependency on SQL routine body */
+ if (languageObjectId == SQLlanguageId && prosqlbody)
+ recordDependencyOnExpr(&myself, prosqlbody, NIL, DEPENDENCY_NORMAL);
+
/* dependency on parameter default expressions */
if (parameterDefaults)
recordDependencyOnExpr(&myself, (Node *) parameterDefaults,
@@ -861,61 +872,81 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
/* Postpone body checks if !check_function_bodies */
if (check_function_bodies)
{
- tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
- if (isnull)
- elog(ERROR, "null prosrc");
-
- prosrc = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport().
*/
callback_arg.proname = NameStr(proc->proname);
- callback_arg.prosrc = prosrc;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_function_parse_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
- /*
- * We can't do full prechecking of the function definition if there
- * are any polymorphic input types, because actual datatypes of
- * expression results will be unresolvable. The check will be done at
- * runtime instead.
- *
- * We can run the text through the raw parser though; this will at
- * least catch silly syntactic errors.
- */
- raw_parsetree_list = pg_parse_query(prosrc);
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ {
+ Node *n;
- if (!haspolyarg)
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosqlbody, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc and prosqlbody");
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = castNode(List, n);
+ else
+ querytree_list = list_make1(list_make1(n));
+ }
+ else
{
+ prosrc = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = prosrc;
+
/*
- * OK to do full precheck: analyze and rewrite the queries, then
- * verify the result type.
+ * We can't do full prechecking of the function definition if there
+ * are any polymorphic input types, because actual datatypes of
+ * expression results will be unresolvable. The check will be done at
+ * runtime instead.
+ *
+ * We can run the text through the raw parser though; this will at
+ * least catch silly syntactic errors.
*/
- SQLFunctionParseInfoPtr pinfo;
- Oid rettype;
- TupleDesc rettupdesc;
+ raw_parsetree_list = pg_parse_query(prosrc);
- /* But first, set up parameter information */
- pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
-
- querytree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (!haspolyarg)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *querytree_sublist;
-
- querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
- prosrc,
- (ParserSetupHook) sql_fn_parser_setup,
- pinfo,
- NULL);
- querytree_list = lappend(querytree_list,
- querytree_sublist);
+ /*
+ * OK to do full precheck: analyze and rewrite the queries, then
+ * verify the result type.
+ */
+ SQLFunctionParseInfoPtr pinfo;
+
+ /* But first, set up parameter information */
+ pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
+
+ querytree_list = NIL;
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *querytree_sublist;
+
+ querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ prosrc,
+ (ParserSetupHook) sql_fn_parser_setup,
+ pinfo,
+ NULL);
+ querytree_list = lappend(querytree_list,
+ querytree_sublist);
+ }
}
+ }
+
+ if (!haspolyarg)
+ {
+ Oid rettype;
+ TupleDesc rettupdesc;
check_sql_fn_statements(querytree_list);
@@ -968,6 +999,9 @@ function_parse_error_transpose(const char *prosrc)
int newerrposition;
const char *queryText;
+ if (!prosrc)
+ return false;
+
/*
* Nothing to do unless we are dealing with a syntax error that has a
* cursor position.
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 69c50ac087..046cf2df08 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -312,9 +312,11 @@ DefineAggregate(ParseState *pstate,
InvalidOid,
OBJECT_AGGREGATE,
¶meterTypes,
+ NULL,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ NULL,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 7a4e104623..8a9faccb03 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -53,9 +53,11 @@
#include "commands/proclang.h"
#include "executor/execdesc.h"
#include "executor/executor.h"
+#include "executor/functions.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
@@ -186,9 +188,11 @@ interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType)
@@ -283,7 +287,11 @@ interpret_function_parameter_list(ParseState *pstate,
/* handle input parameters */
if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
+ {
isinput = true;
+ if (parameterTypes_list)
+ *parameterTypes_list = lappend_oid(*parameterTypes_list, toid);
+ }
/* handle signature parameters */
if (fp->mode == FUNC_PARAM_IN || fp->mode == FUNC_PARAM_INOUT ||
@@ -372,6 +380,9 @@ interpret_function_parameter_list(ParseState *pstate,
have_names = true;
}
+ if (inParameterNames_list)
+ *inParameterNames_list = lappend(*inParameterNames_list, makeString(fp->name ? fp->name : pstrdup("")));
+
if (fp->defexpr)
{
Node *def;
@@ -786,28 +797,10 @@ compute_function_attributes(ParseState *pstate,
defel->defname);
}
- /* process required items */
if (as_item)
*as = (List *) as_item->arg;
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no function body specified")));
- *as = NIL; /* keep compiler quiet */
- }
-
if (language_item)
*language = strVal(language_item->arg);
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no language specified")));
- *language = NULL; /* keep compiler quiet */
- }
-
- /* process optional items */
if (transform_item)
*transform = transform_item->arg;
if (windowfunc_item)
@@ -856,10 +849,21 @@ compute_function_attributes(ParseState *pstate,
*/
static void
interpret_AS_clause(Oid languageOid, const char *languageName,
- char *funcname, List *as,
- char **prosrc_str_p, char **probin_str_p)
+ char *funcname, List *as, Node *sql_body_in,
+ List *parameterTypes, List *inParameterNames,
+ char **prosrc_str_p, char **probin_str_p, Node **sql_body_out)
{
- Assert(as != NIL);
+ if (sql_body_in && as)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("duplicate function body specified")));
+
+ if (sql_body_in && languageOid != SQLlanguageId)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("inline SQL function body only valid for language SQL")));
+
+ *sql_body_out = NULL;
if (languageOid == ClanguageId)
{
@@ -881,6 +885,66 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
*prosrc_str_p = funcname;
}
}
+ else if (sql_body_in)
+ {
+ SQLFunctionParseInfoPtr pinfo;
+
+ pinfo = (SQLFunctionParseInfoPtr) palloc0(sizeof(SQLFunctionParseInfo));
+
+ pinfo->fname = funcname;
+ pinfo->nargs = list_length(parameterTypes);
+ pinfo->argtypes = (Oid *) palloc(pinfo->nargs * sizeof(Oid));
+ pinfo->argnames = (char **) palloc(pinfo->nargs * sizeof(char *));
+ for (int i = 0; i < list_length(parameterTypes); i++)
+ {
+ char *s = strVal(list_nth(inParameterNames, i));
+
+ pinfo->argtypes[i] = list_nth_oid(parameterTypes, i);
+ if (IsPolymorphicType(pinfo->argtypes[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("SQL function with unquoted function body cannot have polymorphic arguments")));
+
+ if (s[0] != '\0')
+ pinfo->argnames[i] = s;
+ else
+ pinfo->argnames[i] = NULL;
+ }
+
+ if (IsA(sql_body_in, List))
+ {
+ List *stmts = linitial_node(List, castNode(List, sql_body_in));
+ ListCell *lc;
+ List *transformed_stmts = NIL;
+
+ foreach(lc, stmts)
+ {
+ Node *stmt = lfirst(lc);
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, stmt);
+ transformed_stmts = lappend(transformed_stmts, q);
+ free_parsestate(pstate);
+ }
+
+ *sql_body_out = (Node *) list_make1(transformed_stmts);
+ }
+ else
+ {
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, sql_body_in);
+
+ *sql_body_out = (Node *) q;
+ }
+
+ *probin_str_p = NULL;
+ *prosrc_str_p = NULL;
+ }
else
{
/* Everything else wants the given string in prosrc. */
@@ -919,6 +983,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
{
char *probin_str;
char *prosrc_str;
+ Node *prosqlbody;
Oid prorettype;
bool returnsSet;
char *language;
@@ -929,9 +994,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
Oid namespaceId;
AclResult aclresult;
oidvector *parameterTypes;
+ List *parameterTypes_list = NIL;
ArrayType *allParameterTypes;
ArrayType *parameterModes;
ArrayType *parameterNames;
+ List *inParameterNames_list = NIL;
List *parameterDefaults;
Oid variadicArgType;
List *trftypes_list = NIL;
@@ -962,6 +1029,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
get_namespace_name(namespaceId));
/* Set default attributes */
+ as_clause = NULL;
+ language = "sql";
isWindowFunc = false;
isStrict = false;
security = false;
@@ -1053,9 +1122,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageOid,
stmt->is_procedure ? OBJECT_PROCEDURE : OBJECT_FUNCTION,
¶meterTypes,
+ ¶meterTypes_list,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ &inParameterNames_list,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
@@ -1112,8 +1183,9 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
trftypes = NULL;
}
- interpret_AS_clause(languageOid, language, funcname, as_clause,
- &prosrc_str, &probin_str);
+ interpret_AS_clause(languageOid, language, funcname, as_clause, stmt->sql_body,
+ parameterTypes_list, inParameterNames_list,
+ &prosrc_str, &probin_str, &prosqlbody);
/*
* Set default values for COST and ROWS depending on other parameters;
@@ -1155,6 +1227,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageValidator,
prosrc_str, /* converted to text later */
probin_str, /* converted to text later */
+ prosqlbody,
stmt->is_procedure ? PROKIND_PROCEDURE : (isWindowFunc ? PROKIND_WINDOW : PROKIND_FUNCTION),
security,
isLeakProof,
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 76218fb47e..e975508ffa 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1775,6 +1775,7 @@ makeRangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR, /* language validator */
prosrc[i], /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
@@ -1839,6 +1840,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR,
"multirange_constructor0", /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
@@ -1882,6 +1884,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR,
"multirange_constructor1", /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
@@ -1922,6 +1925,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR,
"multirange_constructor2", /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 7bb752ace3..642683843e 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -26,6 +26,7 @@
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_func.h"
+#include "rewrite/rewriteHandler.h"
#include "storage/proc.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
@@ -128,21 +129,6 @@ typedef struct
typedef SQLFunctionCache *SQLFunctionCachePtr;
-/*
- * Data structure needed by the parser callback hooks to resolve parameter
- * references during parsing of a SQL function's body. This is separate from
- * SQLFunctionCache since we sometimes do parsing separately from execution.
- */
-typedef struct SQLFunctionParseInfo
-{
- char *fname; /* function's name */
- int nargs; /* number of input arguments */
- Oid *argtypes; /* resolved types of input arguments */
- char **argnames; /* names of input arguments; NULL if none */
- /* Note that argnames[i] can be NULL, if some args are unnamed */
- Oid collation; /* function's input collation, if known */
-} SQLFunctionParseInfo;
-
/* non-export function prototypes */
static Node *sql_fn_param_ref(ParseState *pstate, ParamRef *pref);
@@ -607,7 +593,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
HeapTuple procedureTuple;
Form_pg_proc procedureStruct;
SQLFunctionCachePtr fcache;
- List *raw_parsetree_list;
List *queryTree_list;
List *resulttlist;
ListCell *lc;
@@ -682,9 +667,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
procedureTuple,
Anum_pg_proc_prosrc,
&isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", foid);
- fcache->src = TextDatumGetCString(tmp);
/*
* Parse and rewrite the queries in the function text. Use sublists to
@@ -695,20 +677,55 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
* but we'll not worry about it until the module is rewritten to use
* plancache.c.
*/
- raw_parsetree_list = pg_parse_query(fcache->src);
-
queryTree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (isNull)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *queryTree_sublist;
-
- queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
- fcache->src,
- (ParserSetupHook) sql_fn_parser_setup,
- fcache->pinfo,
- NULL);
- queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ Node *n;
+ List *stored_query_list;
+
+ tmp = SysCacheGetAttr(PROCOID,
+ procedureTuple,
+ Anum_pg_proc_prosqlbody,
+ &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", foid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ stored_query_list = linitial_node(List, castNode(List, n));
+ else
+ stored_query_list = list_make1(n);
+
+ foreach(lc, stored_query_list)
+ {
+ Query *parsetree = lfirst_node(Query, lc);
+ List *queryTree_sublist;
+
+ AcquireRewriteLocks(parsetree, true, false);
+ queryTree_sublist = pg_rewrite_query(parsetree);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ }
+ }
+ else
+ {
+ List *raw_parsetree_list;
+
+ fcache->src = TextDatumGetCString(tmp);
+
+ raw_parsetree_list = pg_parse_query(fcache->src);
+
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *queryTree_sublist;
+
+ queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ fcache->src,
+ (ParserSetupHook) sql_fn_parser_setup,
+ fcache->pinfo,
+ NULL);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ }
}
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index aaba1ec2c4..8d51b39f32 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3126,6 +3126,7 @@ _copyQuery(const Query *from)
COPY_SCALAR_FIELD(hasModifyingCTE);
COPY_SCALAR_FIELD(hasForUpdate);
COPY_SCALAR_FIELD(hasRowSecurity);
+ COPY_SCALAR_FIELD(isReturn);
COPY_NODE_FIELD(cteList);
COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(jointree);
@@ -3254,6 +3255,16 @@ _copySetOperationStmt(const SetOperationStmt *from)
return newnode;
}
+static ReturnStmt *
+_copyReturnStmt(const ReturnStmt *from)
+{
+ ReturnStmt *newnode = makeNode(ReturnStmt);
+
+ COPY_NODE_FIELD(returnval);
+
+ return newnode;
+}
+
static PLAssignStmt *
_copyPLAssignStmt(const PLAssignStmt *from)
{
@@ -3637,6 +3648,7 @@ _copyCreateFunctionStmt(const CreateFunctionStmt *from)
COPY_NODE_FIELD(parameters);
COPY_NODE_FIELD(returnType);
COPY_NODE_FIELD(options);
+ COPY_NODE_FIELD(sql_body);
return newnode;
}
@@ -5293,6 +5305,9 @@ copyObjectImpl(const void *from)
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
+ case T_ReturnStmt:
+ retval = _copyReturnStmt(from);
+ break;
case T_PLAssignStmt:
retval = _copyPLAssignStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c2d73626fc..4642fa24fe 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -969,6 +969,7 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_SCALAR_FIELD(hasModifyingCTE);
COMPARE_SCALAR_FIELD(hasForUpdate);
COMPARE_SCALAR_FIELD(hasRowSecurity);
+ COMPARE_SCALAR_FIELD(isReturn);
COMPARE_NODE_FIELD(cteList);
COMPARE_NODE_FIELD(rtable);
COMPARE_NODE_FIELD(jointree);
@@ -1085,6 +1086,14 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
return true;
}
+static bool
+_equalReturnStmt(const ReturnStmt *a, const ReturnStmt *b)
+{
+ COMPARE_NODE_FIELD(returnval);
+
+ return true;
+}
+
static bool
_equalPLAssignStmt(const PLAssignStmt *a, const PLAssignStmt *b)
{
@@ -1403,6 +1412,7 @@ _equalCreateFunctionStmt(const CreateFunctionStmt *a, const CreateFunctionStmt *
COMPARE_NODE_FIELD(parameters);
COMPARE_NODE_FIELD(returnType);
COMPARE_NODE_FIELD(options);
+ COMPARE_NODE_FIELD(sql_body);
return true;
}
@@ -3318,6 +3328,9 @@ equal(const void *a, const void *b)
case T_SetOperationStmt:
retval = _equalSetOperationStmt(a, b);
break;
+ case T_ReturnStmt:
+ retval = _equalReturnStmt(a, b);
+ break;
case T_PLAssignStmt:
retval = _equalPLAssignStmt(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8fc432bfe1..878185047f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2784,6 +2784,14 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
WRITE_NODE_FIELD(rarg);
}
+static void
+_outReturnStmt(StringInfo str, const ReturnStmt *node)
+{
+ WRITE_NODE_TYPE("RETURN");
+
+ WRITE_NODE_FIELD(returnval);
+}
+
static void
_outPLAssignStmt(StringInfo str, const PLAssignStmt *node)
{
@@ -2986,6 +2994,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_BOOL_FIELD(hasModifyingCTE);
WRITE_BOOL_FIELD(hasForUpdate);
WRITE_BOOL_FIELD(hasRowSecurity);
+ WRITE_BOOL_FIELD(isReturn);
WRITE_NODE_FIELD(cteList);
WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(jointree);
@@ -4265,6 +4274,9 @@ outNode(StringInfo str, const void *obj)
case T_SelectStmt:
_outSelectStmt(str, obj);
break;
+ case T_ReturnStmt:
+ _outReturnStmt(str, obj);
+ break;
case T_PLAssignStmt:
_outPLAssignStmt(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 718fb58e86..78173f09cc 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -263,6 +263,7 @@ _readQuery(void)
READ_BOOL_FIELD(hasModifyingCTE);
READ_BOOL_FIELD(hasForUpdate);
READ_BOOL_FIELD(hasRowSecurity);
+ READ_BOOL_FIELD(isReturn);
READ_NODE_FIELD(cteList);
READ_NODE_FIELD(rtable);
READ_NODE_FIELD(jointree);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index f3786dd2b6..5f238acbe4 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4156,27 +4156,47 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
- /* Fetch the function body */
- tmp = SysCacheGetAttr(PROCOID,
- func_tuple,
- Anum_pg_proc_prosrc,
- &isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", funcid);
- src = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = src;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ {
+ Node *n;
+ List *querytree_list;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", funcid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = linitial_node(List, castNode(List, n));
+ else
+ querytree_list = list_make1(n);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We need a
* dummy FuncExpr node containing the already-simplified arguments to pass
@@ -4220,6 +4240,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
querytree = transformTopLevelStmt(pstate, linitial(raw_parsetree_list));
free_parsestate(pstate);
+ }
/*
* The single command must be a simple "SELECT expression".
@@ -4476,12 +4497,15 @@ sql_inline_error_callback(void *arg)
int syntaxerrposition;
/* If it's a syntax error, convert to internal syntax error report */
- syntaxerrposition = geterrposition();
- if (syntaxerrposition > 0)
+ if (callback_arg->prosrc)
{
- errposition(0);
- internalerrposition(syntaxerrposition);
- internalerrquery(callback_arg->prosrc);
+ syntaxerrposition = geterrposition();
+ if (syntaxerrposition > 0)
+ {
+ errposition(0);
+ internalerrposition(syntaxerrposition);
+ internalerrquery(callback_arg->prosrc);
+ }
}
errcontext("SQL function \"%s\" during inlining", callback_arg->proname);
@@ -4593,7 +4617,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
Oid func_oid;
HeapTuple func_tuple;
Form_pg_proc funcform;
- char *src;
Datum tmp;
bool isNull;
MemoryContext oldcxt;
@@ -4702,27 +4725,53 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
- /* Fetch the function body */
- tmp = SysCacheGetAttr(PROCOID,
- func_tuple,
- Anum_pg_proc_prosrc,
- &isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", func_oid);
- src = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = src;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ {
+ Node *n;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", func_oid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = linitial_node(List, castNode(List, n));
+ else
+ querytree_list = list_make1(n);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+
+ querytree_list = pg_rewrite_query(querytree);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
+ char *src;
+
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We can
* use the FuncExpr just created as the input for
@@ -4732,18 +4781,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
(Node *) fexpr,
fexpr->inputcollid);
- /*
- * Also resolve the actual function result tupdesc, if composite. If the
- * function is just declared to return RECORD, dig the info out of the AS
- * clause.
- */
- functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
- if (functypclass == TYPEFUNC_RECORD)
- rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
- rtfunc->funccoltypes,
- rtfunc->funccoltypmods,
- rtfunc->funccolcollations);
-
/*
* Parse, analyze, and rewrite (unlike inline_function(), we can't skip
* rewriting here). We can fail as soon as we find more than one query,
@@ -4760,6 +4797,19 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
if (list_length(querytree_list) != 1)
goto fail;
querytree = linitial(querytree_list);
+ }
+
+ /*
+ * Also resolve the actual function result tupdesc, if composite. If the
+ * function is just declared to return RECORD, dig the info out of the AS
+ * clause.
+ */
+ functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
+ if (functypclass == TYPEFUNC_RECORD)
+ rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
+ rtfunc->funccoltypes,
+ rtfunc->funccoltypmods,
+ rtfunc->funccolcollations);
/*
* The single command must be a plain SELECT.
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0f3a70c49a..554db99df2 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -68,6 +68,7 @@ static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
bool isTopLevel, List **targetlist);
static void determineRecursiveColTypes(ParseState *pstate,
Node *larg, List *nrtargetlist);
+static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList);
static List *transformUpdateTargetList(ParseState *pstate,
@@ -308,6 +309,10 @@ transformStmt(ParseState *pstate, Node *parseTree)
}
break;
+ case T_ReturnStmt:
+ result = transformReturnStmt(pstate, (ReturnStmt *) parseTree);
+ break;
+
case T_PLAssignStmt:
result = transformPLAssignStmt(pstate,
(PLAssignStmt *) parseTree);
@@ -2227,6 +2232,36 @@ determineRecursiveColTypes(ParseState *pstate, Node *larg, List *nrtargetlist)
}
+/*
+ * transformReturnStmt -
+ * transforms a return statement
+ */
+static Query *
+transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+
+ qry->commandType = CMD_SELECT;
+ qry->isReturn = true;
+
+ qry->targetList = list_make1(makeTargetEntry((Expr *) transformExpr(pstate, stmt->returnval, EXPR_KIND_SELECT_TARGET),
+ 1, NULL, false));
+
+ if (pstate->p_resolve_unknowns)
+ resolveTargetListUnknowns(pstate, qry->targetList);
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasAggs = pstate->p_hasAggs;
+
+ assign_query_collations(pstate, qry);
+
+ return qry;
+}
+
+
/*
* transformUpdateStmt -
* transforms an update statement
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 652be0b96d..6700a49729 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -252,7 +252,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct SelectLimit *selectlimit;
}
-%type <node> stmt schema_stmt
+%type <node> stmt toplevel_stmt schema_stmt routine_body_stmt
AlterEventTrigStmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
@@ -279,9 +279,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
- RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
+ RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt
RuleActionStmt RuleActionStmtOrEmpty RuleStmt
- SecLabelStmt SelectStmt TransactionStmt TruncateStmt
+ SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
UnlistenStmt UpdateStmt VacuumStmt
VariableResetStmt VariableSetStmt VariableShowStmt
ViewStmt CheckPointStmt CreateConversionStmt
@@ -385,14 +385,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> vacuum_relation
%type <selectlimit> opt_select_limit select_limit limit_clause
-%type <list> parse_toplevel stmtmulti
+%type <list> parse_toplevel stmtmulti routine_body_stmt_list
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
reloptions opt_reloptions
OptWith opt_definition func_args func_args_list
func_args_with_defaults func_args_with_defaults_list
aggr_args aggr_args_list
- func_as createfunc_opt_list alterfunc_opt_list
+ func_as createfunc_opt_list opt_createfunc_opt_list alterfunc_opt_list
old_aggr_definition old_aggr_list
oper_argtypes RuleActionList RuleActionMulti
opt_column_list columnList opt_name_list
@@ -418,6 +418,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
vacuum_relation_list opt_vacuum_relation_list
drop_option_list
+%type <node> opt_routine_body
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
@@ -623,7 +624,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATOMIC ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BREADTH BY
@@ -685,7 +686,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING
REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
- RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
+ RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
@@ -855,7 +856,7 @@ parse_toplevel:
* we'd get -1 for the location in such cases.
* We also take care to discard empty statements entirely.
*/
-stmtmulti: stmtmulti ';' stmt
+stmtmulti: stmtmulti ';' toplevel_stmt
{
if ($1 != NIL)
{
@@ -867,7 +868,7 @@ stmtmulti: stmtmulti ';' stmt
else
$$ = $1;
}
- | stmt
+ | toplevel_stmt
{
if ($1 != NULL)
$$ = list_make1(makeRawStmt($1, 0));
@@ -876,7 +877,16 @@ stmtmulti: stmtmulti ';' stmt
}
;
-stmt :
+/*
+ * toplevel_stmt includes BEGIN and END. stmt does not include them, because
+ * those words have different meanings in function bodys.
+ */
+toplevel_stmt:
+ stmt
+ | TransactionStmtLegacy
+ ;
+
+stmt:
AlterEventTrigStmt
| AlterDatabaseStmt
| AlterDatabaseSetStmt
@@ -7399,7 +7409,7 @@ opt_nulls_order: NULLS_LA FIRST_P { $$ = SORTBY_NULLS_FIRST; }
CreateFunctionStmt:
CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS func_return createfunc_opt_list
+ RETURNS func_return opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7408,10 +7418,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = $7;
n->options = $8;
+ n->sql_body = $9;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS TABLE '(' table_func_column_list ')' createfunc_opt_list
+ RETURNS TABLE '(' table_func_column_list ')' opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7421,10 +7432,11 @@ CreateFunctionStmt:
n->returnType = TableFuncTypeName($9);
n->returnType->location = @7;
n->options = $11;
+ n->sql_body = $12;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7433,10 +7445,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
| CREATE opt_or_replace PROCEDURE func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = true;
@@ -7445,6 +7458,7 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
;
@@ -7755,6 +7769,11 @@ aggregate_with_argtypes_list:
{ $$ = lappend($1, $3); }
;
+opt_createfunc_opt_list:
+ createfunc_opt_list
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
createfunc_opt_list:
/* Must be at least one to prevent conflict */
createfunc_opt_item { $$ = list_make1($1); }
@@ -7866,6 +7885,51 @@ func_as: Sconst { $$ = list_make1(makeString($1)); }
}
;
+ReturnStmt: RETURN a_expr
+ {
+ ReturnStmt *r = makeNode(ReturnStmt);
+ r->returnval = (Node *) $2;
+ $$ = (Node *) r;
+ }
+ ;
+
+opt_routine_body:
+ ReturnStmt
+ {
+ $$ = $1;
+ }
+ | BEGIN_P ATOMIC routine_body_stmt_list END_P
+ {
+ /*
+ * A compound statement is stored as a single-item list
+ * containing the list of statements as its member. That
+ * way, the parse analysis code can tell apart an empty
+ * body from no body at all.
+ */
+ $$ = (Node *) list_make1($3);
+ }
+ | /*EMPTY*/
+ {
+ $$ = NULL;
+ }
+ ;
+
+routine_body_stmt_list:
+ routine_body_stmt_list routine_body_stmt ';'
+ {
+ $$ = lappend($1, $2);
+ }
+ | /*EMPTY*/
+ {
+ $$ = NIL;
+ }
+ ;
+
+routine_body_stmt:
+ stmt
+ | ReturnStmt
+ ;
+
transform_type_list:
FOR TYPE_P Typename { $$ = list_make1($3); }
| transform_type_list ',' FOR TYPE_P Typename { $$ = lappend($1, $5); }
@@ -9799,13 +9863,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | BEGIN_P opt_transaction transaction_mode_list_or_empty
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_BEGIN;
- n->options = $3;
- $$ = (Node *)n;
- }
| START TRANSACTION transaction_mode_list_or_empty
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9821,14 +9878,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | END_P opt_transaction opt_transaction_chain
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_COMMIT;
- n->options = NIL;
- n->chain = $3;
- $$ = (Node *)n;
- }
| ROLLBACK opt_transaction opt_transaction_chain
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9895,6 +9944,24 @@ TransactionStmt:
}
;
+TransactionStmtLegacy:
+ BEGIN_P opt_transaction transaction_mode_list_or_empty
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_BEGIN;
+ n->options = $3;
+ $$ = (Node *)n;
+ }
+ | END_P opt_transaction opt_transaction_chain
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_COMMIT;
+ n->options = NIL;
+ n->chain = $3;
+ $$ = (Node *)n;
+ }
+ ;
+
opt_transaction: WORK
| TRANSACTION
| /*EMPTY*/
@@ -15272,6 +15339,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| BACKWARD
@@ -15472,6 +15540,7 @@ unreserved_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| ROLE
@@ -15778,6 +15847,7 @@ bare_label_keyword:
| ASSIGNMENT
| ASYMMETRIC
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| AUTHORIZATION
@@ -16050,6 +16120,7 @@ bare_label_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| RIGHT
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index bb5ccb4578..0a3f8cef00 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -177,7 +177,6 @@ static int interactive_getc(void);
static int SocketBackend(StringInfo inBuf);
static int ReadCommand(StringInfo inBuf);
static void forbidden_in_wal_sender(char firstchar);
-static List *pg_rewrite_query(Query *query);
static bool check_log_statement(List *stmt_list);
static int errdetail_execute(List *raw_parsetree_list);
static int errdetail_params(ParamListInfo params);
@@ -762,7 +761,7 @@ pg_analyze_and_rewrite_params(RawStmt *parsetree,
* Note: query must just have come from the parser, because we do not do
* AcquireRewriteLocks() on it.
*/
-static List *
+List *
pg_rewrite_query(Query *query)
{
List *querytree_list;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 879288c139..28602b01fb 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -172,6 +172,10 @@ typedef struct
List *outer_tlist; /* referent for OUTER_VAR Vars */
List *inner_tlist; /* referent for INNER_VAR Vars */
List *index_tlist; /* referent for INDEX_VAR Vars */
+ /* Special namespace representing a function signature: */
+ char *funcname;
+ int numargs;
+ char **argnames;
} deparse_namespace;
/*
@@ -347,6 +351,7 @@ static int print_function_arguments(StringInfo buf, HeapTuple proctup,
bool print_table_args, bool print_defaults);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
static void print_function_trftypes(StringInfo buf, HeapTuple proctup);
+static void print_function_sqlbody(StringInfo buf, HeapTuple proctup);
static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used);
static void set_deparse_for_query(deparse_namespace *dpns, Query *query,
@@ -2799,6 +2804,13 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
}
/* And finally the function definition ... */
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
+ if (proc->prolang == SQLlanguageId && !isnull)
+ {
+ print_function_sqlbody(&buf, proctup);
+ }
+ else
+ {
appendStringInfoString(&buf, "AS ");
tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
@@ -2830,6 +2842,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, dq.data, dq.len);
appendStringInfoString(&buf, prosrc);
appendBinaryStringInfo(&buf, dq.data, dq.len);
+ }
appendStringInfoChar(&buf, '\n');
@@ -3213,6 +3226,76 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(string_to_text(str));
}
+static void
+print_function_sqlbody(StringInfo buf, HeapTuple proctup)
+{
+ int numargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ deparse_namespace dpns = {0};
+ Datum tmp;
+ bool isnull;
+ Node *n;
+
+ dpns.funcname = pstrdup(NameStr(((Form_pg_proc) GETSTRUCT(proctup))->proname));
+ numargs = get_func_arg_info(proctup,
+ &argtypes, &argnames, &argmodes);
+ dpns.numargs = numargs;
+ dpns.argnames = argnames;
+
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
+ Assert(!isnull);
+ n = stringToNode(TextDatumGetCString(tmp));
+
+ if (IsA(n, List))
+ {
+ List *stmts;
+ ListCell *lc;
+
+ stmts = linitial(castNode(List, n));
+
+ appendStringInfoString(buf, "BEGIN ATOMIC\n");
+
+ foreach(lc, stmts)
+ {
+ Query *query = lfirst_node(Query, lc);
+
+ get_query_def(query, buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ appendStringInfoChar(buf, ';');
+ appendStringInfoChar(buf, '\n');
+ }
+
+ appendStringInfoString(buf, "END");
+ }
+ else
+ {
+ get_query_def(castNode(Query, n), buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ appendStringInfoChar(buf, '\n');
+ }
+}
+
+Datum
+pg_get_function_sqlbody(PG_FUNCTION_ARGS)
+{
+ Oid funcid = PG_GETARG_OID(0);
+ StringInfoData buf;
+ HeapTuple proctup;
+
+ initStringInfo(&buf);
+
+ /* Look up the function */
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ PG_RETURN_NULL();
+
+ print_function_sqlbody(&buf, proctup);
+
+ ReleaseSysCache(proctup);
+
+ PG_RETURN_TEXT_P(cstring_to_text(buf.data));
+}
+
/*
* deparse_expression - General utility for deparsing expressions
@@ -5472,7 +5555,10 @@ get_basic_select_query(Query *query, deparse_context *context,
/*
* Build up the query string - first we say SELECT
*/
- appendStringInfoString(buf, "SELECT");
+ if (query->isReturn)
+ appendStringInfoString(buf, "RETURN");
+ else
+ appendStringInfoString(buf, "SELECT");
/* Add the DISTINCT clause if given */
if (query->distinctClause != NIL)
@@ -7604,6 +7690,50 @@ get_parameter(Param *param, deparse_context *context)
return;
}
+ /*
+ * If it's an external parameter, see if the outermost namespace provides
+ * function argument names.
+ */
+ if (param->paramkind == PARAM_EXTERN)
+ {
+ dpns = lfirst(list_tail(context->namespaces));
+ if (dpns->argnames)
+ {
+ char *argname = dpns->argnames[param->paramid - 1];
+
+ if (argname)
+ {
+ bool should_qualify = false;
+ ListCell *lc;
+
+ /*
+ * Qualify the parameter name if there are any other deparse
+ * namespaces with range tables. This avoids qualifying in
+ * trivial cases like "RETURN a + b", but makes it safe in all
+ * other cases.
+ */
+ foreach (lc, context->namespaces)
+ {
+ deparse_namespace *dpns = lfirst(lc);
+
+ if (list_length(dpns->rtable_names) > 0)
+ {
+ should_qualify = true;
+ break;
+ }
+ }
+ if (should_qualify)
+ {
+ appendStringInfoString(context->buf, quote_identifier(dpns->funcname));
+ appendStringInfoChar(context->buf, '.');
+ }
+
+ appendStringInfoString(context->buf, quote_identifier(argname));
+ return;
+ }
+ }
+ }
+
/*
* Not PARAM_EXEC, or couldn't find referent: just print $N.
*/
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index eb988d7eb4..98a74d1efb 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11979,6 +11979,7 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
char *proretset;
char *prosrc;
char *probin;
+ char *prosqlbody;
char *funcargs;
char *funciargs;
char *funcresult;
@@ -12025,7 +12026,7 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
"provolatile,\n"
"proisstrict,\n"
"prosecdef,\n"
- "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname,\n");
+ "lanname,\n");
if (fout->remoteVersion >= 80300)
appendPQExpBufferStr(query,
@@ -12045,9 +12046,9 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
* pg_get_function_result instead of examining proallargtypes etc.
*/
appendPQExpBufferStr(query,
- "pg_catalog.pg_get_function_arguments(oid) AS funcargs,\n"
- "pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs,\n"
- "pg_catalog.pg_get_function_result(oid) AS funcresult,\n");
+ "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n"
+ "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n"
+ "pg_catalog.pg_get_function_result(p.oid) AS funcresult,\n");
}
else if (fout->remoteVersion >= 80100)
appendPQExpBufferStr(query,
@@ -12090,21 +12091,39 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
if (fout->remoteVersion >= 120000)
appendPQExpBufferStr(query,
- "prosupport\n");
+ "prosupport,\n");
else
appendPQExpBufferStr(query,
- "'-' AS prosupport\n");
+ "'-' AS prosupport,\n");
+
+ if (fout->remoteVersion >= 140000)
+ appendPQExpBufferStr(query,
+ "CASE WHEN prosrc IS NULL AND lanname = 'sql' THEN pg_get_function_sqlbody(p.oid) END AS prosqlbody\n");
+ else
+ appendPQExpBufferStr(query,
+ "NULL AS prosqlbody\n");
appendPQExpBuffer(query,
- "FROM pg_catalog.pg_proc "
- "WHERE oid = '%u'::pg_catalog.oid",
+ "FROM pg_catalog.pg_proc p, pg_catalog.pg_language l\n"
+ "WHERE p.oid = '%u'::pg_catalog.oid "
+ "AND l.oid = p.prolang",
finfo->dobj.catId.oid);
res = ExecuteSqlQueryForSingleRow(fout, query->data);
proretset = PQgetvalue(res, 0, PQfnumber(res, "proretset"));
- prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
- probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ if (PQgetisnull(res, 0, PQfnumber(res, "prosqlbody")))
+ {
+ prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
+ probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ prosqlbody = NULL;
+ }
+ else
+ {
+ prosrc = NULL;
+ probin = NULL;
+ prosqlbody = PQgetvalue(res, 0, PQfnumber(res, "prosqlbody"));
+ }
if (fout->remoteVersion >= 80400)
{
funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
@@ -12141,7 +12160,11 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
* versions would set it to "-". There are no known cases in which prosrc
* is unused, so the tests below for "-" are probably useless.
*/
- if (probin[0] != '\0' && strcmp(probin, "-") != 0)
+ if (prosqlbody)
+ {
+ appendPQExpBufferStr(asPart, prosqlbody);
+ }
+ else if (probin[0] != '\0' && strcmp(probin, "-") != 0)
{
appendPQExpBufferStr(asPart, "AS ");
appendStringLiteralAH(asPart, probin, fout);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 20af5a92b4..a47c78d4bc 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -505,11 +505,18 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
appendPQExpBufferStr(&buf, ",\n ");
printACLColumn(&buf, "p.proacl");
appendPQExpBuffer(&buf,
- ",\n l.lanname as \"%s\""
- ",\n p.prosrc as \"%s\""
+ ",\n l.lanname as \"%s\"",
+ gettext_noop("Language"));
+ if (pset.sversion >= 140000)
+ appendPQExpBuffer(&buf,
+ ",\n COALESCE(p.prosrc, pg_catalog.pg_get_function_sqlbody(p.oid)) as \"%s\"",
+ gettext_noop("Source code"));
+ else
+ appendPQExpBuffer(&buf,
+ ",\n p.prosrc as \"%s\"",
+ gettext_noop("Source code"));
+ appendPQExpBuffer(&buf,
",\n pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"",
- gettext_noop("Language"),
- gettext_noop("Source code"),
gettext_noop("Description"));
}
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 216ff30993..a492a32416 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -645,10 +645,11 @@ other .
";" {
ECHO;
- if (cur_state->paren_depth == 0)
+ if (cur_state->paren_depth == 0 && cur_state->begin_depth == 0)
{
/* Terminate lexing temporarily */
cur_state->start_state = YY_START;
+ cur_state->identifier_count = 0;
return LEXRES_SEMI;
}
}
@@ -661,6 +662,8 @@ other .
"\\"[;:] {
/* Force a semi-colon or colon into the query buffer */
psqlscan_emit(cur_state, yytext + 1, 1);
+ if (yytext[1] == ';')
+ cur_state->identifier_count = 0;
}
"\\" {
@@ -867,6 +870,17 @@ other .
{identifier} {
+ cur_state->identifier_count++;
+ if (pg_strcasecmp(yytext, "begin") == 0)
+ {
+ if (cur_state->identifier_count > 1)
+ cur_state->begin_depth++;
+ }
+ else if (pg_strcasecmp(yytext, "end") == 0)
+ {
+ if (cur_state->begin_depth > 0)
+ cur_state->begin_depth--;
+ }
ECHO;
}
@@ -1054,6 +1068,11 @@ psql_scan(PsqlScanState state,
result = PSCAN_INCOMPLETE;
*prompt = PROMPT_PAREN;
}
+ if (state->begin_depth > 0)
+ {
+ result = PSCAN_INCOMPLETE;
+ *prompt = PROMPT_CONTINUE;
+ }
else if (query_buf->len > 0)
{
result = PSCAN_EOL;
@@ -1170,6 +1189,8 @@ psql_scan_reset(PsqlScanState state)
if (state->dolqstart)
free(state->dolqstart);
state->dolqstart = NULL;
+ state->identifier_count = 0;
+ state->begin_depth = 0;
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 1487710d59..4ba559b0a0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3689,6 +3689,10 @@
proname => 'pg_get_function_arg_default', provolatile => 's',
prorettype => 'text', proargtypes => 'oid int4',
prosrc => 'pg_get_function_arg_default' },
+{ oid => '9704', descr => 'function SQL body',
+ proname => 'pg_get_function_sqlbody', provolatile => 's',
+ prorettype => 'text', proargtypes => 'oid',
+ prosrc => 'pg_get_function_sqlbody' },
{ oid => '1686', descr => 'list of SQL keywords',
proname => 'pg_get_keywords', procost => '10', prorows => '500',
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 78f230894b..8d58067d03 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -112,11 +112,14 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
Oid protrftypes[1] BKI_DEFAULT(_null_) BKI_LOOKUP(pg_type);
/* procedure source text */
- text prosrc BKI_FORCE_NOT_NULL;
+ text prosrc;
/* secondary procedure info (can be NULL) */
text probin BKI_DEFAULT(_null_);
+ /* pre-parsed SQL function body */
+ pg_node_tree prosqlbody BKI_DEFAULT(_null_);
+
/* procedure-local GUC settings */
text proconfig[1] BKI_DEFAULT(_null_);
@@ -194,6 +197,7 @@ extern ObjectAddress ProcedureCreate(const char *procedureName,
Oid languageValidator,
const char *prosrc,
const char *probin,
+ Node *prosqlbody,
char prokind,
bool security_definer,
bool isLeakProof,
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 1a79540c94..2bb0fb10de 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -65,9 +65,11 @@ extern void interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType);
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index c975a93661..dcb8e18437 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -20,6 +20,21 @@
/* This struct is known only within executor/functions.c */
typedef struct SQLFunctionParseInfo *SQLFunctionParseInfoPtr;
+/*
+ * Data structure needed by the parser callback hooks to resolve parameter
+ * references during parsing of a SQL function's body. This is separate from
+ * SQLFunctionCache since we sometimes do parsing separately from execution.
+ */
+typedef struct SQLFunctionParseInfo
+{
+ char *fname; /* function's name */
+ int nargs; /* number of input arguments */
+ Oid *argtypes; /* resolved types of input arguments */
+ char **argnames; /* names of input arguments; NULL if none */
+ /* Note that argnames[i] can be NULL, if some args are unnamed */
+ Oid collation; /* function's input collation, if known */
+} SQLFunctionParseInfo;
+
extern Datum fmgr_sql(PG_FUNCTION_ARGS);
extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTuple,
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 2bc3cc4ae7..91d7d4d5c6 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -114,6 +114,8 @@ typedef struct PsqlScanStateData
int paren_depth; /* depth of nesting in parentheses */
int xcdepth; /* depth of nesting in slash-star comments */
char *dolqstart; /* current $foo$ quote start string */
+ int identifier_count; /* identifiers since start of statement */
+ int begin_depth; /* depth of begin/end routine body blocks */
/*
* Callback functions provided by the program making use of the lexer,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..dcca0e456b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -317,6 +317,7 @@ typedef enum NodeTag
T_DeleteStmt,
T_UpdateStmt,
T_SelectStmt,
+ T_ReturnStmt,
T_PLAssignStmt,
T_AlterTableStmt,
T_AlterTableCmd,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 236832a2ca..97908b597e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -132,6 +132,8 @@ typedef struct Query
bool hasForUpdate; /* FOR [KEY] UPDATE/SHARE was specified */
bool hasRowSecurity; /* rewriter has applied some RLS policy */
+ bool isReturn; /* is a RETURN statement */
+
List *cteList; /* WITH list (of CommonTableExpr's) */
List *rtable; /* list of range table entries */
@@ -1701,6 +1703,16 @@ typedef struct SetOperationStmt
} SetOperationStmt;
+/*
+ * RETURN statement (inside SQL function body)
+ */
+typedef struct ReturnStmt
+{
+ NodeTag type;
+ Node *returnval;
+} ReturnStmt;
+
+
/* ----------------------
* PL/pgSQL Assignment Statement
*
@@ -2884,6 +2896,7 @@ typedef struct CreateFunctionStmt
List *parameters; /* a list of FunctionParameter */
TypeName *returnType; /* the return type */
List *options; /* a list of DefElem */
+ Node *sql_body;
} CreateFunctionStmt;
typedef enum FunctionParameterMode
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 28083aaac9..ec467b5c25 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -48,6 +48,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("atomic", ATOMIC, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -346,6 +347,7 @@ PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("return", RETURN, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD, AS_LABEL)
PG_KEYWORD("returns", RETURNS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index e5472100a4..49700d151a 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -43,6 +43,7 @@ typedef enum
extern PGDLLIMPORT int log_statement;
extern List *pg_parse_query(const char *query_string);
+extern List *pg_rewrite_query(Query *query);
extern List *pg_analyze_and_rewrite(RawStmt *parsetree,
const char *query_string,
Oid *paramTypes, int numParams,
diff --git a/src/interfaces/ecpg/preproc/ecpg.addons b/src/interfaces/ecpg/preproc/ecpg.addons
index 300381eaad..0441561c52 100644
--- a/src/interfaces/ecpg/preproc/ecpg.addons
+++ b/src/interfaces/ecpg/preproc/ecpg.addons
@@ -87,6 +87,12 @@ ECPG: stmtTransactionStmt block
whenever_action(2);
free($1);
}
+ECPG: toplevel_stmtTransactionStmtLegacy block
+ {
+ fprintf(base_yyout, "{ ECPGtrans(__LINE__, %s, \"%s\");", connection ? connection : "NULL", $1);
+ whenever_action(2);
+ free($1);
+ }
ECPG: stmtViewStmt rule
| ECPGAllocateDescr
{
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer
index 0e4a041393..bcabf88341 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -4,8 +4,8 @@ statements: /*EMPTY*/
| statements statement
;
-statement: ecpgstart at stmt ';' { connection = NULL; }
- | ecpgstart stmt ';'
+statement: ecpgstart at toplevel_stmt ';' { connection = NULL; }
+ | ecpgstart toplevel_stmt ';'
| ecpgstart ECPGVarDeclaration
{
fprintf(base_yyout, "%s", $2);
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index f25a407fdd..a9b4b40e1a 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -255,6 +255,151 @@ SELECT pg_get_functiondef('functest_F_2'::regproc);
(1 row)
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_3a() RETURNS boolean
+ BEGIN ATOMIC
+ RETURN false;
+ END;
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+ERROR: SQL function with unquoted function body cannot have polymorphic arguments
+SELECT functest_S_1('abcd', '2020-01-01');
+ functest_s_1
+--------------
+ t
+(1 row)
+
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+ functest_s_2
+--------------
+ 1
+(1 row)
+
+SELECT functest_S_3();
+ functest_s_3
+--------------
+ f
+(1 row)
+
+SELECT functest_S_10('abcd', '2020-01-01');
+ functest_s_10
+---------------
+ t
+(1 row)
+
+SELECT functest_S_13();
+ functest_s_13
+---------------
+ f
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_1(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN ((a = 'abcd'::text) AND (b > '01-01-2001'::date)) +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_2(a text[])+
+ RETURNS integer +
+ LANGUAGE sql +
+ RETURN ((a)[1])::integer +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+ pg_get_functiondef
+----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN false +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3a'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3a()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ RETURN false; +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+ pg_get_functiondef
+-------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_10(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT ((a = 'abcd'::text) AND (b > '01-01-2001'::date)); +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_13()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT 1; +
+ SELECT false AS bool; +
+ END +
+
+(1 row)
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+SELECT functest_S_14();
+ functest_s_14
+---------------
+ 2
+(1 row)
+
+DROP TABLE functest3 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view functestv3
+drop cascades to function functest_s_14()
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
RETURNS int
@@ -292,6 +437,15 @@ CREATE FUNCTION functest_IS_5(x int DEFAULT nextval('functest1'))
RETURNS int
LANGUAGE SQL
AS 'SELECT x';
+CREATE FUNCTION functest_IS_6()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest1');
+CREATE TABLE functest2 (a int, b int);
+CREATE FUNCTION functest_IS_7()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest2);
SELECT r0.routine_name, r1.routine_name
FROM information_schema.routine_routine_usage rru
JOIN information_schema.routines r0 ON r0.specific_name = rru.specific_name
@@ -305,23 +459,29 @@ SELECT routine_name, sequence_name FROM information_schema.routine_sequence_usag
routine_name | sequence_name
---------------+---------------
functest_is_5 | functest1
-(1 row)
+ functest_is_6 | functest1
+(2 rows)
--- currently empty
SELECT routine_name, table_name, column_name FROM information_schema.routine_column_usage;
- routine_name | table_name | column_name
---------------+------------+-------------
-(0 rows)
+ routine_name | table_name | column_name
+---------------+------------+-------------
+ functest_is_7 | functest2 | a
+(1 row)
SELECT routine_name, table_name FROM information_schema.routine_table_usage;
- routine_name | table_name
---------------+------------
-(0 rows)
+ routine_name | table_name
+---------------+------------
+ functest_is_7 | functest2
+(1 row)
DROP FUNCTION functest_IS_4a CASCADE;
NOTICE: drop cascades to function functest_is_4b(integer)
DROP SEQUENCE functest1 CASCADE;
-NOTICE: drop cascades to function functest_is_5(integer)
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to function functest_is_5(integer)
+drop cascades to function functest_is_6()
+DROP TABLE functest2 CASCADE;
+NOTICE: drop cascades to function functest_is_7()
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
@@ -340,6 +500,49 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
ERROR: cannot change routine kind
DETAIL: "functest1" is a function.
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+SELECT * FROM functest_sri1();
+ functest_sri1
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+SELECT * FROM functest_sri2();
+ functest_sri2
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
$$ SELECT a + 1 $$;
@@ -398,7 +601,7 @@ SELECT * FROM voidtest5(3);
-- Cleanup
DROP SCHEMA temp_func_test CASCADE;
-NOTICE: drop cascades to 21 other objects
+NOTICE: drop cascades to 29 other objects
DETAIL: drop cascades to function functest_a_1(text,date)
drop cascades to function functest_a_2(text[])
drop cascades to function functest_a_3()
@@ -414,7 +617,15 @@ drop cascades to function functest_f_1(integer)
drop cascades to function functest_f_2(integer)
drop cascades to function functest_f_3(integer)
drop cascades to function functest_f_4(integer)
+drop cascades to function functest_s_1(text,date)
+drop cascades to function functest_s_2(text[])
+drop cascades to function functest_s_3()
+drop cascades to function functest_s_3a()
+drop cascades to function functest_s_10(text,date)
+drop cascades to function functest_s_13()
drop cascades to function functest_b_2(bigint)
+drop cascades to function functest_sri1()
+drop cascades to function functest_sri2()
drop cascades to function voidtest1(integer)
drop cascades to function voidtest2(integer,integer)
drop cascades to function voidtest3(integer)
diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out
index 3838fa2324..b5f2d4e38c 100644
--- a/src/test/regress/expected/create_procedure.out
+++ b/src/test/regress/expected/create_procedure.out
@@ -65,6 +65,41 @@ SELECT * FROM cp_test ORDER BY b COLLATE "C";
1 | xyzzy
(3 rows)
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+\df ptest1s
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+---------+------------------+---------------------+------
+ public | ptest1s | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest1s'::regproc);
+ pg_get_functiondef
+----------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest1s(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ INSERT INTO cp_test (a, b) +
+ VALUES (1, ptest1s.x); +
+ END +
+
+(1 row)
+
+CALL ptest1s('b');
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+ a | b
+---+-------
+ 1 | 0
+ 1 | a
+ 1 | b
+ 1 | xyzzy
+(4 rows)
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -146,6 +181,28 @@ AS $$
SELECT a = b;
$$;
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+\df ptest8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+--------+------------------+---------------------+------
+ public | ptest8 | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest8'::regproc);
+ pg_get_functiondef
+---------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest8(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ END +
+
+(1 row)
+
+CALL ptest8('');
-- OUT parameters
CREATE PROCEDURE ptest9(OUT a int)
LANGUAGE SQL
@@ -214,6 +271,7 @@ ALTER ROUTINE ptest1a RENAME TO ptest1;
DROP ROUTINE cp_testfunc1(int);
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
DROP USER regress_cp_user1;
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
index 549b34b4b2..2f3143be09 100644
--- a/src/test/regress/sql/create_function_3.sql
+++ b/src/test/regress/sql/create_function_3.sql
@@ -153,6 +153,65 @@ CREATE FUNCTION functest_F_4(int) RETURNS bool LANGUAGE 'sql'
SELECT pg_get_functiondef('functest_F_2'::regproc);
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_3a() RETURNS boolean
+ BEGIN ATOMIC
+ RETURN false;
+ END;
+
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+
+SELECT functest_S_1('abcd', '2020-01-01');
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+SELECT functest_S_3();
+
+SELECT functest_S_10('abcd', '2020-01-01');
+SELECT functest_S_13();
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+SELECT pg_get_functiondef('functest_S_3a'::regproc);
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+
+SELECT functest_S_14();
+
+DROP TABLE functest3 CASCADE;
+
+
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
@@ -188,17 +247,29 @@ CREATE FUNCTION functest_IS_5(x int DEFAULT nextval('functest1'))
LANGUAGE SQL
AS 'SELECT x';
+CREATE FUNCTION functest_IS_6()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest1');
+
+CREATE TABLE functest2 (a int, b int);
+
+CREATE FUNCTION functest_IS_7()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest2);
+
SELECT r0.routine_name, r1.routine_name
FROM information_schema.routine_routine_usage rru
JOIN information_schema.routines r0 ON r0.specific_name = rru.specific_name
JOIN information_schema.routines r1 ON r1.specific_name = rru.routine_name;
SELECT routine_name, sequence_name FROM information_schema.routine_sequence_usage;
--- currently empty
SELECT routine_name, table_name, column_name FROM information_schema.routine_column_usage;
SELECT routine_name, table_name FROM information_schema.routine_table_usage;
DROP FUNCTION functest_IS_4a CASCADE;
DROP SEQUENCE functest1 CASCADE;
+DROP TABLE functest2 CASCADE;
-- overload
@@ -218,6 +289,29 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+
+SELECT * FROM functest_sri1();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+
+SELECT * FROM functest_sri2();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
diff --git a/src/test/regress/sql/create_procedure.sql b/src/test/regress/sql/create_procedure.sql
index 2ef1c82cea..8c0d70cb16 100644
--- a/src/test/regress/sql/create_procedure.sql
+++ b/src/test/regress/sql/create_procedure.sql
@@ -28,6 +28,21 @@ CREATE PROCEDURE ptest1(x text)
SELECT * FROM cp_test ORDER BY b COLLATE "C";
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+
+\df ptest1s
+SELECT pg_get_functiondef('ptest1s'::regproc);
+
+CALL ptest1s('b');
+
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -112,6 +127,16 @@ CREATE PROCEDURE ptest7(a text, b text)
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+
+\df ptest8
+SELECT pg_get_functiondef('ptest8'::regproc);
+CALL ptest8('');
+
+
-- OUT parameters
CREATE PROCEDURE ptest9(OUT a int)
@@ -170,6 +195,7 @@ CREATE USER regress_cp_user1;
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
base-commit: c5530d8474a482d32c0d9bb099707d9a8e117f96
--
2.30.1
On Tue, Mar 2, 2021 at 12:13 PM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:
On 11.02.21 09:02, Peter Eisentraut wrote:
Here is an updated patch to get it building again.
Another updated patch to get things building again. I've also fixed the
last TODO I had in there in qualifying function arguments as necessary
in ruleutils.c.Updated patch to resolve merge conflict. No changes in functionality.
Hi,
I was making some tests with this patch and found this problem:
"""
CREATE OR REPLACE FUNCTION public.make_table()
RETURNS void
LANGUAGE sql
BEGIN ATOMIC
CREATE TABLE created_table AS SELECT * FROM int8_tbl;
END;
ERROR: unrecognized token: "?"
CONTEXT: SQL function "make_table"
"""
Attached a backtrace from the point the error is thrown.
--
Jaime Casanova
Director de Servicios Profesionales
SYSTEMGUARDS - Consultores de PostgreSQL
Attachments:
On 05.03.21 06:58, Jaime Casanova wrote:
I was making some tests with this patch and found this problem:
"""
CREATE OR REPLACE FUNCTION public.make_table()
RETURNS void
LANGUAGE sql
BEGIN ATOMIC
CREATE TABLE created_table AS SELECT * FROM int8_tbl;
END;
ERROR: unrecognized token: "?"
CONTEXT: SQL function "make_table"
"""
I see. The problem is that we don't have serialization and
deserialization support for most utility statements. I think I'll need
to add that eventually. For now, I have added code to prevent utility
statements. I think it's still useful that way for now.
Attachments:
v9-0001-SQL-standard-function-body.patchtext/plain; charset=UTF-8; name=v9-0001-SQL-standard-function-body.patch; x-mac-creator=0; x-mac-type=0Download
From 29de4ec1ae12d3f9c1f6a31cf626a19b2421ae7a Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Tue, 9 Mar 2021 13:25:39 +0100
Subject: [PATCH v9] SQL-standard function body
This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.
Instead of the PostgreSQL-specific AS $$ string literal $$ syntax,
this allows writing out the SQL statements making up the body
unquoted, either as a single statement:
CREATE FUNCTION add(a integer, b integer) RETURNS integer
LANGUAGE SQL
RETURN a + b;
or as a block
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
BEGIN ATOMIC
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
END;
The function body is parsed at function definition time and stored as
expression nodes in a new pg_proc column prosqlbody. So at run time,
no further parsing is required.
However, this form does not support polymorphic arguments, because
there is no more parse analysis done at call time.
Dependencies between the function and the objects it uses are fully
tracked.
A new RETURN statement is introduced. This can only be used inside
function bodies. Internally, it is treated much like a SELECT
statement.
psql needs some new intelligence to keep track of function body
boundaries so that it doesn't send off statements when it sees
semicolons that are inside a function body.
Also, per SQL standard, LANGUAGE SQL is the default, so it does not
need to be specified anymore.
Discussion: https://www.postgresql.org/message-id/flat/1c11f1eb-f00c-43b7-799d-2d44132c02d7@2ndquadrant.com
---
doc/src/sgml/catalogs.sgml | 10 +
doc/src/sgml/ref/create_function.sgml | 126 +++++++++-
doc/src/sgml/ref/create_procedure.sgml | 62 ++++-
src/backend/catalog/pg_aggregate.c | 1 +
src/backend/catalog/pg_proc.c | 116 +++++----
src/backend/commands/aggregatecmds.c | 2 +
src/backend/commands/functioncmds.c | 130 ++++++++--
src/backend/commands/typecmds.c | 4 +
src/backend/executor/functions.c | 79 +++---
src/backend/nodes/copyfuncs.c | 15 ++
src/backend/nodes/equalfuncs.c | 13 +
src/backend/nodes/outfuncs.c | 12 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/util/clauses.c | 126 +++++++---
src/backend/parser/analyze.c | 35 +++
src/backend/parser/gram.y | 129 +++++++---
src/backend/tcop/postgres.c | 3 +-
src/backend/utils/adt/ruleutils.c | 132 +++++++++-
src/bin/pg_dump/pg_dump.c | 45 +++-
src/bin/psql/describe.c | 15 +-
src/fe_utils/psqlscan.l | 23 +-
src/include/catalog/pg_proc.dat | 4 +
src/include/catalog/pg_proc.h | 6 +-
src/include/commands/defrem.h | 2 +
src/include/executor/functions.h | 15 ++
src/include/fe_utils/psqlscan_int.h | 2 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 13 +
src/include/parser/kwlist.h | 2 +
src/include/tcop/tcopprot.h | 1 +
src/interfaces/ecpg/preproc/ecpg.addons | 6 +
src/interfaces/ecpg/preproc/ecpg.trailer | 4 +-
.../regress/expected/create_function_3.out | 231 +++++++++++++++++-
.../regress/expected/create_procedure.out | 65 +++++
src/test/regress/sql/create_function_3.sql | 96 +++++++-
src/test/regress/sql/create_procedure.sql | 33 +++
36 files changed, 1346 insertions(+), 214 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b1de6d0674..146bf0f0b0 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -5980,6 +5980,16 @@ <title><structname>pg_proc</structname> Columns</title>
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>prosqlbody</structfield> <type>pg_node_tree</type>
+ </para>
+ <para>
+ Pre-parsed SQL function body. This will be used for language SQL
+ functions if the body is not specified as a string constant.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>proconfig</structfield> <type>text[]</type>
diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index 3c1eaea651..1b5b9420db 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -38,6 +38,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -257,8 +258,9 @@ <title>Parameters</title>
The name of the language that the function is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -577,6 +579,44 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> function. This can
+ either be a single statement
+<programlisting>
+RETURN <replaceable>expression</replaceable>
+</programlisting>
+ or a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the function body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at function definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at function definition time. This form tracks dependencies between the
+ function and objects used in the function body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling functions. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
<para>
@@ -669,6 +709,15 @@ <title>Examples</title>
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
+</programlisting>
+ The same function written in a more SQL-conforming style, using argument
+ names and an unquoted body:
+<programlisting>
+CREATE FUNCTION add(a integer, b integer) RETURNS integer
+ LANGUAGE SQL
+ IMMUTABLE
+ RETURNS NULL ON NULL INPUT
+ RETURN a + b;
</programlisting>
</para>
@@ -799,23 +848,74 @@ <title>Writing <literal>SECURITY DEFINER</literal> Functions Safely</title>
<title>Compatibility</title>
<para>
- A <command>CREATE FUNCTION</command> command is defined in the SQL standard.
- The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. The attributes are not portable, neither are the
- different available languages.
+ A <command>CREATE FUNCTION</command> command is defined in the SQL
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. Conversely, the SQL
+ standard specifies a number of optional features that are not implemented
+ in <productname>PostgreSQL</productname>.
</para>
<para>
- For compatibility with some other database systems,
- <replaceable class="parameter">argmode</replaceable> can be written
- either before or after <replaceable class="parameter">argname</replaceable>.
- But only the first way is standard-compliant.
+ The following are important compatibility issues:
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>OR REPLACE</literal> is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For compatibility with some other database systems, <replaceable
+ class="parameter">argmode</replaceable> can be written either before or
+ after <replaceable class="parameter">argname</replaceable>. But only
+ the first way is standard-compliant.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For parameter defaults, the SQL standard specifies only the syntax with
+ the <literal>DEFAULT</literal> key word. The syntax with
+ <literal>=</literal> is used in T-SQL and Firebird.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The <literal>SETOF</literal> modifier is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Only <literal>SQL</literal> is standardized as a language.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ All other attributes except <literal>CALLED ON NULL INPUT</literal> and
+ <literal>RETURNS NULL ON NULL INPUT</literal> are not standardized.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For the body of <literal>LANGUAGE SQL</literal> functions, the SQL
+ standard only specifies the <replaceable>sql_body</replaceable> form.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
<para>
- For parameter defaults, the SQL standard specifies only the syntax with
- the <literal>DEFAULT</literal> key word. The syntax
- with <literal>=</literal> is used in T-SQL and Firebird.
+ Simple <literal>LANGUAGE SQL</literal> functions can be written in a way
+ that is both standard-conforming and portable to other implementations.
+ More complex functions using advanced features, optimization attributes, or
+ other languages will necessarily be specific to PostgreSQL in a significant
+ way.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_procedure.sgml b/doc/src/sgml/ref/create_procedure.sgml
index e258eca5ce..ecdeac1629 100644
--- a/doc/src/sgml/ref/create_procedure.sgml
+++ b/doc/src/sgml/ref/create_procedure.sgml
@@ -29,6 +29,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -162,8 +163,9 @@ <title>Parameters</title>
The name of the language that the procedure is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -299,6 +301,41 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> procedure. This should
+ be a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the procedure body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at procedure definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at procedure definition time. This form tracks dependencies between the
+ procedure and objects used in the procedure body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling procedures. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
@@ -318,6 +355,7 @@ <title>Notes</title>
<refsect1 id="sql-createprocedure-examples">
<title>Examples</title>
+ <para>
<programlisting>
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
@@ -325,9 +363,21 @@ <title>Examples</title>
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
$$;
-
+</programlisting>
+ or
+<programlisting>
+CREATE PROCEDURE insert_data(a integer, b integer)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO tbl VALUES (a);
+ INSERT INTO tbl VALUES (b);
+END;
+</programlisting>
+ and call like this:
+<programlisting>
CALL insert_data(1, 2);
</programlisting>
+ </para>
</refsect1>
<refsect1 id="sql-createprocedure-compat">
@@ -335,9 +385,9 @@ <title>Compatibility</title>
<para>
A <command>CREATE PROCEDURE</command> command is defined in the SQL
- standard. The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. For details see
- also <xref linkend="sql-createfunction"/>.
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. For details see also
+ <xref linkend="sql-createfunction"/>.
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 89f23d0add..5197076c76 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -622,6 +622,7 @@ AggregateCreate(const char *aggName,
InvalidOid, /* no validator */
"aggregate_dummy", /* placeholder (no such proc) */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_AGGREGATE,
false, /* security invoker (currently not
* definable for agg) */
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index e14eee5a19..a53b5f408e 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -32,6 +32,7 @@
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_type.h"
#include "tcop/pquery.h"
@@ -76,6 +77,7 @@ ProcedureCreate(const char *procedureName,
Oid languageValidator,
const char *prosrc,
const char *probin,
+ Node *prosqlbody,
char prokind,
bool security_definer,
bool isLeakProof,
@@ -119,8 +121,6 @@ ProcedureCreate(const char *procedureName,
/*
* sanity checks
*/
- Assert(PointerIsValid(prosrc));
-
parameterCount = parameterTypes->dim1;
if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
ereport(ERROR,
@@ -334,11 +334,18 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_protrftypes - 1] = trftypes;
else
nulls[Anum_pg_proc_protrftypes - 1] = true;
- values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ if (prosrc)
+ values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ else
+ nulls[Anum_pg_proc_prosrc - 1] = true;
if (probin)
values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin);
else
nulls[Anum_pg_proc_probin - 1] = true;
+ if (prosqlbody)
+ values[Anum_pg_proc_prosqlbody - 1] = CStringGetTextDatum(nodeToString(prosqlbody));
+ else
+ nulls[Anum_pg_proc_prosqlbody - 1] = true;
if (proconfig != PointerGetDatum(NULL))
values[Anum_pg_proc_proconfig - 1] = proconfig;
else
@@ -638,6 +645,10 @@ ProcedureCreate(const char *procedureName,
record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
free_object_addresses(addrs);
+ /* dependency on SQL routine body */
+ if (languageObjectId == SQLlanguageId && prosqlbody)
+ recordDependencyOnExpr(&myself, prosqlbody, NIL, DEPENDENCY_NORMAL);
+
/* dependency on parameter default expressions */
if (parameterDefaults)
recordDependencyOnExpr(&myself, (Node *) parameterDefaults,
@@ -861,61 +872,81 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
/* Postpone body checks if !check_function_bodies */
if (check_function_bodies)
{
- tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
- if (isnull)
- elog(ERROR, "null prosrc");
-
- prosrc = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport().
*/
callback_arg.proname = NameStr(proc->proname);
- callback_arg.prosrc = prosrc;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_function_parse_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
- /*
- * We can't do full prechecking of the function definition if there
- * are any polymorphic input types, because actual datatypes of
- * expression results will be unresolvable. The check will be done at
- * runtime instead.
- *
- * We can run the text through the raw parser though; this will at
- * least catch silly syntactic errors.
- */
- raw_parsetree_list = pg_parse_query(prosrc);
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ {
+ Node *n;
- if (!haspolyarg)
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosqlbody, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc and prosqlbody");
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = castNode(List, n);
+ else
+ querytree_list = list_make1(list_make1(n));
+ }
+ else
{
+ prosrc = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = prosrc;
+
/*
- * OK to do full precheck: analyze and rewrite the queries, then
- * verify the result type.
+ * We can't do full prechecking of the function definition if there
+ * are any polymorphic input types, because actual datatypes of
+ * expression results will be unresolvable. The check will be done at
+ * runtime instead.
+ *
+ * We can run the text through the raw parser though; this will at
+ * least catch silly syntactic errors.
*/
- SQLFunctionParseInfoPtr pinfo;
- Oid rettype;
- TupleDesc rettupdesc;
+ raw_parsetree_list = pg_parse_query(prosrc);
- /* But first, set up parameter information */
- pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
-
- querytree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (!haspolyarg)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *querytree_sublist;
-
- querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
- prosrc,
- (ParserSetupHook) sql_fn_parser_setup,
- pinfo,
- NULL);
- querytree_list = lappend(querytree_list,
- querytree_sublist);
+ /*
+ * OK to do full precheck: analyze and rewrite the queries, then
+ * verify the result type.
+ */
+ SQLFunctionParseInfoPtr pinfo;
+
+ /* But first, set up parameter information */
+ pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
+
+ querytree_list = NIL;
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *querytree_sublist;
+
+ querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ prosrc,
+ (ParserSetupHook) sql_fn_parser_setup,
+ pinfo,
+ NULL);
+ querytree_list = lappend(querytree_list,
+ querytree_sublist);
+ }
}
+ }
+
+ if (!haspolyarg)
+ {
+ Oid rettype;
+ TupleDesc rettupdesc;
check_sql_fn_statements(querytree_list);
@@ -968,6 +999,9 @@ function_parse_error_transpose(const char *prosrc)
int newerrposition;
const char *queryText;
+ if (!prosrc)
+ return false;
+
/*
* Nothing to do unless we are dealing with a syntax error that has a
* cursor position.
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 69c50ac087..046cf2df08 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -312,9 +312,11 @@ DefineAggregate(ParseState *pstate,
InvalidOid,
OBJECT_AGGREGATE,
¶meterTypes,
+ NULL,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ NULL,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 7a4e104623..d11f6745be 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -53,15 +53,18 @@
#include "commands/proclang.h"
#include "executor/execdesc.h"
#include "executor/executor.h"
+#include "executor/functions.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_func.h"
#include "parser/parse_type.h"
#include "pgstat.h"
+#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
@@ -186,9 +189,11 @@ interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType)
@@ -283,7 +288,11 @@ interpret_function_parameter_list(ParseState *pstate,
/* handle input parameters */
if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
+ {
isinput = true;
+ if (parameterTypes_list)
+ *parameterTypes_list = lappend_oid(*parameterTypes_list, toid);
+ }
/* handle signature parameters */
if (fp->mode == FUNC_PARAM_IN || fp->mode == FUNC_PARAM_INOUT ||
@@ -372,6 +381,9 @@ interpret_function_parameter_list(ParseState *pstate,
have_names = true;
}
+ if (inParameterNames_list)
+ *inParameterNames_list = lappend(*inParameterNames_list, makeString(fp->name ? fp->name : pstrdup("")));
+
if (fp->defexpr)
{
Node *def;
@@ -786,28 +798,10 @@ compute_function_attributes(ParseState *pstate,
defel->defname);
}
- /* process required items */
if (as_item)
*as = (List *) as_item->arg;
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no function body specified")));
- *as = NIL; /* keep compiler quiet */
- }
-
if (language_item)
*language = strVal(language_item->arg);
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no language specified")));
- *language = NULL; /* keep compiler quiet */
- }
-
- /* process optional items */
if (transform_item)
*transform = transform_item->arg;
if (windowfunc_item)
@@ -856,10 +850,21 @@ compute_function_attributes(ParseState *pstate,
*/
static void
interpret_AS_clause(Oid languageOid, const char *languageName,
- char *funcname, List *as,
- char **prosrc_str_p, char **probin_str_p)
+ char *funcname, List *as, Node *sql_body_in,
+ List *parameterTypes, List *inParameterNames,
+ char **prosrc_str_p, char **probin_str_p, Node **sql_body_out)
{
- Assert(as != NIL);
+ if (sql_body_in && as)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("duplicate function body specified")));
+
+ if (sql_body_in && languageOid != SQLlanguageId)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("inline SQL function body only valid for language SQL")));
+
+ *sql_body_out = NULL;
if (languageOid == ClanguageId)
{
@@ -881,6 +886,76 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
*prosrc_str_p = funcname;
}
}
+ else if (sql_body_in)
+ {
+ SQLFunctionParseInfoPtr pinfo;
+
+ pinfo = (SQLFunctionParseInfoPtr) palloc0(sizeof(SQLFunctionParseInfo));
+
+ pinfo->fname = funcname;
+ pinfo->nargs = list_length(parameterTypes);
+ pinfo->argtypes = (Oid *) palloc(pinfo->nargs * sizeof(Oid));
+ pinfo->argnames = (char **) palloc(pinfo->nargs * sizeof(char *));
+ for (int i = 0; i < list_length(parameterTypes); i++)
+ {
+ char *s = strVal(list_nth(inParameterNames, i));
+
+ pinfo->argtypes[i] = list_nth_oid(parameterTypes, i);
+ if (IsPolymorphicType(pinfo->argtypes[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("SQL function with unquoted function body cannot have polymorphic arguments")));
+
+ if (s[0] != '\0')
+ pinfo->argnames[i] = s;
+ else
+ pinfo->argnames[i] = NULL;
+ }
+
+ if (IsA(sql_body_in, List))
+ {
+ List *stmts = linitial_node(List, castNode(List, sql_body_in));
+ ListCell *lc;
+ List *transformed_stmts = NIL;
+
+ foreach(lc, stmts)
+ {
+ Node *stmt = lfirst(lc);
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, stmt);
+ if (q->commandType == CMD_UTILITY)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("%s is not yet supported in unquoted SQL function body",
+ GetCommandTagName(CreateCommandTag(q->utilityStmt))));
+ transformed_stmts = lappend(transformed_stmts, q);
+ free_parsestate(pstate);
+ }
+
+ *sql_body_out = (Node *) list_make1(transformed_stmts);
+ }
+ else
+ {
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, sql_body_in);
+ if (q->commandType == CMD_UTILITY)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("%s is not yet supported in unquoted SQL function body",
+ GetCommandTagName(CreateCommandTag(q->utilityStmt))));
+
+ *sql_body_out = (Node *) q;
+ }
+
+ *probin_str_p = NULL;
+ *prosrc_str_p = NULL;
+ }
else
{
/* Everything else wants the given string in prosrc. */
@@ -919,6 +994,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
{
char *probin_str;
char *prosrc_str;
+ Node *prosqlbody;
Oid prorettype;
bool returnsSet;
char *language;
@@ -929,9 +1005,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
Oid namespaceId;
AclResult aclresult;
oidvector *parameterTypes;
+ List *parameterTypes_list = NIL;
ArrayType *allParameterTypes;
ArrayType *parameterModes;
ArrayType *parameterNames;
+ List *inParameterNames_list = NIL;
List *parameterDefaults;
Oid variadicArgType;
List *trftypes_list = NIL;
@@ -962,6 +1040,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
get_namespace_name(namespaceId));
/* Set default attributes */
+ as_clause = NULL;
+ language = "sql";
isWindowFunc = false;
isStrict = false;
security = false;
@@ -1053,9 +1133,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageOid,
stmt->is_procedure ? OBJECT_PROCEDURE : OBJECT_FUNCTION,
¶meterTypes,
+ ¶meterTypes_list,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ &inParameterNames_list,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
@@ -1112,8 +1194,9 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
trftypes = NULL;
}
- interpret_AS_clause(languageOid, language, funcname, as_clause,
- &prosrc_str, &probin_str);
+ interpret_AS_clause(languageOid, language, funcname, as_clause, stmt->sql_body,
+ parameterTypes_list, inParameterNames_list,
+ &prosrc_str, &probin_str, &prosqlbody);
/*
* Set default values for COST and ROWS depending on other parameters;
@@ -1155,6 +1238,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageValidator,
prosrc_str, /* converted to text later */
probin_str, /* converted to text later */
+ prosqlbody,
stmt->is_procedure ? PROKIND_PROCEDURE : (isWindowFunc ? PROKIND_WINDOW : PROKIND_FUNCTION),
security,
isLeakProof,
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 76218fb47e..e975508ffa 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1775,6 +1775,7 @@ makeRangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR, /* language validator */
prosrc[i], /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
@@ -1839,6 +1840,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR,
"multirange_constructor0", /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
@@ -1882,6 +1884,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR,
"multirange_constructor1", /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
@@ -1922,6 +1925,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR,
"multirange_constructor2", /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 7bb752ace3..642683843e 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -26,6 +26,7 @@
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_func.h"
+#include "rewrite/rewriteHandler.h"
#include "storage/proc.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
@@ -128,21 +129,6 @@ typedef struct
typedef SQLFunctionCache *SQLFunctionCachePtr;
-/*
- * Data structure needed by the parser callback hooks to resolve parameter
- * references during parsing of a SQL function's body. This is separate from
- * SQLFunctionCache since we sometimes do parsing separately from execution.
- */
-typedef struct SQLFunctionParseInfo
-{
- char *fname; /* function's name */
- int nargs; /* number of input arguments */
- Oid *argtypes; /* resolved types of input arguments */
- char **argnames; /* names of input arguments; NULL if none */
- /* Note that argnames[i] can be NULL, if some args are unnamed */
- Oid collation; /* function's input collation, if known */
-} SQLFunctionParseInfo;
-
/* non-export function prototypes */
static Node *sql_fn_param_ref(ParseState *pstate, ParamRef *pref);
@@ -607,7 +593,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
HeapTuple procedureTuple;
Form_pg_proc procedureStruct;
SQLFunctionCachePtr fcache;
- List *raw_parsetree_list;
List *queryTree_list;
List *resulttlist;
ListCell *lc;
@@ -682,9 +667,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
procedureTuple,
Anum_pg_proc_prosrc,
&isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", foid);
- fcache->src = TextDatumGetCString(tmp);
/*
* Parse and rewrite the queries in the function text. Use sublists to
@@ -695,20 +677,55 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
* but we'll not worry about it until the module is rewritten to use
* plancache.c.
*/
- raw_parsetree_list = pg_parse_query(fcache->src);
-
queryTree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (isNull)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *queryTree_sublist;
-
- queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
- fcache->src,
- (ParserSetupHook) sql_fn_parser_setup,
- fcache->pinfo,
- NULL);
- queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ Node *n;
+ List *stored_query_list;
+
+ tmp = SysCacheGetAttr(PROCOID,
+ procedureTuple,
+ Anum_pg_proc_prosqlbody,
+ &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", foid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ stored_query_list = linitial_node(List, castNode(List, n));
+ else
+ stored_query_list = list_make1(n);
+
+ foreach(lc, stored_query_list)
+ {
+ Query *parsetree = lfirst_node(Query, lc);
+ List *queryTree_sublist;
+
+ AcquireRewriteLocks(parsetree, true, false);
+ queryTree_sublist = pg_rewrite_query(parsetree);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ }
+ }
+ else
+ {
+ List *raw_parsetree_list;
+
+ fcache->src = TextDatumGetCString(tmp);
+
+ raw_parsetree_list = pg_parse_query(fcache->src);
+
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *queryTree_sublist;
+
+ queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ fcache->src,
+ (ParserSetupHook) sql_fn_parser_setup,
+ fcache->pinfo,
+ NULL);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ }
}
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index aaba1ec2c4..8d51b39f32 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3126,6 +3126,7 @@ _copyQuery(const Query *from)
COPY_SCALAR_FIELD(hasModifyingCTE);
COPY_SCALAR_FIELD(hasForUpdate);
COPY_SCALAR_FIELD(hasRowSecurity);
+ COPY_SCALAR_FIELD(isReturn);
COPY_NODE_FIELD(cteList);
COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(jointree);
@@ -3254,6 +3255,16 @@ _copySetOperationStmt(const SetOperationStmt *from)
return newnode;
}
+static ReturnStmt *
+_copyReturnStmt(const ReturnStmt *from)
+{
+ ReturnStmt *newnode = makeNode(ReturnStmt);
+
+ COPY_NODE_FIELD(returnval);
+
+ return newnode;
+}
+
static PLAssignStmt *
_copyPLAssignStmt(const PLAssignStmt *from)
{
@@ -3637,6 +3648,7 @@ _copyCreateFunctionStmt(const CreateFunctionStmt *from)
COPY_NODE_FIELD(parameters);
COPY_NODE_FIELD(returnType);
COPY_NODE_FIELD(options);
+ COPY_NODE_FIELD(sql_body);
return newnode;
}
@@ -5293,6 +5305,9 @@ copyObjectImpl(const void *from)
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
+ case T_ReturnStmt:
+ retval = _copyReturnStmt(from);
+ break;
case T_PLAssignStmt:
retval = _copyPLAssignStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c2d73626fc..4642fa24fe 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -969,6 +969,7 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_SCALAR_FIELD(hasModifyingCTE);
COMPARE_SCALAR_FIELD(hasForUpdate);
COMPARE_SCALAR_FIELD(hasRowSecurity);
+ COMPARE_SCALAR_FIELD(isReturn);
COMPARE_NODE_FIELD(cteList);
COMPARE_NODE_FIELD(rtable);
COMPARE_NODE_FIELD(jointree);
@@ -1085,6 +1086,14 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
return true;
}
+static bool
+_equalReturnStmt(const ReturnStmt *a, const ReturnStmt *b)
+{
+ COMPARE_NODE_FIELD(returnval);
+
+ return true;
+}
+
static bool
_equalPLAssignStmt(const PLAssignStmt *a, const PLAssignStmt *b)
{
@@ -1403,6 +1412,7 @@ _equalCreateFunctionStmt(const CreateFunctionStmt *a, const CreateFunctionStmt *
COMPARE_NODE_FIELD(parameters);
COMPARE_NODE_FIELD(returnType);
COMPARE_NODE_FIELD(options);
+ COMPARE_NODE_FIELD(sql_body);
return true;
}
@@ -3318,6 +3328,9 @@ equal(const void *a, const void *b)
case T_SetOperationStmt:
retval = _equalSetOperationStmt(a, b);
break;
+ case T_ReturnStmt:
+ retval = _equalReturnStmt(a, b);
+ break;
case T_PLAssignStmt:
retval = _equalPLAssignStmt(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8fc432bfe1..878185047f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2784,6 +2784,14 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
WRITE_NODE_FIELD(rarg);
}
+static void
+_outReturnStmt(StringInfo str, const ReturnStmt *node)
+{
+ WRITE_NODE_TYPE("RETURN");
+
+ WRITE_NODE_FIELD(returnval);
+}
+
static void
_outPLAssignStmt(StringInfo str, const PLAssignStmt *node)
{
@@ -2986,6 +2994,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_BOOL_FIELD(hasModifyingCTE);
WRITE_BOOL_FIELD(hasForUpdate);
WRITE_BOOL_FIELD(hasRowSecurity);
+ WRITE_BOOL_FIELD(isReturn);
WRITE_NODE_FIELD(cteList);
WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(jointree);
@@ -4265,6 +4274,9 @@ outNode(StringInfo str, const void *obj)
case T_SelectStmt:
_outSelectStmt(str, obj);
break;
+ case T_ReturnStmt:
+ _outReturnStmt(str, obj);
+ break;
case T_PLAssignStmt:
_outPLAssignStmt(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 718fb58e86..78173f09cc 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -263,6 +263,7 @@ _readQuery(void)
READ_BOOL_FIELD(hasModifyingCTE);
READ_BOOL_FIELD(hasForUpdate);
READ_BOOL_FIELD(hasRowSecurity);
+ READ_BOOL_FIELD(isReturn);
READ_NODE_FIELD(cteList);
READ_NODE_FIELD(rtable);
READ_NODE_FIELD(jointree);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index f3786dd2b6..5f238acbe4 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4156,27 +4156,47 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
- /* Fetch the function body */
- tmp = SysCacheGetAttr(PROCOID,
- func_tuple,
- Anum_pg_proc_prosrc,
- &isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", funcid);
- src = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = src;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ {
+ Node *n;
+ List *querytree_list;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", funcid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = linitial_node(List, castNode(List, n));
+ else
+ querytree_list = list_make1(n);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We need a
* dummy FuncExpr node containing the already-simplified arguments to pass
@@ -4220,6 +4240,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
querytree = transformTopLevelStmt(pstate, linitial(raw_parsetree_list));
free_parsestate(pstate);
+ }
/*
* The single command must be a simple "SELECT expression".
@@ -4476,12 +4497,15 @@ sql_inline_error_callback(void *arg)
int syntaxerrposition;
/* If it's a syntax error, convert to internal syntax error report */
- syntaxerrposition = geterrposition();
- if (syntaxerrposition > 0)
+ if (callback_arg->prosrc)
{
- errposition(0);
- internalerrposition(syntaxerrposition);
- internalerrquery(callback_arg->prosrc);
+ syntaxerrposition = geterrposition();
+ if (syntaxerrposition > 0)
+ {
+ errposition(0);
+ internalerrposition(syntaxerrposition);
+ internalerrquery(callback_arg->prosrc);
+ }
}
errcontext("SQL function \"%s\" during inlining", callback_arg->proname);
@@ -4593,7 +4617,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
Oid func_oid;
HeapTuple func_tuple;
Form_pg_proc funcform;
- char *src;
Datum tmp;
bool isNull;
MemoryContext oldcxt;
@@ -4702,27 +4725,53 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
- /* Fetch the function body */
- tmp = SysCacheGetAttr(PROCOID,
- func_tuple,
- Anum_pg_proc_prosrc,
- &isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", func_oid);
- src = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = src;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ {
+ Node *n;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", func_oid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = linitial_node(List, castNode(List, n));
+ else
+ querytree_list = list_make1(n);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+
+ querytree_list = pg_rewrite_query(querytree);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
+ char *src;
+
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We can
* use the FuncExpr just created as the input for
@@ -4732,18 +4781,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
(Node *) fexpr,
fexpr->inputcollid);
- /*
- * Also resolve the actual function result tupdesc, if composite. If the
- * function is just declared to return RECORD, dig the info out of the AS
- * clause.
- */
- functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
- if (functypclass == TYPEFUNC_RECORD)
- rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
- rtfunc->funccoltypes,
- rtfunc->funccoltypmods,
- rtfunc->funccolcollations);
-
/*
* Parse, analyze, and rewrite (unlike inline_function(), we can't skip
* rewriting here). We can fail as soon as we find more than one query,
@@ -4760,6 +4797,19 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
if (list_length(querytree_list) != 1)
goto fail;
querytree = linitial(querytree_list);
+ }
+
+ /*
+ * Also resolve the actual function result tupdesc, if composite. If the
+ * function is just declared to return RECORD, dig the info out of the AS
+ * clause.
+ */
+ functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
+ if (functypclass == TYPEFUNC_RECORD)
+ rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
+ rtfunc->funccoltypes,
+ rtfunc->funccoltypmods,
+ rtfunc->funccolcollations);
/*
* The single command must be a plain SELECT.
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0f3a70c49a..554db99df2 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -68,6 +68,7 @@ static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
bool isTopLevel, List **targetlist);
static void determineRecursiveColTypes(ParseState *pstate,
Node *larg, List *nrtargetlist);
+static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList);
static List *transformUpdateTargetList(ParseState *pstate,
@@ -308,6 +309,10 @@ transformStmt(ParseState *pstate, Node *parseTree)
}
break;
+ case T_ReturnStmt:
+ result = transformReturnStmt(pstate, (ReturnStmt *) parseTree);
+ break;
+
case T_PLAssignStmt:
result = transformPLAssignStmt(pstate,
(PLAssignStmt *) parseTree);
@@ -2227,6 +2232,36 @@ determineRecursiveColTypes(ParseState *pstate, Node *larg, List *nrtargetlist)
}
+/*
+ * transformReturnStmt -
+ * transforms a return statement
+ */
+static Query *
+transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+
+ qry->commandType = CMD_SELECT;
+ qry->isReturn = true;
+
+ qry->targetList = list_make1(makeTargetEntry((Expr *) transformExpr(pstate, stmt->returnval, EXPR_KIND_SELECT_TARGET),
+ 1, NULL, false));
+
+ if (pstate->p_resolve_unknowns)
+ resolveTargetListUnknowns(pstate, qry->targetList);
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasAggs = pstate->p_hasAggs;
+
+ assign_query_collations(pstate, qry);
+
+ return qry;
+}
+
+
/*
* transformUpdateStmt -
* transforms an update statement
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 652be0b96d..6700a49729 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -252,7 +252,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct SelectLimit *selectlimit;
}
-%type <node> stmt schema_stmt
+%type <node> stmt toplevel_stmt schema_stmt routine_body_stmt
AlterEventTrigStmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
@@ -279,9 +279,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
- RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
+ RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt
RuleActionStmt RuleActionStmtOrEmpty RuleStmt
- SecLabelStmt SelectStmt TransactionStmt TruncateStmt
+ SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
UnlistenStmt UpdateStmt VacuumStmt
VariableResetStmt VariableSetStmt VariableShowStmt
ViewStmt CheckPointStmt CreateConversionStmt
@@ -385,14 +385,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> vacuum_relation
%type <selectlimit> opt_select_limit select_limit limit_clause
-%type <list> parse_toplevel stmtmulti
+%type <list> parse_toplevel stmtmulti routine_body_stmt_list
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
reloptions opt_reloptions
OptWith opt_definition func_args func_args_list
func_args_with_defaults func_args_with_defaults_list
aggr_args aggr_args_list
- func_as createfunc_opt_list alterfunc_opt_list
+ func_as createfunc_opt_list opt_createfunc_opt_list alterfunc_opt_list
old_aggr_definition old_aggr_list
oper_argtypes RuleActionList RuleActionMulti
opt_column_list columnList opt_name_list
@@ -418,6 +418,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
vacuum_relation_list opt_vacuum_relation_list
drop_option_list
+%type <node> opt_routine_body
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
%type <node> grouping_sets_clause
@@ -623,7 +624,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATOMIC ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BREADTH BY
@@ -685,7 +686,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING
REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
- RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
+ RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
@@ -855,7 +856,7 @@ parse_toplevel:
* we'd get -1 for the location in such cases.
* We also take care to discard empty statements entirely.
*/
-stmtmulti: stmtmulti ';' stmt
+stmtmulti: stmtmulti ';' toplevel_stmt
{
if ($1 != NIL)
{
@@ -867,7 +868,7 @@ stmtmulti: stmtmulti ';' stmt
else
$$ = $1;
}
- | stmt
+ | toplevel_stmt
{
if ($1 != NULL)
$$ = list_make1(makeRawStmt($1, 0));
@@ -876,7 +877,16 @@ stmtmulti: stmtmulti ';' stmt
}
;
-stmt :
+/*
+ * toplevel_stmt includes BEGIN and END. stmt does not include them, because
+ * those words have different meanings in function bodys.
+ */
+toplevel_stmt:
+ stmt
+ | TransactionStmtLegacy
+ ;
+
+stmt:
AlterEventTrigStmt
| AlterDatabaseStmt
| AlterDatabaseSetStmt
@@ -7399,7 +7409,7 @@ opt_nulls_order: NULLS_LA FIRST_P { $$ = SORTBY_NULLS_FIRST; }
CreateFunctionStmt:
CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS func_return createfunc_opt_list
+ RETURNS func_return opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7408,10 +7418,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = $7;
n->options = $8;
+ n->sql_body = $9;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS TABLE '(' table_func_column_list ')' createfunc_opt_list
+ RETURNS TABLE '(' table_func_column_list ')' opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7421,10 +7432,11 @@ CreateFunctionStmt:
n->returnType = TableFuncTypeName($9);
n->returnType->location = @7;
n->options = $11;
+ n->sql_body = $12;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7433,10 +7445,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
| CREATE opt_or_replace PROCEDURE func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = true;
@@ -7445,6 +7458,7 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
;
@@ -7755,6 +7769,11 @@ aggregate_with_argtypes_list:
{ $$ = lappend($1, $3); }
;
+opt_createfunc_opt_list:
+ createfunc_opt_list
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
createfunc_opt_list:
/* Must be at least one to prevent conflict */
createfunc_opt_item { $$ = list_make1($1); }
@@ -7866,6 +7885,51 @@ func_as: Sconst { $$ = list_make1(makeString($1)); }
}
;
+ReturnStmt: RETURN a_expr
+ {
+ ReturnStmt *r = makeNode(ReturnStmt);
+ r->returnval = (Node *) $2;
+ $$ = (Node *) r;
+ }
+ ;
+
+opt_routine_body:
+ ReturnStmt
+ {
+ $$ = $1;
+ }
+ | BEGIN_P ATOMIC routine_body_stmt_list END_P
+ {
+ /*
+ * A compound statement is stored as a single-item list
+ * containing the list of statements as its member. That
+ * way, the parse analysis code can tell apart an empty
+ * body from no body at all.
+ */
+ $$ = (Node *) list_make1($3);
+ }
+ | /*EMPTY*/
+ {
+ $$ = NULL;
+ }
+ ;
+
+routine_body_stmt_list:
+ routine_body_stmt_list routine_body_stmt ';'
+ {
+ $$ = lappend($1, $2);
+ }
+ | /*EMPTY*/
+ {
+ $$ = NIL;
+ }
+ ;
+
+routine_body_stmt:
+ stmt
+ | ReturnStmt
+ ;
+
transform_type_list:
FOR TYPE_P Typename { $$ = list_make1($3); }
| transform_type_list ',' FOR TYPE_P Typename { $$ = lappend($1, $5); }
@@ -9799,13 +9863,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | BEGIN_P opt_transaction transaction_mode_list_or_empty
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_BEGIN;
- n->options = $3;
- $$ = (Node *)n;
- }
| START TRANSACTION transaction_mode_list_or_empty
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9821,14 +9878,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | END_P opt_transaction opt_transaction_chain
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_COMMIT;
- n->options = NIL;
- n->chain = $3;
- $$ = (Node *)n;
- }
| ROLLBACK opt_transaction opt_transaction_chain
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9895,6 +9944,24 @@ TransactionStmt:
}
;
+TransactionStmtLegacy:
+ BEGIN_P opt_transaction transaction_mode_list_or_empty
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_BEGIN;
+ n->options = $3;
+ $$ = (Node *)n;
+ }
+ | END_P opt_transaction opt_transaction_chain
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_COMMIT;
+ n->options = NIL;
+ n->chain = $3;
+ $$ = (Node *)n;
+ }
+ ;
+
opt_transaction: WORK
| TRANSACTION
| /*EMPTY*/
@@ -15272,6 +15339,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| BACKWARD
@@ -15472,6 +15540,7 @@ unreserved_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| ROLE
@@ -15778,6 +15847,7 @@ bare_label_keyword:
| ASSIGNMENT
| ASYMMETRIC
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| AUTHORIZATION
@@ -16050,6 +16120,7 @@ bare_label_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| RIGHT
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 8a0332dde9..b9ef515506 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -177,7 +177,6 @@ static int interactive_getc(void);
static int SocketBackend(StringInfo inBuf);
static int ReadCommand(StringInfo inBuf);
static void forbidden_in_wal_sender(char firstchar);
-static List *pg_rewrite_query(Query *query);
static bool check_log_statement(List *stmt_list);
static int errdetail_execute(List *raw_parsetree_list);
static int errdetail_params(ParamListInfo params);
@@ -695,7 +694,7 @@ pg_analyze_and_rewrite_params(RawStmt *parsetree,
* Note: query must just have come from the parser, because we do not do
* AcquireRewriteLocks() on it.
*/
-static List *
+List *
pg_rewrite_query(Query *query)
{
List *querytree_list;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 879288c139..28602b01fb 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -172,6 +172,10 @@ typedef struct
List *outer_tlist; /* referent for OUTER_VAR Vars */
List *inner_tlist; /* referent for INNER_VAR Vars */
List *index_tlist; /* referent for INDEX_VAR Vars */
+ /* Special namespace representing a function signature: */
+ char *funcname;
+ int numargs;
+ char **argnames;
} deparse_namespace;
/*
@@ -347,6 +351,7 @@ static int print_function_arguments(StringInfo buf, HeapTuple proctup,
bool print_table_args, bool print_defaults);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
static void print_function_trftypes(StringInfo buf, HeapTuple proctup);
+static void print_function_sqlbody(StringInfo buf, HeapTuple proctup);
static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used);
static void set_deparse_for_query(deparse_namespace *dpns, Query *query,
@@ -2799,6 +2804,13 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
}
/* And finally the function definition ... */
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
+ if (proc->prolang == SQLlanguageId && !isnull)
+ {
+ print_function_sqlbody(&buf, proctup);
+ }
+ else
+ {
appendStringInfoString(&buf, "AS ");
tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
@@ -2830,6 +2842,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, dq.data, dq.len);
appendStringInfoString(&buf, prosrc);
appendBinaryStringInfo(&buf, dq.data, dq.len);
+ }
appendStringInfoChar(&buf, '\n');
@@ -3213,6 +3226,76 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(string_to_text(str));
}
+static void
+print_function_sqlbody(StringInfo buf, HeapTuple proctup)
+{
+ int numargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ deparse_namespace dpns = {0};
+ Datum tmp;
+ bool isnull;
+ Node *n;
+
+ dpns.funcname = pstrdup(NameStr(((Form_pg_proc) GETSTRUCT(proctup))->proname));
+ numargs = get_func_arg_info(proctup,
+ &argtypes, &argnames, &argmodes);
+ dpns.numargs = numargs;
+ dpns.argnames = argnames;
+
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
+ Assert(!isnull);
+ n = stringToNode(TextDatumGetCString(tmp));
+
+ if (IsA(n, List))
+ {
+ List *stmts;
+ ListCell *lc;
+
+ stmts = linitial(castNode(List, n));
+
+ appendStringInfoString(buf, "BEGIN ATOMIC\n");
+
+ foreach(lc, stmts)
+ {
+ Query *query = lfirst_node(Query, lc);
+
+ get_query_def(query, buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ appendStringInfoChar(buf, ';');
+ appendStringInfoChar(buf, '\n');
+ }
+
+ appendStringInfoString(buf, "END");
+ }
+ else
+ {
+ get_query_def(castNode(Query, n), buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ appendStringInfoChar(buf, '\n');
+ }
+}
+
+Datum
+pg_get_function_sqlbody(PG_FUNCTION_ARGS)
+{
+ Oid funcid = PG_GETARG_OID(0);
+ StringInfoData buf;
+ HeapTuple proctup;
+
+ initStringInfo(&buf);
+
+ /* Look up the function */
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ PG_RETURN_NULL();
+
+ print_function_sqlbody(&buf, proctup);
+
+ ReleaseSysCache(proctup);
+
+ PG_RETURN_TEXT_P(cstring_to_text(buf.data));
+}
+
/*
* deparse_expression - General utility for deparsing expressions
@@ -5472,7 +5555,10 @@ get_basic_select_query(Query *query, deparse_context *context,
/*
* Build up the query string - first we say SELECT
*/
- appendStringInfoString(buf, "SELECT");
+ if (query->isReturn)
+ appendStringInfoString(buf, "RETURN");
+ else
+ appendStringInfoString(buf, "SELECT");
/* Add the DISTINCT clause if given */
if (query->distinctClause != NIL)
@@ -7604,6 +7690,50 @@ get_parameter(Param *param, deparse_context *context)
return;
}
+ /*
+ * If it's an external parameter, see if the outermost namespace provides
+ * function argument names.
+ */
+ if (param->paramkind == PARAM_EXTERN)
+ {
+ dpns = lfirst(list_tail(context->namespaces));
+ if (dpns->argnames)
+ {
+ char *argname = dpns->argnames[param->paramid - 1];
+
+ if (argname)
+ {
+ bool should_qualify = false;
+ ListCell *lc;
+
+ /*
+ * Qualify the parameter name if there are any other deparse
+ * namespaces with range tables. This avoids qualifying in
+ * trivial cases like "RETURN a + b", but makes it safe in all
+ * other cases.
+ */
+ foreach (lc, context->namespaces)
+ {
+ deparse_namespace *dpns = lfirst(lc);
+
+ if (list_length(dpns->rtable_names) > 0)
+ {
+ should_qualify = true;
+ break;
+ }
+ }
+ if (should_qualify)
+ {
+ appendStringInfoString(context->buf, quote_identifier(dpns->funcname));
+ appendStringInfoChar(context->buf, '.');
+ }
+
+ appendStringInfoString(context->buf, quote_identifier(argname));
+ return;
+ }
+ }
+ }
+
/*
* Not PARAM_EXEC, or couldn't find referent: just print $N.
*/
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index eb988d7eb4..98a74d1efb 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11979,6 +11979,7 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
char *proretset;
char *prosrc;
char *probin;
+ char *prosqlbody;
char *funcargs;
char *funciargs;
char *funcresult;
@@ -12025,7 +12026,7 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
"provolatile,\n"
"proisstrict,\n"
"prosecdef,\n"
- "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname,\n");
+ "lanname,\n");
if (fout->remoteVersion >= 80300)
appendPQExpBufferStr(query,
@@ -12045,9 +12046,9 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
* pg_get_function_result instead of examining proallargtypes etc.
*/
appendPQExpBufferStr(query,
- "pg_catalog.pg_get_function_arguments(oid) AS funcargs,\n"
- "pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs,\n"
- "pg_catalog.pg_get_function_result(oid) AS funcresult,\n");
+ "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n"
+ "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n"
+ "pg_catalog.pg_get_function_result(p.oid) AS funcresult,\n");
}
else if (fout->remoteVersion >= 80100)
appendPQExpBufferStr(query,
@@ -12090,21 +12091,39 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
if (fout->remoteVersion >= 120000)
appendPQExpBufferStr(query,
- "prosupport\n");
+ "prosupport,\n");
else
appendPQExpBufferStr(query,
- "'-' AS prosupport\n");
+ "'-' AS prosupport,\n");
+
+ if (fout->remoteVersion >= 140000)
+ appendPQExpBufferStr(query,
+ "CASE WHEN prosrc IS NULL AND lanname = 'sql' THEN pg_get_function_sqlbody(p.oid) END AS prosqlbody\n");
+ else
+ appendPQExpBufferStr(query,
+ "NULL AS prosqlbody\n");
appendPQExpBuffer(query,
- "FROM pg_catalog.pg_proc "
- "WHERE oid = '%u'::pg_catalog.oid",
+ "FROM pg_catalog.pg_proc p, pg_catalog.pg_language l\n"
+ "WHERE p.oid = '%u'::pg_catalog.oid "
+ "AND l.oid = p.prolang",
finfo->dobj.catId.oid);
res = ExecuteSqlQueryForSingleRow(fout, query->data);
proretset = PQgetvalue(res, 0, PQfnumber(res, "proretset"));
- prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
- probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ if (PQgetisnull(res, 0, PQfnumber(res, "prosqlbody")))
+ {
+ prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
+ probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ prosqlbody = NULL;
+ }
+ else
+ {
+ prosrc = NULL;
+ probin = NULL;
+ prosqlbody = PQgetvalue(res, 0, PQfnumber(res, "prosqlbody"));
+ }
if (fout->remoteVersion >= 80400)
{
funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
@@ -12141,7 +12160,11 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
* versions would set it to "-". There are no known cases in which prosrc
* is unused, so the tests below for "-" are probably useless.
*/
- if (probin[0] != '\0' && strcmp(probin, "-") != 0)
+ if (prosqlbody)
+ {
+ appendPQExpBufferStr(asPart, prosqlbody);
+ }
+ else if (probin[0] != '\0' && strcmp(probin, "-") != 0)
{
appendPQExpBufferStr(asPart, "AS ");
appendStringLiteralAH(asPart, probin, fout);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 20af5a92b4..a47c78d4bc 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -505,11 +505,18 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
appendPQExpBufferStr(&buf, ",\n ");
printACLColumn(&buf, "p.proacl");
appendPQExpBuffer(&buf,
- ",\n l.lanname as \"%s\""
- ",\n p.prosrc as \"%s\""
+ ",\n l.lanname as \"%s\"",
+ gettext_noop("Language"));
+ if (pset.sversion >= 140000)
+ appendPQExpBuffer(&buf,
+ ",\n COALESCE(p.prosrc, pg_catalog.pg_get_function_sqlbody(p.oid)) as \"%s\"",
+ gettext_noop("Source code"));
+ else
+ appendPQExpBuffer(&buf,
+ ",\n p.prosrc as \"%s\"",
+ gettext_noop("Source code"));
+ appendPQExpBuffer(&buf,
",\n pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"",
- gettext_noop("Language"),
- gettext_noop("Source code"),
gettext_noop("Description"));
}
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 216ff30993..a492a32416 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -645,10 +645,11 @@ other .
";" {
ECHO;
- if (cur_state->paren_depth == 0)
+ if (cur_state->paren_depth == 0 && cur_state->begin_depth == 0)
{
/* Terminate lexing temporarily */
cur_state->start_state = YY_START;
+ cur_state->identifier_count = 0;
return LEXRES_SEMI;
}
}
@@ -661,6 +662,8 @@ other .
"\\"[;:] {
/* Force a semi-colon or colon into the query buffer */
psqlscan_emit(cur_state, yytext + 1, 1);
+ if (yytext[1] == ';')
+ cur_state->identifier_count = 0;
}
"\\" {
@@ -867,6 +870,17 @@ other .
{identifier} {
+ cur_state->identifier_count++;
+ if (pg_strcasecmp(yytext, "begin") == 0)
+ {
+ if (cur_state->identifier_count > 1)
+ cur_state->begin_depth++;
+ }
+ else if (pg_strcasecmp(yytext, "end") == 0)
+ {
+ if (cur_state->begin_depth > 0)
+ cur_state->begin_depth--;
+ }
ECHO;
}
@@ -1054,6 +1068,11 @@ psql_scan(PsqlScanState state,
result = PSCAN_INCOMPLETE;
*prompt = PROMPT_PAREN;
}
+ if (state->begin_depth > 0)
+ {
+ result = PSCAN_INCOMPLETE;
+ *prompt = PROMPT_CONTINUE;
+ }
else if (query_buf->len > 0)
{
result = PSCAN_EOL;
@@ -1170,6 +1189,8 @@ psql_scan_reset(PsqlScanState state)
if (state->dolqstart)
free(state->dolqstart);
state->dolqstart = NULL;
+ state->identifier_count = 0;
+ state->begin_depth = 0;
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index c7619f8cd3..0b0c5818da 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3692,6 +3692,10 @@
proname => 'pg_get_function_arg_default', provolatile => 's',
prorettype => 'text', proargtypes => 'oid int4',
prosrc => 'pg_get_function_arg_default' },
+{ oid => '9704', descr => 'function SQL body',
+ proname => 'pg_get_function_sqlbody', provolatile => 's',
+ prorettype => 'text', proargtypes => 'oid',
+ prosrc => 'pg_get_function_sqlbody' },
{ oid => '1686', descr => 'list of SQL keywords',
proname => 'pg_get_keywords', procost => '10', prorows => '500',
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 78f230894b..8d58067d03 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -112,11 +112,14 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
Oid protrftypes[1] BKI_DEFAULT(_null_) BKI_LOOKUP(pg_type);
/* procedure source text */
- text prosrc BKI_FORCE_NOT_NULL;
+ text prosrc;
/* secondary procedure info (can be NULL) */
text probin BKI_DEFAULT(_null_);
+ /* pre-parsed SQL function body */
+ pg_node_tree prosqlbody BKI_DEFAULT(_null_);
+
/* procedure-local GUC settings */
text proconfig[1] BKI_DEFAULT(_null_);
@@ -194,6 +197,7 @@ extern ObjectAddress ProcedureCreate(const char *procedureName,
Oid languageValidator,
const char *prosrc,
const char *probin,
+ Node *prosqlbody,
char prokind,
bool security_definer,
bool isLeakProof,
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 1a79540c94..2bb0fb10de 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -65,9 +65,11 @@ extern void interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType);
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index c975a93661..dcb8e18437 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -20,6 +20,21 @@
/* This struct is known only within executor/functions.c */
typedef struct SQLFunctionParseInfo *SQLFunctionParseInfoPtr;
+/*
+ * Data structure needed by the parser callback hooks to resolve parameter
+ * references during parsing of a SQL function's body. This is separate from
+ * SQLFunctionCache since we sometimes do parsing separately from execution.
+ */
+typedef struct SQLFunctionParseInfo
+{
+ char *fname; /* function's name */
+ int nargs; /* number of input arguments */
+ Oid *argtypes; /* resolved types of input arguments */
+ char **argnames; /* names of input arguments; NULL if none */
+ /* Note that argnames[i] can be NULL, if some args are unnamed */
+ Oid collation; /* function's input collation, if known */
+} SQLFunctionParseInfo;
+
extern Datum fmgr_sql(PG_FUNCTION_ARGS);
extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTuple,
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 2bc3cc4ae7..91d7d4d5c6 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -114,6 +114,8 @@ typedef struct PsqlScanStateData
int paren_depth; /* depth of nesting in parentheses */
int xcdepth; /* depth of nesting in slash-star comments */
char *dolqstart; /* current $foo$ quote start string */
+ int identifier_count; /* identifiers since start of statement */
+ int begin_depth; /* depth of begin/end routine body blocks */
/*
* Callback functions provided by the program making use of the lexer,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..dcca0e456b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -317,6 +317,7 @@ typedef enum NodeTag
T_DeleteStmt,
T_UpdateStmt,
T_SelectStmt,
+ T_ReturnStmt,
T_PLAssignStmt,
T_AlterTableStmt,
T_AlterTableCmd,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 236832a2ca..97908b597e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -132,6 +132,8 @@ typedef struct Query
bool hasForUpdate; /* FOR [KEY] UPDATE/SHARE was specified */
bool hasRowSecurity; /* rewriter has applied some RLS policy */
+ bool isReturn; /* is a RETURN statement */
+
List *cteList; /* WITH list (of CommonTableExpr's) */
List *rtable; /* list of range table entries */
@@ -1701,6 +1703,16 @@ typedef struct SetOperationStmt
} SetOperationStmt;
+/*
+ * RETURN statement (inside SQL function body)
+ */
+typedef struct ReturnStmt
+{
+ NodeTag type;
+ Node *returnval;
+} ReturnStmt;
+
+
/* ----------------------
* PL/pgSQL Assignment Statement
*
@@ -2884,6 +2896,7 @@ typedef struct CreateFunctionStmt
List *parameters; /* a list of FunctionParameter */
TypeName *returnType; /* the return type */
List *options; /* a list of DefElem */
+ Node *sql_body;
} CreateFunctionStmt;
typedef enum FunctionParameterMode
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 28083aaac9..ec467b5c25 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -48,6 +48,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("atomic", ATOMIC, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -346,6 +347,7 @@ PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("return", RETURN, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD, AS_LABEL)
PG_KEYWORD("returns", RETURNS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index e5472100a4..49700d151a 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -43,6 +43,7 @@ typedef enum
extern PGDLLIMPORT int log_statement;
extern List *pg_parse_query(const char *query_string);
+extern List *pg_rewrite_query(Query *query);
extern List *pg_analyze_and_rewrite(RawStmt *parsetree,
const char *query_string,
Oid *paramTypes, int numParams,
diff --git a/src/interfaces/ecpg/preproc/ecpg.addons b/src/interfaces/ecpg/preproc/ecpg.addons
index 300381eaad..0441561c52 100644
--- a/src/interfaces/ecpg/preproc/ecpg.addons
+++ b/src/interfaces/ecpg/preproc/ecpg.addons
@@ -87,6 +87,12 @@ ECPG: stmtTransactionStmt block
whenever_action(2);
free($1);
}
+ECPG: toplevel_stmtTransactionStmtLegacy block
+ {
+ fprintf(base_yyout, "{ ECPGtrans(__LINE__, %s, \"%s\");", connection ? connection : "NULL", $1);
+ whenever_action(2);
+ free($1);
+ }
ECPG: stmtViewStmt rule
| ECPGAllocateDescr
{
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer
index 0e4a041393..bcabf88341 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -4,8 +4,8 @@ statements: /*EMPTY*/
| statements statement
;
-statement: ecpgstart at stmt ';' { connection = NULL; }
- | ecpgstart stmt ';'
+statement: ecpgstart at toplevel_stmt ';' { connection = NULL; }
+ | ecpgstart toplevel_stmt ';'
| ecpgstart ECPGVarDeclaration
{
fprintf(base_yyout, "%s", $2);
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index f25a407fdd..a9b4b40e1a 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -255,6 +255,151 @@ SELECT pg_get_functiondef('functest_F_2'::regproc);
(1 row)
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_3a() RETURNS boolean
+ BEGIN ATOMIC
+ RETURN false;
+ END;
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+ERROR: SQL function with unquoted function body cannot have polymorphic arguments
+SELECT functest_S_1('abcd', '2020-01-01');
+ functest_s_1
+--------------
+ t
+(1 row)
+
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+ functest_s_2
+--------------
+ 1
+(1 row)
+
+SELECT functest_S_3();
+ functest_s_3
+--------------
+ f
+(1 row)
+
+SELECT functest_S_10('abcd', '2020-01-01');
+ functest_s_10
+---------------
+ t
+(1 row)
+
+SELECT functest_S_13();
+ functest_s_13
+---------------
+ f
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_1(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN ((a = 'abcd'::text) AND (b > '01-01-2001'::date)) +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_2(a text[])+
+ RETURNS integer +
+ LANGUAGE sql +
+ RETURN ((a)[1])::integer +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+ pg_get_functiondef
+----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN false +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3a'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3a()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ RETURN false; +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+ pg_get_functiondef
+-------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_10(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT ((a = 'abcd'::text) AND (b > '01-01-2001'::date)); +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_13()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT 1; +
+ SELECT false AS bool; +
+ END +
+
+(1 row)
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+SELECT functest_S_14();
+ functest_s_14
+---------------
+ 2
+(1 row)
+
+DROP TABLE functest3 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view functestv3
+drop cascades to function functest_s_14()
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
RETURNS int
@@ -292,6 +437,15 @@ CREATE FUNCTION functest_IS_5(x int DEFAULT nextval('functest1'))
RETURNS int
LANGUAGE SQL
AS 'SELECT x';
+CREATE FUNCTION functest_IS_6()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest1');
+CREATE TABLE functest2 (a int, b int);
+CREATE FUNCTION functest_IS_7()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest2);
SELECT r0.routine_name, r1.routine_name
FROM information_schema.routine_routine_usage rru
JOIN information_schema.routines r0 ON r0.specific_name = rru.specific_name
@@ -305,23 +459,29 @@ SELECT routine_name, sequence_name FROM information_schema.routine_sequence_usag
routine_name | sequence_name
---------------+---------------
functest_is_5 | functest1
-(1 row)
+ functest_is_6 | functest1
+(2 rows)
--- currently empty
SELECT routine_name, table_name, column_name FROM information_schema.routine_column_usage;
- routine_name | table_name | column_name
---------------+------------+-------------
-(0 rows)
+ routine_name | table_name | column_name
+---------------+------------+-------------
+ functest_is_7 | functest2 | a
+(1 row)
SELECT routine_name, table_name FROM information_schema.routine_table_usage;
- routine_name | table_name
---------------+------------
-(0 rows)
+ routine_name | table_name
+---------------+------------
+ functest_is_7 | functest2
+(1 row)
DROP FUNCTION functest_IS_4a CASCADE;
NOTICE: drop cascades to function functest_is_4b(integer)
DROP SEQUENCE functest1 CASCADE;
-NOTICE: drop cascades to function functest_is_5(integer)
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to function functest_is_5(integer)
+drop cascades to function functest_is_6()
+DROP TABLE functest2 CASCADE;
+NOTICE: drop cascades to function functest_is_7()
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
@@ -340,6 +500,49 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
ERROR: cannot change routine kind
DETAIL: "functest1" is a function.
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+SELECT * FROM functest_sri1();
+ functest_sri1
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+SELECT * FROM functest_sri2();
+ functest_sri2
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
$$ SELECT a + 1 $$;
@@ -398,7 +601,7 @@ SELECT * FROM voidtest5(3);
-- Cleanup
DROP SCHEMA temp_func_test CASCADE;
-NOTICE: drop cascades to 21 other objects
+NOTICE: drop cascades to 29 other objects
DETAIL: drop cascades to function functest_a_1(text,date)
drop cascades to function functest_a_2(text[])
drop cascades to function functest_a_3()
@@ -414,7 +617,15 @@ drop cascades to function functest_f_1(integer)
drop cascades to function functest_f_2(integer)
drop cascades to function functest_f_3(integer)
drop cascades to function functest_f_4(integer)
+drop cascades to function functest_s_1(text,date)
+drop cascades to function functest_s_2(text[])
+drop cascades to function functest_s_3()
+drop cascades to function functest_s_3a()
+drop cascades to function functest_s_10(text,date)
+drop cascades to function functest_s_13()
drop cascades to function functest_b_2(bigint)
+drop cascades to function functest_sri1()
+drop cascades to function functest_sri2()
drop cascades to function voidtest1(integer)
drop cascades to function voidtest2(integer,integer)
drop cascades to function voidtest3(integer)
diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out
index 3838fa2324..d45575561e 100644
--- a/src/test/regress/expected/create_procedure.out
+++ b/src/test/regress/expected/create_procedure.out
@@ -65,6 +65,48 @@ SELECT * FROM cp_test ORDER BY b COLLATE "C";
1 | xyzzy
(3 rows)
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+\df ptest1s
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+---------+------------------+---------------------+------
+ public | ptest1s | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest1s'::regproc);
+ pg_get_functiondef
+----------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest1s(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ INSERT INTO cp_test (a, b) +
+ VALUES (1, ptest1s.x); +
+ END +
+
+(1 row)
+
+CALL ptest1s('b');
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+ a | b
+---+-------
+ 1 | 0
+ 1 | a
+ 1 | b
+ 1 | xyzzy
+(4 rows)
+
+-- utitlity functions currently not supported here
+CREATE PROCEDURE ptestx()
+LANGUAGE SQL
+BEGIN ATOMIC
+ CREATE TABLE x (a int);
+END;
+ERROR: CREATE TABLE is not yet supported in unquoted SQL function body
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -146,6 +188,28 @@ AS $$
SELECT a = b;
$$;
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+\df ptest8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+--------+------------------+---------------------+------
+ public | ptest8 | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest8'::regproc);
+ pg_get_functiondef
+---------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest8(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ END +
+
+(1 row)
+
+CALL ptest8('');
-- OUT parameters
CREATE PROCEDURE ptest9(OUT a int)
LANGUAGE SQL
@@ -214,6 +278,7 @@ ALTER ROUTINE ptest1a RENAME TO ptest1;
DROP ROUTINE cp_testfunc1(int);
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
DROP USER regress_cp_user1;
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
index 549b34b4b2..2f3143be09 100644
--- a/src/test/regress/sql/create_function_3.sql
+++ b/src/test/regress/sql/create_function_3.sql
@@ -153,6 +153,65 @@ CREATE FUNCTION functest_F_4(int) RETURNS bool LANGUAGE 'sql'
SELECT pg_get_functiondef('functest_F_2'::regproc);
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_3a() RETURNS boolean
+ BEGIN ATOMIC
+ RETURN false;
+ END;
+
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+
+SELECT functest_S_1('abcd', '2020-01-01');
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+SELECT functest_S_3();
+
+SELECT functest_S_10('abcd', '2020-01-01');
+SELECT functest_S_13();
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+SELECT pg_get_functiondef('functest_S_3a'::regproc);
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+
+SELECT functest_S_14();
+
+DROP TABLE functest3 CASCADE;
+
+
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
@@ -188,17 +247,29 @@ CREATE FUNCTION functest_IS_5(x int DEFAULT nextval('functest1'))
LANGUAGE SQL
AS 'SELECT x';
+CREATE FUNCTION functest_IS_6()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest1');
+
+CREATE TABLE functest2 (a int, b int);
+
+CREATE FUNCTION functest_IS_7()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest2);
+
SELECT r0.routine_name, r1.routine_name
FROM information_schema.routine_routine_usage rru
JOIN information_schema.routines r0 ON r0.specific_name = rru.specific_name
JOIN information_schema.routines r1 ON r1.specific_name = rru.routine_name;
SELECT routine_name, sequence_name FROM information_schema.routine_sequence_usage;
--- currently empty
SELECT routine_name, table_name, column_name FROM information_schema.routine_column_usage;
SELECT routine_name, table_name FROM information_schema.routine_table_usage;
DROP FUNCTION functest_IS_4a CASCADE;
DROP SEQUENCE functest1 CASCADE;
+DROP TABLE functest2 CASCADE;
-- overload
@@ -218,6 +289,29 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+
+SELECT * FROM functest_sri1();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+
+SELECT * FROM functest_sri2();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
diff --git a/src/test/regress/sql/create_procedure.sql b/src/test/regress/sql/create_procedure.sql
index 2ef1c82cea..76f781c0b9 100644
--- a/src/test/regress/sql/create_procedure.sql
+++ b/src/test/regress/sql/create_procedure.sql
@@ -28,6 +28,28 @@ CREATE PROCEDURE ptest1(x text)
SELECT * FROM cp_test ORDER BY b COLLATE "C";
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+
+\df ptest1s
+SELECT pg_get_functiondef('ptest1s'::regproc);
+
+CALL ptest1s('b');
+
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+
+-- utitlity functions currently not supported here
+CREATE PROCEDURE ptestx()
+LANGUAGE SQL
+BEGIN ATOMIC
+ CREATE TABLE x (a int);
+END;
+
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -112,6 +134,16 @@ CREATE PROCEDURE ptest7(a text, b text)
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+
+\df ptest8
+SELECT pg_get_functiondef('ptest8'::regproc);
+CALL ptest8('');
+
+
-- OUT parameters
CREATE PROCEDURE ptest9(OUT a int)
@@ -170,6 +202,7 @@ CREATE USER regress_cp_user1;
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
base-commit: ff99918c625a84c91e7391db9032112ec8653623
--
2.30.1
On Tue, Mar 9, 2021 at 7:27 AM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:
I see. The problem is that we don't have serialization and
deserialization support for most utility statements. I think I'll need
to add that eventually. For now, I have added code to prevent utility
statements. I think it's still useful that way for now.
Great! thanks!
I found another problem when using CASE expressions:
CREATE OR REPLACE FUNCTION foo_case()
RETURNS boolean
LANGUAGE SQL
BEGIN ATOMIC
select case when random() > 0.5 then true else false end;
END;
apparently the END in the CASE expression is interpreted as the END of
the function
--
Jaime Casanova
Director de Servicios Profesionales
SYSTEMGUARDS - Consultores de PostgreSQL
On Mon, Mar 15, 2021 at 01:05:11AM -0500, Jaime Casanova wrote:
I found another problem when using CASE expressions:
CREATE OR REPLACE FUNCTION foo_case()
RETURNS boolean
LANGUAGE SQL
BEGIN ATOMIC
select case when random() > 0.5 then true else false end;
END;apparently the END in the CASE expression is interpreted as the END of
the function
I think that it's an issue in psql scanner. If you escape the semicolon or
force a single query execution (say with psql -c), it works as expected.
On Mon, Mar 15, 2021 at 04:03:44PM +0800, Julien Rouhaud wrote:
On Mon, Mar 15, 2021 at 01:05:11AM -0500, Jaime Casanova wrote:
I found another problem when using CASE expressions:
CREATE OR REPLACE FUNCTION foo_case()
RETURNS boolean
LANGUAGE SQL
BEGIN ATOMIC
select case when random() > 0.5 then true else false end;
END;apparently the END in the CASE expression is interpreted as the END of
the functionI think that it's an issue in psql scanner. If you escape the semicolon or
force a single query execution (say with psql -c), it works as expected.
Applying the following diff (not sending a patch to avoid breaking the cfbot)
the issue and doesn't seem to break anything else:
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index a492a32416..58026fe90a 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -871,7 +871,9 @@ other .
{identifier} {
cur_state->identifier_count++;
- if (pg_strcasecmp(yytext, "begin") == 0)
+ if ((pg_strcasecmp(yytext, "begin") == 0)
+ || (pg_strcasecmp(yytext, "case") == 0)
+ )
{
if (cur_state->identifier_count > 1)
cur_state->begin_depth++;
On 15.03.21 09:14, Julien Rouhaud wrote:
On Mon, Mar 15, 2021 at 04:03:44PM +0800, Julien Rouhaud wrote:
On Mon, Mar 15, 2021 at 01:05:11AM -0500, Jaime Casanova wrote:
I found another problem when using CASE expressions:
CREATE OR REPLACE FUNCTION foo_case()
RETURNS boolean
LANGUAGE SQL
BEGIN ATOMIC
select case when random() > 0.5 then true else false end;
END;apparently the END in the CASE expression is interpreted as the END of
the functionI think that it's an issue in psql scanner. If you escape the semicolon or
force a single query execution (say with psql -c), it works as expected.Applying the following diff (not sending a patch to avoid breaking the cfbot)
the issue and doesn't seem to break anything else:
Right. Here is a new patch with that fix added and a small conflict
resolved.
Attachments:
v10-0001-SQL-standard-function-body.patchtext/plain; charset=UTF-8; name=v10-0001-SQL-standard-function-body.patch; x-mac-creator=0; x-mac-type=0Download
From 8559affa4592cc6c0d68bb74326b88b60ab9740b Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Fri, 19 Mar 2021 14:48:04 +0100
Subject: [PATCH v10] SQL-standard function body
This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.
Instead of the PostgreSQL-specific AS $$ string literal $$ syntax,
this allows writing out the SQL statements making up the body
unquoted, either as a single statement:
CREATE FUNCTION add(a integer, b integer) RETURNS integer
LANGUAGE SQL
RETURN a + b;
or as a block
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
BEGIN ATOMIC
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
END;
The function body is parsed at function definition time and stored as
expression nodes in a new pg_proc column prosqlbody. So at run time,
no further parsing is required.
However, this form does not support polymorphic arguments, because
there is no more parse analysis done at call time.
Dependencies between the function and the objects it uses are fully
tracked.
A new RETURN statement is introduced. This can only be used inside
function bodies. Internally, it is treated much like a SELECT
statement.
psql needs some new intelligence to keep track of function body
boundaries so that it doesn't send off statements when it sees
semicolons that are inside a function body.
Also, per SQL standard, LANGUAGE SQL is the default, so it does not
need to be specified anymore.
Discussion: https://www.postgresql.org/message-id/flat/1c11f1eb-f00c-43b7-799d-2d44132c02d7@2ndquadrant.com
---
doc/src/sgml/catalogs.sgml | 10 +
doc/src/sgml/ref/create_function.sgml | 126 ++++++++-
doc/src/sgml/ref/create_procedure.sgml | 62 ++++-
src/backend/catalog/pg_aggregate.c | 1 +
src/backend/catalog/pg_proc.c | 116 +++++---
src/backend/commands/aggregatecmds.c | 2 +
src/backend/commands/functioncmds.c | 130 +++++++--
src/backend/commands/typecmds.c | 4 +
src/backend/executor/functions.c | 79 +++---
src/backend/nodes/copyfuncs.c | 15 ++
src/backend/nodes/equalfuncs.c | 13 +
src/backend/nodes/outfuncs.c | 12 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/util/clauses.c | 126 ++++++---
src/backend/parser/analyze.c | 35 +++
src/backend/parser/gram.y | 129 +++++++--
src/backend/tcop/postgres.c | 3 +-
src/backend/utils/adt/ruleutils.c | 132 ++++++++-
src/bin/pg_dump/pg_dump.c | 45 +++-
src/bin/psql/describe.c | 15 +-
src/fe_utils/psqlscan.l | 24 +-
src/include/catalog/pg_proc.dat | 4 +
src/include/catalog/pg_proc.h | 6 +-
src/include/commands/defrem.h | 2 +
src/include/executor/functions.h | 15 ++
src/include/fe_utils/psqlscan_int.h | 2 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 13 +
src/include/parser/kwlist.h | 2 +
src/include/tcop/tcopprot.h | 1 +
src/interfaces/ecpg/preproc/ecpg.addons | 6 +
src/interfaces/ecpg/preproc/ecpg.trailer | 4 +-
.../regress/expected/create_function_3.out | 254 +++++++++++++++++-
.../regress/expected/create_procedure.out | 65 +++++
src/test/regress/sql/create_function_3.sql | 104 ++++++-
src/test/regress/sql/create_procedure.sql | 33 +++
36 files changed, 1378 insertions(+), 214 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 5c9f4af1d5..562f850951 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -5980,6 +5980,16 @@ <title><structname>pg_proc</structname> Columns</title>
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>prosqlbody</structfield> <type>pg_node_tree</type>
+ </para>
+ <para>
+ Pre-parsed SQL function body. This will be used for language SQL
+ functions if the body is not specified as a string constant.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>proconfig</structfield> <type>text[]</type>
diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index f1001615f4..10bad6927c 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -38,6 +38,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -262,8 +263,9 @@ <title>Parameters</title>
The name of the language that the function is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -582,6 +584,44 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> function. This can
+ either be a single statement
+<programlisting>
+RETURN <replaceable>expression</replaceable>
+</programlisting>
+ or a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the function body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at function definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at function definition time. This form tracks dependencies between the
+ function and objects used in the function body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling functions. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
@@ -667,6 +707,15 @@ <title>Examples</title>
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
+</programlisting>
+ The same function written in a more SQL-conforming style, using argument
+ names and an unquoted body:
+<programlisting>
+CREATE FUNCTION add(a integer, b integer) RETURNS integer
+ LANGUAGE SQL
+ IMMUTABLE
+ RETURNS NULL ON NULL INPUT
+ RETURN a + b;
</programlisting>
</para>
@@ -797,23 +846,74 @@ <title>Writing <literal>SECURITY DEFINER</literal> Functions Safely</title>
<title>Compatibility</title>
<para>
- A <command>CREATE FUNCTION</command> command is defined in the SQL standard.
- The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. The attributes are not portable, neither are the
- different available languages.
+ A <command>CREATE FUNCTION</command> command is defined in the SQL
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. Conversely, the SQL
+ standard specifies a number of optional features that are not implemented
+ in <productname>PostgreSQL</productname>.
</para>
<para>
- For compatibility with some other database systems,
- <replaceable class="parameter">argmode</replaceable> can be written
- either before or after <replaceable class="parameter">argname</replaceable>.
- But only the first way is standard-compliant.
+ The following are important compatibility issues:
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>OR REPLACE</literal> is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For compatibility with some other database systems, <replaceable
+ class="parameter">argmode</replaceable> can be written either before or
+ after <replaceable class="parameter">argname</replaceable>. But only
+ the first way is standard-compliant.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For parameter defaults, the SQL standard specifies only the syntax with
+ the <literal>DEFAULT</literal> key word. The syntax with
+ <literal>=</literal> is used in T-SQL and Firebird.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The <literal>SETOF</literal> modifier is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Only <literal>SQL</literal> is standardized as a language.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ All other attributes except <literal>CALLED ON NULL INPUT</literal> and
+ <literal>RETURNS NULL ON NULL INPUT</literal> are not standardized.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For the body of <literal>LANGUAGE SQL</literal> functions, the SQL
+ standard only specifies the <replaceable>sql_body</replaceable> form.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
<para>
- For parameter defaults, the SQL standard specifies only the syntax with
- the <literal>DEFAULT</literal> key word. The syntax
- with <literal>=</literal> is used in T-SQL and Firebird.
+ Simple <literal>LANGUAGE SQL</literal> functions can be written in a way
+ that is both standard-conforming and portable to other implementations.
+ More complex functions using advanced features, optimization attributes, or
+ other languages will necessarily be specific to PostgreSQL in a significant
+ way.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_procedure.sgml b/doc/src/sgml/ref/create_procedure.sgml
index 6dbc012719..e7aace7ff1 100644
--- a/doc/src/sgml/ref/create_procedure.sgml
+++ b/doc/src/sgml/ref/create_procedure.sgml
@@ -29,6 +29,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -167,8 +168,9 @@ <title>Parameters</title>
The name of the language that the procedure is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -304,6 +306,41 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> procedure. This should
+ be a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the procedure body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at procedure definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at procedure definition time. This form tracks dependencies between the
+ procedure and objects used in the procedure body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling procedures. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
@@ -323,6 +360,7 @@ <title>Notes</title>
<refsect1 id="sql-createprocedure-examples">
<title>Examples</title>
+ <para>
<programlisting>
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
@@ -330,9 +368,21 @@ <title>Examples</title>
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
$$;
-
+</programlisting>
+ or
+<programlisting>
+CREATE PROCEDURE insert_data(a integer, b integer)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO tbl VALUES (a);
+ INSERT INTO tbl VALUES (b);
+END;
+</programlisting>
+ and call like this:
+<programlisting>
CALL insert_data(1, 2);
</programlisting>
+ </para>
</refsect1>
<refsect1 id="sql-createprocedure-compat">
@@ -340,9 +390,9 @@ <title>Compatibility</title>
<para>
A <command>CREATE PROCEDURE</command> command is defined in the SQL
- standard. The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. For details see
- also <xref linkend="sql-createfunction"/>.
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. For details see also
+ <xref linkend="sql-createfunction"/>.
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 89f23d0add..5197076c76 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -622,6 +622,7 @@ AggregateCreate(const char *aggName,
InvalidOid, /* no validator */
"aggregate_dummy", /* placeholder (no such proc) */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_AGGREGATE,
false, /* security invoker (currently not
* definable for agg) */
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index e14eee5a19..a53b5f408e 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -32,6 +32,7 @@
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_type.h"
#include "tcop/pquery.h"
@@ -76,6 +77,7 @@ ProcedureCreate(const char *procedureName,
Oid languageValidator,
const char *prosrc,
const char *probin,
+ Node *prosqlbody,
char prokind,
bool security_definer,
bool isLeakProof,
@@ -119,8 +121,6 @@ ProcedureCreate(const char *procedureName,
/*
* sanity checks
*/
- Assert(PointerIsValid(prosrc));
-
parameterCount = parameterTypes->dim1;
if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
ereport(ERROR,
@@ -334,11 +334,18 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_protrftypes - 1] = trftypes;
else
nulls[Anum_pg_proc_protrftypes - 1] = true;
- values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ if (prosrc)
+ values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ else
+ nulls[Anum_pg_proc_prosrc - 1] = true;
if (probin)
values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin);
else
nulls[Anum_pg_proc_probin - 1] = true;
+ if (prosqlbody)
+ values[Anum_pg_proc_prosqlbody - 1] = CStringGetTextDatum(nodeToString(prosqlbody));
+ else
+ nulls[Anum_pg_proc_prosqlbody - 1] = true;
if (proconfig != PointerGetDatum(NULL))
values[Anum_pg_proc_proconfig - 1] = proconfig;
else
@@ -638,6 +645,10 @@ ProcedureCreate(const char *procedureName,
record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
free_object_addresses(addrs);
+ /* dependency on SQL routine body */
+ if (languageObjectId == SQLlanguageId && prosqlbody)
+ recordDependencyOnExpr(&myself, prosqlbody, NIL, DEPENDENCY_NORMAL);
+
/* dependency on parameter default expressions */
if (parameterDefaults)
recordDependencyOnExpr(&myself, (Node *) parameterDefaults,
@@ -861,61 +872,81 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
/* Postpone body checks if !check_function_bodies */
if (check_function_bodies)
{
- tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
- if (isnull)
- elog(ERROR, "null prosrc");
-
- prosrc = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport().
*/
callback_arg.proname = NameStr(proc->proname);
- callback_arg.prosrc = prosrc;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_function_parse_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
- /*
- * We can't do full prechecking of the function definition if there
- * are any polymorphic input types, because actual datatypes of
- * expression results will be unresolvable. The check will be done at
- * runtime instead.
- *
- * We can run the text through the raw parser though; this will at
- * least catch silly syntactic errors.
- */
- raw_parsetree_list = pg_parse_query(prosrc);
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ {
+ Node *n;
- if (!haspolyarg)
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosqlbody, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc and prosqlbody");
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = castNode(List, n);
+ else
+ querytree_list = list_make1(list_make1(n));
+ }
+ else
{
+ prosrc = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = prosrc;
+
/*
- * OK to do full precheck: analyze and rewrite the queries, then
- * verify the result type.
+ * We can't do full prechecking of the function definition if there
+ * are any polymorphic input types, because actual datatypes of
+ * expression results will be unresolvable. The check will be done at
+ * runtime instead.
+ *
+ * We can run the text through the raw parser though; this will at
+ * least catch silly syntactic errors.
*/
- SQLFunctionParseInfoPtr pinfo;
- Oid rettype;
- TupleDesc rettupdesc;
+ raw_parsetree_list = pg_parse_query(prosrc);
- /* But first, set up parameter information */
- pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
-
- querytree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (!haspolyarg)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *querytree_sublist;
-
- querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
- prosrc,
- (ParserSetupHook) sql_fn_parser_setup,
- pinfo,
- NULL);
- querytree_list = lappend(querytree_list,
- querytree_sublist);
+ /*
+ * OK to do full precheck: analyze and rewrite the queries, then
+ * verify the result type.
+ */
+ SQLFunctionParseInfoPtr pinfo;
+
+ /* But first, set up parameter information */
+ pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
+
+ querytree_list = NIL;
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *querytree_sublist;
+
+ querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ prosrc,
+ (ParserSetupHook) sql_fn_parser_setup,
+ pinfo,
+ NULL);
+ querytree_list = lappend(querytree_list,
+ querytree_sublist);
+ }
}
+ }
+
+ if (!haspolyarg)
+ {
+ Oid rettype;
+ TupleDesc rettupdesc;
check_sql_fn_statements(querytree_list);
@@ -968,6 +999,9 @@ function_parse_error_transpose(const char *prosrc)
int newerrposition;
const char *queryText;
+ if (!prosrc)
+ return false;
+
/*
* Nothing to do unless we are dealing with a syntax error that has a
* cursor position.
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 69c50ac087..046cf2df08 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -312,9 +312,11 @@ DefineAggregate(ParseState *pstate,
InvalidOid,
OBJECT_AGGREGATE,
¶meterTypes,
+ NULL,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ NULL,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 7a4e104623..d11f6745be 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -53,15 +53,18 @@
#include "commands/proclang.h"
#include "executor/execdesc.h"
#include "executor/executor.h"
+#include "executor/functions.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_func.h"
#include "parser/parse_type.h"
#include "pgstat.h"
+#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
@@ -186,9 +189,11 @@ interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType)
@@ -283,7 +288,11 @@ interpret_function_parameter_list(ParseState *pstate,
/* handle input parameters */
if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
+ {
isinput = true;
+ if (parameterTypes_list)
+ *parameterTypes_list = lappend_oid(*parameterTypes_list, toid);
+ }
/* handle signature parameters */
if (fp->mode == FUNC_PARAM_IN || fp->mode == FUNC_PARAM_INOUT ||
@@ -372,6 +381,9 @@ interpret_function_parameter_list(ParseState *pstate,
have_names = true;
}
+ if (inParameterNames_list)
+ *inParameterNames_list = lappend(*inParameterNames_list, makeString(fp->name ? fp->name : pstrdup("")));
+
if (fp->defexpr)
{
Node *def;
@@ -786,28 +798,10 @@ compute_function_attributes(ParseState *pstate,
defel->defname);
}
- /* process required items */
if (as_item)
*as = (List *) as_item->arg;
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no function body specified")));
- *as = NIL; /* keep compiler quiet */
- }
-
if (language_item)
*language = strVal(language_item->arg);
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no language specified")));
- *language = NULL; /* keep compiler quiet */
- }
-
- /* process optional items */
if (transform_item)
*transform = transform_item->arg;
if (windowfunc_item)
@@ -856,10 +850,21 @@ compute_function_attributes(ParseState *pstate,
*/
static void
interpret_AS_clause(Oid languageOid, const char *languageName,
- char *funcname, List *as,
- char **prosrc_str_p, char **probin_str_p)
+ char *funcname, List *as, Node *sql_body_in,
+ List *parameterTypes, List *inParameterNames,
+ char **prosrc_str_p, char **probin_str_p, Node **sql_body_out)
{
- Assert(as != NIL);
+ if (sql_body_in && as)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("duplicate function body specified")));
+
+ if (sql_body_in && languageOid != SQLlanguageId)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("inline SQL function body only valid for language SQL")));
+
+ *sql_body_out = NULL;
if (languageOid == ClanguageId)
{
@@ -881,6 +886,76 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
*prosrc_str_p = funcname;
}
}
+ else if (sql_body_in)
+ {
+ SQLFunctionParseInfoPtr pinfo;
+
+ pinfo = (SQLFunctionParseInfoPtr) palloc0(sizeof(SQLFunctionParseInfo));
+
+ pinfo->fname = funcname;
+ pinfo->nargs = list_length(parameterTypes);
+ pinfo->argtypes = (Oid *) palloc(pinfo->nargs * sizeof(Oid));
+ pinfo->argnames = (char **) palloc(pinfo->nargs * sizeof(char *));
+ for (int i = 0; i < list_length(parameterTypes); i++)
+ {
+ char *s = strVal(list_nth(inParameterNames, i));
+
+ pinfo->argtypes[i] = list_nth_oid(parameterTypes, i);
+ if (IsPolymorphicType(pinfo->argtypes[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("SQL function with unquoted function body cannot have polymorphic arguments")));
+
+ if (s[0] != '\0')
+ pinfo->argnames[i] = s;
+ else
+ pinfo->argnames[i] = NULL;
+ }
+
+ if (IsA(sql_body_in, List))
+ {
+ List *stmts = linitial_node(List, castNode(List, sql_body_in));
+ ListCell *lc;
+ List *transformed_stmts = NIL;
+
+ foreach(lc, stmts)
+ {
+ Node *stmt = lfirst(lc);
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, stmt);
+ if (q->commandType == CMD_UTILITY)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("%s is not yet supported in unquoted SQL function body",
+ GetCommandTagName(CreateCommandTag(q->utilityStmt))));
+ transformed_stmts = lappend(transformed_stmts, q);
+ free_parsestate(pstate);
+ }
+
+ *sql_body_out = (Node *) list_make1(transformed_stmts);
+ }
+ else
+ {
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, sql_body_in);
+ if (q->commandType == CMD_UTILITY)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("%s is not yet supported in unquoted SQL function body",
+ GetCommandTagName(CreateCommandTag(q->utilityStmt))));
+
+ *sql_body_out = (Node *) q;
+ }
+
+ *probin_str_p = NULL;
+ *prosrc_str_p = NULL;
+ }
else
{
/* Everything else wants the given string in prosrc. */
@@ -919,6 +994,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
{
char *probin_str;
char *prosrc_str;
+ Node *prosqlbody;
Oid prorettype;
bool returnsSet;
char *language;
@@ -929,9 +1005,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
Oid namespaceId;
AclResult aclresult;
oidvector *parameterTypes;
+ List *parameterTypes_list = NIL;
ArrayType *allParameterTypes;
ArrayType *parameterModes;
ArrayType *parameterNames;
+ List *inParameterNames_list = NIL;
List *parameterDefaults;
Oid variadicArgType;
List *trftypes_list = NIL;
@@ -962,6 +1040,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
get_namespace_name(namespaceId));
/* Set default attributes */
+ as_clause = NULL;
+ language = "sql";
isWindowFunc = false;
isStrict = false;
security = false;
@@ -1053,9 +1133,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageOid,
stmt->is_procedure ? OBJECT_PROCEDURE : OBJECT_FUNCTION,
¶meterTypes,
+ ¶meterTypes_list,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ &inParameterNames_list,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
@@ -1112,8 +1194,9 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
trftypes = NULL;
}
- interpret_AS_clause(languageOid, language, funcname, as_clause,
- &prosrc_str, &probin_str);
+ interpret_AS_clause(languageOid, language, funcname, as_clause, stmt->sql_body,
+ parameterTypes_list, inParameterNames_list,
+ &prosrc_str, &probin_str, &prosqlbody);
/*
* Set default values for COST and ROWS depending on other parameters;
@@ -1155,6 +1238,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageValidator,
prosrc_str, /* converted to text later */
probin_str, /* converted to text later */
+ prosqlbody,
stmt->is_procedure ? PROKIND_PROCEDURE : (isWindowFunc ? PROKIND_WINDOW : PROKIND_FUNCTION),
security,
isLeakProof,
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 76218fb47e..e975508ffa 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1775,6 +1775,7 @@ makeRangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR, /* language validator */
prosrc[i], /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
@@ -1839,6 +1840,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR,
"multirange_constructor0", /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
@@ -1882,6 +1884,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR,
"multirange_constructor1", /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
@@ -1922,6 +1925,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR,
"multirange_constructor2", /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 7bb752ace3..642683843e 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -26,6 +26,7 @@
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_func.h"
+#include "rewrite/rewriteHandler.h"
#include "storage/proc.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
@@ -128,21 +129,6 @@ typedef struct
typedef SQLFunctionCache *SQLFunctionCachePtr;
-/*
- * Data structure needed by the parser callback hooks to resolve parameter
- * references during parsing of a SQL function's body. This is separate from
- * SQLFunctionCache since we sometimes do parsing separately from execution.
- */
-typedef struct SQLFunctionParseInfo
-{
- char *fname; /* function's name */
- int nargs; /* number of input arguments */
- Oid *argtypes; /* resolved types of input arguments */
- char **argnames; /* names of input arguments; NULL if none */
- /* Note that argnames[i] can be NULL, if some args are unnamed */
- Oid collation; /* function's input collation, if known */
-} SQLFunctionParseInfo;
-
/* non-export function prototypes */
static Node *sql_fn_param_ref(ParseState *pstate, ParamRef *pref);
@@ -607,7 +593,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
HeapTuple procedureTuple;
Form_pg_proc procedureStruct;
SQLFunctionCachePtr fcache;
- List *raw_parsetree_list;
List *queryTree_list;
List *resulttlist;
ListCell *lc;
@@ -682,9 +667,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
procedureTuple,
Anum_pg_proc_prosrc,
&isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", foid);
- fcache->src = TextDatumGetCString(tmp);
/*
* Parse and rewrite the queries in the function text. Use sublists to
@@ -695,20 +677,55 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
* but we'll not worry about it until the module is rewritten to use
* plancache.c.
*/
- raw_parsetree_list = pg_parse_query(fcache->src);
-
queryTree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (isNull)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *queryTree_sublist;
-
- queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
- fcache->src,
- (ParserSetupHook) sql_fn_parser_setup,
- fcache->pinfo,
- NULL);
- queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ Node *n;
+ List *stored_query_list;
+
+ tmp = SysCacheGetAttr(PROCOID,
+ procedureTuple,
+ Anum_pg_proc_prosqlbody,
+ &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", foid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ stored_query_list = linitial_node(List, castNode(List, n));
+ else
+ stored_query_list = list_make1(n);
+
+ foreach(lc, stored_query_list)
+ {
+ Query *parsetree = lfirst_node(Query, lc);
+ List *queryTree_sublist;
+
+ AcquireRewriteLocks(parsetree, true, false);
+ queryTree_sublist = pg_rewrite_query(parsetree);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ }
+ }
+ else
+ {
+ List *raw_parsetree_list;
+
+ fcache->src = TextDatumGetCString(tmp);
+
+ raw_parsetree_list = pg_parse_query(fcache->src);
+
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *queryTree_sublist;
+
+ queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ fcache->src,
+ (ParserSetupHook) sql_fn_parser_setup,
+ fcache->pinfo,
+ NULL);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ }
}
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bda379ba91..0738932f73 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3127,6 +3127,7 @@ _copyQuery(const Query *from)
COPY_SCALAR_FIELD(hasModifyingCTE);
COPY_SCALAR_FIELD(hasForUpdate);
COPY_SCALAR_FIELD(hasRowSecurity);
+ COPY_SCALAR_FIELD(isReturn);
COPY_NODE_FIELD(cteList);
COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(jointree);
@@ -3257,6 +3258,16 @@ _copySetOperationStmt(const SetOperationStmt *from)
return newnode;
}
+static ReturnStmt *
+_copyReturnStmt(const ReturnStmt *from)
+{
+ ReturnStmt *newnode = makeNode(ReturnStmt);
+
+ COPY_NODE_FIELD(returnval);
+
+ return newnode;
+}
+
static PLAssignStmt *
_copyPLAssignStmt(const PLAssignStmt *from)
{
@@ -3640,6 +3651,7 @@ _copyCreateFunctionStmt(const CreateFunctionStmt *from)
COPY_NODE_FIELD(parameters);
COPY_NODE_FIELD(returnType);
COPY_NODE_FIELD(options);
+ COPY_NODE_FIELD(sql_body);
return newnode;
}
@@ -5296,6 +5308,9 @@ copyObjectImpl(const void *from)
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
+ case T_ReturnStmt:
+ retval = _copyReturnStmt(from);
+ break;
case T_PLAssignStmt:
retval = _copyPLAssignStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index bc5e9e52fe..58049dbeea 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -969,6 +969,7 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_SCALAR_FIELD(hasModifyingCTE);
COMPARE_SCALAR_FIELD(hasForUpdate);
COMPARE_SCALAR_FIELD(hasRowSecurity);
+ COMPARE_SCALAR_FIELD(isReturn);
COMPARE_NODE_FIELD(cteList);
COMPARE_NODE_FIELD(rtable);
COMPARE_NODE_FIELD(jointree);
@@ -1087,6 +1088,14 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
return true;
}
+static bool
+_equalReturnStmt(const ReturnStmt *a, const ReturnStmt *b)
+{
+ COMPARE_NODE_FIELD(returnval);
+
+ return true;
+}
+
static bool
_equalPLAssignStmt(const PLAssignStmt *a, const PLAssignStmt *b)
{
@@ -1405,6 +1414,7 @@ _equalCreateFunctionStmt(const CreateFunctionStmt *a, const CreateFunctionStmt *
COMPARE_NODE_FIELD(parameters);
COMPARE_NODE_FIELD(returnType);
COMPARE_NODE_FIELD(options);
+ COMPARE_NODE_FIELD(sql_body);
return true;
}
@@ -3320,6 +3330,9 @@ equal(const void *a, const void *b)
case T_SetOperationStmt:
retval = _equalSetOperationStmt(a, b);
break;
+ case T_ReturnStmt:
+ retval = _equalReturnStmt(a, b);
+ break;
case T_PLAssignStmt:
retval = _equalPLAssignStmt(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 5054490c58..a15216b02f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2787,6 +2787,14 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
WRITE_NODE_FIELD(rarg);
}
+static void
+_outReturnStmt(StringInfo str, const ReturnStmt *node)
+{
+ WRITE_NODE_TYPE("RETURN");
+
+ WRITE_NODE_FIELD(returnval);
+}
+
static void
_outPLAssignStmt(StringInfo str, const PLAssignStmt *node)
{
@@ -2989,6 +2997,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_BOOL_FIELD(hasModifyingCTE);
WRITE_BOOL_FIELD(hasForUpdate);
WRITE_BOOL_FIELD(hasRowSecurity);
+ WRITE_BOOL_FIELD(isReturn);
WRITE_NODE_FIELD(cteList);
WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(jointree);
@@ -4269,6 +4278,9 @@ outNode(StringInfo str, const void *obj)
case T_SelectStmt:
_outSelectStmt(str, obj);
break;
+ case T_ReturnStmt:
+ _outReturnStmt(str, obj);
+ break;
case T_PLAssignStmt:
_outPLAssignStmt(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 9b8f81c523..df66e8efad 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -263,6 +263,7 @@ _readQuery(void)
READ_BOOL_FIELD(hasModifyingCTE);
READ_BOOL_FIELD(hasForUpdate);
READ_BOOL_FIELD(hasRowSecurity);
+ READ_BOOL_FIELD(isReturn);
READ_NODE_FIELD(cteList);
READ_NODE_FIELD(rtable);
READ_NODE_FIELD(jointree);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index c6be4f87c2..6d01594dac 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4713,27 +4713,47 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
- /* Fetch the function body */
- tmp = SysCacheGetAttr(PROCOID,
- func_tuple,
- Anum_pg_proc_prosrc,
- &isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", funcid);
- src = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = src;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ {
+ Node *n;
+ List *querytree_list;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", funcid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = linitial_node(List, castNode(List, n));
+ else
+ querytree_list = list_make1(n);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We need a
* dummy FuncExpr node containing the already-simplified arguments to pass
@@ -4777,6 +4797,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
querytree = transformTopLevelStmt(pstate, linitial(raw_parsetree_list));
free_parsestate(pstate);
+ }
/*
* The single command must be a simple "SELECT expression".
@@ -5033,12 +5054,15 @@ sql_inline_error_callback(void *arg)
int syntaxerrposition;
/* If it's a syntax error, convert to internal syntax error report */
- syntaxerrposition = geterrposition();
- if (syntaxerrposition > 0)
+ if (callback_arg->prosrc)
{
- errposition(0);
- internalerrposition(syntaxerrposition);
- internalerrquery(callback_arg->prosrc);
+ syntaxerrposition = geterrposition();
+ if (syntaxerrposition > 0)
+ {
+ errposition(0);
+ internalerrposition(syntaxerrposition);
+ internalerrquery(callback_arg->prosrc);
+ }
}
errcontext("SQL function \"%s\" during inlining", callback_arg->proname);
@@ -5150,7 +5174,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
Oid func_oid;
HeapTuple func_tuple;
Form_pg_proc funcform;
- char *src;
Datum tmp;
bool isNull;
MemoryContext oldcxt;
@@ -5259,27 +5282,53 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
- /* Fetch the function body */
- tmp = SysCacheGetAttr(PROCOID,
- func_tuple,
- Anum_pg_proc_prosrc,
- &isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", func_oid);
- src = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = src;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ {
+ Node *n;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", func_oid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = linitial_node(List, castNode(List, n));
+ else
+ querytree_list = list_make1(n);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+
+ querytree_list = pg_rewrite_query(querytree);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
+ char *src;
+
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We can
* use the FuncExpr just created as the input for
@@ -5289,18 +5338,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
(Node *) fexpr,
fexpr->inputcollid);
- /*
- * Also resolve the actual function result tupdesc, if composite. If the
- * function is just declared to return RECORD, dig the info out of the AS
- * clause.
- */
- functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
- if (functypclass == TYPEFUNC_RECORD)
- rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
- rtfunc->funccoltypes,
- rtfunc->funccoltypmods,
- rtfunc->funccolcollations);
-
/*
* Parse, analyze, and rewrite (unlike inline_function(), we can't skip
* rewriting here). We can fail as soon as we find more than one query,
@@ -5317,6 +5354,19 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
if (list_length(querytree_list) != 1)
goto fail;
querytree = linitial(querytree_list);
+ }
+
+ /*
+ * Also resolve the actual function result tupdesc, if composite. If the
+ * function is just declared to return RECORD, dig the info out of the AS
+ * clause.
+ */
+ functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
+ if (functypclass == TYPEFUNC_RECORD)
+ rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
+ rtfunc->funccoltypes,
+ rtfunc->funccoltypmods,
+ rtfunc->funccolcollations);
/*
* The single command must be a plain SELECT.
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 7149724953..2a7dee6e67 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -68,6 +68,7 @@ static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
bool isTopLevel, List **targetlist);
static void determineRecursiveColTypes(ParseState *pstate,
Node *larg, List *nrtargetlist);
+static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList);
static List *transformUpdateTargetList(ParseState *pstate,
@@ -308,6 +309,10 @@ transformStmt(ParseState *pstate, Node *parseTree)
}
break;
+ case T_ReturnStmt:
+ result = transformReturnStmt(pstate, (ReturnStmt *) parseTree);
+ break;
+
case T_PLAssignStmt:
result = transformPLAssignStmt(pstate,
(PLAssignStmt *) parseTree);
@@ -2228,6 +2233,36 @@ determineRecursiveColTypes(ParseState *pstate, Node *larg, List *nrtargetlist)
}
+/*
+ * transformReturnStmt -
+ * transforms a return statement
+ */
+static Query *
+transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+
+ qry->commandType = CMD_SELECT;
+ qry->isReturn = true;
+
+ qry->targetList = list_make1(makeTargetEntry((Expr *) transformExpr(pstate, stmt->returnval, EXPR_KIND_SELECT_TARGET),
+ 1, NULL, false));
+
+ if (pstate->p_resolve_unknowns)
+ resolveTargetListUnknowns(pstate, qry->targetList);
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasAggs = pstate->p_hasAggs;
+
+ assign_query_collations(pstate, qry);
+
+ return qry;
+}
+
+
/*
* transformUpdateStmt -
* transforms an update statement
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index fd07e7107d..f4ae566483 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -261,7 +261,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct GroupClause *groupclause;
}
-%type <node> stmt schema_stmt
+%type <node> stmt toplevel_stmt schema_stmt routine_body_stmt
AlterEventTrigStmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
@@ -288,9 +288,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
- RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
+ RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt
RuleActionStmt RuleActionStmtOrEmpty RuleStmt
- SecLabelStmt SelectStmt TransactionStmt TruncateStmt
+ SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
UnlistenStmt UpdateStmt VacuumStmt
VariableResetStmt VariableSetStmt VariableShowStmt
ViewStmt CheckPointStmt CreateConversionStmt
@@ -394,14 +394,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> vacuum_relation
%type <selectlimit> opt_select_limit select_limit limit_clause
-%type <list> parse_toplevel stmtmulti
+%type <list> parse_toplevel stmtmulti routine_body_stmt_list
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
reloptions opt_reloptions
OptWith opt_definition func_args func_args_list
func_args_with_defaults func_args_with_defaults_list
aggr_args aggr_args_list
- func_as createfunc_opt_list alterfunc_opt_list
+ func_as createfunc_opt_list opt_createfunc_opt_list alterfunc_opt_list
old_aggr_definition old_aggr_list
oper_argtypes RuleActionList RuleActionMulti
opt_column_list columnList opt_name_list
@@ -427,6 +427,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
vacuum_relation_list opt_vacuum_relation_list
drop_option_list
+%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -633,7 +634,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATOMIC ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BREADTH BY
@@ -695,7 +696,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING
REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
- RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
+ RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
@@ -865,7 +866,7 @@ parse_toplevel:
* we'd get -1 for the location in such cases.
* We also take care to discard empty statements entirely.
*/
-stmtmulti: stmtmulti ';' stmt
+stmtmulti: stmtmulti ';' toplevel_stmt
{
if ($1 != NIL)
{
@@ -877,7 +878,7 @@ stmtmulti: stmtmulti ';' stmt
else
$$ = $1;
}
- | stmt
+ | toplevel_stmt
{
if ($1 != NULL)
$$ = list_make1(makeRawStmt($1, 0));
@@ -886,7 +887,16 @@ stmtmulti: stmtmulti ';' stmt
}
;
-stmt :
+/*
+ * toplevel_stmt includes BEGIN and END. stmt does not include them, because
+ * those words have different meanings in function bodys.
+ */
+toplevel_stmt:
+ stmt
+ | TransactionStmtLegacy
+ ;
+
+stmt:
AlterEventTrigStmt
| AlterDatabaseStmt
| AlterDatabaseSetStmt
@@ -7409,7 +7419,7 @@ opt_nulls_order: NULLS_LA FIRST_P { $$ = SORTBY_NULLS_FIRST; }
CreateFunctionStmt:
CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS func_return createfunc_opt_list
+ RETURNS func_return opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7418,10 +7428,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = $7;
n->options = $8;
+ n->sql_body = $9;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS TABLE '(' table_func_column_list ')' createfunc_opt_list
+ RETURNS TABLE '(' table_func_column_list ')' opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7431,10 +7442,11 @@ CreateFunctionStmt:
n->returnType = TableFuncTypeName($9);
n->returnType->location = @7;
n->options = $11;
+ n->sql_body = $12;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7443,10 +7455,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
| CREATE opt_or_replace PROCEDURE func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = true;
@@ -7455,6 +7468,7 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
;
@@ -7765,6 +7779,11 @@ aggregate_with_argtypes_list:
{ $$ = lappend($1, $3); }
;
+opt_createfunc_opt_list:
+ createfunc_opt_list
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
createfunc_opt_list:
/* Must be at least one to prevent conflict */
createfunc_opt_item { $$ = list_make1($1); }
@@ -7876,6 +7895,51 @@ func_as: Sconst { $$ = list_make1(makeString($1)); }
}
;
+ReturnStmt: RETURN a_expr
+ {
+ ReturnStmt *r = makeNode(ReturnStmt);
+ r->returnval = (Node *) $2;
+ $$ = (Node *) r;
+ }
+ ;
+
+opt_routine_body:
+ ReturnStmt
+ {
+ $$ = $1;
+ }
+ | BEGIN_P ATOMIC routine_body_stmt_list END_P
+ {
+ /*
+ * A compound statement is stored as a single-item list
+ * containing the list of statements as its member. That
+ * way, the parse analysis code can tell apart an empty
+ * body from no body at all.
+ */
+ $$ = (Node *) list_make1($3);
+ }
+ | /*EMPTY*/
+ {
+ $$ = NULL;
+ }
+ ;
+
+routine_body_stmt_list:
+ routine_body_stmt_list routine_body_stmt ';'
+ {
+ $$ = lappend($1, $2);
+ }
+ | /*EMPTY*/
+ {
+ $$ = NIL;
+ }
+ ;
+
+routine_body_stmt:
+ stmt
+ | ReturnStmt
+ ;
+
transform_type_list:
FOR TYPE_P Typename { $$ = list_make1($3); }
| transform_type_list ',' FOR TYPE_P Typename { $$ = lappend($1, $5); }
@@ -9809,13 +9873,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | BEGIN_P opt_transaction transaction_mode_list_or_empty
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_BEGIN;
- n->options = $3;
- $$ = (Node *)n;
- }
| START TRANSACTION transaction_mode_list_or_empty
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9831,14 +9888,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | END_P opt_transaction opt_transaction_chain
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_COMMIT;
- n->options = NIL;
- n->chain = $3;
- $$ = (Node *)n;
- }
| ROLLBACK opt_transaction opt_transaction_chain
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9905,6 +9954,24 @@ TransactionStmt:
}
;
+TransactionStmtLegacy:
+ BEGIN_P opt_transaction transaction_mode_list_or_empty
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_BEGIN;
+ n->options = $3;
+ $$ = (Node *)n;
+ }
+ | END_P opt_transaction opt_transaction_chain
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_COMMIT;
+ n->options = NIL;
+ n->chain = $3;
+ $$ = (Node *)n;
+ }
+ ;
+
opt_transaction: WORK
| TRANSACTION
| /*EMPTY*/
@@ -15297,6 +15364,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| BACKWARD
@@ -15497,6 +15565,7 @@ unreserved_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| ROLE
@@ -15803,6 +15872,7 @@ bare_label_keyword:
| ASSIGNMENT
| ASYMMETRIC
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| AUTHORIZATION
@@ -16075,6 +16145,7 @@ bare_label_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| RIGHT
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 2b1b68109f..d3d98ba647 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -188,7 +188,6 @@ static int interactive_getc(void);
static int SocketBackend(StringInfo inBuf);
static int ReadCommand(StringInfo inBuf);
static void forbidden_in_wal_sender(char firstchar);
-static List *pg_rewrite_query(Query *query);
static bool check_log_statement(List *stmt_list);
static int errdetail_execute(List *raw_parsetree_list);
static int errdetail_params(ParamListInfo params);
@@ -707,7 +706,7 @@ pg_analyze_and_rewrite_params(RawStmt *parsetree,
* Note: query must just have come from the parser, because we do not do
* AcquireRewriteLocks() on it.
*/
-static List *
+List *
pg_rewrite_query(Query *query)
{
List *querytree_list;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f0de2a25c9..a663f55c4e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -172,6 +172,10 @@ typedef struct
List *outer_tlist; /* referent for OUTER_VAR Vars */
List *inner_tlist; /* referent for INNER_VAR Vars */
List *index_tlist; /* referent for INDEX_VAR Vars */
+ /* Special namespace representing a function signature: */
+ char *funcname;
+ int numargs;
+ char **argnames;
} deparse_namespace;
/*
@@ -347,6 +351,7 @@ static int print_function_arguments(StringInfo buf, HeapTuple proctup,
bool print_table_args, bool print_defaults);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
static void print_function_trftypes(StringInfo buf, HeapTuple proctup);
+static void print_function_sqlbody(StringInfo buf, HeapTuple proctup);
static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used);
static void set_deparse_for_query(deparse_namespace *dpns, Query *query,
@@ -2799,6 +2804,13 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
}
/* And finally the function definition ... */
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
+ if (proc->prolang == SQLlanguageId && !isnull)
+ {
+ print_function_sqlbody(&buf, proctup);
+ }
+ else
+ {
appendStringInfoString(&buf, "AS ");
tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
@@ -2830,6 +2842,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, dq.data, dq.len);
appendStringInfoString(&buf, prosrc);
appendBinaryStringInfo(&buf, dq.data, dq.len);
+ }
appendStringInfoChar(&buf, '\n');
@@ -3213,6 +3226,76 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(string_to_text(str));
}
+static void
+print_function_sqlbody(StringInfo buf, HeapTuple proctup)
+{
+ int numargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ deparse_namespace dpns = {0};
+ Datum tmp;
+ bool isnull;
+ Node *n;
+
+ dpns.funcname = pstrdup(NameStr(((Form_pg_proc) GETSTRUCT(proctup))->proname));
+ numargs = get_func_arg_info(proctup,
+ &argtypes, &argnames, &argmodes);
+ dpns.numargs = numargs;
+ dpns.argnames = argnames;
+
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
+ Assert(!isnull);
+ n = stringToNode(TextDatumGetCString(tmp));
+
+ if (IsA(n, List))
+ {
+ List *stmts;
+ ListCell *lc;
+
+ stmts = linitial(castNode(List, n));
+
+ appendStringInfoString(buf, "BEGIN ATOMIC\n");
+
+ foreach(lc, stmts)
+ {
+ Query *query = lfirst_node(Query, lc);
+
+ get_query_def(query, buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ appendStringInfoChar(buf, ';');
+ appendStringInfoChar(buf, '\n');
+ }
+
+ appendStringInfoString(buf, "END");
+ }
+ else
+ {
+ get_query_def(castNode(Query, n), buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ appendStringInfoChar(buf, '\n');
+ }
+}
+
+Datum
+pg_get_function_sqlbody(PG_FUNCTION_ARGS)
+{
+ Oid funcid = PG_GETARG_OID(0);
+ StringInfoData buf;
+ HeapTuple proctup;
+
+ initStringInfo(&buf);
+
+ /* Look up the function */
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ PG_RETURN_NULL();
+
+ print_function_sqlbody(&buf, proctup);
+
+ ReleaseSysCache(proctup);
+
+ PG_RETURN_TEXT_P(cstring_to_text(buf.data));
+}
+
/*
* deparse_expression - General utility for deparsing expressions
@@ -5472,7 +5555,10 @@ get_basic_select_query(Query *query, deparse_context *context,
/*
* Build up the query string - first we say SELECT
*/
- appendStringInfoString(buf, "SELECT");
+ if (query->isReturn)
+ appendStringInfoString(buf, "RETURN");
+ else
+ appendStringInfoString(buf, "SELECT");
/* Add the DISTINCT clause if given */
if (query->distinctClause != NIL)
@@ -7606,6 +7692,50 @@ get_parameter(Param *param, deparse_context *context)
return;
}
+ /*
+ * If it's an external parameter, see if the outermost namespace provides
+ * function argument names.
+ */
+ if (param->paramkind == PARAM_EXTERN)
+ {
+ dpns = lfirst(list_tail(context->namespaces));
+ if (dpns->argnames)
+ {
+ char *argname = dpns->argnames[param->paramid - 1];
+
+ if (argname)
+ {
+ bool should_qualify = false;
+ ListCell *lc;
+
+ /*
+ * Qualify the parameter name if there are any other deparse
+ * namespaces with range tables. This avoids qualifying in
+ * trivial cases like "RETURN a + b", but makes it safe in all
+ * other cases.
+ */
+ foreach (lc, context->namespaces)
+ {
+ deparse_namespace *dpns = lfirst(lc);
+
+ if (list_length(dpns->rtable_names) > 0)
+ {
+ should_qualify = true;
+ break;
+ }
+ }
+ if (should_qualify)
+ {
+ appendStringInfoString(context->buf, quote_identifier(dpns->funcname));
+ appendStringInfoChar(context->buf, '.');
+ }
+
+ appendStringInfoString(context->buf, quote_identifier(argname));
+ return;
+ }
+ }
+ }
+
/*
* Not PARAM_EXEC, or couldn't find referent: just print $N.
*/
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index eb988d7eb4..98a74d1efb 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11979,6 +11979,7 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
char *proretset;
char *prosrc;
char *probin;
+ char *prosqlbody;
char *funcargs;
char *funciargs;
char *funcresult;
@@ -12025,7 +12026,7 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
"provolatile,\n"
"proisstrict,\n"
"prosecdef,\n"
- "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname,\n");
+ "lanname,\n");
if (fout->remoteVersion >= 80300)
appendPQExpBufferStr(query,
@@ -12045,9 +12046,9 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
* pg_get_function_result instead of examining proallargtypes etc.
*/
appendPQExpBufferStr(query,
- "pg_catalog.pg_get_function_arguments(oid) AS funcargs,\n"
- "pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs,\n"
- "pg_catalog.pg_get_function_result(oid) AS funcresult,\n");
+ "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n"
+ "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n"
+ "pg_catalog.pg_get_function_result(p.oid) AS funcresult,\n");
}
else if (fout->remoteVersion >= 80100)
appendPQExpBufferStr(query,
@@ -12090,21 +12091,39 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
if (fout->remoteVersion >= 120000)
appendPQExpBufferStr(query,
- "prosupport\n");
+ "prosupport,\n");
else
appendPQExpBufferStr(query,
- "'-' AS prosupport\n");
+ "'-' AS prosupport,\n");
+
+ if (fout->remoteVersion >= 140000)
+ appendPQExpBufferStr(query,
+ "CASE WHEN prosrc IS NULL AND lanname = 'sql' THEN pg_get_function_sqlbody(p.oid) END AS prosqlbody\n");
+ else
+ appendPQExpBufferStr(query,
+ "NULL AS prosqlbody\n");
appendPQExpBuffer(query,
- "FROM pg_catalog.pg_proc "
- "WHERE oid = '%u'::pg_catalog.oid",
+ "FROM pg_catalog.pg_proc p, pg_catalog.pg_language l\n"
+ "WHERE p.oid = '%u'::pg_catalog.oid "
+ "AND l.oid = p.prolang",
finfo->dobj.catId.oid);
res = ExecuteSqlQueryForSingleRow(fout, query->data);
proretset = PQgetvalue(res, 0, PQfnumber(res, "proretset"));
- prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
- probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ if (PQgetisnull(res, 0, PQfnumber(res, "prosqlbody")))
+ {
+ prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
+ probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ prosqlbody = NULL;
+ }
+ else
+ {
+ prosrc = NULL;
+ probin = NULL;
+ prosqlbody = PQgetvalue(res, 0, PQfnumber(res, "prosqlbody"));
+ }
if (fout->remoteVersion >= 80400)
{
funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
@@ -12141,7 +12160,11 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
* versions would set it to "-". There are no known cases in which prosrc
* is unused, so the tests below for "-" are probably useless.
*/
- if (probin[0] != '\0' && strcmp(probin, "-") != 0)
+ if (prosqlbody)
+ {
+ appendPQExpBufferStr(asPart, prosqlbody);
+ }
+ else if (probin[0] != '\0' && strcmp(probin, "-") != 0)
{
appendPQExpBufferStr(asPart, "AS ");
appendStringLiteralAH(asPart, probin, fout);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 20af5a92b4..a47c78d4bc 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -505,11 +505,18 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
appendPQExpBufferStr(&buf, ",\n ");
printACLColumn(&buf, "p.proacl");
appendPQExpBuffer(&buf,
- ",\n l.lanname as \"%s\""
- ",\n p.prosrc as \"%s\""
+ ",\n l.lanname as \"%s\"",
+ gettext_noop("Language"));
+ if (pset.sversion >= 140000)
+ appendPQExpBuffer(&buf,
+ ",\n COALESCE(p.prosrc, pg_catalog.pg_get_function_sqlbody(p.oid)) as \"%s\"",
+ gettext_noop("Source code"));
+ else
+ appendPQExpBuffer(&buf,
+ ",\n p.prosrc as \"%s\"",
+ gettext_noop("Source code"));
+ appendPQExpBuffer(&buf,
",\n pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"",
- gettext_noop("Language"),
- gettext_noop("Source code"),
gettext_noop("Description"));
}
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 216ff30993..4ec57e96a9 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -645,10 +645,11 @@ other .
";" {
ECHO;
- if (cur_state->paren_depth == 0)
+ if (cur_state->paren_depth == 0 && cur_state->begin_depth == 0)
{
/* Terminate lexing temporarily */
cur_state->start_state = YY_START;
+ cur_state->identifier_count = 0;
return LEXRES_SEMI;
}
}
@@ -661,6 +662,8 @@ other .
"\\"[;:] {
/* Force a semi-colon or colon into the query buffer */
psqlscan_emit(cur_state, yytext + 1, 1);
+ if (yytext[1] == ';')
+ cur_state->identifier_count = 0;
}
"\\" {
@@ -867,6 +870,18 @@ other .
{identifier} {
+ cur_state->identifier_count++;
+ if (pg_strcasecmp(yytext, "begin") == 0
+ || pg_strcasecmp(yytext, "case") == 0)
+ {
+ if (cur_state->identifier_count > 1)
+ cur_state->begin_depth++;
+ }
+ else if (pg_strcasecmp(yytext, "end") == 0)
+ {
+ if (cur_state->begin_depth > 0)
+ cur_state->begin_depth--;
+ }
ECHO;
}
@@ -1054,6 +1069,11 @@ psql_scan(PsqlScanState state,
result = PSCAN_INCOMPLETE;
*prompt = PROMPT_PAREN;
}
+ if (state->begin_depth > 0)
+ {
+ result = PSCAN_INCOMPLETE;
+ *prompt = PROMPT_CONTINUE;
+ }
else if (query_buf->len > 0)
{
result = PSCAN_EOL;
@@ -1170,6 +1190,8 @@ psql_scan_reset(PsqlScanState state)
if (state->dolqstart)
free(state->dolqstart);
state->dolqstart = NULL;
+ state->identifier_count = 0;
+ state->begin_depth = 0;
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..264c1b980f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3692,6 +3692,10 @@
proname => 'pg_get_function_arg_default', provolatile => 's',
prorettype => 'text', proargtypes => 'oid int4',
prosrc => 'pg_get_function_arg_default' },
+{ oid => '9704', descr => 'function SQL body',
+ proname => 'pg_get_function_sqlbody', provolatile => 's',
+ prorettype => 'text', proargtypes => 'oid',
+ prosrc => 'pg_get_function_sqlbody' },
{ oid => '1686', descr => 'list of SQL keywords',
proname => 'pg_get_keywords', procost => '10', prorows => '500',
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 78f230894b..8d58067d03 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -112,11 +112,14 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
Oid protrftypes[1] BKI_DEFAULT(_null_) BKI_LOOKUP(pg_type);
/* procedure source text */
- text prosrc BKI_FORCE_NOT_NULL;
+ text prosrc;
/* secondary procedure info (can be NULL) */
text probin BKI_DEFAULT(_null_);
+ /* pre-parsed SQL function body */
+ pg_node_tree prosqlbody BKI_DEFAULT(_null_);
+
/* procedure-local GUC settings */
text proconfig[1] BKI_DEFAULT(_null_);
@@ -194,6 +197,7 @@ extern ObjectAddress ProcedureCreate(const char *procedureName,
Oid languageValidator,
const char *prosrc,
const char *probin,
+ Node *prosqlbody,
char prokind,
bool security_definer,
bool isLeakProof,
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 1a79540c94..2bb0fb10de 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -65,9 +65,11 @@ extern void interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType);
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index c975a93661..dcb8e18437 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -20,6 +20,21 @@
/* This struct is known only within executor/functions.c */
typedef struct SQLFunctionParseInfo *SQLFunctionParseInfoPtr;
+/*
+ * Data structure needed by the parser callback hooks to resolve parameter
+ * references during parsing of a SQL function's body. This is separate from
+ * SQLFunctionCache since we sometimes do parsing separately from execution.
+ */
+typedef struct SQLFunctionParseInfo
+{
+ char *fname; /* function's name */
+ int nargs; /* number of input arguments */
+ Oid *argtypes; /* resolved types of input arguments */
+ char **argnames; /* names of input arguments; NULL if none */
+ /* Note that argnames[i] can be NULL, if some args are unnamed */
+ Oid collation; /* function's input collation, if known */
+} SQLFunctionParseInfo;
+
extern Datum fmgr_sql(PG_FUNCTION_ARGS);
extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTuple,
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 2bc3cc4ae7..91d7d4d5c6 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -114,6 +114,8 @@ typedef struct PsqlScanStateData
int paren_depth; /* depth of nesting in parentheses */
int xcdepth; /* depth of nesting in slash-star comments */
char *dolqstart; /* current $foo$ quote start string */
+ int identifier_count; /* identifiers since start of statement */
+ int begin_depth; /* depth of begin/end routine body blocks */
/*
* Callback functions provided by the program making use of the lexer,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..dcca0e456b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -317,6 +317,7 @@ typedef enum NodeTag
T_DeleteStmt,
T_UpdateStmt,
T_SelectStmt,
+ T_ReturnStmt,
T_PLAssignStmt,
T_AlterTableStmt,
T_AlterTableCmd,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3a81d4f267..da01c0ccef 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -140,6 +140,8 @@ typedef struct Query
bool hasForUpdate; /* FOR [KEY] UPDATE/SHARE was specified */
bool hasRowSecurity; /* rewriter has applied some RLS policy */
+ bool isReturn; /* is a RETURN statement */
+
List *cteList; /* WITH list (of CommonTableExpr's) */
List *rtable; /* list of range table entries */
@@ -1711,6 +1713,16 @@ typedef struct SetOperationStmt
} SetOperationStmt;
+/*
+ * RETURN statement (inside SQL function body)
+ */
+typedef struct ReturnStmt
+{
+ NodeTag type;
+ Node *returnval;
+} ReturnStmt;
+
+
/* ----------------------
* PL/pgSQL Assignment Statement
*
@@ -2894,6 +2906,7 @@ typedef struct CreateFunctionStmt
List *parameters; /* a list of FunctionParameter */
TypeName *returnType; /* the return type */
List *options; /* a list of DefElem */
+ Node *sql_body;
} CreateFunctionStmt;
typedef enum FunctionParameterMode
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 28083aaac9..ec467b5c25 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -48,6 +48,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("atomic", ATOMIC, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -346,6 +347,7 @@ PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("return", RETURN, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD, AS_LABEL)
PG_KEYWORD("returns", RETURNS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index e5472100a4..49700d151a 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -43,6 +43,7 @@ typedef enum
extern PGDLLIMPORT int log_statement;
extern List *pg_parse_query(const char *query_string);
+extern List *pg_rewrite_query(Query *query);
extern List *pg_analyze_and_rewrite(RawStmt *parsetree,
const char *query_string,
Oid *paramTypes, int numParams,
diff --git a/src/interfaces/ecpg/preproc/ecpg.addons b/src/interfaces/ecpg/preproc/ecpg.addons
index 300381eaad..0441561c52 100644
--- a/src/interfaces/ecpg/preproc/ecpg.addons
+++ b/src/interfaces/ecpg/preproc/ecpg.addons
@@ -87,6 +87,12 @@ ECPG: stmtTransactionStmt block
whenever_action(2);
free($1);
}
+ECPG: toplevel_stmtTransactionStmtLegacy block
+ {
+ fprintf(base_yyout, "{ ECPGtrans(__LINE__, %s, \"%s\");", connection ? connection : "NULL", $1);
+ whenever_action(2);
+ free($1);
+ }
ECPG: stmtViewStmt rule
| ECPGAllocateDescr
{
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer
index 0e4a041393..bcabf88341 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -4,8 +4,8 @@ statements: /*EMPTY*/
| statements statement
;
-statement: ecpgstart at stmt ';' { connection = NULL; }
- | ecpgstart stmt ';'
+statement: ecpgstart at toplevel_stmt ';' { connection = NULL; }
+ | ecpgstart toplevel_stmt ';'
| ecpgstart ECPGVarDeclaration
{
fprintf(base_yyout, "%s", $2);
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index f25a407fdd..ded33cb811 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -255,6 +255,173 @@ SELECT pg_get_functiondef('functest_F_2'::regproc);
(1 row)
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_3a() RETURNS boolean
+ BEGIN ATOMIC
+ RETURN false;
+ END;
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+ERROR: SQL function with unquoted function body cannot have polymorphic arguments
+-- tricky parsing
+CREATE FUNCTION functest_S_15(x int) RETURNS boolean
+LANGUAGE SQL
+BEGIN ATOMIC
+ select case when x % 2 = 0 then true else false end;
+END;
+SELECT functest_S_1('abcd', '2020-01-01');
+ functest_s_1
+--------------
+ t
+(1 row)
+
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+ functest_s_2
+--------------
+ 1
+(1 row)
+
+SELECT functest_S_3();
+ functest_s_3
+--------------
+ f
+(1 row)
+
+SELECT functest_S_10('abcd', '2020-01-01');
+ functest_s_10
+---------------
+ t
+(1 row)
+
+SELECT functest_S_13();
+ functest_s_13
+---------------
+ f
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_1(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN ((a = 'abcd'::text) AND (b > '01-01-2001'::date)) +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_2(a text[])+
+ RETURNS integer +
+ LANGUAGE sql +
+ RETURN ((a)[1])::integer +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+ pg_get_functiondef
+----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN false +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3a'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3a()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ RETURN false; +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+ pg_get_functiondef
+-------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_10(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT ((a = 'abcd'::text) AND (b > '01-01-2001'::date)); +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_13()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT 1; +
+ SELECT false AS bool; +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_15'::regproc);
+ pg_get_functiondef
+--------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_15(x integer)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT +
+ CASE +
+ WHEN ((x % 2) = 0) THEN true +
+ ELSE false +
+ END AS "case"; +
+ END +
+
+(1 row)
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+SELECT functest_S_14();
+ functest_s_14
+---------------
+ 2
+(1 row)
+
+DROP TABLE functest3 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view functestv3
+drop cascades to function functest_s_14()
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
RETURNS int
@@ -292,6 +459,15 @@ CREATE FUNCTION functest_IS_5(x int DEFAULT nextval('functest1'))
RETURNS int
LANGUAGE SQL
AS 'SELECT x';
+CREATE FUNCTION functest_IS_6()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest1');
+CREATE TABLE functest2 (a int, b int);
+CREATE FUNCTION functest_IS_7()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest2);
SELECT r0.routine_name, r1.routine_name
FROM information_schema.routine_routine_usage rru
JOIN information_schema.routines r0 ON r0.specific_name = rru.specific_name
@@ -305,23 +481,29 @@ SELECT routine_name, sequence_name FROM information_schema.routine_sequence_usag
routine_name | sequence_name
---------------+---------------
functest_is_5 | functest1
-(1 row)
+ functest_is_6 | functest1
+(2 rows)
--- currently empty
SELECT routine_name, table_name, column_name FROM information_schema.routine_column_usage;
- routine_name | table_name | column_name
---------------+------------+-------------
-(0 rows)
+ routine_name | table_name | column_name
+---------------+------------+-------------
+ functest_is_7 | functest2 | a
+(1 row)
SELECT routine_name, table_name FROM information_schema.routine_table_usage;
- routine_name | table_name
---------------+------------
-(0 rows)
+ routine_name | table_name
+---------------+------------
+ functest_is_7 | functest2
+(1 row)
DROP FUNCTION functest_IS_4a CASCADE;
NOTICE: drop cascades to function functest_is_4b(integer)
DROP SEQUENCE functest1 CASCADE;
-NOTICE: drop cascades to function functest_is_5(integer)
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to function functest_is_5(integer)
+drop cascades to function functest_is_6()
+DROP TABLE functest2 CASCADE;
+NOTICE: drop cascades to function functest_is_7()
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
@@ -340,6 +522,49 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
ERROR: cannot change routine kind
DETAIL: "functest1" is a function.
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+SELECT * FROM functest_sri1();
+ functest_sri1
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+SELECT * FROM functest_sri2();
+ functest_sri2
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
$$ SELECT a + 1 $$;
@@ -398,7 +623,7 @@ SELECT * FROM voidtest5(3);
-- Cleanup
DROP SCHEMA temp_func_test CASCADE;
-NOTICE: drop cascades to 21 other objects
+NOTICE: drop cascades to 30 other objects
DETAIL: drop cascades to function functest_a_1(text,date)
drop cascades to function functest_a_2(text[])
drop cascades to function functest_a_3()
@@ -414,7 +639,16 @@ drop cascades to function functest_f_1(integer)
drop cascades to function functest_f_2(integer)
drop cascades to function functest_f_3(integer)
drop cascades to function functest_f_4(integer)
+drop cascades to function functest_s_1(text,date)
+drop cascades to function functest_s_2(text[])
+drop cascades to function functest_s_3()
+drop cascades to function functest_s_3a()
+drop cascades to function functest_s_10(text,date)
+drop cascades to function functest_s_13()
+drop cascades to function functest_s_15(integer)
drop cascades to function functest_b_2(bigint)
+drop cascades to function functest_sri1()
+drop cascades to function functest_sri2()
drop cascades to function voidtest1(integer)
drop cascades to function voidtest2(integer,integer)
drop cascades to function voidtest3(integer)
diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out
index 3838fa2324..d45575561e 100644
--- a/src/test/regress/expected/create_procedure.out
+++ b/src/test/regress/expected/create_procedure.out
@@ -65,6 +65,48 @@ SELECT * FROM cp_test ORDER BY b COLLATE "C";
1 | xyzzy
(3 rows)
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+\df ptest1s
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+---------+------------------+---------------------+------
+ public | ptest1s | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest1s'::regproc);
+ pg_get_functiondef
+----------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest1s(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ INSERT INTO cp_test (a, b) +
+ VALUES (1, ptest1s.x); +
+ END +
+
+(1 row)
+
+CALL ptest1s('b');
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+ a | b
+---+-------
+ 1 | 0
+ 1 | a
+ 1 | b
+ 1 | xyzzy
+(4 rows)
+
+-- utitlity functions currently not supported here
+CREATE PROCEDURE ptestx()
+LANGUAGE SQL
+BEGIN ATOMIC
+ CREATE TABLE x (a int);
+END;
+ERROR: CREATE TABLE is not yet supported in unquoted SQL function body
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -146,6 +188,28 @@ AS $$
SELECT a = b;
$$;
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+\df ptest8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+--------+------------------+---------------------+------
+ public | ptest8 | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest8'::regproc);
+ pg_get_functiondef
+---------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest8(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ END +
+
+(1 row)
+
+CALL ptest8('');
-- OUT parameters
CREATE PROCEDURE ptest9(OUT a int)
LANGUAGE SQL
@@ -214,6 +278,7 @@ ALTER ROUTINE ptest1a RENAME TO ptest1;
DROP ROUTINE cp_testfunc1(int);
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
DROP USER regress_cp_user1;
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
index 549b34b4b2..06b749fbb1 100644
--- a/src/test/regress/sql/create_function_3.sql
+++ b/src/test/regress/sql/create_function_3.sql
@@ -153,6 +153,73 @@ CREATE FUNCTION functest_F_4(int) RETURNS bool LANGUAGE 'sql'
SELECT pg_get_functiondef('functest_F_2'::regproc);
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_3a() RETURNS boolean
+ BEGIN ATOMIC
+ RETURN false;
+ END;
+
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+
+-- tricky parsing
+CREATE FUNCTION functest_S_15(x int) RETURNS boolean
+LANGUAGE SQL
+BEGIN ATOMIC
+ select case when x % 2 = 0 then true else false end;
+END;
+
+SELECT functest_S_1('abcd', '2020-01-01');
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+SELECT functest_S_3();
+
+SELECT functest_S_10('abcd', '2020-01-01');
+SELECT functest_S_13();
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+SELECT pg_get_functiondef('functest_S_3a'::regproc);
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+SELECT pg_get_functiondef('functest_S_15'::regproc);
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+
+SELECT functest_S_14();
+
+DROP TABLE functest3 CASCADE;
+
+
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
@@ -188,17 +255,29 @@ CREATE FUNCTION functest_IS_5(x int DEFAULT nextval('functest1'))
LANGUAGE SQL
AS 'SELECT x';
+CREATE FUNCTION functest_IS_6()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest1');
+
+CREATE TABLE functest2 (a int, b int);
+
+CREATE FUNCTION functest_IS_7()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest2);
+
SELECT r0.routine_name, r1.routine_name
FROM information_schema.routine_routine_usage rru
JOIN information_schema.routines r0 ON r0.specific_name = rru.specific_name
JOIN information_schema.routines r1 ON r1.specific_name = rru.routine_name;
SELECT routine_name, sequence_name FROM information_schema.routine_sequence_usage;
--- currently empty
SELECT routine_name, table_name, column_name FROM information_schema.routine_column_usage;
SELECT routine_name, table_name FROM information_schema.routine_table_usage;
DROP FUNCTION functest_IS_4a CASCADE;
DROP SEQUENCE functest1 CASCADE;
+DROP TABLE functest2 CASCADE;
-- overload
@@ -218,6 +297,29 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+
+SELECT * FROM functest_sri1();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+
+SELECT * FROM functest_sri2();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
diff --git a/src/test/regress/sql/create_procedure.sql b/src/test/regress/sql/create_procedure.sql
index 2ef1c82cea..76f781c0b9 100644
--- a/src/test/regress/sql/create_procedure.sql
+++ b/src/test/regress/sql/create_procedure.sql
@@ -28,6 +28,28 @@ CREATE PROCEDURE ptest1(x text)
SELECT * FROM cp_test ORDER BY b COLLATE "C";
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+
+\df ptest1s
+SELECT pg_get_functiondef('ptest1s'::regproc);
+
+CALL ptest1s('b');
+
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+
+-- utitlity functions currently not supported here
+CREATE PROCEDURE ptestx()
+LANGUAGE SQL
+BEGIN ATOMIC
+ CREATE TABLE x (a int);
+END;
+
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -112,6 +134,16 @@ CREATE PROCEDURE ptest7(a text, b text)
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+
+\df ptest8
+SELECT pg_get_functiondef('ptest8'::regproc);
+CALL ptest8('');
+
+
-- OUT parameters
CREATE PROCEDURE ptest9(OUT a int)
@@ -170,6 +202,7 @@ CREATE USER regress_cp_user1;
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
base-commit: 27ab1981e7c9b8fcbcb143c5f6f706441a52bbc8
--
2.30.2
On Fri, Mar 19, 2021 at 8:49 AM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:
Right. Here is a new patch with that fix added and a small conflict
resolved.
Great.
It seems print_function_sqlbody() is not protected to avoid receiving
a function that hasn't a standard sql body in
src/backend/utils/adt/ruleutils.c:3292, but instead it has an assert
that gets hit with something like this:
CREATE FUNCTION foo() RETURNS int LANGUAGE SQL AS $$ SELECT 1 $$;
SELECT pg_get_function_sqlbody('foo'::regproc);
--
Jaime Casanova
Director de Servicios Profesionales
SYSTEMGUARDS - Consultores de PostgreSQL
On Tue, Mar 23, 2021 at 11:28:55PM -0500, Jaime Casanova wrote:
On Fri, Mar 19, 2021 at 8:49 AM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:Right. Here is a new patch with that fix added and a small conflict
resolved.Great.
It seems print_function_sqlbody() is not protected to avoid receiving
a function that hasn't a standard sql body in
src/backend/utils/adt/ruleutils.c:3292, but instead it has an assert
that gets hit with something like this:CREATE FUNCTION foo() RETURNS int LANGUAGE SQL AS $$ SELECT 1 $$;
SELECT pg_get_function_sqlbody('foo'::regproc);
It would also be good to add a regression test checking that we can't define a
function with both a prosrc and a prosqlbody.
@@ -76,6 +77,7 @@ ProcedureCreate(const char *procedureName,
Oid languageValidator,
const char *prosrc,
const char *probin,
+ Node *prosqlbody,
char prokind,
bool security_definer,
bool isLeakProof,
@@ -119,8 +121,6 @@ ProcedureCreate(const char *procedureName,
/*
* sanity checks
*/
- Assert(PointerIsValid(prosrc));
-
parameterCount = parameterTypes->dim1;
Shouldn't we still assert that we either have a valid procsrc or valid
prosqlbody?
No other comments apart from that!
On 31.03.21 12:12, Julien Rouhaud wrote:
On Tue, Mar 23, 2021 at 11:28:55PM -0500, Jaime Casanova wrote:
On Fri, Mar 19, 2021 at 8:49 AM Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:Right. Here is a new patch with that fix added and a small conflict
resolved.Great.
It seems print_function_sqlbody() is not protected to avoid receiving
a function that hasn't a standard sql body in
src/backend/utils/adt/ruleutils.c:3292, but instead it has an assert
that gets hit with something like this:CREATE FUNCTION foo() RETURNS int LANGUAGE SQL AS $$ SELECT 1 $$;
SELECT pg_get_function_sqlbody('foo'::regproc);
fixed
It would also be good to add a regression test checking that we can't define a
function with both a prosrc and a prosqlbody.
done
@@ -76,6 +77,7 @@ ProcedureCreate(const char *procedureName,
Oid languageValidator,
const char *prosrc,
const char *probin,
+ Node *prosqlbody,
char prokind,
bool security_definer,
bool isLeakProof,
@@ -119,8 +121,6 @@ ProcedureCreate(const char *procedureName,
/*
* sanity checks
*/
- Assert(PointerIsValid(prosrc));
-
parameterCount = parameterTypes->dim1;Shouldn't we still assert that we either have a valid procsrc or valid
prosqlbody?
fixed
New patch attached.
Attachments:
v11-0001-SQL-standard-function-body.patchtext/plain; charset=UTF-8; name=v11-0001-SQL-standard-function-body.patch; x-mac-creator=0; x-mac-type=0Download
From 7292b049b9017e02ffb8bbb830d91f7700e14a76 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Fri, 2 Apr 2021 14:23:14 +0200
Subject: [PATCH v11] SQL-standard function body
This adds support for writing CREATE FUNCTION and CREATE PROCEDURE
statements for language SQL with a function body that conforms to the
SQL standard and is portable to other implementations.
Instead of the PostgreSQL-specific AS $$ string literal $$ syntax,
this allows writing out the SQL statements making up the body
unquoted, either as a single statement:
CREATE FUNCTION add(a integer, b integer) RETURNS integer
LANGUAGE SQL
RETURN a + b;
or as a block
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
BEGIN ATOMIC
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
END;
The function body is parsed at function definition time and stored as
expression nodes in a new pg_proc column prosqlbody. So at run time,
no further parsing is required.
However, this form does not support polymorphic arguments, because
there is no more parse analysis done at call time.
Dependencies between the function and the objects it uses are fully
tracked.
A new RETURN statement is introduced. This can only be used inside
function bodies. Internally, it is treated much like a SELECT
statement.
psql needs some new intelligence to keep track of function body
boundaries so that it doesn't send off statements when it sees
semicolons that are inside a function body.
Also, per SQL standard, LANGUAGE SQL is the default, so it does not
need to be specified anymore.
Discussion: https://www.postgresql.org/message-id/flat/1c11f1eb-f00c-43b7-799d-2d44132c02d7@2ndquadrant.com
---
doc/src/sgml/catalogs.sgml | 10 +
doc/src/sgml/ref/create_function.sgml | 126 ++++++++-
doc/src/sgml/ref/create_procedure.sgml | 62 ++++-
src/backend/catalog/pg_aggregate.c | 1 +
src/backend/catalog/pg_proc.c | 116 +++++---
src/backend/commands/aggregatecmds.c | 2 +
src/backend/commands/functioncmds.c | 130 +++++++--
src/backend/commands/typecmds.c | 4 +
src/backend/executor/functions.c | 79 +++---
src/backend/nodes/copyfuncs.c | 15 +
src/backend/nodes/equalfuncs.c | 13 +
src/backend/nodes/outfuncs.c | 12 +
src/backend/nodes/readfuncs.c | 1 +
src/backend/optimizer/util/clauses.c | 126 ++++++---
src/backend/parser/analyze.c | 35 +++
src/backend/parser/gram.y | 129 +++++++--
src/backend/tcop/postgres.c | 3 +-
src/backend/utils/adt/ruleutils.c | 140 +++++++++-
src/bin/pg_dump/pg_dump.c | 45 ++-
src/bin/psql/describe.c | 15 +-
src/fe_utils/psqlscan.l | 24 +-
src/include/catalog/pg_proc.dat | 4 +
src/include/catalog/pg_proc.h | 6 +-
src/include/commands/defrem.h | 2 +
src/include/executor/functions.h | 15 +
src/include/fe_utils/psqlscan_int.h | 2 +
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 13 +
src/include/parser/kwlist.h | 2 +
src/include/tcop/tcopprot.h | 1 +
src/interfaces/ecpg/preproc/ecpg.addons | 6 +
src/interfaces/ecpg/preproc/ecpg.trailer | 4 +-
.../regress/expected/create_function_3.out | 260 +++++++++++++++++-
.../regress/expected/create_procedure.out | 65 +++++
src/test/regress/sql/create_function_3.sql | 110 +++++++-
src/test/regress/sql/create_procedure.sql | 33 +++
36 files changed, 1399 insertions(+), 213 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index f103d914a6..2656786d1e 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -6002,6 +6002,16 @@ <title><structname>pg_proc</structname> Columns</title>
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>prosqlbody</structfield> <type>pg_node_tree</type>
+ </para>
+ <para>
+ Pre-parsed SQL function body. This will be used for language SQL
+ functions if the body is not specified as a string constant.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>proconfig</structfield> <type>text[]</type>
diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index f1001615f4..10bad6927c 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -38,6 +38,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -262,8 +263,9 @@ <title>Parameters</title>
The name of the language that the function is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -582,6 +584,44 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> function. This can
+ either be a single statement
+<programlisting>
+RETURN <replaceable>expression</replaceable>
+</programlisting>
+ or a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the function body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at function definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at function definition time. This form tracks dependencies between the
+ function and objects used in the function body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling functions. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
@@ -667,6 +707,15 @@ <title>Examples</title>
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
+</programlisting>
+ The same function written in a more SQL-conforming style, using argument
+ names and an unquoted body:
+<programlisting>
+CREATE FUNCTION add(a integer, b integer) RETURNS integer
+ LANGUAGE SQL
+ IMMUTABLE
+ RETURNS NULL ON NULL INPUT
+ RETURN a + b;
</programlisting>
</para>
@@ -797,23 +846,74 @@ <title>Writing <literal>SECURITY DEFINER</literal> Functions Safely</title>
<title>Compatibility</title>
<para>
- A <command>CREATE FUNCTION</command> command is defined in the SQL standard.
- The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. The attributes are not portable, neither are the
- different available languages.
+ A <command>CREATE FUNCTION</command> command is defined in the SQL
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. Conversely, the SQL
+ standard specifies a number of optional features that are not implemented
+ in <productname>PostgreSQL</productname>.
</para>
<para>
- For compatibility with some other database systems,
- <replaceable class="parameter">argmode</replaceable> can be written
- either before or after <replaceable class="parameter">argname</replaceable>.
- But only the first way is standard-compliant.
+ The following are important compatibility issues:
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>OR REPLACE</literal> is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For compatibility with some other database systems, <replaceable
+ class="parameter">argmode</replaceable> can be written either before or
+ after <replaceable class="parameter">argname</replaceable>. But only
+ the first way is standard-compliant.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For parameter defaults, the SQL standard specifies only the syntax with
+ the <literal>DEFAULT</literal> key word. The syntax with
+ <literal>=</literal> is used in T-SQL and Firebird.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The <literal>SETOF</literal> modifier is a PostgreSQL extension.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Only <literal>SQL</literal> is standardized as a language.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ All other attributes except <literal>CALLED ON NULL INPUT</literal> and
+ <literal>RETURNS NULL ON NULL INPUT</literal> are not standardized.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ For the body of <literal>LANGUAGE SQL</literal> functions, the SQL
+ standard only specifies the <replaceable>sql_body</replaceable> form.
+ </para>
+ </listitem>
+ </itemizedlist>
</para>
<para>
- For parameter defaults, the SQL standard specifies only the syntax with
- the <literal>DEFAULT</literal> key word. The syntax
- with <literal>=</literal> is used in T-SQL and Firebird.
+ Simple <literal>LANGUAGE SQL</literal> functions can be written in a way
+ that is both standard-conforming and portable to other implementations.
+ More complex functions using advanced features, optimization attributes, or
+ other languages will necessarily be specific to PostgreSQL in a significant
+ way.
</para>
</refsect1>
diff --git a/doc/src/sgml/ref/create_procedure.sgml b/doc/src/sgml/ref/create_procedure.sgml
index 6dbc012719..e7aace7ff1 100644
--- a/doc/src/sgml/ref/create_procedure.sgml
+++ b/doc/src/sgml/ref/create_procedure.sgml
@@ -29,6 +29,7 @@
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
+ | <replaceable class="parameter">sql_body</replaceable>
} ...
</synopsis>
</refsynopsisdiv>
@@ -167,8 +168,9 @@ <title>Parameters</title>
The name of the language that the procedure is implemented in.
It can be <literal>sql</literal>, <literal>c</literal>,
<literal>internal</literal>, or the name of a user-defined
- procedural language, e.g., <literal>plpgsql</literal>. Enclosing the
- name in single quotes is deprecated and requires matching case.
+ procedural language, e.g., <literal>plpgsql</literal>. The default is
+ <literal>sql</literal>. Enclosing the name in single quotes is
+ deprecated and requires matching case.
</para>
</listitem>
</varlistentry>
@@ -304,6 +306,41 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">sql_body</replaceable></term>
+
+ <listitem>
+ <para>
+ The body of a <literal>LANGUAGE SQL</literal> procedure. This should
+ be a block
+<programlisting>
+BEGIN ATOMIC
+ <replaceable>statement</replaceable>;
+ <replaceable>statement</replaceable>;
+ ...
+ <replaceable>statement</replaceable>;
+END
+</programlisting>
+ </para>
+
+ <para>
+ This is similar to writing the text of the procedure body as a string
+ constant (see <replaceable>definition</replaceable> above), but there
+ are some differences: This form only works for <literal>LANGUAGE
+ SQL</literal>, the string constant form works for all languages. This
+ form is parsed at procedure definition time, the string constant form is
+ parsed at execution time; therefore this form cannot support
+ polymorphic argument types and other constructs that are not resolvable
+ at procedure definition time. This form tracks dependencies between the
+ procedure and objects used in the procedure body, so <literal>DROP
+ ... CASCADE</literal> will work correctly, whereas the form using
+ string literals may leave dangling procedures. Finally, this form is
+ more compatible with the SQL standard and other SQL implementations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
@@ -323,6 +360,7 @@ <title>Notes</title>
<refsect1 id="sql-createprocedure-examples">
<title>Examples</title>
+ <para>
<programlisting>
CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
@@ -330,9 +368,21 @@ <title>Examples</title>
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
$$;
-
+</programlisting>
+ or
+<programlisting>
+CREATE PROCEDURE insert_data(a integer, b integer)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO tbl VALUES (a);
+ INSERT INTO tbl VALUES (b);
+END;
+</programlisting>
+ and call like this:
+<programlisting>
CALL insert_data(1, 2);
</programlisting>
+ </para>
</refsect1>
<refsect1 id="sql-createprocedure-compat">
@@ -340,9 +390,9 @@ <title>Compatibility</title>
<para>
A <command>CREATE PROCEDURE</command> command is defined in the SQL
- standard. The <productname>PostgreSQL</productname> version is similar but
- not fully compatible. For details see
- also <xref linkend="sql-createfunction"/>.
+ standard. The <productname>PostgreSQL</productname> implementation can be
+ used in a compatible way but has many extensions. For details see also
+ <xref linkend="sql-createfunction"/>.
</para>
</refsect1>
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 89f23d0add..5197076c76 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -622,6 +622,7 @@ AggregateCreate(const char *aggName,
InvalidOid, /* no validator */
"aggregate_dummy", /* placeholder (no such proc) */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_AGGREGATE,
false, /* security invoker (currently not
* definable for agg) */
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index e14eee5a19..05de377ba9 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -32,6 +32,7 @@
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_type.h"
#include "tcop/pquery.h"
@@ -76,6 +77,7 @@ ProcedureCreate(const char *procedureName,
Oid languageValidator,
const char *prosrc,
const char *probin,
+ Node *prosqlbody,
char prokind,
bool security_definer,
bool isLeakProof,
@@ -119,7 +121,7 @@ ProcedureCreate(const char *procedureName,
/*
* sanity checks
*/
- Assert(PointerIsValid(prosrc));
+ Assert(PointerIsValid(prosrc) || PointerIsValid(prosqlbody));
parameterCount = parameterTypes->dim1;
if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
@@ -334,11 +336,18 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_protrftypes - 1] = trftypes;
else
nulls[Anum_pg_proc_protrftypes - 1] = true;
- values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ if (prosrc)
+ values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
+ else
+ nulls[Anum_pg_proc_prosrc - 1] = true;
if (probin)
values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin);
else
nulls[Anum_pg_proc_probin - 1] = true;
+ if (prosqlbody)
+ values[Anum_pg_proc_prosqlbody - 1] = CStringGetTextDatum(nodeToString(prosqlbody));
+ else
+ nulls[Anum_pg_proc_prosqlbody - 1] = true;
if (proconfig != PointerGetDatum(NULL))
values[Anum_pg_proc_proconfig - 1] = proconfig;
else
@@ -638,6 +647,10 @@ ProcedureCreate(const char *procedureName,
record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
free_object_addresses(addrs);
+ /* dependency on SQL routine body */
+ if (languageObjectId == SQLlanguageId && prosqlbody)
+ recordDependencyOnExpr(&myself, prosqlbody, NIL, DEPENDENCY_NORMAL);
+
/* dependency on parameter default expressions */
if (parameterDefaults)
recordDependencyOnExpr(&myself, (Node *) parameterDefaults,
@@ -861,61 +874,81 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
/* Postpone body checks if !check_function_bodies */
if (check_function_bodies)
{
- tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
- if (isnull)
- elog(ERROR, "null prosrc");
-
- prosrc = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport().
*/
callback_arg.proname = NameStr(proc->proname);
- callback_arg.prosrc = prosrc;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_function_parse_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
- /*
- * We can't do full prechecking of the function definition if there
- * are any polymorphic input types, because actual datatypes of
- * expression results will be unresolvable. The check will be done at
- * runtime instead.
- *
- * We can run the text through the raw parser though; this will at
- * least catch silly syntactic errors.
- */
- raw_parsetree_list = pg_parse_query(prosrc);
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ {
+ Node *n;
- if (!haspolyarg)
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosqlbody, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc and prosqlbody");
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = castNode(List, n);
+ else
+ querytree_list = list_make1(list_make1(n));
+ }
+ else
{
+ prosrc = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = prosrc;
+
/*
- * OK to do full precheck: analyze and rewrite the queries, then
- * verify the result type.
+ * We can't do full prechecking of the function definition if there
+ * are any polymorphic input types, because actual datatypes of
+ * expression results will be unresolvable. The check will be done at
+ * runtime instead.
+ *
+ * We can run the text through the raw parser though; this will at
+ * least catch silly syntactic errors.
*/
- SQLFunctionParseInfoPtr pinfo;
- Oid rettype;
- TupleDesc rettupdesc;
-
- /* But first, set up parameter information */
- pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
+ raw_parsetree_list = pg_parse_query(prosrc);
- querytree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (!haspolyarg)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *querytree_sublist;
-
- querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
- prosrc,
- (ParserSetupHook) sql_fn_parser_setup,
- pinfo,
- NULL);
- querytree_list = lappend(querytree_list,
- querytree_sublist);
+ /*
+ * OK to do full precheck: analyze and rewrite the queries, then
+ * verify the result type.
+ */
+ SQLFunctionParseInfoPtr pinfo;
+
+ /* But first, set up parameter information */
+ pinfo = prepare_sql_fn_parse_info(tuple, NULL, InvalidOid);
+
+ querytree_list = NIL;
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *querytree_sublist;
+
+ querytree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ prosrc,
+ (ParserSetupHook) sql_fn_parser_setup,
+ pinfo,
+ NULL);
+ querytree_list = lappend(querytree_list,
+ querytree_sublist);
+ }
}
+ }
+
+ if (!haspolyarg)
+ {
+ Oid rettype;
+ TupleDesc rettupdesc;
check_sql_fn_statements(querytree_list);
@@ -968,6 +1001,9 @@ function_parse_error_transpose(const char *prosrc)
int newerrposition;
const char *queryText;
+ if (!prosrc)
+ return false;
+
/*
* Nothing to do unless we are dealing with a syntax error that has a
* cursor position.
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 69c50ac087..046cf2df08 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -312,9 +312,11 @@ DefineAggregate(ParseState *pstate,
InvalidOid,
OBJECT_AGGREGATE,
¶meterTypes,
+ NULL,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ NULL,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 7a4e104623..a2235f27e9 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -53,15 +53,18 @@
#include "commands/proclang.h"
#include "executor/execdesc.h"
#include "executor/executor.h"
+#include "executor/functions.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_func.h"
#include "parser/parse_type.h"
#include "pgstat.h"
+#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
@@ -186,9 +189,11 @@ interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType)
@@ -283,7 +288,11 @@ interpret_function_parameter_list(ParseState *pstate,
/* handle input parameters */
if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
+ {
isinput = true;
+ if (parameterTypes_list)
+ *parameterTypes_list = lappend_oid(*parameterTypes_list, toid);
+ }
/* handle signature parameters */
if (fp->mode == FUNC_PARAM_IN || fp->mode == FUNC_PARAM_INOUT ||
@@ -372,6 +381,9 @@ interpret_function_parameter_list(ParseState *pstate,
have_names = true;
}
+ if (inParameterNames_list)
+ *inParameterNames_list = lappend(*inParameterNames_list, makeString(fp->name ? fp->name : pstrdup("")));
+
if (fp->defexpr)
{
Node *def;
@@ -786,28 +798,10 @@ compute_function_attributes(ParseState *pstate,
defel->defname);
}
- /* process required items */
if (as_item)
*as = (List *) as_item->arg;
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no function body specified")));
- *as = NIL; /* keep compiler quiet */
- }
-
if (language_item)
*language = strVal(language_item->arg);
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("no language specified")));
- *language = NULL; /* keep compiler quiet */
- }
-
- /* process optional items */
if (transform_item)
*transform = transform_item->arg;
if (windowfunc_item)
@@ -856,10 +850,21 @@ compute_function_attributes(ParseState *pstate,
*/
static void
interpret_AS_clause(Oid languageOid, const char *languageName,
- char *funcname, List *as,
- char **prosrc_str_p, char **probin_str_p)
+ char *funcname, List *as, Node *sql_body_in,
+ List *parameterTypes, List *inParameterNames,
+ char **prosrc_str_p, char **probin_str_p, Node **sql_body_out)
{
- Assert(as != NIL);
+ if (sql_body_in && as)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("duplicate function body specified")));
+
+ if (sql_body_in && languageOid != SQLlanguageId)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("inline SQL function body only valid for language SQL")));
+
+ *sql_body_out = NULL;
if (languageOid == ClanguageId)
{
@@ -881,6 +886,76 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
*prosrc_str_p = funcname;
}
}
+ else if (sql_body_in)
+ {
+ SQLFunctionParseInfoPtr pinfo;
+
+ pinfo = (SQLFunctionParseInfoPtr) palloc0(sizeof(SQLFunctionParseInfo));
+
+ pinfo->fname = funcname;
+ pinfo->nargs = list_length(parameterTypes);
+ pinfo->argtypes = (Oid *) palloc(pinfo->nargs * sizeof(Oid));
+ pinfo->argnames = (char **) palloc(pinfo->nargs * sizeof(char *));
+ for (int i = 0; i < list_length(parameterTypes); i++)
+ {
+ char *s = strVal(list_nth(inParameterNames, i));
+
+ pinfo->argtypes[i] = list_nth_oid(parameterTypes, i);
+ if (IsPolymorphicType(pinfo->argtypes[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("SQL function with unquoted function body cannot have polymorphic arguments")));
+
+ if (s[0] != '\0')
+ pinfo->argnames[i] = s;
+ else
+ pinfo->argnames[i] = NULL;
+ }
+
+ if (IsA(sql_body_in, List))
+ {
+ List *stmts = linitial_node(List, castNode(List, sql_body_in));
+ ListCell *lc;
+ List *transformed_stmts = NIL;
+
+ foreach(lc, stmts)
+ {
+ Node *stmt = lfirst(lc);
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, stmt);
+ if (q->commandType == CMD_UTILITY)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("%s is not yet supported in unquoted SQL function body",
+ GetCommandTagName(CreateCommandTag(q->utilityStmt))));
+ transformed_stmts = lappend(transformed_stmts, q);
+ free_parsestate(pstate);
+ }
+
+ *sql_body_out = (Node *) list_make1(transformed_stmts);
+ }
+ else
+ {
+ Query *q;
+ ParseState *pstate = make_parsestate(NULL);
+
+ sql_fn_parser_setup(pstate, pinfo);
+ q = transformStmt(pstate, sql_body_in);
+ if (q->commandType == CMD_UTILITY)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("%s is not yet supported in unquoted SQL function body",
+ GetCommandTagName(CreateCommandTag(q->utilityStmt))));
+
+ *sql_body_out = (Node *) q;
+ }
+
+ *probin_str_p = NULL;
+ *prosrc_str_p = NULL;
+ }
else
{
/* Everything else wants the given string in prosrc. */
@@ -919,6 +994,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
{
char *probin_str;
char *prosrc_str;
+ Node *prosqlbody;
Oid prorettype;
bool returnsSet;
char *language;
@@ -929,9 +1005,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
Oid namespaceId;
AclResult aclresult;
oidvector *parameterTypes;
+ List *parameterTypes_list = NIL;
ArrayType *allParameterTypes;
ArrayType *parameterModes;
ArrayType *parameterNames;
+ List *inParameterNames_list = NIL;
List *parameterDefaults;
Oid variadicArgType;
List *trftypes_list = NIL;
@@ -962,6 +1040,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
get_namespace_name(namespaceId));
/* Set default attributes */
+ as_clause = NULL;
+ language = "sql";
isWindowFunc = false;
isStrict = false;
security = false;
@@ -1053,9 +1133,11 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageOid,
stmt->is_procedure ? OBJECT_PROCEDURE : OBJECT_FUNCTION,
¶meterTypes,
+ ¶meterTypes_list,
&allParameterTypes,
¶meterModes,
¶meterNames,
+ &inParameterNames_list,
¶meterDefaults,
&variadicArgType,
&requiredResultType);
@@ -1112,8 +1194,9 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
trftypes = NULL;
}
- interpret_AS_clause(languageOid, language, funcname, as_clause,
- &prosrc_str, &probin_str);
+ interpret_AS_clause(languageOid, language, funcname, as_clause, stmt->sql_body,
+ parameterTypes_list, inParameterNames_list,
+ &prosrc_str, &probin_str, &prosqlbody);
/*
* Set default values for COST and ROWS depending on other parameters;
@@ -1155,6 +1238,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
languageValidator,
prosrc_str, /* converted to text later */
probin_str, /* converted to text later */
+ prosqlbody,
stmt->is_procedure ? PROKIND_PROCEDURE : (isWindowFunc ? PROKIND_WINDOW : PROKIND_FUNCTION),
security,
isLeakProof,
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 76218fb47e..e975508ffa 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1775,6 +1775,7 @@ makeRangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR, /* language validator */
prosrc[i], /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
@@ -1839,6 +1840,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR,
"multirange_constructor0", /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
@@ -1882,6 +1884,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR,
"multirange_constructor1", /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
@@ -1922,6 +1925,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
F_FMGR_INTERNAL_VALIDATOR,
"multirange_constructor2", /* prosrc */
NULL, /* probin */
+ NULL, /* prosqlbody */
PROKIND_FUNCTION,
false, /* security_definer */
false, /* leakproof */
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 7bb752ace3..642683843e 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -26,6 +26,7 @@
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_func.h"
+#include "rewrite/rewriteHandler.h"
#include "storage/proc.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
@@ -128,21 +129,6 @@ typedef struct
typedef SQLFunctionCache *SQLFunctionCachePtr;
-/*
- * Data structure needed by the parser callback hooks to resolve parameter
- * references during parsing of a SQL function's body. This is separate from
- * SQLFunctionCache since we sometimes do parsing separately from execution.
- */
-typedef struct SQLFunctionParseInfo
-{
- char *fname; /* function's name */
- int nargs; /* number of input arguments */
- Oid *argtypes; /* resolved types of input arguments */
- char **argnames; /* names of input arguments; NULL if none */
- /* Note that argnames[i] can be NULL, if some args are unnamed */
- Oid collation; /* function's input collation, if known */
-} SQLFunctionParseInfo;
-
/* non-export function prototypes */
static Node *sql_fn_param_ref(ParseState *pstate, ParamRef *pref);
@@ -607,7 +593,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
HeapTuple procedureTuple;
Form_pg_proc procedureStruct;
SQLFunctionCachePtr fcache;
- List *raw_parsetree_list;
List *queryTree_list;
List *resulttlist;
ListCell *lc;
@@ -682,9 +667,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
procedureTuple,
Anum_pg_proc_prosrc,
&isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", foid);
- fcache->src = TextDatumGetCString(tmp);
/*
* Parse and rewrite the queries in the function text. Use sublists to
@@ -695,20 +677,55 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
* but we'll not worry about it until the module is rewritten to use
* plancache.c.
*/
- raw_parsetree_list = pg_parse_query(fcache->src);
-
queryTree_list = NIL;
- foreach(lc, raw_parsetree_list)
+ if (isNull)
{
- RawStmt *parsetree = lfirst_node(RawStmt, lc);
- List *queryTree_sublist;
-
- queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
- fcache->src,
- (ParserSetupHook) sql_fn_parser_setup,
- fcache->pinfo,
- NULL);
- queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ Node *n;
+ List *stored_query_list;
+
+ tmp = SysCacheGetAttr(PROCOID,
+ procedureTuple,
+ Anum_pg_proc_prosqlbody,
+ &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", foid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ stored_query_list = linitial_node(List, castNode(List, n));
+ else
+ stored_query_list = list_make1(n);
+
+ foreach(lc, stored_query_list)
+ {
+ Query *parsetree = lfirst_node(Query, lc);
+ List *queryTree_sublist;
+
+ AcquireRewriteLocks(parsetree, true, false);
+ queryTree_sublist = pg_rewrite_query(parsetree);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ }
+ }
+ else
+ {
+ List *raw_parsetree_list;
+
+ fcache->src = TextDatumGetCString(tmp);
+
+ raw_parsetree_list = pg_parse_query(fcache->src);
+
+ foreach(lc, raw_parsetree_list)
+ {
+ RawStmt *parsetree = lfirst_node(RawStmt, lc);
+ List *queryTree_sublist;
+
+ queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
+ fcache->src,
+ (ParserSetupHook) sql_fn_parser_setup,
+ fcache->pinfo,
+ NULL);
+ queryTree_list = lappend(queryTree_list, queryTree_sublist);
+ }
}
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ad729d10a8..fcc5ebb206 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3171,6 +3171,7 @@ _copyQuery(const Query *from)
COPY_SCALAR_FIELD(hasModifyingCTE);
COPY_SCALAR_FIELD(hasForUpdate);
COPY_SCALAR_FIELD(hasRowSecurity);
+ COPY_SCALAR_FIELD(isReturn);
COPY_NODE_FIELD(cteList);
COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(jointree);
@@ -3301,6 +3302,16 @@ _copySetOperationStmt(const SetOperationStmt *from)
return newnode;
}
+static ReturnStmt *
+_copyReturnStmt(const ReturnStmt *from)
+{
+ ReturnStmt *newnode = makeNode(ReturnStmt);
+
+ COPY_NODE_FIELD(returnval);
+
+ return newnode;
+}
+
static PLAssignStmt *
_copyPLAssignStmt(const PLAssignStmt *from)
{
@@ -3684,6 +3695,7 @@ _copyCreateFunctionStmt(const CreateFunctionStmt *from)
COPY_NODE_FIELD(parameters);
COPY_NODE_FIELD(returnType);
COPY_NODE_FIELD(options);
+ COPY_NODE_FIELD(sql_body);
return newnode;
}
@@ -5344,6 +5356,9 @@ copyObjectImpl(const void *from)
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
+ case T_ReturnStmt:
+ retval = _copyReturnStmt(from);
+ break;
case T_PLAssignStmt:
retval = _copyPLAssignStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index f6b37af0ec..936365e09a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -970,6 +970,7 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_SCALAR_FIELD(hasModifyingCTE);
COMPARE_SCALAR_FIELD(hasForUpdate);
COMPARE_SCALAR_FIELD(hasRowSecurity);
+ COMPARE_SCALAR_FIELD(isReturn);
COMPARE_NODE_FIELD(cteList);
COMPARE_NODE_FIELD(rtable);
COMPARE_NODE_FIELD(jointree);
@@ -1088,6 +1089,14 @@ _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
return true;
}
+static bool
+_equalReturnStmt(const ReturnStmt *a, const ReturnStmt *b)
+{
+ COMPARE_NODE_FIELD(returnval);
+
+ return true;
+}
+
static bool
_equalPLAssignStmt(const PLAssignStmt *a, const PLAssignStmt *b)
{
@@ -1406,6 +1415,7 @@ _equalCreateFunctionStmt(const CreateFunctionStmt *a, const CreateFunctionStmt *
COMPARE_NODE_FIELD(parameters);
COMPARE_NODE_FIELD(returnType);
COMPARE_NODE_FIELD(options);
+ COMPARE_NODE_FIELD(sql_body);
return true;
}
@@ -3334,6 +3344,9 @@ equal(const void *a, const void *b)
case T_SetOperationStmt:
retval = _equalSetOperationStmt(a, b);
break;
+ case T_ReturnStmt:
+ retval = _equalReturnStmt(a, b);
+ break;
case T_PLAssignStmt:
retval = _equalPLAssignStmt(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index fa8f65fbc5..4a8dc2d86d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2835,6 +2835,14 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
WRITE_NODE_FIELD(rarg);
}
+static void
+_outReturnStmt(StringInfo str, const ReturnStmt *node)
+{
+ WRITE_NODE_TYPE("RETURN");
+
+ WRITE_NODE_FIELD(returnval);
+}
+
static void
_outPLAssignStmt(StringInfo str, const PLAssignStmt *node)
{
@@ -3047,6 +3055,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_BOOL_FIELD(hasModifyingCTE);
WRITE_BOOL_FIELD(hasForUpdate);
WRITE_BOOL_FIELD(hasRowSecurity);
+ WRITE_BOOL_FIELD(isReturn);
WRITE_NODE_FIELD(cteList);
WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(jointree);
@@ -4337,6 +4346,9 @@ outNode(StringInfo str, const void *obj)
case T_SelectStmt:
_outSelectStmt(str, obj);
break;
+ case T_ReturnStmt:
+ _outReturnStmt(str, obj);
+ break;
case T_PLAssignStmt:
_outPLAssignStmt(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ecce23b747..9924727851 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -263,6 +263,7 @@ _readQuery(void)
READ_BOOL_FIELD(hasModifyingCTE);
READ_BOOL_FIELD(hasForUpdate);
READ_BOOL_FIELD(hasRowSecurity);
+ READ_BOOL_FIELD(isReturn);
READ_NODE_FIELD(cteList);
READ_NODE_FIELD(rtable);
READ_NODE_FIELD(jointree);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index bea1cc4d67..9a6e3dab83 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4253,27 +4253,47 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
- /* Fetch the function body */
- tmp = SysCacheGetAttr(PROCOID,
- func_tuple,
- Anum_pg_proc_prosrc,
- &isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", funcid);
- src = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = src;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ {
+ Node *n;
+ List *querytree_list;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", funcid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = linitial_node(List, castNode(List, n));
+ else
+ querytree_list = list_make1(n);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We need a
* dummy FuncExpr node containing the already-simplified arguments to pass
@@ -4317,6 +4337,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
querytree = transformTopLevelStmt(pstate, linitial(raw_parsetree_list));
free_parsestate(pstate);
+ }
/*
* The single command must be a simple "SELECT expression".
@@ -4573,12 +4594,15 @@ sql_inline_error_callback(void *arg)
int syntaxerrposition;
/* If it's a syntax error, convert to internal syntax error report */
- syntaxerrposition = geterrposition();
- if (syntaxerrposition > 0)
+ if (callback_arg->prosrc)
{
- errposition(0);
- internalerrposition(syntaxerrposition);
- internalerrquery(callback_arg->prosrc);
+ syntaxerrposition = geterrposition();
+ if (syntaxerrposition > 0)
+ {
+ errposition(0);
+ internalerrposition(syntaxerrposition);
+ internalerrquery(callback_arg->prosrc);
+ }
}
errcontext("SQL function \"%s\" during inlining", callback_arg->proname);
@@ -4690,7 +4714,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
Oid func_oid;
HeapTuple func_tuple;
Form_pg_proc funcform;
- char *src;
Datum tmp;
bool isNull;
MemoryContext oldcxt;
@@ -4799,27 +4822,53 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
- /* Fetch the function body */
- tmp = SysCacheGetAttr(PROCOID,
- func_tuple,
- Anum_pg_proc_prosrc,
- &isNull);
- if (isNull)
- elog(ERROR, "null prosrc for function %u", func_oid);
- src = TextDatumGetCString(tmp);
-
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = src;
+ callback_arg.prosrc = NULL;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ {
+ Node *n;
+
+ tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc and prosqlbody for function %u", func_oid);
+
+ n = stringToNode(TextDatumGetCString(tmp));
+ if (IsA(n, List))
+ querytree_list = linitial_node(List, castNode(List, n));
+ else
+ querytree_list = list_make1(n);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+
+ querytree_list = pg_rewrite_query(querytree);
+ if (list_length(querytree_list) != 1)
+ goto fail;
+ querytree = linitial(querytree_list);
+ }
+ else
+ {
+ char *src;
+
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We can
* use the FuncExpr just created as the input for
@@ -4829,18 +4878,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
(Node *) fexpr,
fexpr->inputcollid);
- /*
- * Also resolve the actual function result tupdesc, if composite. If the
- * function is just declared to return RECORD, dig the info out of the AS
- * clause.
- */
- functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
- if (functypclass == TYPEFUNC_RECORD)
- rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
- rtfunc->funccoltypes,
- rtfunc->funccoltypmods,
- rtfunc->funccolcollations);
-
/*
* Parse, analyze, and rewrite (unlike inline_function(), we can't skip
* rewriting here). We can fail as soon as we find more than one query,
@@ -4857,6 +4894,19 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
if (list_length(querytree_list) != 1)
goto fail;
querytree = linitial(querytree_list);
+ }
+
+ /*
+ * Also resolve the actual function result tupdesc, if composite. If the
+ * function is just declared to return RECORD, dig the info out of the AS
+ * clause.
+ */
+ functypclass = get_expr_result_type((Node *) fexpr, NULL, &rettupdesc);
+ if (functypclass == TYPEFUNC_RECORD)
+ rettupdesc = BuildDescFromLists(rtfunc->funccolnames,
+ rtfunc->funccoltypes,
+ rtfunc->funccoltypmods,
+ rtfunc->funccolcollations);
/*
* The single command must be a plain SELECT.
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 5de1307570..47ea473f8a 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -68,6 +68,7 @@ static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
bool isTopLevel, List **targetlist);
static void determineRecursiveColTypes(ParseState *pstate,
Node *larg, List *nrtargetlist);
+static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList);
static List *transformUpdateTargetList(ParseState *pstate,
@@ -308,6 +309,10 @@ transformStmt(ParseState *pstate, Node *parseTree)
}
break;
+ case T_ReturnStmt:
+ result = transformReturnStmt(pstate, (ReturnStmt *) parseTree);
+ break;
+
case T_PLAssignStmt:
result = transformPLAssignStmt(pstate,
(PLAssignStmt *) parseTree);
@@ -2229,6 +2234,36 @@ determineRecursiveColTypes(ParseState *pstate, Node *larg, List *nrtargetlist)
}
+/*
+ * transformReturnStmt -
+ * transforms a return statement
+ */
+static Query *
+transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+
+ qry->commandType = CMD_SELECT;
+ qry->isReturn = true;
+
+ qry->targetList = list_make1(makeTargetEntry((Expr *) transformExpr(pstate, stmt->returnval, EXPR_KIND_SELECT_TARGET),
+ 1, NULL, false));
+
+ if (pstate->p_resolve_unknowns)
+ resolveTargetListUnknowns(pstate, qry->targetList);
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasAggs = pstate->p_hasAggs;
+
+ assign_query_collations(pstate, qry);
+
+ return qry;
+}
+
+
/*
* transformUpdateStmt -
* transforms an update statement
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8b1bad0d79..aae89ab736 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -262,7 +262,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct GroupClause *groupclause;
}
-%type <node> stmt schema_stmt
+%type <node> stmt toplevel_stmt schema_stmt routine_body_stmt
AlterEventTrigStmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
@@ -289,9 +289,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
- RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
+ RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt
RuleActionStmt RuleActionStmtOrEmpty RuleStmt
- SecLabelStmt SelectStmt TransactionStmt TruncateStmt
+ SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
UnlistenStmt UpdateStmt VacuumStmt
VariableResetStmt VariableSetStmt VariableShowStmt
ViewStmt CheckPointStmt CreateConversionStmt
@@ -395,14 +395,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> vacuum_relation
%type <selectlimit> opt_select_limit select_limit limit_clause
-%type <list> parse_toplevel stmtmulti
+%type <list> parse_toplevel stmtmulti routine_body_stmt_list
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
reloptions opt_reloptions
OptWith opt_definition func_args func_args_list
func_args_with_defaults func_args_with_defaults_list
aggr_args aggr_args_list
- func_as createfunc_opt_list alterfunc_opt_list
+ func_as createfunc_opt_list opt_createfunc_opt_list alterfunc_opt_list
old_aggr_definition old_aggr_list
oper_argtypes RuleActionList RuleActionMulti
opt_column_list columnList opt_name_list
@@ -428,6 +428,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
vacuum_relation_list opt_vacuum_relation_list
drop_option_list
+%type <node> opt_routine_body
%type <groupclause> group_clause
%type <list> group_by_list
%type <node> group_by_item empty_grouping_set rollup_clause cube_clause
@@ -637,7 +638,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATOMIC ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BREADTH BY
@@ -699,7 +700,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING
REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
- RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
+ RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
@@ -869,7 +870,7 @@ parse_toplevel:
* we'd get -1 for the location in such cases.
* We also take care to discard empty statements entirely.
*/
-stmtmulti: stmtmulti ';' stmt
+stmtmulti: stmtmulti ';' toplevel_stmt
{
if ($1 != NIL)
{
@@ -881,7 +882,7 @@ stmtmulti: stmtmulti ';' stmt
else
$$ = $1;
}
- | stmt
+ | toplevel_stmt
{
if ($1 != NULL)
$$ = list_make1(makeRawStmt($1, 0));
@@ -890,7 +891,16 @@ stmtmulti: stmtmulti ';' stmt
}
;
-stmt :
+/*
+ * toplevel_stmt includes BEGIN and END. stmt does not include them, because
+ * those words have different meanings in function bodys.
+ */
+toplevel_stmt:
+ stmt
+ | TransactionStmtLegacy
+ ;
+
+stmt:
AlterEventTrigStmt
| AlterDatabaseStmt
| AlterDatabaseSetStmt
@@ -7477,7 +7487,7 @@ opt_nulls_order: NULLS_LA FIRST_P { $$ = SORTBY_NULLS_FIRST; }
CreateFunctionStmt:
CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS func_return createfunc_opt_list
+ RETURNS func_return opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7486,10 +7496,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = $7;
n->options = $8;
+ n->sql_body = $9;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- RETURNS TABLE '(' table_func_column_list ')' createfunc_opt_list
+ RETURNS TABLE '(' table_func_column_list ')' opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7499,10 +7510,11 @@ CreateFunctionStmt:
n->returnType = TableFuncTypeName($9);
n->returnType->location = @7;
n->options = $11;
+ n->sql_body = $12;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = false;
@@ -7511,10 +7523,11 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
| CREATE opt_or_replace PROCEDURE func_name func_args_with_defaults
- createfunc_opt_list
+ opt_createfunc_opt_list opt_routine_body
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->is_procedure = true;
@@ -7523,6 +7536,7 @@ CreateFunctionStmt:
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
+ n->sql_body = $7;
$$ = (Node *)n;
}
;
@@ -7833,6 +7847,11 @@ aggregate_with_argtypes_list:
{ $$ = lappend($1, $3); }
;
+opt_createfunc_opt_list:
+ createfunc_opt_list
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
createfunc_opt_list:
/* Must be at least one to prevent conflict */
createfunc_opt_item { $$ = list_make1($1); }
@@ -7944,6 +7963,51 @@ func_as: Sconst { $$ = list_make1(makeString($1)); }
}
;
+ReturnStmt: RETURN a_expr
+ {
+ ReturnStmt *r = makeNode(ReturnStmt);
+ r->returnval = (Node *) $2;
+ $$ = (Node *) r;
+ }
+ ;
+
+opt_routine_body:
+ ReturnStmt
+ {
+ $$ = $1;
+ }
+ | BEGIN_P ATOMIC routine_body_stmt_list END_P
+ {
+ /*
+ * A compound statement is stored as a single-item list
+ * containing the list of statements as its member. That
+ * way, the parse analysis code can tell apart an empty
+ * body from no body at all.
+ */
+ $$ = (Node *) list_make1($3);
+ }
+ | /*EMPTY*/
+ {
+ $$ = NULL;
+ }
+ ;
+
+routine_body_stmt_list:
+ routine_body_stmt_list routine_body_stmt ';'
+ {
+ $$ = lappend($1, $2);
+ }
+ | /*EMPTY*/
+ {
+ $$ = NIL;
+ }
+ ;
+
+routine_body_stmt:
+ stmt
+ | ReturnStmt
+ ;
+
transform_type_list:
FOR TYPE_P Typename { $$ = list_make1($3); }
| transform_type_list ',' FOR TYPE_P Typename { $$ = lappend($1, $5); }
@@ -9877,13 +9941,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | BEGIN_P opt_transaction transaction_mode_list_or_empty
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_BEGIN;
- n->options = $3;
- $$ = (Node *)n;
- }
| START TRANSACTION transaction_mode_list_or_empty
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9899,14 +9956,6 @@ TransactionStmt:
n->chain = $3;
$$ = (Node *)n;
}
- | END_P opt_transaction opt_transaction_chain
- {
- TransactionStmt *n = makeNode(TransactionStmt);
- n->kind = TRANS_STMT_COMMIT;
- n->options = NIL;
- n->chain = $3;
- $$ = (Node *)n;
- }
| ROLLBACK opt_transaction opt_transaction_chain
{
TransactionStmt *n = makeNode(TransactionStmt);
@@ -9973,6 +10022,24 @@ TransactionStmt:
}
;
+TransactionStmtLegacy:
+ BEGIN_P opt_transaction transaction_mode_list_or_empty
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_BEGIN;
+ n->options = $3;
+ $$ = (Node *)n;
+ }
+ | END_P opt_transaction opt_transaction_chain
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_COMMIT;
+ n->options = NIL;
+ n->chain = $3;
+ $$ = (Node *)n;
+ }
+ ;
+
opt_transaction: WORK
| TRANSACTION
| /*EMPTY*/
@@ -15407,6 +15474,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| BACKWARD
@@ -15609,6 +15677,7 @@ unreserved_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| ROLE
@@ -15915,6 +15984,7 @@ bare_label_keyword:
| ASSIGNMENT
| ASYMMETRIC
| AT
+ | ATOMIC
| ATTACH
| ATTRIBUTE
| AUTHORIZATION
@@ -16189,6 +16259,7 @@ bare_label_keyword:
| RESET
| RESTART
| RESTRICT
+ | RETURN
| RETURNS
| REVOKE
| RIGHT
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 2b1b68109f..d3d98ba647 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -188,7 +188,6 @@ static int interactive_getc(void);
static int SocketBackend(StringInfo inBuf);
static int ReadCommand(StringInfo inBuf);
static void forbidden_in_wal_sender(char firstchar);
-static List *pg_rewrite_query(Query *query);
static bool check_log_statement(List *stmt_list);
static int errdetail_execute(List *raw_parsetree_list);
static int errdetail_params(ParamListInfo params);
@@ -707,7 +706,7 @@ pg_analyze_and_rewrite_params(RawStmt *parsetree,
* Note: query must just have come from the parser, because we do not do
* AcquireRewriteLocks() on it.
*/
-static List *
+List *
pg_rewrite_query(Query *query)
{
List *querytree_list;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 254e8f3050..c083bde168 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -172,6 +172,10 @@ typedef struct
List *outer_tlist; /* referent for OUTER_VAR Vars */
List *inner_tlist; /* referent for INNER_VAR Vars */
List *index_tlist; /* referent for INDEX_VAR Vars */
+ /* Special namespace representing a function signature: */
+ char *funcname;
+ int numargs;
+ char **argnames;
} deparse_namespace;
/*
@@ -348,6 +352,7 @@ static int print_function_arguments(StringInfo buf, HeapTuple proctup,
bool print_table_args, bool print_defaults);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
static void print_function_trftypes(StringInfo buf, HeapTuple proctup);
+static void print_function_sqlbody(StringInfo buf, HeapTuple proctup);
static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used);
static void set_deparse_for_query(deparse_namespace *dpns, Query *query,
@@ -2968,6 +2973,13 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
}
/* And finally the function definition ... */
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
+ if (proc->prolang == SQLlanguageId && !isnull)
+ {
+ print_function_sqlbody(&buf, proctup);
+ }
+ else
+ {
appendStringInfoString(&buf, "AS ");
tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
@@ -2999,6 +3011,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, dq.data, dq.len);
appendStringInfoString(&buf, prosrc);
appendBinaryStringInfo(&buf, dq.data, dq.len);
+ }
appendStringInfoChar(&buf, '\n');
@@ -3382,6 +3395,84 @@ pg_get_function_arg_default(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(string_to_text(str));
}
+static void
+print_function_sqlbody(StringInfo buf, HeapTuple proctup)
+{
+ int numargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ deparse_namespace dpns = {0};
+ Datum tmp;
+ bool isnull;
+ Node *n;
+
+ dpns.funcname = pstrdup(NameStr(((Form_pg_proc) GETSTRUCT(proctup))->proname));
+ numargs = get_func_arg_info(proctup,
+ &argtypes, &argnames, &argmodes);
+ dpns.numargs = numargs;
+ dpns.argnames = argnames;
+
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
+ Assert(!isnull);
+ n = stringToNode(TextDatumGetCString(tmp));
+
+ if (IsA(n, List))
+ {
+ List *stmts;
+ ListCell *lc;
+
+ stmts = linitial(castNode(List, n));
+
+ appendStringInfoString(buf, "BEGIN ATOMIC\n");
+
+ foreach(lc, stmts)
+ {
+ Query *query = lfirst_node(Query, lc);
+
+ get_query_def(query, buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ appendStringInfoChar(buf, ';');
+ appendStringInfoChar(buf, '\n');
+ }
+
+ appendStringInfoString(buf, "END");
+ }
+ else
+ {
+ get_query_def(castNode(Query, n), buf, list_make1(&dpns), NULL, PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
+ appendStringInfoChar(buf, '\n');
+ }
+}
+
+Datum
+pg_get_function_sqlbody(PG_FUNCTION_ARGS)
+{
+ Oid funcid = PG_GETARG_OID(0);
+ StringInfoData buf;
+ HeapTuple proctup;
+ bool isnull;
+
+ initStringInfo(&buf);
+
+ /* Look up the function */
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ PG_RETURN_NULL();
+
+ SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
+ if (isnull)
+ {
+ ReleaseSysCache(proctup);
+ PG_RETURN_NULL();
+ }
+
+ print_function_sqlbody(&buf, proctup);
+
+ ReleaseSysCache(proctup);
+
+ PG_RETURN_TEXT_P(cstring_to_text(buf.data));
+}
+
/*
* deparse_expression - General utility for deparsing expressions
@@ -5637,7 +5728,10 @@ get_basic_select_query(Query *query, deparse_context *context,
/*
* Build up the query string - first we say SELECT
*/
- appendStringInfoString(buf, "SELECT");
+ if (query->isReturn)
+ appendStringInfoString(buf, "RETURN");
+ else
+ appendStringInfoString(buf, "SELECT");
/* Add the DISTINCT clause if given */
if (query->distinctClause != NIL)
@@ -7771,6 +7865,50 @@ get_parameter(Param *param, deparse_context *context)
return;
}
+ /*
+ * If it's an external parameter, see if the outermost namespace provides
+ * function argument names.
+ */
+ if (param->paramkind == PARAM_EXTERN)
+ {
+ dpns = lfirst(list_tail(context->namespaces));
+ if (dpns->argnames)
+ {
+ char *argname = dpns->argnames[param->paramid - 1];
+
+ if (argname)
+ {
+ bool should_qualify = false;
+ ListCell *lc;
+
+ /*
+ * Qualify the parameter name if there are any other deparse
+ * namespaces with range tables. This avoids qualifying in
+ * trivial cases like "RETURN a + b", but makes it safe in all
+ * other cases.
+ */
+ foreach(lc, context->namespaces)
+ {
+ deparse_namespace *dpns = lfirst(lc);
+
+ if (list_length(dpns->rtable_names) > 0)
+ {
+ should_qualify = true;
+ break;
+ }
+ }
+ if (should_qualify)
+ {
+ appendStringInfoString(context->buf, quote_identifier(dpns->funcname));
+ appendStringInfoChar(context->buf, '.');
+ }
+
+ appendStringInfoString(context->buf, quote_identifier(argname));
+ return;
+ }
+ }
+ }
+
/*
* Not PARAM_EXEC, or couldn't find referent: just print $N.
*/
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 25717ce0e6..d0ea489614 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12128,6 +12128,7 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
char *proretset;
char *prosrc;
char *probin;
+ char *prosqlbody;
char *funcargs;
char *funciargs;
char *funcresult;
@@ -12174,7 +12175,7 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
"provolatile,\n"
"proisstrict,\n"
"prosecdef,\n"
- "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname,\n");
+ "lanname,\n");
if (fout->remoteVersion >= 80300)
appendPQExpBufferStr(query,
@@ -12194,9 +12195,9 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
* pg_get_function_result instead of examining proallargtypes etc.
*/
appendPQExpBufferStr(query,
- "pg_catalog.pg_get_function_arguments(oid) AS funcargs,\n"
- "pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs,\n"
- "pg_catalog.pg_get_function_result(oid) AS funcresult,\n");
+ "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs,\n"
+ "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs,\n"
+ "pg_catalog.pg_get_function_result(p.oid) AS funcresult,\n");
}
else if (fout->remoteVersion >= 80100)
appendPQExpBufferStr(query,
@@ -12239,21 +12240,39 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
if (fout->remoteVersion >= 120000)
appendPQExpBufferStr(query,
- "prosupport\n");
+ "prosupport,\n");
else
appendPQExpBufferStr(query,
- "'-' AS prosupport\n");
+ "'-' AS prosupport,\n");
+
+ if (fout->remoteVersion >= 140000)
+ appendPQExpBufferStr(query,
+ "CASE WHEN prosrc IS NULL AND lanname = 'sql' THEN pg_get_function_sqlbody(p.oid) END AS prosqlbody\n");
+ else
+ appendPQExpBufferStr(query,
+ "NULL AS prosqlbody\n");
appendPQExpBuffer(query,
- "FROM pg_catalog.pg_proc "
- "WHERE oid = '%u'::pg_catalog.oid",
+ "FROM pg_catalog.pg_proc p, pg_catalog.pg_language l\n"
+ "WHERE p.oid = '%u'::pg_catalog.oid "
+ "AND l.oid = p.prolang",
finfo->dobj.catId.oid);
res = ExecuteSqlQueryForSingleRow(fout, query->data);
proretset = PQgetvalue(res, 0, PQfnumber(res, "proretset"));
- prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
- probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ if (PQgetisnull(res, 0, PQfnumber(res, "prosqlbody")))
+ {
+ prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
+ probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
+ prosqlbody = NULL;
+ }
+ else
+ {
+ prosrc = NULL;
+ probin = NULL;
+ prosqlbody = PQgetvalue(res, 0, PQfnumber(res, "prosqlbody"));
+ }
if (fout->remoteVersion >= 80400)
{
funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
@@ -12290,7 +12309,11 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
* versions would set it to "-". There are no known cases in which prosrc
* is unused, so the tests below for "-" are probably useless.
*/
- if (probin[0] != '\0' && strcmp(probin, "-") != 0)
+ if (prosqlbody)
+ {
+ appendPQExpBufferStr(asPart, prosqlbody);
+ }
+ else if (probin[0] != '\0' && strcmp(probin, "-") != 0)
{
appendPQExpBufferStr(asPart, "AS ");
appendStringLiteralAH(asPart, probin, fout);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 440249ff69..52f7b2ce78 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -505,11 +505,18 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
appendPQExpBufferStr(&buf, ",\n ");
printACLColumn(&buf, "p.proacl");
appendPQExpBuffer(&buf,
- ",\n l.lanname as \"%s\""
- ",\n p.prosrc as \"%s\""
+ ",\n l.lanname as \"%s\"",
+ gettext_noop("Language"));
+ if (pset.sversion >= 140000)
+ appendPQExpBuffer(&buf,
+ ",\n COALESCE(p.prosrc, pg_catalog.pg_get_function_sqlbody(p.oid)) as \"%s\"",
+ gettext_noop("Source code"));
+ else
+ appendPQExpBuffer(&buf,
+ ",\n p.prosrc as \"%s\"",
+ gettext_noop("Source code"));
+ appendPQExpBuffer(&buf,
",\n pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"",
- gettext_noop("Language"),
- gettext_noop("Source code"),
gettext_noop("Description"));
}
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 216ff30993..4ec57e96a9 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -645,10 +645,11 @@ other .
";" {
ECHO;
- if (cur_state->paren_depth == 0)
+ if (cur_state->paren_depth == 0 && cur_state->begin_depth == 0)
{
/* Terminate lexing temporarily */
cur_state->start_state = YY_START;
+ cur_state->identifier_count = 0;
return LEXRES_SEMI;
}
}
@@ -661,6 +662,8 @@ other .
"\\"[;:] {
/* Force a semi-colon or colon into the query buffer */
psqlscan_emit(cur_state, yytext + 1, 1);
+ if (yytext[1] == ';')
+ cur_state->identifier_count = 0;
}
"\\" {
@@ -867,6 +870,18 @@ other .
{identifier} {
+ cur_state->identifier_count++;
+ if (pg_strcasecmp(yytext, "begin") == 0
+ || pg_strcasecmp(yytext, "case") == 0)
+ {
+ if (cur_state->identifier_count > 1)
+ cur_state->begin_depth++;
+ }
+ else if (pg_strcasecmp(yytext, "end") == 0)
+ {
+ if (cur_state->begin_depth > 0)
+ cur_state->begin_depth--;
+ }
ECHO;
}
@@ -1054,6 +1069,11 @@ psql_scan(PsqlScanState state,
result = PSCAN_INCOMPLETE;
*prompt = PROMPT_PAREN;
}
+ if (state->begin_depth > 0)
+ {
+ result = PSCAN_INCOMPLETE;
+ *prompt = PROMPT_CONTINUE;
+ }
else if (query_buf->len > 0)
{
result = PSCAN_EOL;
@@ -1170,6 +1190,8 @@ psql_scan_reset(PsqlScanState state)
if (state->dolqstart)
free(state->dolqstart);
state->dolqstart = NULL;
+ state->identifier_count = 0;
+ state->begin_depth = 0;
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 69ffd0c3f4..2000bc7560 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3703,6 +3703,10 @@
proname => 'pg_get_function_arg_default', provolatile => 's',
prorettype => 'text', proargtypes => 'oid int4',
prosrc => 'pg_get_function_arg_default' },
+{ oid => '9704', descr => 'function SQL body',
+ proname => 'pg_get_function_sqlbody', provolatile => 's',
+ prorettype => 'text', proargtypes => 'oid',
+ prosrc => 'pg_get_function_sqlbody' },
{ oid => '1686', descr => 'list of SQL keywords',
proname => 'pg_get_keywords', procost => '10', prorows => '500',
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 78f230894b..8d58067d03 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -112,11 +112,14 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
Oid protrftypes[1] BKI_DEFAULT(_null_) BKI_LOOKUP(pg_type);
/* procedure source text */
- text prosrc BKI_FORCE_NOT_NULL;
+ text prosrc;
/* secondary procedure info (can be NULL) */
text probin BKI_DEFAULT(_null_);
+ /* pre-parsed SQL function body */
+ pg_node_tree prosqlbody BKI_DEFAULT(_null_);
+
/* procedure-local GUC settings */
text proconfig[1] BKI_DEFAULT(_null_);
@@ -194,6 +197,7 @@ extern ObjectAddress ProcedureCreate(const char *procedureName,
Oid languageValidator,
const char *prosrc,
const char *probin,
+ Node *prosqlbody,
char prokind,
bool security_definer,
bool isLeakProof,
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 339f29f4c8..6bce4d76fe 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -65,9 +65,11 @@ extern void interpret_function_parameter_list(ParseState *pstate,
Oid languageOid,
ObjectType objtype,
oidvector **parameterTypes,
+ List **parameterTypes_list,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
+ List **inParameterNames_list,
List **parameterDefaults,
Oid *variadicArgType,
Oid *requiredResultType);
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index c975a93661..dcb8e18437 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -20,6 +20,21 @@
/* This struct is known only within executor/functions.c */
typedef struct SQLFunctionParseInfo *SQLFunctionParseInfoPtr;
+/*
+ * Data structure needed by the parser callback hooks to resolve parameter
+ * references during parsing of a SQL function's body. This is separate from
+ * SQLFunctionCache since we sometimes do parsing separately from execution.
+ */
+typedef struct SQLFunctionParseInfo
+{
+ char *fname; /* function's name */
+ int nargs; /* number of input arguments */
+ Oid *argtypes; /* resolved types of input arguments */
+ char **argnames; /* names of input arguments; NULL if none */
+ /* Note that argnames[i] can be NULL, if some args are unnamed */
+ Oid collation; /* function's input collation, if known */
+} SQLFunctionParseInfo;
+
extern Datum fmgr_sql(PG_FUNCTION_ARGS);
extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTuple,
diff --git a/src/include/fe_utils/psqlscan_int.h b/src/include/fe_utils/psqlscan_int.h
index 2bc3cc4ae7..91d7d4d5c6 100644
--- a/src/include/fe_utils/psqlscan_int.h
+++ b/src/include/fe_utils/psqlscan_int.h
@@ -114,6 +114,8 @@ typedef struct PsqlScanStateData
int paren_depth; /* depth of nesting in parentheses */
int xcdepth; /* depth of nesting in slash-star comments */
char *dolqstart; /* current $foo$ quote start string */
+ int identifier_count; /* identifiers since start of statement */
+ int begin_depth; /* depth of begin/end routine body blocks */
/*
* Callback functions provided by the program making use of the lexer,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 2051abbbf9..d9e417bcd7 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -321,6 +321,7 @@ typedef enum NodeTag
T_DeleteStmt,
T_UpdateStmt,
T_SelectStmt,
+ T_ReturnStmt,
T_PLAssignStmt,
T_AlterTableStmt,
T_AlterTableCmd,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7960cfe1a8..14d02a8ab3 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -140,6 +140,8 @@ typedef struct Query
bool hasForUpdate; /* FOR [KEY] UPDATE/SHARE was specified */
bool hasRowSecurity; /* rewriter has applied some RLS policy */
+ bool isReturn; /* is a RETURN statement */
+
List *cteList; /* WITH list (of CommonTableExpr's) */
List *rtable; /* list of range table entries */
@@ -1721,6 +1723,16 @@ typedef struct SetOperationStmt
} SetOperationStmt;
+/*
+ * RETURN statement (inside SQL function body)
+ */
+typedef struct ReturnStmt
+{
+ NodeTag type;
+ Node *returnval;
+} ReturnStmt;
+
+
/* ----------------------
* PL/pgSQL Assignment Statement
*
@@ -2923,6 +2935,7 @@ typedef struct CreateFunctionStmt
List *parameters; /* a list of FunctionParameter */
TypeName *returnType; /* the return type */
List *options; /* a list of DefElem */
+ Node *sql_body;
} CreateFunctionStmt;
typedef enum FunctionParameterMode
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 4bbe53e852..a5f84d43ed 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -48,6 +48,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("atomic", ATOMIC, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -348,6 +349,7 @@ PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("return", RETURN, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD, AS_LABEL)
PG_KEYWORD("returns", RETURNS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index e5472100a4..49700d151a 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -43,6 +43,7 @@ typedef enum
extern PGDLLIMPORT int log_statement;
extern List *pg_parse_query(const char *query_string);
+extern List *pg_rewrite_query(Query *query);
extern List *pg_analyze_and_rewrite(RawStmt *parsetree,
const char *query_string,
Oid *paramTypes, int numParams,
diff --git a/src/interfaces/ecpg/preproc/ecpg.addons b/src/interfaces/ecpg/preproc/ecpg.addons
index 974ce05664..b6e3412cef 100644
--- a/src/interfaces/ecpg/preproc/ecpg.addons
+++ b/src/interfaces/ecpg/preproc/ecpg.addons
@@ -89,6 +89,12 @@ ECPG: stmtTransactionStmt block
whenever_action(2);
free($1);
}
+ECPG: toplevel_stmtTransactionStmtLegacy block
+ {
+ fprintf(base_yyout, "{ ECPGtrans(__LINE__, %s, \"%s\");", connection ? connection : "NULL", $1);
+ whenever_action(2);
+ free($1);
+ }
ECPG: stmtViewStmt rule
| ECPGAllocateDescr
{
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer
index d699e0abbf..3600d7c605 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -4,8 +4,8 @@ statements: /*EMPTY*/
| statements statement
;
-statement: ecpgstart at stmt ';' { connection = NULL; }
- | ecpgstart stmt ';'
+statement: ecpgstart at toplevel_stmt ';' { connection = NULL; }
+ | ecpgstart toplevel_stmt ';'
| ecpgstart ECPGVarDeclaration
{
fprintf(base_yyout, "%s", $2);
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index f25a407fdd..edb58b6ad0 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -255,6 +255,179 @@ SELECT pg_get_functiondef('functest_F_2'::regproc);
(1 row)
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_3a() RETURNS boolean
+ BEGIN ATOMIC
+ RETURN false;
+ END;
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+-- error: duplicate function body
+CREATE FUNCTION functest_S_xxx(x int) RETURNS int
+ LANGUAGE SQL
+ AS $$ SELECT x * 2 $$
+ RETURN x * 3;
+ERROR: duplicate function body specified
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+ERROR: SQL function with unquoted function body cannot have polymorphic arguments
+-- tricky parsing
+CREATE FUNCTION functest_S_15(x int) RETURNS boolean
+LANGUAGE SQL
+BEGIN ATOMIC
+ select case when x % 2 = 0 then true else false end;
+END;
+SELECT functest_S_1('abcd', '2020-01-01');
+ functest_s_1
+--------------
+ t
+(1 row)
+
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+ functest_s_2
+--------------
+ 1
+(1 row)
+
+SELECT functest_S_3();
+ functest_s_3
+--------------
+ f
+(1 row)
+
+SELECT functest_S_10('abcd', '2020-01-01');
+ functest_s_10
+---------------
+ t
+(1 row)
+
+SELECT functest_S_13();
+ functest_s_13
+---------------
+ f
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_1(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN ((a = 'abcd'::text) AND (b > '01-01-2001'::date)) +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+ pg_get_functiondef
+------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_2(a text[])+
+ RETURNS integer +
+ LANGUAGE sql +
+ RETURN ((a)[1])::integer +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+ pg_get_functiondef
+----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ RETURN false +
+ +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_3a'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_3a()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ RETURN false; +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+ pg_get_functiondef
+-------------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_10(a text, b date)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT ((a = 'abcd'::text) AND (b > '01-01-2001'::date)); +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+ pg_get_functiondef
+-----------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_13()+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT 1; +
+ SELECT false AS bool; +
+ END +
+
+(1 row)
+
+SELECT pg_get_functiondef('functest_S_15'::regproc);
+ pg_get_functiondef
+--------------------------------------------------------------------
+ CREATE OR REPLACE FUNCTION temp_func_test.functest_s_15(x integer)+
+ RETURNS boolean +
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ SELECT +
+ CASE +
+ WHEN ((x % 2) = 0) THEN true +
+ ELSE false +
+ END AS "case"; +
+ END +
+
+(1 row)
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+SELECT functest_S_14();
+ functest_s_14
+---------------
+ 2
+(1 row)
+
+DROP TABLE functest3 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to view functestv3
+drop cascades to function functest_s_14()
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
RETURNS int
@@ -292,6 +465,15 @@ CREATE FUNCTION functest_IS_5(x int DEFAULT nextval('functest1'))
RETURNS int
LANGUAGE SQL
AS 'SELECT x';
+CREATE FUNCTION functest_IS_6()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest1');
+CREATE TABLE functest2 (a int, b int);
+CREATE FUNCTION functest_IS_7()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest2);
SELECT r0.routine_name, r1.routine_name
FROM information_schema.routine_routine_usage rru
JOIN information_schema.routines r0 ON r0.specific_name = rru.specific_name
@@ -305,23 +487,29 @@ SELECT routine_name, sequence_name FROM information_schema.routine_sequence_usag
routine_name | sequence_name
---------------+---------------
functest_is_5 | functest1
-(1 row)
+ functest_is_6 | functest1
+(2 rows)
--- currently empty
SELECT routine_name, table_name, column_name FROM information_schema.routine_column_usage;
- routine_name | table_name | column_name
---------------+------------+-------------
-(0 rows)
+ routine_name | table_name | column_name
+---------------+------------+-------------
+ functest_is_7 | functest2 | a
+(1 row)
SELECT routine_name, table_name FROM information_schema.routine_table_usage;
- routine_name | table_name
---------------+------------
-(0 rows)
+ routine_name | table_name
+---------------+------------
+ functest_is_7 | functest2
+(1 row)
DROP FUNCTION functest_IS_4a CASCADE;
NOTICE: drop cascades to function functest_is_4b(integer)
DROP SEQUENCE functest1 CASCADE;
-NOTICE: drop cascades to function functest_is_5(integer)
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to function functest_is_5(integer)
+drop cascades to function functest_is_6()
+DROP TABLE functest2 CASCADE;
+NOTICE: drop cascades to function functest_is_7()
-- overload
CREATE FUNCTION functest_B_2(bigint) RETURNS bool LANGUAGE 'sql'
IMMUTABLE AS 'SELECT $1 > 0';
@@ -340,6 +528,49 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
ERROR: cannot change routine kind
DETAIL: "functest1" is a function.
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+SELECT * FROM functest_sri1();
+ functest_sri1
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+SELECT * FROM functest_sri2();
+ functest_sri2
+---------------
+ 1
+ 2
+ 3
+(3 rows)
+
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+ QUERY PLAN
+------------------------------
+ Values Scan on "*VALUES*"
+ Output: "*VALUES*".column1
+(2 rows)
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
$$ SELECT a + 1 $$;
@@ -398,7 +629,7 @@ SELECT * FROM voidtest5(3);
-- Cleanup
DROP SCHEMA temp_func_test CASCADE;
-NOTICE: drop cascades to 21 other objects
+NOTICE: drop cascades to 30 other objects
DETAIL: drop cascades to function functest_a_1(text,date)
drop cascades to function functest_a_2(text[])
drop cascades to function functest_a_3()
@@ -414,7 +645,16 @@ drop cascades to function functest_f_1(integer)
drop cascades to function functest_f_2(integer)
drop cascades to function functest_f_3(integer)
drop cascades to function functest_f_4(integer)
+drop cascades to function functest_s_1(text,date)
+drop cascades to function functest_s_2(text[])
+drop cascades to function functest_s_3()
+drop cascades to function functest_s_3a()
+drop cascades to function functest_s_10(text,date)
+drop cascades to function functest_s_13()
+drop cascades to function functest_s_15(integer)
drop cascades to function functest_b_2(bigint)
+drop cascades to function functest_sri1()
+drop cascades to function functest_sri2()
drop cascades to function voidtest1(integer)
drop cascades to function voidtest2(integer,integer)
drop cascades to function voidtest3(integer)
diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out
index 3838fa2324..d45575561e 100644
--- a/src/test/regress/expected/create_procedure.out
+++ b/src/test/regress/expected/create_procedure.out
@@ -65,6 +65,48 @@ SELECT * FROM cp_test ORDER BY b COLLATE "C";
1 | xyzzy
(3 rows)
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+\df ptest1s
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+---------+------------------+---------------------+------
+ public | ptest1s | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest1s'::regproc);
+ pg_get_functiondef
+----------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest1s(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ INSERT INTO cp_test (a, b) +
+ VALUES (1, ptest1s.x); +
+ END +
+
+(1 row)
+
+CALL ptest1s('b');
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+ a | b
+---+-------
+ 1 | 0
+ 1 | a
+ 1 | b
+ 1 | xyzzy
+(4 rows)
+
+-- utitlity functions currently not supported here
+CREATE PROCEDURE ptestx()
+LANGUAGE SQL
+BEGIN ATOMIC
+ CREATE TABLE x (a int);
+END;
+ERROR: CREATE TABLE is not yet supported in unquoted SQL function body
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -146,6 +188,28 @@ AS $$
SELECT a = b;
$$;
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+\df ptest8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+--------+------------------+---------------------+------
+ public | ptest8 | | x text | proc
+(1 row)
+
+SELECT pg_get_functiondef('ptest8'::regproc);
+ pg_get_functiondef
+---------------------------------------------------
+ CREATE OR REPLACE PROCEDURE public.ptest8(x text)+
+ LANGUAGE sql +
+ BEGIN ATOMIC +
+ END +
+
+(1 row)
+
+CALL ptest8('');
-- OUT parameters
CREATE PROCEDURE ptest9(OUT a int)
LANGUAGE SQL
@@ -214,6 +278,7 @@ ALTER ROUTINE ptest1a RENAME TO ptest1;
DROP ROUTINE cp_testfunc1(int);
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
DROP USER regress_cp_user1;
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
index 549b34b4b2..695ee3413f 100644
--- a/src/test/regress/sql/create_function_3.sql
+++ b/src/test/regress/sql/create_function_3.sql
@@ -153,6 +153,79 @@ CREATE FUNCTION functest_F_4(int) RETURNS bool LANGUAGE 'sql'
SELECT pg_get_functiondef('functest_F_2'::regproc);
+--
+-- SQL-standard body
+--
+CREATE FUNCTION functest_S_1(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN a = 'abcd' AND b > '2001-01-01';
+CREATE FUNCTION functest_S_2(a text[]) RETURNS int
+ RETURN a[1]::int;
+CREATE FUNCTION functest_S_3() RETURNS boolean
+ RETURN false;
+CREATE FUNCTION functest_S_3a() RETURNS boolean
+ BEGIN ATOMIC
+ RETURN false;
+ END;
+
+CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ SELECT a = 'abcd' AND b > '2001-01-01';
+ END;
+
+CREATE FUNCTION functest_S_13() RETURNS boolean
+ BEGIN ATOMIC
+ SELECT 1;
+ SELECT false;
+ END;
+
+-- error: duplicate function body
+CREATE FUNCTION functest_S_xxx(x int) RETURNS int
+ LANGUAGE SQL
+ AS $$ SELECT x * 2 $$
+ RETURN x * 3;
+
+-- polymorphic arguments not allowed in this form
+CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
+ LANGUAGE SQL
+ RETURN x[1];
+
+-- tricky parsing
+CREATE FUNCTION functest_S_15(x int) RETURNS boolean
+LANGUAGE SQL
+BEGIN ATOMIC
+ select case when x % 2 = 0 then true else false end;
+END;
+
+SELECT functest_S_1('abcd', '2020-01-01');
+SELECT functest_S_2(ARRAY['1', '2', '3']);
+SELECT functest_S_3();
+
+SELECT functest_S_10('abcd', '2020-01-01');
+SELECT functest_S_13();
+
+SELECT pg_get_functiondef('functest_S_1'::regproc);
+SELECT pg_get_functiondef('functest_S_2'::regproc);
+SELECT pg_get_functiondef('functest_S_3'::regproc);
+SELECT pg_get_functiondef('functest_S_3a'::regproc);
+SELECT pg_get_functiondef('functest_S_10'::regproc);
+SELECT pg_get_functiondef('functest_S_13'::regproc);
+SELECT pg_get_functiondef('functest_S_15'::regproc);
+
+-- test with views
+CREATE TABLE functest3 (a int);
+INSERT INTO functest3 VALUES (1), (2);
+CREATE VIEW functestv3 AS SELECT * FROM functest3;
+
+CREATE FUNCTION functest_S_14() RETURNS bigint
+ RETURN (SELECT count(*) FROM functestv3);
+
+SELECT functest_S_14();
+
+DROP TABLE functest3 CASCADE;
+
+
-- information_schema tests
CREATE FUNCTION functest_IS_1(a int, b int default 1, c text default 'foo')
@@ -188,17 +261,29 @@ CREATE FUNCTION functest_IS_5(x int DEFAULT nextval('functest1'))
LANGUAGE SQL
AS 'SELECT x';
+CREATE FUNCTION functest_IS_6()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN nextval('functest1');
+
+CREATE TABLE functest2 (a int, b int);
+
+CREATE FUNCTION functest_IS_7()
+ RETURNS int
+ LANGUAGE SQL
+ RETURN (SELECT count(a) FROM functest2);
+
SELECT r0.routine_name, r1.routine_name
FROM information_schema.routine_routine_usage rru
JOIN information_schema.routines r0 ON r0.specific_name = rru.specific_name
JOIN information_schema.routines r1 ON r1.specific_name = rru.routine_name;
SELECT routine_name, sequence_name FROM information_schema.routine_sequence_usage;
--- currently empty
SELECT routine_name, table_name, column_name FROM information_schema.routine_column_usage;
SELECT routine_name, table_name FROM information_schema.routine_table_usage;
DROP FUNCTION functest_IS_4a CASCADE;
DROP SEQUENCE functest1 CASCADE;
+DROP TABLE functest2 CASCADE;
-- overload
@@ -218,6 +303,29 @@ CREATE OR REPLACE PROCEDURE functest1(a int) LANGUAGE SQL AS 'SELECT $1';
DROP FUNCTION functest1(a int);
+-- inlining of set-returning functions
+
+CREATE FUNCTION functest_sri1() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+AS '
+ VALUES (1), (2), (3);
+';
+
+SELECT * FROM functest_sri1();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri1();
+
+CREATE FUNCTION functest_sri2() RETURNS SETOF int
+LANGUAGE SQL
+STABLE
+BEGIN ATOMIC
+ VALUES (1), (2), (3);
+END;
+
+SELECT * FROM functest_sri2();
+EXPLAIN (verbose, costs off) SELECT * FROM functest_sri2();
+
+
-- Check behavior of VOID-returning SQL functions
CREATE FUNCTION voidtest1(a int) RETURNS VOID LANGUAGE SQL AS
diff --git a/src/test/regress/sql/create_procedure.sql b/src/test/regress/sql/create_procedure.sql
index 2ef1c82cea..76f781c0b9 100644
--- a/src/test/regress/sql/create_procedure.sql
+++ b/src/test/regress/sql/create_procedure.sql
@@ -28,6 +28,28 @@ CREATE PROCEDURE ptest1(x text)
SELECT * FROM cp_test ORDER BY b COLLATE "C";
+-- SQL-standard body
+CREATE PROCEDURE ptest1s(x text)
+LANGUAGE SQL
+BEGIN ATOMIC
+ INSERT INTO cp_test VALUES (1, x);
+END;
+
+\df ptest1s
+SELECT pg_get_functiondef('ptest1s'::regproc);
+
+CALL ptest1s('b');
+
+SELECT * FROM cp_test ORDER BY b COLLATE "C";
+
+-- utitlity functions currently not supported here
+CREATE PROCEDURE ptestx()
+LANGUAGE SQL
+BEGIN ATOMIC
+ CREATE TABLE x (a int);
+END;
+
+
CREATE PROCEDURE ptest2()
LANGUAGE SQL
AS $$
@@ -112,6 +134,16 @@ CREATE PROCEDURE ptest7(a text, b text)
CALL ptest7(least('a', 'b'), 'a');
+-- empty body
+CREATE PROCEDURE ptest8(x text)
+BEGIN ATOMIC
+END;
+
+\df ptest8
+SELECT pg_get_functiondef('ptest8'::regproc);
+CALL ptest8('');
+
+
-- OUT parameters
CREATE PROCEDURE ptest9(OUT a int)
@@ -170,6 +202,7 @@ CREATE USER regress_cp_user1;
-- cleanup
DROP PROCEDURE ptest1;
+DROP PROCEDURE ptest1s;
DROP PROCEDURE ptest2;
DROP TABLE cp_test;
base-commit: 9c5f67fd6256246b2a788a8feb1d42b79dcd0448
--
2.31.0
On Fri, Apr 02, 2021 at 02:25:15PM +0200, Peter Eisentraut wrote:
New patch attached.
Thanks, it all looks good to me. I just spot a few minor formatting issues:
@@ -2968,6 +2973,13 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
}
/* And finally the function definition ... */
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
+ if (proc->prolang == SQLlanguageId && !isnull)
+ {
+ print_function_sqlbody(&buf, proctup);
+ }
+ else
+ {
appendStringInfoString(&buf, "AS ");
tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
@@ -2999,6 +3011,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, dq.data, dq.len);
appendStringInfoString(&buf, prosrc);
appendBinaryStringInfo(&buf, dq.data, dq.len);
+ }
The curly braces could probably be removed for the if branch, and the code in
the else branch isn't properly indented.
Other occurences:
+ else
+ {
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We need a
* dummy FuncExpr node containing the already-simplified arguments to pass
@@ -4317,6 +4337,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
querytree = transformTopLevelStmt(pstate, linitial(raw_parsetree_list));
free_parsestate(pstate);
+ }
and
+ else
+ {
+ char *src;
+
+ src = TextDatumGetCString(tmp);
+
+ callback_arg.prosrc = src;
+
/*
* Set up to handle parameters while parsing the function body. We can
* use the FuncExpr just created as the input for
@@ -4829,18 +4878,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
(Node *) fexpr,
fexpr->inputcollid);
- /*
and
@@ -2968,6 +2973,13 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
}
/* And finally the function definition ... */
+ tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_prosqlbody, &isnull);
+ if (proc->prolang == SQLlanguageId && !isnull)
+ {
+ print_function_sqlbody(&buf, proctup);
+ }
+ else
+ {
appendStringInfoString(&buf, "AS ");
tmp = SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_probin, &isnull);
@@ -2999,6 +3011,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, dq.data, dq.len);
appendStringInfoString(&buf, prosrc);
appendBinaryStringInfo(&buf, dq.data, dq.len);
+ }
and
@@ -12290,7 +12309,11 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
* versions would set it to "-". There are no known cases in which prosrc
* is unused, so the tests below for "-" are probably useless.
*/
- if (probin[0] != '\0' && strcmp(probin, "-") != 0)
+ if (prosqlbody)
+ {
+ appendPQExpBufferStr(asPart, prosqlbody);
+ }
Are you planning to run pg_indent before committing or would that add too much
noise?
Anyway since it's only stylistic issues and the feature freeze is getting
closer I'm marking the patch as ready for committer.
On 03.04.21 05:39, Julien Rouhaud wrote:
Are you planning to run pg_indent before committing or would that add too much
noise?
Yeah, I figured I'd leave that for later, to not bloat the patch so much.
Anyway since it's only stylistic issues and the feature freeze is getting
closer I'm marking the patch as ready for committer.
Committed. Thanks!
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
Committed. Thanks!
Buildfarm suggests this has some issues under force_parallel_mode.
I'm wondering about missed fields in outfuncs/readfuncs, or the like.
regards, tom lane
On Wed, Apr 07, 2021 at 04:22:17PM -0400, Tom Lane wrote:
Buildfarm suggests this has some issues under force_parallel_mode.
I'm wondering about missed fields in outfuncs/readfuncs, or the like.
The problem looks a bit more fundamental to me, as there seems to be
some confusion with the concept of what should be the query string
when it comes to prosqlbody with a parallel run, as it replaces prosrc
in some cases where the function uses SQL as language. If the
buildfarm cannot be put back to green, could it be possible to revert
this patch?
--
Michael
Michael Paquier <michael@paquier.xyz> writes:
On Wed, Apr 07, 2021 at 04:22:17PM -0400, Tom Lane wrote:
Buildfarm suggests this has some issues under force_parallel_mode.
I'm wondering about missed fields in outfuncs/readfuncs, or the like.
The problem looks a bit more fundamental to me, as there seems to be
some confusion with the concept of what should be the query string
when it comes to prosqlbody with a parallel run, as it replaces prosrc
in some cases where the function uses SQL as language. If the
buildfarm cannot be put back to green, could it be possible to revert
this patch?
Andres pushed a stopgap fix. We might end up reverting the patch
altogether for v14, but I don't want to be hasty. This should be enough
to let people take advantage of the last few hours before feature freeze.
regards, tom lane
Hi,
On 2021-04-08 01:16:02 -0400, Tom Lane wrote:
Michael Paquier <michael@paquier.xyz> writes:
On Wed, Apr 07, 2021 at 04:22:17PM -0400, Tom Lane wrote:
Buildfarm suggests this has some issues under force_parallel_mode.
I'm wondering about missed fields in outfuncs/readfuncs, or the like.The problem looks a bit more fundamental to me, as there seems to be
some confusion with the concept of what should be the query string
when it comes to prosqlbody with a parallel run, as it replaces prosrc
in some cases where the function uses SQL as language. If the
buildfarm cannot be put back to green, could it be possible to revert
this patch?Andres pushed a stopgap fix.
Let's hope that it does fix it on the BF as well. One holdup was that
check-world didn't succeed with force_parallel_mode=regress even after
the fix - but that turned out to be the fault of
commit 5fd9dfa5f50e4906c35133a414ebec5b6d518493 (HEAD)
Author: Bruce Momjian <bruce@momjian.us>
Date: 2021-04-07 13:06:47 -0400
Move pg_stat_statements query jumbling to core.
et al.
We might end up reverting the patch altogether for v14, but I don't
want to be hasty. This should be enough to let people take advantage
of the last few hours before feature freeze.
Yea, I think it'd be good to make that decision after a decent night of
sleep or two. And an actual look at the issues the patch might (or might
not) have.
Independent of this patch, it might be a good idea to have
ExecInitParallelPlan() be robust against NULL querystrings. Places like
executor_errposition() are certainly trying to be...
Greetings,
Andres Freund
Andres Freund <andres@anarazel.de> writes:
Independent of this patch, it might be a good idea to have
ExecInitParallelPlan() be robust against NULL querystrings. Places like
executor_errposition() are certainly trying to be...
FWIW, I think the long-term drift of things is definitely that
we want to have the querystring available everywhere. Code like
executor_errposition is from an earlier era before we were trying
to enforce that. In particular, if the querystring is available in
the leader and not the workers, then you will get different error
reporting behavior in parallel query than non-parallel query, which
is surely a bad thing.
So IMO what you did here is definitely a short-term thing that
we should be looking to revert. The question at hand is why
Peter's patch broke this in the first place, and how hard it
will be to fix it properly. I'm entirely on board with reverting
the feature if that isn't readily fixable.
regards, tom lane
On Wed, Apr 07, 2021 at 10:27:59PM -0700, Andres Freund wrote:
One holdup was that
check-world didn't succeed with force_parallel_mode=regress even after
the fix - but that turned out to be the fault ofcommit 5fd9dfa5f50e4906c35133a414ebec5b6d518493 (HEAD)
Author: Bruce Momjian <bruce@momjian.us>
Date: 2021-04-07 13:06:47 -0400Move pg_stat_statements query jumbling to core.
et al.
Yep, I'm on it!
Julien Rouhaud <rjuju123@gmail.com> writes:
On Wed, Apr 07, 2021 at 10:27:59PM -0700, Andres Freund wrote:
One holdup was that
check-world didn't succeed with force_parallel_mode=regress even after
the fix - but that turned out to be the fault of
Move pg_stat_statements query jumbling to core.
Yep, I'm on it!
So far the buildfarm seems to be turning green after b3ee4c503 ...
so I wonder what extra condition is needed to cause the failure
Andres is seeing.
regards, tom lane
Hi,
On 2021-04-08 02:05:25 -0400, Tom Lane wrote:
So far the buildfarm seems to be turning green after b3ee4c503 ...
so I wonder what extra condition is needed to cause the failure
Andres is seeing.
Nothing special, really. Surprised the BF doesn't see it:
andres@awork3:~/build/postgres/dev-assert/vpath$ cat /tmp/test.conf
force_parallel_mode=regress
andres@awork3:~/build/postgres/dev-assert/vpath$ make -j48 -s && EXTRA_REGRESS_OPTS='--temp-config /tmp/test.conf' make -s -C contrib/pg_stat_statements/ check
All of PostgreSQL successfully made. Ready to install.
...
The differences that caused some tests to fail can be viewed in the
file "/home/andres/build/postgres/dev-assert/vpath/contrib/pg_stat_statements/regression.diffs". A copy of the test summary that you see
above is saved in the file "/home/andres/build/postgres/dev-assert/vpath/contrib/pg_stat_statements/regression.out".
...
andres@awork3:~/build/postgres/dev-assert/vpath$ head -n 30 /home/andres/build/postgres/dev-assert/vpath/contrib/pg_stat_statements/regression.diffs
diff -du10 /home/andres/src/postgresql/contrib/pg_stat_statements/expected/pg_stat_statements.out /home/andres/build/postgres/dev-assert/vpath/contrib/pg_stat_statements/results/pg_stat_statements.out
--- /home/andres/src/postgresql/contrib/pg_stat_statements/expected/pg_stat_statements.out 2021-04-06 09:08:42.688697932 -0700
+++ /home/andres/build/postgres/dev-assert/vpath/contrib/pg_stat_statements/results/pg_stat_statements.out 2021-04-07 23:30:26.876071024 -0700
@@ -118,37 +118,38 @@
?column? | ?column?
----------+----------
1 | test
(1 row)
DEALLOCATE pgss_test;
SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
query | calls | rows
------------------------------------------------------------------------------+-------+------
PREPARE pgss_test (int) AS SELECT $1, $2 LIMIT $3 | 1 | 1
- SELECT $1 +| 4 | 4
+ PREPARE pgss_test (int) AS SELECT $1, 'test' LIMIT 1; | 1 | 1
+ SELECT $1 +| 8 | 8
+| |
AS "text" | |
- SELECT $1 + $2 | 2 | 2
- SELECT $1 + $2 + $3 AS "add" | 3 | 3
- SELECT $1 AS "float" | 1 | 1
- SELECT $1 AS "int" | 2 | 2
+ SELECT $1 + $2 | 4 | 4
+ SELECT $1 + $2 + $3 AS "add" | 6 | 6
+ SELECT $1 AS "float" | 2 | 2
+ SELECT $1 AS "int" | 4 | 4
SELECT $1 AS i UNION SELECT $2 ORDER BY i | 1 | 2
- SELECT $1 || $2 | 1 | 1
- SELECT pg_stat_statements_reset() | 1 | 1
Too tired to figure out why the BF doesn't see this. Perhaps the extra
settings aren't used because it's scripted as an install check?
Greetings,
Andres Freund
Hi,
On 2021-04-08 01:41:40 -0400, Tom Lane wrote:
Andres Freund <andres@anarazel.de> writes:
Independent of this patch, it might be a good idea to have
ExecInitParallelPlan() be robust against NULL querystrings. Places like
executor_errposition() are certainly trying to be...FWIW, I think the long-term drift of things is definitely that
we want to have the querystring available everywhere. Code like
executor_errposition is from an earlier era before we were trying
to enforce that. In particular, if the querystring is available in
the leader and not the workers, then you will get different error
reporting behavior in parallel query than non-parallel query, which
is surely a bad thing.
Yea, I think it's a sensible direction - but I think we should put the
line in the sand earlier on / higher up than ExecInitParallelPlan().
Greetings,
Andres Freund
On Wed, Apr 07, 2021 at 11:33:20PM -0700, Andres Freund wrote:
Hi,
On 2021-04-08 02:05:25 -0400, Tom Lane wrote:
So far the buildfarm seems to be turning green after b3ee4c503 ...
so I wonder what extra condition is needed to cause the failure
Andres is seeing.Nothing special, really. Surprised the BF doesn't see it:
andres@awork3:~/build/postgres/dev-assert/vpath$ cat /tmp/test.conf
force_parallel_mode=regress
andres@awork3:~/build/postgres/dev-assert/vpath$ make -j48 -s && EXTRA_REGRESS_OPTS='--temp-config /tmp/test.conf' make -s -C contrib/pg_stat_statements/ check
All of PostgreSQL successfully made. Ready to install.
...
The differences that caused some tests to fail can be viewed in the
file "/home/andres/build/postgres/dev-assert/vpath/contrib/pg_stat_statements/regression.diffs". A copy of the test summary that you see
above is saved in the file "/home/andres/build/postgres/dev-assert/vpath/contrib/pg_stat_statements/regression.out".
...
Is think this is because the buildfarm client is running installcheck for the
contribs rather than check, and pg_stat_statements/Makefile has:
# Disabled because these tests require "shared_preload_libraries=pg_stat_statements",
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
Julien Rouhaud <rjuju123@gmail.com> writes:
On Wed, Apr 07, 2021 at 11:33:20PM -0700, Andres Freund wrote:
Nothing special, really. Surprised the BF doesn't see it:
Is think this is because the buildfarm client is running installcheck for the
contribs rather than check, and pg_stat_statements/Makefile has:
# Disabled because these tests require "shared_preload_libraries=pg_stat_statements",
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
No, because if that were the explanation then we'd be getting no
buildfarm coverage at all for for pg_stat_statements. Which aside
from being awful contradicts the results at coverage.postgresql.org.
I think Andres has the right idea that there's some more-subtle
variation in the test conditions, but (yawn) too tired to look
into it right now.
regards, tom lane
On Wed, Apr 07, 2021 at 11:35:14PM -0700, Andres Freund wrote:
On 2021-04-08 01:41:40 -0400, Tom Lane wrote:
Andres Freund <andres@anarazel.de> writes:
FWIW, I think the long-term drift of things is definitely that
we want to have the querystring available everywhere. Code like
executor_errposition is from an earlier era before we were trying
to enforce that. In particular, if the querystring is available in
the leader and not the workers, then you will get different error
reporting behavior in parallel query than non-parallel query, which
is surely a bad thing.Yea, I think it's a sensible direction - but I think we should put the
line in the sand earlier on / higher up than ExecInitParallelPlan().
Indeed, I agree that enforcing the availability of querystring
everywhere sounds like a sensible thing to do in terms of consistency,
and that's my impression when I scanned the parallel execution code,
and I don't really get why SQL function bodies should not bind by this
rule. Would people object if I add an open item to track that?
--
Michael
On Thu, Apr 08, 2021 at 04:54:56PM +0900, Michael Paquier wrote:
On Wed, Apr 07, 2021 at 11:35:14PM -0700, Andres Freund wrote:
On 2021-04-08 01:41:40 -0400, Tom Lane wrote:
Andres Freund <andres@anarazel.de> writes:
FWIW, I think the long-term drift of things is definitely that
we want to have the querystring available everywhere. Code like
executor_errposition is from an earlier era before we were trying
to enforce that. In particular, if the querystring is available in
the leader and not the workers, then you will get different error
reporting behavior in parallel query than non-parallel query, which
is surely a bad thing.Yea, I think it's a sensible direction - but I think we should put the
line in the sand earlier on / higher up than ExecInitParallelPlan().Indeed, I agree that enforcing the availability of querystring
everywhere sounds like a sensible thing to do in terms of consistency,
and that's my impression when I scanned the parallel execution code,
and I don't really get why SQL function bodies should not bind by this
rule. Would people object if I add an open item to track that?
It makes sense, +1 for an open item.
On Thu, Apr 08, 2021 at 02:58:02AM -0400, Tom Lane wrote:
Julien Rouhaud <rjuju123@gmail.com> writes:
On Wed, Apr 07, 2021 at 11:33:20PM -0700, Andres Freund wrote:
Nothing special, really. Surprised the BF doesn't see it:
Is think this is because the buildfarm client is running installcheck for the
contribs rather than check, and pg_stat_statements/Makefile has:
# Disabled because these tests require "shared_preload_libraries=pg_stat_statements",
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1No, because if that were the explanation then we'd be getting no
buildfarm coverage at all for for pg_stat_statements. Which aside
from being awful contradicts the results at coverage.postgresql.org.
Is there any chance that coverage.postgresql.org isn't backed by the buildfarm
client but a plain make check-world or something like that?
I think Andres has the right idea that there's some more-subtle
variation in the test conditions, but (yawn) too tired to look
into it right now.
I tried to look at some force-parallel-mode animal, like mantid, and I don't
see any evidence of pg_stat_statements being run by a "make check", and only a
few contrib modules seem to have an explicit check phase. However, looking at
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=mantid&dt=2021-04-08%2007%3A07%3A05
I see
https://buildfarm.postgresql.org/cgi-bin/show_stage_log.pl?nm=mantid&dt=2021-04-08%2007%3A07%3A05&stg=contrib-install-check-C:
[...]
make -C pg_stat_statements installcheck
make[1]: Entering directory `/u1/tac/build-farm-11/buildroot/HEAD/pgsql.build/contrib/pg_stat_statements'
make[1]: Nothing to be done for `installcheck'.
Julien Rouhaud <rjuju123@gmail.com> writes:
On Thu, Apr 08, 2021 at 02:58:02AM -0400, Tom Lane wrote:
No, because if that were the explanation then we'd be getting no
buildfarm coverage at all for for pg_stat_statements. Which aside
from being awful contradicts the results at coverage.postgresql.org.
Is there any chance that coverage.postgresql.org isn't backed by the buildfarm
client but a plain make check-world or something like that?
Hmm, I think you're right. Poking around in the log files from one
of my own buildfarm animals, there's no evidence that pg_stat_statements
is being tested at all. Needless to say, that's just horrid :-(
I see that contrib/test_decoding also sets NO_INSTALLCHECK = 1,
and the reason it gets tested is that the buildfarm script has
a special module for that. I guess we need to clone that module,
or maybe better, find a way to generalize it.
There are also some src/test/modules modules that set NO_INSTALLCHECK,
but apparently those do have coverage (modules-check is the step that
runs their SQL tests, and then the TAP tests if any get broken out
as separate buildfarm steps).
regards, tom lane
On 4/8/21 2:40 AM, Julien Rouhaud wrote:
On Wed, Apr 07, 2021 at 11:33:20PM -0700, Andres Freund wrote:
Hi,
On 2021-04-08 02:05:25 -0400, Tom Lane wrote:
So far the buildfarm seems to be turning green after b3ee4c503 ...
so I wonder what extra condition is needed to cause the failure
Andres is seeing.Nothing special, really. Surprised the BF doesn't see it:
andres@awork3:~/build/postgres/dev-assert/vpath$ cat /tmp/test.conf
force_parallel_mode=regress
andres@awork3:~/build/postgres/dev-assert/vpath$ make -j48 -s && EXTRA_REGRESS_OPTS='--temp-config /tmp/test.conf' make -s -C contrib/pg_stat_statements/ check
All of PostgreSQL successfully made. Ready to install.
...
The differences that caused some tests to fail can be viewed in the
file "/home/andres/build/postgres/dev-assert/vpath/contrib/pg_stat_statements/regression.diffs". A copy of the test summary that you see
above is saved in the file "/home/andres/build/postgres/dev-assert/vpath/contrib/pg_stat_statements/regression.out".
...Is think this is because the buildfarm client is running installcheck for the
contribs rather than check, and pg_stat_statements/Makefile has:# Disabled because these tests require "shared_preload_libraries=pg_stat_statements",
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
Yeah, Julien is right, we run "make check" for these in src/test/modules
but I missed contrib. I have fixed this on crake so we get some
immediate coverage and a fix will be pushed to github shortly.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
On 2021-Apr-08, Julien Rouhaud wrote:
On Thu, Apr 08, 2021 at 02:58:02AM -0400, Tom Lane wrote:
No, because if that were the explanation then we'd be getting no
buildfarm coverage at all for for pg_stat_statements. Which aside
from being awful contradicts the results at coverage.postgresql.org.Is there any chance that coverage.postgresql.org isn't backed by the buildfarm
client but a plain make check-world or something like that?
Yes, coverage.pg.org runs "make check-world".
Maybe it would make sense to change that script, so that it runs the
buildfarm's run_build.pl script instead of "make check-world". That
would make coverage.pg.org report what the buildfarm actually tests ...
it would have made this problem a bit more obvious.
--
�lvaro Herrera 39�49'30"S 73�17'W
On Thu, Apr 08, 2021 at 12:21:05PM -0400, Tom Lane wrote:
I see that contrib/test_decoding also sets NO_INSTALLCHECK = 1,
and the reason it gets tested is that the buildfarm script has
a special module for that. I guess we need to clone that module,
or maybe better, find a way to generalize it.There are also some src/test/modules modules that set NO_INSTALLCHECK,
but apparently those do have coverage (modules-check is the step that
runs their SQL tests, and then the TAP tests if any get broken out
as separate buildfarm steps).
FWIW, on Windows any module with NO_INSTALLCHECK does not get tested
as we rely mostly on an installed server to do all the tests and avoid
the performance impact of setting up a new server for each module's
test.
--
Michael
Julien Rouhaud <rjuju123@gmail.com> writes:
On Thu, Apr 08, 2021 at 04:54:56PM +0900, Michael Paquier wrote:
Indeed, I agree that enforcing the availability of querystring
everywhere sounds like a sensible thing to do in terms of consistency,
and that's my impression when I scanned the parallel execution code,
and I don't really get why SQL function bodies should not bind by this
rule. Would people object if I add an open item to track that?
It makes sense, +1 for an open item.
So here's what I propose to do about this.
0001 attached reverts the patch's change to remove the NOT NULL
constraint on pg_proc.prosrc. I think that was an extremely poor
decision; it risks breaking non-core PLs, and for that matter I'm not
sure the core PLs wouldn't crash on null prosrc. It is not any harder
for the SQL-language-related code to condition its checks on not-null
prosqlbody instead of null prosrc. Of course that then requires us to
put something into prosrc for these newfangled functions, but in 0001
I just used an empty string. (This patch also adds an Assert to
standard_ExecutorStart checking that some source text was provided,
responding to Andres' point that we should be checking that upstream
of parallel query. We should then revert b3ee4c503, but for simplicity
I didn't include that here.)
0002 addresses a different missing-source-text problem, which is that
the patch didn't bother to provide source text while running parse
analysis on the SQL function body. That means no error cursors for
problems; which might seem cosmetic on the toy example I added to the
regression tests, but it won't be for people writing functions that
are dozens or hundreds of lines long.
Finally, 0003 might be a bit controversial: it changes the stored
prosrc for new-style SQL functions to be the query text of the CREATE
FUNCTION command. The main argument I can see being made against this
is that it'll bloat the pg_proc entry. But I think that that's
not a terribly reasonable concern, because the source text is going
to be a good deal smaller than the nodeToString representation in
just about every case.
The real value of 0003 of course would be to get an error cursor at
runtime, but I failed to create an example where that would happen
today. Right now there are only three calls of executor_errposition,
and all of them are for cases that are already rejected by the parser,
so they're effectively unreachable. A scenario that seems more likely
to be reachable is a failure reported during function inlining, but
most of the reasons I can think of for that also seem unreachable given
the already-parse-analyzed nature of the function body in these cases.
Maybe I'm just under-caffeinated today.
Another point here is that for any error cursor to appear, we need
not only source text at hand but also token locations in the query
tree nodes. Right now, since readfuncs.c intentionally discards
those locations, we won't have that. There is not-normally-compiled
logic to reload those location fields, though, and I think before too
long we'll want to enable it in some mainstream cases --- notably
parallel query's shipping of querytrees to workers. However, until
it gets easier to reach cases where an error-with-location can be
thrown from the executor, I don't feel a need to do that.
I do have ambitions to make execution-time errors produce cursors
in more cases, so I think this will come to fruition before long,
but not in v14.
One could make an argument, therefore, for holding off 0003 until
there's more support for execution-time error cursors. I don't
think we should though, for two reasons:
1. It'd be better to keep the pg_proc representation of new-style
SQL functions stable across versions.
2. Storing the CREATE text means we'll capture comments associated
with the function text, which is something that at least some
people will complain about the loss of. Admittedly we have no way
to re-integrate the comments into the de-parsed body, but some
folks might be satisfied with grabbing the prosrc text.
Thoughts?
regards, tom lane
Attachments:
0003-use-CREATE-FUNCTION-as-prosrc.patchtext/x-diff; charset=us-ascii; name=0003-use-CREATE-FUNCTION-as-prosrc.patchDownload
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index e7cb5c65e9..73f9fe1f70 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -964,13 +964,13 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
}
/*
- * We must put something in prosrc. For the moment, just record an
- * empty string. It might be useful to store the original text of the
- * CREATE FUNCTION statement --- but to make actual use of that in
- * error reports, we'd also have to adjust readfuncs.c to not throw
- * away node location fields when reading prosqlbody.
+ * Use the text of the CREATE FUNCTION statement as prosrc. Note that
+ * this might be more text than strictly necessary, if CREATE FUNCTION
+ * is part of a multi-statement string; but trimming it would require
+ * adjusting the location fields in prosqlbody, which seems like way
+ * more trouble than the case is worth.
*/
- *prosrc_str_p = pstrdup("");
+ *prosrc_str_p = pstrdup(queryString);
/* But we definitely don't need probin. */
*probin_str_p = NULL;
0001-revert-nullability-of-prosrc.patchtext/x-diff; charset=us-ascii; name=0001-revert-nullability-of-prosrc.patchDownload
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index cd13e63852..478dbde3fe 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -121,7 +121,7 @@ ProcedureCreate(const char *procedureName,
/*
* sanity checks
*/
- Assert(PointerIsValid(prosrc) || PointerIsValid(prosqlbody));
+ Assert(PointerIsValid(prosrc));
parameterCount = parameterTypes->dim1;
if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
@@ -336,10 +336,7 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_protrftypes - 1] = trftypes;
else
nulls[Anum_pg_proc_protrftypes - 1] = true;
- if (prosrc)
- values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
- else
- nulls[Anum_pg_proc_prosrc - 1] = true;
+ values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
if (probin)
values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin);
else
@@ -874,26 +871,29 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
/* Postpone body checks if !check_function_bodies */
if (check_function_bodies)
{
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc");
+
+ prosrc = TextDatumGetCString(tmp);
+
/*
* Setup error traceback support for ereport().
*/
callback_arg.proname = NameStr(proc->proname);
- callback_arg.prosrc = NULL;
+ callback_arg.prosrc = prosrc;
sqlerrcontext.callback = sql_function_parse_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
- tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
- if (isnull)
+ /* If we have prosqlbody, pay attention to that not prosrc */
+ tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosqlbody, &isnull);
+ if (!isnull)
{
Node *n;
- tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosqlbody, &isnull);
- if (isnull)
- elog(ERROR, "null prosrc and prosqlbody");
-
n = stringToNode(TextDatumGetCString(tmp));
if (IsA(n, List))
querytree_list = castNode(List, n);
@@ -902,10 +902,6 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
}
else
{
- prosrc = TextDatumGetCString(tmp);
-
- callback_arg.prosrc = prosrc;
-
/*
* We can't do full prechecking of the function definition if there
* are any polymorphic input types, because actual datatypes of
@@ -1001,9 +997,6 @@ function_parse_error_transpose(const char *prosrc)
int newerrposition;
const char *queryText;
- if (!prosrc)
- return false;
-
/*
* Nothing to do unless we are dealing with a syntax error that has a
* cursor position.
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 199029b7a8..dc317c83af 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -958,8 +958,17 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
*sql_body_out = (Node *) q;
}
+ /*
+ * We must put something in prosrc. For the moment, just record an
+ * empty string. It might be useful to store the original text of the
+ * CREATE FUNCTION statement --- but to make actual use of that in
+ * error reports, we'd also have to adjust readfuncs.c to not throw
+ * away node location fields when reading prosqlbody.
+ */
+ *prosrc_str_p = pstrdup("");
+
+ /* But we definitely don't need probin. */
*probin_str_p = NULL;
- *prosrc_str_p = NULL;
}
else
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b2e2df8773..2cf6dad768 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -195,6 +195,8 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
palloc0(nParamExec * sizeof(ParamExecData));
}
+ /* We now require all callers to provide sourceText */
+ Assert(queryDesc->sourceText != NULL);
estate->es_sourceText = queryDesc->sourceText;
/*
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 642683843e..39580f7d57 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -667,6 +667,15 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
procedureTuple,
Anum_pg_proc_prosrc,
&isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc for function %u", foid);
+ fcache->src = TextDatumGetCString(tmp);
+
+ /* If we have prosqlbody, pay attention to that not prosrc. */
+ tmp = SysCacheGetAttr(PROCOID,
+ procedureTuple,
+ Anum_pg_proc_prosqlbody,
+ &isNull);
/*
* Parse and rewrite the queries in the function text. Use sublists to
@@ -678,18 +687,11 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
* plancache.c.
*/
queryTree_list = NIL;
- if (isNull)
+ if (!isNull)
{
Node *n;
List *stored_query_list;
- tmp = SysCacheGetAttr(PROCOID,
- procedureTuple,
- Anum_pg_proc_prosqlbody,
- &isNull);
- if (isNull)
- elog(ERROR, "null prosrc and prosqlbody for function %u", foid);
-
n = stringToNode(TextDatumGetCString(tmp));
if (IsA(n, List))
stored_query_list = linitial_node(List, castNode(List, n));
@@ -710,8 +712,6 @@ init_sql_fcache(FunctionCallInfo fcinfo, Oid collation, bool lazyEvalOK)
{
List *raw_parsetree_list;
- fcache->src = TextDatumGetCString(tmp);
-
raw_parsetree_list = pg_parse_query(fcache->src);
foreach(lc, raw_parsetree_list)
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 526997327c..d9ad4efc5e 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4317,32 +4317,37 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc for function %u", funcid);
+ src = TextDatumGetCString(tmp);
+
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = NULL;
+ callback_arg.prosrc = src;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
- /* Fetch the function body */
+ /* If we have prosqlbody, pay attention to that not prosrc */
tmp = SysCacheGetAttr(PROCOID,
func_tuple,
- Anum_pg_proc_prosrc,
+ Anum_pg_proc_prosqlbody,
&isNull);
- if (isNull)
+ if (!isNull)
{
Node *n;
List *querytree_list;
- tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull);
- if (isNull)
- elog(ERROR, "null prosrc and prosqlbody for function %u", funcid);
-
n = stringToNode(TextDatumGetCString(tmp));
if (IsA(n, List))
querytree_list = linitial_node(List, castNode(List, n));
@@ -4354,10 +4359,6 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
}
else
{
- src = TextDatumGetCString(tmp);
-
- callback_arg.prosrc = src;
-
/*
* Set up to handle parameters while parsing the function body. We need a
* dummy FuncExpr node containing the already-simplified arguments to pass
@@ -4658,15 +4659,12 @@ sql_inline_error_callback(void *arg)
int syntaxerrposition;
/* If it's a syntax error, convert to internal syntax error report */
- if (callback_arg->prosrc)
+ syntaxerrposition = geterrposition();
+ if (syntaxerrposition > 0)
{
- syntaxerrposition = geterrposition();
- if (syntaxerrposition > 0)
- {
- errposition(0);
- internalerrposition(syntaxerrposition);
- internalerrquery(callback_arg->prosrc);
- }
+ errposition(0);
+ internalerrposition(syntaxerrposition);
+ internalerrquery(callback_arg->prosrc);
}
errcontext("SQL function \"%s\" during inlining", callback_arg->proname);
@@ -4778,6 +4776,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
Oid func_oid;
HeapTuple func_tuple;
Form_pg_proc funcform;
+ char *src;
Datum tmp;
bool isNull;
MemoryContext oldcxt;
@@ -4886,31 +4885,36 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
ALLOCSET_DEFAULT_SIZES);
oldcxt = MemoryContextSwitchTo(mycxt);
+ /* Fetch the function body */
+ tmp = SysCacheGetAttr(PROCOID,
+ func_tuple,
+ Anum_pg_proc_prosrc,
+ &isNull);
+ if (isNull)
+ elog(ERROR, "null prosrc for function %u", func_oid);
+ src = TextDatumGetCString(tmp);
+
/*
* Setup error traceback support for ereport(). This is so that we can
* finger the function that bad information came from.
*/
callback_arg.proname = NameStr(funcform->proname);
- callback_arg.prosrc = NULL;
+ callback_arg.prosrc = src;
sqlerrcontext.callback = sql_inline_error_callback;
sqlerrcontext.arg = (void *) &callback_arg;
sqlerrcontext.previous = error_context_stack;
error_context_stack = &sqlerrcontext;
- /* Fetch the function body */
+ /* If we have prosqlbody, pay attention to that not prosrc */
tmp = SysCacheGetAttr(PROCOID,
func_tuple,
- Anum_pg_proc_prosrc,
+ Anum_pg_proc_prosqlbody,
&isNull);
- if (isNull)
+ if (!isNull)
{
Node *n;
- tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosqlbody, &isNull);
- if (isNull)
- elog(ERROR, "null prosrc and prosqlbody for function %u", func_oid);
-
n = stringToNode(TextDatumGetCString(tmp));
if (IsA(n, List))
querytree_list = linitial_node(List, castNode(List, n));
@@ -4927,12 +4931,6 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
}
else
{
- char *src;
-
- src = TextDatumGetCString(tmp);
-
- callback_arg.prosrc = src;
-
/*
* Set up to handle parameters while parsing the function body. We can
* use the FuncExpr just created as the input for
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d0ea489614..e1c65d8dfc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12247,7 +12247,7 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
if (fout->remoteVersion >= 140000)
appendPQExpBufferStr(query,
- "CASE WHEN prosrc IS NULL AND lanname = 'sql' THEN pg_get_function_sqlbody(p.oid) END AS prosqlbody\n");
+ "pg_get_function_sqlbody(p.oid) AS prosqlbody\n");
else
appendPQExpBufferStr(query,
"NULL AS prosqlbody\n");
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index fdc2a89085..400b683859 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -512,7 +512,7 @@ describeFunctions(const char *functypes, const char *func_pattern,
gettext_noop("Language"));
if (pset.sversion >= 140000)
appendPQExpBuffer(&buf,
- ",\n COALESCE(p.prosrc, pg_catalog.pg_get_function_sqlbody(p.oid)) as \"%s\"",
+ ",\n COALESCE(pg_catalog.pg_get_function_sqlbody(p.oid), p.prosrc) as \"%s\"",
gettext_noop("Source code"));
else
appendPQExpBuffer(&buf,
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 8d58067d03..448d9898cb 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -112,7 +112,7 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
Oid protrftypes[1] BKI_DEFAULT(_null_) BKI_LOOKUP(pg_type);
/* procedure source text */
- text prosrc;
+ text prosrc BKI_FORCE_NOT_NULL;
/* secondary procedure info (can be NULL) */
text probin BKI_DEFAULT(_null_);
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index dcb8e18437..b56ce26da0 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -17,9 +17,6 @@
#include "nodes/execnodes.h"
#include "tcop/dest.h"
-/* This struct is known only within executor/functions.c */
-typedef struct SQLFunctionParseInfo *SQLFunctionParseInfoPtr;
-
/*
* Data structure needed by the parser callback hooks to resolve parameter
* references during parsing of a SQL function's body. This is separate from
@@ -35,6 +32,8 @@ typedef struct SQLFunctionParseInfo
Oid collation; /* function's input collation, if known */
} SQLFunctionParseInfo;
+typedef SQLFunctionParseInfo *SQLFunctionParseInfoPtr;
+
extern Datum fmgr_sql(PG_FUNCTION_ARGS);
extern SQLFunctionParseInfoPtr prepare_sql_fn_parse_info(HeapTuple procedureTuple,
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index 477130e620..9f8733f75c 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -290,6 +290,12 @@ CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
LANGUAGE SQL
RETURN x[1];
ERROR: SQL function with unquoted function body cannot have polymorphic arguments
+-- check reporting of parse-analysis errors
+CREATE FUNCTION functest_S_xx(x date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN x > 1;
+ERROR: operator does not exist: date > integer
+HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
-- tricky parsing
CREATE FUNCTION functest_S_15(x int) RETURNS boolean
LANGUAGE SQL
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
index 3575ecc693..9a03301621 100644
--- a/src/test/regress/sql/create_function_3.sql
+++ b/src/test/regress/sql/create_function_3.sql
@@ -191,6 +191,11 @@ CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
LANGUAGE SQL
RETURN x[1];
+-- check reporting of parse-analysis errors
+CREATE FUNCTION functest_S_xx(x date) RETURNS boolean
+ LANGUAGE SQL
+ RETURN x > 1;
+
-- tricky parsing
CREATE FUNCTION functest_S_15(x int) RETURNS boolean
LANGUAGE SQL
0002-provide-source-in-parse-analysis.patchtext/x-diff; charset=us-ascii; name=0002-provide-source-in-parse-analysis.patchDownload
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index dc317c83af..e7cb5c65e9 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -852,7 +852,9 @@ static void
interpret_AS_clause(Oid languageOid, const char *languageName,
char *funcname, List *as, Node *sql_body_in,
List *parameterTypes, List *inParameterNames,
- char **prosrc_str_p, char **probin_str_p, Node **sql_body_out)
+ char **prosrc_str_p, char **probin_str_p,
+ Node **sql_body_out,
+ const char *queryString)
{
if (!sql_body_in && !as)
ereport(ERROR,
@@ -929,6 +931,7 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
Query *q;
ParseState *pstate = make_parsestate(NULL);
+ pstate->p_sourcetext = queryString;
sql_fn_parser_setup(pstate, pinfo);
q = transformStmt(pstate, stmt);
if (q->commandType == CMD_UTILITY)
@@ -947,6 +950,7 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
Query *q;
ParseState *pstate = make_parsestate(NULL);
+ pstate->p_sourcetext = queryString;
sql_fn_parser_setup(pstate, pinfo);
q = transformStmt(pstate, sql_body_in);
if (q->commandType == CMD_UTILITY)
@@ -954,6 +958,7 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("%s is not yet supported in unquoted SQL function body",
GetCommandTagName(CreateCommandTag(q->utilityStmt))));
+ free_parsestate(pstate);
*sql_body_out = (Node *) q;
}
@@ -1220,7 +1225,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
interpret_AS_clause(languageOid, language, funcname, as_clause, stmt->sql_body,
parameterTypes_list, inParameterNames_list,
- &prosrc_str, &probin_str, &prosqlbody);
+ &prosrc_str, &probin_str, &prosqlbody,
+ pstate->p_sourcetext);
/*
* Set default values for COST and ROWS depending on other parameters;
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index 9f8733f75c..e11a7a2dad 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -295,6 +295,8 @@ CREATE FUNCTION functest_S_xx(x date) RETURNS boolean
LANGUAGE SQL
RETURN x > 1;
ERROR: operator does not exist: date > integer
+LINE 3: RETURN x > 1;
+ ^
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
-- tricky parsing
CREATE FUNCTION functest_S_15(x int) RETURNS boolean
On 4/9/21 12:09 PM, Tom Lane wrote:
One could make an argument, therefore, for holding off 0003 until
there's more support for execution-time error cursors. I don't
think we should though, for two reasons:
1. It'd be better to keep the pg_proc representation of new-style
SQL functions stable across versions.
2. Storing the CREATE text means we'll capture comments associated
with the function text, which is something that at least some
people will complain about the loss of. Admittedly we have no way
to re-integrate the comments into the de-parsed body, but some
folks might be satisfied with grabbing the prosrc text.
+many for storing query text.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
On Fri, Apr 09, 2021 at 12:09:43PM -0400, Tom Lane wrote:
Finally, 0003 might be a bit controversial: it changes the stored
prosrc for new-style SQL functions to be the query text of the CREATE
FUNCTION command. The main argument I can see being made against this
is that it'll bloat the pg_proc entry. But I think that that's
not a terribly reasonable concern
Such storage cost should be acceptable, but ...
The real value of 0003 of course would be to get an error cursor at
runtime
A key benefit of $SUBJECT is the function body following DDL renames:
create table foo ();
insert into foo default values;
create function count_it() returns int begin atomic return (select count(*) from foo); end;
select count_it();
insert into foo default values;
alter table foo rename to some_new_long_table_name;
select count_it(); -- still works
After the rename, any stored prosrc is obsolete. To show accurate error
cursors, deparse prosqlbody and use that in place of prosrc.
Noah Misch <noah@leadboat.com> writes:
On Fri, Apr 09, 2021 at 12:09:43PM -0400, Tom Lane wrote:
The real value of 0003 of course would be to get an error cursor at
runtime
A key benefit of $SUBJECT is the function body following DDL renames:
Agreed. But ...
After the rename, any stored prosrc is obsolete. To show accurate error
cursors, deparse prosqlbody and use that in place of prosrc.
... I'm not sure this conclusion follows. There are two problems with it:
1. I don't see an acceptably low-overhead way to mechanize it.
Deparsing prosqlbody is unlikely to be safe in a post-error transaction,
but surely we'd not want to expend that cost in advance on every use
of a SQL function. Even ignoring that, the act of deparsing would not
in itself tell you what offset to use. Should we deparse and then
re-parse to get a new node tree with corrected token locations?
2. The reason we can get away with showing a fragment of a large query
(or function body) in an error message is that the user is supposed to
be able to correlate the display with what she wrote. That assumption
falls to the ground if the display is based on a deconstruction that is
virtually certain to have line breaks in different places, not to mention
that the details of what is shown may be substantially different from the
original.
Still, I take your point that the original text may be less useful
for this purpose than I was supposing.
regards, tom lane
On Sat, Apr 10, 2021 at 10:52:15AM -0400, Tom Lane wrote:
Noah Misch <noah@leadboat.com> writes:
On Fri, Apr 09, 2021 at 12:09:43PM -0400, Tom Lane wrote:
The real value of 0003 of course would be to get an error cursor at
runtimeA key benefit of $SUBJECT is the function body following DDL renames:
Agreed. But ...
After the rename, any stored prosrc is obsolete. To show accurate error
cursors, deparse prosqlbody and use that in place of prosrc.... I'm not sure this conclusion follows. There are two problems with it:
1. I don't see an acceptably low-overhead way to mechanize it.
Deparsing prosqlbody is unlikely to be safe in a post-error transaction,
but surely we'd not want to expend that cost in advance on every use
of a SQL function. Even ignoring that, the act of deparsing would not
in itself tell you what offset to use. Should we deparse and then
re-parse to get a new node tree with corrected token locations?
If you really want those error cursors, yes. (I feel we can continue to live
without them; their absence is no more important than it was ten years ago.)
One can envision several ways to cache that high-overhead work. Otherwise,
the usual PostgreSQL answer would be to omit an error cursor, not show one
that reflects an obsolete sense of the function.
If the original CREATE FUNCTION query text were so valuable, I'd be arguing to
preserve it across dump/reload.
2. The reason we can get away with showing a fragment of a large query
(or function body) in an error message is that the user is supposed to
be able to correlate the display with what she wrote. That assumption
falls to the ground if the display is based on a deconstruction that is
virtually certain to have line breaks in different places, not to mention
that the details of what is shown may be substantially different from the
original.
Preferences on this matter will be situation-dependent. If I do CREATE
FUNCTION f() ...; SELECT f() all in one sitting, then it's fine for an error
in the SELECT to show the function I wrote. If I'm calling a function defined
years ago, I'm likely to compare the error report to "\sf foo" and not likely
to compare it to a years-old record of the SQL statement. I think it's fine
to expect users to consult "\sf foo" when the user is in doubt.
Show quoted text
Still, I take your point that the original text may be less useful
for this purpose than I was supposing.
Based on the discussion so far, I've committed 0001 and 0002 but not 0003,
and marked this open issue as closed.
regards, tom lane
On Tue, Jun 30, 2020 at 02:51:38PM -0400, Tom Lane wrote:
The point remains that exposing the function body's dependencies will
constrain restore order far more than we are accustomed to see. It
might be possible to build examples that flat out can't be restored,
even granting that we teach pg_dump how to break dependency loops
by first creating the function with empty body and later redefining
it with the real body. (Admittedly, if that's possible then you
likely could make it happen with views too. But somehow it seems
more likely that people would create spaghetti dependencies for
functions than views.)
Should we be okay releasing v14 without support for breaking function
dependency loops, or does that call for an open item?
-- example
create function f() returns int language sql return 1;
create function g() returns int language sql return f();
create or replace function f() returns int language sql return coalesce(2, g());
-- but when a view can break the cycle, pg_dump still does so
create view v as select 1 as c;
create function f() returns int language sql return coalesce(0, (select count(*) from v));
create or replace view v as select f() as c;
Noah Misch <noah@leadboat.com> writes:
Should we be okay releasing v14 without support for breaking function
dependency loops, or does that call for an open item?
Oh! That should definitely be an open item. It doesn't seem
that hard to do something similar to what we do for views,
i.e. create a dummy function and replace it later.
regards, tom lane
On Sun, Apr 18, 2021 at 03:08:44PM -0400, Tom Lane wrote:
Noah Misch <noah@leadboat.com> writes:
Should we be okay releasing v14 without support for breaking function
dependency loops, or does that call for an open item?Oh! That should definitely be an open item. It doesn't seem
that hard to do something similar to what we do for views,
i.e. create a dummy function and replace it later.
... BTW, a dependency loop is also possible without using this feature,
by abusing default-value expressions:
create function f1(x int, y int) returns int language sql
as 'select $1 + $2';
create function f2(x int, y int default f1(1,2)) returns int language sql
as 'select $1 + $2';
create or replace function f1(x int, y int default f2(11,12)) returns int language sql
as 'select $1 + $2';
The actual use-case for that seems pretty thin, so we never bothered
to worry about it before. But if we're going to build loop-breaking
logic to handle function body dependencies, it should deal with this
too. I think that all that's required is for the initial dummy
function declaration to omit defaults as well as providing a dummy
body.
regards, tom lane
On Wed, Apr 7, 2021 at 3:55 PM Peter Eisentraut <
peter.eisentraut@2ndquadrant.com> wrote:
Committed. Thanks!
This commit break line continuation prompts for unbalanced parentheses in
the psql binary. Skimming through this thread, I don't see that this is
intentional or has been noticed before.
with psql -X
Before:
jjanes=# asdf (
jjanes(#
Now:
jjanes=# asdf (
jjanes-#
I've looked through the parts of the commit that change psql, but didn't
see an obvious culprit.
Cheers,
Jeff
On Thu, Apr 22, 2021 at 04:04:18PM -0400, Jeff Janes wrote:
This commit break line continuation prompts for unbalanced parentheses in
the psql binary. Skimming through this thread, I don't see that this is
intentional or has been noticed before.with psql -X
Before:
jjanes=# asdf (
jjanes(#Now:
jjanes=# asdf (
jjanes-#I've looked through the parts of the commit that change psql, but didn't
see an obvious culprit.
I haven't studied it in detail, but it probably needs something like this.
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 991b7de0b5..0fab48a382 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -1098,23 +1098,23 @@ psql_scan(PsqlScanState state,
{
case LEXRES_EOL: /* end of input */
switch (state->start_state)
{
case INITIAL:
case xqs: /* we treat this like INITIAL */
if (state->paren_depth > 0)
{
result = PSCAN_INCOMPLETE;
*prompt = PROMPT_PAREN;
}
- if (state->begin_depth > 0)
+ else if (state->begin_depth > 0)
{
result = PSCAN_INCOMPLETE;
*prompt = PROMPT_CONTINUE;
}
else if (query_buf->len > 0)
{
result = PSCAN_EOL;
*prompt = PROMPT_CONTINUE;
}
else
{
On 18.04.21 23:33, Tom Lane wrote:
... BTW, a dependency loop is also possible without using this feature,
by abusing default-value expressions:create function f1(x int, y int) returns int language sql
as 'select $1 + $2';
create function f2(x int, y int default f1(1,2)) returns int language sql
as 'select $1 + $2';
create or replace function f1(x int, y int default f2(11,12)) returns int language sql
as 'select $1 + $2';The actual use-case for that seems pretty thin, so we never bothered
to worry about it before. But if we're going to build loop-breaking
logic to handle function body dependencies, it should deal with this
too. I think that all that's required is for the initial dummy
function declaration to omit defaults as well as providing a dummy
body.
I have studied this a bit. I'm not sure where the dummy function
declaration should be created. The current dependency-breaking logic in
pg_dump_sort.c doesn't appear to support injecting additional objects
into the set of dumpable objects. So we would need to create it perhaps
in dumpFunc() and then later set flags that indicate whether it will be
required.
Another option would be that we disallow this at creation time. It
seems we could detect dependency loops using findDependentObjects(), so
this might not be so difficult. The use case for recursive SQL
functions is probably low, at least with the current limited set of
control flow options in SQL. (And you can always use a quoted body to
work around it.)
Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:
On 18.04.21 23:33, Tom Lane wrote:
The actual use-case for that seems pretty thin, so we never bothered
to worry about it before. But if we're going to build loop-breaking
logic to handle function body dependencies, it should deal with this
too. I think that all that's required is for the initial dummy
function declaration to omit defaults as well as providing a dummy
body.
I have studied this a bit. I'm not sure where the dummy function
declaration should be created. The current dependency-breaking logic in
pg_dump_sort.c doesn't appear to support injecting additional objects
into the set of dumpable objects. So we would need to create it perhaps
in dumpFunc() and then later set flags that indicate whether it will be
required.
Hmm, good point. The existing code that breaks loops involving views
depends on the fact that the view relation and the view's ON SELECT
rule are already treated as distinct objects within pg_dump. So we
just need to mark the rule object to indicate whether to emit it or
not. To make it work for functions, there would have to be a secondary
object representing the function body (and the default expressions,
I guess).
That's kind of a lot of complication, and inefficiency, for a corner case
that may never arise in practice. We've ignored the risk for default
expressions, and AFAIR have yet to receive any field complaints about it.
So maybe it's okay to do the same for SQL-style function bodies, at least
for now.
Another option would be that we disallow this at creation time.
Don't like that one much. The backend shouldn't be in the business
of rejecting valid commands just because pg_dump might be unable
to cope later.
regards, tom lane
On 27.04.21 04:44, Justin Pryzby wrote:
On Thu, Apr 22, 2021 at 04:04:18PM -0400, Jeff Janes wrote:
This commit break line continuation prompts for unbalanced parentheses in
the psql binary. Skimming through this thread, I don't see that this is
intentional or has been noticed before.with psql -X
Before:
jjanes=# asdf (
jjanes(#Now:
jjanes=# asdf (
jjanes-#I've looked through the parts of the commit that change psql, but didn't
see an obvious culprit.I haven't studied it in detail, but it probably needs something like this.
Yeah, fixed like that.
On 27.04.21 18:16, Tom Lane wrote:
That's kind of a lot of complication, and inefficiency, for a corner case
that may never arise in practice. We've ignored the risk for default
expressions, and AFAIR have yet to receive any field complaints about it.
So maybe it's okay to do the same for SQL-style function bodies, at least
for now.Another option would be that we disallow this at creation time.
Don't like that one much. The backend shouldn't be in the business
of rejecting valid commands just because pg_dump might be unable
to cope later.
Since this is listed as an open item, I want to clarify that I'm
currently not planning to work on this, based on this discussion.
Certainly something to look into sometime later, but it's not in my
plans right now.
Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:
On 27.04.21 18:16, Tom Lane wrote:
That's kind of a lot of complication, and inefficiency, for a corner case
that may never arise in practice. We've ignored the risk for default
expressions, and AFAIR have yet to receive any field complaints about it.
So maybe it's okay to do the same for SQL-style function bodies, at least
for now.
Another option would be that we disallow this at creation time.
Don't like that one much. The backend shouldn't be in the business
of rejecting valid commands just because pg_dump might be unable
to cope later.
Since this is listed as an open item, I want to clarify that I'm
currently not planning to work on this, based on this discussion.
Certainly something to look into sometime later, but it's not in my
plans right now.
Right, I concur with moving it to the "won't fix" category.
regards, tom lane
On Mon, May 10, 2021 at 11:09:43AM -0400, Tom Lane wrote:
Peter Eisentraut <peter.eisentraut@enterprisedb.com> writes:
On 27.04.21 18:16, Tom Lane wrote:
That's kind of a lot of complication, and inefficiency, for a corner case
that may never arise in practice. We've ignored the risk for default
expressions, and AFAIR have yet to receive any field complaints about it.
So maybe it's okay to do the same for SQL-style function bodies, at least
for now.Another option would be that we disallow this at creation time.
Don't like that one much. The backend shouldn't be in the business
of rejecting valid commands just because pg_dump might be unable
to cope later.Since this is listed as an open item, I want to clarify that I'm
currently not planning to work on this, based on this discussion.
Certainly something to look into sometime later, but it's not in my
plans right now.Right, I concur with moving it to the "won't fix" category.
Works for me.
On Wed, Apr 07, 2021 at 09:55:40PM +0200, Peter Eisentraut wrote:
Committed. Thanks!
I get a NULL pointer dereference if the function body has a doubled semicolon:
create function f() returns int language sql begin atomic select 1;; end;
Program received signal SIGSEGV, Segmentation fault.
transformStmt (pstate=pstate@entry=0x2623978, parseTree=parseTree@entry=0x0) at analyze.c:297
297 switch (nodeTag(parseTree))
#0 transformStmt (pstate=pstate@entry=0x2623978, parseTree=parseTree@entry=0x0) at analyze.c:297
#1 0x00000000006132a4 in interpret_AS_clause (queryString=<optimized out>, sql_body_out=<synthetic pointer>, probin_str_p=<synthetic pointer>, prosrc_str_p=<synthetic pointer>, inParameterNames=<optimized out>, parameterTypes=<optimized out>,
sql_body_in=<optimized out>, as=<optimized out>, funcname=<optimized out>, languageName=<optimized out>, languageOid=14) at functioncmds.c:937
#2 CreateFunction (pstate=pstate@entry=0x26213e0, stmt=stmt@entry=0x25fd048) at functioncmds.c:1227
#3 0x0000000000813e23 in ProcessUtilitySlow (pstate=pstate@entry=0x26213e0, pstmt=pstmt@entry=0x25fd3b8, queryString=queryString@entry=0x25fc040 "create function f() returns int language sql begin atomic select 1;; end;",
context=context@entry=PROCESS_UTILITY_TOPLEVEL, params=params@entry=0x0, queryEnv=queryEnv@entry=0x0, qc=qc@entry=0x7fff4b715b70, dest=0x25fd4a8) at utility.c:1607
#4 0x0000000000812944 in standard_ProcessUtility (pstmt=0x25fd3b8, queryString=0x25fc040 "create function f() returns int language sql begin atomic select 1;; end;", context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0x25fd4a8,
qc=0x7fff4b715b70) at utility.c:1034
#5 0x0000000000810efe in PortalRunUtility (portal=portal@entry=0x265fb60, pstmt=0x25fd3b8, isTopLevel=isTopLevel@entry=true, setHoldSnapshot=setHoldSnapshot@entry=false, dest=0x25fd4a8, qc=0x7fff4b715b70) at pquery.c:1147
#6 0x0000000000811053 in PortalRunMulti (portal=portal@entry=0x265fb60, isTopLevel=isTopLevel@entry=true, setHoldSnapshot=setHoldSnapshot@entry=false, dest=dest@entry=0x25fd4a8, altdest=altdest@entry=0x25fd4a8, qc=qc@entry=0x7fff4b715b70) at pquery.c:1310
#7 0x00000000008115e4 in PortalRun (portal=portal@entry=0x265fb60, count=count@entry=9223372036854775807, isTopLevel=isTopLevel@entry=true, run_once=run_once@entry=true, dest=dest@entry=0x25fd4a8, altdest=altdest@entry=0x25fd4a8, qc=qc@entry=0x7fff4b715b70)
at pquery.c:786
#8 0x000000000080d004 in exec_simple_query (query_string=0x25fc040 "create function f() returns int language sql begin atomic select 1;; end;") at postgres.c:1214
#9 0x000000000080ee1f in PostgresMain (argc=argc@entry=1, argv=argv@entry=0x7fff4b716030, dbname=0x2627788 "test", username=<optimized out>) at postgres.c:4486
#10 0x000000000048bc97 in BackendRun (port=<optimized out>, port=<optimized out>) at postmaster.c:4507
#11 BackendStartup (port=0x261f480) at postmaster.c:4229
#12 ServerLoop () at postmaster.c:1745
#13 0x000000000077c278 in PostmasterMain (argc=argc@entry=1, argv=argv@entry=0x25f6a00) at postmaster.c:1417
#14 0x000000000048d51e in main (argc=1, argv=0x25f6a00) at main.c:209
(gdb) p parseTree
$1 = (Node *) 0x0
On Sat, Jun 05, 2021 at 09:44:18PM -0700, Noah Misch wrote:
On Wed, Apr 07, 2021 at 09:55:40PM +0200, Peter Eisentraut wrote:
Committed. Thanks!
I get a NULL pointer dereference if the function body has a doubled semicolon:
create function f() returns int language sql begin atomic select 1;; end;
You don't even need a statements to reproduce the problem, a body containing
only semi-colon(s) will behave the same.
Attached patch should fix the problem.
Attachments:
v1-0001-Fix-SQL-standard-body-empty-statements-handling.patchtext/x-diff; charset=us-asciiDownload
From d03f6f0aee61fb2f2246d72c266671fe975d6a65 Mon Sep 17 00:00:00 2001
From: Julien Rouhaud <julien.rouhaud@free.fr>
Date: Sun, 6 Jun 2021 15:28:59 +0800
Subject: [PATCH v1] Fix SQL-standard body empty statements handling.
---
src/backend/commands/functioncmds.c | 3 +++
src/test/regress/expected/create_function_3.out | 2 +-
src/test/regress/sql/create_function_3.sql | 2 +-
3 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 4c12aa33df..61de9bf11a 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -932,6 +932,9 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
Query *q;
ParseState *pstate = make_parsestate(NULL);
+ if (!stmt)
+ continue;
+
pstate->p_sourcetext = queryString;
sql_fn_parser_setup(pstate, pinfo);
q = transformStmt(pstate, stmt);
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index 5b6bc5eddb..5955859bb5 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -267,7 +267,7 @@ CREATE FUNCTION functest_S_3() RETURNS boolean
RETURN false;
CREATE FUNCTION functest_S_3a() RETURNS boolean
BEGIN ATOMIC
- RETURN false;
+ ;;RETURN false;;
END;
CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
LANGUAGE SQL
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
index 4b778999ed..6e8b838ff2 100644
--- a/src/test/regress/sql/create_function_3.sql
+++ b/src/test/regress/sql/create_function_3.sql
@@ -165,7 +165,7 @@ CREATE FUNCTION functest_S_3() RETURNS boolean
RETURN false;
CREATE FUNCTION functest_S_3a() RETURNS boolean
BEGIN ATOMIC
- RETURN false;
+ ;;RETURN false;;
END;
CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
--
2.31.1
On 06.06.21 09:32, Julien Rouhaud wrote:
On Sat, Jun 05, 2021 at 09:44:18PM -0700, Noah Misch wrote:
I get a NULL pointer dereference if the function body has a doubled semicolon:
create function f() returns int language sql begin atomic select 1;; end;
You don't even need a statements to reproduce the problem, a body containing
only semi-colon(s) will behave the same.Attached patch should fix the problem.
Your patch filters out empty statements at the parse transformation
phase, so they are no longer present when you dump the body back out.
So your edits in the test expected files don't fit.
I suggest we just prohibit empty statements at the parse stage. I don't
see a strong reason to allow them, and if we wanted to, we'd have to do
more work, e.g., in ruleutils.c to print them back out correctly.
Attachments:
0001-Prevent-empty-statement-in-unquoted-SQL-function-bod.patchtext/plain; charset=UTF-8; name=0001-Prevent-empty-statement-in-unquoted-SQL-function-bod.patch; x-mac-creator=0; x-mac-type=0Download
From 19817fa97da9afa4fd35365c6ecb968b53aff7fe Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 7 Jun 2021 10:49:36 +0200
Subject: [PATCH] Prevent empty statement in unquoted SQL function body
This currently crashes. It was not meant to be supported, and there
is no support for example in ruleutils.c, so just prohibit it in the
parser.
Reported-by: Noah Misch <noah@leadboat.com>
Discussion: https://www.postgresql.org/message-id/20210606044418.GA297923@rfd.leadboat.com
---
src/backend/parser/gram.y | 7 ++++---
src/test/regress/expected/create_function_3.out | 8 ++++++++
src/test/regress/sql/create_function_3.sql | 6 ++++++
3 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9ee90e3f13..3cd30c15ec 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -893,11 +893,14 @@ stmtmulti: stmtmulti ';' toplevel_stmt
/*
* toplevel_stmt includes BEGIN and END. stmt does not include them, because
- * those words have different meanings in function bodys.
+ * those words have different meanings in function bodys. Also, empty
+ * statements are allowed at the top level but not in function bodies.
*/
toplevel_stmt:
stmt
| TransactionStmtLegacy
+ | /*EMPTY*/
+ { $$ = NULL; }
;
stmt:
@@ -1024,8 +1027,6 @@ stmt:
| VariableSetStmt
| VariableShowStmt
| ViewStmt
- | /*EMPTY*/
- { $$ = NULL; }
;
/*****************************************************************************
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index 5b6bc5eddb..41d8e49a23 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -290,6 +290,14 @@ CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
LANGUAGE SQL
RETURN x[1];
ERROR: SQL function with unquoted function body cannot have polymorphic arguments
+-- error: empty statement
+CREATE FUNCTION functest_S_xx() RETURNS boolean
+ BEGIN ATOMIC
+ RETURN false;;
+ END;
+ERROR: syntax error at or near ";"
+LINE 3: RETURN false;;
+ ^
-- check reporting of parse-analysis errors
CREATE FUNCTION functest_S_xx(x date) RETURNS boolean
LANGUAGE SQL
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
index 4b778999ed..5c28ddcbce 100644
--- a/src/test/regress/sql/create_function_3.sql
+++ b/src/test/regress/sql/create_function_3.sql
@@ -191,6 +191,12 @@ CREATE FUNCTION functest_S_xx(x anyarray) RETURNS anyelement
LANGUAGE SQL
RETURN x[1];
+-- error: empty statement
+CREATE FUNCTION functest_S_xx() RETURNS boolean
+ BEGIN ATOMIC
+ RETURN false;;
+ END;
+
-- check reporting of parse-analysis errors
CREATE FUNCTION functest_S_xx(x date) RETURNS boolean
LANGUAGE SQL
--
2.31.1
On Mon, Jun 7, 2021 at 4:52 PM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
Your patch filters out empty statements at the parse transformation
phase, so they are no longer present when you dump the body back out.
So your edits in the test expected files don't fit.
Oh, somehow the tests aren't failing here, I'm not sure what I did wrong.
I suggest we just prohibit empty statements at the parse stage. I don't
see a strong reason to allow them, and if we wanted to, we'd have to do
more work, e.g., in ruleutils.c to print them back out correctly.
I always thought extraneous semicolons were tokens to be ignored,
which happens to be internally implemented as empty statements, so
deparsing them is not required, similar to deparsing extraneous
whitespaces. If the spec says otherwise then I agree it's not worth
implementing, but otherwise I'm not sure if it's really helpful to
error out.
Julien Rouhaud <rjuju123@gmail.com> writes:
On Mon, Jun 7, 2021 at 4:52 PM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:Your patch filters out empty statements at the parse transformation
phase, so they are no longer present when you dump the body back out.
So your edits in the test expected files don't fit.
Oh, somehow the tests aren't failing here, I'm not sure what I did wrong.
Modulo getting the tests right ...
I suggest we just prohibit empty statements at the parse stage. I don't
see a strong reason to allow them, and if we wanted to, we'd have to do
more work, e.g., in ruleutils.c to print them back out correctly.
I always thought extraneous semicolons were tokens to be ignored,
... I tend to agree with Julien's position here. It seems really ugly
to prohibit empty statements just for implementation convenience.
However, the way I'd handle it is to have the grammar remove them,
which is what it does in other contexts. I don't think there's any
need to preserve them in ruleutils output --- there's a lot of other
normalization we do on the way to that, and this seems to fit in.
BTW, is it just me, or does SQL:2021 fail to permit multiple
statements in a procedure at all? After much searching, I found the
BEGIN ATOMIC ... END syntax, but it's in <triggered SQL statement>,
in other words the body of a trigger not a procedure. I cannot find
any production that connects a <routine body> to that. There's an
example showing use of BEGIN ATOMIC as a procedure statement, so
they clearly *meant* to allow it, but it looks like somebody messed
up the grammar.
regards, tom lane
On Mon, Jun 7, 2021 at 11:27 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Julien Rouhaud <rjuju123@gmail.com> writes:
On Mon, Jun 7, 2021 at 4:52 PM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:Your patch filters out empty statements at the parse transformation
phase, so they are no longer present when you dump the body back out.
So your edits in the test expected files don't fit.Oh, somehow the tests aren't failing here, I'm not sure what I did wrong.
Modulo getting the tests right ...
I can certainly accept that my patch broke the tests, but I just ran
another make check-world and it passed without any problem. What am I
missing?
I wrote:
... I tend to agree with Julien's position here. It seems really ugly
to prohibit empty statements just for implementation convenience.
However, the way I'd handle it is to have the grammar remove them,
which is what it does in other contexts.
Concretely, I think the right fix is per attached.
Like Julien, I don't see any additional change in regression test outputs.
Maybe Peter thinks there should be some? But I think the reverse-listing
we get for functest_S_3a is fine.
regards, tom lane
Attachments:
fix-empty-statements-2.patchtext/x-diff; charset=us-ascii; name=fix-empty-statements-2.patchDownload
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9ee90e3f13..52a254928f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -7990,7 +7990,11 @@ opt_routine_body:
routine_body_stmt_list:
routine_body_stmt_list routine_body_stmt ';'
{
- $$ = lappend($1, $2);
+ /* As in stmtmulti, discard empty statements */
+ if ($2 != NULL)
+ $$ = lappend($1, $2);
+ else
+ $$ = $1;
}
| /*EMPTY*/
{
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index 5b6bc5eddb..5955859bb5 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -267,7 +267,7 @@ CREATE FUNCTION functest_S_3() RETURNS boolean
RETURN false;
CREATE FUNCTION functest_S_3a() RETURNS boolean
BEGIN ATOMIC
- RETURN false;
+ ;;RETURN false;;
END;
CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
LANGUAGE SQL
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
index 4b778999ed..6e8b838ff2 100644
--- a/src/test/regress/sql/create_function_3.sql
+++ b/src/test/regress/sql/create_function_3.sql
@@ -165,7 +165,7 @@ CREATE FUNCTION functest_S_3() RETURNS boolean
RETURN false;
CREATE FUNCTION functest_S_3a() RETURNS boolean
BEGIN ATOMIC
- RETURN false;
+ ;;RETURN false;;
END;
CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
On 07.06.21 17:27, Tom Lane wrote:
... I tend to agree with Julien's position here. It seems really ugly
to prohibit empty statements just for implementation convenience.
However, the way I'd handle it is to have the grammar remove them,
which is what it does in other contexts. I don't think there's any
need to preserve them in ruleutils output --- there's a lot of other
normalization we do on the way to that, and this seems to fit in.
Ok, if that's what people prefer.
BTW, is it just me, or does SQL:2021 fail to permit multiple
statements in a procedure at all? After much searching, I found the
BEGIN ATOMIC ... END syntax, but it's in <triggered SQL statement>,
in other words the body of a trigger not a procedure. I cannot find
any production that connects a <routine body> to that. There's an
example showing use of BEGIN ATOMIC as a procedure statement, so
they clearly*meant* to allow it, but it looks like somebody messed
up the grammar.
It's in the SQL/PSM part.
On Mon, Jun 07, 2021 at 03:24:33PM -0400, Tom Lane wrote:
Concretely, I think the right fix is per attached.
+1, I agree that this approach is better.