* functions.c
* Execution of SQL-language functions
*
- * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
} DR_sqlfunction;
/*
- * We have an execution_state record for each query in a function. Each
+ * We have an execution_state record for each query in a function. Each
* record contains a plantree for its query. If the query is currently in
* F_EXEC_RUN state then there's a QueryDesc too.
*
ExecStatus status;
bool setsResult; /* true if this query produces func's result */
bool lazyEval; /* true if should fetch one row at a time */
- Node *stmt; /* PlannedStmt or utility statement */
+ PlannedStmt *stmt; /* plan for this query */
QueryDesc *qd; /* null unless status == RUN */
} execution_state;
static void sql_exec_error_callback(void *arg);
static void ShutdownSQLFunction(Datum arg);
static void sqlfunction_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
-static void sqlfunction_receive(TupleTableSlot *slot, DestReceiver *self);
+static bool sqlfunction_receive(TupleTableSlot *slot, DestReceiver *self);
static void sqlfunction_shutdown(DestReceiver *self);
static void sqlfunction_destroy(DestReceiver *self);
* (the first possibility takes precedence)
* A.B.C A = function name, B = record-typed parameter name,
* C = field name
+ * A.* Whole-row reference to composite parameter A.
+ * A.B.* Same, with A = function name, B = parameter name
+ *
+ * Here, it's sufficient to ignore the "*" in the last two cases --- the
+ * main parser will take care of expanding the whole-row reference.
*----------
*/
nnames = list_length(cref->fields);
if (nnames > 3)
return NULL;
+ if (IsA(llast(cref->fields), A_Star))
+ nnames--;
+
field1 = (Node *) linitial(cref->fields);
Assert(IsA(field1, String));
name1 = strVal(field1);
* Set up the per-query execution_state records for a SQL function.
*
* The input is a List of Lists of parsed and rewritten, but not planned,
- * querytrees. The sublist structure denotes the original query boundaries.
+ * querytrees. The sublist structure denotes the original query boundaries.
*/
static List *
init_execution_state(List *queryTree_list,
foreach(lc1, queryTree_list)
{
- List *qtlist = (List *) lfirst(lc1);
+ List *qtlist = castNode(List, lfirst(lc1));
execution_state *firstes = NULL;
execution_state *preves = NULL;
ListCell *lc2;
foreach(lc2, qtlist)
{
- Query *queryTree = (Query *) lfirst(lc2);
- Node *stmt;
+ Query *queryTree = castNode(Query, lfirst(lc2));
+ PlannedStmt *stmt;
execution_state *newes;
- Assert(IsA(queryTree, Query));
-
/* Plan the query if needed */
if (queryTree->commandType == CMD_UTILITY)
- stmt = queryTree->utilityStmt;
+ {
+ /* Utility commands require no planning. */
+ stmt = makeNode(PlannedStmt);
+ stmt->commandType = CMD_UTILITY;
+ stmt->canSetTag = queryTree->canSetTag;
+ stmt->utilityStmt = queryTree->utilityStmt;
+ stmt->stmt_location = queryTree->stmt_location;
+ stmt->stmt_len = queryTree->stmt_len;
+ }
else
- stmt = (Node *) pg_plan_query(queryTree, 0, NULL);
+ stmt = pg_plan_query(queryTree,
+ CURSOR_OPT_PARALLEL_OK,
+ NULL);
- /* Precheck all commands for validity in a function */
- if (IsA(stmt, TransactionStmt))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- /* translator: %s is a SQL statement name */
- errmsg("%s is not allowed in a SQL function",
- CreateCommandTag(stmt))));
+ /*
+ * Precheck all commands for validity in a function. This should
+ * generally match the restrictions spi.c applies.
+ */
+ if (stmt->commandType == CMD_UTILITY)
+ {
+ if (IsA(stmt->utilityStmt, CopyStmt) &&
+ ((CopyStmt *) stmt->utilityStmt)->filename == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot COPY to/from client in a SQL function")));
+
+ if (IsA(stmt->utilityStmt, TransactionStmt))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /* translator: %s is a SQL statement name */
+ errmsg("%s is not allowed in a SQL function",
+ CreateCommandTag(stmt->utilityStmt))));
+ }
if (fcache->readonly_func && !CommandIsReadOnly(stmt))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* translator: %s is a SQL statement name */
errmsg("%s is not allowed in a non-volatile function",
- CreateCommandTag(stmt))));
+ CreateCommandTag((Node *) stmt))));
+
+ if (IsInParallelMode() && !CommandIsReadOnly(stmt))
+ PreventCommandIfParallelMode(CreateCommandTag((Node *) stmt));
/* OK, build the execution_state for this query */
newes = (execution_state *) palloc(sizeof(execution_state));
{
lasttages->setsResult = true;
if (lazyEvalOK &&
- IsA(lasttages->stmt, PlannedStmt))
- {
- PlannedStmt *ps = (PlannedStmt *) lasttages->stmt;
-
- if (ps->commandType == CMD_SELECT &&
- ps->utilityStmt == NULL &&
- !ps->hasModifyingCTE)
- fcache->lazyEval = lasttages->lazyEval = true;
- }
+ lasttages->stmt->commandType == CMD_SELECT &&
+ !lasttages->stmt->hasModifyingCTE)
+ fcache->lazyEval = lasttages->lazyEval = true;
}
return eslist;
bool isNull;
/*
- * Create memory context that holds all the SQLFunctionCache data. It
+ * Create memory context that holds all the SQLFunctionCache data. It
* must be a child of whatever context holds the FmgrInfo.
*/
fcontext = AllocSetContextCreate(finfo->fn_mcxt,
"SQL function data",
- ALLOCSET_DEFAULT_MINSIZE,
- ALLOCSET_DEFAULT_INITSIZE,
- ALLOCSET_DEFAULT_MAXSIZE);
+ ALLOCSET_DEFAULT_SIZES);
oldcontext = MemoryContextSwitchTo(fcontext);
/*
- * Create the struct proper, link it to fcontext and fn_extra. Once this
+ * Create the struct proper, link it to fcontext and fn_extra. Once this
* is done, we'll be able to recover the memory after failure, even if the
* FmgrInfo is long-lived.
*/
fcache->src = TextDatumGetCString(tmp);
/*
- * Parse and rewrite the queries in the function text. Use sublists to
+ * Parse and rewrite the queries in the function text. Use sublists to
* keep track of the original query boundaries. But we also build a
* "flat" list of the rewritten queries to pass to check_sql_fn_retval.
* This is because the last canSetTag query determines the result type
flat_query_list = NIL;
foreach(lc, raw_parsetree_list)
{
- Node *parsetree = (Node *) lfirst(lc);
+ RawStmt *parsetree = castNode(RawStmt, lfirst(lc));
List *queryTree_sublist;
queryTree_sublist = pg_analyze_and_rewrite_params(parsetree,
* any polymorphic arguments.
*
* Note: we set fcache->returnsTuple according to whether we are returning
- * the whole tuple result or just a single column. In the latter case we
+ * the whole tuple result or just a single column. In the latter case we
* clear returnsTuple because we need not act different from the scalar
* result case, even if it's a rowtype column. (However, we have to force
* lazy eval mode in that case; otherwise we'd need extra code to expand
else
dest = None_Receiver;
- if (IsA(es->stmt, PlannedStmt))
- es->qd = CreateQueryDesc((PlannedStmt *) es->stmt,
- fcache->src,
- GetActiveSnapshot(),
- InvalidSnapshot,
- dest,
- fcache->paramLI, 0);
- else
- es->qd = CreateUtilityQueryDesc(es->stmt,
- fcache->src,
- GetActiveSnapshot(),
- dest,
- fcache->paramLI);
+ es->qd = CreateQueryDesc(es->stmt,
+ fcache->src,
+ GetActiveSnapshot(),
+ InvalidSnapshot,
+ dest,
+ fcache->paramLI, 0);
/* Utility commands don't need Executor. */
- if (es->qd->utilitystmt == NULL)
+ if (es->qd->operation != CMD_UTILITY)
{
/*
* In lazyEval mode, do not let the executor set up an AfterTrigger
{
bool result;
- if (es->qd->utilitystmt)
+ if (es->qd->operation == CMD_UTILITY)
{
- /* ProcessUtility needs the PlannedStmt for DECLARE CURSOR */
- ProcessUtility((es->qd->plannedstmt ?
- (Node *) es->qd->plannedstmt :
- es->qd->utilitystmt),
+ ProcessUtility(es->qd->plannedstmt,
fcache->src,
PROCESS_UTILITY_QUERY,
es->qd->params,
else
{
/* Run regular commands to completion unless lazyEval */
- long count = (es->lazyEval) ? 1L : 0L;
+ uint64 count = (es->lazyEval) ? 1 : 0;
- ExecutorRun(es->qd, ForwardScanDirection, count);
+ ExecutorRun(es->qd, ForwardScanDirection, count, !fcache->returnsSet || !es->lazyEval);
/*
* If we requested run to completion OR there was no tuple returned,
* command must be complete.
*/
- result = (count == 0L || es->qd->estate->es_processed == 0);
+ result = (count == 0 || es->qd->estate->es_processed == 0);
}
return result;
es->status = F_EXEC_DONE;
/* Utility commands don't need Executor. */
- if (es->qd->utilitystmt == NULL)
+ if (es->qd->operation != CMD_UTILITY)
{
ExecutorFinish(es->qd);
ExecutorEnd(es->qd);
if (fcache->paramLI == NULL)
{
- /* sizeof(ParamListInfoData) includes the first array element */
- paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
- (nargs - 1) * sizeof(ParamExternData));
+ paramLI = (ParamListInfo)
+ palloc(offsetof(ParamListInfoData, params) +
+ nargs * sizeof(ParamExternData));
/* we have static list of params, so no hooks needed */
paramLI->paramFetch = NULL;
paramLI->paramFetchArg = NULL;
paramLI->parserSetup = NULL;
paramLI->parserSetupArg = NULL;
paramLI->numParams = nargs;
+ paramLI->paramMask = NULL;
fcache->paramLI = paramLI;
}
else
/*
* Set up to return the function value. For pass-by-reference datatypes,
* be sure to allocate the result in resultcontext, not the current memory
- * context (which has query lifespan). We can't leave the data in the
+ * context (which has query lifespan). We can't leave the data in the
* TupleTableSlot because we intend to clear the slot before returning.
*/
oldcontext = MemoryContextSwitchTo(resultcontext);
/* We must return the whole tuple as a Datum. */
fcinfo->isnull = false;
value = ExecFetchSlotTupleDatum(slot);
- value = datumCopy(value, fcache->typbyval, fcache->typlen);
}
else
{
/*
* Switch to context in which the fcache lives. This ensures that our
* tuplestore etc will have sufficient lifetime. The sub-executor is
- * responsible for deleting per-tuple information. (XXX in the case of a
+ * responsible for deleting per-tuple information. (XXX in the case of a
* long-lived FmgrInfo, this policy represents more memory leakage, but
* it's not entirely clear where to keep stuff instead.)
*/
* suspend execution before completion is if we are returning a row from a
* lazily-evaluated SELECT. So, when first entering this loop, we'll
* either start a new query (and push a fresh snapshot) or re-establish
- * the active snapshot from the existing query descriptor. If we need to
+ * the active snapshot from the existing query descriptor. If we need to
* start a new query in a subsequent execution of the loop, either we need
* a fresh snapshot (and pushed_snapshot is false) or the existing
* snapshot is on the active stack and we can just bump its command ID.
* Break from loop if we didn't shut down (implying we got a
* lazily-evaluated row). Otherwise we'll press on till the whole
* function is done, relying on the tuplestore to keep hold of the
- * data to eventually be returned. This is necessary since an
+ * data to eventually be returned. This is necessary since an
* INSERT/UPDATE/DELETE RETURNING that sets the result might be
* followed by additional rule-inserted commands, and we want to
* finish doing all those commands before we return anything.
/*
* Flush the current snapshot so that we will take a new one for
- * the new query list. This ensures that new snaps are taken at
+ * the new query list. This ensures that new snaps are taken at
* original-query boundaries, matching the behavior of interactive
* execution.
*/
else if (fcache->lazyEval)
{
/*
- * We are done with a lazy evaluation. Clean up.
+ * We are done with a lazy evaluation. Clean up.
*/
tuplestore_clear(fcache->tstore);
else
{
/*
- * We are done with a non-lazy evaluation. Return whatever is in
- * the tuplestore. (It is now caller's responsibility to free the
+ * We are done with a non-lazy evaluation. Return whatever is in
+ * the tuplestore. (It is now caller's responsibility to free the
* tuplestore when done.)
*/
rsi->returnMode = SFRM_Materialize;
rsi->setResult = fcache->tstore;
fcache->tstore = NULL;
- /* must copy desc because execQual will free it */
+ /* must copy desc because execSRF.c will free it */
if (fcache->junkFilter)
rsi->setDesc = CreateTupleDescCopy(fcache->junkFilter->jf_cleanTupType);
/*
* Try to determine where in the function we failed. If there is a query
- * with non-null QueryDesc, finger it. (We check this rather than looking
+ * with non-null QueryDesc, finger it. (We check this rather than looking
* for F_EXEC_RUN state, so that errors during ExecutorStart or
* ExecutorEnd are blamed on the appropriate query; see postquel_start and
* postquel_end.)
parse = NULL;
foreach(lc, queryTreeList)
{
- Query *q = (Query *) lfirst(lc);
+ Query *q = castNode(Query, lfirst(lc));
if (q->canSetTag)
parse = q;
* entities.
*/
if (parse &&
- parse->commandType == CMD_SELECT &&
- parse->utilityStmt == NULL)
+ parse->commandType == CMD_SELECT)
{
tlist_ptr = &parse->targetList;
tlist = parse->targetList;
* the function that's calling it.
*
* XXX Note that if rettype is RECORD, the IsBinaryCoercible check
- * will succeed for any composite restype. For the moment we rely on
+ * will succeed for any composite restype. For the moment we rely on
* runtime type checking to catch any discrepancy, but it'd be nice to
* do better at parse time.
*/
/*
* Verify that the targetlist matches the return tuple type. We scan
* the non-deleted attributes to ensure that they match the datatypes
- * of the non-resjunk columns. For deleted attributes, insert NULL
+ * of the non-resjunk columns. For deleted attributes, insert NULL
* result columns if the caller asked for that.
*/
tupnatts = tupdesc->natts;
/*
* sqlfunction_receive --- receive one tuple
*/
-static void
+static bool
sqlfunction_receive(TupleTableSlot *slot, DestReceiver *self)
{
DR_sqlfunction *myState = (DR_sqlfunction *) self;
/* Store the filtered tuple into the tuplestore */
tuplestore_puttupleslot(myState->tstore, slot);
+
+ return true;
}
/*