diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 9ba6dd8..2968225 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -48,7 +48,9 @@ #include "catalog/pg_type_fn.h" #include "commands/defrem.h" #include "commands/proclang.h" +#include "executor/executor.h" #include "miscadmin.h" +#include "optimizer/planmain.h" #include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parse_collate.h" @@ -57,6 +59,7 @@ #include "parser/parse_type.h" #include "utils/acl.h" #include "utils/builtins.h" +#include "utils/datum.h" #include "utils/fmgroids.h" #include "utils/guc.h" #include "utils/lsyscache.h" @@ -1911,7 +1914,7 @@ AlterFunctionNamespace_oid(Oid procOid, Oid nspOid) * Execute inline procedural-language code */ void -ExecuteDoStmt(DoStmt *stmt) +ExecuteDoStmt(DoStmt *stmt, const char *queryString) { InlineCodeBlock *codeblock = makeNode(InlineCodeBlock); ListCell *arg; @@ -2003,6 +2006,131 @@ ExecuteDoStmt(DoStmt *stmt) ReleaseSysCache(languageTuple); + /* evaluate parameters if exists */ + if (stmt->params != NULL) + { + ListCell *current_param; + ListCell *current_expr; + ParseState *pstate; + EState *estate; + int i; + int nparams; + + current_param = list_head(stmt->params); + current_expr = list_head(stmt->expr_list); + + nparams = list_length(stmt->params); + + codeblock->nparams = nparams; + codeblock->dvalues = (Datum *) palloc(nparams * sizeof(Datum)); + codeblock->nulls = (bool *) palloc(nparams * sizeof(bool)); + codeblock->names = (char **) palloc(nparams * sizeof(char *)); + codeblock->typoids = (Oid *) palloc(nparams * sizeof(Oid)); + + /* prepare pstate for parse analysis of param exprs */ + pstate = make_parsestate(NULL); + pstate->p_sourcetext = queryString; + + for (i = 0; i < nparams; i++) + { + FunctionParameter *fp = (FunctionParameter *) lfirst(current_param); + Type typtup; + Oid typid; + Node *expr; + ExprState *exprstate; + MemoryContext oldcontext; + Datum const_val; + bool const_is_null; + int16 resultTypLen; + bool resultTypByVal; + + if (current_expr == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("too few parameters specified for inline code"))); + + codeblock->names[i] = fp->name; + + typtup = LookupTypeName(NULL, fp->argType, NULL); + if (typtup) + { + if (!((Form_pg_type) GETSTRUCT(typtup))->typisdefined) + ereport(NOTICE, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("argument type %s is only a shell", + TypeNameToString(fp->argType)))); + + typid = typeTypeId(typtup); + ReleaseSysCache(typtup); + codeblock->typoids[i] = typid; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("type %s does not exist", + TypeNameToString(fp->argType)))); + typid = InvalidOid; /* keep compiler quiet */ + } + + /* evaluate expression */ + expr = lfirst(current_expr); + expr = transformExpr(pstate, expr); + expr = coerce_to_specific_type(pstate, expr, typid, "DEFAULT"); + + estate = CreateExecutorState(); + oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + + fix_opfuncids(expr); + + /* Prepare expr for execution */ + exprstate = ExecPrepareExpr((Expr *) expr, estate); + + /* And evaluaye it */ + const_val = ExecEvalExprSwitchContext(exprstate, + GetPerTupleExprContext(estate), + &const_is_null, NULL); + /* Get info needed about result datatype */ + get_typlenbyval(typid, &resultTypLen, &resultTypByVal); + + /* Get back outer memory context */ + MemoryContextSwitchTo(oldcontext); + + if (!const_is_null) + const_val = datumCopy(const_val, resultTypByVal, resultTypLen); + + codeblock->nulls[i] = const_is_null; + codeblock->dvalues[i] = const_val; + + FreeExecutorState(estate); + + current_param = lnext(current_param); + current_expr = lnext(current_expr); + } + + /* + * If more parameters were specified than were required to process + * throw an error. + */ + if (current_expr != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("too many parameters specified for inline code"))); + } + else + { + if (stmt->expr_list != NIL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("using USING clause without parameters"))); + + codeblock->nparams = 0; + codeblock->dvalues = NULL; + codeblock->nulls = NULL; + codeblock->names = NULL; + codeblock->typoids = NULL; + } + /* execute the inline handler */ OidFunctionCall1(laninline, PointerGetDatum(codeblock)); } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 1743b8f..a812c6c 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2883,6 +2883,8 @@ _copyDoStmt(const DoStmt *from) DoStmt *newnode = makeNode(DoStmt); COPY_NODE_FIELD(args); + COPY_NODE_FIELD(params); + COPY_NODE_FIELD(expr_list); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index f19ad77..a3ecc22 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1299,6 +1299,8 @@ static bool _equalDoStmt(const DoStmt *a, const DoStmt *b) { COMPARE_NODE_FIELD(args); + COMPARE_NODE_FIELD(params); + COMPARE_NODE_FIELD(expr_list); return true; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 7e6ceed..714687f 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -319,7 +319,8 @@ static void processCASbits(int cas_bits, int location, const char *constrType, execute_param_clause using_clause returning_clause opt_enum_val_list enum_val_list table_func_column_list create_generic_options alter_generic_options - relation_expr_list dostmt_opt_list + relation_expr_list dostmt_opt_list do_args_opt do_args_list + do_expr_list_opt %type opt_fdw_options fdw_options %type fdw_option @@ -328,7 +329,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType, %type into_clause create_as_target %type createfunc_opt_item common_func_opt_item dostmt_opt_item -%type func_arg func_arg_with_default table_func_column +%type func_arg func_arg_with_default table_func_column do_arg %type arg_class %type func_return func_type @@ -463,7 +464,6 @@ static void processCASbits(int cas_bits, int location, const char *constrType, opt_frame_clause frame_extent frame_bound %type opt_existing_window_name - /* * Non-keyword token types. These are hard-wired into the "flex" lexer. * They must be listed first so that their numeric codes do not depend on @@ -6403,10 +6403,12 @@ any_operator: * *****************************************************************************/ -DoStmt: DO dostmt_opt_list +DoStmt: DO do_args_opt dostmt_opt_list do_expr_list_opt { DoStmt *n = makeNode(DoStmt); - n->args = $2; + n->args = $3; + n->params = $2; + n->expr_list = $4; $$ = (Node *)n; } ; @@ -6427,6 +6429,42 @@ dostmt_opt_item: } ; +do_expr_list_opt: + USING expr_list { $$ = $2; } + | /*EMPTY*/ { $$ = NIL; } + ; + +do_args_opt: + '(' do_args_list ')' { $$ = $2; } + | /*EMPTY*/ { $$ = NIL; } + ; + +do_args_list: + do_arg { $$ = list_make1($1); } + | do_args_list ',' do_arg { $$ = lappend($1, $3); } + ; + +do_arg: + param_name Typename + { + FunctionParameter *n = makeNode(FunctionParameter); + n->name = $1; + n->argType = $2; + n->mode = FUNC_PARAM_IN; + n->defexpr = NULL; + $$ = n; + } + | Typename + { + FunctionParameter *n = makeNode(FunctionParameter); + n->name = NULL; + n->argType = $1; + n->mode = FUNC_PARAM_IN; + n->defexpr = NULL; + $$ = n; + } + ; + /***************************************************************************** * * CREATE CAST / DROP CAST diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 33b292e..4c41d91 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -957,7 +957,7 @@ standard_ProcessUtility(Node *parsetree, break; case T_DoStmt: - ExecuteDoStmt((DoStmt *) parsetree); + ExecuteDoStmt((DoStmt *) parsetree, queryString); break; case T_CreatedbStmt: diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 8f3d2c3..5c07d89 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -73,7 +73,7 @@ extern void DropCastById(Oid castOid); extern void AlterFunctionNamespace(List *name, List *argtypes, bool isagg, const char *newschema); extern Oid AlterFunctionNamespace_oid(Oid procOid, Oid nspOid); -extern void ExecuteDoStmt(DoStmt *stmt); +extern void ExecuteDoStmt(DoStmt *stmt, const char *queryString); extern Oid get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok); /* commands/operatorcmds.c */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 50111cb..03f927c 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2096,6 +2096,8 @@ typedef struct DoStmt { NodeTag type; List *args; /* List of DefElem nodes */ + List *params; /* List of FunctionParameter nodes */ + List *expr_list; /* List of expressions */ } DoStmt; typedef struct InlineCodeBlock @@ -2104,6 +2106,11 @@ typedef struct InlineCodeBlock char *source_text; /* source text of anonymous code block */ Oid langOid; /* OID of selected language */ bool langIsTrusted; /* trusted property of the language */ + int nparams; + Datum *dvalues; /* Values of parameters if they are */ + bool *nulls; /* nulls of parameters if they are */ + char **names; /* used names for parameters if they are */ + Oid *typoids; /* array of parameter types if they are */ } InlineCodeBlock; /* ---------------------- diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index 5d2f818..d9e457c 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -755,7 +755,7 @@ do_compile(FunctionCallInfo fcinfo, * ---------- */ PLpgSQL_function * -plpgsql_compile_inline(char *proc_source) +plpgsql_compile_inline(InlineCodeBlock *codeblock) { char *func_name = "inline_code_block"; PLpgSQL_function *function; @@ -765,6 +765,8 @@ plpgsql_compile_inline(char *proc_source) int parse_rc; MemoryContext func_cxt; int i; + char *proc_source = codeblock->source_text; + int *in_arg_varnos = NULL; /* * Setup the scanner input and error info. We assume that this function @@ -845,6 +847,54 @@ plpgsql_compile_inline(char *proc_source) function->found_varno = var->dno; /* + * Complete the function's info + */ + function->fn_nargs = codeblock->nparams; + in_arg_varnos = (int *) palloc(codeblock->nparams * sizeof(int)); + + /* + * Create variables for inline parameters + */ + for(i = 0; i < codeblock->nparams; i++) + { + char buf[32]; + PLpgSQL_type *argtype; + PLpgSQL_variable *argvariable; + int argitemtype; + + /* Create $n name for variable */ + snprintf(buf, sizeof(buf), "$%d", i + 1); + + /* Create datatype info */ + argtype = plpgsql_build_datatype(codeblock->typoids[i], -1, InvalidOid); + + /* Disallow pseudotype argument */ + if (argtype->ttype != PLPGSQL_TTYPE_SCALAR && + argtype->ttype != PLPGSQL_TTYPE_ROW) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("PL/pgSQL inline code cannot accept type %s", + format_type_be(codeblock->typoids[i])))); + + /* Build variable and add to datum list */ + argvariable = plpgsql_build_variable(buf, 0, + argtype, false); + if (argvariable->dtype == PLPGSQL_DTYPE_ROW) + argitemtype = PLPGSQL_NSTYPE_VAR; + else + argitemtype = PLPGSQL_NSTYPE_ROW; + + in_arg_varnos[i] = argvariable->dno; + + /* Add to namespace */ + plpgsql_ns_additem(argitemtype, argvariable->dno, buf); + + if (codeblock->names[i] != NULL) + plpgsql_ns_additem(argitemtype, argvariable->dno, + codeblock->names[i]); + } + + /* * Now parse the function's text */ parse_rc = plpgsql_yyparse(); @@ -864,7 +914,9 @@ plpgsql_compile_inline(char *proc_source) /* * Complete the function's info */ - function->fn_nargs = 0; + for (i = 0; i < function->fn_nargs; i++) + function->fn_argvarnos[i] = in_arg_varnos[i]; + function->ndatums = plpgsql_nDatums; function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums); for (i = 0; i < plpgsql_nDatums; i++) diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c index 022ec3f..b3b4860 100644 --- a/src/pl/plpgsql/src/pl_handler.c +++ b/src/pl/plpgsql/src/pl_handler.c @@ -160,6 +160,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS) FmgrInfo flinfo; Datum retval; int rc; + int i; Assert(IsA(codeblock, InlineCodeBlock)); @@ -170,7 +171,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS) elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc)); /* Compile the anonymous code block */ - func = plpgsql_compile_inline(codeblock->source_text); + func = plpgsql_compile_inline(codeblock); /* Mark the function as busy, just pro forma */ func->use_count++; @@ -180,7 +181,15 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS) * plpgsql_exec_function(). In particular note that this sets things up * with no arguments passed. */ - MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo)); + InitFunctionCallInfoData(fake_fcinfo, &flinfo, codeblock->nparams, + InvalidOid, NULL, NULL); + + for (i = 0; i < codeblock->nparams; i++) + { + fake_fcinfo.arg[i] = codeblock->dvalues[i]; + fake_fcinfo.argnull[i] = codeblock->nulls[i]; + } + MemSet(&flinfo, 0, sizeof(flinfo)); fake_fcinfo.flinfo = &flinfo; flinfo.fn_oid = InvalidOid; diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index b63f336..a6db279 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -876,7 +876,7 @@ extern PLpgSQL_plugin **plugin_ptr; */ extern PLpgSQL_function *plpgsql_compile(FunctionCallInfo fcinfo, bool forValidator); -extern PLpgSQL_function *plpgsql_compile_inline(char *proc_source); +extern PLpgSQL_function *plpgsql_compile_inline(InlineCodeBlock *codeblock); extern void plpgsql_parser_setup(struct ParseState *pstate, PLpgSQL_expr *expr); extern bool plpgsql_parse_word(char *word1, const char *yytxt,