timetravel.c
上传用户:blenddy
上传日期:2007-01-07
资源大小:6495k
文件大小:10k
- /*
- * timetravel.c -- function to get time travel feature
- * using general triggers.
- */
- #include "executor/spi.h" /* this is what you need to work with SPI */
- #include "commands/trigger.h" /* -"- and triggers */
- #include <ctype.h> /* tolower () */
- #define ABSTIMEOID 702 /* it should be in pg_type.h */
- AbsoluteTime currabstime(void);
- HeapTuple timetravel(void);
- int32 set_timetravel(Name relname, int32 on);
- typedef struct
- {
- char *ident;
- void *splan;
- } EPlan;
- static EPlan *Plans = NULL; /* for UPDATE/DELETE */
- static int nPlans = 0;
- static char **TTOff = NULL;
- static int nTTOff = 0;
- static EPlan *find_plan(char *ident, EPlan ** eplan, int *nplans);
- /*
- * timetravel () --
- * 1. IF an update affects tuple with stop_date eq INFINITY
- * then form (and return) new tuple with stop_date eq current date
- * and all other column values as in old tuple, and insert tuple
- * with new data and start_date eq current date and
- * stop_date eq INFINITY
- * ELSE - skip updation of tuple.
- * 2. IF an delete affects tuple with stop_date eq INFINITY
- * then insert the same tuple with stop_date eq current date
- * ELSE - skip deletion of tuple.
- * 3. On INSERT, if start_date is NULL then current date will be
- * inserted, if stop_date is NULL then INFINITY will be inserted.
- *
- * In CREATE TRIGGER you are to specify start_date and stop_date column
- * names:
- * EXECUTE PROCEDURE
- * timetravel ('date_on', 'date_off').
- */
- HeapTuple /* have to return HeapTuple to Executor */
- timetravel()
- {
- Trigger *trigger; /* to get trigger name */
- char **args; /* arguments */
- int attnum[2]; /* fnumbers of start/stop columns */
- Datum oldon,
- oldoff;
- Datum newon,
- newoff;
- 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 (!CurrentTriggerData)
- elog(ERROR, "timetravel: triggers are not initialized");
- /* Should be called for ROW trigger */
- if (TRIGGER_FIRED_FOR_STATEMENT(CurrentTriggerData->tg_event))
- elog(ERROR, "timetravel: can't process STATEMENT events");
- /* Should be called BEFORE */
- if (TRIGGER_FIRED_AFTER(CurrentTriggerData->tg_event))
- elog(ERROR, "timetravel: must be fired before event");
- /* INSERT ? */
- if (TRIGGER_FIRED_BY_INSERT(CurrentTriggerData->tg_event))
- isinsert = true;
- if (TRIGGER_FIRED_BY_UPDATE(CurrentTriggerData->tg_event))
- newtuple = CurrentTriggerData->tg_newtuple;
- trigtuple = CurrentTriggerData->tg_trigtuple;
- rel = CurrentTriggerData->tg_relation;
- relname = SPI_getrelname(rel);
- /* check if TT is OFF for this relation */
- for (i = 0; i < nTTOff; i++)
- if (strcasecmp(TTOff[i], relname) == 0)
- break;
- if (i < nTTOff) /* OFF - nothing to do */
- {
- pfree(relname);
- return ((newtuple != NULL) ? newtuple : trigtuple);
- }
- trigger = CurrentTriggerData->tg_trigger;
- if (trigger->tgnargs != 2)
- elog(ERROR, "timetravel (%s): invalid (!= 2) number of arguments %d",
- relname, trigger->tgnargs);
- args = trigger->tgargs;
- tupdesc = rel->rd_att;
- natts = tupdesc->natts;
- /*
- * Setting CurrentTriggerData to NULL prevents direct calls to trigger
- * functions in queries. Normally, trigger functions have to be called
- * by trigger manager code only.
- */
- CurrentTriggerData = NULL;
- for (i = 0; i < 2; 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): attributes %s and %s must be of abstime type",
- relname, args[0], args[1]);
- }
- if (isinsert) /* INSERT */
- {
- int chnattrs = 0;
- int chattrs[2];
- Datum newvals[2];
- oldon = SPI_getbinval(trigtuple, tupdesc, attnum[0], &isnull);
- if (isnull)
- {
- newvals[chnattrs] = GetCurrentAbsoluteTime();
- chattrs[chnattrs] = attnum[0];
- chnattrs++;
- }
- oldoff = SPI_getbinval(trigtuple, tupdesc, attnum[1], &isnull);
- if (isnull)
- {
- if ((chnattrs == 0 && DatumGetInt32(oldon) >= NOEND_ABSTIME) ||
- (chnattrs > 0 && DatumGetInt32(newvals[0]) >= NOEND_ABSTIME))
- elog(ERROR, "timetravel (%s): %s ge %s",
- relname, args[0], args[1]);
- newvals[chnattrs] = NOEND_ABSTIME;
- chattrs[chnattrs] = attnum[1];
- chnattrs++;
- }
- else
- {
- if ((chnattrs == 0 && DatumGetInt32(oldon) >=
- DatumGetInt32(oldoff)) ||
- (chnattrs > 0 && DatumGetInt32(newvals[0]) >=
- DatumGetInt32(oldoff)))
- elog(ERROR, "timetravel (%s): %s ge %s",
- relname, args[0], args[1]);
- }
- pfree(relname);
- if (chnattrs <= 0)
- return (trigtuple);
- rettuple = SPI_modifytuple(rel, trigtuple, chnattrs,
- chattrs, newvals, NULL);
- return (rettuple);
- }
- oldon = SPI_getbinval(trigtuple, tupdesc, attnum[0], &isnull);
- if (isnull)
- elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[0]);
- oldoff = SPI_getbinval(trigtuple, tupdesc, attnum[1], &isnull);
- if (isnull)
- elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[1]);
- /*
- * If DELETE/UPDATE of tuple with stop_date neq INFINITY then say
- * upper Executor to skip operation for this tuple
- */
- if (newtuple != NULL) /* UPDATE */
- {
- newon = SPI_getbinval(newtuple, tupdesc, attnum[0], &isnull);
- if (isnull)
- elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[0]);
- newoff = SPI_getbinval(newtuple, tupdesc, attnum[1], &isnull);
- if (isnull)
- elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[1]);
- if (oldon != newon || oldoff != newoff)
- elog(ERROR, "timetravel (%s): you can't change %s and/or %s columns (use set_timetravel)",
- relname, args[0], args[1]);
- if (newoff != NOEND_ABSTIME)
- {
- pfree(relname); /* allocated in upper executor context */
- return (NULL);
- }
- }
- else if (oldoff != NOEND_ABSTIME) /* DELETE */
- {
- pfree(relname);
- return (NULL);
- }
- newoff = 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((newtuple != NULL) ? newtuple : trigtuple,
- tupdesc, i + 1, &isnull);
- cnulls[i] = (isnull) ? 'n' : ' ';
- }
- /* change date column(s) */
- if (newtuple) /* UPDATE */
- {
- cvals[attnum[0] - 1] = newoff; /* start_date eq current date */
- cnulls[attnum[0] - 1] = ' ';
- cvals[attnum[1] - 1] = NOEND_ABSTIME; /* stop_date eq INFINITY */
- cnulls[attnum[1] - 1] = ' ';
- }
- else
- /* DELETE */
- {
- cvals[attnum[1] - 1] = newoff; /* stop_date eq current date */
- cnulls[attnum[1] - 1] = ' ';
- }
- /*
- * Construct ident string as TriggerName $ TriggeredRelationId and try
- * to find prepared execution plan.
- */
- sprintf(ident, "%s$%u", trigger->tgname, rel->rd_id);
- plan = find_plan(ident, &Plans, &nPlans);
- /* if there is no plan ... */
- if (plan->splan == NULL)
- {
- void *pplan;
- Oid *ctypes;
- char sql[8192];
- /* allocate ctypes for preparation */
- ctypes = (Oid *) palloc(natts * sizeof(Oid));
- /*
- * Construct query: INSERT INTO _relation_ VALUES ($1, ...)
- */
- sprintf(sql, "INSERT INTO %s VALUES (", relname);
- for (i = 1; i <= natts; i++)
- {
- sprintf(sql + strlen(sql), "$%d%s",
- i, (i < natts) ? ", " : ")");
- ctypes[i - 1] = SPI_gettypeid(tupdesc, i);
- }
- /* 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 latter
- * use.
- */
- pplan = SPI_saveplan(pplan);
- if (pplan == NULL)
- elog(ERROR, "timetravel (%s): SPI_saveplan returned %d", relname, SPI_result);
- 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 */
- {
- HeapTuple tmptuple;
- tmptuple = SPI_copytuple(trigtuple);
- rettuple = SPI_modifytuple(rel, tmptuple, 1, &(attnum[1]), &newoff, NULL);
- /*
- * SPI_copytuple allocates tmptuple in upper executor context -
- * have to free allocation using SPI_pfree
- */
- SPI_pfree(tmptuple);
- }
- else
- /* DELETE */
- rettuple = trigtuple;
- SPI_finish(); /* don't forget say Bye to SPI mgr */
- pfree(relname);
- return (rettuple);
- }
- /*
- * set_timetravel () --
- * turn timetravel for specified relation ON/OFF
- */
- int32
- set_timetravel(Name relname, int32 on)
- {
- char *rname;
- char *d;
- char *s;
- int i;
- for (i = 0; i < nTTOff; i++)
- if (namestrcmp(relname, TTOff[i]) == 0)
- break;
- if (i < nTTOff) /* OFF currently */
- {
- if (on == 0)
- return (0);
- /* turn ON */
- free(TTOff[i]);
- if (nTTOff == 1)
- free(TTOff);
- else
- {
- if (i < nTTOff - 1)
- memcpy(&(TTOff[i]), &(TTOff[i + 1]), (nTTOff - i) * sizeof(char *));
- TTOff = realloc(TTOff, (nTTOff - 1) * sizeof(char *));
- }
- nTTOff--;
- return (0);
- }
- /* ON currently */
- if (on != 0)
- return (1);
- /* turn OFF */
- if (nTTOff == 0)
- TTOff = malloc(sizeof(char *));
- else
- TTOff = realloc(TTOff, (nTTOff + 1) * sizeof(char *));
- s = rname = nameout(relname);
- d = TTOff[nTTOff] = malloc(strlen(rname) + 1);
- while (*s)
- *d++ = tolower(*s++);
- *d = 0;
- pfree(rname);
- nTTOff++;
- return (1);
- }
- AbsoluteTime
- currabstime()
- {
- return (GetCurrentAbsoluteTime());
- }
- static EPlan *
- find_plan(char *ident, EPlan ** eplan, int *nplans)
- {
- 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);
- }