text: /*-------------------------------------------------------------------------
   *
   * plsample.c
   *	  Handler for the PL/Sample procedural language
   *
   * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
   * Portions Copyright (c) 1994, Regents of the University of California
   *
   *
   * IDENTIFICATION
   *		src/test/modules/plsample/plsample.c
   *
   *-------------------------------------------------------------------------
   */
  
  #include "postgres.h"
  
  #include "catalog/pg_proc.h"
  #include "catalog/pg_type.h"
  #include "commands/event_trigger.h"
  #include "commands/trigger.h"
  #include "executor/spi.h"
  #include "funcapi.h"
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  #include "utils/syscache.h"
  
  PG_MODULE_MAGIC;
  
  PG_FUNCTION_INFO_V1(plsample_call_handler);
  
  static Datum plsample_func_handler(PG_FUNCTION_ARGS);
  static HeapTuple plsample_trigger_handler(PG_FUNCTION_ARGS);
  
  /*
   * Handle function, procedure, and trigger calls.
   */
  Datum
  plsample_call_handler(PG_FUNCTION_ARGS)
  {
  	Datum		retval = (Datum) 0;
  
  	/*
  	 * Many languages will require cleanup that happens even in the event of
  	 * an error.  That can happen in the PG_FINALLY block.  If none is needed,
  	 * this PG_TRY construct can be omitted.
  	 */
  	PG_TRY();
  	{
  		/*
  		 * Determine if called as function or trigger and call appropriate
  		 * subhandler.
  		 */
  		if (CALLED_AS_TRIGGER(fcinfo))
  		{
  			/*
  			 * This function has been called as a trigger function, where
  			 * (TriggerData *) fcinfo->context includes the information of the
  			 * context.
  			 */
  			retval = PointerGetDatum(plsample_trigger_handler(fcinfo));
  		}
  		else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
  		{
  			/*
  			 * This function is called as an event trigger function, where
  			 * (EventTriggerData *) fcinfo->context includes the information
  			 * of the context.
  			 *
  			 * TODO: provide an example handler.
  			 */
  		}
  		else
  		{
  			/* Regular function handler */
  			retval = plsample_func_handler(fcinfo);
  		}
  	}
  	PG_FINALLY();
  	{
  	}
  	PG_END_TRY();
  
  	return retval;
  }
  
  /*
   * plsample_func_handler
   *
   * Function called by the call handler for function execution.
   */
  static Datum
  plsample_func_handler(PG_FUNCTION_ARGS)
  {
  	HeapTuple	pl_tuple;
  	Datum		ret;
  	char	   *source;
  	bool		isnull;
  	FmgrInfo   *arg_out_func;
  	Form_pg_type type_struct;
  	HeapTuple	type_tuple;
  	Form_pg_proc pl_struct;
  	volatile MemoryContext proc_cxt = NULL;
  	Oid		   *argtypes;
  	char	  **argnames;
  	char	   *argmodes;
  	char	   *proname;
  	Form_pg_type pg_type_entry;
  	Oid			result_typioparam;
  	Oid			prorettype;
  	FmgrInfo	result_in_func;
  	int			numargs;
  
  	/* Fetch the function's pg_proc entry. */
  	pl_tuple = SearchSysCache1(PROCOID,
  							   ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
  	if (!HeapTupleIsValid(pl_tuple))
  		elog(ERROR, "cache lookup failed for function %u",
  			 fcinfo->flinfo->fn_oid);
  
  	/*
  	 * Extract and print the source text of the function.  This can be used as
  	 * a base for the function validation and execution.
  	 */
  	pl_struct = (Form_pg_proc) GETSTRUCT(pl_tuple);
  	proname = pstrdup(NameStr(pl_struct->proname));
  	ret = SysCacheGetAttr(PROCOID, pl_tuple, Anum_pg_proc_prosrc, &isnull);
  	if (isnull)
  		elog(ERROR, "could not find source text of function \"%s\"",
  			 proname);
  	source = DatumGetCString(DirectFunctionCall1(textout, ret));
  	ereport(NOTICE,
  			(errmsg("source text of function \"%s\": %s",
  					proname, source)));
  
  	/*
  	 * Allocate a context that will hold all the Postgres data for the
  	 * procedure.
  	 */
  	proc_cxt = AllocSetContextCreate(TopMemoryContext,
  									 "PL/Sample function",
  									 ALLOCSET_SMALL_SIZES);
  
  	arg_out_func = (FmgrInfo *) palloc0(fcinfo->nargs * sizeof(FmgrInfo));
  	numargs = get_func_arg_info(pl_tuple, &argtypes, &argnames, &argmodes);
  
  	/*
  	 * Iterate through all of the function arguments, printing each input
  	 * value.
  	 */
  	for (int i = 0; i < numargs; i++)
  	{
  		Oid			argtype = pl_struct->proargtypes.values[i];
  		char	   *value;
  
  		type_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(argtype));
  		if (!HeapTupleIsValid(type_tuple))
  			elog(ERROR, "cache lookup failed for type %u", argtype);
  
  		type_struct = (Form_pg_type) GETSTRUCT(type_tuple);
  		fmgr_info_cxt(type_struct->typoutput, &(arg_out_func[i]), proc_cxt);
  		ReleaseSysCache(type_tuple);
  
  		value = OutputFunctionCall(&arg_out_func[i], fcinfo->args[i].value);
  		ereport(NOTICE,
  				(errmsg("argument: %d; name: %s; value: %s",
  						i, argnames[i], value)));
  	}
  
  	/* Type of the result */
  	prorettype = pl_struct->prorettype;
  	ReleaseSysCache(pl_tuple);
  
  	/*
  	 * Get the required information for input conversion of the return value.
  	 *
  	 * If the function uses VOID as result, it is better to return NULL.
  	 * Anyway, let's be honest.  This is just a template, so there is not much
  	 * we can do here.  This returns NULL except if the result type is text,
  	 * where the result is the source text of the function.
  	 */
  	if (prorettype != TEXTOID)
  		PG_RETURN_NULL();
  
  	type_tuple = SearchSysCache1(TYPEOID,
  								 ObjectIdGetDatum(prorettype));
  	if (!HeapTupleIsValid(type_tuple))
  		elog(ERROR, "cache lookup failed for type %u", prorettype);
  	pg_type_entry = (Form_pg_type) GETSTRUCT(type_tuple);
  	result_typioparam = getTypeIOParam(type_tuple);
  
  	fmgr_info_cxt(pg_type_entry->typinput, &result_in_func, proc_cxt);
  	ReleaseSysCache(type_tuple);
  
  	ret = InputFunctionCall(&result_in_func, source, result_typioparam, -1);
  	PG_RETURN_DATUM(ret);
  }
  
  /*
   * plsample_trigger_handler
   *
   * Function called by the call handler for trigger execution.
   */
  static HeapTuple
  plsample_trigger_handler(PG_FUNCTION_ARGS)
  {
  	TriggerData *trigdata = (TriggerData *) fcinfo->context;
  	char	   *string;
  	volatile HeapTuple rettup;
  	HeapTuple	pl_tuple;
  	Datum		ret;
  	char	   *source;
  	bool		isnull;
  	Form_pg_proc pl_struct;
  	char	   *proname;
  	int			rc PG_USED_FOR_ASSERTS_ONLY;
  
  	/* Make sure this is being called from a trigger. */
  	if (!CALLED_AS_TRIGGER(fcinfo))
  		elog(ERROR, "not called by trigger manager");
  
  	/* Connect to the SPI manager */
  	if (SPI_connect() != SPI_OK_CONNECT)
  		elog(ERROR, "could not connect to SPI manager");
  
  	rc = SPI_register_trigger_data(trigdata);
  	Assert(rc >= 0);
  
  	/* Fetch the function's pg_proc entry. */
  	pl_tuple = SearchSysCache1(PROCOID,
  							   ObjectIdGetDatum(fcinfo->flinfo->fn_oid));
  	if (!HeapTupleIsValid(pl_tuple))
  		elog(ERROR, "cache lookup failed for function %u",
  			 fcinfo->flinfo->fn_oid);
  
  	/*
  	 * Code Retrieval
  	 *
  	 * Extract and print the source text of the function.  This can be used as
  	 * a base for the function validation and execution.
  	 */
  	pl_struct = (Form_pg_proc) GETSTRUCT(pl_tuple);
  	proname = pstrdup(NameStr(pl_struct->proname));
  	ret = SysCacheGetAttr(PROCOID, pl_tuple, Anum_pg_proc_prosrc, &isnull);
  	if (isnull)
  		elog(ERROR, "could not find source text of function \"%s\"",
  			 proname);
  	source = DatumGetCString(DirectFunctionCall1(textout, ret));
  	ereport(NOTICE,
  			(errmsg("source text of function \"%s\": %s",
  					proname, source)));
  
  	/*
  	 * We're done with the pg_proc tuple, so release it.  (Note that the
  	 * "proname" and "source" strings are now standalone copies.)
  	 */
  	ReleaseSysCache(pl_tuple);
  
  	/*
  	 * Code Augmentation
  	 *
  	 * The source text may be augmented here, such as by wrapping it as the
  	 * body of a function in the target language, prefixing a parameter list
  	 * with names like TD_name, TD_relid, TD_table_name, TD_table_schema,
  	 * TD_event, TD_when, TD_level, TD_NEW, TD_OLD, and args, using whatever
  	 * types in the target language are convenient. The augmented text can be
  	 * cached in a longer-lived memory context, or, if the target language
  	 * uses a compilation step, that can be done here, caching the result of
  	 * the compilation.
  	 */
  
  	/*
  	 * Code Execution
  	 *
  	 * Here the function (the possibly-augmented source text, or the result of
  	 * compilation if the target language uses such a step) should be
  	 * executed, after binding values from the TriggerData struct to the
  	 * appropriate parameters.
  	 *
  	 * In this example we just print a lot of info via ereport.
  	 */
  
  	PG_TRY();
  	{
  		ereport(NOTICE,
  				(errmsg("trigger name: %s", trigdata->tg_trigger->tgname)));
  		string = SPI_getrelname(trigdata->tg_relation);
  		ereport(NOTICE, (errmsg("trigger relation: %s", string)));
  
  		string = SPI_getnspname(trigdata->tg_relation);
  		ereport(NOTICE, (errmsg("trigger relation schema: %s", string)));
  
  		/* Example handling of different trigger aspects. */
  
  		if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
  		{
  			ereport(NOTICE, (errmsg("triggered by INSERT")));
  			rettup = trigdata->tg_trigtuple;
  		}
  		else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
  		{
  			ereport(NOTICE, (errmsg("triggered by DELETE")));
  			rettup = trigdata->tg_trigtuple;
  		}
  		else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
  		{
  			ereport(NOTICE, (errmsg("triggered by UPDATE")));
  			rettup = trigdata->tg_trigtuple;
  		}
  		else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
  		{
  			ereport(NOTICE, (errmsg("triggered by TRUNCATE")));
  			rettup = trigdata->tg_trigtuple;
  		}
  		else
  			elog(ERROR, "unrecognized event: %u", trigdata->tg_event);
  
  		if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
  			ereport(NOTICE, (errmsg("triggered BEFORE")));
  		else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  			ereport(NOTICE, (errmsg("triggered AFTER")));
  		else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event))
  			ereport(NOTICE, (errmsg("triggered INSTEAD OF")));
  		else
  			elog(ERROR, "unrecognized when: %u", trigdata->tg_event);
  
  		if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
  			ereport(NOTICE, (errmsg("triggered per row")));
  		else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  			ereport(NOTICE, (errmsg("triggered per statement")));
  		else
  			elog(ERROR, "unrecognized level: %u", trigdata->tg_event);
  
  		/*
  		 * Iterate through all of the trigger arguments, printing each input
  		 * value.
  		 */
  		for (int i = 0; i < trigdata->tg_trigger->tgnargs; i++)
  			ereport(NOTICE,
  					(errmsg("trigger arg[%i]: %s", i,
  							trigdata->tg_trigger->tgargs[i])));
  	}
  	PG_CATCH();
  	{
  		/* Error cleanup code would go here */
  		PG_RE_THROW();
  	}
  	PG_END_TRY();
  
  	if (SPI_finish() != SPI_OK_FINISH)
  		elog(ERROR, "SPI_finish() failed");
  
  	return rettup;
  }
  