~vcs-imports/mammoth-replicator/trunk

« back to all changes in this revision

Viewing changes to contrib/spi/timetravel.c

  • Committer: alvherre
  • Date: 2005-12-16 21:24:52 UTC
  • Revision ID: svn-v4:db760fc0-0f08-0410-9d63-cc6633f64896:trunk:1
Initial import of the REL8_0_3 sources from the Pgsql CVS repository.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * timetravel.c --      function to get time travel feature
 
3
 *              using general triggers.
 
4
 */
 
5
 
 
6
/* Modified by B�JTHE Zolt�n, Hungary, mailto:urdesobt@axelero.hu */
 
7
 
 
8
#include "executor/spi.h"               /* this is what you need to work with SPI */
 
9
#include "commands/trigger.h"   /* -"- and triggers */
 
10
#include "miscadmin.h"                  /* for GetPgUserName() */
 
11
#include <ctype.h>                              /* tolower () */
 
12
 
 
13
#define ABSTIMEOID      702                     /* it should be in pg_type.h */
 
14
 
 
15
/* AbsoluteTime currabstime(void); */
 
16
Datum           timetravel(PG_FUNCTION_ARGS);
 
17
Datum           set_timetravel(PG_FUNCTION_ARGS);
 
18
Datum           get_timetravel(PG_FUNCTION_ARGS);
 
19
 
 
20
typedef struct
 
21
{
 
22
        char       *ident;
 
23
        void       *splan;
 
24
}       EPlan;
 
25
 
 
26
static EPlan *Plans = NULL;             /* for UPDATE/DELETE */
 
27
static int      nPlans = 0;
 
28
 
 
29
typedef struct _TTOffList
 
30
{
 
31
        struct _TTOffList *next;
 
32
        char            name[1];
 
33
}       TTOffList;
 
34
 
 
35
static TTOffList TTOff = {NULL, {0}};
 
36
 
 
37
static int      findTTStatus(char *name);
 
38
static EPlan *find_plan(char *ident, EPlan ** eplan, int *nplans);
 
39
 
 
40
/*
 
41
 * timetravel () --
 
42
 *              1.      IF an update affects tuple with stop_date eq INFINITY
 
43
 *                      then form (and return) new tuple with start_date eq current date
 
44
 *                      and stop_date eq INFINITY [ and update_user eq current user ]
 
45
 *                      and all other column values as in new tuple, and insert tuple
 
46
 *                      with old data and stop_date eq current date
 
47
 *                      ELSE - skip updation of tuple.
 
48
 *              2.      IF an delete affects tuple with stop_date eq INFINITY
 
49
 *                      then insert the same tuple with stop_date eq current date
 
50
 *                      [ and delete_user eq current user ]
 
51
 *                      ELSE - skip deletion of tuple.
 
52
 *              3.      On INSERT, if start_date is NULL then current date will be
 
53
 *                      inserted, if stop_date is NULL then INFINITY will be inserted.
 
54
 *                      [ and insert_user eq current user, update_user and delete_user
 
55
 *                      eq NULL ]
 
56
 *
 
57
 * In CREATE TRIGGER you are to specify start_date and stop_date column
 
58
 * names:
 
59
 * EXECUTE PROCEDURE
 
60
 * timetravel ('date_on', 'date_off' [,'insert_user', 'update_user', 'delete_user' ] ).
 
61
 */
 
62
 
 
63
#define MaxAttrNum      5
 
64
#define MinAttrNum      2
 
65
 
 
66
#define a_time_on       0
 
67
#define a_time_off      1
 
68
#define a_ins_user      2
 
69
#define a_upd_user      3
 
70
#define a_del_user      4
 
71
 
 
72
PG_FUNCTION_INFO_V1(timetravel);
 
73
 
 
74
Datum                                                   /* have to return HeapTuple to Executor */
 
75
timetravel(PG_FUNCTION_ARGS)
 
76
{
 
77
        TriggerData *trigdata = (TriggerData *) fcinfo->context;
 
78
        Trigger    *trigger;            /* to get trigger name */
 
79
        int                     argc;
 
80
        char      **args;                       /* arguments */
 
81
        int                     attnum[MaxAttrNum];             /* fnumbers of start/stop columns */
 
82
        Datum           oldtimeon,
 
83
                                oldtimeoff;
 
84
        Datum           newtimeon,
 
85
                                newtimeoff,
 
86
                                newuser,
 
87
                                nulltext;
 
88
        Datum      *cvals;                      /* column values */
 
89
        char       *cnulls;                     /* column nulls */
 
90
        char       *relname;            /* triggered relation name */
 
91
        Relation        rel;                    /* triggered relation */
 
92
        HeapTuple       trigtuple;
 
93
        HeapTuple       newtuple = NULL;
 
94
        HeapTuple       rettuple;
 
95
        TupleDesc       tupdesc;                /* tuple description */
 
96
        int                     natts;                  /* # of attributes */
 
97
        EPlan      *plan;                       /* prepared plan */
 
98
        char            ident[2 * NAMEDATALEN];
 
99
        bool            isnull;                 /* to know is some column NULL or not */
 
100
        bool            isinsert = false;
 
101
        int                     ret;
 
102
        int                     i;
 
103
 
 
104
        /*
 
105
         * Some checks first...
 
106
         */
 
107
 
 
108
        /* Called by trigger manager ? */
 
109
        if (!CALLED_AS_TRIGGER(fcinfo))
 
110
                elog(ERROR, "timetravel: not fired by trigger manager");
 
111
 
 
112
        /* Should be called for ROW trigger */
 
113
        if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
 
114
                elog(ERROR, "timetravel: can't process STATEMENT events");
 
115
 
 
116
        /* Should be called BEFORE */
 
117
        if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
 
118
                elog(ERROR, "timetravel: must be fired before event");
 
119
 
 
120
        /* INSERT ? */
 
121
        if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
 
122
                isinsert = true;
 
123
 
 
124
        if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
 
125
                newtuple = trigdata->tg_newtuple;
 
126
 
 
127
        trigtuple = trigdata->tg_trigtuple;
 
128
 
 
129
        rel = trigdata->tg_relation;
 
130
        relname = SPI_getrelname(rel);
 
131
 
 
132
        /* check if TT is OFF for this relation */
 
133
        if (0 == findTTStatus(relname))
 
134
        {
 
135
                /* OFF - nothing to do */
 
136
                pfree(relname);
 
137
                return PointerGetDatum((newtuple != NULL) ? newtuple : trigtuple);
 
138
        }
 
139
 
 
140
        trigger = trigdata->tg_trigger;
 
141
 
 
142
        argc = trigger->tgnargs;
 
143
        if (argc != MinAttrNum && argc != MaxAttrNum)
 
144
                elog(ERROR, "timetravel (%s): invalid (!= %d or %d) number of arguments %d",
 
145
                         relname, MinAttrNum, MaxAttrNum, trigger->tgnargs);
 
146
 
 
147
        args = trigger->tgargs;
 
148
        tupdesc = rel->rd_att;
 
149
        natts = tupdesc->natts;
 
150
 
 
151
        for (i = 0; i < MinAttrNum; i++)
 
152
        {
 
153
                attnum[i] = SPI_fnumber(tupdesc, args[i]);
 
154
                if (attnum[i] < 0)
 
155
                        elog(ERROR, "timetravel (%s): there is no attribute %s", relname, args[i]);
 
156
                if (SPI_gettypeid(tupdesc, attnum[i]) != ABSTIMEOID)
 
157
                        elog(ERROR, "timetravel (%s): attribute %s must be of abstime type",
 
158
                                 relname, args[i]);
 
159
        }
 
160
        for (; i < argc; i++)
 
161
        {
 
162
                attnum[i] = SPI_fnumber(tupdesc, args[i]);
 
163
                if (attnum[i] < 0)
 
164
                        elog(ERROR, "timetravel (%s): there is no attribute %s", relname, args[i]);
 
165
                if (SPI_gettypeid(tupdesc, attnum[i]) != TEXTOID)
 
166
                        elog(ERROR, "timetravel (%s): attribute %s must be of text type",
 
167
                                 relname, args[i]);
 
168
        }
 
169
 
 
170
        /* create fields containing name */
 
171
        newuser = DirectFunctionCall1(textin, CStringGetDatum(GetUserNameFromId(GetUserId())));
 
172
 
 
173
        nulltext = (Datum) NULL;
 
174
 
 
175
        if (isinsert)
 
176
        {                                                       /* INSERT */
 
177
                int                     chnattrs = 0;
 
178
                int                     chattrs[MaxAttrNum];
 
179
                Datum           newvals[MaxAttrNum];
 
180
                char            newnulls[MaxAttrNum];
 
181
 
 
182
                oldtimeon = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_on], &isnull);
 
183
                if (isnull)
 
184
                {
 
185
                        newvals[chnattrs] = GetCurrentAbsoluteTime();
 
186
                        newnulls[chnattrs] = ' ';
 
187
                        chattrs[chnattrs] = attnum[a_time_on];
 
188
                        chnattrs++;
 
189
                }
 
190
 
 
191
                oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull);
 
192
                if (isnull)
 
193
                {
 
194
                        if ((chnattrs == 0 && DatumGetInt32(oldtimeon) >= NOEND_ABSTIME) ||
 
195
                                (chnattrs > 0 && DatumGetInt32(newvals[a_time_on]) >= NOEND_ABSTIME))
 
196
                                elog(ERROR, "timetravel (%s): %s is infinity", relname, args[a_time_on]);
 
197
                        newvals[chnattrs] = NOEND_ABSTIME;
 
198
                        newnulls[chnattrs] = ' ';
 
199
                        chattrs[chnattrs] = attnum[a_time_off];
 
200
                        chnattrs++;
 
201
                }
 
202
                else
 
203
                {
 
204
                        if ((chnattrs == 0 && DatumGetInt32(oldtimeon) > DatumGetInt32(oldtimeoff)) ||
 
205
                                (chnattrs > 0 && DatumGetInt32(newvals[a_time_on]) > DatumGetInt32(oldtimeoff)))
 
206
                                elog(ERROR, "timetravel (%s): %s gt %s", relname, args[a_time_on], args[a_time_off]);
 
207
                }
 
208
 
 
209
                pfree(relname);
 
210
                if (chnattrs <= 0)
 
211
                        return PointerGetDatum(trigtuple);
 
212
 
 
213
                if (argc == MaxAttrNum)
 
214
                {
 
215
                        /* clear update_user value */
 
216
                        newvals[chnattrs] = nulltext;
 
217
                        newnulls[chnattrs] = 'n';
 
218
                        chattrs[chnattrs] = attnum[a_upd_user];
 
219
                        chnattrs++;
 
220
                        /* clear delete_user value */
 
221
                        newvals[chnattrs] = nulltext;
 
222
                        newnulls[chnattrs] = 'n';
 
223
                        chattrs[chnattrs] = attnum[a_del_user];
 
224
                        chnattrs++;
 
225
                        /* set insert_user value */
 
226
                        newvals[chnattrs] = newuser;
 
227
                        newnulls[chnattrs] = ' ';
 
228
                        chattrs[chnattrs] = attnum[a_ins_user];
 
229
                        chnattrs++;
 
230
                }
 
231
                rettuple = SPI_modifytuple(rel, trigtuple, chnattrs, chattrs, newvals, newnulls);
 
232
                return PointerGetDatum(rettuple);
 
233
                /* end of INSERT */
 
234
        }
 
235
 
 
236
        /* UPDATE/DELETE: */
 
237
        oldtimeon = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_on], &isnull);
 
238
        if (isnull)
 
239
                elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_on]);
 
240
 
 
241
        oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull);
 
242
        if (isnull)
 
243
                elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_off]);
 
244
 
 
245
        /*
 
246
         * If DELETE/UPDATE of tuple with stop_date neq INFINITY then say
 
247
         * upper Executor to skip operation for this tuple
 
248
         */
 
249
        if (newtuple != NULL)
 
250
        {                                                       /* UPDATE */
 
251
                newtimeon = SPI_getbinval(newtuple, tupdesc, attnum[a_time_on], &isnull);
 
252
                if (isnull)
 
253
                        elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_on]);
 
254
 
 
255
                newtimeoff = SPI_getbinval(newtuple, tupdesc, attnum[a_time_off], &isnull);
 
256
                if (isnull)
 
257
                        elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_off]);
 
258
 
 
259
                if (oldtimeon != newtimeon || oldtimeoff != newtimeoff)
 
260
                        elog(ERROR, "timetravel (%s): you can't change %s and/or %s columns (use set_timetravel)",
 
261
                                 relname, args[a_time_on], args[a_time_off]);
 
262
        }
 
263
        if (oldtimeoff != NOEND_ABSTIME)
 
264
        {                                                       /* current record is a deleted/updated
 
265
                                                                 * record */
 
266
                pfree(relname);
 
267
                return PointerGetDatum(NULL);
 
268
        }
 
269
 
 
270
        newtimeoff = GetCurrentAbsoluteTime();
 
271
 
 
272
        /* Connect to SPI manager */
 
273
        if ((ret = SPI_connect()) < 0)
 
274
                elog(ERROR, "timetravel (%s): SPI_connect returned %d", relname, ret);
 
275
 
 
276
        /* Fetch tuple values and nulls */
 
277
        cvals = (Datum *) palloc(natts * sizeof(Datum));
 
278
        cnulls = (char *) palloc(natts * sizeof(char));
 
279
        for (i = 0; i < natts; i++)
 
280
        {
 
281
                cvals[i] = SPI_getbinval(trigtuple, tupdesc, i + 1, &isnull);
 
282
                cnulls[i] = (isnull) ? 'n' : ' ';
 
283
        }
 
284
 
 
285
        /* change date column(s) */
 
286
        cvals[attnum[a_time_off] - 1] = newtimeoff; /* stop_date eq current
 
287
                                                                                                 * date */
 
288
        cnulls[attnum[a_time_off] - 1] = ' ';
 
289
 
 
290
        if (!newtuple)
 
291
        {                                                       /* DELETE */
 
292
                if (argc == MaxAttrNum)
 
293
                {
 
294
                        cvals[attnum[a_del_user] - 1] = newuser;        /* set delete user */
 
295
                        cnulls[attnum[a_del_user] - 1] = ' ';
 
296
                }
 
297
        }
 
298
 
 
299
        /*
 
300
         * Construct ident string as TriggerName $ TriggeredRelationId and try
 
301
         * to find prepared execution plan.
 
302
         */
 
303
        snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
 
304
        plan = find_plan(ident, &Plans, &nPlans);
 
305
 
 
306
        /* if there is no plan ... */
 
307
        if (plan->splan == NULL)
 
308
        {
 
309
                void       *pplan;
 
310
                Oid                *ctypes;
 
311
                char            sql[8192];
 
312
                char            separ = ' ';
 
313
 
 
314
                /* allocate ctypes for preparation */
 
315
                ctypes = (Oid *) palloc(natts * sizeof(Oid));
 
316
 
 
317
                /*
 
318
                 * Construct query: INSERT INTO _relation_ VALUES ($1, ...)
 
319
                 */
 
320
                snprintf(sql, sizeof(sql), "INSERT INTO %s VALUES (", relname);
 
321
                for (i = 1; i <= natts; i++)
 
322
                {
 
323
                        ctypes[i - 1] = SPI_gettypeid(tupdesc, i);
 
324
                        if (!(tupdesc->attrs[i - 1]->attisdropped)) /* skip dropped columns */
 
325
                        {
 
326
                                snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%c$%d", separ, i);
 
327
                                separ = ',';
 
328
                        }
 
329
                }
 
330
                snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ")");
 
331
 
 
332
                elog(DEBUG4, "timetravel (%s) update: sql: %s", relname, sql);
 
333
 
 
334
                /* Prepare plan for query */
 
335
                pplan = SPI_prepare(sql, natts, ctypes);
 
336
                if (pplan == NULL)
 
337
                        elog(ERROR, "timetravel (%s): SPI_prepare returned %d", relname, SPI_result);
 
338
 
 
339
                /*
 
340
                 * Remember that SPI_prepare places plan in current memory context
 
341
                 * - so, we have to save plan in Top memory context for latter
 
342
                 * use.
 
343
                 */
 
344
                pplan = SPI_saveplan(pplan);
 
345
                if (pplan == NULL)
 
346
                        elog(ERROR, "timetravel (%s): SPI_saveplan returned %d", relname, SPI_result);
 
347
 
 
348
                plan->splan = pplan;
 
349
        }
 
350
 
 
351
        /*
 
352
         * Ok, execute prepared plan.
 
353
         */
 
354
        ret = SPI_execp(plan->splan, cvals, cnulls, 0);
 
355
 
 
356
        if (ret < 0)
 
357
                elog(ERROR, "timetravel (%s): SPI_execp returned %d", relname, ret);
 
358
 
 
359
        /* Tuple to return to upper Executor ... */
 
360
        if (newtuple)
 
361
        {                                                       /* UPDATE */
 
362
                int                     chnattrs = 0;
 
363
                int                     chattrs[MaxAttrNum];
 
364
                Datum           newvals[MaxAttrNum];
 
365
                char            newnulls[MaxAttrNum];
 
366
 
 
367
                newvals[chnattrs] = newtimeoff;
 
368
                newnulls[chnattrs] = ' ';
 
369
                chattrs[chnattrs] = attnum[a_time_on];
 
370
                chnattrs++;
 
371
 
 
372
                newvals[chnattrs] = NOEND_ABSTIME;
 
373
                newnulls[chnattrs] = ' ';
 
374
                chattrs[chnattrs] = attnum[a_time_off];
 
375
                chnattrs++;
 
376
 
 
377
                if (argc == MaxAttrNum)
 
378
                {
 
379
                        /* set update_user value */
 
380
                        newvals[chnattrs] = newuser;
 
381
                        newnulls[chnattrs] = ' ';
 
382
                        chattrs[chnattrs] = attnum[a_upd_user];
 
383
                        chnattrs++;
 
384
                        /* clear delete_user value */
 
385
                        newvals[chnattrs] = nulltext;
 
386
                        newnulls[chnattrs] = 'n';
 
387
                        chattrs[chnattrs] = attnum[a_del_user];
 
388
                        chnattrs++;
 
389
                        /* set insert_user value */
 
390
                        newvals[chnattrs] = nulltext;
 
391
                        newnulls[chnattrs] = 'n';
 
392
                        chattrs[chnattrs] = attnum[a_ins_user];
 
393
                        chnattrs++;
 
394
                }
 
395
 
 
396
                rettuple = SPI_modifytuple(rel, newtuple, chnattrs, chattrs, newvals, newnulls);
 
397
 
 
398
                /*
 
399
                 * SPI_copytuple allocates tmptuple in upper executor context -
 
400
                 * have to free allocation using SPI_pfree
 
401
                 */
 
402
                /* SPI_pfree(tmptuple); */
 
403
        }
 
404
        else
 
405
                /* DELETE case */
 
406
                rettuple = trigtuple;
 
407
 
 
408
        SPI_finish();                           /* don't forget say Bye to SPI mgr */
 
409
 
 
410
        pfree(relname);
 
411
        return PointerGetDatum(rettuple);
 
412
}
 
413
 
 
414
/*
 
415
 * set_timetravel (relname, on) --
 
416
 *                                      turn timetravel for specified relation ON/OFF
 
417
 */
 
418
PG_FUNCTION_INFO_V1(set_timetravel);
 
419
 
 
420
Datum
 
421
set_timetravel(PG_FUNCTION_ARGS)
 
422
{
 
423
        Name            relname = PG_GETARG_NAME(0);
 
424
        int32           on = PG_GETARG_INT32(1);
 
425
        char       *rname;
 
426
        char       *d;
 
427
        char       *s;
 
428
        int32           ret;
 
429
        TTOffList  *p,
 
430
                           *pp;
 
431
 
 
432
        for (pp = (p = &TTOff)->next; pp; pp = (p = pp)->next)
 
433
        {
 
434
                if (namestrcmp(relname, pp->name) == 0)
 
435
                        break;
 
436
        }
 
437
        if (pp)
 
438
        {
 
439
                /* OFF currently */
 
440
                if (on != 0)
 
441
                {
 
442
                        /* turn ON */
 
443
                        p->next = pp->next;
 
444
                        free(pp);
 
445
                }
 
446
                ret = 0;
 
447
        }
 
448
        else
 
449
        {
 
450
                /* ON currently */
 
451
                if (on == 0)
 
452
                {
 
453
                        /* turn OFF */
 
454
                        s = rname = DatumGetCString(DirectFunctionCall1(nameout, NameGetDatum(relname)));
 
455
                        if (s)
 
456
                        {
 
457
                                pp = malloc(sizeof(TTOffList) + strlen(rname));
 
458
                                if (pp)
 
459
                                {
 
460
                                        pp->next = NULL;
 
461
                                        p->next = pp;
 
462
                                        d = pp->name;
 
463
                                        while (*s)
 
464
                                                *d++ = tolower((unsigned char) *s++);
 
465
                                        *d = '\0';
 
466
                                }
 
467
                                pfree(rname);
 
468
                        }
 
469
                }
 
470
                ret = 1;
 
471
        }
 
472
        PG_RETURN_INT32(ret);
 
473
}
 
474
 
 
475
/*
 
476
 * get_timetravel (relname) --
 
477
 *      get timetravel status for specified relation (ON/OFF)
 
478
 */
 
479
PG_FUNCTION_INFO_V1(get_timetravel);
 
480
 
 
481
Datum
 
482
get_timetravel(PG_FUNCTION_ARGS)
 
483
{
 
484
        Name            relname = PG_GETARG_NAME(0);
 
485
        TTOffList  *pp;
 
486
 
 
487
        for (pp = TTOff.next; pp; pp = pp->next)
 
488
        {
 
489
                if (namestrcmp(relname, pp->name) == 0)
 
490
                        PG_RETURN_INT32(0);
 
491
        }
 
492
        PG_RETURN_INT32(1);
 
493
}
 
494
 
 
495
static int
 
496
findTTStatus(char *name)
 
497
{
 
498
        TTOffList  *pp;
 
499
 
 
500
        for (pp = TTOff.next; pp; pp = pp->next)
 
501
                if (pg_strcasecmp(name, pp->name) == 0)
 
502
                        return 0;
 
503
        return 1;
 
504
}
 
505
 
 
506
/*
 
507
AbsoluteTime
 
508
currabstime()
 
509
{
 
510
        return (GetCurrentAbsoluteTime());
 
511
}
 
512
*/
 
513
 
 
514
static EPlan *
 
515
find_plan(char *ident, EPlan ** eplan, int *nplans)
 
516
{
 
517
        EPlan      *newp;
 
518
        int                     i;
 
519
 
 
520
        if (*nplans > 0)
 
521
        {
 
522
                for (i = 0; i < *nplans; i++)
 
523
                {
 
524
                        if (strcmp((*eplan)[i].ident, ident) == 0)
 
525
                                break;
 
526
                }
 
527
                if (i != *nplans)
 
528
                        return (*eplan + i);
 
529
                *eplan = (EPlan *) realloc(*eplan, (i + 1) * sizeof(EPlan));
 
530
                newp = *eplan + i;
 
531
        }
 
532
        else
 
533
        {
 
534
                newp = *eplan = (EPlan *) malloc(sizeof(EPlan));
 
535
                (*nplans) = i = 0;
 
536
        }
 
537
 
 
538
        newp->ident = (char *) malloc(strlen(ident) + 1);
 
539
        strcpy(newp->ident, ident);
 
540
        newp->splan = NULL;
 
541
        (*nplans)++;
 
542
 
 
543
        return (newp);
 
544
}