mirror of
https://github.com/postgres/postgres.git
synced 2025-08-27 07:42:10 +03:00
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. Tested-by: Jaime Casanova <jcasanov@systemguards.com.ec> Reviewed-by: Julien Rouhaud <rjuju123@gmail.com> Discussion: https://www.postgresql.org/message-id/flat/1c11f1eb-f00c-43b7-799d-2d44132c02d7@2ndquadrant.com
This commit is contained in:
@@ -4253,27 +4253,47 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
|
||||
ALLOCSET_DEFAULT_SIZES);
|
||||
oldcxt = MemoryContextSwitchTo(mycxt);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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)
|
||||
elog(ERROR, "null prosrc for function %u", funcid);
|
||||
{
|
||||
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);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
sqlerrcontext.callback = sql_inline_error_callback;
|
||||
sqlerrcontext.arg = (void *) &callback_arg;
|
||||
sqlerrcontext.previous = error_context_stack;
|
||||
error_context_stack = &sqlerrcontext;
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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)
|
||||
elog(ERROR, "null prosrc for function %u", func_oid);
|
||||
{
|
||||
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);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
sqlerrcontext.callback = sql_inline_error_callback;
|
||||
sqlerrcontext.arg = (void *) &callback_arg;
|
||||
sqlerrcontext.previous = error_context_stack;
|
||||
error_context_stack = &sqlerrcontext;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
|
Reference in New Issue
Block a user