| /*------------------------------------------------------------------------- |
| * |
| * plsample.c |
| * Handler for the PL/Sample procedural language |
| * |
| * Portions Copyright (c) 1996-2023, 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; |
| } |