diff options
Diffstat (limited to 'src/backend/executor/functions.c')
-rw-r--r-- | src/backend/executor/functions.c | 277 |
1 files changed, 175 insertions, 102 deletions
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 3611c85a5fc..1db5a4339ff 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.88 2004/09/10 18:39:57 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.89 2004/09/13 20:06:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -65,6 +65,7 @@ typedef struct bool typbyval; /* true if return type is pass by value */ bool returnsTuple; /* true if returning whole tuple result */ bool shutdown_reg; /* true if registered shutdown callback */ + bool readonly_func; /* true to run in "read only" mode */ ParamListInfo paramLI; /* Param list representing current args */ @@ -76,11 +77,12 @@ typedef SQLFunctionCache *SQLFunctionCachePtr; /* non-export function prototypes */ -static execution_state *init_execution_state(List *queryTree_list); +static execution_state *init_execution_state(List *queryTree_list, + bool readonly_func); static void init_sql_fcache(FmgrInfo *finfo); static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache); static TupleTableSlot *postquel_getnext(execution_state *es); -static void postquel_end(execution_state *es); +static void postquel_end(execution_state *es, SQLFunctionCachePtr fcache); static void postquel_sub_params(SQLFunctionCachePtr fcache, FunctionCallInfo fcinfo); static Datum postquel_execute(execution_state *es, @@ -91,7 +93,7 @@ static void ShutdownSQLFunction(Datum arg); static execution_state * -init_execution_state(List *queryTree_list) +init_execution_state(List *queryTree_list, bool readonly_func) { execution_state *firstes = NULL; execution_state *preves = NULL; @@ -103,6 +105,22 @@ init_execution_state(List *queryTree_list) Plan *planTree; execution_state *newes; + /* Precheck all commands for validity in a function */ + if (queryTree->commandType == CMD_UTILITY && + IsA(queryTree->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", + CreateQueryTag(queryTree)))); + + if (readonly_func && !QueryIsReadOnly(queryTree)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + /* translator: %s is a SQL statement name */ + errmsg("%s is not allowed in a non-volatile function", + CreateQueryTag(queryTree)))); + planTree = pg_plan_query(queryTree, NULL); newes = (execution_state *) palloc(sizeof(execution_state)); @@ -172,6 +190,10 @@ init_sql_fcache(FmgrInfo *finfo) fcache->rettype = rettype; + /* Remember if function is STABLE/IMMUTABLE */ + fcache->readonly_func = + (procedureStruct->provolatile != PROVOLATILE_VOLATILE); + /* Now look up the actual result type */ typeTuple = SearchSysCache(TYPEOID, ObjectIdGetDatum(rettype), @@ -253,7 +275,8 @@ init_sql_fcache(FmgrInfo *finfo) queryTree_list); /* Finally, plan the queries */ - fcache->func_state = init_execution_state(queryTree_list); + fcache->func_state = init_execution_state(queryTree_list, + fcache->readonly_func); pfree(src); @@ -267,16 +290,37 @@ init_sql_fcache(FmgrInfo *finfo) static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache) { + Snapshot snapshot; + Assert(es->qd == NULL); + + /* + * In a read-only function, use the surrounding query's snapshot; + * otherwise take a new snapshot for each query. The snapshot should + * include a fresh command ID so that all work to date in this + * transaction is visible. We copy in both cases so that postquel_end + * can unconditionally do FreeSnapshot. + */ + if (fcache->readonly_func) + snapshot = CopySnapshot(ActiveSnapshot); + else + { + CommandCounterIncrement(); + snapshot = CopySnapshot(GetTransactionSnapshot()); + } + es->qd = CreateQueryDesc(es->query, es->plan, + snapshot, InvalidSnapshot, None_Receiver, fcache->paramLI, false); + /* We assume we don't need to set up ActiveSnapshot for ExecutorStart */ + /* Utility commands don't need Executor. */ if (es->qd->operation != CMD_UTILITY) { AfterTriggerBeginQuery(); - ExecutorStart(es->qd, false, false); + ExecutorStart(es->qd, false); } es->status = F_EXEC_RUN; @@ -285,46 +329,82 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache) static TupleTableSlot * postquel_getnext(execution_state *es) { + TupleTableSlot *result; + Snapshot saveActiveSnapshot; long count; - if (es->qd->operation == CMD_UTILITY) + /* Make our snapshot the active one for any called functions */ + saveActiveSnapshot = ActiveSnapshot; + PG_TRY(); { - /* Can't handle starting or committing a transaction */ - if (IsA(es->qd->parsetree->utilityStmt, TransactionStmt)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot begin/end transactions in SQL functions"))); - ProcessUtility(es->qd->parsetree->utilityStmt, es->qd->params, - es->qd->dest, NULL); - return NULL; + ActiveSnapshot = es->qd->snapshot; + + if (es->qd->operation == CMD_UTILITY) + { + ProcessUtility(es->qd->parsetree->utilityStmt, es->qd->params, + es->qd->dest, NULL); + result = NULL; + } + else + { + /* + * If it's the function's last command, and it's a SELECT, fetch + * one row at a time so we can return the results. Otherwise just + * run it to completion. (If we run to completion then + * ExecutorRun is guaranteed to return NULL.) + */ + if (LAST_POSTQUEL_COMMAND(es) && es->qd->operation == CMD_SELECT) + count = 1L; + else + count = 0L; + + result = ExecutorRun(es->qd, ForwardScanDirection, count); + } } + PG_CATCH(); + { + /* Restore global vars and propagate error */ + ActiveSnapshot = saveActiveSnapshot; + PG_RE_THROW(); + } + PG_END_TRY(); - /* - * If it's the function's last command, and it's a SELECT, fetch one - * row at a time so we can return the results. Otherwise just run it - * to completion. - */ - if (LAST_POSTQUEL_COMMAND(es) && es->qd->operation == CMD_SELECT) - count = 1L; - else - count = 0L; + ActiveSnapshot = saveActiveSnapshot; - return ExecutorRun(es->qd, ForwardScanDirection, count); + return result; } static void -postquel_end(execution_state *es) +postquel_end(execution_state *es, SQLFunctionCachePtr fcache) { + Snapshot saveActiveSnapshot; + /* mark status done to ensure we don't do ExecutorEnd twice */ es->status = F_EXEC_DONE; /* Utility commands don't need Executor. */ if (es->qd->operation != CMD_UTILITY) { - ExecutorEnd(es->qd); - AfterTriggerEndQuery(); + /* Make our snapshot the active one for any called functions */ + saveActiveSnapshot = ActiveSnapshot; + PG_TRY(); + { + ActiveSnapshot = es->qd->snapshot; + + ExecutorEnd(es->qd); + AfterTriggerEndQuery(); + } + PG_CATCH(); + { + /* Restore global vars and propagate error */ + ActiveSnapshot = saveActiveSnapshot; + PG_RE_THROW(); + } + PG_END_TRY(); + ActiveSnapshot = saveActiveSnapshot; } + FreeSnapshot(es->qd->snapshot); FreeQueryDesc(es->qd); es->qd = NULL; } @@ -368,6 +448,8 @@ postquel_execute(execution_state *es, SQLFunctionCachePtr fcache) { TupleTableSlot *slot; + HeapTuple tup; + TupleDesc tupDesc; Datum value; if (es->status == F_EXEC_START) @@ -377,101 +459,92 @@ postquel_execute(execution_state *es, if (TupIsNull(slot)) { - postquel_end(es); - fcinfo->isnull = true; - /* - * If this isn't the last command for the function we have to - * increment the command counter so that subsequent commands can - * see changes made by previous ones. + * We fall out here for all cases except where we have obtained + * a row from a function's final SELECT. */ - if (!LAST_POSTQUEL_COMMAND(es)) - CommandCounterIncrement(); + postquel_end(es, fcache); + fcinfo->isnull = true; return (Datum) NULL; } - if (LAST_POSTQUEL_COMMAND(es)) + /* + * If we got a row from a command within the function it has to be + * the final command. All others shouldn't be returning anything. + */ + Assert(LAST_POSTQUEL_COMMAND(es)); + + /* + * Set up to return the function value. + */ + tup = slot->val; + tupDesc = slot->ttc_tupleDescriptor; + + if (fcache->returnsTuple) { /* - * Set up to return the function value. + * We are returning the whole tuple, so copy it into current + * execution context and make sure it is a valid Datum. + * + * XXX do we need to remove junk attrs from the result tuple? + * Probably OK to leave them, as long as they are at the end. */ - HeapTuple tup = slot->val; - TupleDesc tupDesc = slot->ttc_tupleDescriptor; + HeapTupleHeader dtup; + Oid dtuptype; + int32 dtuptypmod; - if (fcache->returnsTuple) - { - /* - * We are returning the whole tuple, so copy it into current - * execution context and make sure it is a valid Datum. - * - * XXX do we need to remove junk attrs from the result tuple? - * Probably OK to leave them, as long as they are at the end. - */ - HeapTupleHeader dtup; - Oid dtuptype; - int32 dtuptypmod; - - dtup = (HeapTupleHeader) palloc(tup->t_len); - memcpy((char *) dtup, (char *) tup->t_data, tup->t_len); + dtup = (HeapTupleHeader) palloc(tup->t_len); + memcpy((char *) dtup, (char *) tup->t_data, tup->t_len); - /* - * Use the declared return type if it's not RECORD; else take - * the type from the computed result, making sure a typmod has - * been assigned. - */ - if (fcache->rettype != RECORDOID) - { - /* function has a named composite return type */ - dtuptype = fcache->rettype; - dtuptypmod = -1; - } - else - { - /* function is declared to return RECORD */ - if (tupDesc->tdtypeid == RECORDOID && - tupDesc->tdtypmod < 0) - assign_record_type_typmod(tupDesc); - dtuptype = tupDesc->tdtypeid; - dtuptypmod = tupDesc->tdtypmod; - } - - HeapTupleHeaderSetDatumLength(dtup, tup->t_len); - HeapTupleHeaderSetTypeId(dtup, dtuptype); - HeapTupleHeaderSetTypMod(dtup, dtuptypmod); - - value = PointerGetDatum(dtup); - fcinfo->isnull = false; + /* + * Use the declared return type if it's not RECORD; else take + * the type from the computed result, making sure a typmod has + * been assigned. + */ + if (fcache->rettype != RECORDOID) + { + /* function has a named composite return type */ + dtuptype = fcache->rettype; + dtuptypmod = -1; } else { - /* - * Returning a scalar, which we have to extract from the first - * column of the SELECT result, and then copy into current - * execution context if needed. - */ - value = heap_getattr(tup, 1, tupDesc, &(fcinfo->isnull)); - - if (!fcinfo->isnull) - value = datumCopy(value, fcache->typbyval, fcache->typlen); + /* function is declared to return RECORD */ + if (tupDesc->tdtypeid == RECORDOID && + tupDesc->tdtypmod < 0) + assign_record_type_typmod(tupDesc); + dtuptype = tupDesc->tdtypeid; + dtuptypmod = tupDesc->tdtypmod; } + HeapTupleHeaderSetDatumLength(dtup, tup->t_len); + HeapTupleHeaderSetTypeId(dtup, dtuptype); + HeapTupleHeaderSetTypMod(dtup, dtuptypmod); + + value = PointerGetDatum(dtup); + fcinfo->isnull = false; + } + else + { /* - * If this is a single valued function we have to end the function - * execution now. + * Returning a scalar, which we have to extract from the first + * column of the SELECT result, and then copy into current + * execution context if needed. */ - if (!fcinfo->flinfo->fn_retset) - postquel_end(es); + value = heap_getattr(tup, 1, tupDesc, &(fcinfo->isnull)); - return value; + if (!fcinfo->isnull) + value = datumCopy(value, fcache->typbyval, fcache->typlen); } /* - * If this isn't the last command for the function, we don't return - * any results, but we have to increment the command counter so that - * subsequent commands can see changes made by previous ones. + * If this is a single valued function we have to end the function + * execution now. */ - CommandCounterIncrement(); - return (Datum) NULL; + if (!fcinfo->flinfo->fn_retset) + postquel_end(es, fcache); + + return value; } Datum @@ -726,7 +799,7 @@ ShutdownSQLFunction(Datum arg) { /* Shut down anything still running */ if (es->status == F_EXEC_RUN) - postquel_end(es); + postquel_end(es, fcache); /* Reset states to START in case we're called again */ es->status = F_EXEC_START; es = es->next; |