Datum Type_invokeSRF(Type self, jclass cls, jmethodID method, jvalue* args, PG_FUNCTION_ARGS) { bool hasRow; CallContextData* ctxData; FuncCallContext* context; MemoryContext currCtx; /* stuff done only on the first call of the function */ if(SRF_IS_FIRSTCALL()) { jobject tmp; /* create a function context for cross-call persistence */ context = SRF_FIRSTCALL_INIT(); currCtx = MemoryContextSwitchTo(context->multi_call_memory_ctx); /* Call the declared Java function. It returns an instance that can produce * the rows. */ tmp = Type_getSRFProducer(self, cls, method, args); if(tmp == 0) { Invocation_assertDisconnect(); MemoryContextSwitchTo(currCtx); fcinfo->isnull = true; SRF_RETURN_DONE(context); } ctxData = (CallContextData*)palloc(sizeof(CallContextData)); context->user_fctx = ctxData; ctxData->elemType = self; ctxData->rowProducer = JNI_newGlobalRef(tmp); JNI_deleteLocalRef(tmp); /* Some row producers will need a writable result set in order * to produce the row. If one is needed, it's created here. */ tmp = Type_getSRFCollector(self, fcinfo); if(tmp == 0) ctxData->rowCollector = 0; else { ctxData->rowCollector = JNI_newGlobalRef(tmp); JNI_deleteLocalRef(tmp); } ctxData->trusted = currentInvocation->trusted; ctxData->hasConnected = currentInvocation->hasConnected; ctxData->invocation = currentInvocation->invocation; if(ctxData->hasConnected) ctxData->spiContext = CurrentMemoryContext; else ctxData->spiContext = 0; ctxData->rowContext = AllocSetContextCreate(context->multi_call_memory_ctx, "PL/Java row context", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); /* Register callback to be called when the function ends */ RegisterExprContextCallback(((ReturnSetInfo*)fcinfo->resultinfo)->econtext, _endOfSetCB, PointerGetDatum(ctxData)); MemoryContextSwitchTo(currCtx); } context = SRF_PERCALL_SETUP(); ctxData = (CallContextData*)context->user_fctx; MemoryContextReset(ctxData->rowContext); currCtx = MemoryContextSwitchTo(ctxData->rowContext); currentInvocation->hasConnected = ctxData->hasConnected; currentInvocation->invocation = ctxData->invocation; hasRow = Type_hasNextSRF(self, ctxData->rowProducer, ctxData->rowCollector, (jint)context->call_cntr); ctxData->hasConnected = currentInvocation->hasConnected; ctxData->invocation = currentInvocation->invocation; currentInvocation->hasConnected = false; currentInvocation->invocation = 0; if(hasRow) { Datum result = Type_nextSRF(self, ctxData->rowProducer, ctxData->rowCollector); MemoryContextSwitchTo(currCtx); SRF_RETURN_NEXT(context, result); } MemoryContextSwitchTo(currCtx); /* Unregister this callback and call it manually. We do this because * otherwise it will be called when the backend is in progress of * cleaning up Portals. If we close cursors (i.e. drop portals) in * the close, then that mechanism fails since attempts are made to * delete portals more then once. */ UnregisterExprContextCallback( ((ReturnSetInfo*)fcinfo->resultinfo)->econtext, _endOfSetCB, PointerGetDatum(ctxData)); _closeIteration(ctxData); /* This is the end of the set. */ SRF_RETURN_DONE(context); }
/* * ExecMakeFunctionResultSet * * Evaluate the arguments to a set-returning function and then call the * function itself. The argument expressions may not contain set-returning * functions (the planner is supposed to have separated evaluation for those). * * This should be called in a short-lived (per-tuple) context, argContext * needs to live until all rows have been returned (i.e. *isDone set to * ExprEndResult or ExprSingleResult). * * This is used by nodeProjectSet.c. */ Datum ExecMakeFunctionResultSet(SetExprState *fcache, ExprContext *econtext, MemoryContext argContext, bool *isNull, ExprDoneCond *isDone) { List *arguments; Datum result; FunctionCallInfo fcinfo; PgStat_FunctionCallUsage fcusage; ReturnSetInfo rsinfo; bool callit; int i; restart: /* Guard against stack overflow due to overly complex expressions */ check_stack_depth(); /* * If a previous call of the function returned a set result in the form of * a tuplestore, continue reading rows from the tuplestore until it's * empty. */ if (fcache->funcResultStore) { TupleTableSlot *slot = fcache->funcResultSlot; MemoryContext oldContext; bool foundTup; /* * Have to make sure tuple in slot lives long enough, otherwise * clearing the slot could end up trying to free something already * freed. */ oldContext = MemoryContextSwitchTo(slot->tts_mcxt); foundTup = tuplestore_gettupleslot(fcache->funcResultStore, true, false, fcache->funcResultSlot); MemoryContextSwitchTo(oldContext); if (foundTup) { *isDone = ExprMultipleResult; if (fcache->funcReturnsTuple) { /* We must return the whole tuple as a Datum. */ *isNull = false; return ExecFetchSlotTupleDatum(fcache->funcResultSlot); } else { /* Extract the first column and return it as a scalar. */ return slot_getattr(fcache->funcResultSlot, 1, isNull); } } /* Exhausted the tuplestore, so clean up */ tuplestore_end(fcache->funcResultStore); fcache->funcResultStore = NULL; *isDone = ExprEndResult; *isNull = true; return (Datum) 0; } /* * arguments is a list of expressions to evaluate before passing to the * function manager. We skip the evaluation if it was already done in the * previous call (ie, we are continuing the evaluation of a set-valued * function). Otherwise, collect the current argument values into fcinfo. * * The arguments have to live in a context that lives at least until all * rows from this SRF have been returned, otherwise ValuePerCall SRFs * would reference freed memory after the first returned row. */ fcinfo = &fcache->fcinfo_data; arguments = fcache->args; if (!fcache->setArgsValid) { MemoryContext oldContext = MemoryContextSwitchTo(argContext); ExecEvalFuncArgs(fcinfo, arguments, econtext); MemoryContextSwitchTo(oldContext); } else { /* Reset flag (we may set it again below) */ fcache->setArgsValid = false; } /* * Now call the function, passing the evaluated parameter values. */ /* Prepare a resultinfo node for communication. */ fcinfo->resultinfo = (Node *) &rsinfo; rsinfo.type = T_ReturnSetInfo; rsinfo.econtext = econtext; rsinfo.expectedDesc = fcache->funcResultDesc; rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); /* note we do not set SFRM_Materialize_Random or _Preferred */ rsinfo.returnMode = SFRM_ValuePerCall; /* isDone is filled below */ rsinfo.setResult = NULL; rsinfo.setDesc = NULL; /* * If function is strict, and there are any NULL arguments, skip calling * the function. */ callit = true; if (fcache->func.fn_strict) { for (i = 0; i < fcinfo->nargs; i++) { if (fcinfo->argnull[i]) { callit = false; break; } } } if (callit) { pgstat_init_function_usage(fcinfo, &fcusage); fcinfo->isnull = false; rsinfo.isDone = ExprSingleResult; result = FunctionCallInvoke(fcinfo); *isNull = fcinfo->isnull; *isDone = rsinfo.isDone; pgstat_end_function_usage(&fcusage, rsinfo.isDone != ExprMultipleResult); } else { /* for a strict SRF, result for NULL is an empty set */ result = (Datum) 0; *isNull = true; *isDone = ExprEndResult; } /* Which protocol does function want to use? */ if (rsinfo.returnMode == SFRM_ValuePerCall) { if (*isDone != ExprEndResult) { /* * Save the current argument values to re-use on the next call. */ if (*isDone == ExprMultipleResult) { fcache->setArgsValid = true; /* Register cleanup callback if we didn't already */ if (!fcache->shutdown_reg) { RegisterExprContextCallback(econtext, ShutdownSetExpr, PointerGetDatum(fcache)); fcache->shutdown_reg = true; } } } } else if (rsinfo.returnMode == SFRM_Materialize) { /* check we're on the same page as the function author */ if (rsinfo.isDone != ExprSingleResult) ereport(ERROR, (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), errmsg("table-function protocol for materialize mode was not followed"))); if (rsinfo.setResult != NULL) { /* prepare to return values from the tuplestore */ ExecPrepareTuplestoreResult(fcache, econtext, rsinfo.setResult, rsinfo.setDesc); /* loop back to top to start returning from tuplestore */ goto restart; } /* if setResult was left null, treat it as empty set */ *isDone = ExprEndResult; *isNull = true; result = (Datum) 0; } else ereport(ERROR, (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), errmsg("unrecognized table-function returnMode: %d", (int) rsinfo.returnMode))); return result; }
/* * init_MultiFuncCall * Create an empty FuncCallContext data structure * and do some other basic Multi-function call setup * and error checking */ FuncCallContext * init_MultiFuncCall(PG_FUNCTION_ARGS) { FuncCallContext *retval; /* * Bail if we're called in the wrong context */ if (fcinfo->resultinfo == NULL || !IsA(fcinfo->resultinfo, ReturnSetInfo)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-valued function called in context that cannot accept a set"))); if (fcinfo->flinfo->fn_extra == NULL) { /* * First call */ ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; MemoryContext multi_call_ctx; /* * Create a suitably long-lived context to hold cross-call data */ multi_call_ctx = AllocSetContextCreate(fcinfo->flinfo->fn_mcxt, "SRF multi-call context", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE); /* * Allocate suitably long-lived space and zero it */ retval = (FuncCallContext *) MemoryContextAllocZero(multi_call_ctx, sizeof(FuncCallContext)); /* * initialize the elements */ retval->call_cntr = 0; retval->max_calls = 0; retval->slot = NULL; retval->user_fctx = NULL; retval->attinmeta = NULL; retval->tuple_desc = NULL; retval->multi_call_memory_ctx = multi_call_ctx; /* * save the pointer for cross-call use */ fcinfo->flinfo->fn_extra = retval; /* * Ensure we will get shut down cleanly if the exprcontext is not run * to completion. */ RegisterExprContextCallback(rsi->econtext, shutdown_MultiFuncCall, PointerGetDatum(fcinfo->flinfo)); } else { /* second and subsequent calls */ elog(ERROR, "init_MultiFuncCall cannot be called more than once"); /* never reached, but keep compiler happy */ retval = NULL; } return retval; }
/* * gp_persistent_relation_node_check() * * Reads the physical filesystem for every defined filespace and returns the * list of relfilenodes that actually exist. This list should match the set of * relfilenodes tracked in gp_persistent_relation_node. */ Datum gp_persistent_relation_node_check(PG_FUNCTION_ARGS) { FuncCallContext *fcontext; node_check_data *fdata; ReturnSetInfo *rsinfo; MemoryContext oldcontext; Oid relfilenode = InvalidOid; int32 segnum = 0; HeapTuple tuple; char *primaryPath = NULL; char *mirrorPath = NULL; if (SRF_IS_FIRSTCALL()) { Relation rel; TupleDesc tupdesc; fcontext = SRF_FIRSTCALL_INIT(); rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* * The fdata cannot be allocated in the multi_call_ctx because the * multi_call_context gets cleaned up by the MultiFuncCall callback * function which gets called before the callback this function * registers to cleanup the fdata structure. So instead we allocate * in the parent context fn_mcxt. */ oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); fdata = (node_check_data*) palloc0(sizeof(node_check_data)); fcontext->user_fctx = fdata; /* * Register a call to cleanup when the function ends. */ RegisterExprContextCallback(rsinfo->econtext, nodeCheckCleanup, PointerGetDatum(fdata)); /* * Setup the main loop over the list of tablespaces */ fdata->tablespaceRelation = heap_open(TableSpaceRelationId, AccessShareLock); fdata->scandesc = heap_beginscan(fdata->tablespaceRelation, SnapshotNow, 0, NULL); /* * Bless a tuple descriptor for the return type */ MemoryContextSwitchTo(fcontext->multi_call_memory_ctx); rel = RelationIdGetRelation(GpPersistentRelationNodeRelationId); tupdesc = RelationGetDescr(rel); fcontext->tuple_desc = BlessTupleDesc(tupdesc); relation_close(rel, NoLock); MemoryContextSwitchTo(oldcontext); } fcontext = SRF_PERCALL_SETUP(); fdata = fcontext->user_fctx; /* * The code here is basically a nested loop that has been unwound so that * it can be wrapped up into a set-returning function. * * Basic structure is: * - Loop over tablespace relation * - Loop over database directories in the tablespace * - Loop over relfilenodes in the directory * - Return each tablespace, database, relfilenode, segment_number. * * The complicating factor is that we return from this function and * reenter the loop at the innermost level, so the entire loop is turned * inside-out. */ while (true) { /* Innermost loop */ if (fdata->databaseDir) { struct dirent *dent; Datum values[Natts_gp_persistent_relation_node]; bool nulls[Natts_gp_persistent_relation_node]; dent = ReadDir(fdata->databaseDir, fdata->databaseDirName); if (!dent) { /* step out of innermost loop */ FreeDir(fdata->databaseDir); fdata->databaseDir = NULL; continue; } /* skip the boring stuff */ if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) continue; /* Skip things that don't look like relfilenodes */ if (!strToRelfilenode(dent->d_name, &relfilenode, &segnum)) continue; /* Return relfilenodes as we find them */ MemSet(nulls, true, sizeof(nulls)); nulls[Anum_gp_persistent_relation_node_tablespace_oid-1] = false; nulls[Anum_gp_persistent_relation_node_database_oid-1] = false; nulls[Anum_gp_persistent_relation_node_relfilenode_oid-1] = false; nulls[Anum_gp_persistent_relation_node_segment_file_num-1] = false; values[Anum_gp_persistent_relation_node_tablespace_oid-1] = ObjectIdGetDatum(fdata->tablespaceOid); values[Anum_gp_persistent_relation_node_database_oid-1] = ObjectIdGetDatum(fdata->databaseOid); values[Anum_gp_persistent_relation_node_relfilenode_oid-1] = ObjectIdGetDatum(relfilenode); values[Anum_gp_persistent_relation_node_segment_file_num-1] = Int32GetDatum(segnum); tuple = heap_form_tuple(fcontext->tuple_desc, values, nulls); SRF_RETURN_NEXT(fcontext, HeapTupleGetDatum(tuple)); } /* Loop over database directories in the tablespace */ if (fdata->tablespaceDir) { struct dirent *dent; dent = ReadDir(fdata->tablespaceDir, fdata->tablespaceDirName); if (!dent) { /* step out of database loop */ FreeDir(fdata->tablespaceDir); fdata->tablespaceDir = NULL; continue; } /* skip the borring stuff */ if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) continue; /* Skip things that don't look like database oids */ if (strlen(dent->d_name) != strspn(dent->d_name, "0123456789")) continue; /* convert the string to an oid */ fdata->databaseOid = pg_atoi(dent->d_name, 4, 0); /* form a database path using this oid */ snprintf(fdata->databaseDirName, MAXPGPATH, "%s/%s", fdata->tablespaceDirName, dent->d_name); oldcontext = MemoryContextSwitchTo(fcontext->multi_call_memory_ctx); fdata->databaseDir = AllocateDir(fdata->databaseDirName); MemoryContextSwitchTo(oldcontext); if (fdata->databaseDir == NULL) ereport(ERROR, (errcode_for_file_access(), errmsg("could not open directory \"%s\": %m", fdata->databaseDirName))); continue; } /* Outermost loop over tablespaces */ tuple = heap_getnext(fdata->scandesc, ForwardScanDirection); if (!HeapTupleIsValid(tuple)) SRF_RETURN_DONE(fcontext); /* FINAL return */ fdata->tablespaceOid = HeapTupleGetOid(tuple); PersistentTablespace_GetPrimaryAndMirrorFilespaces( fdata->tablespaceOid, &primaryPath, &mirrorPath); /* Find the location of this tablespace on disk */ FormTablespacePath(fdata->tablespaceDirName, primaryPath, fdata->tablespaceOid); /* * Primary path is null for the pg_system filespace, additionally * Mirror path is null if there are no mirrors */ if (primaryPath) { pfree(primaryPath); primaryPath = NULL; } if (mirrorPath) { pfree(mirrorPath); mirrorPath = NULL; } oldcontext = MemoryContextSwitchTo(fcontext->multi_call_memory_ctx); fdata->tablespaceDir = AllocateDir(fdata->tablespaceDirName); MemoryContextSwitchTo(oldcontext); if (fdata->tablespaceDir == NULL) ereport(ERROR, (errcode_for_file_access(), errmsg("could not open directory \"%s\": %m", fdata->tablespaceDirName))); /* The global tablespace doesn't have database directories */ if (fdata->tablespaceOid == GLOBALTABLESPACE_OID) { fdata->databaseOid = 0; /* Skip to the innermost loop */ fdata->databaseDir = fdata->tablespaceDir; fdata->tablespaceDir = NULL; strncpy(fdata->databaseDirName, fdata->tablespaceDirName, MAXPGPATH); } } /* Unreachable */ SRF_RETURN_DONE(fcontext); }