OSDN Git Service

Add composite-type attributes to information_schema.element_types view
[pg-rex/syncrep.git] / contrib / spi / refint.c
1 /*
2  * contrib/spi/refint.c
3  *
4  *
5  * refint.c --  set of functions to define referential integrity
6  *              constraints using general triggers.
7  */
8 #include "postgres.h"
9
10 #include <ctype.h>
11
12 #include "commands/trigger.h"
13 #include "executor/spi.h"
14 #include "utils/builtins.h"
15
16 PG_MODULE_MAGIC;
17
18 extern Datum check_primary_key(PG_FUNCTION_ARGS);
19 extern Datum check_foreign_key(PG_FUNCTION_ARGS);
20
21
22 typedef struct
23 {
24         char       *ident;
25         int                     nplans;
26         SPIPlanPtr *splan;
27 } EPlan;
28
29 static EPlan *FPlans = NULL;
30 static int      nFPlans = 0;
31 static EPlan *PPlans = NULL;
32 static int      nPPlans = 0;
33
34 static EPlan *find_plan(char *ident, EPlan **eplan, int *nplans);
35
36 /*
37  * check_primary_key () -- check that key in tuple being inserted/updated
38  *                       references existing tuple in "primary" table.
39  * Though it's called without args You have to specify referenced
40  * table/keys while creating trigger:  key field names in triggered table,
41  * referenced table name, referenced key field names:
42  * EXECUTE PROCEDURE
43  * check_primary_key ('Fkey1', 'Fkey2', 'Ptable', 'Pkey1', 'Pkey2').
44  */
45
46 PG_FUNCTION_INFO_V1(check_primary_key);
47
48 Datum
49 check_primary_key(PG_FUNCTION_ARGS)
50 {
51         TriggerData *trigdata = (TriggerData *) fcinfo->context;
52         Trigger    *trigger;            /* to get trigger name */
53         int                     nargs;                  /* # of args specified in CREATE TRIGGER */
54         char      **args;                       /* arguments: column names and table name */
55         int                     nkeys;                  /* # of key columns (= nargs / 2) */
56         Datum      *kvals;                      /* key values */
57         char       *relname;            /* referenced relation name */
58         Relation        rel;                    /* triggered relation */
59         HeapTuple       tuple = NULL;   /* tuple to return */
60         TupleDesc       tupdesc;                /* tuple description */
61         EPlan      *plan;                       /* prepared plan */
62         Oid                *argtypes = NULL;    /* key types to prepare execution plan */
63         bool            isnull;                 /* to know is some column NULL or not */
64         char            ident[2 * NAMEDATALEN]; /* to identify myself */
65         int                     ret;
66         int                     i;
67
68 #ifdef  DEBUG_QUERY
69         elog(DEBUG4, "check_primary_key: Enter Function");
70 #endif
71
72         /*
73          * Some checks first...
74          */
75
76         /* Called by trigger manager ? */
77         if (!CALLED_AS_TRIGGER(fcinfo))
78                 /* internal error */
79                 elog(ERROR, "check_primary_key: not fired by trigger manager");
80
81         /* Should be called for ROW trigger */
82         if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
83                 /* internal error */
84                 elog(ERROR, "check_primary_key: must be fired for row");
85
86         /* If INSERTion then must check Tuple to being inserted */
87         if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
88                 tuple = trigdata->tg_trigtuple;
89
90         /* Not should be called for DELETE */
91         else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
92                 /* internal error */
93                 elog(ERROR, "check_primary_key: cannot process DELETE events");
94
95         /* If UPDATion the must check new Tuple, not old one */
96         else
97                 tuple = trigdata->tg_newtuple;
98
99         trigger = trigdata->tg_trigger;
100         nargs = trigger->tgnargs;
101         args = trigger->tgargs;
102
103         if (nargs % 2 != 1)                     /* odd number of arguments! */
104                 /* internal error */
105                 elog(ERROR, "check_primary_key: odd number of arguments should be specified");
106
107         nkeys = nargs / 2;
108         relname = args[nkeys];
109         rel = trigdata->tg_relation;
110         tupdesc = rel->rd_att;
111
112         /* Connect to SPI manager */
113         if ((ret = SPI_connect()) < 0)
114                 /* internal error */
115                 elog(ERROR, "check_primary_key: SPI_connect returned %d", ret);
116
117         /*
118          * We use SPI plan preparation feature, so allocate space to place key
119          * values.
120          */
121         kvals = (Datum *) palloc(nkeys * sizeof(Datum));
122
123         /*
124          * Construct ident string as TriggerName $ TriggeredRelationId and try to
125          * find prepared execution plan.
126          */
127         snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
128         plan = find_plan(ident, &PPlans, &nPPlans);
129
130         /* if there is no plan then allocate argtypes for preparation */
131         if (plan->nplans <= 0)
132                 argtypes = (Oid *) palloc(nkeys * sizeof(Oid));
133
134         /* For each column in key ... */
135         for (i = 0; i < nkeys; i++)
136         {
137                 /* get index of column in tuple */
138                 int                     fnumber = SPI_fnumber(tupdesc, args[i]);
139
140                 /* Bad guys may give us un-existing column in CREATE TRIGGER */
141                 if (fnumber < 0)
142                         ereport(ERROR,
143                                         (errcode(ERRCODE_UNDEFINED_COLUMN),
144                                          errmsg("there is no attribute \"%s\" in relation \"%s\"",
145                                                         args[i], SPI_getrelname(rel))));
146
147                 /* Well, get binary (in internal format) value of column */
148                 kvals[i] = SPI_getbinval(tuple, tupdesc, fnumber, &isnull);
149
150                 /*
151                  * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()!
152                  * DON'T FORGET return tuple! Executor inserts tuple you're returning!
153                  * If you return NULL then nothing will be inserted!
154                  */
155                 if (isnull)
156                 {
157                         SPI_finish();
158                         return PointerGetDatum(tuple);
159                 }
160
161                 if (plan->nplans <= 0)  /* Get typeId of column */
162                         argtypes[i] = SPI_gettypeid(tupdesc, fnumber);
163         }
164
165         /*
166          * If we have to prepare plan ...
167          */
168         if (plan->nplans <= 0)
169         {
170                 SPIPlanPtr      pplan;
171                 char            sql[8192];
172
173                 /*
174                  * Construct query: SELECT 1 FROM _referenced_relation_ WHERE Pkey1 =
175                  * $1 [AND Pkey2 = $2 [...]]
176                  */
177                 snprintf(sql, sizeof(sql), "select 1 from %s where ", relname);
178                 for (i = 0; i < nkeys; i++)
179                 {
180                         snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s",
181                                   args[i + nkeys + 1], i + 1, (i < nkeys - 1) ? "and " : "");
182                 }
183
184                 /* Prepare plan for query */
185                 pplan = SPI_prepare(sql, nkeys, argtypes);
186                 if (pplan == NULL)
187                         /* internal error */
188                         elog(ERROR, "check_primary_key: SPI_prepare returned %d", SPI_result);
189
190                 /*
191                  * Remember that SPI_prepare places plan in current memory context -
192                  * so, we have to save plan in Top memory context for latter use.
193                  */
194                 pplan = SPI_saveplan(pplan);
195                 if (pplan == NULL)
196                         /* internal error */
197                         elog(ERROR, "check_primary_key: SPI_saveplan returned %d", SPI_result);
198                 plan->splan = (SPIPlanPtr *) malloc(sizeof(SPIPlanPtr));
199                 *(plan->splan) = pplan;
200                 plan->nplans = 1;
201         }
202
203         /*
204          * Ok, execute prepared plan.
205          */
206         ret = SPI_execp(*(plan->splan), kvals, NULL, 1);
207         /* we have no NULLs - so we pass   ^^^^   here */
208
209         if (ret < 0)
210                 /* internal error */
211                 elog(ERROR, "check_primary_key: SPI_execp returned %d", ret);
212
213         /*
214          * If there are no tuples returned by SELECT then ...
215          */
216         if (SPI_processed == 0)
217                 ereport(ERROR,
218                                 (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
219                                  errmsg("tuple references non-existent key"),
220                                  errdetail("Trigger \"%s\" found tuple referencing non-existent key in \"%s\".", trigger->tgname, relname)));
221
222         SPI_finish();
223
224         return PointerGetDatum(tuple);
225 }
226
227 /*
228  * check_foreign_key () -- check that key in tuple being deleted/updated
229  *                       is not referenced by tuples in "foreign" table(s).
230  * Though it's called without args You have to specify (while creating trigger):
231  * number of references, action to do if key referenced
232  * ('restrict' | 'setnull' | 'cascade'), key field names in triggered
233  * ("primary") table and referencing table(s)/keys:
234  * EXECUTE PROCEDURE
235  * check_foreign_key (2, 'restrict', 'Pkey1', 'Pkey2',
236  * 'Ftable1', 'Fkey11', 'Fkey12', 'Ftable2', 'Fkey21', 'Fkey22').
237  */
238
239 PG_FUNCTION_INFO_V1(check_foreign_key);
240
241 Datum
242 check_foreign_key(PG_FUNCTION_ARGS)
243 {
244         TriggerData *trigdata = (TriggerData *) fcinfo->context;
245         Trigger    *trigger;            /* to get trigger name */
246         int                     nargs;                  /* # of args specified in CREATE TRIGGER */
247         char      **args;                       /* arguments: as described above */
248         char      **args_temp;
249         int                     nrefs;                  /* number of references (== # of plans) */
250         char            action;                 /* 'R'estrict | 'S'etnull | 'C'ascade */
251         int                     nkeys;                  /* # of key columns */
252         Datum      *kvals;                      /* key values */
253         char       *relname;            /* referencing relation name */
254         Relation        rel;                    /* triggered relation */
255         HeapTuple       trigtuple = NULL;               /* tuple to being changed */
256         HeapTuple       newtuple = NULL;        /* tuple to return */
257         TupleDesc       tupdesc;                /* tuple description */
258         EPlan      *plan;                       /* prepared plan(s) */
259         Oid                *argtypes = NULL;    /* key types to prepare execution plan */
260         bool            isnull;                 /* to know is some column NULL or not */
261         bool            isequal = true; /* are keys in both tuples equal (in UPDATE) */
262         char            ident[2 * NAMEDATALEN]; /* to identify myself */
263         int                     is_update = 0;
264         int                     ret;
265         int                     i,
266                                 r;
267
268 #ifdef DEBUG_QUERY
269         elog(DEBUG4, "check_foreign_key: Enter Function");
270 #endif
271
272         /*
273          * Some checks first...
274          */
275
276         /* Called by trigger manager ? */
277         if (!CALLED_AS_TRIGGER(fcinfo))
278                 /* internal error */
279                 elog(ERROR, "check_foreign_key: not fired by trigger manager");
280
281         /* Should be called for ROW trigger */
282         if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
283                 /* internal error */
284                 elog(ERROR, "check_foreign_key: must be fired for row");
285
286         /* Not should be called for INSERT */
287         if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
288                 /* internal error */
289                 elog(ERROR, "check_foreign_key: cannot process INSERT events");
290
291         /* Have to check tg_trigtuple - tuple being deleted */
292         trigtuple = trigdata->tg_trigtuple;
293
294         /*
295          * But if this is UPDATE then we have to return tg_newtuple. Also, if key
296          * in tg_newtuple is the same as in tg_trigtuple then nothing to do.
297          */
298         is_update = 0;
299         if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
300         {
301                 newtuple = trigdata->tg_newtuple;
302                 is_update = 1;
303         }
304         trigger = trigdata->tg_trigger;
305         nargs = trigger->tgnargs;
306         args = trigger->tgargs;
307
308         if (nargs < 5)                          /* nrefs, action, key, Relation, key - at
309                                                                  * least */
310                 /* internal error */
311                 elog(ERROR, "check_foreign_key: too short %d (< 5) list of arguments", nargs);
312
313         nrefs = pg_atoi(args[0], sizeof(int), 0);
314         if (nrefs < 1)
315                 /* internal error */
316                 elog(ERROR, "check_foreign_key: %d (< 1) number of references specified", nrefs);
317         action = tolower((unsigned char) *(args[1]));
318         if (action != 'r' && action != 'c' && action != 's')
319                 /* internal error */
320                 elog(ERROR, "check_foreign_key: invalid action %s", args[1]);
321         nargs -= 2;
322         args += 2;
323         nkeys = (nargs - nrefs) / (nrefs + 1);
324         if (nkeys <= 0 || nargs != (nrefs + nkeys * (nrefs + 1)))
325                 /* internal error */
326                 elog(ERROR, "check_foreign_key: invalid number of arguments %d for %d references",
327                          nargs + 2, nrefs);
328
329         rel = trigdata->tg_relation;
330         tupdesc = rel->rd_att;
331
332         /* Connect to SPI manager */
333         if ((ret = SPI_connect()) < 0)
334                 /* internal error */
335                 elog(ERROR, "check_foreign_key: SPI_connect returned %d", ret);
336
337         /*
338          * We use SPI plan preparation feature, so allocate space to place key
339          * values.
340          */
341         kvals = (Datum *) palloc(nkeys * sizeof(Datum));
342
343         /*
344          * Construct ident string as TriggerName $ TriggeredRelationId and try to
345          * find prepared execution plan(s).
346          */
347         snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
348         plan = find_plan(ident, &FPlans, &nFPlans);
349
350         /* if there is no plan(s) then allocate argtypes for preparation */
351         if (plan->nplans <= 0)
352                 argtypes = (Oid *) palloc(nkeys * sizeof(Oid));
353
354         /*
355          * else - check that we have exactly nrefs plan(s) ready
356          */
357         else if (plan->nplans != nrefs)
358                 /* internal error */
359                 elog(ERROR, "%s: check_foreign_key: # of plans changed in meantime",
360                          trigger->tgname);
361
362         /* For each column in key ... */
363         for (i = 0; i < nkeys; i++)
364         {
365                 /* get index of column in tuple */
366                 int                     fnumber = SPI_fnumber(tupdesc, args[i]);
367
368                 /* Bad guys may give us un-existing column in CREATE TRIGGER */
369                 if (fnumber < 0)
370                         ereport(ERROR,
371                                         (errcode(ERRCODE_UNDEFINED_COLUMN),
372                                          errmsg("there is no attribute \"%s\" in relation \"%s\"",
373                                                         args[i], SPI_getrelname(rel))));
374
375                 /* Well, get binary (in internal format) value of column */
376                 kvals[i] = SPI_getbinval(trigtuple, tupdesc, fnumber, &isnull);
377
378                 /*
379                  * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()!
380                  * DON'T FORGET return tuple! Executor inserts tuple you're returning!
381                  * If you return NULL then nothing will be inserted!
382                  */
383                 if (isnull)
384                 {
385                         SPI_finish();
386                         return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple);
387                 }
388
389                 /*
390                  * If UPDATE then get column value from new tuple being inserted and
391                  * compare is this the same as old one. For the moment we use string
392                  * presentation of values...
393                  */
394                 if (newtuple != NULL)
395                 {
396                         char       *oldval = SPI_getvalue(trigtuple, tupdesc, fnumber);
397                         char       *newval;
398
399                         /* this shouldn't happen! SPI_ERROR_NOOUTFUNC ? */
400                         if (oldval == NULL)
401                                 /* internal error */
402                                 elog(ERROR, "check_foreign_key: SPI_getvalue returned %d", SPI_result);
403                         newval = SPI_getvalue(newtuple, tupdesc, fnumber);
404                         if (newval == NULL || strcmp(oldval, newval) != 0)
405                                 isequal = false;
406                 }
407
408                 if (plan->nplans <= 0)  /* Get typeId of column */
409                         argtypes[i] = SPI_gettypeid(tupdesc, fnumber);
410         }
411         args_temp = args;
412         nargs -= nkeys;
413         args += nkeys;
414
415         /*
416          * If we have to prepare plans ...
417          */
418         if (plan->nplans <= 0)
419         {
420                 SPIPlanPtr      pplan;
421                 char            sql[8192];
422                 char      **args2 = args;
423
424                 plan->splan = (SPIPlanPtr *) malloc(nrefs * sizeof(SPIPlanPtr));
425
426                 for (r = 0; r < nrefs; r++)
427                 {
428                         relname = args2[0];
429
430                         /*---------
431                          * For 'R'estrict action we construct SELECT query:
432                          *
433                          *      SELECT 1
434                          *      FROM _referencing_relation_
435                          *      WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]]
436                          *
437                          *      to check is tuple referenced or not.
438                          *---------
439                          */
440                         if (action == 'r')
441
442                                 snprintf(sql, sizeof(sql), "select 1 from %s where ", relname);
443
444                         /*---------
445                          * For 'C'ascade action we construct DELETE query
446                          *
447                          *      DELETE
448                          *      FROM _referencing_relation_
449                          *      WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]]
450                          *
451                          * to delete all referencing tuples.
452                          *---------
453                          */
454
455                         /*
456                          * Max : Cascade with UPDATE query i create update query that
457                          * updates new key values in referenced tables
458                          */
459
460
461                         else if (action == 'c')
462                         {
463                                 if (is_update == 1)
464                                 {
465                                         int                     fn;
466                                         char       *nv;
467                                         int                     k;
468
469                                         snprintf(sql, sizeof(sql), "update %s set ", relname);
470                                         for (k = 1; k <= nkeys; k++)
471                                         {
472                                                 int                     is_char_type = 0;
473                                                 char       *type;
474
475                                                 fn = SPI_fnumber(tupdesc, args_temp[k - 1]);
476                                                 nv = SPI_getvalue(newtuple, tupdesc, fn);
477                                                 type = SPI_gettype(tupdesc, fn);
478
479                                                 if ((strcmp(type, "text") && strcmp(type, "varchar") &&
480                                                          strcmp(type, "char") && strcmp(type, "bpchar") &&
481                                                          strcmp(type, "date") && strcmp(type, "timestamp")) == 0)
482                                                         is_char_type = 1;
483 #ifdef  DEBUG_QUERY
484                                                 elog(DEBUG4, "check_foreign_key Debug value %s type %s %d",
485                                                          nv, type, is_char_type);
486 #endif
487
488                                                 /*
489                                                  * is_char_type =1 i set ' ' for define a new value
490                                                  */
491                                                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql),
492                                                                  " %s = %s%s%s %s ",
493                                                                  args2[k], (is_char_type > 0) ? "'" : "",
494                                                                  nv, (is_char_type > 0) ? "'" : "", (k < nkeys) ? ", " : "");
495                                                 is_char_type = 0;
496                                         }
497                                         strcat(sql, " where ");
498
499                                 }
500                                 else
501                                         /* DELETE */
502                                         snprintf(sql, sizeof(sql), "delete from %s where ", relname);
503
504                         }
505
506                         /*
507                          * For 'S'etnull action we construct UPDATE query - UPDATE
508                          * _referencing_relation_ SET Fkey1 null [, Fkey2 null [...]]
509                          * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]] - to set key columns in
510                          * all referencing tuples to NULL.
511                          */
512                         else if (action == 's')
513                         {
514                                 snprintf(sql, sizeof(sql), "update %s set ", relname);
515                                 for (i = 1; i <= nkeys; i++)
516                                 {
517                                         snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql),
518                                                          "%s = null%s",
519                                                          args2[i], (i < nkeys) ? ", " : "");
520                                 }
521                                 strcat(sql, " where ");
522                         }
523
524                         /* Construct WHERE qual */
525                         for (i = 1; i <= nkeys; i++)
526                         {
527                                 snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s",
528                                                  args2[i], i, (i < nkeys) ? "and " : "");
529                         }
530
531                         /* Prepare plan for query */
532                         pplan = SPI_prepare(sql, nkeys, argtypes);
533                         if (pplan == NULL)
534                                 /* internal error */
535                                 elog(ERROR, "check_foreign_key: SPI_prepare returned %d", SPI_result);
536
537                         /*
538                          * Remember that SPI_prepare places plan in current memory context
539                          * - so, we have to save plan in Top memory context for latter
540                          * use.
541                          */
542                         pplan = SPI_saveplan(pplan);
543                         if (pplan == NULL)
544                                 /* internal error */
545                                 elog(ERROR, "check_foreign_key: SPI_saveplan returned %d", SPI_result);
546
547                         plan->splan[r] = pplan;
548
549                         args2 += nkeys + 1; /* to the next relation */
550                 }
551                 plan->nplans = nrefs;
552 #ifdef  DEBUG_QUERY
553                 elog(DEBUG4, "check_foreign_key Debug Query is :  %s ", sql);
554 #endif
555         }
556
557         /*
558          * If UPDATE and key is not changed ...
559          */
560         if (newtuple != NULL && isequal)
561         {
562                 SPI_finish();
563                 return PointerGetDatum(newtuple);
564         }
565
566         /*
567          * Ok, execute prepared plan(s).
568          */
569         for (r = 0; r < nrefs; r++)
570         {
571                 /*
572                  * For 'R'estrict we may to execute plan for one tuple only, for other
573                  * actions - for all tuples.
574                  */
575                 int                     tcount = (action == 'r') ? 1 : 0;
576
577                 relname = args[0];
578
579                 snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id);
580                 plan = find_plan(ident, &FPlans, &nFPlans);
581                 ret = SPI_execp(plan->splan[r], kvals, NULL, tcount);
582                 /* we have no NULLs - so we pass   ^^^^  here */
583
584                 if (ret < 0)
585                         ereport(ERROR,
586                                         (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
587                                          errmsg("SPI_execp returned %d", ret)));
588
589                 /* If action is 'R'estrict ... */
590                 if (action == 'r')
591                 {
592                         /* If there is tuple returned by SELECT then ... */
593                         if (SPI_processed > 0)
594                                 ereport(ERROR,
595                                                 (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
596                                                  errmsg("\"%s\": tuple is referenced in \"%s\"",
597                                                                 trigger->tgname, relname)));
598                 }
599                 else
600                 {
601 #ifdef REFINT_VERBOSE
602                         elog(NOTICE, "%s: %d tuple(s) of %s are %s",
603                                  trigger->tgname, SPI_processed, relname,
604                                  (action == 'c') ? "deleted" : "set to null");
605 #endif
606                 }
607                 args += nkeys + 1;              /* to the next relation */
608         }
609
610         SPI_finish();
611
612         return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple);
613 }
614
615 static EPlan *
616 find_plan(char *ident, EPlan **eplan, int *nplans)
617 {
618         EPlan      *newp;
619         int                     i;
620
621         if (*nplans > 0)
622         {
623                 for (i = 0; i < *nplans; i++)
624                 {
625                         if (strcmp((*eplan)[i].ident, ident) == 0)
626                                 break;
627                 }
628                 if (i != *nplans)
629                         return (*eplan + i);
630                 *eplan = (EPlan *) realloc(*eplan, (i + 1) * sizeof(EPlan));
631                 newp = *eplan + i;
632         }
633         else
634         {
635                 newp = *eplan = (EPlan *) malloc(sizeof(EPlan));
636                 (*nplans) = i = 0;
637         }
638
639         newp->ident = (char *) malloc(strlen(ident) + 1);
640         strcpy(newp->ident, ident);
641         newp->nplans = 0;
642         newp->splan = NULL;
643         (*nplans)++;
644
645         return (newp);
646 }