@ -6,11 +6,11 @@
/* Modified by B<> JTHE Zolt<6C> 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 "commands/trigger.h" /* -"- and triggers */
# include "miscadmin.h" /* for GetPgUserName() */
# include <ctype.h> /* tolower () */
# 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); */
Datum timetravel ( PG_FUNCTION_ARGS ) ;
@ -28,13 +28,13 @@ static int nPlans = 0;
typedef struct _TTOffList
{
struct _TTOffList * next ;
char name [ 1 ] ;
} TTOffList ;
struct _TTOffList * next ;
char name [ 1 ] ;
} TTOffList ;
static TTOffList TTOff = { NULL , { 0 } } ;
static TTOffList TTOff = { NULL , { 0 } } ;
static int findTTStatus ( char * name ) ;
static int findTTStatus ( char * name ) ;
static EPlan * find_plan ( char * ident , EPlan * * eplan , int * nplans ) ;
/*
@ -71,57 +71,57 @@ static EPlan *find_plan(char *ident, EPlan ** eplan, int *nplans);
PG_FUNCTION_INFO_V1 ( timetravel ) ;
Datum /* have to return HeapTuple to Executor */
Datum /* have to return HeapTuple to Executor */
timetravel ( PG_FUNCTION_ARGS )
{
TriggerData * trigdata = ( TriggerData * ) fcinfo - > context ;
Trigger * trigger ; /* to get trigger name */
int argc ;
char * * args ; /* arguments */
int attnum [ MaxAttrNum ] ; /* fnumbers of start/stop columns */
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 ;
oldtimeoff ;
Datum newtimeon ,
newtimeoff ,
newuser ,
nulltext ;
Datum * cvals ; /* column values */
char * cnulls ; /* column nulls */
char * relname ; /* triggered relation name */
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 ) )
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 ) )
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 ) )
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 ;
@ -130,7 +130,7 @@ timetravel(PG_FUNCTION_ARGS)
relname = SPI_getrelname ( rel ) ;
/* check if TT is OFF for this relation */
if ( 0 = = findTTStatus ( relname ) )
if ( 0 = = findTTStatus ( relname ) )
{
/* OFF - nothing to do */
pfree ( relname ) ;
@ -140,7 +140,7 @@ timetravel(PG_FUNCTION_ARGS)
trigger = trigdata - > tg_trigger ;
argc = trigger - > tgnargs ;
if ( argc ! = MinAttrNum & & argc ! = MaxAttrNum )
if ( argc ! = MinAttrNum & & argc ! = MaxAttrNum )
elog ( ERROR , " timetravel (%s): invalid (!= %d or %d) number of arguments %d " ,
relname , MinAttrNum , MaxAttrNum , trigger - > tgnargs ) ;
@ -148,39 +148,39 @@ timetravel(PG_FUNCTION_ARGS)
tupdesc = rel - > rd_att ;
natts = tupdesc - > natts ;
for ( i = 0 ; i < MinAttrNum ; i + + )
for ( i = 0 ; i < MinAttrNum ; i + + )
{
attnum [ i ] = SPI_fnumber ( tupdesc , args [ i ] ) ;
if ( attnum [ i ] < 0 )
if ( attnum [ i ] < 0 )
elog ( ERROR , " timetravel (%s): there is no attribute %s " , relname , args [ i ] ) ;
if ( SPI_gettypeid ( tupdesc , attnum [ i ] ) ! = ABSTIMEOID )
if ( SPI_gettypeid ( tupdesc , attnum [ i ] ) ! = ABSTIMEOID )
elog ( ERROR , " timetravel (%s): attribute %s must be of abstime type " ,
relname , args [ i ] ) ;
}
for ( ; i < argc ; i + + )
for ( ; i < argc ; i + + )
{
attnum [ i ] = SPI_fnumber ( tupdesc , args [ i ] ) ;
if ( attnum [ i ] < 0 )
if ( attnum [ i ] < 0 )
elog ( ERROR , " timetravel (%s): there is no attribute %s " , relname , args [ i ] ) ;
if ( SPI_gettypeid ( tupdesc , attnum [ i ] ) ! = TEXTOID )
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 ( ) ) ) ) ;
newuser = DirectFunctionCall1 ( textin , CStringGetDatum ( GetUserNameFromId ( GetUserId ( ) ) ) ) ;
nulltext = ( Datum ) NULL ;
nulltext = ( Datum ) NULL ;
if ( isinsert )
{ /* INSERT */
int chnattrs = 0 ;
int chattrs [ MaxAttrNum ] ;
Datum newvals [ MaxAttrNum ] ;
char newnulls [ MaxAttrNum ] ;
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 )
if ( isnull )
{
newvals [ chnattrs ] = GetCurrentAbsoluteTime ( ) ;
newnulls [ chnattrs ] = ' ' ;
@ -189,10 +189,10 @@ timetravel(PG_FUNCTION_ARGS)
}
oldtimeoff = SPI_getbinval ( trigtuple , tupdesc , attnum [ a_time_off ] , & isnull ) ;
if ( isnull )
if ( isnull )
{
if ( ( chnattrs = = 0 & & DatumGetInt32 ( oldtimeon ) > = NOEND_ABSTIME ) | |
( chnattrs > 0 & & DatumGetInt32 ( newvals [ a_time_on ] ) > = NOEND_ABSTIME ) )
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 ;
newnulls [ chnattrs ] = ' ' ;
@ -201,16 +201,16 @@ timetravel(PG_FUNCTION_ARGS)
}
else
{
if ( ( chnattrs = = 0 & & DatumGetInt32 ( oldtimeon ) > DatumGetInt32 ( oldtimeoff ) ) | |
( chnattrs > 0 & & DatumGetInt32 ( newvals [ a_time_on ] ) > DatumGetInt32 ( oldtimeoff ) ) )
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 ) ;
if ( argc = = MaxAttrNum )
if ( argc = = MaxAttrNum )
{
/* clear update_user value */
newvals [ chnattrs ] = nulltext ;
@ -235,33 +235,34 @@ timetravel(PG_FUNCTION_ARGS)
/* UPDATE/DELETE: */
oldtimeon = SPI_getbinval ( trigtuple , tupdesc , attnum [ a_time_on ] , & isnull ) ;
if ( 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 )
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 */
if ( newtuple ! = NULL )
{ /* UPDATE */
newtimeon = SPI_getbinval ( newtuple , tupdesc , attnum [ a_time_on ] , & isnull ) ;
if ( 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 )
if ( isnull )
elog ( ERROR , " timetravel (%s): %s must be NOT NULL " , relname , args [ a_time_off ] ) ;
if ( oldtimeon ! = newtimeon | | oldtimeoff ! = newtimeoff )
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 ] ) ;
}
if ( oldtimeoff ! = NOEND_ABSTIME )
{ /* current record is a deleted/updated record */
if ( oldtimeoff ! = NOEND_ABSTIME )
{ /* current record is a deleted/updated
* record */
pfree ( relname ) ;
return PointerGetDatum ( NULL ) ;
}
@ -269,27 +270,28 @@ timetravel(PG_FUNCTION_ARGS)
newtimeoff = GetCurrentAbsoluteTime ( ) ;
/* Connect to SPI manager */
if ( ( ret = SPI_connect ( ) ) < 0 )
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 ( trigtuple , tupdesc , i + 1 , & isnull ) ;
cnulls [ i ] = ( isnull ) ? ' n ' : ' ' ;
}
/* change date column(s) */
cvals [ attnum [ a_time_off ] - 1 ] = newtimeoff ; /* stop_date eq current date */
cvals [ attnum [ a_time_off ] - 1 ] = newtimeoff ; /* stop_date eq current
* date */
cnulls [ attnum [ a_time_off ] - 1 ] = ' ' ;
if ( ! newtuple )
{ /* DELETE */
if ( argc = = MaxAttrNum )
if ( ! newtuple )
{ /* DELETE */
if ( argc = = MaxAttrNum )
{
cvals [ attnum [ a_del_user ] - 1 ] = newuser ; /* set delete user */
cvals [ attnum [ a_del_user ] - 1 ] = newuser ; /* set delete user */
cnulls [ attnum [ a_del_user ] - 1 ] = ' ' ;
}
}
@ -302,11 +304,11 @@ 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 ] ;
/* allocate ctypes for preparation */
ctypes = ( Oid * ) palloc ( natts * sizeof ( Oid ) ) ;
@ -315,15 +317,15 @@ 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 + + )
{
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 ) ? " , " : " ) " ) ;
if ( ! ( tupdesc - > attrs [ i - 1 ] - > attisdropped ) ) /* skip dropped columns */
snprintf ( sql + strlen ( sql ) , sizeof ( sql ) - strlen ( sql ) , " $%d%s " ,
i , ( i < natts ) ? " , " : " ) " ) ;
#if 0
snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "$%d /* %d */ %s",
i, ctypes[i- 1], (i < natts) ? ", " : ")" );
snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "$%d /* %d */ %s",
i, ctypes[i - 1], (i < natts) ? ", " : ")");
#endif
}
@ -331,7 +333,7 @@ timetravel(PG_FUNCTION_ARGS)
/* Prepare plan for query */
pplan = SPI_prepare ( sql , natts , ctypes ) ;
if ( pplan = = NULL )
if ( pplan = = NULL )
elog ( ERROR , " timetravel (%s): SPI_prepare returned %d " , relname , SPI_result ) ;
/*
@ -340,7 +342,7 @@ timetravel(PG_FUNCTION_ARGS)
* use.
*/
pplan = SPI_saveplan ( pplan ) ;
if ( pplan = = NULL )
if ( pplan = = NULL )
elog ( ERROR , " timetravel (%s): SPI_saveplan returned %d " , relname , SPI_result ) ;
plan - > splan = pplan ;
@ -351,14 +353,14 @@ timetravel(PG_FUNCTION_ARGS)
*/
ret = SPI_execp ( plan - > splan , cvals , cnulls , 0 ) ;
if ( ret < 0 )
if ( ret < 0 )
elog ( ERROR , " timetravel (%s): SPI_execp returned %d " , relname , ret ) ;
/* Tuple to return to upper Executor ... */
if ( newtuple )
{ /* UPDATE */
int chnattrs = 0 ;
int chattrs [ MaxAttrNum ] ;
if ( newtuple )
{ /* UPDATE */
int chnattrs = 0 ;
int chattrs [ MaxAttrNum ] ;
Datum newvals [ MaxAttrNum ] ;
char newnulls [ MaxAttrNum ] ;
@ -372,7 +374,7 @@ timetravel(PG_FUNCTION_ARGS)
chattrs [ chnattrs ] = attnum [ a_time_off ] ;
chnattrs + + ;
if ( argc = = MaxAttrNum )
if ( argc = = MaxAttrNum )
{
/* set update_user value */
newvals [ chnattrs ] = newuser ;
@ -399,7 +401,8 @@ timetravel(PG_FUNCTION_ARGS)
*/
/* SPI_pfree(tmptuple); */
}
else /* DELETE case */
else
/* DELETE case */
rettuple = trigtuple ;
SPI_finish ( ) ; /* don't forget say Bye to SPI mgr */
@ -417,23 +420,24 @@ 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 ;
Name relname = PG_GETARG_NAME ( 0 ) ;
int32 on = PG_GETARG_INT32 ( 1 ) ;
char * rname ;
char * d ;
char * s ;
int32 ret ;
TTOffList * p , * pp ;
TTOffList * p ,
* pp ;
for ( pp = ( p = & TTOff ) - > next ; pp ; pp = ( p = pp ) - > next )
for ( pp = ( p = & TTOff ) - > next ; pp ; pp = ( p = pp ) - > next )
{
if ( namestrcmp ( relname , pp - > name ) = = 0 )
if ( namestrcmp ( relname , pp - > name ) = = 0 )
break ;
}
if ( pp )
if ( pp )
{
/* OFF currently */
if ( on ! = 0 )
if ( on ! = 0 )
{
/* turn ON */
p - > next = pp - > next ;
@ -444,20 +448,20 @@ set_timetravel(PG_FUNCTION_ARGS)
else
{
/* ON currently */
if ( on = = 0 )
if ( on = = 0 )
{
/* turn OFF */
s = rname = DatumGetCString ( DirectFunctionCall1 ( nameout , NameGetDatum ( relname ) ) ) ;
if ( s )
if ( s )
{
pp = malloc ( sizeof ( TTOffList ) + strlen ( rname ) ) ;
if ( pp )
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 + + = tolower ( ( unsigned char ) * s + + ) ;
* d = ' \0 ' ;
}
pfree ( rname ) ;
@ -470,7 +474,7 @@ set_timetravel(PG_FUNCTION_ARGS)
/*
* get_timetravel (relname) --
* get timetravel status for specified relation (ON/OFF)
* get timetravel status for specified relation (ON/OFF)
*/
PG_FUNCTION_INFO_V1 ( get_timetravel ) ;
@ -478,11 +482,11 @@ Datum
get_timetravel ( PG_FUNCTION_ARGS )
{
Name relname = PG_GETARG_NAME ( 0 ) ;
TTOffList * pp ;
TTOffList * pp ;
for ( pp = TTOff . next ; pp ; pp = pp - > next )
for ( pp = TTOff . next ; pp ; pp = pp - > next )
{
if ( namestrcmp ( relname , pp - > name ) = = 0 )
if ( namestrcmp ( relname , pp - > name ) = = 0 )
PG_RETURN_INT32 ( 0 ) ;
}
PG_RETURN_INT32 ( 1 ) ;
@ -491,9 +495,10 @@ get_timetravel(PG_FUNCTION_ARGS)
static int
findTTStatus ( char * name )
{
TTOffList * pp ;
for ( pp = TTOff . next ; pp ; pp = pp - > next )
if ( strcasecmp ( name , pp - > name ) = = 0 )
TTOffList * pp ;
for ( pp = TTOff . next ; pp ; pp = pp - > next )
if ( strcasecmp ( name , pp - > name ) = = 0 )
return 0 ;
return 1 ;
}
@ -509,17 +514,17 @@ currabstime()
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 ;