Improved way to use transitional values in user function

Once before, I said

Use of global variable in user function - umitanuki::quartet

Be careful to use global variable and avoid it as long as possible!

I found a way to keep transitional value during user function calls. The hint was in fmgr.h

/*
 * This struct holds the system-catalog information that must be looked up
 * before a function can be called through fmgr.  If the same function is
 * to be called multiple times, the lookup need be done only once and the
 * info struct saved for re-use.
 */
typedef struct FmgrInfo
{
  /* pointer to function or handler to be called */
  PGFunction  fn_addr;    
  /* OID of function (NOT of handler, if any) */
  Oid      fn_oid;      
  /* 0..FUNC_MAX_ARGS, or -1 if variable arg * count */
  short    fn_nargs;    
  /* function is "strict" (NULL in => NULL out) */
  bool    fn_strict;    
  /* function returns a set */
  bool    fn_retset;    
  /* extra space for use by handler */
  void     *fn_extra;    
  /* memory context to store fn_extra in */
  MemoryContext fn_mcxt; 
  /* expression parse tree for call, or NULL */   
  fmNodePtr  fn_expr;    
} FmgrInfo;

/*
 * This struct is the data actually passed to an fmgr-called function.
 */
typedef struct FunctionCallInfoData
{
  /* ptr to lookup info used for this call */
  FmgrInfo   *flinfo;    
  /* pass info about context of call */  
  fmNodePtr  context;    
  /* pass or return extra info about result */
  fmNodePtr  resultinfo;    
  /* function must set true if result is NULL */
  bool    isnull;      
  /* # arguments actually passed */
  short    nargs;      
  /* Arguments passed to function */
  Datum    arg[FUNC_MAX_ARGS];    
  /* T if arg[i] is actually NULL */
  bool    argnull[FUNC_MAX_ARGS]; 
} FunctionCallInfoData;

God'em. PG_FUNCTION_ARGS macro means exactly FunctionCallInfo *fcinfo, which has a member pointer to FmgrInfo flinfo, which holds fn_extra with comments /* extra space for use by handler */.

What does this mean?

You use this pointer area as you like. While the fcinfo is made newly call by call because it has arguments list evaluated per call, fcinfo->flinfo is fixed for a sequence of function calls. So the pointer fn_extra is alive during your function calls. But be careful again, if you do something with stored value in this pointer, you should call palloc / palloc0 to allocate memory. The trap is waiting for you. As I referred in the entry before, palloc'ed memory area is in the MemoryContext and default MemoryContext area for the function is only for per call. To store some value during whole the function calls, MemoryContextSwitchTo(). To which? fcinfo->flinfo->fn_mcxt gets there.

So, since fcinof->flinfo->fn_extra is always NULL unless you set something explicitly, you check it and if it is NULL it's a first call. Prepare for the lunch.

if(fcinfo->flinfo->fn_extra == NULL){
  /* this code will be executed only once on the first */
  // do something
  // then, switch MemoryContext
  MemoryContext oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
  void *pointer = palloc(bytes);
  fcinfo->flinfo->fn_extra = pointer;
  MemoryContextSwitchTo(oldcontext);
}else{
  void* pointer = fcinfo->flinfo->fn_extra;
}

Don't forget to restore MemoryContext to old one!