Header And Logo

PostgreSQL
| The world's most advanced open source database.

Data Structures | Defines | Typedefs | Functions | Variables

timetravel.c File Reference

#include "postgres.h"
#include <ctype.h>
#include "catalog/pg_type.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/nabstime.h"
#include "utils/rel.h"
Include dependency graph for timetravel.c:

Go to the source code of this file.

Data Structures

struct  EPlan
struct  _TTOffList

Defines

#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

Typedefs

typedef struct _TTOffList TTOffList

Functions

Datum timetravel (PG_FUNCTION_ARGS)
Datum set_timetravel (PG_FUNCTION_ARGS)
Datum get_timetravel (PG_FUNCTION_ARGS)
static int findTTStatus (char *name)
static EPlanfind_plan (char *ident, EPlan **eplan, int *nplans)
 PG_FUNCTION_INFO_V1 (timetravel)
 PG_FUNCTION_INFO_V1 (set_timetravel)
 PG_FUNCTION_INFO_V1 (get_timetravel)

Variables

 PG_MODULE_MAGIC
static EPlanPlans = NULL
static int nPlans = 0
static TTOffList TTOff = {NULL, {0}}

Define Documentation

#define a_del_user   4

Definition at line 79 of file timetravel.c.

#define a_ins_user   2

Definition at line 77 of file timetravel.c.

#define a_time_off   1

Definition at line 76 of file timetravel.c.

Referenced by timetravel().

#define a_time_on   0

Definition at line 75 of file timetravel.c.

Referenced by timetravel().

#define a_upd_user   3

Definition at line 78 of file timetravel.c.

#define MaxAttrNum   5

Definition at line 72 of file timetravel.c.

Referenced by timetravel().

#define MinAttrNum   2

Definition at line 73 of file timetravel.c.

Referenced by timetravel().


Typedef Documentation

typedef struct _TTOffList TTOffList

Function Documentation

static EPlan * find_plan ( char *  ident,
EPlan **  eplan,
int *  nplans 
) [static]

Definition at line 520 of file timetravel.c.

References i, EPlan::ident, malloc, realloc, and EPlan::splan.

Referenced by timetravel().

{
    EPlan      *newp;
    int         i;

    if (*nplans > 0)
    {
        for (i = 0; i < *nplans; i++)
        {
            if (strcmp((*eplan)[i].ident, ident) == 0)
                break;
        }
        if (i != *nplans)
            return (*eplan + i);
        *eplan = (EPlan *) realloc(*eplan, (i + 1) * sizeof(EPlan));
        newp = *eplan + i;
    }
    else
    {
        newp = *eplan = (EPlan *) malloc(sizeof(EPlan));
        (*nplans) = i = 0;
    }

    newp->ident = (char *) malloc(strlen(ident) + 1);
    strcpy(newp->ident, ident);
    newp->splan = NULL;
    (*nplans)++;

    return (newp);
}

static int findTTStatus ( char *  name  )  [static]

Definition at line 501 of file timetravel.c.

References _TTOffList::name, _TTOffList::next, and pg_strcasecmp().

Referenced by timetravel().

{
    TTOffList  *pp;

    for (pp = TTOff.next; pp; pp = pp->next)
        if (pg_strcasecmp(name, pp->name) == 0)
            return 0;
    return 1;
}

Datum get_timetravel ( PG_FUNCTION_ARGS   ) 

Definition at line 487 of file timetravel.c.

References _TTOffList::name, namestrcmp(), _TTOffList::next, PG_GETARG_NAME, and PG_RETURN_INT32.

{
    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_FUNCTION_INFO_V1 ( set_timetravel   ) 
PG_FUNCTION_INFO_V1 ( get_timetravel   ) 
PG_FUNCTION_INFO_V1 ( timetravel   ) 
Datum set_timetravel ( PG_FUNCTION_ARGS   ) 

Definition at line 426 of file timetravel.c.

References DatumGetCString, DirectFunctionCall1, free, malloc, _TTOffList::name, NameGetDatum, nameout(), namestrcmp(), _TTOffList::next, next(), pfree(), PG_GETARG_INT32, PG_GETARG_NAME, and PG_RETURN_INT32.

{
    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 (pp)
    {
        /* OFF currently */
        if (on != 0)
        {
            /* turn ON */
            p->next = pp->next;
            free(pp);
        }
        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);
}

Datum timetravel ( PG_FUNCTION_ARGS   ) 

Definition at line 84 of file timetravel.c.

References a_time_off, a_time_on, ABSTIMEOID, CALLED_AS_TRIGGER, CStringGetTextDatum, DatumGetInt32, DEBUG4, elog, ERROR, find_plan(), findTTStatus(), GetCurrentAbsoluteTime(), GetUserId(), GetUserNameFromId(), i, MaxAttrNum, MinAttrNum, NAMEDATALEN, NOEND_ABSTIME, nPlans, NULL, palloc(), pfree(), PointerGetDatum, snprintf(), SPI_connect(), SPI_execp(), SPI_finish(), SPI_fnumber(), SPI_getbinval(), SPI_getrelname(), SPI_gettypeid(), SPI_keepplan(), SPI_modifytuple(), SPI_prepare(), SPI_result, TEXTOID, TriggerData::tg_event, TriggerData::tg_newtuple, TriggerData::tg_relation, TriggerData::tg_trigger, TriggerData::tg_trigtuple, Trigger::tgnargs, TRIGGER_FIRED_BEFORE, TRIGGER_FIRED_BY_INSERT, TRIGGER_FIRED_BY_UPDATE, and TRIGGER_FIRED_FOR_ROW.

{
    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 */
    char        ident[2 * NAMEDATALEN];
    bool        isnull;         /* to know is some column NULL or not */
    bool        isinsert = false;
    int         ret;
    int         i;

    /*
     * Some checks first...
     */

    /* Called by trigger manager ? */
    if (!CALLED_AS_TRIGGER(fcinfo))
        elog(ERROR, "timetravel: not fired by trigger manager");

    /* Should be called for ROW trigger */
    if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
        elog(ERROR, "timetravel: must be fired for row");

    /* Should be called BEFORE */
    if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
        elog(ERROR, "timetravel: must be fired before event");

    /* INSERT ? */
    if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
        isinsert = true;

    if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
        newtuple = trigdata->tg_newtuple;

    trigtuple = trigdata->tg_trigtuple;

    rel = trigdata->tg_relation;
    relname = SPI_getrelname(rel);

    /* check if TT is OFF for this relation */
    if (0 == findTTStatus(relname))
    {
        /* OFF - nothing to do */
        pfree(relname);
        return PointerGetDatum((newtuple != NULL) ? newtuple : trigtuple);
    }

    trigger = trigdata->tg_trigger;

    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 < MinAttrNum; i++)
    {
        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]) != ABSTIMEOID)
            elog(ERROR, "timetravel (%s): attribute %s must be of abstime type",
                 relname, args[i]);
    }
    for (; i < argc; i++)
    {
        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 = CStringGetTextDatum(GetUserNameFromId(GetUserId()));

    nulltext = (Datum) NULL;

    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();
            newnulls[chnattrs] = ' ';
            chattrs[chnattrs] = attnum[a_time_on];
            chnattrs++;
        }

        oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull);
        if (isnull)
        {
            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] = ' ';
            chattrs[chnattrs] = attnum[a_time_off];
            chnattrs++;
        }
        else
        {
            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)
            return PointerGetDatum(trigtuple);

        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 */
    }

    /* 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 */
        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 cannot 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 */
        pfree(relname);
        return PointerGetDatum(NULL);
    }

    newtimeoff = GetCurrentAbsoluteTime();

    /* Connect to SPI manager */
    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++)
    {
        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 */
    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] = ' ';
        }
    }

    /*
     * Construct ident string as TriggerName $ TriggeredRelationId and try to
     * find prepared execution plan.
     */
    snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
    plan = find_plan(ident, &Plans, &nPlans);

    /* if there is no plan ... */
    if (plan->splan == NULL)
    {
        SPIPlanPtr  pplan;
        Oid        *ctypes;
        char        sql[8192];
        char        separ = ' ';

        /* allocate ctypes for preparation */
        ctypes = (Oid *) palloc(natts * sizeof(Oid));

        /*
         * Construct query: INSERT INTO _relation_ VALUES ($1, ...)
         */
        snprintf(sql, sizeof(sql), "INSERT INTO %s VALUES (", relname);
        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), "%c$%d", separ, i);
                separ = ',';
            }
        }
        snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ")");

        elog(DEBUG4, "timetravel (%s) update: sql: %s", relname, sql);

        /* Prepare plan for query */
        pplan = SPI_prepare(sql, natts, ctypes);
        if (pplan == NULL)
            elog(ERROR, "timetravel (%s): SPI_prepare returned %d", relname, SPI_result);

        /*
         * Remember that SPI_prepare places plan in current memory context -
         * so, we have to save plan in Top memory context for later use.
         */
        if (SPI_keepplan(pplan))
            elog(ERROR, "timetravel (%s): SPI_keepplan failed", relname);

        plan->splan = pplan;
    }

    /*
     * Ok, execute prepared plan.
     */
    ret = SPI_execp(plan->splan, cvals, cnulls, 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];
        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++;
        }

        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); */
    }
    else
        /* DELETE case */
        rettuple = trigtuple;

    SPI_finish();               /* don't forget say Bye to SPI mgr */

    pfree(relname);
    return PointerGetDatum(rettuple);
}


Variable Documentation

int nPlans = 0 [static]

Definition at line 36 of file timetravel.c.

Referenced by timetravel().

Definition at line 22 of file timetravel.c.

EPlan* Plans = NULL [static]

Definition at line 35 of file timetravel.c.

TTOffList TTOff = {NULL, {0}} [static]

Definition at line 44 of file timetravel.c.