📄 functions.c
字号:
dtuptypmod = -1; } else { /* function is declared to return RECORD */ TupleDesc tupDesc = fcache->junkFilter->jf_cleanTupType; if (tupDesc->tdtypeid == RECORDOID && tupDesc->tdtypmod < 0) assign_record_type_typmod(tupDesc); dtuptype = tupDesc->tdtypeid; dtuptypmod = tupDesc->tdtypmod; } HeapTupleHeaderSetDatumLength(dtup, t_len); HeapTupleHeaderSetTypeId(dtup, dtuptype); HeapTupleHeaderSetTypMod(dtup, dtuptypmod); value = PointerGetDatum(dtup); fcinfo->isnull = false; } else { /* * Returning a scalar, which we have to extract from the first column * of the SELECT result, and then copy into result context if needed. */ value = slot_getattr(slot, 1, &(fcinfo->isnull)); if (!fcinfo->isnull) value = datumCopy(value, fcache->typbyval, fcache->typlen); } MemoryContextSwitchTo(oldcontext); /* * If this is a single valued function we have to end the function * execution now. */ if (!fcinfo->flinfo->fn_retset) postquel_end(es); return value;}Datumfmgr_sql(PG_FUNCTION_ARGS){ MemoryContext oldcontext; SQLFunctionCachePtr fcache; ErrorContextCallback sqlerrcontext; execution_state *es; Datum result = 0; /* * Switch to context in which the fcache lives. This ensures that * parsetrees, plans, etc, will have sufficient lifetime. The * sub-executor is responsible for deleting per-tuple information. */ oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); /* * Setup error traceback support for ereport() */ sqlerrcontext.callback = sql_exec_error_callback; sqlerrcontext.arg = fcinfo->flinfo; sqlerrcontext.previous = error_context_stack; error_context_stack = &sqlerrcontext; /* * Initialize fcache (build plans) if first time through. */ fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra; if (fcache == NULL) { init_sql_fcache(fcinfo->flinfo); fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra; } es = fcache->func_state; /* * Convert params to appropriate format if starting a fresh execution. (If * continuing execution, we can re-use prior params.) */ if (es && es->status == F_EXEC_START) postquel_sub_params(fcache, fcinfo); /* * Find first unfinished query in function. */ while (es && es->status == F_EXEC_DONE) es = es->next; /* * Execute each command in the function one after another until we're * executing the final command and get a result or we run out of commands. */ while (es) { result = postquel_execute(es, fcinfo, fcache, oldcontext); if (es->status != F_EXEC_DONE) break; es = es->next; } /* * If we've gone through every command in this function, we are done. */ if (es == NULL) { /* * Reset the execution states to start over again on next call. */ es = fcache->func_state; while (es) { es->status = F_EXEC_START; es = es->next; } /* * Let caller know we're finished. */ if (fcinfo->flinfo->fn_retset) { ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; if (rsi && IsA(rsi, ReturnSetInfo)) rsi->isDone = ExprEndResult; else ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-valued function called in context that cannot accept a set"))); fcinfo->isnull = true; result = (Datum) 0; /* Deregister shutdown callback, if we made one */ if (fcache->shutdown_reg) { UnregisterExprContextCallback(rsi->econtext, ShutdownSQLFunction, PointerGetDatum(fcache)); fcache->shutdown_reg = false; } } error_context_stack = sqlerrcontext.previous; MemoryContextSwitchTo(oldcontext); return result; } /* * If we got a result 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)); /* * Let caller know we're not finished. */ if (fcinfo->flinfo->fn_retset) { ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; if (rsi && IsA(rsi, ReturnSetInfo)) rsi->isDone = ExprMultipleResult; else ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-valued function called in context that cannot accept a set"))); /* * Ensure we will get shut down cleanly if the exprcontext is not run * to completion. */ if (!fcache->shutdown_reg) { RegisterExprContextCallback(rsi->econtext, ShutdownSQLFunction, PointerGetDatum(fcache)); fcache->shutdown_reg = true; } } error_context_stack = sqlerrcontext.previous; MemoryContextSwitchTo(oldcontext); return result;}/* * error context callback to let us supply a call-stack traceback */static voidsql_exec_error_callback(void *arg){ FmgrInfo *flinfo = (FmgrInfo *) arg; SQLFunctionCachePtr fcache = (SQLFunctionCachePtr) flinfo->fn_extra; HeapTuple func_tuple; Form_pg_proc functup; char *fn_name; int syntaxerrposition; /* Need access to function's pg_proc tuple */ func_tuple = SearchSysCache(PROCOID, ObjectIdGetDatum(flinfo->fn_oid), 0, 0, 0); if (!HeapTupleIsValid(func_tuple)) return; /* shouldn't happen */ functup = (Form_pg_proc) GETSTRUCT(func_tuple); fn_name = NameStr(functup->proname); /* * If there is a syntax error position, convert to internal syntax error */ syntaxerrposition = geterrposition(); if (syntaxerrposition > 0) { bool isnull; Datum tmp; char *prosrc; tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosrc, &isnull); if (isnull) elog(ERROR, "null prosrc"); prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp)); errposition(0); internalerrposition(syntaxerrposition); internalerrquery(prosrc); pfree(prosrc); } /* * 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 * for F_EXEC_RUN state, so that errors during ExecutorStart or * ExecutorEnd are blamed on the appropriate query; see postquel_start and * postquel_end.) */ if (fcache) { execution_state *es; int query_num; es = fcache->func_state; query_num = 1; while (es) { if (es->qd) { errcontext("SQL function \"%s\" statement %d", fn_name, query_num); break; } es = es->next; query_num++; } if (es == NULL) { /* * couldn't identify a running query; might be function entry, * function exit, or between queries. */ errcontext("SQL function \"%s\"", fn_name); } } else { /* must have failed during init_sql_fcache() */ errcontext("SQL function \"%s\" during startup", fn_name); } ReleaseSysCache(func_tuple);}/* * callback function in case a function-returning-set needs to be shut down * before it has been run to completion */static voidShutdownSQLFunction(Datum arg){ SQLFunctionCachePtr fcache = (SQLFunctionCachePtr) DatumGetPointer(arg); execution_state *es = fcache->func_state; while (es != NULL) { /* Shut down anything still running */ if (es->status == F_EXEC_RUN) postquel_end(es); /* Reset states to START in case we're called again */ es->status = F_EXEC_START; es = es->next; } /* execUtils will deregister the callback... */ fcache->shutdown_reg = false;}/* * check_sql_fn_retval() -- check return value of a list of sql parse trees. * * The return value of a sql function is the value returned by * the final query in the function. We do some ad-hoc type checking here * to be sure that the user is returning the type he claims. * * This is normally applied during function definition, but in the case * of a function with polymorphic arguments, we instead apply it during * function execution startup. The rettype is then the actual resolved * output type of the function, rather than the declared type. (Therefore, * we should never see ANYARRAY or ANYELEMENT as rettype.) * * The return value is true if the function returns the entire tuple result * of its final SELECT, and false otherwise. Note that because we allow * "SELECT rowtype_expression", this may be false even when the declared * function return type is a rowtype. * * If junkFilter isn't NULL, then *junkFilter is set to a JunkFilter defined * to convert the function's tuple result to the correct output tuple type. * Whenever the result value is false (ie, the function isn't returning a * tuple result), *junkFilter is set to NULL. */boolcheck_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, JunkFilter **junkFilter){ Query *parse; int cmd; List *tlist; ListCell *tlistitem; int tlistlen; char fn_typtype; Oid restype; if (junkFilter) *junkFilter = NULL; /* default result */ /* guard against empty function body; OK only if void return type */ if (queryTreeList == NIL) { if (rettype != VOIDOID) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("return type mismatch in function declared to return %s", format_type_be(rettype)), errdetail("Function's final statement must be a SELECT."))); return false; } /* find the final query */ parse = (Query *) lfirst(list_tail(queryTreeList)); cmd = parse->commandType; tlist = parse->targetList; /* * The last query must be a SELECT if and only if return type isn't VOID. */ if (rettype == VOIDOID) { if (cmd == CMD_SELECT) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("return type mismatch in function declared to return %s", format_type_be(rettype)), errdetail("Function's final statement must not be a SELECT."))); return false; } /* by here, the function is declared to return some type */ if (cmd != CMD_SELECT) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("return type mismatch in function declared to return %s", format_type_be(rettype)), errdetail("Function's final statement must be a SELECT."))); /* * Count the non-junk entries in the result targetlist. */ tlistlen = ExecCleanTargetListLength(tlist); fn_typtype = get_typtype(rettype); if (fn_typtype == 'b' || fn_typtype == 'd') { /* * For base-type returns, the target list should have exactly one * entry, and its type should agree with what the user declared. (As * of Postgres 7.2, we accept binary-compatible types too.) */ if (tlistlen != 1) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("return type mismatch in function declared to return %s", format_type_be(rettype)), errdetail("Final SELECT must return exactly one column."))); restype = exprType((Node *) ((TargetEntry *) linitial(tlist))->expr); if (!IsBinaryCoercible(restype, rettype)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("return type mismatch in function declared to return %s", format_type_be(rettype)), errdetail("Actual return type is %s.", format_type_be(restype)))); } else if (fn_typtype == 'c' || rettype == RECORDOID) { /* Returns a rowtype */ TupleDesc tupdesc; int tupnatts; /* physical number of columns in tuple */ int tuplogcols; /* # of nondeleted columns in tuple */ int colindex; /* physical column index */ /* * If the target list is of length 1, and the type of the varnode in * the target list matches the declared return type, this is okay. * This can happen, for example, where the body of the function is * 'SELECT func2()', where func2 has the same return type as the * function that's calling it. */ if (tlistlen == 1) { restype = exprType((Node *) ((TargetEntry *) linitial(tlist))->expr); if (IsBinaryCoercible(restype, rettype)) return false; /* NOT returning whole tuple */ } /* Is the rowtype fixed, or determined only at runtime? */ if (get_func_result_type(func_id, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) { /* * Assume we are returning the whole tuple. Crosschecking against * what the caller expects will happen at runtime. */ if (junkFilter) *junkFilter = ExecInitJunkFilter(tlist, false, NULL); return true; } Assert(tupdesc); /* * 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. */ tupnatts = tupdesc->natts; tuplogcols = 0; /* we'll count nondeleted cols as we go */ colindex = 0; foreach(tlistitem, tlist) { TargetEntry *tle = (TargetEntry *) lfirst(tlistitem); Form_pg_attribute attr; Oid tletype; Oid atttype; if (tle->resjunk) continue; do { colindex++; if (colindex > tupnatts) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("return type mismatch in function declared to return %s", format_type_be(rettype)), errdetail("Final SELECT returns too many columns."))); attr = tupdesc->attrs[colindex - 1]; } while (attr->attisdropped); tuplogcols++; tletype = exprType((Node *) tle->expr); atttype = attr->atttypid; if (!IsBinaryCoercible(tletype, atttype)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("return type mismatch in function declared to return %s", format_type_be(rettype)), errdetail("Final SELECT returns %s instead of %s at column %d.", format_type_be(tletype), format_type_be(atttype), tuplogcols))); } for (;;) { colindex++; if (colindex > tupnatts) break; if (!tupdesc->attrs[colindex - 1]->attisdropped) tuplogcols++; } if (tlistlen != tuplogcols) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("return type mismatch in function declared to return %s", format_type_be(rettype)), errdetail("Final SELECT returns too few columns."))); /* Set up junk filter if needed */ if (junkFilter) *junkFilter = ExecInitJunkFilterConversion(tlist, CreateTupleDescCopy(tupdesc), NULL); /* Report that we are returning entire tuple result */ return true; } else if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID) { /* This should already have been caught ... */ ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot determine result data type"), errdetail("A function returning \"anyarray\" or \"anyelement\" must have at least one argument of either type."))); } else ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("return type %s is not supported for SQL functions", format_type_be(rettype)))); return false;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -