Here is 4 file in tgz:
authorBruce Momjian
Sun, 27 Jul 2003 04:51:45 +0000 (04:51 +0000)
committerBruce Momjian
Sun, 27 Jul 2003 04:51:45 +0000 (04:51 +0000)
the new timetravel.c,
new timetravel.README (cut from spi/README and modified),
modified timetravel.sql.in
and modified timetravel.example.

Features:
- optionally 3 parameter for insert/update/delete user name

- work with CREATE UNIQUE INDEX ixxx on table xxx
(unique_field,time_off);
    (the  original version was work with unique index on 6.5.0-6.5.3,
and not work on 7.3.2,7.3.3)
     (before 6.5.0 and between 6.5.3 and 7.3.2 I dont know)

- get_timetravel(tablename) function for check timetravel-status.

- timetravel trigger not change  oid of the active record. (it is not a
good feature, because the  old version is automatice prevent the paralel
update with "where oid=nnn")

B?jthe Zolt?n

contrib/oid2name/oid2name.c
contrib/spi/README.timetravel [new file with mode: 0644]
contrib/spi/timetravel.c
contrib/spi/timetravel.example
contrib/spi/timetravel.sql.in

index a5c18d761f8fc33c92b07ba3d412eed235fc9d9d..9e8320eba182eaf70d7875fea791a0215e22d2b0 100644 (file)
@@ -355,7 +355,9 @@ sql_exec_dumptable(PGconn *conn, int systables)
    if (systables == 1)
        snprintf(todo, 1024, "select relfilenode,relname from pg_class order by relname");
    else
-       snprintf(todo, 1024, "select relfilenode,relname from pg_class where relname not like 'pg_%%' order by relname");
+       snprintf(todo, 1024, "select relfilenode,relname from pg_class "
+                           "where reltype not in ('v','c') and "
+                           "relname not like 'pg_%%' order by relname");
 
    sql_exec(conn, todo, 0);
 }
diff --git a/contrib/spi/README.timetravel b/contrib/spi/README.timetravel
new file mode 100644 (file)
index 0000000..0b4727f
--- /dev/null
@@ -0,0 +1,116 @@
+2. timetravel.c - functions for implementing time travel feature.
+
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+I rewritten this, because:
+
+on original version of postgresql 7.3.2-7.3.3:
+
+the UPDATE not work on timetravel.example if I added
+>create unique index tttest_idx on tttest (price_id,price_off);
+>update tttest set price_val = 30 where price_id = 3;
+ERROR:  Cannot insert a duplicate key into unique index tttest_idx
+
+And UPDATE not work on table tttest after
+>alter table tttest add column q1 text;
+>alter table tttest add column q2 int;
+>alter table tttest drop column q1;
+>update tttest set price_val = 30 where price_id = 3;
+ERROR:  Parameter '$5' is out of range
+
+And I add a new optional feature: my new timetravel have +3 optional parameters:
+inserter_user, updater_user, deleter_user.
+
+And I add a new function: get_timetravel for get timetravel status
+without change it.
+
+A big difference: 
+the old version on UPDATE changed oid on active ('infinity') record,
+the new version UPDATE keep oid, and the overdued record have a new oid.
+I sign with '!!!' my comment in this file.
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+   Old internally supported time-travel (TT) used insert/delete
+transaction commit times. To get the same feature using triggers
+you are to add to a table two columns of abstime type to store
+date when a tuple was inserted (start_date) and changed/deleted 
+(stop_date):
+
+CREATE TABLE XXX (
+   ...     ...
+   date_on     abstime default currabstime(),
+   date_off    abstime default 'infinity'
+   ...     ...
+/* !!! and (if have) */
+   ins_user    text    /* user, who insert this record */
+   upd_user    text    /* user, who updated this record */
+   del_user    text    /* user, who deleted this record */
+   ...     ...
+);
+
+!!! on INSERT my new version:
+ ... and optionally set ins_user to current user, upd_user and del_user to null.
+
+- so, tuples being inserted with NULLs in date_on/date_off will get
+_current_date_ in date_on (name of start_date column in XXX) and INFINITY in
+date_off (name of stop_date column in XXX).
+
+   Tuples with stop_date equal INFINITY are "valid now": when trigger will
+be fired for UPDATE/DELETE of a tuple with stop_date NOT equal INFINITY then
+this tuple will not be changed/deleted!
+
+   If stop_date equal INFINITY then on
+
+UPDATE: 
+original version was:
+ only stop_date in tuple being updated will be changed to current
+ date and new tuple with new data (coming from SET ... in UPDATE) will be
+ inserted. Start_date in this new tuple will be setted to current date and
+ stop_date - to INFINITY.
+On my new version:
+ insert a new tuple with old values, but stop_date changed to current date;
+ and update original tuple with new data, and update start_date to current date
+ and optionally set upd_user to current user and clear ins_user,del_user.
+
+DELETE: new tuple will be inserted with stop_date setted to current date
+(and with the same data in other columns as in tuple being deleted).
+On my new version:
+ ... and optionally set del_user to current user.
+
+   NOTE:
+1. To get tuples "valid now" you are to add _stop_date_ = 'infinity'
+   to WHERE. Internally supported TT allowed to avoid this...
+   Fixed rewriting RULEs could help here...
+   As work arround you may use VIEWs...
+2. You can't change start/stop date columns with UPDATE! 
+   Use set_timetravel (below) if you need in this.
+
+   FUNCTIONs:
+
+timetravel() is general trigger function.
+
+   You are to create trigger BEFORE UPDATE OR DELETE using this
+function on a time-traveled table. You are to specify two arguments: name of
+start_date column and name of stop_date column in triggered table.
+Or add +3 arguments: 
+  name of insert_user column, name of update_user column, name of delete_user column
+
+currabstime() may be used in DEFAULT for start_date column to get
+current date.
+!!! I deleted this function, because I newer used this.
+
+set_timetravel() allows you turn time-travel ON/OFF for a table:
+
+   set_timetravel('XXX', 1) will turn TT ON for table XXX (and report
+old status).
+   set_timetravel('XXX', 0) will turn TT OFF for table XXX (-"-).
+
+Turning TT OFF allows you do with a table ALL what you want.
+
+get_timetravel() reports time-travel status ON(1)/OFF(0) for a table.
+get_timetravel() and set_timetravel() not checking existing of table and
+existing of timetravel trigger on specified table.
+
+   There is example in timetravel.example.
+
+   To CREATE FUNCTIONs use timetravel.sql (will be made by gmake from
+timetravel.source).
index 462ce37fed33f9b4c9ace5939196fb3eefe4e147..b7789a2e20d864d280e4b75c6a65d367a3e0c0f6 100644 (file)
@@ -3,13 +3,16 @@
  *     using general triggers.
  */
 
+/* Modified by BÖJTHE Zoltán, Hungary, mailto:[email protected] */
+
 #include "executor/spi.h"      /* this is what you need to work with SPI */
-#include "commands/trigger.h"  /* -"- and triggers */
-#include 
+#include "commands/trigger.h"      /* -"- and triggers */
+#include "miscadmin.h"         /* for GetPgUserName() */
+#include          /* tolower () */
 
-#define ABSTIMEOID 702         /* it should be in pg_type.h */
+#define ABSTIMEOID 702     /* it should be in pg_type.h */
 
-AbsoluteTime currabstime(void);
+/* AbsoluteTime currabstime(void); */
 Datum      timetravel(PG_FUNCTION_ARGS);
 Datum      set_timetravel(PG_FUNCTION_ARGS);
 
@@ -22,84 +25,102 @@ typedef struct
 static EPlan *Plans = NULL;        /* for UPDATE/DELETE */
 static int nPlans = 0;
 
-static char **TTOff = NULL;
-static int nTTOff = 0;
+typedef struct _TTOffList
+{
+    struct _TTOffList  *next;
+    char       name[1];
+} TTOffList;
+
+static TTOffList TTOff = {NULL,0};
 
+static int findTTStatus(char *name);
 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
+ *         then form (and return) new tuple with start_date eq current date
+ *         and stop_date eq INFINITY [ and update_user eq current user ]
+ *         and all other column values as in new tuple, and insert tuple
+ *         with old data and stop_date eq current date
  *         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
+ *         [ and delete_user eq current user ]
  *         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.
+ *         [ and insert_user eq current user, update_user and delete_user
+ *         eq NULL ]
  *
  * In CREATE TRIGGER you are to specify start_date and stop_date column
  * names:
  * EXECUTE PROCEDURE
- * timetravel ('date_on', 'date_off').
+ * timetravel ('date_on', 'date_off' [,'insert_user', 'update_user', 'delete_user' ] ).
  */
 
+#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
+
 PG_FUNCTION_INFO_V1(timetravel);
 
-Datum
+Datum                      /* have to return HeapTuple to Executor */
 timetravel(PG_FUNCTION_ARGS)
 {
-   TriggerData *trigdata = (TriggerData *) fcinfo->context;
-   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 */
+   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 */
+   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;
+   int     ret;
+   int     i;
 
    /*
     * Some checks first...
     */
 
    /* Called by trigger manager ? */
-   if (!CALLED_AS_TRIGGER(fcinfo))
-       /* internal error */
+   if(!CALLED_AS_TRIGGER(fcinfo))
        elog(ERROR, "timetravel: not fired by trigger manager");
 
    /* Should be called for ROW trigger */
-   if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
-       /* internal error */
+   if(TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
        elog(ERROR, "timetravel: can't process STATEMENT events");
 
    /* Should be called BEFORE */
-   if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
-       /* internal error */
+   if(TRIGGER_FIRED_AFTER(trigdata->tg_event))
        elog(ERROR, "timetravel: must be fired before event");
 
    /* INSERT ? */
-   if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+   if(TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
        isinsert = true;
 
-   if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+   if(TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
        newtuple = trigdata->tg_newtuple;
 
    trigtuple = trigdata->tg_trigtuple;
@@ -108,170 +129,168 @@ timetravel(PG_FUNCTION_ARGS)
    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 */
+   if(0==findTTStatus(relname))
    {
+       /* OFF - nothing to do */
        pfree(relname);
        return PointerGetDatum((newtuple != NULL) ? newtuple : trigtuple);
    }
 
    trigger = trigdata->tg_trigger;
 
-   if (trigger->tgnargs != 2)
-       /* internal error */
-       elog(ERROR, "timetravel (%s): invalid (!= 2) number of arguments %d",
-            relname, trigger->tgnargs);
+   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 < 2; i++)
+   for(i = 0 ; i < MinAttrNum ; i++)
    {
        attnum[i] = SPI_fnumber(tupdesc, args[i]);
-       if (attnum[i] < 0)
-           ereport(ERROR,
-                   (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
-                    errmsg("\"%s\" has no attribute \"%s\"",
-                           relname, args[i])));
-       if (SPI_gettypeid(tupdesc, attnum[i]) != ABSTIMEOID)
-           ereport(ERROR,
-                   (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
-                    errmsg("attribute \"%s\" of \"%s\" must be type ABSTIME",
-                            args[i], relname)));
+       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]);
    }
-
-   if (isinsert)               /* INSERT */
+   for( ; i < argc ; i++)
    {
-       int         chnattrs = 0;
-       int         chattrs[2];
-       Datum       newvals[2];
+       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 = DirectFunctionCall1(textin, CStringGetDatum(GetUserNameFromId(GetUserId())));
+
+   nulltext = (Datum)NULL;
 
-       oldon = SPI_getbinval(trigtuple, tupdesc, attnum[0], &isnull);
-       if (isnull)
+   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();
-           chattrs[chnattrs] = attnum[0];
+           newnulls[chnattrs] = ' ';
+           chattrs[chnattrs] = attnum[a_time_on];
            chnattrs++;
        }
 
-       oldoff = SPI_getbinval(trigtuple, tupdesc, attnum[1], &isnull);
-       if (isnull)
+       oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull);
+       if(isnull)
        {
-           if ((chnattrs == 0 && DatumGetInt32(oldon) >= NOEND_ABSTIME) ||
-           (chnattrs > 0 && DatumGetInt32(newvals[0]) >= NOEND_ABSTIME))
-               ereport(ERROR,
-                       (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
-                        errmsg("timetravel (%s): %s ge %s",
-                        relname, args[0], args[1])));
+           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;
-           chattrs[chnattrs] = attnum[1];
+           newnulls[chnattrs] = ' ';
+           chattrs[chnattrs] = attnum[a_time_off];
            chnattrs++;
        }
        else
        {
-           if ((chnattrs == 0 && DatumGetInt32(oldon) >=
-                DatumGetInt32(oldoff)) ||
-               (chnattrs > 0 && DatumGetInt32(newvals[0]) >=
-                DatumGetInt32(oldoff)))
-               ereport(ERROR,
-                       (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
-                        errmsg("timetravel (%s): %s ge %s",
-                        relname, args[0], args[1])));
+           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)
+       if(chnattrs <= 0)
            return PointerGetDatum(trigtuple);
 
-       rettuple = SPI_modifytuple(rel, trigtuple, chnattrs,
-                                  chattrs, newvals, NULL);
+       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 */
    }
 
-   oldon = SPI_getbinval(trigtuple, tupdesc, attnum[0], &isnull);
-   if (isnull)
-       ereport(ERROR,
-               (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                errmsg("\"%s\" must be NOT NULL in \"%s\"",
-                       args[0],relname)));
-   oldoff = SPI_getbinval(trigtuple, tupdesc, attnum[1], &isnull);
-   if (isnull)
-       ereport(ERROR,
-               (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                errmsg("\"%s\" must be NOT NULL in \"%s\"",
-                       args[1],relname)));
+/* 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 */
-   {
-       newon = SPI_getbinval(newtuple, tupdesc, attnum[0], &isnull);
-       if (isnull)
-           ereport(ERROR,
-                   (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                    errmsg("\"%s\" must be NOT NULL in \"%s\"",
-                           args[0],relname)));
-       newoff = SPI_getbinval(newtuple, tupdesc, attnum[1], &isnull);
-       if (isnull)
-           ereport(ERROR,
-                   (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-                    errmsg("\"%s\" must be NOT NULL in \"%s\"",
-                           args[1],relname)));
-
-       if (oldon != newon || oldoff != newoff)
-           ereport(ERROR,
-                   (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
-                    errmsg("cannot change columns \"%s\" or \"%s\" in \"%s\"",
-                            args[0], args[1], relname),
-                    errhint("Use set_timetravel() instead.")));
-
-       if (newoff != NOEND_ABSTIME)
-       {
-           pfree(relname);     /* allocated in upper executor context */
-           return PointerGetDatum(NULL);
-       }
+   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 can't change %s and/or %s columns (use set_timetravel)",
+                relname, args[a_time_on], args[a_time_off]);
    }
-   else if (oldoff != NOEND_ABSTIME)   /* DELETE */
-   {
+   if(oldtimeoff != NOEND_ABSTIME)
+   { /* current record is a deleted/updated record */
        pfree(relname);
        return PointerGetDatum(NULL);
    }
 
-   newoff = GetCurrentAbsoluteTime();
+   newtimeoff = GetCurrentAbsoluteTime();
 
    /* Connect to SPI manager */
-   if ((ret = SPI_connect()) < 0)
-       /* internal error */
+   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++)
+   for(i = 0; i < natts; i++)
    {
-       cvals[i] = SPI_getbinval((newtuple != NULL) ? newtuple : trigtuple,
-                                tupdesc, i + 1, &isnull);
+       cvals[i] = SPI_getbinval(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] = ' ';
+   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] = ' ';
+       }
    }
 
    /*
@@ -282,11 +301,12 @@ timetravel(PG_FUNCTION_ARGS)
    plan = find_plan(ident, &Plans, &nPlans);
 
    /* if there is no plan ... */
-   if (plan->splan == NULL)
+   if(plan->splan == NULL)
    {
-       void       *pplan;
-       Oid        *ctypes;
-       char        sql[8192];
+       void    *pplan;
+       Oid *ctypes;
+       char    sql[8192];
+       int j;
 
        /* allocate ctypes for preparation */
        ctypes = (Oid *) palloc(natts * sizeof(Oid));
@@ -295,17 +315,21 @@ timetravel(PG_FUNCTION_ARGS)
         * Construct query: INSERT INTO _relation_ VALUES ($1, ...)
         */
        snprintf(sql, sizeof(sql), "INSERT INTO %s VALUES (", relname);
-       for (i = 1; i <= natts; i++)
+       for(i = 1; i <= natts; i++)
        {
-           snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "$%d%s",
-                    i, (i < natts) ? ", " : ")");
            ctypes[i - 1] = SPI_gettypeid(tupdesc, i);
+           if(!(tupdesc->attrs[i - 1]->attisdropped))  /* skip dropped columns */
+               snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "$%d%s",
+                   i, (i < natts) ? ", " : ")" );
+//             snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "$%d /* %d */ %s",
+//                 i, ctypes[i-1], (i < natts) ? ", " : ")" );
        }
 
+//     elog(NOTICE, "timetravel (%s) update: sql: %s", relname, sql);
+
        /* Prepare plan for query */
        pplan = SPI_prepare(sql, natts, ctypes);
-       if (pplan == NULL)
-           /* internal error */
+       if(pplan == NULL)
            elog(ERROR, "timetravel (%s): SPI_prepare returned %d", relname, SPI_result);
 
        /*
@@ -314,8 +338,7 @@ timetravel(PG_FUNCTION_ARGS)
         * use.
         */
        pplan = SPI_saveplan(pplan);
-       if (pplan == NULL)
-           /* internal error */
+       if(pplan == NULL)
            elog(ERROR, "timetravel (%s): SPI_saveplan returned %d", relname, SPI_result);
 
        plan->splan = pplan;
@@ -326,23 +349,54 @@ timetravel(PG_FUNCTION_ARGS)
     */
    ret = SPI_execp(plan->splan, cvals, cnulls, 0);
 
-   if (ret < 0)
-       /* internal error */
+   if(ret < 0)
        elog(ERROR, "timetravel (%s): SPI_execp returned %d", relname, ret);
 
    /* Tuple to return to upper Executor ... */
-   if (newtuple)               /* UPDATE */
-   {
+   if(newtuple)
+   { /* UPDATE */
        HeapTuple   tmptuple;
+       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++;
+       }
 
-       tmptuple = SPI_copytuple(trigtuple);
-       rettuple = SPI_modifytuple(rel, tmptuple, 1, &(attnum[1]), &newoff, NULL);
+       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);
+//     SPI_pfree(tmptuple);
    }
    else
 /* DELETE */
@@ -351,7 +405,6 @@ timetravel(PG_FUNCTION_ARGS)
    SPI_finish();               /* don't forget say Bye to SPI mgr */
 
    pfree(relname);
-
    return PointerGetDatum(rettuple);
 }
 
@@ -364,77 +417,109 @@ PG_FUNCTION_INFO_V1(set_timetravel);
 Datum
 set_timetravel(PG_FUNCTION_ARGS)
 {
-   Name        relname = PG_GETARG_NAME(0);
-   int32       on = PG_GETARG_INT32(1);
-   char       *rname;
-   char       *d;
-   char       *s;
-   int         i;
-
-   for (i = 0; i < nTTOff; i++)
-       if (namestrcmp(relname, TTOff[i]) == 0)
+        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 (i < nTTOff)             /* OFF currently */
+   }
+   if(pp)
    {
-       if (on == 0)
-           PG_RETURN_INT32(0);
-
-       /* turn ON */
-       free(TTOff[i]);
-       if (nTTOff == 1)
-           free(TTOff);
-       else
+       /* OFF currently */
+       if(on != 0)
        {
-           if (i < nTTOff - 1)
-               memcpy(&(TTOff[i]), &(TTOff[i + 1]), (nTTOff - i) * sizeof(char *));
-           TTOff = realloc(TTOff, (nTTOff - 1) * sizeof(char *));
+           /* turn ON */
+           p->next = pp->next;
+           free(pp);
        }
-       nTTOff--;
-       PG_RETURN_INT32(0);
+       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);
+}
 
-   /* ON currently */
-   if (on != 0)
-       PG_RETURN_INT32(1);
+/*
+ * get_timetravel (relname) --
+ *  get timetravel status for specified relation (ON/OFF)
+ */
+PG_FUNCTION_INFO_V1(get_timetravel);
 
-   /* turn OFF */
-   if (nTTOff == 0)
-       TTOff = malloc(sizeof(char *));
-   else
-       TTOff = realloc(TTOff, (nTTOff + 1) * sizeof(char *));
-   s = rname = DatumGetCString(DirectFunctionCall1(nameout,
-                                                NameGetDatum(relname)));
-   d = TTOff[nTTOff] = malloc(strlen(rname) + 1);
-   while (*s)
-       *d++ = tolower((unsigned char) *s++);
-   *d = 0;
-   pfree(rname);
-   nTTOff++;
+Datum
+get_timetravel(PG_FUNCTION_ARGS)
+{
+        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);
 }
 
+static int
+findTTStatus(char *name)
+{
+   TTOffList* pp;
+   for(pp = TTOff.next; pp; pp = pp->next)
+       if(strcasecmp(name, pp->name) == 0)
+           return 0;
+   return 1;
+}
+
+/*
 AbsoluteTime
 currabstime()
 {
    return (GetCurrentAbsoluteTime());
 }
+*/
 
 static EPlan *
 find_plan(char *ident, EPlan ** eplan, int *nplans)
 {
-   EPlan      *newp;
-   int         i;
+   EPlan   *newp;
+   int i;
 
-   if (*nplans > 0)
+   if(*nplans > 0)
    {
-       for (i = 0; i < *nplans; i++)
+       for(i = 0; i < *nplans; i++)
        {
-           if (strcmp((*eplan)[i].ident, ident) == 0)
+           if(strcmp((*eplan)[i].ident, ident) == 0)
                break;
        }
-       if (i != *nplans)
+       if(i != *nplans)
            return (*eplan + i);
        *eplan = (EPlan *) realloc(*eplan, (i + 1) * sizeof(EPlan));
        newp = *eplan + i;
index 4244bed19f5a8ad809e4ba1735db19ac63a6d1c8..1769e48154a81451aa4ba490239fc65bfc9fa58d 100644 (file)
@@ -7,6 +7,11 @@ create table tttest (
    price_off   abstime
 );
 
+create unique index tttest_idx on tttest (price_id,price_off);
+alter table tttest add column q1 text;
+alter table tttest add column q2 int;
+alter table tttest drop column q1;
+
 create trigger timetravel 
    before insert or delete or update on tttest
    for each row 
@@ -17,9 +22,9 @@ insert into tttest values (1, 1, null, null);
 insert into tttest(price_id, price_val) values (2, 2);
 insert into tttest(price_id, price_val,price_off) values (3, 3, 'infinity');
 
-insert into tttest(price_id, price_val,price_off) values (3, 3
+insert into tttest(price_id, price_val,price_off) values (4, 4
    abstime('now'::timestamp - '100 days'::interval));
-insert into tttest(price_id, price_val,price_on) values (3, 3, 'infinity');
+insert into tttest(price_id, price_val,price_on) values (3, 3, 'infinity'); -- duplicate key
 
 select * from tttest;
 delete from tttest where price_id = 2;
@@ -40,6 +45,9 @@ select * from tttest;
 
 -- restore data as before last update:
 select set_timetravel('tttest', 0);    -- turn TT OFF!
+
+select get_timetravel('tttest');   -- check status
+
 delete from tttest where price_id = 5;
 update tttest set price_off = 'infinity' where price_val = 30;
 select * from tttest;
@@ -51,6 +59,8 @@ select * from tttest;
 
 select set_timetravel('tttest', 1);    -- turn TT ON!
 
+select get_timetravel('tttest');   -- check status
+
 -- we want to correct some date
 update tttest set price_on = 'Jan-01-1990 00:00:01' where price_id = 5 and 
    price_off <> 'infinity';
@@ -58,6 +68,9 @@ update tttest set price_on = 'Jan-01-1990 00:00:01' where price_id = 5 and
 
 -- try in this way
 select set_timetravel('tttest', 0);    -- turn TT OFF!
+
+select get_timetravel('tttest');   -- check status
+
 update tttest set price_on = '01-Jan-1990 00:00:01' where price_id = 5 and 
    price_off <> 'infinity';
 select * from tttest;
index f20e9fedf645f9138b336654797b40b71569cee3..675e2d9feb2e0189df56ccb4741319b8bc170641 100644 (file)
@@ -1,6 +1,8 @@
 -- Adjust this setting to control where the objects get created.
 SET search_path = public;
 
+SET autocommit TO 'on';
+
 CREATE OR REPLACE FUNCTION timetravel() 
 RETURNS trigger 
 AS 'MODULE_PATHNAME'
@@ -10,3 +12,8 @@ CREATE OR REPLACE FUNCTION set_timetravel(name, int4)
 RETURNS int4 
 AS 'MODULE_PATHNAME'
 LANGUAGE 'C' WITH (isStrict);
+
+CREATE OR REPLACE FUNCTION get_timetravel(name) 
+RETURNS int4 
+AS 'MODULE_PATHNAME'
+LANGUAGE 'C' WITH (isStrict);