mirror of
https://github.com/postgres/postgres.git
synced 2025-05-13 01:13:08 -04:00
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
This commit is contained in:
parent
38fb906f93
commit
524cfad23f
@ -355,7 +355,9 @@ sql_exec_dumptable(PGconn *conn, int systables)
|
|||||||
if (systables == 1)
|
if (systables == 1)
|
||||||
snprintf(todo, 1024, "select relfilenode,relname from pg_class order by relname");
|
snprintf(todo, 1024, "select relfilenode,relname from pg_class order by relname");
|
||||||
else
|
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);
|
sql_exec(conn, todo, 0);
|
||||||
}
|
}
|
||||||
|
116
contrib/spi/README.timetravel
Normal file
116
contrib/spi/README.timetravel
Normal file
@ -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).
|
@ -3,13 +3,16 @@
|
|||||||
* using general triggers.
|
* 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 "executor/spi.h" /* this is what you need to work with SPI */
|
||||||
#include "commands/trigger.h" /* -"- and triggers */
|
#include "commands/trigger.h" /* -"- and triggers */
|
||||||
#include <ctype.h>
|
#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 timetravel(PG_FUNCTION_ARGS);
|
||||||
Datum set_timetravel(PG_FUNCTION_ARGS);
|
Datum set_timetravel(PG_FUNCTION_ARGS);
|
||||||
|
|
||||||
@ -22,84 +25,102 @@ typedef struct
|
|||||||
static EPlan *Plans = NULL; /* for UPDATE/DELETE */
|
static EPlan *Plans = NULL; /* for UPDATE/DELETE */
|
||||||
static int nPlans = 0;
|
static int nPlans = 0;
|
||||||
|
|
||||||
static char **TTOff = NULL;
|
typedef struct _TTOffList
|
||||||
static int nTTOff = 0;
|
{
|
||||||
|
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);
|
static EPlan *find_plan(char *ident, EPlan ** eplan, int *nplans);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* timetravel () --
|
* timetravel () --
|
||||||
* 1. IF an update affects tuple with stop_date eq INFINITY
|
* 1. IF an update affects tuple with stop_date eq INFINITY
|
||||||
* then form (and return) new tuple with stop_date eq current date
|
* then form (and return) new tuple with start_date eq current date
|
||||||
* and all other column values as in old tuple, and insert tuple
|
* and stop_date eq INFINITY [ and update_user eq current user ]
|
||||||
* with new data and start_date eq current date and
|
* and all other column values as in new tuple, and insert tuple
|
||||||
* stop_date eq INFINITY
|
* with old data and stop_date eq current date
|
||||||
* ELSE - skip updation of tuple.
|
* ELSE - skip updation of tuple.
|
||||||
* 2. IF an delete affects tuple with stop_date eq INFINITY
|
* 2. IF an delete affects tuple with stop_date eq INFINITY
|
||||||
* then insert the same tuple with stop_date eq current date
|
* then insert the same tuple with stop_date eq current date
|
||||||
|
* [ and delete_user eq current user ]
|
||||||
* ELSE - skip deletion of tuple.
|
* ELSE - skip deletion of tuple.
|
||||||
* 3. On INSERT, if start_date is NULL then current date will be
|
* 3. On INSERT, if start_date is NULL then current date will be
|
||||||
* inserted, if stop_date is NULL then INFINITY will be inserted.
|
* 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
|
* In CREATE TRIGGER you are to specify start_date and stop_date column
|
||||||
* names:
|
* names:
|
||||||
* EXECUTE PROCEDURE
|
* 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);
|
PG_FUNCTION_INFO_V1(timetravel);
|
||||||
|
|
||||||
Datum
|
Datum /* have to return HeapTuple to Executor */
|
||||||
timetravel(PG_FUNCTION_ARGS)
|
timetravel(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
TriggerData *trigdata = (TriggerData *) fcinfo->context;
|
TriggerData *trigdata = (TriggerData *) fcinfo->context;
|
||||||
Trigger *trigger; /* to get trigger name */
|
Trigger *trigger; /* to get trigger name */
|
||||||
char **args; /* arguments */
|
int argc;
|
||||||
int attnum[2]; /* fnumbers of start/stop columns */
|
char **args; /* arguments */
|
||||||
Datum oldon,
|
int attnum[MaxAttrNum]; /* fnumbers of start/stop columns */
|
||||||
oldoff;
|
Datum oldtimeon,
|
||||||
Datum newon,
|
oldtimeoff;
|
||||||
newoff;
|
Datum newtimeon,
|
||||||
Datum *cvals; /* column values */
|
newtimeoff,
|
||||||
char *cnulls; /* column nulls */
|
newuser,
|
||||||
char *relname; /* triggered relation name */
|
nulltext;
|
||||||
|
Datum *cvals; /* column values */
|
||||||
|
char *cnulls; /* column nulls */
|
||||||
|
char *relname; /* triggered relation name */
|
||||||
Relation rel; /* triggered relation */
|
Relation rel; /* triggered relation */
|
||||||
HeapTuple trigtuple;
|
HeapTuple trigtuple;
|
||||||
HeapTuple newtuple = NULL;
|
HeapTuple newtuple = NULL;
|
||||||
HeapTuple rettuple;
|
HeapTuple rettuple;
|
||||||
TupleDesc tupdesc; /* tuple description */
|
TupleDesc tupdesc; /* tuple description */
|
||||||
int natts; /* # of attributes */
|
int natts; /* # of attributes */
|
||||||
EPlan *plan; /* prepared plan */
|
EPlan *plan; /* prepared plan */
|
||||||
char ident[2 * NAMEDATALEN];
|
char ident[2 * NAMEDATALEN];
|
||||||
bool isnull; /* to know is some column NULL or not */
|
bool isnull; /* to know is some column NULL or not */
|
||||||
bool isinsert = false;
|
bool isinsert = false;
|
||||||
int ret;
|
int ret;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Some checks first...
|
* Some checks first...
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Called by trigger manager ? */
|
/* Called by trigger manager ? */
|
||||||
if (!CALLED_AS_TRIGGER(fcinfo))
|
if(!CALLED_AS_TRIGGER(fcinfo))
|
||||||
/* internal error */
|
|
||||||
elog(ERROR, "timetravel: not fired by trigger manager");
|
elog(ERROR, "timetravel: not fired by trigger manager");
|
||||||
|
|
||||||
/* Should be called for ROW trigger */
|
/* Should be called for ROW trigger */
|
||||||
if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
|
if(TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
|
||||||
/* internal error */
|
|
||||||
elog(ERROR, "timetravel: can't process STATEMENT events");
|
elog(ERROR, "timetravel: can't process STATEMENT events");
|
||||||
|
|
||||||
/* Should be called BEFORE */
|
/* Should be called BEFORE */
|
||||||
if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
|
if(TRIGGER_FIRED_AFTER(trigdata->tg_event))
|
||||||
/* internal error */
|
|
||||||
elog(ERROR, "timetravel: must be fired before event");
|
elog(ERROR, "timetravel: must be fired before event");
|
||||||
|
|
||||||
/* INSERT ? */
|
/* INSERT ? */
|
||||||
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
|
if(TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
|
||||||
isinsert = true;
|
isinsert = true;
|
||||||
|
|
||||||
if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
|
if(TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
|
||||||
newtuple = trigdata->tg_newtuple;
|
newtuple = trigdata->tg_newtuple;
|
||||||
|
|
||||||
trigtuple = trigdata->tg_trigtuple;
|
trigtuple = trigdata->tg_trigtuple;
|
||||||
@ -108,170 +129,168 @@ timetravel(PG_FUNCTION_ARGS)
|
|||||||
relname = SPI_getrelname(rel);
|
relname = SPI_getrelname(rel);
|
||||||
|
|
||||||
/* check if TT is OFF for this relation */
|
/* check if TT is OFF for this relation */
|
||||||
for (i = 0; i < nTTOff; i++)
|
if(0==findTTStatus(relname))
|
||||||
if (strcasecmp(TTOff[i], relname) == 0)
|
|
||||||
break;
|
|
||||||
if (i < nTTOff) /* OFF - nothing to do */
|
|
||||||
{
|
{
|
||||||
|
/* OFF - nothing to do */
|
||||||
pfree(relname);
|
pfree(relname);
|
||||||
return PointerGetDatum((newtuple != NULL) ? newtuple : trigtuple);
|
return PointerGetDatum((newtuple != NULL) ? newtuple : trigtuple);
|
||||||
}
|
}
|
||||||
|
|
||||||
trigger = trigdata->tg_trigger;
|
trigger = trigdata->tg_trigger;
|
||||||
|
|
||||||
if (trigger->tgnargs != 2)
|
argc = trigger->tgnargs;
|
||||||
/* internal error */
|
if(argc != MinAttrNum && argc != MaxAttrNum)
|
||||||
elog(ERROR, "timetravel (%s): invalid (!= 2) number of arguments %d",
|
elog(ERROR, "timetravel (%s): invalid (!= %d or %d) number of arguments %d",
|
||||||
relname, trigger->tgnargs);
|
relname, MinAttrNum, MaxAttrNum, trigger->tgnargs);
|
||||||
|
|
||||||
args = trigger->tgargs;
|
args = trigger->tgargs;
|
||||||
tupdesc = rel->rd_att;
|
tupdesc = rel->rd_att;
|
||||||
natts = tupdesc->natts;
|
natts = tupdesc->natts;
|
||||||
|
|
||||||
for (i = 0; i < 2; i++)
|
for(i = 0 ; i < MinAttrNum ; i++)
|
||||||
{
|
{
|
||||||
attnum[i] = SPI_fnumber(tupdesc, args[i]);
|
attnum[i] = SPI_fnumber(tupdesc, args[i]);
|
||||||
if (attnum[i] < 0)
|
if(attnum[i] < 0)
|
||||||
ereport(ERROR,
|
elog(ERROR, "timetravel (%s): there is no attribute %s", relname, args[i]);
|
||||||
(errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
|
if(SPI_gettypeid(tupdesc, attnum[i]) != ABSTIMEOID)
|
||||||
errmsg("\"%s\" has no attribute \"%s\"",
|
elog(ERROR, "timetravel (%s): attribute %s must be of abstime type",
|
||||||
relname, args[i])));
|
relname, args[i]);
|
||||||
if (SPI_gettypeid(tupdesc, attnum[i]) != ABSTIMEOID)
|
}
|
||||||
ereport(ERROR,
|
for( ; i < argc ; i++)
|
||||||
(errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
|
{
|
||||||
errmsg("attribute \"%s\" of \"%s\" must be type ABSTIME",
|
attnum[i] = SPI_fnumber(tupdesc, args[i]);
|
||||||
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]) != TEXTOID)
|
||||||
|
elog(ERROR, "timetravel (%s): attribute %s must be of text type",
|
||||||
|
relname, args[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isinsert) /* INSERT */
|
/* create fields containing name */
|
||||||
{
|
newuser = DirectFunctionCall1(textin, CStringGetDatum(GetUserNameFromId(GetUserId())));
|
||||||
int chnattrs = 0;
|
|
||||||
int chattrs[2];
|
|
||||||
Datum newvals[2];
|
|
||||||
|
|
||||||
oldon = SPI_getbinval(trigtuple, tupdesc, attnum[0], &isnull);
|
nulltext = (Datum)NULL;
|
||||||
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();
|
newvals[chnattrs] = GetCurrentAbsoluteTime();
|
||||||
chattrs[chnattrs] = attnum[0];
|
newnulls[chnattrs] = ' ';
|
||||||
|
chattrs[chnattrs] = attnum[a_time_on];
|
||||||
chnattrs++;
|
chnattrs++;
|
||||||
}
|
}
|
||||||
|
|
||||||
oldoff = SPI_getbinval(trigtuple, tupdesc, attnum[1], &isnull);
|
oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull);
|
||||||
if (isnull)
|
if(isnull)
|
||||||
{
|
{
|
||||||
if ((chnattrs == 0 && DatumGetInt32(oldon) >= NOEND_ABSTIME) ||
|
if((chnattrs == 0 && DatumGetInt32(oldtimeon) >= NOEND_ABSTIME) ||
|
||||||
(chnattrs > 0 && DatumGetInt32(newvals[0]) >= NOEND_ABSTIME))
|
(chnattrs > 0 && DatumGetInt32(newvals[a_time_on]) >= NOEND_ABSTIME))
|
||||||
ereport(ERROR,
|
elog(ERROR, "timetravel (%s): %s is infinity", relname, args[a_time_on]);
|
||||||
(errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
|
|
||||||
errmsg("timetravel (%s): %s ge %s",
|
|
||||||
relname, args[0], args[1])));
|
|
||||||
newvals[chnattrs] = NOEND_ABSTIME;
|
newvals[chnattrs] = NOEND_ABSTIME;
|
||||||
chattrs[chnattrs] = attnum[1];
|
newnulls[chnattrs] = ' ';
|
||||||
|
chattrs[chnattrs] = attnum[a_time_off];
|
||||||
chnattrs++;
|
chnattrs++;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if ((chnattrs == 0 && DatumGetInt32(oldon) >=
|
if((chnattrs == 0 && DatumGetInt32(oldtimeon) > DatumGetInt32(oldtimeoff)) ||
|
||||||
DatumGetInt32(oldoff)) ||
|
(chnattrs > 0 && DatumGetInt32(newvals[a_time_on]) > DatumGetInt32(oldtimeoff)))
|
||||||
(chnattrs > 0 && DatumGetInt32(newvals[0]) >=
|
elog(ERROR, "timetravel (%s): %s gt %s", relname, args[a_time_on], args[a_time_off]);
|
||||||
DatumGetInt32(oldoff)))
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
|
|
||||||
errmsg("timetravel (%s): %s ge %s",
|
|
||||||
relname, args[0], args[1])));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pfree(relname);
|
pfree(relname);
|
||||||
if (chnattrs <= 0)
|
if(chnattrs <= 0)
|
||||||
return PointerGetDatum(trigtuple);
|
return PointerGetDatum(trigtuple);
|
||||||
|
|
||||||
rettuple = SPI_modifytuple(rel, trigtuple, chnattrs,
|
if(argc == MaxAttrNum)
|
||||||
chattrs, newvals, NULL);
|
{
|
||||||
|
/* 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);
|
return PointerGetDatum(rettuple);
|
||||||
|
/* end of INSERT */
|
||||||
}
|
}
|
||||||
|
|
||||||
oldon = SPI_getbinval(trigtuple, tupdesc, attnum[0], &isnull);
|
/* UPDATE/DELETE: */
|
||||||
if (isnull)
|
oldtimeon = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_on], &isnull);
|
||||||
ereport(ERROR,
|
if(isnull)
|
||||||
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_on]);
|
||||||
errmsg("\"%s\" must be NOT NULL in \"%s\"",
|
|
||||||
args[0],relname)));
|
oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull);
|
||||||
oldoff = SPI_getbinval(trigtuple, tupdesc, attnum[1], &isnull);
|
if(isnull)
|
||||||
if (isnull)
|
elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_off]);
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
||||||
errmsg("\"%s\" must be NOT NULL in \"%s\"",
|
|
||||||
args[1],relname)));
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If DELETE/UPDATE of tuple with stop_date neq INFINITY then say
|
* If DELETE/UPDATE of tuple with stop_date neq INFINITY then say
|
||||||
* upper Executor to skip operation for this tuple
|
* upper Executor to skip operation for this tuple
|
||||||
*/
|
*/
|
||||||
if (newtuple != NULL) /* UPDATE */
|
if(newtuple != NULL)
|
||||||
{
|
{ /* UPDATE */
|
||||||
newon = SPI_getbinval(newtuple, tupdesc, attnum[0], &isnull);
|
newtimeon = SPI_getbinval(newtuple, tupdesc, attnum[a_time_on], &isnull);
|
||||||
if (isnull)
|
if(isnull)
|
||||||
ereport(ERROR,
|
elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_on]);
|
||||||
(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)
|
newtimeoff = SPI_getbinval(newtuple, tupdesc, attnum[a_time_off], &isnull);
|
||||||
ereport(ERROR,
|
if(isnull)
|
||||||
(errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
|
elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_off]);
|
||||||
errmsg("cannot change columns \"%s\" or \"%s\" in \"%s\"",
|
|
||||||
args[0], args[1], relname),
|
|
||||||
errhint("Use set_timetravel() instead.")));
|
|
||||||
|
|
||||||
if (newoff != NOEND_ABSTIME)
|
if(oldtimeon != newtimeon || oldtimeoff != newtimeoff)
|
||||||
{
|
elog(ERROR, "timetravel (%s): you can't change %s and/or %s columns (use set_timetravel)",
|
||||||
pfree(relname); /* allocated in upper executor context */
|
relname, args[a_time_on], args[a_time_off]);
|
||||||
return PointerGetDatum(NULL);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (oldoff != NOEND_ABSTIME) /* DELETE */
|
if(oldtimeoff != NOEND_ABSTIME)
|
||||||
{
|
{ /* current record is a deleted/updated record */
|
||||||
pfree(relname);
|
pfree(relname);
|
||||||
return PointerGetDatum(NULL);
|
return PointerGetDatum(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
newoff = GetCurrentAbsoluteTime();
|
newtimeoff = GetCurrentAbsoluteTime();
|
||||||
|
|
||||||
/* Connect to SPI manager */
|
/* Connect to SPI manager */
|
||||||
if ((ret = SPI_connect()) < 0)
|
if((ret = SPI_connect()) < 0)
|
||||||
/* internal error */
|
|
||||||
elog(ERROR, "timetravel (%s): SPI_connect returned %d", relname, ret);
|
elog(ERROR, "timetravel (%s): SPI_connect returned %d", relname, ret);
|
||||||
|
|
||||||
/* Fetch tuple values and nulls */
|
/* Fetch tuple values and nulls */
|
||||||
cvals = (Datum *) palloc(natts * sizeof(Datum));
|
cvals = (Datum *) palloc(natts * sizeof(Datum));
|
||||||
cnulls = (char *) palloc(natts * sizeof(char));
|
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,
|
cvals[i] = SPI_getbinval(trigtuple, tupdesc, i + 1, &isnull);
|
||||||
tupdesc, i + 1, &isnull);
|
|
||||||
cnulls[i] = (isnull) ? 'n' : ' ';
|
cnulls[i] = (isnull) ? 'n' : ' ';
|
||||||
}
|
}
|
||||||
|
|
||||||
/* change date column(s) */
|
/* change date column(s) */
|
||||||
if (newtuple) /* UPDATE */
|
cvals[attnum[a_time_off] - 1] = newtimeoff; /* stop_date eq current date */
|
||||||
{
|
cnulls[attnum[a_time_off] - 1] = ' ';
|
||||||
cvals[attnum[0] - 1] = newoff; /* start_date eq current date */
|
|
||||||
cnulls[attnum[0] - 1] = ' ';
|
if(!newtuple)
|
||||||
cvals[attnum[1] - 1] = NOEND_ABSTIME; /* stop_date eq INFINITY */
|
{ /* DELETE */
|
||||||
cnulls[attnum[1] - 1] = ' ';
|
if(argc == MaxAttrNum)
|
||||||
}
|
{
|
||||||
else
|
cvals[attnum[a_del_user] - 1] = newuser; /* set delete user */
|
||||||
/* DELETE */
|
cnulls[attnum[a_del_user] - 1] = ' ';
|
||||||
{
|
}
|
||||||
cvals[attnum[1] - 1] = newoff; /* stop_date eq current date */
|
|
||||||
cnulls[attnum[1] - 1] = ' ';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -282,11 +301,12 @@ timetravel(PG_FUNCTION_ARGS)
|
|||||||
plan = find_plan(ident, &Plans, &nPlans);
|
plan = find_plan(ident, &Plans, &nPlans);
|
||||||
|
|
||||||
/* if there is no plan ... */
|
/* if there is no plan ... */
|
||||||
if (plan->splan == NULL)
|
if(plan->splan == NULL)
|
||||||
{
|
{
|
||||||
void *pplan;
|
void *pplan;
|
||||||
Oid *ctypes;
|
Oid *ctypes;
|
||||||
char sql[8192];
|
char sql[8192];
|
||||||
|
int j;
|
||||||
|
|
||||||
/* allocate ctypes for preparation */
|
/* allocate ctypes for preparation */
|
||||||
ctypes = (Oid *) palloc(natts * sizeof(Oid));
|
ctypes = (Oid *) palloc(natts * sizeof(Oid));
|
||||||
@ -295,17 +315,21 @@ timetravel(PG_FUNCTION_ARGS)
|
|||||||
* Construct query: INSERT INTO _relation_ VALUES ($1, ...)
|
* Construct query: INSERT INTO _relation_ VALUES ($1, ...)
|
||||||
*/
|
*/
|
||||||
snprintf(sql, sizeof(sql), "INSERT INTO %s VALUES (", relname);
|
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);
|
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 */
|
/* Prepare plan for query */
|
||||||
pplan = SPI_prepare(sql, natts, ctypes);
|
pplan = SPI_prepare(sql, natts, ctypes);
|
||||||
if (pplan == NULL)
|
if(pplan == NULL)
|
||||||
/* internal error */
|
|
||||||
elog(ERROR, "timetravel (%s): SPI_prepare returned %d", relname, SPI_result);
|
elog(ERROR, "timetravel (%s): SPI_prepare returned %d", relname, SPI_result);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -314,8 +338,7 @@ timetravel(PG_FUNCTION_ARGS)
|
|||||||
* use.
|
* use.
|
||||||
*/
|
*/
|
||||||
pplan = SPI_saveplan(pplan);
|
pplan = SPI_saveplan(pplan);
|
||||||
if (pplan == NULL)
|
if(pplan == NULL)
|
||||||
/* internal error */
|
|
||||||
elog(ERROR, "timetravel (%s): SPI_saveplan returned %d", relname, SPI_result);
|
elog(ERROR, "timetravel (%s): SPI_saveplan returned %d", relname, SPI_result);
|
||||||
|
|
||||||
plan->splan = pplan;
|
plan->splan = pplan;
|
||||||
@ -326,23 +349,54 @@ timetravel(PG_FUNCTION_ARGS)
|
|||||||
*/
|
*/
|
||||||
ret = SPI_execp(plan->splan, cvals, cnulls, 0);
|
ret = SPI_execp(plan->splan, cvals, cnulls, 0);
|
||||||
|
|
||||||
if (ret < 0)
|
if(ret < 0)
|
||||||
/* internal error */
|
|
||||||
elog(ERROR, "timetravel (%s): SPI_execp returned %d", relname, ret);
|
elog(ERROR, "timetravel (%s): SPI_execp returned %d", relname, ret);
|
||||||
|
|
||||||
/* Tuple to return to upper Executor ... */
|
/* Tuple to return to upper Executor ... */
|
||||||
if (newtuple) /* UPDATE */
|
if(newtuple)
|
||||||
{
|
{ /* UPDATE */
|
||||||
HeapTuple tmptuple;
|
HeapTuple tmptuple;
|
||||||
|
int chnattrs = 0;
|
||||||
|
int chattrs[MaxAttrNum];
|
||||||
|
Datum newvals[MaxAttrNum];
|
||||||
|
char newnulls[MaxAttrNum];
|
||||||
|
|
||||||
tmptuple = SPI_copytuple(trigtuple);
|
newvals[chnattrs] = newtimeoff;
|
||||||
rettuple = SPI_modifytuple(rel, tmptuple, 1, &(attnum[1]), &newoff, NULL);
|
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++;
|
||||||
|
}
|
||||||
|
|
||||||
|
rettuple = SPI_modifytuple(rel, newtuple, chnattrs, chattrs, newvals, newnulls);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SPI_copytuple allocates tmptuple in upper executor context -
|
* SPI_copytuple allocates tmptuple in upper executor context -
|
||||||
* have to free allocation using SPI_pfree
|
* have to free allocation using SPI_pfree
|
||||||
*/
|
*/
|
||||||
SPI_pfree(tmptuple);
|
// SPI_pfree(tmptuple);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
/* DELETE */
|
/* DELETE */
|
||||||
@ -351,7 +405,6 @@ timetravel(PG_FUNCTION_ARGS)
|
|||||||
SPI_finish(); /* don't forget say Bye to SPI mgr */
|
SPI_finish(); /* don't forget say Bye to SPI mgr */
|
||||||
|
|
||||||
pfree(relname);
|
pfree(relname);
|
||||||
|
|
||||||
return PointerGetDatum(rettuple);
|
return PointerGetDatum(rettuple);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,77 +417,109 @@ PG_FUNCTION_INFO_V1(set_timetravel);
|
|||||||
Datum
|
Datum
|
||||||
set_timetravel(PG_FUNCTION_ARGS)
|
set_timetravel(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
Name relname = PG_GETARG_NAME(0);
|
Name relname = PG_GETARG_NAME(0);
|
||||||
int32 on = PG_GETARG_INT32(1);
|
int32 on = PG_GETARG_INT32(1);
|
||||||
char *rname;
|
char *rname;
|
||||||
char *d;
|
char *d;
|
||||||
char *s;
|
char *s;
|
||||||
int i;
|
int32 ret;
|
||||||
|
TTOffList *p,*pp;
|
||||||
|
|
||||||
for (i = 0; i < nTTOff; i++)
|
for(pp = (p = &TTOff)->next; pp; pp=(p=pp)->next)
|
||||||
if (namestrcmp(relname, TTOff[i]) == 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (i < nTTOff) /* OFF currently */
|
|
||||||
{
|
{
|
||||||
if (on == 0)
|
if(namestrcmp(relname, pp->name) == 0)
|
||||||
PG_RETURN_INT32(0);
|
break;
|
||||||
|
}
|
||||||
/* turn ON */
|
if(pp)
|
||||||
free(TTOff[i]);
|
{
|
||||||
if (nTTOff == 1)
|
/* OFF currently */
|
||||||
free(TTOff);
|
if(on != 0)
|
||||||
else
|
{
|
||||||
{
|
/* turn ON */
|
||||||
if (i < nTTOff - 1)
|
p->next = pp->next;
|
||||||
memcpy(&(TTOff[i]), &(TTOff[i + 1]), (nTTOff - i) * sizeof(char *));
|
free(pp);
|
||||||
TTOff = realloc(TTOff, (nTTOff - 1) * sizeof(char *));
|
}
|
||||||
}
|
ret = 0;
|
||||||
nTTOff--;
|
|
||||||
PG_RETURN_INT32(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ON currently */
|
|
||||||
if (on != 0)
|
|
||||||
PG_RETURN_INT32(1);
|
|
||||||
|
|
||||||
/* turn OFF */
|
|
||||||
if (nTTOff == 0)
|
|
||||||
TTOff = malloc(sizeof(char *));
|
|
||||||
else
|
else
|
||||||
TTOff = realloc(TTOff, (nTTOff + 1) * sizeof(char *));
|
{
|
||||||
s = rname = DatumGetCString(DirectFunctionCall1(nameout,
|
/* ON currently */
|
||||||
NameGetDatum(relname)));
|
if(on == 0)
|
||||||
d = TTOff[nTTOff] = malloc(strlen(rname) + 1);
|
{
|
||||||
while (*s)
|
/* turn OFF */
|
||||||
*d++ = tolower((unsigned char) *s++);
|
s = rname = DatumGetCString(DirectFunctionCall1(nameout, NameGetDatum(relname)));
|
||||||
*d = 0;
|
if(s)
|
||||||
pfree(rname);
|
{
|
||||||
nTTOff++;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get_timetravel (relname) --
|
||||||
|
* get timetravel status for specified relation (ON/OFF)
|
||||||
|
*/
|
||||||
|
PG_FUNCTION_INFO_V1(get_timetravel);
|
||||||
|
|
||||||
|
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);
|
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
|
AbsoluteTime
|
||||||
currabstime()
|
currabstime()
|
||||||
{
|
{
|
||||||
return (GetCurrentAbsoluteTime());
|
return (GetCurrentAbsoluteTime());
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
static EPlan *
|
static EPlan *
|
||||||
find_plan(char *ident, EPlan ** eplan, int *nplans)
|
find_plan(char *ident, EPlan ** eplan, int *nplans)
|
||||||
{
|
{
|
||||||
EPlan *newp;
|
EPlan *newp;
|
||||||
int i;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
if (i != *nplans)
|
if(i != *nplans)
|
||||||
return (*eplan + i);
|
return (*eplan + i);
|
||||||
*eplan = (EPlan *) realloc(*eplan, (i + 1) * sizeof(EPlan));
|
*eplan = (EPlan *) realloc(*eplan, (i + 1) * sizeof(EPlan));
|
||||||
newp = *eplan + i;
|
newp = *eplan + i;
|
||||||
|
@ -7,6 +7,11 @@ create table tttest (
|
|||||||
price_off abstime
|
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
|
create trigger timetravel
|
||||||
before insert or delete or update on tttest
|
before insert or delete or update on tttest
|
||||||
for each row
|
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) 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, '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));
|
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;
|
select * from tttest;
|
||||||
delete from tttest where price_id = 2;
|
delete from tttest where price_id = 2;
|
||||||
@ -40,6 +45,9 @@ select * from tttest;
|
|||||||
|
|
||||||
-- restore data as before last update:
|
-- restore data as before last update:
|
||||||
select set_timetravel('tttest', 0); -- turn TT OFF!
|
select set_timetravel('tttest', 0); -- turn TT OFF!
|
||||||
|
|
||||||
|
select get_timetravel('tttest'); -- check status
|
||||||
|
|
||||||
delete from tttest where price_id = 5;
|
delete from tttest where price_id = 5;
|
||||||
update tttest set price_off = 'infinity' where price_val = 30;
|
update tttest set price_off = 'infinity' where price_val = 30;
|
||||||
select * from tttest;
|
select * from tttest;
|
||||||
@ -51,6 +59,8 @@ select * from tttest;
|
|||||||
|
|
||||||
select set_timetravel('tttest', 1); -- turn TT ON!
|
select set_timetravel('tttest', 1); -- turn TT ON!
|
||||||
|
|
||||||
|
select get_timetravel('tttest'); -- check status
|
||||||
|
|
||||||
-- we want to correct some date
|
-- we want to correct some date
|
||||||
update tttest set price_on = 'Jan-01-1990 00:00:01' where price_id = 5 and
|
update tttest set price_on = 'Jan-01-1990 00:00:01' where price_id = 5 and
|
||||||
price_off <> 'infinity';
|
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
|
-- try in this way
|
||||||
select set_timetravel('tttest', 0); -- turn TT OFF!
|
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
|
update tttest set price_on = '01-Jan-1990 00:00:01' where price_id = 5 and
|
||||||
price_off <> 'infinity';
|
price_off <> 'infinity';
|
||||||
select * from tttest;
|
select * from tttest;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
-- Adjust this setting to control where the objects get created.
|
-- Adjust this setting to control where the objects get created.
|
||||||
SET search_path = public;
|
SET search_path = public;
|
||||||
|
|
||||||
|
SET autocommit TO 'on';
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION timetravel()
|
CREATE OR REPLACE FUNCTION timetravel()
|
||||||
RETURNS trigger
|
RETURNS trigger
|
||||||
AS 'MODULE_PATHNAME'
|
AS 'MODULE_PATHNAME'
|
||||||
@ -10,3 +12,8 @@ CREATE OR REPLACE FUNCTION set_timetravel(name, int4)
|
|||||||
RETURNS int4
|
RETURNS int4
|
||||||
AS 'MODULE_PATHNAME'
|
AS 'MODULE_PATHNAME'
|
||||||
LANGUAGE 'C' WITH (isStrict);
|
LANGUAGE 'C' WITH (isStrict);
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION get_timetravel(name)
|
||||||
|
RETURNS int4
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'C' WITH (isStrict);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user