diff options
author | Peter Eisentraut <peter@eisentraut.org> | 2021-04-07 21:30:08 +0200 |
---|---|---|
committer | Peter Eisentraut <peter@eisentraut.org> | 2021-04-07 21:47:55 +0200 |
commit | e717a9a18b2e34c9c40e5259ad4d31cd7e420750 (patch) | |
tree | 6eda5b4cf6468d599efc0da4628bec53d35484af /src/backend/utils/adt/ruleutils.c | |
parent | 1e55e7d1755cefbb44982fbacc7da461fa8684e6 (diff) | |
download | postgresql-e717a9a18b2e34c9c40e5259ad4d31cd7e420750.tar.gz postgresql-e717a9a18b2e34c9c40e5259ad4d31cd7e420750.zip |
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
Diffstat (limited to 'src/backend/utils/adt/ruleutils.c')
-rw-r--r-- | src/backend/utils/adt/ruleutils.c | 139 |
1 files changed, 138 insertions, 1 deletions
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 0b5314e49b3..0a4fa93d016 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,83 @@ 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, 0, WRAP_COLUMN_DEFAULT, 0); + } +} + +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 +5727,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) @@ -7772,6 +7865,50 @@ get_parameter(Param *param, deparse_context *context) } /* + * 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. */ appendStringInfo(context->buf, "$%d", param->paramid); |