inline_code_with_params2.patch
application/octet-stream
Filename: inline_code_with_params2.patch
Type: application/octet-stream
Part: 0
Message:
Re: patch: inline code with params
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| src/backend/commands/functioncmds.c | 125 | 1 |
| src/backend/executor/spi.c | 120 | 0 |
| src/backend/nodes/copyfuncs.c | 2 | 0 |
| src/backend/nodes/equalfuncs.c | 2 | 0 |
| src/backend/parser/gram.y | 43 | 5 |
| src/backend/tcop/utility.c | 1 | 1 |
| src/include/commands/defrem.h | 1 | 1 |
| src/include/executor/spi.h | 2 | 0 |
| src/include/nodes/parsenodes.h | 7 | 0 |
| src/pl/plpgsql/src/pl_comp.c | 54 | 2 |
| src/pl/plpgsql/src/pl_handler.c | 11 | 2 |
| src/pl/plpgsql/src/plpgsql.h | 1 | 1 |
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 <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -328,7 +329,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
%type <into> into_clause create_as_target
%type <defelt> createfunc_opt_item common_func_opt_item dostmt_opt_item
-%type <fun_param> func_arg func_arg_with_default table_func_column
+%type <fun_param> func_arg func_arg_with_default table_func_column do_arg
%type <fun_param_mode> arg_class
%type <typnam> 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 <str> 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,