]> granicus.if.org Git - postgresql/commitdiff
Here is 4 file in tgz:
authorBruce Momjian <bruce@momjian.us>
Sun, 27 Jul 2003 04:51:45 +0000 (04:51 +0000)
committerBruce Momjian <bruce@momjian.us>
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:urdesobt@axelero.hu */
+
 #include "executor/spi.h"              /* this is what you need to work with SPI */
-#include "commands/trigger.h"  /* -"- and triggers */
-#include <ctype.h>
+#include "commands/trigger.h"          /* -"- and triggers */
+#include "miscadmin.h"                 /* for GetPgUserName() */
+#include <ctype.h>                     /* 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);