timetravel.c
上传用户:blenddy
上传日期:2007-01-07
资源大小:6495k
文件大小:10k
源码类别:

数据库系统

开发平台:

Unix_Linux

  1. /*
  2.  * timetravel.c -- function to get time travel feature
  3.  * using general triggers.
  4.  */
  5. #include "executor/spi.h" /* this is what you need to work with SPI */
  6. #include "commands/trigger.h" /* -"- and triggers */
  7. #include <ctype.h> /* tolower () */
  8. #define ABSTIMEOID 702 /* it should be in pg_type.h */
  9. AbsoluteTime currabstime(void);
  10. HeapTuple timetravel(void);
  11. int32 set_timetravel(Name relname, int32 on);
  12. typedef struct
  13. {
  14. char    *ident;
  15. void    *splan;
  16. } EPlan;
  17. static EPlan *Plans = NULL; /* for UPDATE/DELETE */
  18. static int nPlans = 0;
  19. static char **TTOff = NULL;
  20. static int nTTOff = 0;
  21. static EPlan *find_plan(char *ident, EPlan ** eplan, int *nplans);
  22. /*
  23.  * timetravel () --
  24.  * 1. IF an update affects tuple with stop_date eq INFINITY
  25.  * then form (and return) new tuple with stop_date eq current date
  26.  * and all other column values as in old tuple, and insert tuple
  27.  * with new data and start_date eq current date and
  28.  * stop_date eq INFINITY
  29.  * ELSE - skip updation of tuple.
  30.  * 2. IF an delete affects tuple with stop_date eq INFINITY
  31.  * then insert the same tuple with stop_date eq current date
  32.  * ELSE - skip deletion of tuple.
  33.  * 3. On INSERT, if start_date is NULL then current date will be
  34.  * inserted, if stop_date is NULL then INFINITY will be inserted.
  35.  *
  36.  * In CREATE TRIGGER you are to specify start_date and stop_date column
  37.  * names:
  38.  * EXECUTE PROCEDURE
  39.  * timetravel ('date_on', 'date_off').
  40.  */
  41. HeapTuple /* have to return HeapTuple to Executor */
  42. timetravel()
  43. {
  44. Trigger    *trigger; /* to get trigger name */
  45. char   **args; /* arguments */
  46. int attnum[2]; /* fnumbers of start/stop columns */
  47. Datum oldon,
  48. oldoff;
  49. Datum newon,
  50. newoff;
  51. Datum    *cvals; /* column values */
  52. char    *cnulls; /* column nulls */
  53. char    *relname; /* triggered relation name */
  54. Relation rel; /* triggered relation */
  55. HeapTuple trigtuple;
  56. HeapTuple newtuple = NULL;
  57. HeapTuple rettuple;
  58. TupleDesc tupdesc; /* tuple description */
  59. int natts; /* # of attributes */
  60. EPlan    *plan; /* prepared plan */
  61. char ident[2 * NAMEDATALEN];
  62. bool isnull; /* to know is some column NULL or not */
  63. bool isinsert = false;
  64. int ret;
  65. int i;
  66. /*
  67.  * Some checks first...
  68.  */
  69. /* Called by trigger manager ? */
  70. if (!CurrentTriggerData)
  71. elog(ERROR, "timetravel: triggers are not initialized");
  72. /* Should be called for ROW trigger */
  73. if (TRIGGER_FIRED_FOR_STATEMENT(CurrentTriggerData->tg_event))
  74. elog(ERROR, "timetravel: can't process STATEMENT events");
  75. /* Should be called BEFORE */
  76. if (TRIGGER_FIRED_AFTER(CurrentTriggerData->tg_event))
  77. elog(ERROR, "timetravel: must be fired before event");
  78. /* INSERT ? */
  79. if (TRIGGER_FIRED_BY_INSERT(CurrentTriggerData->tg_event))
  80. isinsert = true;
  81. if (TRIGGER_FIRED_BY_UPDATE(CurrentTriggerData->tg_event))
  82. newtuple = CurrentTriggerData->tg_newtuple;
  83. trigtuple = CurrentTriggerData->tg_trigtuple;
  84. rel = CurrentTriggerData->tg_relation;
  85. relname = SPI_getrelname(rel);
  86. /* check if TT is OFF for this relation */
  87. for (i = 0; i < nTTOff; i++)
  88. if (strcasecmp(TTOff[i], relname) == 0)
  89. break;
  90. if (i < nTTOff) /* OFF - nothing to do */
  91. {
  92. pfree(relname);
  93. return ((newtuple != NULL) ? newtuple : trigtuple);
  94. }
  95. trigger = CurrentTriggerData->tg_trigger;
  96. if (trigger->tgnargs != 2)
  97. elog(ERROR, "timetravel (%s): invalid (!= 2) number of arguments %d",
  98.  relname, trigger->tgnargs);
  99. args = trigger->tgargs;
  100. tupdesc = rel->rd_att;
  101. natts = tupdesc->natts;
  102. /*
  103.  * Setting CurrentTriggerData to NULL prevents direct calls to trigger
  104.  * functions in queries. Normally, trigger functions have to be called
  105.  * by trigger manager code only.
  106.  */
  107. CurrentTriggerData = NULL;
  108. for (i = 0; i < 2; i++)
  109. {
  110. attnum[i] = SPI_fnumber(tupdesc, args[i]);
  111. if (attnum[i] < 0)
  112. elog(ERROR, "timetravel (%s): there is no attribute %s", relname, args[i]);
  113. if (SPI_gettypeid(tupdesc, attnum[i]) != ABSTIMEOID)
  114. elog(ERROR, "timetravel (%s): attributes %s and %s must be of abstime type",
  115.  relname, args[0], args[1]);
  116. }
  117. if (isinsert) /* INSERT */
  118. {
  119. int chnattrs = 0;
  120. int chattrs[2];
  121. Datum newvals[2];
  122. oldon = SPI_getbinval(trigtuple, tupdesc, attnum[0], &isnull);
  123. if (isnull)
  124. {
  125. newvals[chnattrs] = GetCurrentAbsoluteTime();
  126. chattrs[chnattrs] = attnum[0];
  127. chnattrs++;
  128. }
  129. oldoff = SPI_getbinval(trigtuple, tupdesc, attnum[1], &isnull);
  130. if (isnull)
  131. {
  132. if ((chnattrs == 0 && DatumGetInt32(oldon) >= NOEND_ABSTIME) ||
  133. (chnattrs > 0 && DatumGetInt32(newvals[0]) >= NOEND_ABSTIME))
  134. elog(ERROR, "timetravel (%s): %s ge %s",
  135.  relname, args[0], args[1]);
  136. newvals[chnattrs] = NOEND_ABSTIME;
  137. chattrs[chnattrs] = attnum[1];
  138. chnattrs++;
  139. }
  140. else
  141. {
  142. if ((chnattrs == 0 && DatumGetInt32(oldon) >=
  143.  DatumGetInt32(oldoff)) ||
  144. (chnattrs > 0 && DatumGetInt32(newvals[0]) >=
  145.  DatumGetInt32(oldoff)))
  146. elog(ERROR, "timetravel (%s): %s ge %s",
  147.  relname, args[0], args[1]);
  148. }
  149. pfree(relname);
  150. if (chnattrs <= 0)
  151. return (trigtuple);
  152. rettuple = SPI_modifytuple(rel, trigtuple, chnattrs,
  153.    chattrs, newvals, NULL);
  154. return (rettuple);
  155. }
  156. oldon = SPI_getbinval(trigtuple, tupdesc, attnum[0], &isnull);
  157. if (isnull)
  158. elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[0]);
  159. oldoff = SPI_getbinval(trigtuple, tupdesc, attnum[1], &isnull);
  160. if (isnull)
  161. elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[1]);
  162. /*
  163.  * If DELETE/UPDATE of tuple with stop_date neq INFINITY then say
  164.  * upper Executor to skip operation for this tuple
  165.  */
  166. if (newtuple != NULL) /* UPDATE */
  167. {
  168. newon = SPI_getbinval(newtuple, tupdesc, attnum[0], &isnull);
  169. if (isnull)
  170. elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[0]);
  171. newoff = SPI_getbinval(newtuple, tupdesc, attnum[1], &isnull);
  172. if (isnull)
  173. elog(ERROR, "timetravel (%s): %s must be NOT NULL", relname, args[1]);
  174. if (oldon != newon || oldoff != newoff)
  175. elog(ERROR, "timetravel (%s): you can't change %s and/or %s columns (use set_timetravel)",
  176.  relname, args[0], args[1]);
  177. if (newoff != NOEND_ABSTIME)
  178. {
  179. pfree(relname); /* allocated in upper executor context */
  180. return (NULL);
  181. }
  182. }
  183. else if (oldoff != NOEND_ABSTIME) /* DELETE */
  184. {
  185. pfree(relname);
  186. return (NULL);
  187. }
  188. newoff = GetCurrentAbsoluteTime();
  189. /* Connect to SPI manager */
  190. if ((ret = SPI_connect()) < 0)
  191. elog(ERROR, "timetravel (%s): SPI_connect returned %d", relname, ret);
  192. /* Fetch tuple values and nulls */
  193. cvals = (Datum *) palloc(natts * sizeof(Datum));
  194. cnulls = (char *) palloc(natts * sizeof(char));
  195. for (i = 0; i < natts; i++)
  196. {
  197. cvals[i] = SPI_getbinval((newtuple != NULL) ? newtuple : trigtuple,
  198.  tupdesc, i + 1, &isnull);
  199. cnulls[i] = (isnull) ? 'n' : ' ';
  200. }
  201. /* change date column(s) */
  202. if (newtuple) /* UPDATE */
  203. {
  204. cvals[attnum[0] - 1] = newoff; /* start_date eq current date */
  205. cnulls[attnum[0] - 1] = ' ';
  206. cvals[attnum[1] - 1] = NOEND_ABSTIME; /* stop_date eq INFINITY */
  207. cnulls[attnum[1] - 1] = ' ';
  208. }
  209. else
  210. /* DELETE */
  211. {
  212. cvals[attnum[1] - 1] = newoff; /* stop_date eq current date */
  213. cnulls[attnum[1] - 1] = ' ';
  214. }
  215. /*
  216.  * Construct ident string as TriggerName $ TriggeredRelationId and try
  217.  * to find prepared execution plan.
  218.  */
  219. sprintf(ident, "%s$%u", trigger->tgname, rel->rd_id);
  220. plan = find_plan(ident, &Plans, &nPlans);
  221. /* if there is no plan ... */
  222. if (plan->splan == NULL)
  223. {
  224. void    *pplan;
  225. Oid    *ctypes;
  226. char sql[8192];
  227. /* allocate ctypes for preparation */
  228. ctypes = (Oid *) palloc(natts * sizeof(Oid));
  229. /*
  230.  * Construct query: INSERT INTO _relation_ VALUES ($1, ...)
  231.  */
  232. sprintf(sql, "INSERT INTO %s VALUES (", relname);
  233. for (i = 1; i <= natts; i++)
  234. {
  235. sprintf(sql + strlen(sql), "$%d%s",
  236. i, (i < natts) ? ", " : ")");
  237. ctypes[i - 1] = SPI_gettypeid(tupdesc, i);
  238. }
  239. /* Prepare plan for query */
  240. pplan = SPI_prepare(sql, natts, ctypes);
  241. if (pplan == NULL)
  242. elog(ERROR, "timetravel (%s): SPI_prepare returned %d", relname, SPI_result);
  243. /*
  244.  * Remember that SPI_prepare places plan in current memory context
  245.  * - so, we have to save plan in Top memory context for latter
  246.  * use.
  247.  */
  248. pplan = SPI_saveplan(pplan);
  249. if (pplan == NULL)
  250. elog(ERROR, "timetravel (%s): SPI_saveplan returned %d", relname, SPI_result);
  251. plan->splan = pplan;
  252. }
  253. /*
  254.  * Ok, execute prepared plan.
  255.  */
  256. ret = SPI_execp(plan->splan, cvals, cnulls, 0);
  257. if (ret < 0)
  258. elog(ERROR, "timetravel (%s): SPI_execp returned %d", relname, ret);
  259. /* Tuple to return to upper Executor ... */
  260. if (newtuple) /* UPDATE */
  261. {
  262. HeapTuple tmptuple;
  263. tmptuple = SPI_copytuple(trigtuple);
  264. rettuple = SPI_modifytuple(rel, tmptuple, 1, &(attnum[1]), &newoff, NULL);
  265. /*
  266.  * SPI_copytuple allocates tmptuple in upper executor context -
  267.  * have to free allocation using SPI_pfree
  268.  */
  269. SPI_pfree(tmptuple);
  270. }
  271. else
  272. /* DELETE */
  273. rettuple = trigtuple;
  274. SPI_finish(); /* don't forget say Bye to SPI mgr */
  275. pfree(relname);
  276. return (rettuple);
  277. }
  278. /*
  279.  * set_timetravel () --
  280.  * turn timetravel for specified relation ON/OFF
  281.  */
  282. int32
  283. set_timetravel(Name relname, int32 on)
  284. {
  285. char    *rname;
  286. char    *d;
  287. char    *s;
  288. int i;
  289. for (i = 0; i < nTTOff; i++)
  290. if (namestrcmp(relname, TTOff[i]) == 0)
  291. break;
  292. if (i < nTTOff) /* OFF currently */
  293. {
  294. if (on == 0)
  295. return (0);
  296. /* turn ON */
  297. free(TTOff[i]);
  298. if (nTTOff == 1)
  299. free(TTOff);
  300. else
  301. {
  302. if (i < nTTOff - 1)
  303. memcpy(&(TTOff[i]), &(TTOff[i + 1]), (nTTOff - i) * sizeof(char *));
  304. TTOff = realloc(TTOff, (nTTOff - 1) * sizeof(char *));
  305. }
  306. nTTOff--;
  307. return (0);
  308. }
  309. /* ON currently */
  310. if (on != 0)
  311. return (1);
  312. /* turn OFF */
  313. if (nTTOff == 0)
  314. TTOff = malloc(sizeof(char *));
  315. else
  316. TTOff = realloc(TTOff, (nTTOff + 1) * sizeof(char *));
  317. s = rname = nameout(relname);
  318. d = TTOff[nTTOff] = malloc(strlen(rname) + 1);
  319. while (*s)
  320. *d++ = tolower(*s++);
  321. *d = 0;
  322. pfree(rname);
  323. nTTOff++;
  324. return (1);
  325. }
  326. AbsoluteTime
  327. currabstime()
  328. {
  329. return (GetCurrentAbsoluteTime());
  330. }
  331. static EPlan *
  332. find_plan(char *ident, EPlan ** eplan, int *nplans)
  333. {
  334. EPlan    *newp;
  335. int i;
  336. if (*nplans > 0)
  337. {
  338. for (i = 0; i < *nplans; i++)
  339. {
  340. if (strcmp((*eplan)[i].ident, ident) == 0)
  341. break;
  342. }
  343. if (i != *nplans)
  344. return (*eplan + i);
  345. *eplan = (EPlan *) realloc(*eplan, (i + 1) * sizeof(EPlan));
  346. newp = *eplan + i;
  347. }
  348. else
  349. {
  350. newp = *eplan = (EPlan *) malloc(sizeof(EPlan));
  351. (*nplans) = i = 0;
  352. }
  353. newp->ident = (char *) malloc(strlen(ident) + 1);
  354. strcpy(newp->ident, ident);
  355. newp->splan = NULL;
  356. (*nplans)++;
  357. return (newp);
  358. }