aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Momjian <bruce@momjian.us>2003-07-27 04:51:45 +0000
committerBruce Momjian <bruce@momjian.us>2003-07-27 04:51:45 +0000
commit524cfad23f31db70a23fc1fe748c050838d5fad0 (patch)
tree58833d4c5bad0d04c34f64b84c33126efc76dfd0
parent38fb906f93b40edc9ef367b5619f431851fc3867 (diff)
downloadpostgresql-524cfad23f31db70a23fc1fe748c050838d5fad0.tar.gz
postgresql-524cfad23f31db70a23fc1fe748c050838d5fad0.zip
Here is 4 file in tgz:
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
-rw-r--r--contrib/oid2name/oid2name.c4
-rw-r--r--contrib/spi/README.timetravel116
-rw-r--r--contrib/spi/timetravel.c497
-rw-r--r--contrib/spi/timetravel.example17
-rw-r--r--contrib/spi/timetravel.sql.in7
5 files changed, 432 insertions, 209 deletions
diff --git a/contrib/oid2name/oid2name.c b/contrib/oid2name/oid2name.c
index a5c18d761f8..9e8320eba18 100644
--- a/contrib/oid2name/oid2name.c
+++ b/contrib/oid2name/oid2name.c
@@ -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
index 00000000000..0b4727fb248
--- /dev/null
+++ b/contrib/spi/README.timetravel
@@ -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).
diff --git a/contrib/spi/timetravel.c b/contrib/spi/timetravel.c
index 462ce37fed3..b7789a2e20d 100644
--- a/contrib/spi/timetravel.c
+++ b/contrib/spi/timetravel.c
@@ -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;
diff --git a/contrib/spi/timetravel.example b/contrib/spi/timetravel.example
index 4244bed19f5..1769e48154a 100644
--- a/contrib/spi/timetravel.example
+++ b/contrib/spi/timetravel.example
@@ -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;
diff --git a/contrib/spi/timetravel.sql.in b/contrib/spi/timetravel.sql.in
index f20e9fedf64..675e2d9feb2 100644
--- a/contrib/spi/timetravel.sql.in
+++ b/contrib/spi/timetravel.sql.in
@@ -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);