postgresql/contrib/spi/moddatetime.c
Tom Lane 9257f07872 Replace uses of SPI_modifytuple that intend to allocate in current context.
Invent a new function heap_modify_tuple_by_cols() that is functionally
equivalent to SPI_modifytuple except that it always allocates its result
by simple palloc.  I chose however to make the API details a bit more
like heap_modify_tuple: pass a tupdesc rather than a Relation, and use
bool convention for the isnull array.

Use this function in place of SPI_modifytuple at all call sites where the
intended behavior is to allocate in current context.  (There actually are
only two call sites left that depend on the old behavior, which makes me
wonder if we should just drop this function rather than keep it.)

This new function is easier to use than heap_modify_tuple() for purposes
of replacing a single column (or, really, any fixed number of columns).
There are a number of places where it would simplify the code to change
over, but I resisted that temptation for the moment ... everywhere except
in plpgsql's exec_assign_value(); changing that might offer some small
performance benefit, so I did it.

This is on the way to removing SPI_push/SPI_pop, but it seems like
good code cleanup in its own right.

Discussion: <9633.1478552022@sss.pgh.pa.us>
2016-11-08 15:36:44 -05:00

131 lines
3.7 KiB
C

/*
moddatetime.c
contrib/spi/moddatetime.c
What is this?
It is a function to be called from a trigger for the purpose of updating
a modification datetime stamp in a record when that record is UPDATEd.
Credits
This is 95%+ based on autoinc.c, which I used as a starting point as I do
not really know what I am doing. I also had help from
Jan Wieck <jwieck@debis.com> who told me about the timestamp_in("now") function.
OH, me, I'm Terry Mackintosh <terry@terrym.com>
*/
#include "postgres.h"
#include "access/htup_details.h"
#include "catalog/pg_type.h"
#include "executor/spi.h"
#include "commands/trigger.h"
#include "utils/rel.h"
#include "utils/timestamp.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(moddatetime);
Datum
moddatetime(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
Trigger *trigger; /* to get trigger name */
int nargs; /* # of arguments */
int attnum; /* positional number of field to change */
Oid atttypid; /* type OID of field to change */
Datum newdt; /* The current datetime. */
bool newdtnull; /* null flag for it */
char **args; /* arguments */
char *relname; /* triggered relation name */
Relation rel; /* triggered relation */
HeapTuple rettuple = NULL;
TupleDesc tupdesc; /* tuple description */
if (!CALLED_AS_TRIGGER(fcinfo))
/* internal error */
elog(ERROR, "moddatetime: not fired by trigger manager");
if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
/* internal error */
elog(ERROR, "moddatetime: must be fired for row");
if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
/* internal error */
elog(ERROR, "moddatetime: must be fired before event");
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
/* internal error */
elog(ERROR, "moddatetime: cannot process INSERT events");
else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
rettuple = trigdata->tg_newtuple;
else
/* internal error */
elog(ERROR, "moddatetime: cannot process DELETE events");
rel = trigdata->tg_relation;
relname = SPI_getrelname(rel);
trigger = trigdata->tg_trigger;
nargs = trigger->tgnargs;
if (nargs != 1)
/* internal error */
elog(ERROR, "moddatetime (%s): A single argument was expected", relname);
args = trigger->tgargs;
/* must be the field layout? */
tupdesc = rel->rd_att;
/*
* This gets the position in the tuple of the field we want. args[0] being
* the name of the field to update, as passed in from the trigger.
*/
attnum = SPI_fnumber(tupdesc, args[0]);
/*
* This is where we check to see if the field we are supposed to update
* even exists.
*/
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
errmsg("\"%s\" has no attribute \"%s\"",
relname, args[0])));
/*
* Check the target field has an allowed type, and get the current
* datetime as a value of that type.
*/
atttypid = SPI_gettypeid(tupdesc, attnum);
if (atttypid == TIMESTAMPOID)
newdt = DirectFunctionCall3(timestamp_in,
CStringGetDatum("now"),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1));
else if (atttypid == TIMESTAMPTZOID)
newdt = DirectFunctionCall3(timestamptz_in,
CStringGetDatum("now"),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1));
else
{
ereport(ERROR,
(errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
errmsg("attribute \"%s\" of \"%s\" must be type TIMESTAMP or TIMESTAMPTZ",
args[0], relname)));
newdt = (Datum) 0; /* keep compiler quiet */
}
newdtnull = false;
/* Replace the attnum'th column with newdt */
rettuple = heap_modify_tuple_by_cols(rettuple, tupdesc,
1, &attnum, &newdt, &newdtnull);
/* Clean up */
pfree(relname);
return PointerGetDatum(rettuple);
}