#include "postgres.h"
#include <ctype.h>
#include "catalog/pg_type.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/nabstime.h"
#include "utils/rel.h"
Go to the source code of this file.
Data Structures | |
struct | EPlan |
struct | _TTOffList |
Defines | |
#define | MaxAttrNum 5 |
#define | MinAttrNum 2 |
#define | a_time_on 0 |
#define | a_time_off 1 |
#define | a_ins_user 2 |
#define | a_upd_user 3 |
#define | a_del_user 4 |
Typedefs | |
typedef struct _TTOffList | TTOffList |
Functions | |
Datum | timetravel (PG_FUNCTION_ARGS) |
Datum | set_timetravel (PG_FUNCTION_ARGS) |
Datum | get_timetravel (PG_FUNCTION_ARGS) |
static int | findTTStatus (char *name) |
static EPlan * | find_plan (char *ident, EPlan **eplan, int *nplans) |
PG_FUNCTION_INFO_V1 (timetravel) | |
PG_FUNCTION_INFO_V1 (set_timetravel) | |
PG_FUNCTION_INFO_V1 (get_timetravel) | |
Variables | |
PG_MODULE_MAGIC | |
static EPlan * | Plans = NULL |
static int | nPlans = 0 |
static TTOffList | TTOff = {NULL, {0}} |
#define a_del_user 4 |
Definition at line 79 of file timetravel.c.
#define a_ins_user 2 |
Definition at line 77 of file timetravel.c.
#define a_time_off 1 |
Definition at line 76 of file timetravel.c.
Referenced by timetravel().
#define a_time_on 0 |
Definition at line 75 of file timetravel.c.
Referenced by timetravel().
#define a_upd_user 3 |
Definition at line 78 of file timetravel.c.
#define MaxAttrNum 5 |
Definition at line 72 of file timetravel.c.
Referenced by timetravel().
#define MinAttrNum 2 |
Definition at line 73 of file timetravel.c.
Referenced by timetravel().
typedef struct _TTOffList TTOffList |
Definition at line 520 of file timetravel.c.
References i, EPlan::ident, malloc, realloc, and EPlan::splan.
Referenced by timetravel().
{ EPlan *newp; int i; if (*nplans > 0) { for (i = 0; i < *nplans; i++) { if (strcmp((*eplan)[i].ident, ident) == 0) break; } if (i != *nplans) return (*eplan + i); *eplan = (EPlan *) realloc(*eplan, (i + 1) * sizeof(EPlan)); newp = *eplan + i; } else { newp = *eplan = (EPlan *) malloc(sizeof(EPlan)); (*nplans) = i = 0; } newp->ident = (char *) malloc(strlen(ident) + 1); strcpy(newp->ident, ident); newp->splan = NULL; (*nplans)++; return (newp); }
static int findTTStatus | ( | char * | name | ) | [static] |
Definition at line 501 of file timetravel.c.
References _TTOffList::name, _TTOffList::next, and pg_strcasecmp().
Referenced by timetravel().
Datum get_timetravel | ( | PG_FUNCTION_ARGS | ) |
Definition at line 487 of file timetravel.c.
References _TTOffList::name, namestrcmp(), _TTOffList::next, PG_GETARG_NAME, and PG_RETURN_INT32.
{ Name relname = PG_GETARG_NAME(0); TTOffList *pp; for (pp = TTOff.next; pp; pp = pp->next) { if (namestrcmp(relname, pp->name) == 0) PG_RETURN_INT32(0); } PG_RETURN_INT32(1); }
PG_FUNCTION_INFO_V1 | ( | set_timetravel | ) |
PG_FUNCTION_INFO_V1 | ( | get_timetravel | ) |
PG_FUNCTION_INFO_V1 | ( | timetravel | ) |
Datum set_timetravel | ( | PG_FUNCTION_ARGS | ) |
Definition at line 426 of file timetravel.c.
References DatumGetCString, DirectFunctionCall1, free, malloc, _TTOffList::name, NameGetDatum, nameout(), namestrcmp(), _TTOffList::next, next(), pfree(), PG_GETARG_INT32, PG_GETARG_NAME, and PG_RETURN_INT32.
{ Name relname = PG_GETARG_NAME(0); int32 on = PG_GETARG_INT32(1); char *rname; char *d; char *s; int32 ret; TTOffList *p, *pp; for (pp = (p = &TTOff)->next; pp; pp = (p = pp)->next) { if (namestrcmp(relname, pp->name) == 0) break; } if (pp) { /* OFF currently */ if (on != 0) { /* turn ON */ p->next = pp->next; free(pp); } ret = 0; } else { /* ON currently */ if (on == 0) { /* turn OFF */ s = rname = DatumGetCString(DirectFunctionCall1(nameout, NameGetDatum(relname))); if (s) { pp = malloc(sizeof(TTOffList) + strlen(rname)); if (pp) { pp->next = NULL; p->next = pp; d = pp->name; while (*s) *d++ = tolower((unsigned char) *s++); *d = '\0'; } pfree(rname); } } ret = 1; } PG_RETURN_INT32(ret); }
Datum timetravel | ( | PG_FUNCTION_ARGS | ) |
Definition at line 84 of file timetravel.c.
References a_time_off, a_time_on, ABSTIMEOID, CALLED_AS_TRIGGER, CStringGetTextDatum, DatumGetInt32, DEBUG4, elog, ERROR, find_plan(), findTTStatus(), GetCurrentAbsoluteTime(), GetUserId(), GetUserNameFromId(), i, MaxAttrNum, MinAttrNum, NAMEDATALEN, NOEND_ABSTIME, nPlans, NULL, palloc(), pfree(), PointerGetDatum, snprintf(), SPI_connect(), SPI_execp(), SPI_finish(), SPI_fnumber(), SPI_getbinval(), SPI_getrelname(), SPI_gettypeid(), SPI_keepplan(), SPI_modifytuple(), SPI_prepare(), SPI_result, TEXTOID, TriggerData::tg_event, TriggerData::tg_newtuple, TriggerData::tg_relation, TriggerData::tg_trigger, TriggerData::tg_trigtuple, Trigger::tgnargs, TRIGGER_FIRED_BEFORE, TRIGGER_FIRED_BY_INSERT, TRIGGER_FIRED_BY_UPDATE, and TRIGGER_FIRED_FOR_ROW.
{ TriggerData *trigdata = (TriggerData *) fcinfo->context; Trigger *trigger; /* to get trigger name */ int argc; char **args; /* arguments */ int attnum[MaxAttrNum]; /* fnumbers of start/stop columns */ Datum oldtimeon, oldtimeoff; Datum newtimeon, newtimeoff, newuser, nulltext; Datum *cvals; /* column values */ char *cnulls; /* column nulls */ char *relname; /* triggered relation name */ Relation rel; /* triggered relation */ HeapTuple trigtuple; HeapTuple newtuple = NULL; HeapTuple rettuple; TupleDesc tupdesc; /* tuple description */ int natts; /* # of attributes */ EPlan *plan; /* prepared plan */ char ident[2 * NAMEDATALEN]; bool isnull; /* to know is some column NULL or not */ bool isinsert = false; int ret; int i; /* * Some checks first... */ /* Called by trigger manager ? */ if (!CALLED_AS_TRIGGER(fcinfo)) elog(ERROR, "timetravel: not fired by trigger manager"); /* Should be called for ROW trigger */ if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) elog(ERROR, "timetravel: must be fired for row"); /* Should be called BEFORE */ if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event)) elog(ERROR, "timetravel: must be fired before event"); /* INSERT ? */ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) isinsert = true; if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) newtuple = trigdata->tg_newtuple; trigtuple = trigdata->tg_trigtuple; rel = trigdata->tg_relation; relname = SPI_getrelname(rel); /* check if TT is OFF for this relation */ if (0 == findTTStatus(relname)) { /* OFF - nothing to do */ pfree(relname); return PointerGetDatum((newtuple != NULL) ? newtuple : trigtuple); } trigger = trigdata->tg_trigger; argc = trigger->tgnargs; if (argc != MinAttrNum && argc != MaxAttrNum) elog(ERROR, "timetravel (%s): invalid (!= %d or %d) number of arguments %d", relname, MinAttrNum, MaxAttrNum, trigger->tgnargs); args = trigger->tgargs; tupdesc = rel->rd_att; natts = tupdesc->natts; for (i = 0; i < MinAttrNum; i++) { attnum[i] = SPI_fnumber(tupdesc, args[i]); if (attnum[i] < 0) elog(ERROR, "timetravel (%s): there is no attribute %s", relname, args[i]); if (SPI_gettypeid(tupdesc, attnum[i]) != ABSTIMEOID) elog(ERROR, "timetravel (%s): attribute %s must be of abstime type", relname, args[i]); } for (; i < argc; i++) { attnum[i] = SPI_fnumber(tupdesc, args[i]); if (attnum[i] < 0) elog(ERROR, "timetravel (%s): there is no attribute %s", relname, args[i]); if (SPI_gettypeid(tupdesc, attnum[i]) != TEXTOID) elog(ERROR, "timetravel (%s): attribute %s must be of text type", relname, args[i]); } /* create fields containing name */ newuser = CStringGetTextDatum(GetUserNameFromId(GetUserId())); nulltext = (Datum) NULL; if (isinsert) { /* INSERT */ int chnattrs = 0; int chattrs[MaxAttrNum]; Datum newvals[MaxAttrNum]; char newnulls[MaxAttrNum]; oldtimeon = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_on], &isnull); if (isnull) { newvals[chnattrs] = GetCurrentAbsoluteTime(); newnulls[chnattrs] = ' '; chattrs[chnattrs] = attnum[a_time_on]; chnattrs++; } oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull); if (isnull) { if ((chnattrs == 0 && DatumGetInt32(oldtimeon) >= NOEND_ABSTIME) || (chnattrs > 0 && DatumGetInt32(newvals[a_time_on]) >= NOEND_ABSTIME)) elog(ERROR, "timetravel (%s): %s is infinity", relname, args[a_time_on]); newvals[chnattrs] = NOEND_ABSTIME; newnulls[chnattrs] = ' '; chattrs[chnattrs] = attnum[a_time_off]; chnattrs++; } else { if ((chnattrs == 0 && DatumGetInt32(oldtimeon) > DatumGetInt32(oldtimeoff)) || (chnattrs > 0 && DatumGetInt32(newvals[a_time_on]) > DatumGetInt32(oldtimeoff))) elog(ERROR, "timetravel (%s): %s gt %s", relname, args[a_time_on], args[a_time_off]); } pfree(relname); if (chnattrs <= 0) return PointerGetDatum(trigtuple); if (argc == MaxAttrNum) { /* clear update_user value */ newvals[chnattrs] = nulltext; newnulls[chnattrs] = 'n'; chattrs[chnattrs] = attnum[a_upd_user]; chnattrs++; /* clear delete_user value */ newvals[chnattrs] = nulltext; newnulls[chnattrs] = 'n'; chattrs[chnattrs] = attnum[a_del_user]; chnattrs++; /* set insert_user value */ newvals[chnattrs] = newuser; newnulls[chnattrs] = ' '; chattrs[chnattrs] = attnum[a_ins_user]; chnattrs++; } rettuple = SPI_modifytuple(rel, trigtuple, chnattrs, chattrs, newvals, newnulls); return PointerGetDatum(rettuple); /* end of INSERT */ } /* UPDATE/DELETE: */ oldtimeon = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_on], &isnull); if (isnull) elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_on]); oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull); if (isnull) elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_off]); /* * If DELETE/UPDATE of tuple with stop_date neq INFINITY then say upper * Executor to skip operation for this tuple */ if (newtuple != NULL) { /* UPDATE */ newtimeon = SPI_getbinval(newtuple, tupdesc, attnum[a_time_on], &isnull); if (isnull) elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_on]); newtimeoff = SPI_getbinval(newtuple, tupdesc, attnum[a_time_off], &isnull); if (isnull) elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_off]); if (oldtimeon != newtimeon || oldtimeoff != newtimeoff) elog(ERROR, "timetravel (%s): you cannot change %s and/or %s columns (use set_timetravel)", relname, args[a_time_on], args[a_time_off]); } if (oldtimeoff != NOEND_ABSTIME) { /* current record is a deleted/updated record */ pfree(relname); return PointerGetDatum(NULL); } newtimeoff = GetCurrentAbsoluteTime(); /* Connect to SPI manager */ if ((ret = SPI_connect()) < 0) elog(ERROR, "timetravel (%s): SPI_connect returned %d", relname, ret); /* Fetch tuple values and nulls */ cvals = (Datum *) palloc(natts * sizeof(Datum)); cnulls = (char *) palloc(natts * sizeof(char)); for (i = 0; i < natts; i++) { cvals[i] = SPI_getbinval(trigtuple, tupdesc, i + 1, &isnull); cnulls[i] = (isnull) ? 'n' : ' '; } /* change date column(s) */ cvals[attnum[a_time_off] - 1] = newtimeoff; /* stop_date eq current date */ cnulls[attnum[a_time_off] - 1] = ' '; if (!newtuple) { /* DELETE */ if (argc == MaxAttrNum) { cvals[attnum[a_del_user] - 1] = newuser; /* set delete user */ cnulls[attnum[a_del_user] - 1] = ' '; } } /* * Construct ident string as TriggerName $ TriggeredRelationId and try to * find prepared execution plan. */ snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id); plan = find_plan(ident, &Plans, &nPlans); /* if there is no plan ... */ if (plan->splan == NULL) { SPIPlanPtr pplan; Oid *ctypes; char sql[8192]; char separ = ' '; /* allocate ctypes for preparation */ ctypes = (Oid *) palloc(natts * sizeof(Oid)); /* * Construct query: INSERT INTO _relation_ VALUES ($1, ...) */ snprintf(sql, sizeof(sql), "INSERT INTO %s VALUES (", relname); for (i = 1; i <= natts; i++) { ctypes[i - 1] = SPI_gettypeid(tupdesc, i); if (!(tupdesc->attrs[i - 1]->attisdropped)) /* skip dropped columns */ { snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%c$%d", separ, i); separ = ','; } } snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ")"); elog(DEBUG4, "timetravel (%s) update: sql: %s", relname, sql); /* Prepare plan for query */ pplan = SPI_prepare(sql, natts, ctypes); if (pplan == NULL) elog(ERROR, "timetravel (%s): SPI_prepare returned %d", relname, SPI_result); /* * Remember that SPI_prepare places plan in current memory context - * so, we have to save plan in Top memory context for later use. */ if (SPI_keepplan(pplan)) elog(ERROR, "timetravel (%s): SPI_keepplan failed", relname); plan->splan = pplan; } /* * Ok, execute prepared plan. */ ret = SPI_execp(plan->splan, cvals, cnulls, 0); if (ret < 0) elog(ERROR, "timetravel (%s): SPI_execp returned %d", relname, ret); /* Tuple to return to upper Executor ... */ if (newtuple) { /* UPDATE */ int chnattrs = 0; int chattrs[MaxAttrNum]; Datum newvals[MaxAttrNum]; char newnulls[MaxAttrNum]; newvals[chnattrs] = newtimeoff; newnulls[chnattrs] = ' '; chattrs[chnattrs] = attnum[a_time_on]; chnattrs++; newvals[chnattrs] = NOEND_ABSTIME; newnulls[chnattrs] = ' '; chattrs[chnattrs] = attnum[a_time_off]; chnattrs++; if (argc == MaxAttrNum) { /* set update_user value */ newvals[chnattrs] = newuser; newnulls[chnattrs] = ' '; chattrs[chnattrs] = attnum[a_upd_user]; chnattrs++; /* clear delete_user value */ newvals[chnattrs] = nulltext; newnulls[chnattrs] = 'n'; chattrs[chnattrs] = attnum[a_del_user]; chnattrs++; /* set insert_user value */ newvals[chnattrs] = nulltext; newnulls[chnattrs] = 'n'; chattrs[chnattrs] = attnum[a_ins_user]; chnattrs++; } rettuple = SPI_modifytuple(rel, newtuple, chnattrs, chattrs, newvals, newnulls); /* * SPI_copytuple allocates tmptuple in upper executor context - have * to free allocation using SPI_pfree */ /* SPI_pfree(tmptuple); */ } else /* DELETE case */ rettuple = trigtuple; SPI_finish(); /* don't forget say Bye to SPI mgr */ pfree(relname); return PointerGetDatum(rettuple); }
int nPlans = 0 [static] |
Definition at line 36 of file timetravel.c.
Referenced by timetravel().
Definition at line 22 of file timetravel.c.
Definition at line 35 of file timetravel.c.
Definition at line 44 of file timetravel.c.