00001
00002
00003
00004
00005
00006
00007
00008
00009
00010 #include "postgres.h"
00011
00012 #include <ctype.h>
00013
00014 #include "catalog/pg_type.h"
00015 #include "commands/trigger.h"
00016 #include "executor/spi.h"
00017 #include "miscadmin.h"
00018 #include "utils/builtins.h"
00019 #include "utils/nabstime.h"
00020 #include "utils/rel.h"
00021
00022 PG_MODULE_MAGIC;
00023
00024
00025 Datum timetravel(PG_FUNCTION_ARGS);
00026 Datum set_timetravel(PG_FUNCTION_ARGS);
00027 Datum get_timetravel(PG_FUNCTION_ARGS);
00028
00029 typedef struct
00030 {
00031 char *ident;
00032 SPIPlanPtr splan;
00033 } EPlan;
00034
00035 static EPlan *Plans = NULL;
00036 static int nPlans = 0;
00037
00038 typedef struct _TTOffList
00039 {
00040 struct _TTOffList *next;
00041 char name[1];
00042 } TTOffList;
00043
00044 static TTOffList TTOff = {NULL, {0}};
00045
00046 static int findTTStatus(char *name);
00047 static EPlan *find_plan(char *ident, EPlan **eplan, int *nplans);
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072 #define MaxAttrNum 5
00073 #define MinAttrNum 2
00074
00075 #define a_time_on 0
00076 #define a_time_off 1
00077 #define a_ins_user 2
00078 #define a_upd_user 3
00079 #define a_del_user 4
00080
00081 PG_FUNCTION_INFO_V1(timetravel);
00082
00083 Datum
00084 timetravel(PG_FUNCTION_ARGS)
00085 {
00086 TriggerData *trigdata = (TriggerData *) fcinfo->context;
00087 Trigger *trigger;
00088 int argc;
00089 char **args;
00090 int attnum[MaxAttrNum];
00091 Datum oldtimeon,
00092 oldtimeoff;
00093 Datum newtimeon,
00094 newtimeoff,
00095 newuser,
00096 nulltext;
00097 Datum *cvals;
00098 char *cnulls;
00099 char *relname;
00100 Relation rel;
00101 HeapTuple trigtuple;
00102 HeapTuple newtuple = NULL;
00103 HeapTuple rettuple;
00104 TupleDesc tupdesc;
00105 int natts;
00106 EPlan *plan;
00107 char ident[2 * NAMEDATALEN];
00108 bool isnull;
00109 bool isinsert = false;
00110 int ret;
00111 int i;
00112
00113
00114
00115
00116
00117
00118 if (!CALLED_AS_TRIGGER(fcinfo))
00119 elog(ERROR, "timetravel: not fired by trigger manager");
00120
00121
00122 if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
00123 elog(ERROR, "timetravel: must be fired for row");
00124
00125
00126 if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
00127 elog(ERROR, "timetravel: must be fired before event");
00128
00129
00130 if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
00131 isinsert = true;
00132
00133 if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
00134 newtuple = trigdata->tg_newtuple;
00135
00136 trigtuple = trigdata->tg_trigtuple;
00137
00138 rel = trigdata->tg_relation;
00139 relname = SPI_getrelname(rel);
00140
00141
00142 if (0 == findTTStatus(relname))
00143 {
00144
00145 pfree(relname);
00146 return PointerGetDatum((newtuple != NULL) ? newtuple : trigtuple);
00147 }
00148
00149 trigger = trigdata->tg_trigger;
00150
00151 argc = trigger->tgnargs;
00152 if (argc != MinAttrNum && argc != MaxAttrNum)
00153 elog(ERROR, "timetravel (%s): invalid (!= %d or %d) number of arguments %d",
00154 relname, MinAttrNum, MaxAttrNum, trigger->tgnargs);
00155
00156 args = trigger->tgargs;
00157 tupdesc = rel->rd_att;
00158 natts = tupdesc->natts;
00159
00160 for (i = 0; i < MinAttrNum; i++)
00161 {
00162 attnum[i] = SPI_fnumber(tupdesc, args[i]);
00163 if (attnum[i] < 0)
00164 elog(ERROR, "timetravel (%s): there is no attribute %s", relname, args[i]);
00165 if (SPI_gettypeid(tupdesc, attnum[i]) != ABSTIMEOID)
00166 elog(ERROR, "timetravel (%s): attribute %s must be of abstime type",
00167 relname, args[i]);
00168 }
00169 for (; i < argc; i++)
00170 {
00171 attnum[i] = SPI_fnumber(tupdesc, args[i]);
00172 if (attnum[i] < 0)
00173 elog(ERROR, "timetravel (%s): there is no attribute %s", relname, args[i]);
00174 if (SPI_gettypeid(tupdesc, attnum[i]) != TEXTOID)
00175 elog(ERROR, "timetravel (%s): attribute %s must be of text type",
00176 relname, args[i]);
00177 }
00178
00179
00180 newuser = CStringGetTextDatum(GetUserNameFromId(GetUserId()));
00181
00182 nulltext = (Datum) NULL;
00183
00184 if (isinsert)
00185 {
00186 int chnattrs = 0;
00187 int chattrs[MaxAttrNum];
00188 Datum newvals[MaxAttrNum];
00189 char newnulls[MaxAttrNum];
00190
00191 oldtimeon = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_on], &isnull);
00192 if (isnull)
00193 {
00194 newvals[chnattrs] = GetCurrentAbsoluteTime();
00195 newnulls[chnattrs] = ' ';
00196 chattrs[chnattrs] = attnum[a_time_on];
00197 chnattrs++;
00198 }
00199
00200 oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull);
00201 if (isnull)
00202 {
00203 if ((chnattrs == 0 && DatumGetInt32(oldtimeon) >= NOEND_ABSTIME) ||
00204 (chnattrs > 0 && DatumGetInt32(newvals[a_time_on]) >= NOEND_ABSTIME))
00205 elog(ERROR, "timetravel (%s): %s is infinity", relname, args[a_time_on]);
00206 newvals[chnattrs] = NOEND_ABSTIME;
00207 newnulls[chnattrs] = ' ';
00208 chattrs[chnattrs] = attnum[a_time_off];
00209 chnattrs++;
00210 }
00211 else
00212 {
00213 if ((chnattrs == 0 && DatumGetInt32(oldtimeon) > DatumGetInt32(oldtimeoff)) ||
00214 (chnattrs > 0 && DatumGetInt32(newvals[a_time_on]) > DatumGetInt32(oldtimeoff)))
00215 elog(ERROR, "timetravel (%s): %s gt %s", relname, args[a_time_on], args[a_time_off]);
00216 }
00217
00218 pfree(relname);
00219 if (chnattrs <= 0)
00220 return PointerGetDatum(trigtuple);
00221
00222 if (argc == MaxAttrNum)
00223 {
00224
00225 newvals[chnattrs] = nulltext;
00226 newnulls[chnattrs] = 'n';
00227 chattrs[chnattrs] = attnum[a_upd_user];
00228 chnattrs++;
00229
00230 newvals[chnattrs] = nulltext;
00231 newnulls[chnattrs] = 'n';
00232 chattrs[chnattrs] = attnum[a_del_user];
00233 chnattrs++;
00234
00235 newvals[chnattrs] = newuser;
00236 newnulls[chnattrs] = ' ';
00237 chattrs[chnattrs] = attnum[a_ins_user];
00238 chnattrs++;
00239 }
00240 rettuple = SPI_modifytuple(rel, trigtuple, chnattrs, chattrs, newvals, newnulls);
00241 return PointerGetDatum(rettuple);
00242
00243 }
00244
00245
00246 oldtimeon = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_on], &isnull);
00247 if (isnull)
00248 elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_on]);
00249
00250 oldtimeoff = SPI_getbinval(trigtuple, tupdesc, attnum[a_time_off], &isnull);
00251 if (isnull)
00252 elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_off]);
00253
00254
00255
00256
00257
00258 if (newtuple != NULL)
00259 {
00260 newtimeon = SPI_getbinval(newtuple, tupdesc, attnum[a_time_on], &isnull);
00261 if (isnull)
00262 elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_on]);
00263
00264 newtimeoff = SPI_getbinval(newtuple, tupdesc, attnum[a_time_off], &isnull);
00265 if (isnull)
00266 elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[a_time_off]);
00267
00268 if (oldtimeon != newtimeon || oldtimeoff != newtimeoff)
00269 elog(ERROR, "timetravel (%s): you cannot change %s and/or %s columns (use set_timetravel)",
00270 relname, args[a_time_on], args[a_time_off]);
00271 }
00272 if (oldtimeoff != NOEND_ABSTIME)
00273 {
00274 pfree(relname);
00275 return PointerGetDatum(NULL);
00276 }
00277
00278 newtimeoff = GetCurrentAbsoluteTime();
00279
00280
00281 if ((ret = SPI_connect()) < 0)
00282 elog(ERROR, "timetravel (%s): SPI_connect returned %d", relname, ret);
00283
00284
00285 cvals = (Datum *) palloc(natts * sizeof(Datum));
00286 cnulls = (char *) palloc(natts * sizeof(char));
00287 for (i = 0; i < natts; i++)
00288 {
00289 cvals[i] = SPI_getbinval(trigtuple, tupdesc, i + 1, &isnull);
00290 cnulls[i] = (isnull) ? 'n' : ' ';
00291 }
00292
00293
00294 cvals[attnum[a_time_off] - 1] = newtimeoff;
00295 cnulls[attnum[a_time_off] - 1] = ' ';
00296
00297 if (!newtuple)
00298 {
00299 if (argc == MaxAttrNum)
00300 {
00301 cvals[attnum[a_del_user] - 1] = newuser;
00302 cnulls[attnum[a_del_user] - 1] = ' ';
00303 }
00304 }
00305
00306
00307
00308
00309
00310 snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
00311 plan = find_plan(ident, &Plans, &nPlans);
00312
00313
00314 if (plan->splan == NULL)
00315 {
00316 SPIPlanPtr pplan;
00317 Oid *ctypes;
00318 char sql[8192];
00319 char separ = ' ';
00320
00321
00322 ctypes = (Oid *) palloc(natts * sizeof(Oid));
00323
00324
00325
00326
00327 snprintf(sql, sizeof(sql), "INSERT INTO %s VALUES (", relname);
00328 for (i = 1; i <= natts; i++)
00329 {
00330 ctypes[i - 1] = SPI_gettypeid(tupdesc, i);
00331 if (!(tupdesc->attrs[i - 1]->attisdropped))
00332 {
00333 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%c$%d", separ, i);
00334 separ = ',';
00335 }
00336 }
00337 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), ")");
00338
00339 elog(DEBUG4, "timetravel (%s) update: sql: %s", relname, sql);
00340
00341
00342 pplan = SPI_prepare(sql, natts, ctypes);
00343 if (pplan == NULL)
00344 elog(ERROR, "timetravel (%s): SPI_prepare returned %d", relname, SPI_result);
00345
00346
00347
00348
00349
00350 if (SPI_keepplan(pplan))
00351 elog(ERROR, "timetravel (%s): SPI_keepplan failed", relname);
00352
00353 plan->splan = pplan;
00354 }
00355
00356
00357
00358
00359 ret = SPI_execp(plan->splan, cvals, cnulls, 0);
00360
00361 if (ret < 0)
00362 elog(ERROR, "timetravel (%s): SPI_execp returned %d", relname, ret);
00363
00364
00365 if (newtuple)
00366 {
00367 int chnattrs = 0;
00368 int chattrs[MaxAttrNum];
00369 Datum newvals[MaxAttrNum];
00370 char newnulls[MaxAttrNum];
00371
00372 newvals[chnattrs] = newtimeoff;
00373 newnulls[chnattrs] = ' ';
00374 chattrs[chnattrs] = attnum[a_time_on];
00375 chnattrs++;
00376
00377 newvals[chnattrs] = NOEND_ABSTIME;
00378 newnulls[chnattrs] = ' ';
00379 chattrs[chnattrs] = attnum[a_time_off];
00380 chnattrs++;
00381
00382 if (argc == MaxAttrNum)
00383 {
00384
00385 newvals[chnattrs] = newuser;
00386 newnulls[chnattrs] = ' ';
00387 chattrs[chnattrs] = attnum[a_upd_user];
00388 chnattrs++;
00389
00390 newvals[chnattrs] = nulltext;
00391 newnulls[chnattrs] = 'n';
00392 chattrs[chnattrs] = attnum[a_del_user];
00393 chnattrs++;
00394
00395 newvals[chnattrs] = nulltext;
00396 newnulls[chnattrs] = 'n';
00397 chattrs[chnattrs] = attnum[a_ins_user];
00398 chnattrs++;
00399 }
00400
00401 rettuple = SPI_modifytuple(rel, newtuple, chnattrs, chattrs, newvals, newnulls);
00402
00403
00404
00405
00406
00407
00408 }
00409 else
00410
00411 rettuple = trigtuple;
00412
00413 SPI_finish();
00414
00415 pfree(relname);
00416 return PointerGetDatum(rettuple);
00417 }
00418
00419
00420
00421
00422
00423 PG_FUNCTION_INFO_V1(set_timetravel);
00424
00425 Datum
00426 set_timetravel(PG_FUNCTION_ARGS)
00427 {
00428 Name relname = PG_GETARG_NAME(0);
00429 int32 on = PG_GETARG_INT32(1);
00430 char *rname;
00431 char *d;
00432 char *s;
00433 int32 ret;
00434 TTOffList *p,
00435 *pp;
00436
00437 for (pp = (p = &TTOff)->next; pp; pp = (p = pp)->next)
00438 {
00439 if (namestrcmp(relname, pp->name) == 0)
00440 break;
00441 }
00442 if (pp)
00443 {
00444
00445 if (on != 0)
00446 {
00447
00448 p->next = pp->next;
00449 free(pp);
00450 }
00451 ret = 0;
00452 }
00453 else
00454 {
00455
00456 if (on == 0)
00457 {
00458
00459 s = rname = DatumGetCString(DirectFunctionCall1(nameout, NameGetDatum(relname)));
00460 if (s)
00461 {
00462 pp = malloc(sizeof(TTOffList) + strlen(rname));
00463 if (pp)
00464 {
00465 pp->next = NULL;
00466 p->next = pp;
00467 d = pp->name;
00468 while (*s)
00469 *d++ = tolower((unsigned char) *s++);
00470 *d = '\0';
00471 }
00472 pfree(rname);
00473 }
00474 }
00475 ret = 1;
00476 }
00477 PG_RETURN_INT32(ret);
00478 }
00479
00480
00481
00482
00483
00484 PG_FUNCTION_INFO_V1(get_timetravel);
00485
00486 Datum
00487 get_timetravel(PG_FUNCTION_ARGS)
00488 {
00489 Name relname = PG_GETARG_NAME(0);
00490 TTOffList *pp;
00491
00492 for (pp = TTOff.next; pp; pp = pp->next)
00493 {
00494 if (namestrcmp(relname, pp->name) == 0)
00495 PG_RETURN_INT32(0);
00496 }
00497 PG_RETURN_INT32(1);
00498 }
00499
00500 static int
00501 findTTStatus(char *name)
00502 {
00503 TTOffList *pp;
00504
00505 for (pp = TTOff.next; pp; pp = pp->next)
00506 if (pg_strcasecmp(name, pp->name) == 0)
00507 return 0;
00508 return 1;
00509 }
00510
00511
00512
00513
00514
00515
00516
00517
00518
00519 static EPlan *
00520 find_plan(char *ident, EPlan **eplan, int *nplans)
00521 {
00522 EPlan *newp;
00523 int i;
00524
00525 if (*nplans > 0)
00526 {
00527 for (i = 0; i < *nplans; i++)
00528 {
00529 if (strcmp((*eplan)[i].ident, ident) == 0)
00530 break;
00531 }
00532 if (i != *nplans)
00533 return (*eplan + i);
00534 *eplan = (EPlan *) realloc(*eplan, (i + 1) * sizeof(EPlan));
00535 newp = *eplan + i;
00536 }
00537 else
00538 {
00539 newp = *eplan = (EPlan *) malloc(sizeof(EPlan));
00540 (*nplans) = i = 0;
00541 }
00542
00543 newp->ident = (char *) malloc(strlen(ident) + 1);
00544 strcpy(newp->ident, ident);
00545 newp->splan = NULL;
00546 (*nplans)++;
00547
00548 return (newp);
00549 }