mirror of
https://github.com/postgres/postgres.git
synced 2025-11-22 12:22:45 +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:
@@ -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,62 +874,82 @@ 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;
|
||||
|
||||
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;
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
/*
|
||||
* OK to do full precheck: analyze and rewrite the queries, then
|
||||
* verify the result type.
|
||||
*/
|
||||
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 = lappend(querytree_list,
|
||||
querytree_sublist);
|
||||
}
|
||||
|
||||
check_sql_fn_statements(querytree_list);
|
||||
|
||||
(void) get_func_result_type(funcoid, &rettype, &rettupdesc);
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user