diff options
author | Bruce Momjian <bruce@momjian.us> | 2003-07-27 04:51:45 +0000 |
---|---|---|
committer | Bruce Momjian <bruce@momjian.us> | 2003-07-27 04:51:45 +0000 |
commit | 524cfad23f31db70a23fc1fe748c050838d5fad0 (patch) | |
tree | 58833d4c5bad0d04c34f64b84c33126efc76dfd0 | |
parent | 38fb906f93b40edc9ef367b5619f431851fc3867 (diff) | |
download | postgresql-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.c | 4 | ||||
-rw-r--r-- | contrib/spi/README.timetravel | 116 | ||||
-rw-r--r-- | contrib/spi/timetravel.c | 497 | ||||
-rw-r--r-- | contrib/spi/timetravel.example | 17 | ||||
-rw-r--r-- | contrib/spi/timetravel.sql.in | 7 |
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); |