Header And Logo

PostgreSQL
| The world's most advanced open source database.

timetravel.c

Go to the documentation of this file.
00001 /*
00002  * contrib/spi/timetravel.c
00003  *
00004  *
00005  * timetravel.c --  function to get time travel feature
00006  *      using general triggers.
00007  *
00008  * Modified by BÖJTHE Zoltán, Hungary, mailto:[email protected]
00009  */
00010 #include "postgres.h"
00011 
00012 #include <ctype.h>
00013 
00014 #include "catalog/pg_type.h"
00015 #include "commands/trigger.h"
00016 #include "executor/spi.h"
00017 #include "miscadmin.h"
00018 #include "utils/builtins.h"
00019 #include "utils/nabstime.h"
00020 #include "utils/rel.h"
00021 
00022 PG_MODULE_MAGIC;
00023 
00024 /* AbsoluteTime currabstime(void); */
00025 Datum       timetravel(PG_FUNCTION_ARGS);
00026 Datum       set_timetravel(PG_FUNCTION_ARGS);
00027 Datum       get_timetravel(PG_FUNCTION_ARGS);
00028 
00029 typedef struct
00030 {
00031     char       *ident;
00032     SPIPlanPtr  splan;
00033 } EPlan;
00034 
00035 static EPlan *Plans = NULL;     /* for UPDATE/DELETE */
00036 static int  nPlans = 0;
00037 
00038 typedef struct _TTOffList
00039 {
00040     struct _TTOffList *next;
00041     char        name[1];
00042 } TTOffList;
00043 
00044 static TTOffList TTOff = {NULL, {0}};
00045 
00046 static int  findTTStatus(char *name);
00047 static EPlan *find_plan(char *ident, EPlan **eplan, int *nplans);
00048 
00049 /*
00050  * timetravel () --
00051  *      1.  IF an update affects tuple with stop_date eq INFINITY
00052  *          then form (and return) new tuple with start_date eq current date
00053  *          and stop_date eq INFINITY [ and update_user eq current user ]
00054  *          and all other column values as in new tuple, and insert tuple
00055  *          with old data and stop_date eq current date
00056  *          ELSE - skip updation of tuple.
00057  *      2.  IF an delete affects tuple with stop_date eq INFINITY
00058  *          then insert the same tuple with stop_date eq current date
00059  *          [ and delete_user eq current user ]
00060  *          ELSE - skip deletion of tuple.
00061  *      3.  On INSERT, if start_date is NULL then current date will be
00062  *          inserted, if stop_date is NULL then INFINITY will be inserted.
00063  *          [ and insert_user eq current user, update_user and delete_user
00064  *          eq NULL ]
00065  *
00066  * In CREATE TRIGGER you are to specify start_date and stop_date column
00067  * names:
00068  * EXECUTE PROCEDURE
00069  * timetravel ('date_on', 'date_off' [,'insert_user', 'update_user', 'delete_user' ] ).
00070  */
00071 
00072 #define MaxAttrNum  5
00073 #define MinAttrNum  2
00074 
00075 #define a_time_on   0
00076 #define a_time_off  1
00077 #define a_ins_user  2
00078 #define a_upd_user  3
00079 #define a_del_user  4
00080 
00081 PG_FUNCTION_INFO_V1(timetravel);
00082 
00083 Datum                           /* have to return HeapTuple to Executor */
00084 timetravel(PG_FUNCTION_ARGS)
00085 {
00086     TriggerData *trigdata = (TriggerData *) fcinfo->context;
00087     Trigger    *trigger;        /* to get trigger name */
00088     int         argc;
00089     char      **args;           /* arguments */
00090     int         attnum[MaxAttrNum];     /* fnumbers of start/stop columns */
00091     Datum       oldtimeon,
00092                 oldtimeoff;
00093     Datum       newtimeon,
00094                 newtimeoff,
00095                 newuser,
00096                 nulltext;
00097     Datum      *cvals;          /* column values */
00098     char       *cnulls;         /* column nulls */
00099     char       *relname;        /* triggered relation name */
00100     Relation    rel;            /* triggered relation */
00101     HeapTuple   trigtuple;
00102     HeapTuple   newtuple = NULL;
00103     HeapTuple   rettuple;
00104     TupleDesc   tupdesc;        /* tuple description */
00105     int         natts;          /* # of attributes */
00106     EPlan      *plan;           /* prepared plan */
00107     char        ident[2 * NAMEDATALEN];
00108     bool        isnull;         /* to know is some column NULL or not */
00109     bool        isinsert = false;
00110     int         ret;
00111     int         i;
00112 
00113     /*
00114      * Some checks first...
00115      */
00116 
00117     /* Called by trigger manager ? */
00118     if (!CALLED_AS_TRIGGER(fcinfo))
00119         elog(ERROR, "timetravel: not fired by trigger manager");
00120 
00121     /* Should be called for ROW trigger */
00122     if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
00123         elog(ERROR, "timetravel: must be fired for row");
00124 
00125     /* Should be called BEFORE */
00126     if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
00127         elog(ERROR, "timetravel: must be fired before event");
00128 
00129     /* INSERT ? */
00130     if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
00131         isinsert = true;
00132 
00133     if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
00134         newtuple = trigdata->tg_newtuple;
00135 
00136     trigtuple = trigdata->tg_trigtuple;
00137 
00138     rel = trigdata->tg_relation;
00139     relname = SPI_getrelname(rel);
00140 
00141     /* check if TT is OFF for this relation */
00142     if (0 == findTTStatus(relname))
00143     {
00144         /* OFF - nothing to do */
00145         pfree(relname);
00146         return PointerGetDatum((newtuple != NULL) ? newtuple : trigtuple);
00147     }
00148 
00149     trigger = trigdata->tg_trigger;
00150 
00151     argc = trigger->tgnargs;
00152     if (argc != MinAttrNum && argc != MaxAttrNum)
00153         elog(ERROR, "timetravel (%s): invalid (!= %d or %d) number of arguments %d",
00154              relname, MinAttrNum, MaxAttrNum, trigger->tgnargs);
00155 
00156     args = trigger->tgargs;
00157     tupdesc = rel->rd_att;
00158     natts = tupdesc->natts;
00159 
00160     for (i = 0; i < MinAttrNum; i++)
00161     {
00162         attnum[i] = SPI_fnumber(tupdesc, args[i]);
00163         if (attnum[i] < 0)
00164             elog(ERROR, "timetravel (%s): there is no attribute %s", relname, args[i]);
00165         if (SPI_gettypeid(tupdesc, attnum[i]) != ABSTIMEOID)
00166             elog(ERROR, "timetravel (%s): attribute %s must be of abstime type",
00167                  relname, args[i]);
00168     }
00169     for (; i < argc; i++)
00170     {
00171         attnum[i] = SPI_fnumber(tupdesc, args[i]);
00172         if (attnum[i] < 0)
00173             elog(ERROR, "timetravel (%s): there is no attribute %s", relname, args[i]);
00174         if (SPI_gettypeid(tupdesc, attnum[i]) != TEXTOID)
00175             elog(ERROR, "timetravel (%s): attribute %s must be of text type",
00176                  relname, args[i]);
00177     }
00178 
00179     /* create fields containing name */
00180     newuser = CStringGetTextDatum(GetUserNameFromId(GetUserId()));
00181 
00182     nulltext = (Datum) NULL;
00183 
00184     if (isinsert)
00185     {                           /* INSERT */
00186         int         chnattrs = 0;
00187         int         chattrs[MaxAttrNum];
00188         Datum       newvals[MaxAttrNum];
00189         char        newnulls[MaxAttrNum];
00190 
00191         oldtimeon = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_on], &isnull);
00192         if (isnull)
00193         {
00194             newvals[chnattrs] = GetCurrentAbsoluteTime();
00195             newnulls[chnattrs] = ' ';
00196             chattrs[chnattrs] = attnum[a_time_on];
00197             chnattrs++;
00198         }
00199 
00200         oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull);
00201         if (isnull)
00202         {
00203             if ((chnattrs == 0 && DatumGetInt32(oldtimeon) >= NOEND_ABSTIME) ||
00204                 (chnattrs > 0 && DatumGetInt32(newvals[a_time_on]) >= NOEND_ABSTIME))
00205                 elog(ERROR, "timetravel (%s): %s is infinity", relname, args[a_time_on]);
00206             newvals[chnattrs] = NOEND_ABSTIME;
00207             newnulls[chnattrs] = ' ';
00208             chattrs[chnattrs] = attnum[a_time_off];
00209             chnattrs++;
00210         }
00211         else
00212         {
00213             if ((chnattrs == 0 && DatumGetInt32(oldtimeon) > DatumGetInt32(oldtimeoff)) ||
00214                 (chnattrs > 0 && DatumGetInt32(newvals[a_time_on]) > DatumGetInt32(oldtimeoff)))
00215                 elog(ERROR, "timetravel (%s): %s gt %s", relname, args[a_time_on], args[a_time_off]);
00216         }
00217 
00218         pfree(relname);
00219         if (chnattrs <= 0)
00220             return PointerGetDatum(trigtuple);
00221 
00222         if (argc == MaxAttrNum)
00223         {
00224             /* clear update_user value */
00225             newvals[chnattrs] = nulltext;
00226             newnulls[chnattrs] = 'n';
00227             chattrs[chnattrs] = attnum[a_upd_user];
00228             chnattrs++;
00229             /* clear delete_user value */
00230             newvals[chnattrs] = nulltext;
00231             newnulls[chnattrs] = 'n';
00232             chattrs[chnattrs] = attnum[a_del_user];
00233             chnattrs++;
00234             /* set insert_user value */
00235             newvals[chnattrs] = newuser;
00236             newnulls[chnattrs] = ' ';
00237             chattrs[chnattrs] = attnum[a_ins_user];
00238             chnattrs++;
00239         }
00240         rettuple = SPI_modifytuple(rel, trigtuple, chnattrs, chattrs, newvals, newnulls);
00241         return PointerGetDatum(rettuple);
00242         /* end of INSERT */
00243     }
00244 
00245     /* UPDATE/DELETE: */
00246     oldtimeon = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_on], &isnull);
00247     if (isnull)
00248         elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_on]);
00249 
00250     oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull);
00251     if (isnull)
00252         elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_off]);
00253 
00254     /*
00255      * If DELETE/UPDATE of tuple with stop_date neq INFINITY then say upper
00256      * Executor to skip operation for this tuple
00257      */
00258     if (newtuple != NULL)
00259     {                           /* UPDATE */
00260         newtimeon = SPI_getbinval(newtuple, tupdesc, attnum[a_time_on], &isnull);
00261         if (isnull)
00262             elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_on]);
00263 
00264         newtimeoff = SPI_getbinval(newtuple, tupdesc, attnum[a_time_off], &isnull);
00265         if (isnull)
00266             elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_off]);
00267 
00268         if (oldtimeon != newtimeon || oldtimeoff != newtimeoff)
00269             elog(ERROR, "timetravel (%s): you cannot change %s and/or %s columns (use set_timetravel)",
00270                  relname, args[a_time_on], args[a_time_off]);
00271     }
00272     if (oldtimeoff != NOEND_ABSTIME)
00273     {                           /* current record is a deleted/updated record */
00274         pfree(relname);
00275         return PointerGetDatum(NULL);
00276     }
00277 
00278     newtimeoff = GetCurrentAbsoluteTime();
00279 
00280     /* Connect to SPI manager */
00281     if ((ret = SPI_connect()) < 0)
00282         elog(ERROR, "timetravel (%s): SPI_connect returned %d", relname, ret);
00283 
00284     /* Fetch tuple values and nulls */
00285     cvals = (Datum *) palloc(natts * sizeof(Datum));
00286     cnulls = (char *) palloc(natts * sizeof(char));
00287     for (i = 0; i < natts; i++)
00288     {
00289         cvals[i] = SPI_getbinval(trigtuple, tupdesc, i + 1, &isnull);
00290         cnulls[i] = (isnull) ? 'n' : ' ';
00291     }
00292 
00293     /* change date column(s) */
00294     cvals[attnum[a_time_off] - 1] = newtimeoff; /* stop_date eq current date */
00295     cnulls[attnum[a_time_off] - 1] = ' ';
00296 
00297     if (!newtuple)
00298     {                           /* DELETE */
00299         if (argc == MaxAttrNum)
00300         {
00301             cvals[attnum[a_del_user] - 1] = newuser;    /* set delete user */
00302             cnulls[attnum[a_del_user] - 1] = ' ';
00303         }
00304     }
00305 
00306     /*
00307      * Construct ident string as TriggerName $ TriggeredRelationId and try to
00308      * find prepared execution plan.
00309      */
00310     snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
00311     plan = find_plan(ident, &Plans, &nPlans);
00312 
00313     /* if there is no plan ... */
00314     if (plan->splan == NULL)
00315     {
00316         SPIPlanPtr  pplan;
00317         Oid        *ctypes;
00318         char        sql[8192];
00319         char        separ = ' ';
00320 
00321         /* allocate ctypes for preparation */
00322         ctypes = (Oid *) palloc(natts * sizeof(Oid));
00323 
00324         /*
00325          * Construct query: INSERT INTO _relation_ VALUES ($1, ...)
00326          */
00327         snprintf(sql, sizeof(sql), "INSERT INTO %s VALUES (", relname);
00328         for (i = 1; i <= natts; i++)
00329         {
00330             ctypes[i - 1] = SPI_gettypeid(tupdesc, i);
00331             if (!(tupdesc->attrs[i - 1]->attisdropped)) /* skip dropped columns */
00332             {
00333                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%c$%d", separ, i);
00334                 separ = ',';
00335             }
00336         }
00337         snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ")");
00338 
00339         elog(DEBUG4, "timetravel (%s) update: sql: %s", relname, sql);
00340 
00341         /* Prepare plan for query */
00342         pplan = SPI_prepare(sql, natts, ctypes);
00343         if (pplan == NULL)
00344             elog(ERROR, "timetravel (%s): SPI_prepare returned %d", relname, SPI_result);
00345 
00346         /*
00347          * Remember that SPI_prepare places plan in current memory context -
00348          * so, we have to save plan in Top memory context for later use.
00349          */
00350         if (SPI_keepplan(pplan))
00351             elog(ERROR, "timetravel (%s): SPI_keepplan failed", relname);
00352 
00353         plan->splan = pplan;
00354     }
00355 
00356     /*
00357      * Ok, execute prepared plan.
00358      */
00359     ret = SPI_execp(plan->splan, cvals, cnulls, 0);
00360 
00361     if (ret < 0)
00362         elog(ERROR, "timetravel (%s): SPI_execp returned %d", relname, ret);
00363 
00364     /* Tuple to return to upper Executor ... */
00365     if (newtuple)
00366     {                           /* UPDATE */
00367         int         chnattrs = 0;
00368         int         chattrs[MaxAttrNum];
00369         Datum       newvals[MaxAttrNum];
00370         char        newnulls[MaxAttrNum];
00371 
00372         newvals[chnattrs] = newtimeoff;
00373         newnulls[chnattrs] = ' ';
00374         chattrs[chnattrs] = attnum[a_time_on];
00375         chnattrs++;
00376 
00377         newvals[chnattrs] = NOEND_ABSTIME;
00378         newnulls[chnattrs] = ' ';
00379         chattrs[chnattrs] = attnum[a_time_off];
00380         chnattrs++;
00381 
00382         if (argc == MaxAttrNum)
00383         {
00384             /* set update_user value */
00385             newvals[chnattrs] = newuser;
00386             newnulls[chnattrs] = ' ';
00387             chattrs[chnattrs] = attnum[a_upd_user];
00388             chnattrs++;
00389             /* clear delete_user value */
00390             newvals[chnattrs] = nulltext;
00391             newnulls[chnattrs] = 'n';
00392             chattrs[chnattrs] = attnum[a_del_user];
00393             chnattrs++;
00394             /* set insert_user value */
00395             newvals[chnattrs] = nulltext;
00396             newnulls[chnattrs] = 'n';
00397             chattrs[chnattrs] = attnum[a_ins_user];
00398             chnattrs++;
00399         }
00400 
00401         rettuple = SPI_modifytuple(rel, newtuple, chnattrs, chattrs, newvals, newnulls);
00402 
00403         /*
00404          * SPI_copytuple allocates tmptuple in upper executor context - have
00405          * to free allocation using SPI_pfree
00406          */
00407         /* SPI_pfree(tmptuple); */
00408     }
00409     else
00410         /* DELETE case */
00411         rettuple = trigtuple;
00412 
00413     SPI_finish();               /* don't forget say Bye to SPI mgr */
00414 
00415     pfree(relname);
00416     return PointerGetDatum(rettuple);
00417 }
00418 
00419 /*
00420  * set_timetravel (relname, on) --
00421  *                  turn timetravel for specified relation ON/OFF
00422  */
00423 PG_FUNCTION_INFO_V1(set_timetravel);
00424 
00425 Datum
00426 set_timetravel(PG_FUNCTION_ARGS)
00427 {
00428     Name        relname = PG_GETARG_NAME(0);
00429     int32       on = PG_GETARG_INT32(1);
00430     char       *rname;
00431     char       *d;
00432     char       *s;
00433     int32       ret;
00434     TTOffList  *p,
00435                *pp;
00436 
00437     for (pp = (p = &TTOff)->next; pp; pp = (p = pp)->next)
00438     {
00439         if (namestrcmp(relname, pp->name) == 0)
00440             break;
00441     }
00442     if (pp)
00443     {
00444         /* OFF currently */
00445         if (on != 0)
00446         {
00447             /* turn ON */
00448             p->next = pp->next;
00449             free(pp);
00450         }
00451         ret = 0;
00452     }
00453     else
00454     {
00455         /* ON currently */
00456         if (on == 0)
00457         {
00458             /* turn OFF */
00459             s = rname = DatumGetCString(DirectFunctionCall1(nameout, NameGetDatum(relname)));
00460             if (s)
00461             {
00462                 pp = malloc(sizeof(TTOffList) + strlen(rname));
00463                 if (pp)
00464                 {
00465                     pp->next = NULL;
00466                     p->next = pp;
00467                     d = pp->name;
00468                     while (*s)
00469                         *d++ = tolower((unsigned char) *s++);
00470                     *d = '\0';
00471                 }
00472                 pfree(rname);
00473             }
00474         }
00475         ret = 1;
00476     }
00477     PG_RETURN_INT32(ret);
00478 }
00479 
00480 /*
00481  * get_timetravel (relname) --
00482  *  get timetravel status for specified relation (ON/OFF)
00483  */
00484 PG_FUNCTION_INFO_V1(get_timetravel);
00485 
00486 Datum
00487 get_timetravel(PG_FUNCTION_ARGS)
00488 {
00489     Name        relname = PG_GETARG_NAME(0);
00490     TTOffList  *pp;
00491 
00492     for (pp = TTOff.next; pp; pp = pp->next)
00493     {
00494         if (namestrcmp(relname, pp->name) == 0)
00495             PG_RETURN_INT32(0);
00496     }
00497     PG_RETURN_INT32(1);
00498 }
00499 
00500 static int
00501 findTTStatus(char *name)
00502 {
00503     TTOffList  *pp;
00504 
00505     for (pp = TTOff.next; pp; pp = pp->next)
00506         if (pg_strcasecmp(name, pp->name) == 0)
00507             return 0;
00508     return 1;
00509 }
00510 
00511 /*
00512 AbsoluteTime
00513 currabstime()
00514 {
00515     return (GetCurrentAbsoluteTime());
00516 }
00517 */
00518 
00519 static EPlan *
00520 find_plan(char *ident, EPlan **eplan, int *nplans)
00521 {
00522     EPlan      *newp;
00523     int         i;
00524 
00525     if (*nplans > 0)
00526     {
00527         for (i = 0; i < *nplans; i++)
00528         {
00529             if (strcmp((*eplan)[i].ident, ident) == 0)
00530                 break;
00531         }
00532         if (i != *nplans)
00533             return (*eplan + i);
00534         *eplan = (EPlan *) realloc(*eplan, (i + 1) * sizeof(EPlan));
00535         newp = *eplan + i;
00536     }
00537     else
00538     {
00539         newp = *eplan = (EPlan *) malloc(sizeof(EPlan));
00540         (*nplans) = i = 0;
00541     }
00542 
00543     newp->ident = (char *) malloc(strlen(ident) + 1);
00544     strcpy(newp->ident, ident);
00545     newp->splan = NULL;
00546     (*nplans)++;
00547 
00548     return (newp);
00549 }