@ -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 ,
( errcode ( ERRCODE_TRIGGERED_ACTION_EXCEPTION ) ,
errmsg ( " attribute \" %s \" of \" %s \" must be type ABSTIME " ,
args [ i ] , relname ) ) ) ;
}
}
for ( ; i < argc ; i + + )
if ( isinsert ) /* INSERT */
{
{
int chnattrs = 0 ;
attnum [ i ] = SPI_fnumber ( tupdesc , args [ i ] ) ;
int chattrs [ 2 ] ;
if ( attnum [ i ] < 0 )
Datum newvals [ 2 ] ;
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 ( isinsert )
if ( isnull )
{ /* 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 ) ;
oldtime off = 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 \" " ,
newtimeoff = SPI_getbinval ( newtuple , tupdesc , attnum [ a_time_off ] , & isnull ) ;
args [ 0 ] , relname ) ) ) ;
if ( isnull )
newoff = SPI_getbinval ( newtuple , tupdesc , attnum [ 1 ] , & isnull ) ;
elog ( ERROR , " timetravel (%s): %s must be NOT NULL " , relname , args [ a_time_off ] ) ;
if ( isnull )
ereport ( ERROR ,
if ( oldtimeon ! = newtimeon | | oldtimeoff ! = newtimeoff )
( errcode ( ERRCODE_NULL_VALUE_NOT_ALLOWED ) ,
elog ( ERROR , " timetravel (%s): you can't change %s and/or %s columns (use set_timetravel) " ,
errmsg ( " \" %s \" must be NOT NULL in \" %s \" " ,
relname , args [ a_time_on ] , args [ a_time_off ] ) ;
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 ) ;
}
}
}
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 ] ;
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 , newtuple , chnattrs , chattrs , newvals , newnulls ) ;
rettuple = SPI_modifytuple ( rel , tmptuple , 1 , & ( attnum [ 1 ] ) , & newoff , NULL ) ;
/*
/*
* 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 + + )
if ( namestrcmp ( relname , TTOff [ i ] ) = = 0 )
for ( pp = ( p = & TTOff ) - > next ; pp ; pp = ( p = pp ) - > next )
{
if ( namestrcmp ( relname , pp - > name ) = = 0 )
break ;
break ;
}
if ( i < nTTOff ) /* OFF currently */
if ( pp )
{
{
if ( on = = 0 )
/* OFF currently */
PG_RETURN_INT32 ( 0 ) ;
if ( on ! = 0 )
/* turn ON */
free ( TTOff [ i ] ) ;
if ( nTTOff = = 1 )
free ( TTOff ) ;
else
{
{
if ( i < nTTOff - 1 )
/* turn ON */
memcpy ( & ( TTOff [ i ] ) , & ( TTOff [ i + 1 ] ) , ( nTTOff - i ) * sizeof ( char * ) ) ;
p - > next = pp - > next ;
TTO ff = realloc ( TTOff , ( nTTOff - 1 ) * siz eof ( char * ) ) ;
free ( pp ) ;
}
}
nTTOff - - ;
ret = 0 ;
PG_RETURN_INT32 ( 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 )
* get_timetravel ( relname ) - -
PG_RETURN_INT32 ( 1 ) ;
* get timetravel status for specified relation ( ON / OFF )
*/
PG_FUNCTION_INFO_V1 ( get_timetravel ) ;
/* turn OFF */
Datum
if ( nTTOff = = 0 )
get_timetravel ( PG_FUNCTION_ARGS )
TTOff = malloc ( sizeof ( char * ) ) ;
{
else
Name relname = PG_GETARG_NAME ( 0 ) ;
TTOff = realloc ( TTOff , ( nTTOff + 1 ) * sizeof ( char * ) ) ;
TTOffList * pp ;
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 + + ;
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 ;