]> granicus.if.org Git - postgresql/blobdiff - src/backend/executor/functions.c
Faster expression evaluation and targetlist projection.
[postgresql] / src / backend / executor / functions.c
index 893a54b21bb0d587bb4b975d0979c850d72bdb8a..3e4b0191c7ecdef3005ee2bd590875dbe54db8a3 100644 (file)
@@ -3,7 +3,7 @@
  * 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
  *
  *
@@ -47,7 +47,7 @@ typedef struct
 } 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.
  *
@@ -66,7 +66,7 @@ typedef struct execution_state
        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;
 
@@ -167,7 +167,7 @@ static Datum postquel_get_single_result(TupleTableSlot *slot,
 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);
 
@@ -310,6 +310,11 @@ sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
         *                      (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);
@@ -317,6 +322,9 @@ sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
        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);
@@ -458,7 +466,7 @@ sql_fn_resolve_param_name(SQLFunctionParseInfoPtr pinfo,
  * 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,
@@ -471,39 +479,62 @@ 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));
@@ -547,15 +578,9 @@ init_execution_state(List *queryTree_list,
        {
                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;
@@ -582,19 +607,17 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
        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.
         */
@@ -664,7 +687,7 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
        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
@@ -684,7 +707,7 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
        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,
@@ -704,7 +727,7 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
         * 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
@@ -781,22 +804,15 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
        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
@@ -824,12 +840,9 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
 {
        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,
@@ -840,15 +853,15 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
        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;
@@ -862,7 +875,7 @@ postquel_end(execution_state *es)
        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);
@@ -888,15 +901,16 @@ postquel_sub_params(SQLFunctionCachePtr fcache,
 
                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
@@ -936,7 +950,7 @@ postquel_get_single_result(TupleTableSlot *slot,
        /*
         * 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);
@@ -946,7 +960,6 @@ postquel_get_single_result(TupleTableSlot *slot,
                /* We must return the whole tuple as a Datum. */
                fcinfo->isnull = false;
                value = ExecFetchSlotTupleDatum(slot);
-               value = datumCopy(value, fcache->typbyval, fcache->typlen);
        }
        else
        {
@@ -1045,7 +1058,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
        /*
         * 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.)
         */
@@ -1099,7 +1112,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
         * 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.
@@ -1155,7 +1168,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
                 * 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.
@@ -1177,7 +1190,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
 
                        /*
                         * 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.
                         */
@@ -1235,7 +1248,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
                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);
 
@@ -1259,14 +1272,14 @@ fmgr_sql(PG_FUNCTION_ARGS)
                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);
 
@@ -1372,7 +1385,7 @@ sql_exec_error_callback(void *arg)
 
        /*
         * 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.)
@@ -1538,7 +1551,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
        parse = NULL;
        foreach(lc, queryTreeList)
        {
-               Query      *q = (Query *) lfirst(lc);
+               Query      *q = castNode(Query, lfirst(lc));
 
                if (q->canSetTag)
                        parse = q;
@@ -1556,8 +1569,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
         * entities.
         */
        if (parse &&
-               parse->commandType == CMD_SELECT &&
-               parse->utilityStmt == NULL)
+               parse->commandType == CMD_SELECT)
        {
                tlist_ptr = &parse->targetList;
                tlist = parse->targetList;
@@ -1664,7 +1676,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
                 * 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.
                 */
@@ -1710,7 +1722,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
                /*
                 * 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;
@@ -1891,7 +1903,7 @@ sqlfunction_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 /*
  * sqlfunction_receive --- receive one tuple
  */
-static void
+static bool
 sqlfunction_receive(TupleTableSlot *slot, DestReceiver *self)
 {
        DR_sqlfunction *myState = (DR_sqlfunction *) self;
@@ -1901,6 +1913,8 @@ sqlfunction_receive(TupleTableSlot *slot, DestReceiver *self)
 
        /* Store the filtered tuple into the tuplestore */
        tuplestore_puttupleslot(myState->tstore, slot);
+
+       return true;
 }
 
 /*