diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 9ba6dd8..1007611 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -48,7 +48,11 @@ #include "catalog/pg_type_fn.h" #include "commands/defrem.h" #include "commands/proclang.h" +#include "executor/executor.h" +#include "executor/execdesc.h" +#include "executor/spi.h" #include "miscadmin.h" +#include "optimizer/planmain.h" #include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parse_collate.h" @@ -57,6 +61,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 +1916,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 +2008,125 @@ ExecuteDoStmt(DoStmt *stmt) ReleaseSysCache(languageTuple); + /* evaluate parameters if exists */ + if (stmt->params != NULL) + { + ListCell *current_param; + ListCell *current_expr; + int i; + int nparams; + List *exprsList; + SelectStmt *s; + MemoryContext oldctx = CurrentMemoryContext; + + 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)); + + exprsList = NIL; + + /* prepare expression list to evaluation and take names */ + for (i = 0; i < nparams; i++) + { + FunctionParameter *fp = (FunctionParameter *) lfirst(current_param); + TypeCast *tc; + + if (current_expr == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("too few parameters specified for inline code"))); + + codeblock->names[i] = fp->name; + + /* forward expected type to expression */ + tc = makeNode(TypeCast); + tc->arg = lfirst(current_expr); + tc->typeName = fp->argType; + tc->location = -1; + exprsList = lappend(exprsList, tc); + + 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"))); + + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + /* Evaluate parameters */ + s = makeNode(SelectStmt); + s->valuesLists = list_make1(exprsList); + + SPI_execute_pt((Node *) s, queryString, true, 0); + + if (SPI_processed != 1) + elog(ERROR, "expected one row"); + + /* get back context before copy result variables */ + MemoryContextSwitchTo(oldctx); + + for (i = 0; i < nparams; i++) + { + Datum datum; + bool isnull; + Oid typid; + int16 typlen; + bool typbyval; + + typid = SPI_gettypeid(SPI_tuptable->tupdesc, i + 1); + + /* Get info needed about result datatype */ + get_typlenbyval(typid, &typlen, &typbyval); + + datum = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, i + 1, &isnull); + + if (!isnull) + { + codeblock->nulls[i] = false; + codeblock->dvalues[i] = datumCopy(datum, typbyval, typlen); + } + else + { + codeblock->nulls[i] = true; + codeblock->dvalues[i] = (Datum) 0; + } + + codeblock->typoids[i] = typid; + } + + SPI_freetuptable(SPI_tuptable); + + SPI_finish(); + } + 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/executor/spi.c b/src/backend/executor/spi.c index e222365..e8b68d6 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -50,6 +50,8 @@ static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams); +static void +_SPI_prepare_plan_pt(Node *raw_parsetree_list, const char *queryString, SPIPlanPtr plan); static int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, Snapshot snapshot, Snapshot crosscheck_snapshot, @@ -371,6 +373,36 @@ SPI_exec(const char *src, long tcount) return SPI_execute(src, false, tcount); } +/* analyze, plan and execute a query with parse tree */ +int +SPI_execute_pt(Node *parsetree, const char *queryString, bool read_only, long tcount) +{ + _SPI_plan plan; + int res; + + if (parsetree == NULL || tcount < 0) + return SPI_ERROR_ARGUMENT; + + res = _SPI_begin_call(true); + if (res < 0) + return res; + + memset(&plan, 0, sizeof(_SPI_plan)); + plan.magic = _SPI_PLAN_MAGIC; + plan.cursor_options = 0; + + + _SPI_prepare_plan_pt(parsetree, queryString, &plan); + + + res = _SPI_execute_plan(&plan, NULL, + InvalidSnapshot, InvalidSnapshot, + read_only, true, tcount); + + _SPI_end_call(true); + return res; +} + /* Execute a previously prepared plan */ int SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls, @@ -1736,6 +1768,94 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) } /* + * Analyze a querystring. + * + * At entry, plan->argtypes and plan->nargs (or alternatively plan->parserSetup + * and plan->parserSetupArg) must be valid, as must plan->cursor_options. + * If boundParams isn't NULL then it represents parameter values that are made + * available to the planner (as either estimates or hard values depending on + * their PARAM_FLAG_CONST marking). The boundParams had better match the + * param type information embedded in the plan! + * + * Results are stored into *plan (specifically, plan->plancache_list). + * Note that the result data is all in CurrentMemoryContext or child contexts + * thereof; in practice this means it is in the SPI executor context, and + * what we are creating is a "temporary" SPIPlan. Cruft generated during + * parsing is also left in CurrentMemoryContext. + */ +static void +_SPI_prepare_plan_pt(Node *parsetree, const char *queryString, SPIPlanPtr plan) +{ + List *plancache_list; + ErrorContextCallback spierrcontext; + int cursor_options = plan->cursor_options; + List *stmt_list; + CachedPlanSource *plansource; + + /* + * Setup error traceback support for ereport() + */ + spierrcontext.callback = _SPI_error_callback; + spierrcontext.arg = (void *) queryString; + spierrcontext.previous = error_context_stack; + error_context_stack = &spierrcontext; + + /* + * Do parse analysis and rule rewrite for each raw parsetree, storing the + * results into unsaved plancache entries. + */ + plancache_list = NIL; + + /* + * Create the CachedPlanSource before we do parse analysis, since it + * needs to see the unmodified raw parse tree. + */ + plansource = CreateCachedPlan(parsetree, + queryString, + CreateCommandTag(parsetree)); + + /* + * Parameter datatypes are driven by parserSetup hook if provided, + * otherwise we use the fixed parameter list. + */ + if (plan->parserSetup != NULL) + { + Assert(plan->nargs == 0); + stmt_list = pg_analyze_and_rewrite_params(parsetree, + queryString, + plan->parserSetup, + plan->parserSetupArg); + } + else + { + stmt_list = pg_analyze_and_rewrite(parsetree, + queryString, + plan->argtypes, + plan->nargs); + } + + /* Finish filling in the CachedPlanSource */ + CompleteCachedPlan(plansource, + stmt_list, + NULL, + plan->argtypes, + plan->nargs, + plan->parserSetup, + plan->parserSetupArg, + cursor_options, + false); /* not fixed result */ + + plancache_list = lappend(plancache_list, plansource); + + plan->plancache_list = plancache_list; + + /* + * Pop the error context stack + */ + error_context_stack = spierrcontext.previous; +} + +/* * Execute the given plan with the given parameter values * * snapshot: query snapshot to use, or InvalidSnapshot for the normal 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/executor/spi.h b/src/include/executor/spi.h index cfbaa14..2c1b11a 100644 --- a/src/include/executor/spi.h +++ b/src/include/executor/spi.h @@ -140,4 +140,6 @@ extern void SPI_cursor_close(Portal portal); extern void AtEOXact_SPI(bool isCommit); extern void AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid); +extern int SPI_execute_pt(Node *parsetree, const char *queryString, bool read_only, long tcount); + #endif /* SPI_H */ 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,